psyclaw 0.27.22 → 0.28.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 (1048) hide show
  1. package/README.md +11 -5
  2. package/dist/apps/panel/index.html +2 -2
  3. package/dist/src/adapters/pi/extension.js +225 -92
  4. package/dist/src/adapters/pi/extension.js.map +1 -1
  5. package/dist/src/agents/import.js +44 -32
  6. package/dist/src/agents/import.js.map +1 -1
  7. package/dist/src/analysis/hooks.d.ts +1 -0
  8. package/dist/src/analysis/hooks.js +20 -1
  9. package/dist/src/analysis/hooks.js.map +1 -1
  10. package/dist/src/ars/bridge.d.ts +20 -0
  11. package/dist/src/ars/bridge.js +132 -0
  12. package/dist/src/ars/bridge.js.map +1 -0
  13. package/dist/src/ars/contracts.d.ts +55 -0
  14. package/dist/src/ars/contracts.js +2 -0
  15. package/dist/src/ars/contracts.js.map +1 -0
  16. package/dist/src/ars/panel-plan.d.ts +4 -0
  17. package/dist/src/ars/panel-plan.js +11 -0
  18. package/dist/src/ars/panel-plan.js.map +1 -0
  19. package/dist/src/ars/pi-panel-executor.d.ts +30 -0
  20. package/dist/src/ars/pi-panel-executor.js +140 -0
  21. package/dist/src/ars/pi-panel-executor.js.map +1 -0
  22. package/dist/src/ars/profile.d.ts +36 -0
  23. package/dist/src/ars/profile.js +119 -0
  24. package/dist/src/ars/profile.js.map +1 -0
  25. package/dist/src/ars/re-review.d.ts +22 -0
  26. package/dist/src/ars/re-review.js +172 -0
  27. package/dist/src/ars/re-review.js.map +1 -0
  28. package/dist/src/branding.d.ts +2 -4
  29. package/dist/src/branding.js +3 -5
  30. package/dist/src/branding.js.map +1 -1
  31. package/dist/src/bundled-tools.d.ts +3 -0
  32. package/dist/src/bundled-tools.js +21 -0
  33. package/dist/src/bundled-tools.js.map +1 -0
  34. package/dist/src/chat.js +6 -5
  35. package/dist/src/chat.js.map +1 -1
  36. package/dist/src/creation/contracts.d.ts +38 -0
  37. package/dist/src/creation/contracts.js +2 -0
  38. package/dist/src/creation/contracts.js.map +1 -0
  39. package/dist/src/creation/service.d.ts +7 -0
  40. package/dist/src/creation/service.js +196 -0
  41. package/dist/src/creation/service.js.map +1 -0
  42. package/dist/src/index.d.ts +10 -0
  43. package/dist/src/index.js +10 -0
  44. package/dist/src/index.js.map +1 -1
  45. package/dist/src/install/installer.js +32 -10
  46. package/dist/src/install/installer.js.map +1 -1
  47. package/dist/src/orchestration/personas.d.ts +15 -0
  48. package/dist/src/orchestration/personas.js +51 -0
  49. package/dist/src/orchestration/personas.js.map +1 -0
  50. package/dist/src/orchestration/pi-executor.d.ts +1 -0
  51. package/dist/src/orchestration/pi-executor.js +1 -1
  52. package/dist/src/orchestration/pi-executor.js.map +1 -1
  53. package/dist/src/panel/extension.js +29 -8
  54. package/dist/src/panel/extension.js.map +1 -1
  55. package/dist/src/panel/server.js +14 -5
  56. package/dist/src/panel/server.js.map +1 -1
  57. package/dist/src/project/paths.d.ts +3 -0
  58. package/dist/src/project/paths.js +7 -0
  59. package/dist/src/project/paths.js.map +1 -1
  60. package/dist/src/rules/user-rules.d.ts +8 -0
  61. package/dist/src/rules/user-rules.js +36 -0
  62. package/dist/src/rules/user-rules.js.map +1 -0
  63. package/dist/src/skills/contracts.d.ts +4 -4
  64. package/dist/src/skills/recommended.js +1 -1
  65. package/dist/src/skills/registry.js +49 -28
  66. package/dist/src/skills/registry.js.map +1 -1
  67. package/dist/src/style/cli-ui.js +0 -1
  68. package/dist/src/style/cli-ui.js.map +1 -1
  69. package/package.json +14 -3
  70. package/scripts/rebrand-pi.mjs +6 -0
  71. package/skills/recommended/catalog.json +2 -11
  72. package/vendor/ars/.claude/CLAUDE.md +371 -0
  73. package/vendor/ars/.command-invariants.toml +24 -0
  74. package/vendor/ars/CITATION.cff +35 -0
  75. package/vendor/ars/LICENSE +417 -0
  76. package/vendor/ars/MODE_REGISTRY.md +76 -0
  77. package/vendor/ars/NOTICE.md +26 -0
  78. package/vendor/ars/POSITIONING.md +99 -0
  79. package/vendor/ars/PSYCLAW_SOURCE.json +10 -0
  80. package/vendor/ars/README.md +751 -0
  81. package/vendor/ars/SECURITY.md +52 -0
  82. package/vendor/ars/THIRD_PARTY.md +70 -0
  83. package/vendor/ars/academic-paper/SKILL.md +542 -0
  84. package/vendor/ars/academic-paper/agents/abstract_bilingual_agent.md +171 -0
  85. package/vendor/ars/academic-paper/agents/argument_builder_agent.md +276 -0
  86. package/vendor/ars/academic-paper/agents/citation_compliance_agent.md +422 -0
  87. package/vendor/ars/academic-paper/agents/draft_writer_agent.md +656 -0
  88. package/vendor/ars/academic-paper/agents/formatter_agent.md +999 -0
  89. package/vendor/ars/academic-paper/agents/intake_agent.md +393 -0
  90. package/vendor/ars/academic-paper/agents/literature_strategist_agent.md +626 -0
  91. package/vendor/ars/academic-paper/agents/peer_reviewer_agent.md +516 -0
  92. package/vendor/ars/academic-paper/agents/revision_coach_agent.md +334 -0
  93. package/vendor/ars/academic-paper/agents/socratic_mentor_agent.md +527 -0
  94. package/vendor/ars/academic-paper/agents/structure_architect_agent.md +401 -0
  95. package/vendor/ars/academic-paper/agents/visualization_agent.md +441 -0
  96. package/vendor/ars/academic-paper/examples/chinese_paper_example.md +278 -0
  97. package/vendor/ars/academic-paper/examples/clinical_citation_verification_checklist.md +95 -0
  98. package/vendor/ars/academic-paper/examples/clinical_epistemic_status_example.md +100 -0
  99. package/vendor/ars/academic-paper/examples/commitment_ledger_example.md +147 -0
  100. package/vendor/ars/academic-paper/examples/imrad_hei_example.md +234 -0
  101. package/vendor/ars/academic-paper/examples/literature_review_example.md +260 -0
  102. package/vendor/ars/academic-paper/examples/plan_mode_guided_writing.md +600 -0
  103. package/vendor/ars/academic-paper/examples/revision_mode_example.md +344 -0
  104. package/vendor/ars/academic-paper/examples/revision_recovery_example.md +506 -0
  105. package/vendor/ars/academic-paper/examples/version_family_reconciliation_example.md +89 -0
  106. package/vendor/ars/academic-paper/references/abstract_writing_guide.md +169 -0
  107. package/vendor/ars/academic-paper/references/academic_writing_style.md +188 -0
  108. package/vendor/ars/academic-paper/references/anti_leakage_protocol.md +83 -0
  109. package/vendor/ars/academic-paper/references/apa7_chinese_citation_guide.md +364 -0
  110. package/vendor/ars/academic-paper/references/apa7_extended_guide.md +198 -0
  111. package/vendor/ars/academic-paper/references/changelog.md +11 -0
  112. package/vendor/ars/academic-paper/references/citation_format_switcher.md +228 -0
  113. package/vendor/ars/academic-paper/references/committee_correspondence_protocol.md +158 -0
  114. package/vendor/ars/academic-paper/references/credit_authorship_guide.md +308 -0
  115. package/vendor/ars/academic-paper/references/disclosure_mode_protocol.md +478 -0
  116. package/vendor/ars/academic-paper/references/domain_evidence_profiles.md +38 -0
  117. package/vendor/ars/academic-paper/references/failure_paths.md +349 -0
  118. package/vendor/ars/academic-paper/references/funding_statement_guide.md +319 -0
  119. package/vendor/ars/academic-paper/references/hei_domain_glossary.md +169 -0
  120. package/vendor/ars/academic-paper/references/intro_title_rhetoric_guide.md +114 -0
  121. package/vendor/ars/academic-paper/references/journal_submission_guide.md +249 -0
  122. package/vendor/ars/academic-paper/references/latex_template_reference.md +378 -0
  123. package/vendor/ars/academic-paper/references/mode_selection_guide.md +378 -0
  124. package/vendor/ars/academic-paper/references/paper_structure_patterns.md +330 -0
  125. package/vendor/ars/academic-paper/references/plan_mode_protocol.md +112 -0
  126. package/vendor/ars/academic-paper/references/policy_anchor_disclosure_protocol.md +200 -0
  127. package/vendor/ars/academic-paper/references/policy_anchor_table.md +157 -0
  128. package/vendor/ars/academic-paper/references/revision_patch_protocol.md +173 -0
  129. package/vendor/ars/academic-paper/references/statistical_visualization_standards.md +750 -0
  130. package/vendor/ars/academic-paper/references/venue_disclosure_policies.md +259 -0
  131. package/vendor/ars/academic-paper/references/vlm_figure_verification.md +126 -0
  132. package/vendor/ars/academic-paper/references/workflow_phase_details.md +135 -0
  133. package/vendor/ars/academic-paper/references/writing_judgment_framework.md +59 -0
  134. package/vendor/ars/academic-paper/references/writing_quality_check.md +173 -0
  135. package/vendor/ars/academic-paper/templates/bilingual_abstract_template.md +78 -0
  136. package/vendor/ars/academic-paper/templates/case_study_template.md +129 -0
  137. package/vendor/ars/academic-paper/templates/conference_paper_template.md +108 -0
  138. package/vendor/ars/academic-paper/templates/credit_statement_template.md +132 -0
  139. package/vendor/ars/academic-paper/templates/funding_statement_template.md +290 -0
  140. package/vendor/ars/academic-paper/templates/imrad_template.md +183 -0
  141. package/vendor/ars/academic-paper/templates/latex_article_template.tex +199 -0
  142. package/vendor/ars/academic-paper/templates/literature_review_template.md +135 -0
  143. package/vendor/ars/academic-paper/templates/policy_brief_template.md +139 -0
  144. package/vendor/ars/academic-paper/templates/revision_tracking_template.md +199 -0
  145. package/vendor/ars/academic-paper/templates/theoretical_paper_template.md +119 -0
  146. package/vendor/ars/academic-paper-reviewer/SKILL.md +491 -0
  147. package/vendor/ars/academic-paper-reviewer/agents/devils_advocate_reviewer_agent.md +443 -0
  148. package/vendor/ars/academic-paper-reviewer/agents/domain_reviewer_agent.md +412 -0
  149. package/vendor/ars/academic-paper-reviewer/agents/editorial_synthesizer_agent.md +478 -0
  150. package/vendor/ars/academic-paper-reviewer/agents/eic_agent.md +339 -0
  151. package/vendor/ars/academic-paper-reviewer/agents/field_analyst_agent.md +221 -0
  152. package/vendor/ars/academic-paper-reviewer/agents/methodology_reviewer_agent.md +449 -0
  153. package/vendor/ars/academic-paper-reviewer/agents/perspective_reviewer_agent.md +427 -0
  154. package/vendor/ars/academic-paper-reviewer/examples/hei_paper_review_example.md +391 -0
  155. package/vendor/ars/academic-paper-reviewer/examples/interdisciplinary_review_example.md +299 -0
  156. package/vendor/ars/academic-paper-reviewer/examples/subclaim_decomposition_example.md +80 -0
  157. package/vendor/ars/academic-paper-reviewer/references/calibration_mode_protocol.md +256 -0
  158. package/vendor/ars/academic-paper-reviewer/references/changelog.md +10 -0
  159. package/vendor/ars/academic-paper-reviewer/references/editorial_decision_standards.md +236 -0
  160. package/vendor/ars/academic-paper-reviewer/references/guided_mode_protocol.md +34 -0
  161. package/vendor/ars/academic-paper-reviewer/references/integration_guide.md +15 -0
  162. package/vendor/ars/academic-paper-reviewer/references/quality_rubrics.md +84 -0
  163. package/vendor/ars/academic-paper-reviewer/references/re_review_mode_protocol.md +340 -0
  164. package/vendor/ars/academic-paper-reviewer/references/review_criteria_framework.md +98 -0
  165. package/vendor/ars/academic-paper-reviewer/references/review_panel_provenance_protocol.md +197 -0
  166. package/vendor/ars/academic-paper-reviewer/references/review_quality_thinking.md +58 -0
  167. package/vendor/ars/academic-paper-reviewer/references/reviewer_sprint_prompt_source.md +324 -0
  168. package/vendor/ars/academic-paper-reviewer/references/sprint_contract_protocol.md +296 -0
  169. package/vendor/ars/academic-paper-reviewer/references/statistical_reporting_standards.md +505 -0
  170. package/vendor/ars/academic-paper-reviewer/references/top_journals_by_field.md +206 -0
  171. package/vendor/ars/academic-paper-reviewer/templates/editorial_decision_template.md +235 -0
  172. package/vendor/ars/academic-paper-reviewer/templates/peer_review_report_template.md +305 -0
  173. package/vendor/ars/academic-paper-reviewer/templates/revision_response_template.md +248 -0
  174. package/vendor/ars/academic-pipeline/SKILL.md +736 -0
  175. package/vendor/ars/academic-pipeline/agents/claim_ref_alignment_audit_agent.md +382 -0
  176. package/vendor/ars/academic-pipeline/agents/collaboration_depth_agent.md +164 -0
  177. package/vendor/ars/academic-pipeline/agents/integrity_verification_agent.md +870 -0
  178. package/vendor/ars/academic-pipeline/agents/pipeline_orchestrator_agent.md +1379 -0
  179. package/vendor/ars/academic-pipeline/agents/state_tracker_agent.md +622 -0
  180. package/vendor/ars/academic-pipeline/examples/full_pipeline_example.md +482 -0
  181. package/vendor/ars/academic-pipeline/examples/integrity_failure_recovery.md +389 -0
  182. package/vendor/ars/academic-pipeline/examples/mid_entry_example.md +414 -0
  183. package/vendor/ars/academic-pipeline/references/adapters/.gitkeep +0 -0
  184. package/vendor/ars/academic-pipeline/references/adapters/overview.md +153 -0
  185. package/vendor/ars/academic-pipeline/references/ai_research_failure_modes.md +185 -0
  186. package/vendor/ars/academic-pipeline/references/changelog.md +14 -0
  187. package/vendor/ars/academic-pipeline/references/claim_audit_calibration_protocol.md +175 -0
  188. package/vendor/ars/academic-pipeline/references/claim_verification_protocol.md +282 -0
  189. package/vendor/ars/academic-pipeline/references/external_review_protocol.md +131 -0
  190. package/vendor/ars/academic-pipeline/references/integrity_review_protocol.md +110 -0
  191. package/vendor/ars/academic-pipeline/references/literature_corpus_consumers.md +193 -0
  192. package/vendor/ars/academic-pipeline/references/mode_advisor.md +135 -0
  193. package/vendor/ars/academic-pipeline/references/passport_as_reset_boundary.md +132 -0
  194. package/vendor/ars/academic-pipeline/references/pipeline_state_machine.md +405 -0
  195. package/vendor/ars/academic-pipeline/references/plagiarism_detection_protocol.md +239 -0
  196. package/vendor/ars/academic-pipeline/references/process_summary_protocol.md +209 -0
  197. package/vendor/ars/academic-pipeline/references/progress_dashboard_template.md +38 -0
  198. package/vendor/ars/academic-pipeline/references/reinforcement_content.md +15 -0
  199. package/vendor/ars/academic-pipeline/references/reproducibility_audit.md +55 -0
  200. package/vendor/ars/academic-pipeline/references/score_trajectory_protocol.md +78 -0
  201. package/vendor/ars/academic-pipeline/references/team_collaboration_protocol.md +261 -0
  202. package/vendor/ars/academic-pipeline/references/two_stage_review_protocol.md +27 -0
  203. package/vendor/ars/academic-pipeline/templates/pipeline_status_template.md +146 -0
  204. package/vendor/ars/agents/report_compiler_agent.md +341 -0
  205. package/vendor/ars/agents/research_architect_agent.md +298 -0
  206. package/vendor/ars/agents/synthesis_agent.md +356 -0
  207. package/vendor/ars/commands/ars-3w.md +10 -0
  208. package/vendor/ars/commands/ars-abstract.md +10 -0
  209. package/vendor/ars/commands/ars-cache-invalidate.md +20 -0
  210. package/vendor/ars/commands/ars-citation-check.md +10 -0
  211. package/vendor/ars/commands/ars-disclosure.md +10 -0
  212. package/vendor/ars/commands/ars-format-convert.md +10 -0
  213. package/vendor/ars/commands/ars-full.md +9 -0
  214. package/vendor/ars/commands/ars-lit-review.md +12 -0
  215. package/vendor/ars/commands/ars-mark-read.md +18 -0
  216. package/vendor/ars/commands/ars-outline.md +10 -0
  217. package/vendor/ars/commands/ars-plan.md +10 -0
  218. package/vendor/ars/commands/ars-rebuttal-audit.md +12 -0
  219. package/vendor/ars/commands/ars-reviewer.md +9 -0
  220. package/vendor/ars/commands/ars-revision-coach.md +9 -0
  221. package/vendor/ars/commands/ars-revision.md +10 -0
  222. package/vendor/ars/commands/ars-unmark-read.md +16 -0
  223. package/vendor/ars/deep-research/SKILL.md +600 -0
  224. package/vendor/ars/deep-research/agents/bibliography_agent.md +473 -0
  225. package/vendor/ars/deep-research/agents/devils_advocate_agent.md +192 -0
  226. package/vendor/ars/deep-research/agents/editor_in_chief_agent.md +167 -0
  227. package/vendor/ars/deep-research/agents/ethics_review_agent.md +267 -0
  228. package/vendor/ars/deep-research/agents/meta_analysis_agent.md +325 -0
  229. package/vendor/ars/deep-research/agents/monitoring_agent.md +209 -0
  230. package/vendor/ars/deep-research/agents/report_compiler_agent.md +341 -0
  231. package/vendor/ars/deep-research/agents/research_architect_agent.md +298 -0
  232. package/vendor/ars/deep-research/agents/research_question_agent.md +216 -0
  233. package/vendor/ars/deep-research/agents/risk_of_bias_agent.md +231 -0
  234. package/vendor/ars/deep-research/agents/socratic_mentor_agent.md +764 -0
  235. package/vendor/ars/deep-research/agents/source_verification_agent.md +219 -0
  236. package/vendor/ars/deep-research/agents/synthesis_agent.md +356 -0
  237. package/vendor/ars/deep-research/agents/timeline_extraction_agent.md +99 -0
  238. package/vendor/ars/deep-research/examples/exploratory_research.md +157 -0
  239. package/vendor/ars/deep-research/examples/fact_check_mode.md +173 -0
  240. package/vendor/ars/deep-research/examples/handoff_to_paper.md +318 -0
  241. package/vendor/ars/deep-research/examples/idea_diversity_coverage_gap_advisory.md +64 -0
  242. package/vendor/ars/deep-research/examples/policy_analysis.md +161 -0
  243. package/vendor/ars/deep-research/examples/review_mode.md +253 -0
  244. package/vendor/ars/deep-research/examples/socratic_guided_research.md +331 -0
  245. package/vendor/ars/deep-research/examples/systematic_review.md +133 -0
  246. package/vendor/ars/deep-research/references/apa7_style_guide.md +162 -0
  247. package/vendor/ars/deep-research/references/argumentation_reasoning_framework.md +68 -0
  248. package/vendor/ars/deep-research/references/arxiv_api_protocol.md +76 -0
  249. package/vendor/ars/deep-research/references/changelog.md +22 -0
  250. package/vendor/ars/deep-research/references/chinese_literature_api_protocol.md +317 -0
  251. package/vendor/ars/deep-research/references/cross_agent_quality_definitions.md +14 -0
  252. package/vendor/ars/deep-research/references/crossref_api_protocol.md +84 -0
  253. package/vendor/ars/deep-research/references/equator_reporting_guidelines.md +482 -0
  254. package/vendor/ars/deep-research/references/ethics_checklist.md +282 -0
  255. package/vendor/ars/deep-research/references/failure_paths.md +355 -0
  256. package/vendor/ars/deep-research/references/interdisciplinary_bridges.md +292 -0
  257. package/vendor/ars/deep-research/references/irb_decision_tree.md +315 -0
  258. package/vendor/ars/deep-research/references/literature_monitoring_strategies.md +263 -0
  259. package/vendor/ars/deep-research/references/logical_fallacies.md +192 -0
  260. package/vendor/ars/deep-research/references/methodology_patterns.md +462 -0
  261. package/vendor/ars/deep-research/references/mode_selection_guide.md +331 -0
  262. package/vendor/ars/deep-research/references/openalex_api_protocol.md +82 -0
  263. package/vendor/ars/deep-research/references/preregistration_guide.md +324 -0
  264. package/vendor/ars/deep-research/references/semantic_scholar_api_protocol.md +107 -0
  265. package/vendor/ars/deep-research/references/socratic_mode_protocol.md +99 -0
  266. package/vendor/ars/deep-research/references/socratic_questioning_framework.md +232 -0
  267. package/vendor/ars/deep-research/references/source_quality_hierarchy.md +188 -0
  268. package/vendor/ars/deep-research/references/systematic_review_protocol.md +95 -0
  269. package/vendor/ars/deep-research/references/systematic_review_toolkit.md +353 -0
  270. package/vendor/ars/deep-research/templates/evidence_assessment_template.md +127 -0
  271. package/vendor/ars/deep-research/templates/literature_matrix_template.md +85 -0
  272. package/vendor/ars/deep-research/templates/preregistration_template.md +318 -0
  273. package/vendor/ars/deep-research/templates/prisma_protocol_template.md +248 -0
  274. package/vendor/ars/deep-research/templates/prisma_report_template.md +415 -0
  275. package/vendor/ars/deep-research/templates/research_brief_template.md +93 -0
  276. package/vendor/ars/package.json +24 -0
  277. package/vendor/ars/pi/README.md +161 -0
  278. package/vendor/ars/pi/package.json +26 -0
  279. package/vendor/ars/pi/wrapper.js +193 -0
  280. package/vendor/ars/pi/wrapper.test.mjs +201 -0
  281. package/vendor/ars/pyproject.toml +2 -0
  282. package/vendor/ars/requirements-pdf-content-classifier.txt +5 -0
  283. package/vendor/ars/scripts/_block_parser.py +396 -0
  284. package/vendor/ars/scripts/_ci_pytest_manifest.toml +661 -0
  285. package/vendor/ars/scripts/_claim_audit_constants.py +268 -0
  286. package/vendor/ars/scripts/_e4_evidence.py +110 -0
  287. package/vendor/ars/scripts/_eval_threshold_gate.py +73 -0
  288. package/vendor/ars/scripts/_markdown_lint_util.py +224 -0
  289. package/vendor/ars/scripts/_next_verified_at_ms.py +176 -0
  290. package/vendor/ars/scripts/_passport_yaml.py +53 -0
  291. package/vendor/ars/scripts/_skill_lint.py +254 -0
  292. package/vendor/ars/scripts/_text_similarity.py +141 -0
  293. package/vendor/ars/scripts/adapters/README.md +89 -0
  294. package/vendor/ars/scripts/adapters/_common.py +209 -0
  295. package/vendor/ars/scripts/adapters/examples/folder_scan/expected_passport.yaml +25 -0
  296. package/vendor/ars/scripts/adapters/examples/folder_scan/expected_rejection_log.yaml +18 -0
  297. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Chen2024_AIAssessment.pdf +0 -0
  298. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Wang_2023_formative_feedback.pdf +0 -0
  299. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/paper1.pdf +0 -0
  300. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture//344/270/255/346/226/207/346/252/224/345/220/215_2024.pdf +0 -0
  301. package/vendor/ars/scripts/adapters/examples/obsidian/expected_passport.yaml +40 -0
  302. package/vendor/ars/scripts/adapters/examples/obsidian/expected_rejection_log.yaml +13 -0
  303. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.gitkeep +0 -0
  304. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.obsidian/app.json +1 -0
  305. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/_templates/tmpl.md +7 -0
  306. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/chen2024ai.md +13 -0
  307. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/invalid.md +1 -0
  308. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/wang2023formative.md +10 -0
  309. package/vendor/ars/scripts/adapters/examples/zotero/expected_passport.yaml +34 -0
  310. package/vendor/ars/scripts/adapters/examples/zotero/expected_rejection_log.yaml +34 -0
  311. package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/.gitkeep +0 -0
  312. package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/export.json +43 -0
  313. package/vendor/ars/scripts/adapters/folder_scan.py +214 -0
  314. package/vendor/ars/scripts/adapters/obsidian.py +336 -0
  315. package/vendor/ars/scripts/adapters/tests/.gitkeep +0 -0
  316. package/vendor/ars/scripts/adapters/tests/conftest.py +77 -0
  317. package/vendor/ars/scripts/adapters/tests/test_check_corpus_consumer_protocol.py +632 -0
  318. package/vendor/ars/scripts/adapters/tests/test_check_literature_corpus_schema.py +631 -0
  319. package/vendor/ars/scripts/adapters/tests/test_common.py +365 -0
  320. package/vendor/ars/scripts/adapters/tests/test_conftest.py +96 -0
  321. package/vendor/ars/scripts/adapters/tests/test_folder_scan.py +260 -0
  322. package/vendor/ars/scripts/adapters/tests/test_literature_corpus_entry_schema.py +745 -0
  323. package/vendor/ars/scripts/adapters/tests/test_obsidian.py +357 -0
  324. package/vendor/ars/scripts/adapters/tests/test_rejection_log_schema.py +271 -0
  325. package/vendor/ars/scripts/adapters/tests/test_sync_adapter_docs.py +88 -0
  326. package/vendor/ars/scripts/adapters/tests/test_zotero.py +454 -0
  327. package/vendor/ars/scripts/adapters/zotero.py +318 -0
  328. package/vendor/ars/scripts/adjudication_activity.py +1592 -0
  329. package/vendor/ars/scripts/announce-ars-loaded.sh +144 -0
  330. package/vendor/ars/scripts/ars_anchorize_draft.py +170 -0
  331. package/vendor/ars/scripts/ars_apply_revision_patch.py +912 -0
  332. package/vendor/ars/scripts/ars_cache_invalidate.py +40 -0
  333. package/vendor/ars/scripts/ars_mark_read.py +521 -0
  334. package/vendor/ars/scripts/ars_phase_scope_manifest.json +33 -0
  335. package/vendor/ars/scripts/ars_update_check.sh +215 -0
  336. package/vendor/ars/scripts/ars_write_scope_guard.py +506 -0
  337. package/vendor/ars/scripts/arxiv_client.py +222 -0
  338. package/vendor/ars/scripts/audit_snapshot.py +572 -0
  339. package/vendor/ars/scripts/bibliographic_integrity_signals.py +800 -0
  340. package/vendor/ars/scripts/bootstrap_timeline_yaml.py +146 -0
  341. package/vendor/ars/scripts/build_claim_standing_candidate_ledger.py +1238 -0
  342. package/vendor/ars/scripts/build_claim_standing_query_plan.py +643 -0
  343. package/vendor/ars/scripts/build_content_coverage_advisory.py +1205 -0
  344. package/vendor/ars/scripts/build_cross_document_consistency_advisory.py +2332 -0
  345. package/vendor/ars/scripts/build_review_pathway_rule_trace.py +830 -0
  346. package/vendor/ars/scripts/build_submission_packet_manifest.py +2310 -0
  347. package/vendor/ars/scripts/check_215_field_norm.py +173 -0
  348. package/vendor/ars/scripts/check_216_surface_form.py +250 -0
  349. package/vendor/ars/scripts/check_268_nested_commitment_ledger.py +180 -0
  350. package/vendor/ars/scripts/check_390_revision_patch_discipline.py +296 -0
  351. package/vendor/ars/scripts/check_392_citation_verification_intake.py +126 -0
  352. package/vendor/ars/scripts/check_394_submission_policy.py +178 -0
  353. package/vendor/ars/scripts/check_439_format_profile.py +307 -0
  354. package/vendor/ars/scripts/check_619_disclosure_closeout.py +237 -0
  355. package/vendor/ars/scripts/check_630_codex_subscription_transport.py +456 -0
  356. package/vendor/ars/scripts/check_669_review_pathway_rule_trace.py +662 -0
  357. package/vendor/ars/scripts/check_670_revision_roadmap_integration.py +518 -0
  358. package/vendor/ars/scripts/check_673_adjudication_activity.py +684 -0
  359. package/vendor/ars/scripts/check_684_review_criteria_binding.py +557 -0
  360. package/vendor/ars/scripts/check_agents_mirror_sync.py +115 -0
  361. package/vendor/ars/scripts/check_audit_artifact_consistency.py +2313 -0
  362. package/vendor/ars/scripts/check_benchmark_report.py +79 -0
  363. package/vendor/ars/scripts/check_bibliographic_integrity_signals.py +831 -0
  364. package/vendor/ars/scripts/check_calibration_tiers.py +235 -0
  365. package/vendor/ars/scripts/check_changelog_covers_merges.py +289 -0
  366. package/vendor/ars/scripts/check_ci_pytest_manifest.py +204 -0
  367. package/vendor/ars/scripts/check_claim_audit_consistency.py +1664 -0
  368. package/vendor/ars/scripts/check_claim_standing_candidate_ledger_integration.py +500 -0
  369. package/vendor/ars/scripts/check_claim_standing_freshness.py +253 -0
  370. package/vendor/ars/scripts/check_claim_standing_transmissions.py +449 -0
  371. package/vendor/ars/scripts/check_collaboration_depth_rubric.py +180 -0
  372. package/vendor/ars/scripts/check_command_frontmatter_name.py +116 -0
  373. package/vendor/ars/scripts/check_committee_correspondence.py +333 -0
  374. package/vendor/ars/scripts/check_compliance_report.py +108 -0
  375. package/vendor/ars/scripts/check_content_coverage_advisory_integration.py +796 -0
  376. package/vendor/ars/scripts/check_control_availability.py +172 -0
  377. package/vendor/ars/scripts/check_corpus_consumer_protocol.py +404 -0
  378. package/vendor/ars/scripts/check_cross_document_consistency_advisory_integration.py +1191 -0
  379. package/vendor/ars/scripts/check_cross_model_handoff_contract.py +234 -0
  380. package/vendor/ars/scripts/check_cross_model_verification_sync.py +261 -0
  381. package/vendor/ars/scripts/check_data_access_level.py +131 -0
  382. package/vendor/ars/scripts/check_data_flows.py +252 -0
  383. package/vendor/ars/scripts/check_decision_contract.py +464 -0
  384. package/vendor/ars/scripts/check_degradation_registry.py +326 -0
  385. package/vendor/ars/scripts/check_distribution_surface_claims.py +226 -0
  386. package/vendor/ars/scripts/check_domain_evidence_profile.py +538 -0
  387. package/vendor/ars/scripts/check_e4_promotion.py +195 -0
  388. package/vendor/ars/scripts/check_evals_gold_set.py +279 -0
  389. package/vendor/ars/scripts/check_evidence_row_integration.py +396 -0
  390. package/vendor/ars/scripts/check_experiment_provenance.py +117 -0
  391. package/vendor/ars/scripts/check_field_norm_severity.py +144 -0
  392. package/vendor/ars/scripts/check_firm_rules_sync.py +375 -0
  393. package/vendor/ars/scripts/check_heldout_measurement_report.py +1179 -0
  394. package/vendor/ars/scripts/check_human_subjects_output_contract.py +139 -0
  395. package/vendor/ars/scripts/check_human_subjects_reference_migration.py +844 -0
  396. package/vendor/ars/scripts/check_indirect_prompt_injection_no_call.py +328 -0
  397. package/vendor/ars/scripts/check_instruction_data_boundary.py +236 -0
  398. package/vendor/ars/scripts/check_judge_prompt_version.py +125 -0
  399. package/vendor/ars/scripts/check_literature_corpus_schema.py +402 -0
  400. package/vendor/ars/scripts/check_model_tiering.py +223 -0
  401. package/vendor/ars/scripts/check_panel_synthesis.py +1331 -0
  402. package/vendor/ars/scripts/check_passport_reset_contract.py +214 -0
  403. package/vendor/ars/scripts/check_pattern_eval_manifest.py +422 -0
  404. package/vendor/ars/scripts/check_persuasion_invariance_fixtures.py +637 -0
  405. package/vendor/ars/scripts/check_phase_conformance.py +2180 -0
  406. package/vendor/ars/scripts/check_pipeline_boundary_semantics.py +600 -0
  407. package/vendor/ars/scripts/check_pipeline_integrity.py +340 -0
  408. package/vendor/ars/scripts/check_policy_anchor_protocol.py +159 -0
  409. package/vendor/ars/scripts/check_policy_anchor_table.py +286 -0
  410. package/vendor/ars/scripts/check_preprint_venues_consistency.py +123 -0
  411. package/vendor/ars/scripts/check_prisma_trAIce_freshness.py +84 -0
  412. package/vendor/ars/scripts/check_promotion_bakeoff_preregistration.py +1303 -0
  413. package/vendor/ars/scripts/check_ranking_lift.py +323 -0
  414. package/vendor/ars/scripts/check_re_review_synthesis.py +2719 -0
  415. package/vendor/ars/scripts/check_receipt_enum_sync.py +203 -0
  416. package/vendor/ars/scripts/check_repro_lock.py +85 -0
  417. package/vendor/ars/scripts/check_review_pathway_output.py +276 -0
  418. package/vendor/ars/scripts/check_reviewer_data_fences.py +224 -0
  419. package/vendor/ars/scripts/check_reviewer_finding_contract.py +783 -0
  420. package/vendor/ars/scripts/check_reviewer_role_label.py +447 -0
  421. package/vendor/ars/scripts/check_reviewer_scoring_honesty.py +292 -0
  422. package/vendor/ars/scripts/check_reviewer_sprint_prompt_sync.py +409 -0
  423. package/vendor/ars/scripts/check_revision_claim_drift_suite_v2.py +1622 -0
  424. package/vendor/ars/scripts/check_revision_token_conservation.py +263 -0
  425. package/vendor/ars/scripts/check_risk_register.py +280 -0
  426. package/vendor/ars/scripts/check_role_scoped_contract.py +702 -0
  427. package/vendor/ars/scripts/check_rq_framing_patterns.py +184 -0
  428. package/vendor/ars/scripts/check_rubric_weight_consistency.py +32 -0
  429. package/vendor/ars/scripts/check_seeded_defect_fixtures.py +425 -0
  430. package/vendor/ars/scripts/check_setup_cross_model_parity.py +139 -0
  431. package/vendor/ars/scripts/check_spec_consistency.py +1249 -0
  432. package/vendor/ars/scripts/check_sprint_contract.py +371 -0
  433. package/vendor/ars/scripts/check_stage_capability_matrix.py +784 -0
  434. package/vendor/ars/scripts/check_submission_packet_manifest_integration.py +358 -0
  435. package/vendor/ars/scripts/check_surface_form_parity.py +445 -0
  436. package/vendor/ars/scripts/check_task_type.py +22 -0
  437. package/vendor/ars/scripts/check_tools_allowlist.py +538 -0
  438. package/vendor/ars/scripts/check_tortured_phrase_screening_integration.py +1974 -0
  439. package/vendor/ars/scripts/check_v3_10_134_write_scope.py +286 -0
  440. package/vendor/ars/scripts/check_v3_10_policy.py +656 -0
  441. package/vendor/ars/scripts/check_v3_6_6_ab_manifest.py +364 -0
  442. package/vendor/ars/scripts/check_v3_6_7_pattern_protection.py +1366 -0
  443. package/vendor/ars/scripts/check_v3_6_8_audit_scope_block.py +464 -0
  444. package/vendor/ars/scripts/check_v3_6_8_cite_provenance_pipeline.py +231 -0
  445. package/vendor/ars/scripts/check_v3_6_8_frontmatter_trust_schema.py +248 -0
  446. package/vendor/ars/scripts/check_v3_6_8_mark_read_commands.py +79 -0
  447. package/vendor/ars/scripts/check_v3_6_8_pattern_protection.py +941 -0
  448. package/vendor/ars/scripts/check_v3_7_3_three_layer_citation.py +318 -0
  449. package/vendor/ars/scripts/check_v3_8_annotation_literal_sync.py +228 -0
  450. package/vendor/ars/scripts/check_v3_9_0_triangulation.py +366 -0
  451. package/vendor/ars/scripts/check_v3_9_2_phase_boundary.py +270 -0
  452. package/vendor/ars/scripts/check_v3_9_4_temporal_verification.py +139 -0
  453. package/vendor/ars/scripts/check_venue_disclosure_policies.py +63 -0
  454. package/vendor/ars/scripts/check_version_consistency.py +826 -0
  455. package/vendor/ars/scripts/check_workflow_classification.py +223 -0
  456. package/vendor/ars/scripts/chinese_literature_client.py +1938 -0
  457. package/vendor/ars/scripts/citation_verification_summary.py +85 -0
  458. package/vendor/ars/scripts/claim_audit_calibration.py +517 -0
  459. package/vendor/ars/scripts/claim_audit_finalizer.py +456 -0
  460. package/vendor/ars/scripts/claim_audit_pipeline.py +1594 -0
  461. package/vendor/ars/scripts/claim_registry_coverage.py +493 -0
  462. package/vendor/ars/scripts/claim_standing_discovery.py +784 -0
  463. package/vendor/ars/scripts/claim_standing_stance_runner.py +758 -0
  464. package/vendor/ars/scripts/claim_standing_stance_scorer.py +239 -0
  465. package/vendor/ars/scripts/claim_strength_drift_disposition.py +666 -0
  466. package/vendor/ars/scripts/contamination_signals.py +689 -0
  467. package/vendor/ars/scripts/corpus_consumer_manifest.json +19 -0
  468. package/vendor/ars/scripts/cross_model_codex_transport.py +1374 -0
  469. package/vendor/ars/scripts/cross_model_codex_verify.sh +6 -0
  470. package/vendor/ars/scripts/cross_model_handoff.py +359 -0
  471. package/vendor/ars/scripts/cross_model_smoke_test.sh +183 -0
  472. package/vendor/ars/scripts/cross_model_smoke_test_codex.sh +35 -0
  473. package/vendor/ars/scripts/cross_model_verification/gemini_is_grounded.jq +45 -0
  474. package/vendor/ars/scripts/cross_model_verification/gemini_sources.jq +45 -0
  475. package/vendor/ars/scripts/cross_model_verification/normalize_compat_verdict.py +57 -0
  476. package/vendor/ars/scripts/cross_model_verification/openai_has_completed_web_search.jq +15 -0
  477. package/vendor/ars/scripts/cross_model_verification/openai_sources.jq +17 -0
  478. package/vendor/ars/scripts/cross_model_verification/openai_text.jq +11 -0
  479. package/vendor/ars/scripts/crossref_client.py +225 -0
  480. package/vendor/ars/scripts/dispatch_e4_panel.py +2731 -0
  481. package/vendor/ars/scripts/evidence_rows.py +2043 -0
  482. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3.json +32 -0
  483. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_input.json +27 -0
  484. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime.json +32 -0
  485. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime_input.json +27 -0
  486. package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override.json +50 -0
  487. package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override_action.json +14 -0
  488. package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_pass.json +44 -0
  489. package/vendor/ars/scripts/fixtures/adjudication_activity/explicit_user_request_log.json +26 -0
  490. package/vendor/ars/scripts/fixtures/adjudication_activity/mandatory_checkpoint_log.json +116 -0
  491. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/README.md +56 -0
  492. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +24 -0
  493. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
  494. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
  495. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
  496. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a7_orphan_completion/2026-04-30T15-22-04Z-d8f3.jsonl +3 -0
  497. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +26 -0
  498. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
  499. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
  500. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
  501. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +24 -0
  502. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
  503. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
  504. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.verdict.yaml +12 -0
  505. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction.json +46 -0
  506. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction_check_attestation.json +44 -0
  507. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase.json +44 -0
  508. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_abstract_missing.json +107 -0
  509. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_detected.json +124 -0
  510. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/expected_outcomes.json +77 -0
  511. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/manifest.yaml +37 -0
  512. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/001-valid-doi-test.json +20 -0
  513. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/002-valid-arxiv-test.json +19 -0
  514. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/003-fabricated-test.json +20 -0
  515. package/vendor/ars/scripts/fixtures/claim_audit_calibration/gold_set.json +344 -0
  516. package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/query_plan.json +108 -0
  517. package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/retrieval_input.json +179 -0
  518. package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/concern_tracker.json +129 -0
  519. package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/response_skeleton.md +19 -0
  520. package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/source_letter.txt +9 -0
  521. package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_draft.json +48 -0
  522. package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_inventory.json +38 -0
  523. package/vendor/ars/scripts/fixtures/content_coverage_advisory/packet/consent.txt +1 -0
  524. package/vendor/ars/scripts/fixtures/content_coverage_advisory/session_sources.json +3 -0
  525. package/vendor/ars/scripts/fixtures/cross_document_consistency/README.md +6 -0
  526. package/vendor/ars/scripts/fixtures/cross_document_consistency/accepted_draft.md +35 -0
  527. package/vendor/ars/scripts/fixtures/cross_document_consistency/cases.json +75 -0
  528. package/vendor/ars/scripts/fixtures/cross_document_consistency/preregistration.md +5 -0
  529. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/forbidden_event.jsonl +4 -0
  530. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/grounded_verified.jsonl +3 -0
  531. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/malformed.jsonl +3 -0
  532. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/missing_search.jsonl +2 -0
  533. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/multiple_finals.jsonl +4 -0
  534. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/not_found.jsonl +3 -0
  535. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/unbound_source.jsonl +3 -0
  536. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/wrong_search_shape.jsonl +3 -0
  537. package/vendor/ars/scripts/fixtures/evidence_rows/phase_e_inputs.json +114 -0
  538. package/vendor/ars/scripts/fixtures/evidence_rows/session_sources.json +5 -0
  539. package/vendor/ars/scripts/fixtures/human_subjects_authority/cross-border-us-tw-gdpr.json +71 -0
  540. package/vendor/ars/scripts/fixtures/human_subjects_authority/gdpr-member-state-unresolved.json +62 -0
  541. package/vendor/ars/scripts/fixtures/human_subjects_authority/missing-data-axis.json +45 -0
  542. package/vendor/ars/scripts/fixtures/human_subjects_authority/no-profile.json +33 -0
  543. package/vendor/ars/scripts/fixtures/human_subjects_authority/tw-gdpr-two-axis.json +74 -0
  544. package/vendor/ars/scripts/fixtures/human_subjects_authority/us-gdpr-two-axis.json +74 -0
  545. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/lint-near-misses.json +72 -0
  546. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/no-profile-request.json +18 -0
  547. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/tw-candidates-request.json +63 -0
  548. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/us-candidates-request.json +77 -0
  549. package/vendor/ars/scripts/fixtures/review_target_context/exact-declaration.json +24 -0
  550. package/vendor/ars/scripts/fixtures/review_target_context/field-general-declaration.json +24 -0
  551. package/vendor/ars/scripts/fixtures/review_target_context/msr-2027-technical-full-declaration.json +24 -0
  552. package/vendor/ars/scripts/fixtures/review_target_context/synthetic-registry.json +92 -0
  553. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/README.md +21 -0
  554. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_attested_only.json +55 -0
  555. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_machine_supported.json +49 -0
  556. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_not_isolated.json +56 -0
  557. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_unknown.json +49 -0
  558. package/vendor/ars/scripts/fixtures/submission_package/clean/paper.md +16 -0
  559. package/vendor/ars/scripts/fixtures/submission_package/clean/references.bib +13 -0
  560. package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/paper.md +11 -0
  561. package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/references.bib +13 -0
  562. package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/paper.tex +9 -0
  563. package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/references.bib +13 -0
  564. package/vendor/ars/scripts/fixtures/submission_package/marker_no_join/paper.md +7 -0
  565. package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/paper.md +8 -0
  566. package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/references.bib +6 -0
  567. package/vendor/ars/scripts/fixtures/submission_package/passports/corpus_only.yaml +9 -0
  568. package/vendor/ars/scripts/fixtures/submission_package/passports/summary_join.yaml +16 -0
  569. package/vendor/ars/scripts/fixtures/submission_package/profiles/full.yaml +15 -0
  570. package/vendor/ars/scripts/fixtures/submission_package/profiles/tight.yaml +15 -0
  571. package/vendor/ars/scripts/fixtures/submission_package/summary_join/paper.md +7 -0
  572. package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/paper.md +9 -0
  573. package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/references.bib +13 -0
  574. package/vendor/ars/scripts/fixtures/submission_package/venue_clean/paper.md +26 -0
  575. package/vendor/ars/scripts/fixtures/submission_package/venue_clean/references.bib +13 -0
  576. package/vendor/ars/scripts/fixtures/submission_package/venue_violations/paper.md +20 -0
  577. package/vendor/ars/scripts/fixtures/submission_package/venue_violations/references.bib +13 -0
  578. package/vendor/ars/scripts/fixtures/submission_packet_manifest/base_inventory.json +101 -0
  579. package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/consent-materials.txt +3 -0
  580. package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/training-certificate.txt +2 -0
  581. package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/tw-consent-materials.txt +3 -0
  582. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/corpus_input.yaml +30 -0
  583. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.md +36 -0
  584. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.tex +21 -0
  585. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/seed_expectations.json +218 -0
  586. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot.json +164 -0
  587. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot_manifest.json +29 -0
  588. package/vendor/ars/scripts/fixtures/transport_bodies/README.md +55 -0
  589. package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/empty_feed.xml +10 -0
  590. package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/error_5xx.html +7 -0
  591. package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/id_hit.xml +26 -0
  592. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/README.md +69 -0
  593. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/cnki_landing_page.html +13 -0
  594. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/error_5xx.html +2 -0
  595. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_ambiguous.json +16 -0
  596. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_hit.json +16 -0
  597. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_zero.json +14 -0
  598. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_hit.json +15 -0
  599. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_zero.json +13 -0
  600. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_hit.json +43 -0
  601. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_issn_mismatch.json +31 -0
  602. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_no_doi.json +31 -0
  603. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_unknown_ra.json +34 -0
  604. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_year_mismatch.json +31 -0
  605. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_absent.json +4 -0
  606. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_exists.json +16 -0
  607. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_internal_error.json +4 -0
  608. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_hit.json +25 -0
  609. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_other_title.json +18 -0
  610. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_cnki.json +6 -0
  611. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_crossref.json +6 -0
  612. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_istic.json +6 -0
  613. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_unknown_prefix.json +6 -0
  614. package/vendor/ars/scripts/fixtures/transport_bodies/crossref/doi_hit.json +24 -0
  615. package/vendor/ars/scripts/fixtures/transport_bodies/crossref/error_5xx.html +7 -0
  616. package/vendor/ars/scripts/fixtures/transport_bodies/crossref/title_search_miss.json +15 -0
  617. package/vendor/ars/scripts/fixtures/transport_bodies/openalex/doi_hit.json +29 -0
  618. package/vendor/ars/scripts/fixtures/transport_bodies/openalex/error_5xx.json +4 -0
  619. package/vendor/ars/scripts/fixtures/transport_bodies/openalex/title_search_miss.json +11 -0
  620. package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/doi_hit.json +14 -0
  621. package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/error_5xx.json +3 -0
  622. package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/title_search_miss.json +5 -0
  623. package/vendor/ars/scripts/human_read_attestation_resolver.py +467 -0
  624. package/vendor/ars/scripts/ideation_diversity_assignment_gate.py +430 -0
  625. package/vendor/ars/scripts/inquiry_branch_ledger.py +2540 -0
  626. package/vendor/ars/scripts/legacy/ars_apply_revision_patch_v1_0.py +715 -0
  627. package/vendor/ars/scripts/legacy/check_re_review_synthesis_v1_0.py +2215 -0
  628. package/vendor/ars/scripts/migrate_literature_corpus_to_v3_10.py +223 -0
  629. package/vendor/ars/scripts/migrate_literature_corpus_to_v3_7_3.py +277 -0
  630. package/vendor/ars/scripts/migrate_literature_corpus_to_v3_9_0.py +304 -0
  631. package/vendor/ars/scripts/model_tiering_manifest.json +46 -0
  632. package/vendor/ars/scripts/openalex_client.py +232 -0
  633. package/vendor/ars/scripts/parse_audit_verdict.py +802 -0
  634. package/vendor/ars/scripts/pdf_content_classifier_worker.py +176 -0
  635. package/vendor/ars/scripts/pdf_read_preflight.py +1455 -0
  636. package/vendor/ars/scripts/policy_anchor_disclosure_referee.py +354 -0
  637. package/vendor/ars/scripts/recompute_receipts.py +1414 -0
  638. package/vendor/ars/scripts/render_claim_standing_view.py +374 -0
  639. package/vendor/ars/scripts/render_eval_comment.py +130 -0
  640. package/vendor/ars/scripts/render_harness_retirement_issue.py +167 -0
  641. package/vendor/ars/scripts/repro_lock_validation.py +90 -0
  642. package/vendor/ars/scripts/research_workflow_profile.py +1079 -0
  643. package/vendor/ars/scripts/resolve_human_subjects_authority.py +1158 -0
  644. package/vendor/ars/scripts/resolve_review_target_context.py +683 -0
  645. package/vendor/ars/scripts/resume_e4_record.py +509 -0
  646. package/vendor/ars/scripts/retraction_status.py +484 -0
  647. package/vendor/ars/scripts/review_criteria_binding.py +889 -0
  648. package/vendor/ars/scripts/review_panel_provenance.py +744 -0
  649. package/vendor/ars/scripts/revision_roadmap.py +1967 -0
  650. package/vendor/ars/scripts/run_ci_pytest_manifest.py +116 -0
  651. package/vendor/ars/scripts/run_codex_audit.sh +1191 -0
  652. package/vendor/ars/scripts/run_evals.py +513 -0
  653. package/vendor/ars/scripts/run_ideation_diversity_no_call.py +3505 -0
  654. package/vendor/ars/scripts/run_indirect_prompt_injection_no_call.py +3330 -0
  655. package/vendor/ars/scripts/run_indirect_prompt_injection_probe.py +399 -0
  656. package/vendor/ars/scripts/run_review_criteria_constructive_value.py +1895 -0
  657. package/vendor/ars/scripts/run_role_topology_utility_dry_run.py +606 -0
  658. package/vendor/ars/scripts/score_review_criteria_constructive_value.py +617 -0
  659. package/vendor/ars/scripts/semantic_scholar_client.py +291 -0
  660. package/vendor/ars/scripts/slr_lineage.py +59 -0
  661. package/vendor/ars/scripts/sync_adapter_docs.py +118 -0
  662. package/vendor/ars/scripts/temporal_integrity_audit.py +840 -0
  663. package/vendor/ars/scripts/test_431_exact_or_bust.py +253 -0
  664. package/vendor/ars/scripts/test__eval_threshold_gate.py +125 -0
  665. package/vendor/ars/scripts/test__markdown_lint_util.py +129 -0
  666. package/vendor/ars/scripts/test__next_verified_at_ms.py +256 -0
  667. package/vendor/ars/scripts/test_adjacent_framing_probe_lint.py +171 -0
  668. package/vendor/ars/scripts/test_adjudication_activity.py +1515 -0
  669. package/vendor/ars/scripts/test_ars_anchorize_draft.py +178 -0
  670. package/vendor/ars/scripts/test_ars_apply_revision_patch.py +1335 -0
  671. package/vendor/ars/scripts/test_ars_cache_invalidate.py +51 -0
  672. package/vendor/ars/scripts/test_ars_mark_read.py +910 -0
  673. package/vendor/ars/scripts/test_ars_update_check.py +816 -0
  674. package/vendor/ars/scripts/test_ars_write_scope_guard.py +790 -0
  675. package/vendor/ars/scripts/test_arxiv_client.py +374 -0
  676. package/vendor/ars/scripts/test_audit_schemas.py +560 -0
  677. package/vendor/ars/scripts/test_audit_snapshot_render_section_0.py +105 -0
  678. package/vendor/ars/scripts/test_block_parser.py +259 -0
  679. package/vendor/ars/scripts/test_bootstrap_timeline_yaml.py +148 -0
  680. package/vendor/ars/scripts/test_build_claim_standing_candidate_ledger.py +1203 -0
  681. package/vendor/ars/scripts/test_build_claim_standing_query_plan.py +791 -0
  682. package/vendor/ars/scripts/test_build_submission_packet_manifest.py +2644 -0
  683. package/vendor/ars/scripts/test_check_215_field_norm.py +238 -0
  684. package/vendor/ars/scripts/test_check_216_surface_form.py +341 -0
  685. package/vendor/ars/scripts/test_check_268_nested_commitment_ledger.py +194 -0
  686. package/vendor/ars/scripts/test_check_390_revision_patch_discipline.py +279 -0
  687. package/vendor/ars/scripts/test_check_392_citation_verification_intake.py +144 -0
  688. package/vendor/ars/scripts/test_check_394_submission_policy.py +200 -0
  689. package/vendor/ars/scripts/test_check_439_format_profile.py +252 -0
  690. package/vendor/ars/scripts/test_check_619_disclosure_closeout.py +158 -0
  691. package/vendor/ars/scripts/test_check_630_codex_subscription_transport.py +245 -0
  692. package/vendor/ars/scripts/test_check_669_review_pathway_rule_trace.py +304 -0
  693. package/vendor/ars/scripts/test_check_670_revision_roadmap_integration.py +260 -0
  694. package/vendor/ars/scripts/test_check_673_adjudication_activity.py +367 -0
  695. package/vendor/ars/scripts/test_check_684_review_criteria_binding.py +274 -0
  696. package/vendor/ars/scripts/test_check_agents_mirror_sync.py +137 -0
  697. package/vendor/ars/scripts/test_check_audit_artifact_consistency.py +2133 -0
  698. package/vendor/ars/scripts/test_check_benchmark_report.py +117 -0
  699. package/vendor/ars/scripts/test_check_bibliographic_integrity_signals.py +609 -0
  700. package/vendor/ars/scripts/test_check_calibration_tiers.py +376 -0
  701. package/vendor/ars/scripts/test_check_changelog_covers_merges.py +509 -0
  702. package/vendor/ars/scripts/test_check_ci_pytest_manifest.py +425 -0
  703. package/vendor/ars/scripts/test_check_claim_standing_candidate_ledger_integration.py +256 -0
  704. package/vendor/ars/scripts/test_check_claim_standing_freshness.py +277 -0
  705. package/vendor/ars/scripts/test_check_collaboration_depth_rubric.py +239 -0
  706. package/vendor/ars/scripts/test_check_command_frontmatter_name.py +178 -0
  707. package/vendor/ars/scripts/test_check_committee_correspondence.py +298 -0
  708. package/vendor/ars/scripts/test_check_compliance_report.py +381 -0
  709. package/vendor/ars/scripts/test_check_content_coverage_advisory_integration.py +308 -0
  710. package/vendor/ars/scripts/test_check_control_availability.py +361 -0
  711. package/vendor/ars/scripts/test_check_cross_document_consistency_advisory_integration.py +698 -0
  712. package/vendor/ars/scripts/test_check_cross_model_handoff_contract.py +301 -0
  713. package/vendor/ars/scripts/test_check_cross_model_verification_sync.py +203 -0
  714. package/vendor/ars/scripts/test_check_data_access_level.py +227 -0
  715. package/vendor/ars/scripts/test_check_data_flows.py +382 -0
  716. package/vendor/ars/scripts/test_check_decision_contract.py +387 -0
  717. package/vendor/ars/scripts/test_check_degradation_registry.py +270 -0
  718. package/vendor/ars/scripts/test_check_distribution_surface_claims.py +225 -0
  719. package/vendor/ars/scripts/test_check_domain_evidence_profile.py +439 -0
  720. package/vendor/ars/scripts/test_check_e4_promotion.py +163 -0
  721. package/vendor/ars/scripts/test_check_evals_gold_set.py +312 -0
  722. package/vendor/ars/scripts/test_check_evidence_row_integration.py +187 -0
  723. package/vendor/ars/scripts/test_check_field_norm_severity.py +173 -0
  724. package/vendor/ars/scripts/test_check_firm_rules_sync.py +342 -0
  725. package/vendor/ars/scripts/test_check_heldout_measurement_report.py +1508 -0
  726. package/vendor/ars/scripts/test_check_human_subjects_output_contract.py +129 -0
  727. package/vendor/ars/scripts/test_check_human_subjects_reference_migration.py +756 -0
  728. package/vendor/ars/scripts/test_check_instruction_data_boundary.py +204 -0
  729. package/vendor/ars/scripts/test_check_judge_prompt_version.py +90 -0
  730. package/vendor/ars/scripts/test_check_model_tiering.py +236 -0
  731. package/vendor/ars/scripts/test_check_panel_synthesis.py +1658 -0
  732. package/vendor/ars/scripts/test_check_passport_reset_contract.py +249 -0
  733. package/vendor/ars/scripts/test_check_pattern_eval_manifest.py +381 -0
  734. package/vendor/ars/scripts/test_check_persuasion_invariance_fixtures.py +591 -0
  735. package/vendor/ars/scripts/test_check_phase_conformance.py +4366 -0
  736. package/vendor/ars/scripts/test_check_pipeline_boundary_semantics.py +931 -0
  737. package/vendor/ars/scripts/test_check_pipeline_integrity.py +243 -0
  738. package/vendor/ars/scripts/test_check_policy_anchor_protocol.py +236 -0
  739. package/vendor/ars/scripts/test_check_policy_anchor_table.py +295 -0
  740. package/vendor/ars/scripts/test_check_prisma_trAIce_freshness.py +68 -0
  741. package/vendor/ars/scripts/test_check_promotion_bakeoff_preregistration.py +799 -0
  742. package/vendor/ars/scripts/test_check_ranking_lift.py +377 -0
  743. package/vendor/ars/scripts/test_check_re_review_synthesis.py +3398 -0
  744. package/vendor/ars/scripts/test_check_receipt_enum_sync.py +176 -0
  745. package/vendor/ars/scripts/test_check_repro_lock.py +107 -0
  746. package/vendor/ars/scripts/test_check_reviewer_data_fences.py +334 -0
  747. package/vendor/ars/scripts/test_check_reviewer_finding_contract.py +1107 -0
  748. package/vendor/ars/scripts/test_check_reviewer_role_label.py +491 -0
  749. package/vendor/ars/scripts/test_check_reviewer_scoring_honesty.py +222 -0
  750. package/vendor/ars/scripts/test_check_reviewer_sprint_prompt_sync.py +373 -0
  751. package/vendor/ars/scripts/test_check_revision_claim_drift_suite_v2.py +1925 -0
  752. package/vendor/ars/scripts/test_check_revision_token_conservation.py +398 -0
  753. package/vendor/ars/scripts/test_check_risk_register.py +336 -0
  754. package/vendor/ars/scripts/test_check_role_scoped_contract.py +1104 -0
  755. package/vendor/ars/scripts/test_check_rq_framing_patterns.py +110 -0
  756. package/vendor/ars/scripts/test_check_rubric_weight_consistency.py +32 -0
  757. package/vendor/ars/scripts/test_check_seeded_defect_fixtures.py +392 -0
  758. package/vendor/ars/scripts/test_check_setup_cross_model_parity.py +121 -0
  759. package/vendor/ars/scripts/test_check_spec_consistency.py +1081 -0
  760. package/vendor/ars/scripts/test_check_sprint_contract.py +458 -0
  761. package/vendor/ars/scripts/test_check_stage_capability_matrix.py +698 -0
  762. package/vendor/ars/scripts/test_check_submission_packet_manifest_integration.py +177 -0
  763. package/vendor/ars/scripts/test_check_surface_form_parity.py +417 -0
  764. package/vendor/ars/scripts/test_check_task_type.py +116 -0
  765. package/vendor/ars/scripts/test_check_tools_allowlist.py +835 -0
  766. package/vendor/ars/scripts/test_check_tortured_phrase_screening_integration.py +1306 -0
  767. package/vendor/ars/scripts/test_check_v3_10_134_write_scope.py +251 -0
  768. package/vendor/ars/scripts/test_check_v3_10_policy.py +547 -0
  769. package/vendor/ars/scripts/test_check_v3_6_7_pattern_protection.py +960 -0
  770. package/vendor/ars/scripts/test_check_v3_6_8_audit_scope_block.py +1000 -0
  771. package/vendor/ars/scripts/test_check_v3_6_8_cite_provenance_pipeline.py +454 -0
  772. package/vendor/ars/scripts/test_check_v3_6_8_frontmatter_trust_schema.py +581 -0
  773. package/vendor/ars/scripts/test_check_v3_6_8_mark_read_commands.py +120 -0
  774. package/vendor/ars/scripts/test_check_v3_6_8_pattern_protection.py +1138 -0
  775. package/vendor/ars/scripts/test_check_v3_7_3_three_layer_citation.py +566 -0
  776. package/vendor/ars/scripts/test_check_v3_8_annotation_literal_sync.py +262 -0
  777. package/vendor/ars/scripts/test_check_v3_9_0_triangulation.py +321 -0
  778. package/vendor/ars/scripts/test_check_v3_9_2_phase_boundary.py +190 -0
  779. package/vendor/ars/scripts/test_check_v3_9_4_temporal_verification.py +471 -0
  780. package/vendor/ars/scripts/test_check_version_consistency.py +1462 -0
  781. package/vendor/ars/scripts/test_check_workflow_classification.py +225 -0
  782. package/vendor/ars/scripts/test_chinese_literature_client.py +1889 -0
  783. package/vendor/ars/scripts/test_citation_existence_policy.py +480 -0
  784. package/vendor/ars/scripts/test_citation_verification_summary.py +342 -0
  785. package/vendor/ars/scripts/test_claim_audit_calibration.py +882 -0
  786. package/vendor/ars/scripts/test_claim_audit_finalizer.py +979 -0
  787. package/vendor/ars/scripts/test_claim_audit_pipeline.py +2398 -0
  788. package/vendor/ars/scripts/test_claim_audit_schema.py +1778 -0
  789. package/vendor/ars/scripts/test_claim_intent_manifest.py +666 -0
  790. package/vendor/ars/scripts/test_claim_registry_coverage.py +246 -0
  791. package/vendor/ars/scripts/test_claim_standing_discovery.py +579 -0
  792. package/vendor/ars/scripts/test_claim_standing_pipeline_wiring.py +290 -0
  793. package/vendor/ars/scripts/test_claim_standing_stance_assets.py +399 -0
  794. package/vendor/ars/scripts/test_claim_standing_stance_contracts.py +383 -0
  795. package/vendor/ars/scripts/test_claim_standing_stance_runner.py +349 -0
  796. package/vendor/ars/scripts/test_claim_standing_transmissions.py +605 -0
  797. package/vendor/ars/scripts/test_claim_strength_drift_disposition.py +517 -0
  798. package/vendor/ars/scripts/test_claim_verification_coverage_contract.py +104 -0
  799. package/vendor/ars/scripts/test_contamination_signals.py +1086 -0
  800. package/vendor/ars/scripts/test_content_coverage_advisory.py +1767 -0
  801. package/vendor/ars/scripts/test_cross_document_consistency_advisory.py +1270 -0
  802. package/vendor/ars/scripts/test_cross_model_codex_transport.py +1152 -0
  803. package/vendor/ars/scripts/test_cross_model_handoff.py +571 -0
  804. package/vendor/ars/scripts/test_cross_model_verification_guards.py +792 -0
  805. package/vendor/ars/scripts/test_crossref_client.py +393 -0
  806. package/vendor/ars/scripts/test_dispatch_e4_panel.py +4235 -0
  807. package/vendor/ars/scripts/test_e2e_claim_audit.py +540 -0
  808. package/vendor/ars/scripts/test_eval_harness_workflow.py +140 -0
  809. package/vendor/ars/scripts/test_evals_citation_extraction.py +150 -0
  810. package/vendor/ars/scripts/test_evals_lift_report_schema.py +108 -0
  811. package/vendor/ars/scripts/test_evidence_rows.py +2593 -0
  812. package/vendor/ars/scripts/test_experiment_provenance.py +915 -0
  813. package/vendor/ars/scripts/test_human_read_attestation_resolver.py +477 -0
  814. package/vendor/ars/scripts/test_ideation_diversity_assignment_gate.py +615 -0
  815. package/vendor/ars/scripts/test_indirect_prompt_injection_behavior_probe.py +250 -0
  816. package/vendor/ars/scripts/test_inquiry_branch_ledger.py +2296 -0
  817. package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_10.py +248 -0
  818. package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_7_3.py +545 -0
  819. package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_9_0.py +497 -0
  820. package/vendor/ars/scripts/test_normalize_compat_verdict.py +149 -0
  821. package/vendor/ars/scripts/test_openalex_client.py +490 -0
  822. package/vendor/ars/scripts/test_passport_yaml.py +104 -0
  823. package/vendor/ars/scripts/test_pattern_eval_runtime.py +1295 -0
  824. package/vendor/ars/scripts/test_pdf_read_preflight.py +1943 -0
  825. package/vendor/ars/scripts/test_policy_anchor_disclosure.py +666 -0
  826. package/vendor/ars/scripts/test_reading_probe_lint.py +218 -0
  827. package/vendor/ars/scripts/test_recompute_receipts.py +778 -0
  828. package/vendor/ars/scripts/test_render_claim_standing_view.py +192 -0
  829. package/vendor/ars/scripts/test_render_eval_comment.py +162 -0
  830. package/vendor/ars/scripts/test_render_harness_retirement_issue.py +110 -0
  831. package/vendor/ars/scripts/test_repro_lock_validation_drift.py +100 -0
  832. package/vendor/ars/scripts/test_research_workflow_profile.py +734 -0
  833. package/vendor/ars/scripts/test_resolve_human_subjects_authority.py +1219 -0
  834. package/vendor/ars/scripts/test_resolve_review_target_context.py +703 -0
  835. package/vendor/ars/scripts/test_resume_e4_record.py +315 -0
  836. package/vendor/ars/scripts/test_retraction_status.py +456 -0
  837. package/vendor/ars/scripts/test_review_criteria_binding.py +629 -0
  838. package/vendor/ars/scripts/test_review_panel_provenance.py +565 -0
  839. package/vendor/ars/scripts/test_review_pathway_rule_trace.py +821 -0
  840. package/vendor/ars/scripts/test_revision_roadmap.py +1255 -0
  841. package/vendor/ars/scripts/test_run_ci_pytest_manifest.py +186 -0
  842. package/vendor/ars/scripts/test_run_codex_audit_e2e.py +369 -0
  843. package/vendor/ars/scripts/test_run_evals.py +430 -0
  844. package/vendor/ars/scripts/test_run_guard_launcher.py +500 -0
  845. package/vendor/ars/scripts/test_run_ideation_diversity_no_call.py +1833 -0
  846. package/vendor/ars/scripts/test_run_indirect_prompt_injection_no_call.py +1889 -0
  847. package/vendor/ars/scripts/test_run_review_criteria_constructive_value.py +586 -0
  848. package/vendor/ars/scripts/test_run_role_topology_utility_dry_run.py +428 -0
  849. package/vendor/ars/scripts/test_score_review_criteria_constructive_value.py +340 -0
  850. package/vendor/ars/scripts/test_semantic_scholar_client.py +554 -0
  851. package/vendor/ars/scripts/test_slr_lineage_emission.py +230 -0
  852. package/vendor/ars/scripts/test_socratic_rq_non_generation_contract.py +173 -0
  853. package/vendor/ars/scripts/test_temporal_integrity_audit.py +438 -0
  854. package/vendor/ars/scripts/test_text_similarity.py +95 -0
  855. package/vendor/ars/scripts/test_title_fuzzy_false_positive.py +111 -0
  856. package/vendor/ars/scripts/test_tortured_phrase_screening.py +2959 -0
  857. package/vendor/ars/scripts/test_transport_fixture_citation_gate.py +338 -0
  858. package/vendor/ars/scripts/test_uncited_assertion.py +558 -0
  859. package/vendor/ars/scripts/test_v3_6_7_phase_6_6.py +1279 -0
  860. package/vendor/ars/scripts/test_validate_compliance_fixtures.py +36 -0
  861. package/vendor/ars/scripts/test_validate_ideation_diversity_assets.py +230 -0
  862. package/vendor/ars/scripts/test_venue_disclosure_contract.py +755 -0
  863. package/vendor/ars/scripts/test_verification_cache.py +280 -0
  864. package/vendor/ars/scripts/test_verification_gate.py +461 -0
  865. package/vendor/ars/scripts/test_verify_passport_cli.py +123 -0
  866. package/vendor/ars/scripts/test_verify_submission_package.py +1407 -0
  867. package/vendor/ars/scripts/test_version_records_schema.py +211 -0
  868. package/vendor/ars/scripts/tortured_phrase_screening.py +3502 -0
  869. package/vendor/ars/scripts/uncited_assertion_detector.py +254 -0
  870. package/vendor/ars/scripts/v3_6_7_inversion_manifest.json +9 -0
  871. package/vendor/ars/scripts/v3_6_8_inversion_manifest.json +10 -0
  872. package/vendor/ars/scripts/validate_claim_standing_stance_assets.py +249 -0
  873. package/vendor/ars/scripts/validate_compliance_fixtures.py +56 -0
  874. package/vendor/ars/scripts/validate_ideation_diversity_assets.py +303 -0
  875. package/vendor/ars/scripts/venue_disclosure_contract_harness.py +837 -0
  876. package/vendor/ars/scripts/verification_cache.py +276 -0
  877. package/vendor/ars/scripts/verification_gate/__init__.py +345 -0
  878. package/vendor/ars/scripts/verify_passport.py +133 -0
  879. package/vendor/ars/scripts/verify_submission_package.py +1657 -0
  880. package/vendor/ars/shared/agents/compliance_agent.md +136 -0
  881. package/vendor/ars/shared/artifact_reproducibility_pattern.md +173 -0
  882. package/vendor/ars/shared/benchmark_report.schema.json +81 -0
  883. package/vendor/ars/shared/benchmark_report_pattern.md +180 -0
  884. package/vendor/ars/shared/bibliographic_integrity_signals.md +142 -0
  885. package/vendor/ars/shared/collaboration_depth_rubric.md +154 -0
  886. package/vendor/ars/shared/compliance_checkpoint_protocol.md +162 -0
  887. package/vendor/ars/shared/compliance_report.schema.json +187 -0
  888. package/vendor/ars/shared/contracts/README.md +938 -0
  889. package/vendor/ars/shared/contracts/activity/adjudication_activity_input.schema.json +555 -0
  890. package/vendor/ars/shared/contracts/activity/adjudication_activity_store.schema.json +560 -0
  891. package/vendor/ars/shared/contracts/audit/audit_jsonl.schema.json +128 -0
  892. package/vendor/ars/shared/contracts/audit/audit_sidecar.schema.json +169 -0
  893. package/vendor/ars/shared/contracts/audit/audit_verdict.schema.json +133 -0
  894. package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory.schema.json +480 -0
  895. package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory_draft.schema.json +564 -0
  896. package/vendor/ars/shared/contracts/audit/cross_document_source_manifest.schema.json +202 -0
  897. package/vendor/ars/shared/contracts/audit/tortured_phrase_advisory.schema.json +1362 -0
  898. package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot.schema.json +208 -0
  899. package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot_manifest.schema.json +335 -0
  900. package/vendor/ars/shared/contracts/capability/stage_capability_matrix.json +434 -0
  901. package/vendor/ars/shared/contracts/claim_standing/candidate_ledger.schema.json +705 -0
  902. package/vendor/ars/shared/contracts/claim_standing/query_plan.schema.json +389 -0
  903. package/vendor/ars/shared/contracts/claim_standing/query_plan_v1_1.schema.json +782 -0
  904. package/vendor/ars/shared/contracts/claim_standing/retrieval_input.schema.json +440 -0
  905. package/vendor/ars/shared/contracts/claim_standing/stance_record.schema.json +460 -0
  906. package/vendor/ars/shared/contracts/claim_standing/transmission_ledger.schema.json +282 -0
  907. package/vendor/ars/shared/contracts/cross_model/codex_citation_receipt.schema.json +157 -0
  908. package/vendor/ars/shared/contracts/cross_model/codex_citation_request.schema.json +23 -0
  909. package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_commitment.schema.json +55 -0
  910. package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_reveal.schema.json +50 -0
  911. package/vendor/ars/shared/contracts/degradation_registry.json +428 -0
  912. package/vendor/ars/shared/contracts/evaluator/full.json +126 -0
  913. package/vendor/ars/shared/contracts/evidence/claim_registry.schema.json +50 -0
  914. package/vendor/ars/shared/contracts/evidence/claim_registry_coverage_report.schema.json +79 -0
  915. package/vendor/ars/shared/contracts/evidence/evidence_row.schema.json +504 -0
  916. package/vendor/ars/shared/contracts/evidence/evidence_row_v1_1.schema.json +364 -0
  917. package/vendor/ars/shared/contracts/evidence/evidence_row_v1_2.schema.json +714 -0
  918. package/vendor/ars/shared/contracts/evidence/evidence_row_v1_3.schema.json +524 -0
  919. package/vendor/ars/shared/contracts/human_subjects/authority_profile_registry.schema.json +497 -0
  920. package/vendor/ars/shared/contracts/human_subjects/committee_correspondence.schema.json +253 -0
  921. package/vendor/ars/shared/contracts/human_subjects/content_coverage_advisory.schema.json +620 -0
  922. package/vendor/ars/shared/contracts/human_subjects/irb_context_record.schema.json +329 -0
  923. package/vendor/ars/shared/contracts/human_subjects/resolved_authority_context.schema.json +347 -0
  924. package/vendor/ars/shared/contracts/human_subjects/review_pathway_rule_trace.schema.json +265 -0
  925. package/vendor/ars/shared/contracts/human_subjects/review_pathway_trace_request.schema.json +127 -0
  926. package/vendor/ars/shared/contracts/human_subjects/submission_packet_inventory.schema.json +240 -0
  927. package/vendor/ars/shared/contracts/human_subjects/submission_packet_manifest.schema.json +999 -0
  928. package/vendor/ars/shared/contracts/passport/audit_artifact_entry.schema.json +266 -0
  929. package/vendor/ars/shared/contracts/passport/bibliographic_integrity_signal.schema.json +1676 -0
  930. package/vendor/ars/shared/contracts/passport/citation_provenance.schema.json +107 -0
  931. package/vendor/ars/shared/contracts/passport/citation_verification_summary.schema.json +164 -0
  932. package/vendor/ars/shared/contracts/passport/claim_audit_result.schema.json +124 -0
  933. package/vendor/ars/shared/contracts/passport/claim_drift.schema.json +58 -0
  934. package/vendor/ars/shared/contracts/passport/claim_intent_manifest.schema.json +107 -0
  935. package/vendor/ars/shared/contracts/passport/constraint_violation.schema.json +65 -0
  936. package/vendor/ars/shared/contracts/passport/experiment_alignment_result.schema.json +69 -0
  937. package/vendor/ars/shared/contracts/passport/experiment_provenance_entry.schema.json +169 -0
  938. package/vendor/ars/shared/contracts/passport/human_read_log.schema.json +86 -0
  939. package/vendor/ars/shared/contracts/passport/inquiry_ledger_ref.schema.json +22 -0
  940. package/vendor/ars/shared/contracts/passport/literature_corpus_entry.schema.json +650 -0
  941. package/vendor/ars/shared/contracts/passport/preregistration_artifact.schema.json +132 -0
  942. package/vendor/ars/shared/contracts/passport/rejection_log.schema.json +89 -0
  943. package/vendor/ars/shared/contracts/passport/reset_ledger_entry.schema.json +158 -0
  944. package/vendor/ars/shared/contracts/passport/temporal_audit_results.schema.json +208 -0
  945. package/vendor/ars/shared/contracts/passport/terminal_policies.schema.json +50 -0
  946. package/vendor/ars/shared/contracts/passport/timeline.schema.json +102 -0
  947. package/vendor/ars/shared/contracts/passport/uncited_assertion.schema.json +56 -0
  948. package/vendor/ars/shared/contracts/passport/uncited_audit_failure.schema.json +72 -0
  949. package/vendor/ars/shared/contracts/passport/user_attested_read_resolution.schema.json +87 -0
  950. package/vendor/ars/shared/contracts/passport/version_records.schema.json +138 -0
  951. package/vendor/ars/shared/contracts/patch/block_manifest.schema.json +45 -0
  952. package/vendor/ars/shared/contracts/patch/legacy/v1_0/revision_patch.schema.json +110 -0
  953. package/vendor/ars/shared/contracts/patch/revision_patch.schema.json +250 -0
  954. package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_diagnostic.schema.json +39 -0
  955. package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_worker.schema.json +88 -0
  956. package/vendor/ars/shared/contracts/pdf/pdf_read_preflight.schema.json +227 -0
  957. package/vendor/ars/shared/contracts/re_review/input_manifest.schema.json +161 -0
  958. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/input_manifest.schema.json +134 -0
  959. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/precommitment.schema.json +193 -0
  960. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/traceability.schema.json +841 -0
  961. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/verdict_record.schema.json +244 -0
  962. package/vendor/ars/shared/contracts/re_review/precommitment.schema.json +193 -0
  963. package/vendor/ars/shared/contracts/re_review/traceability.schema.json +916 -0
  964. package/vendor/ars/shared/contracts/re_review/verdict_record.schema.json +245 -0
  965. package/vendor/ars/shared/contracts/research_workflow/inquiry_branch_ledger.schema.json +449 -0
  966. package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile.schema.json +222 -0
  967. package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile_selection_receipt.schema.json +111 -0
  968. package/vendor/ars/shared/contracts/review_target/constructive_review_findings.schema.json +172 -0
  969. package/vendor/ars/shared/contracts/review_target/criteria_registry.schema.json +97 -0
  970. package/vendor/ars/shared/contracts/review_target/review_criteria_binding_manifest.schema.json +235 -0
  971. package/vendor/ars/shared/contracts/review_target/review_criteria_source_receipt.schema.json +158 -0
  972. package/vendor/ars/shared/contracts/review_target/review_target_context.schema.json +106 -0
  973. package/vendor/ars/shared/contracts/review_target/review_target_declaration.schema.json +118 -0
  974. package/vendor/ars/shared/contracts/reviewer/full.json +114 -0
  975. package/vendor/ars/shared/contracts/reviewer/methodology_focus.json +75 -0
  976. package/vendor/ars/shared/contracts/reviewer/review_panel_provenance.schema.json +263 -0
  977. package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_carrier.schema.json +122 -0
  978. package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_input.schema.json +120 -0
  979. package/vendor/ars/shared/contracts/revision/author_adjudication.schema.json +251 -0
  980. package/vendor/ars/shared/contracts/revision/author_adjudication_input.schema.json +25 -0
  981. package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition.schema.json +97 -0
  982. package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition_input.schema.json +80 -0
  983. package/vendor/ars/shared/contracts/revision/claim_strength_drift_findings.schema.json +139 -0
  984. package/vendor/ars/shared/contracts/revision/claim_surface_manifest.schema.json +99 -0
  985. package/vendor/ars/shared/contracts/revision/integrity_correction_authorization.schema.json +140 -0
  986. package/vendor/ars/shared/contracts/revision/integrity_correction_authorization_input.schema.json +28 -0
  987. package/vendor/ars/shared/contracts/revision/integrity_correction_list.schema.json +48 -0
  988. package/vendor/ars/shared/contracts/revision/integrity_pass_receipt.schema.json +16 -0
  989. package/vendor/ars/shared/contracts/revision/revision_evidence_bundle.schema.json +134 -0
  990. package/vendor/ars/shared/contracts/revision/revision_roadmap.schema.json +334 -0
  991. package/vendor/ars/shared/contracts/submission/format_profile.example.yaml +33 -0
  992. package/vendor/ars/shared/contracts/submission/format_profile.schema.json +102 -0
  993. package/vendor/ars/shared/contracts/submission/submission_verification_report.schema.json +233 -0
  994. package/vendor/ars/shared/contracts/submission/venue_profile.schema.json +114 -0
  995. package/vendor/ars/shared/contracts/writer/full.json +87 -0
  996. package/vendor/ars/shared/cross_model_verification.md +714 -0
  997. package/vendor/ars/shared/evals_lift_report.schema.json +141 -0
  998. package/vendor/ars/shared/ground_truth_isolation_pattern.md +275 -0
  999. package/vendor/ars/shared/handoff_schemas.md +1209 -0
  1000. package/vendor/ars/shared/human_subjects_authority_registry.json +1278 -0
  1001. package/vendor/ars/shared/mode_spectrum.md +57 -0
  1002. package/vendor/ars/shared/model_tiering.md +83 -0
  1003. package/vendor/ars/shared/policy_data/nature_policy.md +56 -0
  1004. package/vendor/ars/shared/prisma_trAIce_protocol.md +157 -0
  1005. package/vendor/ars/shared/raise_framework.md +129 -0
  1006. package/vendor/ars/shared/references/authority_content_coverage_advisory_protocol.md +275 -0
  1007. package/vendor/ars/shared/references/claim_standing_candidate_ledger_protocol.md +66 -0
  1008. package/vendor/ars/shared/references/claim_strength_ladder.md +93 -0
  1009. package/vendor/ars/shared/references/cross_document_consistency_advisory_protocol.md +263 -0
  1010. package/vendor/ars/shared/references/evidence_row_protocol.md +260 -0
  1011. package/vendor/ars/shared/references/firm_rules.md +90 -0
  1012. package/vendor/ars/shared/references/human_subjects_authority_protocol.md +274 -0
  1013. package/vendor/ars/shared/references/intent_clarification_protocol.md +168 -0
  1014. package/vendor/ars/shared/references/irb_terminology_glossary.md +229 -0
  1015. package/vendor/ars/shared/references/protected_hedging_phrases.md +118 -0
  1016. package/vendor/ars/shared/references/psychometric_terminology_glossary.md +109 -0
  1017. package/vendor/ars/shared/references/review_criteria_consumer_protocol.md +238 -0
  1018. package/vendor/ars/shared/references/review_pathway_rule_trace_protocol.md +166 -0
  1019. package/vendor/ars/shared/references/submission_packet_manifest_protocol.md +292 -0
  1020. package/vendor/ars/shared/references/word_count_conventions.md +124 -0
  1021. package/vendor/ars/shared/research_workflow_profiles/field_general.json +1 -0
  1022. package/vendor/ars/shared/review_criteria_registry.json +207 -0
  1023. package/vendor/ars/shared/review_criteria_sources/msr-2027-technical-papers.2026-08-24.json +46 -0
  1024. package/vendor/ars/shared/review_criteria_sources/sigsoft-empirical-standards.2026-08-24.json +31 -0
  1025. package/vendor/ars/shared/sprint_contract.schema.json +482 -0
  1026. package/vendor/ars/shared/style_calibration_protocol.md +151 -0
  1027. package/vendor/ars/shared/templates/codex_audit_multifile_template.md +263 -0
  1028. package/vendor/ars/tools/release-discipline/.toolkit-version +1 -0
  1029. package/vendor/ars/tools/release-discipline/README.md +4 -0
  1030. package/vendor/ars/tools/release-discipline/scripts/_release_doc_alignment_schema.py +1011 -0
  1031. package/vendor/ars/tools/release-discipline/scripts/check_command_invariants.py +497 -0
  1032. package/vendor/ars/tools/release-discipline/scripts/check_release_doc_alignment.py +263 -0
  1033. package/vendor/ars/tools/release-discipline/scripts/sync-toolkit.sh +147 -0
  1034. package/vendor/windows/NOTICE.md +12 -0
  1035. package/vendor/windows/arm64/fd.exe +0 -0
  1036. package/vendor/windows/arm64/licenses/fd/LICENSE-APACHE +201 -0
  1037. package/vendor/windows/arm64/licenses/fd/LICENSE-MIT +21 -0
  1038. package/vendor/windows/arm64/licenses/ripgrep/COPYING +3 -0
  1039. package/vendor/windows/arm64/licenses/ripgrep/LICENSE-MIT +21 -0
  1040. package/vendor/windows/arm64/licenses/ripgrep/UNLICENSE +24 -0
  1041. package/vendor/windows/arm64/rg.exe +0 -0
  1042. package/vendor/windows/x64/fd.exe +0 -0
  1043. package/vendor/windows/x64/licenses/fd/LICENSE-APACHE +201 -0
  1044. package/vendor/windows/x64/licenses/fd/LICENSE-MIT +21 -0
  1045. package/vendor/windows/x64/licenses/ripgrep/COPYING +3 -0
  1046. package/vendor/windows/x64/licenses/ripgrep/LICENSE-MIT +21 -0
  1047. package/vendor/windows/x64/licenses/ripgrep/UNLICENSE +24 -0
  1048. package/vendor/windows/x64/rg.exe +0 -0
@@ -0,0 +1,2215 @@
1
+ #!/usr/bin/env python3
2
+ """Recompute the #576 Spec B three-gate re-review contract from its artifacts.
3
+
4
+ Stdlib-only, same architecture class as ``check_panel_synthesis.py`` (#510:
5
+ recompute every layer from the primary artifacts; any mismatch voids the
6
+ synthesis). Normative source:
7
+ ``docs/design/2026-07-27-576-spec-b-re-review-precommitment-contract-spec.md``
8
+ §5 (artifact shapes), §6 (decision derivation), §10 (reviewer-label
9
+ normalization grammar), §11 (input manifest + apply-chain witness), §13
10
+ (checker responsibilities).
11
+
12
+ Responsibilities (spec §13):
13
+
14
+ 1. Schema-validate the three phase artifacts + manifest (self-contained
15
+ validators, no jsonschema dependency — repo convention).
16
+ 2. Hash-chain: ``input_manifest_hash`` -> ``precommitment_hash`` ->
17
+ ``verdict_record_hash`` verbatim binding; criterion inheritance binding
18
+ (roadmap_text / letter_text byte comparison + R<n> ordinal recomputation
19
+ with the §5.1 contiguity degradation).
20
+ 3. Consumer-side apply-chain witness (§11) incl. ``patch_digest``
21
+ content-bound positional pairing and the ``[PATCH-BINDING-ABSENT]``
22
+ pre-1.2 absence policy.
23
+ 4. Recompute invariants: coverage cardinalities, applied_criterion rules,
24
+ adjustment-chain grammar, deferral-loop referential integrity
25
+ (reapplications / resolutions / adjudications / acceptances / pending
26
+ rebuttal upgrades), the G2 deferral biconditional, DecisionInputs
27
+ operand equality, and the §6 Step 1-3 derivation (Steps 2-3 recomputed
28
+ BOTH from DecisionInputs AND independently from the raw records).
29
+ 5. Goalpost witness: the frozen new-issue set is byte-identical across
30
+ §5.2/§5.3 and only ``regression`` attribution enters the decision path.
31
+
32
+ Exit codes (graded, spec §13):
33
+
34
+ 0 pass
35
+ 1 recomputation mismatch -> ``[RE-REVIEW-ABORT: synthesis_mismatch]``
36
+ (referential-integrity failures are recomputation mismatches and
37
+ always exit 1)
38
+ 2 schema or manifest invalid -> the matching §3.5 abort reason
39
+ (``manifest_incomplete`` / ``manifest_hash_mismatch`` for the
40
+ manifest layer; ``phase1_lint_failed`` / ``phase2a_lint_failed`` /
41
+ ``phase2b_lint_failed`` for a phase artifact that fails its schema)
42
+
43
+ Classification note (documented design decision): the three hash-chain
44
+ links and the manifest-side §11 rules (artifact byte hashes, freshness,
45
+ patch_digest pairing, apply-chain witness ``fail``) are the same-inputs
46
+ proof — their violation is graded exit 2 with ``manifest_hash_mismatch``,
47
+ mirroring §11's "-> manifest_hash_mismatch, G0". Content recomputation on
48
+ top of a sound binding (criterion quotes, letter ordinals, chains,
49
+ decision) is graded exit 1.
50
+
51
+ Hashing convention: sha256 hex over JSON Canonical Form (RFC 8785 / JCS,
52
+ the v3.6.3 passport convention). For this contract's value domain
53
+ (objects/arrays/strings/integers/booleans/null) Python's
54
+ ``json.dumps(obj, sort_keys=True, separators=(",", ":"),
55
+ ensure_ascii=False)`` produces the JCS byte stream.
56
+
57
+ Usage:
58
+ python scripts/check_re_review_synthesis.py \
59
+ --manifest M.json --precommitment P.json \
60
+ --verdict-record V.json --traceability T.json \
61
+ --roadmap roadmap.json [--letter letter.md] \
62
+ [--apply-report R1.json ...]
63
+
64
+ The ``--roadmap`` / ``--letter`` / ``--apply-report`` files are the
65
+ hash-bound §11 artifacts the checker must READ (criterion binding, ordinal
66
+ derivation, chain witness); every other §11 artifact participates through
67
+ its manifest hash alone. ``--apply-report`` order must follow the
68
+ manifest's ``apply_reports[]`` order.
69
+ """
70
+ from __future__ import annotations
71
+
72
+ import argparse
73
+ import hashlib
74
+ import json
75
+ import math
76
+ import re
77
+ import sys
78
+ import unicodedata
79
+ from pathlib import Path
80
+
81
+ EXIT_PASS = 0
82
+ EXIT_SYNTHESIS = 1
83
+ EXIT_INVALID = 2
84
+
85
+ VERDICTS = ("FULLY_ADDRESSED", "PARTIALLY_ADDRESSED", "NOT_ADDRESSED", "MADE_WORSE", "CANNOT_VERIFY")
86
+ JUDGE_VERDICTS = ("FULLY_ADDRESSED", "PARTIALLY_ADDRESSED", "NOT_ADDRESSED", "MADE_WORSE")
87
+ PRIORITIES = ("must_fix", "should_fix", "consider")
88
+ ROW_PRIORITY_MAP = {"MUST_FIX": "must_fix", "SHOULD_FIX": "should_fix", "CONSIDER": "consider"}
89
+ SEVERITIES = ("critical", "major", "minor")
90
+ MAGNITUDES = ("must_fix", "should_fix", "consider")
91
+ SEAT_LABELS = ("EIC", "R1", "R2", "R3", "DA")
92
+ VERIFIER_SEATS = ("EIC", "R1", "R2", "R3")
93
+ ADJUSTMENT_BASES = (
94
+ "author_pointer_located_evidence",
95
+ "valid_rebuttal",
96
+ "scope_correction",
97
+ "user_accepted_fail_closed",
98
+ "cross_model_adjudication",
99
+ )
100
+ ATTRIBUTIONS = ("regression", "previously_missed", "indeterminate")
101
+ DECISION_STATES = ("Accept", "Minor Revision", "Major Revision", "user_review_required", "aborted")
102
+ ABORT_REASONS = (
103
+ "phase1_lint_failed",
104
+ "phase2a_lint_failed",
105
+ "phase2b_lint_failed",
106
+ "manifest_incomplete",
107
+ "manifest_hash_mismatch",
108
+ "criteria_drift",
109
+ "synthesis_mismatch",
110
+ )
111
+ WITNESS_STATES = ("pass", "fail", "first_link_not_run", "not_run_no_reports")
112
+ DECISION_ORDER = {"Accept": 0, "Minor Revision": 1, "Major Revision": 2}
113
+ # §5.3 mechanical 5->4 map (MADE_WORSE -> NO: the concern stands unresolved —
114
+ # verification happened, outcome negative).
115
+ VERIFIED_MAP = {
116
+ "FULLY_ADDRESSED": "YES",
117
+ "PARTIALLY_ADDRESSED": "PARTIAL",
118
+ "NOT_ADDRESSED": "NO",
119
+ "MADE_WORSE": "NO",
120
+ "CANNOT_VERIFY": "CANNOT_VERIFY",
121
+ }
122
+ ESCALATION_CLASSES = ("research_integrity", "ethics", "safety", "legal_compliance", "fatal_validity")
123
+
124
+ _SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
125
+ _HASH12_RE = re.compile(r"^[0-9a-f]{12}$")
126
+ _REPORT_VERSION_RE = re.compile(r"^[0-9]+(\.[0-9]+)*$")
127
+ _ITEM_ID_RE = re.compile(r"^REV-[A-Za-z0-9-]+$")
128
+ _CONCERN_ID_RE = re.compile(r"^[RSN][1-9][0-9]*$")
129
+ _LETTER_REF_RE = re.compile(r"^R([1-9][0-9]*)$")
130
+ _ANCHOR_RE = re.compile(r"^(text|table|figure|equation|dataset|absence): .+$")
131
+ _ARTIFACT_REF_RE = re.compile(r"^(passport|path):.+$")
132
+ _ANSWER_REF_RE = re.compile(r"^(adjudication:DIS-[1-9][0-9]*|intent:INT-[1-9][0-9]*)$")
133
+ _CRITERION_REF_RE = re.compile(r"^(phase1:REV-[A-Za-z0-9-]+|dissent:DIS-[1-9][0-9]*)$")
134
+ _SOURCE_REF_RE = re.compile(r"^(reapplication:RAP-[1-9][0-9]*|acceptance:ACC-[1-9][0-9]*)$")
135
+ _CHECK_ADJUDICATED_RE = re.compile(r"^adjudicated:(RADJ-[1-9][0-9]*)$")
136
+ _ID_RES = {
137
+ "NS": re.compile(r"^NS-[1-9][0-9]*$"),
138
+ "NEW": re.compile(r"^NEW-[1-9][0-9]*$"),
139
+ "DIS": re.compile(r"^DIS-[1-9][0-9]*$"),
140
+ "ESC": re.compile(r"^ESC-[1-9][0-9]*$"),
141
+ "ADJ": re.compile(r"^ADJ-[1-9][0-9]*$"),
142
+ "INT": re.compile(r"^INT-[1-9][0-9]*$"),
143
+ "RES": re.compile(r"^RES-[1-9][0-9]*$"),
144
+ "RADJ": re.compile(r"^RADJ-[1-9][0-9]*$"),
145
+ "ACC": re.compile(r"^ACC-[1-9][0-9]*$"),
146
+ "RAP": re.compile(r"^RAP-[1-9][0-9]*$"),
147
+ "PRB": re.compile(r"^PRB-[1-9][0-9]*$"),
148
+ }
149
+
150
+
151
+ class ManifestError(Exception):
152
+ """Manifest-layer failure -> exit 2 with a §3.5 G0 reason."""
153
+
154
+ def __init__(self, reason: str, message: str):
155
+ self.reason = reason
156
+ super().__init__(message)
157
+
158
+
159
+ class ArtifactSchemaError(Exception):
160
+ """Phase-artifact schema failure -> exit 2 with the phase lint reason."""
161
+
162
+ def __init__(self, reason: str, message: str):
163
+ self.reason = reason
164
+ super().__init__(message)
165
+
166
+
167
+ def canonical_hash(obj) -> str:
168
+ """sha256 hex over the object's JSON Canonical Form (RFC 8785 / JCS)."""
169
+ payload = json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
170
+ return hashlib.sha256(payload.encode("utf-8")).hexdigest()
171
+
172
+
173
+ # --- §10 normalization grammar -------------------------------------------------
174
+
175
+ _PAREN_RE = re.compile(r"\([^()]*\)")
176
+ _SEPARATOR_RE = re.compile(r",|/|;| and |&")
177
+ _R_TOKEN_RES = (
178
+ re.compile(r"^r([1-3])$"),
179
+ re.compile(r"^reviewer ([1-3])$"),
180
+ re.compile(r"^peer reviewer ([1-3])$"),
181
+ )
182
+
183
+
184
+ def normalize_reviewer_labels(raw: str):
185
+ """§10 emitter-side normalization grammar, in declared order.
186
+
187
+ (1a) strip every parenthetical group FIRST; (1b) THEN strip any
188
+ " — <suffix>" tail from the residual (order normative and
189
+ non-commutative); (2) split on {",", "/", ";", " and ", "&"};
190
+ (3) trim whitespace; case-fold. A stripped token normalizes by
191
+ WHOLE-TOKEN EXACT MATCH (no substring extraction); unrecognized
192
+ tokens are DROPPED, never guessed. Returns ``(labels, parse_failure)``
193
+ where labels is the deduplicated, original-order result and
194
+ ``parse_failure`` is True when a non-empty input produced zero
195
+ recognized tokens (§10: routes to EIC but counts as unmapped — a
196
+ distinct state from a genuinely empty list).
197
+ """
198
+ text = unicodedata.normalize("NFC", raw)
199
+ prev = None
200
+ while prev != text: # nested groups collapse outside-in
201
+ prev = text
202
+ text = _PAREN_RE.sub("", text)
203
+ dash_idx = text.find(" — ")
204
+ if dash_idx != -1:
205
+ text = text[:dash_idx]
206
+ labels = []
207
+ for token in _SEPARATOR_RE.split(text):
208
+ token = token.strip().casefold()
209
+ if not token:
210
+ continue
211
+ label = None
212
+ if token in ("eic", "editor-in-chief"):
213
+ label = "EIC"
214
+ elif token in ("da", "devil's advocate", "devil’s advocate"):
215
+ label = "DA"
216
+ else:
217
+ for pattern in _R_TOKEN_RES:
218
+ match = pattern.match(token)
219
+ if match:
220
+ label = "R" + match.group(1)
221
+ break
222
+ if label is not None and label not in labels:
223
+ labels.append(label)
224
+ parse_failure = bool(raw.strip()) and not labels
225
+ return labels, parse_failure
226
+
227
+
228
+ # --- validation helpers --------------------------------------------------------
229
+
230
+
231
+ def _is_str(value, nonempty=True) -> bool:
232
+ return isinstance(value, str) and (not nonempty or len(value) > 0)
233
+
234
+
235
+ class _V:
236
+ """Accumulating validator for one artifact; raises on first failure."""
237
+
238
+ def __init__(self, reason: str, artifact_name: str):
239
+ self.reason = reason
240
+ self.artifact = artifact_name
241
+
242
+ def fail(self, path: str, message: str):
243
+ raise ArtifactSchemaError(self.reason, f"{self.artifact}: {path}: {message}")
244
+
245
+ def obj(self, value, path, required, optional=()):
246
+ if not isinstance(value, dict):
247
+ self.fail(path, "must be an object")
248
+ allowed = set(required) | set(optional)
249
+ for key in value:
250
+ if key not in allowed:
251
+ self.fail(path, f"unexpected field {key!r}")
252
+ for key in required:
253
+ if key not in value:
254
+ self.fail(path, f"missing required field {key!r}")
255
+
256
+ def string(self, value, path, nonempty=True):
257
+ if not _is_str(value, nonempty):
258
+ self.fail(path, "must be a non-empty string" if nonempty else "must be a string")
259
+
260
+ def enum(self, value, path, allowed):
261
+ if value not in allowed:
262
+ self.fail(path, f"must be one of {sorted(allowed)!r}, got {value!r}")
263
+
264
+ def pattern(self, value, path, regex, label):
265
+ if not isinstance(value, str) or not regex.match(value):
266
+ self.fail(path, f"must match {label}, got {value!r}")
267
+
268
+ def integer(self, value, path, minimum=None, maximum=None):
269
+ if not isinstance(value, int) or isinstance(value, bool):
270
+ self.fail(path, "must be an integer")
271
+ if minimum is not None and value < minimum:
272
+ self.fail(path, f"must be >= {minimum}")
273
+ if maximum is not None and value > maximum:
274
+ self.fail(path, f"must be <= {maximum}")
275
+
276
+ def array(self, value, path):
277
+ if not isinstance(value, list):
278
+ self.fail(path, "must be an array")
279
+
280
+
281
+ def _validate_residual_gap(v: _V, gap, path):
282
+ v.obj(gap, path, ["text", "residual_magnitude"])
283
+ v.string(gap["text"], f"{path}.text")
284
+ v.enum(gap["residual_magnitude"], f"{path}.residual_magnitude", MAGNITUDES)
285
+
286
+
287
+ def _validate_tagged_anchors(v: _V, anchors, path):
288
+ v.array(anchors, path)
289
+ if not anchors:
290
+ v.fail(path, "must carry at least one anchor")
291
+ for i, entry in enumerate(anchors):
292
+ v.obj(entry, f"{path}[{i}]", ["anchor", "anchor_artifact"])
293
+ v.pattern(entry["anchor"], f"{path}[{i}].anchor", _ANCHOR_RE, "the Schema 6 typed-anchor grammar")
294
+ v.enum(entry["anchor_artifact"], f"{path}[{i}].anchor_artifact", ("manuscript", "letter"))
295
+
296
+
297
+ def _validate_new_issue(v: _V, rec, path):
298
+ v.obj(rec, path, [
299
+ "new_issue_id", "description", "location_anchor", "severity", "found_by",
300
+ "confidence", "competence_basis", "attribution", "attribution_evidence",
301
+ "nearest_roadmap_item", "non_match_rationale",
302
+ ])
303
+ v.pattern(rec["new_issue_id"], f"{path}.new_issue_id", _ID_RES["NEW"], "NEW-<n>")
304
+ v.string(rec["description"], f"{path}.description")
305
+ v.pattern(rec["location_anchor"], f"{path}.location_anchor", _ANCHOR_RE, "typed anchor")
306
+ v.enum(rec["severity"], f"{path}.severity", SEVERITIES)
307
+ v.enum(rec["found_by"], f"{path}.found_by", VERIFIER_SEATS)
308
+ v.integer(rec["confidence"], f"{path}.confidence", 1, 5)
309
+ v.string(rec["competence_basis"], f"{path}.competence_basis")
310
+ v.enum(rec["attribution"], f"{path}.attribution", ATTRIBUTIONS)
311
+ v.string(rec["attribution_evidence"], f"{path}.attribution_evidence")
312
+ nearest = rec["nearest_roadmap_item"]
313
+ if nearest is not None:
314
+ v.pattern(nearest, f"{path}.nearest_roadmap_item", _ITEM_ID_RE, "REV-<id> or null")
315
+ v.string(rec["non_match_rationale"], f"{path}.non_match_rationale")
316
+
317
+
318
+ def validate_precommitment(art: dict):
319
+ v = _V("phase1_lint_failed", "precommitment")
320
+ v.obj(art, "$", ["contract_version", "round_id", "input_manifest_hash", "items", "new_standards"])
321
+ v.enum(art["contract_version"], "$.contract_version", ("1.0",))
322
+ v.string(art["round_id"], "$.round_id")
323
+ v.pattern(art["input_manifest_hash"], "$.input_manifest_hash", _SHA256_RE, "sha256 hex")
324
+ v.array(art["items"], "$.items")
325
+ for i, rec in enumerate(art["items"]):
326
+ path = f"$.items[{i}]"
327
+ v.obj(rec, path, [
328
+ "item_id", "priority", "inherited_criterion", "operationalization",
329
+ "expected_change_surface", "equivalence_policy", "source_reviewer",
330
+ "source_reviewer_labels",
331
+ ])
332
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
333
+ v.enum(rec["priority"], f"{path}.priority", ("must_fix", "should_fix"))
334
+ crit = rec["inherited_criterion"]
335
+ v.obj(crit, f"{path}.inherited_criterion", ["roadmap_text"], ["letter_text", "letter_item_ref"])
336
+ v.string(crit["roadmap_text"], f"{path}.inherited_criterion.roadmap_text")
337
+ has_text = "letter_text" in crit
338
+ has_ref = "letter_item_ref" in crit
339
+ if has_text != has_ref:
340
+ v.fail(f"{path}.inherited_criterion", "letter_text and letter_item_ref are biconditional (§5.1)")
341
+ if has_text:
342
+ v.string(crit["letter_text"], f"{path}.inherited_criterion.letter_text")
343
+ v.pattern(crit["letter_item_ref"], f"{path}.inherited_criterion.letter_item_ref", _LETTER_REF_RE, "R<n>")
344
+ if rec["priority"] != "must_fix":
345
+ v.fail(f"{path}.inherited_criterion", "letter fields exist ONLY on must_fix items (§5.1)")
346
+ op = rec["operationalization"]
347
+ v.obj(op, f"{path}.operationalization", ["fully_addressed"], ["partially_addressed", "made_worse_discriminator"])
348
+ v.string(op["fully_addressed"], f"{path}.operationalization.fully_addressed")
349
+ if rec["priority"] == "must_fix":
350
+ for field in ("partially_addressed", "made_worse_discriminator"):
351
+ if field not in op:
352
+ v.fail(f"{path}.operationalization", f"must_fix items require {field!r} (§5.1)")
353
+ v.string(op[field], f"{path}.operationalization.{field}")
354
+ else:
355
+ for field in ("partially_addressed", "made_worse_discriminator"):
356
+ if field in op:
357
+ v.fail(f"{path}.operationalization", f"P2 lighter form forbids {field!r} (§5.1)")
358
+ v.string(rec["expected_change_surface"], f"{path}.expected_change_surface")
359
+ v.enum(rec["equivalence_policy"], f"{path}.equivalence_policy", ("allowed",))
360
+ v.string(rec["source_reviewer"], f"{path}.source_reviewer")
361
+ v.array(rec["source_reviewer_labels"], f"{path}.source_reviewer_labels")
362
+ seen = set()
363
+ for j, label in enumerate(rec["source_reviewer_labels"]):
364
+ v.enum(label, f"{path}.source_reviewer_labels[{j}]", SEAT_LABELS)
365
+ if label in seen:
366
+ v.fail(f"{path}.source_reviewer_labels", f"duplicate label {label!r}")
367
+ seen.add(label)
368
+ v.array(art["new_standards"], "$.new_standards")
369
+ ns_ids = set()
370
+ for i, rec in enumerate(art["new_standards"]):
371
+ path = f"$.new_standards[{i}]"
372
+ v.obj(rec, path, ["new_standard_id", "item_id", "standard_text", "why_not_in_round1", "classification"])
373
+ v.pattern(rec["new_standard_id"], f"{path}.new_standard_id", _ID_RES["NS"], "NS-<n>")
374
+ if rec["new_standard_id"] in ns_ids:
375
+ v.fail(path, f"duplicate new_standard_id {rec['new_standard_id']!r}")
376
+ ns_ids.add(rec["new_standard_id"])
377
+ if rec["item_id"] != "global":
378
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, 'REV-<id> or "global"')
379
+ v.string(rec["standard_text"], f"{path}.standard_text")
380
+ v.string(rec["why_not_in_round1"], f"{path}.why_not_in_round1")
381
+ v.enum(rec["classification"], f"{path}.classification", ("advisory", "escalation_requested"))
382
+
383
+
384
+ def validate_verdict_record(art: dict):
385
+ v = _V("phase2a_lint_failed", "verdict_record")
386
+ v.obj(art, "$", ["round_id", "precommitment_hash", "items", "new_issues", "dissents", "escalation_exceptions"])
387
+ v.string(art["round_id"], "$.round_id")
388
+ v.pattern(art["precommitment_hash"], "$.precommitment_hash", _SHA256_RE, "sha256 hex")
389
+ v.array(art["items"], "$.items")
390
+ for i, rec in enumerate(art["items"]):
391
+ path = f"$.items[{i}]"
392
+ v.obj(rec, path, ["item_id", "verdict", "change_summary", "verified_by", "applied_criterion"],
393
+ ["evidence_anchor", "cannot_verify_reason", "residual_gap"])
394
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
395
+ v.enum(rec["verdict"], f"{path}.verdict", VERDICTS)
396
+ if rec["verdict"] == "CANNOT_VERIFY":
397
+ if "cannot_verify_reason" not in rec or "evidence_anchor" in rec:
398
+ v.fail(path, "CANNOT_VERIFY carries cannot_verify_reason and no evidence_anchor (§3.3)")
399
+ v.string(rec["cannot_verify_reason"], f"{path}.cannot_verify_reason")
400
+ else:
401
+ if "evidence_anchor" not in rec or "cannot_verify_reason" in rec:
402
+ v.fail(path, "non-CANNOT_VERIFY verdicts carry evidence_anchor and no cannot_verify_reason (§3.3)")
403
+ v.array(rec["evidence_anchor"], f"{path}.evidence_anchor")
404
+ if not rec["evidence_anchor"]:
405
+ v.fail(f"{path}.evidence_anchor", "must carry at least one anchor")
406
+ for j, anchor in enumerate(rec["evidence_anchor"]):
407
+ v.pattern(anchor, f"{path}.evidence_anchor[{j}]", _ANCHOR_RE, "typed anchor")
408
+ if rec["verdict"] == "PARTIALLY_ADDRESSED":
409
+ if "residual_gap" not in rec:
410
+ v.fail(path, "PARTIALLY_ADDRESSED requires residual_gap (§3.3)")
411
+ _validate_residual_gap(v, rec["residual_gap"], f"{path}.residual_gap")
412
+ elif "residual_gap" in rec:
413
+ v.fail(path, "residual_gap is required iff verdict = PARTIALLY_ADDRESSED")
414
+ v.string(rec["change_summary"], f"{path}.change_summary")
415
+ v.enum(rec["verified_by"], f"{path}.verified_by", VERIFIER_SEATS)
416
+ applied = rec["applied_criterion"]
417
+ if applied not in ("precommitted", "not_precommitted") and not (
418
+ isinstance(applied, str) and re.match(r"^dissented:DIS-[1-9][0-9]*$", applied)
419
+ ):
420
+ v.fail(f"{path}.applied_criterion", f"invalid value {applied!r}")
421
+ v.array(art["new_issues"], "$.new_issues")
422
+ for i, rec in enumerate(art["new_issues"]):
423
+ _validate_new_issue(v, rec, f"$.new_issues[{i}]")
424
+ v.array(art["dissents"], "$.dissents")
425
+ for i, rec in enumerate(art["dissents"]):
426
+ path = f"$.dissents[{i}]"
427
+ v.obj(rec, path, [
428
+ "dissent_id", "item_id", "criterion_hash", "reason_code",
429
+ "original_operationalization", "replacement_operationalization",
430
+ "evidence", "decision_impact_note",
431
+ ])
432
+ v.pattern(rec["dissent_id"], f"{path}.dissent_id", _ID_RES["DIS"], "DIS-<n>")
433
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
434
+ v.pattern(rec["criterion_hash"], f"{path}.criterion_hash", _SHA256_RE, "sha256 hex")
435
+ v.enum(rec["reason_code"], f"{path}.reason_code",
436
+ ("criterion_ambiguous", "criterion_infeasible_as_written", "evidence_surface_moved", "criterion_error"))
437
+ for field in ("original_operationalization", "replacement_operationalization", "evidence", "decision_impact_note"):
438
+ v.string(rec[field], f"{path}.{field}")
439
+ v.array(art["escalation_exceptions"], "$.escalation_exceptions")
440
+ for i, rec in enumerate(art["escalation_exceptions"]):
441
+ path = f"$.escalation_exceptions[{i}]"
442
+ v.obj(rec, path, [
443
+ "exception_id", "escalation_class", "reason_code", "evidence_anchor",
444
+ "why_round1_missed_it", "mechanical_decision_impact", "approval_state",
445
+ ], ["new_standard_ref"])
446
+ v.pattern(rec["exception_id"], f"{path}.exception_id", _ID_RES["ESC"], "ESC-<n>")
447
+ if "new_standard_ref" in rec:
448
+ v.pattern(rec["new_standard_ref"], f"{path}.new_standard_ref", _ID_RES["NS"], "NS-<n>")
449
+ v.enum(rec["escalation_class"], f"{path}.escalation_class", ESCALATION_CLASSES)
450
+ v.string(rec["reason_code"], f"{path}.reason_code")
451
+ v.pattern(rec["evidence_anchor"], f"{path}.evidence_anchor", _ANCHOR_RE, "typed anchor")
452
+ v.string(rec["why_round1_missed_it"], f"{path}.why_round1_missed_it")
453
+ v.enum(rec["mechanical_decision_impact"], f"{path}.mechanical_decision_impact", ("Minor Revision", "Major Revision"))
454
+ v.enum(rec["approval_state"], f"{path}.approval_state", ("pending",))
455
+
456
+
457
+ def _validate_adjustment_body(v: _V, rec, path, *, drafted: bool):
458
+ required = ["item_id", "from_verdict", "to_verdict", "basis", "rationale"]
459
+ optional = ["evidence_anchor", "cannot_verify_reason", "residual_gap"]
460
+ if drafted:
461
+ pass # drafted bodies carry no booking-assigned fields
462
+ else:
463
+ required.insert(0, "adjustment_id")
464
+ optional += ["critical_rebuttal_check", "source_ref", "supersedes_adjustment_id"]
465
+ v.obj(rec, path, required, optional)
466
+ if not drafted:
467
+ v.pattern(rec["adjustment_id"], f"{path}.adjustment_id", _ID_RES["ADJ"], "ADJ-<n>")
468
+ if "supersedes_adjustment_id" in rec:
469
+ v.pattern(rec["supersedes_adjustment_id"], f"{path}.supersedes_adjustment_id", _ID_RES["ADJ"], "ADJ-<n>")
470
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
471
+ v.enum(rec["from_verdict"], f"{path}.from_verdict", VERDICTS)
472
+ v.enum(rec["to_verdict"], f"{path}.to_verdict", VERDICTS)
473
+ if drafted:
474
+ v.enum(rec["basis"], f"{path}.basis", ("valid_rebuttal",))
475
+ else:
476
+ v.enum(rec["basis"], f"{path}.basis", ADJUSTMENT_BASES)
477
+ v.string(rec["rationale"], f"{path}.rationale")
478
+ if rec["to_verdict"] == "CANNOT_VERIFY":
479
+ if "cannot_verify_reason" not in rec or "evidence_anchor" in rec:
480
+ v.fail(path, "to_verdict CANNOT_VERIFY carries cannot_verify_reason and no evidence_anchor")
481
+ v.string(rec["cannot_verify_reason"], f"{path}.cannot_verify_reason")
482
+ if rec["basis"] != "user_accepted_fail_closed":
483
+ v.fail(path, "user_accepted_fail_closed is the ONLY basis that may land on CANNOT_VERIFY (§3.4)")
484
+ else:
485
+ if "evidence_anchor" not in rec or "cannot_verify_reason" in rec:
486
+ v.fail(path, "non-CANNOT_VERIFY adjustments carry evidence_anchor and no cannot_verify_reason")
487
+ _validate_tagged_anchors(v, rec["evidence_anchor"], f"{path}.evidence_anchor")
488
+ if rec["basis"] in ("author_pointer_located_evidence", "scope_correction"):
489
+ for j, anchor in enumerate(rec["evidence_anchor"]):
490
+ if anchor["anchor_artifact"] != "manuscript":
491
+ v.fail(f"{path}.evidence_anchor[{j}]",
492
+ f"basis {rec['basis']} requires manuscript-side anchors (§5.3)")
493
+ if rec["to_verdict"] == "PARTIALLY_ADDRESSED":
494
+ if "residual_gap" not in rec:
495
+ v.fail(path, "to_verdict PARTIALLY_ADDRESSED requires residual_gap (§5.3)")
496
+ _validate_residual_gap(v, rec["residual_gap"], f"{path}.residual_gap")
497
+ elif "residual_gap" in rec:
498
+ v.fail(path, "residual_gap is required iff to_verdict = PARTIALLY_ADDRESSED")
499
+ # §3.4 Direction column. valid_rebuttal — the only basis allowing
500
+ # letter-side anchors — is "upgrade to FULLY_ADDRESSED"; anything else
501
+ # would let a persuasive letter move a verdict sideways/downward, the
502
+ # exact channel §1 exists to close. author_pointer_located_evidence is
503
+ # an UPGRADE: located evidence satisfies the Phase-1 operationalization
504
+ # (fully or partially), so the target is PARTIALLY/FULLY and strictly
505
+ # better than the source (MADE_WORSE/CANNOT_VERIFY rank lowest).
506
+ if rec["basis"] == "valid_rebuttal":
507
+ if rec["to_verdict"] != "FULLY_ADDRESSED" or rec["from_verdict"] == "FULLY_ADDRESSED":
508
+ v.fail(path, "valid_rebuttal adjustments upgrade to FULLY_ADDRESSED (§3.4 Direction)")
509
+ elif rec["basis"] == "author_pointer_located_evidence":
510
+ rank = {"MADE_WORSE": 0, "CANNOT_VERIFY": 0, "NOT_ADDRESSED": 1, "PARTIALLY_ADDRESSED": 2, "FULLY_ADDRESSED": 3}
511
+ if rec["to_verdict"] not in ("PARTIALLY_ADDRESSED", "FULLY_ADDRESSED") or rank[rec["to_verdict"]] <= rank[rec["from_verdict"]]:
512
+ v.fail(path, "author_pointer_located_evidence adjustments are upgrades to PARTIALLY/FULLY_ADDRESSED (§3.4 Direction)")
513
+ if drafted:
514
+ return
515
+ source_ref = rec.get("source_ref")
516
+ if rec["basis"] == "cross_model_adjudication":
517
+ if not (isinstance(source_ref, str) and source_ref.startswith("reapplication:")):
518
+ v.fail(path, 'basis cross_model_adjudication requires source_ref "reapplication:<id>"')
519
+ elif rec["basis"] == "user_accepted_fail_closed":
520
+ if not (isinstance(source_ref, str) and source_ref.startswith("acceptance:")):
521
+ v.fail(path, 'basis user_accepted_fail_closed requires source_ref "acceptance:<id>"')
522
+ elif source_ref is not None:
523
+ v.fail(path, f"source_ref is FORBIDDEN on basis {rec['basis']} (§5.3)")
524
+ if source_ref is not None:
525
+ v.pattern(source_ref, f"{path}.source_ref", _SOURCE_REF_RE, "reapplication:RAP-<n> | acceptance:ACC-<n>")
526
+ check = rec.get("critical_rebuttal_check")
527
+ if check is not None:
528
+ if rec["basis"] != "valid_rebuttal":
529
+ v.fail(path, "critical_rebuttal_check exists only on valid_rebuttal adjustments (§3.4)")
530
+ if check not in ("single_family_disclosed", "pass_unavailable_disclosed") and not _CHECK_ADJUDICATED_RE.match(check):
531
+ v.fail(f"{path}.critical_rebuttal_check", f"invalid value {check!r}")
532
+
533
+
534
+ def validate_traceability(art: dict):
535
+ v = _V("phase2b_lint_failed", "traceability")
536
+ v.obj(art, "$", [
537
+ "round_id", "revision", "verdict_record_hash", "rows", "adjustments",
538
+ "new_issues", "post_letter_observations", "dissent_adjudications",
539
+ "resolution_intents", "cross_model_resolutions", "rebuttal_adjudications",
540
+ "g2d_acceptances", "pending_rebuttal_upgrades", "escalation_approvals",
541
+ "reapplications", "decision_inputs", "decision_state",
542
+ ], ["supersedes_hash", "abort_reason"])
543
+ v.string(art["round_id"], "$.round_id")
544
+ v.integer(art["revision"], "$.revision", minimum=1)
545
+ if art["revision"] == 1:
546
+ if "supersedes_hash" in art:
547
+ v.fail("$", "revision 1 must not carry supersedes_hash")
548
+ else:
549
+ if "supersedes_hash" not in art:
550
+ v.fail("$", "revision >= 2 requires supersedes_hash")
551
+ v.pattern(art["supersedes_hash"], "$.supersedes_hash", _SHA256_RE, "sha256 hex")
552
+ v.pattern(art["verdict_record_hash"], "$.verdict_record_hash", _SHA256_RE, "sha256 hex")
553
+ v.enum(art["decision_state"], "$.decision_state", DECISION_STATES)
554
+ if art["decision_state"] == "aborted":
555
+ if "abort_reason" not in art:
556
+ v.fail("$", "decision_state aborted requires abort_reason")
557
+ v.enum(art["abort_reason"], "$.abort_reason", ABORT_REASONS)
558
+ elif "abort_reason" in art:
559
+ v.fail("$", "abort_reason is required iff decision_state = aborted")
560
+
561
+ v.array(art["rows"], "$.rows")
562
+ for i, row in enumerate(art["rows"]):
563
+ path = f"$.rows[{i}]"
564
+ v.obj(row, path, [
565
+ "item_id", "concern_id", "priority", "original_comment", "authors_claim",
566
+ "revision_location", "verified", "status", "quality_assessment",
567
+ "final_verdict", "phase2a_verdict", "verified_by",
568
+ ], ["adjustment_id", "addressed_by_rebuttal", "cross_model_status", "cross_model_verdict"])
569
+ v.pattern(row["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
570
+ v.pattern(row["concern_id"], f"{path}.concern_id", _CONCERN_ID_RE, "R<n>/S<n>/N<n>")
571
+ v.enum(row["priority"], f"{path}.priority", tuple(ROW_PRIORITY_MAP))
572
+ for field in ("original_comment", "authors_claim", "revision_location", "quality_assessment"):
573
+ v.string(row[field], f"{path}.{field}")
574
+ v.enum(row["verified"], f"{path}.verified", ("YES", "PARTIAL", "NO", "CANNOT_VERIFY"))
575
+ v.enum(row["status"], f"{path}.status", VERDICTS)
576
+ v.enum(row["final_verdict"], f"{path}.final_verdict", VERDICTS)
577
+ v.enum(row["phase2a_verdict"], f"{path}.phase2a_verdict", VERDICTS)
578
+ v.enum(row["verified_by"], f"{path}.verified_by", VERIFIER_SEATS)
579
+ if "adjustment_id" in row:
580
+ v.pattern(row["adjustment_id"], f"{path}.adjustment_id", _ID_RES["ADJ"], "ADJ-<n>")
581
+ if "addressed_by_rebuttal" in row and row["addressed_by_rebuttal"] is not True:
582
+ v.fail(f"{path}.addressed_by_rebuttal", "when present the marker is the literal true")
583
+ has_status = "cross_model_status" in row
584
+ has_cmv = "cross_model_verdict" in row
585
+ if row["priority"] == "MUST_FIX":
586
+ if not has_status:
587
+ v.fail(path, "MUST_FIX rows always carry cross_model_status (§5.3 / #539)")
588
+ elif has_status or has_cmv:
589
+ v.fail(path, "SHOULD_FIX/CONSIDER rows carry neither cross-model field (#539 scope)")
590
+ if has_status:
591
+ v.enum(row["cross_model_status"], f"{path}.cross_model_status",
592
+ ("agree", "diverges", "unavailable", "not_configured"))
593
+ if row["cross_model_status"] in ("agree", "diverges"):
594
+ if not has_cmv:
595
+ v.fail(path, "evaluated (agree/diverges) rows carry cross_model_verdict")
596
+ v.enum(row["cross_model_verdict"], f"{path}.cross_model_verdict", JUDGE_VERDICTS)
597
+ elif has_cmv:
598
+ v.fail(path, "unavailable/not_configured rows carry no cross_model_verdict")
599
+
600
+ v.array(art["adjustments"], "$.adjustments")
601
+ for i, rec in enumerate(art["adjustments"]):
602
+ _validate_adjustment_body(v, rec, f"$.adjustments[{i}]", drafted=False)
603
+
604
+ v.array(art["new_issues"], "$.new_issues")
605
+ for i, rec in enumerate(art["new_issues"]):
606
+ _validate_new_issue(v, rec, f"$.new_issues[{i}]")
607
+
608
+ v.array(art["post_letter_observations"], "$.post_letter_observations")
609
+ for i, entry in enumerate(art["post_letter_observations"]):
610
+ v.string(entry, f"$.post_letter_observations[{i}]")
611
+
612
+ v.array(art["dissent_adjudications"], "$.dissent_adjudications")
613
+ for i, rec in enumerate(art["dissent_adjudications"]):
614
+ path = f"$.dissent_adjudications[{i}]"
615
+ v.obj(rec, path, ["dissent_id", "adjudicator", "outcome", "rationale"])
616
+ v.pattern(rec["dissent_id"], f"{path}.dissent_id", _ID_RES["DIS"], "DIS-<n>")
617
+ v.enum(rec["adjudicator"], f"{path}.adjudicator", ("cross_model", "user"))
618
+ v.enum(rec["outcome"], f"{path}.outcome", ("replacement_approved", "original_upheld"))
619
+ v.string(rec["rationale"], f"{path}.rationale")
620
+
621
+ v.array(art["resolution_intents"], "$.resolution_intents")
622
+ for i, rec in enumerate(art["resolution_intents"]):
623
+ path = f"$.resolution_intents[{i}]"
624
+ v.obj(rec, path, ["intent_id", "item_id", "answered_by"], ["guidance_note"])
625
+ v.pattern(rec["intent_id"], f"{path}.intent_id", _ID_RES["INT"], "INT-<n>")
626
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
627
+ v.enum(rec["answered_by"], f"{path}.answered_by", ("system", "user"))
628
+ if "guidance_note" in rec:
629
+ if rec["answered_by"] == "system":
630
+ v.fail(path, "guidance_note exists only on user intents (§5.3)")
631
+ v.string(rec["guidance_note"], f"{path}.guidance_note")
632
+
633
+ v.array(art["cross_model_resolutions"], "$.cross_model_resolutions")
634
+ for i, rec in enumerate(art["cross_model_resolutions"]):
635
+ path = f"$.cross_model_resolutions[{i}]"
636
+ v.obj(rec, path, ["resolution_id", "item_id", "intent_id", "reapplication_id", "state", "resolved_by", "rationale"])
637
+ v.pattern(rec["resolution_id"], f"{path}.resolution_id", _ID_RES["RES"], "RES-<n>")
638
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
639
+ v.pattern(rec["intent_id"], f"{path}.intent_id", _ID_RES["INT"], "INT-<n>")
640
+ v.pattern(rec["reapplication_id"], f"{path}.reapplication_id", _ID_RES["RAP"], "RAP-<n>")
641
+ v.enum(rec["state"], f"{path}.state", ("primary_upheld", "primary_revised"))
642
+ v.enum(rec["resolved_by"], f"{path}.resolved_by", ("system", "user"))
643
+ if rec["resolved_by"] == "user" and rec["state"] != "primary_upheld":
644
+ v.fail(path, "the user acceptance form is always primary_upheld — the user never hand-sets a verdict (§5.3)")
645
+ v.string(rec["rationale"], f"{path}.rationale")
646
+
647
+ v.array(art["rebuttal_adjudications"], "$.rebuttal_adjudications")
648
+ for i, rec in enumerate(art["rebuttal_adjudications"]):
649
+ path = f"$.rebuttal_adjudications[{i}]"
650
+ v.obj(rec, path, ["rebuttal_adjudication_id", "item_id", "verdict", "rationale"])
651
+ v.pattern(rec["rebuttal_adjudication_id"], f"{path}.rebuttal_adjudication_id", _ID_RES["RADJ"], "RADJ-<n>")
652
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
653
+ v.enum(rec["verdict"], f"{path}.verdict", ("upheld", "challenged"))
654
+ v.string(rec["rationale"], f"{path}.rationale")
655
+
656
+ v.array(art["g2d_acceptances"], "$.g2d_acceptances")
657
+ for i, rec in enumerate(art["g2d_acceptances"]):
658
+ path = f"$.g2d_acceptances[{i}]"
659
+ v.obj(rec, path, ["acceptance_id", "item_id", "reapplication_id", "accepted_by"])
660
+ v.pattern(rec["acceptance_id"], f"{path}.acceptance_id", _ID_RES["ACC"], "ACC-<n>")
661
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
662
+ v.pattern(rec["reapplication_id"], f"{path}.reapplication_id", _ID_RES["RAP"], "RAP-<n>")
663
+ v.enum(rec["accepted_by"], f"{path}.accepted_by", ("user",))
664
+
665
+ v.array(art["pending_rebuttal_upgrades"], "$.pending_rebuttal_upgrades")
666
+ for i, rec in enumerate(art["pending_rebuttal_upgrades"]):
667
+ path = f"$.pending_rebuttal_upgrades[{i}]"
668
+ v.obj(rec, path, ["proposal_id", "item_id", "drafted_adjustment", "disposition"])
669
+ v.pattern(rec["proposal_id"], f"{path}.proposal_id", _ID_RES["PRB"], "PRB-<n>")
670
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
671
+ _validate_adjustment_body(v, rec["drafted_adjustment"], f"{path}.drafted_adjustment", drafted=True)
672
+ disposition = rec["disposition"]
673
+ if not isinstance(disposition, str) or not re.match(
674
+ r"^(booked:ADJ-[1-9][0-9]*|challenged:RADJ-[1-9][0-9]*|booked_disclosed:ADJ-[1-9][0-9]*|booked_single_family:ADJ-[1-9][0-9]*)$",
675
+ disposition,
676
+ ):
677
+ v.fail(f"{path}.disposition", f"invalid disposition {disposition!r}")
678
+
679
+ v.array(art["escalation_approvals"], "$.escalation_approvals")
680
+ for i, rec in enumerate(art["escalation_approvals"]):
681
+ path = f"$.escalation_approvals[{i}]"
682
+ v.obj(rec, path, ["exception_id", "approval_state", "approved_by"])
683
+ v.pattern(rec["exception_id"], f"{path}.exception_id", _ID_RES["ESC"], "ESC-<n>")
684
+ v.enum(rec["approval_state"], f"{path}.approval_state", ("approved", "rejected"))
685
+ v.enum(rec["approved_by"], f"{path}.approved_by", ("user",))
686
+
687
+ v.array(art["reapplications"], "$.reapplications")
688
+ for i, rec in enumerate(art["reapplications"]):
689
+ path = f"$.reapplications[{i}]"
690
+ v.obj(rec, path, [
691
+ "reapplication_id", "item_id", "answer_refs", "pre_reapplication_verdict",
692
+ "reapplied_verdict", "rationale", "criterion_ref",
693
+ ], ["supersedes_reapplication_id", "evidence_anchor", "cannot_verify_reason", "residual_gap"])
694
+ v.pattern(rec["reapplication_id"], f"{path}.reapplication_id", _ID_RES["RAP"], "RAP-<n>")
695
+ v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
696
+ v.array(rec["answer_refs"], f"{path}.answer_refs")
697
+ if not rec["answer_refs"]:
698
+ v.fail(f"{path}.answer_refs", "must carry at least one tagged ref")
699
+ seen_refs = set()
700
+ for j, ref in enumerate(rec["answer_refs"]):
701
+ v.pattern(ref, f"{path}.answer_refs[{j}]", _ANSWER_REF_RE, "adjudication:DIS-<n> | intent:INT-<n>")
702
+ if ref in seen_refs:
703
+ v.fail(f"{path}.answer_refs", f"duplicate ref {ref!r}")
704
+ seen_refs.add(ref)
705
+ if "supersedes_reapplication_id" in rec:
706
+ v.pattern(rec["supersedes_reapplication_id"], f"{path}.supersedes_reapplication_id", _ID_RES["RAP"], "RAP-<n>")
707
+ v.enum(rec["pre_reapplication_verdict"], f"{path}.pre_reapplication_verdict", VERDICTS)
708
+ v.enum(rec["reapplied_verdict"], f"{path}.reapplied_verdict", VERDICTS)
709
+ if rec["reapplied_verdict"] == "CANNOT_VERIFY":
710
+ if "cannot_verify_reason" not in rec or "evidence_anchor" in rec:
711
+ v.fail(path, "CANNOT_VERIFY carries cannot_verify_reason and no evidence_anchor")
712
+ v.string(rec["cannot_verify_reason"], f"{path}.cannot_verify_reason")
713
+ else:
714
+ if "evidence_anchor" not in rec or "cannot_verify_reason" in rec:
715
+ v.fail(path, "non-CANNOT_VERIFY reapplications carry evidence_anchor and no cannot_verify_reason")
716
+ _validate_tagged_anchors(v, rec["evidence_anchor"], f"{path}.evidence_anchor")
717
+ if rec["reapplied_verdict"] == "PARTIALLY_ADDRESSED":
718
+ if "residual_gap" not in rec:
719
+ v.fail(path, "reapplied PARTIALLY_ADDRESSED requires residual_gap")
720
+ _validate_residual_gap(v, rec["residual_gap"], f"{path}.residual_gap")
721
+ elif "residual_gap" in rec:
722
+ v.fail(path, "residual_gap is required iff reapplied_verdict = PARTIALLY_ADDRESSED")
723
+ v.string(rec["rationale"], f"{path}.rationale")
724
+ v.pattern(rec["criterion_ref"], f"{path}.criterion_ref", _CRITERION_REF_RE, "phase1:REV-<id> | dissent:DIS-<n>")
725
+
726
+ di = art["decision_inputs"]
727
+ v.obj(di, "$.decision_inputs", [
728
+ "per_item", "verdict_counts", "residual_magnitude_counts", "p2_addressed_rate",
729
+ "regressions", "non_regression_new_issue_ids", "escalations", "apply_chain_witness",
730
+ ], ["reject_recommended"])
731
+ v.array(di["per_item"], "$.decision_inputs.per_item")
732
+ for i, entry in enumerate(di["per_item"]):
733
+ path = f"$.decision_inputs.per_item[{i}]"
734
+ v.obj(entry, path, ["item_id", "final_verdict", "driving_severity"], ["residual_magnitude"])
735
+ v.pattern(entry["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
736
+ v.enum(entry["final_verdict"], f"{path}.final_verdict", VERDICTS)
737
+ if entry["driving_severity"] is not None:
738
+ v.enum(entry["driving_severity"], f"{path}.driving_severity", SEVERITIES)
739
+ if "residual_magnitude" in entry:
740
+ v.enum(entry["residual_magnitude"], f"{path}.residual_magnitude", MAGNITUDES)
741
+ for bucket_name in ("verdict_counts", "residual_magnitude_counts"):
742
+ bucket = di[bucket_name]
743
+ v.obj(bucket, f"$.decision_inputs.{bucket_name}", list(PRIORITIES))
744
+ keys = VERDICTS if bucket_name == "verdict_counts" else MAGNITUDES
745
+ for prio in PRIORITIES:
746
+ v.obj(bucket[prio], f"$.decision_inputs.{bucket_name}.{prio}", list(keys))
747
+ for key in keys:
748
+ v.integer(bucket[prio][key], f"$.decision_inputs.{bucket_name}.{prio}.{key}", minimum=0)
749
+ rate = di["p2_addressed_rate"]
750
+ v.obj(rate, "$.decision_inputs.p2_addressed_rate", ["numerator", "denominator"])
751
+ v.integer(rate["numerator"], "$.decision_inputs.p2_addressed_rate.numerator", minimum=0)
752
+ v.integer(rate["denominator"], "$.decision_inputs.p2_addressed_rate.denominator", minimum=0)
753
+ v.array(di["regressions"], "$.decision_inputs.regressions")
754
+ for i, entry in enumerate(di["regressions"]):
755
+ path = f"$.decision_inputs.regressions[{i}]"
756
+ v.obj(entry, path, ["new_issue_id", "severity"])
757
+ v.pattern(entry["new_issue_id"], f"{path}.new_issue_id", _ID_RES["NEW"], "NEW-<n>")
758
+ v.enum(entry["severity"], f"{path}.severity", SEVERITIES)
759
+ v.array(di["non_regression_new_issue_ids"], "$.decision_inputs.non_regression_new_issue_ids")
760
+ for i, entry in enumerate(di["non_regression_new_issue_ids"]):
761
+ v.pattern(entry, f"$.decision_inputs.non_regression_new_issue_ids[{i}]", _ID_RES["NEW"], "NEW-<n>")
762
+ v.array(di["escalations"], "$.decision_inputs.escalations")
763
+ for i, entry in enumerate(di["escalations"]):
764
+ path = f"$.decision_inputs.escalations[{i}]"
765
+ v.obj(entry, path, ["exception_id", "effective_approval_state", "escalation_class", "mechanical_decision_impact"])
766
+ v.pattern(entry["exception_id"], f"{path}.exception_id", _ID_RES["ESC"], "ESC-<n>")
767
+ v.enum(entry["effective_approval_state"], f"{path}.effective_approval_state", ("pending", "approved", "rejected"))
768
+ v.enum(entry["escalation_class"], f"{path}.escalation_class", ESCALATION_CLASSES)
769
+ v.enum(entry["mechanical_decision_impact"], f"{path}.mechanical_decision_impact", ("Minor Revision", "Major Revision"))
770
+ if "reject_recommended" in di and not isinstance(di["reject_recommended"], bool):
771
+ v.fail("$.decision_inputs.reject_recommended", "must be a boolean")
772
+ v.enum(di["apply_chain_witness"], "$.decision_inputs.apply_chain_witness", WITNESS_STATES)
773
+
774
+
775
+ # --- manifest ------------------------------------------------------------------
776
+
777
+
778
+ def validate_manifest(manifest: dict):
779
+ """Structural manifest validation (§11). Failures -> manifest_incomplete."""
780
+
781
+ def fail(message: str):
782
+ raise ManifestError("manifest_incomplete", f"manifest: {message}")
783
+
784
+ if not isinstance(manifest, dict):
785
+ fail("must be an object")
786
+ expected_top = {"round_id", "cross_model_active", "artifacts"}
787
+ if set(manifest) != expected_top:
788
+ fail(f"top-level fields must be exactly {sorted(expected_top)}")
789
+ if not _is_str(manifest["round_id"]):
790
+ fail("round_id must be a non-empty string")
791
+ if not isinstance(manifest["cross_model_active"], bool):
792
+ fail("cross_model_active must be a boolean")
793
+ artifacts = manifest["artifacts"]
794
+ single_keys = (
795
+ "original_manuscript", "revised_manuscript", "revision_roadmap",
796
+ "editorial_decision_letter", "response_to_reviewers",
797
+ "round1_findings", "round1_config_cards",
798
+ )
799
+ array_keys = ("revision_patches", "apply_reports")
800
+ if not isinstance(artifacts, dict) or set(artifacts) != set(single_keys) | set(array_keys):
801
+ fail("artifacts must carry exactly the nine §11 keys")
802
+
803
+ def check_entry_fields(entry, key, path):
804
+ ref = entry.get("path_or_passport_ref")
805
+ if not _is_str(ref) or not _ARTIFACT_REF_RE.match(ref):
806
+ fail(f"{path}: path_or_passport_ref must be passport:<ref> | path:<relative path>")
807
+ if ref.startswith("path:"):
808
+ rel = ref[len("path:"):]
809
+ segments = re.split(r"[/\\]", rel)
810
+ if (
811
+ rel.startswith(("/", "\\"))
812
+ or re.match(r"^[A-Za-z]:", rel)
813
+ or any(segment == ".." for segment in segments)
814
+ or not rel
815
+ ):
816
+ fail(f"{path}: path: refs carry a RELATIVE path — no absolute, drive-letter, or traversal segments (§11)")
817
+ sha = entry.get("sha256")
818
+ if not isinstance(sha, str) or not _SHA256_RE.match(sha):
819
+ fail(f"{path}: sha256 must be 64-hex")
820
+ for field in ("version_label", "origin_date"):
821
+ value = entry.get(field, "MISSING")
822
+ if value == "MISSING":
823
+ fail(f"{path}: missing {field}")
824
+ if value is not None and not _is_str(value):
825
+ fail(f"{path}: {field} must be a non-empty string or null")
826
+ if value is None and entry["path_or_passport_ref"].startswith("passport:"):
827
+ fail(f"{path}: null {field} on a passport:-tagged entry (§11)")
828
+
829
+ for key in single_keys:
830
+ entry = artifacts[key]
831
+ if not isinstance(entry, dict) or not isinstance(entry.get("present"), bool):
832
+ fail(f"artifacts.{key}: present-discriminated union required")
833
+ if entry["present"]:
834
+ if set(entry) != {"present", "path_or_passport_ref", "sha256", "version_label", "origin_date"}:
835
+ fail(f"artifacts.{key}: present entry carries exactly ref/sha256/version_label/origin_date")
836
+ check_entry_fields(entry, key, f"artifacts.{key}")
837
+ elif set(entry) != {"present"}:
838
+ fail(f"artifacts.{key}: an absent artifact carries NO ref, hash, or freshness fields (§11)")
839
+ for key in array_keys:
840
+ entry = artifacts[key]
841
+ if not isinstance(entry, dict) or not isinstance(entry.get("present"), bool):
842
+ fail(f"artifacts.{key}: present-discriminated union required")
843
+ if entry["present"]:
844
+ if set(entry) != {"present", "items"}:
845
+ fail(f"artifacts.{key}: present array entry carries exactly items")
846
+ items = entry["items"]
847
+ if not isinstance(items, list) or not items:
848
+ fail(f"artifacts.{key}: present: false is the canonical empty encoding (§11)")
849
+ for i, element in enumerate(items):
850
+ if not isinstance(element, dict) or set(element) != {"path_or_passport_ref", "sha256", "version_label", "origin_date"}:
851
+ fail(f"artifacts.{key}[{i}]: every element FULLY populated (no per-element absence)")
852
+ check_entry_fields(element, key, f"artifacts.{key}[{i}]")
853
+ elif set(entry) != {"present"}:
854
+ fail(f"artifacts.{key}: an absent artifact carries NO ref, hash, or freshness fields (§11)")
855
+
856
+ # Required set + array pairing (G0).
857
+ for key in ("revised_manuscript", "revision_roadmap"):
858
+ if not artifacts[key]["present"]:
859
+ raise ManifestError("manifest_incomplete", f"manifest: hard-required artifact {key} absent (§11)")
860
+ patches = artifacts["revision_patches"]
861
+ reports = artifacts["apply_reports"]
862
+ if reports["present"]:
863
+ if not patches["present"]:
864
+ raise ManifestError("manifest_incomplete", "manifest: apply_reports present with revision_patches absent (§11)")
865
+ if len(reports["items"]) != len(patches["items"]):
866
+ raise ManifestError("manifest_incomplete", "manifest: apply_reports / revision_patches length mismatch (§11)")
867
+
868
+ # Freshness: a roadmap older than the letter it should pair with fails
869
+ # closed (§11). Comparable only when both origin_dates are non-null.
870
+ roadmap_entry = artifacts["revision_roadmap"]
871
+ letter_entry = artifacts["editorial_decision_letter"]
872
+ if letter_entry["present"] and roadmap_entry["origin_date"] is not None and letter_entry["origin_date"] is not None:
873
+ if roadmap_entry["origin_date"] < letter_entry["origin_date"]:
874
+ raise ManifestError(
875
+ "manifest_hash_mismatch",
876
+ "manifest: revision_roadmap origin_date predates editorial_decision_letter (freshness, §11)",
877
+ )
878
+
879
+
880
+ def _hash_prefix(sha256_hex: str) -> str:
881
+ """§11: apply reports use the 12-hex base_draft_hash format."""
882
+ return sha256_hex[:12]
883
+
884
+
885
+ def compute_apply_chain_witness(manifest: dict, report_payloads):
886
+ """§11 apply-chain witness over the ordered report payloads.
887
+
888
+ Returns (witness, notes). A broken checked link raises ManifestError
889
+ (manifest_hash_mismatch, G0) — positive breakage evidence outranks the
890
+ absence markers, matching the declared precedence
891
+ fail > not_run_no_reports > first_link_not_run > pass.
892
+ """
893
+ artifacts = manifest["artifacts"]
894
+ notes = []
895
+ if not report_payloads:
896
+ return "not_run_no_reports", notes
897
+ patches = artifacts["revision_patches"]["items"]
898
+ for i, report in enumerate(report_payloads):
899
+ if not isinstance(report, dict):
900
+ raise ManifestError("manifest_incomplete", f"apply report [{i}]: must be a JSON object")
901
+ for field in ("report_format_version", "base_draft_hash", "output_draft_hash"):
902
+ if not _is_str(report.get(field)):
903
+ raise ManifestError("manifest_incomplete", f"apply report [{i}]: missing {field}")
904
+ if not _REPORT_VERSION_RE.match(report["report_format_version"]):
905
+ raise ManifestError(
906
+ "manifest_incomplete",
907
+ f"apply report [{i}]: report_format_version {report['report_format_version']!r} is not a numeric "
908
+ "dotted version — the pre-1.2 absence policy applies only to a VALID explicit version below 1.2 (§11)",
909
+ )
910
+ for field in ("base_draft_hash", "output_draft_hash"):
911
+ if not _HASH12_RE.match(report[field]):
912
+ raise ManifestError(
913
+ "manifest_incomplete",
914
+ f"apply report [{i}]: {field} must be the 12-hex base_draft_hash format (§11)",
915
+ )
916
+ version = tuple(int(part) for part in report["report_format_version"].split("."))
917
+ if version >= (1, 2):
918
+ digest = report.get("patch_digest")
919
+ if not isinstance(digest, str) or not _SHA256_RE.match(digest):
920
+ raise ManifestError("manifest_incomplete", f"apply report [{i}]: format >= 1.2 requires patch_digest")
921
+ if digest != patches[i]["sha256"]:
922
+ raise ManifestError(
923
+ "manifest_hash_mismatch",
924
+ f"apply report [{i}]: patch_digest does not equal the paired revision_patches[{i}].sha256 (§11 content-bound pairing)",
925
+ )
926
+ else:
927
+ notes.append(f"[PATCH-BINDING-ABSENT: report format < 1.2] (apply report [{i}])")
928
+ first_link_not_run = False
929
+ original = artifacts["original_manuscript"]
930
+ if original["present"]:
931
+ if report_payloads[0]["base_draft_hash"] != _hash_prefix(original["sha256"]):
932
+ raise ManifestError(
933
+ "manifest_hash_mismatch",
934
+ "apply-chain witness: first report base_draft_hash does not equal the original_manuscript hash prefix (§11)",
935
+ )
936
+ else:
937
+ first_link_not_run = True
938
+ for i in range(1, len(report_payloads)):
939
+ if report_payloads[i]["base_draft_hash"] != report_payloads[i - 1]["output_draft_hash"]:
940
+ raise ManifestError(
941
+ "manifest_hash_mismatch",
942
+ f"apply-chain witness: report [{i}] base_draft_hash does not equal report [{i - 1}] output_draft_hash (§11)",
943
+ )
944
+ revised = artifacts["revised_manuscript"]
945
+ if report_payloads[-1]["output_draft_hash"] != _hash_prefix(revised["sha256"]):
946
+ raise ManifestError(
947
+ "manifest_hash_mismatch",
948
+ "apply-chain witness: last report output_draft_hash does not equal the revised_manuscript hash prefix (§11)",
949
+ )
950
+ return ("first_link_not_run" if first_link_not_run else "pass"), notes
951
+
952
+
953
+ # --- letter + roadmap parsing --------------------------------------------------
954
+
955
+ _LETTER_SECTION_RE = re.compile(r"^### Required Item Details\s*$")
956
+ _LETTER_BLOCK_RE = re.compile(r"^\*\*(R[1-9][0-9]*): .*\*\*\s*$")
957
+ _LETTER_CRITERIA_RE = re.compile(r"^- \*\*Acceptance criteria\*\*: (.*)$")
958
+
959
+
960
+ def parse_letter_blocks(text: str):
961
+ """Extract the letter's Required Item Details blocks in document order.
962
+
963
+ Returns an ordered list of (rid, acceptance_text) where acceptance_text
964
+ is the single-line payload after the ``- **Acceptance criteria**:``
965
+ bullet, or None when the block carries no such bullet (a block without
966
+ an Acceptance-criteria field offers nothing to inherit — treated as
967
+ no-block-at-ordinal for the §5.1 letter-fields rule; its id still
968
+ participates in the contiguity check).
969
+ """
970
+ lines = text.splitlines()
971
+ blocks = []
972
+ in_section = False
973
+ current = None
974
+ for line in lines:
975
+ if _LETTER_SECTION_RE.match(line):
976
+ in_section = True
977
+ continue
978
+ if not in_section:
979
+ continue
980
+ if line.startswith("## "):
981
+ break
982
+ header = _LETTER_BLOCK_RE.match(line)
983
+ if header:
984
+ current = [header.group(1), None]
985
+ blocks.append(current)
986
+ continue
987
+ if current is not None and current[1] is None:
988
+ criteria = _LETTER_CRITERIA_RE.match(line)
989
+ if criteria:
990
+ current[1] = criteria.group(1)
991
+ return [(rid, text_value) for rid, text_value in blocks]
992
+
993
+
994
+ def letter_ordinals_sound(blocks, must_fix_count: int) -> bool:
995
+ """§5.1 contiguity: ids EXACTLY the contiguous sequence R1..Rk, k <= n.
996
+
997
+ A gap, duplicate, or extra id (beyond the must_fix count) makes the
998
+ ordinal derivation unsound and degrades the WHOLE letter layer to
999
+ absent; a mere count shortfall (contiguous prefix) does not.
1000
+ """
1001
+ for i, (rid, _text) in enumerate(blocks):
1002
+ if rid != f"R{i + 1}":
1003
+ return False
1004
+ return len(blocks) <= must_fix_count
1005
+
1006
+
1007
+ def load_roadmap(payload: dict):
1008
+ """Minimal Schema 7 machine-form validation for checker consumption."""
1009
+
1010
+ def fail(message: str):
1011
+ raise ManifestError("manifest_incomplete", f"revision_roadmap: {message}")
1012
+
1013
+ if not isinstance(payload, dict) or not isinstance(payload.get("items"), list):
1014
+ fail("must be an object with an items array (Schema 7)")
1015
+ items = []
1016
+ seen = set()
1017
+ for i, item in enumerate(payload["items"]):
1018
+ if not isinstance(item, dict):
1019
+ fail(f"items[{i}] must be an object")
1020
+ for field in ("id", "priority", "verification_criteria", "reviewer"):
1021
+ if not _is_str(item.get(field)):
1022
+ fail(f"items[{i}].{field} must be a non-empty string")
1023
+ if not _ITEM_ID_RE.match(item["id"]):
1024
+ fail(f"items[{i}].id must be REV-<id>")
1025
+ if item["id"] in seen:
1026
+ fail(f"duplicate roadmap id {item['id']!r}")
1027
+ seen.add(item["id"])
1028
+ if item["priority"] not in PRIORITIES:
1029
+ fail(f"items[{i}].priority must be one of {PRIORITIES}")
1030
+ severity = item.get("severity")
1031
+ if severity is not None and severity not in SEVERITIES:
1032
+ fail(f"items[{i}].severity must be one of {SEVERITIES} when present")
1033
+ source_kind = item.get("source_kind")
1034
+ if source_kind is not None and source_kind not in ("question", "editorial"):
1035
+ fail(f"items[{i}].source_kind must be question|editorial when present")
1036
+ items.append(item)
1037
+ return items
1038
+
1039
+
1040
+ # --- synthesis recomputation ---------------------------------------------------
1041
+
1042
+
1043
+ class Failures:
1044
+ def __init__(self):
1045
+ self.messages = []
1046
+
1047
+ def add(self, message: str):
1048
+ self.messages.append(message)
1049
+
1050
+
1051
+ def _decision_max(*decisions):
1052
+ return max(decisions, key=lambda d: DECISION_ORDER[d])
1053
+
1054
+
1055
+ def derive_decision(p1_items, p2_partial_magnitudes, p2_made_worse, rate_num, rate_den, regression_severities, escalations):
1056
+ """§6 Steps 2-3. ``p1_items`` = [(final_verdict, driving_severity, residual_magnitude)].
1057
+
1058
+ Returns (decision, reject_recommended, base_rule).
1059
+ """
1060
+ p1_count = len(p1_items)
1061
+ rate_below_80 = rate_den > 0 and rate_num * 5 < rate_den * 4 # < 80%, integer-exact
1062
+ base = None
1063
+ rule = None
1064
+ if any(v == "MADE_WORSE" and sev == "critical" for v, sev, _m in p1_items) or ("critical" in regression_severities):
1065
+ base, rule = "Major Revision", "B1"
1066
+ elif p1_count > 0 and 2 * sum(1 for v, _s, _m in p1_items if v in ("NOT_ADDRESSED", "MADE_WORSE")) >= p1_count:
1067
+ base, rule = "Major Revision", "B2"
1068
+ elif any(v in ("NOT_ADDRESSED", "MADE_WORSE", "CANNOT_VERIFY") for v, _s, _m in p1_items) or ("major" in regression_severities):
1069
+ base, rule = "Major Revision", "B3"
1070
+ elif any(v == "PARTIALLY_ADDRESSED" and m == "must_fix" for v, _s, m in p1_items) or ("must_fix" in p2_partial_magnitudes):
1071
+ base, rule = "Major Revision", "B4"
1072
+ elif (
1073
+ any(v == "PARTIALLY_ADDRESSED" and m in ("should_fix", "consider") for v, _s, m in p1_items)
1074
+ or rate_below_80
1075
+ or p2_made_worse
1076
+ or ("minor" in regression_severities)
1077
+ ):
1078
+ base, rule = "Minor Revision", "B5"
1079
+ else:
1080
+ base, rule = "Accept", "B6"
1081
+ decision = base
1082
+ reject = rule in ("B1", "B2")
1083
+ for esc in escalations:
1084
+ if esc["effective_approval_state"] == "approved":
1085
+ decision = _decision_max(decision, esc["mechanical_decision_impact"])
1086
+ if esc["escalation_class"] == "research_integrity":
1087
+ reject = True
1088
+ return decision, reject, rule
1089
+
1090
+
1091
+ def _adjustment_chains(traceability, fails: Failures):
1092
+ """Build per-item ordered adjustment chains (§5.3 grammar).
1093
+
1094
+ Returns {item_id: [records head..tail]} (empty dict entries omitted);
1095
+ reports every grammar violation into ``fails``.
1096
+ """
1097
+ by_id = {}
1098
+ for rec in traceability["adjustments"]:
1099
+ if rec["adjustment_id"] in by_id:
1100
+ fails.add(f"adjustment ids must be unique: {rec['adjustment_id']}")
1101
+ return {}
1102
+ by_id[rec["adjustment_id"]] = rec
1103
+ chains = {}
1104
+ by_item = {}
1105
+ for rec in traceability["adjustments"]:
1106
+ by_item.setdefault(rec["item_id"], []).append(rec)
1107
+ for item_id, records in by_item.items():
1108
+ superseded = {}
1109
+ heads = []
1110
+ for rec in records:
1111
+ prev = rec.get("supersedes_adjustment_id")
1112
+ if prev is None:
1113
+ heads.append(rec)
1114
+ else:
1115
+ prev_rec = by_id.get(prev)
1116
+ if prev_rec is None or prev_rec["item_id"] != item_id:
1117
+ fails.add(f"adjustment {rec['adjustment_id']}: supersedes_adjustment_id {prev!r} does not resolve on item {item_id}")
1118
+ return {}
1119
+ if prev in superseded:
1120
+ fails.add(f"adjustment chain on {item_id}: {prev} superseded twice")
1121
+ return {}
1122
+ superseded[prev] = rec
1123
+ if len(heads) != 1:
1124
+ fails.add(f"adjustment chain on {item_id}: exactly one head required, found {len(heads)}")
1125
+ return {}
1126
+ chain = [heads[0]]
1127
+ while chain[-1]["adjustment_id"] in superseded:
1128
+ chain.append(superseded[chain[-1]["adjustment_id"]])
1129
+ if len(chain) != len(records):
1130
+ fails.add(f"adjustment chain on {item_id}: records outside the linear chain (cycle or fork)")
1131
+ return {}
1132
+ for prev_rec, next_rec in zip(chain, chain[1:]):
1133
+ if next_rec["from_verdict"] != prev_rec["to_verdict"]:
1134
+ fails.add(
1135
+ f"adjustment chain on {item_id}: {next_rec['adjustment_id']}.from_verdict "
1136
+ f"!= {prev_rec['adjustment_id']}.to_verdict (head-to-tail join)"
1137
+ )
1138
+ chains[item_id] = chain
1139
+ return chains
1140
+
1141
+
1142
+ def check(manifest, precommitment, verdict_record, traceability, roadmap_items, letter_blocks, witness, witness_notes):
1143
+ """All §13 recomputation on schema-valid, hash-bound inputs.
1144
+
1145
+ Returns (failures, warnings, recomputed_outcome) where
1146
+ recomputed_outcome is the Step 1-3 emission expectation.
1147
+ """
1148
+ fails = Failures()
1149
+ warnings = list(witness_notes)
1150
+
1151
+ roadmap_by_id = {item["id"]: item for item in roadmap_items}
1152
+ must_fix_order = [item["id"] for item in roadmap_items if item["priority"] == "must_fix"]
1153
+ p12_ids = {item["id"] for item in roadmap_items if item["priority"] in ("must_fix", "should_fix")}
1154
+ cross_model_active = manifest["cross_model_active"]
1155
+
1156
+ # round_id equality across all artifacts.
1157
+ for name, art in (("precommitment", precommitment), ("verdict_record", verdict_record), ("traceability", traceability)):
1158
+ if art["round_id"] != manifest["round_id"]:
1159
+ fails.add(f"{name}.round_id != manifest.round_id")
1160
+
1161
+ # --- Phase 1 coverage + criterion binding (§4/§5.1) ---
1162
+ pre_by_item = {}
1163
+ for rec in precommitment["items"]:
1164
+ if rec["item_id"] in pre_by_item:
1165
+ fails.add(f"precommitment: duplicate record for {rec['item_id']}")
1166
+ pre_by_item[rec["item_id"]] = rec
1167
+ if set(pre_by_item) != p12_ids:
1168
+ fails.add(
1169
+ "precommitment coverage: records must exist for exactly the must_fix/should_fix roadmap items "
1170
+ f"(missing {sorted(p12_ids - set(pre_by_item))}, extra {sorted(set(pre_by_item) - p12_ids)})"
1171
+ )
1172
+ letter_present = manifest["artifacts"]["editorial_decision_letter"]["present"]
1173
+ letter_sound = letter_blocks is not None and letter_ordinals_sound(letter_blocks, len(must_fix_order))
1174
+ if letter_present and letter_blocks is not None and not letter_sound:
1175
+ warnings.append("[CRITERIA-LAYER-ABSENT: letter/roadmap ordinal mismatch]")
1176
+ if letter_present and letter_sound and not letter_blocks:
1177
+ warnings.append(
1178
+ "NOTE: editorial decision letter present but no Required Item Details blocks parsed — the level-2 "
1179
+ "criteria layer is empty (template drift or a genuinely block-less letter)"
1180
+ )
1181
+ block_by_rid = {}
1182
+ if letter_sound and letter_blocks:
1183
+ block_by_rid = {rid: text for rid, text in letter_blocks}
1184
+ for item_id, rec in pre_by_item.items():
1185
+ item = roadmap_by_id.get(item_id)
1186
+ if item is None:
1187
+ continue
1188
+ if rec["priority"] != item["priority"]:
1189
+ fails.add(f"precommitment {item_id}: priority {rec['priority']} != roadmap {item['priority']}")
1190
+ if rec["inherited_criterion"]["roadmap_text"] != item["verification_criteria"]:
1191
+ fails.add(f"precommitment {item_id}: inherited_criterion.roadmap_text does not match the roadmap verbatim (§4)")
1192
+ if rec["source_reviewer"] != item["reviewer"]:
1193
+ fails.add(
1194
+ f"precommitment {item_id}: source_reviewer is a VERBATIM copy of the Schema 7 reviewer field "
1195
+ f"({rec['source_reviewer']!r} != {item['reviewer']!r}, §5.1)"
1196
+ )
1197
+ labels, _parse_failure = normalize_reviewer_labels(rec["source_reviewer"])
1198
+ if rec["source_reviewer_labels"] != labels:
1199
+ fails.add(
1200
+ f"precommitment {item_id}: source_reviewer_labels {rec['source_reviewer_labels']!r} "
1201
+ f"!= §10 normalization of source_reviewer ({labels!r})"
1202
+ )
1203
+ has_letter_fields = "letter_text" in rec["inherited_criterion"]
1204
+ if item["priority"] == "must_fix":
1205
+ ordinal = must_fix_order.index(item_id) + 1
1206
+ derived_ref = f"R{ordinal}"
1207
+ block_text = block_by_rid.get(derived_ref) if letter_present and letter_sound else None
1208
+ if block_text is not None:
1209
+ if not has_letter_fields:
1210
+ fails.add(f"precommitment {item_id}: letter carries block {derived_ref} — letter_text/letter_item_ref REQUIRED (§5.1)")
1211
+ else:
1212
+ if rec["inherited_criterion"]["letter_item_ref"] != derived_ref:
1213
+ fails.add(
1214
+ f"precommitment {item_id}: letter_item_ref {rec['inherited_criterion']['letter_item_ref']!r} "
1215
+ f"!= derived ordinal {derived_ref!r} (§5.1 — the ref is DERIVED, never chosen)"
1216
+ )
1217
+ if rec["inherited_criterion"]["letter_text"] != block_text:
1218
+ fails.add(f"precommitment {item_id}: letter_text does not match the letter's Acceptance criteria byte-for-byte (§13)")
1219
+ elif has_letter_fields:
1220
+ fails.add(f"precommitment {item_id}: letter layer absent at this item — letter fields must be absent (§5.1/§13)")
1221
+ elif has_letter_fields:
1222
+ fails.add(f"precommitment {item_id}: letter fields on a non-must_fix item (§5.1)")
1223
+
1224
+ # --- Phase 2A coverage + applied_criterion (§5.2) ---
1225
+ verdict_by_item = {}
1226
+ for rec in verdict_record["items"]:
1227
+ if rec["item_id"] in verdict_by_item:
1228
+ fails.add(f"verdict_record: duplicate record for {rec['item_id']}")
1229
+ verdict_by_item[rec["item_id"]] = rec
1230
+ if set(verdict_by_item) != set(roadmap_by_id):
1231
+ fails.add(
1232
+ "verdict_record coverage: exactly one record per roadmap item, ALL priorities "
1233
+ f"(missing {sorted(set(roadmap_by_id) - set(verdict_by_item))}, extra {sorted(set(verdict_by_item) - set(roadmap_by_id))})"
1234
+ )
1235
+ dissent_by_id = {}
1236
+ for rec in verdict_record["dissents"]:
1237
+ if rec["dissent_id"] in dissent_by_id:
1238
+ fails.add(f"verdict_record: duplicate dissent_id {rec['dissent_id']}")
1239
+ dissent_by_id[rec["dissent_id"]] = rec
1240
+ pre = pre_by_item.get(rec["item_id"])
1241
+ if pre is None:
1242
+ fails.add(f"dissent {rec['dissent_id']}: item {rec['item_id']} has no pre-commitment record")
1243
+ elif rec["criterion_hash"] != canonical_hash(pre):
1244
+ fails.add(f"dissent {rec['dissent_id']}: criterion_hash does not recompute from the Phase-1 record (§7)")
1245
+ # Reverse witness (§7): a dissent swaps the criterion BEFORE the
1246
+ # verdict — "The item's verdict record then carries
1247
+ # applied_criterion: dissented:<dissent_id>". A ghost dissent that
1248
+ # was never applied cannot exist, trip the §7 bound, or authorize
1249
+ # an original_upheld re-application of a criterion that was in
1250
+ # fact applied at 2A.
1251
+ applied = verdict_by_item.get(rec["item_id"], {}).get("applied_criterion")
1252
+ if applied != f"dissented:{rec['dissent_id']}":
1253
+ fails.add(
1254
+ f"dissent {rec['dissent_id']}: the item's verdict record must carry "
1255
+ f"applied_criterion dissented:{rec['dissent_id']} — a dissent that was never applied is a ghost (§7)"
1256
+ )
1257
+ # §7 bounds (SD-5): dissent on a P1 item, or dissents on > ceil(N/3) of
1258
+ # all items, trips independent adjudication of EVERY dissent record.
1259
+ total_items = len(roadmap_items)
1260
+ bound_tripped = any(
1261
+ roadmap_by_id.get(dissent["item_id"], {}).get("priority") == "must_fix"
1262
+ for dissent in dissent_by_id.values()
1263
+ ) or (len(dissent_by_id) > math.ceil(total_items / 3))
1264
+ for item_id, rec in verdict_by_item.items():
1265
+ item = roadmap_by_id.get(item_id)
1266
+ if item is None:
1267
+ continue
1268
+ applied = rec["applied_criterion"]
1269
+ if item["priority"] == "consider":
1270
+ if applied != "not_precommitted":
1271
+ fails.add(f"verdict {item_id}: consider items carry applied_criterion not_precommitted (§5.2)")
1272
+ else:
1273
+ if applied == "not_precommitted":
1274
+ fails.add(f"verdict {item_id}: not_precommitted is valid ONLY for consider items (§5.2)")
1275
+ elif applied.startswith("dissented:"):
1276
+ dissent = dissent_by_id.get(applied.split(":", 1)[1])
1277
+ if dissent is None or dissent["item_id"] != item_id:
1278
+ fails.add(f"verdict {item_id}: applied_criterion {applied!r} does not resolve to a dissent on this item")
1279
+
1280
+ ns_by_id = {rec["new_standard_id"]: rec for rec in precommitment["new_standards"]}
1281
+ if not manifest["artifacts"]["original_manuscript"]["present"] and verdict_record["escalation_exceptions"]:
1282
+ fails.add(
1283
+ "escalation exceptions cannot be substantiated with the original manuscript absent — the required "
1284
+ "original-text anchor cannot be produced, so the new_standard stays advisory "
1285
+ "([ESCALATION-UNSUBSTANTIATABLE], §11 degradation (iii))"
1286
+ )
1287
+ exceptions_by_id = {}
1288
+ for rec in verdict_record["escalation_exceptions"]:
1289
+ if rec["exception_id"] in exceptions_by_id:
1290
+ fails.add(f"verdict_record: duplicate exception_id {rec['exception_id']}")
1291
+ exceptions_by_id[rec["exception_id"]] = rec
1292
+ ref = rec.get("new_standard_ref")
1293
+ if ref is not None:
1294
+ target = ns_by_id.get(ref)
1295
+ if target is None:
1296
+ fails.add(f"escalation exception {rec['exception_id']}: new_standard_ref {ref!r} does not resolve")
1297
+ elif target["classification"] != "escalation_requested":
1298
+ fails.add(f"escalation exception {rec['exception_id']}: new_standard_ref target is not escalation_requested (§5.1)")
1299
+
1300
+ # --- new-issue freeze witness (§13.5) ---
1301
+ if traceability["new_issues"] != verdict_record["new_issues"]:
1302
+ fails.add("new_issues: §5.3 set is not WHOLE-RECORD byte-identical to the frozen §5.2 set (§3.3 freeze witness)")
1303
+ new_issue_by_id = {}
1304
+ for rec in verdict_record["new_issues"]:
1305
+ if rec["new_issue_id"] in new_issue_by_id:
1306
+ fails.add(f"verdict_record: duplicate new_issue_id {rec['new_issue_id']}")
1307
+ new_issue_by_id[rec["new_issue_id"]] = rec
1308
+ nearest = rec["nearest_roadmap_item"]
1309
+ if nearest is not None and nearest not in roadmap_by_id:
1310
+ fails.add(f"new issue {rec['new_issue_id']}: nearest_roadmap_item {nearest!r} does not resolve")
1311
+ if rec["attribution"] == "regression" and nearest is not None:
1312
+ warnings.append(
1313
+ f"ADVISORY: regression {rec['new_issue_id']} names nearest_roadmap_item {nearest} — "
1314
+ "review for roadmap-item overlap (§8)"
1315
+ )
1316
+ if not manifest["artifacts"]["original_manuscript"]["present"] and rec["attribution"] != "indeterminate":
1317
+ fails.add(
1318
+ f"new issue {rec['new_issue_id']}: with the original manuscript absent every attribution is "
1319
+ "indeterminate, never a guess (§11 degradation (i))"
1320
+ )
1321
+
1322
+ # --- rows (§5.3) ---
1323
+ row_by_item = {}
1324
+ for row in traceability["rows"]:
1325
+ if row["item_id"] in row_by_item:
1326
+ fails.add(f"traceability: duplicate row for {row['item_id']}")
1327
+ row_by_item[row["item_id"]] = row
1328
+ if set(row_by_item) != set(roadmap_by_id):
1329
+ fails.add(
1330
+ "row coverage: EVERY roadmap item, all priorities, has exactly one row "
1331
+ f"(missing {sorted(set(roadmap_by_id) - set(row_by_item))}, extra {sorted(set(row_by_item) - set(roadmap_by_id))})"
1332
+ )
1333
+ for item_id, row in row_by_item.items():
1334
+ item = roadmap_by_id.get(item_id)
1335
+ if item is None:
1336
+ continue
1337
+ if ROW_PRIORITY_MAP[row["priority"]] != item["priority"]:
1338
+ fails.add(f"row {item_id}: priority {row['priority']} does not match the roadmap ({item['priority']})")
1339
+ vrec = verdict_by_item.get(item_id)
1340
+ if vrec is not None:
1341
+ if row["phase2a_verdict"] != vrec["verdict"]:
1342
+ fails.add(f"row {item_id}: phase2a_verdict != committed §5.2 verdict")
1343
+ if row["verified_by"] != vrec["verified_by"]:
1344
+ fails.add(f"row {item_id}: verified_by not copied from the §5.2 verdict record")
1345
+ if row["status"] != row["final_verdict"]:
1346
+ fails.add(f"row {item_id}: status must map 1:1 from final_verdict (§5.3)")
1347
+ if row["verified"] != VERIFIED_MAP[row["final_verdict"]]:
1348
+ fails.add(f"row {item_id}: verified must follow the mechanical 5->4 map (§5.3)")
1349
+ if "cross_model_verdict" in row:
1350
+ derived = "agree" if row["cross_model_verdict"] == row["final_verdict"] else "diverges"
1351
+ if row["cross_model_status"] != derived:
1352
+ fails.add(f"row {item_id}: cross_model_status {row['cross_model_status']!r} != per-emission derivation {derived!r} (§5.3)")
1353
+
1354
+ # --- adjustment chains (§5.3) ---
1355
+ chains = _adjustment_chains(traceability, fails)
1356
+ for item_id, row in row_by_item.items():
1357
+ chain = chains.get(item_id)
1358
+ if chain:
1359
+ if row.get("adjustment_id") != chain[-1]["adjustment_id"]:
1360
+ fails.add(f"row {item_id}: adjustment_id must name the chain's LATEST record")
1361
+ if chain[0]["from_verdict"] != row["phase2a_verdict"]:
1362
+ fails.add(f"adjustment chain on {item_id}: head from_verdict != phase2a_verdict")
1363
+ if chain[-1]["to_verdict"] != row["final_verdict"]:
1364
+ fails.add(f"adjustment chain on {item_id}: tail to_verdict != final_verdict")
1365
+ else:
1366
+ if "adjustment_id" in row:
1367
+ fails.add(f"row {item_id}: adjustment_id present with no adjustment chain")
1368
+ tail_rebuttal = bool(chain) and chain[-1]["basis"] == "valid_rebuttal" and chain[-1]["to_verdict"] == "FULLY_ADDRESSED"
1369
+ if bool(row.get("addressed_by_rebuttal")) != tail_rebuttal:
1370
+ fails.add(f"row {item_id}: addressed_by_rebuttal marker must reflect a valid_rebuttal chain-tail upgrade to FULLY_ADDRESSED (§3.4)")
1371
+ for item_id in chains:
1372
+ if item_id not in row_by_item:
1373
+ fails.add(f"adjustment chain on {item_id}: no matrix row exists")
1374
+
1375
+ # --- deferral-loop referential integrity (§5.3/§6) ---
1376
+ intents_by_id = {}
1377
+ for rec in traceability["resolution_intents"]:
1378
+ if rec["intent_id"] in intents_by_id:
1379
+ fails.add(f"duplicate intent_id {rec['intent_id']}")
1380
+ intents_by_id[rec["intent_id"]] = rec
1381
+ if rec["answered_by"] == "system":
1382
+ # §5.3: system intents are emitted for every P1 diverges row —
1383
+ # the row must be an EVALUATED P1 row (cross_model_verdict
1384
+ # present implies an active configuration).
1385
+ intent_row = row_by_item.get(rec["item_id"])
1386
+ if intent_row is None or intent_row["priority"] != "MUST_FIX" or "cross_model_verdict" not in intent_row:
1387
+ fails.add(
1388
+ f"resolution intent {rec['intent_id']}: system intents exist only for evaluated P1 diverges "
1389
+ "rows under ACTIVE cross-model (§5.3)"
1390
+ )
1391
+ adjudications_by_dissent = {}
1392
+ for rec in traceability["dissent_adjudications"]:
1393
+ if rec["dissent_id"] in adjudications_by_dissent:
1394
+ fails.add(f"duplicate dissent adjudication for {rec['dissent_id']}")
1395
+ adjudications_by_dissent[rec["dissent_id"]] = rec
1396
+ if rec["dissent_id"] not in dissent_by_id:
1397
+ fails.add(f"dissent adjudication {rec['dissent_id']}: no such dissent record")
1398
+ if rec["adjudicator"] == "cross_model" and not cross_model_active:
1399
+ fails.add(f"dissent adjudication {rec['dissent_id']}: cross_model adjudicator on an inactive configuration (§11)")
1400
+ if not bound_tripped:
1401
+ fails.add(
1402
+ f"dissent adjudication {rec['dissent_id']}: no §7 bound tripped — dissents below the bound stand "
1403
+ "unadjudicated by design (§7)"
1404
+ )
1405
+ dissent = dissent_by_id.get(rec["dissent_id"])
1406
+ dissent_item = roadmap_by_id.get(dissent["item_id"]) if dissent else None
1407
+ if rec["adjudicator"] == "cross_model":
1408
+ if dissent_item is None or dissent_item["priority"] != "must_fix":
1409
+ fails.add(
1410
+ f"dissent adjudication {rec['dissent_id']}: the judge's adjudication scope EQUALS the §9 pass's "
1411
+ "P1 coverage — dissents on non-P1 items always take the G2(a) user path, even on an active "
1412
+ "setup (§7)"
1413
+ )
1414
+ # A `user` adjudication on an active-setup P1 dissent stays LEGAL:
1415
+ # the §6 deferral loop records "a user-adjudicated DissentAdjudication
1416
+ # ... DIRECTLY" without an activity qualifier, and the §9 pass can be
1417
+ # per-row unavailable — rejecting the user path would make a
1418
+ # judge-transport failure unrecoverable (adjudicated against the
1419
+ # stricter one-way reading; see PR #605 round-4/5 record).
1420
+ reapp_by_id = {}
1421
+ for rec in traceability["reapplications"]:
1422
+ if rec["reapplication_id"] in reapp_by_id:
1423
+ fails.add(f"duplicate reapplication_id {rec['reapplication_id']}")
1424
+ reapp_by_id[rec["reapplication_id"]] = rec
1425
+ superseded_reapps = {}
1426
+ for rec in traceability["reapplications"]:
1427
+ prev = rec.get("supersedes_reapplication_id")
1428
+ if prev is not None:
1429
+ prev_rec = reapp_by_id.get(prev)
1430
+ if prev_rec is None or prev_rec["item_id"] != rec["item_id"]:
1431
+ fails.add(f"reapplication {rec['reapplication_id']}: supersedes_reapplication_id {prev!r} does not resolve on the same item")
1432
+ elif prev in superseded_reapps:
1433
+ fails.add(f"reapplication supersession: {prev} superseded twice")
1434
+ else:
1435
+ superseded_reapps[prev] = rec["reapplication_id"]
1436
+ # acyclicity of supersession
1437
+ for rec in traceability["reapplications"]:
1438
+ seen_ids = set()
1439
+ cursor = rec
1440
+ while cursor is not None:
1441
+ if cursor["reapplication_id"] in seen_ids:
1442
+ fails.add(f"reapplication supersession cycle at {cursor['reapplication_id']}")
1443
+ break
1444
+ seen_ids.add(cursor["reapplication_id"])
1445
+ prev = cursor.get("supersedes_reapplication_id")
1446
+ cursor = reapp_by_id.get(prev) if prev is not None else None
1447
+ current_reapps = [rec for rec in traceability["reapplications"] if rec["reapplication_id"] not in superseded_reapps]
1448
+
1449
+ def _chain_tail_verdict(item_id):
1450
+ chain = chains.get(item_id)
1451
+ if chain:
1452
+ return chain[-1]["to_verdict"]
1453
+ row = row_by_item.get(item_id)
1454
+ return row["phase2a_verdict"] if row else None
1455
+
1456
+ for rec in traceability["reapplications"]:
1457
+ item_id = rec["item_id"]
1458
+ has_adjudication_ref = False
1459
+ has_intent_ref = False
1460
+ for ref in rec["answer_refs"]:
1461
+ kind, ref_id = ref.split(":", 1)
1462
+ if kind == "intent":
1463
+ has_intent_ref = True
1464
+ if kind == "adjudication":
1465
+ has_adjudication_ref = True
1466
+ adjudication = adjudications_by_dissent.get(ref_id)
1467
+ dissent = dissent_by_id.get(ref_id)
1468
+ if adjudication is None or dissent is None:
1469
+ fails.add(f"reapplication {rec['reapplication_id']}: answer ref {ref!r} does not resolve")
1470
+ continue
1471
+ if adjudication["outcome"] != "original_upheld":
1472
+ fails.add(f"reapplication {rec['reapplication_id']}: adjudication ref {ref!r} must resolve to original_upheld (§6)")
1473
+ if dissent["item_id"] != item_id:
1474
+ fails.add(f"reapplication {rec['reapplication_id']}: adjudication ref {ref!r} is on a different item")
1475
+ else:
1476
+ intent = intents_by_id.get(ref_id)
1477
+ if intent is None:
1478
+ fails.add(f"reapplication {rec['reapplication_id']}: answer ref {ref!r} does not resolve")
1479
+ elif intent["item_id"] != item_id:
1480
+ fails.add(f"reapplication {rec['reapplication_id']}: intent ref {ref!r} is on a different item")
1481
+ # effective-criterion rule (§6)
1482
+ replacement_dissents = [
1483
+ dissent_id for dissent_id, adjudication in adjudications_by_dissent.items()
1484
+ if adjudication["outcome"] == "replacement_approved"
1485
+ and dissent_by_id.get(dissent_id, {}).get("item_id") == item_id
1486
+ ]
1487
+ if has_adjudication_ref:
1488
+ expected_ref = f"phase1:{item_id}"
1489
+ elif replacement_dissents:
1490
+ expected_ref = f"dissent:{replacement_dissents[0]}"
1491
+ else:
1492
+ expected_ref = f"phase1:{item_id}"
1493
+ if rec["criterion_ref"] != expected_ref:
1494
+ fails.add(
1495
+ f"reapplication {rec['reapplication_id']}: criterion_ref {rec['criterion_ref']!r} "
1496
+ f"violates the §6 effective-criterion rule (expected {expected_ref!r})"
1497
+ )
1498
+ # derived adjustment (§6 loop step 2)
1499
+ derived = [
1500
+ adj for adj in traceability["adjustments"]
1501
+ if adj.get("source_ref") == f"reapplication:{rec['reapplication_id']}"
1502
+ ]
1503
+ # §6 trigger binding: a reapplication whose answer_refs carry ONLY
1504
+ # intent refs is a DIVERGENCE re-verification — it exists only for
1505
+ # an EVALUATED P1 row (one carrying cross_model_verdict, which
1506
+ # itself implies an active configuration): "diverges rows exist
1507
+ # only under ACTIVE cross-model (a not_configured run has none)".
1508
+ # A G2(d) retry is unaffected — its refs are CUMULATIVE and always
1509
+ # include the mandating adjudication.
1510
+ if has_intent_ref and not has_adjudication_ref:
1511
+ row = row_by_item.get(item_id)
1512
+ if row is None or row["priority"] != "MUST_FIX" or "cross_model_verdict" not in row:
1513
+ fails.add(
1514
+ f"reapplication {rec['reapplication_id']}: a divergence-only re-application exists only for an "
1515
+ "evaluated P1 row under ACTIVE cross-model (§6/§5.3) — no committed verdict moves without its "
1516
+ "triggering divergence"
1517
+ )
1518
+ elif row["cross_model_verdict"] == rec["pre_reapplication_verdict"]:
1519
+ # §6 dispatch order is normative: diverges is identified
1520
+ # FIRST (judge verdict vs the row's verdict), THEN the
1521
+ # system intent is emitted and the 2B' call dispatched. A
1522
+ # judge verdict equal to the dispatch-time pre-value means
1523
+ # no divergence existed to resolve — the chain manufactures
1524
+ # its own diverges status after the fact.
1525
+ fails.add(
1526
+ f"reapplication {rec['reapplication_id']}: no divergence existed at dispatch — the judge's "
1527
+ "verdict equals the dispatch-time pre_reapplication_verdict (§6)"
1528
+ )
1529
+ if not any(
1530
+ intents_by_id.get(ref.split(":", 1)[1], {}).get("answered_by") == "system"
1531
+ for ref in rec["answer_refs"]
1532
+ if ref.startswith("intent:")
1533
+ ):
1534
+ fails.add(
1535
+ f"reapplication {rec['reapplication_id']}: a divergence re-application chain carries the "
1536
+ "ORIGINAL mandating system intent (§6 — divergence resolution is MECHANICAL by default; user "
1537
+ "intents exist only on the retry paths, cumulatively alongside it)"
1538
+ )
1539
+ # §5.3 letter-tag condition: a letter-tagged anchor on a
1540
+ # reapplication (and hence on its mechanically-copied
1541
+ # cross_model_adjudication adjustment) is valid EXACTLY when the
1542
+ # re-examined chain projection carried a booked valid_rebuttal
1543
+ # record — the §3.4 machine witness that an assertion in the letter
1544
+ # with no locatable manuscript evidence changes nothing. The 2B'
1545
+ # input set withholds the Response Letter itself, so a booked
1546
+ # rebuttal anchor is the only legal letter-side source.
1547
+ if rec["reapplied_verdict"] != "CANNOT_VERIFY" and any(
1548
+ anchor["anchor_artifact"] == "letter" for anchor in rec["evidence_anchor"]
1549
+ ):
1550
+ letter_tag_allowed = False
1551
+ for chain_adj in chains.get(item_id, []):
1552
+ if len(derived) == 1 and chain_adj["adjustment_id"] == derived[0]["adjustment_id"]:
1553
+ break
1554
+ if chain_adj["basis"] == "valid_rebuttal":
1555
+ letter_tag_allowed = True
1556
+ break
1557
+ if not letter_tag_allowed:
1558
+ fails.add(
1559
+ f"reapplication {rec['reapplication_id']}: letter-tagged anchors are valid exactly when the "
1560
+ "re-examined chain carries a booked valid_rebuttal record (§5.3/§3.4)"
1561
+ )
1562
+ changing = rec["reapplied_verdict"] != "CANNOT_VERIFY" and rec["reapplied_verdict"] != rec["pre_reapplication_verdict"]
1563
+ if rec["reapplied_verdict"] == "CANNOT_VERIFY":
1564
+ if derived:
1565
+ fails.add(f"reapplication {rec['reapplication_id']}: CANNOT_VERIFY appends NOTHING mechanically (§6)")
1566
+ elif changing:
1567
+ if len(derived) != 1:
1568
+ fails.add(f"reapplication {rec['reapplication_id']}: a verdict-changing reapplication requires exactly one derived adjustment (§13)")
1569
+ else:
1570
+ adj = derived[0]
1571
+ if adj["item_id"] != item_id or adj["basis"] != "cross_model_adjudication":
1572
+ fails.add(f"reapplication {rec['reapplication_id']}: derived adjustment must be a cross_model_adjudication on the same item")
1573
+ if adj["from_verdict"] != rec["pre_reapplication_verdict"] or adj["to_verdict"] != rec["reapplied_verdict"]:
1574
+ fails.add(f"reapplication {rec['reapplication_id']}: derived adjustment endpoints must bind to the RECORDED pre-value (§6)")
1575
+ if adj["rationale"] != rec["rationale"]:
1576
+ fails.add(f"reapplication {rec['reapplication_id']}: derived adjustment rationale must be MECHANICALLY COPIED (§6)")
1577
+ if adj.get("residual_gap") != rec.get("residual_gap"):
1578
+ fails.add(f"reapplication {rec['reapplication_id']}: derived adjustment residual_gap must be MECHANICALLY COPIED (§6)")
1579
+ if adj.get("evidence_anchor") != rec.get("evidence_anchor"):
1580
+ fails.add(f"reapplication {rec['reapplication_id']}: derived adjustment anchors must be MECHANICALLY COPIED, tags included (§5.3)")
1581
+ else:
1582
+ if derived:
1583
+ fails.add(f"reapplication {rec['reapplication_id']}: no verdict change, so no derived adjustment may exist (§6)")
1584
+
1585
+ # every original_upheld adjudication + every intent appears in exactly one CURRENT record's answer_refs
1586
+ def _answers_in_current(ref: str):
1587
+ return [rec for rec in current_reapps if ref in rec["answer_refs"]]
1588
+
1589
+ for dissent_id, adjudication in adjudications_by_dissent.items():
1590
+ if adjudication["outcome"] == "original_upheld":
1591
+ holders = _answers_in_current(f"adjudication:{dissent_id}")
1592
+ if len(holders) != 1:
1593
+ fails.add(
1594
+ f"dissent adjudication {dissent_id}: original_upheld must appear in exactly one CURRENT reapplication's "
1595
+ f"answer_refs (found {len(holders)}) — silent non-reapplication cannot pass (§6)"
1596
+ )
1597
+ for intent_id in intents_by_id:
1598
+ holders = _answers_in_current(f"intent:{intent_id}")
1599
+ if len(holders) != 1:
1600
+ fails.add(
1601
+ f"resolution intent {intent_id}: must appear in exactly one CURRENT reapplication's answer_refs "
1602
+ f"(found {len(holders)}) (§6)"
1603
+ )
1604
+
1605
+ # --- cross-model resolutions (§5.3/§9) ---
1606
+ resolutions_by_reapp = {}
1607
+ for rec in traceability["cross_model_resolutions"]:
1608
+ if rec["reapplication_id"] in resolutions_by_reapp:
1609
+ fails.add(f"reapplication {rec['reapplication_id']}: referenced by more than one CrossModelResolution")
1610
+ resolutions_by_reapp[rec["reapplication_id"]] = rec
1611
+ # §6 loop step 2: when an intent-mandated re-application derives a state
1612
+ # (reapplied_verdict != CANNOT_VERIFY) the resolution is created
1613
+ # MECHANICALLY — its absence means the pipeline skipped a mechanical step.
1614
+ for rec in current_reapps:
1615
+ has_intent_ref = any(ref.startswith("intent:") for ref in rec["answer_refs"])
1616
+ if has_intent_ref and rec["reapplied_verdict"] != "CANNOT_VERIFY":
1617
+ resolution = resolutions_by_reapp.get(rec["reapplication_id"])
1618
+ if resolution is None or resolution["resolved_by"] != "system":
1619
+ fails.add(
1620
+ f"reapplication {rec['reapplication_id']}: a derived state mandates a mechanically-created "
1621
+ "system CrossModelResolution (§6 loop step 2)"
1622
+ )
1623
+ for rec in traceability["cross_model_resolutions"]:
1624
+ intent = intents_by_id.get(rec["intent_id"])
1625
+ reapp = reapp_by_id.get(rec["reapplication_id"])
1626
+ if intent is None or intent["item_id"] != rec["item_id"]:
1627
+ fails.add(f"resolution {rec['resolution_id']}: intent_id does not resolve on the same item")
1628
+ if reapp is None or reapp["item_id"] != rec["item_id"]:
1629
+ fails.add(f"resolution {rec['resolution_id']}: reapplication_id does not resolve on the same item")
1630
+ continue
1631
+ if f"intent:{rec['intent_id']}" not in reapp["answer_refs"]:
1632
+ fails.add(
1633
+ f"resolution {rec['resolution_id']}: reapplication {rec['reapplication_id']} did not answer "
1634
+ f"intent {rec['intent_id']} — the resolution derives FROM the mandated re-application, never from "
1635
+ "an unrelated same-item record (§6 loop step 2)"
1636
+ )
1637
+ if rec["resolved_by"] == "system":
1638
+ if reapp["reapplied_verdict"] == "CANNOT_VERIFY":
1639
+ fails.add(f"resolution {rec['resolution_id']}: a CANNOT_VERIFY reapplication derives no system resolution (§5.3)")
1640
+ else:
1641
+ expected_state = "primary_upheld" if reapp["reapplied_verdict"] == reapp["pre_reapplication_verdict"] else "primary_revised"
1642
+ if rec["state"] != expected_state:
1643
+ fails.add(f"resolution {rec['resolution_id']}: state {rec['state']!r} != §9 derivation ({expected_state!r})")
1644
+ else:
1645
+ if reapp["reapplied_verdict"] != "CANNOT_VERIFY":
1646
+ fails.add(f"resolution {rec['resolution_id']}: the user acceptance form requires a CANNOT_VERIFY reapplication (§5.3)")
1647
+
1648
+ # --- G2(d) acceptances ---
1649
+ acceptances_by_id = {}
1650
+ for rec in traceability["g2d_acceptances"]:
1651
+ if rec["acceptance_id"] in acceptances_by_id:
1652
+ fails.add(f"duplicate acceptance_id {rec['acceptance_id']}")
1653
+ acceptances_by_id[rec["acceptance_id"]] = rec
1654
+ reapp = reapp_by_id.get(rec["reapplication_id"])
1655
+ if reapp is None or reapp["item_id"] != rec["item_id"]:
1656
+ fails.add(f"acceptance {rec['acceptance_id']}: reapplication_id does not resolve on the same item")
1657
+ elif reapp["reapplied_verdict"] != "CANNOT_VERIFY":
1658
+ fails.add(f"acceptance {rec['acceptance_id']}: the accepted reapplication must be CANNOT_VERIFY (§6 G2(d))")
1659
+ if rec["reapplication_id"] in superseded_reapps:
1660
+ fails.add(
1661
+ f"acceptance {rec['acceptance_id']}: references a SUPERSEDED reapplication — an acceptance binds "
1662
+ "to the CURRENT failed attempt, never a stale one (§6 G2(d))"
1663
+ )
1664
+ row = row_by_item.get(rec["item_id"])
1665
+ if row is not None and row["final_verdict"] != "CANNOT_VERIFY":
1666
+ fails.add(f"acceptance {rec['acceptance_id']}: the accepted item's final_verdict must be CANNOT_VERIFY (§6 G2(d))")
1667
+ backing = [
1668
+ adj for adj in traceability["adjustments"]
1669
+ if adj["basis"] == "user_accepted_fail_closed"
1670
+ and adj.get("source_ref") == f"acceptance:{rec['acceptance_id']}"
1671
+ ]
1672
+ if len(backing) != 1:
1673
+ fails.add(
1674
+ f"acceptance {rec['acceptance_id']}: a G2(d) acceptance backs exactly ONE user_accepted_fail_closed "
1675
+ f"adjustment (found {len(backing)}) — the one legal CANNOT_VERIFY append; an orphan acceptance cannot "
1676
+ "clear the deferral (§6 G2(d)/§3.4)"
1677
+ )
1678
+ acceptance_adj_by_reapp = {}
1679
+ for adj in traceability["adjustments"]:
1680
+ if adj["basis"] == "user_accepted_fail_closed":
1681
+ acc_id = adj["source_ref"].split(":", 1)[1]
1682
+ acceptance = acceptances_by_id.get(acc_id)
1683
+ if acceptance is None or acceptance["item_id"] != adj["item_id"]:
1684
+ fails.add(f"adjustment {adj['adjustment_id']}: acceptance ref does not resolve on the same item")
1685
+ continue
1686
+ acceptance_adj_by_reapp[acceptance["reapplication_id"]] = adj
1687
+ reapp = reapp_by_id.get(acceptance["reapplication_id"])
1688
+ if reapp is not None and reapp.get("cannot_verify_reason") != adj.get("cannot_verify_reason"):
1689
+ fails.add(f"adjustment {adj['adjustment_id']}: cannot_verify_reason must be copied from the accepted reapplication (§3.4)")
1690
+ elif adj["basis"] == "cross_model_adjudication":
1691
+ rap_id = adj["source_ref"].split(":", 1)[1]
1692
+ if rap_id not in reapp_by_id:
1693
+ fails.add(f"adjustment {adj['adjustment_id']}: reapplication ref does not resolve")
1694
+
1695
+ # pre_reapplication_verdict consistency (§13): the recorded pre-value is
1696
+ # the TIME ANCHOR — it equals this reapplication's own derived
1697
+ # adjustment's from_verdict when one exists; on the G2(d) path it equals
1698
+ # the acceptance-backed adjustment's from_verdict (that adjustment
1699
+ # consumed this reapplication, so the tail moved AFTER dispatch); else
1700
+ # the current chain tail; else phase2a_verdict when the item has no chain.
1701
+ for rec in traceability["reapplications"]:
1702
+ derived = [
1703
+ adj for adj in traceability["adjustments"]
1704
+ if adj.get("source_ref") == f"reapplication:{rec['reapplication_id']}"
1705
+ ]
1706
+ if rec["reapplication_id"] in superseded_reapps:
1707
+ # UNCONDITIONAL for every superseded record — a derived
1708
+ # adjustment must not bypass this branch. Supersession is
1709
+ # defined for FAILED attempts only (§6 — "a retry names the
1710
+ # failed attempt it supersedes"), and a failed attempt appends
1711
+ # nothing, so its dispatch tail equals its direct retry's
1712
+ # recorded pre-value. The current-tail fallback never applies
1713
+ # to a historical record (the tail may have moved since).
1714
+ successor = reapp_by_id[superseded_reapps[rec["reapplication_id"]]]
1715
+ if rec["reapplied_verdict"] != "CANNOT_VERIFY":
1716
+ fails.add(
1717
+ f"reapplication {rec['reapplication_id']}: only FAILED (CANNOT_VERIFY) attempts are superseded "
1718
+ "by a retry (§6)"
1719
+ )
1720
+ if rec["pre_reapplication_verdict"] != successor["pre_reapplication_verdict"]:
1721
+ fails.add(
1722
+ f"reapplication {rec['reapplication_id']}: a failed attempt appends nothing, so its dispatch "
1723
+ f"tail equals its direct retry's pre_reapplication_verdict "
1724
+ f"({rec['pre_reapplication_verdict']!r} != {successor['pre_reapplication_verdict']!r}, §6/§13)"
1725
+ )
1726
+ continue
1727
+ if len(derived) == 1:
1728
+ expected_pre = derived[0]["from_verdict"]
1729
+ elif rec["reapplication_id"] in acceptance_adj_by_reapp:
1730
+ expected_pre = acceptance_adj_by_reapp[rec["reapplication_id"]]["from_verdict"]
1731
+ else:
1732
+ expected_pre = _chain_tail_verdict(rec["item_id"])
1733
+ if expected_pre is not None and rec["pre_reapplication_verdict"] != expected_pre:
1734
+ fails.add(
1735
+ f"reapplication {rec['reapplication_id']}: pre_reapplication_verdict {rec['pre_reapplication_verdict']!r} "
1736
+ f"!= the chain tail as it stood at dispatch ({expected_pre!r}, §13)"
1737
+ )
1738
+
1739
+ # --- critical-rebuttal machinery (§3.4/§5.3) ---
1740
+ radj_by_id = {}
1741
+ for rec in traceability["rebuttal_adjudications"]:
1742
+ if rec["rebuttal_adjudication_id"] in radj_by_id:
1743
+ fails.add(f"duplicate rebuttal_adjudication_id {rec['rebuttal_adjudication_id']}")
1744
+ radj_by_id[rec["rebuttal_adjudication_id"]] = rec
1745
+ if not cross_model_active:
1746
+ fails.add(f"rebuttal adjudication {rec['rebuttal_adjudication_id']}: the §3.4 pass exists only under active cross-model (§11)")
1747
+ adjustments_by_id = {adj["adjustment_id"]: adj for adj in traceability["adjustments"]}
1748
+
1749
+ def _is_critical(item_id):
1750
+ item = roadmap_by_id.get(item_id)
1751
+ return item is not None and item.get("severity") == "critical"
1752
+
1753
+ for adj in traceability["adjustments"]:
1754
+ should_have_check = adj["basis"] == "valid_rebuttal" and _is_critical(adj["item_id"])
1755
+ has_check = "critical_rebuttal_check" in adj
1756
+ if should_have_check != has_check:
1757
+ fails.add(
1758
+ f"adjustment {adj['adjustment_id']}: critical_rebuttal_check present iff the adjustment is a critical "
1759
+ f"valid_rebuttal (§3.4)"
1760
+ )
1761
+ if has_check:
1762
+ check_value = adj["critical_rebuttal_check"]
1763
+ match = _CHECK_ADJUDICATED_RE.match(check_value) if isinstance(check_value, str) else None
1764
+ if match:
1765
+ radj = radj_by_id.get(match.group(1))
1766
+ if radj is None or radj["verdict"] != "upheld" or radj["item_id"] != adj["item_id"]:
1767
+ fails.add(f"adjustment {adj['adjustment_id']}: adjudicated:* must resolve to an upheld RebuttalAdjudication on the same item (§3.4)")
1768
+ elif check_value == "single_family_disclosed" and cross_model_active:
1769
+ fails.add(f"adjustment {adj['adjustment_id']}: single_family_disclosed is invalid on an active cross-model run (§3.4)")
1770
+
1771
+ proposals_by_id = {}
1772
+ booked_by_adjustment = {}
1773
+ seen_drafted_bodies = {}
1774
+ challenged_bodies = []
1775
+ for rec in traceability["pending_rebuttal_upgrades"]:
1776
+ if rec["proposal_id"] in proposals_by_id:
1777
+ fails.add(f"duplicate proposal_id {rec['proposal_id']}")
1778
+ proposals_by_id[rec["proposal_id"]] = rec
1779
+ kind, ref_id = rec["disposition"].split(":", 1)
1780
+ if rec["drafted_adjustment"]["item_id"] != rec["item_id"]:
1781
+ fails.add(f"proposal {rec['proposal_id']}: drafted_adjustment.item_id must equal the proposal's item_id (§5.3)")
1782
+ body_key = (rec["item_id"], canonical_hash(rec["drafted_adjustment"]))
1783
+ if body_key in seen_drafted_bodies:
1784
+ fails.add(
1785
+ f"proposal {rec['proposal_id']}: duplicate PendingRebuttalUpgrade drafted body on item {rec['item_id']} "
1786
+ f"(same canonical body as {seen_drafted_bodies[body_key]}) — one proposal per drafted upgrade (§5.3)"
1787
+ )
1788
+ seen_drafted_bodies[body_key] = rec["proposal_id"]
1789
+ if kind == "challenged":
1790
+ challenged_bodies.append((rec["proposal_id"], rec["item_id"], rec["drafted_adjustment"]))
1791
+ if kind == "challenged":
1792
+ radj = radj_by_id.get(ref_id)
1793
+ if radj is None or radj["item_id"] != rec["item_id"]:
1794
+ fails.add(f"proposal {rec['proposal_id']}: challenged ref does not resolve on the same item")
1795
+ elif radj["verdict"] != "challenged":
1796
+ fails.add(f"proposal {rec['proposal_id']}: challenged disposition must reference a challenged adjudication")
1797
+ else:
1798
+ adj = adjustments_by_id.get(ref_id)
1799
+ if adj is None or adj["item_id"] != rec["item_id"] or adj["basis"] != "valid_rebuttal":
1800
+ fails.add(f"proposal {rec['proposal_id']}: booked ref must resolve to a valid_rebuttal adjustment on the same item")
1801
+ continue
1802
+ if ref_id in booked_by_adjustment:
1803
+ fails.add(f"proposal {rec['proposal_id']}: adjustment {ref_id} already booked by {booked_by_adjustment[ref_id]}")
1804
+ booked_by_adjustment[ref_id] = rec["proposal_id"]
1805
+ drafted = dict(rec["drafted_adjustment"])
1806
+ booked = {
1807
+ key: value for key, value in adj.items()
1808
+ if key not in ("adjustment_id", "supersedes_adjustment_id", "critical_rebuttal_check")
1809
+ }
1810
+ if drafted != booked:
1811
+ fails.add(
1812
+ f"proposal {rec['proposal_id']}: booked adjustment must content-equal the drafted body "
1813
+ "field-for-field, excluding EXACTLY {adjustment_id, supersedes_adjustment_id, critical_rebuttal_check} (§5.3)"
1814
+ )
1815
+ check_value = adj.get("critical_rebuttal_check")
1816
+ if kind == "booked":
1817
+ match = _CHECK_ADJUDICATED_RE.match(check_value) if isinstance(check_value, str) else None
1818
+ if not match or radj_by_id.get(match.group(1), {}).get("verdict") != "upheld":
1819
+ fails.add(f"proposal {rec['proposal_id']}: booked disposition requires an upheld adjudicated:* check (§3.4)")
1820
+ elif kind == "booked_disclosed":
1821
+ if check_value != "pass_unavailable_disclosed":
1822
+ fails.add(f"proposal {rec['proposal_id']}: booked_disclosed requires critical_rebuttal_check pass_unavailable_disclosed")
1823
+ if not cross_model_active:
1824
+ fails.add(f"proposal {rec['proposal_id']}: booked_disclosed exists only under active cross-model (§11)")
1825
+ elif kind == "booked_single_family":
1826
+ if check_value != "single_family_disclosed":
1827
+ fails.add(f"proposal {rec['proposal_id']}: booked_single_family requires critical_rebuttal_check single_family_disclosed")
1828
+ if cross_model_active:
1829
+ fails.add(f"proposal {rec['proposal_id']}: booked_single_family is invalid under active cross-model (§3.4)")
1830
+ for adj in traceability["adjustments"]:
1831
+ if adj["basis"] == "valid_rebuttal" and _is_critical(adj["item_id"]) and adj["adjustment_id"] not in booked_by_adjustment:
1832
+ fails.add(
1833
+ f"adjustment {adj['adjustment_id']}: every critical valid_rebuttal adjustment traces to exactly one "
1834
+ "booked* PendingRebuttalUpgrade (§5.3)"
1835
+ )
1836
+ if adj["basis"] == "valid_rebuttal":
1837
+ booked_view = {
1838
+ key: value for key, value in adj.items()
1839
+ if key not in ("adjustment_id", "supersedes_adjustment_id", "critical_rebuttal_check")
1840
+ }
1841
+ for proposal_id, item_id, drafted in challenged_bodies:
1842
+ if adj["item_id"] == item_id and booked_view == drafted:
1843
+ fails.add(
1844
+ f"adjustment {adj['adjustment_id']}: content-equals CHALLENGED proposal {proposal_id}'s "
1845
+ "drafted body — a challenged upgrade is NEVER booked (§3.4)"
1846
+ )
1847
+
1848
+ # --- escalation approvals ---
1849
+ approvals_by_exception = {}
1850
+ for rec in traceability["escalation_approvals"]:
1851
+ if rec["exception_id"] in approvals_by_exception:
1852
+ fails.add(f"duplicate escalation approval for {rec['exception_id']}")
1853
+ approvals_by_exception[rec["exception_id"]] = rec
1854
+ if rec["exception_id"] not in exceptions_by_id:
1855
+ fails.add(f"escalation approval {rec['exception_id']}: no such exception record")
1856
+
1857
+ # --- cross-model activity consistency (§11) ---
1858
+ if not cross_model_active:
1859
+ for item_id, row in row_by_item.items():
1860
+ if row.get("cross_model_status") in ("agree", "diverges", "unavailable"):
1861
+ fails.add(f"row {item_id}: cross_model_status {row['cross_model_status']!r} implies an active configuration (§11)")
1862
+
1863
+ # --- DecisionInputs recomputation (§13) ---
1864
+ di = traceability["decision_inputs"]
1865
+
1866
+ def _final_residual_magnitude(item_id):
1867
+ row = row_by_item.get(item_id)
1868
+ if row is None or row["final_verdict"] != "PARTIALLY_ADDRESSED":
1869
+ return None
1870
+ chain = chains.get(item_id)
1871
+ record = chain[-1] if chain else verdict_by_item.get(item_id)
1872
+ if record is None:
1873
+ return None
1874
+ gap = record.get("residual_gap")
1875
+ return gap["residual_magnitude"] if gap else None
1876
+
1877
+ expected_per_item = []
1878
+ for item_id in must_fix_order:
1879
+ row = row_by_item.get(item_id)
1880
+ if row is None:
1881
+ continue
1882
+ item = roadmap_by_id[item_id]
1883
+ severity = item.get("severity")
1884
+ # §5.3: a null driving_severity is legal ONLY for source_kind items
1885
+ # and legacy roadmaps. A P1 item carrying OTHER transported markers
1886
+ # (#574 A2/A3) but no severity is a half-transported finding-driven
1887
+ # item, not a legacy one — B1's critical join would be silently
1888
+ # suppressed, so it fails here.
1889
+ if (
1890
+ severity is None
1891
+ and item.get("source_kind") is None
1892
+ and any(item.get(field) is not None for field in ("evidence_anchor", "confidence", "competence_basis"))
1893
+ ):
1894
+ fails.add(
1895
+ f"roadmap item {item_id}: transported fields present but severity absent — a non-legacy "
1896
+ "finding-driven P1 item cannot carry driving_severity null (§5.3)"
1897
+ )
1898
+ entry = {
1899
+ "item_id": item_id,
1900
+ "final_verdict": row["final_verdict"],
1901
+ "driving_severity": severity if severity in SEVERITIES else None,
1902
+ }
1903
+ magnitude = _final_residual_magnitude(item_id)
1904
+ if magnitude is not None:
1905
+ entry["residual_magnitude"] = magnitude
1906
+ expected_per_item.append(entry)
1907
+ if di["per_item"] != expected_per_item:
1908
+ fails.add("decision_inputs.per_item does not equal the recomputed per-P1 operand list (roadmap must_fix order, §13)")
1909
+
1910
+ expected_counts = {prio: {verdict: 0 for verdict in VERDICTS} for prio in PRIORITIES}
1911
+ expected_magnitudes = {prio: {mag: 0 for mag in MAGNITUDES} for prio in PRIORITIES}
1912
+ for item_id, row in row_by_item.items():
1913
+ item = roadmap_by_id.get(item_id)
1914
+ if item is None:
1915
+ continue
1916
+ expected_counts[item["priority"]][row["final_verdict"]] += 1
1917
+ if row["final_verdict"] == "PARTIALLY_ADDRESSED":
1918
+ magnitude = _final_residual_magnitude(item_id)
1919
+ if magnitude is not None:
1920
+ expected_magnitudes[item["priority"]][magnitude] += 1
1921
+ if di["verdict_counts"] != expected_counts:
1922
+ fails.add("decision_inputs.verdict_counts does not recompute from the final row verdicts (§13)")
1923
+ if di["residual_magnitude_counts"] != expected_magnitudes:
1924
+ fails.add("decision_inputs.residual_magnitude_counts does not recompute from the PARTIALLY_ADDRESSED rows (§13)")
1925
+
1926
+ p2_ids = [item["id"] for item in roadmap_items if item["priority"] == "should_fix"]
1927
+ expected_num = sum(
1928
+ 1 for item_id in p2_ids
1929
+ if row_by_item.get(item_id, {}).get("final_verdict") in ("FULLY_ADDRESSED", "PARTIALLY_ADDRESSED")
1930
+ )
1931
+ if di["p2_addressed_rate"] != {"numerator": expected_num, "denominator": len(p2_ids)}:
1932
+ fails.add("decision_inputs.p2_addressed_rate does not recompute over FINAL P2 verdicts (§6)")
1933
+
1934
+ expected_regressions = [
1935
+ {"new_issue_id": rec["new_issue_id"], "severity": rec["severity"]}
1936
+ for rec in verdict_record["new_issues"] if rec["attribution"] == "regression"
1937
+ ]
1938
+ expected_non_regression = [
1939
+ rec["new_issue_id"] for rec in verdict_record["new_issues"] if rec["attribution"] != "regression"
1940
+ ]
1941
+ if di["regressions"] != expected_regressions:
1942
+ fails.add("decision_inputs.regressions does not equal the regression-attributed frozen new issues (§8/§13)")
1943
+ if di["non_regression_new_issue_ids"] != expected_non_regression:
1944
+ fails.add("decision_inputs.non_regression_new_issue_ids does not equal the frozen previously_missed/indeterminate ids (§8/§13)")
1945
+
1946
+ expected_escalations = []
1947
+ for rec in verdict_record["escalation_exceptions"]:
1948
+ approval = approvals_by_exception.get(rec["exception_id"])
1949
+ expected_escalations.append({
1950
+ "exception_id": rec["exception_id"],
1951
+ "effective_approval_state": approval["approval_state"] if approval else "pending",
1952
+ "escalation_class": rec["escalation_class"],
1953
+ "mechanical_decision_impact": rec["mechanical_decision_impact"],
1954
+ })
1955
+ if di["escalations"] != expected_escalations:
1956
+ fails.add("decision_inputs.escalations does not equal the joined exception/approval summary (§5.3)")
1957
+
1958
+ if di["apply_chain_witness"] != witness:
1959
+ fails.add(f"decision_inputs.apply_chain_witness {di['apply_chain_witness']!r} != recomputed witness {witness!r} (§11)")
1960
+
1961
+ # --- §6 Step 1 recomputation (gates) ---
1962
+ g1 = any(
1963
+ row["final_verdict"] != row["phase2a_verdict"] and row["item_id"] not in chains
1964
+ for row in traceability["rows"]
1965
+ )
1966
+ pending = []
1967
+ # bound_tripped computed once, next to dissent_by_id (§7 SD-5).
1968
+ if bound_tripped:
1969
+ for dissent_id in dissent_by_id:
1970
+ if dissent_id not in adjudications_by_dissent:
1971
+ pending.append(f"G2(a): dissent {dissent_id} lacks its DissentAdjudication (bound tripped — covering cardinality)")
1972
+
1973
+ def _covering_resolution(item_id, final_verdict):
1974
+ for rec in traceability["cross_model_resolutions"]:
1975
+ if rec["item_id"] != item_id:
1976
+ continue
1977
+ reapp = reapp_by_id.get(rec["reapplication_id"])
1978
+ if reapp is None:
1979
+ continue
1980
+ anchor = reapp["reapplied_verdict"] if rec["resolved_by"] == "system" else reapp["pre_reapplication_verdict"]
1981
+ if anchor == final_verdict:
1982
+ return True
1983
+ return False
1984
+
1985
+ for item_id, row in row_by_item.items():
1986
+ if row.get("cross_model_status") == "diverges" and ROW_PRIORITY_MAP[row["priority"]] == "must_fix":
1987
+ if not _covering_resolution(item_id, row["final_verdict"]):
1988
+ pending.append(f"G2(b): P1 diverges row {item_id} has no COVERING CrossModelResolution")
1989
+ for rec in verdict_record["escalation_exceptions"]:
1990
+ if rec["exception_id"] not in approvals_by_exception:
1991
+ pending.append(f"G2(c): escalation exception {rec['exception_id']} is pending user approval")
1992
+ accepted_reapps = {rec["reapplication_id"] for rec in traceability["g2d_acceptances"]}
1993
+ for dissent_id, adjudication in adjudications_by_dissent.items():
1994
+ if adjudication["outcome"] != "original_upheld":
1995
+ continue
1996
+ holders = _answers_in_current(f"adjudication:{dissent_id}")
1997
+ if len(holders) != 1:
1998
+ continue # already a synthesis failure above
1999
+ current = holders[0]
2000
+ if current["reapplied_verdict"] == "CANNOT_VERIFY" and current["reapplication_id"] not in accepted_reapps:
2001
+ pending.append(f"G2(d): original_upheld dissent {dissent_id} fail-closed reapplication awaits user resolution")
2002
+
2003
+ # --- emission-kind-scoped equality set (§13) ---
2004
+ recomputed = {"pending": pending, "g1": g1}
2005
+ emitted_state = traceability["decision_state"]
2006
+ if emitted_state == "aborted":
2007
+ # §13: an aborted emission carries its true root-cause abort_reason
2008
+ # and is EXEMPT from the deferral biconditional and Steps 2-3 (abort
2009
+ # precedence over deferral). The only checker-recomputable reason is
2010
+ # criteria_drift; the phase-lint reasons are fenced-call facts the
2011
+ # checker cannot re-derive and are accepted as recorded (the
2012
+ # manifest-class reasons would already have exited 2 above).
2013
+ reason = traceability.get("abort_reason")
2014
+ recomputed["expect"] = ("aborted", reason)
2015
+ if "reject_recommended" in di:
2016
+ fails.add("decision_inputs.reject_recommended must be ABSENT on a gated emission (§5.3 presence biconditional)")
2017
+ if reason in ("manifest_incomplete", "manifest_hash_mismatch", "synthesis_mismatch"):
2018
+ fails.add(
2019
+ f"aborted emission claims {reason!r} as root cause, but the checker reached recomputation — the "
2020
+ "§11 manifest layer validated against these same hash-bound inputs, so the claimed abort cannot be "
2021
+ "the true root cause (§13: an aborted emission carries its TRUE root-cause abort_reason)"
2022
+ )
2023
+ if g1 and reason != "criteria_drift":
2024
+ fails.add("Step 1 recomputes G1 (silent verdict change) — the aborted emission's true root cause is criteria_drift (§13)")
2025
+ if reason == "criteria_drift" and not g1:
2026
+ fails.add("the emission claims [RE-REVIEW-ABORT: criteria_drift] but no silent verdict change recomputes (§13)")
2027
+ elif g1:
2028
+ recomputed["expect"] = ("aborted", "criteria_drift")
2029
+ fails.add("Step 1 recomputes G1 (silent verdict change) — the emission must be [RE-REVIEW-ABORT: criteria_drift]")
2030
+ elif pending:
2031
+ recomputed["expect"] = ("user_review_required", None)
2032
+ if emitted_state != "user_review_required":
2033
+ fails.add(
2034
+ "G2 deferral biconditional: pending user-input state(s) exist, so user_review_required is the only "
2035
+ "checker-valid decision_state — " + "; ".join(pending)
2036
+ )
2037
+ if "reject_recommended" in di:
2038
+ fails.add("decision_inputs.reject_recommended must be ABSENT on a gated emission (§5.3 presence biconditional)")
2039
+ else:
2040
+ if emitted_state == "user_review_required":
2041
+ fails.add(
2042
+ "Step 1 recomputes no gate, but the emission carries decision_state 'user_review_required' "
2043
+ "(the G2 biconditional runs both directions)"
2044
+ )
2045
+ else:
2046
+ # Steps 2-3, recomputed independently from the raw records...
2047
+ p1_operands = []
2048
+ for item_id in must_fix_order:
2049
+ row = row_by_item.get(item_id)
2050
+ if row is None:
2051
+ continue
2052
+ item = roadmap_by_id[item_id]
2053
+ severity = item.get("severity") if item.get("severity") in SEVERITIES else None
2054
+ p1_operands.append((row["final_verdict"], severity, _final_residual_magnitude(item_id)))
2055
+ p2_partial_magnitudes = [
2056
+ _final_residual_magnitude(item_id) for item_id in p2_ids
2057
+ if row_by_item.get(item_id, {}).get("final_verdict") == "PARTIALLY_ADDRESSED"
2058
+ ]
2059
+ p2_made_worse = any(
2060
+ row_by_item.get(item_id, {}).get("final_verdict") == "MADE_WORSE" for item_id in p2_ids
2061
+ )
2062
+ regression_severities = {entry["severity"] for entry in expected_regressions}
2063
+ raw_decision, raw_reject, raw_rule = derive_decision(
2064
+ p1_operands, p2_partial_magnitudes, p2_made_worse,
2065
+ expected_num, len(p2_ids), regression_severities, expected_escalations,
2066
+ )
2067
+ # ...AND from the emitted DecisionInputs copies.
2068
+ di_p1 = [
2069
+ (entry["final_verdict"], entry["driving_severity"], entry.get("residual_magnitude"))
2070
+ for entry in di["per_item"]
2071
+ ]
2072
+ di_p2_partials = [
2073
+ mag for mag in MAGNITUDES
2074
+ for _ in range(di["residual_magnitude_counts"]["should_fix"][mag])
2075
+ ]
2076
+ di_decision, di_reject, _di_rule = derive_decision(
2077
+ di_p1,
2078
+ di_p2_partials,
2079
+ di["verdict_counts"]["should_fix"]["MADE_WORSE"] > 0,
2080
+ di["p2_addressed_rate"]["numerator"],
2081
+ di["p2_addressed_rate"]["denominator"],
2082
+ {entry["severity"] for entry in di["regressions"]},
2083
+ di["escalations"],
2084
+ )
2085
+ recomputed["expect"] = (raw_decision, None)
2086
+ recomputed["rule"] = raw_rule
2087
+ if emitted_state != raw_decision:
2088
+ fails.add(f"decision_state {emitted_state!r} != Steps 2-3 recomputed from the raw records ({raw_decision!r}, base {raw_rule})")
2089
+ if di_decision != raw_decision:
2090
+ fails.add(f"Steps 2-3 from DecisionInputs ({di_decision!r}) diverge from the raw-record recomputation ({raw_decision!r})")
2091
+ if "reject_recommended" not in di:
2092
+ fails.add("decision_inputs.reject_recommended must be PRESENT on a non-gated emission (§5.3 presence biconditional)")
2093
+ else:
2094
+ if di["reject_recommended"] != raw_reject:
2095
+ fails.add(f"reject_recommended {di['reject_recommended']!r} != recomputed {raw_reject!r} (§6)")
2096
+ if di_reject != raw_reject:
2097
+ fails.add("reject_recommended recomputed from DecisionInputs diverges from the raw-record recomputation")
2098
+
2099
+ return fails, warnings, recomputed
2100
+
2101
+
2102
+ # --- CLI -----------------------------------------------------------------------
2103
+
2104
+
2105
+ def _load_json(path: Path, reason: str, label: str):
2106
+ try:
2107
+ return json.loads(path.read_text(encoding="utf-8"))
2108
+ except (OSError, json.JSONDecodeError, UnicodeDecodeError) as exc:
2109
+ if reason.startswith("manifest"):
2110
+ raise ManifestError(reason, f"{label}: unreadable or invalid JSON ({exc})")
2111
+ raise ArtifactSchemaError(reason, f"{label}: unreadable or invalid JSON ({exc})")
2112
+
2113
+
2114
+ def _verify_file_hash(path: Path, expected_sha256: str, label: str) -> bytes:
2115
+ try:
2116
+ raw = path.read_bytes()
2117
+ except OSError as exc:
2118
+ raise ManifestError("manifest_incomplete", f"{label}: unreadable ({exc})")
2119
+ actual = hashlib.sha256(raw).hexdigest()
2120
+ if actual != expected_sha256:
2121
+ raise ManifestError("manifest_hash_mismatch", f"{label}: file bytes do not match the manifest sha256 (§11)")
2122
+ return raw
2123
+
2124
+
2125
+ def run(argv=None) -> int:
2126
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
2127
+ parser.add_argument("--manifest", type=Path, required=True)
2128
+ parser.add_argument("--precommitment", type=Path, required=True)
2129
+ parser.add_argument("--verdict-record", type=Path, required=True)
2130
+ parser.add_argument("--traceability", type=Path, required=True)
2131
+ parser.add_argument("--roadmap", type=Path, required=True,
2132
+ help="the file the manifest's revision_roadmap entry hash-binds (Schema 7 machine form)")
2133
+ parser.add_argument("--letter", type=Path, default=None,
2134
+ help="the file the manifest's editorial_decision_letter entry hash-binds (markdown)")
2135
+ parser.add_argument("--apply-report", type=Path, action="append", default=[], dest="apply_reports",
2136
+ help="apply-report file(s), in the manifest's apply_reports[] order")
2137
+ args = parser.parse_args(argv)
2138
+
2139
+ try:
2140
+ manifest = _load_json(args.manifest, "manifest_incomplete", "manifest")
2141
+ validate_manifest(manifest)
2142
+ artifacts = manifest["artifacts"]
2143
+
2144
+ roadmap_raw = _verify_file_hash(args.roadmap, artifacts["revision_roadmap"]["sha256"], "revision_roadmap file")
2145
+ roadmap_items = load_roadmap(json.loads(roadmap_raw.decode("utf-8")))
2146
+
2147
+ letter_blocks = None
2148
+ if artifacts["editorial_decision_letter"]["present"]:
2149
+ if args.letter is None:
2150
+ raise ManifestError("manifest_incomplete", "manifest declares editorial_decision_letter present — --letter is required")
2151
+ letter_raw = _verify_file_hash(args.letter, artifacts["editorial_decision_letter"]["sha256"], "editorial_decision_letter file")
2152
+ letter_blocks = parse_letter_blocks(letter_raw.decode("utf-8"))
2153
+ elif args.letter is not None:
2154
+ raise ManifestError("manifest_incomplete", "--letter provided but the manifest declares the letter absent")
2155
+
2156
+ report_entries = artifacts["apply_reports"]["items"] if artifacts["apply_reports"]["present"] else []
2157
+ if len(args.apply_reports) != len(report_entries):
2158
+ raise ManifestError(
2159
+ "manifest_incomplete",
2160
+ f"manifest declares {len(report_entries)} apply report(s); {len(args.apply_reports)} provided via --apply-report",
2161
+ )
2162
+ report_payloads = []
2163
+ for i, (path, entry) in enumerate(zip(args.apply_reports, report_entries)):
2164
+ raw = _verify_file_hash(path, entry["sha256"], f"apply report [{i}]")
2165
+ try:
2166
+ report_payloads.append(json.loads(raw.decode("utf-8")))
2167
+ except (json.JSONDecodeError, UnicodeDecodeError) as exc:
2168
+ raise ManifestError("manifest_incomplete", f"apply report [{i}]: invalid JSON ({exc})")
2169
+ witness, witness_notes = compute_apply_chain_witness(manifest, report_payloads)
2170
+
2171
+ precommitment = _load_json(args.precommitment, "phase1_lint_failed", "precommitment")
2172
+ validate_precommitment(precommitment)
2173
+ verdict_record = _load_json(args.verdict_record, "phase2a_lint_failed", "verdict_record")
2174
+ validate_verdict_record(verdict_record)
2175
+ traceability = _load_json(args.traceability, "phase2b_lint_failed", "traceability")
2176
+ validate_traceability(traceability)
2177
+
2178
+ # Hash chain (§11): the same-inputs proof.
2179
+ if precommitment["input_manifest_hash"] != canonical_hash(manifest):
2180
+ raise ManifestError("manifest_hash_mismatch", "precommitment.input_manifest_hash does not recompute from the manifest (§11 chain)")
2181
+ if verdict_record["precommitment_hash"] != canonical_hash(precommitment):
2182
+ raise ManifestError("manifest_hash_mismatch", "verdict_record.precommitment_hash does not recompute from the precommitment artifact (§11 chain)")
2183
+ if traceability["verdict_record_hash"] != canonical_hash(verdict_record):
2184
+ raise ManifestError("manifest_hash_mismatch", "traceability.verdict_record_hash does not recompute from the verdict_record artifact (§11 chain)")
2185
+ except ManifestError as exc:
2186
+ print(f"[RE-REVIEW-ABORT: {exc.reason}]")
2187
+ print(f"FAIL: {exc}")
2188
+ return EXIT_INVALID
2189
+ except ArtifactSchemaError as exc:
2190
+ print(f"[RE-REVIEW-ABORT: {exc.reason}]")
2191
+ print(f"FAIL: {exc}")
2192
+ return EXIT_INVALID
2193
+
2194
+ fails, warnings, recomputed = check(
2195
+ manifest, precommitment, verdict_record, traceability,
2196
+ roadmap_items, letter_blocks, witness, witness_notes,
2197
+ )
2198
+ for note in warnings:
2199
+ print(note, file=sys.stderr)
2200
+ if fails.messages:
2201
+ print("[RE-REVIEW-ABORT: synthesis_mismatch]")
2202
+ for message in fails.messages:
2203
+ print(f"MISMATCH: {message}")
2204
+ return EXIT_SYNTHESIS
2205
+ expect = recomputed.get("expect")
2206
+ outcome = expect[0] if expect else traceability["decision_state"]
2207
+ print(
2208
+ f"re-review synthesis ok: round {manifest['round_id']!r}, revision {traceability['revision']}, "
2209
+ f"decision_state {outcome!r}, apply_chain_witness {witness!r}"
2210
+ )
2211
+ return EXIT_PASS
2212
+
2213
+
2214
+ if __name__ == "__main__":
2215
+ raise SystemExit(run())