psyclaw 0.27.23 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/agents/recommended/catalog.json +99 -0
- package/dist/apps/panel/index.html +5 -5
- package/dist/src/adapters/pi/extension.js +339 -94
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/adapters/pi/rpc.d.ts +2 -0
- package/dist/src/adapters/pi/rpc.js +9 -2
- package/dist/src/adapters/pi/rpc.js.map +1 -1
- package/dist/src/agents/import.js +44 -32
- package/dist/src/agents/import.js.map +1 -1
- package/dist/src/agents/recommended-personas.d.ts +42 -0
- package/dist/src/agents/recommended-personas.js +135 -0
- package/dist/src/agents/recommended-personas.js.map +1 -0
- package/dist/src/analysis/hooks.d.ts +1 -0
- package/dist/src/analysis/hooks.js +20 -1
- package/dist/src/analysis/hooks.js.map +1 -1
- package/dist/src/ars/bridge.d.ts +20 -0
- package/dist/src/ars/bridge.js +132 -0
- package/dist/src/ars/bridge.js.map +1 -0
- package/dist/src/ars/contracts.d.ts +55 -0
- package/dist/src/ars/contracts.js +2 -0
- package/dist/src/ars/contracts.js.map +1 -0
- package/dist/src/ars/panel-plan.d.ts +4 -0
- package/dist/src/ars/panel-plan.js +11 -0
- package/dist/src/ars/panel-plan.js.map +1 -0
- package/dist/src/ars/pi-panel-executor.d.ts +30 -0
- package/dist/src/ars/pi-panel-executor.js +140 -0
- package/dist/src/ars/pi-panel-executor.js.map +1 -0
- package/dist/src/ars/profile.d.ts +36 -0
- package/dist/src/ars/profile.js +119 -0
- package/dist/src/ars/profile.js.map +1 -0
- package/dist/src/ars/re-review.d.ts +22 -0
- package/dist/src/ars/re-review.js +172 -0
- package/dist/src/ars/re-review.js.map +1 -0
- package/dist/src/branding.d.ts +2 -4
- package/dist/src/branding.js +3 -5
- package/dist/src/branding.js.map +1 -1
- package/dist/src/bundled-tools.d.ts +3 -0
- package/dist/src/bundled-tools.js +21 -0
- package/dist/src/bundled-tools.js.map +1 -0
- package/dist/src/chat.js +6 -5
- package/dist/src/chat.js.map +1 -1
- package/dist/src/cli.js +6 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/creation/contracts.d.ts +41 -0
- package/dist/src/creation/contracts.js +2 -0
- package/dist/src/creation/contracts.js.map +1 -0
- package/dist/src/creation/service.d.ts +7 -0
- package/dist/src/creation/service.js +208 -0
- package/dist/src/creation/service.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/install/installer.js +32 -10
- package/dist/src/install/installer.js.map +1 -1
- package/dist/src/orchestration/bundled-personas.d.ts +16 -0
- package/dist/src/orchestration/bundled-personas.js +101 -0
- package/dist/src/orchestration/bundled-personas.js.map +1 -0
- package/dist/src/orchestration/effects.d.ts +8 -0
- package/dist/src/orchestration/effects.js +39 -0
- package/dist/src/orchestration/effects.js.map +1 -0
- package/dist/src/orchestration/personas.d.ts +16 -0
- package/dist/src/orchestration/personas.js +64 -0
- package/dist/src/orchestration/personas.js.map +1 -0
- package/dist/src/orchestration/pi-executor.d.ts +9 -4
- package/dist/src/orchestration/pi-executor.js +55 -22
- package/dist/src/orchestration/pi-executor.js.map +1 -1
- package/dist/src/orchestration/research-agents.d.ts +3 -7
- package/dist/src/orchestration/research-agents.js +5 -69
- package/dist/src/orchestration/research-agents.js.map +1 -1
- package/dist/src/panel/server.js +19 -4
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/project/paths.d.ts +3 -0
- package/dist/src/project/paths.js +7 -0
- package/dist/src/project/paths.js.map +1 -1
- package/dist/src/rules/user-rules.d.ts +8 -0
- package/dist/src/rules/user-rules.js +36 -0
- package/dist/src/rules/user-rules.js.map +1 -0
- package/dist/src/skills/contracts.d.ts +4 -4
- package/dist/src/skills/recommended.js +1 -1
- package/dist/src/skills/registry.js +49 -28
- package/dist/src/skills/registry.js.map +1 -1
- package/dist/src/style/cli-ui.d.ts +14 -0
- package/dist/src/style/cli-ui.js +45 -1
- package/dist/src/style/cli-ui.js.map +1 -1
- package/dist/src/tui/skill-manager.js +1 -1
- package/dist/src/tui/skill-manager.js.map +1 -1
- package/dist/src/updates/update.js +9 -3
- package/dist/src/updates/update.js.map +1 -1
- package/package.json +15 -3
- package/scripts/rebrand-pi.mjs +6 -0
- package/skills/recommended/catalog.json +2 -11
- package/vendor/ars/.claude/CLAUDE.md +371 -0
- package/vendor/ars/.command-invariants.toml +24 -0
- package/vendor/ars/CITATION.cff +35 -0
- package/vendor/ars/LICENSE +417 -0
- package/vendor/ars/MODE_REGISTRY.md +76 -0
- package/vendor/ars/NOTICE.md +26 -0
- package/vendor/ars/POSITIONING.md +99 -0
- package/vendor/ars/PSYCLAW_SOURCE.json +10 -0
- package/vendor/ars/README.md +751 -0
- package/vendor/ars/SECURITY.md +52 -0
- package/vendor/ars/THIRD_PARTY.md +70 -0
- package/vendor/ars/academic-paper/SKILL.md +542 -0
- package/vendor/ars/academic-paper/agents/abstract_bilingual_agent.md +171 -0
- package/vendor/ars/academic-paper/agents/argument_builder_agent.md +276 -0
- package/vendor/ars/academic-paper/agents/citation_compliance_agent.md +422 -0
- package/vendor/ars/academic-paper/agents/draft_writer_agent.md +656 -0
- package/vendor/ars/academic-paper/agents/formatter_agent.md +999 -0
- package/vendor/ars/academic-paper/agents/intake_agent.md +393 -0
- package/vendor/ars/academic-paper/agents/literature_strategist_agent.md +626 -0
- package/vendor/ars/academic-paper/agents/peer_reviewer_agent.md +516 -0
- package/vendor/ars/academic-paper/agents/revision_coach_agent.md +334 -0
- package/vendor/ars/academic-paper/agents/socratic_mentor_agent.md +527 -0
- package/vendor/ars/academic-paper/agents/structure_architect_agent.md +401 -0
- package/vendor/ars/academic-paper/agents/visualization_agent.md +441 -0
- package/vendor/ars/academic-paper/examples/chinese_paper_example.md +278 -0
- package/vendor/ars/academic-paper/examples/clinical_citation_verification_checklist.md +95 -0
- package/vendor/ars/academic-paper/examples/clinical_epistemic_status_example.md +100 -0
- package/vendor/ars/academic-paper/examples/commitment_ledger_example.md +147 -0
- package/vendor/ars/academic-paper/examples/imrad_hei_example.md +234 -0
- package/vendor/ars/academic-paper/examples/literature_review_example.md +260 -0
- package/vendor/ars/academic-paper/examples/plan_mode_guided_writing.md +600 -0
- package/vendor/ars/academic-paper/examples/revision_mode_example.md +344 -0
- package/vendor/ars/academic-paper/examples/revision_recovery_example.md +506 -0
- package/vendor/ars/academic-paper/examples/version_family_reconciliation_example.md +89 -0
- package/vendor/ars/academic-paper/references/abstract_writing_guide.md +169 -0
- package/vendor/ars/academic-paper/references/academic_writing_style.md +188 -0
- package/vendor/ars/academic-paper/references/anti_leakage_protocol.md +83 -0
- package/vendor/ars/academic-paper/references/apa7_chinese_citation_guide.md +364 -0
- package/vendor/ars/academic-paper/references/apa7_extended_guide.md +198 -0
- package/vendor/ars/academic-paper/references/changelog.md +11 -0
- package/vendor/ars/academic-paper/references/citation_format_switcher.md +228 -0
- package/vendor/ars/academic-paper/references/committee_correspondence_protocol.md +158 -0
- package/vendor/ars/academic-paper/references/credit_authorship_guide.md +308 -0
- package/vendor/ars/academic-paper/references/disclosure_mode_protocol.md +478 -0
- package/vendor/ars/academic-paper/references/domain_evidence_profiles.md +38 -0
- package/vendor/ars/academic-paper/references/failure_paths.md +349 -0
- package/vendor/ars/academic-paper/references/funding_statement_guide.md +319 -0
- package/vendor/ars/academic-paper/references/hei_domain_glossary.md +169 -0
- package/vendor/ars/academic-paper/references/intro_title_rhetoric_guide.md +114 -0
- package/vendor/ars/academic-paper/references/journal_submission_guide.md +249 -0
- package/vendor/ars/academic-paper/references/latex_template_reference.md +378 -0
- package/vendor/ars/academic-paper/references/mode_selection_guide.md +378 -0
- package/vendor/ars/academic-paper/references/paper_structure_patterns.md +330 -0
- package/vendor/ars/academic-paper/references/plan_mode_protocol.md +112 -0
- package/vendor/ars/academic-paper/references/policy_anchor_disclosure_protocol.md +200 -0
- package/vendor/ars/academic-paper/references/policy_anchor_table.md +157 -0
- package/vendor/ars/academic-paper/references/revision_patch_protocol.md +173 -0
- package/vendor/ars/academic-paper/references/statistical_visualization_standards.md +750 -0
- package/vendor/ars/academic-paper/references/venue_disclosure_policies.md +259 -0
- package/vendor/ars/academic-paper/references/vlm_figure_verification.md +126 -0
- package/vendor/ars/academic-paper/references/workflow_phase_details.md +135 -0
- package/vendor/ars/academic-paper/references/writing_judgment_framework.md +59 -0
- package/vendor/ars/academic-paper/references/writing_quality_check.md +173 -0
- package/vendor/ars/academic-paper/templates/bilingual_abstract_template.md +78 -0
- package/vendor/ars/academic-paper/templates/case_study_template.md +129 -0
- package/vendor/ars/academic-paper/templates/conference_paper_template.md +108 -0
- package/vendor/ars/academic-paper/templates/credit_statement_template.md +132 -0
- package/vendor/ars/academic-paper/templates/funding_statement_template.md +290 -0
- package/vendor/ars/academic-paper/templates/imrad_template.md +183 -0
- package/vendor/ars/academic-paper/templates/latex_article_template.tex +199 -0
- package/vendor/ars/academic-paper/templates/literature_review_template.md +135 -0
- package/vendor/ars/academic-paper/templates/policy_brief_template.md +139 -0
- package/vendor/ars/academic-paper/templates/revision_tracking_template.md +199 -0
- package/vendor/ars/academic-paper/templates/theoretical_paper_template.md +119 -0
- package/vendor/ars/academic-paper-reviewer/SKILL.md +491 -0
- package/vendor/ars/academic-paper-reviewer/agents/devils_advocate_reviewer_agent.md +443 -0
- package/vendor/ars/academic-paper-reviewer/agents/domain_reviewer_agent.md +412 -0
- package/vendor/ars/academic-paper-reviewer/agents/editorial_synthesizer_agent.md +478 -0
- package/vendor/ars/academic-paper-reviewer/agents/eic_agent.md +339 -0
- package/vendor/ars/academic-paper-reviewer/agents/field_analyst_agent.md +221 -0
- package/vendor/ars/academic-paper-reviewer/agents/methodology_reviewer_agent.md +449 -0
- package/vendor/ars/academic-paper-reviewer/agents/perspective_reviewer_agent.md +427 -0
- package/vendor/ars/academic-paper-reviewer/examples/hei_paper_review_example.md +391 -0
- package/vendor/ars/academic-paper-reviewer/examples/interdisciplinary_review_example.md +299 -0
- package/vendor/ars/academic-paper-reviewer/examples/subclaim_decomposition_example.md +80 -0
- package/vendor/ars/academic-paper-reviewer/references/calibration_mode_protocol.md +256 -0
- package/vendor/ars/academic-paper-reviewer/references/changelog.md +10 -0
- package/vendor/ars/academic-paper-reviewer/references/editorial_decision_standards.md +236 -0
- package/vendor/ars/academic-paper-reviewer/references/guided_mode_protocol.md +34 -0
- package/vendor/ars/academic-paper-reviewer/references/integration_guide.md +15 -0
- package/vendor/ars/academic-paper-reviewer/references/quality_rubrics.md +84 -0
- package/vendor/ars/academic-paper-reviewer/references/re_review_mode_protocol.md +340 -0
- package/vendor/ars/academic-paper-reviewer/references/review_criteria_framework.md +98 -0
- package/vendor/ars/academic-paper-reviewer/references/review_panel_provenance_protocol.md +197 -0
- package/vendor/ars/academic-paper-reviewer/references/review_quality_thinking.md +58 -0
- package/vendor/ars/academic-paper-reviewer/references/reviewer_sprint_prompt_source.md +324 -0
- package/vendor/ars/academic-paper-reviewer/references/sprint_contract_protocol.md +296 -0
- package/vendor/ars/academic-paper-reviewer/references/statistical_reporting_standards.md +505 -0
- package/vendor/ars/academic-paper-reviewer/references/top_journals_by_field.md +206 -0
- package/vendor/ars/academic-paper-reviewer/templates/editorial_decision_template.md +235 -0
- package/vendor/ars/academic-paper-reviewer/templates/peer_review_report_template.md +305 -0
- package/vendor/ars/academic-paper-reviewer/templates/revision_response_template.md +248 -0
- package/vendor/ars/academic-pipeline/SKILL.md +736 -0
- package/vendor/ars/academic-pipeline/agents/claim_ref_alignment_audit_agent.md +382 -0
- package/vendor/ars/academic-pipeline/agents/collaboration_depth_agent.md +164 -0
- package/vendor/ars/academic-pipeline/agents/integrity_verification_agent.md +870 -0
- package/vendor/ars/academic-pipeline/agents/pipeline_orchestrator_agent.md +1379 -0
- package/vendor/ars/academic-pipeline/agents/state_tracker_agent.md +622 -0
- package/vendor/ars/academic-pipeline/examples/full_pipeline_example.md +482 -0
- package/vendor/ars/academic-pipeline/examples/integrity_failure_recovery.md +389 -0
- package/vendor/ars/academic-pipeline/examples/mid_entry_example.md +414 -0
- package/vendor/ars/academic-pipeline/references/adapters/.gitkeep +0 -0
- package/vendor/ars/academic-pipeline/references/adapters/overview.md +153 -0
- package/vendor/ars/academic-pipeline/references/ai_research_failure_modes.md +185 -0
- package/vendor/ars/academic-pipeline/references/changelog.md +14 -0
- package/vendor/ars/academic-pipeline/references/claim_audit_calibration_protocol.md +175 -0
- package/vendor/ars/academic-pipeline/references/claim_verification_protocol.md +282 -0
- package/vendor/ars/academic-pipeline/references/external_review_protocol.md +131 -0
- package/vendor/ars/academic-pipeline/references/integrity_review_protocol.md +110 -0
- package/vendor/ars/academic-pipeline/references/literature_corpus_consumers.md +193 -0
- package/vendor/ars/academic-pipeline/references/mode_advisor.md +135 -0
- package/vendor/ars/academic-pipeline/references/passport_as_reset_boundary.md +132 -0
- package/vendor/ars/academic-pipeline/references/pipeline_state_machine.md +405 -0
- package/vendor/ars/academic-pipeline/references/plagiarism_detection_protocol.md +239 -0
- package/vendor/ars/academic-pipeline/references/process_summary_protocol.md +209 -0
- package/vendor/ars/academic-pipeline/references/progress_dashboard_template.md +38 -0
- package/vendor/ars/academic-pipeline/references/reinforcement_content.md +15 -0
- package/vendor/ars/academic-pipeline/references/reproducibility_audit.md +55 -0
- package/vendor/ars/academic-pipeline/references/score_trajectory_protocol.md +78 -0
- package/vendor/ars/academic-pipeline/references/team_collaboration_protocol.md +261 -0
- package/vendor/ars/academic-pipeline/references/two_stage_review_protocol.md +27 -0
- package/vendor/ars/academic-pipeline/templates/pipeline_status_template.md +146 -0
- package/vendor/ars/agents/report_compiler_agent.md +341 -0
- package/vendor/ars/agents/research_architect_agent.md +298 -0
- package/vendor/ars/agents/synthesis_agent.md +356 -0
- package/vendor/ars/commands/ars-3w.md +10 -0
- package/vendor/ars/commands/ars-abstract.md +10 -0
- package/vendor/ars/commands/ars-cache-invalidate.md +20 -0
- package/vendor/ars/commands/ars-citation-check.md +10 -0
- package/vendor/ars/commands/ars-disclosure.md +10 -0
- package/vendor/ars/commands/ars-format-convert.md +10 -0
- package/vendor/ars/commands/ars-full.md +9 -0
- package/vendor/ars/commands/ars-lit-review.md +12 -0
- package/vendor/ars/commands/ars-mark-read.md +18 -0
- package/vendor/ars/commands/ars-outline.md +10 -0
- package/vendor/ars/commands/ars-plan.md +10 -0
- package/vendor/ars/commands/ars-rebuttal-audit.md +12 -0
- package/vendor/ars/commands/ars-reviewer.md +9 -0
- package/vendor/ars/commands/ars-revision-coach.md +9 -0
- package/vendor/ars/commands/ars-revision.md +10 -0
- package/vendor/ars/commands/ars-unmark-read.md +16 -0
- package/vendor/ars/deep-research/SKILL.md +600 -0
- package/vendor/ars/deep-research/agents/bibliography_agent.md +473 -0
- package/vendor/ars/deep-research/agents/devils_advocate_agent.md +192 -0
- package/vendor/ars/deep-research/agents/editor_in_chief_agent.md +167 -0
- package/vendor/ars/deep-research/agents/ethics_review_agent.md +267 -0
- package/vendor/ars/deep-research/agents/meta_analysis_agent.md +325 -0
- package/vendor/ars/deep-research/agents/monitoring_agent.md +209 -0
- package/vendor/ars/deep-research/agents/report_compiler_agent.md +341 -0
- package/vendor/ars/deep-research/agents/research_architect_agent.md +298 -0
- package/vendor/ars/deep-research/agents/research_question_agent.md +216 -0
- package/vendor/ars/deep-research/agents/risk_of_bias_agent.md +231 -0
- package/vendor/ars/deep-research/agents/socratic_mentor_agent.md +764 -0
- package/vendor/ars/deep-research/agents/source_verification_agent.md +219 -0
- package/vendor/ars/deep-research/agents/synthesis_agent.md +356 -0
- package/vendor/ars/deep-research/agents/timeline_extraction_agent.md +99 -0
- package/vendor/ars/deep-research/examples/exploratory_research.md +157 -0
- package/vendor/ars/deep-research/examples/fact_check_mode.md +173 -0
- package/vendor/ars/deep-research/examples/handoff_to_paper.md +318 -0
- package/vendor/ars/deep-research/examples/idea_diversity_coverage_gap_advisory.md +64 -0
- package/vendor/ars/deep-research/examples/policy_analysis.md +161 -0
- package/vendor/ars/deep-research/examples/review_mode.md +253 -0
- package/vendor/ars/deep-research/examples/socratic_guided_research.md +331 -0
- package/vendor/ars/deep-research/examples/systematic_review.md +133 -0
- package/vendor/ars/deep-research/references/apa7_style_guide.md +162 -0
- package/vendor/ars/deep-research/references/argumentation_reasoning_framework.md +68 -0
- package/vendor/ars/deep-research/references/arxiv_api_protocol.md +76 -0
- package/vendor/ars/deep-research/references/changelog.md +22 -0
- package/vendor/ars/deep-research/references/chinese_literature_api_protocol.md +317 -0
- package/vendor/ars/deep-research/references/cross_agent_quality_definitions.md +14 -0
- package/vendor/ars/deep-research/references/crossref_api_protocol.md +84 -0
- package/vendor/ars/deep-research/references/equator_reporting_guidelines.md +482 -0
- package/vendor/ars/deep-research/references/ethics_checklist.md +282 -0
- package/vendor/ars/deep-research/references/failure_paths.md +355 -0
- package/vendor/ars/deep-research/references/interdisciplinary_bridges.md +292 -0
- package/vendor/ars/deep-research/references/irb_decision_tree.md +315 -0
- package/vendor/ars/deep-research/references/literature_monitoring_strategies.md +263 -0
- package/vendor/ars/deep-research/references/logical_fallacies.md +192 -0
- package/vendor/ars/deep-research/references/methodology_patterns.md +462 -0
- package/vendor/ars/deep-research/references/mode_selection_guide.md +331 -0
- package/vendor/ars/deep-research/references/openalex_api_protocol.md +82 -0
- package/vendor/ars/deep-research/references/preregistration_guide.md +324 -0
- package/vendor/ars/deep-research/references/semantic_scholar_api_protocol.md +107 -0
- package/vendor/ars/deep-research/references/socratic_mode_protocol.md +99 -0
- package/vendor/ars/deep-research/references/socratic_questioning_framework.md +232 -0
- package/vendor/ars/deep-research/references/source_quality_hierarchy.md +188 -0
- package/vendor/ars/deep-research/references/systematic_review_protocol.md +95 -0
- package/vendor/ars/deep-research/references/systematic_review_toolkit.md +353 -0
- package/vendor/ars/deep-research/templates/evidence_assessment_template.md +127 -0
- package/vendor/ars/deep-research/templates/literature_matrix_template.md +85 -0
- package/vendor/ars/deep-research/templates/preregistration_template.md +318 -0
- package/vendor/ars/deep-research/templates/prisma_protocol_template.md +248 -0
- package/vendor/ars/deep-research/templates/prisma_report_template.md +415 -0
- package/vendor/ars/deep-research/templates/research_brief_template.md +93 -0
- package/vendor/ars/package.json +24 -0
- package/vendor/ars/pi/README.md +161 -0
- package/vendor/ars/pi/package.json +26 -0
- package/vendor/ars/pi/wrapper.js +193 -0
- package/vendor/ars/pi/wrapper.test.mjs +201 -0
- package/vendor/ars/pyproject.toml +2 -0
- package/vendor/ars/requirements-pdf-content-classifier.txt +5 -0
- package/vendor/ars/scripts/_block_parser.py +396 -0
- package/vendor/ars/scripts/_ci_pytest_manifest.toml +661 -0
- package/vendor/ars/scripts/_claim_audit_constants.py +268 -0
- package/vendor/ars/scripts/_e4_evidence.py +110 -0
- package/vendor/ars/scripts/_eval_threshold_gate.py +73 -0
- package/vendor/ars/scripts/_markdown_lint_util.py +224 -0
- package/vendor/ars/scripts/_next_verified_at_ms.py +176 -0
- package/vendor/ars/scripts/_passport_yaml.py +53 -0
- package/vendor/ars/scripts/_skill_lint.py +254 -0
- package/vendor/ars/scripts/_text_similarity.py +141 -0
- package/vendor/ars/scripts/adapters/README.md +89 -0
- package/vendor/ars/scripts/adapters/_common.py +209 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/expected_passport.yaml +25 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/expected_rejection_log.yaml +18 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Chen2024_AIAssessment.pdf +0 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Wang_2023_formative_feedback.pdf +0 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/paper1.pdf +0 -0
- 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
- package/vendor/ars/scripts/adapters/examples/obsidian/expected_passport.yaml +40 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/expected_rejection_log.yaml +13 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.gitkeep +0 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.obsidian/app.json +1 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/_templates/tmpl.md +7 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/chen2024ai.md +13 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/invalid.md +1 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/wang2023formative.md +10 -0
- package/vendor/ars/scripts/adapters/examples/zotero/expected_passport.yaml +34 -0
- package/vendor/ars/scripts/adapters/examples/zotero/expected_rejection_log.yaml +34 -0
- package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/.gitkeep +0 -0
- package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/export.json +43 -0
- package/vendor/ars/scripts/adapters/folder_scan.py +214 -0
- package/vendor/ars/scripts/adapters/obsidian.py +336 -0
- package/vendor/ars/scripts/adapters/tests/.gitkeep +0 -0
- package/vendor/ars/scripts/adapters/tests/conftest.py +77 -0
- package/vendor/ars/scripts/adapters/tests/test_check_corpus_consumer_protocol.py +632 -0
- package/vendor/ars/scripts/adapters/tests/test_check_literature_corpus_schema.py +631 -0
- package/vendor/ars/scripts/adapters/tests/test_common.py +365 -0
- package/vendor/ars/scripts/adapters/tests/test_conftest.py +96 -0
- package/vendor/ars/scripts/adapters/tests/test_folder_scan.py +260 -0
- package/vendor/ars/scripts/adapters/tests/test_literature_corpus_entry_schema.py +745 -0
- package/vendor/ars/scripts/adapters/tests/test_obsidian.py +357 -0
- package/vendor/ars/scripts/adapters/tests/test_rejection_log_schema.py +271 -0
- package/vendor/ars/scripts/adapters/tests/test_sync_adapter_docs.py +88 -0
- package/vendor/ars/scripts/adapters/tests/test_zotero.py +454 -0
- package/vendor/ars/scripts/adapters/zotero.py +318 -0
- package/vendor/ars/scripts/adjudication_activity.py +1592 -0
- package/vendor/ars/scripts/announce-ars-loaded.sh +144 -0
- package/vendor/ars/scripts/ars_anchorize_draft.py +170 -0
- package/vendor/ars/scripts/ars_apply_revision_patch.py +912 -0
- package/vendor/ars/scripts/ars_cache_invalidate.py +40 -0
- package/vendor/ars/scripts/ars_mark_read.py +521 -0
- package/vendor/ars/scripts/ars_phase_scope_manifest.json +33 -0
- package/vendor/ars/scripts/ars_update_check.sh +215 -0
- package/vendor/ars/scripts/ars_write_scope_guard.py +506 -0
- package/vendor/ars/scripts/arxiv_client.py +222 -0
- package/vendor/ars/scripts/audit_snapshot.py +572 -0
- package/vendor/ars/scripts/bibliographic_integrity_signals.py +800 -0
- package/vendor/ars/scripts/bootstrap_timeline_yaml.py +146 -0
- package/vendor/ars/scripts/build_claim_standing_candidate_ledger.py +1238 -0
- package/vendor/ars/scripts/build_claim_standing_query_plan.py +643 -0
- package/vendor/ars/scripts/build_content_coverage_advisory.py +1205 -0
- package/vendor/ars/scripts/build_cross_document_consistency_advisory.py +2332 -0
- package/vendor/ars/scripts/build_review_pathway_rule_trace.py +830 -0
- package/vendor/ars/scripts/build_submission_packet_manifest.py +2310 -0
- package/vendor/ars/scripts/check_215_field_norm.py +173 -0
- package/vendor/ars/scripts/check_216_surface_form.py +250 -0
- package/vendor/ars/scripts/check_268_nested_commitment_ledger.py +180 -0
- package/vendor/ars/scripts/check_390_revision_patch_discipline.py +296 -0
- package/vendor/ars/scripts/check_392_citation_verification_intake.py +126 -0
- package/vendor/ars/scripts/check_394_submission_policy.py +178 -0
- package/vendor/ars/scripts/check_439_format_profile.py +307 -0
- package/vendor/ars/scripts/check_619_disclosure_closeout.py +237 -0
- package/vendor/ars/scripts/check_630_codex_subscription_transport.py +456 -0
- package/vendor/ars/scripts/check_669_review_pathway_rule_trace.py +662 -0
- package/vendor/ars/scripts/check_670_revision_roadmap_integration.py +518 -0
- package/vendor/ars/scripts/check_673_adjudication_activity.py +684 -0
- package/vendor/ars/scripts/check_684_review_criteria_binding.py +557 -0
- package/vendor/ars/scripts/check_agents_mirror_sync.py +115 -0
- package/vendor/ars/scripts/check_audit_artifact_consistency.py +2313 -0
- package/vendor/ars/scripts/check_benchmark_report.py +79 -0
- package/vendor/ars/scripts/check_bibliographic_integrity_signals.py +831 -0
- package/vendor/ars/scripts/check_calibration_tiers.py +235 -0
- package/vendor/ars/scripts/check_changelog_covers_merges.py +289 -0
- package/vendor/ars/scripts/check_ci_pytest_manifest.py +204 -0
- package/vendor/ars/scripts/check_claim_audit_consistency.py +1664 -0
- package/vendor/ars/scripts/check_claim_standing_candidate_ledger_integration.py +500 -0
- package/vendor/ars/scripts/check_claim_standing_freshness.py +253 -0
- package/vendor/ars/scripts/check_claim_standing_transmissions.py +449 -0
- package/vendor/ars/scripts/check_collaboration_depth_rubric.py +180 -0
- package/vendor/ars/scripts/check_command_frontmatter_name.py +116 -0
- package/vendor/ars/scripts/check_committee_correspondence.py +333 -0
- package/vendor/ars/scripts/check_compliance_report.py +108 -0
- package/vendor/ars/scripts/check_content_coverage_advisory_integration.py +796 -0
- package/vendor/ars/scripts/check_control_availability.py +172 -0
- package/vendor/ars/scripts/check_corpus_consumer_protocol.py +404 -0
- package/vendor/ars/scripts/check_cross_document_consistency_advisory_integration.py +1191 -0
- package/vendor/ars/scripts/check_cross_model_handoff_contract.py +234 -0
- package/vendor/ars/scripts/check_cross_model_verification_sync.py +261 -0
- package/vendor/ars/scripts/check_data_access_level.py +131 -0
- package/vendor/ars/scripts/check_data_flows.py +252 -0
- package/vendor/ars/scripts/check_decision_contract.py +464 -0
- package/vendor/ars/scripts/check_degradation_registry.py +326 -0
- package/vendor/ars/scripts/check_distribution_surface_claims.py +226 -0
- package/vendor/ars/scripts/check_domain_evidence_profile.py +538 -0
- package/vendor/ars/scripts/check_e4_promotion.py +195 -0
- package/vendor/ars/scripts/check_evals_gold_set.py +279 -0
- package/vendor/ars/scripts/check_evidence_row_integration.py +396 -0
- package/vendor/ars/scripts/check_experiment_provenance.py +117 -0
- package/vendor/ars/scripts/check_field_norm_severity.py +144 -0
- package/vendor/ars/scripts/check_firm_rules_sync.py +375 -0
- package/vendor/ars/scripts/check_heldout_measurement_report.py +1179 -0
- package/vendor/ars/scripts/check_human_subjects_output_contract.py +139 -0
- package/vendor/ars/scripts/check_human_subjects_reference_migration.py +844 -0
- package/vendor/ars/scripts/check_indirect_prompt_injection_no_call.py +328 -0
- package/vendor/ars/scripts/check_instruction_data_boundary.py +236 -0
- package/vendor/ars/scripts/check_judge_prompt_version.py +125 -0
- package/vendor/ars/scripts/check_literature_corpus_schema.py +402 -0
- package/vendor/ars/scripts/check_model_tiering.py +223 -0
- package/vendor/ars/scripts/check_panel_synthesis.py +1331 -0
- package/vendor/ars/scripts/check_passport_reset_contract.py +214 -0
- package/vendor/ars/scripts/check_pattern_eval_manifest.py +422 -0
- package/vendor/ars/scripts/check_persuasion_invariance_fixtures.py +637 -0
- package/vendor/ars/scripts/check_phase_conformance.py +2180 -0
- package/vendor/ars/scripts/check_pipeline_boundary_semantics.py +600 -0
- package/vendor/ars/scripts/check_pipeline_integrity.py +340 -0
- package/vendor/ars/scripts/check_policy_anchor_protocol.py +159 -0
- package/vendor/ars/scripts/check_policy_anchor_table.py +286 -0
- package/vendor/ars/scripts/check_preprint_venues_consistency.py +123 -0
- package/vendor/ars/scripts/check_prisma_trAIce_freshness.py +84 -0
- package/vendor/ars/scripts/check_promotion_bakeoff_preregistration.py +1303 -0
- package/vendor/ars/scripts/check_ranking_lift.py +323 -0
- package/vendor/ars/scripts/check_re_review_synthesis.py +2719 -0
- package/vendor/ars/scripts/check_receipt_enum_sync.py +203 -0
- package/vendor/ars/scripts/check_repro_lock.py +85 -0
- package/vendor/ars/scripts/check_review_pathway_output.py +276 -0
- package/vendor/ars/scripts/check_reviewer_data_fences.py +224 -0
- package/vendor/ars/scripts/check_reviewer_finding_contract.py +783 -0
- package/vendor/ars/scripts/check_reviewer_role_label.py +447 -0
- package/vendor/ars/scripts/check_reviewer_scoring_honesty.py +292 -0
- package/vendor/ars/scripts/check_reviewer_sprint_prompt_sync.py +409 -0
- package/vendor/ars/scripts/check_revision_claim_drift_suite_v2.py +1622 -0
- package/vendor/ars/scripts/check_revision_token_conservation.py +263 -0
- package/vendor/ars/scripts/check_risk_register.py +280 -0
- package/vendor/ars/scripts/check_role_scoped_contract.py +702 -0
- package/vendor/ars/scripts/check_rq_framing_patterns.py +184 -0
- package/vendor/ars/scripts/check_rubric_weight_consistency.py +32 -0
- package/vendor/ars/scripts/check_seeded_defect_fixtures.py +425 -0
- package/vendor/ars/scripts/check_setup_cross_model_parity.py +139 -0
- package/vendor/ars/scripts/check_spec_consistency.py +1249 -0
- package/vendor/ars/scripts/check_sprint_contract.py +371 -0
- package/vendor/ars/scripts/check_stage_capability_matrix.py +784 -0
- package/vendor/ars/scripts/check_submission_packet_manifest_integration.py +358 -0
- package/vendor/ars/scripts/check_surface_form_parity.py +445 -0
- package/vendor/ars/scripts/check_task_type.py +22 -0
- package/vendor/ars/scripts/check_tools_allowlist.py +538 -0
- package/vendor/ars/scripts/check_tortured_phrase_screening_integration.py +1974 -0
- package/vendor/ars/scripts/check_v3_10_134_write_scope.py +286 -0
- package/vendor/ars/scripts/check_v3_10_policy.py +656 -0
- package/vendor/ars/scripts/check_v3_6_6_ab_manifest.py +364 -0
- package/vendor/ars/scripts/check_v3_6_7_pattern_protection.py +1366 -0
- package/vendor/ars/scripts/check_v3_6_8_audit_scope_block.py +464 -0
- package/vendor/ars/scripts/check_v3_6_8_cite_provenance_pipeline.py +231 -0
- package/vendor/ars/scripts/check_v3_6_8_frontmatter_trust_schema.py +248 -0
- package/vendor/ars/scripts/check_v3_6_8_mark_read_commands.py +79 -0
- package/vendor/ars/scripts/check_v3_6_8_pattern_protection.py +941 -0
- package/vendor/ars/scripts/check_v3_7_3_three_layer_citation.py +318 -0
- package/vendor/ars/scripts/check_v3_8_annotation_literal_sync.py +228 -0
- package/vendor/ars/scripts/check_v3_9_0_triangulation.py +366 -0
- package/vendor/ars/scripts/check_v3_9_2_phase_boundary.py +270 -0
- package/vendor/ars/scripts/check_v3_9_4_temporal_verification.py +139 -0
- package/vendor/ars/scripts/check_venue_disclosure_policies.py +63 -0
- package/vendor/ars/scripts/check_version_consistency.py +826 -0
- package/vendor/ars/scripts/check_workflow_classification.py +223 -0
- package/vendor/ars/scripts/chinese_literature_client.py +1938 -0
- package/vendor/ars/scripts/citation_verification_summary.py +85 -0
- package/vendor/ars/scripts/claim_audit_calibration.py +517 -0
- package/vendor/ars/scripts/claim_audit_finalizer.py +456 -0
- package/vendor/ars/scripts/claim_audit_pipeline.py +1594 -0
- package/vendor/ars/scripts/claim_registry_coverage.py +493 -0
- package/vendor/ars/scripts/claim_standing_discovery.py +784 -0
- package/vendor/ars/scripts/claim_standing_stance_runner.py +758 -0
- package/vendor/ars/scripts/claim_standing_stance_scorer.py +239 -0
- package/vendor/ars/scripts/claim_strength_drift_disposition.py +666 -0
- package/vendor/ars/scripts/contamination_signals.py +689 -0
- package/vendor/ars/scripts/corpus_consumer_manifest.json +19 -0
- package/vendor/ars/scripts/cross_model_codex_transport.py +1374 -0
- package/vendor/ars/scripts/cross_model_codex_verify.sh +6 -0
- package/vendor/ars/scripts/cross_model_handoff.py +359 -0
- package/vendor/ars/scripts/cross_model_smoke_test.sh +183 -0
- package/vendor/ars/scripts/cross_model_smoke_test_codex.sh +35 -0
- package/vendor/ars/scripts/cross_model_verification/gemini_is_grounded.jq +45 -0
- package/vendor/ars/scripts/cross_model_verification/gemini_sources.jq +45 -0
- package/vendor/ars/scripts/cross_model_verification/normalize_compat_verdict.py +57 -0
- package/vendor/ars/scripts/cross_model_verification/openai_has_completed_web_search.jq +15 -0
- package/vendor/ars/scripts/cross_model_verification/openai_sources.jq +17 -0
- package/vendor/ars/scripts/cross_model_verification/openai_text.jq +11 -0
- package/vendor/ars/scripts/crossref_client.py +225 -0
- package/vendor/ars/scripts/dispatch_e4_panel.py +2731 -0
- package/vendor/ars/scripts/evidence_rows.py +2043 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3.json +32 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_input.json +27 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime.json +32 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime_input.json +27 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override.json +50 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override_action.json +14 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_pass.json +44 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/explicit_user_request_log.json +26 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/mandatory_checkpoint_log.json +116 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/README.md +56 -0
- 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
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a7_orphan_completion/2026-04-30T15-22-04Z-d8f3.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +26 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +24 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.verdict.yaml +12 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction.json +46 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction_check_attestation.json +44 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase.json +44 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_abstract_missing.json +107 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_detected.json +124 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/expected_outcomes.json +77 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/manifest.yaml +37 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/001-valid-doi-test.json +20 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/002-valid-arxiv-test.json +19 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/003-fabricated-test.json +20 -0
- package/vendor/ars/scripts/fixtures/claim_audit_calibration/gold_set.json +344 -0
- package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/query_plan.json +108 -0
- package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/retrieval_input.json +179 -0
- package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/concern_tracker.json +129 -0
- package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/response_skeleton.md +19 -0
- package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/source_letter.txt +9 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_draft.json +48 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_inventory.json +38 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/packet/consent.txt +1 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/session_sources.json +3 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/README.md +6 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/accepted_draft.md +35 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/cases.json +75 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/preregistration.md +5 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/forbidden_event.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/grounded_verified.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/malformed.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/missing_search.jsonl +2 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/multiple_finals.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/not_found.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/unbound_source.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/wrong_search_shape.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/evidence_rows/phase_e_inputs.json +114 -0
- package/vendor/ars/scripts/fixtures/evidence_rows/session_sources.json +5 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/cross-border-us-tw-gdpr.json +71 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/gdpr-member-state-unresolved.json +62 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/missing-data-axis.json +45 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/no-profile.json +33 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/tw-gdpr-two-axis.json +74 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/us-gdpr-two-axis.json +74 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/lint-near-misses.json +72 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/no-profile-request.json +18 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/tw-candidates-request.json +63 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/us-candidates-request.json +77 -0
- package/vendor/ars/scripts/fixtures/review_target_context/exact-declaration.json +24 -0
- package/vendor/ars/scripts/fixtures/review_target_context/field-general-declaration.json +24 -0
- package/vendor/ars/scripts/fixtures/review_target_context/msr-2027-technical-full-declaration.json +24 -0
- package/vendor/ars/scripts/fixtures/review_target_context/synthetic-registry.json +92 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/README.md +21 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_attested_only.json +55 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_machine_supported.json +49 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_not_isolated.json +56 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_unknown.json +49 -0
- package/vendor/ars/scripts/fixtures/submission_package/clean/paper.md +16 -0
- package/vendor/ars/scripts/fixtures/submission_package/clean/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/paper.md +11 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/paper.tex +9 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/marker_no_join/paper.md +7 -0
- package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/paper.md +8 -0
- package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/references.bib +6 -0
- package/vendor/ars/scripts/fixtures/submission_package/passports/corpus_only.yaml +9 -0
- package/vendor/ars/scripts/fixtures/submission_package/passports/summary_join.yaml +16 -0
- package/vendor/ars/scripts/fixtures/submission_package/profiles/full.yaml +15 -0
- package/vendor/ars/scripts/fixtures/submission_package/profiles/tight.yaml +15 -0
- package/vendor/ars/scripts/fixtures/submission_package/summary_join/paper.md +7 -0
- package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/paper.md +9 -0
- package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_clean/paper.md +26 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_clean/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_violations/paper.md +20 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_violations/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/base_inventory.json +101 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/consent-materials.txt +3 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/training-certificate.txt +2 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/tw-consent-materials.txt +3 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/corpus_input.yaml +30 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.md +36 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.tex +21 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/seed_expectations.json +218 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot.json +164 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot_manifest.json +29 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/README.md +55 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/empty_feed.xml +10 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/error_5xx.html +7 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/id_hit.xml +26 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/README.md +69 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/cnki_landing_page.html +13 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/error_5xx.html +2 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_ambiguous.json +16 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_hit.json +16 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_zero.json +14 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_hit.json +15 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_zero.json +13 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_hit.json +43 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_issn_mismatch.json +31 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_no_doi.json +31 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_unknown_ra.json +34 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_year_mismatch.json +31 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_absent.json +4 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_exists.json +16 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_internal_error.json +4 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_hit.json +25 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_other_title.json +18 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_cnki.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_crossref.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_istic.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_unknown_prefix.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/crossref/doi_hit.json +24 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/crossref/error_5xx.html +7 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/crossref/title_search_miss.json +15 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/openalex/doi_hit.json +29 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/openalex/error_5xx.json +4 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/openalex/title_search_miss.json +11 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/doi_hit.json +14 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/error_5xx.json +3 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/title_search_miss.json +5 -0
- package/vendor/ars/scripts/human_read_attestation_resolver.py +467 -0
- package/vendor/ars/scripts/ideation_diversity_assignment_gate.py +430 -0
- package/vendor/ars/scripts/inquiry_branch_ledger.py +2540 -0
- package/vendor/ars/scripts/legacy/ars_apply_revision_patch_v1_0.py +715 -0
- package/vendor/ars/scripts/legacy/check_re_review_synthesis_v1_0.py +2215 -0
- package/vendor/ars/scripts/migrate_literature_corpus_to_v3_10.py +223 -0
- package/vendor/ars/scripts/migrate_literature_corpus_to_v3_7_3.py +277 -0
- package/vendor/ars/scripts/migrate_literature_corpus_to_v3_9_0.py +304 -0
- package/vendor/ars/scripts/model_tiering_manifest.json +46 -0
- package/vendor/ars/scripts/openalex_client.py +232 -0
- package/vendor/ars/scripts/parse_audit_verdict.py +802 -0
- package/vendor/ars/scripts/pdf_content_classifier_worker.py +176 -0
- package/vendor/ars/scripts/pdf_read_preflight.py +1455 -0
- package/vendor/ars/scripts/policy_anchor_disclosure_referee.py +354 -0
- package/vendor/ars/scripts/recompute_receipts.py +1414 -0
- package/vendor/ars/scripts/render_claim_standing_view.py +374 -0
- package/vendor/ars/scripts/render_eval_comment.py +130 -0
- package/vendor/ars/scripts/render_harness_retirement_issue.py +167 -0
- package/vendor/ars/scripts/repro_lock_validation.py +90 -0
- package/vendor/ars/scripts/research_workflow_profile.py +1079 -0
- package/vendor/ars/scripts/resolve_human_subjects_authority.py +1158 -0
- package/vendor/ars/scripts/resolve_review_target_context.py +683 -0
- package/vendor/ars/scripts/resume_e4_record.py +509 -0
- package/vendor/ars/scripts/retraction_status.py +484 -0
- package/vendor/ars/scripts/review_criteria_binding.py +889 -0
- package/vendor/ars/scripts/review_panel_provenance.py +744 -0
- package/vendor/ars/scripts/revision_roadmap.py +1967 -0
- package/vendor/ars/scripts/run_ci_pytest_manifest.py +116 -0
- package/vendor/ars/scripts/run_codex_audit.sh +1191 -0
- package/vendor/ars/scripts/run_evals.py +513 -0
- package/vendor/ars/scripts/run_ideation_diversity_no_call.py +3505 -0
- package/vendor/ars/scripts/run_indirect_prompt_injection_no_call.py +3330 -0
- package/vendor/ars/scripts/run_indirect_prompt_injection_probe.py +399 -0
- package/vendor/ars/scripts/run_review_criteria_constructive_value.py +1895 -0
- package/vendor/ars/scripts/run_role_topology_utility_dry_run.py +606 -0
- package/vendor/ars/scripts/score_review_criteria_constructive_value.py +617 -0
- package/vendor/ars/scripts/semantic_scholar_client.py +291 -0
- package/vendor/ars/scripts/slr_lineage.py +59 -0
- package/vendor/ars/scripts/sync_adapter_docs.py +118 -0
- package/vendor/ars/scripts/temporal_integrity_audit.py +840 -0
- package/vendor/ars/scripts/test_431_exact_or_bust.py +253 -0
- package/vendor/ars/scripts/test__eval_threshold_gate.py +125 -0
- package/vendor/ars/scripts/test__markdown_lint_util.py +129 -0
- package/vendor/ars/scripts/test__next_verified_at_ms.py +256 -0
- package/vendor/ars/scripts/test_adjacent_framing_probe_lint.py +171 -0
- package/vendor/ars/scripts/test_adjudication_activity.py +1515 -0
- package/vendor/ars/scripts/test_ars_anchorize_draft.py +178 -0
- package/vendor/ars/scripts/test_ars_apply_revision_patch.py +1335 -0
- package/vendor/ars/scripts/test_ars_cache_invalidate.py +51 -0
- package/vendor/ars/scripts/test_ars_mark_read.py +910 -0
- package/vendor/ars/scripts/test_ars_update_check.py +816 -0
- package/vendor/ars/scripts/test_ars_write_scope_guard.py +790 -0
- package/vendor/ars/scripts/test_arxiv_client.py +374 -0
- package/vendor/ars/scripts/test_audit_schemas.py +560 -0
- package/vendor/ars/scripts/test_audit_snapshot_render_section_0.py +105 -0
- package/vendor/ars/scripts/test_block_parser.py +259 -0
- package/vendor/ars/scripts/test_bootstrap_timeline_yaml.py +148 -0
- package/vendor/ars/scripts/test_build_claim_standing_candidate_ledger.py +1203 -0
- package/vendor/ars/scripts/test_build_claim_standing_query_plan.py +791 -0
- package/vendor/ars/scripts/test_build_submission_packet_manifest.py +2644 -0
- package/vendor/ars/scripts/test_check_215_field_norm.py +238 -0
- package/vendor/ars/scripts/test_check_216_surface_form.py +341 -0
- package/vendor/ars/scripts/test_check_268_nested_commitment_ledger.py +194 -0
- package/vendor/ars/scripts/test_check_390_revision_patch_discipline.py +279 -0
- package/vendor/ars/scripts/test_check_392_citation_verification_intake.py +144 -0
- package/vendor/ars/scripts/test_check_394_submission_policy.py +200 -0
- package/vendor/ars/scripts/test_check_439_format_profile.py +252 -0
- package/vendor/ars/scripts/test_check_619_disclosure_closeout.py +158 -0
- package/vendor/ars/scripts/test_check_630_codex_subscription_transport.py +245 -0
- package/vendor/ars/scripts/test_check_669_review_pathway_rule_trace.py +304 -0
- package/vendor/ars/scripts/test_check_670_revision_roadmap_integration.py +260 -0
- package/vendor/ars/scripts/test_check_673_adjudication_activity.py +367 -0
- package/vendor/ars/scripts/test_check_684_review_criteria_binding.py +274 -0
- package/vendor/ars/scripts/test_check_agents_mirror_sync.py +137 -0
- package/vendor/ars/scripts/test_check_audit_artifact_consistency.py +2133 -0
- package/vendor/ars/scripts/test_check_benchmark_report.py +117 -0
- package/vendor/ars/scripts/test_check_bibliographic_integrity_signals.py +609 -0
- package/vendor/ars/scripts/test_check_calibration_tiers.py +376 -0
- package/vendor/ars/scripts/test_check_changelog_covers_merges.py +509 -0
- package/vendor/ars/scripts/test_check_ci_pytest_manifest.py +425 -0
- package/vendor/ars/scripts/test_check_claim_standing_candidate_ledger_integration.py +256 -0
- package/vendor/ars/scripts/test_check_claim_standing_freshness.py +277 -0
- package/vendor/ars/scripts/test_check_collaboration_depth_rubric.py +239 -0
- package/vendor/ars/scripts/test_check_command_frontmatter_name.py +178 -0
- package/vendor/ars/scripts/test_check_committee_correspondence.py +298 -0
- package/vendor/ars/scripts/test_check_compliance_report.py +381 -0
- package/vendor/ars/scripts/test_check_content_coverage_advisory_integration.py +308 -0
- package/vendor/ars/scripts/test_check_control_availability.py +361 -0
- package/vendor/ars/scripts/test_check_cross_document_consistency_advisory_integration.py +698 -0
- package/vendor/ars/scripts/test_check_cross_model_handoff_contract.py +301 -0
- package/vendor/ars/scripts/test_check_cross_model_verification_sync.py +203 -0
- package/vendor/ars/scripts/test_check_data_access_level.py +227 -0
- package/vendor/ars/scripts/test_check_data_flows.py +382 -0
- package/vendor/ars/scripts/test_check_decision_contract.py +387 -0
- package/vendor/ars/scripts/test_check_degradation_registry.py +270 -0
- package/vendor/ars/scripts/test_check_distribution_surface_claims.py +225 -0
- package/vendor/ars/scripts/test_check_domain_evidence_profile.py +439 -0
- package/vendor/ars/scripts/test_check_e4_promotion.py +163 -0
- package/vendor/ars/scripts/test_check_evals_gold_set.py +312 -0
- package/vendor/ars/scripts/test_check_evidence_row_integration.py +187 -0
- package/vendor/ars/scripts/test_check_field_norm_severity.py +173 -0
- package/vendor/ars/scripts/test_check_firm_rules_sync.py +342 -0
- package/vendor/ars/scripts/test_check_heldout_measurement_report.py +1508 -0
- package/vendor/ars/scripts/test_check_human_subjects_output_contract.py +129 -0
- package/vendor/ars/scripts/test_check_human_subjects_reference_migration.py +756 -0
- package/vendor/ars/scripts/test_check_instruction_data_boundary.py +204 -0
- package/vendor/ars/scripts/test_check_judge_prompt_version.py +90 -0
- package/vendor/ars/scripts/test_check_model_tiering.py +236 -0
- package/vendor/ars/scripts/test_check_panel_synthesis.py +1658 -0
- package/vendor/ars/scripts/test_check_passport_reset_contract.py +249 -0
- package/vendor/ars/scripts/test_check_pattern_eval_manifest.py +381 -0
- package/vendor/ars/scripts/test_check_persuasion_invariance_fixtures.py +591 -0
- package/vendor/ars/scripts/test_check_phase_conformance.py +4366 -0
- package/vendor/ars/scripts/test_check_pipeline_boundary_semantics.py +931 -0
- package/vendor/ars/scripts/test_check_pipeline_integrity.py +243 -0
- package/vendor/ars/scripts/test_check_policy_anchor_protocol.py +236 -0
- package/vendor/ars/scripts/test_check_policy_anchor_table.py +295 -0
- package/vendor/ars/scripts/test_check_prisma_trAIce_freshness.py +68 -0
- package/vendor/ars/scripts/test_check_promotion_bakeoff_preregistration.py +799 -0
- package/vendor/ars/scripts/test_check_ranking_lift.py +377 -0
- package/vendor/ars/scripts/test_check_re_review_synthesis.py +3398 -0
- package/vendor/ars/scripts/test_check_receipt_enum_sync.py +176 -0
- package/vendor/ars/scripts/test_check_repro_lock.py +107 -0
- package/vendor/ars/scripts/test_check_reviewer_data_fences.py +334 -0
- package/vendor/ars/scripts/test_check_reviewer_finding_contract.py +1107 -0
- package/vendor/ars/scripts/test_check_reviewer_role_label.py +491 -0
- package/vendor/ars/scripts/test_check_reviewer_scoring_honesty.py +222 -0
- package/vendor/ars/scripts/test_check_reviewer_sprint_prompt_sync.py +373 -0
- package/vendor/ars/scripts/test_check_revision_claim_drift_suite_v2.py +1925 -0
- package/vendor/ars/scripts/test_check_revision_token_conservation.py +398 -0
- package/vendor/ars/scripts/test_check_risk_register.py +336 -0
- package/vendor/ars/scripts/test_check_role_scoped_contract.py +1104 -0
- package/vendor/ars/scripts/test_check_rq_framing_patterns.py +110 -0
- package/vendor/ars/scripts/test_check_rubric_weight_consistency.py +32 -0
- package/vendor/ars/scripts/test_check_seeded_defect_fixtures.py +392 -0
- package/vendor/ars/scripts/test_check_setup_cross_model_parity.py +121 -0
- package/vendor/ars/scripts/test_check_spec_consistency.py +1081 -0
- package/vendor/ars/scripts/test_check_sprint_contract.py +458 -0
- package/vendor/ars/scripts/test_check_stage_capability_matrix.py +698 -0
- package/vendor/ars/scripts/test_check_submission_packet_manifest_integration.py +177 -0
- package/vendor/ars/scripts/test_check_surface_form_parity.py +417 -0
- package/vendor/ars/scripts/test_check_task_type.py +116 -0
- package/vendor/ars/scripts/test_check_tools_allowlist.py +835 -0
- package/vendor/ars/scripts/test_check_tortured_phrase_screening_integration.py +1306 -0
- package/vendor/ars/scripts/test_check_v3_10_134_write_scope.py +251 -0
- package/vendor/ars/scripts/test_check_v3_10_policy.py +547 -0
- package/vendor/ars/scripts/test_check_v3_6_7_pattern_protection.py +960 -0
- package/vendor/ars/scripts/test_check_v3_6_8_audit_scope_block.py +1000 -0
- package/vendor/ars/scripts/test_check_v3_6_8_cite_provenance_pipeline.py +454 -0
- package/vendor/ars/scripts/test_check_v3_6_8_frontmatter_trust_schema.py +581 -0
- package/vendor/ars/scripts/test_check_v3_6_8_mark_read_commands.py +120 -0
- package/vendor/ars/scripts/test_check_v3_6_8_pattern_protection.py +1138 -0
- package/vendor/ars/scripts/test_check_v3_7_3_three_layer_citation.py +566 -0
- package/vendor/ars/scripts/test_check_v3_8_annotation_literal_sync.py +262 -0
- package/vendor/ars/scripts/test_check_v3_9_0_triangulation.py +321 -0
- package/vendor/ars/scripts/test_check_v3_9_2_phase_boundary.py +190 -0
- package/vendor/ars/scripts/test_check_v3_9_4_temporal_verification.py +471 -0
- package/vendor/ars/scripts/test_check_version_consistency.py +1462 -0
- package/vendor/ars/scripts/test_check_workflow_classification.py +225 -0
- package/vendor/ars/scripts/test_chinese_literature_client.py +1889 -0
- package/vendor/ars/scripts/test_citation_existence_policy.py +480 -0
- package/vendor/ars/scripts/test_citation_verification_summary.py +342 -0
- package/vendor/ars/scripts/test_claim_audit_calibration.py +882 -0
- package/vendor/ars/scripts/test_claim_audit_finalizer.py +979 -0
- package/vendor/ars/scripts/test_claim_audit_pipeline.py +2398 -0
- package/vendor/ars/scripts/test_claim_audit_schema.py +1778 -0
- package/vendor/ars/scripts/test_claim_intent_manifest.py +666 -0
- package/vendor/ars/scripts/test_claim_registry_coverage.py +246 -0
- package/vendor/ars/scripts/test_claim_standing_discovery.py +579 -0
- package/vendor/ars/scripts/test_claim_standing_pipeline_wiring.py +290 -0
- package/vendor/ars/scripts/test_claim_standing_stance_assets.py +399 -0
- package/vendor/ars/scripts/test_claim_standing_stance_contracts.py +383 -0
- package/vendor/ars/scripts/test_claim_standing_stance_runner.py +349 -0
- package/vendor/ars/scripts/test_claim_standing_transmissions.py +605 -0
- package/vendor/ars/scripts/test_claim_strength_drift_disposition.py +517 -0
- package/vendor/ars/scripts/test_claim_verification_coverage_contract.py +104 -0
- package/vendor/ars/scripts/test_contamination_signals.py +1086 -0
- package/vendor/ars/scripts/test_content_coverage_advisory.py +1767 -0
- package/vendor/ars/scripts/test_cross_document_consistency_advisory.py +1270 -0
- package/vendor/ars/scripts/test_cross_model_codex_transport.py +1152 -0
- package/vendor/ars/scripts/test_cross_model_handoff.py +571 -0
- package/vendor/ars/scripts/test_cross_model_verification_guards.py +792 -0
- package/vendor/ars/scripts/test_crossref_client.py +393 -0
- package/vendor/ars/scripts/test_dispatch_e4_panel.py +4235 -0
- package/vendor/ars/scripts/test_e2e_claim_audit.py +540 -0
- package/vendor/ars/scripts/test_eval_harness_workflow.py +140 -0
- package/vendor/ars/scripts/test_evals_citation_extraction.py +150 -0
- package/vendor/ars/scripts/test_evals_lift_report_schema.py +108 -0
- package/vendor/ars/scripts/test_evidence_rows.py +2593 -0
- package/vendor/ars/scripts/test_experiment_provenance.py +915 -0
- package/vendor/ars/scripts/test_human_read_attestation_resolver.py +477 -0
- package/vendor/ars/scripts/test_ideation_diversity_assignment_gate.py +615 -0
- package/vendor/ars/scripts/test_indirect_prompt_injection_behavior_probe.py +250 -0
- package/vendor/ars/scripts/test_inquiry_branch_ledger.py +2296 -0
- package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_10.py +248 -0
- package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_7_3.py +545 -0
- package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_9_0.py +497 -0
- package/vendor/ars/scripts/test_normalize_compat_verdict.py +149 -0
- package/vendor/ars/scripts/test_openalex_client.py +490 -0
- package/vendor/ars/scripts/test_passport_yaml.py +104 -0
- package/vendor/ars/scripts/test_pattern_eval_runtime.py +1295 -0
- package/vendor/ars/scripts/test_pdf_read_preflight.py +1943 -0
- package/vendor/ars/scripts/test_policy_anchor_disclosure.py +666 -0
- package/vendor/ars/scripts/test_reading_probe_lint.py +218 -0
- package/vendor/ars/scripts/test_recompute_receipts.py +778 -0
- package/vendor/ars/scripts/test_render_claim_standing_view.py +192 -0
- package/vendor/ars/scripts/test_render_eval_comment.py +162 -0
- package/vendor/ars/scripts/test_render_harness_retirement_issue.py +110 -0
- package/vendor/ars/scripts/test_repro_lock_validation_drift.py +100 -0
- package/vendor/ars/scripts/test_research_workflow_profile.py +734 -0
- package/vendor/ars/scripts/test_resolve_human_subjects_authority.py +1219 -0
- package/vendor/ars/scripts/test_resolve_review_target_context.py +703 -0
- package/vendor/ars/scripts/test_resume_e4_record.py +315 -0
- package/vendor/ars/scripts/test_retraction_status.py +456 -0
- package/vendor/ars/scripts/test_review_criteria_binding.py +629 -0
- package/vendor/ars/scripts/test_review_panel_provenance.py +565 -0
- package/vendor/ars/scripts/test_review_pathway_rule_trace.py +821 -0
- package/vendor/ars/scripts/test_revision_roadmap.py +1255 -0
- package/vendor/ars/scripts/test_run_ci_pytest_manifest.py +186 -0
- package/vendor/ars/scripts/test_run_codex_audit_e2e.py +369 -0
- package/vendor/ars/scripts/test_run_evals.py +430 -0
- package/vendor/ars/scripts/test_run_guard_launcher.py +500 -0
- package/vendor/ars/scripts/test_run_ideation_diversity_no_call.py +1833 -0
- package/vendor/ars/scripts/test_run_indirect_prompt_injection_no_call.py +1889 -0
- package/vendor/ars/scripts/test_run_review_criteria_constructive_value.py +586 -0
- package/vendor/ars/scripts/test_run_role_topology_utility_dry_run.py +428 -0
- package/vendor/ars/scripts/test_score_review_criteria_constructive_value.py +340 -0
- package/vendor/ars/scripts/test_semantic_scholar_client.py +554 -0
- package/vendor/ars/scripts/test_slr_lineage_emission.py +230 -0
- package/vendor/ars/scripts/test_socratic_rq_non_generation_contract.py +173 -0
- package/vendor/ars/scripts/test_temporal_integrity_audit.py +438 -0
- package/vendor/ars/scripts/test_text_similarity.py +95 -0
- package/vendor/ars/scripts/test_title_fuzzy_false_positive.py +111 -0
- package/vendor/ars/scripts/test_tortured_phrase_screening.py +2959 -0
- package/vendor/ars/scripts/test_transport_fixture_citation_gate.py +338 -0
- package/vendor/ars/scripts/test_uncited_assertion.py +558 -0
- package/vendor/ars/scripts/test_v3_6_7_phase_6_6.py +1279 -0
- package/vendor/ars/scripts/test_validate_compliance_fixtures.py +36 -0
- package/vendor/ars/scripts/test_validate_ideation_diversity_assets.py +230 -0
- package/vendor/ars/scripts/test_venue_disclosure_contract.py +755 -0
- package/vendor/ars/scripts/test_verification_cache.py +280 -0
- package/vendor/ars/scripts/test_verification_gate.py +461 -0
- package/vendor/ars/scripts/test_verify_passport_cli.py +123 -0
- package/vendor/ars/scripts/test_verify_submission_package.py +1407 -0
- package/vendor/ars/scripts/test_version_records_schema.py +211 -0
- package/vendor/ars/scripts/tortured_phrase_screening.py +3502 -0
- package/vendor/ars/scripts/uncited_assertion_detector.py +254 -0
- package/vendor/ars/scripts/v3_6_7_inversion_manifest.json +9 -0
- package/vendor/ars/scripts/v3_6_8_inversion_manifest.json +10 -0
- package/vendor/ars/scripts/validate_claim_standing_stance_assets.py +249 -0
- package/vendor/ars/scripts/validate_compliance_fixtures.py +56 -0
- package/vendor/ars/scripts/validate_ideation_diversity_assets.py +303 -0
- package/vendor/ars/scripts/venue_disclosure_contract_harness.py +837 -0
- package/vendor/ars/scripts/verification_cache.py +276 -0
- package/vendor/ars/scripts/verification_gate/__init__.py +345 -0
- package/vendor/ars/scripts/verify_passport.py +133 -0
- package/vendor/ars/scripts/verify_submission_package.py +1657 -0
- package/vendor/ars/shared/agents/compliance_agent.md +136 -0
- package/vendor/ars/shared/artifact_reproducibility_pattern.md +173 -0
- package/vendor/ars/shared/benchmark_report.schema.json +81 -0
- package/vendor/ars/shared/benchmark_report_pattern.md +180 -0
- package/vendor/ars/shared/bibliographic_integrity_signals.md +142 -0
- package/vendor/ars/shared/collaboration_depth_rubric.md +154 -0
- package/vendor/ars/shared/compliance_checkpoint_protocol.md +162 -0
- package/vendor/ars/shared/compliance_report.schema.json +187 -0
- package/vendor/ars/shared/contracts/README.md +938 -0
- package/vendor/ars/shared/contracts/activity/adjudication_activity_input.schema.json +555 -0
- package/vendor/ars/shared/contracts/activity/adjudication_activity_store.schema.json +560 -0
- package/vendor/ars/shared/contracts/audit/audit_jsonl.schema.json +128 -0
- package/vendor/ars/shared/contracts/audit/audit_sidecar.schema.json +169 -0
- package/vendor/ars/shared/contracts/audit/audit_verdict.schema.json +133 -0
- package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory.schema.json +480 -0
- package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory_draft.schema.json +564 -0
- package/vendor/ars/shared/contracts/audit/cross_document_source_manifest.schema.json +202 -0
- package/vendor/ars/shared/contracts/audit/tortured_phrase_advisory.schema.json +1362 -0
- package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot.schema.json +208 -0
- package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot_manifest.schema.json +335 -0
- package/vendor/ars/shared/contracts/capability/stage_capability_matrix.json +434 -0
- package/vendor/ars/shared/contracts/claim_standing/candidate_ledger.schema.json +705 -0
- package/vendor/ars/shared/contracts/claim_standing/query_plan.schema.json +389 -0
- package/vendor/ars/shared/contracts/claim_standing/query_plan_v1_1.schema.json +782 -0
- package/vendor/ars/shared/contracts/claim_standing/retrieval_input.schema.json +440 -0
- package/vendor/ars/shared/contracts/claim_standing/stance_record.schema.json +460 -0
- package/vendor/ars/shared/contracts/claim_standing/transmission_ledger.schema.json +282 -0
- package/vendor/ars/shared/contracts/cross_model/codex_citation_receipt.schema.json +157 -0
- package/vendor/ars/shared/contracts/cross_model/codex_citation_request.schema.json +23 -0
- package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_commitment.schema.json +55 -0
- package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_reveal.schema.json +50 -0
- package/vendor/ars/shared/contracts/degradation_registry.json +428 -0
- package/vendor/ars/shared/contracts/evaluator/full.json +126 -0
- package/vendor/ars/shared/contracts/evidence/claim_registry.schema.json +50 -0
- package/vendor/ars/shared/contracts/evidence/claim_registry_coverage_report.schema.json +79 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row.schema.json +504 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row_v1_1.schema.json +364 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row_v1_2.schema.json +714 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row_v1_3.schema.json +524 -0
- package/vendor/ars/shared/contracts/human_subjects/authority_profile_registry.schema.json +497 -0
- package/vendor/ars/shared/contracts/human_subjects/committee_correspondence.schema.json +253 -0
- package/vendor/ars/shared/contracts/human_subjects/content_coverage_advisory.schema.json +620 -0
- package/vendor/ars/shared/contracts/human_subjects/irb_context_record.schema.json +329 -0
- package/vendor/ars/shared/contracts/human_subjects/resolved_authority_context.schema.json +347 -0
- package/vendor/ars/shared/contracts/human_subjects/review_pathway_rule_trace.schema.json +265 -0
- package/vendor/ars/shared/contracts/human_subjects/review_pathway_trace_request.schema.json +127 -0
- package/vendor/ars/shared/contracts/human_subjects/submission_packet_inventory.schema.json +240 -0
- package/vendor/ars/shared/contracts/human_subjects/submission_packet_manifest.schema.json +999 -0
- package/vendor/ars/shared/contracts/passport/audit_artifact_entry.schema.json +266 -0
- package/vendor/ars/shared/contracts/passport/bibliographic_integrity_signal.schema.json +1676 -0
- package/vendor/ars/shared/contracts/passport/citation_provenance.schema.json +107 -0
- package/vendor/ars/shared/contracts/passport/citation_verification_summary.schema.json +164 -0
- package/vendor/ars/shared/contracts/passport/claim_audit_result.schema.json +124 -0
- package/vendor/ars/shared/contracts/passport/claim_drift.schema.json +58 -0
- package/vendor/ars/shared/contracts/passport/claim_intent_manifest.schema.json +107 -0
- package/vendor/ars/shared/contracts/passport/constraint_violation.schema.json +65 -0
- package/vendor/ars/shared/contracts/passport/experiment_alignment_result.schema.json +69 -0
- package/vendor/ars/shared/contracts/passport/experiment_provenance_entry.schema.json +169 -0
- package/vendor/ars/shared/contracts/passport/human_read_log.schema.json +86 -0
- package/vendor/ars/shared/contracts/passport/inquiry_ledger_ref.schema.json +22 -0
- package/vendor/ars/shared/contracts/passport/literature_corpus_entry.schema.json +650 -0
- package/vendor/ars/shared/contracts/passport/preregistration_artifact.schema.json +132 -0
- package/vendor/ars/shared/contracts/passport/rejection_log.schema.json +89 -0
- package/vendor/ars/shared/contracts/passport/reset_ledger_entry.schema.json +158 -0
- package/vendor/ars/shared/contracts/passport/temporal_audit_results.schema.json +208 -0
- package/vendor/ars/shared/contracts/passport/terminal_policies.schema.json +50 -0
- package/vendor/ars/shared/contracts/passport/timeline.schema.json +102 -0
- package/vendor/ars/shared/contracts/passport/uncited_assertion.schema.json +56 -0
- package/vendor/ars/shared/contracts/passport/uncited_audit_failure.schema.json +72 -0
- package/vendor/ars/shared/contracts/passport/user_attested_read_resolution.schema.json +87 -0
- package/vendor/ars/shared/contracts/passport/version_records.schema.json +138 -0
- package/vendor/ars/shared/contracts/patch/block_manifest.schema.json +45 -0
- package/vendor/ars/shared/contracts/patch/legacy/v1_0/revision_patch.schema.json +110 -0
- package/vendor/ars/shared/contracts/patch/revision_patch.schema.json +250 -0
- package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_diagnostic.schema.json +39 -0
- package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_worker.schema.json +88 -0
- package/vendor/ars/shared/contracts/pdf/pdf_read_preflight.schema.json +227 -0
- package/vendor/ars/shared/contracts/re_review/input_manifest.schema.json +161 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/input_manifest.schema.json +134 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/precommitment.schema.json +193 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/traceability.schema.json +841 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/verdict_record.schema.json +244 -0
- package/vendor/ars/shared/contracts/re_review/precommitment.schema.json +193 -0
- package/vendor/ars/shared/contracts/re_review/traceability.schema.json +916 -0
- package/vendor/ars/shared/contracts/re_review/verdict_record.schema.json +245 -0
- package/vendor/ars/shared/contracts/research_workflow/inquiry_branch_ledger.schema.json +449 -0
- package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile.schema.json +222 -0
- package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile_selection_receipt.schema.json +111 -0
- package/vendor/ars/shared/contracts/review_target/constructive_review_findings.schema.json +172 -0
- package/vendor/ars/shared/contracts/review_target/criteria_registry.schema.json +97 -0
- package/vendor/ars/shared/contracts/review_target/review_criteria_binding_manifest.schema.json +235 -0
- package/vendor/ars/shared/contracts/review_target/review_criteria_source_receipt.schema.json +158 -0
- package/vendor/ars/shared/contracts/review_target/review_target_context.schema.json +106 -0
- package/vendor/ars/shared/contracts/review_target/review_target_declaration.schema.json +118 -0
- package/vendor/ars/shared/contracts/reviewer/full.json +114 -0
- package/vendor/ars/shared/contracts/reviewer/methodology_focus.json +75 -0
- package/vendor/ars/shared/contracts/reviewer/review_panel_provenance.schema.json +263 -0
- package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_carrier.schema.json +122 -0
- package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_input.schema.json +120 -0
- package/vendor/ars/shared/contracts/revision/author_adjudication.schema.json +251 -0
- package/vendor/ars/shared/contracts/revision/author_adjudication_input.schema.json +25 -0
- package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition.schema.json +97 -0
- package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition_input.schema.json +80 -0
- package/vendor/ars/shared/contracts/revision/claim_strength_drift_findings.schema.json +139 -0
- package/vendor/ars/shared/contracts/revision/claim_surface_manifest.schema.json +99 -0
- package/vendor/ars/shared/contracts/revision/integrity_correction_authorization.schema.json +140 -0
- package/vendor/ars/shared/contracts/revision/integrity_correction_authorization_input.schema.json +28 -0
- package/vendor/ars/shared/contracts/revision/integrity_correction_list.schema.json +48 -0
- package/vendor/ars/shared/contracts/revision/integrity_pass_receipt.schema.json +16 -0
- package/vendor/ars/shared/contracts/revision/revision_evidence_bundle.schema.json +134 -0
- package/vendor/ars/shared/contracts/revision/revision_roadmap.schema.json +334 -0
- package/vendor/ars/shared/contracts/submission/format_profile.example.yaml +33 -0
- package/vendor/ars/shared/contracts/submission/format_profile.schema.json +102 -0
- package/vendor/ars/shared/contracts/submission/submission_verification_report.schema.json +233 -0
- package/vendor/ars/shared/contracts/submission/venue_profile.schema.json +114 -0
- package/vendor/ars/shared/contracts/writer/full.json +87 -0
- package/vendor/ars/shared/cross_model_verification.md +714 -0
- package/vendor/ars/shared/evals_lift_report.schema.json +141 -0
- package/vendor/ars/shared/ground_truth_isolation_pattern.md +275 -0
- package/vendor/ars/shared/handoff_schemas.md +1209 -0
- package/vendor/ars/shared/human_subjects_authority_registry.json +1278 -0
- package/vendor/ars/shared/mode_spectrum.md +57 -0
- package/vendor/ars/shared/model_tiering.md +83 -0
- package/vendor/ars/shared/policy_data/nature_policy.md +56 -0
- package/vendor/ars/shared/prisma_trAIce_protocol.md +157 -0
- package/vendor/ars/shared/raise_framework.md +129 -0
- package/vendor/ars/shared/references/authority_content_coverage_advisory_protocol.md +275 -0
- package/vendor/ars/shared/references/claim_standing_candidate_ledger_protocol.md +66 -0
- package/vendor/ars/shared/references/claim_strength_ladder.md +93 -0
- package/vendor/ars/shared/references/cross_document_consistency_advisory_protocol.md +263 -0
- package/vendor/ars/shared/references/evidence_row_protocol.md +260 -0
- package/vendor/ars/shared/references/firm_rules.md +90 -0
- package/vendor/ars/shared/references/human_subjects_authority_protocol.md +274 -0
- package/vendor/ars/shared/references/intent_clarification_protocol.md +168 -0
- package/vendor/ars/shared/references/irb_terminology_glossary.md +229 -0
- package/vendor/ars/shared/references/protected_hedging_phrases.md +118 -0
- package/vendor/ars/shared/references/psychometric_terminology_glossary.md +109 -0
- package/vendor/ars/shared/references/review_criteria_consumer_protocol.md +238 -0
- package/vendor/ars/shared/references/review_pathway_rule_trace_protocol.md +166 -0
- package/vendor/ars/shared/references/submission_packet_manifest_protocol.md +292 -0
- package/vendor/ars/shared/references/word_count_conventions.md +124 -0
- package/vendor/ars/shared/research_workflow_profiles/field_general.json +1 -0
- package/vendor/ars/shared/review_criteria_registry.json +207 -0
- package/vendor/ars/shared/review_criteria_sources/msr-2027-technical-papers.2026-08-24.json +46 -0
- package/vendor/ars/shared/review_criteria_sources/sigsoft-empirical-standards.2026-08-24.json +31 -0
- package/vendor/ars/shared/sprint_contract.schema.json +482 -0
- package/vendor/ars/shared/style_calibration_protocol.md +151 -0
- package/vendor/ars/shared/templates/codex_audit_multifile_template.md +263 -0
- package/vendor/ars/tools/release-discipline/.toolkit-version +1 -0
- package/vendor/ars/tools/release-discipline/README.md +4 -0
- package/vendor/ars/tools/release-discipline/scripts/_release_doc_alignment_schema.py +1011 -0
- package/vendor/ars/tools/release-discipline/scripts/check_command_invariants.py +497 -0
- package/vendor/ars/tools/release-discipline/scripts/check_release_doc_alignment.py +263 -0
- package/vendor/ars/tools/release-discipline/scripts/sync-toolkit.sh +147 -0
- package/vendor/windows/NOTICE.md +12 -0
- package/vendor/windows/arm64/fd.exe +0 -0
- package/vendor/windows/arm64/licenses/fd/LICENSE-APACHE +201 -0
- package/vendor/windows/arm64/licenses/fd/LICENSE-MIT +21 -0
- package/vendor/windows/arm64/licenses/ripgrep/COPYING +3 -0
- package/vendor/windows/arm64/licenses/ripgrep/LICENSE-MIT +21 -0
- package/vendor/windows/arm64/licenses/ripgrep/UNLICENSE +24 -0
- package/vendor/windows/arm64/rg.exe +0 -0
- package/vendor/windows/x64/fd.exe +0 -0
- package/vendor/windows/x64/licenses/fd/LICENSE-APACHE +201 -0
- package/vendor/windows/x64/licenses/fd/LICENSE-MIT +21 -0
- package/vendor/windows/x64/licenses/ripgrep/COPYING +3 -0
- package/vendor/windows/x64/licenses/ripgrep/LICENSE-MIT +21 -0
- package/vendor/windows/x64/licenses/ripgrep/UNLICENSE +24 -0
- package/vendor/windows/x64/rg.exe +0 -0
|
@@ -0,0 +1,2398 @@
|
|
|
1
|
+
"""Audit-pipeline unit tests for v3.8 claim_ref_alignment_audit_agent (T-P1..T-P11).
|
|
2
|
+
|
|
3
|
+
Per spec §7.2 in
|
|
4
|
+
docs/design/2026-05-15-issue-103-claim-alignment-audit-spec.md.
|
|
5
|
+
|
|
6
|
+
These tests pin the contract of `scripts/claim_audit_pipeline.py`, the
|
|
7
|
+
Python module that implements the §4 Step 1-6 pipeline the agent prompt
|
|
8
|
+
narrates. Retrieval and judge are dependency-injected so tests can drive
|
|
9
|
+
every error path (paywall, audit_tool_failure, not_found, VIOLATED, etc.)
|
|
10
|
+
without touching the network or the on-disk cache.
|
|
11
|
+
|
|
12
|
+
Spec §7 names the test file `tests/test_claim_audit_pipeline.py`. Per
|
|
13
|
+
repo convention, tests live under `scripts/test_*.py` (CI uses
|
|
14
|
+
`python -m unittest scripts.test_*`); we keep the spec-named stem.
|
|
15
|
+
|
|
16
|
+
Run:
|
|
17
|
+
python -m unittest scripts.test_claim_audit_pipeline -v
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import unittest
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Callable
|
|
24
|
+
from unittest import mock
|
|
25
|
+
|
|
26
|
+
from tests.test_helpers import build_schema_validator, load_json_schema
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from scripts.claim_audit_pipeline import run_audit_pipeline # noqa: F401
|
|
30
|
+
_MODULE_IMPORT_ERR: Exception | None = None
|
|
31
|
+
except Exception as exc: # pragma: no cover — import-time error pathway is exercised in RED state
|
|
32
|
+
_MODULE_IMPORT_ERR = exc
|
|
33
|
+
|
|
34
|
+
def run_audit_pipeline(*args: Any, **kwargs: Any) -> Any:
|
|
35
|
+
raise _MODULE_IMPORT_ERR # type: ignore[misc]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# claim_audit_result schema validator — an emitted row MUST satisfy the
|
|
39
|
+
# passport entry schema (incl. rationale maxLength=2000). Some failure paths
|
|
40
|
+
# build the rationale from untrusted judge output, so a row that is supposed to
|
|
41
|
+
# be a clean inconclusive fallback can still overflow the schema (#355 P2#3).
|
|
42
|
+
_CAR_SCHEMA = load_json_schema(
|
|
43
|
+
Path(__file__).resolve().parent.parent / "shared/contracts/passport/claim_audit_result.schema.json"
|
|
44
|
+
)
|
|
45
|
+
_CAR_VALIDATOR = build_schema_validator(_CAR_SCHEMA)
|
|
46
|
+
|
|
47
|
+
# constraint_violation schema validator — a VIOLATED uncited claim rides in its
|
|
48
|
+
# own aggregate (rationale maxLength=2000 too). Its rationale is also copied
|
|
49
|
+
# straight from untrusted judge output on the success path (#360), so the same
|
|
50
|
+
# overflow can land a schema-invalid constraint_violation row.
|
|
51
|
+
_CV_SCHEMA = load_json_schema(
|
|
52
|
+
Path(__file__).resolve().parent.parent / "shared/contracts/passport/constraint_violation.schema.json"
|
|
53
|
+
)
|
|
54
|
+
_CV_VALIDATOR = build_schema_validator(_CV_SCHEMA)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
MANIFEST_ID = "M-2026-05-15T10:00:00Z-a1b2"
|
|
58
|
+
MANIFEST_ID_OTHER = "M-2026-05-15T10:05:00Z-c3d4"
|
|
59
|
+
AUDIT_RUN_ID = "2026-05-15T10:10:00Z-9f8e"
|
|
60
|
+
NOW = "2026-05-15T10:11:00Z"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _manifest(
|
|
64
|
+
*,
|
|
65
|
+
claims: list[dict[str, Any]] | None = None,
|
|
66
|
+
mncs: list[dict[str, str]] | None = None,
|
|
67
|
+
manifest_id: str = MANIFEST_ID,
|
|
68
|
+
) -> dict[str, Any]:
|
|
69
|
+
return {
|
|
70
|
+
"manifest_version": "1.0",
|
|
71
|
+
"manifest_id": manifest_id,
|
|
72
|
+
"emitted_by": "synthesis_agent",
|
|
73
|
+
"emitted_at": "2026-05-15T09:55:00Z",
|
|
74
|
+
"claims": claims
|
|
75
|
+
if claims is not None
|
|
76
|
+
else [
|
|
77
|
+
{
|
|
78
|
+
"claim_id": "C-001",
|
|
79
|
+
"claim_text": "Sample preprints accounted for 67% of corpus.",
|
|
80
|
+
"intended_evidence_kind": "empirical",
|
|
81
|
+
"planned_refs": ["smith2024preprints"],
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"manifest_negative_constraints": mncs or [],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _citation(
|
|
89
|
+
*,
|
|
90
|
+
claim_id: str = "C-001",
|
|
91
|
+
claim_text: str = "Sample preprints accounted for 67% of corpus.",
|
|
92
|
+
ref_slug: str = "smith2024preprints",
|
|
93
|
+
anchor_kind: str = "page",
|
|
94
|
+
anchor_value: str = "12",
|
|
95
|
+
section_path: str = "3. Results > 3.1 Overview",
|
|
96
|
+
scoped_manifest_id: str = MANIFEST_ID,
|
|
97
|
+
) -> dict[str, Any]:
|
|
98
|
+
return {
|
|
99
|
+
"claim_id": claim_id,
|
|
100
|
+
"scoped_manifest_id": scoped_manifest_id,
|
|
101
|
+
"claim_text": claim_text,
|
|
102
|
+
"ref_slug": ref_slug,
|
|
103
|
+
"anchor_kind": anchor_kind,
|
|
104
|
+
"anchor_value": anchor_value,
|
|
105
|
+
"section_path": section_path,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _config(**overrides: Any) -> dict[str, Any]:
|
|
110
|
+
base: dict[str, Any] = {
|
|
111
|
+
"max_claims_per_paper": 100,
|
|
112
|
+
"judge_model": "gpt-5.5-xhigh",
|
|
113
|
+
"gold_set_path": None,
|
|
114
|
+
"cache_dir": None, # Inject in-memory cache via run_audit_pipeline kwargs.
|
|
115
|
+
}
|
|
116
|
+
base.update(overrides)
|
|
117
|
+
return base
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _retrieval_ok(
|
|
121
|
+
*,
|
|
122
|
+
excerpt: str = "The cited page reports the 67% figure verbatim.",
|
|
123
|
+
) -> Callable[[dict[str, Any]], dict[str, Any]]:
|
|
124
|
+
def fn(citation: dict[str, Any]) -> dict[str, Any]:
|
|
125
|
+
return {
|
|
126
|
+
"ref_retrieval_method": "api",
|
|
127
|
+
"retrieved_excerpt": excerpt,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return fn
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _judge_supported() -> Callable[..., dict[str, Any]]:
|
|
134
|
+
def fn(**kwargs: Any) -> dict[str, Any]:
|
|
135
|
+
return {
|
|
136
|
+
"judgment": "SUPPORTED",
|
|
137
|
+
"rationale": "Cited page contains the 67% figure verbatim.",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return fn
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _judge_unsupported(*, defect_stage: str = "source_description") -> Callable[..., dict[str, Any]]:
|
|
144
|
+
def fn(**kwargs: Any) -> dict[str, Any]:
|
|
145
|
+
return {
|
|
146
|
+
"judgment": "UNSUPPORTED",
|
|
147
|
+
"rationale": f"Source describes a different population than the claim asserts.",
|
|
148
|
+
"defect_stage_hint": defect_stage,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return fn
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _judge_violated(*, violated_constraint_id: str) -> Callable[..., dict[str, Any]]:
|
|
155
|
+
def fn(**kwargs: Any) -> dict[str, Any]:
|
|
156
|
+
return {
|
|
157
|
+
"judgment": "VIOLATED",
|
|
158
|
+
"violated_constraint_id": violated_constraint_id,
|
|
159
|
+
"rationale": "Constraint forbids unqualified causal language.",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return fn
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _judge_partial(
|
|
166
|
+
*, breakdown: list[dict[str, Any]] | None = None
|
|
167
|
+
) -> Callable[..., dict[str, Any]]:
|
|
168
|
+
"""#213: a judge that returns a well-formed PARTIAL with a true-partial breakdown."""
|
|
169
|
+
def fn(**kwargs: Any) -> dict[str, Any]:
|
|
170
|
+
return {
|
|
171
|
+
"judgment": "PARTIAL",
|
|
172
|
+
"rationale": "Reference supports the first sub-claim but not the second.",
|
|
173
|
+
"sub_claim_breakdown": breakdown
|
|
174
|
+
if breakdown is not None
|
|
175
|
+
else [
|
|
176
|
+
{"sub_claim_text": "preprints are 67%", "sub_verdict": "SUPPORTED", "evidence_pointer": "p.12"},
|
|
177
|
+
{"sub_claim_text": "trend held across venues", "sub_verdict": "UNSUPPORTED", "evidence_pointer": None},
|
|
178
|
+
],
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return fn
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _judge_partial_malformed(
|
|
185
|
+
*, breakdown: Any
|
|
186
|
+
) -> Callable[..., dict[str, Any]]:
|
|
187
|
+
"""#213: a judge that returns PARTIAL with a malformed (not true-partial) breakdown."""
|
|
188
|
+
def fn(**kwargs: Any) -> dict[str, Any]:
|
|
189
|
+
return {"judgment": "PARTIAL", "rationale": "partial but malformed", "sub_claim_breakdown": breakdown}
|
|
190
|
+
|
|
191
|
+
return fn
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class _PipelineTestBase(unittest.TestCase):
|
|
195
|
+
"""Skip the entire pipeline suite cleanly when the module is missing.
|
|
196
|
+
|
|
197
|
+
During the RED phase (Step 4 of the TDD plan in spec §13), the module
|
|
198
|
+
`scripts/claim_audit_pipeline.py` does not exist yet — these tests
|
|
199
|
+
document the wished-for API. Once Step 5 lands the module, they will
|
|
200
|
+
flip from skipped (RED-as-skip) to executed pass/fail.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def setUpClass(cls) -> None:
|
|
205
|
+
if _MODULE_IMPORT_ERR is not None:
|
|
206
|
+
raise unittest.SkipTest(
|
|
207
|
+
f"scripts.claim_audit_pipeline not importable yet: {_MODULE_IMPORT_ERR!r} "
|
|
208
|
+
"(expected during RED phase — implementation lands in spec §13 step 5)"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def run_pipeline(self, **kwargs: Any) -> dict[str, list[dict[str, Any]]]:
|
|
212
|
+
defaults: dict[str, Any] = {
|
|
213
|
+
"manifests": [_manifest()],
|
|
214
|
+
"corpus": [],
|
|
215
|
+
"config": _config(),
|
|
216
|
+
"audit_run_id": AUDIT_RUN_ID,
|
|
217
|
+
"now_iso": NOW,
|
|
218
|
+
"retrieve_fn": _retrieval_ok(),
|
|
219
|
+
"judge_fn": _judge_supported(),
|
|
220
|
+
}
|
|
221
|
+
defaults.update(kwargs)
|
|
222
|
+
return run_audit_pipeline(**defaults)
|
|
223
|
+
|
|
224
|
+
def _validate_passport(
|
|
225
|
+
self, out: dict[str, Any], manifests: list[dict[str, Any]] | None = None
|
|
226
|
+
) -> list[Any]:
|
|
227
|
+
from scripts.check_claim_audit_consistency import validate_passport
|
|
228
|
+
|
|
229
|
+
body = {
|
|
230
|
+
"claim_intent_manifests": manifests if manifests is not None else [_manifest()],
|
|
231
|
+
"claim_audit_results": out["claim_audit_results"],
|
|
232
|
+
"uncited_assertions": out.get("uncited_assertions", []),
|
|
233
|
+
"claim_drifts": out.get("claim_drifts", []),
|
|
234
|
+
"constraint_violations": out.get("constraint_violations", []),
|
|
235
|
+
"audit_sampling_summaries": out.get("audit_sampling_summaries", []),
|
|
236
|
+
"uncited_audit_failures": out.get("uncited_audit_failures", []),
|
|
237
|
+
}
|
|
238
|
+
return validate_passport(body)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
# T-P1 — Step 1 anchor=none short-circuit.
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class TP1AnchorNoneShortCircuit(_PipelineTestBase):
|
|
247
|
+
"""T-P1: anchor=none input emits the canonical RETRIEVAL_FAILED triple and skips the judge."""
|
|
248
|
+
|
|
249
|
+
def test_anchor_none_skips_judge(self) -> None:
|
|
250
|
+
invocations: list[Any] = []
|
|
251
|
+
|
|
252
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
253
|
+
invocations.append(kwargs)
|
|
254
|
+
return {"judgment": "SUPPORTED", "rationale": "should not be called"}
|
|
255
|
+
|
|
256
|
+
out = self.run_pipeline(
|
|
257
|
+
citations=[_citation(anchor_kind="none", anchor_value="")],
|
|
258
|
+
judge_fn=judge_fn,
|
|
259
|
+
)
|
|
260
|
+
self.assertEqual(invocations, [], "judge MUST NOT be invoked for anchor=none rows")
|
|
261
|
+
results = out["claim_audit_results"]
|
|
262
|
+
self.assertEqual(len(results), 1)
|
|
263
|
+
e = results[0]
|
|
264
|
+
self.assertEqual(e["judgment"], "RETRIEVAL_FAILED")
|
|
265
|
+
self.assertEqual(e["audit_status"], "inconclusive")
|
|
266
|
+
self.assertEqual(e["defect_stage"], "not_applicable")
|
|
267
|
+
self.assertEqual(e["ref_retrieval_method"], "not_attempted")
|
|
268
|
+
self.assertTrue(
|
|
269
|
+
e["rationale"].startswith("v3.7.3 R-L3-1-A violation"),
|
|
270
|
+
f"rationale must start with INV-6 firm-rule prefix; got {e['rationale']!r}",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def test_anchor_none_pins_empty_sentinel_anchor_value(self) -> None:
|
|
274
|
+
# INV-6 sentinel: even if the caller passes a stale residual anchor_value
|
|
275
|
+
# on an anchor_kind=none citation, the pipeline MUST coerce it to the
|
|
276
|
+
# empty string per the schema contract (Step 13 R1 Gemini finding).
|
|
277
|
+
out = self.run_pipeline(
|
|
278
|
+
citations=[_citation(anchor_kind="none", anchor_value="123")],
|
|
279
|
+
judge_fn=lambda **_kw: {"judgment": "SUPPORTED", "rationale": "n/a"},
|
|
280
|
+
)
|
|
281
|
+
results = out["claim_audit_results"]
|
|
282
|
+
self.assertEqual(len(results), 1)
|
|
283
|
+
self.assertEqual(
|
|
284
|
+
results[0]["anchor_value"],
|
|
285
|
+
"",
|
|
286
|
+
"anchor_kind=none rows must carry the empty sentinel anchor_value per INV-6",
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# ---------------------------------------------------------------------------
|
|
291
|
+
# T-P2 / T-P3 — Step 3 cache hit / miss.
|
|
292
|
+
# ---------------------------------------------------------------------------
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class TP2P3CacheBehavior(_PipelineTestBase):
|
|
296
|
+
"""T-P2/T-P3: cache keyed by (claim, ref, anchor, retrieved_excerpt_hash, constraint_set, judge_model)."""
|
|
297
|
+
|
|
298
|
+
def test_p2_cache_hit_skips_judge(self) -> None:
|
|
299
|
+
cache: dict[str, Any] = {}
|
|
300
|
+
invocations: list[Any] = []
|
|
301
|
+
|
|
302
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
303
|
+
invocations.append(kwargs)
|
|
304
|
+
return {"judgment": "SUPPORTED", "rationale": "judge ran"}
|
|
305
|
+
|
|
306
|
+
# First run populates the cache.
|
|
307
|
+
self.run_pipeline(
|
|
308
|
+
citations=[_citation()],
|
|
309
|
+
judge_fn=judge_fn,
|
|
310
|
+
cache=cache,
|
|
311
|
+
)
|
|
312
|
+
self.assertEqual(len(invocations), 1, "first run must invoke judge")
|
|
313
|
+
|
|
314
|
+
# Second run with same inputs MUST hit cache.
|
|
315
|
+
self.run_pipeline(
|
|
316
|
+
citations=[_citation()],
|
|
317
|
+
judge_fn=judge_fn,
|
|
318
|
+
cache=cache,
|
|
319
|
+
)
|
|
320
|
+
self.assertEqual(len(invocations), 1, "second run with identical inputs must NOT re-invoke judge")
|
|
321
|
+
|
|
322
|
+
def test_p3_cache_miss_after_manual_pdf_uploaded(self) -> None:
|
|
323
|
+
cache: dict[str, Any] = {}
|
|
324
|
+
invocations: list[Any] = []
|
|
325
|
+
|
|
326
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
327
|
+
invocations.append(kwargs)
|
|
328
|
+
return {"judgment": "SUPPORTED", "rationale": "judge ran"}
|
|
329
|
+
|
|
330
|
+
# First run with API retrieval.
|
|
331
|
+
self.run_pipeline(
|
|
332
|
+
citations=[_citation()],
|
|
333
|
+
judge_fn=judge_fn,
|
|
334
|
+
retrieve_fn=_retrieval_ok(excerpt="api-served excerpt"),
|
|
335
|
+
cache=cache,
|
|
336
|
+
)
|
|
337
|
+
# Second run with manual_pdf uploading a different excerpt -> retrieved_excerpt_hash changes
|
|
338
|
+
# -> cache MUST miss and re-invoke the judge.
|
|
339
|
+
|
|
340
|
+
def manual_pdf_retrieval(citation: dict[str, Any]) -> dict[str, Any]:
|
|
341
|
+
return {
|
|
342
|
+
"ref_retrieval_method": "manual_pdf",
|
|
343
|
+
"retrieved_excerpt": "different excerpt from manual PDF upload",
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
self.run_pipeline(
|
|
347
|
+
citations=[_citation()],
|
|
348
|
+
judge_fn=judge_fn,
|
|
349
|
+
retrieve_fn=manual_pdf_retrieval,
|
|
350
|
+
cache=cache,
|
|
351
|
+
)
|
|
352
|
+
self.assertEqual(
|
|
353
|
+
len(invocations),
|
|
354
|
+
2,
|
|
355
|
+
"manual PDF excerpt with different hash MUST force a fresh judge invocation",
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
# #361 — prompt-version partitions the judge cache keyspace.
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
class TP361PromptVersionCacheKey(_PipelineTestBase):
|
|
365
|
+
"""#361: the judge cache key must include a prompt-version component so a
|
|
366
|
+
judge-prompt revision (e.g. #213 Step-0 decomposition) invalidates stale
|
|
367
|
+
entries automatically — a verdict cached under prompt A must NOT be served
|
|
368
|
+
once the active prompt is B. Same-version entries still dedup (no
|
|
369
|
+
regression). When no concrete prompt version can be resolved (caller
|
|
370
|
+
declares it unknown), the cache fails CLOSED — stale entries are never
|
|
371
|
+
served across an unknown-version boundary.
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
@staticmethod
|
|
375
|
+
def _counting_judge(invocations: list[Any]) -> Callable[..., dict[str, Any]]:
|
|
376
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
377
|
+
invocations.append(kwargs)
|
|
378
|
+
return {"judgment": "SUPPORTED", "rationale": "judge ran"}
|
|
379
|
+
|
|
380
|
+
return judge_fn
|
|
381
|
+
|
|
382
|
+
def test_prompt_version_change_misses_cache(self) -> None:
|
|
383
|
+
cache: dict[str, Any] = {}
|
|
384
|
+
invocations: list[Any] = []
|
|
385
|
+
judge_fn = self._counting_judge(invocations)
|
|
386
|
+
|
|
387
|
+
# Populate under prompt version A.
|
|
388
|
+
self.run_pipeline(
|
|
389
|
+
citations=[_citation()],
|
|
390
|
+
judge_fn=judge_fn,
|
|
391
|
+
cache=cache,
|
|
392
|
+
config=_config(judge_prompt_version="promptA"),
|
|
393
|
+
)
|
|
394
|
+
self.assertEqual(len(invocations), 1, "first run must invoke judge")
|
|
395
|
+
|
|
396
|
+
# Same (claim, ref, anchor, excerpt, constraints, model) but a NEW
|
|
397
|
+
# prompt version MUST miss and re-invoke the judge.
|
|
398
|
+
self.run_pipeline(
|
|
399
|
+
citations=[_citation()],
|
|
400
|
+
judge_fn=judge_fn,
|
|
401
|
+
cache=cache,
|
|
402
|
+
config=_config(judge_prompt_version="promptB"),
|
|
403
|
+
)
|
|
404
|
+
self.assertEqual(
|
|
405
|
+
len(invocations), 2,
|
|
406
|
+
"a prompt-version change MUST invalidate the stale entry and re-invoke the judge",
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
def test_same_prompt_version_still_hits(self) -> None:
|
|
410
|
+
# Regression guard: identical prompt version keeps the existing dedup.
|
|
411
|
+
cache: dict[str, Any] = {}
|
|
412
|
+
invocations: list[Any] = []
|
|
413
|
+
judge_fn = self._counting_judge(invocations)
|
|
414
|
+
|
|
415
|
+
for _ in range(2):
|
|
416
|
+
self.run_pipeline(
|
|
417
|
+
citations=[_citation()],
|
|
418
|
+
judge_fn=judge_fn,
|
|
419
|
+
cache=cache,
|
|
420
|
+
config=_config(judge_prompt_version="promptA"),
|
|
421
|
+
)
|
|
422
|
+
self.assertEqual(
|
|
423
|
+
len(invocations), 1,
|
|
424
|
+
"two runs under the same prompt version must hit the cache (no dedup regression)",
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def test_unknown_prompt_version_fails_closed(self) -> None:
|
|
428
|
+
# Caller declares the prompt version unknown (None). Across two distinct
|
|
429
|
+
# runs (different audit_run_id) the cache must NOT serve the stale entry
|
|
430
|
+
# — the unknown version binds a run-local component, so each run misses.
|
|
431
|
+
cache: dict[str, Any] = {}
|
|
432
|
+
invocations: list[Any] = []
|
|
433
|
+
judge_fn = self._counting_judge(invocations)
|
|
434
|
+
|
|
435
|
+
for run_id in ("2026-05-15T10:10:00Z-run1", "2026-05-15T10:20:00Z-run2"):
|
|
436
|
+
self.run_pipeline(
|
|
437
|
+
citations=[_citation()],
|
|
438
|
+
judge_fn=judge_fn,
|
|
439
|
+
cache=cache,
|
|
440
|
+
config=_config(judge_prompt_version=None),
|
|
441
|
+
audit_run_id=run_id,
|
|
442
|
+
)
|
|
443
|
+
self.assertEqual(
|
|
444
|
+
len(invocations), 2,
|
|
445
|
+
"an unknown prompt version must fail closed — no cross-run cache hit",
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
def test_unknown_prompt_version_dedups_within_a_run(self) -> None:
|
|
449
|
+
# Fail-closed must not break WITHIN-run dedup: two identical citations in
|
|
450
|
+
# the SAME run (same audit_run_id) share the run-local component, so the
|
|
451
|
+
# second is a hit — the judge runs once.
|
|
452
|
+
cache: dict[str, Any] = {}
|
|
453
|
+
invocations: list[Any] = []
|
|
454
|
+
judge_fn = self._counting_judge(invocations)
|
|
455
|
+
|
|
456
|
+
self.run_pipeline(
|
|
457
|
+
citations=[_citation(), _citation()],
|
|
458
|
+
judge_fn=judge_fn,
|
|
459
|
+
cache=cache,
|
|
460
|
+
config=_config(judge_prompt_version=None),
|
|
461
|
+
)
|
|
462
|
+
self.assertEqual(
|
|
463
|
+
len(invocations), 1,
|
|
464
|
+
"two identical citations in one run must still dedup under the run-local key",
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
def test_default_prompt_version_hits_across_runs(self) -> None:
|
|
468
|
+
# When the caller does NOT declare a version at all, the repo constant
|
|
469
|
+
# JUDGE_PROMPT_VERSION supplies a real version → normal dedup holds.
|
|
470
|
+
cache: dict[str, Any] = {}
|
|
471
|
+
invocations: list[Any] = []
|
|
472
|
+
judge_fn = self._counting_judge(invocations)
|
|
473
|
+
|
|
474
|
+
for _ in range(2):
|
|
475
|
+
self.run_pipeline(citations=[_citation()], judge_fn=judge_fn, cache=cache)
|
|
476
|
+
self.assertEqual(
|
|
477
|
+
len(invocations), 1,
|
|
478
|
+
"absent an explicit version, the repo constant is a real version and dedup holds",
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def test_default_tracks_prompt_hash_not_version_label(self) -> None:
|
|
482
|
+
# codex P2: with NO explicit judge_prompt_version, the default cache-key
|
|
483
|
+
# prompt component must be the prompt FINGERPRINT (JUDGE_PROMPT_SHA256),
|
|
484
|
+
# not the decoupled human-readable JUDGE_PROMPT_VERSION label. A prompt
|
|
485
|
+
# edit that re-pins the SHA256 (lint enforces this) must AUTOMATICALLY
|
|
486
|
+
# invalidate stale entries — even if the author forgot to bump the
|
|
487
|
+
# version label. Patch the hash to two distinct 64-char values across two
|
|
488
|
+
# runs sharing one cache; the judge must be invoked TWICE (cache miss).
|
|
489
|
+
cache: dict[str, Any] = {}
|
|
490
|
+
invocations: list[Any] = []
|
|
491
|
+
judge_fn = self._counting_judge(invocations)
|
|
492
|
+
hash_a = "a" * 64
|
|
493
|
+
hash_b = "b" * 64
|
|
494
|
+
|
|
495
|
+
with mock.patch(
|
|
496
|
+
"scripts.claim_audit_pipeline.JUDGE_PROMPT_SHA256", hash_a
|
|
497
|
+
):
|
|
498
|
+
self.run_pipeline(citations=[_citation()], judge_fn=judge_fn, cache=cache)
|
|
499
|
+
self.assertEqual(len(invocations), 1, "first run must invoke judge")
|
|
500
|
+
|
|
501
|
+
with mock.patch(
|
|
502
|
+
"scripts.claim_audit_pipeline.JUDGE_PROMPT_SHA256", hash_b
|
|
503
|
+
):
|
|
504
|
+
self.run_pipeline(citations=[_citation()], judge_fn=judge_fn, cache=cache)
|
|
505
|
+
self.assertEqual(
|
|
506
|
+
len(invocations), 2,
|
|
507
|
+
"a re-pinned prompt hash MUST invalidate the stale entry and re-invoke "
|
|
508
|
+
"the judge — the default cache-key prompt component is the hash, not the "
|
|
509
|
+
"decoupled version label",
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
# ---------------------------------------------------------------------------
|
|
514
|
+
# T-P4 — Step 2 ref_retrieval_method=failed → LOW-WARN paywall path.
|
|
515
|
+
# ---------------------------------------------------------------------------
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
class TP4FailedRetrievalPaywall(_PipelineTestBase):
|
|
519
|
+
"""T-P4: paywall path produces (RETRIEVAL_FAILED, inconclusive, not_applicable, failed)."""
|
|
520
|
+
|
|
521
|
+
def test_paywall_triple(self) -> None:
|
|
522
|
+
def paywall(citation: dict[str, Any]) -> dict[str, Any]:
|
|
523
|
+
return {"ref_retrieval_method": "failed", "retrieved_excerpt": None}
|
|
524
|
+
|
|
525
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
526
|
+
raise AssertionError("judge MUST NOT be called on paywall path")
|
|
527
|
+
|
|
528
|
+
out = self.run_pipeline(
|
|
529
|
+
citations=[_citation()],
|
|
530
|
+
retrieve_fn=paywall,
|
|
531
|
+
judge_fn=judge_fn,
|
|
532
|
+
)
|
|
533
|
+
self.assertEqual(len(out["claim_audit_results"]), 1)
|
|
534
|
+
e = out["claim_audit_results"][0]
|
|
535
|
+
self.assertEqual(e["judgment"], "RETRIEVAL_FAILED")
|
|
536
|
+
self.assertEqual(e["audit_status"], "inconclusive")
|
|
537
|
+
self.assertEqual(e["defect_stage"], "not_applicable")
|
|
538
|
+
self.assertEqual(e["ref_retrieval_method"], "failed")
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
# ---------------------------------------------------------------------------
|
|
542
|
+
# T-P5 — Step 2 manual_pdf accepted; not_found triggers retrieval_existence.
|
|
543
|
+
# ---------------------------------------------------------------------------
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class TP5RetrievalPathways(_PipelineTestBase):
|
|
547
|
+
"""T-P5: manual_pdf accepted; not_found triggers defect_stage=retrieval_existence."""
|
|
548
|
+
|
|
549
|
+
def test_manual_pdf_accepted(self) -> None:
|
|
550
|
+
def manual_pdf(citation: dict[str, Any]) -> dict[str, Any]:
|
|
551
|
+
return {"ref_retrieval_method": "manual_pdf", "retrieved_excerpt": "user-uploaded excerpt"}
|
|
552
|
+
|
|
553
|
+
out = self.run_pipeline(citations=[_citation()], retrieve_fn=manual_pdf)
|
|
554
|
+
e = out["claim_audit_results"][0]
|
|
555
|
+
self.assertEqual(e["ref_retrieval_method"], "manual_pdf")
|
|
556
|
+
self.assertEqual(e["judgment"], "SUPPORTED")
|
|
557
|
+
|
|
558
|
+
def test_not_found_triggers_retrieval_existence(self) -> None:
|
|
559
|
+
def not_found(citation: dict[str, Any]) -> dict[str, Any]:
|
|
560
|
+
return {"ref_retrieval_method": "not_found", "retrieved_excerpt": None}
|
|
561
|
+
|
|
562
|
+
out = self.run_pipeline(citations=[_citation()], retrieve_fn=not_found)
|
|
563
|
+
e = out["claim_audit_results"][0]
|
|
564
|
+
self.assertEqual(e["judgment"], "RETRIEVAL_FAILED")
|
|
565
|
+
self.assertEqual(e["audit_status"], "completed")
|
|
566
|
+
self.assertEqual(e["defect_stage"], "retrieval_existence")
|
|
567
|
+
self.assertEqual(e["ref_retrieval_method"], "not_found")
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
# ---------------------------------------------------------------------------
|
|
571
|
+
# T-P6 — Step 5 judge VIOLATED routes to negative_constraint_violation.
|
|
572
|
+
# ---------------------------------------------------------------------------
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
class TP6ConstraintViolation(_PipelineTestBase):
|
|
576
|
+
"""T-P6: cited claim with VIOLATED judge verdict emits claim_audit_result with negative_constraint_violation."""
|
|
577
|
+
|
|
578
|
+
def test_violated_routes_to_claim_audit_result(self) -> None:
|
|
579
|
+
manifest = _manifest(
|
|
580
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "No causal language without RCT."}],
|
|
581
|
+
)
|
|
582
|
+
out = self.run_pipeline(
|
|
583
|
+
citations=[_citation()],
|
|
584
|
+
manifests=[manifest],
|
|
585
|
+
judge_fn=_judge_violated(violated_constraint_id="MNC-1"),
|
|
586
|
+
)
|
|
587
|
+
results = out["claim_audit_results"]
|
|
588
|
+
self.assertEqual(len(results), 1)
|
|
589
|
+
e = results[0]
|
|
590
|
+
self.assertEqual(e["judgment"], "UNSUPPORTED")
|
|
591
|
+
self.assertEqual(e["defect_stage"], "negative_constraint_violation")
|
|
592
|
+
self.assertEqual(e["violated_constraint_id"], "MNC-1")
|
|
593
|
+
self.assertEqual(out["constraint_violations"], [], "cited violation MUST emit into claim_audit_results, not constraint_violations")
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
# ---------------------------------------------------------------------------
|
|
597
|
+
# T-P7 — Step 6 defect_stage classification fixtures.
|
|
598
|
+
# ---------------------------------------------------------------------------
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
class TP7DefectStageClassification(_PipelineTestBase):
|
|
602
|
+
"""T-P7: each of 6 substantive defect_stages has a fixture mapping."""
|
|
603
|
+
|
|
604
|
+
DEFECT_STAGES_TO_TEST = [
|
|
605
|
+
("retrieval_existence", "not_found"),
|
|
606
|
+
("metadata", "api"),
|
|
607
|
+
("source_description", "api"),
|
|
608
|
+
("citation_anchor", "api"),
|
|
609
|
+
("synthesis_overclaim", "api"),
|
|
610
|
+
("negative_constraint_violation", "api"),
|
|
611
|
+
]
|
|
612
|
+
|
|
613
|
+
def test_each_defect_stage_mappable(self) -> None:
|
|
614
|
+
for defect_stage, method in self.DEFECT_STAGES_TO_TEST:
|
|
615
|
+
with self.subTest(defect_stage=defect_stage):
|
|
616
|
+
# Each defect_stage corresponds to a distinct pipeline path; we
|
|
617
|
+
# exercise the dispatch by configuring retrieval + judge to that
|
|
618
|
+
# combination, then assert the emitted entry carries the right
|
|
619
|
+
# defect_stage tag.
|
|
620
|
+
if defect_stage == "retrieval_existence":
|
|
621
|
+
out = self.run_pipeline(
|
|
622
|
+
citations=[_citation()],
|
|
623
|
+
retrieve_fn=lambda c: {"ref_retrieval_method": "not_found", "retrieved_excerpt": None},
|
|
624
|
+
)
|
|
625
|
+
elif defect_stage == "negative_constraint_violation":
|
|
626
|
+
manifest = _manifest(
|
|
627
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "Rule."}],
|
|
628
|
+
)
|
|
629
|
+
out = self.run_pipeline(
|
|
630
|
+
citations=[_citation()],
|
|
631
|
+
manifests=[manifest],
|
|
632
|
+
judge_fn=_judge_violated(violated_constraint_id="MNC-1"),
|
|
633
|
+
)
|
|
634
|
+
else:
|
|
635
|
+
out = self.run_pipeline(
|
|
636
|
+
citations=[_citation()],
|
|
637
|
+
judge_fn=_judge_unsupported(defect_stage=defect_stage),
|
|
638
|
+
)
|
|
639
|
+
results = out["claim_audit_results"]
|
|
640
|
+
self.assertEqual(len(results), 1, msg=f"expected 1 row for {defect_stage}")
|
|
641
|
+
self.assertEqual(results[0]["defect_stage"], defect_stage)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
# ---------------------------------------------------------------------------
|
|
645
|
+
# T-P8 — Precedence rule 1: drift + constraint violation → constraint absorbs drift.
|
|
646
|
+
# ---------------------------------------------------------------------------
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
class TP8DriftConstraintPrecedence(_PipelineTestBase):
|
|
650
|
+
"""T-P8: a claim that drifts AND violates a constraint emits only the constraint_audit_result row."""
|
|
651
|
+
|
|
652
|
+
def test_constraint_absorbs_drift(self) -> None:
|
|
653
|
+
# Manifest mentions one claim; the prose drifts AND violates.
|
|
654
|
+
manifest = _manifest(
|
|
655
|
+
claims=[
|
|
656
|
+
{
|
|
657
|
+
"claim_id": "C-001",
|
|
658
|
+
"claim_text": "Sample preprints accounted for 67% of corpus.",
|
|
659
|
+
"intended_evidence_kind": "empirical",
|
|
660
|
+
"planned_refs": [],
|
|
661
|
+
}
|
|
662
|
+
],
|
|
663
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "Rule."}],
|
|
664
|
+
)
|
|
665
|
+
# The emitted citation is for a different claim_text (drifted) AND triggers VIOLATED.
|
|
666
|
+
drifted_cite = _citation(
|
|
667
|
+
claim_id="C-002", # not in manifest
|
|
668
|
+
claim_text="We observed causality between A and B.",
|
|
669
|
+
)
|
|
670
|
+
out = self.run_pipeline(
|
|
671
|
+
citations=[drifted_cite],
|
|
672
|
+
manifests=[manifest],
|
|
673
|
+
judge_fn=_judge_violated(violated_constraint_id="MNC-1"),
|
|
674
|
+
)
|
|
675
|
+
results = out["claim_audit_results"]
|
|
676
|
+
self.assertEqual(len(results), 1, "must emit claim_audit_result")
|
|
677
|
+
self.assertEqual(results[0]["defect_stage"], "negative_constraint_violation")
|
|
678
|
+
drifts = out["claim_drifts"]
|
|
679
|
+
self.assertEqual(
|
|
680
|
+
drifts,
|
|
681
|
+
[],
|
|
682
|
+
"constraint violation MUST absorb drift signal — no companion claim_drifts[] entry per T-P8",
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
# ---------------------------------------------------------------------------
|
|
687
|
+
# T-P9 — Precedence rule 2: citation_anchor distinct from source_description.
|
|
688
|
+
# ---------------------------------------------------------------------------
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
class TP9AnchorVsDescription(_PipelineTestBase):
|
|
692
|
+
"""T-P9: anchor-wrong + description-correct => defect_stage=citation_anchor (not source_description)."""
|
|
693
|
+
|
|
694
|
+
def test_anchor_wrong_description_correct(self) -> None:
|
|
695
|
+
out = self.run_pipeline(
|
|
696
|
+
citations=[_citation()],
|
|
697
|
+
judge_fn=_judge_unsupported(defect_stage="citation_anchor"),
|
|
698
|
+
)
|
|
699
|
+
e = out["claim_audit_results"][0]
|
|
700
|
+
self.assertEqual(e["defect_stage"], "citation_anchor")
|
|
701
|
+
self.assertNotEqual(e["defect_stage"], "source_description")
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
# ---------------------------------------------------------------------------
|
|
705
|
+
# T-P10 — Precedence rule 3: uncited + manifest-claim sentence => uncited_assertion only.
|
|
706
|
+
# ---------------------------------------------------------------------------
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
class TP10UncitedOverDrift(_PipelineTestBase):
|
|
710
|
+
"""T-P10: a sentence that is BOTH uncited AND a drifted manifest claim emits only uncited_assertions[]."""
|
|
711
|
+
|
|
712
|
+
def test_uncited_takes_precedence_over_drift(self) -> None:
|
|
713
|
+
manifest = _manifest()
|
|
714
|
+
# The emitted draft contains an uncited sentence (no ref) AND it differs from manifest -> drift.
|
|
715
|
+
uncited_sentences = [
|
|
716
|
+
{
|
|
717
|
+
"sentence_text": "Half of all submissions showed positive results.",
|
|
718
|
+
"section_path": "3. Results",
|
|
719
|
+
"manifest_claim_id": None,
|
|
720
|
+
# Detector-supplied per the v3.8 Step 6 contract: callers
|
|
721
|
+
# must pre-process raw sentences through
|
|
722
|
+
# detect_uncited_assertions (or surface explicit
|
|
723
|
+
# trigger_tokens). _uncited_assertion_entry raises if both
|
|
724
|
+
# the keyword arg and this field are missing.
|
|
725
|
+
"trigger_tokens": ["showed"],
|
|
726
|
+
}
|
|
727
|
+
]
|
|
728
|
+
out = self.run_pipeline(
|
|
729
|
+
citations=[], # no citation -> no claim_audit_result row
|
|
730
|
+
manifests=[manifest],
|
|
731
|
+
uncited_sentences=uncited_sentences,
|
|
732
|
+
)
|
|
733
|
+
self.assertEqual(out["claim_audit_results"], [], "uncited sentence has no ref -> no claim_audit_result row")
|
|
734
|
+
self.assertEqual(len(out["uncited_assertions"]), 1, "uncited entry MUST emit")
|
|
735
|
+
# Sentence is not in manifest, and a companion claim_drifts[] entry would
|
|
736
|
+
# also be a natural drift signal — but precedence rule 3 forbids the drift
|
|
737
|
+
# row when uncited fires for the same sentence.
|
|
738
|
+
same_text_drift = [d for d in out["claim_drifts"] if d.get("claim_text") == uncited_sentences[0]["sentence_text"]]
|
|
739
|
+
self.assertEqual(
|
|
740
|
+
same_text_drift,
|
|
741
|
+
[],
|
|
742
|
+
"no companion claim_drifts[] entry for the same sentence per T-P10 / D-INV-4",
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
# ---------------------------------------------------------------------------
|
|
747
|
+
# T-P11 — Cap sampling behavior.
|
|
748
|
+
# ---------------------------------------------------------------------------
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
class TP11CapSampling(_PipelineTestBase):
|
|
752
|
+
"""T-P11: N>cap emits stratified summary; N<=cap emits no summary OR telemetry summary; cap=0 rejected."""
|
|
753
|
+
|
|
754
|
+
def test_large_n_emits_stratified_summary(self) -> None:
|
|
755
|
+
# 150 citations, cap=100 -> exactly 1 sampling summary, audited_count=100.
|
|
756
|
+
citations = [
|
|
757
|
+
_citation(
|
|
758
|
+
claim_id=f"C-{i:03d}",
|
|
759
|
+
ref_slug=f"ref-{i:03d}",
|
|
760
|
+
scoped_manifest_id=MANIFEST_ID,
|
|
761
|
+
)
|
|
762
|
+
for i in range(1, 151)
|
|
763
|
+
]
|
|
764
|
+
# Manifest carries 150 claims to satisfy INV-15 cross-array integrity.
|
|
765
|
+
big_manifest = _manifest(
|
|
766
|
+
claims=[
|
|
767
|
+
{
|
|
768
|
+
"claim_id": f"C-{i:03d}",
|
|
769
|
+
"claim_text": f"Claim {i}.",
|
|
770
|
+
"intended_evidence_kind": "empirical",
|
|
771
|
+
"planned_refs": [],
|
|
772
|
+
}
|
|
773
|
+
for i in range(1, 151)
|
|
774
|
+
],
|
|
775
|
+
)
|
|
776
|
+
out = self.run_pipeline(
|
|
777
|
+
citations=citations,
|
|
778
|
+
manifests=[big_manifest],
|
|
779
|
+
config=_config(max_claims_per_paper=100),
|
|
780
|
+
)
|
|
781
|
+
samplings = out["audit_sampling_summaries"]
|
|
782
|
+
self.assertEqual(len(samplings), 1)
|
|
783
|
+
s = samplings[0]
|
|
784
|
+
self.assertEqual(s["audited_count"], 100)
|
|
785
|
+
self.assertEqual(s["total_citation_count"], 150)
|
|
786
|
+
self.assertEqual(s["max_claims_per_paper"], 100)
|
|
787
|
+
self.assertEqual(s["sampling_strategy"], "stratified_buckets_v1")
|
|
788
|
+
indices = s["audited_indices"]
|
|
789
|
+
self.assertEqual(len(indices), 100)
|
|
790
|
+
self.assertEqual(sorted(set(indices)), indices, "audited_indices strictly ascending and unique")
|
|
791
|
+
|
|
792
|
+
def test_small_n_no_summary_or_telemetry(self) -> None:
|
|
793
|
+
# 50 citations, cap=100 -> no summary OR summary with audited_count == total.
|
|
794
|
+
citations = [_citation(claim_id=f"C-{i:03d}", ref_slug=f"ref-{i:03d}") for i in range(1, 51)]
|
|
795
|
+
manifest = _manifest(
|
|
796
|
+
claims=[
|
|
797
|
+
{
|
|
798
|
+
"claim_id": f"C-{i:03d}",
|
|
799
|
+
"claim_text": f"Claim {i}.",
|
|
800
|
+
"intended_evidence_kind": "empirical",
|
|
801
|
+
"planned_refs": [],
|
|
802
|
+
}
|
|
803
|
+
for i in range(1, 51)
|
|
804
|
+
],
|
|
805
|
+
)
|
|
806
|
+
out = self.run_pipeline(citations=citations, manifests=[manifest], config=_config(max_claims_per_paper=100))
|
|
807
|
+
samplings = out["audit_sampling_summaries"]
|
|
808
|
+
# Two valid outcomes per spec §4 step 3: zero summaries OR exactly one
|
|
809
|
+
# telemetry-mode summary where audited_count == total_citation_count.
|
|
810
|
+
if samplings:
|
|
811
|
+
self.assertEqual(len(samplings), 1)
|
|
812
|
+
self.assertEqual(samplings[0]["audited_count"], 50)
|
|
813
|
+
self.assertEqual(samplings[0]["total_citation_count"], 50)
|
|
814
|
+
|
|
815
|
+
def test_cap_zero_rejected(self) -> None:
|
|
816
|
+
with self.assertRaises((ValueError, AssertionError)):
|
|
817
|
+
self.run_pipeline(
|
|
818
|
+
citations=[_citation()],
|
|
819
|
+
config=_config(max_claims_per_paper=0),
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
# ---------------------------------------------------------------------------
|
|
824
|
+
# T-P12 — Judge invocation failure mapping to INV-14 audit_tool_failure rows.
|
|
825
|
+
# Spec §4 step 2 + INV-14; Step 13 R1 codex P1 finding (judge errors must not
|
|
826
|
+
# abort the audit pass — they MUST surface as MED-WARN audit_tool_failure rows).
|
|
827
|
+
# ---------------------------------------------------------------------------
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
class TP12JudgeFailureAuditToolFailure(_PipelineTestBase):
|
|
831
|
+
"""T-P12: judge_fn exceptions / malformed output → audit_tool_failure row."""
|
|
832
|
+
|
|
833
|
+
def _run_one(self, judge_fn: Any) -> dict[str, Any]:
|
|
834
|
+
return self.run_pipeline(citations=[_citation()], judge_fn=judge_fn)
|
|
835
|
+
|
|
836
|
+
def _assert_audit_tool_failure(self, out: dict[str, Any], expected_tag: str) -> None:
|
|
837
|
+
results = out["claim_audit_results"]
|
|
838
|
+
self.assertEqual(len(results), 1, "exactly one row emitted on judge failure")
|
|
839
|
+
e = results[0]
|
|
840
|
+
self.assertEqual(e["judgment"], "RETRIEVAL_FAILED")
|
|
841
|
+
self.assertEqual(e["audit_status"], "inconclusive")
|
|
842
|
+
self.assertEqual(e["defect_stage"], "not_applicable")
|
|
843
|
+
self.assertEqual(e["ref_retrieval_method"], "audit_tool_failure")
|
|
844
|
+
self.assertTrue(
|
|
845
|
+
e["rationale"].startswith(expected_tag + ":"),
|
|
846
|
+
f"rationale must lead with INV-14 fault-class tag {expected_tag!r}; got {e['rationale']!r}",
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
def test_timeout_error_becomes_judge_timeout(self) -> None:
|
|
850
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
851
|
+
raise TimeoutError("judge call exceeded 30s")
|
|
852
|
+
|
|
853
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_timeout")
|
|
854
|
+
|
|
855
|
+
def test_value_error_becomes_judge_parse_error(self) -> None:
|
|
856
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
857
|
+
raise ValueError("response payload was not parseable")
|
|
858
|
+
|
|
859
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
860
|
+
|
|
861
|
+
def test_generic_exception_becomes_judge_api_error(self) -> None:
|
|
862
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
863
|
+
raise RuntimeError("upstream returned 503")
|
|
864
|
+
|
|
865
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_api_error")
|
|
866
|
+
|
|
867
|
+
def test_malformed_return_missing_judgment_key(self) -> None:
|
|
868
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
869
|
+
return {"rationale": "shaped wrong"}
|
|
870
|
+
|
|
871
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
872
|
+
|
|
873
|
+
def test_malformed_return_non_dict(self) -> None:
|
|
874
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
875
|
+
return "this is not a dict" # type: ignore[return-value]
|
|
876
|
+
|
|
877
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
878
|
+
|
|
879
|
+
def test_unknown_judgment_value_rejected(self) -> None:
|
|
880
|
+
# Step 13 R2 codex P2: _invoke_judge must validate the judgment enum,
|
|
881
|
+
# not only check key presence. An unknown value MUST map to
|
|
882
|
+
# judge_parse_error rather than reach passport-lint stage.
|
|
883
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
884
|
+
return {"judgment": "MAYBE_SUPPORTED", "rationale": "garbage"}
|
|
885
|
+
|
|
886
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
887
|
+
|
|
888
|
+
def test_violated_without_constraint_id_rejected(self) -> None:
|
|
889
|
+
# Step 13 R2 codex P2: VIOLATED without a violated_constraint_id would
|
|
890
|
+
# otherwise emit an INV-7-failing negative_constraint_violation row.
|
|
891
|
+
# Reject at invocation boundary instead.
|
|
892
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
893
|
+
return {"judgment": "VIOLATED", "rationale": "missing id"}
|
|
894
|
+
|
|
895
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
896
|
+
|
|
897
|
+
def test_violated_with_blank_constraint_id_rejected(self) -> None:
|
|
898
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
899
|
+
return {
|
|
900
|
+
"judgment": "VIOLATED",
|
|
901
|
+
"violated_constraint_id": " ",
|
|
902
|
+
"rationale": "whitespace id",
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
906
|
+
|
|
907
|
+
def test_cited_path_rejects_retrieval_failed_verdict(self) -> None:
|
|
908
|
+
# Step 13 R3 codex P2 #2: cited path must not accept RETRIEVAL_FAILED
|
|
909
|
+
# / NOT_VIOLATED — they would crash in _judge_result_entry.
|
|
910
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
911
|
+
return {"judgment": "RETRIEVAL_FAILED", "rationale": "wrong path"}
|
|
912
|
+
|
|
913
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
914
|
+
|
|
915
|
+
def test_cited_path_rejects_not_violated_verdict(self) -> None:
|
|
916
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
917
|
+
return {"judgment": "NOT_VIOLATED", "rationale": "wrong path"}
|
|
918
|
+
|
|
919
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
920
|
+
|
|
921
|
+
def test_violated_id_outside_active_set_rejected(self) -> None:
|
|
922
|
+
# Step 13 R3 codex P2 #1: VIOLATED with an id the author never declared
|
|
923
|
+
# would otherwise gate-refuse the formatter on a hallucinated rule.
|
|
924
|
+
# The default _citation() has no active constraints, so any nonblank
|
|
925
|
+
# id is outside the active set.
|
|
926
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
927
|
+
return {
|
|
928
|
+
"judgment": "VIOLATED",
|
|
929
|
+
"violated_constraint_id": "MNC-99",
|
|
930
|
+
"rationale": "hallucinated constraint",
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
934
|
+
|
|
935
|
+
# Step 13 R8 codex P2-3 — judgment isinstance(str) guard before set
|
|
936
|
+
# membership. Pre-fix: a malformed return like {"judgment": [1, 2], ...}
|
|
937
|
+
# raised TypeError("unhashable type: 'list'") inside the set membership
|
|
938
|
+
# test, aborting the audit. Post-fix: translation boundary catches it
|
|
939
|
+
# as judge_parse_error → audit_tool_failure (#120 P2-3).
|
|
940
|
+
|
|
941
|
+
def test_judgment_non_string_list_becomes_judge_parse_error(self) -> None:
|
|
942
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
943
|
+
return {"judgment": [1, 2], "rationale": "unhashable list"}
|
|
944
|
+
|
|
945
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
946
|
+
|
|
947
|
+
def test_judgment_non_string_dict_becomes_judge_parse_error(self) -> None:
|
|
948
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
949
|
+
return {"judgment": {"nested": "obj"}, "rationale": "unhashable dict"}
|
|
950
|
+
|
|
951
|
+
self._assert_audit_tool_failure(self._run_one(judge_fn), "judge_parse_error")
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
# ---------------------------------------------------------------------------
|
|
955
|
+
# T-P14 — retrieve_fn invocation failure mapping to INV-14 retrieval_* tags.
|
|
956
|
+
# Spec §4 step 2 + INV-14; Step 13 R2 codex P2 finding (symmetric to TP12 —
|
|
957
|
+
# transient retrieval errors must surface as audit_tool_failure rows).
|
|
958
|
+
# ---------------------------------------------------------------------------
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
class TP14RetrieveFailureAuditToolFailure(_PipelineTestBase):
|
|
962
|
+
"""T-P14: retrieve_fn exceptions / malformed output → audit_tool_failure row."""
|
|
963
|
+
|
|
964
|
+
def _run_one(self, retrieve_fn: Any) -> dict[str, Any]:
|
|
965
|
+
return self.run_pipeline(citations=[_citation()], retrieve_fn=retrieve_fn)
|
|
966
|
+
|
|
967
|
+
def _assert_audit_tool_failure(self, out: dict[str, Any], expected_tag: str) -> None:
|
|
968
|
+
results = out["claim_audit_results"]
|
|
969
|
+
self.assertEqual(len(results), 1)
|
|
970
|
+
e = results[0]
|
|
971
|
+
self.assertEqual(e["judgment"], "RETRIEVAL_FAILED")
|
|
972
|
+
self.assertEqual(e["audit_status"], "inconclusive")
|
|
973
|
+
self.assertEqual(e["defect_stage"], "not_applicable")
|
|
974
|
+
self.assertEqual(e["ref_retrieval_method"], "audit_tool_failure")
|
|
975
|
+
self.assertTrue(
|
|
976
|
+
e["rationale"].startswith(expected_tag + ":"),
|
|
977
|
+
f"rationale must lead with INV-14 fault-class tag {expected_tag!r}; got {e['rationale']!r}",
|
|
978
|
+
)
|
|
979
|
+
|
|
980
|
+
def test_timeout_error_becomes_retrieval_timeout(self) -> None:
|
|
981
|
+
def retrieve_fn(_c: dict[str, Any]) -> dict[str, Any]:
|
|
982
|
+
raise TimeoutError("retrieval exceeded 60s")
|
|
983
|
+
|
|
984
|
+
self._assert_audit_tool_failure(self._run_one(retrieve_fn), "retrieval_timeout")
|
|
985
|
+
|
|
986
|
+
def test_connection_error_becomes_retrieval_network_error(self) -> None:
|
|
987
|
+
def retrieve_fn(_c: dict[str, Any]) -> dict[str, Any]:
|
|
988
|
+
raise ConnectionError("DNS resolution failed")
|
|
989
|
+
|
|
990
|
+
self._assert_audit_tool_failure(self._run_one(retrieve_fn), "retrieval_network_error")
|
|
991
|
+
|
|
992
|
+
def test_generic_exception_becomes_retrieval_api_error(self) -> None:
|
|
993
|
+
def retrieve_fn(_c: dict[str, Any]) -> dict[str, Any]:
|
|
994
|
+
raise RuntimeError("upstream returned 503")
|
|
995
|
+
|
|
996
|
+
self._assert_audit_tool_failure(self._run_one(retrieve_fn), "retrieval_api_error")
|
|
997
|
+
|
|
998
|
+
def test_malformed_return_non_dict(self) -> None:
|
|
999
|
+
def retrieve_fn(_c: dict[str, Any]) -> dict[str, Any]:
|
|
1000
|
+
return ["not a dict"] # type: ignore[return-value]
|
|
1001
|
+
|
|
1002
|
+
self._assert_audit_tool_failure(self._run_one(retrieve_fn), "retrieval_api_error")
|
|
1003
|
+
|
|
1004
|
+
def test_malformed_return_missing_ref_retrieval_method(self) -> None:
|
|
1005
|
+
def retrieve_fn(_c: dict[str, Any]) -> dict[str, Any]:
|
|
1006
|
+
return {"retrieved_excerpt": "no method key"}
|
|
1007
|
+
|
|
1008
|
+
self._assert_audit_tool_failure(self._run_one(retrieve_fn), "retrieval_api_error")
|
|
1009
|
+
|
|
1010
|
+
def test_malformed_return_unknown_method(self) -> None:
|
|
1011
|
+
def retrieve_fn(_c: dict[str, Any]) -> dict[str, Any]:
|
|
1012
|
+
return {"ref_retrieval_method": "magic_protocol"}
|
|
1013
|
+
|
|
1014
|
+
self._assert_audit_tool_failure(self._run_one(retrieve_fn), "retrieval_api_error")
|
|
1015
|
+
|
|
1016
|
+
def test_malformed_return_non_string_method(self) -> None:
|
|
1017
|
+
# Step 13 R8 codex P2-4: ref_retrieval_method as a list raises
|
|
1018
|
+
# TypeError on set membership outside the translation boundary;
|
|
1019
|
+
# must surface as retrieval_api_error → audit_tool_failure.
|
|
1020
|
+
def retrieve_fn(_c: dict[str, Any]) -> dict[str, Any]:
|
|
1021
|
+
return {"ref_retrieval_method": ["api", "manual_pdf"], "retrieved_excerpt": "n/a"}
|
|
1022
|
+
|
|
1023
|
+
self._assert_audit_tool_failure(self._run_one(retrieve_fn), "retrieval_api_error")
|
|
1024
|
+
|
|
1025
|
+
def test_api_method_without_excerpt_rejected(self) -> None:
|
|
1026
|
+
# Step 13 R3 codex P2 #3 — api with empty excerpt would let the judge
|
|
1027
|
+
# mark SUPPORTED with no source text.
|
|
1028
|
+
def retrieve_fn(_c: dict[str, Any]) -> dict[str, Any]:
|
|
1029
|
+
return {"ref_retrieval_method": "api", "retrieved_excerpt": ""}
|
|
1030
|
+
|
|
1031
|
+
self._assert_audit_tool_failure(self._run_one(retrieve_fn), "retrieval_api_error")
|
|
1032
|
+
|
|
1033
|
+
def test_manual_pdf_with_none_excerpt_rejected(self) -> None:
|
|
1034
|
+
def retrieve_fn(_c: dict[str, Any]) -> dict[str, Any]:
|
|
1035
|
+
return {"ref_retrieval_method": "manual_pdf", "retrieved_excerpt": None}
|
|
1036
|
+
|
|
1037
|
+
self._assert_audit_tool_failure(self._run_one(retrieve_fn), "retrieval_api_error")
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
# ---------------------------------------------------------------------------
|
|
1041
|
+
# T-P13 — EMITTED_NOT_INTENDED set-dedup per D6 (Step 13 R1 codex P2).
|
|
1042
|
+
# When the same drifted claim_text carries multiple citation markers (e.g.
|
|
1043
|
+
# one sentence with two ref slugs), one drift row should emit, not one per
|
|
1044
|
+
# citation.
|
|
1045
|
+
# ---------------------------------------------------------------------------
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
# ---------------------------------------------------------------------------
|
|
1049
|
+
# T-P16 — Uncited stream split (Step 13 R4 codex P1 #2).
|
|
1050
|
+
# Constraint judging runs over the full uncited set; uncited_assertion[]
|
|
1051
|
+
# LOW-WARN advisory runs over the D4-c filtered subset.
|
|
1052
|
+
# ---------------------------------------------------------------------------
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
class TP21UncitedClaimLevelNC(_PipelineTestBase):
|
|
1056
|
+
"""T-P21: Step 13 R7 codex P1 — uncited stream (d) must include claim-level
|
|
1057
|
+
NC-C constraints when the sentence binds a manifest_claim_id.
|
|
1058
|
+
|
|
1059
|
+
Pre-fix: only manifest_negative_constraints (MNC-) was passed to the
|
|
1060
|
+
judge; NC-C... for the bound claim was silently dropped.
|
|
1061
|
+
"""
|
|
1062
|
+
|
|
1063
|
+
def test_nc_c_included_when_sentence_binds_claim_id(self) -> None:
|
|
1064
|
+
manifest = _manifest(
|
|
1065
|
+
claims=[
|
|
1066
|
+
{
|
|
1067
|
+
"claim_id": "C-001",
|
|
1068
|
+
"claim_text": "Bound claim.",
|
|
1069
|
+
"intended_evidence_kind": "empirical",
|
|
1070
|
+
"planned_refs": [],
|
|
1071
|
+
"negative_constraints": [
|
|
1072
|
+
{
|
|
1073
|
+
"constraint_id": "NC-C001-1",
|
|
1074
|
+
"rule": "MUST NOT generalize beyond cohort.",
|
|
1075
|
+
}
|
|
1076
|
+
],
|
|
1077
|
+
}
|
|
1078
|
+
],
|
|
1079
|
+
mncs=[],
|
|
1080
|
+
)
|
|
1081
|
+
sentence = {
|
|
1082
|
+
"sentence_text": "All practitioners benefit.",
|
|
1083
|
+
"section_path": "Discussion",
|
|
1084
|
+
"manifest_claim_id": "C-001",
|
|
1085
|
+
"scoped_manifest_id": MANIFEST_ID,
|
|
1086
|
+
}
|
|
1087
|
+
seen_ids: list[set[str]] = []
|
|
1088
|
+
|
|
1089
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
1090
|
+
active = kwargs.get("active_constraints") or []
|
|
1091
|
+
seen_ids.append({c["constraint_id"] for c in active})
|
|
1092
|
+
return {
|
|
1093
|
+
"judgment": "VIOLATED",
|
|
1094
|
+
"violated_constraint_id": "NC-C001-1",
|
|
1095
|
+
"rationale": "Generalizes beyond cohort.",
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
out = self.run_pipeline(
|
|
1099
|
+
citations=[],
|
|
1100
|
+
manifests=[manifest],
|
|
1101
|
+
uncited_sentences=[],
|
|
1102
|
+
all_uncited_sentences=[sentence],
|
|
1103
|
+
judge_fn=judge_fn,
|
|
1104
|
+
)
|
|
1105
|
+
self.assertEqual(seen_ids, [{"NC-C001-1"}])
|
|
1106
|
+
cv = out["constraint_violations"]
|
|
1107
|
+
self.assertEqual(len(cv), 1)
|
|
1108
|
+
self.assertEqual(cv[0]["violated_constraint_id"], "NC-C001-1")
|
|
1109
|
+
self.assertEqual(cv[0]["scoped_manifest_id"], MANIFEST_ID)
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
class TP22DuplicateMNCIdAcrossManifests(_PipelineTestBase):
|
|
1113
|
+
"""T-P22: Step 13 R7 codex P2 — when two manifests use the same MNC id
|
|
1114
|
+
string (e.g. both have MNC-1), an uncited violation MUST be attributed
|
|
1115
|
+
to the correct manifest. Per-manifest judge calls + scope binding by
|
|
1116
|
+
construction (not by first-match-wins lookup).
|
|
1117
|
+
"""
|
|
1118
|
+
|
|
1119
|
+
def test_violation_attributed_to_correct_manifest(self) -> None:
|
|
1120
|
+
manifest_a = _manifest(
|
|
1121
|
+
manifest_id="M-2026-05-16T09:00:00Z-a111",
|
|
1122
|
+
claims=[],
|
|
1123
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "MUST NOT use A-words"}],
|
|
1124
|
+
)
|
|
1125
|
+
manifest_b = _manifest(
|
|
1126
|
+
manifest_id="M-2026-05-16T09:00:00Z-b222",
|
|
1127
|
+
claims=[],
|
|
1128
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "MUST NOT use B-words"}],
|
|
1129
|
+
)
|
|
1130
|
+
sentence = {
|
|
1131
|
+
"sentence_text": "Uses B-words.",
|
|
1132
|
+
"section_path": "Discussion",
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
# judge_fn distinguishes by rule text: only violates manifest B's rule.
|
|
1136
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
1137
|
+
active = kwargs.get("active_constraints") or []
|
|
1138
|
+
for c in active:
|
|
1139
|
+
if "B-words" in c.get("rule", ""):
|
|
1140
|
+
return {
|
|
1141
|
+
"judgment": "VIOLATED",
|
|
1142
|
+
"violated_constraint_id": "MNC-1",
|
|
1143
|
+
"rationale": "Uses B-words.",
|
|
1144
|
+
}
|
|
1145
|
+
return {"judgment": "NOT_VIOLATED", "rationale": "n/a"}
|
|
1146
|
+
|
|
1147
|
+
out = self.run_pipeline(
|
|
1148
|
+
citations=[],
|
|
1149
|
+
manifests=[manifest_a, manifest_b],
|
|
1150
|
+
uncited_sentences=[],
|
|
1151
|
+
all_uncited_sentences=[sentence],
|
|
1152
|
+
judge_fn=judge_fn,
|
|
1153
|
+
)
|
|
1154
|
+
cv = out["constraint_violations"]
|
|
1155
|
+
self.assertEqual(len(cv), 1, f"exactly one CV row; got {cv!r}")
|
|
1156
|
+
# MUST be attributed to manifest B, NOT manifest A (alphabetic first).
|
|
1157
|
+
self.assertEqual(
|
|
1158
|
+
cv[0]["scoped_manifest_id"],
|
|
1159
|
+
"M-2026-05-16T09:00:00Z-b222",
|
|
1160
|
+
f"violation MUST attribute to the manifest whose rule the judge actually violated; got {cv[0]['scoped_manifest_id']!r}",
|
|
1161
|
+
)
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
class TP20UncitedSentenceWithoutScope(_PipelineTestBase):
|
|
1165
|
+
"""T-P20: Step 13 R6 codex P1 — sentences in the documented all_uncited_sentences
|
|
1166
|
+
shape (sentence_text + section_path + optional adjacent_text only, NO
|
|
1167
|
+
scoped_manifest_id) MUST still trigger the constraint judge.
|
|
1168
|
+
|
|
1169
|
+
Pre-fix: the loop required sentence.get("scoped_manifest_id") to resolve
|
|
1170
|
+
constraints, so orchestrator callers following the contract never saw the
|
|
1171
|
+
HIGH-WARN-CONSTRAINT-VIOLATION-UNCITED gate fire. The runtime now applies
|
|
1172
|
+
every manifest-level MNC when no caller scope is provided; constraint
|
|
1173
|
+
violation rows derive their scoped_manifest_id from the violated_constraint_id
|
|
1174
|
+
↔ source-manifest mapping.
|
|
1175
|
+
"""
|
|
1176
|
+
|
|
1177
|
+
def test_uncited_sentence_no_scope_triggers_mnc_judge(self) -> None:
|
|
1178
|
+
manifest = _manifest(
|
|
1179
|
+
claims=[],
|
|
1180
|
+
mncs=[
|
|
1181
|
+
{
|
|
1182
|
+
"constraint_id": "MNC-1",
|
|
1183
|
+
"rule": "MUST NOT use causal language",
|
|
1184
|
+
}
|
|
1185
|
+
],
|
|
1186
|
+
)
|
|
1187
|
+
sentence = {
|
|
1188
|
+
"sentence_text": "The program caused outcome improvement.",
|
|
1189
|
+
"section_path": "Discussion",
|
|
1190
|
+
# No scoped_manifest_id — documented Stage 4 sentence shape.
|
|
1191
|
+
}
|
|
1192
|
+
out = self.run_pipeline(
|
|
1193
|
+
citations=[],
|
|
1194
|
+
manifests=[manifest],
|
|
1195
|
+
uncited_sentences=[], # not D4-c-flagged
|
|
1196
|
+
all_uncited_sentences=[sentence],
|
|
1197
|
+
judge_fn=lambda **kw: {
|
|
1198
|
+
"judgment": "VIOLATED",
|
|
1199
|
+
"violated_constraint_id": "MNC-1",
|
|
1200
|
+
"rationale": "Causal language.",
|
|
1201
|
+
},
|
|
1202
|
+
)
|
|
1203
|
+
cv = out["constraint_violations"]
|
|
1204
|
+
self.assertEqual(
|
|
1205
|
+
len(cv),
|
|
1206
|
+
1,
|
|
1207
|
+
f"R6 P1: uncited sentence without scope must reach MNC judge; got {cv!r}",
|
|
1208
|
+
)
|
|
1209
|
+
# Schema requires concrete scoped_manifest_id matching the M-pattern.
|
|
1210
|
+
self.assertEqual(cv[0]["scoped_manifest_id"], MANIFEST_ID)
|
|
1211
|
+
self.assertEqual(cv[0]["violated_constraint_id"], "MNC-1")
|
|
1212
|
+
|
|
1213
|
+
def test_caller_provided_scope_restricts_mnc_set(self) -> None:
|
|
1214
|
+
# When the caller pins scoped_manifest_id, only that manifest's MNCs
|
|
1215
|
+
# apply (cross-manifest leakage prevented).
|
|
1216
|
+
manifest_a = _manifest(
|
|
1217
|
+
manifest_id="M-aaaa-A",
|
|
1218
|
+
claims=[],
|
|
1219
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "MUST NOT use A-words"}],
|
|
1220
|
+
)
|
|
1221
|
+
manifest_b = _manifest(
|
|
1222
|
+
manifest_id="M-bbbb-B",
|
|
1223
|
+
claims=[],
|
|
1224
|
+
mncs=[{"constraint_id": "MNC-2", "rule": "MUST NOT use B-words"}],
|
|
1225
|
+
)
|
|
1226
|
+
# Sentence pinned to manifest A — judge should only see MNC-1.
|
|
1227
|
+
sentence = {
|
|
1228
|
+
"sentence_text": "Uses A-words.",
|
|
1229
|
+
"section_path": "Discussion",
|
|
1230
|
+
"scoped_manifest_id": "M-aaaa-A",
|
|
1231
|
+
}
|
|
1232
|
+
seen_constraint_ids: list[set[str]] = []
|
|
1233
|
+
|
|
1234
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
1235
|
+
active = kwargs.get("active_constraints") or []
|
|
1236
|
+
seen_constraint_ids.append({c["constraint_id"] for c in active})
|
|
1237
|
+
return {"judgment": "NOT_VIOLATED", "rationale": "n/a"}
|
|
1238
|
+
|
|
1239
|
+
self.run_pipeline(
|
|
1240
|
+
citations=[],
|
|
1241
|
+
manifests=[manifest_a, manifest_b],
|
|
1242
|
+
uncited_sentences=[],
|
|
1243
|
+
all_uncited_sentences=[sentence],
|
|
1244
|
+
judge_fn=judge_fn,
|
|
1245
|
+
)
|
|
1246
|
+
self.assertEqual(
|
|
1247
|
+
seen_constraint_ids,
|
|
1248
|
+
[{"MNC-1"}],
|
|
1249
|
+
"caller-provided scope must restrict judge to that manifest's MNCs",
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
class TP19ConstraintAbsorptionFullManifestScope(_PipelineTestBase):
|
|
1254
|
+
"""T-P19: Step 13 R5 codex P3 — when a citation in manifest M violates a
|
|
1255
|
+
negative constraint, ALL of M's drift findings are absorbed, including
|
|
1256
|
+
same-manifest citations whose claim_id is NOT in M's declared claim set.
|
|
1257
|
+
|
|
1258
|
+
Pre-fix: pair-level absorption (M, cid_in_manifest) plus the violating
|
|
1259
|
+
citation's own pair. A different same-manifest emitted citation with
|
|
1260
|
+
claim_id=C-999 (drifted) still produced EMITTED_NOT_INTENDED — that
|
|
1261
|
+
contradicted the "absorbed in full" precedence rule.
|
|
1262
|
+
"""
|
|
1263
|
+
|
|
1264
|
+
def test_drifted_claim_id_in_same_manifest_as_violation_absorbed(self) -> None:
|
|
1265
|
+
manifest = _manifest(
|
|
1266
|
+
claims=[
|
|
1267
|
+
{
|
|
1268
|
+
"claim_id": "C-001",
|
|
1269
|
+
"claim_text": "Manifest claim about X.",
|
|
1270
|
+
"intended_evidence_kind": "empirical",
|
|
1271
|
+
"planned_refs": [],
|
|
1272
|
+
}
|
|
1273
|
+
],
|
|
1274
|
+
mncs=[
|
|
1275
|
+
{
|
|
1276
|
+
"constraint_id": "MNC-1",
|
|
1277
|
+
"rule": "MUST NOT use causal language",
|
|
1278
|
+
}
|
|
1279
|
+
],
|
|
1280
|
+
)
|
|
1281
|
+
# Citation 1 violates the MNC on a declared claim_id (C-001).
|
|
1282
|
+
violating = _citation(
|
|
1283
|
+
claim_id="C-001",
|
|
1284
|
+
claim_text="Manifest claim about X.",
|
|
1285
|
+
ref_slug="ref-violator",
|
|
1286
|
+
)
|
|
1287
|
+
# Citation 2 is in the SAME manifest but has a drifted (non-manifest)
|
|
1288
|
+
# claim_id. Pre-fix: produced an extra EMITTED_NOT_INTENDED row.
|
|
1289
|
+
drifted_sibling = _citation(
|
|
1290
|
+
claim_id="C-999",
|
|
1291
|
+
claim_text="Sibling claim with drifted id.",
|
|
1292
|
+
ref_slug="ref-sibling",
|
|
1293
|
+
)
|
|
1294
|
+
|
|
1295
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
1296
|
+
ct = kwargs.get("claim_text", "")
|
|
1297
|
+
if "X" in ct:
|
|
1298
|
+
return {
|
|
1299
|
+
"judgment": "VIOLATED",
|
|
1300
|
+
"violated_constraint_id": "MNC-1",
|
|
1301
|
+
"rationale": "Causal language violates MNC-1.",
|
|
1302
|
+
}
|
|
1303
|
+
return {"judgment": "SUPPORTED", "rationale": "Cited page supports."}
|
|
1304
|
+
|
|
1305
|
+
out = self.run_pipeline(
|
|
1306
|
+
citations=[violating, drifted_sibling],
|
|
1307
|
+
manifests=[manifest],
|
|
1308
|
+
judge_fn=judge_fn,
|
|
1309
|
+
)
|
|
1310
|
+
drifts = [d for d in out["claim_drifts"] if d["drift_kind"] == "EMITTED_NOT_INTENDED"]
|
|
1311
|
+
self.assertEqual(
|
|
1312
|
+
drifts,
|
|
1313
|
+
[],
|
|
1314
|
+
f"Same-manifest drifted claim_id MUST be absorbed alongside the constraint violation; got {len(drifts)} drift row(s): {drifts!r}",
|
|
1315
|
+
)
|
|
1316
|
+
# The constraint violation itself still emits.
|
|
1317
|
+
cv = [
|
|
1318
|
+
r for r in out["claim_audit_results"]
|
|
1319
|
+
if r.get("defect_stage") == "negative_constraint_violation"
|
|
1320
|
+
]
|
|
1321
|
+
self.assertEqual(len(cv), 1, "constraint violation row MUST emit")
|
|
1322
|
+
|
|
1323
|
+
def test_drift_in_other_manifest_not_absorbed_by_violation_in_first(self) -> None:
|
|
1324
|
+
# Cross-manifest absorption is forbidden — manifest A violation MUST
|
|
1325
|
+
# NOT silence drift in manifest B.
|
|
1326
|
+
manifest_a = _manifest(
|
|
1327
|
+
manifest_id="M-aaaa-A",
|
|
1328
|
+
claims=[],
|
|
1329
|
+
mncs=[
|
|
1330
|
+
{"constraint_id": "MNC-1", "rule": "MUST NOT use causal language"}
|
|
1331
|
+
],
|
|
1332
|
+
)
|
|
1333
|
+
manifest_b = _manifest(
|
|
1334
|
+
manifest_id="M-bbbb-B",
|
|
1335
|
+
claims=[
|
|
1336
|
+
{
|
|
1337
|
+
"claim_id": "C-100",
|
|
1338
|
+
"claim_text": "Manifest B intended claim.",
|
|
1339
|
+
"intended_evidence_kind": "empirical",
|
|
1340
|
+
"planned_refs": [],
|
|
1341
|
+
}
|
|
1342
|
+
],
|
|
1343
|
+
)
|
|
1344
|
+
violating_a = _citation(
|
|
1345
|
+
claim_id="C-001",
|
|
1346
|
+
scoped_manifest_id="M-aaaa-A",
|
|
1347
|
+
claim_text="Causal claim in A.",
|
|
1348
|
+
)
|
|
1349
|
+
|
|
1350
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
1351
|
+
ct = kwargs.get("claim_text", "")
|
|
1352
|
+
if "A" in ct:
|
|
1353
|
+
return {
|
|
1354
|
+
"judgment": "VIOLATED",
|
|
1355
|
+
"violated_constraint_id": "MNC-1",
|
|
1356
|
+
"rationale": "Causal.",
|
|
1357
|
+
}
|
|
1358
|
+
return {"judgment": "SUPPORTED", "rationale": "ok"}
|
|
1359
|
+
|
|
1360
|
+
out = self.run_pipeline(
|
|
1361
|
+
citations=[violating_a],
|
|
1362
|
+
manifests=[manifest_a, manifest_b],
|
|
1363
|
+
judge_fn=judge_fn,
|
|
1364
|
+
)
|
|
1365
|
+
# Manifest B's C-100 is NOT emitted → INTENDED_NOT_EMITTED MUST still fire.
|
|
1366
|
+
intended_drift_b = [
|
|
1367
|
+
d for d in out["claim_drifts"]
|
|
1368
|
+
if d["drift_kind"] == "INTENDED_NOT_EMITTED" and d.get("scoped_manifest_id") == "M-bbbb-B"
|
|
1369
|
+
]
|
|
1370
|
+
self.assertEqual(
|
|
1371
|
+
len(intended_drift_b),
|
|
1372
|
+
1,
|
|
1373
|
+
"manifest B drift MUST NOT be absorbed by violation in manifest A",
|
|
1374
|
+
)
|
|
1375
|
+
|
|
1376
|
+
|
|
1377
|
+
class TP18ManifestMissingNoSpuriousDrift(_PipelineTestBase):
|
|
1378
|
+
"""T-P18: MANIFEST-MISSING run (manifests=[]) produces no claim_drifts[]
|
|
1379
|
+
rows. There's no pre-commitment baseline to diff against; emitting
|
|
1380
|
+
EMITTED_NOT_INTENDED for every citation would be spurious noise on top
|
|
1381
|
+
of the MANIFEST-MISSING advisory the formatter already surfaces.
|
|
1382
|
+
Step 13 R5 codex P2 #2.
|
|
1383
|
+
"""
|
|
1384
|
+
|
|
1385
|
+
def test_no_manifest_no_drift_rows(self) -> None:
|
|
1386
|
+
from scripts._claim_audit_constants import SENTINEL_MANIFEST_ID
|
|
1387
|
+
|
|
1388
|
+
citation = {
|
|
1389
|
+
"claim_id": "C-001",
|
|
1390
|
+
"scoped_manifest_id": SENTINEL_MANIFEST_ID,
|
|
1391
|
+
"claim_text": "Some supported claim.",
|
|
1392
|
+
"ref_slug": "ref-1",
|
|
1393
|
+
"anchor_kind": "page",
|
|
1394
|
+
"anchor_value": "10",
|
|
1395
|
+
"section_path": "Discussion",
|
|
1396
|
+
}
|
|
1397
|
+
out = self.run_pipeline(
|
|
1398
|
+
citations=[citation],
|
|
1399
|
+
manifests=[],
|
|
1400
|
+
judge_fn=_judge_supported(),
|
|
1401
|
+
)
|
|
1402
|
+
self.assertEqual(
|
|
1403
|
+
out["claim_drifts"],
|
|
1404
|
+
[],
|
|
1405
|
+
f"MANIFEST-MISSING run must not emit drift rows; got {out['claim_drifts']!r}",
|
|
1406
|
+
)
|
|
1407
|
+
# Audit row still emits — the fallback is audit-only, not no-op.
|
|
1408
|
+
self.assertEqual(len(out["claim_audit_results"]), 1)
|
|
1409
|
+
|
|
1410
|
+
def test_manifest_present_but_empty_claims_no_drift(self) -> None:
|
|
1411
|
+
# A manifest with zero claims is equivalent to no baseline; drift
|
|
1412
|
+
# detection should still short-circuit.
|
|
1413
|
+
empty_manifest = _manifest(claims=[])
|
|
1414
|
+
citation = _citation(claim_text="Drifted claim text.")
|
|
1415
|
+
out = self.run_pipeline(
|
|
1416
|
+
citations=[citation],
|
|
1417
|
+
manifests=[empty_manifest],
|
|
1418
|
+
judge_fn=_judge_supported(),
|
|
1419
|
+
)
|
|
1420
|
+
self.assertEqual(out["claim_drifts"], [])
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
class TP17ManifestMissingSentinelFallback(_PipelineTestBase):
|
|
1424
|
+
"""T-P17: MANIFEST-MISSING fallback path must not KeyError when caller
|
|
1425
|
+
omits scoped_manifest_id from the citation dict.
|
|
1426
|
+
|
|
1427
|
+
Step 13 R4 codex P2 #3 — row builders previously did
|
|
1428
|
+
`citation["scoped_manifest_id"]` directly; the caller's _written_scope_for
|
|
1429
|
+
helper would default to SENTINEL_MANIFEST_ID but only AFTER row
|
|
1430
|
+
construction, so the index lookup crashed before the sentinel could be
|
|
1431
|
+
applied. Row builders now use .get(SENTINEL_MANIFEST_ID) so the fallback
|
|
1432
|
+
path works end-to-end.
|
|
1433
|
+
"""
|
|
1434
|
+
|
|
1435
|
+
def test_citation_without_scoped_manifest_id_emits_sentinel_row(self) -> None:
|
|
1436
|
+
citation = {
|
|
1437
|
+
"claim_id": "C-001",
|
|
1438
|
+
"claim_text": "Manifest-missing test claim.",
|
|
1439
|
+
"ref_slug": "ref-1",
|
|
1440
|
+
"anchor_kind": "page",
|
|
1441
|
+
"anchor_value": "10",
|
|
1442
|
+
# scoped_manifest_id deliberately omitted (MANIFEST-MISSING caller).
|
|
1443
|
+
}
|
|
1444
|
+
out = self.run_pipeline(
|
|
1445
|
+
citations=[citation],
|
|
1446
|
+
manifests=[],
|
|
1447
|
+
judge_fn=_judge_supported(),
|
|
1448
|
+
)
|
|
1449
|
+
results = out["claim_audit_results"]
|
|
1450
|
+
self.assertEqual(len(results), 1, "fallback row must emit, not KeyError")
|
|
1451
|
+
# The caller's _written_scope_for would default to SENTINEL_MANIFEST_ID
|
|
1452
|
+
# because no manifest binds this claim_id.
|
|
1453
|
+
from scripts._claim_audit_constants import SENTINEL_MANIFEST_ID
|
|
1454
|
+
|
|
1455
|
+
self.assertEqual(results[0]["scoped_manifest_id"], SENTINEL_MANIFEST_ID)
|
|
1456
|
+
|
|
1457
|
+
|
|
1458
|
+
class TP16UncitedStreamSplit(_PipelineTestBase):
|
|
1459
|
+
"""T-P16: constraint stream (d) sees full uncited set; LOW-WARN sees D4-c only."""
|
|
1460
|
+
|
|
1461
|
+
def _build_manifest_with_mnc(self) -> dict[str, Any]:
|
|
1462
|
+
return _manifest(
|
|
1463
|
+
claims=[],
|
|
1464
|
+
mncs=[
|
|
1465
|
+
{
|
|
1466
|
+
"constraint_id": "MNC-1",
|
|
1467
|
+
"rule": "MUST NOT use causal language",
|
|
1468
|
+
}
|
|
1469
|
+
],
|
|
1470
|
+
)
|
|
1471
|
+
|
|
1472
|
+
def test_constraint_violation_outside_d4c_trigger_still_emitted(self) -> None:
|
|
1473
|
+
# Sentence violates MNC ("caused improvement") but lacks D4-c trigger
|
|
1474
|
+
# tokens (no quantifier, no "%", no "p<"). Pre-R4 the constraint stream
|
|
1475
|
+
# would never see it.
|
|
1476
|
+
manifest = self._build_manifest_with_mnc()
|
|
1477
|
+
full_uncited = [
|
|
1478
|
+
{
|
|
1479
|
+
"sentence_text": "The program caused improvement.",
|
|
1480
|
+
"section_path": "Discussion",
|
|
1481
|
+
"scoped_manifest_id": MANIFEST_ID,
|
|
1482
|
+
}
|
|
1483
|
+
]
|
|
1484
|
+
d4c_uncited: list[dict[str, Any]] = [] # detector filtered this out
|
|
1485
|
+
|
|
1486
|
+
out = self.run_pipeline(
|
|
1487
|
+
citations=[],
|
|
1488
|
+
manifests=[manifest],
|
|
1489
|
+
uncited_sentences=d4c_uncited,
|
|
1490
|
+
all_uncited_sentences=full_uncited,
|
|
1491
|
+
judge_fn=lambda **kw: {
|
|
1492
|
+
"judgment": "VIOLATED",
|
|
1493
|
+
"violated_constraint_id": "MNC-1",
|
|
1494
|
+
"rationale": "Uses causal language 'caused'.",
|
|
1495
|
+
},
|
|
1496
|
+
)
|
|
1497
|
+
cv = out["constraint_violations"]
|
|
1498
|
+
self.assertEqual(len(cv), 1, "constraint judging must run over full uncited set")
|
|
1499
|
+
self.assertEqual(cv[0]["violated_constraint_id"], "MNC-1")
|
|
1500
|
+
# The LOW-WARN uncited_assertion is NOT emitted because the sentence
|
|
1501
|
+
# was outside D4-c trigger filter.
|
|
1502
|
+
self.assertEqual(out["uncited_assertions"], [])
|
|
1503
|
+
|
|
1504
|
+
def test_d4c_positive_emits_both_streams(self) -> None:
|
|
1505
|
+
# Sentence is BOTH D4-c-positive AND violates MNC → both rows emit
|
|
1506
|
+
# (CV-INV-4 explicitly permits the dual presence).
|
|
1507
|
+
manifest = self._build_manifest_with_mnc()
|
|
1508
|
+
d4c_sentence = {
|
|
1509
|
+
"sentence_text": "The program caused 95% improvement.",
|
|
1510
|
+
"section_path": "Discussion",
|
|
1511
|
+
"scoped_manifest_id": MANIFEST_ID,
|
|
1512
|
+
"trigger_tokens": ["95%"],
|
|
1513
|
+
}
|
|
1514
|
+
out = self.run_pipeline(
|
|
1515
|
+
citations=[],
|
|
1516
|
+
manifests=[manifest],
|
|
1517
|
+
uncited_sentences=[d4c_sentence],
|
|
1518
|
+
all_uncited_sentences=[d4c_sentence],
|
|
1519
|
+
judge_fn=lambda **kw: {
|
|
1520
|
+
"judgment": "VIOLATED",
|
|
1521
|
+
"violated_constraint_id": "MNC-1",
|
|
1522
|
+
"rationale": "Causal claim.",
|
|
1523
|
+
},
|
|
1524
|
+
)
|
|
1525
|
+
self.assertEqual(len(out["constraint_violations"]), 1)
|
|
1526
|
+
self.assertEqual(len(out["uncited_assertions"]), 1)
|
|
1527
|
+
|
|
1528
|
+
def test_backwards_compat_when_all_uncited_omitted(self) -> None:
|
|
1529
|
+
# Legacy caller passes only uncited_sentences (the D4-c subset). The
|
|
1530
|
+
# pipeline falls back to using that subset for stream (d) too — the
|
|
1531
|
+
# constraint check is narrower than the R4 expansion, but the API
|
|
1532
|
+
# surface still works.
|
|
1533
|
+
manifest = self._build_manifest_with_mnc()
|
|
1534
|
+
d4c_sentence = {
|
|
1535
|
+
"sentence_text": "The program caused improvement (p<0.01).",
|
|
1536
|
+
"section_path": "Discussion",
|
|
1537
|
+
"scoped_manifest_id": MANIFEST_ID,
|
|
1538
|
+
"trigger_tokens": ["p<0.01"],
|
|
1539
|
+
}
|
|
1540
|
+
out = self.run_pipeline(
|
|
1541
|
+
citations=[],
|
|
1542
|
+
manifests=[manifest],
|
|
1543
|
+
uncited_sentences=[d4c_sentence],
|
|
1544
|
+
# all_uncited_sentences intentionally omitted — defaults to
|
|
1545
|
+
# uncited_sentences for backwards compat.
|
|
1546
|
+
judge_fn=lambda **kw: {
|
|
1547
|
+
"judgment": "VIOLATED",
|
|
1548
|
+
"violated_constraint_id": "MNC-1",
|
|
1549
|
+
"rationale": "Causal claim.",
|
|
1550
|
+
},
|
|
1551
|
+
)
|
|
1552
|
+
self.assertEqual(len(out["constraint_violations"]), 1)
|
|
1553
|
+
self.assertEqual(len(out["uncited_assertions"]), 1)
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
# ---------------------------------------------------------------------------
|
|
1557
|
+
# T-P15 — Malformed cache hit → cache_corruption audit_tool_failure row.
|
|
1558
|
+
# Step 13 R3 codex P2 #4: persistent or injected cache entries can carry
|
|
1559
|
+
# malformed values; revalidate every hit before routing.
|
|
1560
|
+
# ---------------------------------------------------------------------------
|
|
1561
|
+
|
|
1562
|
+
|
|
1563
|
+
class TP15CacheCorruption(_PipelineTestBase):
|
|
1564
|
+
"""T-P15: cache hit re-validated through _validate_judge_dict."""
|
|
1565
|
+
|
|
1566
|
+
def test_missing_judgment_key_in_cache(self) -> None:
|
|
1567
|
+
# Pre-seed cache with a malformed entry that lacks `judgment`.
|
|
1568
|
+
cache = {}
|
|
1569
|
+
# First populate using a valid run so we know the key shape works.
|
|
1570
|
+
invoked: list[Any] = []
|
|
1571
|
+
|
|
1572
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
1573
|
+
invoked.append(kwargs)
|
|
1574
|
+
return {"judgment": "SUPPORTED", "rationale": "fresh"}
|
|
1575
|
+
|
|
1576
|
+
# Run once to capture the cache_key the pipeline computes.
|
|
1577
|
+
self.run_pipeline(citations=[_citation()], judge_fn=judge_fn, cache=cache)
|
|
1578
|
+
self.assertEqual(len(cache), 1)
|
|
1579
|
+
cache_key = next(iter(cache))
|
|
1580
|
+
|
|
1581
|
+
# Corrupt the cached value to simulate a stale / partial dump.
|
|
1582
|
+
cache[cache_key] = {"rationale": "but no judgment key"}
|
|
1583
|
+
|
|
1584
|
+
out = self.run_pipeline(
|
|
1585
|
+
citations=[_citation()],
|
|
1586
|
+
judge_fn=lambda **_kw: {"judgment": "SUPPORTED", "rationale": "should not be called"},
|
|
1587
|
+
cache=cache,
|
|
1588
|
+
)
|
|
1589
|
+
results = out["claim_audit_results"]
|
|
1590
|
+
self.assertEqual(len(results), 1)
|
|
1591
|
+
e = results[0]
|
|
1592
|
+
self.assertEqual(e["ref_retrieval_method"], "audit_tool_failure")
|
|
1593
|
+
self.assertTrue(
|
|
1594
|
+
e["rationale"].startswith("cache_corruption:"),
|
|
1595
|
+
f"cache hit failure must use INV-14 cache_corruption tag; got {e['rationale']!r}",
|
|
1596
|
+
)
|
|
1597
|
+
|
|
1598
|
+
def test_unknown_judgment_in_cache(self) -> None:
|
|
1599
|
+
cache = {}
|
|
1600
|
+
|
|
1601
|
+
def judge_fn(**_kw: Any) -> dict[str, Any]:
|
|
1602
|
+
return {"judgment": "SUPPORTED", "rationale": "fresh"}
|
|
1603
|
+
|
|
1604
|
+
self.run_pipeline(citations=[_citation()], judge_fn=judge_fn, cache=cache)
|
|
1605
|
+
cache_key = next(iter(cache))
|
|
1606
|
+
cache[cache_key] = {"judgment": "GIBBERISH", "rationale": "stale"}
|
|
1607
|
+
|
|
1608
|
+
out = self.run_pipeline(
|
|
1609
|
+
citations=[_citation()],
|
|
1610
|
+
judge_fn=lambda **_kw: {"judgment": "SUPPORTED", "rationale": "n/a"},
|
|
1611
|
+
cache=cache,
|
|
1612
|
+
)
|
|
1613
|
+
results = out["claim_audit_results"]
|
|
1614
|
+
self.assertTrue(results[0]["rationale"].startswith("cache_corruption:"))
|
|
1615
|
+
|
|
1616
|
+
|
|
1617
|
+
class TP13EmittedNotIntendedDedupe(_PipelineTestBase):
|
|
1618
|
+
"""T-P13: D6 set semantics — one drift row per drifted claim_text."""
|
|
1619
|
+
|
|
1620
|
+
def test_intended_not_emitted_text_match_under_renumbered_claim_id(self) -> None:
|
|
1621
|
+
# Step 13 R2 codex P2: when the draft carries the manifest claim_text
|
|
1622
|
+
# but under a different claim_id (claim_id was renumbered), the
|
|
1623
|
+
# INTENDED_NOT_EMITTED side MUST use the same set-of-text semantics
|
|
1624
|
+
# as the EMITTED_NOT_INTENDED side. Otherwise a benign renumbering
|
|
1625
|
+
# produces false LOW-WARN drift findings.
|
|
1626
|
+
manifest = _manifest(
|
|
1627
|
+
claims=[
|
|
1628
|
+
{
|
|
1629
|
+
"claim_id": "C-001",
|
|
1630
|
+
"claim_text": "Renumbered manifest claim about Y.",
|
|
1631
|
+
"intended_evidence_kind": "empirical",
|
|
1632
|
+
"planned_refs": [],
|
|
1633
|
+
}
|
|
1634
|
+
]
|
|
1635
|
+
)
|
|
1636
|
+
# Drafter emits the same claim_text but assigns claim_id C-999 (e.g.
|
|
1637
|
+
# the manifest was authored, then claim_ids reshuffled before prose).
|
|
1638
|
+
citations = [
|
|
1639
|
+
_citation(
|
|
1640
|
+
claim_id="C-999",
|
|
1641
|
+
claim_text="Renumbered manifest claim about Y.",
|
|
1642
|
+
ref_slug="ref-a",
|
|
1643
|
+
)
|
|
1644
|
+
]
|
|
1645
|
+
out = self.run_pipeline(citations=citations, manifests=[manifest])
|
|
1646
|
+
intended_not_emitted = [
|
|
1647
|
+
d for d in out["claim_drifts"] if d["drift_kind"] == "INTENDED_NOT_EMITTED"
|
|
1648
|
+
]
|
|
1649
|
+
self.assertEqual(
|
|
1650
|
+
intended_not_emitted,
|
|
1651
|
+
[],
|
|
1652
|
+
"claim_text-match must short-circuit INTENDED_NOT_EMITTED per D6 set semantics",
|
|
1653
|
+
)
|
|
1654
|
+
|
|
1655
|
+
def test_two_refs_one_drift(self) -> None:
|
|
1656
|
+
# Manifest pre-commits to C-001 only; the drafter emits a different
|
|
1657
|
+
# claim_text twice, once per citation marker (typical for a sentence
|
|
1658
|
+
# like "X is correlated with Y (Ref1, Ref2)" where the drafter chose
|
|
1659
|
+
# not to add this claim to the manifest).
|
|
1660
|
+
manifest = _manifest(
|
|
1661
|
+
claims=[
|
|
1662
|
+
{
|
|
1663
|
+
"claim_id": "C-001",
|
|
1664
|
+
"claim_text": "Manifest-intended claim about X.",
|
|
1665
|
+
"intended_evidence_kind": "empirical",
|
|
1666
|
+
"planned_refs": [],
|
|
1667
|
+
}
|
|
1668
|
+
]
|
|
1669
|
+
)
|
|
1670
|
+
drifted_text = "This claim was never in the manifest."
|
|
1671
|
+
citations = [
|
|
1672
|
+
_citation(
|
|
1673
|
+
claim_id="C-001",
|
|
1674
|
+
claim_text=drifted_text,
|
|
1675
|
+
ref_slug="ref-a",
|
|
1676
|
+
anchor_value="10",
|
|
1677
|
+
),
|
|
1678
|
+
_citation(
|
|
1679
|
+
claim_id="C-001",
|
|
1680
|
+
claim_text=drifted_text,
|
|
1681
|
+
ref_slug="ref-b",
|
|
1682
|
+
anchor_value="20",
|
|
1683
|
+
),
|
|
1684
|
+
]
|
|
1685
|
+
out = self.run_pipeline(citations=citations, manifests=[manifest])
|
|
1686
|
+
drifts = [d for d in out["claim_drifts"] if d["drift_kind"] == "EMITTED_NOT_INTENDED"]
|
|
1687
|
+
self.assertEqual(
|
|
1688
|
+
len(drifts),
|
|
1689
|
+
1,
|
|
1690
|
+
f"D6 Emitted is a set of claim_text — one drifted text + two refs MUST yield 1 drift row; got {len(drifts)}",
|
|
1691
|
+
)
|
|
1692
|
+
self.assertEqual(drifts[0]["claim_text"], drifted_text)
|
|
1693
|
+
|
|
1694
|
+
|
|
1695
|
+
# ---------------------------------------------------------------------------
|
|
1696
|
+
# T-P23 — v3.8.2 / #118: uncited path judge outage emits UAF row, not synthetic
|
|
1697
|
+
# NOT_VIOLATED. Validates the fix for the issue where JudgeInvocationError on
|
|
1698
|
+
# the uncited constraint-judging path was silently substituted as
|
|
1699
|
+
# {"judgment": "NOT_VIOLATED", ...}, suppressing HIGH-WARN constraint checks.
|
|
1700
|
+
# ---------------------------------------------------------------------------
|
|
1701
|
+
|
|
1702
|
+
|
|
1703
|
+
class TP23UncitedJudgeOutageEmitsUAF(_PipelineTestBase):
|
|
1704
|
+
"""T-P23 (v3.8.2 / #118): JudgeInvocationError on uncited path → UAF row."""
|
|
1705
|
+
|
|
1706
|
+
def _manifest_with_mnc(self) -> dict[str, Any]:
|
|
1707
|
+
return _manifest(
|
|
1708
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "No causal language without RCT."}],
|
|
1709
|
+
)
|
|
1710
|
+
|
|
1711
|
+
def test_uncited_judge_timeout_emits_uaf(self) -> None:
|
|
1712
|
+
# judge_fn raises a raw TimeoutError; _invoke_judge maps it to
|
|
1713
|
+
# JudgeInvocationError("judge_timeout", ...) per the exception
|
|
1714
|
+
# translation layer at scripts/claim_audit_pipeline.py:_invoke_judge.
|
|
1715
|
+
def failing_judge(**_kw: Any) -> dict[str, Any]:
|
|
1716
|
+
raise TimeoutError("judge timed out after 30s")
|
|
1717
|
+
|
|
1718
|
+
uncited_sentences = [
|
|
1719
|
+
{
|
|
1720
|
+
"sentence_text": "We observed causality between A and B.",
|
|
1721
|
+
"section_path": "4. Discussion > 4.3 Limitations",
|
|
1722
|
+
"manifest_claim_id": None,
|
|
1723
|
+
"trigger_tokens": ["observed"],
|
|
1724
|
+
}
|
|
1725
|
+
]
|
|
1726
|
+
out = self.run_pipeline(
|
|
1727
|
+
citations=[],
|
|
1728
|
+
manifests=[self._manifest_with_mnc()],
|
|
1729
|
+
uncited_sentences=uncited_sentences,
|
|
1730
|
+
judge_fn=failing_judge,
|
|
1731
|
+
)
|
|
1732
|
+
uaf = out["uncited_audit_failures"]
|
|
1733
|
+
self.assertEqual(
|
|
1734
|
+
len(uaf),
|
|
1735
|
+
1,
|
|
1736
|
+
f"judge_timeout on uncited path MUST emit 1 UAF row; got {uaf}",
|
|
1737
|
+
)
|
|
1738
|
+
e = uaf[0]
|
|
1739
|
+
self.assertEqual(e["fault_class"], "judge_timeout")
|
|
1740
|
+
self.assertTrue(
|
|
1741
|
+
e["rationale"].startswith("judge_timeout:"),
|
|
1742
|
+
f"UAF rationale MUST begin with fault_class prefix; got {e['rationale']!r}",
|
|
1743
|
+
)
|
|
1744
|
+
# No fake NOT_VIOLATED leaked into constraint_violations[].
|
|
1745
|
+
self.assertEqual(
|
|
1746
|
+
out["constraint_violations"],
|
|
1747
|
+
[],
|
|
1748
|
+
"judge_timeout MUST NOT emit a constraint_violations[] row — that would be silent suppression of the HIGH-WARN check (pre-v3.8.2 bug)",
|
|
1749
|
+
)
|
|
1750
|
+
# No synthetic NOT_VIOLATED leaked into any aggregate either.
|
|
1751
|
+
for agg_name in ("claim_audit_results", "constraint_violations"):
|
|
1752
|
+
for entry in out.get(agg_name, []):
|
|
1753
|
+
rationale = entry.get("rationale", "")
|
|
1754
|
+
self.assertNotIn(
|
|
1755
|
+
"judge_fn failure on uncited path",
|
|
1756
|
+
rationale,
|
|
1757
|
+
f"pre-v3.8.2 synthetic NOT_VIOLATED rationale MUST NOT appear in {agg_name}",
|
|
1758
|
+
)
|
|
1759
|
+
|
|
1760
|
+
def test_uncited_judge_outage_no_audit_abort(self) -> None:
|
|
1761
|
+
# Coverage preservation: an outage on one sentence MUST NOT abort the
|
|
1762
|
+
# whole audit. With 3 sentences + judge that fails on the second, we
|
|
1763
|
+
# expect: rows for sentence 1, UAF for sentence 2, rows for sentence
|
|
1764
|
+
# 3 unaffected. ConnectionError → judge_api_error per _invoke_judge's
|
|
1765
|
+
# generic Exception translation branch.
|
|
1766
|
+
call_count = [0]
|
|
1767
|
+
|
|
1768
|
+
def selectively_failing_judge(**kw: Any) -> dict[str, Any]:
|
|
1769
|
+
call_count[0] += 1
|
|
1770
|
+
if call_count[0] == 2:
|
|
1771
|
+
raise ConnectionError("transient 5xx on call #2")
|
|
1772
|
+
return {"judgment": "NOT_VIOLATED", "rationale": "fine"}
|
|
1773
|
+
|
|
1774
|
+
uncited_sentences = [
|
|
1775
|
+
{
|
|
1776
|
+
"sentence_text": f"Uncited sentence number {i}.",
|
|
1777
|
+
"section_path": f"3. Results > 3.{i}",
|
|
1778
|
+
"manifest_claim_id": None,
|
|
1779
|
+
"trigger_tokens": ["showed"],
|
|
1780
|
+
}
|
|
1781
|
+
for i in range(1, 4)
|
|
1782
|
+
]
|
|
1783
|
+
out = self.run_pipeline(
|
|
1784
|
+
citations=[],
|
|
1785
|
+
manifests=[self._manifest_with_mnc()],
|
|
1786
|
+
uncited_sentences=uncited_sentences,
|
|
1787
|
+
judge_fn=selectively_failing_judge,
|
|
1788
|
+
)
|
|
1789
|
+
# Exactly 1 UAF row from the call #2 outage; the other two sentences
|
|
1790
|
+
# judged fine (NOT_VIOLATED → no CV row, no UAF row).
|
|
1791
|
+
self.assertEqual(len(out["uncited_audit_failures"]), 1)
|
|
1792
|
+
self.assertEqual(out["uncited_audit_failures"][0]["fault_class"], "judge_api_error")
|
|
1793
|
+
# Audit pass did NOT abort — 3 judge invocations attempted.
|
|
1794
|
+
self.assertEqual(call_count[0], 3)
|
|
1795
|
+
|
|
1796
|
+
def test_uncited_judge_outage_nc_path_carries_manifest_claim_id(self) -> None:
|
|
1797
|
+
# When the sentence is bound to a manifest claim (NC-C path), the UAF
|
|
1798
|
+
# row MUST carry the manifest_claim_id so the failure can be traced
|
|
1799
|
+
# back to which (manifest, claim) constraint set was being judged.
|
|
1800
|
+
# ValueError → judge_parse_error per _invoke_judge translation.
|
|
1801
|
+
def failing_judge(**_kw: Any) -> dict[str, Any]:
|
|
1802
|
+
raise ValueError("malformed judge output: not JSON")
|
|
1803
|
+
|
|
1804
|
+
manifest = _manifest(
|
|
1805
|
+
claims=[
|
|
1806
|
+
{
|
|
1807
|
+
"claim_id": "C-001",
|
|
1808
|
+
"claim_text": "Sample preprints accounted for 67% of corpus.",
|
|
1809
|
+
"intended_evidence_kind": "empirical",
|
|
1810
|
+
"planned_refs": [],
|
|
1811
|
+
"negative_constraints": [
|
|
1812
|
+
{"constraint_id": "NC-C001-1", "rule": "No causal language."}
|
|
1813
|
+
],
|
|
1814
|
+
}
|
|
1815
|
+
],
|
|
1816
|
+
)
|
|
1817
|
+
uncited_sentences = [
|
|
1818
|
+
{
|
|
1819
|
+
"sentence_text": "Sample preprints accounted for 67% of corpus.",
|
|
1820
|
+
"section_path": "3. Results > 3.1 Overview",
|
|
1821
|
+
"manifest_claim_id": "C-001",
|
|
1822
|
+
"scoped_manifest_id": MANIFEST_ID,
|
|
1823
|
+
"trigger_tokens": ["67%"],
|
|
1824
|
+
}
|
|
1825
|
+
]
|
|
1826
|
+
out = self.run_pipeline(
|
|
1827
|
+
citations=[],
|
|
1828
|
+
manifests=[manifest],
|
|
1829
|
+
uncited_sentences=uncited_sentences,
|
|
1830
|
+
judge_fn=failing_judge,
|
|
1831
|
+
)
|
|
1832
|
+
uaf = out["uncited_audit_failures"]
|
|
1833
|
+
self.assertEqual(len(uaf), 1)
|
|
1834
|
+
self.assertEqual(uaf[0]["manifest_claim_id"], "C-001")
|
|
1835
|
+
self.assertEqual(uaf[0]["fault_class"], "judge_parse_error")
|
|
1836
|
+
|
|
1837
|
+
def test_uaf_multi_manifest_claim_id_polarity(self) -> None:
|
|
1838
|
+
# Codex cross-model review P2-2 (2026-05-17): when sentence carries
|
|
1839
|
+
# manifest_claim_id but is judged against MULTIPLE manifests (no
|
|
1840
|
+
# scoped_manifest_id pin), the UAF row's manifest_claim_id must
|
|
1841
|
+
# ONLY be set when the current (mid) actually owns the claim
|
|
1842
|
+
# binding. Without this guard, a UAF row would inherit a claim_id
|
|
1843
|
+
# that doesn't exist in this manifest's claims[], failing UAF-INV-3.
|
|
1844
|
+
def failing_judge(**_kw: Any) -> dict[str, Any]:
|
|
1845
|
+
raise TimeoutError("judge timed out")
|
|
1846
|
+
|
|
1847
|
+
# Two manifests in the passport. Sentence binds to C-001, which
|
|
1848
|
+
# exists ONLY in manifest_a; manifest_b contributes MNCs only.
|
|
1849
|
+
# When the sentence is judged against both manifests and BOTH
|
|
1850
|
+
# judge calls fail, the UAF row for manifest_a should set
|
|
1851
|
+
# manifest_claim_id="C-001"; the UAF row for manifest_b MUST set
|
|
1852
|
+
# manifest_claim_id=None (no claim binding in that manifest).
|
|
1853
|
+
manifest_a = _manifest(
|
|
1854
|
+
manifest_id=MANIFEST_ID,
|
|
1855
|
+
claims=[
|
|
1856
|
+
{
|
|
1857
|
+
"claim_id": "C-001",
|
|
1858
|
+
"claim_text": "Causal claim.",
|
|
1859
|
+
"intended_evidence_kind": "empirical",
|
|
1860
|
+
"planned_refs": [],
|
|
1861
|
+
"negative_constraints": [
|
|
1862
|
+
{"constraint_id": "NC-C001-1", "rule": "No causal."}
|
|
1863
|
+
],
|
|
1864
|
+
}
|
|
1865
|
+
],
|
|
1866
|
+
)
|
|
1867
|
+
manifest_b = _manifest(
|
|
1868
|
+
manifest_id=MANIFEST_ID_OTHER,
|
|
1869
|
+
claims=[
|
|
1870
|
+
{
|
|
1871
|
+
"claim_id": "C-002", # different claim id; C-001 is NOT here
|
|
1872
|
+
"claim_text": "Unrelated claim in manifest_b.",
|
|
1873
|
+
"intended_evidence_kind": "empirical",
|
|
1874
|
+
"planned_refs": [],
|
|
1875
|
+
}
|
|
1876
|
+
],
|
|
1877
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "Global rule."}],
|
|
1878
|
+
)
|
|
1879
|
+
uncited_sentences = [
|
|
1880
|
+
{
|
|
1881
|
+
"sentence_text": "We observed causality between A and B.",
|
|
1882
|
+
"section_path": "4. Discussion > 4.3",
|
|
1883
|
+
"manifest_claim_id": "C-001",
|
|
1884
|
+
# scoped_manifest_id absent → judge against ALL manifests
|
|
1885
|
+
"trigger_tokens": ["observed"],
|
|
1886
|
+
}
|
|
1887
|
+
]
|
|
1888
|
+
out = self.run_pipeline(
|
|
1889
|
+
citations=[],
|
|
1890
|
+
manifests=[manifest_a, manifest_b],
|
|
1891
|
+
uncited_sentences=uncited_sentences,
|
|
1892
|
+
judge_fn=failing_judge,
|
|
1893
|
+
)
|
|
1894
|
+
uaf = out["uncited_audit_failures"]
|
|
1895
|
+
self.assertEqual(len(uaf), 2, f"expected 2 UAF rows (one per manifest); got {uaf}")
|
|
1896
|
+
by_mid = {row["scoped_manifest_id"]: row for row in uaf}
|
|
1897
|
+
self.assertEqual(by_mid[MANIFEST_ID]["manifest_claim_id"], "C-001")
|
|
1898
|
+
self.assertIsNone(
|
|
1899
|
+
by_mid[MANIFEST_ID_OTHER]["manifest_claim_id"],
|
|
1900
|
+
"manifest_b does not own C-001; UAF row MUST set manifest_claim_id=None to avoid UAF-INV-3 fail",
|
|
1901
|
+
)
|
|
1902
|
+
|
|
1903
|
+
def test_uaf_mnc_only_claim_stays_null_manifest_claim_id(self) -> None:
|
|
1904
|
+
# Codex R2 P2-1 (2026-05-17): when sentence binds to a claim that
|
|
1905
|
+
# exists in the manifest but the claim has NO negative_constraints,
|
|
1906
|
+
# the judge call is MNC-only and the UAF row's manifest_claim_id
|
|
1907
|
+
# MUST stay null. Pre-R2 fix would set manifest_claim_id to the
|
|
1908
|
+
# sentence's claim_id any time the claim resolved, conflating
|
|
1909
|
+
# MNC-only outages with NC-C outages for downstream consumers.
|
|
1910
|
+
def failing_judge(**_kw: Any) -> dict[str, Any]:
|
|
1911
|
+
raise TimeoutError("judge timed out")
|
|
1912
|
+
|
|
1913
|
+
manifest = _manifest(
|
|
1914
|
+
claims=[
|
|
1915
|
+
{
|
|
1916
|
+
"claim_id": "C-001",
|
|
1917
|
+
"claim_text": "Claim with no negative_constraints.",
|
|
1918
|
+
"intended_evidence_kind": "empirical",
|
|
1919
|
+
"planned_refs": [],
|
|
1920
|
+
# IMPORTANT: empty negative_constraints — judge call is MNC-only
|
|
1921
|
+
"negative_constraints": [],
|
|
1922
|
+
}
|
|
1923
|
+
],
|
|
1924
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "Global rule."}],
|
|
1925
|
+
)
|
|
1926
|
+
uncited_sentences = [
|
|
1927
|
+
{
|
|
1928
|
+
"sentence_text": "Sentence bound to C-001 but tested vs MNC-1 only.",
|
|
1929
|
+
"section_path": "3.1",
|
|
1930
|
+
"manifest_claim_id": "C-001",
|
|
1931
|
+
"scoped_manifest_id": MANIFEST_ID,
|
|
1932
|
+
"trigger_tokens": ["showed"],
|
|
1933
|
+
}
|
|
1934
|
+
]
|
|
1935
|
+
out = self.run_pipeline(
|
|
1936
|
+
citations=[],
|
|
1937
|
+
manifests=[manifest],
|
|
1938
|
+
uncited_sentences=uncited_sentences,
|
|
1939
|
+
judge_fn=failing_judge,
|
|
1940
|
+
)
|
|
1941
|
+
uaf = out["uncited_audit_failures"]
|
|
1942
|
+
self.assertEqual(len(uaf), 1)
|
|
1943
|
+
self.assertIsNone(
|
|
1944
|
+
uaf[0]["manifest_claim_id"],
|
|
1945
|
+
"claim has no NC entries → judge call was MNC-only → manifest_claim_id must be null per spec §3.6",
|
|
1946
|
+
)
|
|
1947
|
+
|
|
1948
|
+
|
|
1949
|
+
class TP24PartialDecomposition(_PipelineTestBase):
|
|
1950
|
+
"""#213: end-to-end PARTIAL handling through the REAL runtime (_judge_result_entry).
|
|
1951
|
+
|
|
1952
|
+
This is the layer all prior #213 tests skipped — schema/lint tests built rows
|
|
1953
|
+
by hand, calibration used a stub judge. These tests drive run_audit_pipeline
|
|
1954
|
+
so the prompt-verdict PARTIAL actually flows: judge -> _validate_judge_dict ->
|
|
1955
|
+
_judge_result_entry -> emitted claim_audit_result row, then cross-checked
|
|
1956
|
+
against both the schema and the INV-19 lint.
|
|
1957
|
+
"""
|
|
1958
|
+
|
|
1959
|
+
|
|
1960
|
+
def test_partial_normalizes_to_unsupported_source_description(self) -> None:
|
|
1961
|
+
out = self.run_pipeline(citations=[_citation()], judge_fn=_judge_partial())
|
|
1962
|
+
results = out["claim_audit_results"]
|
|
1963
|
+
self.assertEqual(len(results), 1)
|
|
1964
|
+
e = results[0]
|
|
1965
|
+
self.assertEqual(e["judgment"], "UNSUPPORTED", "PARTIAL must normalize to UNSUPPORTED (B1)")
|
|
1966
|
+
self.assertEqual(e["audit_status"], "completed")
|
|
1967
|
+
self.assertEqual(e["defect_stage"], "source_description")
|
|
1968
|
+
|
|
1969
|
+
def test_partial_copies_breakdown_onto_row(self) -> None:
|
|
1970
|
+
out = self.run_pipeline(citations=[_citation()], judge_fn=_judge_partial())
|
|
1971
|
+
e = out["claim_audit_results"][0]
|
|
1972
|
+
self.assertIn("sub_claim_breakdown", e, "breakdown is the machine-readable partial signal")
|
|
1973
|
+
bd = e["sub_claim_breakdown"]
|
|
1974
|
+
self.assertEqual(len(bd), 2)
|
|
1975
|
+
self.assertEqual(bd[0]["sub_verdict"], "SUPPORTED")
|
|
1976
|
+
self.assertEqual(bd[1]["sub_verdict"], "UNSUPPORTED")
|
|
1977
|
+
|
|
1978
|
+
def test_partial_row_passes_schema_and_inv19(self) -> None:
|
|
1979
|
+
# The emitted row must satisfy BOTH the schema and the INV-19 lint —
|
|
1980
|
+
# this is the end-to-end binding the prior layer-isolated tests missed.
|
|
1981
|
+
out = self.run_pipeline(citations=[_citation()], judge_fn=_judge_partial())
|
|
1982
|
+
findings = self._validate_passport(out)
|
|
1983
|
+
self.assertEqual(
|
|
1984
|
+
findings, [], f"emitted PARTIAL row must be lint-clean (incl. INV-19); got {findings!r}"
|
|
1985
|
+
)
|
|
1986
|
+
|
|
1987
|
+
def test_supported_row_has_no_breakdown(self) -> None:
|
|
1988
|
+
# Non-PARTIAL rows must NOT carry sub_claim_breakdown (presence is the signal).
|
|
1989
|
+
out = self.run_pipeline(citations=[_citation()], judge_fn=_judge_supported())
|
|
1990
|
+
self.assertNotIn("sub_claim_breakdown", out["claim_audit_results"][0])
|
|
1991
|
+
|
|
1992
|
+
def test_malformed_partial_routes_to_judge_parse_error_not_bare_unsupported(self) -> None:
|
|
1993
|
+
# A malformed PARTIAL (here: all-SUPPORTED, not true-partial) must NOT
|
|
1994
|
+
# silently become a bare UNSUPPORTED. It routes to the judge_parse_error
|
|
1995
|
+
# inconclusive triple (the only contract-valid path) — ship-gate review finding.
|
|
1996
|
+
bad = [
|
|
1997
|
+
{"sub_claim_text": "a", "sub_verdict": "SUPPORTED"},
|
|
1998
|
+
{"sub_claim_text": "b", "sub_verdict": "SUPPORTED"},
|
|
1999
|
+
]
|
|
2000
|
+
out = self.run_pipeline(
|
|
2001
|
+
citations=[_citation()], judge_fn=_judge_partial_malformed(breakdown=bad)
|
|
2002
|
+
)
|
|
2003
|
+
e = out["claim_audit_results"][0]
|
|
2004
|
+
self.assertEqual(e["judgment"], "RETRIEVAL_FAILED", "malformed PARTIAL must NOT become bare UNSUPPORTED")
|
|
2005
|
+
self.assertEqual(e["audit_status"], "inconclusive")
|
|
2006
|
+
self.assertEqual(e["defect_stage"], "not_applicable")
|
|
2007
|
+
self.assertEqual(e["ref_retrieval_method"], "audit_tool_failure")
|
|
2008
|
+
self.assertTrue(
|
|
2009
|
+
e["rationale"].startswith("judge_parse_error"),
|
|
2010
|
+
f"rationale must lead with judge_parse_error tag; got {e['rationale']!r}",
|
|
2011
|
+
)
|
|
2012
|
+
self.assertNotIn("sub_claim_breakdown", e, "no breakdown on a malformed-PARTIAL inconclusive row")
|
|
2013
|
+
|
|
2014
|
+
def test_malformed_partial_item_missing_sub_claim_text_routes_inconclusive(self) -> None:
|
|
2015
|
+
# Ship-gate round-2: an item that passes the verdict-MIX gate but lacks a
|
|
2016
|
+
# sub_claim_text would, if copied onto a completed row, emit
|
|
2017
|
+
# sub_claim_text=None (schema-invalid). It MUST take the judge_parse_error
|
|
2018
|
+
# path instead. This is the item-shape half of is_emittable_partial_breakdown.
|
|
2019
|
+
bad = [
|
|
2020
|
+
{"sub_claim_text": "first", "sub_verdict": "SUPPORTED"},
|
|
2021
|
+
{"sub_verdict": "UNSUPPORTED"}, # missing sub_claim_text
|
|
2022
|
+
]
|
|
2023
|
+
out = self.run_pipeline(
|
|
2024
|
+
citations=[_citation()], judge_fn=_judge_partial_malformed(breakdown=bad)
|
|
2025
|
+
)
|
|
2026
|
+
e = out["claim_audit_results"][0]
|
|
2027
|
+
self.assertEqual(e["judgment"], "RETRIEVAL_FAILED")
|
|
2028
|
+
self.assertEqual(e["audit_status"], "inconclusive")
|
|
2029
|
+
self.assertTrue(e["rationale"].startswith("judge_parse_error"))
|
|
2030
|
+
self.assertEqual(self._validate_passport(out), [], "fallback row must be lint-clean")
|
|
2031
|
+
|
|
2032
|
+
def test_malformed_partial_item_empty_sub_claim_text_routes_inconclusive(self) -> None:
|
|
2033
|
+
bad = [
|
|
2034
|
+
{"sub_claim_text": "first", "sub_verdict": "SUPPORTED"},
|
|
2035
|
+
{"sub_claim_text": " ", "sub_verdict": "UNSUPPORTED"}, # blank
|
|
2036
|
+
]
|
|
2037
|
+
out = self.run_pipeline(
|
|
2038
|
+
citations=[_citation()], judge_fn=_judge_partial_malformed(breakdown=bad)
|
|
2039
|
+
)
|
|
2040
|
+
self.assertEqual(out["claim_audit_results"][0]["judgment"], "RETRIEVAL_FAILED")
|
|
2041
|
+
|
|
2042
|
+
def test_malformed_partial_item_wrong_evidence_pointer_type_routes_inconclusive(self) -> None:
|
|
2043
|
+
# Ship-gate round-3: the runtime COPIES evidence_pointer onto the row, so a
|
|
2044
|
+
# wrong-typed one (a number) would emit a schema-invalid completed row. It
|
|
2045
|
+
# MUST route to judge_parse_error instead (the evidence_pointer-type half of
|
|
2046
|
+
# is_emittable_partial_breakdown).
|
|
2047
|
+
bad = [
|
|
2048
|
+
{"sub_claim_text": "a", "sub_verdict": "SUPPORTED", "evidence_pointer": 123},
|
|
2049
|
+
{"sub_claim_text": "b", "sub_verdict": "UNSUPPORTED"},
|
|
2050
|
+
]
|
|
2051
|
+
out = self.run_pipeline(
|
|
2052
|
+
citations=[_citation()], judge_fn=_judge_partial_malformed(breakdown=bad)
|
|
2053
|
+
)
|
|
2054
|
+
e = out["claim_audit_results"][0]
|
|
2055
|
+
self.assertEqual(e["judgment"], "RETRIEVAL_FAILED")
|
|
2056
|
+
self.assertEqual(e["audit_status"], "inconclusive")
|
|
2057
|
+
self.assertTrue(e["rationale"].startswith("judge_parse_error"))
|
|
2058
|
+
self.assertEqual(self._validate_passport(out), [], "fallback row must be lint-clean")
|
|
2059
|
+
|
|
2060
|
+
def test_partial_with_null_evidence_pointer_emits_valid_row(self) -> None:
|
|
2061
|
+
# A genuine PARTIAL with str + null evidence_pointers is emittable + lint-clean.
|
|
2062
|
+
good = [
|
|
2063
|
+
{"sub_claim_text": "a", "sub_verdict": "SUPPORTED", "evidence_pointer": "p.4"},
|
|
2064
|
+
{"sub_claim_text": "b", "sub_verdict": "UNSUPPORTED", "evidence_pointer": None},
|
|
2065
|
+
]
|
|
2066
|
+
out = self.run_pipeline(citations=[_citation()], judge_fn=_judge_partial(breakdown=good))
|
|
2067
|
+
e = out["claim_audit_results"][0]
|
|
2068
|
+
self.assertEqual(e["judgment"], "UNSUPPORTED")
|
|
2069
|
+
self.assertEqual(e["sub_claim_breakdown"][1]["evidence_pointer"], None)
|
|
2070
|
+
self.assertEqual(self._validate_passport(out), [])
|
|
2071
|
+
|
|
2072
|
+
def test_malformed_partial_single_item_also_routes_inconclusive(self) -> None:
|
|
2073
|
+
out = self.run_pipeline(
|
|
2074
|
+
citations=[_citation()],
|
|
2075
|
+
judge_fn=_judge_partial_malformed(breakdown=[{"sub_claim_text": "a", "sub_verdict": "SUPPORTED"}]),
|
|
2076
|
+
)
|
|
2077
|
+
e = out["claim_audit_results"][0]
|
|
2078
|
+
self.assertEqual(e["judgment"], "RETRIEVAL_FAILED")
|
|
2079
|
+
self.assertEqual(e["audit_status"], "inconclusive")
|
|
2080
|
+
|
|
2081
|
+
def test_malformed_partial_row_passes_lint(self) -> None:
|
|
2082
|
+
# The fallback inconclusive row must itself be lint-clean.
|
|
2083
|
+
out = self.run_pipeline(
|
|
2084
|
+
citations=[_citation()],
|
|
2085
|
+
judge_fn=_judge_partial_malformed(
|
|
2086
|
+
breakdown=[{"sub_claim_text": "a", "sub_verdict": "SUPPORTED"}]
|
|
2087
|
+
),
|
|
2088
|
+
)
|
|
2089
|
+
self.assertEqual(self._validate_passport(out), [])
|
|
2090
|
+
|
|
2091
|
+
def test_malformed_partial_with_oversized_text_emits_schema_valid_row(self) -> None:
|
|
2092
|
+
# #355 P2#3: the malformed-PARTIAL fallback embeds the offending
|
|
2093
|
+
# breakdown's repr in the rationale. A >1000-char sub_claim_text is
|
|
2094
|
+
# itself a malformed trigger (is_emittable rejects len>1000), so its repr
|
|
2095
|
+
# alone overflows the rationale maxLength=2000 and the "clean inconclusive"
|
|
2096
|
+
# fallback row becomes schema-INVALID. The row MUST satisfy the schema.
|
|
2097
|
+
# 1700-char text overflows the rationale (measured: 2017 chars > 2000).
|
|
2098
|
+
# A judge is an LLM with no pre-emission length guarantee, so an
|
|
2099
|
+
# over-long claim or an over-decomposed breakdown is a real malformed
|
|
2100
|
+
# input — not a synthetic edge.
|
|
2101
|
+
bad = [
|
|
2102
|
+
{"sub_claim_text": "x" * 1700, "sub_verdict": "SUPPORTED"},
|
|
2103
|
+
{"sub_claim_text": "second", "sub_verdict": "UNSUPPORTED"},
|
|
2104
|
+
]
|
|
2105
|
+
out = self.run_pipeline(
|
|
2106
|
+
citations=[_citation()], judge_fn=_judge_partial_malformed(breakdown=bad)
|
|
2107
|
+
)
|
|
2108
|
+
e = out["claim_audit_results"][0]
|
|
2109
|
+
self.assertEqual(e["judgment"], "RETRIEVAL_FAILED")
|
|
2110
|
+
self.assertEqual(e["audit_status"], "inconclusive")
|
|
2111
|
+
self.assertTrue(e["rationale"].startswith("judge_parse_error"))
|
|
2112
|
+
self.assertLessEqual(
|
|
2113
|
+
len(e["rationale"]), 2000,
|
|
2114
|
+
f"fallback rationale must fit schema maxLength=2000; got {len(e['rationale'])}",
|
|
2115
|
+
)
|
|
2116
|
+
errors = sorted(_CAR_VALIDATOR.iter_errors(e), key=str)
|
|
2117
|
+
self.assertEqual(
|
|
2118
|
+
errors, [], f"malformed-PARTIAL fallback row must satisfy claim_audit_result schema; got {errors}"
|
|
2119
|
+
)
|
|
2120
|
+
self.assertEqual(self._validate_passport(out), [], "fallback row must also be lint-clean")
|
|
2121
|
+
|
|
2122
|
+
def test_malformed_partial_short_breakdown_fallback_is_schema_valid(self) -> None:
|
|
2123
|
+
# Regression guard: the existing short-breakdown malformed paths
|
|
2124
|
+
# (single-item, all-SUPPORTED) must STILL emit schema-valid rows after
|
|
2125
|
+
# the #355 P2#3 truncation fix — i.e. the bound must not drop the
|
|
2126
|
+
# fault-class tag or mangle short messages that never needed truncating.
|
|
2127
|
+
for bad in (
|
|
2128
|
+
[{"sub_claim_text": "a", "sub_verdict": "SUPPORTED"}], # single-item
|
|
2129
|
+
[
|
|
2130
|
+
{"sub_claim_text": "a", "sub_verdict": "SUPPORTED"},
|
|
2131
|
+
{"sub_claim_text": "b", "sub_verdict": "SUPPORTED"},
|
|
2132
|
+
], # all-supported, not true-partial
|
|
2133
|
+
):
|
|
2134
|
+
with self.subTest(bad=bad):
|
|
2135
|
+
out = self.run_pipeline(
|
|
2136
|
+
citations=[_citation()], judge_fn=_judge_partial_malformed(breakdown=bad)
|
|
2137
|
+
)
|
|
2138
|
+
e = out["claim_audit_results"][0]
|
|
2139
|
+
self.assertTrue(e["rationale"].startswith("judge_parse_error"))
|
|
2140
|
+
self.assertEqual(sorted(_CAR_VALIDATOR.iter_errors(e), key=str), [])
|
|
2141
|
+
|
|
2142
|
+
|
|
2143
|
+
class TP360JudgeRationaleBoundOnSuccessPath(_PipelineTestBase):
|
|
2144
|
+
"""#360: a judge-returned `rationale` is copied onto SUCCESS-path rows with
|
|
2145
|
+
no length bound. A judge is an LLM with no pre-emission length guarantee, so
|
|
2146
|
+
an over-long rationale yields a schema-INVALID row on the *clean* success
|
|
2147
|
+
path — the same defect class as the #359 fallback fix, but on completed /
|
|
2148
|
+
constraint_violation rows rather than the inconclusive fallback. Both
|
|
2149
|
+
success-path rationale assignments MUST clamp to the schema maxLength=2000.
|
|
2150
|
+
"""
|
|
2151
|
+
|
|
2152
|
+
# 2500 > 2000 schema maxLength, so an unbounded copy overflows the row.
|
|
2153
|
+
_OVERLONG = "Cited page supports the claim. " + ("y" * 2500)
|
|
2154
|
+
|
|
2155
|
+
def test_completed_row_clamps_overlong_judge_rationale(self) -> None:
|
|
2156
|
+
# SUPPORTED verdict → _judge_result_entry completed row (pipeline line 558).
|
|
2157
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
2158
|
+
return {"judgment": "SUPPORTED", "rationale": self._OVERLONG}
|
|
2159
|
+
|
|
2160
|
+
out = self.run_pipeline(citations=[_citation()], judge_fn=judge_fn)
|
|
2161
|
+
e = out["claim_audit_results"][0]
|
|
2162
|
+
self.assertEqual(e["judgment"], "SUPPORTED")
|
|
2163
|
+
self.assertEqual(e["audit_status"], "completed")
|
|
2164
|
+
# Diagnostic head preserved (so the bound truncates the tail, not the head).
|
|
2165
|
+
self.assertTrue(
|
|
2166
|
+
e["rationale"].startswith("Cited page supports the claim."),
|
|
2167
|
+
f"clamp must preserve the diagnostic head; got {e['rationale'][:60]!r}",
|
|
2168
|
+
)
|
|
2169
|
+
self.assertLessEqual(
|
|
2170
|
+
len(e["rationale"]), 2000,
|
|
2171
|
+
f"completed-row rationale must fit schema maxLength=2000; got {len(e['rationale'])}",
|
|
2172
|
+
)
|
|
2173
|
+
errors = sorted(_CAR_VALIDATOR.iter_errors(e), key=str)
|
|
2174
|
+
self.assertEqual(
|
|
2175
|
+
errors, [], f"completed row with over-long judge rationale must satisfy schema; got {errors}"
|
|
2176
|
+
)
|
|
2177
|
+
self.assertEqual(self._validate_passport(out), [], "completed row must also be lint-clean")
|
|
2178
|
+
|
|
2179
|
+
def test_completed_row_keeps_short_rationale_verbatim(self) -> None:
|
|
2180
|
+
# Regression guard: a short rationale that never needed truncating must
|
|
2181
|
+
# pass through unchanged (the bound must not mangle the common case).
|
|
2182
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
2183
|
+
return {"judgment": "SUPPORTED", "rationale": "Cited page contains the figure verbatim."}
|
|
2184
|
+
|
|
2185
|
+
out = self.run_pipeline(citations=[_citation()], judge_fn=judge_fn)
|
|
2186
|
+
e = out["claim_audit_results"][0]
|
|
2187
|
+
self.assertEqual(e["rationale"], "Cited page contains the figure verbatim.")
|
|
2188
|
+
self.assertEqual(sorted(_CAR_VALIDATOR.iter_errors(e), key=str), [])
|
|
2189
|
+
|
|
2190
|
+
def test_constraint_violation_row_clamps_overlong_judge_rationale(self) -> None:
|
|
2191
|
+
# VIOLATED uncited claim → _constraint_violation_entry (pipeline line 644).
|
|
2192
|
+
manifest = _manifest(
|
|
2193
|
+
claims=[
|
|
2194
|
+
{
|
|
2195
|
+
"claim_id": "C-001",
|
|
2196
|
+
"claim_text": "Manifest claim about the cohort.",
|
|
2197
|
+
"intended_evidence_kind": "empirical",
|
|
2198
|
+
"planned_refs": [],
|
|
2199
|
+
}
|
|
2200
|
+
],
|
|
2201
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "MUST NOT generalize beyond cohort"}],
|
|
2202
|
+
)
|
|
2203
|
+
sentence = {
|
|
2204
|
+
"sentence_text": "All practitioners benefit.",
|
|
2205
|
+
"section_path": "Discussion",
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
2209
|
+
return {
|
|
2210
|
+
"judgment": "VIOLATED",
|
|
2211
|
+
"violated_constraint_id": "MNC-1",
|
|
2212
|
+
"rationale": self._OVERLONG,
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
out = self.run_pipeline(
|
|
2216
|
+
citations=[],
|
|
2217
|
+
manifests=[manifest],
|
|
2218
|
+
uncited_sentences=[],
|
|
2219
|
+
all_uncited_sentences=[sentence],
|
|
2220
|
+
judge_fn=judge_fn,
|
|
2221
|
+
)
|
|
2222
|
+
cv = out["constraint_violations"]
|
|
2223
|
+
self.assertEqual(len(cv), 1, f"exactly one CV row; got {cv!r}")
|
|
2224
|
+
e = cv[0]
|
|
2225
|
+
self.assertTrue(
|
|
2226
|
+
e["rationale"].startswith("Cited page supports the claim."),
|
|
2227
|
+
f"clamp must preserve the diagnostic head; got {e['rationale'][:60]!r}",
|
|
2228
|
+
)
|
|
2229
|
+
self.assertLessEqual(
|
|
2230
|
+
len(e["rationale"]), 2000,
|
|
2231
|
+
f"constraint_violation rationale must fit schema maxLength=2000; got {len(e['rationale'])}",
|
|
2232
|
+
)
|
|
2233
|
+
errors = sorted(_CV_VALIDATOR.iter_errors(e), key=str)
|
|
2234
|
+
self.assertEqual(
|
|
2235
|
+
errors, [], f"constraint_violation row with over-long judge rationale must satisfy schema; got {errors}"
|
|
2236
|
+
)
|
|
2237
|
+
self.assertEqual(self._validate_passport(out, [manifest]), [], "constraint_violation row must also be lint-clean")
|
|
2238
|
+
|
|
2239
|
+
|
|
2240
|
+
class TP360NonStringJudgeRationale(_PipelineTestBase):
|
|
2241
|
+
"""#360 follow-up: `_validate_judge_dict` accepts a present-but-null
|
|
2242
|
+
`rationale` (it only checks key presence, not value type). The success-path
|
|
2243
|
+
clamp must NOT call len() on a non-string value — a JSON-null rationale from
|
|
2244
|
+
the judge/cache must degrade to the default placeholder, not abort the audit
|
|
2245
|
+
run with TypeError.
|
|
2246
|
+
"""
|
|
2247
|
+
|
|
2248
|
+
def test_completed_row_null_rationale_falls_back_to_placeholder(self) -> None:
|
|
2249
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
2250
|
+
return {"judgment": "SUPPORTED", "rationale": None}
|
|
2251
|
+
|
|
2252
|
+
out = self.run_pipeline(citations=[_citation()], judge_fn=judge_fn)
|
|
2253
|
+
e = out["claim_audit_results"][0]
|
|
2254
|
+
self.assertEqual(e["rationale"], "(no rationale provided)")
|
|
2255
|
+
self.assertEqual(sorted(_CAR_VALIDATOR.iter_errors(e), key=str), [])
|
|
2256
|
+
self.assertEqual(self._validate_passport(out), [])
|
|
2257
|
+
|
|
2258
|
+
def test_constraint_violation_null_rationale_falls_back_to_default(self) -> None:
|
|
2259
|
+
manifest = _manifest(
|
|
2260
|
+
claims=[
|
|
2261
|
+
{
|
|
2262
|
+
"claim_id": "C-001",
|
|
2263
|
+
"claim_text": "Manifest claim about the cohort.",
|
|
2264
|
+
"intended_evidence_kind": "empirical",
|
|
2265
|
+
"planned_refs": [],
|
|
2266
|
+
}
|
|
2267
|
+
],
|
|
2268
|
+
mncs=[{"constraint_id": "MNC-1", "rule": "MUST NOT generalize beyond cohort"}],
|
|
2269
|
+
)
|
|
2270
|
+
sentence = {"sentence_text": "All practitioners benefit.", "section_path": "Discussion"}
|
|
2271
|
+
|
|
2272
|
+
def judge_fn(**kwargs: Any) -> dict[str, Any]:
|
|
2273
|
+
return {"judgment": "VIOLATED", "violated_constraint_id": "MNC-1", "rationale": None}
|
|
2274
|
+
|
|
2275
|
+
out = self.run_pipeline(
|
|
2276
|
+
citations=[],
|
|
2277
|
+
manifests=[manifest],
|
|
2278
|
+
uncited_sentences=[],
|
|
2279
|
+
all_uncited_sentences=[sentence],
|
|
2280
|
+
judge_fn=judge_fn,
|
|
2281
|
+
)
|
|
2282
|
+
cv = out["constraint_violations"]
|
|
2283
|
+
self.assertEqual(len(cv), 1, f"exactly one CV row; got {cv!r}")
|
|
2284
|
+
e = cv[0]
|
|
2285
|
+
# Non-empty (schema minLength=1) and schema-valid — the null degraded
|
|
2286
|
+
# to the default, not to None (which would be schema-invalid).
|
|
2287
|
+
self.assertTrue(e["rationale"], "null rationale must degrade to a non-empty default")
|
|
2288
|
+
self.assertEqual(sorted(_CV_VALIDATOR.iter_errors(e), key=str), [])
|
|
2289
|
+
self.assertEqual(self._validate_passport(out, [manifest]), [])
|
|
2290
|
+
|
|
2291
|
+
|
|
2292
|
+
|
|
2293
|
+
# ---------------------------------------------------------------------------
|
|
2294
|
+
# T-512 — PDF read-integrity tag on manual_pdf page-anchor rows (#512).
|
|
2295
|
+
# ---------------------------------------------------------------------------
|
|
2296
|
+
|
|
2297
|
+
|
|
2298
|
+
class T512PdfReadIntegrityTag(_PipelineTestBase):
|
|
2299
|
+
"""#512: completed manual_pdf page-anchor rows are tagged when the preflight
|
|
2300
|
+
sidecar is missing or non-PASS; cache hits cannot bypass; None = legacy."""
|
|
2301
|
+
|
|
2302
|
+
@staticmethod
|
|
2303
|
+
def _manual_pdf(citation: dict[str, Any]) -> dict[str, Any]:
|
|
2304
|
+
return {"ref_retrieval_method": "manual_pdf", "retrieved_excerpt": "uploaded excerpt"}
|
|
2305
|
+
|
|
2306
|
+
def _tag(self) -> str:
|
|
2307
|
+
from scripts.claim_audit_pipeline import PDF_READ_INTEGRITY_TAG
|
|
2308
|
+
|
|
2309
|
+
return PDF_READ_INTEGRITY_TAG
|
|
2310
|
+
|
|
2311
|
+
def test_missing_sidecar_tags_rationale(self) -> None:
|
|
2312
|
+
out = self.run_pipeline(
|
|
2313
|
+
citations=[_citation()], retrieve_fn=self._manual_pdf, pdf_preflight_sidecars={}
|
|
2314
|
+
)
|
|
2315
|
+
self.assertIn(self._tag(), out["claim_audit_results"][0]["rationale"])
|
|
2316
|
+
|
|
2317
|
+
def test_pass_sidecar_not_tagged(self) -> None:
|
|
2318
|
+
out = self.run_pipeline(
|
|
2319
|
+
citations=[_citation()],
|
|
2320
|
+
retrieve_fn=self._manual_pdf,
|
|
2321
|
+
pdf_preflight_sidecars={"smith2024preprints": {"verdict": "PASS"}},
|
|
2322
|
+
)
|
|
2323
|
+
self.assertNotIn(self._tag(), out["claim_audit_results"][0]["rationale"])
|
|
2324
|
+
|
|
2325
|
+
def test_fail_and_unavailable_sidecars_tagged(self) -> None:
|
|
2326
|
+
for verdict in ("FAIL", "UNAVAILABLE"):
|
|
2327
|
+
with self.subTest(verdict=verdict):
|
|
2328
|
+
out = self.run_pipeline(
|
|
2329
|
+
citations=[_citation()],
|
|
2330
|
+
retrieve_fn=self._manual_pdf,
|
|
2331
|
+
pdf_preflight_sidecars={"smith2024preprints": {"verdict": verdict}},
|
|
2332
|
+
)
|
|
2333
|
+
self.assertIn(self._tag(), out["claim_audit_results"][0]["rationale"])
|
|
2334
|
+
|
|
2335
|
+
def test_none_param_is_legacy_untagged(self) -> None:
|
|
2336
|
+
out = self.run_pipeline(citations=[_citation()], retrieve_fn=self._manual_pdf)
|
|
2337
|
+
self.assertNotIn(self._tag(), out["claim_audit_results"][0]["rationale"])
|
|
2338
|
+
|
|
2339
|
+
def test_api_rows_never_tagged(self) -> None:
|
|
2340
|
+
out = self.run_pipeline(citations=[_citation()], pdf_preflight_sidecars={})
|
|
2341
|
+
self.assertNotIn(self._tag(), out["claim_audit_results"][0]["rationale"])
|
|
2342
|
+
|
|
2343
|
+
def test_non_page_anchor_never_tagged(self) -> None:
|
|
2344
|
+
out = self.run_pipeline(
|
|
2345
|
+
citations=[_citation(anchor_kind="quote", anchor_value="verbatim%20text")],
|
|
2346
|
+
retrieve_fn=self._manual_pdf,
|
|
2347
|
+
pdf_preflight_sidecars={},
|
|
2348
|
+
)
|
|
2349
|
+
self.assertNotIn(self._tag(), out["claim_audit_results"][0]["rationale"])
|
|
2350
|
+
|
|
2351
|
+
def test_cache_hit_cannot_bypass_tag(self) -> None:
|
|
2352
|
+
cache: dict[str, Any] = {}
|
|
2353
|
+
first = self.run_pipeline(
|
|
2354
|
+
citations=[_citation()],
|
|
2355
|
+
retrieve_fn=self._manual_pdf,
|
|
2356
|
+
cache=cache,
|
|
2357
|
+
pdf_preflight_sidecars={},
|
|
2358
|
+
)
|
|
2359
|
+
self.assertEqual(len(cache), 1)
|
|
2360
|
+
second = self.run_pipeline(
|
|
2361
|
+
citations=[_citation()],
|
|
2362
|
+
retrieve_fn=self._manual_pdf,
|
|
2363
|
+
cache=cache,
|
|
2364
|
+
pdf_preflight_sidecars={},
|
|
2365
|
+
)
|
|
2366
|
+
for out in (first, second):
|
|
2367
|
+
self.assertIn(self._tag(), out["claim_audit_results"][0]["rationale"])
|
|
2368
|
+
# Tag never contaminates the cached judge body (run-context only).
|
|
2369
|
+
(cached,) = cache.values()
|
|
2370
|
+
self.assertNotIn(self._tag(), str(cached))
|
|
2371
|
+
|
|
2372
|
+
def test_retrieve_fn_receives_preflight_verdict(self) -> None:
|
|
2373
|
+
seen: list[Any] = []
|
|
2374
|
+
|
|
2375
|
+
def spy_retrieve(citation: dict[str, Any]) -> dict[str, Any]:
|
|
2376
|
+
seen.append(citation.get("pdf_preflight_verdict"))
|
|
2377
|
+
return {"ref_retrieval_method": "manual_pdf", "retrieved_excerpt": "x"}
|
|
2378
|
+
|
|
2379
|
+
self.run_pipeline(
|
|
2380
|
+
citations=[_citation()],
|
|
2381
|
+
retrieve_fn=spy_retrieve,
|
|
2382
|
+
pdf_preflight_sidecars={"smith2024preprints": {"verdict": "FAIL"}},
|
|
2383
|
+
)
|
|
2384
|
+
self.run_pipeline(
|
|
2385
|
+
citations=[_citation()], retrieve_fn=spy_retrieve, pdf_preflight_sidecars={}
|
|
2386
|
+
)
|
|
2387
|
+
self.run_pipeline(citations=[_citation()], retrieve_fn=spy_retrieve)
|
|
2388
|
+
self.assertEqual(seen, ["FAIL", "MISSING", None])
|
|
2389
|
+
|
|
2390
|
+
def test_tagged_row_passes_consistency_lint(self) -> None:
|
|
2391
|
+
out = self.run_pipeline(
|
|
2392
|
+
citations=[_citation()], retrieve_fn=self._manual_pdf, pdf_preflight_sidecars={}
|
|
2393
|
+
)
|
|
2394
|
+
self.assertEqual(self._validate_passport(out), [])
|
|
2395
|
+
|
|
2396
|
+
|
|
2397
|
+
if __name__ == "__main__":
|
|
2398
|
+
unittest.main()
|