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,1778 @@
1
+ """Schema-validation tests for v3.8 claim_ref_alignment_audit_agent (T-S1..T-S8).
2
+
3
+ Per spec §7.1 in
4
+ docs/design/2026-05-15-issue-103-claim-alignment-audit-spec.md.
5
+
6
+ The test plan section §7.1 lists T-S1..T-S8 keyed to claim_audit_result
7
+ (§3.1) and claim_intent_manifest (§3.2) and uncited_assertion (§3.3).
8
+ §6 lint coverage is broader: it includes claim_drift D-INV-1..4 (§3.4),
9
+ constraint_violation CV-INV-1..4 (§3.5) and audit_sampling_summary
10
+ S-INV-1..4 (§4 step 3). This file follows §6 because that is the lint
11
+ contract under test — drift / constraint-violation / sampling
12
+ invariants get their own pos/neg fixtures alongside INV / M-INV / U-INV
13
+ so the lint's full 38-invariant surface is covered before the agent
14
+ prompt ships in Step 5.
15
+
16
+ Spec §7 names the test file `tests/test_claim_audit_schema.py`. This
17
+ repo's CI workflows discover tests under `scripts/test_*.py` and the
18
+ 30+ existing test files all live there; we honor the repo convention
19
+ and keep the spec name's stem (`test_claim_audit_schema`) so anyone
20
+ greppping the spec lands at the right file.
21
+
22
+ Run:
23
+ python -m unittest scripts.test_claim_audit_schema -v
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import copy
28
+ import json
29
+ import unittest
30
+ from pathlib import Path
31
+ from typing import Any
32
+
33
+ from tests.test_helpers import (
34
+ build_schema_validator,
35
+ load_json_schema,
36
+ run_script,
37
+ )
38
+
39
+ REPO = Path(__file__).resolve().parent.parent
40
+ PASSPORT = REPO / "shared/contracts/passport"
41
+ LINT = REPO / "scripts/check_claim_audit_consistency.py"
42
+
43
+ SCHEMA_PATHS: dict[str, Path] = {
44
+ "claim_audit_result": PASSPORT / "claim_audit_result.schema.json",
45
+ "claim_intent_manifest": PASSPORT / "claim_intent_manifest.schema.json",
46
+ "uncited_assertion": PASSPORT / "uncited_assertion.schema.json",
47
+ "claim_drift": PASSPORT / "claim_drift.schema.json",
48
+ "constraint_violation": PASSPORT / "constraint_violation.schema.json",
49
+ "uncited_audit_failure": PASSPORT / "uncited_audit_failure.schema.json",
50
+ }
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Canonical fixture builders. Each helper returns a fresh dict so individual
55
+ # tests can mutate one field without leaking to siblings.
56
+ # ---------------------------------------------------------------------------
57
+
58
+ MANIFEST_ID = "M-2026-05-15T10:00:00Z-a1b2"
59
+ MANIFEST_ID_OTHER = "M-2026-05-15T10:05:00Z-c3d4"
60
+ SENTINEL_MANIFEST_ID = "M-0000-00-00T00:00:00Z-0000"
61
+ AUDIT_RUN_ID = "2026-05-15T10:10:00Z-9f8e"
62
+
63
+
64
+ def supported_entry() -> dict[str, Any]:
65
+ """Minimal SUPPORTED row — INV-1 positive baseline."""
66
+ return {
67
+ "claim_id": "C-001",
68
+ "scoped_manifest_id": MANIFEST_ID,
69
+ "claim_text": "Sample preprints accounted for 67% of corpus.",
70
+ "ref_slug": "smith2024preprints",
71
+ "anchor_kind": "page",
72
+ "anchor_value": "12",
73
+ "judgment": "SUPPORTED",
74
+ "audit_status": "completed",
75
+ "defect_stage": None,
76
+ "rationale": "The cited page reports the 67% figure verbatim.",
77
+ "judge_model": "gpt-5.5-xhigh",
78
+ "judge_run_at": "2026-05-15T10:11:00Z",
79
+ "ref_retrieval_method": "api",
80
+ "audit_run_id": AUDIT_RUN_ID,
81
+ }
82
+
83
+
84
+ def manifest_entry(
85
+ manifest_id: str = MANIFEST_ID,
86
+ *,
87
+ emitted_by: str = "synthesis_agent",
88
+ claims: list[dict[str, Any]] | None = None,
89
+ mncs: list[dict[str, str]] | None = None,
90
+ ) -> dict[str, Any]:
91
+ """Build a minimal claim_intent_manifest entry."""
92
+ return {
93
+ "manifest_version": "1.0",
94
+ "manifest_id": manifest_id,
95
+ "emitted_by": emitted_by,
96
+ "emitted_at": "2026-05-15T09:55:00Z",
97
+ "claims": claims
98
+ if claims is not None
99
+ else [
100
+ {
101
+ "claim_id": "C-001",
102
+ "claim_text": "Sample preprints accounted for 67% of corpus.",
103
+ "intended_evidence_kind": "empirical",
104
+ "planned_refs": ["smith2024preprints"],
105
+ }
106
+ ],
107
+ "manifest_negative_constraints": mncs or [],
108
+ }
109
+
110
+
111
+ def uncited_assertion_entry() -> dict[str, Any]:
112
+ """Minimal U-INV-* positive baseline."""
113
+ return {
114
+ "finding_id": "UA-001",
115
+ "sentence_text": "Half of all submissions showed positive results.",
116
+ "section_path": "3. Results > 3.1 Overview",
117
+ "trigger_tokens": ["50%", "showed"],
118
+ "detected_at": "2026-05-15T10:12:00Z",
119
+ "rule_version": "D4-c-v1",
120
+ }
121
+
122
+
123
+ def claim_drift_entry(*, drift_kind: str = "EMITTED_NOT_INTENDED") -> dict[str, Any]:
124
+ """Minimal D-INV-* positive baseline."""
125
+ base: dict[str, Any] = {
126
+ "finding_id": "CD-001",
127
+ "drift_kind": drift_kind,
128
+ "claim_text": "Drifted prose sentence the writer added without manifesting.",
129
+ "detected_at": "2026-05-15T10:13:00Z",
130
+ "rule_version": "D4-a-v1",
131
+ }
132
+ if drift_kind == "EMITTED_NOT_INTENDED":
133
+ base["section_path"] = "4. Discussion > 4.2 Implications"
134
+ base["manifest_claim_id"] = None
135
+ base["scoped_manifest_id"] = None
136
+ else: # INTENDED_NOT_EMITTED
137
+ base["manifest_claim_id"] = "C-001"
138
+ base["scoped_manifest_id"] = MANIFEST_ID
139
+ return base
140
+
141
+
142
+ def constraint_violation_entry(
143
+ *,
144
+ constraint_id: str = "MNC-1",
145
+ manifest_claim_id: str | None = None,
146
+ ) -> dict[str, Any]:
147
+ """Minimal CV-INV-* positive baseline."""
148
+ return {
149
+ "finding_id": "CV-001",
150
+ "claim_text": "We observed causality between A and B.",
151
+ "section_path": "4. Discussion > 4.3 Limitations",
152
+ "violated_constraint_id": constraint_id,
153
+ "scoped_manifest_id": MANIFEST_ID,
154
+ "manifest_claim_id": manifest_claim_id,
155
+ "judge_verdict": "VIOLATED",
156
+ "rationale": "The MNC bars causal language without RCT evidence.",
157
+ "judge_model": "gpt-5.5-xhigh",
158
+ "judge_run_at": "2026-05-15T10:14:00Z",
159
+ "rule_version": "D4-a-v1",
160
+ }
161
+
162
+
163
+ def sampling_summary_entry(
164
+ *,
165
+ total: int = 150,
166
+ cap: int = 100,
167
+ ) -> dict[str, Any]:
168
+ """Minimal S-INV-* positive baseline (sampled run)."""
169
+ audited = list(range(0, total, max(1, total // cap)))[:cap]
170
+ return {
171
+ "audit_run_id": AUDIT_RUN_ID,
172
+ "max_claims_per_paper": cap,
173
+ "total_citation_count": total,
174
+ "audited_count": len(audited),
175
+ "audited_indices": audited,
176
+ "sampling_strategy": "stratified_buckets_v1",
177
+ "emitted_at": "2026-05-15T10:15:00Z",
178
+ }
179
+
180
+
181
+ def uncited_audit_failure_entry(
182
+ *,
183
+ finding_id: str = "UAF-001",
184
+ fault_class: str = "judge_timeout",
185
+ manifest_claim_id: str | None = None,
186
+ ) -> dict[str, Any]:
187
+ """Minimal UAF-INV-* positive baseline (v3.8.2 / #118)."""
188
+ return {
189
+ "finding_id": finding_id,
190
+ "claim_text": "We observed causality between A and B.",
191
+ "section_path": "4. Discussion > 4.3 Limitations",
192
+ "scoped_manifest_id": MANIFEST_ID,
193
+ "manifest_claim_id": manifest_claim_id,
194
+ "fault_class": fault_class,
195
+ "rationale": f"{fault_class}: judge invocation failed after 30s",
196
+ "judge_model": "gpt-5.5-xhigh",
197
+ "judge_run_at": "2026-05-15T10:14:00Z",
198
+ "rule_version": "D4-c-v1-uaf-v1",
199
+ }
200
+
201
+
202
+ def build_passport(
203
+ *,
204
+ manifests: list[dict[str, Any]] | None = None,
205
+ results: list[dict[str, Any]] | None = None,
206
+ uncited: list[dict[str, Any]] | None = None,
207
+ drifts: list[dict[str, Any]] | None = None,
208
+ violations: list[dict[str, Any]] | None = None,
209
+ samplings: list[dict[str, Any]] | None = None,
210
+ uaf: list[dict[str, Any]] | None = None,
211
+ ) -> dict[str, Any]:
212
+ """Build a minimal passport JSON wrapping the seven aggregates the lint reads (six pre-v3.8.2 + uncited_audit_failures[])."""
213
+ return {
214
+ "claim_intent_manifests": manifests if manifests is not None else [manifest_entry()],
215
+ "claim_audit_results": results or [],
216
+ "uncited_assertions": uncited or [],
217
+ "claim_drifts": drifts or [],
218
+ "constraint_violations": violations or [],
219
+ "audit_sampling_summaries": samplings or [],
220
+ "uncited_audit_failures": uaf or [],
221
+ }
222
+
223
+
224
+ def write_passport(tmp: Path, body: dict[str, Any]) -> Path:
225
+ path = tmp / "passport.json"
226
+ path.write_text(json.dumps(body, ensure_ascii=False, indent=2), encoding="utf-8")
227
+ return path
228
+
229
+
230
+ def run_lint(passport: Path) -> tuple[int, str, str]:
231
+ """Invoke the lint subprocess; returns (exit_code, stdout, stderr)."""
232
+ proc = run_script(
233
+ LINT,
234
+ "--passport",
235
+ str(passport),
236
+ extra_env={"PYTHONPATH": str(REPO)},
237
+ )
238
+ return proc.returncode, proc.stdout, proc.stderr
239
+
240
+
241
+ # ---------------------------------------------------------------------------
242
+ # T-S1: Schema-shape only — minimal SUPPORTED entry validates against the
243
+ # claim_audit_result schema. No lint invocation; pure Draft 2020-12 validate.
244
+ # ---------------------------------------------------------------------------
245
+
246
+ class TS1ValidMinimalEntry(unittest.TestCase):
247
+ """T-S1: Valid minimal entry validates (SUPPORTED, all required fields)."""
248
+
249
+ def test_supported_entry_validates(self) -> None:
250
+ schema = load_json_schema(SCHEMA_PATHS["claim_audit_result"])
251
+ validator = build_schema_validator(schema)
252
+ errors = list(validator.iter_errors(supported_entry()))
253
+ self.assertEqual(errors, [], msg=f"unexpected validation errors: {errors}")
254
+
255
+ def test_all_five_schemas_parse_as_draft_2020_12(self) -> None:
256
+ for name, path in SCHEMA_PATHS.items():
257
+ with self.subTest(schema=name):
258
+ load_json_schema(path)
259
+
260
+ def test_subclaim_breakdown_optional_field_validates(self) -> None:
261
+ """T-S9 (#213): sub_claim_breakdown is an additive optional field."""
262
+ schema = load_json_schema(SCHEMA_PATHS["claim_audit_result"])
263
+ validator = build_schema_validator(schema)
264
+ entry = supported_entry()
265
+ entry["judgment"] = "UNSUPPORTED"
266
+ entry["defect_stage"] = "source_description"
267
+ entry["sub_claim_breakdown"] = [
268
+ {
269
+ "sub_claim_text": "n-values are reported",
270
+ "sub_verdict": "SUPPORTED",
271
+ "evidence_pointer": "p.4 Table 1",
272
+ },
273
+ {
274
+ "sub_claim_text": "reporting is consistent across models",
275
+ "sub_verdict": "UNSUPPORTED",
276
+ "evidence_pointer": None,
277
+ },
278
+ ]
279
+ errors = list(validator.iter_errors(entry))
280
+ self.assertEqual(errors, [], msg=f"unexpected validation errors: {errors}")
281
+
282
+ def test_subclaim_breakdown_rejects_unknown_subfield(self) -> None:
283
+ """additionalProperties:false holds inside each breakdown item.
284
+
285
+ Two valid items satisfy minItems:2 so the ONLY violation is the bogus
286
+ subfield — this isolates additionalProperties:false from the array-length
287
+ constraint (round-2 review #2: a one-item fixture also tripped minItems,
288
+ so it would still pass even if additionalProperties were removed).
289
+ """
290
+ schema = load_json_schema(SCHEMA_PATHS["claim_audit_result"])
291
+ validator = build_schema_validator(schema)
292
+ entry = supported_entry()
293
+ entry["judgment"] = "UNSUPPORTED"
294
+ entry["defect_stage"] = "source_description"
295
+ entry["sub_claim_breakdown"] = [
296
+ {"sub_claim_text": "x", "sub_verdict": "SUPPORTED", "bogus": 1},
297
+ {"sub_claim_text": "y", "sub_verdict": "UNSUPPORTED"},
298
+ ]
299
+ errors = list(validator.iter_errors(entry))
300
+ self.assertNotEqual(errors, [], msg="expected unknown subfield to be rejected")
301
+ self.assertTrue(
302
+ any(e.validator == "additionalProperties" for e in errors),
303
+ msg=f"expected an additionalProperties error, got: {[e.validator for e in errors]}",
304
+ )
305
+
306
+
307
+ # ---------------------------------------------------------------------------
308
+ # Lint-driven invariant tests share a base that writes a tmp passport,
309
+ # invokes the lint subprocess, and asserts findings.
310
+ # ---------------------------------------------------------------------------
311
+
312
+ class _LintTestBase(unittest.TestCase):
313
+ def setUp(self) -> None:
314
+ # Each test owns its tmpdir so concurrent unittest runners don't collide.
315
+ import tempfile
316
+
317
+ self._tmp_obj = tempfile.TemporaryDirectory()
318
+ self.addCleanup(self._tmp_obj.cleanup)
319
+ self.tmp = Path(self._tmp_obj.name)
320
+
321
+ def assertLintFinds(
322
+ self,
323
+ passport_body: dict[str, Any],
324
+ *,
325
+ invariant: str,
326
+ msg: str | None = None,
327
+ ) -> tuple[int, str]:
328
+ """Assert lint exits non-zero AND stdout contains `invariant` tag."""
329
+ path = write_passport(self.tmp, passport_body)
330
+ code, out, err = run_lint(path)
331
+ full_msg = (
332
+ f"\nexpected lint to flag {invariant}\n"
333
+ f"--- exit={code} ---\nstdout:\n{out}\nstderr:\n{err}\n"
334
+ )
335
+ self.assertEqual(code, 1, msg=full_msg)
336
+ self.assertIn(invariant, out, msg=full_msg)
337
+ return code, out
338
+
339
+ def assertLintClean(
340
+ self,
341
+ passport_body: dict[str, Any],
342
+ *,
343
+ msg: str | None = None,
344
+ ) -> None:
345
+ """Assert lint exits 0 (no findings) on a passport that should pass."""
346
+ path = write_passport(self.tmp, passport_body)
347
+ code, out, err = run_lint(path)
348
+ self.assertEqual(
349
+ code,
350
+ 0,
351
+ msg=(msg or "")
352
+ + f"\nexpected lint clean\nexit={code}\nstdout:\n{out}\nstderr:\n{err}\n",
353
+ )
354
+
355
+
356
+ # ---------------------------------------------------------------------------
357
+ # T-S2: INV-1..INV-19 paired positive + negative fixtures (claim_audit_result).
358
+ # Each invariant is one subTest; baseline = SUPPORTED entry, negative cases
359
+ # mutate the field combination the invariant forbids.
360
+ # ---------------------------------------------------------------------------
361
+
362
+ class TS2ClaimAuditInvariants(_LintTestBase):
363
+ """T-S2: each INV-N paired positive/negative fixture."""
364
+
365
+ # ----- Positive: every INV holds when the canonical SUPPORTED row stands.
366
+ def test_inv_baseline_positive(self) -> None:
367
+ self.assertLintClean(build_passport(results=[supported_entry()]))
368
+
369
+ # ----- Negative cases per invariant.
370
+ def test_inv_1_supported_with_non_null_defect(self) -> None:
371
+ # INV-1: SUPPORTED -> defect_stage=null AND violated_constraint_id=null
372
+ e = supported_entry()
373
+ e["defect_stage"] = "source_description"
374
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-1")
375
+
376
+ def test_inv_2_unsupported_with_null_defect(self) -> None:
377
+ # INV-2: UNSUPPORTED -> defect_stage != null
378
+ e = supported_entry()
379
+ e["judgment"] = "UNSUPPORTED"
380
+ e["defect_stage"] = None
381
+ e["rationale"] = "Mismatch found in source description."
382
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-2")
383
+
384
+ def test_inv_3_ambiguous_with_disallowed_defect(self) -> None:
385
+ # INV-3: AMBIGUOUS -> defect_stage NOT in {metadata, negative_constraint_violation}
386
+ e = supported_entry()
387
+ e["judgment"] = "AMBIGUOUS"
388
+ e["defect_stage"] = "metadata"
389
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-3")
390
+
391
+ def test_inv_4_retrieval_failed_inconclusive_wrong_defect(self) -> None:
392
+ # INV-4: RETRIEVAL_FAILED + inconclusive -> defect_stage=not_applicable
393
+ e = supported_entry()
394
+ e["judgment"] = "RETRIEVAL_FAILED"
395
+ e["audit_status"] = "inconclusive"
396
+ e["defect_stage"] = "retrieval_existence"
397
+ e["ref_retrieval_method"] = "failed"
398
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-4")
399
+
400
+ def test_inv_5_retrieval_failed_completed_wrong_defect(self) -> None:
401
+ # INV-5: RETRIEVAL_FAILED + completed -> defect_stage=retrieval_existence
402
+ e = supported_entry()
403
+ e["judgment"] = "RETRIEVAL_FAILED"
404
+ e["audit_status"] = "completed"
405
+ e["defect_stage"] = "source_description"
406
+ e["ref_retrieval_method"] = "not_found"
407
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-5")
408
+
409
+ def test_inv_6_anchor_none_without_rationale_prefix(self) -> None:
410
+ # INV-6: anchor_kind=none -> rationale starts with the canonical prefix
411
+ e = supported_entry()
412
+ e["anchor_kind"] = "none"
413
+ e["anchor_value"] = ""
414
+ e["judgment"] = "RETRIEVAL_FAILED"
415
+ e["audit_status"] = "inconclusive"
416
+ e["defect_stage"] = "not_applicable"
417
+ e["ref_retrieval_method"] = "not_attempted"
418
+ e["rationale"] = "anchor missing for this claim" # no v3.7.3 prefix
419
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-6")
420
+
421
+ def test_inv_6_anchor_none_with_non_empty_anchor_value(self) -> None:
422
+ # INV-6: anchor_kind=none MUST carry empty sentinel anchor_value.
423
+ # A stale residual value (e.g. "123") violates the schema contract.
424
+ # Step 13 R1 Gemini finding (a832d3f).
425
+ e = supported_entry()
426
+ e["anchor_kind"] = "none"
427
+ e["anchor_value"] = "123" # stale residual — must be rejected
428
+ e["judgment"] = "RETRIEVAL_FAILED"
429
+ e["audit_status"] = "inconclusive"
430
+ e["defect_stage"] = "not_applicable"
431
+ e["ref_retrieval_method"] = "not_attempted"
432
+ e["rationale"] = (
433
+ "v3.7.3 R-L3-1-A violation: cited claim C-001 carries anchor=none; "
434
+ "v3.7.3 finalizer should have gate-refused upstream."
435
+ )
436
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-6")
437
+
438
+ def test_inv_7_constraint_violation_without_violated_id(self) -> None:
439
+ # INV-7: negative_constraint_violation -> violated_constraint_id != null
440
+ e = supported_entry()
441
+ e["judgment"] = "UNSUPPORTED"
442
+ e["defect_stage"] = "negative_constraint_violation"
443
+ e["rationale"] = "Violated declared negative constraint."
444
+ e["violated_constraint_id"] = None
445
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-7")
446
+
447
+ def test_inv_8_constraint_violation_wrong_judgment(self) -> None:
448
+ # INV-8: negative_constraint_violation -> judgment=UNSUPPORTED
449
+ e = supported_entry()
450
+ e["judgment"] = "AMBIGUOUS"
451
+ e["defect_stage"] = "negative_constraint_violation"
452
+ e["violated_constraint_id"] = "MNC-1"
453
+ e["rationale"] = "Ambiguous violation of MNC-1."
454
+ # INV-3 also fires here; we assert INV-8 specifically.
455
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-8")
456
+
457
+ def test_inv_9_dispute_on_not_applicable(self) -> None:
458
+ # INV-9: upstream_dispute != null -> defect_stage NOT in {null, not_applicable}
459
+ e = supported_entry()
460
+ e["judgment"] = "RETRIEVAL_FAILED"
461
+ e["audit_status"] = "inconclusive"
462
+ e["defect_stage"] = "not_applicable"
463
+ e["ref_retrieval_method"] = "failed"
464
+ e["rationale"] = "Paywalled."
465
+ e["upstream_dispute"] = "Author disputes this paywall classification."
466
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-9")
467
+
468
+ def test_inv_10_failed_method_wrong_state(self) -> None:
469
+ # INV-10: ref_retrieval_method=failed -> RETRIEVAL_FAILED + inconclusive + not_applicable
470
+ e = supported_entry()
471
+ e["ref_retrieval_method"] = "failed"
472
+ # judgment still SUPPORTED -> INV-10 violation
473
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-10")
474
+
475
+ def test_inv_11_not_attempted_without_anchor_none(self) -> None:
476
+ # INV-11: ref_retrieval_method=not_attempted iff anchor_kind=none
477
+ e = supported_entry()
478
+ e["ref_retrieval_method"] = "not_attempted"
479
+ # anchor_kind still page -> INV-11 violation
480
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-11")
481
+
482
+ def test_inv_12_not_found_wrong_state(self) -> None:
483
+ # INV-12: ref_retrieval_method=not_found iff fabricated-reference triple
484
+ e = supported_entry()
485
+ e["ref_retrieval_method"] = "not_found"
486
+ # judgment still SUPPORTED -> INV-12 violation
487
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-12")
488
+
489
+ def test_inv_13_metadata_wrong_method(self) -> None:
490
+ # INV-13: defect_stage=metadata -> ref_retrieval_method in {api, manual_pdf}
491
+ e = supported_entry()
492
+ e["judgment"] = "UNSUPPORTED"
493
+ e["defect_stage"] = "metadata"
494
+ e["rationale"] = "Author/year mismatch."
495
+ e["ref_retrieval_method"] = "failed"
496
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-13")
497
+
498
+ def test_inv_14_audit_tool_failure_without_fault_class_prefix(self) -> None:
499
+ # INV-14: ref_retrieval_method=audit_tool_failure -> rationale begins with fault-class tag
500
+ e = supported_entry()
501
+ e["judgment"] = "RETRIEVAL_FAILED"
502
+ e["audit_status"] = "inconclusive"
503
+ e["defect_stage"] = "not_applicable"
504
+ e["ref_retrieval_method"] = "audit_tool_failure"
505
+ e["rationale"] = "Something went wrong." # missing tag
506
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-14")
507
+
508
+ def test_inv_15_dangling_scoped_manifest_id(self) -> None:
509
+ # INV-15: (scoped_manifest_id, claim_id) must resolve in some manifest entry
510
+ e = supported_entry()
511
+ e["scoped_manifest_id"] = "M-2099-01-01T00:00:00Z-dead" # not in manifests
512
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-15")
513
+
514
+ def test_inv_15_sentinel_manifest_permitted(self) -> None:
515
+ # INV-15 sentinel positive case — MANIFEST-MISSING fallback row.
516
+ e = supported_entry()
517
+ e["scoped_manifest_id"] = SENTINEL_MANIFEST_ID
518
+ passport = build_passport(manifests=[], results=[e])
519
+ self.assertLintClean(
520
+ passport,
521
+ msg="sentinel scoped_manifest_id must be accepted in MANIFEST-MISSING fallback",
522
+ )
523
+
524
+ def test_inv_16_empty_anchor_value_with_non_none_kind(self) -> None:
525
+ # INV-16: anchor_kind != none -> anchor_value non-empty after strip
526
+ e = supported_entry()
527
+ e["anchor_value"] = " " # whitespace only
528
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-16")
529
+
530
+ def test_inv_16_url_encoded_whitespace_does_not_bypass(self) -> None:
531
+ # INV-16: URL-encoded whitespace (%20, %09) must NOT bypass the firm
532
+ # rule — docstring + schema both require non-empty *after* URL-decode.
533
+ # Step 13 R1 Gemini finding (a832d3f).
534
+ e = supported_entry()
535
+ e["anchor_value"] = "%20%20%09"
536
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-16")
537
+
538
+ def test_inv_16_url_encoded_non_whitespace_passes(self) -> None:
539
+ # INV-16: URL-encoded printable content must still satisfy the rule.
540
+ e = supported_entry()
541
+ e["anchor_kind"] = "quote"
542
+ e["anchor_value"] = "ten%20cited%20words" # 'ten cited words' after decode
543
+ self.assertLintClean(build_passport(results=[e]))
544
+
545
+ def test_inv_17_constraint_id_inner_hyphen_form(self) -> None:
546
+ # INV-17: NC-C{n}-{m} parse rule — NO inner hyphen between C and digits
547
+ manifest = manifest_entry(
548
+ claims=[
549
+ {
550
+ "claim_id": "C-001",
551
+ "claim_text": "Causal claim.",
552
+ "intended_evidence_kind": "empirical",
553
+ "planned_refs": [],
554
+ "negative_constraints": [
555
+ {
556
+ # malformed: NC-C-001-1 instead of NC-C001-1
557
+ "constraint_id": "NC-C-001-1",
558
+ "rule": "Must not claim causality without RCT.",
559
+ }
560
+ ],
561
+ }
562
+ ],
563
+ )
564
+ # Schema pattern itself rejects this — lint surfaces INV-17 explicitly.
565
+ passport = build_passport(manifests=[manifest], results=[])
566
+ self.assertLintFinds(passport, invariant="INV-17")
567
+
568
+ def test_inv_18_inconclusive_not_applicable_wrong_method(self) -> None:
569
+ # INV-18: (RETRIEVAL_FAILED, inconclusive, not_applicable) -> method in
570
+ # {not_attempted, failed, audit_tool_failure}. `api` is forbidden.
571
+ e = supported_entry()
572
+ e["judgment"] = "RETRIEVAL_FAILED"
573
+ e["audit_status"] = "inconclusive"
574
+ e["defect_stage"] = "not_applicable"
575
+ e["ref_retrieval_method"] = "api"
576
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-18")
577
+
578
+ # ----- INV-19 (#213): sub_claim_breakdown pins the normalized-PARTIAL shape.
579
+ def _partial_entry(self) -> dict[str, Any]:
580
+ """True-partial UNSUPPORTED row: the INV-19 positive baseline."""
581
+ e = supported_entry()
582
+ e["judgment"] = "UNSUPPORTED"
583
+ e["audit_status"] = "completed"
584
+ e["defect_stage"] = "source_description"
585
+ e["sub_claim_breakdown"] = [
586
+ {"sub_claim_text": "a", "sub_verdict": "SUPPORTED"},
587
+ {"sub_claim_text": "b", "sub_verdict": "UNSUPPORTED"},
588
+ ]
589
+ return e
590
+
591
+ def test_inv_19_valid_partial_breakdown_passes(self) -> None:
592
+ # INV-19 positive baseline: a true-partial row is clean.
593
+ self.assertLintClean(build_passport(results=[self._partial_entry()]))
594
+
595
+ def test_inv_19_breakdown_on_supported_row_flagged(self) -> None:
596
+ # INV-19: a sub_claim_breakdown on a non-UNSUPPORTED row is illegal.
597
+ e = supported_entry() # judgment=SUPPORTED, defect_stage=None
598
+ e["sub_claim_breakdown"] = [
599
+ {"sub_claim_text": "a", "sub_verdict": "SUPPORTED"},
600
+ {"sub_claim_text": "b", "sub_verdict": "UNSUPPORTED"},
601
+ ]
602
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-19")
603
+
604
+ def test_inv_19_breakdown_wrong_defect_stage_flagged(self) -> None:
605
+ # INV-19: breakdown row must carry defect_stage=source_description.
606
+ e = self._partial_entry()
607
+ e["defect_stage"] = "synthesis_overclaim"
608
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-19")
609
+
610
+ def test_inv_19_all_unsupported_breakdown_flagged(self) -> None:
611
+ # INV-19: not true-partial — needs >=1 SUPPORTED (round-1 review #2).
612
+ e = self._partial_entry()
613
+ e["sub_claim_breakdown"] = [
614
+ {"sub_claim_text": "a", "sub_verdict": "UNSUPPORTED"},
615
+ {"sub_claim_text": "b", "sub_verdict": "UNSUPPORTED"},
616
+ ]
617
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-19")
618
+
619
+ def test_inv_19_all_supported_breakdown_flagged(self) -> None:
620
+ # INV-19: not true-partial — needs >=1 non-SUPPORTED.
621
+ e = self._partial_entry()
622
+ e["sub_claim_breakdown"] = [
623
+ {"sub_claim_text": "a", "sub_verdict": "SUPPORTED"},
624
+ {"sub_claim_text": "b", "sub_verdict": "SUPPORTED"},
625
+ ]
626
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-19")
627
+
628
+ def test_inv_19_single_item_breakdown_flagged(self) -> None:
629
+ # INV-19: a decomposition with <2 items is not a true partial.
630
+ e = self._partial_entry()
631
+ e["sub_claim_breakdown"] = [
632
+ {"sub_claim_text": "a", "sub_verdict": "SUPPORTED"},
633
+ ]
634
+ # schema minItems:2 also rejects this; lint must independently flag it
635
+ # so a future schema relaxation can't silently admit a 1-item breakdown.
636
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-19")
637
+
638
+ def test_inv_19_breakdown_on_ambiguous_row_flagged(self) -> None:
639
+ # INV-19 judgment-pin isolation: AMBIGUOUS + completed + source_description
640
+ # passes INV-3 AND is in ALLOWED_MATRIX, so the ONLY invariant that can
641
+ # fire is INV-19's judgment check. This catches a partially-broken guard
642
+ # that forgets the judgment pin but keeps the defect_stage pin — which the
643
+ # SUPPORTED-row case can't catch (it also has defect_stage=None).
644
+ # (round-2 review #3.)
645
+ e = self._partial_entry()
646
+ e["judgment"] = "AMBIGUOUS" # defect_stage stays source_description
647
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-19")
648
+
649
+ def test_inv_19_missing_sub_verdict_not_counted_as_non_supported(self) -> None:
650
+ # INV-19 must NOT read a missing/out-of-enum sub_verdict as "non-SUPPORTED"
651
+ # (round-2 review #1). [SUPPORTED, <missing>] is NOT true-partial: it has no
652
+ # valid non-SUPPORTED verdict. A guard using `v != "SUPPORTED"` would wrongly
653
+ # accept it. The schema also rejects the missing required key, but INV-19 must
654
+ # independently flag the not-true-partial shape with its own tag.
655
+ e = self._partial_entry()
656
+ e["sub_claim_breakdown"] = [
657
+ {"sub_claim_text": "a", "sub_verdict": "SUPPORTED"},
658
+ {"sub_claim_text": "b"}, # sub_verdict missing -> not a valid non-SUPPORTED
659
+ ]
660
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-19")
661
+
662
+
663
+ # ---------------------------------------------------------------------------
664
+ # T-S3: anchor_kind=none + INV-6 violation paths.
665
+ # ---------------------------------------------------------------------------
666
+
667
+ class TS3AnchorNoneInv6(_LintTestBase):
668
+ """T-S3: anchor=none entries that miss rationale prefix or use wrong method."""
669
+
670
+ def _none_entry(self) -> dict[str, Any]:
671
+ e = supported_entry()
672
+ e["anchor_kind"] = "none"
673
+ e["anchor_value"] = ""
674
+ e["judgment"] = "RETRIEVAL_FAILED"
675
+ e["audit_status"] = "inconclusive"
676
+ e["defect_stage"] = "not_applicable"
677
+ e["ref_retrieval_method"] = "not_attempted"
678
+ e["rationale"] = "v3.7.3 R-L3-1-A violation: no anchor on cited claim."
679
+ return e
680
+
681
+ def test_anchor_none_positive_baseline(self) -> None:
682
+ # Canonical INV-6 compliant row should pass.
683
+ self.assertLintClean(build_passport(results=[self._none_entry()]))
684
+
685
+ def test_anchor_none_missing_rationale_prefix(self) -> None:
686
+ e = self._none_entry()
687
+ e["rationale"] = "anchor missing" # no prefix
688
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-6")
689
+
690
+ def test_anchor_none_wrong_retrieval_method(self) -> None:
691
+ e = self._none_entry()
692
+ e["ref_retrieval_method"] = "failed" # should be not_attempted
693
+ # INV-11 ↔ INV-6 mismatch — either fires; spec couples the firm rule via INV-6/11.
694
+ self.assertLintFinds(build_passport(results=[e]), invariant="INV-6")
695
+
696
+
697
+ # ---------------------------------------------------------------------------
698
+ # T-S4: M-INV-1 duplicate claim_id within ONE manifest.
699
+ # ---------------------------------------------------------------------------
700
+
701
+ class TS4ManifestInv1(_LintTestBase):
702
+ """T-S4: duplicate claim_id within one manifest is rejected; cross-manifest collision permitted."""
703
+
704
+ def test_duplicate_claim_id_within_one_manifest_rejected(self) -> None:
705
+ manifest = manifest_entry(
706
+ claims=[
707
+ {
708
+ "claim_id": "C-001",
709
+ "claim_text": "First claim.",
710
+ "intended_evidence_kind": "empirical",
711
+ "planned_refs": [],
712
+ },
713
+ {
714
+ "claim_id": "C-001", # duplicate within ONE manifest
715
+ "claim_text": "Second collision.",
716
+ "intended_evidence_kind": "empirical",
717
+ "planned_refs": [],
718
+ },
719
+ ],
720
+ )
721
+ self.assertLintFinds(
722
+ build_passport(manifests=[manifest]),
723
+ invariant="M-INV-1",
724
+ )
725
+
726
+ def test_cross_manifest_claim_id_collision_permitted(self) -> None:
727
+ manifest_a = manifest_entry(manifest_id=MANIFEST_ID)
728
+ manifest_b = manifest_entry(
729
+ manifest_id=MANIFEST_ID_OTHER,
730
+ emitted_by="draft_writer_agent",
731
+ )
732
+ self.assertLintClean(
733
+ build_passport(manifests=[manifest_a, manifest_b]),
734
+ msg="cross-manifest C-001 collision MUST be permitted (joinable pair)",
735
+ )
736
+
737
+ def test_m_inv_4_duplicate_manifest_id_across_passport_rejected(self) -> None:
738
+ manifest_a = manifest_entry(manifest_id=MANIFEST_ID)
739
+ manifest_b = manifest_entry(
740
+ manifest_id=MANIFEST_ID, # collides with manifest_a
741
+ emitted_by="draft_writer_agent",
742
+ )
743
+ self.assertLintFinds(
744
+ build_passport(manifests=[manifest_a, manifest_b]),
745
+ invariant="M-INV-4",
746
+ )
747
+
748
+
749
+ # ---------------------------------------------------------------------------
750
+ # T-S5: M-INV-2 dangling NC-C{n}-{m} (no parent claim with C-{n}).
751
+ # ---------------------------------------------------------------------------
752
+
753
+ class TS5ManifestInv2(_LintTestBase):
754
+ """T-S5: NC-C{n}-{m} must scope under a claims[] entry with claim_id=C-{n}."""
755
+
756
+ def test_dangling_claim_level_nc(self) -> None:
757
+ manifest = manifest_entry(
758
+ claims=[
759
+ {
760
+ "claim_id": "C-001",
761
+ "claim_text": "First claim.",
762
+ "intended_evidence_kind": "empirical",
763
+ "planned_refs": [],
764
+ "negative_constraints": [
765
+ {
766
+ # NC scoped to C-002, but no C-002 claim exists.
767
+ "constraint_id": "NC-C002-1",
768
+ "rule": "Mismatched parent.",
769
+ }
770
+ ],
771
+ }
772
+ ],
773
+ )
774
+ self.assertLintFinds(
775
+ build_passport(manifests=[manifest]),
776
+ invariant="M-INV-2",
777
+ )
778
+
779
+
780
+ # ---------------------------------------------------------------------------
781
+ # T-S6: M-INV-3 claim-level NC attempting to override MNC.
782
+ # ---------------------------------------------------------------------------
783
+
784
+ class TS6ManifestInv3(_LintTestBase):
785
+ """T-S6: claim-level NC cannot DROP a global MNC; ADD is permitted."""
786
+
787
+ def test_claim_level_nc_collides_with_mnc_id(self) -> None:
788
+ # M-INV-3 — claim-level constraint reusing an MNC-* id is the override
789
+ # signature the lint must reject (claim level can ADD via fresh NC-C{n}-{m}
790
+ # ids, never via re-using an MNC-* id).
791
+ manifest = manifest_entry(
792
+ claims=[
793
+ {
794
+ "claim_id": "C-001",
795
+ "claim_text": "Causal claim.",
796
+ "intended_evidence_kind": "empirical",
797
+ "planned_refs": [],
798
+ "negative_constraints": [
799
+ {
800
+ "constraint_id": "MNC-1", # disallowed at claim level
801
+ "rule": "Trying to override.",
802
+ }
803
+ ],
804
+ }
805
+ ],
806
+ mncs=[
807
+ {
808
+ "constraint_id": "MNC-1",
809
+ "rule": "Must not claim causality.",
810
+ }
811
+ ],
812
+ )
813
+ self.assertLintFinds(
814
+ build_passport(manifests=[manifest]),
815
+ invariant="M-INV-3",
816
+ )
817
+
818
+
819
+ # ---------------------------------------------------------------------------
820
+ # T-S7: U-INV-1..U-INV-4 paired positive/negative.
821
+ # ---------------------------------------------------------------------------
822
+
823
+ class TS7UncitedAssertionInvariants(_LintTestBase):
824
+ """T-S7: U-INV-1..U-INV-4 paired pos/neg fixtures."""
825
+
826
+ def test_u_baseline_positive(self) -> None:
827
+ self.assertLintClean(build_passport(uncited=[uncited_assertion_entry()]))
828
+
829
+ def test_u_inv_1_duplicate_finding_id(self) -> None:
830
+ a = uncited_assertion_entry()
831
+ b = uncited_assertion_entry() # same UA-001
832
+ self.assertLintFinds(
833
+ build_passport(uncited=[a, b]),
834
+ invariant="U-INV-1",
835
+ )
836
+
837
+ def test_u_inv_2_empty_trigger_tokens(self) -> None:
838
+ e = uncited_assertion_entry()
839
+ e["trigger_tokens"] = []
840
+ # Schema also rejects (minItems=1); lint surfaces U-INV-2 specifically.
841
+ self.assertLintFinds(
842
+ build_passport(uncited=[e]),
843
+ invariant="U-INV-2",
844
+ )
845
+
846
+ def test_u_inv_3_wrong_rule_version(self) -> None:
847
+ e = uncited_assertion_entry()
848
+ e["rule_version"] = "D4-c-v0"
849
+ # Schema const rejects; lint surfaces U-INV-3 specifically.
850
+ self.assertLintFinds(
851
+ build_passport(uncited=[e]),
852
+ invariant="U-INV-3",
853
+ )
854
+
855
+ def test_u_inv_4_orphan_manifest_pointer(self) -> None:
856
+ # manifest_claim_id set without matching manifest entry -> U-INV-4
857
+ e = uncited_assertion_entry()
858
+ e["manifest_claim_id"] = "C-999"
859
+ e["scoped_manifest_id"] = MANIFEST_ID
860
+ self.assertLintFinds(
861
+ build_passport(uncited=[e]),
862
+ invariant="U-INV-4",
863
+ )
864
+
865
+ def test_u_inv_4_null_manifest_id_with_set_claim_id(self) -> None:
866
+ # manifest_claim_id != null requires scoped_manifest_id != null.
867
+ e = uncited_assertion_entry()
868
+ e["manifest_claim_id"] = "C-001"
869
+ e["scoped_manifest_id"] = None
870
+ self.assertLintFinds(
871
+ build_passport(uncited=[e]),
872
+ invariant="U-INV-4",
873
+ )
874
+
875
+
876
+ # ---------------------------------------------------------------------------
877
+ # T-S8: (judgment, audit_status, defect_stage) matrix exhaustive coverage.
878
+ # Spec §3.1 table: 9 positive rows, lint rejects every (j, a, d) triple
879
+ # outside the table; ≥5 disallowed combinations exercised explicitly.
880
+ # ---------------------------------------------------------------------------
881
+
882
+ class TS8AllowedMatrix(_LintTestBase):
883
+ """T-S8: every allowed triple validates; ≥5 disallowed combinations rejected."""
884
+
885
+ # Positive matrix rows — each is a self-contained passport fragment.
886
+ ALLOWED_ROWS = [
887
+ # (judgment, audit_status, defect_stage, ref_retrieval_method overrides)
888
+ ("SUPPORTED", "completed", None, {"ref_retrieval_method": "api"}),
889
+ ("AMBIGUOUS", "completed", "source_description", {"ref_retrieval_method": "api"}),
890
+ ("AMBIGUOUS", "completed", "citation_anchor", {"ref_retrieval_method": "api"}),
891
+ ("AMBIGUOUS", "completed", "synthesis_overclaim", {"ref_retrieval_method": "api"}),
892
+ ("AMBIGUOUS", "completed", None, {"ref_retrieval_method": "api"}),
893
+ ("UNSUPPORTED", "completed", "source_description", {"ref_retrieval_method": "api"}),
894
+ ("UNSUPPORTED", "completed", "metadata", {"ref_retrieval_method": "api"}),
895
+ ("UNSUPPORTED", "completed", "citation_anchor", {"ref_retrieval_method": "api"}),
896
+ ("UNSUPPORTED", "completed", "synthesis_overclaim", {"ref_retrieval_method": "api"}),
897
+ (
898
+ "UNSUPPORTED",
899
+ "completed",
900
+ "negative_constraint_violation",
901
+ {
902
+ "ref_retrieval_method": "api",
903
+ "violated_constraint_id": "MNC-1",
904
+ },
905
+ ),
906
+ (
907
+ "RETRIEVAL_FAILED",
908
+ "completed",
909
+ "retrieval_existence",
910
+ {"ref_retrieval_method": "not_found"},
911
+ ),
912
+ (
913
+ "RETRIEVAL_FAILED",
914
+ "inconclusive",
915
+ "not_applicable",
916
+ {"ref_retrieval_method": "failed"},
917
+ ),
918
+ ]
919
+
920
+ # Negative cases — ≥5 representative disallowed combinations.
921
+ DISALLOWED_ROWS = [
922
+ # (judgment, audit_status, defect_stage, overrides, expected_invariant)
923
+ (
924
+ "SUPPORTED",
925
+ "completed",
926
+ "source_description",
927
+ {"ref_retrieval_method": "api"},
928
+ "INV-1",
929
+ ),
930
+ (
931
+ "UNSUPPORTED",
932
+ "completed",
933
+ None,
934
+ {"ref_retrieval_method": "api"},
935
+ "INV-2",
936
+ ),
937
+ (
938
+ "RETRIEVAL_FAILED",
939
+ "completed",
940
+ "not_applicable",
941
+ {"ref_retrieval_method": "api"},
942
+ "matrix",
943
+ ),
944
+ (
945
+ "SUPPORTED",
946
+ "inconclusive",
947
+ None,
948
+ {"ref_retrieval_method": "api"},
949
+ "matrix",
950
+ ),
951
+ (
952
+ "AMBIGUOUS",
953
+ "completed",
954
+ "metadata",
955
+ {"ref_retrieval_method": "api"},
956
+ "INV-3",
957
+ ),
958
+ (
959
+ "AMBIGUOUS",
960
+ "inconclusive",
961
+ None,
962
+ {"ref_retrieval_method": "api"},
963
+ "matrix",
964
+ ),
965
+ ]
966
+
967
+ def _build_row(self, j: str, a: str, d: Any, overrides: dict[str, Any]) -> dict[str, Any]:
968
+ e = supported_entry()
969
+ e["judgment"] = j
970
+ e["audit_status"] = a
971
+ e["defect_stage"] = d
972
+ if d == "negative_constraint_violation":
973
+ e["rationale"] = "Violated declared negative constraint."
974
+ elif d == "retrieval_existence":
975
+ e["rationale"] = "Reference does not exist."
976
+ elif d == "not_applicable":
977
+ e["rationale"] = "Paywall — full text not retrievable."
978
+ elif d is not None:
979
+ e["rationale"] = f"Defect at {d}."
980
+ e.update(overrides)
981
+ return e
982
+
983
+ def test_every_allowed_row_passes(self) -> None:
984
+ for j, a, d, overrides in self.ALLOWED_ROWS:
985
+ with self.subTest(judgment=j, audit_status=a, defect_stage=d):
986
+ e = self._build_row(j, a, d, overrides)
987
+ self.assertLintClean(
988
+ build_passport(results=[e]),
989
+ msg=f"row ({j}, {a}, {d}) should pass",
990
+ )
991
+
992
+ def test_disallowed_rows_rejected(self) -> None:
993
+ for j, a, d, overrides, invariant in self.DISALLOWED_ROWS:
994
+ with self.subTest(judgment=j, audit_status=a, defect_stage=d):
995
+ e = self._build_row(j, a, d, overrides)
996
+ self.assertLintFinds(
997
+ build_passport(results=[e]),
998
+ invariant=invariant,
999
+ )
1000
+
1001
+
1002
+ # ---------------------------------------------------------------------------
1003
+ # Spec §6 lint 4a — D-INV-1..D-INV-4 claim_drift cross-array integrity.
1004
+ # Not labelled T-Sx in spec §7.1 but mandatory per §6.4a. Paired pos/neg.
1005
+ # ---------------------------------------------------------------------------
1006
+
1007
+ class TSDDriftInvariants(_LintTestBase):
1008
+ """Spec §6.4a: claim_drift D-INV-1..D-INV-4."""
1009
+
1010
+ def test_d_baseline_emitted_not_intended(self) -> None:
1011
+ self.assertLintClean(build_passport(drifts=[claim_drift_entry()]))
1012
+
1013
+ def test_d_baseline_intended_not_emitted(self) -> None:
1014
+ drift = claim_drift_entry(drift_kind="INTENDED_NOT_EMITTED")
1015
+ self.assertLintClean(build_passport(drifts=[drift]))
1016
+
1017
+ def test_d_inv_1_duplicate_finding_id(self) -> None:
1018
+ a = claim_drift_entry()
1019
+ b = claim_drift_entry() # CD-001 collides
1020
+ self.assertLintFinds(build_passport(drifts=[a, b]), invariant="D-INV-1")
1021
+
1022
+ def test_d_inv_2_intended_not_emitted_missing_manifest_pointer(self) -> None:
1023
+ drift = claim_drift_entry(drift_kind="INTENDED_NOT_EMITTED")
1024
+ drift["manifest_claim_id"] = None # MUST be non-null for INTENDED_NOT_EMITTED
1025
+ drift["scoped_manifest_id"] = None
1026
+ self.assertLintFinds(build_passport(drifts=[drift]), invariant="D-INV-2")
1027
+
1028
+ def test_d_inv_2_emitted_not_intended_with_manifest_pointer(self) -> None:
1029
+ drift = claim_drift_entry(drift_kind="EMITTED_NOT_INTENDED")
1030
+ drift["manifest_claim_id"] = "C-001" # MUST be null for EMITTED_NOT_INTENDED
1031
+ drift["scoped_manifest_id"] = MANIFEST_ID
1032
+ self.assertLintFinds(build_passport(drifts=[drift]), invariant="D-INV-2")
1033
+
1034
+ def test_d_inv_2_dangling_intended_not_emitted_pair(self) -> None:
1035
+ drift = claim_drift_entry(drift_kind="INTENDED_NOT_EMITTED")
1036
+ drift["manifest_claim_id"] = "C-999" # no such claim in manifest
1037
+ drift["scoped_manifest_id"] = MANIFEST_ID
1038
+ self.assertLintFinds(build_passport(drifts=[drift]), invariant="D-INV-2")
1039
+
1040
+ def test_d_inv_3_wrong_rule_version(self) -> None:
1041
+ drift = claim_drift_entry()
1042
+ drift["rule_version"] = "D4-a-v0"
1043
+ self.assertLintFinds(build_passport(drifts=[drift]), invariant="D-INV-3")
1044
+
1045
+ def test_d_inv_4_uncited_and_drift_collision(self) -> None:
1046
+ # A single sentence appears in both uncited_assertions[] and claim_drifts[].
1047
+ sentence = "Half of submissions showed positive results."
1048
+ uncited = uncited_assertion_entry()
1049
+ uncited["sentence_text"] = sentence
1050
+ drift = claim_drift_entry()
1051
+ drift["claim_text"] = sentence
1052
+ self.assertLintFinds(
1053
+ build_passport(uncited=[uncited], drifts=[drift]),
1054
+ invariant="D-INV-4",
1055
+ )
1056
+
1057
+
1058
+ # ---------------------------------------------------------------------------
1059
+ # Spec §6.4b — CV-INV-1..CV-INV-4 constraint_violation cross-array integrity.
1060
+ # ---------------------------------------------------------------------------
1061
+
1062
+ class TSCVConstraintViolationInvariants(_LintTestBase):
1063
+ """Spec §6.4b: constraint_violation CV-INV-1..CV-INV-4."""
1064
+
1065
+ def _manifest_with_mnc_and_nc(self) -> dict[str, Any]:
1066
+ return manifest_entry(
1067
+ claims=[
1068
+ {
1069
+ "claim_id": "C-001",
1070
+ "claim_text": "Causal claim.",
1071
+ "intended_evidence_kind": "empirical",
1072
+ "planned_refs": [],
1073
+ "negative_constraints": [
1074
+ {
1075
+ "constraint_id": "NC-C001-1",
1076
+ "rule": "No causal language without RCT.",
1077
+ }
1078
+ ],
1079
+ }
1080
+ ],
1081
+ mncs=[{"constraint_id": "MNC-1", "rule": "Global rule."}],
1082
+ )
1083
+
1084
+ def test_cv_baseline_mnc_violation(self) -> None:
1085
+ passport = build_passport(
1086
+ manifests=[self._manifest_with_mnc_and_nc()],
1087
+ violations=[constraint_violation_entry()],
1088
+ )
1089
+ self.assertLintClean(passport)
1090
+
1091
+ def test_cv_baseline_nc_violation(self) -> None:
1092
+ passport = build_passport(
1093
+ manifests=[self._manifest_with_mnc_and_nc()],
1094
+ violations=[
1095
+ constraint_violation_entry(
1096
+ constraint_id="NC-C001-1",
1097
+ manifest_claim_id="C-001",
1098
+ )
1099
+ ],
1100
+ )
1101
+ self.assertLintClean(passport)
1102
+
1103
+ def test_cv_inv_1_duplicate_finding_id(self) -> None:
1104
+ a = constraint_violation_entry()
1105
+ b = constraint_violation_entry()
1106
+ passport = build_passport(
1107
+ manifests=[self._manifest_with_mnc_and_nc()],
1108
+ violations=[a, b],
1109
+ )
1110
+ self.assertLintFinds(passport, invariant="CV-INV-1")
1111
+
1112
+ def test_cv_inv_2_dangling_mnc(self) -> None:
1113
+ passport = build_passport(
1114
+ manifests=[self._manifest_with_mnc_and_nc()],
1115
+ violations=[constraint_violation_entry(constraint_id="MNC-99")],
1116
+ )
1117
+ self.assertLintFinds(passport, invariant="CV-INV-2")
1118
+
1119
+ def test_cv_inv_2_dangling_nc(self) -> None:
1120
+ passport = build_passport(
1121
+ manifests=[self._manifest_with_mnc_and_nc()],
1122
+ violations=[
1123
+ constraint_violation_entry(
1124
+ constraint_id="NC-C999-1",
1125
+ manifest_claim_id="C-999",
1126
+ )
1127
+ ],
1128
+ )
1129
+ self.assertLintFinds(passport, invariant="CV-INV-2")
1130
+
1131
+ def test_cv_inv_3_mnc_with_set_manifest_claim_id(self) -> None:
1132
+ passport = build_passport(
1133
+ manifests=[self._manifest_with_mnc_and_nc()],
1134
+ violations=[
1135
+ constraint_violation_entry(
1136
+ constraint_id="MNC-1",
1137
+ manifest_claim_id="C-001", # MUST be null for MNC
1138
+ )
1139
+ ],
1140
+ )
1141
+ self.assertLintFinds(passport, invariant="CV-INV-3")
1142
+
1143
+ def test_cv_inv_3_nc_polarity_mismatch(self) -> None:
1144
+ passport = build_passport(
1145
+ manifests=[self._manifest_with_mnc_and_nc()],
1146
+ violations=[
1147
+ constraint_violation_entry(
1148
+ constraint_id="NC-C001-1",
1149
+ manifest_claim_id="C-002", # MUST match the C-001 in NC id
1150
+ )
1151
+ ],
1152
+ )
1153
+ self.assertLintFinds(passport, invariant="CV-INV-3")
1154
+
1155
+ def test_cv_inv_4_duplicate_per_constraint(self) -> None:
1156
+ # Two violations for (same sentence, same constraint) -> dedup rule.
1157
+ a = constraint_violation_entry()
1158
+ b = constraint_violation_entry()
1159
+ b["finding_id"] = "CV-002" # avoid CV-INV-1 collision
1160
+ passport = build_passport(
1161
+ manifests=[self._manifest_with_mnc_and_nc()],
1162
+ violations=[a, b],
1163
+ )
1164
+ self.assertLintFinds(passport, invariant="CV-INV-4")
1165
+
1166
+
1167
+ # ---------------------------------------------------------------------------
1168
+ # Step 13 R8 codex P2-1 — CV-INV-4 dedupe key extension. Two manifests in
1169
+ # the same passport can carry colliding MNC-* / NC-* ids; the same sentence
1170
+ # text may legitimately violate both. The pre-fix dedupe key
1171
+ # (section_path, claim_text_hash, violated_constraint_id) false-positives
1172
+ # these as duplicates. Extend the key to scope by manifest_id as well.
1173
+ # ---------------------------------------------------------------------------
1174
+
1175
+
1176
+ class TSCVDedupeManifestScope(_LintTestBase):
1177
+ """T-SCV-DEDUPE: CV-INV-4 dedupe must scope by scoped_manifest_id."""
1178
+
1179
+ def _two_manifests_with_colliding_mnc(self) -> list[dict[str, Any]]:
1180
+ # Two manifests each carrying the SAME constraint_id "MNC-1" but
1181
+ # bound to different manifests (M-INV-4 permits — manifest_id is
1182
+ # the joinable scope). Schema requires claims[] non-empty so we
1183
+ # add a minimal claim to each manifest; the claim itself is not
1184
+ # under test.
1185
+ return [
1186
+ manifest_entry(
1187
+ manifest_id=MANIFEST_ID,
1188
+ mncs=[{"constraint_id": "MNC-1", "rule": "No comparative claim."}],
1189
+ ),
1190
+ manifest_entry(
1191
+ manifest_id=MANIFEST_ID_OTHER,
1192
+ mncs=[{"constraint_id": "MNC-1", "rule": "No temporal claim."}],
1193
+ ),
1194
+ ]
1195
+
1196
+ def test_same_constraint_id_across_manifests_not_deduped(self) -> None:
1197
+ # Two CV rows on the same sentence text + same constraint_id (MNC-1)
1198
+ # but DIFFERENT scoped_manifest_id. Legitimate per M-INV-4 +
1199
+ # manifest_negative_constraints scoping — dedupe must keep both.
1200
+ a = constraint_violation_entry(constraint_id="MNC-1")
1201
+ a["scoped_manifest_id"] = MANIFEST_ID
1202
+ b = constraint_violation_entry(constraint_id="MNC-1")
1203
+ b["finding_id"] = "CV-002"
1204
+ b["scoped_manifest_id"] = MANIFEST_ID_OTHER
1205
+ passport = build_passport(
1206
+ manifests=self._two_manifests_with_colliding_mnc(),
1207
+ violations=[a, b],
1208
+ )
1209
+ # Pre-fix: CV-INV-4 false-positives → lint reports CV-INV-4 line.
1210
+ # Post-fix: lint MUST NOT fire CV-INV-4 on cross-manifest collision.
1211
+ self.assertLintClean(passport)
1212
+
1213
+ def test_same_constraint_id_same_manifest_still_deduped(self) -> None:
1214
+ # Negative test: dedupe still catches true within-manifest duplicates.
1215
+ a = constraint_violation_entry(constraint_id="MNC-1")
1216
+ b = constraint_violation_entry(constraint_id="MNC-1")
1217
+ b["finding_id"] = "CV-002"
1218
+ # Both rows reference the same manifest_id (default MANIFEST_ID).
1219
+ passport = build_passport(
1220
+ manifests=[self._two_manifests_with_colliding_mnc()[0]], # single manifest
1221
+ violations=[a, b],
1222
+ )
1223
+ self.assertLintFinds(passport, invariant="CV-INV-4")
1224
+
1225
+
1226
+ # ---------------------------------------------------------------------------
1227
+ # Spec §6.4c — S-INV-1..S-INV-4 audit_sampling_summary invariants.
1228
+ # ---------------------------------------------------------------------------
1229
+
1230
+ class TSSamplingInvariants(_LintTestBase):
1231
+ """Spec §6.4c: audit_sampling_summary S-INV-1..S-INV-4."""
1232
+
1233
+ def test_s_baseline(self) -> None:
1234
+ self.assertLintClean(build_passport(samplings=[sampling_summary_entry()]))
1235
+
1236
+ def test_s_inv_1_count_vs_indices_mismatch(self) -> None:
1237
+ e = sampling_summary_entry()
1238
+ e["audited_count"] = e["audited_count"] - 1 # off-by-one
1239
+ self.assertLintFinds(build_passport(samplings=[e]), invariant="S-INV-1")
1240
+
1241
+ def test_s_inv_2_count_exceeds_cap(self) -> None:
1242
+ e = sampling_summary_entry(total=50, cap=10)
1243
+ e["audited_count"] = 20
1244
+ e["audited_indices"] = list(range(20))
1245
+ self.assertLintFinds(build_passport(samplings=[e]), invariant="S-INV-2")
1246
+
1247
+ def test_s_inv_2_count_exceeds_total(self) -> None:
1248
+ e = sampling_summary_entry(total=10, cap=100)
1249
+ e["audited_count"] = 20
1250
+ e["audited_indices"] = list(range(20))
1251
+ self.assertLintFinds(build_passport(samplings=[e]), invariant="S-INV-2")
1252
+
1253
+ def test_s_inv_4_non_ascending_indices(self) -> None:
1254
+ e = sampling_summary_entry(total=20, cap=10)
1255
+ e["audited_indices"] = [5, 3, 7, 9, 11, 13, 15, 17, 19, 2]
1256
+ e["audited_count"] = 10
1257
+ self.assertLintFinds(build_passport(samplings=[e]), invariant="S-INV-4")
1258
+
1259
+ def test_s_inv_4_duplicate_indices(self) -> None:
1260
+ e = sampling_summary_entry(total=20, cap=10)
1261
+ e["audited_indices"] = [0, 1, 1, 3, 4, 5, 6, 7, 8, 9]
1262
+ e["audited_count"] = 10
1263
+ self.assertLintFinds(build_passport(samplings=[e]), invariant="S-INV-4")
1264
+
1265
+
1266
+ # ---------------------------------------------------------------------------
1267
+ # Spec §6.4d (v3.8.2 / #118) — UAF-INV-1..UAF-INV-5 uncited_audit_failure
1268
+ # cross-array integrity and dedup. Mirrors CV-INV pattern.
1269
+ # ---------------------------------------------------------------------------
1270
+
1271
+
1272
+ class TSUAFUncitedAuditFailureInvariants(_LintTestBase):
1273
+ """Spec §6.4d: uncited_audit_failure UAF-INV-1..UAF-INV-5 (v3.8.2 / #118)."""
1274
+
1275
+ def _manifest_with_mnc_and_nc(self) -> dict[str, Any]:
1276
+ return manifest_entry(
1277
+ claims=[
1278
+ {
1279
+ "claim_id": "C-001",
1280
+ "claim_text": "Causal claim.",
1281
+ "intended_evidence_kind": "empirical",
1282
+ "planned_refs": [],
1283
+ "negative_constraints": [
1284
+ {
1285
+ "constraint_id": "NC-C001-1",
1286
+ "rule": "No causal language without RCT.",
1287
+ }
1288
+ ],
1289
+ }
1290
+ ],
1291
+ mncs=[{"constraint_id": "MNC-1", "rule": "Global rule."}],
1292
+ )
1293
+
1294
+ # ----- Schema-shape only.
1295
+ def test_uaf_schema_valid_minimal_entry(self) -> None:
1296
+ schema = load_json_schema(SCHEMA_PATHS["uncited_audit_failure"])
1297
+ validator = build_schema_validator(schema)
1298
+ errors = list(validator.iter_errors(uncited_audit_failure_entry()))
1299
+ self.assertEqual(errors, [], msg=f"unexpected validation errors: {errors}")
1300
+
1301
+ def test_uaf_schema_fault_class_enum_matches_constants(self) -> None:
1302
+ """The schema's fault_class enum MUST match INV14_FAULT_CLASS_TAGS exactly.
1303
+
1304
+ Without this guard, extending INV14_FAULT_CLASS_TAGS in the Python
1305
+ constants module would silently produce UAF rows that fail schema
1306
+ validation (the pipeline maps a new exception class to a new tag,
1307
+ but the schema's closed enum hasn't been bumped). Per Gemini cross-
1308
+ model review R2 P2 (2026-05-17): both surfaces must move together,
1309
+ and a rule_version bump (D4-c-v1-uaf-v2 or later) should accompany
1310
+ any enum extension.
1311
+ """
1312
+ from scripts._claim_audit_constants import INV14_FAULT_CLASS_TAGS
1313
+
1314
+ schema = load_json_schema(SCHEMA_PATHS["uncited_audit_failure"])
1315
+ schema_enum = set(schema["properties"]["fault_class"]["enum"])
1316
+ constants_set = set(INV14_FAULT_CLASS_TAGS)
1317
+ self.assertEqual(
1318
+ schema_enum,
1319
+ constants_set,
1320
+ msg=(
1321
+ "uncited_audit_failure.schema.json `fault_class` enum drifted "
1322
+ "from INV14_FAULT_CLASS_TAGS. Either align the schema with the "
1323
+ "python constant, or if intentionally extending the taxonomy "
1324
+ "bump `rule_version` (e.g., D4-c-v1-uaf-v2) and document the "
1325
+ "ramp in spec §3.6."
1326
+ ),
1327
+ )
1328
+
1329
+ def test_uaf_schema_rejects_missing_fault_class(self) -> None:
1330
+ schema = load_json_schema(SCHEMA_PATHS["uncited_audit_failure"])
1331
+ validator = build_schema_validator(schema)
1332
+ e = uncited_audit_failure_entry()
1333
+ del e["fault_class"]
1334
+ errors = list(validator.iter_errors(e))
1335
+ self.assertNotEqual(errors, [], msg="missing fault_class must fail schema validation")
1336
+
1337
+ def test_uaf_schema_rejects_unknown_fault_class(self) -> None:
1338
+ schema = load_json_schema(SCHEMA_PATHS["uncited_audit_failure"])
1339
+ validator = build_schema_validator(schema)
1340
+ e = uncited_audit_failure_entry()
1341
+ e["fault_class"] = "made_up_class"
1342
+ errors = list(validator.iter_errors(e))
1343
+ self.assertNotEqual(errors, [], msg="unknown fault_class must fail schema validation")
1344
+
1345
+ # ----- Lint baseline: clean passport with one UAF row passes.
1346
+ def test_uaf_baseline_mnc_failure(self) -> None:
1347
+ passport = build_passport(
1348
+ manifests=[self._manifest_with_mnc_and_nc()],
1349
+ uaf=[uncited_audit_failure_entry()],
1350
+ )
1351
+ self.assertLintClean(passport)
1352
+
1353
+ def test_uaf_baseline_nc_failure(self) -> None:
1354
+ # NC-C path: judge failed on a claim-level constraint check; manifest_claim_id set.
1355
+ passport = build_passport(
1356
+ manifests=[self._manifest_with_mnc_and_nc()],
1357
+ uaf=[uncited_audit_failure_entry(manifest_claim_id="C-001")],
1358
+ )
1359
+ self.assertLintClean(passport)
1360
+
1361
+ # ----- UAF-INV-1: finding_id uniqueness.
1362
+ def test_uaf_inv_1_duplicate_finding_id(self) -> None:
1363
+ a = uncited_audit_failure_entry()
1364
+ b = uncited_audit_failure_entry() # same finding_id "UAF-001"
1365
+ # Per Gemini cross-model review P2 (2026-05-17): keep this test
1366
+ # isolated to UAF-INV-1 by giving b a different claim_text so the
1367
+ # passport does NOT simultaneously trip UAF-INV-4 (per-(sentence,
1368
+ # manifest) dedup). One test should target one invariant cleanly.
1369
+ b["claim_text"] = "Different text so UAF-INV-4 dedup does not fire."
1370
+ passport = build_passport(
1371
+ manifests=[self._manifest_with_mnc_and_nc()],
1372
+ uaf=[a, b],
1373
+ )
1374
+ self.assertLintFinds(passport, invariant="UAF-INV-1")
1375
+
1376
+ # ----- UAF-INV-2: scoped_manifest_id must resolve in claim_intent_manifests[].
1377
+ def test_uaf_inv_2_dangling_manifest_id(self) -> None:
1378
+ e = uncited_audit_failure_entry()
1379
+ e["scoped_manifest_id"] = SENTINEL_MANIFEST_ID # not in manifests
1380
+ passport = build_passport(
1381
+ manifests=[self._manifest_with_mnc_and_nc()],
1382
+ uaf=[e],
1383
+ )
1384
+ self.assertLintFinds(passport, invariant="UAF-INV-2")
1385
+
1386
+ # ----- UAF-INV-3: (scoped_manifest_id, manifest_claim_id) pair integrity.
1387
+ def test_uaf_inv_3_dangling_claim_id(self) -> None:
1388
+ e = uncited_audit_failure_entry(manifest_claim_id="C-999") # no such claim in manifest
1389
+ passport = build_passport(
1390
+ manifests=[self._manifest_with_mnc_and_nc()],
1391
+ uaf=[e],
1392
+ )
1393
+ self.assertLintFinds(passport, invariant="UAF-INV-3")
1394
+
1395
+ def test_uaf_inv_3_null_claim_id_allowed_when_mnc_only(self) -> None:
1396
+ # manifest_claim_id=null is legitimate when the failure was against MNCs only.
1397
+ passport = build_passport(
1398
+ manifests=[self._manifest_with_mnc_and_nc()],
1399
+ uaf=[uncited_audit_failure_entry(manifest_claim_id=None)],
1400
+ )
1401
+ self.assertLintClean(passport)
1402
+
1403
+ # ----- UAF-INV-4: per-(sentence, manifest) dedup.
1404
+ def test_uaf_inv_4_same_sentence_same_manifest_dup(self) -> None:
1405
+ a = uncited_audit_failure_entry()
1406
+ b = uncited_audit_failure_entry(finding_id="UAF-002")
1407
+ # b shares (scoped_manifest_id, section_path, claim_text) with a.
1408
+ passport = build_passport(
1409
+ manifests=[self._manifest_with_mnc_and_nc()],
1410
+ uaf=[a, b],
1411
+ )
1412
+ self.assertLintFinds(passport, invariant="UAF-INV-4")
1413
+
1414
+ def test_uaf_inv_4_same_sentence_different_manifests_not_deduped(self) -> None:
1415
+ # Two manifests both failing on the same sentence text emit two rows
1416
+ # legitimately (mirrors CV-INV-4 cross-manifest reasoning).
1417
+ other_manifest = manifest_entry(
1418
+ manifest_id=MANIFEST_ID_OTHER,
1419
+ mncs=[{"constraint_id": "MNC-1", "rule": "Different rule."}],
1420
+ )
1421
+ a = uncited_audit_failure_entry()
1422
+ a["scoped_manifest_id"] = MANIFEST_ID
1423
+ b = uncited_audit_failure_entry(finding_id="UAF-002")
1424
+ b["scoped_manifest_id"] = MANIFEST_ID_OTHER
1425
+ passport = build_passport(
1426
+ manifests=[self._manifest_with_mnc_and_nc(), other_manifest],
1427
+ uaf=[a, b],
1428
+ )
1429
+ self.assertLintClean(passport)
1430
+
1431
+ # ----- UAF-INV-5: rationale fault_class prefix.
1432
+ def test_uaf_inv_5_rationale_missing_fault_prefix(self) -> None:
1433
+ e = uncited_audit_failure_entry()
1434
+ e["rationale"] = "Some narrative without the fault_class prefix"
1435
+ passport = build_passport(
1436
+ manifests=[self._manifest_with_mnc_and_nc()],
1437
+ uaf=[e],
1438
+ )
1439
+ self.assertLintFinds(passport, invariant="UAF-INV-5")
1440
+
1441
+ def test_uaf_inv_5_rationale_wrong_fault_prefix(self) -> None:
1442
+ # Rationale starts with "made_up_class:" — schema rejected via fault_class
1443
+ # enum, but UAF-INV-5 also flags rationale prefix mismatch independently.
1444
+ # Here we keep fault_class valid but flip rationale to a different valid
1445
+ # tag — should still trip UAF-INV-5 (prefix must match the row's
1446
+ # fault_class, not just any known tag).
1447
+ e = uncited_audit_failure_entry(fault_class="judge_timeout")
1448
+ e["rationale"] = "judge_api_error: wrong tag for this row"
1449
+ passport = build_passport(
1450
+ manifests=[self._manifest_with_mnc_and_nc()],
1451
+ uaf=[e],
1452
+ )
1453
+ self.assertLintFinds(passport, invariant="UAF-INV-5")
1454
+
1455
+ # ----- Cross-aggregate exclusivity: UAF vs constraint_violation.
1456
+ def test_uaf_cross_aggregate_exclusive_with_cv(self) -> None:
1457
+ # Same (sentence, manifest) MUST NOT have both an audit_tool_failure (UAF)
1458
+ # and a VIOLATED row (CV). They are mutually exclusive verdict states.
1459
+ cv = constraint_violation_entry()
1460
+ uaf = uncited_audit_failure_entry()
1461
+ passport = build_passport(
1462
+ manifests=[self._manifest_with_mnc_and_nc()],
1463
+ violations=[cv],
1464
+ uaf=[uaf],
1465
+ )
1466
+ self.assertLintFinds(passport, invariant="UAF-INV-6")
1467
+
1468
+ # ----- Malformed-payload hardening (Codex R2 P2-2 + P2-3, 2026-05-17).
1469
+ def test_uaf_malformed_manifest_claim_id_does_not_crash_lint(self) -> None:
1470
+ # Schema validator flags type errors separately; lint walker must
1471
+ # skip cleanly on unhashable values rather than raise TypeError.
1472
+ e = uncited_audit_failure_entry()
1473
+ e["manifest_claim_id"] = ["unhashable", "list", "as", "claim_id"]
1474
+ passport = build_passport(
1475
+ manifests=[self._manifest_with_mnc_and_nc()],
1476
+ uaf=[e],
1477
+ )
1478
+ # Lint should report SOMETHING (schema-shape finding), exit 1, NOT crash.
1479
+ path = write_passport(self.tmp, passport)
1480
+ code, _, err = run_lint(path)
1481
+ self.assertEqual(
1482
+ code,
1483
+ 1,
1484
+ msg=f"expected clean lint failure on malformed manifest_claim_id, got exit={code}\nstderr:\n{err}",
1485
+ )
1486
+ self.assertNotIn(
1487
+ "Traceback",
1488
+ err,
1489
+ msg="lint MUST NOT crash with a traceback on malformed manifest_claim_id (Codex R2 P2-2)",
1490
+ )
1491
+
1492
+ def test_uaf_malformed_rationale_does_not_crash_lint(self) -> None:
1493
+ e = uncited_audit_failure_entry()
1494
+ e["rationale"] = ["unhashable", "rationale"]
1495
+ passport = build_passport(
1496
+ manifests=[self._manifest_with_mnc_and_nc()],
1497
+ uaf=[e],
1498
+ )
1499
+ path = write_passport(self.tmp, passport)
1500
+ code, _, err = run_lint(path)
1501
+ self.assertEqual(
1502
+ code,
1503
+ 1,
1504
+ msg=f"expected clean lint failure on malformed rationale, got exit={code}\nstderr:\n{err}",
1505
+ )
1506
+ self.assertNotIn(
1507
+ "Traceback",
1508
+ err,
1509
+ msg="lint MUST NOT crash with a traceback on malformed rationale (Codex R2 P2-3)",
1510
+ )
1511
+
1512
+ # ----- Co-existence with uncited_assertions[] IS permitted.
1513
+ def test_uaf_coexists_with_uncited_assertion(self) -> None:
1514
+ # D4-c detector positive (UA) + judge failure (UAF) on the same sentence
1515
+ # are independent signals; cross-aggregate exclusivity does NOT apply.
1516
+ ua = uncited_assertion_entry()
1517
+ uaf = uncited_audit_failure_entry()
1518
+ passport = build_passport(
1519
+ manifests=[self._manifest_with_mnc_and_nc()],
1520
+ uncited=[ua],
1521
+ uaf=[uaf],
1522
+ )
1523
+ self.assertLintClean(passport)
1524
+
1525
+
1526
+ # ---------------------------------------------------------------------------
1527
+ # T-S9: Defensive guard against malformed passports.
1528
+ # Step 13 R4 codex P2 #4 — malformed aggregates (non-list / non-dict entries)
1529
+ # must surface as schema findings rather than crash the lint with an
1530
+ # AttributeError traceback.
1531
+ # ---------------------------------------------------------------------------
1532
+
1533
+
1534
+ class TS9MalformedPassportGuard(_LintTestBase):
1535
+ """T-S9: passport with malformed aggregate yields schema finding, not crash."""
1536
+
1537
+ def test_claim_audit_results_with_non_dict_entry(self) -> None:
1538
+ # Pre-R4: list of non-dict in claim_audit_results raised AttributeError
1539
+ # because dict-only invariant loops ran .get() before noticing the shape.
1540
+ body = build_passport()
1541
+ body["claim_audit_results"] = ["this should be an object, not a string"]
1542
+ path = write_passport(self.tmp, body)
1543
+ code, out, err = run_lint(path)
1544
+ self.assertEqual(
1545
+ code,
1546
+ 1,
1547
+ msg=f"expected clean lint failure on malformed aggregate; got exit={code}\nstderr:\n{err}",
1548
+ )
1549
+ self.assertNotIn(
1550
+ "Traceback",
1551
+ err,
1552
+ msg=f"lint must not raise — got traceback:\n{err}",
1553
+ )
1554
+ self.assertIn("schema", out, msg=f"expected schema finding tag in stdout:\n{out}")
1555
+
1556
+ def test_claim_intent_manifests_as_dict_instead_of_list(self) -> None:
1557
+ body = build_passport()
1558
+ body["claim_intent_manifests"] = {"oops": "should be a list"}
1559
+ path = write_passport(self.tmp, body)
1560
+ code, out, err = run_lint(path)
1561
+ self.assertEqual(code, 1, msg=f"expected exit=1; got {code}\nstderr:\n{err}")
1562
+ self.assertNotIn("Traceback", err, msg=f"lint must not raise:\n{err}")
1563
+ self.assertIn("schema", out)
1564
+
1565
+ def test_non_object_passport_body_yields_clean_finding(self) -> None:
1566
+ # Step 13 R7 codex P3: `[]`, `null`, scalar top-level JSON would
1567
+ # previously crash with AttributeError on `.get()`. Validate that
1568
+ # each surfaces as a schema finding without traceback.
1569
+ for malformed_body in ([], None, 42, "passport"):
1570
+ with self.subTest(body=malformed_body):
1571
+ path = self.tmp / f"malformed_{type(malformed_body).__name__}.json"
1572
+ path.write_text(json.dumps(malformed_body), encoding="utf-8")
1573
+ code, out, err = run_lint(path)
1574
+ self.assertEqual(
1575
+ code,
1576
+ 1,
1577
+ msg=f"expected exit=1 for body={malformed_body!r}; got {code}\nstderr:\n{err}",
1578
+ )
1579
+ self.assertNotIn(
1580
+ "Traceback",
1581
+ err,
1582
+ msg=f"lint must not raise for body={malformed_body!r}:\n{err}",
1583
+ )
1584
+ self.assertIn("schema", out, msg=f"expected schema finding:\n{out}")
1585
+
1586
+ def test_falsey_malformed_aggregate_does_not_bypass_schema(self) -> None:
1587
+ # Step 13 R5 codex P2 #1: `body.get(k, []) or []` would silently
1588
+ # convert a malformed `{}` / `null` / `0` / `""` value to an empty
1589
+ # list and skip schema validation. Each falsey-but-malformed shape
1590
+ # must surface as a schema finding.
1591
+ for malformed in ({}, None, 0, ""):
1592
+ with self.subTest(malformed=malformed):
1593
+ body = build_passport()
1594
+ body["claim_audit_results"] = malformed
1595
+ path = write_passport(self.tmp, body)
1596
+ code, out, err = run_lint(path)
1597
+ self.assertEqual(
1598
+ code,
1599
+ 1,
1600
+ msg=f"expected exit=1 for malformed={malformed!r}; got {code}\nstdout:\n{out}\nstderr:\n{err}",
1601
+ )
1602
+ self.assertIn("schema", out, msg=f"expected schema finding for malformed={malformed!r}:\n{out}")
1603
+ self.assertNotIn("Traceback", err, msg=f"lint must not raise for malformed={malformed!r}:\n{err}")
1604
+
1605
+ # Step 13 R6 codex P2 + R8 P2-2 — nested schema-invalid shapes must not
1606
+ # crash the cross-field invariant walkers. Schema validator records the
1607
+ # finding; the invariant helpers then iterate the malformed nested shape
1608
+ # and previously hit TypeError/AttributeError. Issue #119 + #120 P2-2.
1609
+
1610
+ def test_manifest_claims_as_string_does_not_crash_invariant_walker(self) -> None:
1611
+ # #119 + #120 P2-2: claim_intent_manifests[0].claims is a string,
1612
+ # not a list. Schema validator records the finding; the cross-field
1613
+ # invariant walker (M-INV-1..M-INV-4) must NOT iterate the string
1614
+ # and crash. Expect clean exit=1 with schema finding, no traceback.
1615
+ body = build_passport()
1616
+ body["claim_intent_manifests"][0]["claims"] = "should be a list"
1617
+ path = write_passport(self.tmp, body)
1618
+ code, out, err = run_lint(path)
1619
+ self.assertEqual(code, 1, msg=f"expected exit=1; got {code}\nstderr:\n{err}")
1620
+ self.assertNotIn("Traceback", err, msg=f"lint must not crash on nested string:\n{err}")
1621
+ self.assertIn("schema", out, msg=f"expected schema finding:\n{out}")
1622
+
1623
+ def test_manifest_claim_with_non_string_claim_id_does_not_crash(self) -> None:
1624
+ # #119: manifest.claims[].claim_id is a non-string. Schema records
1625
+ # the type mismatch; invariant walker must skip this manifest's
1626
+ # cross-field checks instead of crashing on .get() / regex match.
1627
+ body = build_passport()
1628
+ body["claim_intent_manifests"][0]["claims"][0]["claim_id"] = 42
1629
+ path = write_passport(self.tmp, body)
1630
+ code, out, err = run_lint(path)
1631
+ self.assertEqual(code, 1, msg=f"expected exit=1; got {code}\nstderr:\n{err}")
1632
+ self.assertNotIn("Traceback", err, msg=f"lint must not crash on non-string claim_id:\n{err}")
1633
+ self.assertIn("schema", out, msg=f"expected schema finding:\n{out}")
1634
+
1635
+ def test_audited_indices_mixed_types_does_not_crash_sampling_walker(self) -> None:
1636
+ # #119: audit_sampling_summaries[].audited_indices contains mixed
1637
+ # str/int types. Schema records the type mismatch; S-INV-4 walker
1638
+ # (indices[i] <= indices[i-1]) must skip instead of raising
1639
+ # TypeError on '<=' between str and int.
1640
+ body = build_passport()
1641
+ body["audit_sampling_summaries"] = [{
1642
+ "audit_run_id": AUDIT_RUN_ID,
1643
+ "max_claims_per_paper": 10,
1644
+ "total_citation_count": 5,
1645
+ "audited_count": 2,
1646
+ "audited_indices": ["a", 1],
1647
+ "sampling_strategy": "stratified_buckets_v1",
1648
+ "emitted_at": "2026-05-15T10:15:00Z",
1649
+ }]
1650
+ path = write_passport(self.tmp, body)
1651
+ code, out, err = run_lint(path)
1652
+ self.assertEqual(code, 1, msg=f"expected exit=1; got {code}\nstderr:\n{err}")
1653
+ self.assertNotIn("Traceback", err, msg=f"lint must not crash on mixed-type indices:\n{err}")
1654
+ self.assertIn("schema", out, msg=f"expected schema finding:\n{out}")
1655
+
1656
+ # v3.8.1 Round 2 codex review P2 + adjacent unhashable-id surfaces.
1657
+ # Schema validator records the type mismatch separately; uniqueness loops
1658
+ # and dedupe key construction must not crash on unhashable nested ids.
1659
+
1660
+ def test_unhashable_claim_id_does_not_crash_m_inv_1(self) -> None:
1661
+ # codex round 2 P2: claim.get("claim_id") returns a list/dict
1662
+ # (unhashable). M-INV-1 uniqueness `cid in claim_ids` crashes with
1663
+ # TypeError. Schema records the type mismatch but the invariant walker
1664
+ # still raises, masking the schema findings.
1665
+ body = build_passport()
1666
+ body["claim_intent_manifests"][0]["claims"][0]["claim_id"] = ["bad", "type"]
1667
+ path = write_passport(self.tmp, body)
1668
+ code, out, err = run_lint(path)
1669
+ self.assertEqual(code, 1, msg=f"expected exit=1; got {code}\nstderr:\n{err}")
1670
+ self.assertNotIn("Traceback", err, msg=f"lint must not crash on unhashable claim_id:\n{err}")
1671
+ self.assertIn("schema", out, msg=f"expected schema finding:\n{out}")
1672
+
1673
+ def test_unhashable_finding_id_does_not_crash_cv_inv_1(self) -> None:
1674
+ # CV-INV-1 finding_id uniqueness `fid in seen` crashes when finding_id
1675
+ # is a list / dict (schema-invalid). Same surface class as the M-INV-1
1676
+ # case codex flagged; included for parity coverage across all four
1677
+ # finding_id uniqueness sites (M-INV-4 / U-INV-1 / D-INV-1 / CV-INV-1).
1678
+ body = build_passport()
1679
+ body["constraint_violations"] = [
1680
+ {
1681
+ "finding_id": ["unhashable"],
1682
+ "claim_text": "x",
1683
+ "section_path": "Results > Findings",
1684
+ "violated_constraint_id": "MNC-1",
1685
+ "scoped_manifest_id": MANIFEST_ID,
1686
+ "manifest_claim_id": None,
1687
+ "judge_verdict": "VIOLATED",
1688
+ "rationale": "rationale",
1689
+ "judge_model": "gpt-5.5-xhigh",
1690
+ "judge_run_at": "2026-05-15T10:14:00Z",
1691
+ "rule_version": "D4-a-v1",
1692
+ }
1693
+ ]
1694
+ path = write_passport(self.tmp, body)
1695
+ code, out, err = run_lint(path)
1696
+ self.assertEqual(code, 1, msg=f"expected exit=1; got {code}\nstderr:\n{err}")
1697
+ self.assertNotIn("Traceback", err, msg=f"lint must not crash on unhashable finding_id:\n{err}")
1698
+ self.assertIn("schema", out, msg=f"expected schema finding:\n{out}")
1699
+
1700
+ def test_unhashable_mnc_constraint_id_does_not_crash_m_inv_3(self) -> None:
1701
+ # codex round 3 P2: manifest_negative_constraints[].constraint_id as
1702
+ # list/dict crashes the `mnc_ids = {nc.get("constraint_id") for ...}`
1703
+ # set comprehension when the implementation builds an (unused) set.
1704
+ # Schema records the type mismatch but the lint still raises.
1705
+ body = build_passport()
1706
+ body["claim_intent_manifests"][0]["manifest_negative_constraints"] = [
1707
+ {"constraint_id": ["unhashable"], "rule": "bad"}
1708
+ ]
1709
+ path = write_passport(self.tmp, body)
1710
+ code, out, err = run_lint(path)
1711
+ self.assertEqual(code, 1, msg=f"expected exit=1; got {code}\nstderr:\n{err}")
1712
+ self.assertNotIn("Traceback", err, msg=f"lint must not crash on unhashable MNC constraint_id:\n{err}")
1713
+ self.assertIn("schema", out, msg=f"expected schema finding:\n{out}")
1714
+
1715
+ def test_non_string_claim_text_does_not_crash_cv_inv_4_dedupe(self) -> None:
1716
+ # CV-INV-4 dedupe key constructs hashlib.sha256((claim_text or "").encode())
1717
+ # — if claim_text is a list / dict / int (schema-invalid), the `or ""`
1718
+ # idiom passes the non-string through and .encode() raises AttributeError.
1719
+ # Codex round 2 adversarial probing surfaced this same class.
1720
+ body = build_passport()
1721
+ body["constraint_violations"] = [
1722
+ {
1723
+ "finding_id": "CV-001",
1724
+ "claim_text": ["not", "a", "string"],
1725
+ "section_path": "Results > Findings",
1726
+ "violated_constraint_id": "MNC-1",
1727
+ "scoped_manifest_id": MANIFEST_ID,
1728
+ "manifest_claim_id": None,
1729
+ "judge_verdict": "VIOLATED",
1730
+ "rationale": "rationale",
1731
+ "judge_model": "gpt-5.5-xhigh",
1732
+ "judge_run_at": "2026-05-15T10:14:00Z",
1733
+ "rule_version": "D4-a-v1",
1734
+ }
1735
+ ]
1736
+ path = write_passport(self.tmp, body)
1737
+ code, out, err = run_lint(path)
1738
+ self.assertEqual(code, 1, msg=f"expected exit=1; got {code}\nstderr:\n{err}")
1739
+ self.assertNotIn("Traceback", err, msg=f"lint must not crash on non-string claim_text:\n{err}")
1740
+ self.assertIn("schema", out, msg=f"expected schema finding:\n{out}")
1741
+
1742
+
1743
+ # v3.9.1 / #130 — manifest_id non-string guard in _build_manifest_index +
1744
+ # _build_manifest_constraint_index. Schema validator records the type
1745
+ # mismatch finding; the index builders must skip non-string IDs cleanly
1746
+ # instead of raising TypeError("unhashable type") on setdefault() / [mid]=.
1747
+ class TSManifestIdNonStringGuard(_LintTestBase):
1748
+ """Issue #130: manifest_id of type list/dict must not crash index builders."""
1749
+
1750
+ def test_manifest_id_as_list_does_not_crash(self) -> None:
1751
+ body = build_passport()
1752
+ body["claim_intent_manifests"][0]["manifest_id"] = ["oops"]
1753
+ path = write_passport(self.tmp, body)
1754
+ code, out, err = run_lint(path)
1755
+ self.assertEqual(code, 1, msg=f"expected exit=1; got {code}\nstderr:\n{err}")
1756
+ self.assertNotIn(
1757
+ "Traceback",
1758
+ err,
1759
+ msg=f"lint must not crash on list manifest_id (unhashable in setdefault):\n{err}",
1760
+ )
1761
+ self.assertIn("schema", out, msg=f"expected schema finding:\n{out}")
1762
+
1763
+ def test_manifest_id_as_dict_does_not_crash(self) -> None:
1764
+ body = build_passport()
1765
+ body["claim_intent_manifests"][0]["manifest_id"] = {"oops": "dict id"}
1766
+ path = write_passport(self.tmp, body)
1767
+ code, out, err = run_lint(path)
1768
+ self.assertEqual(code, 1, msg=f"expected exit=1; got {code}\nstderr:\n{err}")
1769
+ self.assertNotIn(
1770
+ "Traceback",
1771
+ err,
1772
+ msg=f"lint must not crash on dict manifest_id (unhashable in setdefault):\n{err}",
1773
+ )
1774
+ self.assertIn("schema", out, msg=f"expected schema finding:\n{out}")
1775
+
1776
+
1777
+ if __name__ == "__main__":
1778
+ unittest.main()