psyclaw 0.27.22 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1048) hide show
  1. package/README.md +11 -5
  2. package/dist/apps/panel/index.html +2 -2
  3. package/dist/src/adapters/pi/extension.js +225 -92
  4. package/dist/src/adapters/pi/extension.js.map +1 -1
  5. package/dist/src/agents/import.js +44 -32
  6. package/dist/src/agents/import.js.map +1 -1
  7. package/dist/src/analysis/hooks.d.ts +1 -0
  8. package/dist/src/analysis/hooks.js +20 -1
  9. package/dist/src/analysis/hooks.js.map +1 -1
  10. package/dist/src/ars/bridge.d.ts +20 -0
  11. package/dist/src/ars/bridge.js +132 -0
  12. package/dist/src/ars/bridge.js.map +1 -0
  13. package/dist/src/ars/contracts.d.ts +55 -0
  14. package/dist/src/ars/contracts.js +2 -0
  15. package/dist/src/ars/contracts.js.map +1 -0
  16. package/dist/src/ars/panel-plan.d.ts +4 -0
  17. package/dist/src/ars/panel-plan.js +11 -0
  18. package/dist/src/ars/panel-plan.js.map +1 -0
  19. package/dist/src/ars/pi-panel-executor.d.ts +30 -0
  20. package/dist/src/ars/pi-panel-executor.js +140 -0
  21. package/dist/src/ars/pi-panel-executor.js.map +1 -0
  22. package/dist/src/ars/profile.d.ts +36 -0
  23. package/dist/src/ars/profile.js +119 -0
  24. package/dist/src/ars/profile.js.map +1 -0
  25. package/dist/src/ars/re-review.d.ts +22 -0
  26. package/dist/src/ars/re-review.js +172 -0
  27. package/dist/src/ars/re-review.js.map +1 -0
  28. package/dist/src/branding.d.ts +2 -4
  29. package/dist/src/branding.js +3 -5
  30. package/dist/src/branding.js.map +1 -1
  31. package/dist/src/bundled-tools.d.ts +3 -0
  32. package/dist/src/bundled-tools.js +21 -0
  33. package/dist/src/bundled-tools.js.map +1 -0
  34. package/dist/src/chat.js +6 -5
  35. package/dist/src/chat.js.map +1 -1
  36. package/dist/src/creation/contracts.d.ts +38 -0
  37. package/dist/src/creation/contracts.js +2 -0
  38. package/dist/src/creation/contracts.js.map +1 -0
  39. package/dist/src/creation/service.d.ts +7 -0
  40. package/dist/src/creation/service.js +196 -0
  41. package/dist/src/creation/service.js.map +1 -0
  42. package/dist/src/index.d.ts +10 -0
  43. package/dist/src/index.js +10 -0
  44. package/dist/src/index.js.map +1 -1
  45. package/dist/src/install/installer.js +32 -10
  46. package/dist/src/install/installer.js.map +1 -1
  47. package/dist/src/orchestration/personas.d.ts +15 -0
  48. package/dist/src/orchestration/personas.js +51 -0
  49. package/dist/src/orchestration/personas.js.map +1 -0
  50. package/dist/src/orchestration/pi-executor.d.ts +1 -0
  51. package/dist/src/orchestration/pi-executor.js +1 -1
  52. package/dist/src/orchestration/pi-executor.js.map +1 -1
  53. package/dist/src/panel/extension.js +29 -8
  54. package/dist/src/panel/extension.js.map +1 -1
  55. package/dist/src/panel/server.js +14 -5
  56. package/dist/src/panel/server.js.map +1 -1
  57. package/dist/src/project/paths.d.ts +3 -0
  58. package/dist/src/project/paths.js +7 -0
  59. package/dist/src/project/paths.js.map +1 -1
  60. package/dist/src/rules/user-rules.d.ts +8 -0
  61. package/dist/src/rules/user-rules.js +36 -0
  62. package/dist/src/rules/user-rules.js.map +1 -0
  63. package/dist/src/skills/contracts.d.ts +4 -4
  64. package/dist/src/skills/recommended.js +1 -1
  65. package/dist/src/skills/registry.js +49 -28
  66. package/dist/src/skills/registry.js.map +1 -1
  67. package/dist/src/style/cli-ui.js +0 -1
  68. package/dist/src/style/cli-ui.js.map +1 -1
  69. package/package.json +14 -3
  70. package/scripts/rebrand-pi.mjs +6 -0
  71. package/skills/recommended/catalog.json +2 -11
  72. package/vendor/ars/.claude/CLAUDE.md +371 -0
  73. package/vendor/ars/.command-invariants.toml +24 -0
  74. package/vendor/ars/CITATION.cff +35 -0
  75. package/vendor/ars/LICENSE +417 -0
  76. package/vendor/ars/MODE_REGISTRY.md +76 -0
  77. package/vendor/ars/NOTICE.md +26 -0
  78. package/vendor/ars/POSITIONING.md +99 -0
  79. package/vendor/ars/PSYCLAW_SOURCE.json +10 -0
  80. package/vendor/ars/README.md +751 -0
  81. package/vendor/ars/SECURITY.md +52 -0
  82. package/vendor/ars/THIRD_PARTY.md +70 -0
  83. package/vendor/ars/academic-paper/SKILL.md +542 -0
  84. package/vendor/ars/academic-paper/agents/abstract_bilingual_agent.md +171 -0
  85. package/vendor/ars/academic-paper/agents/argument_builder_agent.md +276 -0
  86. package/vendor/ars/academic-paper/agents/citation_compliance_agent.md +422 -0
  87. package/vendor/ars/academic-paper/agents/draft_writer_agent.md +656 -0
  88. package/vendor/ars/academic-paper/agents/formatter_agent.md +999 -0
  89. package/vendor/ars/academic-paper/agents/intake_agent.md +393 -0
  90. package/vendor/ars/academic-paper/agents/literature_strategist_agent.md +626 -0
  91. package/vendor/ars/academic-paper/agents/peer_reviewer_agent.md +516 -0
  92. package/vendor/ars/academic-paper/agents/revision_coach_agent.md +334 -0
  93. package/vendor/ars/academic-paper/agents/socratic_mentor_agent.md +527 -0
  94. package/vendor/ars/academic-paper/agents/structure_architect_agent.md +401 -0
  95. package/vendor/ars/academic-paper/agents/visualization_agent.md +441 -0
  96. package/vendor/ars/academic-paper/examples/chinese_paper_example.md +278 -0
  97. package/vendor/ars/academic-paper/examples/clinical_citation_verification_checklist.md +95 -0
  98. package/vendor/ars/academic-paper/examples/clinical_epistemic_status_example.md +100 -0
  99. package/vendor/ars/academic-paper/examples/commitment_ledger_example.md +147 -0
  100. package/vendor/ars/academic-paper/examples/imrad_hei_example.md +234 -0
  101. package/vendor/ars/academic-paper/examples/literature_review_example.md +260 -0
  102. package/vendor/ars/academic-paper/examples/plan_mode_guided_writing.md +600 -0
  103. package/vendor/ars/academic-paper/examples/revision_mode_example.md +344 -0
  104. package/vendor/ars/academic-paper/examples/revision_recovery_example.md +506 -0
  105. package/vendor/ars/academic-paper/examples/version_family_reconciliation_example.md +89 -0
  106. package/vendor/ars/academic-paper/references/abstract_writing_guide.md +169 -0
  107. package/vendor/ars/academic-paper/references/academic_writing_style.md +188 -0
  108. package/vendor/ars/academic-paper/references/anti_leakage_protocol.md +83 -0
  109. package/vendor/ars/academic-paper/references/apa7_chinese_citation_guide.md +364 -0
  110. package/vendor/ars/academic-paper/references/apa7_extended_guide.md +198 -0
  111. package/vendor/ars/academic-paper/references/changelog.md +11 -0
  112. package/vendor/ars/academic-paper/references/citation_format_switcher.md +228 -0
  113. package/vendor/ars/academic-paper/references/committee_correspondence_protocol.md +158 -0
  114. package/vendor/ars/academic-paper/references/credit_authorship_guide.md +308 -0
  115. package/vendor/ars/academic-paper/references/disclosure_mode_protocol.md +478 -0
  116. package/vendor/ars/academic-paper/references/domain_evidence_profiles.md +38 -0
  117. package/vendor/ars/academic-paper/references/failure_paths.md +349 -0
  118. package/vendor/ars/academic-paper/references/funding_statement_guide.md +319 -0
  119. package/vendor/ars/academic-paper/references/hei_domain_glossary.md +169 -0
  120. package/vendor/ars/academic-paper/references/intro_title_rhetoric_guide.md +114 -0
  121. package/vendor/ars/academic-paper/references/journal_submission_guide.md +249 -0
  122. package/vendor/ars/academic-paper/references/latex_template_reference.md +378 -0
  123. package/vendor/ars/academic-paper/references/mode_selection_guide.md +378 -0
  124. package/vendor/ars/academic-paper/references/paper_structure_patterns.md +330 -0
  125. package/vendor/ars/academic-paper/references/plan_mode_protocol.md +112 -0
  126. package/vendor/ars/academic-paper/references/policy_anchor_disclosure_protocol.md +200 -0
  127. package/vendor/ars/academic-paper/references/policy_anchor_table.md +157 -0
  128. package/vendor/ars/academic-paper/references/revision_patch_protocol.md +173 -0
  129. package/vendor/ars/academic-paper/references/statistical_visualization_standards.md +750 -0
  130. package/vendor/ars/academic-paper/references/venue_disclosure_policies.md +259 -0
  131. package/vendor/ars/academic-paper/references/vlm_figure_verification.md +126 -0
  132. package/vendor/ars/academic-paper/references/workflow_phase_details.md +135 -0
  133. package/vendor/ars/academic-paper/references/writing_judgment_framework.md +59 -0
  134. package/vendor/ars/academic-paper/references/writing_quality_check.md +173 -0
  135. package/vendor/ars/academic-paper/templates/bilingual_abstract_template.md +78 -0
  136. package/vendor/ars/academic-paper/templates/case_study_template.md +129 -0
  137. package/vendor/ars/academic-paper/templates/conference_paper_template.md +108 -0
  138. package/vendor/ars/academic-paper/templates/credit_statement_template.md +132 -0
  139. package/vendor/ars/academic-paper/templates/funding_statement_template.md +290 -0
  140. package/vendor/ars/academic-paper/templates/imrad_template.md +183 -0
  141. package/vendor/ars/academic-paper/templates/latex_article_template.tex +199 -0
  142. package/vendor/ars/academic-paper/templates/literature_review_template.md +135 -0
  143. package/vendor/ars/academic-paper/templates/policy_brief_template.md +139 -0
  144. package/vendor/ars/academic-paper/templates/revision_tracking_template.md +199 -0
  145. package/vendor/ars/academic-paper/templates/theoretical_paper_template.md +119 -0
  146. package/vendor/ars/academic-paper-reviewer/SKILL.md +491 -0
  147. package/vendor/ars/academic-paper-reviewer/agents/devils_advocate_reviewer_agent.md +443 -0
  148. package/vendor/ars/academic-paper-reviewer/agents/domain_reviewer_agent.md +412 -0
  149. package/vendor/ars/academic-paper-reviewer/agents/editorial_synthesizer_agent.md +478 -0
  150. package/vendor/ars/academic-paper-reviewer/agents/eic_agent.md +339 -0
  151. package/vendor/ars/academic-paper-reviewer/agents/field_analyst_agent.md +221 -0
  152. package/vendor/ars/academic-paper-reviewer/agents/methodology_reviewer_agent.md +449 -0
  153. package/vendor/ars/academic-paper-reviewer/agents/perspective_reviewer_agent.md +427 -0
  154. package/vendor/ars/academic-paper-reviewer/examples/hei_paper_review_example.md +391 -0
  155. package/vendor/ars/academic-paper-reviewer/examples/interdisciplinary_review_example.md +299 -0
  156. package/vendor/ars/academic-paper-reviewer/examples/subclaim_decomposition_example.md +80 -0
  157. package/vendor/ars/academic-paper-reviewer/references/calibration_mode_protocol.md +256 -0
  158. package/vendor/ars/academic-paper-reviewer/references/changelog.md +10 -0
  159. package/vendor/ars/academic-paper-reviewer/references/editorial_decision_standards.md +236 -0
  160. package/vendor/ars/academic-paper-reviewer/references/guided_mode_protocol.md +34 -0
  161. package/vendor/ars/academic-paper-reviewer/references/integration_guide.md +15 -0
  162. package/vendor/ars/academic-paper-reviewer/references/quality_rubrics.md +84 -0
  163. package/vendor/ars/academic-paper-reviewer/references/re_review_mode_protocol.md +340 -0
  164. package/vendor/ars/academic-paper-reviewer/references/review_criteria_framework.md +98 -0
  165. package/vendor/ars/academic-paper-reviewer/references/review_panel_provenance_protocol.md +197 -0
  166. package/vendor/ars/academic-paper-reviewer/references/review_quality_thinking.md +58 -0
  167. package/vendor/ars/academic-paper-reviewer/references/reviewer_sprint_prompt_source.md +324 -0
  168. package/vendor/ars/academic-paper-reviewer/references/sprint_contract_protocol.md +296 -0
  169. package/vendor/ars/academic-paper-reviewer/references/statistical_reporting_standards.md +505 -0
  170. package/vendor/ars/academic-paper-reviewer/references/top_journals_by_field.md +206 -0
  171. package/vendor/ars/academic-paper-reviewer/templates/editorial_decision_template.md +235 -0
  172. package/vendor/ars/academic-paper-reviewer/templates/peer_review_report_template.md +305 -0
  173. package/vendor/ars/academic-paper-reviewer/templates/revision_response_template.md +248 -0
  174. package/vendor/ars/academic-pipeline/SKILL.md +736 -0
  175. package/vendor/ars/academic-pipeline/agents/claim_ref_alignment_audit_agent.md +382 -0
  176. package/vendor/ars/academic-pipeline/agents/collaboration_depth_agent.md +164 -0
  177. package/vendor/ars/academic-pipeline/agents/integrity_verification_agent.md +870 -0
  178. package/vendor/ars/academic-pipeline/agents/pipeline_orchestrator_agent.md +1379 -0
  179. package/vendor/ars/academic-pipeline/agents/state_tracker_agent.md +622 -0
  180. package/vendor/ars/academic-pipeline/examples/full_pipeline_example.md +482 -0
  181. package/vendor/ars/academic-pipeline/examples/integrity_failure_recovery.md +389 -0
  182. package/vendor/ars/academic-pipeline/examples/mid_entry_example.md +414 -0
  183. package/vendor/ars/academic-pipeline/references/adapters/.gitkeep +0 -0
  184. package/vendor/ars/academic-pipeline/references/adapters/overview.md +153 -0
  185. package/vendor/ars/academic-pipeline/references/ai_research_failure_modes.md +185 -0
  186. package/vendor/ars/academic-pipeline/references/changelog.md +14 -0
  187. package/vendor/ars/academic-pipeline/references/claim_audit_calibration_protocol.md +175 -0
  188. package/vendor/ars/academic-pipeline/references/claim_verification_protocol.md +282 -0
  189. package/vendor/ars/academic-pipeline/references/external_review_protocol.md +131 -0
  190. package/vendor/ars/academic-pipeline/references/integrity_review_protocol.md +110 -0
  191. package/vendor/ars/academic-pipeline/references/literature_corpus_consumers.md +193 -0
  192. package/vendor/ars/academic-pipeline/references/mode_advisor.md +135 -0
  193. package/vendor/ars/academic-pipeline/references/passport_as_reset_boundary.md +132 -0
  194. package/vendor/ars/academic-pipeline/references/pipeline_state_machine.md +405 -0
  195. package/vendor/ars/academic-pipeline/references/plagiarism_detection_protocol.md +239 -0
  196. package/vendor/ars/academic-pipeline/references/process_summary_protocol.md +209 -0
  197. package/vendor/ars/academic-pipeline/references/progress_dashboard_template.md +38 -0
  198. package/vendor/ars/academic-pipeline/references/reinforcement_content.md +15 -0
  199. package/vendor/ars/academic-pipeline/references/reproducibility_audit.md +55 -0
  200. package/vendor/ars/academic-pipeline/references/score_trajectory_protocol.md +78 -0
  201. package/vendor/ars/academic-pipeline/references/team_collaboration_protocol.md +261 -0
  202. package/vendor/ars/academic-pipeline/references/two_stage_review_protocol.md +27 -0
  203. package/vendor/ars/academic-pipeline/templates/pipeline_status_template.md +146 -0
  204. package/vendor/ars/agents/report_compiler_agent.md +341 -0
  205. package/vendor/ars/agents/research_architect_agent.md +298 -0
  206. package/vendor/ars/agents/synthesis_agent.md +356 -0
  207. package/vendor/ars/commands/ars-3w.md +10 -0
  208. package/vendor/ars/commands/ars-abstract.md +10 -0
  209. package/vendor/ars/commands/ars-cache-invalidate.md +20 -0
  210. package/vendor/ars/commands/ars-citation-check.md +10 -0
  211. package/vendor/ars/commands/ars-disclosure.md +10 -0
  212. package/vendor/ars/commands/ars-format-convert.md +10 -0
  213. package/vendor/ars/commands/ars-full.md +9 -0
  214. package/vendor/ars/commands/ars-lit-review.md +12 -0
  215. package/vendor/ars/commands/ars-mark-read.md +18 -0
  216. package/vendor/ars/commands/ars-outline.md +10 -0
  217. package/vendor/ars/commands/ars-plan.md +10 -0
  218. package/vendor/ars/commands/ars-rebuttal-audit.md +12 -0
  219. package/vendor/ars/commands/ars-reviewer.md +9 -0
  220. package/vendor/ars/commands/ars-revision-coach.md +9 -0
  221. package/vendor/ars/commands/ars-revision.md +10 -0
  222. package/vendor/ars/commands/ars-unmark-read.md +16 -0
  223. package/vendor/ars/deep-research/SKILL.md +600 -0
  224. package/vendor/ars/deep-research/agents/bibliography_agent.md +473 -0
  225. package/vendor/ars/deep-research/agents/devils_advocate_agent.md +192 -0
  226. package/vendor/ars/deep-research/agents/editor_in_chief_agent.md +167 -0
  227. package/vendor/ars/deep-research/agents/ethics_review_agent.md +267 -0
  228. package/vendor/ars/deep-research/agents/meta_analysis_agent.md +325 -0
  229. package/vendor/ars/deep-research/agents/monitoring_agent.md +209 -0
  230. package/vendor/ars/deep-research/agents/report_compiler_agent.md +341 -0
  231. package/vendor/ars/deep-research/agents/research_architect_agent.md +298 -0
  232. package/vendor/ars/deep-research/agents/research_question_agent.md +216 -0
  233. package/vendor/ars/deep-research/agents/risk_of_bias_agent.md +231 -0
  234. package/vendor/ars/deep-research/agents/socratic_mentor_agent.md +764 -0
  235. package/vendor/ars/deep-research/agents/source_verification_agent.md +219 -0
  236. package/vendor/ars/deep-research/agents/synthesis_agent.md +356 -0
  237. package/vendor/ars/deep-research/agents/timeline_extraction_agent.md +99 -0
  238. package/vendor/ars/deep-research/examples/exploratory_research.md +157 -0
  239. package/vendor/ars/deep-research/examples/fact_check_mode.md +173 -0
  240. package/vendor/ars/deep-research/examples/handoff_to_paper.md +318 -0
  241. package/vendor/ars/deep-research/examples/idea_diversity_coverage_gap_advisory.md +64 -0
  242. package/vendor/ars/deep-research/examples/policy_analysis.md +161 -0
  243. package/vendor/ars/deep-research/examples/review_mode.md +253 -0
  244. package/vendor/ars/deep-research/examples/socratic_guided_research.md +331 -0
  245. package/vendor/ars/deep-research/examples/systematic_review.md +133 -0
  246. package/vendor/ars/deep-research/references/apa7_style_guide.md +162 -0
  247. package/vendor/ars/deep-research/references/argumentation_reasoning_framework.md +68 -0
  248. package/vendor/ars/deep-research/references/arxiv_api_protocol.md +76 -0
  249. package/vendor/ars/deep-research/references/changelog.md +22 -0
  250. package/vendor/ars/deep-research/references/chinese_literature_api_protocol.md +317 -0
  251. package/vendor/ars/deep-research/references/cross_agent_quality_definitions.md +14 -0
  252. package/vendor/ars/deep-research/references/crossref_api_protocol.md +84 -0
  253. package/vendor/ars/deep-research/references/equator_reporting_guidelines.md +482 -0
  254. package/vendor/ars/deep-research/references/ethics_checklist.md +282 -0
  255. package/vendor/ars/deep-research/references/failure_paths.md +355 -0
  256. package/vendor/ars/deep-research/references/interdisciplinary_bridges.md +292 -0
  257. package/vendor/ars/deep-research/references/irb_decision_tree.md +315 -0
  258. package/vendor/ars/deep-research/references/literature_monitoring_strategies.md +263 -0
  259. package/vendor/ars/deep-research/references/logical_fallacies.md +192 -0
  260. package/vendor/ars/deep-research/references/methodology_patterns.md +462 -0
  261. package/vendor/ars/deep-research/references/mode_selection_guide.md +331 -0
  262. package/vendor/ars/deep-research/references/openalex_api_protocol.md +82 -0
  263. package/vendor/ars/deep-research/references/preregistration_guide.md +324 -0
  264. package/vendor/ars/deep-research/references/semantic_scholar_api_protocol.md +107 -0
  265. package/vendor/ars/deep-research/references/socratic_mode_protocol.md +99 -0
  266. package/vendor/ars/deep-research/references/socratic_questioning_framework.md +232 -0
  267. package/vendor/ars/deep-research/references/source_quality_hierarchy.md +188 -0
  268. package/vendor/ars/deep-research/references/systematic_review_protocol.md +95 -0
  269. package/vendor/ars/deep-research/references/systematic_review_toolkit.md +353 -0
  270. package/vendor/ars/deep-research/templates/evidence_assessment_template.md +127 -0
  271. package/vendor/ars/deep-research/templates/literature_matrix_template.md +85 -0
  272. package/vendor/ars/deep-research/templates/preregistration_template.md +318 -0
  273. package/vendor/ars/deep-research/templates/prisma_protocol_template.md +248 -0
  274. package/vendor/ars/deep-research/templates/prisma_report_template.md +415 -0
  275. package/vendor/ars/deep-research/templates/research_brief_template.md +93 -0
  276. package/vendor/ars/package.json +24 -0
  277. package/vendor/ars/pi/README.md +161 -0
  278. package/vendor/ars/pi/package.json +26 -0
  279. package/vendor/ars/pi/wrapper.js +193 -0
  280. package/vendor/ars/pi/wrapper.test.mjs +201 -0
  281. package/vendor/ars/pyproject.toml +2 -0
  282. package/vendor/ars/requirements-pdf-content-classifier.txt +5 -0
  283. package/vendor/ars/scripts/_block_parser.py +396 -0
  284. package/vendor/ars/scripts/_ci_pytest_manifest.toml +661 -0
  285. package/vendor/ars/scripts/_claim_audit_constants.py +268 -0
  286. package/vendor/ars/scripts/_e4_evidence.py +110 -0
  287. package/vendor/ars/scripts/_eval_threshold_gate.py +73 -0
  288. package/vendor/ars/scripts/_markdown_lint_util.py +224 -0
  289. package/vendor/ars/scripts/_next_verified_at_ms.py +176 -0
  290. package/vendor/ars/scripts/_passport_yaml.py +53 -0
  291. package/vendor/ars/scripts/_skill_lint.py +254 -0
  292. package/vendor/ars/scripts/_text_similarity.py +141 -0
  293. package/vendor/ars/scripts/adapters/README.md +89 -0
  294. package/vendor/ars/scripts/adapters/_common.py +209 -0
  295. package/vendor/ars/scripts/adapters/examples/folder_scan/expected_passport.yaml +25 -0
  296. package/vendor/ars/scripts/adapters/examples/folder_scan/expected_rejection_log.yaml +18 -0
  297. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Chen2024_AIAssessment.pdf +0 -0
  298. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Wang_2023_formative_feedback.pdf +0 -0
  299. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/paper1.pdf +0 -0
  300. package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture//344/270/255/346/226/207/346/252/224/345/220/215_2024.pdf +0 -0
  301. package/vendor/ars/scripts/adapters/examples/obsidian/expected_passport.yaml +40 -0
  302. package/vendor/ars/scripts/adapters/examples/obsidian/expected_rejection_log.yaml +13 -0
  303. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.gitkeep +0 -0
  304. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.obsidian/app.json +1 -0
  305. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/_templates/tmpl.md +7 -0
  306. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/chen2024ai.md +13 -0
  307. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/invalid.md +1 -0
  308. package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/wang2023formative.md +10 -0
  309. package/vendor/ars/scripts/adapters/examples/zotero/expected_passport.yaml +34 -0
  310. package/vendor/ars/scripts/adapters/examples/zotero/expected_rejection_log.yaml +34 -0
  311. package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/.gitkeep +0 -0
  312. package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/export.json +43 -0
  313. package/vendor/ars/scripts/adapters/folder_scan.py +214 -0
  314. package/vendor/ars/scripts/adapters/obsidian.py +336 -0
  315. package/vendor/ars/scripts/adapters/tests/.gitkeep +0 -0
  316. package/vendor/ars/scripts/adapters/tests/conftest.py +77 -0
  317. package/vendor/ars/scripts/adapters/tests/test_check_corpus_consumer_protocol.py +632 -0
  318. package/vendor/ars/scripts/adapters/tests/test_check_literature_corpus_schema.py +631 -0
  319. package/vendor/ars/scripts/adapters/tests/test_common.py +365 -0
  320. package/vendor/ars/scripts/adapters/tests/test_conftest.py +96 -0
  321. package/vendor/ars/scripts/adapters/tests/test_folder_scan.py +260 -0
  322. package/vendor/ars/scripts/adapters/tests/test_literature_corpus_entry_schema.py +745 -0
  323. package/vendor/ars/scripts/adapters/tests/test_obsidian.py +357 -0
  324. package/vendor/ars/scripts/adapters/tests/test_rejection_log_schema.py +271 -0
  325. package/vendor/ars/scripts/adapters/tests/test_sync_adapter_docs.py +88 -0
  326. package/vendor/ars/scripts/adapters/tests/test_zotero.py +454 -0
  327. package/vendor/ars/scripts/adapters/zotero.py +318 -0
  328. package/vendor/ars/scripts/adjudication_activity.py +1592 -0
  329. package/vendor/ars/scripts/announce-ars-loaded.sh +144 -0
  330. package/vendor/ars/scripts/ars_anchorize_draft.py +170 -0
  331. package/vendor/ars/scripts/ars_apply_revision_patch.py +912 -0
  332. package/vendor/ars/scripts/ars_cache_invalidate.py +40 -0
  333. package/vendor/ars/scripts/ars_mark_read.py +521 -0
  334. package/vendor/ars/scripts/ars_phase_scope_manifest.json +33 -0
  335. package/vendor/ars/scripts/ars_update_check.sh +215 -0
  336. package/vendor/ars/scripts/ars_write_scope_guard.py +506 -0
  337. package/vendor/ars/scripts/arxiv_client.py +222 -0
  338. package/vendor/ars/scripts/audit_snapshot.py +572 -0
  339. package/vendor/ars/scripts/bibliographic_integrity_signals.py +800 -0
  340. package/vendor/ars/scripts/bootstrap_timeline_yaml.py +146 -0
  341. package/vendor/ars/scripts/build_claim_standing_candidate_ledger.py +1238 -0
  342. package/vendor/ars/scripts/build_claim_standing_query_plan.py +643 -0
  343. package/vendor/ars/scripts/build_content_coverage_advisory.py +1205 -0
  344. package/vendor/ars/scripts/build_cross_document_consistency_advisory.py +2332 -0
  345. package/vendor/ars/scripts/build_review_pathway_rule_trace.py +830 -0
  346. package/vendor/ars/scripts/build_submission_packet_manifest.py +2310 -0
  347. package/vendor/ars/scripts/check_215_field_norm.py +173 -0
  348. package/vendor/ars/scripts/check_216_surface_form.py +250 -0
  349. package/vendor/ars/scripts/check_268_nested_commitment_ledger.py +180 -0
  350. package/vendor/ars/scripts/check_390_revision_patch_discipline.py +296 -0
  351. package/vendor/ars/scripts/check_392_citation_verification_intake.py +126 -0
  352. package/vendor/ars/scripts/check_394_submission_policy.py +178 -0
  353. package/vendor/ars/scripts/check_439_format_profile.py +307 -0
  354. package/vendor/ars/scripts/check_619_disclosure_closeout.py +237 -0
  355. package/vendor/ars/scripts/check_630_codex_subscription_transport.py +456 -0
  356. package/vendor/ars/scripts/check_669_review_pathway_rule_trace.py +662 -0
  357. package/vendor/ars/scripts/check_670_revision_roadmap_integration.py +518 -0
  358. package/vendor/ars/scripts/check_673_adjudication_activity.py +684 -0
  359. package/vendor/ars/scripts/check_684_review_criteria_binding.py +557 -0
  360. package/vendor/ars/scripts/check_agents_mirror_sync.py +115 -0
  361. package/vendor/ars/scripts/check_audit_artifact_consistency.py +2313 -0
  362. package/vendor/ars/scripts/check_benchmark_report.py +79 -0
  363. package/vendor/ars/scripts/check_bibliographic_integrity_signals.py +831 -0
  364. package/vendor/ars/scripts/check_calibration_tiers.py +235 -0
  365. package/vendor/ars/scripts/check_changelog_covers_merges.py +289 -0
  366. package/vendor/ars/scripts/check_ci_pytest_manifest.py +204 -0
  367. package/vendor/ars/scripts/check_claim_audit_consistency.py +1664 -0
  368. package/vendor/ars/scripts/check_claim_standing_candidate_ledger_integration.py +500 -0
  369. package/vendor/ars/scripts/check_claim_standing_freshness.py +253 -0
  370. package/vendor/ars/scripts/check_claim_standing_transmissions.py +449 -0
  371. package/vendor/ars/scripts/check_collaboration_depth_rubric.py +180 -0
  372. package/vendor/ars/scripts/check_command_frontmatter_name.py +116 -0
  373. package/vendor/ars/scripts/check_committee_correspondence.py +333 -0
  374. package/vendor/ars/scripts/check_compliance_report.py +108 -0
  375. package/vendor/ars/scripts/check_content_coverage_advisory_integration.py +796 -0
  376. package/vendor/ars/scripts/check_control_availability.py +172 -0
  377. package/vendor/ars/scripts/check_corpus_consumer_protocol.py +404 -0
  378. package/vendor/ars/scripts/check_cross_document_consistency_advisory_integration.py +1191 -0
  379. package/vendor/ars/scripts/check_cross_model_handoff_contract.py +234 -0
  380. package/vendor/ars/scripts/check_cross_model_verification_sync.py +261 -0
  381. package/vendor/ars/scripts/check_data_access_level.py +131 -0
  382. package/vendor/ars/scripts/check_data_flows.py +252 -0
  383. package/vendor/ars/scripts/check_decision_contract.py +464 -0
  384. package/vendor/ars/scripts/check_degradation_registry.py +326 -0
  385. package/vendor/ars/scripts/check_distribution_surface_claims.py +226 -0
  386. package/vendor/ars/scripts/check_domain_evidence_profile.py +538 -0
  387. package/vendor/ars/scripts/check_e4_promotion.py +195 -0
  388. package/vendor/ars/scripts/check_evals_gold_set.py +279 -0
  389. package/vendor/ars/scripts/check_evidence_row_integration.py +396 -0
  390. package/vendor/ars/scripts/check_experiment_provenance.py +117 -0
  391. package/vendor/ars/scripts/check_field_norm_severity.py +144 -0
  392. package/vendor/ars/scripts/check_firm_rules_sync.py +375 -0
  393. package/vendor/ars/scripts/check_heldout_measurement_report.py +1179 -0
  394. package/vendor/ars/scripts/check_human_subjects_output_contract.py +139 -0
  395. package/vendor/ars/scripts/check_human_subjects_reference_migration.py +844 -0
  396. package/vendor/ars/scripts/check_indirect_prompt_injection_no_call.py +328 -0
  397. package/vendor/ars/scripts/check_instruction_data_boundary.py +236 -0
  398. package/vendor/ars/scripts/check_judge_prompt_version.py +125 -0
  399. package/vendor/ars/scripts/check_literature_corpus_schema.py +402 -0
  400. package/vendor/ars/scripts/check_model_tiering.py +223 -0
  401. package/vendor/ars/scripts/check_panel_synthesis.py +1331 -0
  402. package/vendor/ars/scripts/check_passport_reset_contract.py +214 -0
  403. package/vendor/ars/scripts/check_pattern_eval_manifest.py +422 -0
  404. package/vendor/ars/scripts/check_persuasion_invariance_fixtures.py +637 -0
  405. package/vendor/ars/scripts/check_phase_conformance.py +2180 -0
  406. package/vendor/ars/scripts/check_pipeline_boundary_semantics.py +600 -0
  407. package/vendor/ars/scripts/check_pipeline_integrity.py +340 -0
  408. package/vendor/ars/scripts/check_policy_anchor_protocol.py +159 -0
  409. package/vendor/ars/scripts/check_policy_anchor_table.py +286 -0
  410. package/vendor/ars/scripts/check_preprint_venues_consistency.py +123 -0
  411. package/vendor/ars/scripts/check_prisma_trAIce_freshness.py +84 -0
  412. package/vendor/ars/scripts/check_promotion_bakeoff_preregistration.py +1303 -0
  413. package/vendor/ars/scripts/check_ranking_lift.py +323 -0
  414. package/vendor/ars/scripts/check_re_review_synthesis.py +2719 -0
  415. package/vendor/ars/scripts/check_receipt_enum_sync.py +203 -0
  416. package/vendor/ars/scripts/check_repro_lock.py +85 -0
  417. package/vendor/ars/scripts/check_review_pathway_output.py +276 -0
  418. package/vendor/ars/scripts/check_reviewer_data_fences.py +224 -0
  419. package/vendor/ars/scripts/check_reviewer_finding_contract.py +783 -0
  420. package/vendor/ars/scripts/check_reviewer_role_label.py +447 -0
  421. package/vendor/ars/scripts/check_reviewer_scoring_honesty.py +292 -0
  422. package/vendor/ars/scripts/check_reviewer_sprint_prompt_sync.py +409 -0
  423. package/vendor/ars/scripts/check_revision_claim_drift_suite_v2.py +1622 -0
  424. package/vendor/ars/scripts/check_revision_token_conservation.py +263 -0
  425. package/vendor/ars/scripts/check_risk_register.py +280 -0
  426. package/vendor/ars/scripts/check_role_scoped_contract.py +702 -0
  427. package/vendor/ars/scripts/check_rq_framing_patterns.py +184 -0
  428. package/vendor/ars/scripts/check_rubric_weight_consistency.py +32 -0
  429. package/vendor/ars/scripts/check_seeded_defect_fixtures.py +425 -0
  430. package/vendor/ars/scripts/check_setup_cross_model_parity.py +139 -0
  431. package/vendor/ars/scripts/check_spec_consistency.py +1249 -0
  432. package/vendor/ars/scripts/check_sprint_contract.py +371 -0
  433. package/vendor/ars/scripts/check_stage_capability_matrix.py +784 -0
  434. package/vendor/ars/scripts/check_submission_packet_manifest_integration.py +358 -0
  435. package/vendor/ars/scripts/check_surface_form_parity.py +445 -0
  436. package/vendor/ars/scripts/check_task_type.py +22 -0
  437. package/vendor/ars/scripts/check_tools_allowlist.py +538 -0
  438. package/vendor/ars/scripts/check_tortured_phrase_screening_integration.py +1974 -0
  439. package/vendor/ars/scripts/check_v3_10_134_write_scope.py +286 -0
  440. package/vendor/ars/scripts/check_v3_10_policy.py +656 -0
  441. package/vendor/ars/scripts/check_v3_6_6_ab_manifest.py +364 -0
  442. package/vendor/ars/scripts/check_v3_6_7_pattern_protection.py +1366 -0
  443. package/vendor/ars/scripts/check_v3_6_8_audit_scope_block.py +464 -0
  444. package/vendor/ars/scripts/check_v3_6_8_cite_provenance_pipeline.py +231 -0
  445. package/vendor/ars/scripts/check_v3_6_8_frontmatter_trust_schema.py +248 -0
  446. package/vendor/ars/scripts/check_v3_6_8_mark_read_commands.py +79 -0
  447. package/vendor/ars/scripts/check_v3_6_8_pattern_protection.py +941 -0
  448. package/vendor/ars/scripts/check_v3_7_3_three_layer_citation.py +318 -0
  449. package/vendor/ars/scripts/check_v3_8_annotation_literal_sync.py +228 -0
  450. package/vendor/ars/scripts/check_v3_9_0_triangulation.py +366 -0
  451. package/vendor/ars/scripts/check_v3_9_2_phase_boundary.py +270 -0
  452. package/vendor/ars/scripts/check_v3_9_4_temporal_verification.py +139 -0
  453. package/vendor/ars/scripts/check_venue_disclosure_policies.py +63 -0
  454. package/vendor/ars/scripts/check_version_consistency.py +826 -0
  455. package/vendor/ars/scripts/check_workflow_classification.py +223 -0
  456. package/vendor/ars/scripts/chinese_literature_client.py +1938 -0
  457. package/vendor/ars/scripts/citation_verification_summary.py +85 -0
  458. package/vendor/ars/scripts/claim_audit_calibration.py +517 -0
  459. package/vendor/ars/scripts/claim_audit_finalizer.py +456 -0
  460. package/vendor/ars/scripts/claim_audit_pipeline.py +1594 -0
  461. package/vendor/ars/scripts/claim_registry_coverage.py +493 -0
  462. package/vendor/ars/scripts/claim_standing_discovery.py +784 -0
  463. package/vendor/ars/scripts/claim_standing_stance_runner.py +758 -0
  464. package/vendor/ars/scripts/claim_standing_stance_scorer.py +239 -0
  465. package/vendor/ars/scripts/claim_strength_drift_disposition.py +666 -0
  466. package/vendor/ars/scripts/contamination_signals.py +689 -0
  467. package/vendor/ars/scripts/corpus_consumer_manifest.json +19 -0
  468. package/vendor/ars/scripts/cross_model_codex_transport.py +1374 -0
  469. package/vendor/ars/scripts/cross_model_codex_verify.sh +6 -0
  470. package/vendor/ars/scripts/cross_model_handoff.py +359 -0
  471. package/vendor/ars/scripts/cross_model_smoke_test.sh +183 -0
  472. package/vendor/ars/scripts/cross_model_smoke_test_codex.sh +35 -0
  473. package/vendor/ars/scripts/cross_model_verification/gemini_is_grounded.jq +45 -0
  474. package/vendor/ars/scripts/cross_model_verification/gemini_sources.jq +45 -0
  475. package/vendor/ars/scripts/cross_model_verification/normalize_compat_verdict.py +57 -0
  476. package/vendor/ars/scripts/cross_model_verification/openai_has_completed_web_search.jq +15 -0
  477. package/vendor/ars/scripts/cross_model_verification/openai_sources.jq +17 -0
  478. package/vendor/ars/scripts/cross_model_verification/openai_text.jq +11 -0
  479. package/vendor/ars/scripts/crossref_client.py +225 -0
  480. package/vendor/ars/scripts/dispatch_e4_panel.py +2731 -0
  481. package/vendor/ars/scripts/evidence_rows.py +2043 -0
  482. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3.json +32 -0
  483. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_input.json +27 -0
  484. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime.json +32 -0
  485. package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime_input.json +27 -0
  486. package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override.json +50 -0
  487. package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override_action.json +14 -0
  488. package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_pass.json +44 -0
  489. package/vendor/ars/scripts/fixtures/adjudication_activity/explicit_user_request_log.json +26 -0
  490. package/vendor/ars/scripts/fixtures/adjudication_activity/mandatory_checkpoint_log.json +116 -0
  491. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/README.md +56 -0
  492. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +24 -0
  493. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
  494. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
  495. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
  496. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a7_orphan_completion/2026-04-30T15-22-04Z-d8f3.jsonl +3 -0
  497. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +26 -0
  498. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
  499. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
  500. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
  501. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +24 -0
  502. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
  503. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
  504. package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.verdict.yaml +12 -0
  505. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction.json +46 -0
  506. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction_check_attestation.json +44 -0
  507. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase.json +44 -0
  508. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_abstract_missing.json +107 -0
  509. package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_detected.json +124 -0
  510. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/expected_outcomes.json +77 -0
  511. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/manifest.yaml +37 -0
  512. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/001-valid-doi-test.json +20 -0
  513. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/002-valid-arxiv-test.json +19 -0
  514. package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/003-fabricated-test.json +20 -0
  515. package/vendor/ars/scripts/fixtures/claim_audit_calibration/gold_set.json +344 -0
  516. package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/query_plan.json +108 -0
  517. package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/retrieval_input.json +179 -0
  518. package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/concern_tracker.json +129 -0
  519. package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/response_skeleton.md +19 -0
  520. package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/source_letter.txt +9 -0
  521. package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_draft.json +48 -0
  522. package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_inventory.json +38 -0
  523. package/vendor/ars/scripts/fixtures/content_coverage_advisory/packet/consent.txt +1 -0
  524. package/vendor/ars/scripts/fixtures/content_coverage_advisory/session_sources.json +3 -0
  525. package/vendor/ars/scripts/fixtures/cross_document_consistency/README.md +6 -0
  526. package/vendor/ars/scripts/fixtures/cross_document_consistency/accepted_draft.md +35 -0
  527. package/vendor/ars/scripts/fixtures/cross_document_consistency/cases.json +75 -0
  528. package/vendor/ars/scripts/fixtures/cross_document_consistency/preregistration.md +5 -0
  529. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/forbidden_event.jsonl +4 -0
  530. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/grounded_verified.jsonl +3 -0
  531. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/malformed.jsonl +3 -0
  532. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/missing_search.jsonl +2 -0
  533. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/multiple_finals.jsonl +4 -0
  534. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/not_found.jsonl +3 -0
  535. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/unbound_source.jsonl +3 -0
  536. package/vendor/ars/scripts/fixtures/cross_model_codex_transport/wrong_search_shape.jsonl +3 -0
  537. package/vendor/ars/scripts/fixtures/evidence_rows/phase_e_inputs.json +114 -0
  538. package/vendor/ars/scripts/fixtures/evidence_rows/session_sources.json +5 -0
  539. package/vendor/ars/scripts/fixtures/human_subjects_authority/cross-border-us-tw-gdpr.json +71 -0
  540. package/vendor/ars/scripts/fixtures/human_subjects_authority/gdpr-member-state-unresolved.json +62 -0
  541. package/vendor/ars/scripts/fixtures/human_subjects_authority/missing-data-axis.json +45 -0
  542. package/vendor/ars/scripts/fixtures/human_subjects_authority/no-profile.json +33 -0
  543. package/vendor/ars/scripts/fixtures/human_subjects_authority/tw-gdpr-two-axis.json +74 -0
  544. package/vendor/ars/scripts/fixtures/human_subjects_authority/us-gdpr-two-axis.json +74 -0
  545. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/lint-near-misses.json +72 -0
  546. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/no-profile-request.json +18 -0
  547. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/tw-candidates-request.json +63 -0
  548. package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/us-candidates-request.json +77 -0
  549. package/vendor/ars/scripts/fixtures/review_target_context/exact-declaration.json +24 -0
  550. package/vendor/ars/scripts/fixtures/review_target_context/field-general-declaration.json +24 -0
  551. package/vendor/ars/scripts/fixtures/review_target_context/msr-2027-technical-full-declaration.json +24 -0
  552. package/vendor/ars/scripts/fixtures/review_target_context/synthetic-registry.json +92 -0
  553. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/README.md +21 -0
  554. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_attested_only.json +55 -0
  555. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_machine_supported.json +49 -0
  556. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_not_isolated.json +56 -0
  557. package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_unknown.json +49 -0
  558. package/vendor/ars/scripts/fixtures/submission_package/clean/paper.md +16 -0
  559. package/vendor/ars/scripts/fixtures/submission_package/clean/references.bib +13 -0
  560. package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/paper.md +11 -0
  561. package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/references.bib +13 -0
  562. package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/paper.tex +9 -0
  563. package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/references.bib +13 -0
  564. package/vendor/ars/scripts/fixtures/submission_package/marker_no_join/paper.md +7 -0
  565. package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/paper.md +8 -0
  566. package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/references.bib +6 -0
  567. package/vendor/ars/scripts/fixtures/submission_package/passports/corpus_only.yaml +9 -0
  568. package/vendor/ars/scripts/fixtures/submission_package/passports/summary_join.yaml +16 -0
  569. package/vendor/ars/scripts/fixtures/submission_package/profiles/full.yaml +15 -0
  570. package/vendor/ars/scripts/fixtures/submission_package/profiles/tight.yaml +15 -0
  571. package/vendor/ars/scripts/fixtures/submission_package/summary_join/paper.md +7 -0
  572. package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/paper.md +9 -0
  573. package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/references.bib +13 -0
  574. package/vendor/ars/scripts/fixtures/submission_package/venue_clean/paper.md +26 -0
  575. package/vendor/ars/scripts/fixtures/submission_package/venue_clean/references.bib +13 -0
  576. package/vendor/ars/scripts/fixtures/submission_package/venue_violations/paper.md +20 -0
  577. package/vendor/ars/scripts/fixtures/submission_package/venue_violations/references.bib +13 -0
  578. package/vendor/ars/scripts/fixtures/submission_packet_manifest/base_inventory.json +101 -0
  579. package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/consent-materials.txt +3 -0
  580. package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/training-certificate.txt +2 -0
  581. package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/tw-consent-materials.txt +3 -0
  582. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/corpus_input.yaml +30 -0
  583. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.md +36 -0
  584. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.tex +21 -0
  585. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/seed_expectations.json +218 -0
  586. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot.json +164 -0
  587. package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot_manifest.json +29 -0
  588. package/vendor/ars/scripts/fixtures/transport_bodies/README.md +55 -0
  589. package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/empty_feed.xml +10 -0
  590. package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/error_5xx.html +7 -0
  591. package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/id_hit.xml +26 -0
  592. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/README.md +69 -0
  593. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/cnki_landing_page.html +13 -0
  594. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/error_5xx.html +2 -0
  595. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_ambiguous.json +16 -0
  596. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_hit.json +16 -0
  597. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_zero.json +14 -0
  598. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_hit.json +15 -0
  599. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_zero.json +13 -0
  600. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_hit.json +43 -0
  601. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_issn_mismatch.json +31 -0
  602. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_no_doi.json +31 -0
  603. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_unknown_ra.json +34 -0
  604. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_year_mismatch.json +31 -0
  605. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_absent.json +4 -0
  606. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_exists.json +16 -0
  607. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_internal_error.json +4 -0
  608. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_hit.json +25 -0
  609. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_other_title.json +18 -0
  610. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_cnki.json +6 -0
  611. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_crossref.json +6 -0
  612. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_istic.json +6 -0
  613. package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_unknown_prefix.json +6 -0
  614. package/vendor/ars/scripts/fixtures/transport_bodies/crossref/doi_hit.json +24 -0
  615. package/vendor/ars/scripts/fixtures/transport_bodies/crossref/error_5xx.html +7 -0
  616. package/vendor/ars/scripts/fixtures/transport_bodies/crossref/title_search_miss.json +15 -0
  617. package/vendor/ars/scripts/fixtures/transport_bodies/openalex/doi_hit.json +29 -0
  618. package/vendor/ars/scripts/fixtures/transport_bodies/openalex/error_5xx.json +4 -0
  619. package/vendor/ars/scripts/fixtures/transport_bodies/openalex/title_search_miss.json +11 -0
  620. package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/doi_hit.json +14 -0
  621. package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/error_5xx.json +3 -0
  622. package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/title_search_miss.json +5 -0
  623. package/vendor/ars/scripts/human_read_attestation_resolver.py +467 -0
  624. package/vendor/ars/scripts/ideation_diversity_assignment_gate.py +430 -0
  625. package/vendor/ars/scripts/inquiry_branch_ledger.py +2540 -0
  626. package/vendor/ars/scripts/legacy/ars_apply_revision_patch_v1_0.py +715 -0
  627. package/vendor/ars/scripts/legacy/check_re_review_synthesis_v1_0.py +2215 -0
  628. package/vendor/ars/scripts/migrate_literature_corpus_to_v3_10.py +223 -0
  629. package/vendor/ars/scripts/migrate_literature_corpus_to_v3_7_3.py +277 -0
  630. package/vendor/ars/scripts/migrate_literature_corpus_to_v3_9_0.py +304 -0
  631. package/vendor/ars/scripts/model_tiering_manifest.json +46 -0
  632. package/vendor/ars/scripts/openalex_client.py +232 -0
  633. package/vendor/ars/scripts/parse_audit_verdict.py +802 -0
  634. package/vendor/ars/scripts/pdf_content_classifier_worker.py +176 -0
  635. package/vendor/ars/scripts/pdf_read_preflight.py +1455 -0
  636. package/vendor/ars/scripts/policy_anchor_disclosure_referee.py +354 -0
  637. package/vendor/ars/scripts/recompute_receipts.py +1414 -0
  638. package/vendor/ars/scripts/render_claim_standing_view.py +374 -0
  639. package/vendor/ars/scripts/render_eval_comment.py +130 -0
  640. package/vendor/ars/scripts/render_harness_retirement_issue.py +167 -0
  641. package/vendor/ars/scripts/repro_lock_validation.py +90 -0
  642. package/vendor/ars/scripts/research_workflow_profile.py +1079 -0
  643. package/vendor/ars/scripts/resolve_human_subjects_authority.py +1158 -0
  644. package/vendor/ars/scripts/resolve_review_target_context.py +683 -0
  645. package/vendor/ars/scripts/resume_e4_record.py +509 -0
  646. package/vendor/ars/scripts/retraction_status.py +484 -0
  647. package/vendor/ars/scripts/review_criteria_binding.py +889 -0
  648. package/vendor/ars/scripts/review_panel_provenance.py +744 -0
  649. package/vendor/ars/scripts/revision_roadmap.py +1967 -0
  650. package/vendor/ars/scripts/run_ci_pytest_manifest.py +116 -0
  651. package/vendor/ars/scripts/run_codex_audit.sh +1191 -0
  652. package/vendor/ars/scripts/run_evals.py +513 -0
  653. package/vendor/ars/scripts/run_ideation_diversity_no_call.py +3505 -0
  654. package/vendor/ars/scripts/run_indirect_prompt_injection_no_call.py +3330 -0
  655. package/vendor/ars/scripts/run_indirect_prompt_injection_probe.py +399 -0
  656. package/vendor/ars/scripts/run_review_criteria_constructive_value.py +1895 -0
  657. package/vendor/ars/scripts/run_role_topology_utility_dry_run.py +606 -0
  658. package/vendor/ars/scripts/score_review_criteria_constructive_value.py +617 -0
  659. package/vendor/ars/scripts/semantic_scholar_client.py +291 -0
  660. package/vendor/ars/scripts/slr_lineage.py +59 -0
  661. package/vendor/ars/scripts/sync_adapter_docs.py +118 -0
  662. package/vendor/ars/scripts/temporal_integrity_audit.py +840 -0
  663. package/vendor/ars/scripts/test_431_exact_or_bust.py +253 -0
  664. package/vendor/ars/scripts/test__eval_threshold_gate.py +125 -0
  665. package/vendor/ars/scripts/test__markdown_lint_util.py +129 -0
  666. package/vendor/ars/scripts/test__next_verified_at_ms.py +256 -0
  667. package/vendor/ars/scripts/test_adjacent_framing_probe_lint.py +171 -0
  668. package/vendor/ars/scripts/test_adjudication_activity.py +1515 -0
  669. package/vendor/ars/scripts/test_ars_anchorize_draft.py +178 -0
  670. package/vendor/ars/scripts/test_ars_apply_revision_patch.py +1335 -0
  671. package/vendor/ars/scripts/test_ars_cache_invalidate.py +51 -0
  672. package/vendor/ars/scripts/test_ars_mark_read.py +910 -0
  673. package/vendor/ars/scripts/test_ars_update_check.py +816 -0
  674. package/vendor/ars/scripts/test_ars_write_scope_guard.py +790 -0
  675. package/vendor/ars/scripts/test_arxiv_client.py +374 -0
  676. package/vendor/ars/scripts/test_audit_schemas.py +560 -0
  677. package/vendor/ars/scripts/test_audit_snapshot_render_section_0.py +105 -0
  678. package/vendor/ars/scripts/test_block_parser.py +259 -0
  679. package/vendor/ars/scripts/test_bootstrap_timeline_yaml.py +148 -0
  680. package/vendor/ars/scripts/test_build_claim_standing_candidate_ledger.py +1203 -0
  681. package/vendor/ars/scripts/test_build_claim_standing_query_plan.py +791 -0
  682. package/vendor/ars/scripts/test_build_submission_packet_manifest.py +2644 -0
  683. package/vendor/ars/scripts/test_check_215_field_norm.py +238 -0
  684. package/vendor/ars/scripts/test_check_216_surface_form.py +341 -0
  685. package/vendor/ars/scripts/test_check_268_nested_commitment_ledger.py +194 -0
  686. package/vendor/ars/scripts/test_check_390_revision_patch_discipline.py +279 -0
  687. package/vendor/ars/scripts/test_check_392_citation_verification_intake.py +144 -0
  688. package/vendor/ars/scripts/test_check_394_submission_policy.py +200 -0
  689. package/vendor/ars/scripts/test_check_439_format_profile.py +252 -0
  690. package/vendor/ars/scripts/test_check_619_disclosure_closeout.py +158 -0
  691. package/vendor/ars/scripts/test_check_630_codex_subscription_transport.py +245 -0
  692. package/vendor/ars/scripts/test_check_669_review_pathway_rule_trace.py +304 -0
  693. package/vendor/ars/scripts/test_check_670_revision_roadmap_integration.py +260 -0
  694. package/vendor/ars/scripts/test_check_673_adjudication_activity.py +367 -0
  695. package/vendor/ars/scripts/test_check_684_review_criteria_binding.py +274 -0
  696. package/vendor/ars/scripts/test_check_agents_mirror_sync.py +137 -0
  697. package/vendor/ars/scripts/test_check_audit_artifact_consistency.py +2133 -0
  698. package/vendor/ars/scripts/test_check_benchmark_report.py +117 -0
  699. package/vendor/ars/scripts/test_check_bibliographic_integrity_signals.py +609 -0
  700. package/vendor/ars/scripts/test_check_calibration_tiers.py +376 -0
  701. package/vendor/ars/scripts/test_check_changelog_covers_merges.py +509 -0
  702. package/vendor/ars/scripts/test_check_ci_pytest_manifest.py +425 -0
  703. package/vendor/ars/scripts/test_check_claim_standing_candidate_ledger_integration.py +256 -0
  704. package/vendor/ars/scripts/test_check_claim_standing_freshness.py +277 -0
  705. package/vendor/ars/scripts/test_check_collaboration_depth_rubric.py +239 -0
  706. package/vendor/ars/scripts/test_check_command_frontmatter_name.py +178 -0
  707. package/vendor/ars/scripts/test_check_committee_correspondence.py +298 -0
  708. package/vendor/ars/scripts/test_check_compliance_report.py +381 -0
  709. package/vendor/ars/scripts/test_check_content_coverage_advisory_integration.py +308 -0
  710. package/vendor/ars/scripts/test_check_control_availability.py +361 -0
  711. package/vendor/ars/scripts/test_check_cross_document_consistency_advisory_integration.py +698 -0
  712. package/vendor/ars/scripts/test_check_cross_model_handoff_contract.py +301 -0
  713. package/vendor/ars/scripts/test_check_cross_model_verification_sync.py +203 -0
  714. package/vendor/ars/scripts/test_check_data_access_level.py +227 -0
  715. package/vendor/ars/scripts/test_check_data_flows.py +382 -0
  716. package/vendor/ars/scripts/test_check_decision_contract.py +387 -0
  717. package/vendor/ars/scripts/test_check_degradation_registry.py +270 -0
  718. package/vendor/ars/scripts/test_check_distribution_surface_claims.py +225 -0
  719. package/vendor/ars/scripts/test_check_domain_evidence_profile.py +439 -0
  720. package/vendor/ars/scripts/test_check_e4_promotion.py +163 -0
  721. package/vendor/ars/scripts/test_check_evals_gold_set.py +312 -0
  722. package/vendor/ars/scripts/test_check_evidence_row_integration.py +187 -0
  723. package/vendor/ars/scripts/test_check_field_norm_severity.py +173 -0
  724. package/vendor/ars/scripts/test_check_firm_rules_sync.py +342 -0
  725. package/vendor/ars/scripts/test_check_heldout_measurement_report.py +1508 -0
  726. package/vendor/ars/scripts/test_check_human_subjects_output_contract.py +129 -0
  727. package/vendor/ars/scripts/test_check_human_subjects_reference_migration.py +756 -0
  728. package/vendor/ars/scripts/test_check_instruction_data_boundary.py +204 -0
  729. package/vendor/ars/scripts/test_check_judge_prompt_version.py +90 -0
  730. package/vendor/ars/scripts/test_check_model_tiering.py +236 -0
  731. package/vendor/ars/scripts/test_check_panel_synthesis.py +1658 -0
  732. package/vendor/ars/scripts/test_check_passport_reset_contract.py +249 -0
  733. package/vendor/ars/scripts/test_check_pattern_eval_manifest.py +381 -0
  734. package/vendor/ars/scripts/test_check_persuasion_invariance_fixtures.py +591 -0
  735. package/vendor/ars/scripts/test_check_phase_conformance.py +4366 -0
  736. package/vendor/ars/scripts/test_check_pipeline_boundary_semantics.py +931 -0
  737. package/vendor/ars/scripts/test_check_pipeline_integrity.py +243 -0
  738. package/vendor/ars/scripts/test_check_policy_anchor_protocol.py +236 -0
  739. package/vendor/ars/scripts/test_check_policy_anchor_table.py +295 -0
  740. package/vendor/ars/scripts/test_check_prisma_trAIce_freshness.py +68 -0
  741. package/vendor/ars/scripts/test_check_promotion_bakeoff_preregistration.py +799 -0
  742. package/vendor/ars/scripts/test_check_ranking_lift.py +377 -0
  743. package/vendor/ars/scripts/test_check_re_review_synthesis.py +3398 -0
  744. package/vendor/ars/scripts/test_check_receipt_enum_sync.py +176 -0
  745. package/vendor/ars/scripts/test_check_repro_lock.py +107 -0
  746. package/vendor/ars/scripts/test_check_reviewer_data_fences.py +334 -0
  747. package/vendor/ars/scripts/test_check_reviewer_finding_contract.py +1107 -0
  748. package/vendor/ars/scripts/test_check_reviewer_role_label.py +491 -0
  749. package/vendor/ars/scripts/test_check_reviewer_scoring_honesty.py +222 -0
  750. package/vendor/ars/scripts/test_check_reviewer_sprint_prompt_sync.py +373 -0
  751. package/vendor/ars/scripts/test_check_revision_claim_drift_suite_v2.py +1925 -0
  752. package/vendor/ars/scripts/test_check_revision_token_conservation.py +398 -0
  753. package/vendor/ars/scripts/test_check_risk_register.py +336 -0
  754. package/vendor/ars/scripts/test_check_role_scoped_contract.py +1104 -0
  755. package/vendor/ars/scripts/test_check_rq_framing_patterns.py +110 -0
  756. package/vendor/ars/scripts/test_check_rubric_weight_consistency.py +32 -0
  757. package/vendor/ars/scripts/test_check_seeded_defect_fixtures.py +392 -0
  758. package/vendor/ars/scripts/test_check_setup_cross_model_parity.py +121 -0
  759. package/vendor/ars/scripts/test_check_spec_consistency.py +1081 -0
  760. package/vendor/ars/scripts/test_check_sprint_contract.py +458 -0
  761. package/vendor/ars/scripts/test_check_stage_capability_matrix.py +698 -0
  762. package/vendor/ars/scripts/test_check_submission_packet_manifest_integration.py +177 -0
  763. package/vendor/ars/scripts/test_check_surface_form_parity.py +417 -0
  764. package/vendor/ars/scripts/test_check_task_type.py +116 -0
  765. package/vendor/ars/scripts/test_check_tools_allowlist.py +835 -0
  766. package/vendor/ars/scripts/test_check_tortured_phrase_screening_integration.py +1306 -0
  767. package/vendor/ars/scripts/test_check_v3_10_134_write_scope.py +251 -0
  768. package/vendor/ars/scripts/test_check_v3_10_policy.py +547 -0
  769. package/vendor/ars/scripts/test_check_v3_6_7_pattern_protection.py +960 -0
  770. package/vendor/ars/scripts/test_check_v3_6_8_audit_scope_block.py +1000 -0
  771. package/vendor/ars/scripts/test_check_v3_6_8_cite_provenance_pipeline.py +454 -0
  772. package/vendor/ars/scripts/test_check_v3_6_8_frontmatter_trust_schema.py +581 -0
  773. package/vendor/ars/scripts/test_check_v3_6_8_mark_read_commands.py +120 -0
  774. package/vendor/ars/scripts/test_check_v3_6_8_pattern_protection.py +1138 -0
  775. package/vendor/ars/scripts/test_check_v3_7_3_three_layer_citation.py +566 -0
  776. package/vendor/ars/scripts/test_check_v3_8_annotation_literal_sync.py +262 -0
  777. package/vendor/ars/scripts/test_check_v3_9_0_triangulation.py +321 -0
  778. package/vendor/ars/scripts/test_check_v3_9_2_phase_boundary.py +190 -0
  779. package/vendor/ars/scripts/test_check_v3_9_4_temporal_verification.py +471 -0
  780. package/vendor/ars/scripts/test_check_version_consistency.py +1462 -0
  781. package/vendor/ars/scripts/test_check_workflow_classification.py +225 -0
  782. package/vendor/ars/scripts/test_chinese_literature_client.py +1889 -0
  783. package/vendor/ars/scripts/test_citation_existence_policy.py +480 -0
  784. package/vendor/ars/scripts/test_citation_verification_summary.py +342 -0
  785. package/vendor/ars/scripts/test_claim_audit_calibration.py +882 -0
  786. package/vendor/ars/scripts/test_claim_audit_finalizer.py +979 -0
  787. package/vendor/ars/scripts/test_claim_audit_pipeline.py +2398 -0
  788. package/vendor/ars/scripts/test_claim_audit_schema.py +1778 -0
  789. package/vendor/ars/scripts/test_claim_intent_manifest.py +666 -0
  790. package/vendor/ars/scripts/test_claim_registry_coverage.py +246 -0
  791. package/vendor/ars/scripts/test_claim_standing_discovery.py +579 -0
  792. package/vendor/ars/scripts/test_claim_standing_pipeline_wiring.py +290 -0
  793. package/vendor/ars/scripts/test_claim_standing_stance_assets.py +399 -0
  794. package/vendor/ars/scripts/test_claim_standing_stance_contracts.py +383 -0
  795. package/vendor/ars/scripts/test_claim_standing_stance_runner.py +349 -0
  796. package/vendor/ars/scripts/test_claim_standing_transmissions.py +605 -0
  797. package/vendor/ars/scripts/test_claim_strength_drift_disposition.py +517 -0
  798. package/vendor/ars/scripts/test_claim_verification_coverage_contract.py +104 -0
  799. package/vendor/ars/scripts/test_contamination_signals.py +1086 -0
  800. package/vendor/ars/scripts/test_content_coverage_advisory.py +1767 -0
  801. package/vendor/ars/scripts/test_cross_document_consistency_advisory.py +1270 -0
  802. package/vendor/ars/scripts/test_cross_model_codex_transport.py +1152 -0
  803. package/vendor/ars/scripts/test_cross_model_handoff.py +571 -0
  804. package/vendor/ars/scripts/test_cross_model_verification_guards.py +792 -0
  805. package/vendor/ars/scripts/test_crossref_client.py +393 -0
  806. package/vendor/ars/scripts/test_dispatch_e4_panel.py +4235 -0
  807. package/vendor/ars/scripts/test_e2e_claim_audit.py +540 -0
  808. package/vendor/ars/scripts/test_eval_harness_workflow.py +140 -0
  809. package/vendor/ars/scripts/test_evals_citation_extraction.py +150 -0
  810. package/vendor/ars/scripts/test_evals_lift_report_schema.py +108 -0
  811. package/vendor/ars/scripts/test_evidence_rows.py +2593 -0
  812. package/vendor/ars/scripts/test_experiment_provenance.py +915 -0
  813. package/vendor/ars/scripts/test_human_read_attestation_resolver.py +477 -0
  814. package/vendor/ars/scripts/test_ideation_diversity_assignment_gate.py +615 -0
  815. package/vendor/ars/scripts/test_indirect_prompt_injection_behavior_probe.py +250 -0
  816. package/vendor/ars/scripts/test_inquiry_branch_ledger.py +2296 -0
  817. package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_10.py +248 -0
  818. package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_7_3.py +545 -0
  819. package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_9_0.py +497 -0
  820. package/vendor/ars/scripts/test_normalize_compat_verdict.py +149 -0
  821. package/vendor/ars/scripts/test_openalex_client.py +490 -0
  822. package/vendor/ars/scripts/test_passport_yaml.py +104 -0
  823. package/vendor/ars/scripts/test_pattern_eval_runtime.py +1295 -0
  824. package/vendor/ars/scripts/test_pdf_read_preflight.py +1943 -0
  825. package/vendor/ars/scripts/test_policy_anchor_disclosure.py +666 -0
  826. package/vendor/ars/scripts/test_reading_probe_lint.py +218 -0
  827. package/vendor/ars/scripts/test_recompute_receipts.py +778 -0
  828. package/vendor/ars/scripts/test_render_claim_standing_view.py +192 -0
  829. package/vendor/ars/scripts/test_render_eval_comment.py +162 -0
  830. package/vendor/ars/scripts/test_render_harness_retirement_issue.py +110 -0
  831. package/vendor/ars/scripts/test_repro_lock_validation_drift.py +100 -0
  832. package/vendor/ars/scripts/test_research_workflow_profile.py +734 -0
  833. package/vendor/ars/scripts/test_resolve_human_subjects_authority.py +1219 -0
  834. package/vendor/ars/scripts/test_resolve_review_target_context.py +703 -0
  835. package/vendor/ars/scripts/test_resume_e4_record.py +315 -0
  836. package/vendor/ars/scripts/test_retraction_status.py +456 -0
  837. package/vendor/ars/scripts/test_review_criteria_binding.py +629 -0
  838. package/vendor/ars/scripts/test_review_panel_provenance.py +565 -0
  839. package/vendor/ars/scripts/test_review_pathway_rule_trace.py +821 -0
  840. package/vendor/ars/scripts/test_revision_roadmap.py +1255 -0
  841. package/vendor/ars/scripts/test_run_ci_pytest_manifest.py +186 -0
  842. package/vendor/ars/scripts/test_run_codex_audit_e2e.py +369 -0
  843. package/vendor/ars/scripts/test_run_evals.py +430 -0
  844. package/vendor/ars/scripts/test_run_guard_launcher.py +500 -0
  845. package/vendor/ars/scripts/test_run_ideation_diversity_no_call.py +1833 -0
  846. package/vendor/ars/scripts/test_run_indirect_prompt_injection_no_call.py +1889 -0
  847. package/vendor/ars/scripts/test_run_review_criteria_constructive_value.py +586 -0
  848. package/vendor/ars/scripts/test_run_role_topology_utility_dry_run.py +428 -0
  849. package/vendor/ars/scripts/test_score_review_criteria_constructive_value.py +340 -0
  850. package/vendor/ars/scripts/test_semantic_scholar_client.py +554 -0
  851. package/vendor/ars/scripts/test_slr_lineage_emission.py +230 -0
  852. package/vendor/ars/scripts/test_socratic_rq_non_generation_contract.py +173 -0
  853. package/vendor/ars/scripts/test_temporal_integrity_audit.py +438 -0
  854. package/vendor/ars/scripts/test_text_similarity.py +95 -0
  855. package/vendor/ars/scripts/test_title_fuzzy_false_positive.py +111 -0
  856. package/vendor/ars/scripts/test_tortured_phrase_screening.py +2959 -0
  857. package/vendor/ars/scripts/test_transport_fixture_citation_gate.py +338 -0
  858. package/vendor/ars/scripts/test_uncited_assertion.py +558 -0
  859. package/vendor/ars/scripts/test_v3_6_7_phase_6_6.py +1279 -0
  860. package/vendor/ars/scripts/test_validate_compliance_fixtures.py +36 -0
  861. package/vendor/ars/scripts/test_validate_ideation_diversity_assets.py +230 -0
  862. package/vendor/ars/scripts/test_venue_disclosure_contract.py +755 -0
  863. package/vendor/ars/scripts/test_verification_cache.py +280 -0
  864. package/vendor/ars/scripts/test_verification_gate.py +461 -0
  865. package/vendor/ars/scripts/test_verify_passport_cli.py +123 -0
  866. package/vendor/ars/scripts/test_verify_submission_package.py +1407 -0
  867. package/vendor/ars/scripts/test_version_records_schema.py +211 -0
  868. package/vendor/ars/scripts/tortured_phrase_screening.py +3502 -0
  869. package/vendor/ars/scripts/uncited_assertion_detector.py +254 -0
  870. package/vendor/ars/scripts/v3_6_7_inversion_manifest.json +9 -0
  871. package/vendor/ars/scripts/v3_6_8_inversion_manifest.json +10 -0
  872. package/vendor/ars/scripts/validate_claim_standing_stance_assets.py +249 -0
  873. package/vendor/ars/scripts/validate_compliance_fixtures.py +56 -0
  874. package/vendor/ars/scripts/validate_ideation_diversity_assets.py +303 -0
  875. package/vendor/ars/scripts/venue_disclosure_contract_harness.py +837 -0
  876. package/vendor/ars/scripts/verification_cache.py +276 -0
  877. package/vendor/ars/scripts/verification_gate/__init__.py +345 -0
  878. package/vendor/ars/scripts/verify_passport.py +133 -0
  879. package/vendor/ars/scripts/verify_submission_package.py +1657 -0
  880. package/vendor/ars/shared/agents/compliance_agent.md +136 -0
  881. package/vendor/ars/shared/artifact_reproducibility_pattern.md +173 -0
  882. package/vendor/ars/shared/benchmark_report.schema.json +81 -0
  883. package/vendor/ars/shared/benchmark_report_pattern.md +180 -0
  884. package/vendor/ars/shared/bibliographic_integrity_signals.md +142 -0
  885. package/vendor/ars/shared/collaboration_depth_rubric.md +154 -0
  886. package/vendor/ars/shared/compliance_checkpoint_protocol.md +162 -0
  887. package/vendor/ars/shared/compliance_report.schema.json +187 -0
  888. package/vendor/ars/shared/contracts/README.md +938 -0
  889. package/vendor/ars/shared/contracts/activity/adjudication_activity_input.schema.json +555 -0
  890. package/vendor/ars/shared/contracts/activity/adjudication_activity_store.schema.json +560 -0
  891. package/vendor/ars/shared/contracts/audit/audit_jsonl.schema.json +128 -0
  892. package/vendor/ars/shared/contracts/audit/audit_sidecar.schema.json +169 -0
  893. package/vendor/ars/shared/contracts/audit/audit_verdict.schema.json +133 -0
  894. package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory.schema.json +480 -0
  895. package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory_draft.schema.json +564 -0
  896. package/vendor/ars/shared/contracts/audit/cross_document_source_manifest.schema.json +202 -0
  897. package/vendor/ars/shared/contracts/audit/tortured_phrase_advisory.schema.json +1362 -0
  898. package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot.schema.json +208 -0
  899. package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot_manifest.schema.json +335 -0
  900. package/vendor/ars/shared/contracts/capability/stage_capability_matrix.json +434 -0
  901. package/vendor/ars/shared/contracts/claim_standing/candidate_ledger.schema.json +705 -0
  902. package/vendor/ars/shared/contracts/claim_standing/query_plan.schema.json +389 -0
  903. package/vendor/ars/shared/contracts/claim_standing/query_plan_v1_1.schema.json +782 -0
  904. package/vendor/ars/shared/contracts/claim_standing/retrieval_input.schema.json +440 -0
  905. package/vendor/ars/shared/contracts/claim_standing/stance_record.schema.json +460 -0
  906. package/vendor/ars/shared/contracts/claim_standing/transmission_ledger.schema.json +282 -0
  907. package/vendor/ars/shared/contracts/cross_model/codex_citation_receipt.schema.json +157 -0
  908. package/vendor/ars/shared/contracts/cross_model/codex_citation_request.schema.json +23 -0
  909. package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_commitment.schema.json +55 -0
  910. package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_reveal.schema.json +50 -0
  911. package/vendor/ars/shared/contracts/degradation_registry.json +428 -0
  912. package/vendor/ars/shared/contracts/evaluator/full.json +126 -0
  913. package/vendor/ars/shared/contracts/evidence/claim_registry.schema.json +50 -0
  914. package/vendor/ars/shared/contracts/evidence/claim_registry_coverage_report.schema.json +79 -0
  915. package/vendor/ars/shared/contracts/evidence/evidence_row.schema.json +504 -0
  916. package/vendor/ars/shared/contracts/evidence/evidence_row_v1_1.schema.json +364 -0
  917. package/vendor/ars/shared/contracts/evidence/evidence_row_v1_2.schema.json +714 -0
  918. package/vendor/ars/shared/contracts/evidence/evidence_row_v1_3.schema.json +524 -0
  919. package/vendor/ars/shared/contracts/human_subjects/authority_profile_registry.schema.json +497 -0
  920. package/vendor/ars/shared/contracts/human_subjects/committee_correspondence.schema.json +253 -0
  921. package/vendor/ars/shared/contracts/human_subjects/content_coverage_advisory.schema.json +620 -0
  922. package/vendor/ars/shared/contracts/human_subjects/irb_context_record.schema.json +329 -0
  923. package/vendor/ars/shared/contracts/human_subjects/resolved_authority_context.schema.json +347 -0
  924. package/vendor/ars/shared/contracts/human_subjects/review_pathway_rule_trace.schema.json +265 -0
  925. package/vendor/ars/shared/contracts/human_subjects/review_pathway_trace_request.schema.json +127 -0
  926. package/vendor/ars/shared/contracts/human_subjects/submission_packet_inventory.schema.json +240 -0
  927. package/vendor/ars/shared/contracts/human_subjects/submission_packet_manifest.schema.json +999 -0
  928. package/vendor/ars/shared/contracts/passport/audit_artifact_entry.schema.json +266 -0
  929. package/vendor/ars/shared/contracts/passport/bibliographic_integrity_signal.schema.json +1676 -0
  930. package/vendor/ars/shared/contracts/passport/citation_provenance.schema.json +107 -0
  931. package/vendor/ars/shared/contracts/passport/citation_verification_summary.schema.json +164 -0
  932. package/vendor/ars/shared/contracts/passport/claim_audit_result.schema.json +124 -0
  933. package/vendor/ars/shared/contracts/passport/claim_drift.schema.json +58 -0
  934. package/vendor/ars/shared/contracts/passport/claim_intent_manifest.schema.json +107 -0
  935. package/vendor/ars/shared/contracts/passport/constraint_violation.schema.json +65 -0
  936. package/vendor/ars/shared/contracts/passport/experiment_alignment_result.schema.json +69 -0
  937. package/vendor/ars/shared/contracts/passport/experiment_provenance_entry.schema.json +169 -0
  938. package/vendor/ars/shared/contracts/passport/human_read_log.schema.json +86 -0
  939. package/vendor/ars/shared/contracts/passport/inquiry_ledger_ref.schema.json +22 -0
  940. package/vendor/ars/shared/contracts/passport/literature_corpus_entry.schema.json +650 -0
  941. package/vendor/ars/shared/contracts/passport/preregistration_artifact.schema.json +132 -0
  942. package/vendor/ars/shared/contracts/passport/rejection_log.schema.json +89 -0
  943. package/vendor/ars/shared/contracts/passport/reset_ledger_entry.schema.json +158 -0
  944. package/vendor/ars/shared/contracts/passport/temporal_audit_results.schema.json +208 -0
  945. package/vendor/ars/shared/contracts/passport/terminal_policies.schema.json +50 -0
  946. package/vendor/ars/shared/contracts/passport/timeline.schema.json +102 -0
  947. package/vendor/ars/shared/contracts/passport/uncited_assertion.schema.json +56 -0
  948. package/vendor/ars/shared/contracts/passport/uncited_audit_failure.schema.json +72 -0
  949. package/vendor/ars/shared/contracts/passport/user_attested_read_resolution.schema.json +87 -0
  950. package/vendor/ars/shared/contracts/passport/version_records.schema.json +138 -0
  951. package/vendor/ars/shared/contracts/patch/block_manifest.schema.json +45 -0
  952. package/vendor/ars/shared/contracts/patch/legacy/v1_0/revision_patch.schema.json +110 -0
  953. package/vendor/ars/shared/contracts/patch/revision_patch.schema.json +250 -0
  954. package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_diagnostic.schema.json +39 -0
  955. package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_worker.schema.json +88 -0
  956. package/vendor/ars/shared/contracts/pdf/pdf_read_preflight.schema.json +227 -0
  957. package/vendor/ars/shared/contracts/re_review/input_manifest.schema.json +161 -0
  958. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/input_manifest.schema.json +134 -0
  959. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/precommitment.schema.json +193 -0
  960. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/traceability.schema.json +841 -0
  961. package/vendor/ars/shared/contracts/re_review/legacy/v1_0/verdict_record.schema.json +244 -0
  962. package/vendor/ars/shared/contracts/re_review/precommitment.schema.json +193 -0
  963. package/vendor/ars/shared/contracts/re_review/traceability.schema.json +916 -0
  964. package/vendor/ars/shared/contracts/re_review/verdict_record.schema.json +245 -0
  965. package/vendor/ars/shared/contracts/research_workflow/inquiry_branch_ledger.schema.json +449 -0
  966. package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile.schema.json +222 -0
  967. package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile_selection_receipt.schema.json +111 -0
  968. package/vendor/ars/shared/contracts/review_target/constructive_review_findings.schema.json +172 -0
  969. package/vendor/ars/shared/contracts/review_target/criteria_registry.schema.json +97 -0
  970. package/vendor/ars/shared/contracts/review_target/review_criteria_binding_manifest.schema.json +235 -0
  971. package/vendor/ars/shared/contracts/review_target/review_criteria_source_receipt.schema.json +158 -0
  972. package/vendor/ars/shared/contracts/review_target/review_target_context.schema.json +106 -0
  973. package/vendor/ars/shared/contracts/review_target/review_target_declaration.schema.json +118 -0
  974. package/vendor/ars/shared/contracts/reviewer/full.json +114 -0
  975. package/vendor/ars/shared/contracts/reviewer/methodology_focus.json +75 -0
  976. package/vendor/ars/shared/contracts/reviewer/review_panel_provenance.schema.json +263 -0
  977. package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_carrier.schema.json +122 -0
  978. package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_input.schema.json +120 -0
  979. package/vendor/ars/shared/contracts/revision/author_adjudication.schema.json +251 -0
  980. package/vendor/ars/shared/contracts/revision/author_adjudication_input.schema.json +25 -0
  981. package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition.schema.json +97 -0
  982. package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition_input.schema.json +80 -0
  983. package/vendor/ars/shared/contracts/revision/claim_strength_drift_findings.schema.json +139 -0
  984. package/vendor/ars/shared/contracts/revision/claim_surface_manifest.schema.json +99 -0
  985. package/vendor/ars/shared/contracts/revision/integrity_correction_authorization.schema.json +140 -0
  986. package/vendor/ars/shared/contracts/revision/integrity_correction_authorization_input.schema.json +28 -0
  987. package/vendor/ars/shared/contracts/revision/integrity_correction_list.schema.json +48 -0
  988. package/vendor/ars/shared/contracts/revision/integrity_pass_receipt.schema.json +16 -0
  989. package/vendor/ars/shared/contracts/revision/revision_evidence_bundle.schema.json +134 -0
  990. package/vendor/ars/shared/contracts/revision/revision_roadmap.schema.json +334 -0
  991. package/vendor/ars/shared/contracts/submission/format_profile.example.yaml +33 -0
  992. package/vendor/ars/shared/contracts/submission/format_profile.schema.json +102 -0
  993. package/vendor/ars/shared/contracts/submission/submission_verification_report.schema.json +233 -0
  994. package/vendor/ars/shared/contracts/submission/venue_profile.schema.json +114 -0
  995. package/vendor/ars/shared/contracts/writer/full.json +87 -0
  996. package/vendor/ars/shared/cross_model_verification.md +714 -0
  997. package/vendor/ars/shared/evals_lift_report.schema.json +141 -0
  998. package/vendor/ars/shared/ground_truth_isolation_pattern.md +275 -0
  999. package/vendor/ars/shared/handoff_schemas.md +1209 -0
  1000. package/vendor/ars/shared/human_subjects_authority_registry.json +1278 -0
  1001. package/vendor/ars/shared/mode_spectrum.md +57 -0
  1002. package/vendor/ars/shared/model_tiering.md +83 -0
  1003. package/vendor/ars/shared/policy_data/nature_policy.md +56 -0
  1004. package/vendor/ars/shared/prisma_trAIce_protocol.md +157 -0
  1005. package/vendor/ars/shared/raise_framework.md +129 -0
  1006. package/vendor/ars/shared/references/authority_content_coverage_advisory_protocol.md +275 -0
  1007. package/vendor/ars/shared/references/claim_standing_candidate_ledger_protocol.md +66 -0
  1008. package/vendor/ars/shared/references/claim_strength_ladder.md +93 -0
  1009. package/vendor/ars/shared/references/cross_document_consistency_advisory_protocol.md +263 -0
  1010. package/vendor/ars/shared/references/evidence_row_protocol.md +260 -0
  1011. package/vendor/ars/shared/references/firm_rules.md +90 -0
  1012. package/vendor/ars/shared/references/human_subjects_authority_protocol.md +274 -0
  1013. package/vendor/ars/shared/references/intent_clarification_protocol.md +168 -0
  1014. package/vendor/ars/shared/references/irb_terminology_glossary.md +229 -0
  1015. package/vendor/ars/shared/references/protected_hedging_phrases.md +118 -0
  1016. package/vendor/ars/shared/references/psychometric_terminology_glossary.md +109 -0
  1017. package/vendor/ars/shared/references/review_criteria_consumer_protocol.md +238 -0
  1018. package/vendor/ars/shared/references/review_pathway_rule_trace_protocol.md +166 -0
  1019. package/vendor/ars/shared/references/submission_packet_manifest_protocol.md +292 -0
  1020. package/vendor/ars/shared/references/word_count_conventions.md +124 -0
  1021. package/vendor/ars/shared/research_workflow_profiles/field_general.json +1 -0
  1022. package/vendor/ars/shared/review_criteria_registry.json +207 -0
  1023. package/vendor/ars/shared/review_criteria_sources/msr-2027-technical-papers.2026-08-24.json +46 -0
  1024. package/vendor/ars/shared/review_criteria_sources/sigsoft-empirical-standards.2026-08-24.json +31 -0
  1025. package/vendor/ars/shared/sprint_contract.schema.json +482 -0
  1026. package/vendor/ars/shared/style_calibration_protocol.md +151 -0
  1027. package/vendor/ars/shared/templates/codex_audit_multifile_template.md +263 -0
  1028. package/vendor/ars/tools/release-discipline/.toolkit-version +1 -0
  1029. package/vendor/ars/tools/release-discipline/README.md +4 -0
  1030. package/vendor/ars/tools/release-discipline/scripts/_release_doc_alignment_schema.py +1011 -0
  1031. package/vendor/ars/tools/release-discipline/scripts/check_command_invariants.py +497 -0
  1032. package/vendor/ars/tools/release-discipline/scripts/check_release_doc_alignment.py +263 -0
  1033. package/vendor/ars/tools/release-discipline/scripts/sync-toolkit.sh +147 -0
  1034. package/vendor/windows/NOTICE.md +12 -0
  1035. package/vendor/windows/arm64/fd.exe +0 -0
  1036. package/vendor/windows/arm64/licenses/fd/LICENSE-APACHE +201 -0
  1037. package/vendor/windows/arm64/licenses/fd/LICENSE-MIT +21 -0
  1038. package/vendor/windows/arm64/licenses/ripgrep/COPYING +3 -0
  1039. package/vendor/windows/arm64/licenses/ripgrep/LICENSE-MIT +21 -0
  1040. package/vendor/windows/arm64/licenses/ripgrep/UNLICENSE +24 -0
  1041. package/vendor/windows/arm64/rg.exe +0 -0
  1042. package/vendor/windows/x64/fd.exe +0 -0
  1043. package/vendor/windows/x64/licenses/fd/LICENSE-APACHE +201 -0
  1044. package/vendor/windows/x64/licenses/fd/LICENSE-MIT +21 -0
  1045. package/vendor/windows/x64/licenses/ripgrep/COPYING +3 -0
  1046. package/vendor/windows/x64/licenses/ripgrep/LICENSE-MIT +21 -0
  1047. package/vendor/windows/x64/licenses/ripgrep/UNLICENSE +24 -0
  1048. package/vendor/windows/x64/rg.exe +0 -0
@@ -0,0 +1,1657 @@
1
+ #!/usr/bin/env python3
2
+ """verify_submission_package CLI — deterministic submission-package verifier
3
+ (#394; slices 1-2 shipped: Family C reference integrity + Family B venue
4
+ limits).
5
+
6
+ python scripts/verify_submission_package.py <package_dir> \
7
+ [--passport passport.yaml] [--join-map map.yaml] \
8
+ [--venue-profile profile.yaml] [--report-out path]
9
+
10
+ Reads the files in an output package and runs the Family C two-way reference
11
+ integrity check (in-text citation keys <-> reference-list entries) plus the
12
+ Family B venue-limits checks (B1-B5, against a scholar-declared venue profile —
13
+ without one they report NOT-CHECKED, never a guess from the journal name),
14
+ writing `submission_verification_report.json` (validating against
15
+ shared/contracts/submission/submission_verification_report.schema.json) plus a
16
+ human-readable summary to stdout.
17
+
18
+ Design contract (spec docs/design/2026-06-10-394-submission-package-verifier-spec.md):
19
+
20
+ - Detection is unconditional; terminality is the policy evaluator's job. This
21
+ script NEVER reads `terminal_policies` (§5.3) — `policy_slug` is emitted null.
22
+ - The joined marker path is deterministic; it needs a real prose-reference join
23
+ (§3.3): the run's `citation_verification_summary[]` (via --passport), an
24
+ explicit scholar-supplied join map (--join-map), or a package `.bib` whose
25
+ keys map to slugs by the documented identity relation (draft_writer_agent.md:
26
+ the slug IS the corpus `citation_key`). Markers with NO join source report
27
+ `not_checked(missing prose-reference join)` — never a guessed comparison,
28
+ and a slug an explicit join source does not cover is reported as unjoined,
29
+ never identity-guessed.
30
+ - Fallback extraction (`\\cite{}` for LaTeX, author-year regex for Markdown
31
+ text) is heuristic-classed: advisory-only, `strict_eligible: false`, header
32
+ `extraction_path: best_effort` (§3.3).
33
+ - Every check reports pass | fail | warn | not_checked; `not_checked` is
34
+ surfaced in the header count, never folded into pass (§1.4).
35
+ - `package_fingerprint` reuses the audit-snapshot manifest convention
36
+ (scripts/audit_snapshot.py; spec §10 open item 3, adjudicated at slice 1):
37
+ `<relative-path>:<sha256>` lines, byte-sorted, trailing newline, fingerprint
38
+ = SHA-256 of the manifest text. The report file itself is excluded.
39
+
40
+ Exit codes: 0 = no fail (warns allowed) and everything checked; 1 = >=1 fail;
41
+ 2 = usage/IO error; 3 = no fail but >=1 not_checked ("passed what was
42
+ checkable", §8).
43
+ """
44
+ from __future__ import annotations
45
+
46
+ import argparse
47
+ import json
48
+ import re
49
+ import sys
50
+ import xml.etree.ElementTree as ET
51
+ import zipfile
52
+ from pathlib import Path
53
+ from typing import Any, Iterator, Optional
54
+
55
+ import yaml
56
+
57
+ try:
58
+ from audit_snapshot import sha256_hex
59
+ except ImportError: # pragma: no cover - dual-path import
60
+ from scripts.audit_snapshot import sha256_hex
61
+
62
+ try:
63
+ import pypdf
64
+ except ImportError: # the A1 PDF scan reports NOT-CHECKED without it (§1.4)
65
+ pypdf = None
66
+
67
+ try:
68
+ # Hardened XML parsing (XML-bomb/XXE protection) when available; the
69
+ # stdlib fallback does not fetch external entities but is exposed to
70
+ # entity-expansion DoS — acceptable only because the input is the
71
+ # scholar's own package, and requirements-dev installs the hardened path.
72
+ from defusedxml.ElementTree import fromstring as _xml_fromstring
73
+ from defusedxml.common import DefusedXmlException as _DefusedXmlException
74
+ except ImportError:
75
+ _xml_fromstring = ET.fromstring
76
+
77
+ class _DefusedXmlException(Exception):
78
+ """Placeholder when defusedxml is absent — never raised."""
79
+
80
+
81
+ # A hardened-parser rejection (entity/DOCTYPE tricks) is an unreadable
82
+ # artifact, reported as NOT-CHECKED — it must not crash the scan.
83
+ _XML_READ_ERRORS = (zipfile.BadZipFile, ET.ParseError, OSError, KeyError,
84
+ ValueError, _DefusedXmlException)
85
+
86
+ # Zip-bomb guards for the raw DOCX part reads (read-only scan, no extraction
87
+ # — path traversal is not exposed, memory is).
88
+ _MAX_XML_PART_BYTES = 50 * 1024 * 1024
89
+ _MAX_ZIP_ENTRIES = 10_000
90
+
91
+ REPORT_BASENAME = "submission_verification_report.json"
92
+
93
+ # Files scanned for in-text citations. provenance_summary.md is an advisory
94
+ # carrier that legitimately repeats ref_slugs / citation_keys (#333) — scanning
95
+ # it would manufacture false in-text hits.
96
+ _MANUSCRIPT_SUFFIXES = {".md", ".tex", ".txt"}
97
+ _SCAN_EXCLUDED_NAMES = {"provenance_summary.md", REPORT_BASENAME}
98
+
99
+ # Files excluded from the package fingerprint (slice 4). Same two names as
100
+ # _SCAN_EXCLUDED_NAMES today, but DELIBERATELY a separate constant — the two
101
+ # sets answer different questions (scan: "is this manuscript prose?";
102
+ # fingerprint: "can this file change without invalidating the report?") and a
103
+ # future entry in one does not automatically belong in the other. The report
104
+ # cannot fingerprint its own bytes; provenance_summary.md is appended to by
105
+ # the formatter AFTER the report is stamped (the advisories section), so
106
+ # fingerprinting it would self-stale every evaluated report.
107
+ _FINGERPRINT_EXCLUDED_NAMES = frozenset({REPORT_BASENAME,
108
+ "provenance_summary.md"})
109
+
110
+ # v3.7.1+ marker grammar with the canonical slug charset (the lint-side
111
+ # REF_PATTERN in check_v3_7_3_three_layer_citation.py). The suffix handling is
112
+ # deliberately broader than REF_PATTERN's `[^-]*?` status group: a finalized
113
+ # package carries `LOW-WARN` / `CONTAMINATED-*` suffix tokens (formatter
114
+ # pass-through allowlist) that REF_PATTERN does not match, and missing those
115
+ # markers here would fabricate orphans. Anchor markers (`<!--anchor:...-->`)
116
+ # are a different grammar and never match.
117
+ _REF_MARKER_RE = re.compile(r"<!--ref:([A-Za-z][A-Za-z0-9_:-]*)(?:\s[^>]*)?-->")
118
+
119
+ # BibTeX entry heads after an `^@` split: `article{key,`. @comment/@preamble/
120
+ # @string carry no citation key and are excluded.
121
+ _BIB_ENTRY_HEAD_RE = re.compile(
122
+ r"(?!comment|preamble|string)[A-Za-z]+\s*\{\s*([^,\s}]+)\s*,",
123
+ re.IGNORECASE,
124
+ )
125
+
126
+ _LOCATION_CAP = 5 # findings listed per check detail before truncation
127
+
128
+ # Check registry mirroring the spec §3 family tables: id -> (family,
129
+ # fail_capable, fixed_signal_class). strict_eligible = fail_capable AND
130
+ # deterministic signal (§3.1 separate axes; a warn-only check is never
131
+ # policy-promotable, §5.3). fixed_signal_class None = path-dependent (Family
132
+ # C: deterministic on the joined marker path, heuristic on the fallback); a
133
+ # non-None class is bound HERE and wins over the call site, so a
134
+ # structurally-heuristic check (slice 3's A5/A6) is excluded from strict by
135
+ # CLASS, never "defaulted out of it" (§3.1) — a forgotten kwarg cannot
136
+ # fail open. build_report enforces the roster: a runner that silently omits
137
+ # a registered check cannot emit a report (the §1.4/#349 fail-open guard).
138
+ _CHECK_REGISTRY = {
139
+ "A1": ("blind_review_residue", True, "deterministic"), # PDF metadata authors
140
+ "A2": ("blind_review_residue", True, "deterministic"), # DOCX metadata authors
141
+ "A3": ("blind_review_residue", True, "deterministic"), # DOCX revision/comment authors
142
+ "A4": ("blind_review_residue", True, "deterministic"), # acknowledgments in blind variant (strict only by profile declaration, §3.1)
143
+ "A5": ("blind_review_residue", True, "heuristic"), # self-citation phrasing
144
+ "A6": ("blind_review_residue", True, "heuristic"), # filename leakage
145
+ "A7": ("blind_review_residue", True, "deterministic"), # blind variant missing under declared double-blind
146
+ "B1": ("venue_limits", True, "deterministic"), # manuscript word count
147
+ "B2": ("venue_limits", True, "deterministic"), # abstract word count
148
+ "B3": ("venue_limits", True, "deterministic"), # keyword count range
149
+ "B4": ("venue_limits", True, "deterministic"), # required sections
150
+ "B5": ("venue_limits", True, "deterministic"), # reference count ceiling
151
+ "C1": ("reference_integrity", True, None),
152
+ "C2": ("reference_integrity", False, None),
153
+ }
154
+
155
+ # --- Fallback (best-effort) extraction grammar (§3.3, heuristic-classed) -----
156
+
157
+ # \cite / \citep / \citet / \citealp / starred forms, up to two optional args.
158
+ _LATEX_CITE_RE = re.compile(r"\\cite[a-zA-Z]*\*?(?:\[[^\]]*\]){0,2}\{([^}]*)\}")
159
+
160
+ # Reference-list section titles — the single source for BOTH the fallback
161
+ # prose-scan boundary (Family C) and the B1 body_only word-count scope; one
162
+ # list so the two checks can never disagree about where the references start.
163
+ _REFS_TITLES = ("references", "bibliography", "參考文獻")
164
+ _REFS_HEADING_RE = re.compile(
165
+ r"^#{0,6}\s*(?:" + "|".join(_REFS_TITLES) + r")\s*$",
166
+ re.IGNORECASE | re.MULTILINE,
167
+ )
168
+
169
+ _NAME = r"[A-Z][\w'’-]+"
170
+ # Narrative: `Smith (2024)`, `Smith et al. (2024)`, `Smith and Chen (2024)`,
171
+ # with an optional page-locator tail: `Smith (2024, p. 12)`.
172
+ _NARRATIVE_CITE_RE = re.compile(
173
+ r"(" + _NAME + r")(?:\s+et al\.?|\s+(?:and|&)\s+" + _NAME + r")?"
174
+ r"\s+\((\d{4})[a-z]?(?:\s*,\s*pp?\.?[^)]*)?\)")
175
+ # Parenthetical group content is split on `;` and each segment matched:
176
+ # `(Smith, 2024)`, `(Chen & Lee, 2023)`, `(Smith et al., 2024a)`,
177
+ # `(Chen & Lee, 2023, pp. 45–67)`.
178
+ _PAREN_GROUP_RE = re.compile(r"\(([^()]+)\)")
179
+ _PAREN_SEGMENT_RE = re.compile(
180
+ r"^\s*(" + _NAME + r")[^\d]*?(\d{4})[a-z]?(?:\s*,\s*pp?\.?[^;]*)?\s*$")
181
+
182
+
183
+ def compute_package_fingerprint(package_dir: Path,
184
+ report_relpath: Optional[str] = None) -> str:
185
+ """Audit-snapshot manifest convention over the package files (§10 item 3):
186
+ one `<package-relative-path>:<sha256>` line per file, LC_ALL=C byte-sorted,
187
+ trailing newline; fingerprint = SHA-256 of the manifest text. The report
188
+ file is excluded — the report cannot fingerprint its own bytes — including
189
+ a custom --report-out path inside the package (report_relpath, as a
190
+ package-relative posix path), or reruns would self-reference.
191
+ provenance_summary.md is excluded too (slice 4): it is the pipeline's own
192
+ advisory carrier — the formatter appends the Submission Package Advisories
193
+ section AFTER the report is stamped, which would otherwise immediately
194
+ stale the very report whose findings it carries. The freshness guard's
195
+ threat model is manuscript/package drift, not the advisory carrier."""
196
+ excluded = _FINGERPRINT_EXCLUDED_NAMES | {report_relpath}
197
+ lines = []
198
+ for path in package_dir.rglob("*"):
199
+ if not path.is_file():
200
+ continue
201
+ rel = path.relative_to(package_dir).as_posix()
202
+ if rel in excluded:
203
+ continue
204
+ lines.append(f"{rel}:{sha256_hex(path.read_bytes())}")
205
+ lines.sort() # byte sort over the composed line, matching audit_snapshot
206
+ manifest_text = "\n".join(lines) + "\n"
207
+ return sha256_hex(manifest_text.encode("utf-8"))
208
+
209
+
210
+ def compute_inputs_fingerprint(venue_profile_path: Optional[str],
211
+ join_map_path: Optional[str],
212
+ passport_path: Optional[str]) -> str:
213
+ """SHA-256 over the external-inputs manifest (gate-2 review P1): one
214
+ `<name>:<sha256-of-file-bytes|absent>` line per input, sorted by name,
215
+ trailing newline. Family B/C verdicts depend on these inputs, so the
216
+ freshness guard must see them — the package fingerprint alone cannot
217
+ (the inputs live outside the package). An absent input hashes as the
218
+ literal token `absent`, so declared→absent is a visible change."""
219
+ lines = []
220
+ for name, raw in (("join_map", join_map_path),
221
+ ("passport", passport_path),
222
+ ("venue_profile", venue_profile_path)):
223
+ digest = sha256_hex(Path(raw).read_bytes()) if raw else "absent"
224
+ lines.append(f"{name}:{digest}")
225
+ manifest_text = "\n".join(sorted(lines)) + "\n"
226
+ return sha256_hex(manifest_text.encode("utf-8"))
227
+
228
+
229
+ def _collect_package_texts(package_dir: Path
230
+ ) -> tuple[dict[str, str], dict[str, str]]:
231
+ """One walk, one read per file: ({manuscript rel: text}, {bib rel: text})."""
232
+ manuscripts: dict[str, str] = {}
233
+ bibs: dict[str, str] = {}
234
+ for path in sorted(package_dir.rglob("*")):
235
+ if not path.is_file() or path.name in _SCAN_EXCLUDED_NAMES:
236
+ continue
237
+ suffix = path.suffix.lower()
238
+ if suffix not in _MANUSCRIPT_SUFFIXES and suffix != ".bib":
239
+ continue
240
+ rel = path.relative_to(package_dir).as_posix()
241
+ text = path.read_text(encoding="utf-8", errors="replace")
242
+ (bibs if suffix == ".bib" else manuscripts)[rel] = text
243
+ return manuscripts, bibs
244
+
245
+
246
+ def extract_ref_markers(manuscripts: dict[str, str]) -> dict[str, str]:
247
+ """{slug: first-seen package-relative location} from <!--ref:slug--> markers."""
248
+ found: dict[str, str] = {}
249
+ for rel in sorted(manuscripts):
250
+ for m in _REF_MARKER_RE.finditer(manuscripts[rel]):
251
+ found.setdefault(m.group(1), rel)
252
+ return found
253
+
254
+
255
+ def _iter_bib_entries(bibs: dict[str, str]) -> Iterator[tuple[str, str]]:
256
+ """Yield (citation_key, raw entry body) per BibTeX entry across the
257
+ package's .bib files — the single entry-head grammar both the key set and
258
+ the author-year metadata derive from, so they cannot drift."""
259
+ for rel in sorted(bibs):
260
+ for chunk in re.split(r"(?m)^\s*@", bibs[rel])[1:]:
261
+ head = _BIB_ENTRY_HEAD_RE.match(chunk)
262
+ if head:
263
+ yield head.group(1), chunk
264
+
265
+
266
+ def parse_bib_keys(bibs: dict[str, str]) -> set[str]:
267
+ return {key for key, _body in _iter_bib_entries(bibs)}
268
+
269
+
270
+ def _parse_bib_metadata(bibs: dict[str, str]) -> dict[tuple, set]:
271
+ """{(first-author-surname-lower, year): {citation_key, ...}} from package
272
+ .bib entries, for author-year fallback matching. Best-effort field parsing
273
+ — the whole fallback path is heuristic-classed anyway (§3.3)."""
274
+ metadata: dict[tuple, set] = {}
275
+ for key, body in _iter_bib_entries(bibs):
276
+ author = re.search(r"author\s*=\s*[{\"]([^}\"]+)", body, re.IGNORECASE)
277
+ year = re.search(r"year\s*=\s*[{\"]?(\d{4})", body, re.IGNORECASE)
278
+ if not (author and year):
279
+ continue
280
+ surname = _first_author_surname(author.group(1))
281
+ if surname:
282
+ metadata.setdefault(
283
+ (surname.lower(), year.group(1)), set()).add(key)
284
+ return metadata
285
+
286
+
287
+ def _first_author_surname(author_field: str) -> str:
288
+ first = author_field.split(" and ")[0].strip()
289
+ if "," in first:
290
+ return first.split(",")[0].strip()
291
+ parts = first.split()
292
+ return parts[-1] if parts else ""
293
+
294
+
295
+ def _corpus_metadata(passport: dict[str, Any]) -> dict[tuple, set]:
296
+ metadata: dict[tuple, set] = {}
297
+ for e in passport.get("literature_corpus") or []:
298
+ key = e.get("citation_key")
299
+ year = e.get("year")
300
+ authors = e.get("authors") or []
301
+ family = authors[0].get("family") if (
302
+ authors and isinstance(authors[0], dict)) else None
303
+ if isinstance(key, str) and family and year is not None:
304
+ metadata.setdefault((str(family).lower(), str(year)), set()).add(key)
305
+ return metadata
306
+
307
+
308
+ def _strip_reference_section(text: str) -> str:
309
+ m = _REFS_HEADING_RE.search(text)
310
+ return text[: m.start()] if m else text
311
+
312
+
313
+ def _extract_fallback(manuscripts: dict[str, str],
314
+ metadata: dict[tuple, set]
315
+ ) -> tuple[dict[str, str], dict[str, str]]:
316
+ """Best-effort in-text extraction (§3.3 fallback path): \\cite{} keys from
317
+ .tex, author-year hits from .md/.txt matched against reference metadata.
318
+ Returns (in_text {citation_key: location}, unresolved {display token:
319
+ location}) — unresolved hits stay out of the citation-key namespace so a
320
+ key that textually equals the token never silently merges."""
321
+ in_text: dict[str, str] = {}
322
+ unresolved: dict[str, str] = {}
323
+ for rel in sorted(manuscripts):
324
+ text = manuscripts[rel]
325
+ if rel.lower().endswith(".tex"):
326
+ for m in _LATEX_CITE_RE.finditer(text):
327
+ for key in m.group(1).split(","):
328
+ key = key.strip()
329
+ if key:
330
+ in_text.setdefault(key, rel)
331
+ continue
332
+ prose = _strip_reference_section(text)
333
+ hits = [(m.group(1), m.group(2))
334
+ for m in _NARRATIVE_CITE_RE.finditer(prose)]
335
+ for g in _PAREN_GROUP_RE.finditer(prose):
336
+ for segment in g.group(1).split(";"):
337
+ m = _PAREN_SEGMENT_RE.match(segment)
338
+ if m:
339
+ hits.append((m.group(1), m.group(2)))
340
+ for surname, year in hits:
341
+ keys = metadata.get((surname.lower(), year))
342
+ if keys:
343
+ for key in keys:
344
+ in_text.setdefault(key, rel)
345
+ else:
346
+ unresolved.setdefault(f"{surname} ({year})", rel)
347
+ return in_text, unresolved
348
+
349
+
350
+ def _check(check_id: str, status: str, detail: str, *,
351
+ signal_class: str = "deterministic",
352
+ location: Optional[str] = None,
353
+ strict_eligible: Optional[bool] = None) -> dict[str, Any]:
354
+ family, fail_capable, fixed_class = _CHECK_REGISTRY[check_id]
355
+ if fixed_class is not None:
356
+ signal_class = fixed_class # registry-bound class wins (§3.1)
357
+ eligible = fail_capable and signal_class == "deterministic"
358
+ if strict_eligible is not None:
359
+ # Downward-only override: a call site can RESTRICT eligibility (A4 is
360
+ # advisory unless the venue profile declares acknowledgments must be
361
+ # removed, §3.1) but can never promote past the class rule above.
362
+ eligible = eligible and strict_eligible
363
+ return {
364
+ "id": check_id,
365
+ "family": family,
366
+ "signal_class": signal_class,
367
+ "strict_eligible": eligible,
368
+ "status": status,
369
+ "detail": detail,
370
+ "location": location,
371
+ }
372
+
373
+
374
+ def _not_checked_pair(reason: str) -> list[dict[str, Any]]:
375
+ return [
376
+ _check("C1", "not_checked", reason),
377
+ _check("C2", "not_checked", reason),
378
+ ]
379
+
380
+
381
+ def _listed(keys: set[str]) -> str:
382
+ shown = sorted(keys)[:_LOCATION_CAP]
383
+ extra = len(keys) - len(shown)
384
+ listing = ", ".join(shown)
385
+ if extra > 0:
386
+ listing += f", … (+{extra} more)"
387
+ return listing
388
+
389
+
390
+ def _compare_sets(in_text: dict[str, str], reference_keys: set[str],
391
+ *, signal_class: str, in_text_label: str,
392
+ reference_label: str,
393
+ unjoined: Optional[dict[str, str]] = None,
394
+ unjoined_label: str = ("with no join entry in the supplied "
395
+ "join source")
396
+ ) -> list[dict[str, Any]]:
397
+ """Two-way set check (§3.3): orphan in-text citation = fail (C1); uncited
398
+ reference entry = warn (C2 — some venues allow further-reading entries).
399
+ `unjoined` carries in-text hits that cannot be placed in the citation-key
400
+ namespace (marker slugs the join source does not cover; fallback hits with
401
+ no metadata match): they are a C1 fail in their own right — NEVER compared
402
+ via an identity guess (§3.3), which would silently pass a slug that
403
+ coincidentally equals a citation_key."""
404
+ unjoined = unjoined or {}
405
+ orphans = {k for k in in_text if k not in reference_keys}
406
+ uncited = reference_keys - set(in_text)
407
+ checks = []
408
+ if orphans or unjoined:
409
+ parts = []
410
+ if orphans:
411
+ parts.append(
412
+ f"{len(orphans)} in-text citation(s) absent from "
413
+ f"{reference_label}: {_listed(orphans)}")
414
+ if unjoined:
415
+ parts.append(
416
+ f"{len(unjoined)} in-text citation(s) {unjoined_label}: "
417
+ f"{_listed(set(unjoined))}")
418
+ first_loc = min(
419
+ [in_text[k] for k in orphans] + list(unjoined.values()))
420
+ checks.append(_check(
421
+ "C1", "fail",
422
+ "; ".join(parts) + f" [{in_text_label}]",
423
+ signal_class=signal_class, location=first_loc))
424
+ else:
425
+ checks.append(_check(
426
+ "C1", "pass",
427
+ f"all {len(in_text)} in-text citation(s) present in "
428
+ f"{reference_label} [{in_text_label}]",
429
+ signal_class=signal_class))
430
+ if uncited:
431
+ checks.append(_check(
432
+ "C2", "warn",
433
+ f"{len(uncited)} reference entr(ies) never cited in text: "
434
+ f"{_listed(uncited)} [{in_text_label}]",
435
+ signal_class=signal_class))
436
+ else:
437
+ checks.append(_check(
438
+ "C2", "pass",
439
+ f"all {len(reference_keys)} reference entr(ies) cited in text "
440
+ f"[{in_text_label}]",
441
+ signal_class=signal_class))
442
+ return checks
443
+
444
+
445
+ def _load_yaml(path: Path) -> dict[str, Any]:
446
+ data = yaml.safe_load(path.read_text(encoding="utf-8"))
447
+ if not isinstance(data, dict):
448
+ raise ValueError(f"expected a YAML mapping in {path}")
449
+ return data
450
+
451
+
452
+ def _join_from_passport(passport: dict[str, Any]) -> dict[str, str]:
453
+ """{ref_slug: citation_key} from the passport's
454
+ citation_verification_summary[] rows (the per-citation prose join the
455
+ Stage 4->5 run already established, §3.3)."""
456
+ join: dict[str, str] = {}
457
+ for row in passport.get("citation_verification_summary") or []:
458
+ slug = row.get("ref_slug")
459
+ key = row.get("citation_key")
460
+ if isinstance(slug, str) and slug and isinstance(key, str) and key:
461
+ join[slug] = key
462
+ return join
463
+
464
+
465
+ def _corpus_keys(passport: dict[str, Any]) -> set[str]:
466
+ return {
467
+ e.get("citation_key")
468
+ for e in passport.get("literature_corpus") or []
469
+ if isinstance(e.get("citation_key"), str)
470
+ }
471
+
472
+
473
+ _NO_REFERENCE_LIST_REASON = (
474
+ "no machine-readable reference list (no package .bib and no "
475
+ "passport literature_corpus[])")
476
+ _NO_MANUSCRIPT_REASON = (
477
+ "no manuscript found (no .md/.tex/.txt file in the package)")
478
+
479
+
480
+ def _reference_list(bib_keys: set[str],
481
+ passport: Optional[dict[str, Any]]
482
+ ) -> tuple[set[str], str]:
483
+ """The machine-readable reference list both Family C and B5 compare
484
+ against: package .bib keys, or the passport's declared
485
+ literature_corpus[] keys. Empty set + empty label = no source."""
486
+ if bib_keys:
487
+ return bib_keys, "the package .bib reference list"
488
+ corpus_keys = _corpus_keys(passport) if passport else set()
489
+ if corpus_keys:
490
+ return corpus_keys, "the passport literature_corpus reference list"
491
+ return set(), ""
492
+
493
+
494
+ def run_family_c(manuscripts: dict[str, str], bibs: dict[str, str],
495
+ bib_keys: set[str],
496
+ reference_keys: set[str], reference_label: str,
497
+ passport: Optional[dict[str, Any]] = None,
498
+ join_map: Optional[dict[str, str]] = None
499
+ ) -> tuple[list[dict[str, Any]], str]:
500
+ """Run Family C over the collected package texts.
501
+ Returns (checks, extraction_path)."""
502
+ if not manuscripts:
503
+ return _not_checked_pair(_NO_MANUSCRIPT_REASON), "none"
504
+
505
+ markers = extract_ref_markers(manuscripts)
506
+ summary_join = _join_from_passport(passport) if passport else {}
507
+
508
+ if not reference_keys:
509
+ return _not_checked_pair(_NO_REFERENCE_LIST_REASON), "none"
510
+
511
+ if markers:
512
+ # Joined marker path (deterministic). Join precedence: explicit
513
+ # scholar-supplied map > the run's citation_verification_summary[] >
514
+ # .bib identity relation.
515
+ if join_map is not None:
516
+ join: Optional[dict[str, str]] = dict(join_map)
517
+ elif summary_join:
518
+ join = summary_join
519
+ elif bib_keys:
520
+ # Documented identity relation (draft_writer_agent.md: the slug IS
521
+ # the corpus citation_key): every marker slug joins to itself, so
522
+ # a slug that is not a .bib key is simply an orphan.
523
+ join = None
524
+ else:
525
+ return _not_checked_pair(
526
+ "missing prose-reference join: <!--ref:slug--> markers found "
527
+ "but no citation_verification_summary, --join-map, or package "
528
+ ".bib supplies the slug->citation_key join (§3.3 — never a "
529
+ "guessed comparison)"), "none"
530
+ if join is None: # .bib identity relation
531
+ in_text, unjoined = dict(markers), {}
532
+ else:
533
+ # An explicit join source (summary / --join-map) must cover every
534
+ # cited slug; a slug it does not cover is reported as such — NEVER
535
+ # compared via an identity guess, which would silently pass a slug
536
+ # that coincidentally equals a citation_key (§3.3).
537
+ in_text, unjoined = {}, {}
538
+ for slug, loc in markers.items():
539
+ if slug in join:
540
+ in_text.setdefault(join[slug], loc)
541
+ else:
542
+ unjoined.setdefault(slug, loc)
543
+ return _compare_sets(
544
+ in_text, reference_keys, signal_class="deterministic",
545
+ in_text_label="joined marker path",
546
+ reference_label=reference_label, unjoined=unjoined), "joined_marker"
547
+
548
+ # Fallback path (§3.3): no markers — non-ARS or post-converted source.
549
+ # Format-aware best-effort extraction, heuristic-classed (advisory-only).
550
+ metadata = _parse_bib_metadata(bibs)
551
+ if passport:
552
+ for k, v in _corpus_metadata(passport).items():
553
+ metadata.setdefault(k, set()).update(v)
554
+ in_text, unresolved = _extract_fallback(manuscripts, metadata)
555
+ return _compare_sets(
556
+ in_text, reference_keys, signal_class="heuristic",
557
+ in_text_label="best-effort extraction",
558
+ reference_label=reference_label, unjoined=unresolved,
559
+ unjoined_label="unmatched against any reference metadata"
560
+ ), "best_effort"
561
+
562
+
563
+ _FAMILY_B_IDS = tuple(
564
+ cid for cid, (fam, _fc, _sc) in sorted(_CHECK_REGISTRY.items())
565
+ if fam == "venue_limits")
566
+
567
+ _WORD_COUNT_TOLERANCE = 1.02 # §3.2: ±2% before fail (format-conversion noise)
568
+
569
+ _MD_HEADING_RE = re.compile(r"^(#{1,6})\s+(.+?)\s*#*\s*$", re.MULTILINE)
570
+ _KEYWORDS_LINE_RE = re.compile(
571
+ r"^\s*(?:\*\*|__)?\s*keywords?\s*(?:\*\*|__)?\s*[::]\s*(.+?)\s*$",
572
+ re.IGNORECASE | re.MULTILINE)
573
+ _TEX_KEYWORDS_RE = re.compile(r"\\keywords\s*\{([^}]*)\}", re.IGNORECASE)
574
+ _TEX_SECTION_RE = re.compile(r"\\(?:sub)*section\*?\s*\{([^}]*)\}")
575
+ _TEX_ABSTRACT_RE = re.compile(
576
+ r"\\begin\{abstract\}(.*?)\\end\{abstract\}", re.DOTALL)
577
+ _TEX_BIBLIO_RE = re.compile(
578
+ r"\\begin\{thebibliography\}.*?\\end\{thebibliography\}|\\bibliography\s*\{[^}]*\}",
579
+ re.DOTALL)
580
+ _HTML_COMMENT_RE = re.compile(r"<!--.*?-->", re.DOTALL)
581
+
582
+
583
+ def _word_count(text: str) -> int:
584
+ """Canonical whitespace-split (shared/references/word_count_conventions.md)."""
585
+ return len(text.split())
586
+
587
+
588
+ def _md_sections(text: str) -> list[tuple[str, int, int]]:
589
+ """[(heading title, content start, content end)] per markdown section."""
590
+ heads = list(_MD_HEADING_RE.finditer(text))
591
+ return [(m.group(2).strip(),
592
+ m.end(),
593
+ heads[i + 1].start() if i + 1 < len(heads) else len(text))
594
+ for i, m in enumerate(heads)]
595
+
596
+
597
+ def _is_abstract_title(title: str) -> bool:
598
+ return title.lower().strip("*_ ").startswith("abstract")
599
+
600
+
601
+ def _is_refs_title(title: str) -> bool:
602
+ return title.lower().strip("*_ ") in _REFS_TITLES
603
+
604
+
605
+ def _md_drop_sections(text: str, title_predicates) -> str:
606
+ """Remove every section (heading included) whose title matches any
607
+ predicate."""
608
+ heads = list(_MD_HEADING_RE.finditer(text))
609
+ keep, cursor = [], 0
610
+ for i, m in enumerate(heads):
611
+ end = heads[i + 1].start() if i + 1 < len(heads) else len(text)
612
+ if any(p(m.group(2).strip()) for p in title_predicates):
613
+ keep.append(text[cursor:m.start()])
614
+ cursor = end
615
+ keep.append(text[cursor:])
616
+ return "".join(keep)
617
+
618
+
619
+ def _detex(text: str) -> str:
620
+ """Naive detex (§10 item 4, adjudicated at slice 2: naive detex +
621
+ whitespace-split, the method is DECLARED in the report and never promised
622
+ venue-exact): drop comments and \\commands, unwrap braces/brackets."""
623
+ text = re.sub(r"(?<!\\)%.*", " ", text)
624
+ text = re.sub(r"\\[a-zA-Z]+\*?", " ", text)
625
+ return re.sub(r"[{}\[\]]", " ", text)
626
+
627
+
628
+ def _countable_body(rel: str, text: str, scope: str) -> tuple[str, str]:
629
+ """(countable text, human description of what was counted) for B1."""
630
+ if rel.lower().endswith(".tex"):
631
+ if scope == "body_only":
632
+ return (_detex(_TEX_BIBLIO_RE.sub(
633
+ " ", _TEX_ABSTRACT_RE.sub(" ", text))),
634
+ "naive detex; abstract + bibliography excluded")
635
+ if scope == "body_plus_references":
636
+ return (_detex(_TEX_ABSTRACT_RE.sub(" ", text)),
637
+ "naive detex; abstract excluded")
638
+ return _detex(text), "naive detex; everything counted"
639
+ text = _HTML_COMMENT_RE.sub(" ", text)
640
+ if scope == "body_only":
641
+ return (_md_drop_sections(_KEYWORDS_LINE_RE.sub(" ", text),
642
+ (_is_abstract_title, _is_refs_title)),
643
+ "abstract + references + keywords line excluded")
644
+ if scope == "body_plus_references":
645
+ return (_md_drop_sections(_KEYWORDS_LINE_RE.sub(" ", text),
646
+ (_is_abstract_title,)),
647
+ "abstract + keywords line excluded")
648
+ # `all` counts everything the author wrote — only the ARS tool markers
649
+ # (HTML comments) are stripped, and that is declared.
650
+ return text, "everything counted (tool markers stripped)"
651
+
652
+
653
+ def _abstract_text(rel: str, text: str) -> Optional[str]:
654
+ if rel.lower().endswith(".tex"):
655
+ m = _TEX_ABSTRACT_RE.search(text)
656
+ return _detex(m.group(1)) if m else None
657
+ for title, start, end in _md_sections(text):
658
+ if _is_abstract_title(title):
659
+ body = _HTML_COMMENT_RE.sub(" ", text[start:end])
660
+ return _KEYWORDS_LINE_RE.sub(" ", body)
661
+ return None
662
+
663
+
664
+ def _keyword_list(text: str) -> Optional[list[str]]:
665
+ m = _KEYWORDS_LINE_RE.search(text) or _TEX_KEYWORDS_RE.search(text)
666
+ if not m:
667
+ return None
668
+ return [k for k in re.split(r"[,;、;]", m.group(1)) if k.strip()]
669
+
670
+
671
+ def _headings(rel: str, text: str) -> list[str]:
672
+ if rel.lower().endswith(".tex"):
673
+ return [m.group(1).strip() for m in _TEX_SECTION_RE.finditer(text)]
674
+ return [t for t, _s, _e in _md_sections(text)]
675
+
676
+
677
+ _CANONICAL_MANUSCRIPT_STEMS = frozenset({"paper", "manuscript", "main"})
678
+ _NON_MANUSCRIPT_PREFIXES = (
679
+ "cover_letter", "cover-letter", "response", "rebuttal", "readme")
680
+
681
+
682
+ def _primary_manuscript(manuscripts: dict[str, str]
683
+ ) -> tuple[Optional[str], Optional[str], Optional[str]]:
684
+ """(rel, text, blocked_reason) — the manuscript the limits are checked
685
+ against; rel/text are None iff blocked_reason says why. Canonical
686
+ filenames (paper/manuscript/main) win; known package-document names
687
+ (cover letters, response letters, READMEs) are excluded; with several
688
+ remaining non-canonical candidates the verifier reports ambiguity instead
689
+ of silently picking the wordiest (it could be an appendix or a response
690
+ letter). Which file was counted is declared in every detail string."""
691
+ if not manuscripts:
692
+ return None, None, _NO_MANUSCRIPT_REASON
693
+ candidates = {
694
+ rel: t for rel, t in manuscripts.items()
695
+ if not Path(rel).name.lower().startswith(_NON_MANUSCRIPT_PREFIXES)}
696
+ candidates = candidates or manuscripts
697
+ canonical = {rel: t for rel, t in candidates.items()
698
+ if Path(rel).stem.lower() in _CANONICAL_MANUSCRIPT_STEMS}
699
+ pool = canonical or candidates
700
+ if not canonical and len(candidates) > 1:
701
+ return None, None, (
702
+ "ambiguous manuscript: several candidates and none carries a "
703
+ f"canonical name (paper/manuscript/main): "
704
+ f"{', '.join(sorted(candidates))} — rename the manuscript or "
705
+ "remove the extras")
706
+ rel = max(sorted(pool), key=lambda r: _word_count(pool[r]))
707
+ return rel, pool[rel], None
708
+
709
+
710
+ def _ceiling_check(check_id: str, count: int, limit: int, what: str,
711
+ location: Optional[str] = None,
712
+ tolerance: float = 1.0) -> dict[str, Any]:
713
+ tol_note = " (±2% tolerance)" if tolerance > 1.0 else ""
714
+ status = "pass" if count <= limit * tolerance else "fail"
715
+ return _check(check_id, status,
716
+ f"{what}: {count} vs declared limit {limit}{tol_note}",
717
+ location=location)
718
+
719
+
720
+ def run_family_b(manuscripts: dict[str, str],
721
+ reference_keys: set[str], reference_label: str,
722
+ profile: Optional[dict[str, Any]]
723
+ ) -> list[dict[str, Any]]:
724
+ """Family B: venue-declared limits vs actuals (§3.2). Without a profile,
725
+ every check is NOT-CHECKED — limits are never guessed from the journal
726
+ name (R-L3-2-D mirror). A partially-declared profile runs the checks it
727
+ can and NOT-CHECKEDs the rest (§4)."""
728
+ if profile is None:
729
+ return [_check(i, "not_checked",
730
+ "no venue profile declared — limits are never guessed "
731
+ "from the journal name (R-L3-2-D mirror)")
732
+ for i in _FAMILY_B_IDS]
733
+
734
+ checks: list[dict[str, Any]] = []
735
+ rel, text, no_manuscript_reason = _primary_manuscript(manuscripts)
736
+
737
+ def not_declared(check_id: str, field: str) -> dict[str, Any]:
738
+ return _check(check_id, "not_checked",
739
+ f"{field} not declared in venue profile")
740
+
741
+ # B1 — manuscript word count vs word_limit (±2%, §3.2)
742
+ word_limit = profile.get("word_limit")
743
+ if word_limit is None:
744
+ checks.append(not_declared("B1", "word_limit"))
745
+ elif rel is None:
746
+ checks.append(_check("B1", "not_checked", no_manuscript_reason))
747
+ else:
748
+ scope = profile.get("word_count_scope")
749
+ scope_decl = scope or "body_only (default — scope not declared)"
750
+ body, counted_desc = _countable_body(rel, text, scope or "body_only")
751
+ checks.append(_ceiling_check(
752
+ "B1", _word_count(body), word_limit,
753
+ f"manuscript word count of {rel}, scope {scope_decl} "
754
+ f"({counted_desc}; whitespace-split per "
755
+ f"shared/references/word_count_conventions.md)",
756
+ location=rel, tolerance=_WORD_COUNT_TOLERANCE))
757
+
758
+ # B2 — abstract word count vs abstract_word_limit (±2%)
759
+ abstract_limit = profile.get("abstract_word_limit")
760
+ if abstract_limit is None:
761
+ checks.append(not_declared("B2", "abstract_word_limit"))
762
+ elif rel is None:
763
+ checks.append(_check("B2", "not_checked", no_manuscript_reason))
764
+ else:
765
+ abstract = _abstract_text(rel, text)
766
+ if abstract is None:
767
+ checks.append(_check(
768
+ "B2", "not_checked",
769
+ f"no abstract section found in {rel}", location=rel))
770
+ else:
771
+ checks.append(_ceiling_check(
772
+ "B2", _word_count(abstract), abstract_limit,
773
+ f"abstract word count of {rel} (whitespace-split)",
774
+ location=rel, tolerance=_WORD_COUNT_TOLERANCE))
775
+
776
+ # B3 — keyword count vs keyword_range (exact)
777
+ keyword_range = profile.get("keyword_range")
778
+ if keyword_range is None:
779
+ checks.append(not_declared("B3", "keyword_range"))
780
+ elif rel is None:
781
+ checks.append(_check("B3", "not_checked", no_manuscript_reason))
782
+ else:
783
+ keywords = _keyword_list(text)
784
+ if keywords is None:
785
+ checks.append(_check(
786
+ "B3", "not_checked",
787
+ f"no keywords line found in {rel}", location=rel))
788
+ else:
789
+ lo, hi = keyword_range["min"], keyword_range["max"]
790
+ ok = lo <= len(keywords) <= hi
791
+ checks.append(_check(
792
+ "B3", "pass" if ok else "fail",
793
+ f"keyword count of {rel}: {len(keywords)} vs declared range "
794
+ f"{lo}–{hi}", location=rel))
795
+
796
+ # B4 — required sections present (set comparison)
797
+ required = profile.get("required_sections")
798
+ if required is None:
799
+ checks.append(not_declared("B4", "required_sections"))
800
+ elif rel is None:
801
+ checks.append(_check("B4", "not_checked", no_manuscript_reason))
802
+ else:
803
+ headings = [h.lower() for h in _headings(rel, text)]
804
+ missing = [s for s in required
805
+ if not any(s.lower() in h for h in headings)]
806
+ if missing:
807
+ checks.append(_check(
808
+ "B4", "fail",
809
+ f"required section(s) missing from {rel} (case-insensitive "
810
+ f"heading containment): {', '.join(missing)}", location=rel))
811
+ else:
812
+ checks.append(_check(
813
+ "B4", "pass",
814
+ f"all {len(required)} required section(s) present in {rel}",
815
+ location=rel))
816
+
817
+ # B5 — reference count vs reference_limit (exact)
818
+ reference_limit = profile.get("reference_limit")
819
+ if reference_limit is None:
820
+ checks.append(not_declared("B5", "reference_limit"))
821
+ elif not reference_keys:
822
+ checks.append(_check("B5", "not_checked", _NO_REFERENCE_LIST_REASON))
823
+ else:
824
+ checks.append(_ceiling_check(
825
+ "B5", len(reference_keys), reference_limit,
826
+ f"reference entries in {reference_label}"))
827
+
828
+ return checks
829
+
830
+
831
+ _PROFILE_SCHEMA_PATH = (
832
+ Path(__file__).resolve().parent.parent / "shared" / "contracts"
833
+ / "submission" / "venue_profile.schema.json")
834
+ _profile_schema_cache: Optional[dict[str, Any]] = None
835
+
836
+
837
+ def _profile_schema() -> dict[str, Any]:
838
+ """The formal venue-profile contract, loaded once. The validator derives
839
+ its allowed-field set and enums FROM the schema file so the contract has
840
+ one source of truth (a schema edit cannot silently desync the CLI gate)."""
841
+ global _profile_schema_cache
842
+ if _profile_schema_cache is None:
843
+ _profile_schema_cache = json.loads(
844
+ _PROFILE_SCHEMA_PATH.read_text(encoding="utf-8"))
845
+ return _profile_schema_cache
846
+
847
+
848
+ def _schema_enum(field: str) -> tuple:
849
+ return tuple(v for v in _profile_schema()["properties"][field]["enum"]
850
+ if v is not None)
851
+
852
+
853
+ def _is_int(v: Any) -> bool:
854
+ """A real integer — bool is an int subclass and must not pass as one."""
855
+ return isinstance(v, int) and not isinstance(v, bool)
856
+
857
+
858
+ def _validate_venue_profile(raw: dict[str, Any]) -> dict[str, Any]:
859
+ """Shape validation for a --venue-profile file. The allowed-field set and
860
+ enums are derived from the formal contract
861
+ (shared/contracts/submission/venue_profile.schema.json,
862
+ additionalProperties false included) so a malformed or typoed profile is a
863
+ usage error, never a silently-skewed or silently-skipped comparison."""
864
+ allowed = set(_profile_schema()["properties"])
865
+ unknown = set(raw) - allowed
866
+ if unknown:
867
+ raise ValueError(
868
+ f"venue profile has unknown field(s) {sorted(unknown)} — the "
869
+ f"schema is closed (a typoed limit would otherwise be silently "
870
+ f"ignored); allowed: {sorted(allowed)}")
871
+ if raw.get("declared_by") != "scholar":
872
+ raise ValueError(
873
+ "venue profile must carry `declared_by: scholar` — the profile is "
874
+ "scholar-declared only, never scraped or inferred (spec §4)")
875
+ name = raw.get("venue_name")
876
+ if name is not None and not isinstance(name, str):
877
+ raise ValueError(f"venue profile venue_name must be a string or null, "
878
+ f"got {name!r}")
879
+ for field in ("word_limit", "abstract_word_limit", "reference_limit"):
880
+ v = raw.get(field)
881
+ if v is not None and (not _is_int(v) or v < 1):
882
+ raise ValueError(f"venue profile {field} must be a positive "
883
+ f"integer or null, got {v!r}")
884
+ scope = raw.get("word_count_scope")
885
+ if scope is not None and scope not in _schema_enum("word_count_scope"):
886
+ raise ValueError(f"venue profile word_count_scope must be one of "
887
+ f"{'/'.join(_schema_enum('word_count_scope'))}/null, "
888
+ f"got {scope!r}")
889
+ blind = raw.get("blind_review")
890
+ if blind is not None and blind not in _schema_enum("blind_review"):
891
+ raise ValueError(f"venue profile blind_review must be one of "
892
+ f"{'/'.join(_schema_enum('blind_review'))}/null, "
893
+ f"got {blind!r}")
894
+ kr = raw.get("keyword_range")
895
+ if kr is not None:
896
+ if (not isinstance(kr, dict) or set(kr) != {"min", "max"}
897
+ or not _is_int(kr.get("min")) or kr["min"] < 0
898
+ or not _is_int(kr.get("max")) or kr["max"] < 1
899
+ or kr["min"] > kr["max"]):
900
+ raise ValueError(
901
+ f"venue profile keyword_range must be {{min >= 0, max >= 1}} "
902
+ f"integers with min <= max, got {kr!r}")
903
+ sections = raw.get("required_sections")
904
+ if sections is not None and (
905
+ not isinstance(sections, list)
906
+ or not all(isinstance(s, str) and s for s in sections)):
907
+ raise ValueError("venue profile required_sections must be a list of "
908
+ "non-empty strings or null")
909
+ ack = raw.get("acknowledgments_forbidden_in_blind")
910
+ if ack is not None and not isinstance(ack, bool):
911
+ raise ValueError(
912
+ f"venue profile acknowledgments_forbidden_in_blind must be a "
913
+ f"boolean or null, got {ack!r}")
914
+ return raw
915
+
916
+
917
+ # --- Family A: blind-review residue (§3.1) -----------------------------------
918
+
919
+ _FAMILY_A_IDS = tuple(
920
+ cid for cid, (fam, _fc, _sc) in sorted(_CHECK_REGISTRY.items())
921
+ if fam == "blind_review_residue")
922
+ _SCAN_A_IDS = ("A1", "A2", "A3", "A4", "A5", "A6") # the variant-scanning subset
923
+
924
+ # Filename convention for the anonymized (blind-review) variant: a stem token
925
+ # match, so `paper_anonymized.docx` / `manuscript-blind.pdf` classify but
926
+ # `canonical.md` does not.
927
+ _BLIND_STEM_TOKENS = frozenset(
928
+ {"anonymized", "anonymised", "anonymous", "anon", "blind", "blinded"})
929
+ # Only manuscript-class artifacts count as "the blind variant" — a
930
+ # blind-named ancillary file (blind_survey.csv) is not a blind manuscript
931
+ # and must not satisfy A7 under a declared double-blind venue.
932
+ _VARIANT_SUFFIXES = (".md", ".tex", ".txt", ".docx", ".pdf")
933
+
934
+ # A5 self-citation phrasing (§3.1 — heuristic by class). zh-TW list curated
935
+ # by the maintainer (#394 follow-up, 2026-06-10). 本文作者先前 is anchored on
936
+ # the 本文 prefix because bare 作者先前 matches 該作者先前 (a third party).
937
+ _SELF_CITATION_PHRASES = (
938
+ "our previous work", "our earlier study", "our prior work",
939
+ "we previously showed", "we have previously", "in our previous",
940
+ "我們先前的研究", "我們過去的研究", "我們先前曾", "我們已於先前",
941
+ "筆者先前的研究", "本研究團隊先前", "本文作者先前",
942
+ )
943
+
944
+ _ACK_TITLE_RE = re.compile(r"acknowledg(?:e)?ments?|致謝", re.IGNORECASE)
945
+
946
+
947
+ def _is_anonymized_name(rel: str) -> bool:
948
+ if not rel.lower().endswith(_VARIANT_SUFFIXES):
949
+ return False
950
+ tokens = re.split(r"[-_.\s]+", Path(rel).stem.lower())
951
+ return any(t in _BLIND_STEM_TOKENS for t in tokens)
952
+
953
+
954
+ def _a4_strict(profile: Optional[dict[str, Any]]) -> bool:
955
+ """A4 is strict-eligible ONLY when the venue profile explicitly declares
956
+ acknowledgments must be removed from the blind version (§3.1 load-bearing
957
+ — the deterministic signal stays advisory otherwise because the
958
+ de-anonymization judgment is the scholar's). Single definition so EVERY
959
+ A4 emission path (pass/fail/not_checked/not_applicable) agrees."""
960
+ return bool(profile) and (
961
+ profile.get("acknowledgments_forbidden_in_blind") is True)
962
+
963
+
964
+ def _package_files(package_dir: Path) -> list[str]:
965
+ return sorted(
966
+ p.relative_to(package_dir).as_posix()
967
+ for p in package_dir.rglob("*")
968
+ if p.is_file() and p.name not in _SCAN_EXCLUDED_NAMES)
969
+
970
+
971
+ def _blanket_family_a(ids, status: str, reason: str,
972
+ profile: Optional[dict[str, Any]]
973
+ ) -> list[dict[str, Any]]:
974
+ """One emitter for every whole-family blanket path (untriggered
975
+ not_applicable / no-variant not_checked), so A4's profile-conditional
976
+ eligibility cannot diverge between them."""
977
+ return [_check(i, status, reason,
978
+ strict_eligible=_a4_strict(profile) if i == "A4" else None)
979
+ for i in ids]
980
+
981
+
982
+ def run_family_a(package_dir: Path, manuscripts: dict[str, str],
983
+ profile: Optional[dict[str, Any]]
984
+ ) -> list[dict[str, Any]]:
985
+ """Family A: blind-review residue scan (§3.1). Trigger is
986
+ presence-or-declaration: an anonymized variant in the package, or a
987
+ declared double-blind venue. Untriggered = not_applicable (the package
988
+ has no blind submission set to protect)."""
989
+ declared_double = bool(profile) and profile.get("blind_review") == "double"
990
+ all_rels = _package_files(package_dir)
991
+ anon_rels = [r for r in all_rels if _is_anonymized_name(r)]
992
+
993
+ if not anon_rels and not declared_double:
994
+ return _blanket_family_a(
995
+ _FAMILY_A_IDS, "not_applicable",
996
+ "not triggered: no anonymized variant in the package and no "
997
+ "declared double-blind review (§3.1 presence-or-declaration)",
998
+ profile)
999
+
1000
+ checks: list[dict[str, Any]] = []
1001
+ if declared_double and not anon_rels:
1002
+ checks.append(_check(
1003
+ "A7", "fail",
1004
+ "venue profile declares double-blind review but the package "
1005
+ "contains no anonymized manuscript variant "
1006
+ f"({'/'.join(_VARIANT_SUFFIXES)} with a name token among "
1007
+ f"{'/'.join(sorted(_BLIND_STEM_TOKENS))}) — the blind version "
1008
+ "is missing (the most basic residue of all, §3.1)"))
1009
+ checks.extend(_blanket_family_a(
1010
+ _SCAN_A_IDS, "not_checked", "no anonymized variant to scan",
1011
+ profile))
1012
+ return checks
1013
+
1014
+ checks.append(_check(
1015
+ "A7", "pass",
1016
+ f"anonymized variant present: {', '.join(anon_rels)}"))
1017
+ checks.extend(_scan_blind_set(
1018
+ package_dir, manuscripts, all_rels, anon_rels, profile))
1019
+ return checks
1020
+
1021
+
1022
+ def _scan_blind_set(package_dir: Path, manuscripts: dict[str, str],
1023
+ all_rels: list[str], anon_rels: list[str],
1024
+ profile: Optional[dict[str, Any]]
1025
+ ) -> list[dict[str, Any]]:
1026
+ """A1-A6 over the blind submission set (the anonymized variants)."""
1027
+ checks: list[dict[str, Any]] = []
1028
+ pdf_rels = [r for r in anon_rels if r.lower().endswith(".pdf")]
1029
+ docx_rels = [r for r in anon_rels if r.lower().endswith(".docx")]
1030
+ text_rels = [r for r in anon_rels if r in manuscripts]
1031
+
1032
+ checks.extend(_pdf_metadata_checks(package_dir, pdf_rels))
1033
+ checks.extend(_docx_residue_checks(package_dir, docx_rels))
1034
+ checks.extend(_text_residue_checks(manuscripts, text_rels, profile))
1035
+ checks.append(_filename_leakage_check(package_dir, all_rels, anon_rels))
1036
+ return checks
1037
+
1038
+
1039
+ def _residue_verdict(check_id: str, findings: "list[tuple[str, str]]",
1040
+ unreadable: list[str], what: str) -> dict[str, Any]:
1041
+ """fail on any (rel, message) finding; otherwise not_checked if any
1042
+ artifact was unreadable (incompleteness is never folded into pass, §1.4);
1043
+ else pass."""
1044
+ if findings:
1045
+ listed = "; ".join(f"{rel}: {msg}"
1046
+ for rel, msg in findings[:_LOCATION_CAP])
1047
+ return _check(check_id, "fail",
1048
+ f"{what} in the blind submission set: {listed}",
1049
+ location=findings[0][0])
1050
+ if unreadable:
1051
+ return _check(check_id, "not_checked",
1052
+ f"unreadable artifact(s), {what} could not be scanned: "
1053
+ f"{', '.join(unreadable[:_LOCATION_CAP])}")
1054
+ return _check(check_id, "pass", f"no {what} in the blind submission set")
1055
+
1056
+
1057
+ def _pdf_metadata_checks(package_dir: Path,
1058
+ pdf_rels: list[str]) -> list[dict[str, Any]]:
1059
+ """A1: PDF info dict /Author + XMP dc:creator non-empty (§3.1)."""
1060
+ if not pdf_rels:
1061
+ return [_check("A1", "not_applicable",
1062
+ "no PDF in the blind submission set")]
1063
+ if pypdf is None:
1064
+ return [_check("A1", "not_checked",
1065
+ "parser unavailable: pypdf is not installed (pip "
1066
+ "install pypdf) — PDF metadata cannot be scanned")]
1067
+ findings: "list[tuple[str, str]]" = []
1068
+ unreadable: list[str] = []
1069
+ for rel in pdf_rels:
1070
+ try:
1071
+ reader = pypdf.PdfReader(str(package_dir / rel))
1072
+ author = _pdf_info_author(reader)
1073
+ creators: list[str] = []
1074
+ try:
1075
+ xmp = reader.xmp_metadata
1076
+ creators = [c for c in (xmp.dc_creator or []) if str(c).strip()
1077
+ ] if xmp else []
1078
+ except Exception:
1079
+ # A malformed XMP stream means HALF of A1's signal could not
1080
+ # be read — incompleteness, not a silent pass (§1.4). The
1081
+ # info-dict half still contributes findings if present.
1082
+ unreadable.append(f"{rel} (XMP stream)")
1083
+ except Exception:
1084
+ unreadable.append(rel)
1085
+ continue
1086
+ if author:
1087
+ findings.append((rel, f"/Author={author!r}"))
1088
+ if creators:
1089
+ findings.append((rel, f"XMP dc:creator={creators!r}"))
1090
+ return [_residue_verdict("A1", findings, unreadable,
1091
+ "PDF metadata author(s)")]
1092
+
1093
+
1094
+ def _pdf_info_author(reader) -> str:
1095
+ """The PDF info-dict /Author — the one definition A1 and the A6 token
1096
+ harvest share."""
1097
+ meta = reader.metadata
1098
+ return str(meta.get("/Author") or "").strip() if meta else ""
1099
+
1100
+
1101
+ def _read_zip_part(z: "zipfile.ZipFile", name: str) -> bytes:
1102
+ """Bounded read of one zip part (the zip-bomb guard): the declared
1103
+ uncompressed size must stay under _MAX_XML_PART_BYTES."""
1104
+ if z.getinfo(name).file_size > _MAX_XML_PART_BYTES:
1105
+ raise ValueError(f"zip part {name} over the size limit")
1106
+ return z.read(name)
1107
+
1108
+
1109
+ def _open_docx_guarded(path: Path) -> "zipfile.ZipFile":
1110
+ """Open a .docx with the zip-bomb entry-count guard applied — the one
1111
+ opening path for every DOCX reader (A2/A3 and the A6 token harvest), so
1112
+ the guards cannot drift apart."""
1113
+ z = zipfile.ZipFile(path)
1114
+ if len(z.namelist()) > _MAX_ZIP_ENTRIES:
1115
+ z.close()
1116
+ raise ValueError("zip entry count over limit")
1117
+ return z
1118
+
1119
+
1120
+ def _docx_core_author_fields(z: "zipfile.ZipFile") -> "list[tuple[str, str]]":
1121
+ """[(label, value)] for the non-empty author fields of docProps/core.xml
1122
+ — the one extraction A2 and the A6 token harvest share."""
1123
+ if "docProps/core.xml" not in set(z.namelist()):
1124
+ return []
1125
+ root = _xml_fromstring(_read_zip_part(z, "docProps/core.xml"))
1126
+ fields = []
1127
+ for tag, label in ((_NS_DC + "creator", "creator"),
1128
+ (_NS_CP + "lastModifiedBy", "lastModifiedBy")):
1129
+ el = root.find(tag)
1130
+ if el is not None and (el.text or "").strip():
1131
+ fields.append((label, el.text.strip()))
1132
+ return fields
1133
+
1134
+
1135
+ # OOXML namespaces for the raw-structure DOCX scan. Read with stdlib
1136
+ # zipfile+ElementTree rather than python-docx: the parts we need
1137
+ # (docProps/core.xml, word/*.xml author attributes) are plain XML, and a
1138
+ # stdlib reader means the DOCX residue class has NO missing-parser
1139
+ # NOT-CHECKED hole at all (§1.3/§1.4; recorded as a spec §9 slice-3
1140
+ # refinement — pypdf remains the only new dependency).
1141
+ _NS_DC = "{http://purl.org/dc/elements/1.1/}"
1142
+ _NS_CP = ("{http://schemas.openxmlformats.org/package/2006/"
1143
+ "metadata/core-properties}")
1144
+ _NS_W = ("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}")
1145
+
1146
+
1147
+ def _docx_residue_checks(package_dir: Path,
1148
+ docx_rels: list[str]) -> list[dict[str, Any]]:
1149
+ """A2: docProps/core.xml creator/lastModifiedBy non-empty. A3: w:ins /
1150
+ w:del / w:comment author attributes in document parts (§3.1)."""
1151
+ if not docx_rels:
1152
+ return [_check("A2", "not_applicable",
1153
+ "no DOCX in the blind submission set"),
1154
+ _check("A3", "not_applicable",
1155
+ "no DOCX in the blind submission set")]
1156
+ meta_findings: "list[tuple[str, str]]" = []
1157
+ rev_findings: "list[tuple[str, str]]" = []
1158
+ unreadable: list[str] = []
1159
+ for rel in docx_rels:
1160
+ try:
1161
+ with _open_docx_guarded(package_dir / rel) as z:
1162
+ for label, value in _docx_core_author_fields(z):
1163
+ meta_findings.append((rel, f"{label}={value!r}"))
1164
+ for part in sorted(n for n in z.namelist()
1165
+ if n.startswith("word/")
1166
+ and n.endswith(".xml")):
1167
+ root = _xml_fromstring(_read_zip_part(z, part))
1168
+ for el in root.iter():
1169
+ if el.tag in (_NS_W + "ins", _NS_W + "del",
1170
+ _NS_W + "comment"):
1171
+ author = (el.get(_NS_W + "author") or "").strip()
1172
+ if author:
1173
+ kind = el.tag.split("}", 1)[1]
1174
+ rev_findings.append(
1175
+ (rel, f"w:{kind} author={author!r}"))
1176
+ except _XML_READ_ERRORS:
1177
+ unreadable.append(rel)
1178
+ return [
1179
+ _residue_verdict("A2", meta_findings, unreadable,
1180
+ "DOCX metadata author(s)"),
1181
+ _residue_verdict("A3", rev_findings, unreadable,
1182
+ "DOCX revision/comment author(s)"),
1183
+ ]
1184
+
1185
+
1186
+ def _text_residue_checks(manuscripts: dict[str, str], text_rels: list[str],
1187
+ profile: Optional[dict[str, Any]]
1188
+ ) -> list[dict[str, Any]]:
1189
+ """A4 (acknowledgments section) + A5 (self-citation phrasing) over the
1190
+ text-form anonymized variants."""
1191
+ checks: list[dict[str, Any]] = []
1192
+ a4_strict = _a4_strict(profile)
1193
+ if not text_rels:
1194
+ # The blind variant exists but only in a form we cannot read headings
1195
+ # from — the scan SHOULD run and cannot: not_checked honesty (§1.4),
1196
+ # never a not_applicable masquerade.
1197
+ checks.append(_check(
1198
+ "A4", "not_checked",
1199
+ "acknowledgments scan requires a text-form (md/tex/txt) "
1200
+ "anonymized variant; DOCX/PDF heading extraction is not supported",
1201
+ strict_eligible=a4_strict))
1202
+ checks.append(_check("A5", "not_checked",
1203
+ "no extractable text in the blind submission set"))
1204
+ return checks
1205
+
1206
+ ack_hits = []
1207
+ for rel in text_rels:
1208
+ for title in _headings(rel, manuscripts[rel]):
1209
+ if _ACK_TITLE_RE.search(title):
1210
+ ack_hits.append((rel, title))
1211
+ break
1212
+ if ack_hits:
1213
+ rel, title = ack_hits[0]
1214
+ checks.append(_check(
1215
+ "A4", "fail",
1216
+ f"acknowledgments section present in the anonymized variant "
1217
+ f"(heading {title!r}) — whether it de-anonymizes is the scholar's "
1218
+ f"judgment (§3.1); flagged, never auto-removed"
1219
+ + (" [venue declares acknowledgments forbidden in the blind "
1220
+ "version]" if a4_strict else ""),
1221
+ location=rel, strict_eligible=a4_strict))
1222
+ else:
1223
+ checks.append(_check(
1224
+ "A4", "pass",
1225
+ "no acknowledgments section in the anonymized variant",
1226
+ strict_eligible=a4_strict))
1227
+
1228
+ phrase_hits = []
1229
+ for rel in text_rels:
1230
+ lowered = manuscripts[rel].lower()
1231
+ for phrase in _SELF_CITATION_PHRASES:
1232
+ if phrase.lower() in lowered:
1233
+ phrase_hits.append((rel, phrase))
1234
+ if phrase_hits:
1235
+ rel, phrase = phrase_hits[0]
1236
+ listed = ", ".join(sorted({p for _r, p in phrase_hits})[:_LOCATION_CAP])
1237
+ checks.append(_check(
1238
+ "A5", "fail",
1239
+ f"self-citation phrasing in the anonymized variant: {listed} — "
1240
+ f"legitimate prose can false-positive (heuristic, advisory-only)",
1241
+ signal_class="heuristic", location=rel))
1242
+ else:
1243
+ checks.append(_check(
1244
+ "A5", "pass",
1245
+ "no self-citation phrasing detected in the anonymized variant",
1246
+ signal_class="heuristic"))
1247
+ return checks
1248
+
1249
+
1250
+ def _author_metadata_strings(package_dir: Path,
1251
+ rels: list[str]) -> list[str]:
1252
+ """Author strings from PDF /Author + DOCX core.xml creator/lastModifiedBy
1253
+ of the given artifacts — the SAME extraction (and zip guards) as A1/A2,
1254
+ consumed best-effort (A6 is heuristic by class, so unreadable artifacts
1255
+ are simply skipped here)."""
1256
+ authors: list[str] = []
1257
+ for rel in rels:
1258
+ low = rel.lower()
1259
+ try:
1260
+ if low.endswith(".docx"):
1261
+ with _open_docx_guarded(package_dir / rel) as z:
1262
+ authors.extend(
1263
+ value for _label, value in _docx_core_author_fields(z))
1264
+ elif low.endswith(".pdf") and pypdf is not None:
1265
+ author = _pdf_info_author(
1266
+ pypdf.PdfReader(str(package_dir / rel)))
1267
+ if author:
1268
+ authors.append(author)
1269
+ except Exception:
1270
+ continue
1271
+ return authors
1272
+
1273
+
1274
+ def _filename_leakage_check(package_dir: Path, all_rels: list[str],
1275
+ anon_rels: list[str]) -> dict[str, Any]:
1276
+ """A6 (§3.1): author-name tokens from the NON-anonymized variant's
1277
+ metadata appearing in package filenames. Heuristic by class (coincidental
1278
+ name tokens false-positive). The metadata-source originals themselves are
1279
+ not scanned — their identified filenames are expected."""
1280
+ anon = set(anon_rels)
1281
+ original_rels = [r for r in all_rels
1282
+ if r not in anon
1283
+ and r.lower().endswith((".docx", ".pdf"))]
1284
+ authors = _author_metadata_strings(package_dir, original_rels)
1285
+ if not authors:
1286
+ return _check(
1287
+ "A6", "not_checked",
1288
+ "no author metadata available on the non-anonymized artifacts to "
1289
+ "derive name tokens from")
1290
+ # NOTE: not `\w` — underscore is a word char, and `smith_appendix` must
1291
+ # split into tokens. Token-to-token comparison under one delimiter model:
1292
+ # `smith` flags `smith_appendix.csv` but never `blacksmith_notes.md`.
1293
+ def name_tokens(s: str) -> set:
1294
+ return {t for t in re.split(r"[^a-z0-9]+", s.lower()) if t}
1295
+
1296
+ tokens = {t for a in authors for t in name_tokens(a) if len(t) >= 3}
1297
+ originals = set(original_rels)
1298
+ scanned = [r for r in all_rels if r not in originals]
1299
+ hits = sorted(
1300
+ r for r in scanned if tokens & name_tokens(Path(r).name))
1301
+ if hits:
1302
+ return _check(
1303
+ "A6", "fail",
1304
+ f"author-name token(s) in package filename(s): "
1305
+ f"{', '.join(hits[:_LOCATION_CAP])} (tokens from "
1306
+ f"{', '.join(original_rels[:_LOCATION_CAP])} metadata) — "
1307
+ f"coincidental tokens can false-positive (heuristic)",
1308
+ location=hits[0])
1309
+ return _check(
1310
+ "A6", "pass",
1311
+ "no author-name tokens from the non-anonymized metadata appear in "
1312
+ "package filenames")
1313
+
1314
+
1315
+ def run_checks(package_dir: Path,
1316
+ passport: Optional[dict[str, Any]] = None,
1317
+ join_map: Optional[dict[str, str]] = None,
1318
+ venue_profile: Optional[dict[str, Any]] = None
1319
+ ) -> tuple[list[dict[str, Any]], str]:
1320
+ """Collect the package texts once and run every check family.
1321
+ Returns (checks sorted by id, extraction_path)."""
1322
+ manuscripts, bibs = _collect_package_texts(package_dir)
1323
+ bib_keys = parse_bib_keys(bibs)
1324
+ reference_keys, reference_label = _reference_list(bib_keys, passport)
1325
+ checks_c, extraction_path = run_family_c(
1326
+ manuscripts, bibs, bib_keys, reference_keys, reference_label,
1327
+ passport=passport, join_map=join_map)
1328
+ checks_b = run_family_b(
1329
+ manuscripts, reference_keys, reference_label, venue_profile)
1330
+ checks_a = run_family_a(package_dir, manuscripts, venue_profile)
1331
+ return (sorted(checks_a + checks_b + checks_c, key=lambda c: c["id"]),
1332
+ extraction_path)
1333
+
1334
+
1335
+ def _report_relpath(package_dir: Path,
1336
+ report_path: Optional[Path]) -> Optional[str]:
1337
+ """Package-relative posix path of the report file, or None when the
1338
+ report lives outside the package (nothing extra to exclude from the
1339
+ fingerprint). Single home for the resolve-relative logic so the
1340
+ stamping side (build_report) and the freshness side (check_freshness)
1341
+ can never drift apart on what they exclude."""
1342
+ if report_path is None:
1343
+ return None
1344
+ try:
1345
+ return report_path.resolve().relative_to(
1346
+ package_dir.resolve()).as_posix()
1347
+ except ValueError:
1348
+ return None
1349
+
1350
+
1351
+ def build_report(package_dir: Path, checks: list[dict[str, Any]],
1352
+ extraction_path: str,
1353
+ report_path: Optional[Path] = None,
1354
+ policy_slug: Optional[str] = None,
1355
+ inputs_fingerprint: Optional[str] = None) -> dict[str, Any]:
1356
+ emitted = {c["id"] for c in checks}
1357
+ if emitted != set(_CHECK_REGISTRY):
1358
+ # Roster guard (§1.4/#349): a runner that silently omits a registered
1359
+ # check would read as "covered"; fail loud instead.
1360
+ raise ValueError(
1361
+ f"check roster mismatch: emitted {sorted(emitted)}, "
1362
+ f"registered {sorted(_CHECK_REGISTRY)}")
1363
+ report_relpath = _report_relpath(package_dir, report_path)
1364
+ return {
1365
+ "header": {
1366
+ "extraction_path": extraction_path,
1367
+ "not_checked_count": sum(
1368
+ 1 for c in checks if c["status"] == "not_checked"),
1369
+ "package_fingerprint": compute_package_fingerprint(
1370
+ package_dir, report_relpath),
1371
+ # Gate-2 P1: external inputs (venue profile / passport / join
1372
+ # map) shape Family B/C verdicts; the freshness guard compares
1373
+ # this against the reusing invocation's inputs.
1374
+ "inputs_fingerprint": (inputs_fingerprint
1375
+ or compute_inputs_fingerprint(
1376
+ None, None, None)),
1377
+ # §5.2/§5.3: the value handed down by the policy evaluator via
1378
+ # --policy; None = standalone unevaluated run (the argparse
1379
+ # default is None, never "advisory" — a null-stamped report can
1380
+ # never satisfy pipeline freshness).
1381
+ "policy_slug": policy_slug,
1382
+ },
1383
+ "checks": checks,
1384
+ }
1385
+
1386
+
1387
+ def render_human(report: dict[str, Any]) -> str:
1388
+ h = report["header"]
1389
+ lines = [
1390
+ "submission package verification "
1391
+ f"(extraction: {h['extraction_path']}, "
1392
+ f"not-checked: {h['not_checked_count']}, "
1393
+ f"fingerprint: {h['package_fingerprint'][:12]}…)",
1394
+ ]
1395
+ for c in report["checks"]:
1396
+ status = c["status"].upper().replace("_", "-")
1397
+ loc = f" @ {c['location']}" if c["location"] else ""
1398
+ lines.append(
1399
+ f" [{status}] {c['id']} ({c['family']}, {c['signal_class']})"
1400
+ f"{loc}: {c['detail']}")
1401
+ return "\n".join(lines)
1402
+
1403
+
1404
+ def exit_code_for(report: dict[str, Any]) -> int:
1405
+ statuses = {c["status"] for c in report["checks"]}
1406
+ if "fail" in statuses:
1407
+ return 1
1408
+ if "not_checked" in statuses:
1409
+ return 3 # "passed what was checkable" (§8) — distinct from a full pass
1410
+ return 0
1411
+
1412
+
1413
+ def evaluate_policy(report: dict[str, Any],
1414
+ policy: Optional[str]) -> tuple[Optional[str], int]:
1415
+ """Slice-4 policy evaluation (§5.2/§5.3, applied with an
1416
+ already-resolved policy value — the orchestrator selects the policy,
1417
+ this is its deterministic tooling). The advisory/strict divergence
1418
+ lives HERE, not at the call site: any policy other than "strict"
1419
+ (advisory or None/standalone) is byte-equivalent slice-3 behavior.
1420
+
1421
+ Returns (terminal_token_line | None, exit_code). Terminal signals are
1422
+ the STDOUT TOKENS, never raw exit codes: exit 1 also covers nonterminal
1423
+ heuristic fails (no token), so automation MUST key on the token. The
1424
+ evaluator keys on STATUS (fail / not_checked) gated by strict_eligible;
1425
+ not_applicable is neither, so an untriggered family can never compose
1426
+ into a block or an incompleteness verdict (the slice-3 schema pin)."""
1427
+ if policy != "strict":
1428
+ return None, exit_code_for(report)
1429
+ strict_fails = sorted(
1430
+ c["id"] for c in report["checks"]
1431
+ if c["strict_eligible"] and c["status"] == "fail")
1432
+ if strict_fails:
1433
+ return (
1434
+ "TERMINAL-BLOCK policy=submission_package "
1435
+ f"strict_eligible_fails={','.join(strict_fails)}",
1436
+ 1,
1437
+ )
1438
+ incomplete = sorted(
1439
+ c["id"] for c in report["checks"]
1440
+ if c["strict_eligible"] and c["status"] == "not_checked")
1441
+ if incomplete:
1442
+ # Fail-closed (§5.2): a missing parser/profile must not silently
1443
+ # waive the one check class the scholar opted into blocking on.
1444
+ return (
1445
+ "VERIFICATION-INCOMPLETE "
1446
+ f"strict_eligible_not_checked={','.join(incomplete)}",
1447
+ 4,
1448
+ )
1449
+ return None, exit_code_for(report)
1450
+
1451
+
1452
+ def check_freshness(package_dir: Path, report_path: Path,
1453
+ expected_policy: str,
1454
+ expected_inputs_fingerprint: str) -> tuple[str, int]:
1455
+ """Slice-4 freshness guard (§5.2): the orchestrator MUST run this before
1456
+ ever reusing a report. Does NOT re-run checks and never writes; it
1457
+ recomputes the package fingerprint with the SAME exclusion set used at
1458
+ write time (report file + provenance_summary.md) and compares
1459
+ fingerprint + inputs fingerprint + policy_slug against what the reusing
1460
+ invocation carries. A null-stamped (standalone, unevaluated) report
1461
+ never satisfies freshness.
1462
+
1463
+ A FRESH report re-emits its policy verdict (gate-2 review P1): freshness
1464
+ alone means "the report is trustworthy", not "the package passed" — a
1465
+ fresh strict report that recorded a blocking fail prints its terminal
1466
+ token again and exits accordingly, so a verdict can never evaporate
1467
+ across a resume/reuse.
1468
+
1469
+ Returns (message, exit_code): exit 5 stale; fresh returns the
1470
+ re-evaluated verdict's exit code (0/1/3/4)."""
1471
+ if not report_path.is_file():
1472
+ return "STALE-REPORT reason=missing_report", 5
1473
+ try:
1474
+ report = json.loads(report_path.read_text(encoding="utf-8"))
1475
+ header = report["header"]
1476
+ stamped_slug = header["policy_slug"]
1477
+ stamped_fingerprint = header["package_fingerprint"]
1478
+ except (OSError, ValueError, KeyError, TypeError):
1479
+ return "STALE-REPORT reason=unreadable_report", 5
1480
+ if stamped_slug is None:
1481
+ return "STALE-REPORT reason=null_policy_slug", 5
1482
+ if stamped_slug != expected_policy:
1483
+ # !r: the slug comes from the report FILE (untrusted bytes under the
1484
+ # freshness threat model) — repr collapses embedded newlines so a
1485
+ # forged slug cannot inject a fake second token line into stdout.
1486
+ return (
1487
+ f"STALE-REPORT reason=policy_mismatch stamped={stamped_slug!r} "
1488
+ f"expected={expected_policy}",
1489
+ 5,
1490
+ )
1491
+ if header.get("inputs_fingerprint") != expected_inputs_fingerprint:
1492
+ # Covers a changed/added/dropped venue profile, passport, or join
1493
+ # map, AND a legacy report predating the field (gets None).
1494
+ return "STALE-REPORT reason=inputs_mismatch", 5
1495
+ current = compute_package_fingerprint(
1496
+ package_dir, _report_relpath(package_dir, report_path))
1497
+ if current != stamped_fingerprint:
1498
+ return "STALE-REPORT reason=fingerprint_mismatch", 5
1499
+ try:
1500
+ emitted = {c["id"] for c in report["checks"]}
1501
+ except (KeyError, TypeError):
1502
+ return "STALE-REPORT reason=unreadable_report", 5
1503
+ if emitted != set(_CHECK_REGISTRY):
1504
+ # Roster guard on REUSE (same rule build_report enforces at write
1505
+ # time): the report file is excluded from the package fingerprint,
1506
+ # so a hand-edited report (e.g. checks: []) would otherwise read as
1507
+ # fresh and re-evaluate to a clean exit — a strict verdict must not
1508
+ # evaporate through a thinned-out roster (final-round review).
1509
+ return "STALE-REPORT reason=roster_mismatch", 5
1510
+ try:
1511
+ token, code = evaluate_policy(report, expected_policy)
1512
+ except (KeyError, TypeError):
1513
+ return "STALE-REPORT reason=unreadable_report", 5
1514
+ message = f"report fresh (policy={expected_policy})"
1515
+ if token is not None:
1516
+ message = f"{message}\n{token}"
1517
+ return message, code
1518
+
1519
+
1520
+ def run(argv: Optional[list[str]] = None) -> int:
1521
+ parser = argparse.ArgumentParser(
1522
+ prog="verify_submission_package",
1523
+ description="Deterministic submission-package verifier (#394: Family "
1524
+ "C reference integrity + Family B venue limits).",
1525
+ epilog="Exit codes: 0 all-checked no-fail; 1 at least one fail; "
1526
+ "2 usage/IO error; 3 no fail but at least one NOT-CHECKED; "
1527
+ "4 VERIFICATION-INCOMPLETE (strict only: a strict-eligible "
1528
+ "check is NOT-CHECKED — fail-closed); 5 STALE-REPORT "
1529
+ "(--check-freshness: fingerprint, inputs, or policy-slug "
1530
+ "mismatch). A FRESH report under --check-freshness re-emits "
1531
+ "its verdict — token + exit semantics identical to a live "
1532
+ "run; 'fresh' alone is never a pass. Terminal signals for "
1533
+ "automation are the stdout tokens (TERMINAL-BLOCK / "
1534
+ "VERIFICATION-INCOMPLETE / STALE-REPORT), NEVER raw exit "
1535
+ "codes — exit 1 also covers nonterminal heuristic fails.")
1536
+ parser.add_argument("package_dir", help="Output package directory to verify.")
1537
+ parser.add_argument(
1538
+ "--passport", default=None,
1539
+ help="Material Passport YAML supplying citation_verification_summary[] "
1540
+ "(the prose-reference join) and/or literature_corpus[] (the "
1541
+ "declared reference list).")
1542
+ parser.add_argument(
1543
+ "--join-map", default=None,
1544
+ help="Explicit scholar-supplied {ref_slug: citation_key} YAML/JSON "
1545
+ "mapping (overrides every other join source).")
1546
+ parser.add_argument(
1547
+ "--venue-profile", default=None,
1548
+ help="Scholar-declared venue profile YAML (schema shared/contracts/"
1549
+ "submission/venue_profile.schema.json) enabling the Family B "
1550
+ "limits checks. Absent: every Family B check reports "
1551
+ "NOT-CHECKED(no venue profile) — never guessed from the "
1552
+ "journal name.")
1553
+ parser.add_argument(
1554
+ "--report-out", default=None,
1555
+ help=f"Report path (default: <package_dir>/{REPORT_BASENAME}).")
1556
+ parser.add_argument(
1557
+ "--policy", choices=("advisory", "strict"), default=None,
1558
+ help="Already-resolved terminal-policy value handed down by the "
1559
+ "policy evaluator (the orchestrator reads terminal_policies "
1560
+ "and resolves key absence to advisory; this script never reads "
1561
+ "the passport's policy block, §5.3). Stamps header.policy_slug. "
1562
+ "Absent: standalone unevaluated run, policy_slug stays null.")
1563
+ parser.add_argument(
1564
+ "--check-freshness", action="store_true",
1565
+ help="Do not run checks; verify the existing report is fresh: its "
1566
+ "package_fingerprint still matches the package bytes and its "
1567
+ "policy_slug matches --policy (REQUIRED with this flag). "
1568
+ "Stale or null-stamped → STALE-REPORT + exit 5 (§5.2: never "
1569
+ "reuse a stale report).")
1570
+ args = parser.parse_args(argv)
1571
+
1572
+ package_dir = Path(args.package_dir)
1573
+ if not package_dir.is_dir():
1574
+ print(f"[verify_submission_package ERROR] not a directory: "
1575
+ f"{package_dir}", file=sys.stderr)
1576
+ return 2
1577
+
1578
+ if args.check_freshness:
1579
+ if args.policy is None:
1580
+ print("[verify_submission_package ERROR] --check-freshness "
1581
+ "requires --policy: freshness is always relative to an "
1582
+ "expected policy, never free-floating.", file=sys.stderr)
1583
+ return 2
1584
+ report_path = (Path(args.report_out) if args.report_out
1585
+ else package_dir / REPORT_BASENAME)
1586
+ try:
1587
+ expected_inputs = compute_inputs_fingerprint(
1588
+ args.venue_profile, args.join_map, args.passport)
1589
+ except OSError as e:
1590
+ print(f"[verify_submission_package ERROR] could not read an "
1591
+ f"input file for the freshness comparison: {e}",
1592
+ file=sys.stderr)
1593
+ return 2
1594
+ message, code = check_freshness(package_dir, report_path,
1595
+ args.policy, expected_inputs)
1596
+ print(message)
1597
+ return code
1598
+
1599
+ passport = None
1600
+ if args.passport is not None:
1601
+ try:
1602
+ passport = _load_yaml(Path(args.passport))
1603
+ except (OSError, ValueError, yaml.YAMLError) as e:
1604
+ print(f"[verify_submission_package ERROR] could not load passport: "
1605
+ f"{e}", file=sys.stderr)
1606
+ return 2
1607
+
1608
+ join_map = None
1609
+ if args.join_map is not None:
1610
+ try:
1611
+ raw = _load_yaml(Path(args.join_map))
1612
+ except (OSError, ValueError, yaml.YAMLError) as e:
1613
+ print(f"[verify_submission_package ERROR] could not load join map: "
1614
+ f"{e}", file=sys.stderr)
1615
+ return 2
1616
+ join_map = {str(slug): str(key) for slug, key in raw.items()}
1617
+
1618
+ venue_profile = None
1619
+ if args.venue_profile is not None:
1620
+ try:
1621
+ venue_profile = _validate_venue_profile(
1622
+ _load_yaml(Path(args.venue_profile)))
1623
+ except (OSError, ValueError, yaml.YAMLError) as e:
1624
+ print(f"[verify_submission_package ERROR] could not load venue "
1625
+ f"profile: {e}", file=sys.stderr)
1626
+ return 2
1627
+
1628
+ checks, extraction_path = run_checks(
1629
+ package_dir, passport=passport, join_map=join_map,
1630
+ venue_profile=venue_profile)
1631
+ report_path = (Path(args.report_out) if args.report_out
1632
+ else package_dir / REPORT_BASENAME)
1633
+ report = build_report(package_dir, checks, extraction_path,
1634
+ report_path=report_path,
1635
+ policy_slug=args.policy,
1636
+ inputs_fingerprint=compute_inputs_fingerprint(
1637
+ args.venue_profile, args.join_map,
1638
+ args.passport))
1639
+
1640
+ try:
1641
+ report_path.write_text(
1642
+ json.dumps(report, indent=2, ensure_ascii=False) + "\n",
1643
+ encoding="utf-8")
1644
+ except OSError as e:
1645
+ print(f"[verify_submission_package ERROR] could not write report: {e}",
1646
+ file=sys.stderr)
1647
+ return 2
1648
+
1649
+ print(render_human(report))
1650
+ token, code = evaluate_policy(report, args.policy)
1651
+ if token is not None:
1652
+ print(token)
1653
+ return code
1654
+
1655
+
1656
+ if __name__ == "__main__":
1657
+ sys.exit(run())