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,2043 @@
1
+ #!/usr/bin/env python3
2
+ """Build, validate, page, and inertly render ARS shared evidence rows.
3
+
4
+ The module is deliberately standard-library-only and hermetic. Builders may
5
+ inspect only the ``session_source_or_none`` string explicitly supplied by their
6
+ caller. Validators optionally replay against that same explicit string.
7
+ Renderers require an explicit in-memory source map to replay source-bound rows;
8
+ they never retrieve, re-extract, invoke a model, consult a cache, follow a row
9
+ pointer, or read/write the human-read ledger. Integrity validation checks that
10
+ the persisted encoded and decoded anchors agree but never alters provenance.
11
+
12
+ CLI exit codes: 0 success, 1 contract/data failure, 2 named-input or argparse
13
+ usage failure.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import copy
20
+ import datetime as dt
21
+ import hashlib
22
+ import html
23
+ import json
24
+ import re
25
+ import sys
26
+ import unicodedata
27
+ from collections.abc import Mapping, Sequence
28
+ from pathlib import Path
29
+ from typing import Any
30
+ from urllib.parse import unquote
31
+
32
+
33
+ SCHEMA_VERSION = "evidence-row/1.0"
34
+ SURFACE = "phase_e_claim_verification"
35
+ ADVISORY_SCHEMA_VERSION = "evidence-row/1.1"
36
+ ADVISORY_SURFACE = "authority_profile_content_coverage"
37
+ EXTRACTOR_VERSION = "evidence-rows/1.0"
38
+ QUOTE_WORD_CAP = 25
39
+ TEXT_CHAR_CAP = 1000
40
+ DEFAULT_PAGE_SIZE = 25
41
+ MAX_PAGE_SIZE = 25
42
+ LEGACY_UNAVAILABLE_LABEL = "LEGACY — EVIDENCE ROWS UNAVAILABLE"
43
+
44
+ ANCHOR_KINDS = frozenset({"quote", "page", "section", "paragraph", "none"})
45
+ EXCERPT_STATES = frozenset(
46
+ {
47
+ "verified_exact_match",
48
+ "agent_extracted",
49
+ "unconfirmed_anchor",
50
+ "not_checked",
51
+ "source_missing",
52
+ "access_failed",
53
+ "retrieval_failed",
54
+ "anchorless",
55
+ }
56
+ )
57
+ STATE_LABELS = {
58
+ "verified_exact_match": "VERIFIED EXACT MATCH",
59
+ "agent_extracted": "AGENT-EXTRACTED — NOT AUTHORITATIVE",
60
+ "unconfirmed_anchor": "UNCONFIRMED ANCHOR",
61
+ "not_checked": "NOT CHECKED",
62
+ "source_missing": "SOURCE NOT HELD",
63
+ "access_failed": "ACCESS FAILED",
64
+ "retrieval_failed": "RETRIEVAL FAILED",
65
+ "anchorless": "ANCHORLESS — NO EXCERPT",
66
+ }
67
+ EMPTY_STATES = frozenset(
68
+ {"not_checked", "source_missing", "access_failed", "retrieval_failed", "anchorless"}
69
+ )
70
+ SOURCE_BOUND_STATES = frozenset(
71
+ {"verified_exact_match", "agent_extracted", "unconfirmed_anchor"}
72
+ )
73
+ EXCERPT_TEXT_STATES = frozenset({"verified_exact_match", "agent_extracted"})
74
+ FAILURE_STATES = frozenset({"not_checked", "source_missing", "access_failed", "retrieval_failed", "anchorless"})
75
+ SELECTION_TIERS = frozenset({"HIGH-IMPACT", "RANDOM", "TOP-UP", "ALL"})
76
+ VERDICTS = frozenset(
77
+ {
78
+ "VERIFIED",
79
+ "MINOR_DISTORTION",
80
+ "MAJOR_DISTORTION",
81
+ "UNVERIFIABLE",
82
+ "UNVERIFIABLE_ACCESS",
83
+ }
84
+ )
85
+ CACHE_STATUSES = frozenset({"not_used", "miss", "hit"})
86
+ SHARING_SCOPES = frozenset({"session_only", "user_confirmed_shareable"})
87
+ RIGHTS_BASES = frozenset({"not_assessed", "user_declared_authorized"})
88
+ ADVISORY_ANCHOR_KINDS = frozenset({"quote", "none"})
89
+ ADVISORY_EXCERPT_STATES = frozenset(
90
+ {
91
+ "agent_extracted",
92
+ "checked_no_match",
93
+ "not_checked",
94
+ "source_missing",
95
+ "access_failed",
96
+ "retrieval_failed",
97
+ }
98
+ )
99
+ ADVISORY_SOURCE_BOUND_STATES = frozenset({"agent_extracted", "checked_no_match"})
100
+ ADVISORY_EMPTY_STATES = frozenset(
101
+ {"checked_no_match", "not_checked", "source_missing", "access_failed", "retrieval_failed"}
102
+ )
103
+ ADVISORY_FAILURE_STATES = frozenset(
104
+ {"not_checked", "source_missing", "access_failed", "retrieval_failed"}
105
+ )
106
+ DOCUMENT_LOCATOR_KINDS = frozenset(
107
+ {"document", "page", "section", "paragraph", "other"}
108
+ )
109
+ DOCUMENT_LOCATOR_PROVENANCE = "agent_supplied_not_independently_authenticated"
110
+ MAX_JSON_NESTING = 100
111
+
112
+ _SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
113
+ _REF_SLUG_RE = re.compile(r"^[A-Za-z][A-Za-z0-9_:-]*$")
114
+ _ROW_ID_RE = re.compile(r"^EVR-[A-Za-z0-9._:-]+$")
115
+ _AUTHORITY_ID_RE = re.compile(r"^[a-z0-9][a-z0-9._-]*$")
116
+ _POINTER_RE = re.compile(r"^/(?:profiles|overlays)/[0-9]+/requirements/[0-9]+(?:/.*)?$")
117
+ _RFC3339_RE = re.compile(
118
+ r"^[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt]"
119
+ r"(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]"
120
+ r"(?:\.[0-9]+)?(?:[Zz]|[+-](?:[01][0-9]|2[0-3]):[0-5][0-9])$"
121
+ )
122
+ _HEX = frozenset("0123456789abcdefABCDEF")
123
+
124
+
125
+ class EvidenceRowError(ValueError):
126
+ """A shared evidence row violates the closed #656 contract."""
127
+
128
+
129
+ class EvidenceRowInputError(EvidenceRowError):
130
+ """A named CLI input could not be read or parsed as strict JSON."""
131
+
132
+
133
+ def _fail(path: str, message: str) -> None:
134
+ raise EvidenceRowError(f"{path}: {message}")
135
+
136
+
137
+ def _input_fail(path: str, message: str) -> None:
138
+ raise EvidenceRowInputError(f"{path}: {message}")
139
+
140
+
141
+ def _closed(value: Any, path: str, required: set[str], allowed: set[str] | None = None) -> dict[str, Any]:
142
+ if not isinstance(value, dict):
143
+ _fail(path, "must be an object")
144
+ allowed = required if allowed is None else allowed
145
+ missing = sorted(required - set(value))
146
+ extra = sorted(set(value) - allowed)
147
+ if missing:
148
+ _fail(path, f"missing required key(s): {', '.join(missing)}")
149
+ if extra:
150
+ _fail(path, f"unexpected key(s): {', '.join(extra)}")
151
+ return value
152
+
153
+
154
+ def _string(value: Any, path: str, *, minimum: int = 0, maximum: int | None = None) -> str:
155
+ if not isinstance(value, str):
156
+ _fail(path, "must be a string")
157
+ if len(value) < minimum:
158
+ _fail(path, f"must contain at least {minimum} character(s)")
159
+ if maximum is not None and len(value) > maximum:
160
+ _fail(path, f"exceeds {maximum} Unicode code points")
161
+ _reject_unsafe_controls(value, path)
162
+ return value
163
+
164
+
165
+ def _nullable_string(value: Any, path: str, *, minimum: int = 0, maximum: int | None = None) -> str | None:
166
+ if value is None:
167
+ return None
168
+ return _string(value, path, minimum=minimum, maximum=maximum)
169
+
170
+
171
+ def _sha(value: Any, path: str, *, nullable: bool = False) -> str | None:
172
+ if value is None and nullable:
173
+ return None
174
+ if not isinstance(value, str) or _SHA256_RE.fullmatch(value) is None:
175
+ _fail(path, "must be a lowercase 64-hex SHA-256 digest" + (" or null" if nullable else ""))
176
+ return value
177
+
178
+
179
+ def _integer(value: Any, path: str, *, minimum: int = 0) -> int:
180
+ if isinstance(value, bool) or not isinstance(value, int):
181
+ _fail(path, "must be an integer")
182
+ if value < minimum:
183
+ _fail(path, f"must be >= {minimum}")
184
+ return value
185
+
186
+
187
+ def _enum(value: Any, path: str, allowed: frozenset[str] | set[str]) -> str:
188
+ if not isinstance(value, str) or value not in allowed:
189
+ _fail(path, f"must be one of {sorted(allowed)}")
190
+ return value
191
+
192
+
193
+ def _authority_identifier(value: Any, path: str) -> str:
194
+ text = _string(value, path, minimum=1, maximum=200)
195
+ if _AUTHORITY_ID_RE.fullmatch(text) is None:
196
+ _fail(path, "must use lowercase letters, digits, dot, underscore, or hyphen")
197
+ return text
198
+
199
+
200
+ def _json_pointer(value: Any, path: str) -> str:
201
+ text = _string(value, path, minimum=1, maximum=2000)
202
+ if _POINTER_RE.fullmatch(text) is None or any(char.isspace() for char in text):
203
+ _fail(path, "must be a normalized profile/overlay requirement JSON Pointer")
204
+ return text
205
+
206
+
207
+ def _single_line(value: Any, path: str, *, maximum: int) -> str:
208
+ text = _string(value, path, minimum=1, maximum=maximum)
209
+ if any(char in text for char in "\r\n") or any(
210
+ unicodedata.category(char) in {"Cc", "Zl", "Zp"} for char in text
211
+ ):
212
+ _fail(path, "must be a single line without control characters")
213
+ return text
214
+
215
+
216
+ def _advisory_relative_path(value: Any, path: str) -> str:
217
+ text = _single_line(value, path, maximum=1024)
218
+ if "\\" in text or text.startswith("/") or text.endswith("/"):
219
+ _fail(path, "must be a normalized relative POSIX path")
220
+ parts = text.split("/")
221
+ if any(part in {"", ".", ".."} for part in parts):
222
+ _fail(path, "must not contain empty, dot, or parent-traversal components")
223
+ return text
224
+
225
+
226
+ def _reject_unsafe_controls(value: str, path: str) -> None:
227
+ for char in value:
228
+ code = ord(char)
229
+ if (code < 0x20 and char not in "\t\n\r") or code == 0x7F:
230
+ _fail(path, f"contains forbidden control U+{code:04X}")
231
+ if 0xD800 <= code <= 0xDFFF:
232
+ _fail(path, "contains an unpaired UTF-16 surrogate")
233
+
234
+
235
+ def _utf8(value: str, path: str) -> bytes:
236
+ try:
237
+ return value.encode("utf-8", errors="strict")
238
+ except UnicodeError as exc:
239
+ _fail(path, f"is not strict UTF-8 encodable: {exc}")
240
+
241
+
242
+ def _hash_bytes(value: bytes) -> str:
243
+ return hashlib.sha256(value).hexdigest()
244
+
245
+
246
+ def _hash_text(value: str, path: str = "text") -> str:
247
+ return _hash_bytes(_utf8(value, path))
248
+
249
+
250
+ def strict_percent_decode(value: str) -> str:
251
+ """Decode RFC-3986 percent escapes exactly once using strict UTF-8.
252
+
253
+ ``+`` remains a literal plus. A decoded ``%HH`` sequence is deliberately
254
+ left as text, so ``%2520`` becomes ``%20`` rather than a space.
255
+ """
256
+
257
+ _string(value, "value", maximum=12000)
258
+ index = 0
259
+ while index < len(value):
260
+ if value[index] == "%":
261
+ if index + 2 >= len(value) or value[index + 1] not in _HEX or value[index + 2] not in _HEX:
262
+ _fail("value", f"malformed percent escape at character {index}")
263
+ index += 3
264
+ else:
265
+ index += 1
266
+ try:
267
+ decoded = unquote(value, encoding="utf-8", errors="strict")
268
+ except UnicodeError as exc:
269
+ _fail("value", f"percent escapes are not valid UTF-8: {exc}")
270
+ _string(decoded, "decoded value", maximum=TEXT_CHAR_CAP)
271
+ return decoded
272
+
273
+
274
+ def _bounded_external_text(value: str, path: str) -> str:
275
+ value = _string(value, path, minimum=1, maximum=TEXT_CHAR_CAP)
276
+ words = len(value.split())
277
+ if words > QUOTE_WORD_CAP:
278
+ _fail(path, f"exceeds {QUOTE_WORD_CAP} words by whitespace split (got {words})")
279
+ return value
280
+
281
+
282
+ def _canonical_json(value: Mapping[str, Any]) -> str:
283
+ try:
284
+ return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
285
+ except (TypeError, ValueError, UnicodeError) as exc:
286
+ _fail("row", f"cannot be canonically serialized: {exc}")
287
+
288
+
289
+ def _row_sha256(row: Mapping[str, Any]) -> str:
290
+ payload = copy.deepcopy(dict(row))
291
+ payload.pop("row_sha256", None)
292
+ return _hash_text(_canonical_json(payload), "canonical row")
293
+
294
+
295
+ def _cache_key(
296
+ source_content_sha256: str,
297
+ anchor_kind: str,
298
+ anchor_decoded: str,
299
+ *,
300
+ claim_id: str,
301
+ ref_slug: str,
302
+ state: str,
303
+ excerpt_candidate_sha256: str | None,
304
+ ) -> str:
305
+ payload = {
306
+ "schema_version": SCHEMA_VERSION,
307
+ "surface": SURFACE,
308
+ "claim_id": claim_id,
309
+ "ref_slug": ref_slug,
310
+ "source_content_sha256": source_content_sha256,
311
+ "anchor_kind": anchor_kind,
312
+ "anchor_decoded_sha256": _hash_text(anchor_decoded, "anchor.value_decoded"),
313
+ "state": state,
314
+ "excerpt_candidate_sha256": excerpt_candidate_sha256,
315
+ "extractor_version": EXTRACTOR_VERSION,
316
+ "quote_word_cap": QUOTE_WORD_CAP,
317
+ "text_char_cap": TEXT_CHAR_CAP,
318
+ }
319
+ return _hash_text(_canonical_json(payload), "cache key payload")
320
+
321
+
322
+ def _timestamp_now() -> str:
323
+ return dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
324
+
325
+
326
+ def _timestamp(value: Any, path: str) -> str:
327
+ value = _string(value, path, minimum=1, maximum=100)
328
+ if _RFC3339_RE.fullmatch(value) is None:
329
+ _fail(path, "must be an RFC 3339 date-time")
330
+ candidate = value[:-1] + "+00:00" if value[-1] in {"Z", "z"} else value
331
+ try:
332
+ dt.datetime.fromisoformat(candidate)
333
+ except ValueError as exc:
334
+ _fail(path, f"must be an RFC 3339 date-time: {exc}")
335
+ return value
336
+
337
+
338
+ def _span(source: str, excerpt: str) -> dict[str, int]:
339
+ char_start = source.find(excerpt)
340
+ if char_start < 0:
341
+ _fail("excerpt.text", "must be an exact contiguous substring of session_source_or_none")
342
+ start = len(_utf8(source[:char_start], "session_source_or_none prefix"))
343
+ return {"start": start, "end": start + len(_utf8(excerpt, "excerpt.text"))}
344
+
345
+
346
+ def _empty_excerpt(state: str) -> dict[str, Any]:
347
+ return {
348
+ "state": state,
349
+ "text": None,
350
+ "excerpt_sha256": None,
351
+ "source_span_utf8": None,
352
+ "captured_at": None,
353
+ }
354
+
355
+
356
+ def _text_excerpt(
357
+ state: str,
358
+ text: str,
359
+ source: str,
360
+ *,
361
+ captured_at: str | None = None,
362
+ ) -> dict[str, Any]:
363
+ text = _bounded_external_text(text, "excerpt.text")
364
+ return {
365
+ "state": state,
366
+ "text": text,
367
+ "excerpt_sha256": _hash_text(text, "excerpt.text"),
368
+ "source_span_utf8": _span(source, text),
369
+ "captured_at": (
370
+ _timestamp_now()
371
+ if captured_at is None
372
+ else _timestamp(captured_at, "captured_at")
373
+ ),
374
+ }
375
+
376
+
377
+ def _template(row: Mapping[str, Any]) -> dict[str, Any]:
378
+ if not isinstance(row, Mapping):
379
+ _fail("row", "must be a mapping")
380
+ raw = copy.deepcopy(dict(row))
381
+ required = {"surface", "row_id", "claim", "source", "anchor", "verdict", "detail"}
382
+ allowed = required | {"schema_version", "excerpt", "cache", "content_handling", "row_sha256"}
383
+ _closed(raw, "row template", required, allowed)
384
+ if raw.get("schema_version", SCHEMA_VERSION) != SCHEMA_VERSION:
385
+ _fail("schema_version", f"must equal {SCHEMA_VERSION}")
386
+ if raw["surface"] != SURFACE:
387
+ _fail("surface", f"must equal {SURFACE}")
388
+ row_id = _string(raw["row_id"], "row_id", minimum=1, maximum=200)
389
+ if _ROW_ID_RE.fullmatch(row_id) is None:
390
+ _fail("row_id", "must match ^EVR-[A-Za-z0-9._:-]+$")
391
+
392
+ claim = _closed(raw["claim"], "claim", {"claim_id", "text", "paper_locator", "selection_tier"})
393
+ normalized_claim = {
394
+ "claim_id": _string(claim["claim_id"], "claim.claim_id", minimum=1, maximum=200),
395
+ "text": _string(claim["text"], "claim.text", minimum=1, maximum=2000),
396
+ "paper_locator": _string(claim["paper_locator"], "claim.paper_locator", minimum=1, maximum=500),
397
+ "selection_tier": _enum(claim["selection_tier"], "claim.selection_tier", SELECTION_TIERS),
398
+ }
399
+
400
+ source = _closed(
401
+ raw["source"],
402
+ "source",
403
+ {"ref_slug", "display_label"},
404
+ {
405
+ "ref_slug",
406
+ "display_label",
407
+ "source_content_sha256",
408
+ "source_content_utf8_bytes",
409
+ "source_artifact_sha256",
410
+ },
411
+ )
412
+ ref_slug = _nullable_string(source["ref_slug"], "source.ref_slug", minimum=1, maximum=300)
413
+ if ref_slug is not None and _REF_SLUG_RE.fullmatch(ref_slug) is None:
414
+ _fail("source.ref_slug", "does not match the citation slug grammar")
415
+ display_label = _nullable_string(source["display_label"], "source.display_label", minimum=1, maximum=500)
416
+ artifact_sha = _sha(source.get("source_artifact_sha256"), "source.source_artifact_sha256", nullable=True)
417
+
418
+ anchor = _closed(
419
+ raw["anchor"],
420
+ "anchor",
421
+ {"kind", "value_encoded"},
422
+ {"kind", "value_encoded", "value_decoded"},
423
+ )
424
+ kind = _enum(anchor["kind"], "anchor.kind", ANCHOR_KINDS)
425
+ encoded = _string(anchor["value_encoded"], "anchor.value_encoded", maximum=12000)
426
+ decoded = strict_percent_decode(encoded)
427
+ if "value_decoded" in anchor and anchor["value_decoded"] != decoded:
428
+ _fail("anchor.value_decoded", "must equal one strict percent decode of anchor.value_encoded")
429
+ if kind == "none":
430
+ if encoded or decoded:
431
+ _fail("anchor", "kind=none requires empty encoded and decoded values")
432
+ elif not decoded.strip():
433
+ _fail("anchor.value_decoded", "non-none anchors require a non-empty decoded value")
434
+ if kind == "quote":
435
+ _bounded_external_text(decoded, "anchor.value_decoded")
436
+
437
+ handling = raw.get("content_handling", {})
438
+ if not isinstance(handling, dict):
439
+ _fail("content_handling", "must be an object")
440
+ allowed_handling = {"contains_external_text", "sharing_scope", "rights_basis"}
441
+ extra_handling = sorted(set(handling) - allowed_handling)
442
+ if extra_handling:
443
+ _fail("content_handling", f"unexpected key(s): {', '.join(extra_handling)}")
444
+ sharing_scope = _enum(handling.get("sharing_scope", "session_only"), "content_handling.sharing_scope", SHARING_SCOPES)
445
+ rights_basis = _enum(handling.get("rights_basis", "not_assessed"), "content_handling.rights_basis", RIGHTS_BASES)
446
+ if (sharing_scope == "user_confirmed_shareable") != (rights_basis == "user_declared_authorized"):
447
+ _fail("content_handling", "user_confirmed_shareable and user_declared_authorized must appear together")
448
+
449
+ return {
450
+ "schema_version": SCHEMA_VERSION,
451
+ "surface": SURFACE,
452
+ "row_id": row_id,
453
+ "claim": normalized_claim,
454
+ "source": {
455
+ "ref_slug": ref_slug,
456
+ "display_label": display_label,
457
+ "source_content_sha256": None,
458
+ "source_content_utf8_bytes": None,
459
+ "source_artifact_sha256": artifact_sha,
460
+ },
461
+ "anchor": {"kind": kind, "value_encoded": encoded, "value_decoded": decoded},
462
+ "verdict": _enum(raw["verdict"], "verdict", VERDICTS),
463
+ "detail": _string(raw["detail"], "detail", maximum=2000),
464
+ "content_handling": {
465
+ "contains_external_text": False,
466
+ "sharing_scope": sharing_scope,
467
+ "rights_basis": rights_basis,
468
+ },
469
+ }
470
+
471
+
472
+ def _advisory_template(row: Mapping[str, Any]) -> dict[str, Any]:
473
+ """Normalize caller-supplied metadata for one #681 evidence row."""
474
+
475
+ if not isinstance(row, Mapping):
476
+ _fail("row", "must be a mapping")
477
+ raw = copy.deepcopy(dict(row))
478
+ required = {"surface", "row_id", "coverage_subject", "source", "anchor"}
479
+ allowed = required | {
480
+ "schema_version",
481
+ "excerpt",
482
+ "cache",
483
+ "content_handling",
484
+ "row_sha256",
485
+ }
486
+ _closed(raw, "row template", required, allowed)
487
+ if raw.get("schema_version", ADVISORY_SCHEMA_VERSION) != ADVISORY_SCHEMA_VERSION:
488
+ _fail("schema_version", f"must equal {ADVISORY_SCHEMA_VERSION}")
489
+ if raw["surface"] != ADVISORY_SURFACE:
490
+ _fail("surface", f"must equal {ADVISORY_SURFACE}")
491
+ row_id = _string(raw["row_id"], "row_id", minimum=1, maximum=200)
492
+ if _ROW_ID_RE.fullmatch(row_id) is None:
493
+ _fail("row_id", "must match ^EVR-[A-Za-z0-9._:-]+$")
494
+
495
+ subject = _closed(
496
+ raw["coverage_subject"],
497
+ "coverage_subject",
498
+ {
499
+ "requirement_id",
500
+ "requirement_pointer",
501
+ "authority_anchor_pointer",
502
+ "expectation_field_id",
503
+ "expectation_pointer",
504
+ "expectation_digest",
505
+ "document_locator",
506
+ },
507
+ )
508
+ requirement_pointer = _json_pointer(
509
+ subject["requirement_pointer"], "coverage_subject.requirement_pointer"
510
+ )
511
+ if not re.fullmatch(r"/(?:profiles|overlays)/[0-9]+/requirements/[0-9]+", requirement_pointer):
512
+ _fail("coverage_subject.requirement_pointer", "must point to one exact requirement")
513
+ authority_anchor_pointer = _json_pointer(
514
+ subject["authority_anchor_pointer"],
515
+ "coverage_subject.authority_anchor_pointer",
516
+ )
517
+ if authority_anchor_pointer != requirement_pointer + "/authority_anchor":
518
+ _fail(
519
+ "coverage_subject.authority_anchor_pointer",
520
+ "must be the requirement's exact authority_anchor pointer",
521
+ )
522
+ expectation_pointer = _json_pointer(
523
+ subject["expectation_pointer"], "coverage_subject.expectation_pointer"
524
+ )
525
+ if re.fullmatch(
526
+ re.escape(requirement_pointer) + r"/structured_expectations/[0-9]+",
527
+ expectation_pointer,
528
+ ) is None:
529
+ _fail(
530
+ "coverage_subject.expectation_pointer",
531
+ "must point to one structured expectation under the requirement",
532
+ )
533
+ locator = _closed(
534
+ subject["document_locator"],
535
+ "coverage_subject.document_locator",
536
+ {"kind", "value", "provenance"},
537
+ )
538
+ if locator["provenance"] != DOCUMENT_LOCATOR_PROVENANCE:
539
+ _fail(
540
+ "coverage_subject.document_locator.provenance",
541
+ f"must equal {DOCUMENT_LOCATOR_PROVENANCE}",
542
+ )
543
+
544
+ source = _closed(
545
+ raw["source"],
546
+ "source",
547
+ {"artifact_id", "relative_path", "source_artifact_sha256", "source_artifact_size_bytes"},
548
+ {
549
+ "artifact_id",
550
+ "relative_path",
551
+ "source_artifact_sha256",
552
+ "source_artifact_size_bytes",
553
+ "source_content_sha256",
554
+ "source_content_utf8_bytes",
555
+ },
556
+ )
557
+ anchor = _closed(
558
+ raw["anchor"],
559
+ "anchor",
560
+ {"kind", "value_encoded"},
561
+ {"kind", "value_encoded", "value_decoded"},
562
+ )
563
+ kind = _enum(anchor["kind"], "anchor.kind", ADVISORY_ANCHOR_KINDS)
564
+ encoded = _string(anchor["value_encoded"], "anchor.value_encoded", maximum=12000)
565
+ decoded = strict_percent_decode(encoded)
566
+ if "value_decoded" in anchor and anchor["value_decoded"] != decoded:
567
+ _fail(
568
+ "anchor.value_decoded",
569
+ "must equal one strict percent decode of anchor.value_encoded",
570
+ )
571
+ if kind == "none":
572
+ if encoded or decoded:
573
+ _fail("anchor", "kind=none requires empty encoded and decoded values")
574
+ else:
575
+ if not decoded.strip():
576
+ _fail("anchor.value_decoded", "quote anchors require non-empty text")
577
+ _bounded_external_text(decoded, "anchor.value_decoded")
578
+
579
+ handling = raw.get("content_handling", {})
580
+ if not isinstance(handling, dict):
581
+ _fail("content_handling", "must be an object")
582
+ extra_handling = sorted(
583
+ set(handling) - {"contains_external_text", "sharing_scope", "rights_basis"}
584
+ )
585
+ if extra_handling:
586
+ _fail("content_handling", f"unexpected key(s): {', '.join(extra_handling)}")
587
+ sharing_scope = _enum(
588
+ handling.get("sharing_scope", "session_only"),
589
+ "content_handling.sharing_scope",
590
+ SHARING_SCOPES,
591
+ )
592
+ rights_basis = _enum(
593
+ handling.get("rights_basis", "not_assessed"),
594
+ "content_handling.rights_basis",
595
+ RIGHTS_BASES,
596
+ )
597
+ if (sharing_scope == "user_confirmed_shareable") != (
598
+ rights_basis == "user_declared_authorized"
599
+ ):
600
+ _fail(
601
+ "content_handling",
602
+ "user_confirmed_shareable and user_declared_authorized must appear together",
603
+ )
604
+
605
+ return {
606
+ "schema_version": ADVISORY_SCHEMA_VERSION,
607
+ "surface": ADVISORY_SURFACE,
608
+ "row_id": row_id,
609
+ "coverage_subject": {
610
+ "requirement_id": _authority_identifier(
611
+ subject["requirement_id"], "coverage_subject.requirement_id"
612
+ ),
613
+ "requirement_pointer": requirement_pointer,
614
+ "authority_anchor_pointer": authority_anchor_pointer,
615
+ "expectation_field_id": _authority_identifier(
616
+ subject["expectation_field_id"],
617
+ "coverage_subject.expectation_field_id",
618
+ ),
619
+ "expectation_pointer": expectation_pointer,
620
+ "expectation_digest": _sha(
621
+ subject["expectation_digest"], "coverage_subject.expectation_digest"
622
+ ),
623
+ "document_locator": {
624
+ "kind": _enum(
625
+ locator["kind"],
626
+ "coverage_subject.document_locator.kind",
627
+ DOCUMENT_LOCATOR_KINDS,
628
+ ),
629
+ "value": _single_line(
630
+ locator["value"],
631
+ "coverage_subject.document_locator.value",
632
+ maximum=500,
633
+ ),
634
+ "provenance": DOCUMENT_LOCATOR_PROVENANCE,
635
+ },
636
+ },
637
+ "source": {
638
+ "artifact_id": _authority_identifier(source["artifact_id"], "source.artifact_id"),
639
+ "relative_path": _advisory_relative_path(
640
+ source["relative_path"], "source.relative_path"
641
+ ),
642
+ "source_artifact_sha256": _sha(
643
+ source["source_artifact_sha256"], "source.source_artifact_sha256"
644
+ ),
645
+ "source_artifact_size_bytes": _integer(
646
+ source["source_artifact_size_bytes"],
647
+ "source.source_artifact_size_bytes",
648
+ ),
649
+ "source_content_sha256": None,
650
+ "source_content_utf8_bytes": None,
651
+ },
652
+ "anchor": {"kind": kind, "value_encoded": encoded, "value_decoded": decoded},
653
+ "content_handling": {
654
+ "contains_external_text": False,
655
+ "sharing_scope": sharing_scope,
656
+ "rights_basis": rights_basis,
657
+ },
658
+ }
659
+
660
+
661
+ def build_advisory(
662
+ row: Mapping[str, Any],
663
+ session_source_or_none: str | None,
664
+ *,
665
+ extracted_text: str | None = None,
666
+ failure_state: str | None = None,
667
+ cached_row: Mapping[str, Any] | None = None,
668
+ captured_at: str | None = None,
669
+ ) -> dict[str, Any]:
670
+ """Build one closed #681 row from explicit evaluator metadata and source text.
671
+
672
+ This function verifies provenance only. It never infers a coverage status,
673
+ invokes an evaluator, opens a source path, or performs retrieval.
674
+ """
675
+
676
+ output = _advisory_template(row)
677
+ if output["source"]["source_artifact_size_bytes"] > 64 * 1024 * 1024:
678
+ _fail("source.source_artifact_size_bytes", "must be <= 67108864")
679
+ if cached_row is not None:
680
+ _fail("cached_row", "evidence-row/1.1 forbids cache reuse")
681
+ if session_source_or_none is not None and not isinstance(session_source_or_none, str):
682
+ _fail("session_source_or_none", "must be a string or null")
683
+ kind = output["anchor"]["kind"]
684
+ decoded = output["anchor"]["value_decoded"]
685
+ if extracted_text is not None:
686
+ extracted_text = _bounded_external_text(extracted_text, "extracted_text")
687
+ if kind != "quote" or extracted_text != decoded:
688
+ _fail("extracted_text", "must exactly equal the decoded quote anchor")
689
+
690
+ if failure_state is not None:
691
+ if captured_at is not None:
692
+ _fail("captured_at", "failure states require null captured_at")
693
+ state = _enum(failure_state, "failure_state", ADVISORY_FAILURE_STATES)
694
+ if kind != "none":
695
+ _fail("anchor.kind", f"state={state} requires anchor.kind=none")
696
+ excerpt = _empty_excerpt(state)
697
+ elif kind == "quote":
698
+ if captured_at is None:
699
+ _fail(
700
+ "captured_at",
701
+ "agent_extracted requires an explicit evaluator-supplied timestamp",
702
+ )
703
+ if session_source_or_none is None:
704
+ _fail("failure_state", "a missing source requires an explicit failure state")
705
+ if decoded not in session_source_or_none:
706
+ _fail(
707
+ "anchor.value_decoded",
708
+ "must be an exact contiguous substring of session_source_or_none",
709
+ )
710
+ source_bytes = _utf8(session_source_or_none, "session_source_or_none")
711
+ if len(source_bytes) > 64 * 1024 * 1024:
712
+ _fail("session_source_or_none", "exceeds 67108864 UTF-8 bytes")
713
+ output["source"]["source_content_sha256"] = _hash_bytes(source_bytes)
714
+ output["source"]["source_content_utf8_bytes"] = len(source_bytes)
715
+ state = "agent_extracted"
716
+ excerpt = _text_excerpt(
717
+ state,
718
+ decoded,
719
+ session_source_or_none,
720
+ captured_at=captured_at,
721
+ )
722
+ else:
723
+ if captured_at is not None:
724
+ _fail("captured_at", "checked_no_match requires null captured_at")
725
+ if session_source_or_none is None:
726
+ _fail("failure_state", "a missing source requires an explicit failure state")
727
+ source_bytes = _utf8(session_source_or_none, "session_source_or_none")
728
+ if len(source_bytes) > 64 * 1024 * 1024:
729
+ _fail("session_source_or_none", "exceeds 67108864 UTF-8 bytes")
730
+ output["source"]["source_content_sha256"] = _hash_bytes(source_bytes)
731
+ output["source"]["source_content_utf8_bytes"] = len(source_bytes)
732
+ state = "checked_no_match"
733
+ excerpt = _empty_excerpt(state)
734
+
735
+ output["excerpt"] = excerpt
736
+ output["cache"] = {"status": "not_used", "key_sha256": None}
737
+ output["content_handling"]["contains_external_text"] = bool(
738
+ excerpt["text"] is not None or (kind == "quote" and decoded)
739
+ )
740
+ output["row_sha256"] = _row_sha256(output)
741
+ replay_source = (
742
+ session_source_or_none if state in ADVISORY_SOURCE_BOUND_STATES else None
743
+ )
744
+ return _validate_v1_1(output, replay_source)
745
+
746
+
747
+ def build(
748
+ row: Mapping[str, Any],
749
+ session_source_or_none: str | None,
750
+ *,
751
+ extracted_text: str | None = None,
752
+ failure_state: str | None = None,
753
+ cached_row: Mapping[str, Any] | None = None,
754
+ ) -> dict[str, Any]:
755
+ """Build one closed evidence row from metadata and explicit session text.
756
+
757
+ ``failure_state`` accepts only an explicit empty state. Positive states and
758
+ ``unconfirmed_anchor`` are derived, never caller-asserted. ``cached_row`` is
759
+ an optional previously built whole row; a matching source-bound cache key
760
+ replays its excerpt block and original timestamp.
761
+ """
762
+
763
+ output = _template(row)
764
+ kind = output["anchor"]["kind"]
765
+ decoded = output["anchor"]["value_decoded"]
766
+ if session_source_or_none is not None and not isinstance(session_source_or_none, str):
767
+ _fail("session_source_or_none", "must be a string or null")
768
+ if session_source_or_none is not None:
769
+ source_bytes = _utf8(session_source_or_none, "session_source_or_none")
770
+ source_sha = _hash_bytes(source_bytes)
771
+ else:
772
+ source_bytes = b""
773
+ source_sha = None
774
+
775
+ if failure_state is not None:
776
+ failure_state = _enum(failure_state, "failure_state", FAILURE_STATES)
777
+ # A current explicit failure always wins. Do not even inspect a cached
778
+ # success: stale evidence must not overwrite access/retrieval reality.
779
+ if extracted_text is not None:
780
+ _fail("extracted_text", "cannot be combined with failure_state")
781
+ if failure_state == "anchorless" and kind != "none":
782
+ _fail("failure_state", "anchorless requires anchor.kind=none")
783
+ if failure_state != "anchorless" and kind == "none":
784
+ _fail("failure_state", "anchor.kind=none can only produce anchorless")
785
+ if failure_state in {"source_missing", "access_failed", "retrieval_failed"} and session_source_or_none is not None:
786
+ _fail("session_source_or_none", f"must be null for {failure_state}")
787
+ state = failure_state
788
+ excerpt = _empty_excerpt(state)
789
+ elif kind == "none":
790
+ if extracted_text is not None:
791
+ _fail("extracted_text", "anchor.kind=none cannot carry an excerpt")
792
+ state = "anchorless"
793
+ excerpt = _empty_excerpt(state)
794
+ elif kind == "paragraph":
795
+ if extracted_text is not None:
796
+ _fail("extracted_text", "paragraph anchors are an explicit not_checked empty state in V1")
797
+ state = "not_checked"
798
+ excerpt = _empty_excerpt(state)
799
+ elif session_source_or_none is None:
800
+ if extracted_text is not None:
801
+ _fail("extracted_text", "cannot be accepted without session-held source text")
802
+ state = "source_missing"
803
+ excerpt = _empty_excerpt(state)
804
+ else:
805
+ if output["source"]["ref_slug"] is None:
806
+ _fail("source.ref_slug", "source-bound quote/page/section processing requires a citation slug")
807
+ output["source"]["source_content_sha256"] = source_sha
808
+ output["source"]["source_content_utf8_bytes"] = len(source_bytes)
809
+
810
+ candidate_text: str | None = None
811
+ if kind == "quote":
812
+ if extracted_text is not None:
813
+ _fail("extracted_text", "quote evidence is fixed by the decoded writer anchor")
814
+ prospective_state = "verified_exact_match" if decoded in session_source_or_none else "unconfirmed_anchor"
815
+ elif kind in {"page", "section"}:
816
+ if extracted_text is not None:
817
+ candidate_text = _bounded_external_text(extracted_text, "extracted_text")
818
+ _span(session_source_or_none, candidate_text)
819
+ prospective_state = "agent_extracted"
820
+ else:
821
+ prospective_state = "not_checked"
822
+ else: # Defensive totality; paragraph/none returned above.
823
+ _fail("anchor.kind", f"unsupported builder state for {kind}")
824
+
825
+ cached: dict[str, Any] | None = None
826
+ if cached_row is not None:
827
+ try:
828
+ cached = validate(cached_row)
829
+ except EvidenceRowError:
830
+ cached = None
831
+ if cached is not None and cached["excerpt"]["state"] not in SOURCE_BOUND_STATES:
832
+ cached = None
833
+ if cached is not None and kind in {"page", "section"} and extracted_text is None:
834
+ cached_text = cached["excerpt"]["text"]
835
+ if cached["excerpt"]["state"] == "agent_extracted" and isinstance(cached_text, str):
836
+ candidate_text = cached_text
837
+ prospective_state = "agent_extracted"
838
+ if cached is not None:
839
+ expected_key = _cache_key(
840
+ source_sha, # type: ignore[arg-type]
841
+ kind,
842
+ decoded,
843
+ claim_id=output["claim"]["claim_id"],
844
+ ref_slug=output["source"]["ref_slug"],
845
+ state=prospective_state,
846
+ excerpt_candidate_sha256=(
847
+ _hash_text(candidate_text, "extracted_text")
848
+ if prospective_state == "agent_extracted" and candidate_text is not None
849
+ else None
850
+ ),
851
+ )
852
+ if cached["cache"]["key_sha256"] != expected_key:
853
+ cached = None
854
+ if cached is not None:
855
+ # Match first, then replay against the exact current source. This
856
+ # catches a forged span/excerpt even if an attacker recomputed the
857
+ # row digest and cache key around it.
858
+ try:
859
+ cached = validate(cached, session_source_or_none)
860
+ except EvidenceRowError:
861
+ cached = None
862
+ if cached is not None:
863
+ output["excerpt"] = copy.deepcopy(cached["excerpt"])
864
+ output["cache"] = {"status": "hit", "key_sha256": expected_key}
865
+ output["content_handling"]["contains_external_text"] = bool(
866
+ output["excerpt"]["text"] is not None or (kind == "quote" and decoded)
867
+ )
868
+ output["row_sha256"] = _row_sha256(output)
869
+ return validate(output, session_source_or_none)
870
+
871
+ if kind == "quote":
872
+ if decoded in session_source_or_none:
873
+ state = "verified_exact_match"
874
+ excerpt = _text_excerpt(state, decoded, session_source_or_none)
875
+ else:
876
+ state = "unconfirmed_anchor"
877
+ excerpt = _empty_excerpt(state)
878
+ elif kind in {"page", "section"}:
879
+ if extracted_text is None:
880
+ state = "not_checked"
881
+ excerpt = _empty_excerpt(state)
882
+ output["source"]["source_content_sha256"] = None
883
+ output["source"]["source_content_utf8_bytes"] = None
884
+ else:
885
+ state = "agent_extracted"
886
+ excerpt = _text_excerpt(state, candidate_text, session_source_or_none) # type: ignore[arg-type]
887
+
888
+ if state in SOURCE_BOUND_STATES:
889
+ expected_key = _cache_key(
890
+ source_sha, # type: ignore[arg-type]
891
+ kind,
892
+ decoded,
893
+ claim_id=output["claim"]["claim_id"],
894
+ ref_slug=output["source"]["ref_slug"],
895
+ state=state,
896
+ excerpt_candidate_sha256=(
897
+ excerpt["excerpt_sha256"] if state == "agent_extracted" else None
898
+ ),
899
+ )
900
+ output["cache"] = {"status": "miss", "key_sha256": expected_key}
901
+ else:
902
+ output["cache"] = {"status": "not_used", "key_sha256": None}
903
+ output["excerpt"] = excerpt
904
+
905
+ if state in EMPTY_STATES:
906
+ output["source"]["source_content_sha256"] = None
907
+ output["source"]["source_content_utf8_bytes"] = None
908
+ output["cache"] = {"status": "not_used", "key_sha256": None}
909
+ output["excerpt"] = excerpt
910
+ output["content_handling"]["contains_external_text"] = bool(
911
+ excerpt["text"] is not None or (kind == "quote" and decoded)
912
+ )
913
+ output["row_sha256"] = _row_sha256(output)
914
+ return validate(output, session_source_or_none if state in SOURCE_BOUND_STATES else None)
915
+
916
+
917
+ def _validate_v1_0(row: Mapping[str, Any], session_source_or_none: str | None = None) -> dict[str, Any]:
918
+ """Return a deep-copied validated row; optionally replay source binding."""
919
+
920
+ if not isinstance(row, Mapping):
921
+ _fail("row", "must be a mapping")
922
+ value = copy.deepcopy(dict(row))
923
+ root_keys = {
924
+ "schema_version", "surface", "row_id", "claim", "source", "anchor",
925
+ "verdict", "detail", "excerpt", "cache", "content_handling", "row_sha256",
926
+ }
927
+ _closed(value, "row", root_keys)
928
+ if value["schema_version"] != SCHEMA_VERSION:
929
+ _fail("schema_version", f"must equal {SCHEMA_VERSION}")
930
+ if value["surface"] != SURFACE:
931
+ _fail("surface", f"must equal {SURFACE}")
932
+ row_id = _string(value["row_id"], "row_id", minimum=1, maximum=200)
933
+ if _ROW_ID_RE.fullmatch(row_id) is None:
934
+ _fail("row_id", "does not match the EVR identifier grammar")
935
+
936
+ claim = _closed(value["claim"], "claim", {"claim_id", "text", "paper_locator", "selection_tier"})
937
+ _string(claim["claim_id"], "claim.claim_id", minimum=1, maximum=200)
938
+ _string(claim["text"], "claim.text", minimum=1, maximum=2000)
939
+ _string(claim["paper_locator"], "claim.paper_locator", minimum=1, maximum=500)
940
+ _enum(claim["selection_tier"], "claim.selection_tier", SELECTION_TIERS)
941
+
942
+ source = _closed(
943
+ value["source"], "source",
944
+ {"ref_slug", "display_label", "source_content_sha256", "source_content_utf8_bytes", "source_artifact_sha256"},
945
+ )
946
+ ref_slug = _nullable_string(source["ref_slug"], "source.ref_slug", minimum=1, maximum=300)
947
+ if ref_slug is not None and _REF_SLUG_RE.fullmatch(ref_slug) is None:
948
+ _fail("source.ref_slug", "does not match the citation slug grammar")
949
+ _nullable_string(source["display_label"], "source.display_label", minimum=1, maximum=500)
950
+ content_sha = _sha(source["source_content_sha256"], "source.source_content_sha256", nullable=True)
951
+ content_length = source["source_content_utf8_bytes"]
952
+ if content_length is not None:
953
+ content_length = _integer(content_length, "source.source_content_utf8_bytes")
954
+ _sha(source["source_artifact_sha256"], "source.source_artifact_sha256", nullable=True)
955
+
956
+ anchor = _closed(value["anchor"], "anchor", {"kind", "value_encoded", "value_decoded"})
957
+ kind = _enum(anchor["kind"], "anchor.kind", ANCHOR_KINDS)
958
+ encoded = _string(anchor["value_encoded"], "anchor.value_encoded", maximum=12000)
959
+ decoded = _string(anchor["value_decoded"], "anchor.value_decoded", maximum=TEXT_CHAR_CAP)
960
+ if strict_percent_decode(encoded) != decoded:
961
+ _fail("anchor.value_decoded", "must equal one strict percent decode of anchor.value_encoded")
962
+ if kind == "none":
963
+ if encoded or decoded:
964
+ _fail("anchor", "kind=none requires empty encoded and decoded values")
965
+ elif not decoded.strip():
966
+ _fail("anchor.value_decoded", "non-none anchors require a non-empty decoded value")
967
+ if kind == "quote":
968
+ _bounded_external_text(decoded, "anchor.value_decoded")
969
+
970
+ _enum(value["verdict"], "verdict", VERDICTS)
971
+ _string(value["detail"], "detail", maximum=2000)
972
+
973
+ excerpt = _closed(value["excerpt"], "excerpt", {"state", "text", "excerpt_sha256", "source_span_utf8", "captured_at"})
974
+ state = _enum(excerpt["state"], "excerpt.state", EXCERPT_STATES)
975
+ text_value = excerpt["text"]
976
+ if text_value is not None:
977
+ text_value = _bounded_external_text(text_value, "excerpt.text")
978
+ excerpt_sha = _sha(excerpt["excerpt_sha256"], "excerpt.excerpt_sha256", nullable=True)
979
+ span = excerpt["source_span_utf8"]
980
+ if span is not None:
981
+ span = _closed(span, "excerpt.source_span_utf8", {"start", "end"})
982
+ start = _integer(span["start"], "excerpt.source_span_utf8.start")
983
+ end = _integer(span["end"], "excerpt.source_span_utf8.end", minimum=1)
984
+ if end <= start:
985
+ _fail("excerpt.source_span_utf8", "end must be greater than start")
986
+ if content_length is not None and end > content_length:
987
+ _fail("excerpt.source_span_utf8.end", "exceeds source_content_utf8_bytes")
988
+ captured = excerpt["captured_at"]
989
+ if captured is not None:
990
+ _timestamp(captured, "excerpt.captured_at")
991
+
992
+ cache = _closed(value["cache"], "cache", {"status", "key_sha256"})
993
+ cache_status = _enum(cache["status"], "cache.status", CACHE_STATUSES)
994
+ cache_key = _sha(cache["key_sha256"], "cache.key_sha256", nullable=True)
995
+
996
+ handling = _closed(value["content_handling"], "content_handling", {"contains_external_text", "sharing_scope", "rights_basis"})
997
+ if not isinstance(handling["contains_external_text"], bool):
998
+ _fail("content_handling.contains_external_text", "must be a boolean")
999
+ sharing = _enum(handling["sharing_scope"], "content_handling.sharing_scope", SHARING_SCOPES)
1000
+ rights = _enum(handling["rights_basis"], "content_handling.rights_basis", RIGHTS_BASES)
1001
+ if (sharing == "user_confirmed_shareable") != (rights == "user_declared_authorized"):
1002
+ _fail("content_handling", "user-confirmed sharing and user-declared authorization must appear together")
1003
+ expected_external = bool(text_value is not None or (kind == "quote" and decoded))
1004
+ if handling["contains_external_text"] != expected_external:
1005
+ _fail("content_handling.contains_external_text", f"must equal derived value {expected_external}")
1006
+
1007
+ if state == "verified_exact_match" and kind != "quote":
1008
+ _fail("excerpt.state", "verified_exact_match requires anchor.kind=quote")
1009
+ if state == "agent_extracted" and kind not in {"page", "section"}:
1010
+ _fail("excerpt.state", "agent_extracted requires page or section anchor")
1011
+ if state == "unconfirmed_anchor" and kind != "quote":
1012
+ _fail("excerpt.state", "unconfirmed_anchor requires anchor.kind=quote")
1013
+ if kind == "paragraph" and state != "not_checked":
1014
+ _fail("excerpt.state", "paragraph anchors are not_checked in V1")
1015
+ if kind == "none" and state != "anchorless":
1016
+ _fail("excerpt.state", "anchor.kind=none requires anchorless")
1017
+ if state == "anchorless" and kind != "none":
1018
+ _fail("excerpt.state", "anchorless requires anchor.kind=none")
1019
+ if kind in {"page", "section"} and state not in {"agent_extracted", "not_checked", "source_missing", "access_failed", "retrieval_failed"}:
1020
+ _fail("excerpt.state", f"is not permitted with anchor.kind={kind}")
1021
+
1022
+ if state in EXCERPT_TEXT_STATES:
1023
+ if ref_slug is None:
1024
+ _fail("source.ref_slug", "positive excerpt states require a citation slug")
1025
+ if text_value is None or excerpt_sha is None or span is None or captured is None:
1026
+ _fail("excerpt", "positive excerpt states require text, digest, span, and captured_at")
1027
+ if content_sha is None or content_length is None:
1028
+ _fail("source", "positive excerpt states require source content digest and byte length")
1029
+ if excerpt_sha != _hash_text(text_value, "excerpt.text"):
1030
+ _fail("excerpt.excerpt_sha256", "does not match excerpt.text")
1031
+ else:
1032
+ if any(item is not None for item in (text_value, excerpt_sha, span, captured)):
1033
+ _fail("excerpt", f"state={state} requires explicit null text/hash/span/captured_at")
1034
+
1035
+ if state == "verified_exact_match" and text_value != decoded:
1036
+ _fail("excerpt.text", "verified_exact_match must equal the once-decoded quote anchor byte-for-byte")
1037
+ if state == "unconfirmed_anchor":
1038
+ if ref_slug is None or content_sha is None or content_length is None:
1039
+ _fail("source", "unconfirmed_anchor requires ref_slug and source content binding")
1040
+
1041
+ if state in SOURCE_BOUND_STATES:
1042
+ if cache_status not in {"miss", "hit"} or cache_key is None:
1043
+ _fail("cache", "source-bound states require miss/hit plus key_sha256")
1044
+ expected_key = _cache_key(
1045
+ content_sha, # type: ignore[arg-type]
1046
+ kind,
1047
+ decoded,
1048
+ claim_id=claim["claim_id"],
1049
+ ref_slug=ref_slug, # type: ignore[arg-type]
1050
+ state=state,
1051
+ excerpt_candidate_sha256=(excerpt_sha if state == "agent_extracted" else None),
1052
+ )
1053
+ if cache_key != expected_key:
1054
+ _fail("cache.key_sha256", "does not match the canonical source/anchor cache key")
1055
+ else:
1056
+ if content_sha is not None or content_length is not None:
1057
+ _fail("source", f"state={state} must not claim inspected source content")
1058
+ if cache_status != "not_used" or cache_key is not None:
1059
+ _fail("cache", f"state={state} requires not_used and null key")
1060
+
1061
+ if session_source_or_none is not None:
1062
+ if not isinstance(session_source_or_none, str):
1063
+ _fail("session_source_or_none", "must be a string or null")
1064
+ if state in SOURCE_BOUND_STATES:
1065
+ source_bytes = _utf8(session_source_or_none, "session_source_or_none")
1066
+ if content_sha != _hash_bytes(source_bytes) or content_length != len(source_bytes):
1067
+ _fail("source", "content digest/length does not match the supplied session source")
1068
+ if state in EXCERPT_TEXT_STATES:
1069
+ assert isinstance(span, dict) and isinstance(text_value, str)
1070
+ text_bytes = _utf8(text_value, "excerpt.text")
1071
+ if source_bytes[span["start"] : span["end"]] != text_bytes:
1072
+ _fail("excerpt.source_span_utf8", "does not select excerpt.text from the supplied source")
1073
+ elif decoded in session_source_or_none:
1074
+ _fail("excerpt.state", "unconfirmed_anchor is invalid because the decoded quote exactly matches the supplied source")
1075
+
1076
+ digest = _sha(value["row_sha256"], "row_sha256")
1077
+ if digest != _row_sha256(value):
1078
+ _fail("row_sha256", "does not match canonical row content")
1079
+ return value
1080
+
1081
+
1082
+ def _validate_v1_1(
1083
+ row: Mapping[str, Any], session_source_or_none: str | None = None
1084
+ ) -> dict[str, Any]:
1085
+ """Validate an evidence-row/1.1 carrier and optionally replay held content."""
1086
+
1087
+ if not isinstance(row, Mapping):
1088
+ _fail("row", "must be a mapping")
1089
+ value = copy.deepcopy(dict(row))
1090
+ root_keys = {
1091
+ "schema_version",
1092
+ "surface",
1093
+ "row_id",
1094
+ "coverage_subject",
1095
+ "source",
1096
+ "anchor",
1097
+ "excerpt",
1098
+ "cache",
1099
+ "content_handling",
1100
+ "row_sha256",
1101
+ }
1102
+ _closed(value, "row", root_keys)
1103
+ if value["schema_version"] != ADVISORY_SCHEMA_VERSION:
1104
+ _fail("schema_version", f"must equal {ADVISORY_SCHEMA_VERSION}")
1105
+ if value["surface"] != ADVISORY_SURFACE:
1106
+ _fail("surface", f"must equal {ADVISORY_SURFACE}")
1107
+ row_id = _string(value["row_id"], "row_id", minimum=1, maximum=200)
1108
+ if _ROW_ID_RE.fullmatch(row_id) is None:
1109
+ _fail("row_id", "does not match the EVR identifier grammar")
1110
+
1111
+ subject = _closed(
1112
+ value["coverage_subject"],
1113
+ "coverage_subject",
1114
+ {
1115
+ "requirement_id",
1116
+ "requirement_pointer",
1117
+ "authority_anchor_pointer",
1118
+ "expectation_field_id",
1119
+ "expectation_pointer",
1120
+ "expectation_digest",
1121
+ "document_locator",
1122
+ },
1123
+ )
1124
+ _authority_identifier(subject["requirement_id"], "coverage_subject.requirement_id")
1125
+ requirement_pointer = _json_pointer(
1126
+ subject["requirement_pointer"], "coverage_subject.requirement_pointer"
1127
+ )
1128
+ if re.fullmatch(r"/(?:profiles|overlays)/[0-9]+/requirements/[0-9]+", requirement_pointer) is None:
1129
+ _fail("coverage_subject.requirement_pointer", "must point to one exact requirement")
1130
+ authority_pointer = _json_pointer(
1131
+ subject["authority_anchor_pointer"], "coverage_subject.authority_anchor_pointer"
1132
+ )
1133
+ if authority_pointer != requirement_pointer + "/authority_anchor":
1134
+ _fail("coverage_subject.authority_anchor_pointer", "does not match requirement_pointer")
1135
+ _authority_identifier(
1136
+ subject["expectation_field_id"], "coverage_subject.expectation_field_id"
1137
+ )
1138
+ expectation_pointer = _json_pointer(
1139
+ subject["expectation_pointer"], "coverage_subject.expectation_pointer"
1140
+ )
1141
+ if re.fullmatch(
1142
+ re.escape(requirement_pointer) + r"/structured_expectations/[0-9]+",
1143
+ expectation_pointer,
1144
+ ) is None:
1145
+ _fail("coverage_subject.expectation_pointer", "does not belong to requirement_pointer")
1146
+ _sha(subject["expectation_digest"], "coverage_subject.expectation_digest")
1147
+ locator = _closed(
1148
+ subject["document_locator"],
1149
+ "coverage_subject.document_locator",
1150
+ {"kind", "value", "provenance"},
1151
+ )
1152
+ _enum(
1153
+ locator["kind"], "coverage_subject.document_locator.kind", DOCUMENT_LOCATOR_KINDS
1154
+ )
1155
+ _single_line(locator["value"], "coverage_subject.document_locator.value", maximum=500)
1156
+ if locator["provenance"] != DOCUMENT_LOCATOR_PROVENANCE:
1157
+ _fail(
1158
+ "coverage_subject.document_locator.provenance",
1159
+ f"must equal {DOCUMENT_LOCATOR_PROVENANCE}",
1160
+ )
1161
+
1162
+ source = _closed(
1163
+ value["source"],
1164
+ "source",
1165
+ {
1166
+ "artifact_id",
1167
+ "relative_path",
1168
+ "source_artifact_sha256",
1169
+ "source_artifact_size_bytes",
1170
+ "source_content_sha256",
1171
+ "source_content_utf8_bytes",
1172
+ },
1173
+ )
1174
+ _authority_identifier(source["artifact_id"], "source.artifact_id")
1175
+ _advisory_relative_path(source["relative_path"], "source.relative_path")
1176
+ _sha(source["source_artifact_sha256"], "source.source_artifact_sha256")
1177
+ artifact_size = _integer(
1178
+ source["source_artifact_size_bytes"], "source.source_artifact_size_bytes"
1179
+ )
1180
+ if artifact_size > 64 * 1024 * 1024:
1181
+ _fail("source.source_artifact_size_bytes", "must be <= 67108864")
1182
+ content_sha = _sha(
1183
+ source["source_content_sha256"], "source.source_content_sha256", nullable=True
1184
+ )
1185
+ content_length = source["source_content_utf8_bytes"]
1186
+ if content_length is not None:
1187
+ content_length = _integer(content_length, "source.source_content_utf8_bytes")
1188
+ if content_length > 64 * 1024 * 1024:
1189
+ _fail("source.source_content_utf8_bytes", "must be <= 67108864")
1190
+
1191
+ anchor = _closed(value["anchor"], "anchor", {"kind", "value_encoded", "value_decoded"})
1192
+ kind = _enum(anchor["kind"], "anchor.kind", ADVISORY_ANCHOR_KINDS)
1193
+ encoded = _string(anchor["value_encoded"], "anchor.value_encoded", maximum=12000)
1194
+ decoded = _string(anchor["value_decoded"], "anchor.value_decoded", maximum=TEXT_CHAR_CAP)
1195
+ if strict_percent_decode(encoded) != decoded:
1196
+ _fail("anchor.value_decoded", "must equal one strict percent decode of anchor.value_encoded")
1197
+ if kind == "none":
1198
+ if encoded or decoded:
1199
+ _fail("anchor", "kind=none requires empty encoded and decoded values")
1200
+ else:
1201
+ _bounded_external_text(decoded, "anchor.value_decoded")
1202
+
1203
+ excerpt = _closed(
1204
+ value["excerpt"],
1205
+ "excerpt",
1206
+ {"state", "text", "excerpt_sha256", "source_span_utf8", "captured_at"},
1207
+ )
1208
+ state = _enum(excerpt["state"], "excerpt.state", ADVISORY_EXCERPT_STATES)
1209
+ text_value = excerpt["text"]
1210
+ if text_value is not None:
1211
+ text_value = _bounded_external_text(text_value, "excerpt.text")
1212
+ excerpt_sha = _sha(excerpt["excerpt_sha256"], "excerpt.excerpt_sha256", nullable=True)
1213
+ span = excerpt["source_span_utf8"]
1214
+ if span is not None:
1215
+ span = _closed(span, "excerpt.source_span_utf8", {"start", "end"})
1216
+ start = _integer(span["start"], "excerpt.source_span_utf8.start")
1217
+ end = _integer(span["end"], "excerpt.source_span_utf8.end", minimum=1)
1218
+ if end <= start:
1219
+ _fail("excerpt.source_span_utf8", "end must be greater than start")
1220
+ if content_length is not None and end > content_length:
1221
+ _fail("excerpt.source_span_utf8.end", "exceeds source_content_utf8_bytes")
1222
+ captured = excerpt["captured_at"]
1223
+ if captured is not None:
1224
+ _timestamp(captured, "excerpt.captured_at")
1225
+
1226
+ cache = _closed(value["cache"], "cache", {"status", "key_sha256"})
1227
+ if cache != {"status": "not_used", "key_sha256": None}:
1228
+ _fail("cache", "evidence-row/1.1 requires not_used and null key_sha256")
1229
+ handling = _closed(
1230
+ value["content_handling"],
1231
+ "content_handling",
1232
+ {"contains_external_text", "sharing_scope", "rights_basis"},
1233
+ )
1234
+ if not isinstance(handling["contains_external_text"], bool):
1235
+ _fail("content_handling.contains_external_text", "must be a boolean")
1236
+ sharing = _enum(
1237
+ handling["sharing_scope"], "content_handling.sharing_scope", SHARING_SCOPES
1238
+ )
1239
+ rights = _enum(handling["rights_basis"], "content_handling.rights_basis", RIGHTS_BASES)
1240
+ if (sharing == "user_confirmed_shareable") != (
1241
+ rights == "user_declared_authorized"
1242
+ ):
1243
+ _fail(
1244
+ "content_handling",
1245
+ "user-confirmed sharing and user-declared authorization must appear together",
1246
+ )
1247
+
1248
+ if state == "agent_extracted":
1249
+ if kind != "quote" or not decoded:
1250
+ _fail("anchor", "agent_extracted requires a non-empty quote anchor")
1251
+ if text_value != decoded:
1252
+ _fail("excerpt.text", "agent_extracted must equal the decoded quote anchor")
1253
+ if any(item is None for item in (content_sha, content_length, excerpt_sha, span, captured)):
1254
+ _fail(
1255
+ "excerpt",
1256
+ "agent_extracted requires source binding, text hash, span, and captured_at",
1257
+ )
1258
+ if excerpt_sha != _hash_text(text_value, "excerpt.text"):
1259
+ _fail("excerpt.excerpt_sha256", "does not match excerpt.text")
1260
+ elif state == "checked_no_match":
1261
+ if kind != "none" or content_sha is None or content_length is None:
1262
+ _fail("excerpt", "checked_no_match requires an empty anchor and source binding")
1263
+ if any(item is not None for item in (text_value, excerpt_sha, span, captured)):
1264
+ _fail("excerpt", "checked_no_match requires a null excerpt payload")
1265
+ else:
1266
+ if kind != "none":
1267
+ _fail("anchor.kind", f"state={state} requires anchor.kind=none")
1268
+ if content_sha is not None or content_length is not None:
1269
+ _fail("source", f"state={state} requires null source-content binding")
1270
+ if any(item is not None for item in (text_value, excerpt_sha, span, captured)):
1271
+ _fail("excerpt", f"state={state} requires a null excerpt payload")
1272
+
1273
+ expected_external = state == "agent_extracted"
1274
+ if handling["contains_external_text"] != expected_external:
1275
+ _fail(
1276
+ "content_handling.contains_external_text",
1277
+ f"must equal derived value {expected_external}",
1278
+ )
1279
+
1280
+ if session_source_or_none is not None:
1281
+ if not isinstance(session_source_or_none, str):
1282
+ _fail("session_source_or_none", "must be a string or null")
1283
+ if state in ADVISORY_SOURCE_BOUND_STATES:
1284
+ source_bytes = _utf8(session_source_or_none, "session_source_or_none")
1285
+ if len(source_bytes) > 64 * 1024 * 1024:
1286
+ _fail("session_source_or_none", "exceeds 67108864 UTF-8 bytes")
1287
+ if content_sha != _hash_bytes(source_bytes) or content_length != len(source_bytes):
1288
+ _fail("source", "content digest/length does not match the supplied session source")
1289
+ if state == "agent_extracted":
1290
+ assert isinstance(span, dict) and isinstance(text_value, str)
1291
+ if source_bytes[span["start"] : span["end"]] != _utf8(
1292
+ text_value, "excerpt.text"
1293
+ ):
1294
+ _fail(
1295
+ "excerpt.source_span_utf8",
1296
+ "does not select excerpt.text from the supplied source",
1297
+ )
1298
+ if decoded not in session_source_or_none:
1299
+ _fail("anchor.value_decoded", "does not occur in supplied source")
1300
+ digest = _sha(value["row_sha256"], "row_sha256")
1301
+ if digest != _row_sha256(value):
1302
+ _fail("row_sha256", "does not match canonical row content")
1303
+ return value
1304
+
1305
+
1306
+ def validate(
1307
+ row: Mapping[str, Any], session_source_or_none: str | None = None
1308
+ ) -> dict[str, Any]:
1309
+ """Validate evidence-row/1.0 or evidence-row/1.1 without version coercion."""
1310
+
1311
+ if not isinstance(row, Mapping):
1312
+ _fail("row", "must be a mapping")
1313
+ version = row.get("schema_version")
1314
+ if version == SCHEMA_VERSION:
1315
+ return _validate_v1_0(row, session_source_or_none)
1316
+ if version == ADVISORY_SCHEMA_VERSION:
1317
+ return _validate_v1_1(row, session_source_or_none)
1318
+ _fail(
1319
+ "schema_version",
1320
+ f"must equal {SCHEMA_VERSION} or {ADVISORY_SCHEMA_VERSION}",
1321
+ )
1322
+
1323
+
1324
+ def paginate(
1325
+ rows: Sequence[Mapping[str, Any]],
1326
+ page: int = 1,
1327
+ page_size: int = DEFAULT_PAGE_SIZE,
1328
+ ) -> dict[str, Any]:
1329
+ """Validate and return one deterministic, document-order page.
1330
+
1331
+ Persistence is never capped or truncated. A render call can expose at most
1332
+ 25 rows, so even a 1001-row report remains accessible page by page without a
1333
+ resource-unbounded ``--all`` path.
1334
+ """
1335
+
1336
+ if isinstance(rows, (str, bytes, bytearray)) or not isinstance(rows, Sequence):
1337
+ _fail("rows", "must be a sequence of evidence-row mappings")
1338
+ page = _integer(page, "page", minimum=1)
1339
+ page_size = _integer(page_size, "page_size", minimum=1)
1340
+ if page_size > MAX_PAGE_SIZE:
1341
+ _fail("page_size", f"must be <= {MAX_PAGE_SIZE}")
1342
+
1343
+ validated: list[dict[str, Any]] = []
1344
+ row_ids: set[str] = set()
1345
+ anchors: set[tuple[Any, ...]] = set()
1346
+ claim_views: dict[str, tuple[dict[str, Any], str]] = {}
1347
+ row_version: str | None = None
1348
+ for index, raw in enumerate(rows):
1349
+ try:
1350
+ current = validate(raw)
1351
+ except EvidenceRowError as exc:
1352
+ _fail(f"rows[{index}]", str(exc))
1353
+ row_id = current["row_id"]
1354
+ if row_id in row_ids:
1355
+ _fail(f"rows[{index}].row_id", f"duplicate row_id {row_id!r}")
1356
+ row_ids.add(row_id)
1357
+ if row_version is None:
1358
+ row_version = current["schema_version"]
1359
+ elif current["schema_version"] != row_version:
1360
+ _fail("rows", "cannot mix evidence-row schema versions on one page")
1361
+ if current["schema_version"] == SCHEMA_VERSION:
1362
+ anchor_key = (
1363
+ current["claim"]["claim_id"],
1364
+ current["source"]["ref_slug"],
1365
+ current["anchor"]["kind"],
1366
+ current["anchor"]["value_decoded"],
1367
+ )
1368
+ duplicate_message = "duplicates an earlier (claim_id, ref_slug, anchor) row"
1369
+ else:
1370
+ anchor_key = (
1371
+ current["coverage_subject"]["requirement_id"],
1372
+ current["coverage_subject"]["expectation_pointer"],
1373
+ current["source"]["artifact_id"],
1374
+ current["anchor"]["kind"],
1375
+ current["anchor"]["value_decoded"],
1376
+ )
1377
+ duplicate_message = (
1378
+ "duplicates an earlier (requirement, expectation, artifact, anchor) row"
1379
+ )
1380
+ if anchor_key in anchors:
1381
+ _fail(f"rows[{index}]", duplicate_message)
1382
+ anchors.add(anchor_key)
1383
+ if current["schema_version"] == SCHEMA_VERSION:
1384
+ claim_id = current["claim"]["claim_id"]
1385
+ claim_view = (current["claim"], current["verdict"])
1386
+ previous_claim_view = claim_views.get(claim_id)
1387
+ if previous_claim_view is not None and previous_claim_view != claim_view:
1388
+ _fail(
1389
+ f"rows[{index}].claim",
1390
+ "rows sharing claim_id must carry the same closed claim object and claim-level verdict",
1391
+ )
1392
+ claim_views[claim_id] = claim_view
1393
+ validated.append(current)
1394
+
1395
+ total_rows = len(validated)
1396
+ total_pages = max(1, (total_rows + page_size - 1) // page_size)
1397
+ if page > total_pages:
1398
+ _fail("page", f"{page} exceeds total_pages={total_pages}")
1399
+ start_index = (page - 1) * page_size
1400
+ end_index = min(start_index + page_size, total_rows)
1401
+ if total_rows == 0:
1402
+ row_start = 0
1403
+ row_end = 0
1404
+ else:
1405
+ row_start = start_index + 1
1406
+ row_end = end_index
1407
+ return {
1408
+ "page": page,
1409
+ "page_size": page_size,
1410
+ "total_pages": total_pages,
1411
+ "total_rows": total_rows,
1412
+ "row_start": row_start,
1413
+ "row_end": row_end,
1414
+ "has_previous": page > 1,
1415
+ "has_next": page < total_pages,
1416
+ "truncated": False,
1417
+ "rows": validated[start_index:end_index],
1418
+ }
1419
+
1420
+
1421
+ _BIDI_CONTROL_CODES = frozenset(
1422
+ {
1423
+ 0x061C,
1424
+ 0x200E,
1425
+ 0x200F,
1426
+ 0x202A,
1427
+ 0x202B,
1428
+ 0x202C,
1429
+ 0x202D,
1430
+ 0x202E,
1431
+ 0x2066,
1432
+ 0x2067,
1433
+ 0x2068,
1434
+ 0x2069,
1435
+ }
1436
+ )
1437
+
1438
+
1439
+ def _visible_single_line(value: Any) -> str:
1440
+ text = "" if value is None else str(value)
1441
+ text = text.replace("\r\n", "\n").replace("\r", "\n")
1442
+ parts: list[str] = []
1443
+ for char in text:
1444
+ code = ord(char)
1445
+ if char == "\n":
1446
+ parts.append(" ␤ ")
1447
+ elif char == "\t":
1448
+ parts.append(" ⇥ ")
1449
+ elif (
1450
+ code in _BIDI_CONTROL_CODES
1451
+ or code in {0x2028, 0x2029}
1452
+ or unicodedata.category(char) in {"Cc", "Cf"}
1453
+ ):
1454
+ escape = f"\\u{code:04X}" if code <= 0xFFFF else f"\\U{code:08X}"
1455
+ parts.append(escape)
1456
+ else:
1457
+ parts.append(char)
1458
+ return "".join(parts)
1459
+
1460
+
1461
+ def _markdown_escape(value: Any) -> str:
1462
+ # HTML escaping first prevents raw tags/autolinks. Backslash escaping then
1463
+ # neutralizes CommonMark table, link/image, emphasis, code, heading, and list
1464
+ # syntax. Newlines were already rendered as a visible data glyph.
1465
+ text = html.escape(_visible_single_line(value), quote=True)
1466
+ text = text.replace("\\", "\\\\")
1467
+ # Colon, slash, and at-sign are included to disable GFM bare URL/email
1468
+ # autolinking as well as explicit CommonMark link syntax.
1469
+ for char in "`*{}_[]()#+-.!|>~:/@":
1470
+ text = text.replace(char, "\\" + char)
1471
+ return text
1472
+
1473
+
1474
+ def _html_escape(value: Any) -> str:
1475
+ return html.escape(_visible_single_line(value), quote=True)
1476
+
1477
+
1478
+ def _source_display(row: Mapping[str, Any]) -> str:
1479
+ source = row["source"]
1480
+ label = source["display_label"] or "(source label unavailable)"
1481
+ slug = source["ref_slug"] or "(no ref slug)"
1482
+ digest = source["source_content_sha256"]
1483
+ suffix = f"; content sha256 {digest}" if digest else ""
1484
+ return f"{label} [{slug}]{suffix}"
1485
+
1486
+
1487
+ def _anchor_display(row: Mapping[str, Any]) -> str:
1488
+ anchor = row["anchor"]
1489
+ if anchor["kind"] == "none":
1490
+ return "none"
1491
+ return f"{anchor['kind']}:{anchor['value_decoded']}"
1492
+
1493
+
1494
+ def _evidence_display(row: Mapping[str, Any]) -> str:
1495
+ state = row["excerpt"]["state"]
1496
+ label = STATE_LABELS[state]
1497
+ excerpt = row["excerpt"]["text"]
1498
+ if excerpt is not None:
1499
+ return f'{label} — "{excerpt}"'
1500
+ if row["anchor"]["kind"] == "quote" and row["anchor"]["value_decoded"]:
1501
+ return (
1502
+ f'{label} — NO EXCERPT; writer-emitted anchor remains unconfirmed: '
1503
+ f'"{row["anchor"]["value_decoded"]}"'
1504
+ )
1505
+ return f"{label} — NO EXCERPT"
1506
+
1507
+
1508
+ _HANDLING_CAVEAT = (
1509
+ "External quote anchors and excerpts are limited to 25 words and 1000 Unicode code points each, "
1510
+ "and remain session-only unless the user explicitly confirmed sharing rights. "
1511
+ "Those limits are data minimization, not a rights determination. Viewing an "
1512
+ "evidence row is not a human-read attestation. Evidence labels describe excerpt "
1513
+ "provenance only, never whether the claim is supported."
1514
+ )
1515
+
1516
+
1517
+ def _rows_replayed_for_render(
1518
+ rows: Sequence[Mapping[str, Any]],
1519
+ session_sources: Mapping[str, str] | None,
1520
+ ) -> list[dict[str, Any]]:
1521
+ if isinstance(rows, (str, bytes, bytearray)) or not isinstance(rows, Sequence):
1522
+ _fail("rows", "must be a sequence of evidence-row mappings")
1523
+ if session_sources is not None and not isinstance(session_sources, Mapping):
1524
+ _fail("session_sources", "must be a mapping from ref_slug to exact source text")
1525
+
1526
+ replayed: list[dict[str, Any]] = []
1527
+ for index, raw in enumerate(rows):
1528
+ try:
1529
+ current = validate(raw)
1530
+ except EvidenceRowError as exc:
1531
+ _fail(f"rows[{index}]", str(exc))
1532
+ advisory = current["schema_version"] == ADVISORY_SCHEMA_VERSION
1533
+ source_bound = current["excerpt"]["state"] in (
1534
+ ADVISORY_SOURCE_BOUND_STATES if advisory else SOURCE_BOUND_STATES
1535
+ )
1536
+ if source_bound:
1537
+ source_key = (
1538
+ current["source"]["artifact_id"]
1539
+ if advisory
1540
+ else current["source"]["ref_slug"]
1541
+ )
1542
+ if session_sources is None or source_key not in session_sources:
1543
+ _fail(
1544
+ (
1545
+ f"rows[{index}].source.artifact_id"
1546
+ if advisory
1547
+ else f"rows[{index}].source.ref_slug"
1548
+ ),
1549
+ f"source-bound row requires {source_key!r} in session_sources",
1550
+ )
1551
+ source_text = session_sources[source_key]
1552
+ if not isinstance(source_text, str):
1553
+ _fail(
1554
+ f"session_sources.{source_key}",
1555
+ "must be an exact source-text string",
1556
+ )
1557
+ try:
1558
+ current = validate(current, source_text)
1559
+ except EvidenceRowError as exc:
1560
+ _fail(f"rows[{index}] source replay", str(exc))
1561
+ replayed.append(current)
1562
+ return replayed
1563
+
1564
+
1565
+ def _navigation_text(paged: Mapping[str, Any]) -> str:
1566
+ previous = str(paged["page"] - 1) if paged["has_previous"] else "none"
1567
+ following = str(paged["page"] + 1) if paged["has_next"] else "none"
1568
+ return (
1569
+ f"Navigation: Previous page {previous}; Next page {following}; "
1570
+ f"Explicit page range 1–{paged['total_pages']} (current {paged['page']})."
1571
+ )
1572
+
1573
+
1574
+ def _render_advisory_markdown_page(paged: Mapping[str, Any]) -> str:
1575
+ lines = [
1576
+ f"### LLM-ADVISORY content-coverage evidence rows — Page {paged['page']}/{paged['total_pages']}",
1577
+ "",
1578
+ f"Rows {paged['row_start']}–{paged['row_end']} of {paged['total_rows']}.",
1579
+ "",
1580
+ _navigation_text(paged),
1581
+ "",
1582
+ _markdown_escape(
1583
+ "LLM-ADVISORY: passages are bounded evaluator-supplied observations, not adequacy, authorization, institutional acceptance, or an efficacy measurement."
1584
+ ),
1585
+ "",
1586
+ "| Row | Requirement | Expectation | Artifact | Document locator | Evidence state | Evidence | Handling |",
1587
+ "|---|---|---|---|---|---|---|---|",
1588
+ ]
1589
+ if not paged["rows"]:
1590
+ lines.append("| — | No evidence rows on this page. | — | — | — | — | — | — |")
1591
+ else:
1592
+ for row in paged["rows"]:
1593
+ subject = row["coverage_subject"]
1594
+ locator = subject["document_locator"]
1595
+ excerpt = row["excerpt"]
1596
+ evidence = excerpt["text"] if excerpt["text"] is not None else "NO EXCERPT"
1597
+ cells = [
1598
+ row["row_id"],
1599
+ subject["requirement_id"],
1600
+ f"{subject['expectation_field_id']} [{subject['expectation_pointer']}]",
1601
+ f"{row['source']['artifact_id']} [{row['source']['relative_path']}]",
1602
+ f"{locator['kind']}:{locator['value']} ({locator['provenance']})",
1603
+ excerpt["state"],
1604
+ evidence,
1605
+ (
1606
+ f"{row['content_handling']['sharing_scope']} / "
1607
+ f"{row['content_handling']['rights_basis']}"
1608
+ ),
1609
+ ]
1610
+ lines.append("| " + " | ".join(_markdown_escape(cell) for cell in cells) + " |")
1611
+ return "\n".join(lines) + "\n"
1612
+
1613
+
1614
+ def _render_advisory_html_page(paged: Mapping[str, Any]) -> str:
1615
+ parts = [
1616
+ (
1617
+ f'<section class="ars-evidence-rows ars-content-coverage-advisory" '
1618
+ f'data-layer="LLM-ADVISORY" data-page="{paged["page"]}" '
1619
+ f'data-total-pages="{paged["total_pages"]}">'
1620
+ ),
1621
+ f"<h3>LLM-ADVISORY content-coverage evidence rows — Page {paged['page']}/{paged['total_pages']}</h3>",
1622
+ f"<p>Rows {paged['row_start']}–{paged['row_end']} of {paged['total_rows']}.</p>",
1623
+ (
1624
+ '<nav class="ars-evidence-navigation" aria-label="Evidence row page navigation">'
1625
+ f"{_html_escape(_navigation_text(paged))}</nav>"
1626
+ ),
1627
+ (
1628
+ '<p class="ars-evidence-caveat">'
1629
+ + _html_escape(
1630
+ "LLM-ADVISORY: passages are bounded evaluator-supplied observations, not adequacy, authorization, institutional acceptance, or an efficacy measurement."
1631
+ )
1632
+ + "</p>"
1633
+ ),
1634
+ "<table>",
1635
+ (
1636
+ "<thead><tr><th>Row</th><th>Requirement</th><th>Expectation</th>"
1637
+ "<th>Artifact</th><th>Document locator</th><th>Evidence state</th>"
1638
+ "<th>Evidence</th><th>Handling</th></tr></thead>"
1639
+ ),
1640
+ "<tbody>",
1641
+ ]
1642
+ if not paged["rows"]:
1643
+ parts.append('<tr><td colspan="8">No evidence rows on this page.</td></tr>')
1644
+ else:
1645
+ for row in paged["rows"]:
1646
+ subject = row["coverage_subject"]
1647
+ locator = subject["document_locator"]
1648
+ excerpt = row["excerpt"]
1649
+ evidence = excerpt["text"] if excerpt["text"] is not None else "NO EXCERPT"
1650
+ cells = [
1651
+ row["row_id"],
1652
+ subject["requirement_id"],
1653
+ f"{subject['expectation_field_id']} [{subject['expectation_pointer']}]",
1654
+ f"{row['source']['artifact_id']} [{row['source']['relative_path']}]",
1655
+ f"{locator['kind']}:{locator['value']} ({locator['provenance']})",
1656
+ excerpt["state"],
1657
+ evidence,
1658
+ (
1659
+ f"{row['content_handling']['sharing_scope']} / "
1660
+ f"{row['content_handling']['rights_basis']}"
1661
+ ),
1662
+ ]
1663
+ parts.append(f'<tr data-evidence-state="{_html_escape(excerpt["state"])}">')
1664
+ parts.extend(f"<td>{_html_escape(cell)}</td>" for cell in cells)
1665
+ parts.append("</tr>")
1666
+ parts.extend(["</tbody>", "</table>", "</section>"])
1667
+ return "\n".join(parts) + "\n"
1668
+
1669
+
1670
+ def render_markdown(
1671
+ rows: Sequence[Mapping[str, Any]],
1672
+ page: int = 1,
1673
+ page_size: int = DEFAULT_PAGE_SIZE,
1674
+ *,
1675
+ session_sources: Mapping[str, str] | None = None,
1676
+ ) -> str:
1677
+ """Render one replay-validated page as inert Markdown without retrieval."""
1678
+
1679
+ replayed = _rows_replayed_for_render(rows, session_sources)
1680
+ paged = paginate(replayed, page=page, page_size=page_size)
1681
+ if replayed and replayed[0]["schema_version"] == ADVISORY_SCHEMA_VERSION:
1682
+ return _render_advisory_markdown_page(paged)
1683
+ lines = [
1684
+ f"### Phase E evidence rows — Page {paged['page']}/{paged['total_pages']}",
1685
+ "",
1686
+ f"Rows {paged['row_start']}–{paged['row_end']} of {paged['total_rows']}.",
1687
+ "",
1688
+ _navigation_text(paged),
1689
+ "",
1690
+ _markdown_escape(_HANDLING_CAVEAT),
1691
+ "",
1692
+ "| Row | Claim | Paper locator | Source | Anchor | Tier | Phase E claim verdict (unchanged) | Detail | Evidence | Handling |",
1693
+ "|---|---|---|---|---|---|---|---|---|---|",
1694
+ ]
1695
+ if not paged["rows"]:
1696
+ lines.append("| — | No evidence rows on this page. | — | — | — | — | — | — | — | — |")
1697
+ else:
1698
+ for row in paged["rows"]:
1699
+ cells = [
1700
+ row["row_id"],
1701
+ f"{row['claim']['claim_id']}: {row['claim']['text']}",
1702
+ row["claim"]["paper_locator"],
1703
+ _source_display(row),
1704
+ _anchor_display(row),
1705
+ row["claim"]["selection_tier"],
1706
+ row["verdict"],
1707
+ row["detail"],
1708
+ _evidence_display(row),
1709
+ (
1710
+ f"{row['content_handling']['sharing_scope']} / "
1711
+ f"{row['content_handling']['rights_basis']}"
1712
+ ),
1713
+ ]
1714
+ lines.append("| " + " | ".join(_markdown_escape(cell) for cell in cells) + " |")
1715
+ return "\n".join(lines) + "\n"
1716
+
1717
+
1718
+ def render_html(
1719
+ rows: Sequence[Mapping[str, Any]],
1720
+ page: int = 1,
1721
+ page_size: int = DEFAULT_PAGE_SIZE,
1722
+ *,
1723
+ session_sources: Mapping[str, str] | None = None,
1724
+ ) -> str:
1725
+ """Render one replay-validated page as escaped HTML without retrieval."""
1726
+
1727
+ replayed = _rows_replayed_for_render(rows, session_sources)
1728
+ paged = paginate(replayed, page=page, page_size=page_size)
1729
+ if replayed and replayed[0]["schema_version"] == ADVISORY_SCHEMA_VERSION:
1730
+ return _render_advisory_html_page(paged)
1731
+ parts = [
1732
+ (
1733
+ f'<section class="ars-evidence-rows" data-page="{paged["page"]}" '
1734
+ f'data-total-pages="{paged["total_pages"]}">'
1735
+ ),
1736
+ f"<h3>Phase E evidence rows — Page {paged['page']}/{paged['total_pages']}</h3>",
1737
+ f"<p>Rows {paged['row_start']}–{paged['row_end']} of {paged['total_rows']}.</p>",
1738
+ (
1739
+ '<nav class="ars-evidence-navigation" aria-label="Evidence row page navigation" '
1740
+ f'data-has-previous="{str(paged["has_previous"]).lower()}" '
1741
+ f'data-has-next="{str(paged["has_next"]).lower()}">'
1742
+ f"{_html_escape(_navigation_text(paged))}</nav>"
1743
+ ),
1744
+ f'<p class="ars-evidence-caveat">{_html_escape(_HANDLING_CAVEAT)}</p>',
1745
+ "<table>",
1746
+ (
1747
+ "<thead><tr><th>Row</th><th>Claim</th><th>Paper locator</th>"
1748
+ "<th>Source</th><th>Anchor</th><th>Tier</th><th>Phase E claim verdict (unchanged)</th>"
1749
+ "<th>Detail</th><th>Evidence</th><th>Handling</th></tr></thead>"
1750
+ ),
1751
+ "<tbody>",
1752
+ ]
1753
+ if not paged["rows"]:
1754
+ parts.append('<tr><td colspan="10">No evidence rows on this page.</td></tr>')
1755
+ else:
1756
+ for row in paged["rows"]:
1757
+ cells = [
1758
+ row["row_id"],
1759
+ f"{row['claim']['claim_id']}: {row['claim']['text']}",
1760
+ row["claim"]["paper_locator"],
1761
+ _source_display(row),
1762
+ _anchor_display(row),
1763
+ row["claim"]["selection_tier"],
1764
+ row["verdict"],
1765
+ row["detail"],
1766
+ _evidence_display(row),
1767
+ (
1768
+ f"{row['content_handling']['sharing_scope']} / "
1769
+ f"{row['content_handling']['rights_basis']}"
1770
+ ),
1771
+ ]
1772
+ state = _html_escape(row["excerpt"]["state"])
1773
+ parts.append(f'<tr data-evidence-state="{state}">')
1774
+ parts.extend(f"<td>{_html_escape(cell)}</td>" for cell in cells)
1775
+ parts.append("</tr>")
1776
+ parts.extend(["</tbody>", "</table>", "</section>"])
1777
+ return "\n".join(parts) + "\n"
1778
+
1779
+
1780
+ def _no_duplicate_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
1781
+ result: dict[str, Any] = {}
1782
+ for key, value in pairs:
1783
+ if key in result:
1784
+ _input_fail("JSON", f"duplicate key {key!r}")
1785
+ result[key] = value
1786
+ return result
1787
+
1788
+
1789
+ def _reject_excessive_json_nesting(text: str, path: Path) -> None:
1790
+ """Apply one parser-independent nesting limit before strict JSON decoding."""
1791
+ depth = 0
1792
+ in_string = False
1793
+ escaped = False
1794
+ for character in text:
1795
+ if in_string:
1796
+ if escaped:
1797
+ escaped = False
1798
+ elif character == "\\":
1799
+ escaped = True
1800
+ elif character == '"':
1801
+ in_string = False
1802
+ continue
1803
+ if character == '"':
1804
+ in_string = True
1805
+ elif character in "[{":
1806
+ depth += 1
1807
+ if depth > MAX_JSON_NESTING:
1808
+ _input_fail(
1809
+ str(path),
1810
+ (
1811
+ "cannot read strict JSON: nesting depth exceeds "
1812
+ f"{MAX_JSON_NESTING}"
1813
+ ),
1814
+ )
1815
+ elif character in "]}" and depth:
1816
+ depth -= 1
1817
+
1818
+
1819
+ def _load_json(path: Path) -> Any:
1820
+ try:
1821
+ text = path.read_text(encoding="utf-8")
1822
+ _reject_excessive_json_nesting(text, path)
1823
+ return json.loads(
1824
+ text,
1825
+ object_pairs_hook=_no_duplicate_object,
1826
+ parse_constant=lambda token: _input_fail("JSON", f"non-finite number {token!r} is forbidden"),
1827
+ )
1828
+ except EvidenceRowError:
1829
+ raise
1830
+ except (OSError, UnicodeError, json.JSONDecodeError, ValueError, RecursionError) as exc:
1831
+ _input_fail(str(path), f"cannot read strict JSON: {exc}")
1832
+
1833
+
1834
+ def _validate_report_claim_summary(
1835
+ e_claims: Mapping[str, Any],
1836
+ rows: Sequence[Mapping[str, Any]],
1837
+ ) -> None:
1838
+ has_checked = "checked" in e_claims
1839
+ has_verified = "verified" in e_claims
1840
+ if not has_checked or not has_verified:
1841
+ _fail(
1842
+ "phases.E_claims",
1843
+ "a current report with evidence_rows requires integer checked and verified counters",
1844
+ )
1845
+
1846
+ checked = _integer(e_claims["checked"], "phases.E_claims.checked")
1847
+ verified = _integer(e_claims["verified"], "phases.E_claims.verified")
1848
+ # This validates every row and enforces row/semantic-anchor uniqueness plus
1849
+ # a single claim object and verdict for each repeated claim_id.
1850
+ paginate(rows)
1851
+ claim_verdicts: dict[str, str] = {}
1852
+ for row in rows:
1853
+ claim_id = row["claim"]["claim_id"]
1854
+ claim_verdicts[claim_id] = row["verdict"]
1855
+ if len(claim_verdicts) != checked:
1856
+ _fail(
1857
+ "phases.E_claims.checked",
1858
+ f"is {checked}, but evidence_rows contains {len(claim_verdicts)} unique selected claim_id(s)",
1859
+ )
1860
+ verified_count = sum(verdict == "VERIFIED" for verdict in claim_verdicts.values())
1861
+ if verified_count != verified:
1862
+ _fail(
1863
+ "phases.E_claims.verified",
1864
+ f"is {verified}, but evidence_rows contains {verified_count} unique VERIFIED claim_id(s)",
1865
+ )
1866
+
1867
+
1868
+ def _rows_from_document(
1869
+ document: Any,
1870
+ *,
1871
+ allow_legacy_absence: bool = False,
1872
+ ) -> list[Mapping[str, Any]] | None:
1873
+ if isinstance(document, list):
1874
+ return document
1875
+ if isinstance(document, dict) and document.get("schema_version") == SCHEMA_VERSION:
1876
+ return [document]
1877
+ if isinstance(document, dict):
1878
+ try:
1879
+ e_claims = document["phases"]["E_claims"]
1880
+ except (KeyError, TypeError):
1881
+ _fail("input", "must be an evidence-row object, row array, or Integrity Report with phases.E_claims.evidence_rows")
1882
+ if not isinstance(e_claims, dict):
1883
+ _fail("phases.E_claims", "must be an object")
1884
+ if "evidence_rows" not in e_claims:
1885
+ if allow_legacy_absence:
1886
+ return None
1887
+ _fail(
1888
+ "phases.E_claims.evidence_rows",
1889
+ "is absent; current evidence-row contract validation is unavailable",
1890
+ )
1891
+ rows = e_claims["evidence_rows"]
1892
+ if not isinstance(rows, list):
1893
+ _fail("phases.E_claims.evidence_rows", "must be an array")
1894
+ _validate_report_claim_summary(e_claims, rows)
1895
+ return rows
1896
+ _fail("input", "must be a JSON object or array")
1897
+
1898
+
1899
+ def _source_map(path: Path | None) -> dict[str, str]:
1900
+ if path is None:
1901
+ return {}
1902
+ document = _load_json(path)
1903
+ if not isinstance(document, dict):
1904
+ _fail("source map", "must be an object mapping ref_slug to exact source text")
1905
+ result: dict[str, str] = {}
1906
+ for key, value in document.items():
1907
+ if not isinstance(key, str) or _REF_SLUG_RE.fullmatch(key) is None:
1908
+ _fail("source map", f"invalid ref_slug key {key!r}")
1909
+ if not isinstance(value, str):
1910
+ _fail(f"source map.{key}", "must be a string")
1911
+ result[key] = value
1912
+ return result
1913
+
1914
+
1915
+ def _parser() -> argparse.ArgumentParser:
1916
+ parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
1917
+ sub = parser.add_subparsers(dest="command", required=True)
1918
+ validate_parser = sub.add_parser(
1919
+ "validate",
1920
+ help="validate persisted evidence rows",
1921
+ allow_abbrev=False,
1922
+ )
1923
+ validate_parser.add_argument("rows", type=Path)
1924
+ validate_parser.add_argument("--source-map", type=Path)
1925
+ render_parser = sub.add_parser(
1926
+ "render",
1927
+ help="render one persisted evidence-row page",
1928
+ allow_abbrev=False,
1929
+ )
1930
+ render_parser.add_argument("rows", type=Path)
1931
+ render_parser.add_argument("--format", choices=("markdown", "html"), required=True)
1932
+ render_parser.add_argument(
1933
+ "--source-map",
1934
+ type=Path,
1935
+ help="explicit ref_slug-to-source-text JSON used only for source replay",
1936
+ )
1937
+ render_parser.add_argument(
1938
+ "--allow-legacy-absence",
1939
+ action="store_true",
1940
+ help=(
1941
+ "explicitly treat an Integrity Report with no evidence_rows field as "
1942
+ "a pre-#656 legacy artifact and render the fixed unavailable marker"
1943
+ ),
1944
+ )
1945
+ render_parser.add_argument("--page", type=int, default=1)
1946
+ render_parser.add_argument("--page-size", type=int, default=DEFAULT_PAGE_SIZE)
1947
+ return parser
1948
+
1949
+
1950
+ def main(argv: Sequence[str] | None = None) -> int:
1951
+ args = _parser().parse_args(argv)
1952
+ try:
1953
+ rows = _rows_from_document(
1954
+ _load_json(args.rows),
1955
+ allow_legacy_absence=(
1956
+ args.command == "render"
1957
+ and bool(getattr(args, "allow_legacy_absence", False))
1958
+ ),
1959
+ )
1960
+ if rows is None:
1961
+ if args.format == "markdown":
1962
+ sys.stdout.write(LEGACY_UNAVAILABLE_LABEL + "\n")
1963
+ else:
1964
+ sys.stdout.write(
1965
+ '<p class="ars-evidence-rows-legacy">'
1966
+ + _html_escape(LEGACY_UNAVAILABLE_LABEL)
1967
+ + "</p>\n"
1968
+ )
1969
+ return 0
1970
+ if args.command == "validate":
1971
+ sources = _source_map(args.source_map)
1972
+ seen: list[dict[str, Any]] = []
1973
+ for index, row in enumerate(rows):
1974
+ if not isinstance(row, Mapping):
1975
+ _fail(f"rows[{index}]", "must be an object")
1976
+ slug = row.get("source", {}).get("ref_slug") if isinstance(row.get("source"), dict) else None
1977
+ state = row.get("excerpt", {}).get("state") if isinstance(row.get("excerpt"), dict) else None
1978
+ if state in SOURCE_BOUND_STATES and slug not in sources:
1979
+ _fail(
1980
+ f"rows[{index}].source.ref_slug",
1981
+ f"source-bound row requires {slug!r} in --source-map for CLI trust establishment",
1982
+ )
1983
+ seen.append(validate(row, sources.get(slug)))
1984
+ paginate(seen)
1985
+ print(f"PASS: {len(seen)} evidence row(s)")
1986
+ return 0
1987
+ sources = _source_map(args.source_map)
1988
+ if args.format == "markdown":
1989
+ sys.stdout.write(
1990
+ render_markdown(
1991
+ rows,
1992
+ page=args.page,
1993
+ page_size=args.page_size,
1994
+ session_sources=sources,
1995
+ )
1996
+ )
1997
+ else:
1998
+ sys.stdout.write(
1999
+ render_html(
2000
+ rows,
2001
+ page=args.page,
2002
+ page_size=args.page_size,
2003
+ session_sources=sources,
2004
+ )
2005
+ )
2006
+ return 0
2007
+ except EvidenceRowInputError as exc:
2008
+ print(f"ERROR: {exc}", file=sys.stderr)
2009
+ return 2
2010
+ except EvidenceRowError as exc:
2011
+ print(f"ERROR: {exc}", file=sys.stderr)
2012
+ return 1
2013
+
2014
+
2015
+ __all__ = [
2016
+ "ADVISORY_EXCERPT_STATES",
2017
+ "ADVISORY_SCHEMA_VERSION",
2018
+ "ADVISORY_SURFACE",
2019
+ "ANCHOR_KINDS",
2020
+ "DEFAULT_PAGE_SIZE",
2021
+ "EvidenceRowError",
2022
+ "EvidenceRowInputError",
2023
+ "EXCERPT_STATES",
2024
+ "LEGACY_UNAVAILABLE_LABEL",
2025
+ "MAX_PAGE_SIZE",
2026
+ "QUOTE_WORD_CAP",
2027
+ "SCHEMA_VERSION",
2028
+ "STATE_LABELS",
2029
+ "SURFACE",
2030
+ "TEXT_CHAR_CAP",
2031
+ "build",
2032
+ "build_advisory",
2033
+ "main",
2034
+ "paginate",
2035
+ "render_html",
2036
+ "render_markdown",
2037
+ "strict_percent_decode",
2038
+ "validate",
2039
+ ]
2040
+
2041
+
2042
+ if __name__ == "__main__":
2043
+ raise SystemExit(main())