eduevidence 5.2.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/CONTRIBUTING.md +105 -0
  2. package/README.md +142 -75
  3. package/README.zh-CN.md +73 -30
  4. package/SKILL.md +397 -131
  5. package/agents/openai.yaml +4 -0
  6. package/assets/readme/controlled-execution.svg +34 -0
  7. package/assets/readme/landing-tour.gif +0 -0
  8. package/assets/readme/logo.png +0 -0
  9. package/assets/readme/research-workflow.svg +56 -0
  10. package/assets/readme/studio-graph.png +0 -0
  11. package/assets/readme/studio-overview.png +0 -0
  12. package/assets/readme/studio-reports.png +0 -0
  13. package/assets/readme/studio-tour.gif +0 -0
  14. package/autoevolve/config.yaml +17 -0
  15. package/autoevolve/program.md +25 -0
  16. package/autoevolve/protected.manifest.yaml +34 -0
  17. package/benchmarks/adversarial/cases.jsonl +7 -0
  18. package/benchmarks/evidence-library.json +5268 -0
  19. package/benchmarks/partitions.json +8 -0
  20. package/bin/eduevidence.js +2 -1
  21. package/docs/architecture.md +496 -0
  22. package/docs/autoresearch-evolution-plan.md +2903 -0
  23. package/docs/autoresearch-implementation-status.md +101 -0
  24. package/docs/demo-storyboard.md +20 -0
  25. package/docs/demo-workplace-ai.md +92 -0
  26. package/docs/demo.md +32 -0
  27. package/docs/install-guide.md +150 -0
  28. package/docs/orchestration-role-model.md +1254 -0
  29. package/docs/release-closeout/README.md +17 -0
  30. package/docs/release-closeout/frontend-acceptance.md +23 -0
  31. package/docs/release-closeout/issues.md +19 -0
  32. package/docs/release-closeout/verification.md +28 -0
  33. package/docs/release-contract.md +108 -0
  34. package/docs/research-studio-guide.zh-CN.md +166 -0
  35. package/docs/sciverse-api.md +125 -0
  36. package/eduevidence_cli.py +29 -13
  37. package/engine/_resources.py +13 -0
  38. package/engine/autoevolve/__init__.py +3 -0
  39. package/engine/autoevolve/agent_view.py +167 -0
  40. package/engine/autoevolve/core.py +357 -0
  41. package/engine/autoevolve/events.py +11 -0
  42. package/engine/autoevolve/git_workspace.py +77 -0
  43. package/engine/autoevolve/projection.py +23 -0
  44. package/engine/autoevolve/runner.py +413 -0
  45. package/engine/autoevolve/trust.py +146 -0
  46. package/engine/autoresearch/__init__.py +6 -0
  47. package/engine/autoresearch/commit.py +132 -0
  48. package/engine/autoresearch/contracts.py +126 -0
  49. package/engine/autoresearch/controller.py +207 -0
  50. package/engine/autoresearch/events.py +12 -0
  51. package/engine/autoresearch/gap_priority.py +168 -0
  52. package/engine/autoresearch/projection.py +30 -0
  53. package/engine/autoresearch/research_memory.py +59 -0
  54. package/engine/autoresearch/saturation.py +91 -0
  55. package/engine/briefs.py +2 -1
  56. package/engine/capabilities.py +1 -0
  57. package/engine/contracts.py +3 -1
  58. package/engine/decision_policy.py +96 -0
  59. package/engine/evidence_graph.py +14 -10
  60. package/engine/evidencecore.py +7 -5
  61. package/engine/gaps.py +132 -73
  62. package/engine/ids.py +2 -0
  63. package/engine/judge_pack.py +65 -0
  64. package/engine/library.py +6 -2
  65. package/engine/library_builtin.py +3 -1
  66. package/engine/living.py +36 -5
  67. package/engine/meta_synthesis.py +3 -1
  68. package/engine/migration.py +88 -3
  69. package/engine/orchestration.py +460 -0
  70. package/engine/paths.py +2 -0
  71. package/engine/pilot.py +36 -33
  72. package/engine/project.py +2 -2
  73. package/engine/research_service.py +113 -0
  74. package/engine/studio_read_model.py +400 -0
  75. package/engine/taxonomy.py +211 -0
  76. package/engine/tribunal.py +44 -33
  77. package/engine/update.py +1 -0
  78. package/engine/versions.py +1 -1
  79. package/engine/worker_result.py +109 -0
  80. package/engine/workflows.py +70 -0
  81. package/examples/ai-coding-assistant-evidence/EduEvidence_Report.html +2934 -0
  82. package/examples/ai-coding-assistant-evidence/artifact_manifest.json +15 -0
  83. package/examples/ai-coding-assistant-evidence/citation_check.json +79 -0
  84. package/examples/ai-coding-assistant-evidence/claims.jsonl +12 -0
  85. package/examples/ai-coding-assistant-evidence/evaluation.json +35 -0
  86. package/examples/ai-coding-assistant-evidence/evidence.jsonl +12 -0
  87. package/examples/ai-coding-assistant-evidence/final_verdict.json +107 -0
  88. package/examples/ai-coding-assistant-evidence/frame.json +48 -0
  89. package/examples/ai-coding-assistant-evidence/gate_report.json +101 -0
  90. package/examples/ai-coding-assistant-evidence/intervention.json +51 -0
  91. package/examples/ai-coding-assistant-evidence/methodology.json +36 -0
  92. package/examples/ai-coding-assistant-evidence/raw_verdict.json +86 -0
  93. package/examples/ai-coding-assistant-evidence/report_spec.json +230 -0
  94. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_academic.html +2934 -0
  95. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_claude.html +2934 -0
  96. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab-dark.html +2934 -0
  97. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab.html +2934 -0
  98. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_presentation.html +2934 -0
  99. package/examples/ai-coding-assistant-evidence/reports-5themes/report_academic.html +2934 -0
  100. package/examples/ai-coding-assistant-evidence/reports-5themes/report_claude.html +2934 -0
  101. package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab-dark.html +2934 -0
  102. package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab.html +2934 -0
  103. package/examples/ai-coding-assistant-evidence/reports-5themes/report_presentation.html +2934 -0
  104. package/examples/ai-coding-assistant-evidence/result.json +1457 -0
  105. package/examples/ai-coding-assistant-evidence/result.zh.json +1457 -0
  106. package/examples/ai-coding-assistant-evidence/skeptic.json +72 -0
  107. package/examples/ai-coding-assistant-evidence/sources.jsonl +8 -0
  108. package/examples/ai-coding-assistant-evidence/verdict.json +107 -0
  109. package/examples/spaced-retrieval-practice/applicability.json +14 -0
  110. package/examples/spaced-retrieval-practice/artifact_manifest.json +15 -0
  111. package/examples/spaced-retrieval-practice/claims.jsonl +3 -0
  112. package/examples/spaced-retrieval-practice/evidence.jsonl +6 -0
  113. package/examples/spaced-retrieval-practice/final_verdict.json +93 -0
  114. package/examples/spaced-retrieval-practice/frame.json +58 -0
  115. package/examples/spaced-retrieval-practice/gate_report.json +101 -0
  116. package/examples/spaced-retrieval-practice/methodology.json +78 -0
  117. package/examples/spaced-retrieval-practice/report_spec.json +212 -0
  118. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_academic.html +2728 -0
  119. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_claude.html +2728 -0
  120. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab-dark.html +2728 -0
  121. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab.html +2728 -0
  122. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_presentation.html +2728 -0
  123. package/examples/spaced-retrieval-practice/reports-5themes/report_academic.html +2728 -0
  124. package/examples/spaced-retrieval-practice/reports-5themes/report_claude.html +2728 -0
  125. package/examples/spaced-retrieval-practice/reports-5themes/report_datalab-dark.html +2728 -0
  126. package/examples/spaced-retrieval-practice/reports-5themes/report_datalab.html +2728 -0
  127. package/examples/spaced-retrieval-practice/reports-5themes/report_presentation.html +2728 -0
  128. package/examples/spaced-retrieval-practice/result.json +942 -0
  129. package/examples/spaced-retrieval-practice/result.zh.json +942 -0
  130. package/examples/spaced-retrieval-practice/skeptic.json +70 -0
  131. package/examples/spaced-retrieval-practice/sources.jsonl +7 -0
  132. package/examples/spaced-retrieval-practice/verdict.json +93 -0
  133. package/examples/workplace-ai-assistant/artifact_manifest.json +15 -0
  134. package/examples/workplace-ai-assistant/claims.jsonl +4 -0
  135. package/examples/workplace-ai-assistant/evaluation.json +19 -0
  136. package/examples/workplace-ai-assistant/evidence.jsonl +4 -0
  137. package/examples/workplace-ai-assistant/evidence_graph.json +444 -0
  138. package/examples/workplace-ai-assistant/final_verdict.json +78 -0
  139. package/examples/workplace-ai-assistant/frame.json +41 -0
  140. package/examples/workplace-ai-assistant/gate_report.json +101 -0
  141. package/examples/workplace-ai-assistant/intervention.json +27 -0
  142. package/examples/workplace-ai-assistant/legacy-link-check.json +16 -0
  143. package/examples/workplace-ai-assistant/methodology.json +60 -0
  144. package/examples/workplace-ai-assistant/report_spec.json +224 -0
  145. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_academic.html +2814 -0
  146. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_claude.html +2814 -0
  147. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab-dark.html +2814 -0
  148. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab.html +2814 -0
  149. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_presentation.html +2814 -0
  150. package/examples/workplace-ai-assistant/reports-5themes/report_academic.html +2814 -0
  151. package/examples/workplace-ai-assistant/reports-5themes/report_claude.html +2814 -0
  152. package/examples/workplace-ai-assistant/reports-5themes/report_datalab-dark.html +2814 -0
  153. package/examples/workplace-ai-assistant/reports-5themes/report_datalab.html +2814 -0
  154. package/examples/workplace-ai-assistant/reports-5themes/report_presentation.html +2814 -0
  155. package/examples/workplace-ai-assistant/result.json +615 -0
  156. package/examples/workplace-ai-assistant/result.zh.json +615 -0
  157. package/examples/workplace-ai-assistant/search_log.json +19 -0
  158. package/examples/workplace-ai-assistant/skeptic.json +72 -0
  159. package/examples/workplace-ai-assistant/sources.jsonl +3 -0
  160. package/examples/workplace-ai-assistant/validation_result.json +9 -0
  161. package/examples/workplace-ai-assistant/verdict.json +78 -0
  162. package/install.sh +7 -7
  163. package/integrations/agent_mcp.py +2 -2
  164. package/integrations/orchestration_dispatch.py +146 -0
  165. package/package.json +46 -3
  166. package/pyproject.toml +14 -22
  167. package/references/autoresearch.md +30 -0
  168. package/references/evaluation-policy.md +24 -0
  169. package/references/orchestration.md +22 -0
  170. package/references/report-copy-style.md +67 -0
  171. package/references/retrieval-compliance.md +75 -0
  172. package/references/retrieval-protocol.md +20 -0
  173. package/references/scientific-invariants.md +19 -0
  174. package/retrieval/audit.py +178 -0
  175. package/retrieval/fetch.py +96 -0
  176. package/retrieval/sciverse.py +398 -0
  177. package/retrieval/search.py +47 -7
  178. package/schemas/applicability.schema.json +94 -0
  179. package/schemas/chart-spec.schema.json +10 -3
  180. package/schemas/evidence.schema.json +316 -43
  181. package/schemas/fetch-result.schema.json +2 -1
  182. package/schemas/intervention.schema.json +106 -21
  183. package/schemas/report-result.schema.json +12 -4
  184. package/schemas/report-spec.schema.json +98 -100
  185. package/schemas/skeptic.schema.json +86 -0
  186. package/schemas/source.schema.json +21 -2
  187. package/schemas/v2/finding.schema.json +5 -1
  188. package/schemas/v2/methodology-audit.schema.json +5 -1
  189. package/schemas/v2/outcome.schema.json +28 -5
  190. package/schemas/v2/project.schema.json +2 -2
  191. package/schemas/v2/run.schema.json +1 -1
  192. package/schemas/v2/study.schema.json +5 -1
  193. package/schemas/vNext/autoevolve-session.schema.json +34 -0
  194. package/schemas/vNext/eval-snapshot.schema.json +77 -0
  195. package/schemas/vNext/execution-plan.schema.json +50 -0
  196. package/schemas/vNext/gap-priority.schema.json +54 -0
  197. package/schemas/vNext/negative-search-record.schema.json +68 -0
  198. package/schemas/vNext/research-iteration.schema.json +87 -0
  199. package/schemas/vNext/research-strategy.schema.json +62 -0
  200. package/schemas/vNext/skill-experiment.schema.json +90 -0
  201. package/schemas/vNext/task-spec.schema.json +156 -0
  202. package/schemas/vNext/worker-result.schema.json +60 -0
  203. package/schemas/verdict.schema.json +164 -28
  204. package/scripts/benchmark_judge.py +2 -2
  205. package/scripts/benchmark_v3.py +26 -43
  206. package/scripts/build_esl_artifacts.py +4 -4
  207. package/scripts/build_evidence_library.py +2 -2
  208. package/scripts/build_gh_pages.py +98 -0
  209. package/scripts/build_readme_diagrams.py +72 -0
  210. package/scripts/build_report_variants.py +101 -0
  211. package/scripts/build_result.py +74 -9
  212. package/scripts/check_autoresearch_invariants.py +95 -0
  213. package/scripts/check_package_parity.py +85 -0
  214. package/scripts/check_protocol_alignment.py +375 -0
  215. package/scripts/check_versioned_schemas.py +254 -0
  216. package/scripts/claim_audit.py +13 -8
  217. package/scripts/compute_confidence.py +10 -0
  218. package/scripts/daily_evolve.py +30 -0
  219. package/scripts/dashboard_server.py +130 -101
  220. package/scripts/did_regression.py +17 -32
  221. package/scripts/enrich_projects_human_and_lieflat.py +1 -1
  222. package/scripts/evidence_score.py +5 -2
  223. package/scripts/generate_metrics.py +4 -3
  224. package/scripts/generate_new_projects.py +5 -5
  225. package/scripts/orchestrator.py +286 -36
  226. package/scripts/pre_verdict_gate.py +224 -26
  227. package/scripts/quickstart.py +18 -2
  228. package/scripts/rebake_all_5themes.py +1 -2
  229. package/scripts/research_auto_cli.py +475 -0
  230. package/scripts/run_workspace.py +24 -8
  231. package/scripts/search_provenance.py +64 -0
  232. package/scripts/serve_web.py +9 -10
  233. package/scripts/skill_lint.py +1 -1
  234. package/scripts/skill_payload.py +81 -0
  235. package/scripts/test_adversarial_empirical.py +26 -19
  236. package/scripts/validate_schema.py +46 -2
  237. package/scripts/vnext_cli.py +133 -0
  238. package/setup.py +12 -0
  239. package/skill/agents/evaluation-designer.md +20 -4
  240. package/skill/agents/evidence-analyst.md +19 -3
  241. package/skill/agents/evidence-judge.md +50 -2
  242. package/skill/agents/evidence-retriever.md +20 -3
  243. package/skill/agents/intervention-designer.md +20 -4
  244. package/skill/agents/method-reviewer.md +18 -2
  245. package/skill/agents/{education-planner.md → research-planner.md} +19 -3
  246. package/skill/agents/skeptic.md +18 -2
  247. package/skill/roles/registry.yaml +45 -0
  248. package/skill/sub-skills/aihot-trend-analysis/SKILL.md +28 -9
  249. package/skill/sub-skills/contradiction-analysis/SKILL.md +31 -11
  250. package/skill/sub-skills/data-analysis/SKILL.md +34 -15
  251. package/skill/sub-skills/ethics-review/SKILL.md +33 -10
  252. package/skill/sub-skills/evidence-extraction/SKILL.md +29 -11
  253. package/skill/sub-skills/evidence-review/SKILL.md +31 -12
  254. package/skill/sub-skills/gap-analysis/SKILL.md +31 -9
  255. package/skill/sub-skills/literature-review/SKILL.md +35 -14
  256. package/skill/sub-skills/methodology-audit/SKILL.md +29 -12
  257. package/skill/sub-skills/report-generation/SKILL.md +40 -6
  258. package/skill/sub-skills/research-planning/SKILL.md +41 -14
  259. package/skill/sub-skills/study-design/SKILL.md +30 -9
  260. package/skill/task-briefs/adjudicate.md +32 -7
  261. package/skill/task-briefs/applicability.md +38 -0
  262. package/skill/task-briefs/audit.md +32 -7
  263. package/skill/task-briefs/challenge.md +34 -5
  264. package/skill/task-briefs/evaluate.md +30 -5
  265. package/skill/task-briefs/extract.md +31 -8
  266. package/skill/task-briefs/frame.md +39 -10
  267. package/skill/task-briefs/intervene.md +32 -6
  268. package/skill/task-briefs/present.md +32 -8
  269. package/skill/task-briefs/projection.md +37 -0
  270. package/skill/task-briefs/retrieve.md +36 -6
  271. package/skill/workflows/decision-and-pilot.md +85 -0
  272. package/skill/workflows/evaluate-and-update.md +93 -0
  273. package/skill/workflows/evidence-review.md +117 -0
  274. package/visualization/eduevidence-report/assets/base.css +2 -2
  275. package/visualization/eduevidence-report/assets/reader.css +752 -0
  276. package/visualization/eduevidence-report/assets/reader.js +132 -0
  277. package/visualization/eduevidence-report/references/chart-selection-catalog.md +109 -0
  278. package/visualization/eduevidence-report/references/lieflat-composition.md +3 -1
  279. package/visualization/eduevidence-report/scripts/build_figures.py +25 -3
  280. package/visualization/eduevidence-report/scripts/build_infographics.py +5 -1
  281. package/visualization/eduevidence-report/scripts/build_report.py +561 -121
  282. package/visualization/eduevidence-report/scripts/charts_data.py +2 -0
  283. package/visualization/eduevidence-report/scripts/lieflat_engine.py +371 -136
  284. package/visualization/eduevidence-report/scripts/zh_labels.py +80 -1
  285. package/visualization/eduevidence-report/themes/academic.css +1 -1
  286. package/visualization/eduevidence-report/themes/claude.css +1 -1
  287. package/visualization/eduevidence-report/themes/datalab-dark.css +2 -2
  288. package/visualization/eduevidence-report/themes/datalab.css +2 -2
  289. package/visualization/eduevidence-report/themes/presentation.css +2 -2
  290. package/web/README.md +18 -0
  291. package/web/architecture.html +14885 -0
  292. package/web/index.html +53 -0
  293. package/web/studio/THIRD_PARTY_LICENSES.txt +146 -0
  294. package/web/studio/assets/index-B8tkF44Q.css +1 -0
  295. package/web/studio/assets/index-CQ6Keoyc.js +230 -0
  296. package/web/studio/config.json +1 -0
  297. package/web/studio/index.html +14 -0
  298. package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
  299. package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
  300. package/engine/__pycache__/bias.cpython-312.pyc +0 -0
  301. package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
  302. package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
  303. package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
  304. package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
  305. package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
  306. package/engine/__pycache__/events.cpython-312.pyc +0 -0
  307. package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
  308. package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
  309. package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
  310. package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
  311. package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
  312. package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
  313. package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
  314. package/engine/__pycache__/ids.cpython-312.pyc +0 -0
  315. package/engine/__pycache__/library.cpython-312.pyc +0 -0
  316. package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
  317. package/engine/__pycache__/living.cpython-312.pyc +0 -0
  318. package/engine/__pycache__/log.cpython-312.pyc +0 -0
  319. package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
  320. package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
  321. package/engine/__pycache__/migration.cpython-312.pyc +0 -0
  322. package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
  323. package/engine/__pycache__/paths.cpython-312.pyc +0 -0
  324. package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
  325. package/engine/__pycache__/planner.cpython-312.pyc +0 -0
  326. package/engine/__pycache__/project.cpython-312.pyc +0 -0
  327. package/engine/__pycache__/projections.cpython-312.pyc +0 -0
  328. package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
  329. package/engine/__pycache__/run.cpython-312.pyc +0 -0
  330. package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
  331. package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
  332. package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
  333. package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
  334. package/engine/__pycache__/update.cpython-312.pyc +0 -0
  335. package/engine/__pycache__/versions.cpython-312.pyc +0 -0
  336. package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
  337. package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
  338. package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
  339. package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
  340. package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
  341. package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
  342. package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
  343. package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
  344. package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
  345. package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
  346. package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
  347. package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
  348. package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
  349. package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
  350. package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
  351. package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
  352. package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
  353. package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
  354. package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
  355. package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
  356. package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
  357. package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
  358. package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
  359. package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
  360. package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
  361. package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
  362. package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
  363. package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
  364. package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
  365. package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
  366. package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
  367. package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
  368. package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
  369. package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
  370. package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
  371. package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
  372. package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
  373. package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
  374. package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
  375. package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
  376. package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
  377. package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
  378. package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
  379. package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
  380. package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
  381. package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
  382. package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
  383. package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
  384. package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
  385. package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
  386. package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env python3
2
+ """check_protocol_alignment.py — Protocol five-way alignment gate.
3
+
4
+ Every scientific contract in this repository is declared in more than one
5
+ place: the workflow registry (engine/workflows.py), the capability registry
6
+ (engine/capabilities.py), the role registry (skill/roles/registry.yaml), the
7
+ role prompts (skill/agents/*.md), the stage briefs (skill/task-briefs/*.md),
8
+ the sub-skill recipes (skill/sub-skills/*/SKILL.md), the routing requirements
9
+ (integrations/agent_mcp.py) and the packaging manifest (packaging/*).
10
+
11
+ Drift between them is silent: a role can lose its brief, a capability can
12
+ exist with no recipe, a version can be bumped in one file only. This gate
13
+ makes that drift fail loudly. Stdlib only; a non-zero exit blocks CI.
14
+
15
+ Usage:
16
+ python3 scripts/check_protocol_alignment.py
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import re
22
+ import sys
23
+ from pathlib import Path
24
+
25
+ ROOT = Path(__file__).resolve().parent.parent
26
+ sys.path.insert(0, str(ROOT))
27
+
28
+ from engine.capabilities import capability_registry # noqa: E402
29
+ from engine.versions import ENGINE_VERSION # noqa: E402
30
+ from engine.workflows import execution_stages, workflow_registry # noqa: E402
31
+
32
+ FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
33
+
34
+ #: Projection-layer capabilities sit outside the scientific stage model
35
+ #: (engine/workflows.py: Projection is not a scientific stage), so they are
36
+ #: owned by the projection brief rather than by a scientific role.
37
+ PROJECTION_CAPABILITIES = {"report_projection", "report_rendering"}
38
+
39
+
40
+ def list_domains_from_registry() -> list[dict]:
41
+ """Registered domains (evidencecore is the registry owner)."""
42
+ from engine.evidencecore import list_domains
43
+
44
+ return list_domains()
45
+
46
+
47
+ def _frontmatter(path: Path) -> dict[str, str]:
48
+ """Parse the flat key: value frontmatter used by skills and role prompts."""
49
+ match = FRONTMATTER_RE.match(path.read_text(encoding="utf-8"))
50
+ if not match:
51
+ return {}
52
+ fields: dict[str, str] = {}
53
+ for line in match.group(1).splitlines():
54
+ if line.strip().startswith("#") or ":" not in line:
55
+ continue
56
+ key, _, value = line.partition(":")
57
+ fields[key.strip()] = value.split("#", 1)[0].strip()
58
+ return fields
59
+
60
+
61
+ def _registry_roles() -> dict[str, dict]:
62
+ """The role registry is a small fixed-shape YAML file; parse it narrowly."""
63
+ text = (ROOT / "skill" / "roles" / "registry.yaml").read_text(encoding="utf-8")
64
+ roles: dict[str, dict] = {}
65
+ current: str | None = None
66
+ for raw in text.splitlines():
67
+ if not raw.strip() or raw.strip().startswith("#"):
68
+ continue
69
+ if raw.startswith("roles:"):
70
+ continue
71
+ if raw.startswith("execution:"):
72
+ break
73
+ if re.match(r"^ [A-Za-z0-9_-]+:\s*$", raw):
74
+ current = raw.strip().rstrip(":")
75
+ roles[current] = {}
76
+ continue
77
+ if current and raw.strip().startswith("stages:"):
78
+ stages = raw.split(":", 1)[1].strip().strip("[]")
79
+ roles[current]["stages"] = [s.strip() for s in stages.split(",") if s.strip()]
80
+ elif current and raw.strip().startswith("capabilities:"):
81
+ caps = raw.split(":", 1)[1].strip().strip("[]")
82
+ roles[current]["capabilities"] = [c.strip() for c in caps.split(",") if c.strip()]
83
+ elif current and ":" in raw.strip():
84
+ key, _, value = raw.strip().partition(":")
85
+ roles[current][key.strip()] = value.strip()
86
+ return roles
87
+
88
+
89
+ def _merge_role(roles: dict[str, dict], name: str, stage: str, capability: str,
90
+ critical: bool | None = None, independence: bool | None = None) -> None:
91
+ entry = roles.setdefault(name, {"stages": [], "capabilities": []})
92
+ if stage not in entry["stages"]:
93
+ entry["stages"].append(stage)
94
+ for cap in capability.split("+"):
95
+ cap = cap.strip()
96
+ if cap and cap not in entry["capabilities"]:
97
+ entry["capabilities"].append(cap)
98
+ if critical is not None:
99
+ entry["critical_path"] = "true" if critical else "false"
100
+ if independence:
101
+ entry["independence_required"] = "true"
102
+
103
+
104
+ def check() -> list[str]:
105
+ errors: list[str] = []
106
+
107
+ stages = list(execution_stages())
108
+ scientific_stages = [s for s in stages if s != "projection"]
109
+
110
+ # ---------------------------------------------------------------- briefs
111
+ brief_dir = ROOT / "skill" / "task-briefs"
112
+ briefs = {p.stem for p in brief_dir.glob("*.md")}
113
+ for stage in stages:
114
+ if stage not in briefs:
115
+ errors.append(f"stage {stage!r} has no task brief in skill/task-briefs/")
116
+ for extra in sorted(briefs - set(stages) - {"present"}):
117
+ errors.append(f"task brief {extra!r} does not map to a canonical stage")
118
+
119
+ # ---------------------------------------------------------------- roles
120
+ registry = _registry_roles()
121
+ if not registry:
122
+ errors.append("skill/roles/registry.yaml declares no roles")
123
+ agent_files = {p.stem: p for p in (ROOT / "skill" / "agents").glob("*.md")}
124
+ if set(registry) != set(agent_files):
125
+ errors.append("role registry and skill/agents/*.md disagree: "
126
+ f"registry-only={sorted(set(registry) - set(agent_files))} "
127
+ f"agent-only={sorted(set(agent_files) - set(registry))}")
128
+
129
+ stage_owner: dict[str, str] = {}
130
+ for role, entry in registry.items():
131
+ for stage in entry.get("stages", []):
132
+ if stage in stage_owner:
133
+ errors.append(f"stage {stage!r} is owned by both {stage_owner[stage]!r} and {role!r}")
134
+ stage_owner[stage] = role
135
+ for stage in scientific_stages:
136
+ if stage not in stage_owner:
137
+ errors.append(f"stage {stage!r} has no owning role in the registry")
138
+
139
+ # Registry capabilities must be engine capability IDs: a free-text label
140
+ # here silently detaches the role from the capability it claims to run.
141
+ for role, entry in registry.items():
142
+ for cap in entry.get("capabilities", []):
143
+ if cap not in capability_registry():
144
+ errors.append(f"registry role {role!r} declares capability {cap!r}, "
145
+ "which is not in engine/capabilities.py")
146
+
147
+ # Independence is graded: the skeptic must come from a different model
148
+ # family, the method reviewer must be separated from the content judgement.
149
+ independence = {role: entry.get("independence_required")
150
+ for role, entry in registry.items() if entry.get("independence_required")}
151
+ if independence != {"skeptic": "different-model-family",
152
+ "method-reviewer": "role-separation"}:
153
+ expected = {"skeptic": "different-model-family", "method-reviewer": "role-separation"}
154
+ errors.append("independence_required must be "
155
+ f"{expected}, found {independence}")
156
+
157
+ # ------------------------------------------------- role prompt frontmatter
158
+ for role, path in agent_files.items():
159
+ fields = _frontmatter(path)
160
+ if fields.get("name") != role:
161
+ errors.append(f"{path.name}: frontmatter name {fields.get('name')!r} != filename {role!r}")
162
+ if fields.get("role_id") != role:
163
+ errors.append(f"{path.name}: missing role_id: {role}")
164
+ if not fields.get("capabilities"):
165
+ errors.append(f"{path.name}: missing capabilities declaration")
166
+ if not fields.get("output_contracts"):
167
+ errors.append(f"{path.name}: missing output_contracts declaration")
168
+ for banned in ("default_cli", "default_model"):
169
+ if banned in fields:
170
+ errors.append(f"{path.name}: {banned} must not be bound in the role prompt "
171
+ "(model/CLI choice is a user-confirmed routing decision)")
172
+ for token in ("claude-", "gpt-", "deepseek-", "glm-", "kimi-"):
173
+ if token in fields.get("recommended_reasoning", ""):
174
+ errors.append(f"{path.name}: recommended_reasoning must not contain a model name")
175
+ if role in registry:
176
+ declared = set(registry[role].get("capabilities", []))
177
+ prompt_caps = {c.strip() for c in fields.get("capabilities", "").split(",") if c.strip()}
178
+ unknown = {c for c in prompt_caps if c not in capability_registry()}
179
+ unmapped = {c for c in unknown if not c.startswith("(")}
180
+ if unmapped:
181
+ errors.append(f"{path.name}: capabilities not in the capability registry: {sorted(unmapped)}")
182
+ missing = {c for c in declared if c in capability_registry()} - prompt_caps
183
+ if missing:
184
+ errors.append(f"{path.name}: registry capabilities absent from the prompt: "
185
+ f"{sorted(missing)}")
186
+ if registry.get(role, {}).get("critical_path") == "true":
187
+ if fields.get("critical_path") != "true":
188
+ errors.append(f"{path.name}: registry marks this role critical_path but the "
189
+ "prompt does not declare critical_path: true")
190
+
191
+ # ----------------------------------------------- routing-side requirements
192
+ from integrations.agent_mcp import ROLE_REQUIREMENTS # noqa: E402
193
+ if set(ROLE_REQUIREMENTS) != set(registry):
194
+ errors.append("integrations.agent_mcp.ROLE_REQUIREMENTS and the role registry disagree: "
195
+ f"routing-only={sorted(set(ROLE_REQUIREMENTS) - set(registry))} "
196
+ f"registry-only={sorted(set(registry) - set(ROLE_REQUIREMENTS))}")
197
+ for role, reqs in ROLE_REQUIREMENTS.items():
198
+ for banned in ("default_cli", "default_model", "model", "cli"):
199
+ if banned in reqs:
200
+ errors.append(f"ROLE_REQUIREMENTS[{role!r}] must not bind {banned!r}")
201
+ wants_family = registry.get(role, {}).get("independence_required") == "different-model-family"
202
+ if wants_family and reqs.get("independence") != "different-model-family":
203
+ errors.append(f"ROLE_REQUIREMENTS[{role!r}] must require a different model family")
204
+
205
+ # ------------------------------------------------------------ capabilities
206
+ capabilities = capability_registry()
207
+ sub_skills = sorted(p for p in (ROOT / "skill" / "sub-skills").iterdir()
208
+ if p.is_dir() and not p.name.startswith("."))
209
+ if len(sub_skills) < 5:
210
+ errors.append(f"expected at least 5 sub-skills, found {len(sub_skills)}")
211
+ mapped: set[str] = set()
212
+ for skill_dir in sub_skills:
213
+ path = skill_dir / "SKILL.md"
214
+ if not path.is_file():
215
+ errors.append(f"sub-skill {skill_dir.name} has no SKILL.md")
216
+ continue
217
+ fields = _frontmatter(path)
218
+ if fields.get("name") != skill_dir.name:
219
+ errors.append(f"{skill_dir.name}/SKILL.md: name {fields.get('name')!r} != directory")
220
+ declared = fields.get("capability", "")
221
+ if not declared:
222
+ errors.append(f"{skill_dir.name}/SKILL.md: missing capability declaration")
223
+ continue
224
+ for cap in declared.split("+"):
225
+ cap = cap.strip().split()[0] if cap.strip() else ""
226
+ if cap and not cap.startswith("("):
227
+ mapped.add(cap)
228
+ if cap not in capabilities:
229
+ errors.append(f"{skill_dir.name}/SKILL.md: capability {cap!r} is not in "
230
+ "engine/capabilities.py")
231
+ # Every registered capability must be owned by at least one role, and every
232
+ # recipe capability must be one the engine actually registers.
233
+ owned: set[str] = set()
234
+ for entry in registry.values():
235
+ owned.update(entry.get("capabilities", []))
236
+ for cap in capabilities:
237
+ if cap not in owned and cap not in PROJECTION_CAPABILITIES:
238
+ errors.append(f"capability {cap!r} is registered in engine/capabilities.py "
239
+ "but no role owns it")
240
+ for cap in sorted(mapped):
241
+ if cap in capabilities and cap not in owned and cap not in PROJECTION_CAPABILITIES:
242
+ errors.append(f"sub-skill capability {cap!r} is owned by no role")
243
+
244
+ # ---------------------------------------------------------------- workflows
245
+ workflow_dir = ROOT / "skill" / "workflows"
246
+ workflow_files = {p.stem for p in workflow_dir.glob("*.md")}
247
+ registry_workflows = workflow_registry()
248
+ public = {w for w in registry_workflows if w != "full_research_cycle"}
249
+ normalised = {name.replace("_", "-") for name in public}
250
+ if not normalised <= workflow_files:
251
+ errors.append("workflows missing a runbook: "
252
+ f"{sorted(normalised - workflow_files)}")
253
+ skill_text = (ROOT / "SKILL.md").read_text(encoding="utf-8")
254
+ for name in public:
255
+ reference = f"skill/workflows/{name.replace('_', '-')}.md"
256
+ if reference not in skill_text:
257
+ errors.append(f"SKILL.md does not route to {reference}")
258
+
259
+ skill_md = root_skill_text = skill_text # alias for readability
260
+ for stage in scientific_stages:
261
+ if f"| {stage.capitalize()} " not in skill_md and stage not in skill_md:
262
+ errors.append(f"SKILL.md does not mention stage {stage!r}")
263
+
264
+ # ------------------------------------------------------------- taxonomy
265
+ # The registry is the authority for outcome tokens and their categories;
266
+ # the JSON Schemas carry static enums because JSON Schema cannot read a
267
+ # file at validation time. This dimension is what keeps the static enums
268
+ # honest: drift between a schema enum and the registry fails the gate.
269
+ from engine.taxonomy import (
270
+ all_tokens_ordered,
271
+ categories as taxonomy_categories,
272
+ )
273
+
274
+ registered_tokens = set(all_tokens_ordered())
275
+ evidence_path = ROOT / "schemas" / "evidence.schema.json"
276
+ if evidence_path.is_file():
277
+ evidence_schema = json.loads(evidence_path.read_text(encoding="utf-8"))
278
+ enum = (evidence_schema.get("properties", {})
279
+ .get("outcome_type", {}).get("enum"))
280
+ if not isinstance(enum, list) or not enum:
281
+ errors.append("schemas/evidence.schema.json declares no outcome_type enum")
282
+ else:
283
+ missing = sorted(registered_tokens - set(enum))
284
+ extra = sorted(set(enum) - registered_tokens)
285
+ if missing:
286
+ errors.append(
287
+ "outcome_type enum is missing registered token(s): " + repr(missing))
288
+ if extra:
289
+ errors.append(
290
+ "outcome_type enum declares unregistered token(s): " + repr(extra))
291
+
292
+ # Every domain's ADOPT-gate categories must be categories it declares.
293
+ from engine.tribunal import primary_effect_categories
294
+
295
+ for domain_id in (d["id"] for d in list_domains_from_registry()):
296
+ declared = set(taxonomy_categories(domain_id))
297
+ try:
298
+ primary = primary_effect_categories(domain_id)
299
+ except ValueError as exc:
300
+ errors.append(f"domain {domain_id!r}: ADOPT gate misconfigured: {exc}")
301
+ continue
302
+ for category in primary:
303
+ if category not in declared:
304
+ errors.append(
305
+ f"domain {domain_id!r}: ADOPT gate names undeclared category "
306
+ + repr(category))
307
+
308
+ # Every V2 outcome bucket must be a category some domain declares.
309
+ v2_outcome = ROOT / "schemas" / "v2" / "outcome.schema.json"
310
+ if v2_outcome.is_file():
311
+ v2_schema = json.loads(v2_outcome.read_text(encoding="utf-8"))
312
+ buckets = (v2_schema.get("properties", {})
313
+ .get("outcome_type", {}).get("enum"))
314
+ if isinstance(buckets, list) and buckets:
315
+ every = set()
316
+ for domain_id in (d["id"] for d in list_domains_from_registry()):
317
+ every.update(taxonomy_categories(domain_id))
318
+ orphan = sorted(set(buckets) - every)
319
+ # Reverse direction too: a category a domain declares but the V2
320
+ # contract omits would reject that domain's outcomes at the graph
321
+ # layer, which is exactly how policy was blocked.
322
+ missing_bucket = sorted(every - set(buckets))
323
+ if missing_bucket:
324
+ errors.append(
325
+ "schemas/v2/outcome.schema.json is missing category bucket(s) "
326
+ "that domains declare: " + repr(missing_bucket))
327
+ if orphan:
328
+ errors.append(
329
+ "schemas/v2/outcome.schema.json declares bucket(s) no domain "
330
+ "registers: " + repr(orphan))
331
+
332
+ # ---------------------------------------------------------------- versions
333
+ for relative in ("packaging/scp-manifest.json",):
334
+ path = ROOT / relative
335
+ if not path.is_file():
336
+ continue
337
+ data = json.loads(path.read_text(encoding="utf-8"))
338
+ declared = (data.get("skill") or {}).get("version")
339
+ if declared != ENGINE_VERSION:
340
+ errors.append(f"{relative}: skill.version {declared!r} != ENGINE_VERSION {ENGINE_VERSION!r}")
341
+ start_here = ROOT / "packaging" / "START-HERE.md"
342
+ if start_here.is_file():
343
+ text = start_here.read_text(encoding="utf-8")
344
+ major_minor = ".".join(ENGINE_VERSION.split(".")[:2])
345
+ if f"EduEvidence {major_minor}" not in text:
346
+ errors.append(f"packaging/START-HERE.md does not state EduEvidence {major_minor}")
347
+
348
+ # ------------------------------------------------------- docs must not drift
349
+ for doc in ("docs/architecture.md", "README.zh-CN.md", "docs/install-guide.md"):
350
+ path = ROOT / doc
351
+ if not path.is_file():
352
+ continue
353
+ text = path.read_text(encoding="utf-8")
354
+ for stale in ("752 个测试", "752 tests"):
355
+ if stale in text:
356
+ errors.append(f"{doc}: stale test count {stale!r}; use docs/metrics.json")
357
+
358
+ return errors
359
+
360
+
361
+ def main() -> int:
362
+ print("[*] Checking protocol alignment across workflows, roles, capabilities and packaging...")
363
+ errors = check()
364
+ if errors:
365
+ print(f"[-] Protocol alignment FAILED with {len(errors)} error(s):", file=sys.stderr)
366
+ for error in errors:
367
+ print(f" • {error}", file=sys.stderr)
368
+ return 1
369
+ print("[+] Protocol alignment PASSED: stages, briefs, roles, prompts, capabilities, "
370
+ "sub-skills, workflows and packaging agree.")
371
+ return 0
372
+
373
+
374
+ if __name__ == "__main__":
375
+ sys.exit(main())
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env python3
2
+ """check_versioned_schemas.py - data-level validation for v3 / v4 / vNext.
3
+
4
+ CI validated this contract family by parsing the JSON only, so a schema could
5
+ drift arbitrarily (renamed required fields, changed enums, wrong types) and
6
+ stay green. Each family is exercised here by building a record from its own
7
+ dataclass or builder and validating that record against its schema, which is
8
+ the check that would have caught a real mismatch.
9
+
10
+ Stdlib only; exit 1 on any invalid record.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ ROOT = Path(__file__).resolve().parent.parent
19
+ sys.path.insert(0, str(ROOT))
20
+ sys.path.insert(0, str(ROOT / 'scripts'))
21
+
22
+
23
+ def _validate(record, schema_rel, label):
24
+ from validate_schema import SchemaError, Validator
25
+
26
+ path = ROOT / schema_rel
27
+ schema = json.loads(path.read_text(encoding='utf-8'))
28
+ validator = Validator(schema, base_dir=path.parent)
29
+ try:
30
+ validator.validate(record, schema, label)
31
+ except SchemaError as exc:
32
+ return f'{label}: {exc}'
33
+ return None
34
+
35
+
36
+ def main() -> int:
37
+ problems = []
38
+ checked = 0
39
+
40
+ # --- vNext: records built from their own dataclasses -------------------
41
+ try:
42
+ from engine.autoresearch.contracts import NegativeSearchRecord
43
+
44
+ record = NegativeSearchRecord(
45
+ negative_search_id='NSR-1', research_iteration_id='RI-1',
46
+ gap_id='GAP-1', queries=('a', 'b'), providers=('openalex',),
47
+ candidate_count=0, fetched_count=0, eligible_count=0)
48
+ payload = record.__dict__ if hasattr(record, '__dict__') else record
49
+ # JSON round-trip puts tuples back into their serialized form, which is
50
+ # what actually gets persisted; validate that shape.
51
+ payload = json.loads(json.dumps(payload, default=str))
52
+ err = _validate(payload, 'schemas/vNext/negative-search-record.schema.json',
53
+ 'negative-search-record')
54
+ if err:
55
+ problems.append(err)
56
+ checked += 1
57
+ except Exception as exc: # import or construction failure is itself a defect
58
+ problems.append(f'negative-search-record: could not build record: {exc}')
59
+
60
+ # --- vNext: autoresearch lifecycle records ---------------------------
61
+ # Every schema in this family is exercised against the dataclass that
62
+ # actually produces it, so a renamed field, retyped value or narrowed
63
+ # enum fails CI instead of drifting silently. Only `negative-search-record`
64
+ # was wired before; the rest parsed as JSON and nothing more.
65
+ try:
66
+ import dataclasses
67
+
68
+ from engine.autoresearch.contracts import (ResearchBudget,
69
+ ResearchExperimentType,
70
+ ResearchIteration,
71
+ ResearchStrategy,
72
+ IterationStatus)
73
+ from engine.autoresearch.gap_priority import GapPriority
74
+
75
+ strategy = ResearchStrategy(
76
+ strategy_id='RST-1',
77
+ experiment_type=ResearchExperimentType.TARGETED_RETRIEVAL,
78
+ hypothesis='a targeted retrieval closes the retention gap',
79
+ expected_gain='one or more direct retention findings',
80
+ budget=ResearchBudget())
81
+ iteration = ResearchIteration(
82
+ iteration_id='RI-1', project_id='PRJ-1', base_graph_revision=1,
83
+ gap_id='GAP-1', strategy=strategy)
84
+ iteration.complete(IterationStatus.SEARCH_SATURATED)
85
+ iteration_payload = json.loads(json.dumps(iteration.as_dict(), default=str))
86
+ strategy_payload = json.loads(json.dumps(dataclasses.asdict(strategy), default=str))
87
+ strategy_payload['experiment_type'] = strategy.experiment_type.value
88
+ priority = GapPriority(
89
+ gap_id='GAP-1', dvi_band='HIGH', cost_band='LOW',
90
+ decision_material=True, drivers=('missing_retention',),
91
+ # The enum is the controller's own vocabulary (gap_priority.py and
92
+ # controller.py emit these three); it is not the experiment-type
93
+ # vocabulary. Using a plausible-looking but wrong value here is
94
+ # exactly the drift this check exists to catch.
95
+ next_research_mode='secondary_evidence_search', score=1)
96
+ priority_payload = json.loads(json.dumps(dataclasses.asdict(priority), default=str))
97
+
98
+ for label, payload, schema_rel in (
99
+ ('research-strategy', strategy_payload, 'schemas/vNext/research-strategy.schema.json'),
100
+ ('research-iteration', iteration_payload, 'schemas/vNext/research-iteration.schema.json'),
101
+ ('gap-priority', priority_payload, 'schemas/vNext/gap-priority.schema.json'),
102
+ ):
103
+ err = _validate(payload, schema_rel, label)
104
+ if err:
105
+ problems.append(err)
106
+ checked += 1
107
+ except Exception as exc:
108
+ problems.append(f'autoresearch lifecycle records: could not build record: {exc}')
109
+
110
+ # --- vNext: orchestration plan and task contracts ---------------------
111
+ try:
112
+ import dataclasses as _dc
113
+
114
+ from engine.orchestration import ExecutionPlanner, ExecutionMode
115
+
116
+ # A delegated plan needs a run id and a base revision; without them
117
+ # TaskSpec.validate_for_dispatch refuses to describe a dispatchable task.
118
+ plan = ExecutionPlanner().plan('M', run_id='RUN-1', base_revision=1)
119
+ plan_payload = json.loads(json.dumps(_dc.asdict(plan), default=str))
120
+ plan_payload['complexity'] = str(getattr(plan.complexity, 'value', plan.complexity))
121
+ err = _validate(plan_payload, 'schemas/vNext/execution-plan.schema.json',
122
+ 'execution-plan')
123
+ if err:
124
+ problems.append(err)
125
+ checked += 1
126
+
127
+ delegated = [t for t in plan.tasks if t.execution_mode is ExecutionMode.DELEGATED]
128
+ if not delegated:
129
+ problems.append('execution plan has no delegated task to exercise task-spec')
130
+ else:
131
+ task_payload = json.loads(json.dumps(delegated[0].to_dict(), default=str))
132
+ err = _validate(task_payload, 'schemas/vNext/task-spec.schema.json', 'task-spec')
133
+ if err:
134
+ problems.append(err)
135
+ checked += 1
136
+
137
+ # A WorkerResult is built only by the lead process, which ignores
138
+ # worker self-attestation; exercise that real path, not a hand-built dict.
139
+ from engine.worker_result import validate_worker_output
140
+ result = validate_worker_output(delegated[0], {
141
+ 'task_id': delegated[0].task_id,
142
+ 'status': 'completed',
143
+ 'staging_artifacts': [{'artifact_type': delegated[0].expected_outputs[0],
144
+ 'summary': 'staged'}],
145
+ 'validated': True,
146
+ 'metrics': {},
147
+ 'summary': 'ok',
148
+ })
149
+ err = _validate(json.loads(json.dumps(result.to_dict(), default=str)),
150
+ 'schemas/vNext/worker-result.schema.json', 'worker-result')
151
+ if err:
152
+ problems.append(err)
153
+ checked += 1
154
+ except Exception as exc:
155
+ problems.append(f'orchestration records: could not build record: {exc}')
156
+
157
+ # --- vNext: autoevolve session and experiment records -----------------
158
+ try:
159
+ import dataclasses as _dc2
160
+
161
+ from engine.autoevolve.core import EvalSnapshot, SkillExperiment
162
+
163
+ snapshot = EvalSnapshot(
164
+ eval_id='EVAL-1', hard_gates_passed=True, science_score=0.8,
165
+ research_score=0.7, robustness=0.9, cost=0.0,
166
+ latency=12.5, complexity=0.5, repeats=3, noise_floor=0.01,
167
+ dev_passed=True, holdout_passed=True, adversarial_passed=True,
168
+ holdout_isolation_verified=False, eval_suite_hash='deadbeef')
169
+ snapshot_payload = json.loads(json.dumps(_dc2.asdict(snapshot), default=str))
170
+ err = _validate(snapshot_payload, 'schemas/vNext/eval-snapshot.schema.json',
171
+ 'eval-snapshot')
172
+ if err:
173
+ problems.append(err)
174
+ checked += 1
175
+
176
+ experiment = SkillExperiment(
177
+ experiment_id='EXP-1', session_id='session-1',
178
+ parent_skill_revision='rev-1', hypothesis='a narrower prompt scores higher',
179
+ mutation_scope=('safe',), changed_files=('skill/agents/skeptic.md',),
180
+ candidate_commit='deadbeef', baseline_eval_id='EVAL-1',
181
+ candidate_eval_id='EVAL-2', protected_hash_before='h1',
182
+ protected_hash_after='h1', status='REJECT',
183
+ promotion_reason='no significant improvement')
184
+ experiment_payload = json.loads(json.dumps(_dc2.asdict(experiment), default=str))
185
+ err = _validate(experiment_payload, 'schemas/vNext/skill-experiment.schema.json',
186
+ 'skill-experiment')
187
+ if err:
188
+ problems.append(err)
189
+ checked += 1
190
+
191
+ # The session report is the real runner payload (runner.py writes
192
+ # daily-report.json); validate that documented shape directly.
193
+ session_report = {
194
+ 'run_tag': 'session-1', 'branch': 'autoevolve/session-1',
195
+ 'experiments': 1, 'statuses': ['REJECT'], 'best_experiment_id': None,
196
+ 'best_candidate_commit': None, 'cost': 0.0, 'wall_minutes': 1.0,
197
+ 'plateau': False, 'stop_reason': 'completed',
198
+ 'promotion': 'branch_only', 'branch_push_requested': False,
199
+ 'branch_pushed': False, 'mutation_view': 'dev_only_context_isolation',
200
+ 'holdout_isolation_verified': False, 'isolation_provider': 'none',
201
+ 'isolation_reason': 'no os isolation provider available',
202
+ 'eval_suite_hash': 'deadbeef', 'security_note': 'branch only',
203
+ 'candidate_artifacts': 'local session state only; never auto-pushed',
204
+ }
205
+ err = _validate(session_report, 'schemas/vNext/autoevolve-session.schema.json',
206
+ 'autoevolve-session')
207
+ if err:
208
+ problems.append(err)
209
+ checked += 1
210
+ except Exception as exc:
211
+ problems.append(f'autoevolve records: could not build record: {exc}')
212
+
213
+ # --- v3: the BENCHMARK run manifest (scripts/benchmark_v3.py) ---------
214
+ # Note: schemas/v3/run-manifest.schema.json describes the empirical
215
+ # benchmark harness record, not the run-workspace manifest. Validating the
216
+ # wrong producer is how a contract silently stops describing reality.
217
+ try:
218
+ fixture = sorted((ROOT / 'benchmarks' / 'empirical').glob('*/manifest.json'))
219
+ if fixture:
220
+ payload = json.loads(fixture[0].read_text(encoding='utf-8'))
221
+ err = _validate(payload, 'schemas/v3/run-manifest.schema.json',
222
+ f'run-manifest ({fixture[0].parent.name})')
223
+ if err:
224
+ problems.append(err)
225
+ checked += 1
226
+ else:
227
+ # benchmarks/empirical/ holds local empirical runs and is excluded
228
+ # from the submission package on purpose. Its absence means there is
229
+ # nothing to check in this checkout, not that the contract is broken.
230
+ print('note: no benchmarks/empirical/*/manifest.json in this checkout; '
231
+ 'skipping the v3 run-manifest data check')
232
+ except Exception as exc:
233
+ problems.append(f'v3 run-manifest: {exc}')
234
+
235
+ # --- sanity: every declared schema still parses -----------------------
236
+ for family in ('v3', 'v4', 'vNext'):
237
+ for path in sorted((ROOT / 'schemas' / family).glob('*.json')):
238
+ try:
239
+ json.loads(path.read_text(encoding='utf-8'))
240
+ checked += 1
241
+ except (OSError, json.JSONDecodeError) as exc:
242
+ problems.append(f'{path.relative_to(ROOT)}: {exc}')
243
+
244
+ if problems:
245
+ print('ERROR: versioned schema validation failed', file=sys.stderr)
246
+ for item in problems:
247
+ print(f' {item}', file=sys.stderr)
248
+ return 1
249
+ print(f'versioned schemas OK ({checked} checks across v3 / v4 / vNext)')
250
+ return 0
251
+
252
+
253
+ if __name__ == '__main__':
254
+ sys.exit(main())
@@ -27,14 +27,19 @@ from pathlib import Path
27
27
 
28
28
  from evidence_semantics import claim_relation
29
29
 
30
- SUPPORTED_OUTCOMES = {
31
- "knowledge_gain", "concept_understanding", "retention", "transfer",
32
- "independent_problem_solving", "completion_time", "accuracy",
33
- "code_quality", "assignment_score", "engagement", "motivation",
34
- "cognitive_load", "help_seeking", "metacognition", "ai_dependency",
35
- "over_reliance", "reduced_effort", "reduced_transfer",
36
- "academic_integrity_risk", "false_confidence",
37
- }
30
+ def _supported_outcomes() -> set[str]:
31
+ """Every registered outcome token, read from the domain registry.
32
+
33
+ This was a hand-copied 20-token education list in three separate files;
34
+ it silently rejected policy tokens such as policy_effectiveness. The
35
+ registry (domains/<id>/outcome_taxonomy.json) is the single authority.
36
+ """
37
+ from engine.taxonomy import all_tokens_ordered
38
+
39
+ return set(all_tokens_ordered())
40
+
41
+
42
+ SUPPORTED_OUTCOMES = _supported_outcomes()
38
43
 
39
44
 
40
45
  def load_records(path: Path) -> list[dict]:
@@ -40,6 +40,16 @@ import json
40
40
  import sys
41
41
  from pathlib import Path
42
42
 
43
+ # This module is also documented as a standalone command (SKILL.md), so it
44
+ # has to find the repository root on its own path; the orchestrator sets
45
+ # sys.path for it, which hid the missing import from that caller.
46
+ import sys as _sys
47
+ from pathlib import Path as _Path
48
+ _ROOT = _Path(__file__).resolve().parent.parent
49
+ if str(_ROOT) not in _sys.path:
50
+ _sys.path.insert(0, str(_ROOT))
51
+
52
+
43
53
  from evidence_score import (CONFIDENCE_POLICY_VERSION,
44
54
  decision_consistency_score, directness_score,
45
55
  independent_samples, independent_studies)