psyclaw 0.27.23 → 0.28.1

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 (1069) hide show
  1. package/README.md +11 -5
  2. package/agents/recommended/catalog.json +99 -0
  3. package/dist/apps/panel/index.html +5 -5
  4. package/dist/src/adapters/pi/extension.js +339 -94
  5. package/dist/src/adapters/pi/extension.js.map +1 -1
  6. package/dist/src/adapters/pi/rpc.d.ts +2 -0
  7. package/dist/src/adapters/pi/rpc.js +9 -2
  8. package/dist/src/adapters/pi/rpc.js.map +1 -1
  9. package/dist/src/agents/import.js +44 -32
  10. package/dist/src/agents/import.js.map +1 -1
  11. package/dist/src/agents/recommended-personas.d.ts +42 -0
  12. package/dist/src/agents/recommended-personas.js +135 -0
  13. package/dist/src/agents/recommended-personas.js.map +1 -0
  14. package/dist/src/analysis/hooks.d.ts +1 -0
  15. package/dist/src/analysis/hooks.js +20 -1
  16. package/dist/src/analysis/hooks.js.map +1 -1
  17. package/dist/src/ars/bridge.d.ts +20 -0
  18. package/dist/src/ars/bridge.js +132 -0
  19. package/dist/src/ars/bridge.js.map +1 -0
  20. package/dist/src/ars/contracts.d.ts +55 -0
  21. package/dist/src/ars/contracts.js +2 -0
  22. package/dist/src/ars/contracts.js.map +1 -0
  23. package/dist/src/ars/panel-plan.d.ts +4 -0
  24. package/dist/src/ars/panel-plan.js +11 -0
  25. package/dist/src/ars/panel-plan.js.map +1 -0
  26. package/dist/src/ars/pi-panel-executor.d.ts +30 -0
  27. package/dist/src/ars/pi-panel-executor.js +140 -0
  28. package/dist/src/ars/pi-panel-executor.js.map +1 -0
  29. package/dist/src/ars/profile.d.ts +36 -0
  30. package/dist/src/ars/profile.js +119 -0
  31. package/dist/src/ars/profile.js.map +1 -0
  32. package/dist/src/ars/re-review.d.ts +22 -0
  33. package/dist/src/ars/re-review.js +172 -0
  34. package/dist/src/ars/re-review.js.map +1 -0
  35. package/dist/src/branding.d.ts +2 -4
  36. package/dist/src/branding.js +3 -5
  37. package/dist/src/branding.js.map +1 -1
  38. package/dist/src/bundled-tools.d.ts +3 -0
  39. package/dist/src/bundled-tools.js +21 -0
  40. package/dist/src/bundled-tools.js.map +1 -0
  41. package/dist/src/chat.js +6 -5
  42. package/dist/src/chat.js.map +1 -1
  43. package/dist/src/cli.js +6 -3
  44. package/dist/src/cli.js.map +1 -1
  45. package/dist/src/creation/contracts.d.ts +41 -0
  46. package/dist/src/creation/contracts.js +2 -0
  47. package/dist/src/creation/contracts.js.map +1 -0
  48. package/dist/src/creation/service.d.ts +7 -0
  49. package/dist/src/creation/service.js +208 -0
  50. package/dist/src/creation/service.js.map +1 -0
  51. package/dist/src/index.d.ts +11 -0
  52. package/dist/src/index.js +11 -0
  53. package/dist/src/index.js.map +1 -1
  54. package/dist/src/install/installer.js +32 -10
  55. package/dist/src/install/installer.js.map +1 -1
  56. package/dist/src/orchestration/bundled-personas.d.ts +16 -0
  57. package/dist/src/orchestration/bundled-personas.js +101 -0
  58. package/dist/src/orchestration/bundled-personas.js.map +1 -0
  59. package/dist/src/orchestration/effects.d.ts +8 -0
  60. package/dist/src/orchestration/effects.js +39 -0
  61. package/dist/src/orchestration/effects.js.map +1 -0
  62. package/dist/src/orchestration/personas.d.ts +16 -0
  63. package/dist/src/orchestration/personas.js +64 -0
  64. package/dist/src/orchestration/personas.js.map +1 -0
  65. package/dist/src/orchestration/pi-executor.d.ts +9 -4
  66. package/dist/src/orchestration/pi-executor.js +55 -22
  67. package/dist/src/orchestration/pi-executor.js.map +1 -1
  68. package/dist/src/orchestration/research-agents.d.ts +3 -7
  69. package/dist/src/orchestration/research-agents.js +5 -69
  70. package/dist/src/orchestration/research-agents.js.map +1 -1
  71. package/dist/src/panel/server.js +19 -4
  72. package/dist/src/panel/server.js.map +1 -1
  73. package/dist/src/project/paths.d.ts +3 -0
  74. package/dist/src/project/paths.js +7 -0
  75. package/dist/src/project/paths.js.map +1 -1
  76. package/dist/src/rules/user-rules.d.ts +8 -0
  77. package/dist/src/rules/user-rules.js +36 -0
  78. package/dist/src/rules/user-rules.js.map +1 -0
  79. package/dist/src/skills/contracts.d.ts +4 -4
  80. package/dist/src/skills/recommended.js +1 -1
  81. package/dist/src/skills/registry.js +49 -28
  82. package/dist/src/skills/registry.js.map +1 -1
  83. package/dist/src/style/cli-ui.d.ts +14 -0
  84. package/dist/src/style/cli-ui.js +45 -1
  85. package/dist/src/style/cli-ui.js.map +1 -1
  86. package/dist/src/tui/skill-manager.js +1 -1
  87. package/dist/src/tui/skill-manager.js.map +1 -1
  88. package/dist/src/updates/update.js +9 -3
  89. package/dist/src/updates/update.js.map +1 -1
  90. package/package.json +15 -3
  91. package/scripts/rebrand-pi.mjs +6 -0
  92. package/skills/recommended/catalog.json +2 -11
  93. package/vendor/ars/.claude/CLAUDE.md +371 -0
  94. package/vendor/ars/.command-invariants.toml +24 -0
  95. package/vendor/ars/CITATION.cff +35 -0
  96. package/vendor/ars/LICENSE +417 -0
  97. package/vendor/ars/MODE_REGISTRY.md +76 -0
  98. package/vendor/ars/NOTICE.md +26 -0
  99. package/vendor/ars/POSITIONING.md +99 -0
  100. package/vendor/ars/PSYCLAW_SOURCE.json +10 -0
  101. package/vendor/ars/README.md +751 -0
  102. package/vendor/ars/SECURITY.md +52 -0
  103. package/vendor/ars/THIRD_PARTY.md +70 -0
  104. package/vendor/ars/academic-paper/SKILL.md +542 -0
  105. package/vendor/ars/academic-paper/agents/abstract_bilingual_agent.md +171 -0
  106. package/vendor/ars/academic-paper/agents/argument_builder_agent.md +276 -0
  107. package/vendor/ars/academic-paper/agents/citation_compliance_agent.md +422 -0
  108. package/vendor/ars/academic-paper/agents/draft_writer_agent.md +656 -0
  109. package/vendor/ars/academic-paper/agents/formatter_agent.md +999 -0
  110. package/vendor/ars/academic-paper/agents/intake_agent.md +393 -0
  111. package/vendor/ars/academic-paper/agents/literature_strategist_agent.md +626 -0
  112. package/vendor/ars/academic-paper/agents/peer_reviewer_agent.md +516 -0
  113. package/vendor/ars/academic-paper/agents/revision_coach_agent.md +334 -0
  114. package/vendor/ars/academic-paper/agents/socratic_mentor_agent.md +527 -0
  115. package/vendor/ars/academic-paper/agents/structure_architect_agent.md +401 -0
  116. package/vendor/ars/academic-paper/agents/visualization_agent.md +441 -0
  117. package/vendor/ars/academic-paper/examples/chinese_paper_example.md +278 -0
  118. package/vendor/ars/academic-paper/examples/clinical_citation_verification_checklist.md +95 -0
  119. package/vendor/ars/academic-paper/examples/clinical_epistemic_status_example.md +100 -0
  120. package/vendor/ars/academic-paper/examples/commitment_ledger_example.md +147 -0
  121. package/vendor/ars/academic-paper/examples/imrad_hei_example.md +234 -0
  122. package/vendor/ars/academic-paper/examples/literature_review_example.md +260 -0
  123. package/vendor/ars/academic-paper/examples/plan_mode_guided_writing.md +600 -0
  124. package/vendor/ars/academic-paper/examples/revision_mode_example.md +344 -0
  125. package/vendor/ars/academic-paper/examples/revision_recovery_example.md +506 -0
  126. package/vendor/ars/academic-paper/examples/version_family_reconciliation_example.md +89 -0
  127. package/vendor/ars/academic-paper/references/abstract_writing_guide.md +169 -0
  128. package/vendor/ars/academic-paper/references/academic_writing_style.md +188 -0
  129. package/vendor/ars/academic-paper/references/anti_leakage_protocol.md +83 -0
  130. package/vendor/ars/academic-paper/references/apa7_chinese_citation_guide.md +364 -0
  131. package/vendor/ars/academic-paper/references/apa7_extended_guide.md +198 -0
  132. package/vendor/ars/academic-paper/references/changelog.md +11 -0
  133. package/vendor/ars/academic-paper/references/citation_format_switcher.md +228 -0
  134. package/vendor/ars/academic-paper/references/committee_correspondence_protocol.md +158 -0
  135. package/vendor/ars/academic-paper/references/credit_authorship_guide.md +308 -0
  136. package/vendor/ars/academic-paper/references/disclosure_mode_protocol.md +478 -0
  137. package/vendor/ars/academic-paper/references/domain_evidence_profiles.md +38 -0
  138. package/vendor/ars/academic-paper/references/failure_paths.md +349 -0
  139. package/vendor/ars/academic-paper/references/funding_statement_guide.md +319 -0
  140. package/vendor/ars/academic-paper/references/hei_domain_glossary.md +169 -0
  141. package/vendor/ars/academic-paper/references/intro_title_rhetoric_guide.md +114 -0
  142. package/vendor/ars/academic-paper/references/journal_submission_guide.md +249 -0
  143. package/vendor/ars/academic-paper/references/latex_template_reference.md +378 -0
  144. package/vendor/ars/academic-paper/references/mode_selection_guide.md +378 -0
  145. package/vendor/ars/academic-paper/references/paper_structure_patterns.md +330 -0
  146. package/vendor/ars/academic-paper/references/plan_mode_protocol.md +112 -0
  147. package/vendor/ars/academic-paper/references/policy_anchor_disclosure_protocol.md +200 -0
  148. package/vendor/ars/academic-paper/references/policy_anchor_table.md +157 -0
  149. package/vendor/ars/academic-paper/references/revision_patch_protocol.md +173 -0
  150. package/vendor/ars/academic-paper/references/statistical_visualization_standards.md +750 -0
  151. package/vendor/ars/academic-paper/references/venue_disclosure_policies.md +259 -0
  152. package/vendor/ars/academic-paper/references/vlm_figure_verification.md +126 -0
  153. package/vendor/ars/academic-paper/references/workflow_phase_details.md +135 -0
  154. package/vendor/ars/academic-paper/references/writing_judgment_framework.md +59 -0
  155. package/vendor/ars/academic-paper/references/writing_quality_check.md +173 -0
  156. package/vendor/ars/academic-paper/templates/bilingual_abstract_template.md +78 -0
  157. package/vendor/ars/academic-paper/templates/case_study_template.md +129 -0
  158. package/vendor/ars/academic-paper/templates/conference_paper_template.md +108 -0
  159. package/vendor/ars/academic-paper/templates/credit_statement_template.md +132 -0
  160. package/vendor/ars/academic-paper/templates/funding_statement_template.md +290 -0
  161. package/vendor/ars/academic-paper/templates/imrad_template.md +183 -0
  162. package/vendor/ars/academic-paper/templates/latex_article_template.tex +199 -0
  163. package/vendor/ars/academic-paper/templates/literature_review_template.md +135 -0
  164. package/vendor/ars/academic-paper/templates/policy_brief_template.md +139 -0
  165. package/vendor/ars/academic-paper/templates/revision_tracking_template.md +199 -0
  166. package/vendor/ars/academic-paper/templates/theoretical_paper_template.md +119 -0
  167. package/vendor/ars/academic-paper-reviewer/SKILL.md +491 -0
  168. package/vendor/ars/academic-paper-reviewer/agents/devils_advocate_reviewer_agent.md +443 -0
  169. package/vendor/ars/academic-paper-reviewer/agents/domain_reviewer_agent.md +412 -0
  170. package/vendor/ars/academic-paper-reviewer/agents/editorial_synthesizer_agent.md +478 -0
  171. package/vendor/ars/academic-paper-reviewer/agents/eic_agent.md +339 -0
  172. package/vendor/ars/academic-paper-reviewer/agents/field_analyst_agent.md +221 -0
  173. package/vendor/ars/academic-paper-reviewer/agents/methodology_reviewer_agent.md +449 -0
  174. package/vendor/ars/academic-paper-reviewer/agents/perspective_reviewer_agent.md +427 -0
  175. package/vendor/ars/academic-paper-reviewer/examples/hei_paper_review_example.md +391 -0
  176. package/vendor/ars/academic-paper-reviewer/examples/interdisciplinary_review_example.md +299 -0
  177. package/vendor/ars/academic-paper-reviewer/examples/subclaim_decomposition_example.md +80 -0
  178. package/vendor/ars/academic-paper-reviewer/references/calibration_mode_protocol.md +256 -0
  179. package/vendor/ars/academic-paper-reviewer/references/changelog.md +10 -0
  180. package/vendor/ars/academic-paper-reviewer/references/editorial_decision_standards.md +236 -0
  181. package/vendor/ars/academic-paper-reviewer/references/guided_mode_protocol.md +34 -0
  182. package/vendor/ars/academic-paper-reviewer/references/integration_guide.md +15 -0
  183. package/vendor/ars/academic-paper-reviewer/references/quality_rubrics.md +84 -0
  184. package/vendor/ars/academic-paper-reviewer/references/re_review_mode_protocol.md +340 -0
  185. package/vendor/ars/academic-paper-reviewer/references/review_criteria_framework.md +98 -0
  186. package/vendor/ars/academic-paper-reviewer/references/review_panel_provenance_protocol.md +197 -0
  187. package/vendor/ars/academic-paper-reviewer/references/review_quality_thinking.md +58 -0
  188. package/vendor/ars/academic-paper-reviewer/references/reviewer_sprint_prompt_source.md +324 -0
  189. package/vendor/ars/academic-paper-reviewer/references/sprint_contract_protocol.md +296 -0
  190. package/vendor/ars/academic-paper-reviewer/references/statistical_reporting_standards.md +505 -0
  191. package/vendor/ars/academic-paper-reviewer/references/top_journals_by_field.md +206 -0
  192. package/vendor/ars/academic-paper-reviewer/templates/editorial_decision_template.md +235 -0
  193. package/vendor/ars/academic-paper-reviewer/templates/peer_review_report_template.md +305 -0
  194. package/vendor/ars/academic-paper-reviewer/templates/revision_response_template.md +248 -0
  195. package/vendor/ars/academic-pipeline/SKILL.md +736 -0
  196. package/vendor/ars/academic-pipeline/agents/claim_ref_alignment_audit_agent.md +382 -0
  197. package/vendor/ars/academic-pipeline/agents/collaboration_depth_agent.md +164 -0
  198. package/vendor/ars/academic-pipeline/agents/integrity_verification_agent.md +870 -0
  199. package/vendor/ars/academic-pipeline/agents/pipeline_orchestrator_agent.md +1379 -0
  200. package/vendor/ars/academic-pipeline/agents/state_tracker_agent.md +622 -0
  201. package/vendor/ars/academic-pipeline/examples/full_pipeline_example.md +482 -0
  202. package/vendor/ars/academic-pipeline/examples/integrity_failure_recovery.md +389 -0
  203. package/vendor/ars/academic-pipeline/examples/mid_entry_example.md +414 -0
  204. package/vendor/ars/academic-pipeline/references/adapters/.gitkeep +0 -0
  205. package/vendor/ars/academic-pipeline/references/adapters/overview.md +153 -0
  206. package/vendor/ars/academic-pipeline/references/ai_research_failure_modes.md +185 -0
  207. package/vendor/ars/academic-pipeline/references/changelog.md +14 -0
  208. package/vendor/ars/academic-pipeline/references/claim_audit_calibration_protocol.md +175 -0
  209. package/vendor/ars/academic-pipeline/references/claim_verification_protocol.md +282 -0
  210. package/vendor/ars/academic-pipeline/references/external_review_protocol.md +131 -0
  211. package/vendor/ars/academic-pipeline/references/integrity_review_protocol.md +110 -0
  212. package/vendor/ars/academic-pipeline/references/literature_corpus_consumers.md +193 -0
  213. package/vendor/ars/academic-pipeline/references/mode_advisor.md +135 -0
  214. package/vendor/ars/academic-pipeline/references/passport_as_reset_boundary.md +132 -0
  215. package/vendor/ars/academic-pipeline/references/pipeline_state_machine.md +405 -0
  216. package/vendor/ars/academic-pipeline/references/plagiarism_detection_protocol.md +239 -0
  217. package/vendor/ars/academic-pipeline/references/process_summary_protocol.md +209 -0
  218. package/vendor/ars/academic-pipeline/references/progress_dashboard_template.md +38 -0
  219. package/vendor/ars/academic-pipeline/references/reinforcement_content.md +15 -0
  220. package/vendor/ars/academic-pipeline/references/reproducibility_audit.md +55 -0
  221. package/vendor/ars/academic-pipeline/references/score_trajectory_protocol.md +78 -0
  222. package/vendor/ars/academic-pipeline/references/team_collaboration_protocol.md +261 -0
  223. package/vendor/ars/academic-pipeline/references/two_stage_review_protocol.md +27 -0
  224. package/vendor/ars/academic-pipeline/templates/pipeline_status_template.md +146 -0
  225. package/vendor/ars/agents/report_compiler_agent.md +341 -0
  226. package/vendor/ars/agents/research_architect_agent.md +298 -0
  227. package/vendor/ars/agents/synthesis_agent.md +356 -0
  228. package/vendor/ars/commands/ars-3w.md +10 -0
  229. package/vendor/ars/commands/ars-abstract.md +10 -0
  230. package/vendor/ars/commands/ars-cache-invalidate.md +20 -0
  231. package/vendor/ars/commands/ars-citation-check.md +10 -0
  232. package/vendor/ars/commands/ars-disclosure.md +10 -0
  233. package/vendor/ars/commands/ars-format-convert.md +10 -0
  234. package/vendor/ars/commands/ars-full.md +9 -0
  235. package/vendor/ars/commands/ars-lit-review.md +12 -0
  236. package/vendor/ars/commands/ars-mark-read.md +18 -0
  237. package/vendor/ars/commands/ars-outline.md +10 -0
  238. package/vendor/ars/commands/ars-plan.md +10 -0
  239. package/vendor/ars/commands/ars-rebuttal-audit.md +12 -0
  240. package/vendor/ars/commands/ars-reviewer.md +9 -0
  241. package/vendor/ars/commands/ars-revision-coach.md +9 -0
  242. package/vendor/ars/commands/ars-revision.md +10 -0
  243. package/vendor/ars/commands/ars-unmark-read.md +16 -0
  244. package/vendor/ars/deep-research/SKILL.md +600 -0
  245. package/vendor/ars/deep-research/agents/bibliography_agent.md +473 -0
  246. package/vendor/ars/deep-research/agents/devils_advocate_agent.md +192 -0
  247. package/vendor/ars/deep-research/agents/editor_in_chief_agent.md +167 -0
  248. package/vendor/ars/deep-research/agents/ethics_review_agent.md +267 -0
  249. package/vendor/ars/deep-research/agents/meta_analysis_agent.md +325 -0
  250. package/vendor/ars/deep-research/agents/monitoring_agent.md +209 -0
  251. package/vendor/ars/deep-research/agents/report_compiler_agent.md +341 -0
  252. package/vendor/ars/deep-research/agents/research_architect_agent.md +298 -0
  253. package/vendor/ars/deep-research/agents/research_question_agent.md +216 -0
  254. package/vendor/ars/deep-research/agents/risk_of_bias_agent.md +231 -0
  255. package/vendor/ars/deep-research/agents/socratic_mentor_agent.md +764 -0
  256. package/vendor/ars/deep-research/agents/source_verification_agent.md +219 -0
  257. package/vendor/ars/deep-research/agents/synthesis_agent.md +356 -0
  258. package/vendor/ars/deep-research/agents/timeline_extraction_agent.md +99 -0
  259. package/vendor/ars/deep-research/examples/exploratory_research.md +157 -0
  260. package/vendor/ars/deep-research/examples/fact_check_mode.md +173 -0
  261. package/vendor/ars/deep-research/examples/handoff_to_paper.md +318 -0
  262. package/vendor/ars/deep-research/examples/idea_diversity_coverage_gap_advisory.md +64 -0
  263. package/vendor/ars/deep-research/examples/policy_analysis.md +161 -0
  264. package/vendor/ars/deep-research/examples/review_mode.md +253 -0
  265. package/vendor/ars/deep-research/examples/socratic_guided_research.md +331 -0
  266. package/vendor/ars/deep-research/examples/systematic_review.md +133 -0
  267. package/vendor/ars/deep-research/references/apa7_style_guide.md +162 -0
  268. package/vendor/ars/deep-research/references/argumentation_reasoning_framework.md +68 -0
  269. package/vendor/ars/deep-research/references/arxiv_api_protocol.md +76 -0
  270. package/vendor/ars/deep-research/references/changelog.md +22 -0
  271. package/vendor/ars/deep-research/references/chinese_literature_api_protocol.md +317 -0
  272. package/vendor/ars/deep-research/references/cross_agent_quality_definitions.md +14 -0
  273. package/vendor/ars/deep-research/references/crossref_api_protocol.md +84 -0
  274. package/vendor/ars/deep-research/references/equator_reporting_guidelines.md +482 -0
  275. package/vendor/ars/deep-research/references/ethics_checklist.md +282 -0
  276. package/vendor/ars/deep-research/references/failure_paths.md +355 -0
  277. package/vendor/ars/deep-research/references/interdisciplinary_bridges.md +292 -0
  278. package/vendor/ars/deep-research/references/irb_decision_tree.md +315 -0
  279. package/vendor/ars/deep-research/references/literature_monitoring_strategies.md +263 -0
  280. package/vendor/ars/deep-research/references/logical_fallacies.md +192 -0
  281. package/vendor/ars/deep-research/references/methodology_patterns.md +462 -0
  282. package/vendor/ars/deep-research/references/mode_selection_guide.md +331 -0
  283. package/vendor/ars/deep-research/references/openalex_api_protocol.md +82 -0
  284. package/vendor/ars/deep-research/references/preregistration_guide.md +324 -0
  285. package/vendor/ars/deep-research/references/semantic_scholar_api_protocol.md +107 -0
  286. package/vendor/ars/deep-research/references/socratic_mode_protocol.md +99 -0
  287. package/vendor/ars/deep-research/references/socratic_questioning_framework.md +232 -0
  288. package/vendor/ars/deep-research/references/source_quality_hierarchy.md +188 -0
  289. package/vendor/ars/deep-research/references/systematic_review_protocol.md +95 -0
  290. package/vendor/ars/deep-research/references/systematic_review_toolkit.md +353 -0
  291. package/vendor/ars/deep-research/templates/evidence_assessment_template.md +127 -0
  292. package/vendor/ars/deep-research/templates/literature_matrix_template.md +85 -0
  293. package/vendor/ars/deep-research/templates/preregistration_template.md +318 -0
  294. package/vendor/ars/deep-research/templates/prisma_protocol_template.md +248 -0
  295. package/vendor/ars/deep-research/templates/prisma_report_template.md +415 -0
  296. package/vendor/ars/deep-research/templates/research_brief_template.md +93 -0
  297. package/vendor/ars/package.json +24 -0
  298. package/vendor/ars/pi/README.md +161 -0
  299. package/vendor/ars/pi/package.json +26 -0
  300. package/vendor/ars/pi/wrapper.js +193 -0
  301. package/vendor/ars/pi/wrapper.test.mjs +201 -0
  302. package/vendor/ars/pyproject.toml +2 -0
  303. package/vendor/ars/requirements-pdf-content-classifier.txt +5 -0
  304. package/vendor/ars/scripts/_block_parser.py +396 -0
  305. package/vendor/ars/scripts/_ci_pytest_manifest.toml +661 -0
  306. package/vendor/ars/scripts/_claim_audit_constants.py +268 -0
  307. package/vendor/ars/scripts/_e4_evidence.py +110 -0
  308. package/vendor/ars/scripts/_eval_threshold_gate.py +73 -0
  309. package/vendor/ars/scripts/_markdown_lint_util.py +224 -0
  310. package/vendor/ars/scripts/_next_verified_at_ms.py +176 -0
  311. package/vendor/ars/scripts/_passport_yaml.py +53 -0
  312. package/vendor/ars/scripts/_skill_lint.py +254 -0
  313. package/vendor/ars/scripts/_text_similarity.py +141 -0
  314. package/vendor/ars/scripts/adapters/README.md +89 -0
  315. package/vendor/ars/scripts/adapters/_common.py +209 -0
  316. package/vendor/ars/scripts/adapters/examples/folder_scan/expected_passport.yaml +25 -0
  317. package/vendor/ars/scripts/adapters/examples/folder_scan/expected_rejection_log.yaml +18 -0
  318. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Chen2024_AIAssessment.pdf +0 -0
  319. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Wang_2023_formative_feedback.pdf +0 -0
  320. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/paper1.pdf +0 -0
  321. 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
  322. package/vendor/ars/scripts/adapters/examples/obsidian/expected_passport.yaml +40 -0
  323. package/vendor/ars/scripts/adapters/examples/obsidian/expected_rejection_log.yaml +13 -0
  324. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.gitkeep +0 -0
  325. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.obsidian/app.json +1 -0
  326. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/_templates/tmpl.md +7 -0
  327. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/chen2024ai.md +13 -0
  328. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/invalid.md +1 -0
  329. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/wang2023formative.md +10 -0
  330. package/vendor/ars/scripts/adapters/examples/zotero/expected_passport.yaml +34 -0
  331. package/vendor/ars/scripts/adapters/examples/zotero/expected_rejection_log.yaml +34 -0
  332. package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/.gitkeep +0 -0
  333. package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/export.json +43 -0
  334. package/vendor/ars/scripts/adapters/folder_scan.py +214 -0
  335. package/vendor/ars/scripts/adapters/obsidian.py +336 -0
  336. package/vendor/ars/scripts/adapters/tests/.gitkeep +0 -0
  337. package/vendor/ars/scripts/adapters/tests/conftest.py +77 -0
  338. package/vendor/ars/scripts/adapters/tests/test_check_corpus_consumer_protocol.py +632 -0
  339. package/vendor/ars/scripts/adapters/tests/test_check_literature_corpus_schema.py +631 -0
  340. package/vendor/ars/scripts/adapters/tests/test_common.py +365 -0
  341. package/vendor/ars/scripts/adapters/tests/test_conftest.py +96 -0
  342. package/vendor/ars/scripts/adapters/tests/test_folder_scan.py +260 -0
  343. package/vendor/ars/scripts/adapters/tests/test_literature_corpus_entry_schema.py +745 -0
  344. package/vendor/ars/scripts/adapters/tests/test_obsidian.py +357 -0
  345. package/vendor/ars/scripts/adapters/tests/test_rejection_log_schema.py +271 -0
  346. package/vendor/ars/scripts/adapters/tests/test_sync_adapter_docs.py +88 -0
  347. package/vendor/ars/scripts/adapters/tests/test_zotero.py +454 -0
  348. package/vendor/ars/scripts/adapters/zotero.py +318 -0
  349. package/vendor/ars/scripts/adjudication_activity.py +1592 -0
  350. package/vendor/ars/scripts/announce-ars-loaded.sh +144 -0
  351. package/vendor/ars/scripts/ars_anchorize_draft.py +170 -0
  352. package/vendor/ars/scripts/ars_apply_revision_patch.py +912 -0
  353. package/vendor/ars/scripts/ars_cache_invalidate.py +40 -0
  354. package/vendor/ars/scripts/ars_mark_read.py +521 -0
  355. package/vendor/ars/scripts/ars_phase_scope_manifest.json +33 -0
  356. package/vendor/ars/scripts/ars_update_check.sh +215 -0
  357. package/vendor/ars/scripts/ars_write_scope_guard.py +506 -0
  358. package/vendor/ars/scripts/arxiv_client.py +222 -0
  359. package/vendor/ars/scripts/audit_snapshot.py +572 -0
  360. package/vendor/ars/scripts/bibliographic_integrity_signals.py +800 -0
  361. package/vendor/ars/scripts/bootstrap_timeline_yaml.py +146 -0
  362. package/vendor/ars/scripts/build_claim_standing_candidate_ledger.py +1238 -0
  363. package/vendor/ars/scripts/build_claim_standing_query_plan.py +643 -0
  364. package/vendor/ars/scripts/build_content_coverage_advisory.py +1205 -0
  365. package/vendor/ars/scripts/build_cross_document_consistency_advisory.py +2332 -0
  366. package/vendor/ars/scripts/build_review_pathway_rule_trace.py +830 -0
  367. package/vendor/ars/scripts/build_submission_packet_manifest.py +2310 -0
  368. package/vendor/ars/scripts/check_215_field_norm.py +173 -0
  369. package/vendor/ars/scripts/check_216_surface_form.py +250 -0
  370. package/vendor/ars/scripts/check_268_nested_commitment_ledger.py +180 -0
  371. package/vendor/ars/scripts/check_390_revision_patch_discipline.py +296 -0
  372. package/vendor/ars/scripts/check_392_citation_verification_intake.py +126 -0
  373. package/vendor/ars/scripts/check_394_submission_policy.py +178 -0
  374. package/vendor/ars/scripts/check_439_format_profile.py +307 -0
  375. package/vendor/ars/scripts/check_619_disclosure_closeout.py +237 -0
  376. package/vendor/ars/scripts/check_630_codex_subscription_transport.py +456 -0
  377. package/vendor/ars/scripts/check_669_review_pathway_rule_trace.py +662 -0
  378. package/vendor/ars/scripts/check_670_revision_roadmap_integration.py +518 -0
  379. package/vendor/ars/scripts/check_673_adjudication_activity.py +684 -0
  380. package/vendor/ars/scripts/check_684_review_criteria_binding.py +557 -0
  381. package/vendor/ars/scripts/check_agents_mirror_sync.py +115 -0
  382. package/vendor/ars/scripts/check_audit_artifact_consistency.py +2313 -0
  383. package/vendor/ars/scripts/check_benchmark_report.py +79 -0
  384. package/vendor/ars/scripts/check_bibliographic_integrity_signals.py +831 -0
  385. package/vendor/ars/scripts/check_calibration_tiers.py +235 -0
  386. package/vendor/ars/scripts/check_changelog_covers_merges.py +289 -0
  387. package/vendor/ars/scripts/check_ci_pytest_manifest.py +204 -0
  388. package/vendor/ars/scripts/check_claim_audit_consistency.py +1664 -0
  389. package/vendor/ars/scripts/check_claim_standing_candidate_ledger_integration.py +500 -0
  390. package/vendor/ars/scripts/check_claim_standing_freshness.py +253 -0
  391. package/vendor/ars/scripts/check_claim_standing_transmissions.py +449 -0
  392. package/vendor/ars/scripts/check_collaboration_depth_rubric.py +180 -0
  393. package/vendor/ars/scripts/check_command_frontmatter_name.py +116 -0
  394. package/vendor/ars/scripts/check_committee_correspondence.py +333 -0
  395. package/vendor/ars/scripts/check_compliance_report.py +108 -0
  396. package/vendor/ars/scripts/check_content_coverage_advisory_integration.py +796 -0
  397. package/vendor/ars/scripts/check_control_availability.py +172 -0
  398. package/vendor/ars/scripts/check_corpus_consumer_protocol.py +404 -0
  399. package/vendor/ars/scripts/check_cross_document_consistency_advisory_integration.py +1191 -0
  400. package/vendor/ars/scripts/check_cross_model_handoff_contract.py +234 -0
  401. package/vendor/ars/scripts/check_cross_model_verification_sync.py +261 -0
  402. package/vendor/ars/scripts/check_data_access_level.py +131 -0
  403. package/vendor/ars/scripts/check_data_flows.py +252 -0
  404. package/vendor/ars/scripts/check_decision_contract.py +464 -0
  405. package/vendor/ars/scripts/check_degradation_registry.py +326 -0
  406. package/vendor/ars/scripts/check_distribution_surface_claims.py +226 -0
  407. package/vendor/ars/scripts/check_domain_evidence_profile.py +538 -0
  408. package/vendor/ars/scripts/check_e4_promotion.py +195 -0
  409. package/vendor/ars/scripts/check_evals_gold_set.py +279 -0
  410. package/vendor/ars/scripts/check_evidence_row_integration.py +396 -0
  411. package/vendor/ars/scripts/check_experiment_provenance.py +117 -0
  412. package/vendor/ars/scripts/check_field_norm_severity.py +144 -0
  413. package/vendor/ars/scripts/check_firm_rules_sync.py +375 -0
  414. package/vendor/ars/scripts/check_heldout_measurement_report.py +1179 -0
  415. package/vendor/ars/scripts/check_human_subjects_output_contract.py +139 -0
  416. package/vendor/ars/scripts/check_human_subjects_reference_migration.py +844 -0
  417. package/vendor/ars/scripts/check_indirect_prompt_injection_no_call.py +328 -0
  418. package/vendor/ars/scripts/check_instruction_data_boundary.py +236 -0
  419. package/vendor/ars/scripts/check_judge_prompt_version.py +125 -0
  420. package/vendor/ars/scripts/check_literature_corpus_schema.py +402 -0
  421. package/vendor/ars/scripts/check_model_tiering.py +223 -0
  422. package/vendor/ars/scripts/check_panel_synthesis.py +1331 -0
  423. package/vendor/ars/scripts/check_passport_reset_contract.py +214 -0
  424. package/vendor/ars/scripts/check_pattern_eval_manifest.py +422 -0
  425. package/vendor/ars/scripts/check_persuasion_invariance_fixtures.py +637 -0
  426. package/vendor/ars/scripts/check_phase_conformance.py +2180 -0
  427. package/vendor/ars/scripts/check_pipeline_boundary_semantics.py +600 -0
  428. package/vendor/ars/scripts/check_pipeline_integrity.py +340 -0
  429. package/vendor/ars/scripts/check_policy_anchor_protocol.py +159 -0
  430. package/vendor/ars/scripts/check_policy_anchor_table.py +286 -0
  431. package/vendor/ars/scripts/check_preprint_venues_consistency.py +123 -0
  432. package/vendor/ars/scripts/check_prisma_trAIce_freshness.py +84 -0
  433. package/vendor/ars/scripts/check_promotion_bakeoff_preregistration.py +1303 -0
  434. package/vendor/ars/scripts/check_ranking_lift.py +323 -0
  435. package/vendor/ars/scripts/check_re_review_synthesis.py +2719 -0
  436. package/vendor/ars/scripts/check_receipt_enum_sync.py +203 -0
  437. package/vendor/ars/scripts/check_repro_lock.py +85 -0
  438. package/vendor/ars/scripts/check_review_pathway_output.py +276 -0
  439. package/vendor/ars/scripts/check_reviewer_data_fences.py +224 -0
  440. package/vendor/ars/scripts/check_reviewer_finding_contract.py +783 -0
  441. package/vendor/ars/scripts/check_reviewer_role_label.py +447 -0
  442. package/vendor/ars/scripts/check_reviewer_scoring_honesty.py +292 -0
  443. package/vendor/ars/scripts/check_reviewer_sprint_prompt_sync.py +409 -0
  444. package/vendor/ars/scripts/check_revision_claim_drift_suite_v2.py +1622 -0
  445. package/vendor/ars/scripts/check_revision_token_conservation.py +263 -0
  446. package/vendor/ars/scripts/check_risk_register.py +280 -0
  447. package/vendor/ars/scripts/check_role_scoped_contract.py +702 -0
  448. package/vendor/ars/scripts/check_rq_framing_patterns.py +184 -0
  449. package/vendor/ars/scripts/check_rubric_weight_consistency.py +32 -0
  450. package/vendor/ars/scripts/check_seeded_defect_fixtures.py +425 -0
  451. package/vendor/ars/scripts/check_setup_cross_model_parity.py +139 -0
  452. package/vendor/ars/scripts/check_spec_consistency.py +1249 -0
  453. package/vendor/ars/scripts/check_sprint_contract.py +371 -0
  454. package/vendor/ars/scripts/check_stage_capability_matrix.py +784 -0
  455. package/vendor/ars/scripts/check_submission_packet_manifest_integration.py +358 -0
  456. package/vendor/ars/scripts/check_surface_form_parity.py +445 -0
  457. package/vendor/ars/scripts/check_task_type.py +22 -0
  458. package/vendor/ars/scripts/check_tools_allowlist.py +538 -0
  459. package/vendor/ars/scripts/check_tortured_phrase_screening_integration.py +1974 -0
  460. package/vendor/ars/scripts/check_v3_10_134_write_scope.py +286 -0
  461. package/vendor/ars/scripts/check_v3_10_policy.py +656 -0
  462. package/vendor/ars/scripts/check_v3_6_6_ab_manifest.py +364 -0
  463. package/vendor/ars/scripts/check_v3_6_7_pattern_protection.py +1366 -0
  464. package/vendor/ars/scripts/check_v3_6_8_audit_scope_block.py +464 -0
  465. package/vendor/ars/scripts/check_v3_6_8_cite_provenance_pipeline.py +231 -0
  466. package/vendor/ars/scripts/check_v3_6_8_frontmatter_trust_schema.py +248 -0
  467. package/vendor/ars/scripts/check_v3_6_8_mark_read_commands.py +79 -0
  468. package/vendor/ars/scripts/check_v3_6_8_pattern_protection.py +941 -0
  469. package/vendor/ars/scripts/check_v3_7_3_three_layer_citation.py +318 -0
  470. package/vendor/ars/scripts/check_v3_8_annotation_literal_sync.py +228 -0
  471. package/vendor/ars/scripts/check_v3_9_0_triangulation.py +366 -0
  472. package/vendor/ars/scripts/check_v3_9_2_phase_boundary.py +270 -0
  473. package/vendor/ars/scripts/check_v3_9_4_temporal_verification.py +139 -0
  474. package/vendor/ars/scripts/check_venue_disclosure_policies.py +63 -0
  475. package/vendor/ars/scripts/check_version_consistency.py +826 -0
  476. package/vendor/ars/scripts/check_workflow_classification.py +223 -0
  477. package/vendor/ars/scripts/chinese_literature_client.py +1938 -0
  478. package/vendor/ars/scripts/citation_verification_summary.py +85 -0
  479. package/vendor/ars/scripts/claim_audit_calibration.py +517 -0
  480. package/vendor/ars/scripts/claim_audit_finalizer.py +456 -0
  481. package/vendor/ars/scripts/claim_audit_pipeline.py +1594 -0
  482. package/vendor/ars/scripts/claim_registry_coverage.py +493 -0
  483. package/vendor/ars/scripts/claim_standing_discovery.py +784 -0
  484. package/vendor/ars/scripts/claim_standing_stance_runner.py +758 -0
  485. package/vendor/ars/scripts/claim_standing_stance_scorer.py +239 -0
  486. package/vendor/ars/scripts/claim_strength_drift_disposition.py +666 -0
  487. package/vendor/ars/scripts/contamination_signals.py +689 -0
  488. package/vendor/ars/scripts/corpus_consumer_manifest.json +19 -0
  489. package/vendor/ars/scripts/cross_model_codex_transport.py +1374 -0
  490. package/vendor/ars/scripts/cross_model_codex_verify.sh +6 -0
  491. package/vendor/ars/scripts/cross_model_handoff.py +359 -0
  492. package/vendor/ars/scripts/cross_model_smoke_test.sh +183 -0
  493. package/vendor/ars/scripts/cross_model_smoke_test_codex.sh +35 -0
  494. package/vendor/ars/scripts/cross_model_verification/gemini_is_grounded.jq +45 -0
  495. package/vendor/ars/scripts/cross_model_verification/gemini_sources.jq +45 -0
  496. package/vendor/ars/scripts/cross_model_verification/normalize_compat_verdict.py +57 -0
  497. package/vendor/ars/scripts/cross_model_verification/openai_has_completed_web_search.jq +15 -0
  498. package/vendor/ars/scripts/cross_model_verification/openai_sources.jq +17 -0
  499. package/vendor/ars/scripts/cross_model_verification/openai_text.jq +11 -0
  500. package/vendor/ars/scripts/crossref_client.py +225 -0
  501. package/vendor/ars/scripts/dispatch_e4_panel.py +2731 -0
  502. package/vendor/ars/scripts/evidence_rows.py +2043 -0
  503. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3.json +32 -0
  504. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_input.json +27 -0
  505. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime.json +32 -0
  506. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime_input.json +27 -0
  507. package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override.json +50 -0
  508. package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override_action.json +14 -0
  509. package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_pass.json +44 -0
  510. package/vendor/ars/scripts/fixtures/adjudication_activity/explicit_user_request_log.json +26 -0
  511. package/vendor/ars/scripts/fixtures/adjudication_activity/mandatory_checkpoint_log.json +116 -0
  512. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/README.md +56 -0
  513. 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
  514. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
  515. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
  516. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
  517. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a7_orphan_completion/2026-04-30T15-22-04Z-d8f3.jsonl +3 -0
  518. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +26 -0
  519. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
  520. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
  521. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
  522. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +24 -0
  523. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
  524. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
  525. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.verdict.yaml +12 -0
  526. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction.json +46 -0
  527. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction_check_attestation.json +44 -0
  528. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase.json +44 -0
  529. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_abstract_missing.json +107 -0
  530. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_detected.json +124 -0
  531. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/expected_outcomes.json +77 -0
  532. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/manifest.yaml +37 -0
  533. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/001-valid-doi-test.json +20 -0
  534. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/002-valid-arxiv-test.json +19 -0
  535. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/003-fabricated-test.json +20 -0
  536. package/vendor/ars/scripts/fixtures/claim_audit_calibration/gold_set.json +344 -0
  537. package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/query_plan.json +108 -0
  538. package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/retrieval_input.json +179 -0
  539. package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/concern_tracker.json +129 -0
  540. package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/response_skeleton.md +19 -0
  541. package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/source_letter.txt +9 -0
  542. package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_draft.json +48 -0
  543. package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_inventory.json +38 -0
  544. package/vendor/ars/scripts/fixtures/content_coverage_advisory/packet/consent.txt +1 -0
  545. package/vendor/ars/scripts/fixtures/content_coverage_advisory/session_sources.json +3 -0
  546. package/vendor/ars/scripts/fixtures/cross_document_consistency/README.md +6 -0
  547. package/vendor/ars/scripts/fixtures/cross_document_consistency/accepted_draft.md +35 -0
  548. package/vendor/ars/scripts/fixtures/cross_document_consistency/cases.json +75 -0
  549. package/vendor/ars/scripts/fixtures/cross_document_consistency/preregistration.md +5 -0
  550. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/forbidden_event.jsonl +4 -0
  551. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/grounded_verified.jsonl +3 -0
  552. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/malformed.jsonl +3 -0
  553. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/missing_search.jsonl +2 -0
  554. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/multiple_finals.jsonl +4 -0
  555. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/not_found.jsonl +3 -0
  556. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/unbound_source.jsonl +3 -0
  557. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/wrong_search_shape.jsonl +3 -0
  558. package/vendor/ars/scripts/fixtures/evidence_rows/phase_e_inputs.json +114 -0
  559. package/vendor/ars/scripts/fixtures/evidence_rows/session_sources.json +5 -0
  560. package/vendor/ars/scripts/fixtures/human_subjects_authority/cross-border-us-tw-gdpr.json +71 -0
  561. package/vendor/ars/scripts/fixtures/human_subjects_authority/gdpr-member-state-unresolved.json +62 -0
  562. package/vendor/ars/scripts/fixtures/human_subjects_authority/missing-data-axis.json +45 -0
  563. package/vendor/ars/scripts/fixtures/human_subjects_authority/no-profile.json +33 -0
  564. package/vendor/ars/scripts/fixtures/human_subjects_authority/tw-gdpr-two-axis.json +74 -0
  565. package/vendor/ars/scripts/fixtures/human_subjects_authority/us-gdpr-two-axis.json +74 -0
  566. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/lint-near-misses.json +72 -0
  567. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/no-profile-request.json +18 -0
  568. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/tw-candidates-request.json +63 -0
  569. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/us-candidates-request.json +77 -0
  570. package/vendor/ars/scripts/fixtures/review_target_context/exact-declaration.json +24 -0
  571. package/vendor/ars/scripts/fixtures/review_target_context/field-general-declaration.json +24 -0
  572. package/vendor/ars/scripts/fixtures/review_target_context/msr-2027-technical-full-declaration.json +24 -0
  573. package/vendor/ars/scripts/fixtures/review_target_context/synthetic-registry.json +92 -0
  574. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/README.md +21 -0
  575. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_attested_only.json +55 -0
  576. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_machine_supported.json +49 -0
  577. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_not_isolated.json +56 -0
  578. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_unknown.json +49 -0
  579. package/vendor/ars/scripts/fixtures/submission_package/clean/paper.md +16 -0
  580. package/vendor/ars/scripts/fixtures/submission_package/clean/references.bib +13 -0
  581. package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/paper.md +11 -0
  582. package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/references.bib +13 -0
  583. package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/paper.tex +9 -0
  584. package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/references.bib +13 -0
  585. package/vendor/ars/scripts/fixtures/submission_package/marker_no_join/paper.md +7 -0
  586. package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/paper.md +8 -0
  587. package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/references.bib +6 -0
  588. package/vendor/ars/scripts/fixtures/submission_package/passports/corpus_only.yaml +9 -0
  589. package/vendor/ars/scripts/fixtures/submission_package/passports/summary_join.yaml +16 -0
  590. package/vendor/ars/scripts/fixtures/submission_package/profiles/full.yaml +15 -0
  591. package/vendor/ars/scripts/fixtures/submission_package/profiles/tight.yaml +15 -0
  592. package/vendor/ars/scripts/fixtures/submission_package/summary_join/paper.md +7 -0
  593. package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/paper.md +9 -0
  594. package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/references.bib +13 -0
  595. package/vendor/ars/scripts/fixtures/submission_package/venue_clean/paper.md +26 -0
  596. package/vendor/ars/scripts/fixtures/submission_package/venue_clean/references.bib +13 -0
  597. package/vendor/ars/scripts/fixtures/submission_package/venue_violations/paper.md +20 -0
  598. package/vendor/ars/scripts/fixtures/submission_package/venue_violations/references.bib +13 -0
  599. package/vendor/ars/scripts/fixtures/submission_packet_manifest/base_inventory.json +101 -0
  600. package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/consent-materials.txt +3 -0
  601. package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/training-certificate.txt +2 -0
  602. package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/tw-consent-materials.txt +3 -0
  603. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/corpus_input.yaml +30 -0
  604. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.md +36 -0
  605. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.tex +21 -0
  606. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/seed_expectations.json +218 -0
  607. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot.json +164 -0
  608. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot_manifest.json +29 -0
  609. package/vendor/ars/scripts/fixtures/transport_bodies/README.md +55 -0
  610. package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/empty_feed.xml +10 -0
  611. package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/error_5xx.html +7 -0
  612. package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/id_hit.xml +26 -0
  613. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/README.md +69 -0
  614. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/cnki_landing_page.html +13 -0
  615. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/error_5xx.html +2 -0
  616. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_ambiguous.json +16 -0
  617. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_hit.json +16 -0
  618. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_zero.json +14 -0
  619. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_hit.json +15 -0
  620. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_zero.json +13 -0
  621. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_hit.json +43 -0
  622. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_issn_mismatch.json +31 -0
  623. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_no_doi.json +31 -0
  624. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_unknown_ra.json +34 -0
  625. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_year_mismatch.json +31 -0
  626. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_absent.json +4 -0
  627. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_exists.json +16 -0
  628. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_internal_error.json +4 -0
  629. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_hit.json +25 -0
  630. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_other_title.json +18 -0
  631. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_cnki.json +6 -0
  632. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_crossref.json +6 -0
  633. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_istic.json +6 -0
  634. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_unknown_prefix.json +6 -0
  635. package/vendor/ars/scripts/fixtures/transport_bodies/crossref/doi_hit.json +24 -0
  636. package/vendor/ars/scripts/fixtures/transport_bodies/crossref/error_5xx.html +7 -0
  637. package/vendor/ars/scripts/fixtures/transport_bodies/crossref/title_search_miss.json +15 -0
  638. package/vendor/ars/scripts/fixtures/transport_bodies/openalex/doi_hit.json +29 -0
  639. package/vendor/ars/scripts/fixtures/transport_bodies/openalex/error_5xx.json +4 -0
  640. package/vendor/ars/scripts/fixtures/transport_bodies/openalex/title_search_miss.json +11 -0
  641. package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/doi_hit.json +14 -0
  642. package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/error_5xx.json +3 -0
  643. package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/title_search_miss.json +5 -0
  644. package/vendor/ars/scripts/human_read_attestation_resolver.py +467 -0
  645. package/vendor/ars/scripts/ideation_diversity_assignment_gate.py +430 -0
  646. package/vendor/ars/scripts/inquiry_branch_ledger.py +2540 -0
  647. package/vendor/ars/scripts/legacy/ars_apply_revision_patch_v1_0.py +715 -0
  648. package/vendor/ars/scripts/legacy/check_re_review_synthesis_v1_0.py +2215 -0
  649. package/vendor/ars/scripts/migrate_literature_corpus_to_v3_10.py +223 -0
  650. package/vendor/ars/scripts/migrate_literature_corpus_to_v3_7_3.py +277 -0
  651. package/vendor/ars/scripts/migrate_literature_corpus_to_v3_9_0.py +304 -0
  652. package/vendor/ars/scripts/model_tiering_manifest.json +46 -0
  653. package/vendor/ars/scripts/openalex_client.py +232 -0
  654. package/vendor/ars/scripts/parse_audit_verdict.py +802 -0
  655. package/vendor/ars/scripts/pdf_content_classifier_worker.py +176 -0
  656. package/vendor/ars/scripts/pdf_read_preflight.py +1455 -0
  657. package/vendor/ars/scripts/policy_anchor_disclosure_referee.py +354 -0
  658. package/vendor/ars/scripts/recompute_receipts.py +1414 -0
  659. package/vendor/ars/scripts/render_claim_standing_view.py +374 -0
  660. package/vendor/ars/scripts/render_eval_comment.py +130 -0
  661. package/vendor/ars/scripts/render_harness_retirement_issue.py +167 -0
  662. package/vendor/ars/scripts/repro_lock_validation.py +90 -0
  663. package/vendor/ars/scripts/research_workflow_profile.py +1079 -0
  664. package/vendor/ars/scripts/resolve_human_subjects_authority.py +1158 -0
  665. package/vendor/ars/scripts/resolve_review_target_context.py +683 -0
  666. package/vendor/ars/scripts/resume_e4_record.py +509 -0
  667. package/vendor/ars/scripts/retraction_status.py +484 -0
  668. package/vendor/ars/scripts/review_criteria_binding.py +889 -0
  669. package/vendor/ars/scripts/review_panel_provenance.py +744 -0
  670. package/vendor/ars/scripts/revision_roadmap.py +1967 -0
  671. package/vendor/ars/scripts/run_ci_pytest_manifest.py +116 -0
  672. package/vendor/ars/scripts/run_codex_audit.sh +1191 -0
  673. package/vendor/ars/scripts/run_evals.py +513 -0
  674. package/vendor/ars/scripts/run_ideation_diversity_no_call.py +3505 -0
  675. package/vendor/ars/scripts/run_indirect_prompt_injection_no_call.py +3330 -0
  676. package/vendor/ars/scripts/run_indirect_prompt_injection_probe.py +399 -0
  677. package/vendor/ars/scripts/run_review_criteria_constructive_value.py +1895 -0
  678. package/vendor/ars/scripts/run_role_topology_utility_dry_run.py +606 -0
  679. package/vendor/ars/scripts/score_review_criteria_constructive_value.py +617 -0
  680. package/vendor/ars/scripts/semantic_scholar_client.py +291 -0
  681. package/vendor/ars/scripts/slr_lineage.py +59 -0
  682. package/vendor/ars/scripts/sync_adapter_docs.py +118 -0
  683. package/vendor/ars/scripts/temporal_integrity_audit.py +840 -0
  684. package/vendor/ars/scripts/test_431_exact_or_bust.py +253 -0
  685. package/vendor/ars/scripts/test__eval_threshold_gate.py +125 -0
  686. package/vendor/ars/scripts/test__markdown_lint_util.py +129 -0
  687. package/vendor/ars/scripts/test__next_verified_at_ms.py +256 -0
  688. package/vendor/ars/scripts/test_adjacent_framing_probe_lint.py +171 -0
  689. package/vendor/ars/scripts/test_adjudication_activity.py +1515 -0
  690. package/vendor/ars/scripts/test_ars_anchorize_draft.py +178 -0
  691. package/vendor/ars/scripts/test_ars_apply_revision_patch.py +1335 -0
  692. package/vendor/ars/scripts/test_ars_cache_invalidate.py +51 -0
  693. package/vendor/ars/scripts/test_ars_mark_read.py +910 -0
  694. package/vendor/ars/scripts/test_ars_update_check.py +816 -0
  695. package/vendor/ars/scripts/test_ars_write_scope_guard.py +790 -0
  696. package/vendor/ars/scripts/test_arxiv_client.py +374 -0
  697. package/vendor/ars/scripts/test_audit_schemas.py +560 -0
  698. package/vendor/ars/scripts/test_audit_snapshot_render_section_0.py +105 -0
  699. package/vendor/ars/scripts/test_block_parser.py +259 -0
  700. package/vendor/ars/scripts/test_bootstrap_timeline_yaml.py +148 -0
  701. package/vendor/ars/scripts/test_build_claim_standing_candidate_ledger.py +1203 -0
  702. package/vendor/ars/scripts/test_build_claim_standing_query_plan.py +791 -0
  703. package/vendor/ars/scripts/test_build_submission_packet_manifest.py +2644 -0
  704. package/vendor/ars/scripts/test_check_215_field_norm.py +238 -0
  705. package/vendor/ars/scripts/test_check_216_surface_form.py +341 -0
  706. package/vendor/ars/scripts/test_check_268_nested_commitment_ledger.py +194 -0
  707. package/vendor/ars/scripts/test_check_390_revision_patch_discipline.py +279 -0
  708. package/vendor/ars/scripts/test_check_392_citation_verification_intake.py +144 -0
  709. package/vendor/ars/scripts/test_check_394_submission_policy.py +200 -0
  710. package/vendor/ars/scripts/test_check_439_format_profile.py +252 -0
  711. package/vendor/ars/scripts/test_check_619_disclosure_closeout.py +158 -0
  712. package/vendor/ars/scripts/test_check_630_codex_subscription_transport.py +245 -0
  713. package/vendor/ars/scripts/test_check_669_review_pathway_rule_trace.py +304 -0
  714. package/vendor/ars/scripts/test_check_670_revision_roadmap_integration.py +260 -0
  715. package/vendor/ars/scripts/test_check_673_adjudication_activity.py +367 -0
  716. package/vendor/ars/scripts/test_check_684_review_criteria_binding.py +274 -0
  717. package/vendor/ars/scripts/test_check_agents_mirror_sync.py +137 -0
  718. package/vendor/ars/scripts/test_check_audit_artifact_consistency.py +2133 -0
  719. package/vendor/ars/scripts/test_check_benchmark_report.py +117 -0
  720. package/vendor/ars/scripts/test_check_bibliographic_integrity_signals.py +609 -0
  721. package/vendor/ars/scripts/test_check_calibration_tiers.py +376 -0
  722. package/vendor/ars/scripts/test_check_changelog_covers_merges.py +509 -0
  723. package/vendor/ars/scripts/test_check_ci_pytest_manifest.py +425 -0
  724. package/vendor/ars/scripts/test_check_claim_standing_candidate_ledger_integration.py +256 -0
  725. package/vendor/ars/scripts/test_check_claim_standing_freshness.py +277 -0
  726. package/vendor/ars/scripts/test_check_collaboration_depth_rubric.py +239 -0
  727. package/vendor/ars/scripts/test_check_command_frontmatter_name.py +178 -0
  728. package/vendor/ars/scripts/test_check_committee_correspondence.py +298 -0
  729. package/vendor/ars/scripts/test_check_compliance_report.py +381 -0
  730. package/vendor/ars/scripts/test_check_content_coverage_advisory_integration.py +308 -0
  731. package/vendor/ars/scripts/test_check_control_availability.py +361 -0
  732. package/vendor/ars/scripts/test_check_cross_document_consistency_advisory_integration.py +698 -0
  733. package/vendor/ars/scripts/test_check_cross_model_handoff_contract.py +301 -0
  734. package/vendor/ars/scripts/test_check_cross_model_verification_sync.py +203 -0
  735. package/vendor/ars/scripts/test_check_data_access_level.py +227 -0
  736. package/vendor/ars/scripts/test_check_data_flows.py +382 -0
  737. package/vendor/ars/scripts/test_check_decision_contract.py +387 -0
  738. package/vendor/ars/scripts/test_check_degradation_registry.py +270 -0
  739. package/vendor/ars/scripts/test_check_distribution_surface_claims.py +225 -0
  740. package/vendor/ars/scripts/test_check_domain_evidence_profile.py +439 -0
  741. package/vendor/ars/scripts/test_check_e4_promotion.py +163 -0
  742. package/vendor/ars/scripts/test_check_evals_gold_set.py +312 -0
  743. package/vendor/ars/scripts/test_check_evidence_row_integration.py +187 -0
  744. package/vendor/ars/scripts/test_check_field_norm_severity.py +173 -0
  745. package/vendor/ars/scripts/test_check_firm_rules_sync.py +342 -0
  746. package/vendor/ars/scripts/test_check_heldout_measurement_report.py +1508 -0
  747. package/vendor/ars/scripts/test_check_human_subjects_output_contract.py +129 -0
  748. package/vendor/ars/scripts/test_check_human_subjects_reference_migration.py +756 -0
  749. package/vendor/ars/scripts/test_check_instruction_data_boundary.py +204 -0
  750. package/vendor/ars/scripts/test_check_judge_prompt_version.py +90 -0
  751. package/vendor/ars/scripts/test_check_model_tiering.py +236 -0
  752. package/vendor/ars/scripts/test_check_panel_synthesis.py +1658 -0
  753. package/vendor/ars/scripts/test_check_passport_reset_contract.py +249 -0
  754. package/vendor/ars/scripts/test_check_pattern_eval_manifest.py +381 -0
  755. package/vendor/ars/scripts/test_check_persuasion_invariance_fixtures.py +591 -0
  756. package/vendor/ars/scripts/test_check_phase_conformance.py +4366 -0
  757. package/vendor/ars/scripts/test_check_pipeline_boundary_semantics.py +931 -0
  758. package/vendor/ars/scripts/test_check_pipeline_integrity.py +243 -0
  759. package/vendor/ars/scripts/test_check_policy_anchor_protocol.py +236 -0
  760. package/vendor/ars/scripts/test_check_policy_anchor_table.py +295 -0
  761. package/vendor/ars/scripts/test_check_prisma_trAIce_freshness.py +68 -0
  762. package/vendor/ars/scripts/test_check_promotion_bakeoff_preregistration.py +799 -0
  763. package/vendor/ars/scripts/test_check_ranking_lift.py +377 -0
  764. package/vendor/ars/scripts/test_check_re_review_synthesis.py +3398 -0
  765. package/vendor/ars/scripts/test_check_receipt_enum_sync.py +176 -0
  766. package/vendor/ars/scripts/test_check_repro_lock.py +107 -0
  767. package/vendor/ars/scripts/test_check_reviewer_data_fences.py +334 -0
  768. package/vendor/ars/scripts/test_check_reviewer_finding_contract.py +1107 -0
  769. package/vendor/ars/scripts/test_check_reviewer_role_label.py +491 -0
  770. package/vendor/ars/scripts/test_check_reviewer_scoring_honesty.py +222 -0
  771. package/vendor/ars/scripts/test_check_reviewer_sprint_prompt_sync.py +373 -0
  772. package/vendor/ars/scripts/test_check_revision_claim_drift_suite_v2.py +1925 -0
  773. package/vendor/ars/scripts/test_check_revision_token_conservation.py +398 -0
  774. package/vendor/ars/scripts/test_check_risk_register.py +336 -0
  775. package/vendor/ars/scripts/test_check_role_scoped_contract.py +1104 -0
  776. package/vendor/ars/scripts/test_check_rq_framing_patterns.py +110 -0
  777. package/vendor/ars/scripts/test_check_rubric_weight_consistency.py +32 -0
  778. package/vendor/ars/scripts/test_check_seeded_defect_fixtures.py +392 -0
  779. package/vendor/ars/scripts/test_check_setup_cross_model_parity.py +121 -0
  780. package/vendor/ars/scripts/test_check_spec_consistency.py +1081 -0
  781. package/vendor/ars/scripts/test_check_sprint_contract.py +458 -0
  782. package/vendor/ars/scripts/test_check_stage_capability_matrix.py +698 -0
  783. package/vendor/ars/scripts/test_check_submission_packet_manifest_integration.py +177 -0
  784. package/vendor/ars/scripts/test_check_surface_form_parity.py +417 -0
  785. package/vendor/ars/scripts/test_check_task_type.py +116 -0
  786. package/vendor/ars/scripts/test_check_tools_allowlist.py +835 -0
  787. package/vendor/ars/scripts/test_check_tortured_phrase_screening_integration.py +1306 -0
  788. package/vendor/ars/scripts/test_check_v3_10_134_write_scope.py +251 -0
  789. package/vendor/ars/scripts/test_check_v3_10_policy.py +547 -0
  790. package/vendor/ars/scripts/test_check_v3_6_7_pattern_protection.py +960 -0
  791. package/vendor/ars/scripts/test_check_v3_6_8_audit_scope_block.py +1000 -0
  792. package/vendor/ars/scripts/test_check_v3_6_8_cite_provenance_pipeline.py +454 -0
  793. package/vendor/ars/scripts/test_check_v3_6_8_frontmatter_trust_schema.py +581 -0
  794. package/vendor/ars/scripts/test_check_v3_6_8_mark_read_commands.py +120 -0
  795. package/vendor/ars/scripts/test_check_v3_6_8_pattern_protection.py +1138 -0
  796. package/vendor/ars/scripts/test_check_v3_7_3_three_layer_citation.py +566 -0
  797. package/vendor/ars/scripts/test_check_v3_8_annotation_literal_sync.py +262 -0
  798. package/vendor/ars/scripts/test_check_v3_9_0_triangulation.py +321 -0
  799. package/vendor/ars/scripts/test_check_v3_9_2_phase_boundary.py +190 -0
  800. package/vendor/ars/scripts/test_check_v3_9_4_temporal_verification.py +471 -0
  801. package/vendor/ars/scripts/test_check_version_consistency.py +1462 -0
  802. package/vendor/ars/scripts/test_check_workflow_classification.py +225 -0
  803. package/vendor/ars/scripts/test_chinese_literature_client.py +1889 -0
  804. package/vendor/ars/scripts/test_citation_existence_policy.py +480 -0
  805. package/vendor/ars/scripts/test_citation_verification_summary.py +342 -0
  806. package/vendor/ars/scripts/test_claim_audit_calibration.py +882 -0
  807. package/vendor/ars/scripts/test_claim_audit_finalizer.py +979 -0
  808. package/vendor/ars/scripts/test_claim_audit_pipeline.py +2398 -0
  809. package/vendor/ars/scripts/test_claim_audit_schema.py +1778 -0
  810. package/vendor/ars/scripts/test_claim_intent_manifest.py +666 -0
  811. package/vendor/ars/scripts/test_claim_registry_coverage.py +246 -0
  812. package/vendor/ars/scripts/test_claim_standing_discovery.py +579 -0
  813. package/vendor/ars/scripts/test_claim_standing_pipeline_wiring.py +290 -0
  814. package/vendor/ars/scripts/test_claim_standing_stance_assets.py +399 -0
  815. package/vendor/ars/scripts/test_claim_standing_stance_contracts.py +383 -0
  816. package/vendor/ars/scripts/test_claim_standing_stance_runner.py +349 -0
  817. package/vendor/ars/scripts/test_claim_standing_transmissions.py +605 -0
  818. package/vendor/ars/scripts/test_claim_strength_drift_disposition.py +517 -0
  819. package/vendor/ars/scripts/test_claim_verification_coverage_contract.py +104 -0
  820. package/vendor/ars/scripts/test_contamination_signals.py +1086 -0
  821. package/vendor/ars/scripts/test_content_coverage_advisory.py +1767 -0
  822. package/vendor/ars/scripts/test_cross_document_consistency_advisory.py +1270 -0
  823. package/vendor/ars/scripts/test_cross_model_codex_transport.py +1152 -0
  824. package/vendor/ars/scripts/test_cross_model_handoff.py +571 -0
  825. package/vendor/ars/scripts/test_cross_model_verification_guards.py +792 -0
  826. package/vendor/ars/scripts/test_crossref_client.py +393 -0
  827. package/vendor/ars/scripts/test_dispatch_e4_panel.py +4235 -0
  828. package/vendor/ars/scripts/test_e2e_claim_audit.py +540 -0
  829. package/vendor/ars/scripts/test_eval_harness_workflow.py +140 -0
  830. package/vendor/ars/scripts/test_evals_citation_extraction.py +150 -0
  831. package/vendor/ars/scripts/test_evals_lift_report_schema.py +108 -0
  832. package/vendor/ars/scripts/test_evidence_rows.py +2593 -0
  833. package/vendor/ars/scripts/test_experiment_provenance.py +915 -0
  834. package/vendor/ars/scripts/test_human_read_attestation_resolver.py +477 -0
  835. package/vendor/ars/scripts/test_ideation_diversity_assignment_gate.py +615 -0
  836. package/vendor/ars/scripts/test_indirect_prompt_injection_behavior_probe.py +250 -0
  837. package/vendor/ars/scripts/test_inquiry_branch_ledger.py +2296 -0
  838. package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_10.py +248 -0
  839. package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_7_3.py +545 -0
  840. package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_9_0.py +497 -0
  841. package/vendor/ars/scripts/test_normalize_compat_verdict.py +149 -0
  842. package/vendor/ars/scripts/test_openalex_client.py +490 -0
  843. package/vendor/ars/scripts/test_passport_yaml.py +104 -0
  844. package/vendor/ars/scripts/test_pattern_eval_runtime.py +1295 -0
  845. package/vendor/ars/scripts/test_pdf_read_preflight.py +1943 -0
  846. package/vendor/ars/scripts/test_policy_anchor_disclosure.py +666 -0
  847. package/vendor/ars/scripts/test_reading_probe_lint.py +218 -0
  848. package/vendor/ars/scripts/test_recompute_receipts.py +778 -0
  849. package/vendor/ars/scripts/test_render_claim_standing_view.py +192 -0
  850. package/vendor/ars/scripts/test_render_eval_comment.py +162 -0
  851. package/vendor/ars/scripts/test_render_harness_retirement_issue.py +110 -0
  852. package/vendor/ars/scripts/test_repro_lock_validation_drift.py +100 -0
  853. package/vendor/ars/scripts/test_research_workflow_profile.py +734 -0
  854. package/vendor/ars/scripts/test_resolve_human_subjects_authority.py +1219 -0
  855. package/vendor/ars/scripts/test_resolve_review_target_context.py +703 -0
  856. package/vendor/ars/scripts/test_resume_e4_record.py +315 -0
  857. package/vendor/ars/scripts/test_retraction_status.py +456 -0
  858. package/vendor/ars/scripts/test_review_criteria_binding.py +629 -0
  859. package/vendor/ars/scripts/test_review_panel_provenance.py +565 -0
  860. package/vendor/ars/scripts/test_review_pathway_rule_trace.py +821 -0
  861. package/vendor/ars/scripts/test_revision_roadmap.py +1255 -0
  862. package/vendor/ars/scripts/test_run_ci_pytest_manifest.py +186 -0
  863. package/vendor/ars/scripts/test_run_codex_audit_e2e.py +369 -0
  864. package/vendor/ars/scripts/test_run_evals.py +430 -0
  865. package/vendor/ars/scripts/test_run_guard_launcher.py +500 -0
  866. package/vendor/ars/scripts/test_run_ideation_diversity_no_call.py +1833 -0
  867. package/vendor/ars/scripts/test_run_indirect_prompt_injection_no_call.py +1889 -0
  868. package/vendor/ars/scripts/test_run_review_criteria_constructive_value.py +586 -0
  869. package/vendor/ars/scripts/test_run_role_topology_utility_dry_run.py +428 -0
  870. package/vendor/ars/scripts/test_score_review_criteria_constructive_value.py +340 -0
  871. package/vendor/ars/scripts/test_semantic_scholar_client.py +554 -0
  872. package/vendor/ars/scripts/test_slr_lineage_emission.py +230 -0
  873. package/vendor/ars/scripts/test_socratic_rq_non_generation_contract.py +173 -0
  874. package/vendor/ars/scripts/test_temporal_integrity_audit.py +438 -0
  875. package/vendor/ars/scripts/test_text_similarity.py +95 -0
  876. package/vendor/ars/scripts/test_title_fuzzy_false_positive.py +111 -0
  877. package/vendor/ars/scripts/test_tortured_phrase_screening.py +2959 -0
  878. package/vendor/ars/scripts/test_transport_fixture_citation_gate.py +338 -0
  879. package/vendor/ars/scripts/test_uncited_assertion.py +558 -0
  880. package/vendor/ars/scripts/test_v3_6_7_phase_6_6.py +1279 -0
  881. package/vendor/ars/scripts/test_validate_compliance_fixtures.py +36 -0
  882. package/vendor/ars/scripts/test_validate_ideation_diversity_assets.py +230 -0
  883. package/vendor/ars/scripts/test_venue_disclosure_contract.py +755 -0
  884. package/vendor/ars/scripts/test_verification_cache.py +280 -0
  885. package/vendor/ars/scripts/test_verification_gate.py +461 -0
  886. package/vendor/ars/scripts/test_verify_passport_cli.py +123 -0
  887. package/vendor/ars/scripts/test_verify_submission_package.py +1407 -0
  888. package/vendor/ars/scripts/test_version_records_schema.py +211 -0
  889. package/vendor/ars/scripts/tortured_phrase_screening.py +3502 -0
  890. package/vendor/ars/scripts/uncited_assertion_detector.py +254 -0
  891. package/vendor/ars/scripts/v3_6_7_inversion_manifest.json +9 -0
  892. package/vendor/ars/scripts/v3_6_8_inversion_manifest.json +10 -0
  893. package/vendor/ars/scripts/validate_claim_standing_stance_assets.py +249 -0
  894. package/vendor/ars/scripts/validate_compliance_fixtures.py +56 -0
  895. package/vendor/ars/scripts/validate_ideation_diversity_assets.py +303 -0
  896. package/vendor/ars/scripts/venue_disclosure_contract_harness.py +837 -0
  897. package/vendor/ars/scripts/verification_cache.py +276 -0
  898. package/vendor/ars/scripts/verification_gate/__init__.py +345 -0
  899. package/vendor/ars/scripts/verify_passport.py +133 -0
  900. package/vendor/ars/scripts/verify_submission_package.py +1657 -0
  901. package/vendor/ars/shared/agents/compliance_agent.md +136 -0
  902. package/vendor/ars/shared/artifact_reproducibility_pattern.md +173 -0
  903. package/vendor/ars/shared/benchmark_report.schema.json +81 -0
  904. package/vendor/ars/shared/benchmark_report_pattern.md +180 -0
  905. package/vendor/ars/shared/bibliographic_integrity_signals.md +142 -0
  906. package/vendor/ars/shared/collaboration_depth_rubric.md +154 -0
  907. package/vendor/ars/shared/compliance_checkpoint_protocol.md +162 -0
  908. package/vendor/ars/shared/compliance_report.schema.json +187 -0
  909. package/vendor/ars/shared/contracts/README.md +938 -0
  910. package/vendor/ars/shared/contracts/activity/adjudication_activity_input.schema.json +555 -0
  911. package/vendor/ars/shared/contracts/activity/adjudication_activity_store.schema.json +560 -0
  912. package/vendor/ars/shared/contracts/audit/audit_jsonl.schema.json +128 -0
  913. package/vendor/ars/shared/contracts/audit/audit_sidecar.schema.json +169 -0
  914. package/vendor/ars/shared/contracts/audit/audit_verdict.schema.json +133 -0
  915. package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory.schema.json +480 -0
  916. package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory_draft.schema.json +564 -0
  917. package/vendor/ars/shared/contracts/audit/cross_document_source_manifest.schema.json +202 -0
  918. package/vendor/ars/shared/contracts/audit/tortured_phrase_advisory.schema.json +1362 -0
  919. package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot.schema.json +208 -0
  920. package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot_manifest.schema.json +335 -0
  921. package/vendor/ars/shared/contracts/capability/stage_capability_matrix.json +434 -0
  922. package/vendor/ars/shared/contracts/claim_standing/candidate_ledger.schema.json +705 -0
  923. package/vendor/ars/shared/contracts/claim_standing/query_plan.schema.json +389 -0
  924. package/vendor/ars/shared/contracts/claim_standing/query_plan_v1_1.schema.json +782 -0
  925. package/vendor/ars/shared/contracts/claim_standing/retrieval_input.schema.json +440 -0
  926. package/vendor/ars/shared/contracts/claim_standing/stance_record.schema.json +460 -0
  927. package/vendor/ars/shared/contracts/claim_standing/transmission_ledger.schema.json +282 -0
  928. package/vendor/ars/shared/contracts/cross_model/codex_citation_receipt.schema.json +157 -0
  929. package/vendor/ars/shared/contracts/cross_model/codex_citation_request.schema.json +23 -0
  930. package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_commitment.schema.json +55 -0
  931. package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_reveal.schema.json +50 -0
  932. package/vendor/ars/shared/contracts/degradation_registry.json +428 -0
  933. package/vendor/ars/shared/contracts/evaluator/full.json +126 -0
  934. package/vendor/ars/shared/contracts/evidence/claim_registry.schema.json +50 -0
  935. package/vendor/ars/shared/contracts/evidence/claim_registry_coverage_report.schema.json +79 -0
  936. package/vendor/ars/shared/contracts/evidence/evidence_row.schema.json +504 -0
  937. package/vendor/ars/shared/contracts/evidence/evidence_row_v1_1.schema.json +364 -0
  938. package/vendor/ars/shared/contracts/evidence/evidence_row_v1_2.schema.json +714 -0
  939. package/vendor/ars/shared/contracts/evidence/evidence_row_v1_3.schema.json +524 -0
  940. package/vendor/ars/shared/contracts/human_subjects/authority_profile_registry.schema.json +497 -0
  941. package/vendor/ars/shared/contracts/human_subjects/committee_correspondence.schema.json +253 -0
  942. package/vendor/ars/shared/contracts/human_subjects/content_coverage_advisory.schema.json +620 -0
  943. package/vendor/ars/shared/contracts/human_subjects/irb_context_record.schema.json +329 -0
  944. package/vendor/ars/shared/contracts/human_subjects/resolved_authority_context.schema.json +347 -0
  945. package/vendor/ars/shared/contracts/human_subjects/review_pathway_rule_trace.schema.json +265 -0
  946. package/vendor/ars/shared/contracts/human_subjects/review_pathway_trace_request.schema.json +127 -0
  947. package/vendor/ars/shared/contracts/human_subjects/submission_packet_inventory.schema.json +240 -0
  948. package/vendor/ars/shared/contracts/human_subjects/submission_packet_manifest.schema.json +999 -0
  949. package/vendor/ars/shared/contracts/passport/audit_artifact_entry.schema.json +266 -0
  950. package/vendor/ars/shared/contracts/passport/bibliographic_integrity_signal.schema.json +1676 -0
  951. package/vendor/ars/shared/contracts/passport/citation_provenance.schema.json +107 -0
  952. package/vendor/ars/shared/contracts/passport/citation_verification_summary.schema.json +164 -0
  953. package/vendor/ars/shared/contracts/passport/claim_audit_result.schema.json +124 -0
  954. package/vendor/ars/shared/contracts/passport/claim_drift.schema.json +58 -0
  955. package/vendor/ars/shared/contracts/passport/claim_intent_manifest.schema.json +107 -0
  956. package/vendor/ars/shared/contracts/passport/constraint_violation.schema.json +65 -0
  957. package/vendor/ars/shared/contracts/passport/experiment_alignment_result.schema.json +69 -0
  958. package/vendor/ars/shared/contracts/passport/experiment_provenance_entry.schema.json +169 -0
  959. package/vendor/ars/shared/contracts/passport/human_read_log.schema.json +86 -0
  960. package/vendor/ars/shared/contracts/passport/inquiry_ledger_ref.schema.json +22 -0
  961. package/vendor/ars/shared/contracts/passport/literature_corpus_entry.schema.json +650 -0
  962. package/vendor/ars/shared/contracts/passport/preregistration_artifact.schema.json +132 -0
  963. package/vendor/ars/shared/contracts/passport/rejection_log.schema.json +89 -0
  964. package/vendor/ars/shared/contracts/passport/reset_ledger_entry.schema.json +158 -0
  965. package/vendor/ars/shared/contracts/passport/temporal_audit_results.schema.json +208 -0
  966. package/vendor/ars/shared/contracts/passport/terminal_policies.schema.json +50 -0
  967. package/vendor/ars/shared/contracts/passport/timeline.schema.json +102 -0
  968. package/vendor/ars/shared/contracts/passport/uncited_assertion.schema.json +56 -0
  969. package/vendor/ars/shared/contracts/passport/uncited_audit_failure.schema.json +72 -0
  970. package/vendor/ars/shared/contracts/passport/user_attested_read_resolution.schema.json +87 -0
  971. package/vendor/ars/shared/contracts/passport/version_records.schema.json +138 -0
  972. package/vendor/ars/shared/contracts/patch/block_manifest.schema.json +45 -0
  973. package/vendor/ars/shared/contracts/patch/legacy/v1_0/revision_patch.schema.json +110 -0
  974. package/vendor/ars/shared/contracts/patch/revision_patch.schema.json +250 -0
  975. package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_diagnostic.schema.json +39 -0
  976. package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_worker.schema.json +88 -0
  977. package/vendor/ars/shared/contracts/pdf/pdf_read_preflight.schema.json +227 -0
  978. package/vendor/ars/shared/contracts/re_review/input_manifest.schema.json +161 -0
  979. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/input_manifest.schema.json +134 -0
  980. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/precommitment.schema.json +193 -0
  981. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/traceability.schema.json +841 -0
  982. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/verdict_record.schema.json +244 -0
  983. package/vendor/ars/shared/contracts/re_review/precommitment.schema.json +193 -0
  984. package/vendor/ars/shared/contracts/re_review/traceability.schema.json +916 -0
  985. package/vendor/ars/shared/contracts/re_review/verdict_record.schema.json +245 -0
  986. package/vendor/ars/shared/contracts/research_workflow/inquiry_branch_ledger.schema.json +449 -0
  987. package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile.schema.json +222 -0
  988. package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile_selection_receipt.schema.json +111 -0
  989. package/vendor/ars/shared/contracts/review_target/constructive_review_findings.schema.json +172 -0
  990. package/vendor/ars/shared/contracts/review_target/criteria_registry.schema.json +97 -0
  991. package/vendor/ars/shared/contracts/review_target/review_criteria_binding_manifest.schema.json +235 -0
  992. package/vendor/ars/shared/contracts/review_target/review_criteria_source_receipt.schema.json +158 -0
  993. package/vendor/ars/shared/contracts/review_target/review_target_context.schema.json +106 -0
  994. package/vendor/ars/shared/contracts/review_target/review_target_declaration.schema.json +118 -0
  995. package/vendor/ars/shared/contracts/reviewer/full.json +114 -0
  996. package/vendor/ars/shared/contracts/reviewer/methodology_focus.json +75 -0
  997. package/vendor/ars/shared/contracts/reviewer/review_panel_provenance.schema.json +263 -0
  998. package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_carrier.schema.json +122 -0
  999. package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_input.schema.json +120 -0
  1000. package/vendor/ars/shared/contracts/revision/author_adjudication.schema.json +251 -0
  1001. package/vendor/ars/shared/contracts/revision/author_adjudication_input.schema.json +25 -0
  1002. package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition.schema.json +97 -0
  1003. package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition_input.schema.json +80 -0
  1004. package/vendor/ars/shared/contracts/revision/claim_strength_drift_findings.schema.json +139 -0
  1005. package/vendor/ars/shared/contracts/revision/claim_surface_manifest.schema.json +99 -0
  1006. package/vendor/ars/shared/contracts/revision/integrity_correction_authorization.schema.json +140 -0
  1007. package/vendor/ars/shared/contracts/revision/integrity_correction_authorization_input.schema.json +28 -0
  1008. package/vendor/ars/shared/contracts/revision/integrity_correction_list.schema.json +48 -0
  1009. package/vendor/ars/shared/contracts/revision/integrity_pass_receipt.schema.json +16 -0
  1010. package/vendor/ars/shared/contracts/revision/revision_evidence_bundle.schema.json +134 -0
  1011. package/vendor/ars/shared/contracts/revision/revision_roadmap.schema.json +334 -0
  1012. package/vendor/ars/shared/contracts/submission/format_profile.example.yaml +33 -0
  1013. package/vendor/ars/shared/contracts/submission/format_profile.schema.json +102 -0
  1014. package/vendor/ars/shared/contracts/submission/submission_verification_report.schema.json +233 -0
  1015. package/vendor/ars/shared/contracts/submission/venue_profile.schema.json +114 -0
  1016. package/vendor/ars/shared/contracts/writer/full.json +87 -0
  1017. package/vendor/ars/shared/cross_model_verification.md +714 -0
  1018. package/vendor/ars/shared/evals_lift_report.schema.json +141 -0
  1019. package/vendor/ars/shared/ground_truth_isolation_pattern.md +275 -0
  1020. package/vendor/ars/shared/handoff_schemas.md +1209 -0
  1021. package/vendor/ars/shared/human_subjects_authority_registry.json +1278 -0
  1022. package/vendor/ars/shared/mode_spectrum.md +57 -0
  1023. package/vendor/ars/shared/model_tiering.md +83 -0
  1024. package/vendor/ars/shared/policy_data/nature_policy.md +56 -0
  1025. package/vendor/ars/shared/prisma_trAIce_protocol.md +157 -0
  1026. package/vendor/ars/shared/raise_framework.md +129 -0
  1027. package/vendor/ars/shared/references/authority_content_coverage_advisory_protocol.md +275 -0
  1028. package/vendor/ars/shared/references/claim_standing_candidate_ledger_protocol.md +66 -0
  1029. package/vendor/ars/shared/references/claim_strength_ladder.md +93 -0
  1030. package/vendor/ars/shared/references/cross_document_consistency_advisory_protocol.md +263 -0
  1031. package/vendor/ars/shared/references/evidence_row_protocol.md +260 -0
  1032. package/vendor/ars/shared/references/firm_rules.md +90 -0
  1033. package/vendor/ars/shared/references/human_subjects_authority_protocol.md +274 -0
  1034. package/vendor/ars/shared/references/intent_clarification_protocol.md +168 -0
  1035. package/vendor/ars/shared/references/irb_terminology_glossary.md +229 -0
  1036. package/vendor/ars/shared/references/protected_hedging_phrases.md +118 -0
  1037. package/vendor/ars/shared/references/psychometric_terminology_glossary.md +109 -0
  1038. package/vendor/ars/shared/references/review_criteria_consumer_protocol.md +238 -0
  1039. package/vendor/ars/shared/references/review_pathway_rule_trace_protocol.md +166 -0
  1040. package/vendor/ars/shared/references/submission_packet_manifest_protocol.md +292 -0
  1041. package/vendor/ars/shared/references/word_count_conventions.md +124 -0
  1042. package/vendor/ars/shared/research_workflow_profiles/field_general.json +1 -0
  1043. package/vendor/ars/shared/review_criteria_registry.json +207 -0
  1044. package/vendor/ars/shared/review_criteria_sources/msr-2027-technical-papers.2026-08-24.json +46 -0
  1045. package/vendor/ars/shared/review_criteria_sources/sigsoft-empirical-standards.2026-08-24.json +31 -0
  1046. package/vendor/ars/shared/sprint_contract.schema.json +482 -0
  1047. package/vendor/ars/shared/style_calibration_protocol.md +151 -0
  1048. package/vendor/ars/shared/templates/codex_audit_multifile_template.md +263 -0
  1049. package/vendor/ars/tools/release-discipline/.toolkit-version +1 -0
  1050. package/vendor/ars/tools/release-discipline/README.md +4 -0
  1051. package/vendor/ars/tools/release-discipline/scripts/_release_doc_alignment_schema.py +1011 -0
  1052. package/vendor/ars/tools/release-discipline/scripts/check_command_invariants.py +497 -0
  1053. package/vendor/ars/tools/release-discipline/scripts/check_release_doc_alignment.py +263 -0
  1054. package/vendor/ars/tools/release-discipline/scripts/sync-toolkit.sh +147 -0
  1055. package/vendor/windows/NOTICE.md +12 -0
  1056. package/vendor/windows/arm64/fd.exe +0 -0
  1057. package/vendor/windows/arm64/licenses/fd/LICENSE-APACHE +201 -0
  1058. package/vendor/windows/arm64/licenses/fd/LICENSE-MIT +21 -0
  1059. package/vendor/windows/arm64/licenses/ripgrep/COPYING +3 -0
  1060. package/vendor/windows/arm64/licenses/ripgrep/LICENSE-MIT +21 -0
  1061. package/vendor/windows/arm64/licenses/ripgrep/UNLICENSE +24 -0
  1062. package/vendor/windows/arm64/rg.exe +0 -0
  1063. package/vendor/windows/x64/fd.exe +0 -0
  1064. package/vendor/windows/x64/licenses/fd/LICENSE-APACHE +201 -0
  1065. package/vendor/windows/x64/licenses/fd/LICENSE-MIT +21 -0
  1066. package/vendor/windows/x64/licenses/ripgrep/COPYING +3 -0
  1067. package/vendor/windows/x64/licenses/ripgrep/LICENSE-MIT +21 -0
  1068. package/vendor/windows/x64/licenses/ripgrep/UNLICENSE +24 -0
  1069. package/vendor/windows/x64/rg.exe +0 -0
@@ -0,0 +1,2180 @@
1
+ #!/usr/bin/env python3
2
+ """Fail-closed Phase 1 -> Phase 2 conformance checker for reviewer Schema 13.2.
3
+
4
+ Usage:
5
+ python scripts/check_phase_conformance.py --contract C.json --role eic \
6
+ --phase1 eic.phase1.md --phase2 eic.phase2.md \
7
+ --manuscript manuscript.md --metadata metadata.json
8
+
9
+ Exit 0 pass, 2 contract/infra failure, 3 reviewer conformance failure.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import html
15
+ import json
16
+ import re
17
+ import sys
18
+ import unicodedata
19
+ from collections import Counter
20
+ from dataclasses import dataclass
21
+ from pathlib import Path
22
+
23
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
24
+ import check_panel_synthesis as panel # noqa: E402
25
+ import recompute_receipts as recompute # noqa: E402
26
+
27
+ EXIT_PASS = 0
28
+ EXIT_CONTRACT = 2
29
+ EXIT_CONFORMANCE = 3
30
+ _METADATA_KEYS = frozenset({"title", "field", "word_count"})
31
+ _FIELD_PATTERNS = {
32
+ "dimension_id": re.compile(r"^dimension_id: (?P<value>D\d+)$"),
33
+ "what_to_look_for": re.compile(r"^what_to_look_for: (?P<value>\S.*)$"),
34
+ "what_triggers_block": re.compile(
35
+ r"^what_triggers_block: (?P<value>\S.*)$"
36
+ ),
37
+ "what_triggers_warn": re.compile(
38
+ r"^what_triggers_warn: (?P<value>\S.*)$"
39
+ ),
40
+ "what_triggers_fatal": re.compile(
41
+ r"^what_triggers_fatal: (?P<value>\S.*)$"
42
+ ),
43
+ }
44
+ _DISSENT_DIM_RE = re.compile(r"^dimension_id: (?P<dim>D\d+)\s*$")
45
+ _DISSENT_RATIONALE_RE = re.compile(r"^rationale: (?P<text>\S.*)\s*$")
46
+ _SEVERITY_RE = re.compile(
47
+ r"(?:^|\|\s*)\s*(?:[-*]\s*)?\*\*Severity\*\*:\s*"
48
+ r"(?P<severity>Critical|Major|Minor)\b"
49
+ )
50
+ _ANCHOR_RE = re.compile(
51
+ r"(?:^|\|\s*)\s*(?:[-*]\s*)?\*\*Evidence Anchor\*\*:\s*"
52
+ r"(?P<value>[^|]+)"
53
+ )
54
+ _SEVERITY_DECL_RE = re.compile(
55
+ r"\*\*Severity(?:\*\*)?\s*:",
56
+ re.IGNORECASE,
57
+ )
58
+ _ANCHOR_DECL_RE = re.compile(
59
+ r"\*\*Evidence Anchor(?:\*\*)?\s*:",
60
+ re.IGNORECASE,
61
+ )
62
+ _FINDING_H3_RE = re.compile(r"^W[1-9]\d*: \S.*$")
63
+ # --- #610 methodology arithmetic-receipt grammar -------------------------
64
+ # Receipt machine lines are lowercase snake_case on purpose: they can never
65
+ # collide with the finding grammar's bold `**Severity**:` / `**Evidence
66
+ # Anchor**:` declarations, so the receipt section cannot trip the
67
+ # outside-Review-Body severity gate and a finding cannot satisfy a receipt
68
+ # field. Decoration tolerance is bounded to the two shapes the finding
69
+ # grammar already accepts (one leading list marker, bold around the key).
70
+ # The section is read fence-transparently (#637/#638 display-form
71
+ # discipline): a fenced receipt line is still content, so a stylistic fence
72
+ # neither hides a receipt nor turns one into a false abort, and any other
73
+ # decoration of a field-shaped line is detected and aborts loudly instead of
74
+ # silently passing a forbidden-field guard or failing a required count.
75
+ _RECEIPT_SECTION = "Arithmetic Receipts"
76
+ _RECEIPT_H3_RE = re.compile(r"^AR[1-9]\d*$")
77
+ _RECEIPT_PROCEDURES = frozenset(
78
+ {"p_from_test_statistic", "grim", "grimmer", "n_from_df"}
79
+ )
80
+ _RECEIPT_STATUSES = frozenset(
81
+ {"consistent", "mismatch", "not_computable", "not_applicable"}
82
+ )
83
+ _NOT_COMPUTABLE_REASONS = frozenset({
84
+ "missing_reported_value",
85
+ "test_family_ambiguous",
86
+ "tail_ambiguous",
87
+ "nonstandard_p_procedure",
88
+ "inequality_unresolvable",
89
+ "rounding_rule_ambiguous",
90
+ "rounding_boundary_ambiguous",
91
+ "scale_granularity_unknown",
92
+ "scale_support_unknown",
93
+ "analytic_n_ambiguous",
94
+ "aggregation_or_weighting_unknown",
95
+ "sd_convention_unknown",
96
+ "mean_grim_inconsistent",
97
+ "df_identity_ambiguous",
98
+ "model_correction_or_pooling",
99
+ "reachability_not_completed",
100
+ })
101
+ _TAIL_CONVENTIONS = frozenset(
102
+ {"two-tailed", "one-tailed", "upper-tail", "unstated"}
103
+ )
104
+
105
+
106
+ def _receipt_field_re(key: str) -> re.Pattern[str]:
107
+ # Bold is balanced-or-absent: a half-bold `**key:` is a decorated line
108
+ # for the shape guard to abort, never a canonical field (#610 round-2).
109
+ return re.compile(
110
+ rf"^(?:[-*] )?(?:{key}|\*\*{key}\*\*): (?P<value>\S.*?)\s*$"
111
+ )
112
+
113
+
114
+ _RECEIPT_FIELD_RES = {
115
+ key: _receipt_field_re(key)
116
+ for key in (
117
+ "procedure_id",
118
+ "evidence_anchor",
119
+ "reported_inputs",
120
+ "assumptions",
121
+ "derivation",
122
+ "derived_value_or_range",
123
+ "comparison_rule",
124
+ "status",
125
+ "not_computable_reason",
126
+ "finding_ref",
127
+ "tail_convention",
128
+ "rounding_interval",
129
+ "nearest_achievable",
130
+ "df_identity",
131
+ )
132
+ }
133
+ _RECEIPT_ATTESTATION_RE = _receipt_field_re("no_recomputable_statistics")
134
+ _RECEIPT_ATTESTATION_ADVISORY = (
135
+ "[RECEIPT-ATTESTATION: declaration-only — applicability not "
136
+ "machine-verified; adjudication judges the attestation]"
137
+ )
138
+ # Folded field-name alphabets for the decoration-agnostic shape test, the
139
+ # same construction as `_is_dissent_field_shaped`: a line is receipt-field-
140
+ # shaped when the letters before its first colon spell a field name after
141
+ # markup-span strip + NFKC fold. A shaped line that the canonical grammar
142
+ # does not parse is a loud abort, never a silently ignored decoration.
143
+ _RECEIPT_FIELD_SHAPE_NAMES = frozenset(
144
+ "".join(char for char in key if char.isalpha())
145
+ for key in (*_RECEIPT_FIELD_RES, "no_recomputable_statistics")
146
+ )
147
+ _FINDING_REF_VALUE_RE = re.compile(r"^W[1-9]\d*$")
148
+ # The back-reference value is exact: `AR<n>` up to end-of-line or the next
149
+ # table pipe. `AR1, AR2` and trailing prose fail this parse and then abort
150
+ # via the lenient declaration count below rather than half-reading `AR1`.
151
+ _RECEIPT_BACKREF_RE = re.compile(
152
+ r"(?:^|\|\s*)\s*(?:[-*]\s*)?\*\*Arithmetic Receipt\*\*:\s*"
153
+ r"(?P<value>AR[1-9]\d*)\s*(?=\||$)"
154
+ )
155
+ # Lenient declaration shape for the back-reference, checked against the
156
+ # canonical parse per line (`_is_dissent_field_shaped` construction): a line
157
+ # whose letters before the first colon spell exactly `arithmeticreceipt` is
158
+ # a declaration the seat made, so a decorated, fenced, or prose-trailed
159
+ # back-reference the canonical regex refuses aborts instead of vanishing.
160
+ # Prose that merely mentions the section (`the Arithmetic Receipts
161
+ # section: ...`) carries extra letters in its head and stays prose.
162
+ _RECEIPT_BACKREF_SHAPE_NAME = "arithmeticreceipt"
163
+ # Both-tails value rule (#610 rounds 1-2): under an unstated tail, each tail
164
+ # label must share its own `;`-delimited segment of `derived_value_or_range`
165
+ # with a digit — either side of the label, so `p = .192 (two-tailed)` is a
166
+ # shown value while a bare label in a digitless segment is not. Hyphen
167
+ # variants (space, ASCII hyphen, U+2010-U+2015) are folded into the label
168
+ # match, the value text is NFKC-folded first, and the label needs letter
169
+ # boundaries so `notwo-tailed` prose cannot satisfy the rule; a fused
170
+ # `twotailed` typo still counts as the label — it carries its value, and
171
+ # rejecting it would be a false abort on an unretryable phase.
172
+ _TAIL_LABEL_RES = {
173
+ label: re.compile(rf"(?<![a-z]){label}[\s‐-―-]?tailed(?![a-z])")
174
+ for label in ("two", "one")
175
+ }
176
+ # Conditional-field matrix, spec §4/§5 (2026-08-02 #610 spec): which
177
+ # procedure-specific lines each procedure may carry. `tail_convention` is
178
+ # required on every p_from_test_statistic receipt (what the paper states is
179
+ # always statable — `unstated` exists for exactly that case); the completed-
180
+ # procedure fields (`rounding_interval`, `nearest_achievable`, `df_identity`)
181
+ # are required only under a verdict status, because `not_computable` may
182
+ # legitimately stop before the field can exist, and remain permitted there
183
+ # for a partial attempt that stopped late.
184
+ _RECEIPT_VERDICT_STATUSES = frozenset({"consistent", "mismatch"})
185
+ _PROCEDURE_FIELDS = {
186
+ "p_from_test_statistic": ("tail_convention",),
187
+ "grim": ("rounding_interval", "nearest_achievable"),
188
+ "grimmer": ("rounding_interval", "nearest_achievable"),
189
+ "n_from_df": ("df_identity",),
190
+ }
191
+ # Inverted view driving the conditional-field loop, so the matrix above is
192
+ # the single authority: field -> the procedures that carry it.
193
+ _PROCEDURE_FIELD_OWNERS = {
194
+ field: tuple(
195
+ procedure
196
+ for procedure, fields in _PROCEDURE_FIELDS.items()
197
+ if field in fields
198
+ )
199
+ for fields in _PROCEDURE_FIELDS.values()
200
+ for field in fields
201
+ }
202
+ _ALWAYS_REQUIRED_PROCEDURE_FIELDS = frozenset({"tail_convention"})
203
+ # --- end #610 receipt grammar --------------------------------------------
204
+ _DISSENT_FIELD_NAMES = frozenset({"dimensionid", "rationale"})
205
+ _MARKUP_SPAN_RE = re.compile(
206
+ r"<[^>]*>|\]\((?:[^()]|\([^()]*\))*\)|\]\[[^\]]*\]|\[[ xX]?\]"
207
+ )
208
+ # Non-comment raw HTML is forbidden only inside the dissent span (#682).
209
+ # Match the opening delimiter rather than requiring a complete tag: CommonMark
210
+ # raw blocks such as a line ending in ``<script`` can still hide the canonical
211
+ # fields below it, and a malformed claimed dissent must abort rather than fall
212
+ # through to the empty-section advisory. Autolinks such as
213
+ # ``<https://example.test>`` do not match because ``:`` is not a tag-name
214
+ # boundary. Comment delimiters remain owned by the #613 state machine so its
215
+ # more specific ``[DISSENT-HIDDEN]`` diagnostic keeps precedence.
216
+ _DISSENT_RAW_HTML_RE = re.compile(
217
+ r"<(?:(?:/?[A-Za-z][A-Za-z0-9-]*)(?=[\s/>]|$)|![A-Za-z]|!\[CDATA\[|\?)",
218
+ re.IGNORECASE,
219
+ )
220
+ # A block opener, including one behind list or blockquote markers: rendered
221
+ # through CommonMark, `- <!--` opens raw HTML just as a bare `<!--` does, and
222
+ # needs no closer to swallow the rest of the item. The indentation allowances
223
+ # must not add up to four, which would make the whole line indented code: a
224
+ # list marker takes one to four following spaces before its content, a block
225
+ # quote at most one, and a block start may still be indented up to three
226
+ # inside the quote it opens. Erring narrow costs a miss; erring wide aborts a
227
+ # valid card. Only an ordered list beginning at 1 may interrupt an open
228
+ # paragraph, so `2. <!--` under a paragraph line is text, not a list opening
229
+ # a raw-HTML block, and the fields below it stay on the page.
230
+ def _container_prefix(ordered: str) -> str:
231
+ """Matched against a tab-expanded line, so spaces are the only gap.
232
+
233
+ Every gap is one unambiguous run: letting a single space be claimed by
234
+ either of two adjacent optional groups gave two ways to match each marker
235
+ and doubled the work per nesting level, so a short nested quote stalled
236
+ the checker rather than returning a verdict.
237
+ """
238
+ return rf"(?:(?:[-*+]|{ordered}) {{1,4}}|> {{0,4}})*"
239
+
240
+
241
+ # Lines that end the paragraph above them, so the NEXT line sits at a real
242
+ # block start where any ordered marker may open a list. An ATX heading of any
243
+ # level, a thematic break, and a lone `-` do that from either state, the last
244
+ # because it reads as a setext underline under a paragraph and as an empty
245
+ # list item without one. A bare `>` starts a container rather than closing a
246
+ # paragraph and is left to #613 with the rest of the container family.
247
+ _CLOSES_PARAGRAPH_RE = re.compile(
248
+ r"^ {0,3}(?:"
249
+ r"#{1,6}(?:[ \t].*)?$"
250
+ r"|(?:\*[ \t]*){3,}$|(?:_[ \t]*){3,}$|(?:-[ \t]*){3,}$"
251
+ r"|-[ \t]*$"
252
+ r")"
253
+ )
254
+ # A `=` run or two-or-more hyphens underlines a paragraph, so it closes one
255
+ # only when there is a paragraph to underline; with none, it is ordinary
256
+ # paragraph text and OPENS one.
257
+ _SETEXT_UNDERLINE_RE = re.compile(r"^ {0,3}(?:=+|-{2,})[ \t]*$")
258
+ # An empty list item has a blank first line, so it cannot interrupt an open
259
+ # paragraph. It only holds the state down where none is open. Reading every
260
+ # lone marker as a closer looked symmetrical and aborted valid cards.
261
+ _EMPTY_LIST_ITEM_RE = re.compile(r"^ {0,3}(?:[*+]|\d{1,9}[.)])[ \t]*$")
262
+ _ANY_ORDERED_MARKER = r"\d{1,9}[.)]"
263
+ _PARAGRAPH_INTERRUPTING_MARKER = r"1[.)]"
264
+ _COMMENT_OPENER_RE = re.compile(
265
+ rf"^ {{0,3}}{_container_prefix(_ANY_ORDERED_MARKER)}<!--"
266
+ )
267
+ _PARAGRAPH_OPENER_RE = re.compile(
268
+ rf"^ {{0,3}}{_container_prefix(_PARAGRAPH_INTERRUPTING_MARKER)}<!--"
269
+ )
270
+
271
+
272
+ class ConformanceError(Exception):
273
+ """Reviewer conformance failure -> exit 3."""
274
+
275
+
276
+ @dataclass
277
+ class PhaseOnePlan:
278
+ commitments: dict[str, dict[str, str]]
279
+ warnings: list[str]
280
+
281
+
282
+ @dataclass
283
+ class DissentSpan:
284
+ """The dissent section as written: every line, plus the commented ones.
285
+
286
+ `strip_fences` leaves HTML comments in place, so a canonical field inside
287
+ `<!-- ... -->` otherwise parses as a real dissent and collects the
288
+ trigger-binding exemption while the visible card claims nothing.
289
+ """
290
+
291
+ lines: list[str]
292
+ hidden_by_comment: list[str]
293
+ raw_html: list[str]
294
+
295
+
296
+ @dataclass
297
+ class DissentParse:
298
+ dimensions: set[str]
299
+ diagnostics: list[str]
300
+
301
+
302
+ def _normalise(text: str) -> str:
303
+ return " ".join(text.casefold().split())
304
+
305
+
306
+ def _is_dissent_field_shaped(line: str) -> bool:
307
+ """True when a line spells a dissent field, canonically or decorated.
308
+
309
+ Decoration-agnostic by construction: a line is field-shaped when the
310
+ letters preceding its first colon spell exactly a field name, whatever
311
+ non-letters surround them. Enumerating Markdown wrappers instead would
312
+ leave the next unenumerated wrapper (task item, table cell, link label)
313
+ reading as prose. Folded first so a fullwidth re-spelling cannot pass.
314
+
315
+ Markup spans carrying letters of their own (HTML tags, reference targets,
316
+ task-list markers, and link destinations up to one nesting level deep) are
317
+ dropped BEFORE the colon is located, not after: an absolute link target
318
+ carries its own `https:` and would otherwise win the partition.
319
+
320
+ Decoration beyond those — a deeper nested destination, a quoted HTML
321
+ attribute containing `>`, exotic custom syntax — is deliberately out of
322
+ scope, because absorbing it costs one advisory-flagged record while a
323
+ broader rule costs false aborts, which is the failure this tolerance
324
+ exists to remove. `test_a_nested_paren_link_destination_is_a_declared_
325
+ limit` pins that boundary so it cannot move by accident.
326
+
327
+ `str.isalpha` keeps every script's letters, so CJK prose that happens to
328
+ name a field stays prose rather than collapsing onto the field name and
329
+ aborting a panel it should tolerate.
330
+ """
331
+ stripped = _MARKUP_SPAN_RE.sub(
332
+ "", unicodedata.normalize("NFKC", line).casefold()
333
+ )
334
+ head, separator, _ = stripped.partition(":")
335
+ label = "".join(char for char in head if char.isalpha())
336
+ return bool(separator) and label in _DISSENT_FIELD_NAMES
337
+
338
+
339
+ def _display_fold(line: str) -> str:
340
+ """Unescape HTML entities FIRST, then NFKC-fold and casefold.
341
+
342
+ Order matters (#610 round-3 P1): a fullwidth-colon entity `&#xFF1A;`
343
+ decodes to a fullwidth colon that only the SUBSEQUENT NFKC fold maps to
344
+ `:`, so folding before unescaping left the decoded colon unfolded and
345
+ the declaration unseen. A doubly-encoded or fullwidth-ampersand entity
346
+ stays literal, which matches what the renderer shows.
347
+ """
348
+ return unicodedata.normalize("NFKC", html.unescape(line)).casefold()
349
+
350
+
351
+ _ESCAPED_PIPE_RE = re.compile(r"\\\|")
352
+ # CommonMark §2.4: a backslash escapes the next punctuation character, so
353
+ # `\\` is a literal backslash and ``\` `` a literal backtick that can
354
+ # neither open nor close a code span. One left-to-right pass sequences the
355
+ # two correctly (`\\` consumes its backslash before a following backtick is
356
+ # considered). Ignoring this let an escaped-backtick "span" blank a live
357
+ # `<!--` and credit a hidden dissent field (#613 security round 1, P1).
358
+ _ESCAPED_BACKTICK_RE = re.compile(r"\\[\\`]")
359
+
360
+
361
+ def _blank_code_spans(line: str) -> str:
362
+ """Blank CommonMark inline code spans, matching runs of EQUAL length.
363
+
364
+ A regex accepting unequal delimiter runs (#610 round-4 P1) blanked
365
+ from a single-backtick opener to a double-backtick closer — a stretch
366
+ the renderer does NOT treat as code, re-opening the later-cell attack
367
+ — and conversely swallowed legitimate prose. This scanner pairs an
368
+ opening run only with the next run of exactly its length, as the
369
+ renderer does; an unmatched run stays literal. Backslash-escaped
370
+ backticks are blanked FIRST: they are literal to the renderer and must
371
+ not participate in pairing.
372
+ """
373
+ line = _ESCAPED_BACKTICK_RE.sub(" ", line)
374
+ out: list[str] = []
375
+ index, length = 0, len(line)
376
+ while index < length:
377
+ if line[index] != "`":
378
+ out.append(line[index])
379
+ index += 1
380
+ continue
381
+ run_end = index
382
+ while run_end < length and line[run_end] == "`":
383
+ run_end += 1
384
+ run = run_end - index
385
+ scan, close = run_end, -1
386
+ while scan < length:
387
+ if line[scan] != "`":
388
+ scan += 1
389
+ continue
390
+ candidate_end = scan
391
+ while candidate_end < length and line[candidate_end] == "`":
392
+ candidate_end += 1
393
+ if candidate_end - scan == run:
394
+ close = scan
395
+ break
396
+ scan = candidate_end
397
+ if close >= 0:
398
+ out.append(" " * (close + run - index))
399
+ index = close + run
400
+ else:
401
+ out.append(line[index:run_end])
402
+ index = run_end
403
+ return "".join(out)
404
+
405
+
406
+ def _cell_split_form(line: str) -> str:
407
+ """The line with pipe-bearing non-cell constructs blanked for splitting.
408
+
409
+ A naive `split("|")` read the pipe inside a Markdown link destination,
410
+ an escaped `\\|`, or an inline code span as a GFM cell boundary and
411
+ minted a phantom field-shaped cell out of legitimate prose — a false
412
+ abort on an unretryable phase (#610 round-3). Those spans are literal
413
+ or non-tabular to the renderer, so they are blanked before the split;
414
+ the whole-line candidate is always tested unmodified.
415
+ """
416
+ blanked = _MARKUP_SPAN_RE.sub(" ", line)
417
+ blanked = _blank_code_spans(blanked)
418
+ return _ESCAPED_PIPE_RE.sub(" ", blanked)
419
+
420
+
421
+ def _is_receipt_field_shaped(line: str) -> bool:
422
+ """True when a line spells a receipt machine field, however decorated.
423
+
424
+ Same construction as `_is_dissent_field_shaped` (#610 round-1 fix 4):
425
+ markup spans are stripped, the line is NFKC-folded and HTML-unescaped
426
+ (an entity colon `&#58;` renders as a colon and must read as one), and
427
+ the letters before the first colon must spell exactly a receipt field
428
+ name. An unenumerated decoration — `**status:** mismatch`, a table
429
+ cell, an indented, ordered, or blockquoted marker — is therefore a
430
+ declaration the seat made, and a shaped line the canonical grammar
431
+ refuses aborts loudly instead of silently passing a forbidden-field
432
+ guard or starving a required-field count.
433
+ """
434
+ stripped = _MARKUP_SPAN_RE.sub("", _display_fold(line))
435
+ head, separator, _ = stripped.partition(":")
436
+ label = "".join(char for char in head if char.isalpha())
437
+ return bool(separator) and label in _RECEIPT_FIELD_SHAPE_NAMES
438
+
439
+
440
+ def _receipt_shape_candidates(line: str) -> list[str]:
441
+ """The line plus, when it carries pipes, each `|`-delimited cell.
442
+
443
+ The head-of-line shape test alone lets a machine declaration hide in a
444
+ LATER table cell (`| note | **tail_convention:** two-tailed |`), where
445
+ the head letters of the whole line spell nothing (#610 round-2). Cells
446
+ are scanned only when the line does not canonically parse, so a
447
+ canonical field whose free-text value happens to contain a pipe is
448
+ never re-partitioned against itself.
449
+ """
450
+ if "|" not in line:
451
+ return [line]
452
+ return [line, *_cell_split_form(line).split("|")]
453
+
454
+
455
+ def _tail_value_shown(derived_fold: str, label_re: re.Pattern[str]) -> bool:
456
+ """Whether a tail label shares a `;`-delimited segment with a digit.
457
+
458
+ Segments are the `;`-delimited pieces of the (NFKC-folded, casefolded)
459
+ `derived_value_or_range` value: a bare label whose number sits in some
460
+ other segment — or nowhere — is a label, not a shown value, and cannot
461
+ satisfy the both-tails display rule. Digit-adjacency is direction-free
462
+ within the segment (`two-tailed p ≈ .192` and `p = .192 (two-tailed)`
463
+ both show the value); the one-pass any() also keeps the scan linear.
464
+ """
465
+ return any(
466
+ label_re.search(segment)
467
+ and any(char.isdigit() for char in segment)
468
+ for segment in derived_fold.split(";")
469
+ )
470
+
471
+
472
+ def _is_backref_shaped(line: str) -> bool:
473
+ """True when a line spells the `**Arithmetic Receipt**:` field, however
474
+ decorated — the same head shape test as the receipt fields."""
475
+ stripped = _MARKUP_SPAN_RE.sub("", _display_fold(line))
476
+ head, separator, _ = stripped.partition(":")
477
+ label = "".join(char for char in head if char.isalpha())
478
+ return bool(separator) and label == _RECEIPT_BACKREF_SHAPE_NAME
479
+
480
+
481
+ def _strip_inline_comment_spans(
482
+ line: str, open_: bool
483
+ ) -> tuple[str, list[str], bool]:
484
+ """(rendered remainder, hidden segments, state) for inline spans.
485
+
486
+ The block visibility model deliberately refuses to read a mid-paragraph
487
+ `prose <!--` as an opener (see `_raw_dissent_span`), but CommonMark
488
+ treats it as raw inline HTML whose comment hides everything until
489
+ `-->`. The Review Body backref walk therefore strips those spans and
490
+ parses only what the renderer shows (#610 rounds 3-5): content after
491
+ the opener on the SAME line is stripped too, and content after a
492
+ closing `-->` on the same line is kept and parsed — so a hidden
493
+ back-reference is never credited and a rendered one is never dropped.
494
+ The hidden segments are returned as well, because a declaration inside
495
+ a span must abort even when the line also carries visible content —
496
+ silently dropping it re-opened the hidden-declaration channel one
497
+ partial-visibility case at a time (#610 round-5). They stay one entry
498
+ PER SPAN, never joined (#610 round-6): joining let a prose span prefix
499
+ shield a declaration in the next span, and conversely synthesized a
500
+ phantom declaration out of harmless fragments. Callers pass a
501
+ code-span-blanked line, matching the renderer's precedence of code
502
+ spans over raw HTML.
503
+ """
504
+ visible_parts: list[str] = []
505
+ hidden_segments: list[str] = []
506
+ index, state = 0, open_
507
+ while True:
508
+ token = "-->" if state else "<!--"
509
+ position = line.find(token, index)
510
+ if position < 0:
511
+ if state:
512
+ hidden_segments.append(line[index:])
513
+ else:
514
+ visible_parts.append(line[index:])
515
+ return "".join(visible_parts), hidden_segments, state
516
+ if state:
517
+ hidden_segments.append(line[index:position])
518
+ else:
519
+ visible_parts.append(line[index:position])
520
+ state, index = not state, position + len(token)
521
+
522
+
523
+ def _backref_declared_count(line: str) -> int:
524
+ """Back-reference declarations on the line, counted per table cell.
525
+
526
+ Counting the whole line once let a malformed second declaration ride
527
+ beside a canonical one (`… AR1 | **Arithmetic Receipt:** see AR2`) —
528
+ the canonical parse found a match, the shape test never ran, and the
529
+ visible second declaration vanished (#610 round-2). Cells are the
530
+ declaration slots the canonical regex itself recognises (its
531
+ start-or-pipe prefix), so declared-vs-parsed equality is exact.
532
+ """
533
+ if "|" not in line:
534
+ return 1 if _is_backref_shaped(line) else 0
535
+ return sum(
536
+ 1 for cell in _cell_split_form(line).split("|")
537
+ if _is_backref_shaped(cell)
538
+ )
539
+
540
+
541
+ def _lines_with_fence_state(text: str):
542
+ """Yield every line with whether it sits inside a fenced block.
543
+
544
+ Mirrors the fence bookkeeping of `panel.strip_fences`, which drops those
545
+ lines rather than reporting them. The dissent scan needs both facts: the
546
+ content, so a fenced field cannot hide, and the state, so a fenced heading
547
+ is not mistaken for a section boundary.
548
+
549
+ Fenced lines are yielded in DISPLAY form: CommonMark strips up to the
550
+ opener's indentation from every content line of an indented fence, so a
551
+ consumer reading fenced content (the receipt gate) sees what the page
552
+ shows rather than false-aborting on the indent (#610 round-2).
553
+ """
554
+ fence_char, fence_len, fence_indent = None, 0, 0
555
+ for line in panel._COMMONMARK_LINE_END_RE.split(text):
556
+ if fence_char is not None:
557
+ if match := panel._FENCE_CLOSE_RE.fullmatch(line):
558
+ token = match.group("fence")
559
+ if token[0] == fence_char and len(token) >= fence_len:
560
+ fence_char, fence_len, fence_indent = None, 0, 0
561
+ continue
562
+ leading = len(line) - len(line.lstrip(" "))
563
+ yield line[min(fence_indent, leading):], True
564
+ continue
565
+ if match := panel._FENCE_OPEN_RE.fullmatch(line):
566
+ token, info = match.group("fence"), match.group("info")
567
+ if token[0] != "`" or "`" not in info:
568
+ fence_char, fence_len = token[0], len(token)
569
+ fence_indent = len(line) - len(line.lstrip(" "))
570
+ continue
571
+ yield line, False
572
+
573
+
574
+ def _opens_comment(line: str, *, paragraph_open: bool) -> bool:
575
+ """Whether the line starts an HTML comment at a block position.
576
+
577
+ Tabs are measured to the next four-column stop rather than counted as one
578
+ character, the way CommonMark reads them wherever indentation defines
579
+ block structure. Counting characters put `> \\t<!--` (column four, a live
580
+ comment) and ` - \\t<!--` (column eight, indented code) on the wrong
581
+ sides of the boundary in opposite directions.
582
+
583
+ With a paragraph open, an ordered marker other than 1 cannot start a list,
584
+ so it cannot open a comment either.
585
+ """
586
+ pattern = _PARAGRAPH_OPENER_RE if paragraph_open else _COMMENT_OPENER_RE
587
+ return bool(pattern.match(line.expandtabs(4)))
588
+
589
+
590
+ def _comment_state_after(
591
+ line: str, *, commented: bool, paragraph_open: bool = False
592
+ ) -> bool:
593
+ """Whether the line ends inside an HTML comment.
594
+
595
+ Resolved by delimiter ORDER, not by presence: `<!-- a --> <!--` closes
596
+ and reopens on one line, and reading that as closed credited a comment
597
+ carrying canonical fields as a real dissent. Only a block opener starts
598
+ one, list and blockquote markers included, so a marker discussed mid-line
599
+ is still not a comment; once a line has opened one, its later delimiters
600
+ are that same block's raw HTML.
601
+
602
+ The closer may reuse the opener's own last two dashes, which is how
603
+ CommonMark closes `<!-->` and `<!--->`. Ordering the scan without that
604
+ overlap would read them as unterminated and abort a card that presence-
605
+ testing for a closer had passed.
606
+ """
607
+ index = 0
608
+ if not commented:
609
+ if not _opens_comment(line, paragraph_open=paragraph_open):
610
+ return False
611
+ # Located in the ORIGINAL line: the prefix carries no `<!--`, so the
612
+ # first occurrence is the opener wherever tab expansion moved it.
613
+ commented, index = True, line.find("<!--") + 2
614
+ while True:
615
+ token = "-->" if commented else "<!--"
616
+ position = line.find(token, index)
617
+ if position < 0:
618
+ return commented
619
+ commented, index = not commented, position + len(token)
620
+
621
+
622
+ def _inline_comment_state_after(line: str, *, commented: bool) -> bool:
623
+ """Delimiter-order comment state with NO block-position requirement.
624
+
625
+ Span-scoped (#613): inside the dissent span the output grammar makes a
626
+ bare ``<!--`` out-of-grammar prose — the delivered Phase 2 prompts and
627
+ the protocol now require inline code for any mention — so every
628
+ occurrence is an opener, including the two CommonMark shapes the block
629
+ visibility model deliberately does not read (a marker following text on
630
+ its own line; a marker indented as a lazy paragraph continuation).
631
+ Callers blank code spans first, so the sanctioned inline-code mention
632
+ never opens.
633
+ """
634
+ index = 0
635
+ while True:
636
+ token = "-->" if commented else "<!--"
637
+ position = line.find(token, index)
638
+ if position < 0:
639
+ return commented
640
+ commented = not commented
641
+ # After an opener, resume at +2 rather than +4: the closer may
642
+ # reuse the opener's own last two dashes (`<!-->`, `<!--->`) —
643
+ # the same overlap rule the block scanner applies (codex #650
644
+ # round 1, P2: skipping it left the empty comment "open" and
645
+ # false-aborted the rendered fields below it).
646
+ index = position + (2 if commented else len(token))
647
+
648
+
649
+ def _lines_with_hidden_state(text: str):
650
+ """Yield (line, fenced, hidden): fence plus HTML-comment visibility.
651
+
652
+ The comment and paragraph bookkeeping mirror `_raw_dissent_span` line
653
+ for line, so the receipt gate and the dissent gate share one visibility
654
+ model (#610 round-2, both tracks): `hidden` is True when the line sits
655
+ inside — or itself opens — an HTML comment at a block position, i.e.
656
+ when a CommonMark renderer would not display it. `code_indented` is
657
+ True when the line starts an indented code block (four columns at a
658
+ block position, no paragraph open), i.e. when the renderer shows it as
659
+ literal text rather than a field line (#610 round-3). Fence state is
660
+ layered first, exactly as in `_raw_dissent_span`: a comment marker
661
+ inside a fence is literal text and advances no comment state.
662
+ """
663
+ commented = False
664
+ paragraph_open = False
665
+ for line, fenced in _lines_with_fence_state(text):
666
+ entered_commented = commented
667
+ opens_comment = not fenced and _opens_comment(
668
+ line, paragraph_open=paragraph_open
669
+ )
670
+ if not fenced:
671
+ commented = _comment_state_after(
672
+ line, commented=commented, paragraph_open=paragraph_open
673
+ )
674
+ expanded = line.expandtabs(4)
675
+ code_indented = (
676
+ not fenced
677
+ and not paragraph_open
678
+ and bool(line.strip(" \t"))
679
+ and expanded.startswith(" ")
680
+ )
681
+ state_dependent = (
682
+ _SETEXT_UNDERLINE_RE if paragraph_open else _EMPTY_LIST_ITEM_RE
683
+ )
684
+ closes_paragraph = bool(
685
+ _CLOSES_PARAGRAPH_RE.match(expanded)
686
+ or state_dependent.match(expanded)
687
+ )
688
+ paragraph_open = (
689
+ not fenced
690
+ and bool(line.strip(" \t"))
691
+ and not closes_paragraph
692
+ and not (entered_commented or opens_comment)
693
+ # An indented-code line is literal, never a paragraph: letting
694
+ # it open one made the NEXT code line read as a live paragraph
695
+ # continuation and earn credit the renderer does not show
696
+ # (#610 round-4).
697
+ and not code_indented
698
+ )
699
+ yield line, fenced, entered_commented or opens_comment, code_indented
700
+
701
+
702
+ def _raw_dissent_span(text: str) -> DissentSpan:
703
+ """Dissent-section lines as written, before any sanitizer runs.
704
+
705
+ Comment delimiters are opened up rather than dropped, so a field inside an
706
+ HTML comment is scanned instead of vanishing with the comment. A
707
+ field-shaped H2 immediately inside the span rides along, because a field
708
+ spelled as its own `## dimension_id: D1` heading leaves the section body
709
+ empty and would otherwise be invisible; a field-shaped heading anywhere
710
+ else is an ordinary extra section, which the report grammar permits.
711
+
712
+ Only an unfenced heading delimits the span, so a heading written inside a
713
+ fenced block cannot end it early and hide the fields that follow — not
714
+ even one repeating a title that exists structurally elsewhere. A COMMENTED
715
+ heading still delimits, agreeing with `split_sections` rather than second-
716
+ guessing it: disagreeing cost four false aborts across review rounds and
717
+ bought only a miss that credits the seat nothing, while agreeing keeps a
718
+ comment opened above the heading from laundering the fields below it.
719
+ """
720
+ span, hidden_by_comment, raw_html = [], [], []
721
+ inside, commented = False, False
722
+ paragraph_open = False
723
+ # #613: span-scoped inline comment state. Outside the span, only a
724
+ # block-position opener counts (the #612 model, unchanged, because
725
+ # prose there may legitimately mention a bare marker). INSIDE the span
726
+ # the delivered output grammar requires inline code for any mention, so
727
+ # a bare `<!--` is an opener wherever it appears — closing the two
728
+ # residual shapes (#613): a marker following text on its own line, and
729
+ # a marker indented as a lazy paragraph continuation.
730
+ span_inline = False
731
+ # Code spans pair by equal-length runs ACROSS soft line breaks within a
732
+ # paragraph, which a per-line blanker cannot see: a trailing unpaired
733
+ # run on one line can pair into the next line and pull a `<!--` out of
734
+ # (or into) code (#613 security round 1, P1). Once a span line leaves
735
+ # an odd number of backtick runs, local blanking is untrustworthy for
736
+ # the REST of that paragraph: stop blanking and read every `<!--` as an
737
+ # opener — abort-direction, since the sanctioned mention is a
738
+ # same-line inline-code span in a paragraph with balanced runs.
739
+ code_parity_suspect = False
740
+ for line, fenced in _lines_with_fence_state(text):
741
+ entered_commented = commented
742
+ entered_inline = span_inline
743
+ opens_comment = not fenced and _opens_comment(
744
+ line, paragraph_open=paragraph_open
745
+ )
746
+ if not fenced:
747
+ # A block opener only, list and blockquote markers included:
748
+ # four columns STARTING a block makes indented code, and a fence
749
+ # makes every marker inert. Not read as an opener HERE: a marker
750
+ # mid-line, one in an inline-code span, or one indented as a lazy
751
+ # paragraph continuation — inside the dissent span those are the
752
+ # #613 inline state's job, now that the output grammar makes a
753
+ # bare marker out-of-grammar prose. #613 tracks, and leaves
754
+ # OPEN, the wider hiding channel this visibility model does not
755
+ # cover at all: raw HTML that is not a comment, such as a
756
+ # `<script>` or `<template>` block — the shipped closure is the
757
+ # comment channel only.
758
+ commented = _comment_state_after(
759
+ line, commented=commented, paragraph_open=paragraph_open
760
+ )
761
+ if not fenced and (match := panel._H2_RE.fullmatch(line)):
762
+ title = match.group(1)
763
+ if inside and _is_dissent_field_shaped(title):
764
+ span.append(title)
765
+ else:
766
+ inside = title == "Scoring Plan Dissent"
767
+ paragraph_open = False
768
+ span_inline = False
769
+ code_parity_suspect = False
770
+ continue
771
+ # CommonMark counts only spaces and tabs as blank, so a line holding
772
+ # an ideographic space is a paragraph. Calling it blank put the next
773
+ # line at a block start and aborted a valid zh-TW card.
774
+ expanded = line.expandtabs(4)
775
+ state_dependent = (
776
+ _SETEXT_UNDERLINE_RE if paragraph_open else _EMPTY_LIST_ITEM_RE
777
+ )
778
+ closes_paragraph = bool(
779
+ _CLOSES_PARAGRAPH_RE.match(expanded)
780
+ or state_dependent.match(expanded)
781
+ )
782
+ paragraph_open = (
783
+ not fenced
784
+ and bool(line.strip(" \t"))
785
+ and not closes_paragraph
786
+ # A recognized comment block is raw HTML, never a paragraph, so
787
+ # the line after one is at a block start again. Recording its own
788
+ # lines as an open paragraph read the next `2. <!--` with the
789
+ # restricted pattern and credited the fields it hides.
790
+ and not (entered_commented or opens_comment)
791
+ )
792
+ if inside:
793
+ if not line.strip(" \t"):
794
+ # A blank line closes the paragraph, and with it any
795
+ # cross-line code-span ambiguity.
796
+ code_parity_suspect = False
797
+ opens_inline = False
798
+ blanked = None
799
+ if (not fenced and not entered_commented and not entered_inline
800
+ and not opens_comment):
801
+ # #613: only lines the block model does NOT already own can
802
+ # open the inline state; code spans are blanked first so the
803
+ # grammar's sanctioned `` `<!--` `` mention stays prose —
804
+ # unless this paragraph's runs stopped pairing locally, in
805
+ # which case blanking is off and every marker opens.
806
+ escaped = _ESCAPED_BACKTICK_RE.sub(" ", line)
807
+ blanked = (
808
+ escaped if code_parity_suspect
809
+ else _blank_code_spans(line)
810
+ )
811
+ opens_inline = "<!--" in blanked
812
+ if len(re.findall(r"`+", escaped)) % 2:
813
+ code_parity_suspect = True
814
+ # #682: any non-comment raw-HTML tag/delimiter in the dissent
815
+ # span is out of grammar even when it hides no field. Scan the
816
+ # display form after inline-code blanking; fenced examples retain
817
+ # their previous semantics, and the exact H2 boundaries above
818
+ # keep the rule span-scoped. Under cross-line code ambiguity we
819
+ # take the same abort-direction as the #613 comment guard rather
820
+ # than letting a malformed tag earn a dissent exemption.
821
+ if not fenced:
822
+ html_scan = (
823
+ _ESCAPED_BACKTICK_RE.sub(" ", line)
824
+ if code_parity_suspect
825
+ else _blank_code_spans(line)
826
+ )
827
+ if _DISSENT_RAW_HTML_RE.search(html_scan):
828
+ raw_html.append(line)
829
+ # Opened up only where a comment actually is. Rewriting every line
830
+ # carrying the tokens would break a canonical `rationale:` that
831
+ # merely mentions them in inline code from matching the
832
+ # canonical parse, aborting an unretryable Phase 2 on a valid
833
+ # card.
834
+ if (entered_commented or opens_comment or entered_inline
835
+ or opens_inline):
836
+ span.append(line.replace("<!--", " ").replace("-->", " "))
837
+ else:
838
+ span.append(line)
839
+ if entered_commented or entered_inline:
840
+ hidden_by_comment.append(line)
841
+ if not fenced and (entered_inline or opens_inline):
842
+ span_inline = _inline_comment_state_after(
843
+ blanked if blanked is not None
844
+ else _blank_code_spans(line),
845
+ commented=entered_inline,
846
+ )
847
+ return DissentSpan(span, hidden_by_comment, raw_html)
848
+
849
+
850
+ def _empty_dissent_section_diagnostic(raw_span: list[str]) -> str:
851
+ """Counted on the raw span: fenced prose is archived content too."""
852
+ non_blank = sum(1 for line in raw_span if line.strip())
853
+ return (
854
+ "[DISSENT-EMPTY-SECTION: ## Scoring Plan Dissent spells no dissent "
855
+ "field; read as no dissent, with full Phase 1 trigger binding "
856
+ f"enforced on every dimension; {non_blank} non-blank line(s) present "
857
+ "— read the archived response if any narrate a deviation]"
858
+ )
859
+
860
+
861
+ def _one_field(
862
+ lines: list[str],
863
+ field: str,
864
+ path: str,
865
+ *,
866
+ required: bool,
867
+ dimension_id: str,
868
+ ) -> str | None:
869
+ hits = [match.group("value") for line in lines
870
+ if (match := _FIELD_PATTERNS[field].fullmatch(line))]
871
+ expected = "exactly one" if required else "at most one"
872
+ if (required and len(hits) != 1) or (not required and len(hits) > 1):
873
+ raise ConformanceError(
874
+ f"[PHASE1-GRAMMAR: {path}: expected {expected} canonical "
875
+ f"{field}: line for dimension {dimension_id}, found {len(hits)}]"
876
+ )
877
+ return hits[0] if hits else None
878
+
879
+
880
+ def parse_phase1(
881
+ path: str, text: str, contract: dict, role: str
882
+ ) -> PhaseOnePlan:
883
+ lines = panel.strip_fences(text)
884
+ sections, dupes = panel.split_sections(lines)
885
+ if "Scoring Plan" in dupes or "Scoring Plan" not in sections:
886
+ raise ConformanceError(
887
+ f"[PHASE1-GRAMMAR: {path}: exactly one ## Scoring Plan required]"
888
+ )
889
+ # §4 names three requirements, not one: the paraphrase section and the
890
+ # terminal acknowledgement are as mandatory as the plan, and a plan
891
+ # alone passing would let the dispatcher retry -- or accept -- a
892
+ # protocol-invalid Phase 1.
893
+ if "Contract Paraphrase" in dupes or "Contract Paraphrase" \
894
+ not in sections:
895
+ raise ConformanceError(
896
+ f"[PHASE1-GRAMMAR: {path}: exactly one ## Contract Paraphrase "
897
+ "required]"
898
+ )
899
+ # BOTH tails must be the marker. The raw tail catches output after
900
+ # the acknowledgement (a trailing fenced block would vanish from the
901
+ # structural view before the tail is computed); the fence-aware tail
902
+ # catches an acknowledgement that exists only as fenced code.
903
+ # Located by nonblankness but compared UNSTRIPPED (bar the line
904
+ # ending): an indented ` [CONTRACT-ACKNOWLEDGED]` renders as a
905
+ # code block, and stripping before comparison let it pass the exact
906
+ # terminal-line requirement.
907
+ raw_tail = next(
908
+ (line for line in reversed(text.splitlines())
909
+ if line.strip()), "")
910
+ fenced_tail = next(
911
+ (line for line in reversed(lines) if line.strip()), "")
912
+ if raw_tail.rstrip() != "[CONTRACT-ACKNOWLEDGED]" or \
913
+ fenced_tail.rstrip() != "[CONTRACT-ACKNOWLEDGED]":
914
+ raise ConformanceError(
915
+ f"[PHASE1-GRAMMAR: {path}: final nonblank line must be "
916
+ "[CONTRACT-ACKNOWLEDGED]]"
917
+ )
918
+ # And in §4's exact order, with nothing else at H2: presence alone
919
+ # would let a reordered or extra-sectioned precommitment pass. All
920
+ # four real dispatch outputs carry exactly this sequence.
921
+ h2_titles = [line[3:].strip() for line in lines
922
+ if line.startswith("## ")]
923
+ if h2_titles != ["Contract Paraphrase", "Scoring Plan"]:
924
+ raise ConformanceError(
925
+ f"[PHASE1-GRAMMAR: {path}: H2 sections must be exactly "
926
+ "## Contract Paraphrase then ## Scoring Plan, found "
927
+ f"{h2_titles!r}]"
928
+ )
929
+ dimensions = {d["id"]: d for d in contract["acceptance_dimensions"]}
930
+ # §4's paragraph floor: a bare heading over an empty or one-line body
931
+ # is not the paraphrase the contract's `paraphrase_minimum_dimensions`
932
+ # names ("all" = one paragraph per dimension). The count is the
933
+ # machine-checkable lower bound; whether each paragraph is TIED to a
934
+ # distinct dimension stays with the seat's own §4 preflight.
935
+ # A CLOSED list of zero-content lines that separate and never count:
936
+ # ATX headings, thematic breaks, single-line HTML comments -- six
937
+ # bare `### Dn` headings (or six `---` rules) used to count as six
938
+ # paragraphs, satisfying the floor with no paraphrase prose at all.
939
+ # Deliberately NOT a full CommonMark block classifier: a list item
940
+ # still counts, because a bulleted six-point paraphrase is real
941
+ # content and refusing it would abort a panel over formatting (the
942
+ # false-abort channel #609 exists to remove). Blocks are still
943
+ # blank-line separated -- a TIGHT six-item list is one block and
944
+ # does not meet a six-paragraph floor, same as before this list.
945
+ # TERMINATION BOUND: zero-content variants beyond this list
946
+ # (container-prefixed comments like `- <!-- -->`, malformed comments
947
+ # like `<!-->`, entity/whitespace tricks) are out of scope by
948
+ # declared design -- the variant space is unbounded, the observed
949
+ # base rate in committed panels is zero, and §4's substantive
950
+ # judgment sits with the seat's preflight, not this floor.
951
+ separator = re.compile(
952
+ r"#{1,6}(\s|$)" # ATX heading
953
+ r"|([-*_])(\s*\2){2,}$" # thematic break
954
+ r"|<!--.*-->$" # single-line HTML comment
955
+ r"|[-*+]$" # lone list marker, no item text
956
+ )
957
+ paragraphs, in_paragraph, in_comment = 0, False, False
958
+ for line in sections["Contract Paraphrase"]:
959
+ stripped = line.strip()
960
+ if in_comment:
961
+ # Hidden until the closing `-->`, closer line included: six
962
+ # multi-line comment blocks are as unrendered as six
963
+ # single-line ones.
964
+ if "-->" in stripped:
965
+ in_comment = False
966
+ in_paragraph = False
967
+ continue
968
+ if stripped.startswith("<!--") and "-->" not in stripped:
969
+ # Conservative entry -- a line-LEADING opener with no closer
970
+ # on the same line. Prose that merely mentions `<!--`
971
+ # mid-line stays countable content.
972
+ in_comment = True
973
+ in_paragraph = False
974
+ continue
975
+ if stripped and not separator.match(stripped):
976
+ if not in_paragraph:
977
+ paragraphs += 1
978
+ in_paragraph = True
979
+ else:
980
+ in_paragraph = False
981
+ minimum = contract.get("measurement_procedure", {}).get(
982
+ "paraphrase_minimum_dimensions")
983
+ required = len(dimensions) if minimum == "all" else (
984
+ minimum if isinstance(minimum, int) else 0)
985
+ if paragraphs < required:
986
+ raise ConformanceError(
987
+ f"[PHASE1-GRAMMAR: {path}: Contract Paraphrase has "
988
+ f"{paragraphs} paragraph(s), fewer than the {required} "
989
+ "required]"
990
+ )
991
+ eligible = {
992
+ did for did, dim in dimensions.items()
993
+ if role in dim["eligible_roles"]
994
+ }
995
+ subsections, subsection_dupes = panel.split_subsections(
996
+ sections["Scoring Plan"]
997
+ )
998
+ if subsection_dupes:
999
+ raise ConformanceError(
1000
+ f"[PHASE1-GRAMMAR: {path}: duplicate scoring-plan subsection: "
1001
+ f"{', '.join(sorted(subsection_dupes))}]"
1002
+ )
1003
+ commitments: dict[str, dict[str, str]] = {}
1004
+ warnings: list[str] = []
1005
+ for title, sublines in subsections.items():
1006
+ match = panel._DIM_H3_RE.fullmatch(title)
1007
+ if not match or match.group("dim") not in dimensions:
1008
+ raise ConformanceError(
1009
+ f"[PHASE1-GRAMMAR: {path}: invalid subsection '### {title}']"
1010
+ )
1011
+ did = match.group("dim")
1012
+ if did not in eligible:
1013
+ raise ConformanceError(
1014
+ f"[PHASE1-OUT-OF-ROLE: {path}: role {role} planned {did}]"
1015
+ )
1016
+ if match.group("name") != dimensions[did]["name"]:
1017
+ raise ConformanceError(
1018
+ f"[PHASE1-GRAMMAR: {path}: {did} name mismatch]"
1019
+ )
1020
+ fields = {
1021
+ field: _one_field(
1022
+ sublines,
1023
+ field,
1024
+ path,
1025
+ required=True,
1026
+ dimension_id=did,
1027
+ )
1028
+ for field in (
1029
+ "dimension_id", "what_to_look_for",
1030
+ "what_triggers_block", "what_triggers_warn",
1031
+ )
1032
+ }
1033
+ mandatory = dimensions[did]["priority"] == "mandatory"
1034
+ fields["what_triggers_fatal"] = _one_field(
1035
+ sublines,
1036
+ "what_triggers_fatal",
1037
+ path,
1038
+ required=mandatory,
1039
+ dimension_id=did,
1040
+ )
1041
+ if not mandatory and fields["what_triggers_fatal"] is not None:
1042
+ raise ConformanceError(
1043
+ f"[PHASE1-GRAMMAR: {path}: what_triggers_fatal is forbidden "
1044
+ f"on non-mandatory dimension {did}]"
1045
+ )
1046
+ if fields["dimension_id"] != did:
1047
+ raise ConformanceError(
1048
+ f"[PHASE1-GRAMMAR: {path}: heading {did} disagrees with "
1049
+ f"dimension_id={fields['dimension_id']}]"
1050
+ )
1051
+ triggers = [
1052
+ fields["what_triggers_block"],
1053
+ fields["what_triggers_warn"],
1054
+ ]
1055
+ if mandatory:
1056
+ triggers.append(fields["what_triggers_fatal"])
1057
+ if len({_normalise(value) for value in triggers}) != len(triggers):
1058
+ raise ConformanceError(
1059
+ f"[PHASE1-TRIGGER-COLLISION: {path}: {did} trigger "
1060
+ "commitments must be pairwise distinct]"
1061
+ )
1062
+ for field in (
1063
+ "what_triggers_block", "what_triggers_warn",
1064
+ "what_triggers_fatal",
1065
+ ):
1066
+ value = fields[field]
1067
+ if value is not None and len(value.split()) < 8:
1068
+ warnings.append(
1069
+ f"[PHASE1-TRIGGER-SHORT: {path}: {did} {field} "
1070
+ "has fewer than 8 words]"
1071
+ )
1072
+ commitments[did] = fields
1073
+ if set(commitments) != eligible:
1074
+ raise ConformanceError(
1075
+ f"[PHASE1-SCOPE: {path}: planned={sorted(commitments)}, "
1076
+ f"eligible={sorted(eligible)}]"
1077
+ )
1078
+ return PhaseOnePlan(commitments, warnings)
1079
+
1080
+
1081
+ def _flatten_metadata_values(value) -> list[str]:
1082
+ if isinstance(value, dict):
1083
+ return [
1084
+ item
1085
+ for nested in value.values()
1086
+ for item in _flatten_metadata_values(nested)
1087
+ ]
1088
+ if isinstance(value, list):
1089
+ return [
1090
+ item
1091
+ for nested in value
1092
+ for item in _flatten_metadata_values(nested)
1093
+ ]
1094
+ if isinstance(value, (str, int, float, bool)):
1095
+ return [str(value)]
1096
+ return []
1097
+
1098
+
1099
+ def validate_metadata_envelope(metadata) -> None:
1100
+ if not isinstance(metadata, dict) or set(metadata) != _METADATA_KEYS:
1101
+ actual = (
1102
+ sorted(metadata)
1103
+ if isinstance(metadata, dict)
1104
+ else type(metadata).__name__
1105
+ )
1106
+ raise panel.ContractError(
1107
+ "[METADATA-INVALID: expected exact title/field/word_count "
1108
+ f"envelope, got {actual}]"
1109
+ )
1110
+ if (
1111
+ not isinstance(metadata["title"], str)
1112
+ or not metadata["title"].strip()
1113
+ or not isinstance(metadata["field"], str)
1114
+ or not metadata["field"].strip()
1115
+ or isinstance(metadata["word_count"], bool)
1116
+ or not isinstance(metadata["word_count"], int)
1117
+ or metadata["word_count"] < 0
1118
+ ):
1119
+ raise panel.ContractError(
1120
+ "[METADATA-INVALID: title and field must be non-empty strings; "
1121
+ "word_count must be a non-negative integer]"
1122
+ )
1123
+
1124
+
1125
+ def check_manuscript_leakage(
1126
+ phase1_text: str, manuscript_text: str, metadata: dict, contract: dict
1127
+ ) -> None:
1128
+ validate_metadata_envelope(metadata)
1129
+ phase1_norm = _normalise(phase1_text)
1130
+ words = _normalise(manuscript_text).split()
1131
+ exemption_haystacks = [
1132
+ _normalise(value) for value in _flatten_metadata_values(metadata)
1133
+ ]
1134
+ exemption_haystacks.append(_normalise(json.dumps(
1135
+ contract, ensure_ascii=False, sort_keys=True
1136
+ )))
1137
+ for index in range(max(0, len(words) - 11)):
1138
+ shingle = " ".join(words[index:index + 12])
1139
+ if shingle not in phase1_norm:
1140
+ continue
1141
+ if any(shingle in haystack for haystack in exemption_haystacks):
1142
+ continue
1143
+ raise ConformanceError(
1144
+ "[PHASE1-MANUSCRIPT-LEAK: 12-word manuscript shingle appears "
1145
+ "in Phase 1 outside metadata/contract exemptions]"
1146
+ )
1147
+
1148
+
1149
+ def parse_dissent_dimensions(text: str) -> DissentParse:
1150
+ lines = panel.strip_fences(text)
1151
+ sections, dupes = panel.split_sections(lines)
1152
+ if "Scoring Plan Dissent" in dupes:
1153
+ raise ConformanceError(
1154
+ "[DISSENT-GRAMMAR: duplicate ## Scoring Plan Dissent]"
1155
+ )
1156
+ if "Scoring Plan Dissent" not in sections:
1157
+ return DissentParse(set(), [])
1158
+ body = sections["Scoring Plan Dissent"]
1159
+ span = _raw_dissent_span(text)
1160
+ raw_span = span.lines
1161
+ # A commented-out field is not a claim the seat made, so it is struck from
1162
+ # the canonical parse and left to fail below as an unparsed occurrence.
1163
+ outstanding = Counter(span.hidden_by_comment)
1164
+ visible = []
1165
+ for line in body:
1166
+ if outstanding.get(line):
1167
+ outstanding[line] -= 1
1168
+ continue
1169
+ visible.append(line)
1170
+ parsed = Counter(
1171
+ line for line in visible
1172
+ if _DISSENT_DIM_RE.fullmatch(line)
1173
+ or _DISSENT_RATIONALE_RE.fullmatch(line)
1174
+ )
1175
+ dims = [match.group("dim") for line in visible
1176
+ if (match := _DISSENT_DIM_RE.fullmatch(line))]
1177
+ rationales = [
1178
+ match.group("text") for line in visible
1179
+ if (match := _DISSENT_RATIONALE_RE.fullmatch(line))
1180
+ ]
1181
+ # Scanned on the RAW span, not the sanitized body: a field the sanitizers
1182
+ # delete (fence, comment) or relocate (its own H2) would otherwise reach
1183
+ # the tolerance branch as an empty section. A field line the canonical
1184
+ # parse never saw is a dissent this seat cannot be credited with, so it
1185
+ # fails whether or not it is canonically spelled. Counted rather than
1186
+ # matched by value, so a hidden copy of a canonical field is still one
1187
+ # unparsed occurrence and cannot ride in on its twin's identity.
1188
+ hidden = Counter(
1189
+ candidate for candidate in raw_span
1190
+ if _is_dissent_field_shaped(candidate)
1191
+ )
1192
+ # Raw HTML owns its own diagnostic even when stripping the tag leaves a
1193
+ # field-shaped line. Checking it first prevents a ``<span>dimension_id``
1194
+ # wrapper from being mislabeled as comment hiding by the older generic
1195
+ # raw-span occurrence guard. Comment-only cards never populate this list
1196
+ # and retain the #613 ``[DISSENT-HIDDEN]`` path below.
1197
+ if span.raw_html:
1198
+ raise ConformanceError(
1199
+ "[DISSENT-RAW-HTML: raw HTML tags or delimiters are forbidden "
1200
+ "inside ## Scoring Plan Dissent; put markup mentions in inline "
1201
+ "code]"
1202
+ )
1203
+ if any(count > parsed[value] for value, count in hidden.items()):
1204
+ # Distinct marker (#613 security round 1, P3): these fields ARE
1205
+ # canonical — the failure is that comment markup hides them from
1206
+ # the rendered card, and pointing the operator at line grammar
1207
+ # misattributes an unretryable abort.
1208
+ raise ConformanceError(
1209
+ "[DISSENT-HIDDEN: a canonical dissent field is hidden from the "
1210
+ "rendered card by comment markup; write dissent fields in the "
1211
+ "clear and mention comment syntax only in inline code]"
1212
+ )
1213
+ if any(
1214
+ _is_dissent_field_shaped(candidate) and candidate not in parsed
1215
+ for candidate in visible
1216
+ ):
1217
+ raise ConformanceError(
1218
+ "[DISSENT-GRAMMAR: dissent fields must be canonical unbulleted "
1219
+ "dimension_id: and rationale: lines]"
1220
+ )
1221
+ if not dims and not rationales:
1222
+ # A section that spells no dissent field carries the same information
1223
+ # as an absent section, so it is read as no dissent instead of
1224
+ # aborting an unretryable Phase 2. The occurrence stays auditable in
1225
+ # the run record, and full Phase 1 trigger binding still applies to
1226
+ # every dimension.
1227
+ return DissentParse(
1228
+ set(), [_empty_dissent_section_diagnostic(raw_span)]
1229
+ )
1230
+ h2_positions = {
1231
+ match.group(1): index
1232
+ for index, line in enumerate(lines)
1233
+ if (match := panel._H2_RE.fullmatch(line))
1234
+ }
1235
+ if h2_positions["Scoring Plan Dissent"] > h2_positions.get(
1236
+ "Dimension Scores", -1
1237
+ ):
1238
+ raise ConformanceError(
1239
+ "[DISSENT-GRAMMAR: ## Scoring Plan Dissent must precede "
1240
+ "## Dimension Scores]"
1241
+ )
1242
+ if not dims:
1243
+ raise ConformanceError(
1244
+ "[DISSENT-GRAMMAR: dissent section must name dimension_id]"
1245
+ )
1246
+ if len(dims) != len(set(dims)):
1247
+ raise ConformanceError("[DISSENT-GRAMMAR: duplicate dimension_id]")
1248
+ if len(rationales) != len(dims):
1249
+ raise ConformanceError(
1250
+ "[DISSENT-GRAMMAR: each dissent requires one rationale: line]"
1251
+ )
1252
+ return DissentParse(set(dims), [])
1253
+
1254
+
1255
+ def check_trigger_binding(
1256
+ report: panel.ReviewerReport,
1257
+ plan: PhaseOnePlan,
1258
+ dimensions: dict[str, dict],
1259
+ dissent: set[str],
1260
+ ) -> None:
1261
+ if len(dissent) >= 2:
1262
+ raise ConformanceError(
1263
+ f"[PROTOCOL-VIOLATION: multi_dissent=true, "
1264
+ f"dimensions={sorted(dissent)}]"
1265
+ )
1266
+ unknown = dissent - set(dimensions)
1267
+ if unknown:
1268
+ raise ConformanceError(
1269
+ f"[DISSENT-GRAMMAR: unknown dimensions {sorted(unknown)}]"
1270
+ )
1271
+ uncommitted = dissent - set(plan.commitments)
1272
+ if uncommitted:
1273
+ raise ConformanceError(
1274
+ f"[DISSENT-GRAMMAR: dissent dimensions were not committed by "
1275
+ f"this seat {sorted(uncommitted)}]"
1276
+ )
1277
+ for did, value in report.scores.items():
1278
+ eligible = report.role in dimensions[did]["eligible_roles"]
1279
+ needs_trigger = eligible and value.score in {"block", "warn"}
1280
+ if needs_trigger != bool(value.trigger):
1281
+ raise ConformanceError(
1282
+ f"[TRIGGER-GRAMMAR: {report.path}: {did} trigger is required "
1283
+ "iff an eligible dimension scores block or warn]"
1284
+ )
1285
+ if did in dissent:
1286
+ if value.block_class == "fatal":
1287
+ raise ConformanceError(
1288
+ f"[DISSENT-FATALITY: {did} dissent may not mint fatality]"
1289
+ )
1290
+ continue
1291
+ if not value.trigger:
1292
+ continue
1293
+ if value.score == "warn":
1294
+ field = "what_triggers_warn"
1295
+ elif value.block_class == "fatal":
1296
+ field = "what_triggers_fatal"
1297
+ else:
1298
+ field = "what_triggers_block"
1299
+ committed = plan.commitments.get(did, {}).get(field)
1300
+ if not committed or _normalise(value.trigger) not in _normalise(committed):
1301
+ raise ConformanceError(
1302
+ f"[TRIGGER-DRIFT: {did} {field} does not contain emitted "
1303
+ "trigger text]"
1304
+ )
1305
+ matching_fields = {
1306
+ candidate
1307
+ for candidate in (
1308
+ "what_triggers_block", "what_triggers_warn",
1309
+ "what_triggers_fatal",
1310
+ )
1311
+ if plan.commitments.get(did, {}).get(candidate)
1312
+ and _normalise(value.trigger) in _normalise(
1313
+ plan.commitments[did][candidate]
1314
+ )
1315
+ }
1316
+ if matching_fields != {field}:
1317
+ raise ConformanceError(
1318
+ f"[TRIGGER-AMBIGUOUS: {did} emitted trigger matches "
1319
+ f"{sorted(matching_fields)}, expected only {field}]"
1320
+ )
1321
+
1322
+
1323
+ def _validate_anchor(anchor: str, context: str) -> None:
1324
+ try:
1325
+ panel.validate_evidence_anchor(anchor, context)
1326
+ except panel.ReportError as exc:
1327
+ raise ConformanceError(str(exc)) from exc
1328
+
1329
+
1330
+ def check_scoring_seat_anchors(report: panel.ReviewerReport) -> None:
1331
+ lines = panel.strip_fences(report.text)
1332
+ sections, dupes = panel.split_sections(lines)
1333
+ if "Review Body" in dupes or "Review Body" not in sections:
1334
+ raise ConformanceError(
1335
+ f"[REVIEW-BODY-MISSING: {report.path}]"
1336
+ )
1337
+ current_h2 = None
1338
+ for line in lines:
1339
+ if match := panel._H2_RE.fullmatch(line):
1340
+ current_h2 = match.group(1)
1341
+ elif _SEVERITY_DECL_RE.search(line) and current_h2 != "Review Body":
1342
+ raise ConformanceError(
1343
+ f"[FINDING-GRAMMAR: {report.path}: Severity outside "
1344
+ "## Review Body]"
1345
+ )
1346
+ review_lines = sections["Review Body"]
1347
+ blocks, subsection_dupes = panel.split_subsections(review_lines)
1348
+ if subsection_dupes:
1349
+ raise ConformanceError(
1350
+ f"[FINDING-GRAMMAR: {report.path}: duplicate finding heading]"
1351
+ )
1352
+ preamble = []
1353
+ for line in review_lines:
1354
+ if panel._H3_RE.fullmatch(line):
1355
+ break
1356
+ preamble.append(line)
1357
+ if any(_SEVERITY_DECL_RE.search(line) for line in preamble):
1358
+ raise ConformanceError(
1359
+ f"[FINDING-GRAMMAR: {report.path}: every finding with "
1360
+ "Severity must have its own ### finding heading]"
1361
+ )
1362
+ for title, block in blocks.items():
1363
+ severity_declarations = sum(
1364
+ len(_SEVERITY_DECL_RE.findall(line)) for line in block
1365
+ )
1366
+ severities = [
1367
+ match.group("severity") for line in block
1368
+ for match in _SEVERITY_RE.finditer(line)
1369
+ ]
1370
+ is_finding = _FINDING_H3_RE.fullmatch(title) is not None
1371
+ if severity_declarations and not is_finding:
1372
+ raise ConformanceError(
1373
+ f"[FINDING-GRAMMAR: {report.path}: every finding with "
1374
+ "Severity must have its own ### W<n>: <title> heading]"
1375
+ )
1376
+ if is_finding and any(panel._H4_RE.fullmatch(line) for line in block):
1377
+ raise ConformanceError(
1378
+ f"[FINDING-GRAMMAR: {report.path}: {title} may not nest "
1379
+ "a Severity finding under H4]"
1380
+ )
1381
+ if not is_finding:
1382
+ continue
1383
+ # A finding needs at least one parseable Severity declaration, and
1384
+ # every declaration must parse. When a card declares more than one
1385
+ # ACROSS lines and the chain strictly ESCALATES (Minor < Major <
1386
+ # Critical), the LAST in reading order is operative — the current
1387
+ # model generation self-corrects mid-card with explicit supersession
1388
+ # prose ("See the Severity line below, which supersedes the line
1389
+ # above", #637 ms01_quant r1: Major -> Critical), and Phase 2 permits
1390
+ # no retry for this class, so a strict exactly-one rule turns a
1391
+ # visible, reader-unambiguous correction into a whole-panel abort.
1392
+ # Every other multi-declaration shape keeps the loud abort:
1393
+ # de-escalation could waive the Critical/Major Evidence-Anchor
1394
+ # requirement by appending one weaker line; a non-monotone or
1395
+ # repeated-value chain signals several findings bundled under one W
1396
+ # heading (the one-finding-per-heading accounting feeds the severity
1397
+ # ladder); and two parseable declarations on ONE line are not a
1398
+ # reading-order correction at all. The advisory line below keeps the
1399
+ # full declaration trail in the gate log for adjudication.
1400
+ parseable_per_line = [
1401
+ sum(1 for _ in _SEVERITY_RE.finditer(line)) for line in block
1402
+ ]
1403
+ if (not severities or len(severities) != severity_declarations
1404
+ or any(count > 1 for count in parseable_per_line)):
1405
+ raise ConformanceError(
1406
+ f"[FINDING-GRAMMAR: {report.path}: {title} must contain "
1407
+ "exactly one parseable Severity declaration]"
1408
+ )
1409
+ severity_rank = {"Minor": 0, "Major": 1, "Critical": 2}
1410
+ if any(severity_rank[later] <= severity_rank[earlier]
1411
+ for earlier, later in zip(severities, severities[1:])):
1412
+ raise ConformanceError(
1413
+ f"[FINDING-GRAMMAR: {report.path}: {title}: multiple "
1414
+ "Severity declarations must form a strictly escalating "
1415
+ "self-correction chain]"
1416
+ )
1417
+ if len(severities) > 1:
1418
+ print(
1419
+ f"[SEVERITY-SUPERSEDED: {report.path}: {title}: "
1420
+ + " -> ".join(severities) + "]"
1421
+ )
1422
+ operative_severity = severities[-1]
1423
+ anchor_declarations = sum(
1424
+ len(_ANCHOR_DECL_RE.findall(line)) for line in block
1425
+ )
1426
+ anchors = [
1427
+ match.group("value") for line in block
1428
+ for match in _ANCHOR_RE.finditer(line)
1429
+ ]
1430
+ if operative_severity not in {"Critical", "Major"}:
1431
+ if anchor_declarations > 1 or len(anchors) != anchor_declarations:
1432
+ raise ConformanceError(
1433
+ f"[FINDING-GRAMMAR: {report.path}: {title} may contain "
1434
+ "at most one parseable Evidence Anchor declaration]"
1435
+ )
1436
+ if anchors:
1437
+ _validate_anchor(anchors[0], f"{report.path}:{title}")
1438
+ continue
1439
+ if len(anchors) != 1 or anchor_declarations != 1:
1440
+ raise ConformanceError(
1441
+ f"[ANCHOR-MISSING: {report.path}: {title} "
1442
+ f"{operative_severity} finding needs exactly one "
1443
+ "Evidence Anchor]"
1444
+ )
1445
+ _validate_anchor(anchors[0], f"{report.path}:{title}")
1446
+
1447
+
1448
+ def check_da_anchors(report: panel.ReviewerReport) -> None:
1449
+ try:
1450
+ rows, major_anchors = panel.parse_da_tables(report.text, report.path)
1451
+ except panel.ReportError as exc:
1452
+ raise ConformanceError(str(exc)) from exc
1453
+ expected = [f"C{index}" for index in range(1, len(rows) + 1)]
1454
+ if list(rows) != expected:
1455
+ raise ConformanceError(
1456
+ f"[DA-CRITICAL-ID: {report.path}: IDs must be dense C1..Cn; "
1457
+ f"got={list(rows)}]"
1458
+ )
1459
+ for finding_id, anchor in rows.items():
1460
+ if not anchor:
1461
+ raise ConformanceError(
1462
+ f"[ANCHOR-MISSING: {report.path}: {finding_id}]"
1463
+ )
1464
+ _validate_anchor(anchor, f"{report.path}:{finding_id}")
1465
+
1466
+ for anchor in major_anchors:
1467
+ if not anchor:
1468
+ raise ConformanceError(
1469
+ f"[ANCHOR-MISSING: {report.path}: DA MAJOR row]"
1470
+ )
1471
+ _validate_anchor(anchor, f"{report.path}:DA MAJOR")
1472
+
1473
+
1474
+ def _one_receipt_field(
1475
+ lines: list[str], key: str, path: str, receipt_id: str, *, required: bool
1476
+ ) -> str | None:
1477
+ hits = [
1478
+ match.group("value")
1479
+ for line in lines
1480
+ if (match := _RECEIPT_FIELD_RES[key].fullmatch(line))
1481
+ ]
1482
+ expected = "exactly one" if required else "at most one"
1483
+ if (required and len(hits) != 1) or (not required and len(hits) > 1):
1484
+ raise ConformanceError(
1485
+ f"[RECEIPT-GRAMMAR: {path}: {receipt_id} expected {expected} "
1486
+ f"canonical {key}: line, found {len(hits)}]"
1487
+ )
1488
+ return hits[0] if hits else None
1489
+
1490
+
1491
+ def _review_body_receipt_backrefs(
1492
+ report: panel.ReviewerReport,
1493
+ ) -> tuple[set[str], dict[str, list[str]]]:
1494
+ """W-finding ids and per-block `**Arithmetic Receipt**:` back-references.
1495
+
1496
+ Back-references are collected across the WHOLE Review Body — preamble and
1497
+ non-W subsections included — so a back-reference stranded outside its
1498
+ weakness block is a linkage failure, not invisible. The walk is
1499
+ fence-aware: only unfenced headings delimit (agreeing with
1500
+ `split_sections`), a fenced or decorated back-reference declaration is
1501
+ counted by the lenient detector, and a declaration count exceeding the
1502
+ canonical parse aborts — a back-reference the seat spelled but the
1503
+ grammar refused is a linkage failure, never a silent drop.
1504
+ """
1505
+ lines = panel.strip_fences(report.text)
1506
+ sections, _ = panel.split_sections(lines)
1507
+ review_lines = sections.get("Review Body", [])
1508
+ blocks, _ = panel.split_subsections(review_lines)
1509
+ finding_ids = {
1510
+ title.split(":", 1)[0]
1511
+ for title in blocks
1512
+ if _FINDING_H3_RE.fullmatch(title)
1513
+ }
1514
+ backrefs: dict[str, list[str]] = {}
1515
+ current = "(preamble)"
1516
+ inside = False
1517
+ inline_commented = False
1518
+ for line, fenced, hidden, code_indented in _lines_with_hidden_state(
1519
+ report.text
1520
+ ):
1521
+ if not fenced and (match := panel._H2_RE.fullmatch(line)):
1522
+ inside = match.group(1) == "Review Body"
1523
+ current = "(preamble)"
1524
+ inline_commented = False
1525
+ continue
1526
+ if not inside:
1527
+ continue
1528
+ parse_line = line
1529
+ if not fenced and not hidden and not code_indented:
1530
+ # A paragraph-inline comment span cannot cross a paragraph
1531
+ # boundary: a blank line or an interrupting ATX heading (any
1532
+ # level) ends the paragraph and with it the raw-HTML span, so
1533
+ # the state resets there instead of leaking a false abort into
1534
+ # later prose. Code spans are blanked first, matching the
1535
+ # renderer's precedence: a literal `<!--` inside inline code
1536
+ # opens nothing (#610 round-4).
1537
+ if (
1538
+ not line.strip(" \t")
1539
+ or _ATX_HEADING_RE.match(line)
1540
+ ):
1541
+ inline_commented = False
1542
+ else:
1543
+ code_blanked = _blank_code_spans(line)
1544
+ visible, hidden_segments, inline_commented = (
1545
+ _strip_inline_comment_spans(
1546
+ code_blanked, inline_commented
1547
+ )
1548
+ )
1549
+ if visible != code_blanked:
1550
+ # A declaration inside a hidden span aborts whether or
1551
+ # not the line also shows visible content (#610 round
1552
+ # 5): hidden prose is skippable, a hidden machine
1553
+ # declaration never is. Each span is checked on its
1554
+ # own (#610 round-6).
1555
+ if any(
1556
+ _backref_declared_count(segment)
1557
+ for segment in hidden_segments
1558
+ ):
1559
+ raise ConformanceError(
1560
+ f"[RECEIPT-LINKAGE: {report.path}: an "
1561
+ "Arithmetic Receipt back-reference "
1562
+ "declaration sits inside a paragraph-"
1563
+ "inline HTML comment span and does not "
1564
+ "render; it is non-conforming]"
1565
+ )
1566
+ if not visible.strip(" \t"):
1567
+ continue
1568
+ # Only the rendered remainder is parsed: content after
1569
+ # a closing `-->` stays live, content behind an opener
1570
+ # is gone.
1571
+ parse_line = visible
1572
+ if fenced or hidden or code_indented:
1573
+ # Never credited: a machine declaration the page does not show
1574
+ # as a field line — fenced, commented, or rendered as indented
1575
+ # code — is a loud failure, not a silent drop or a silent
1576
+ # credit (#610 rounds 2-3).
1577
+ if _backref_declared_count(line):
1578
+ kind = (
1579
+ "fenced" if fenced
1580
+ else "commented-out" if hidden
1581
+ else "indented-code"
1582
+ )
1583
+ raise ConformanceError(
1584
+ f"[RECEIPT-LINKAGE: {report.path}: a {kind} Arithmetic "
1585
+ "Receipt back-reference declaration does not render as "
1586
+ "a field line and is non-conforming]"
1587
+ )
1588
+ continue
1589
+ if match := panel._H3_RE.fullmatch(line):
1590
+ title = match.group(1)
1591
+ current = (
1592
+ title.split(":", 1)[0]
1593
+ if _FINDING_H3_RE.fullmatch(title)
1594
+ else f"(non-finding: {title})"
1595
+ )
1596
+ continue
1597
+ # Canonical parsing and the declaration count run on the SAME
1598
+ # display form (#610 round-4 P1): parsing the raw line while
1599
+ # counting on a blanked one let a code-span declaration be
1600
+ # credited over the rendered field beside it.
1601
+ display = _cell_split_form(parse_line)
1602
+ matches = list(_RECEIPT_BACKREF_RE.finditer(display))
1603
+ if _backref_declared_count(display) != len(matches):
1604
+ raise ConformanceError(
1605
+ f"[RECEIPT-LINKAGE: {report.path}: back-reference "
1606
+ f"declaration(s) on {line.strip()!r} do not all parse "
1607
+ "canonically — each value must be exactly "
1608
+ "**Arithmetic Receipt**: AR<n> with no trailing text]"
1609
+ )
1610
+ for match in matches:
1611
+ backrefs.setdefault(current, []).append(match.group("value"))
1612
+ return finding_ids, backrefs
1613
+
1614
+
1615
+ _INDENTED_H2_RE = re.compile(r"^ {1,3}##\s+\S")
1616
+ # Any ATX heading interrupts a paragraph (CommonMark), so any level ends a
1617
+ # paragraph-inline comment span — resetting only on H3 false-aborted a
1618
+ # rendered backref after an interrupting H4 (#610 round-4).
1619
+ _ATX_HEADING_RE = re.compile(r"^ {0,3}#{1,6}(?:[ \t]|$)")
1620
+
1621
+
1622
+ def _receipt_section_view(
1623
+ text: str,
1624
+ ) -> tuple[list[tuple[str, bool]], list[str], bool]:
1625
+ """Receipt-section lines with visibility, the unfenced H2 order, and
1626
+ whether an indented H2 renders after the section opened.
1627
+
1628
+ The section body is read through `_lines_with_hidden_state` rather than
1629
+ `strip_fences` (#610 rounds 1-2, the #637/#609 convergence): a model
1630
+ that fences its receipt block still WROTE the receipts, so the fenced
1631
+ lines are read as content — no false abort — while a fenced `### AR<n>`
1632
+ block sitting beside an unfenced attestation is SEEN and aborts as
1633
+ receipts-plus-attestation instead of hiding. Each content line carries
1634
+ its hidden flag so a machine line inside an HTML comment — which the
1635
+ rendered card does not show — can abort rather than be credited. Only
1636
+ an unfenced H2 delimits, agreeing with `split_sections` (a COMMENTED
1637
+ heading therefore still delimits, as in `_raw_dissent_span`), so a
1638
+ fenced heading can neither open nor close the section. The H2 title is
1639
+ matched exactly, case-sensitively, consistent with every other section
1640
+ grammar. A 1-3-space-indented `##` line is not a section to this
1641
+ grammar but still renders as a heading, so one appearing after the
1642
+ receipt section opens is reported for the terminal-section rule.
1643
+ """
1644
+ h2_titles: list[str] = []
1645
+ section: list[tuple[str, bool, bool]] = []
1646
+ inside = False
1647
+ indented_h2_after = False
1648
+ for line, fenced, hidden, _ in _lines_with_hidden_state(text):
1649
+ if not fenced and (match := panel._H2_RE.fullmatch(line)):
1650
+ h2_titles.append(match.group(1))
1651
+ inside = match.group(1) == _RECEIPT_SECTION
1652
+ continue
1653
+ if inside:
1654
+ if not fenced and not hidden and _INDENTED_H2_RE.match(line):
1655
+ indented_h2_after = True
1656
+ section.append((line, hidden, fenced))
1657
+ return section, h2_titles, indented_h2_after
1658
+
1659
+
1660
+ def check_methodology_receipts(report: panel.ReviewerReport) -> None:
1661
+ """#610 arithmetic-receipt gate, methodology seat only.
1662
+
1663
+ Auditability, not truth: this gate proves the receipt fields exist, use
1664
+ the closed enums, and link mismatches to weaknesses bidirectionally. It
1665
+ never attests that the arithmetic is correct — that judgment stays with
1666
+ human adjudication (`VERIFIED` / `MISCOMPUTED`), per the spec's invariant
1667
+ 6 and the receipt block's leading epistemic-status note.
1668
+ """
1669
+ view, h2_titles, indented_h2_after = _receipt_section_view(report.text)
1670
+ occurrences = h2_titles.count(_RECEIPT_SECTION)
1671
+ if occurrences > 1:
1672
+ raise ConformanceError(
1673
+ f"[RECEIPT-GRAMMAR: {report.path}: duplicate "
1674
+ f"## {_RECEIPT_SECTION}]"
1675
+ )
1676
+ if occurrences == 0:
1677
+ raise ConformanceError(
1678
+ f"[RECEIPT-MISSING: {report.path}: methodology card requires "
1679
+ f"exactly one ## {_RECEIPT_SECTION} section]"
1680
+ )
1681
+ if h2_titles[-1] != _RECEIPT_SECTION or indented_h2_after:
1682
+ raise ConformanceError(
1683
+ f"[RECEIPT-GRAMMAR: {report.path}: ## {_RECEIPT_SECTION} must "
1684
+ "be the final section of the card]"
1685
+ )
1686
+ in_subsection = False
1687
+ for line, hidden, fenced in view:
1688
+ if not fenced and ("<!--" in line or "-->" in line):
1689
+ # Comment-free zone (#610 round-3 P1): the receipt section is
1690
+ # machine lines, so a paragraph-inline `prose <!--` opener —
1691
+ # which the block-position visibility model deliberately does
1692
+ # not read — could otherwise launder the receipts below it out
1693
+ # of the rendered card. Banning the markup outright is
1694
+ # deterministic and costs no legitimate content; fenced lines
1695
+ # render the markup literally and stay exempt.
1696
+ raise ConformanceError(
1697
+ f"[RECEIPT-GRAMMAR: {report.path}: HTML comment markup "
1698
+ f"{line.strip()!r} is not allowed in the receipt section]"
1699
+ )
1700
+ if panel._H3_RE.fullmatch(line):
1701
+ if hidden:
1702
+ raise ConformanceError(
1703
+ f"[RECEIPT-GRAMMAR: {report.path}: receipt heading "
1704
+ f"{line.strip()!r} is inside an HTML comment; the "
1705
+ "rendered card does not show it]"
1706
+ )
1707
+ in_subsection = True
1708
+ continue
1709
+ canonical = bool(
1710
+ _RECEIPT_ATTESTATION_RE.fullmatch(line)
1711
+ or any(
1712
+ pattern.fullmatch(line)
1713
+ for pattern in _RECEIPT_FIELD_RES.values()
1714
+ )
1715
+ )
1716
+ if canonical:
1717
+ if hidden:
1718
+ raise ConformanceError(
1719
+ f"[RECEIPT-GRAMMAR: {report.path}: receipt machine "
1720
+ f"line {line.strip()!r} is inside an HTML comment; the "
1721
+ "rendered card does not show it]"
1722
+ )
1723
+ if (
1724
+ not in_subsection
1725
+ and not _RECEIPT_ATTESTATION_RE.fullmatch(line)
1726
+ ):
1727
+ raise ConformanceError(
1728
+ f"[RECEIPT-GRAMMAR: {report.path}: receipt machine "
1729
+ f"line {line.strip()!r} sits outside every ### AR<n> "
1730
+ "subsection; the enum and linkage gates never inspect "
1731
+ "it there]"
1732
+ )
1733
+ continue
1734
+ if any(
1735
+ _is_receipt_field_shaped(candidate)
1736
+ for candidate in _receipt_shape_candidates(line)
1737
+ ):
1738
+ # One neutral message for both visibility states: an opener
1739
+ # line like `<!-- c --> key: value` is hidden to the model but
1740
+ # partially renders, so claiming "commented-out" would misread
1741
+ # the page to the seat (#610 round-3 P3).
1742
+ raise ConformanceError(
1743
+ f"[RECEIPT-GRAMMAR: {report.path}: receipt machine line "
1744
+ f"{line.strip()!r} is decorated or non-canonical; write "
1745
+ "plain unbulleted key: value text (tolerated: one "
1746
+ "leading list marker, balanced bold around the key)]"
1747
+ )
1748
+ body = [line for line, _, _ in view]
1749
+ attestations = [
1750
+ line for line in body if _RECEIPT_ATTESTATION_RE.fullmatch(line)
1751
+ ]
1752
+ subsections, sub_dupes = panel.split_subsections(body)
1753
+ if sub_dupes:
1754
+ raise ConformanceError(
1755
+ f"[RECEIPT-GRAMMAR: {report.path}: duplicate receipt heading(s) "
1756
+ f"{sorted(sub_dupes)}]"
1757
+ )
1758
+ for title in subsections:
1759
+ if not _RECEIPT_H3_RE.fullmatch(title):
1760
+ raise ConformanceError(
1761
+ f"[RECEIPT-GRAMMAR: {report.path}: invalid receipt "
1762
+ f"subsection '### {title}'; expected ### AR<n>]"
1763
+ )
1764
+ if not subsections:
1765
+ if len(attestations) != 1:
1766
+ raise ConformanceError(
1767
+ f"[RECEIPT-GRAMMAR: {report.path}: a receipt section with "
1768
+ "no ### AR<n> subsection requires exactly one "
1769
+ f"no_recomputable_statistics: line, found {len(attestations)}]"
1770
+ )
1771
+ # Honest-claim boundary (#610 round-1 fix 2): the gate proved only
1772
+ # that the declaration EXISTS. Whether the manuscript really has
1773
+ # nothing recomputable is judged at adjudication, so the pass is
1774
+ # annotated rather than silent.
1775
+ print(_RECEIPT_ATTESTATION_ADVISORY)
1776
+ return
1777
+ if attestations:
1778
+ raise ConformanceError(
1779
+ f"[RECEIPT-GRAMMAR: {report.path}: no_recomputable_statistics: "
1780
+ "is forbidden when ### AR<n> receipts exist]"
1781
+ )
1782
+ expected_ids = [f"AR{index}" for index in range(1, len(subsections) + 1)]
1783
+ if list(subsections) != expected_ids:
1784
+ raise ConformanceError(
1785
+ f"[RECEIPT-GRAMMAR: {report.path}: receipt IDs must be dense "
1786
+ f"AR1..ARn in order; got={list(subsections)}]"
1787
+ )
1788
+ finding_ids, backrefs = _review_body_receipt_backrefs(report)
1789
+ mismatch_refs: dict[str, str] = {}
1790
+ for receipt_id, sublines in subsections.items():
1791
+ procedure = _one_receipt_field(
1792
+ sublines, "procedure_id", report.path, receipt_id, required=True
1793
+ )
1794
+ if procedure not in _RECEIPT_PROCEDURES:
1795
+ raise ConformanceError(
1796
+ f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} "
1797
+ f"procedure_id '{procedure}' is not a bounded procedure]"
1798
+ )
1799
+ anchor = _one_receipt_field(
1800
+ sublines, "evidence_anchor", report.path, receipt_id,
1801
+ required=True,
1802
+ )
1803
+ _validate_anchor(anchor, f"{report.path}:{receipt_id}")
1804
+ for key in (
1805
+ "reported_inputs", "assumptions", "derivation",
1806
+ "comparison_rule",
1807
+ ):
1808
+ _one_receipt_field(
1809
+ sublines, key, report.path, receipt_id, required=True
1810
+ )
1811
+ derived = _one_receipt_field(
1812
+ sublines, "derived_value_or_range", report.path, receipt_id,
1813
+ required=True,
1814
+ )
1815
+ status = _one_receipt_field(
1816
+ sublines, "status", report.path, receipt_id, required=True
1817
+ )
1818
+ if status not in _RECEIPT_STATUSES:
1819
+ raise ConformanceError(
1820
+ f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} status "
1821
+ f"'{status}' is not in the closed status enum]"
1822
+ )
1823
+ reason = _one_receipt_field(
1824
+ sublines, "not_computable_reason", report.path, receipt_id,
1825
+ required=(status == "not_computable"),
1826
+ )
1827
+ if status != "not_computable" and reason is not None:
1828
+ raise ConformanceError(
1829
+ f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} "
1830
+ "not_computable_reason is forbidden unless status is "
1831
+ "not_computable]"
1832
+ )
1833
+ if reason is not None and reason not in _NOT_COMPUTABLE_REASONS:
1834
+ raise ConformanceError(
1835
+ f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} "
1836
+ f"not_computable_reason '{reason}' is not in the closed v1 "
1837
+ "enum]"
1838
+ )
1839
+ tail = None
1840
+ for key, procedures in _PROCEDURE_FIELD_OWNERS.items():
1841
+ if procedure in procedures:
1842
+ required = (
1843
+ key in _ALWAYS_REQUIRED_PROCEDURE_FIELDS
1844
+ or status in _RECEIPT_VERDICT_STATUSES
1845
+ )
1846
+ value = _one_receipt_field(
1847
+ sublines, key, report.path, receipt_id, required=required
1848
+ )
1849
+ if key == "tail_convention":
1850
+ tail = value
1851
+ else:
1852
+ if _one_receipt_field(
1853
+ sublines, key, report.path, receipt_id, required=False
1854
+ ) is not None:
1855
+ raise ConformanceError(
1856
+ f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} "
1857
+ f"{key} is forbidden for procedure {procedure}]"
1858
+ )
1859
+ if procedure == "p_from_test_statistic":
1860
+ if tail is not None and tail not in _TAIL_CONVENTIONS:
1861
+ raise ConformanceError(
1862
+ f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} "
1863
+ f"tail_convention '{tail}' is not in the closed enum]"
1864
+ )
1865
+ if tail == "unstated" and status in _RECEIPT_VERDICT_STATUSES:
1866
+ derived_fold = unicodedata.normalize(
1867
+ "NFKC", derived
1868
+ ).casefold()
1869
+ if not all(
1870
+ _tail_value_shown(derived_fold, label_re)
1871
+ for label_re in _TAIL_LABEL_RES.values()
1872
+ ):
1873
+ raise ConformanceError(
1874
+ f"[RECEIPT-TAILS: {report.path}: {receipt_id} "
1875
+ "unstated tail requires derived_value_or_range to "
1876
+ "show BOTH labeled VALUES — each of the two-tailed "
1877
+ "and one-tailed labels sharing its own ;-segment "
1878
+ "with its derived number, either order]"
1879
+ )
1880
+ finding_ref = _one_receipt_field(
1881
+ sublines, "finding_ref", report.path, receipt_id,
1882
+ required=(status == "mismatch"),
1883
+ )
1884
+ if status != "mismatch":
1885
+ if finding_ref is not None:
1886
+ raise ConformanceError(
1887
+ f"[RECEIPT-LINKAGE: {report.path}: {receipt_id} "
1888
+ "finding_ref is forbidden unless status is mismatch]"
1889
+ )
1890
+ continue
1891
+ if not _FINDING_REF_VALUE_RE.fullmatch(finding_ref):
1892
+ raise ConformanceError(
1893
+ f"[RECEIPT-LINKAGE: {report.path}: {receipt_id} finding_ref "
1894
+ f"'{finding_ref}' must name one W<n> weakness]"
1895
+ )
1896
+ if finding_ref not in finding_ids:
1897
+ raise ConformanceError(
1898
+ f"[RECEIPT-LINKAGE: {report.path}: {receipt_id} finding_ref "
1899
+ f"{finding_ref} has no matching ### {finding_ref} weakness "
1900
+ "in ## Review Body]"
1901
+ )
1902
+ if finding_ref in mismatch_refs:
1903
+ raise ConformanceError(
1904
+ f"[RECEIPT-LINKAGE: {report.path}: {finding_ref} is claimed "
1905
+ f"by both {mismatch_refs[finding_ref]} and {receipt_id}; "
1906
+ "no two receipts share a finding_ref]"
1907
+ )
1908
+ mismatch_refs[finding_ref] = receipt_id
1909
+ for finding_id, receipt_id in mismatch_refs.items():
1910
+ if backrefs.get(finding_id) != [receipt_id]:
1911
+ raise ConformanceError(
1912
+ f"[RECEIPT-LINKAGE: {report.path}: weakness {finding_id} "
1913
+ "must carry exactly one **Arithmetic Receipt**: "
1914
+ f"{receipt_id} back-reference, found "
1915
+ f"{backrefs.get(finding_id, [])}]"
1916
+ )
1917
+ for location, values in backrefs.items():
1918
+ for value in values:
1919
+ if mismatch_refs.get(location) != value:
1920
+ raise ConformanceError(
1921
+ f"[RECEIPT-LINKAGE: {report.path}: back-reference "
1922
+ f"{value} in {location} does not correspond to a "
1923
+ "mismatch receipt naming that weakness]"
1924
+ )
1925
+
1926
+
1927
+ def check_recompute_extraction(path: str, text: str) -> None:
1928
+ """#610 step-5 extraction gate, methodology seat only.
1929
+
1930
+ The isolated numeric input surface: the response must be exactly one
1931
+ ``## Recompute Extraction`` section of machine lines. The grammar
1932
+ authority is the calculator's own parser (`recompute_receipts`), so the
1933
+ gate and the consumer can never disagree about what an extraction says;
1934
+ this gate adds the response-level structure rules and the anchor-grammar
1935
+ check the calculator deliberately does not own.
1936
+ """
1937
+ lines = panel.strip_fences(text)
1938
+ sections, dupes = panel.split_sections(lines)
1939
+ if recompute.EXTRACTION_SECTION in dupes:
1940
+ raise ConformanceError(
1941
+ f"[EXTRACTION-GRAMMAR: {path}: duplicate "
1942
+ f"## {recompute.EXTRACTION_SECTION}]"
1943
+ )
1944
+ if recompute.EXTRACTION_SECTION not in sections:
1945
+ raise ConformanceError(
1946
+ f"[EXTRACTION-GRAMMAR: {path}: exactly one "
1947
+ f"## {recompute.EXTRACTION_SECTION} section is required]"
1948
+ )
1949
+ extra = [
1950
+ title for title in list(sections) + list(dupes)
1951
+ if title != recompute.EXTRACTION_SECTION
1952
+ ]
1953
+ if extra:
1954
+ raise ConformanceError(
1955
+ f"[EXTRACTION-GRAMMAR: {path}: the response may carry no "
1956
+ f"section other than ## {recompute.EXTRACTION_SECTION}; "
1957
+ f"found {extra}]"
1958
+ )
1959
+ preamble = []
1960
+ for line in lines:
1961
+ if panel._H2_RE.fullmatch(line):
1962
+ break
1963
+ preamble.append(line)
1964
+ if any(line.strip() for line in preamble):
1965
+ raise ConformanceError(
1966
+ f"[EXTRACTION-GRAMMAR: {path}: no content is allowed before "
1967
+ f"## {recompute.EXTRACTION_SECTION}]"
1968
+ )
1969
+ try:
1970
+ extraction = recompute.parse_extraction(text)
1971
+ except recompute.ExtractionError as exc:
1972
+ raise ConformanceError(
1973
+ f"[EXTRACTION-GRAMMAR: {path}: {exc}]"
1974
+ ) from exc
1975
+ for request in extraction.requests:
1976
+ _validate_anchor(
1977
+ request["evidence_anchor"], f"{path}:{request.rr_id}"
1978
+ )
1979
+ if extraction.attestation is not None:
1980
+ # Same declaration-only honesty boundary as the receipt attestation.
1981
+ print(_RECEIPT_ATTESTATION_ADVISORY)
1982
+
1983
+
1984
+ def check_injected_receipts(
1985
+ report: panel.ReviewerReport, injected_text: str
1986
+ ) -> None:
1987
+ """#610 step-5 identity gate: the card's receipt section must be the
1988
+ dispatcher-injected receipts verbatim, plus only the canonical
1989
+ ``finding_ref:`` lines the mismatch receipts require.
1990
+
1991
+ Runs AFTER `check_methodology_receipts`, so grammar, enum, linkage, and
1992
+ per-subsection `finding_ref` placement are already proven; this gate
1993
+ proves nothing else changed. The comparison ignores blank lines and
1994
+ reads the card fence-transparently — the same view the receipt gate
1995
+ uses — so a decorated or re-spelled injected line fails identity loudly
1996
+ rather than being silently re-read.
1997
+ """
1998
+ injected_lines = [
1999
+ line.rstrip("\r") for line in injected_text.split("\n")
2000
+ ]
2001
+ heading = f"## {recompute.RECEIPT_SECTION}"
2002
+ if not injected_lines or injected_lines[0] != heading:
2003
+ raise panel.ContractError(
2004
+ f"[INJECTED-RECEIPTS-INVALID: the injected file must begin "
2005
+ f"with {heading!r}]"
2006
+ )
2007
+ expected = [line for line in injected_lines[1:] if line.strip()]
2008
+ view, _, _ = _receipt_section_view(report.text)
2009
+ # Only the PLAIN spelling is the permitted addition (codex round 1,
2010
+ # P2-4): the receipt grammar tolerates a decorated finding_ref, but
2011
+ # under injection "add exactly one finding_ref: line" means the
2012
+ # undecorated canonical form — a bolded or bulleted spelling stays in
2013
+ # the comparison and fails identity loudly.
2014
+ plain_finding_ref = re.compile(r"^finding_ref: W[1-9]\d*$")
2015
+ actual = [
2016
+ line for line, _, _ in view
2017
+ if line.strip() and not plain_finding_ref.fullmatch(line)
2018
+ ]
2019
+ if actual != expected:
2020
+ divergence = next(
2021
+ (
2022
+ f"card={card!r} vs injected={wanted!r}"
2023
+ for card, wanted in zip(actual, expected)
2024
+ if card != wanted
2025
+ ),
2026
+ f"card has {len(actual)} content lines, injected has "
2027
+ f"{len(expected)}",
2028
+ )
2029
+ raise ConformanceError(
2030
+ f"[RECEIPT-IDENTITY: {report.path}: the ## "
2031
+ f"{recompute.RECEIPT_SECTION} section must reproduce the "
2032
+ "dispatcher-computed receipts verbatim, adding only "
2033
+ f"finding_ref: lines on mismatch receipts; first divergence: "
2034
+ f"{divergence}]"
2035
+ )
2036
+
2037
+
2038
+ def check_receipt_section_forbidden(report: panel.ReviewerReport) -> None:
2039
+ # Declared boundary: this guard sees what `split_sections` sees, so a
2040
+ # fenced or indented `## Arithmetic Receipts` on a non-methodology seat
2041
+ # is an inert displayed block, not a section — it earns the seat
2042
+ # nothing (no consumer reads it) and is deliberately not chased here.
2043
+ lines = panel.strip_fences(report.text)
2044
+ sections, dupes = panel.split_sections(lines)
2045
+ if _RECEIPT_SECTION in sections or _RECEIPT_SECTION in dupes:
2046
+ raise ConformanceError(
2047
+ f"[RECEIPT-SECTION-FORBIDDEN: {report.path}: role "
2048
+ f"{report.role} may not emit ## {_RECEIPT_SECTION}; the #610 "
2049
+ "receipt gate is methodology-only]"
2050
+ )
2051
+
2052
+
2053
+ def _parse_args(argv):
2054
+ parser = argparse.ArgumentParser(description=__doc__)
2055
+ parser.add_argument("--contract", required=True, type=Path)
2056
+ parser.add_argument("--role", required=True)
2057
+ parser.add_argument("--phase1", required=True, type=Path)
2058
+ # The retry decision is taken while Phase 2 has not been requested yet, so
2059
+ # the gate has to be answerable on Phase 1 alone. Mirrors the
2060
+ # --synthesis / --layer1-only split in check_panel_synthesis.py.
2061
+ stage = parser.add_mutually_exclusive_group(required=True)
2062
+ stage.add_argument("--phase2", type=Path)
2063
+ stage.add_argument("--phase1-only", action="store_true")
2064
+ # #610 step 5: the methodology extraction call is its own gated stage,
2065
+ # answerable between Phase 1 and Phase 2 like --phase1-only is before
2066
+ # Phase 2.
2067
+ stage.add_argument("--extraction", type=Path)
2068
+ parser.add_argument("--injected-receipts", type=Path)
2069
+ parser.add_argument("--manuscript", required=True, type=Path)
2070
+ parser.add_argument("--metadata", required=True, type=Path)
2071
+ args = parser.parse_args(argv)
2072
+ if args.injected_receipts is not None and args.phase2 is None:
2073
+ parser.error("--injected-receipts requires --phase2")
2074
+ return args
2075
+
2076
+
2077
+ def main(argv=None) -> int:
2078
+ args = _parse_args(argv)
2079
+ try:
2080
+ contract, _ = panel.load_contract(args.contract)
2081
+ if args.role not in panel.ROLE_SETS[contract["mode"]]:
2082
+ raise panel.ContractError(
2083
+ f"[ROLE-BINDING: --role {args.role} is invalid for "
2084
+ f"{contract['mode']}]"
2085
+ )
2086
+ phase1_text = panel._read_text(args.phase1)
2087
+ phase2_text = (
2088
+ panel._read_text(args.phase2) if args.phase2 is not None
2089
+ else None
2090
+ )
2091
+ manuscript_text = panel._read_text(args.manuscript)
2092
+ try:
2093
+ metadata = json.loads(panel._read_text(args.metadata))
2094
+ except json.JSONDecodeError as exc:
2095
+ raise panel.ContractError(
2096
+ f"[METADATA-INVALID: {args.metadata}: {exc}]"
2097
+ ) from exc
2098
+ if args.phase1_only:
2099
+ # Blindness FIRST, before structural parsing: a response both
2100
+ # malformed and leaking would otherwise report only the grammar
2101
+ # failure, and the dispatcher would grant the retry a proven
2102
+ # leak must never receive. It is the half a retry must not be
2103
+ # granted in spite of.
2104
+ check_manuscript_leakage(
2105
+ phase1_text, manuscript_text, metadata, contract
2106
+ )
2107
+ plan = parse_phase1(str(args.phase1), phase1_text, contract, args.role)
2108
+ for warning in plan.warnings:
2109
+ print(warning)
2110
+ if args.phase1_only:
2111
+ print("PHASE1-CONFORMANCE: PASS")
2112
+ return EXIT_PASS
2113
+ if args.extraction is not None:
2114
+ # #610 step 5. The extraction call is methodology-only by
2115
+ # design: no other seat has an extraction stage to gate.
2116
+ if args.role != "methodology":
2117
+ raise panel.ContractError(
2118
+ f"[ROLE-BINDING: --extraction is methodology-only, "
2119
+ f"dispatched as {args.role}]"
2120
+ )
2121
+ check_manuscript_leakage(
2122
+ phase1_text, manuscript_text, metadata, contract
2123
+ )
2124
+ check_recompute_extraction(
2125
+ str(args.extraction), panel._read_text(args.extraction)
2126
+ )
2127
+ print("EXTRACTION-CONFORMANCE: PASS")
2128
+ return EXIT_PASS
2129
+ report = panel.parse_report(
2130
+ str(args.phase2), phase2_text, contract
2131
+ )
2132
+ if report.role != args.role:
2133
+ raise ConformanceError(
2134
+ f"[ROLE-BINDING: report declares {report.role}, dispatched "
2135
+ f"as {args.role}]"
2136
+ )
2137
+ check_manuscript_leakage(
2138
+ phase1_text, manuscript_text, metadata, contract
2139
+ )
2140
+ dissent = parse_dissent_dimensions(phase2_text)
2141
+ for diagnostic in dissent.diagnostics:
2142
+ print(diagnostic)
2143
+ dimensions = {
2144
+ dim["id"]: dim for dim in contract["acceptance_dimensions"]
2145
+ }
2146
+ check_trigger_binding(report, plan, dimensions, dissent.dimensions)
2147
+ if report.role == "da":
2148
+ check_da_anchors(report)
2149
+ else:
2150
+ check_scoring_seat_anchors(report)
2151
+ if report.role == "methodology":
2152
+ check_methodology_receipts(report)
2153
+ if args.injected_receipts is not None:
2154
+ check_injected_receipts(
2155
+ report, panel._read_text(args.injected_receipts)
2156
+ )
2157
+ # A distinct witness line (security round 1, P2-5): the
2158
+ # evidence contract names this gate as contract content, so
2159
+ # a Phase 2 gated WITHOUT it must be distinguishable in the
2160
+ # preserved gate log.
2161
+ print("RECEIPT-IDENTITY: PASS")
2162
+ else:
2163
+ if args.injected_receipts is not None:
2164
+ raise panel.ContractError(
2165
+ f"[ROLE-BINDING: --injected-receipts is "
2166
+ f"methodology-only, dispatched as {args.role}]"
2167
+ )
2168
+ check_receipt_section_forbidden(report)
2169
+ except panel.ContractError as exc:
2170
+ print(exc)
2171
+ return EXIT_CONTRACT
2172
+ except (panel.ReportError, ConformanceError) as exc:
2173
+ print(exc)
2174
+ return EXIT_CONFORMANCE
2175
+ print("PHASE-CONFORMANCE: PASS")
2176
+ return EXIT_PASS
2177
+
2178
+
2179
+ if __name__ == "__main__":
2180
+ sys.exit(main())