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