psyclaw 0.27.23 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/agents/recommended/catalog.json +99 -0
- package/dist/apps/panel/index.html +5 -5
- package/dist/src/adapters/pi/extension.js +339 -94
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/adapters/pi/rpc.d.ts +2 -0
- package/dist/src/adapters/pi/rpc.js +9 -2
- package/dist/src/adapters/pi/rpc.js.map +1 -1
- package/dist/src/agents/import.js +44 -32
- package/dist/src/agents/import.js.map +1 -1
- package/dist/src/agents/recommended-personas.d.ts +42 -0
- package/dist/src/agents/recommended-personas.js +135 -0
- package/dist/src/agents/recommended-personas.js.map +1 -0
- package/dist/src/analysis/hooks.d.ts +1 -0
- package/dist/src/analysis/hooks.js +20 -1
- package/dist/src/analysis/hooks.js.map +1 -1
- package/dist/src/ars/bridge.d.ts +20 -0
- package/dist/src/ars/bridge.js +132 -0
- package/dist/src/ars/bridge.js.map +1 -0
- package/dist/src/ars/contracts.d.ts +55 -0
- package/dist/src/ars/contracts.js +2 -0
- package/dist/src/ars/contracts.js.map +1 -0
- package/dist/src/ars/panel-plan.d.ts +4 -0
- package/dist/src/ars/panel-plan.js +11 -0
- package/dist/src/ars/panel-plan.js.map +1 -0
- package/dist/src/ars/pi-panel-executor.d.ts +30 -0
- package/dist/src/ars/pi-panel-executor.js +140 -0
- package/dist/src/ars/pi-panel-executor.js.map +1 -0
- package/dist/src/ars/profile.d.ts +36 -0
- package/dist/src/ars/profile.js +119 -0
- package/dist/src/ars/profile.js.map +1 -0
- package/dist/src/ars/re-review.d.ts +22 -0
- package/dist/src/ars/re-review.js +172 -0
- package/dist/src/ars/re-review.js.map +1 -0
- package/dist/src/branding.d.ts +2 -4
- package/dist/src/branding.js +3 -5
- package/dist/src/branding.js.map +1 -1
- package/dist/src/bundled-tools.d.ts +3 -0
- package/dist/src/bundled-tools.js +21 -0
- package/dist/src/bundled-tools.js.map +1 -0
- package/dist/src/chat.js +6 -5
- package/dist/src/chat.js.map +1 -1
- package/dist/src/cli.js +6 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/creation/contracts.d.ts +41 -0
- package/dist/src/creation/contracts.js +2 -0
- package/dist/src/creation/contracts.js.map +1 -0
- package/dist/src/creation/service.d.ts +7 -0
- package/dist/src/creation/service.js +208 -0
- package/dist/src/creation/service.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/install/installer.js +32 -10
- package/dist/src/install/installer.js.map +1 -1
- package/dist/src/orchestration/bundled-personas.d.ts +16 -0
- package/dist/src/orchestration/bundled-personas.js +101 -0
- package/dist/src/orchestration/bundled-personas.js.map +1 -0
- package/dist/src/orchestration/effects.d.ts +8 -0
- package/dist/src/orchestration/effects.js +39 -0
- package/dist/src/orchestration/effects.js.map +1 -0
- package/dist/src/orchestration/personas.d.ts +16 -0
- package/dist/src/orchestration/personas.js +64 -0
- package/dist/src/orchestration/personas.js.map +1 -0
- package/dist/src/orchestration/pi-executor.d.ts +9 -4
- package/dist/src/orchestration/pi-executor.js +55 -22
- package/dist/src/orchestration/pi-executor.js.map +1 -1
- package/dist/src/orchestration/research-agents.d.ts +3 -7
- package/dist/src/orchestration/research-agents.js +5 -69
- package/dist/src/orchestration/research-agents.js.map +1 -1
- package/dist/src/panel/server.js +19 -4
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/project/paths.d.ts +3 -0
- package/dist/src/project/paths.js +7 -0
- package/dist/src/project/paths.js.map +1 -1
- package/dist/src/rules/user-rules.d.ts +8 -0
- package/dist/src/rules/user-rules.js +36 -0
- package/dist/src/rules/user-rules.js.map +1 -0
- package/dist/src/skills/contracts.d.ts +4 -4
- package/dist/src/skills/recommended.js +1 -1
- package/dist/src/skills/registry.js +49 -28
- package/dist/src/skills/registry.js.map +1 -1
- package/dist/src/style/cli-ui.d.ts +14 -0
- package/dist/src/style/cli-ui.js +45 -1
- package/dist/src/style/cli-ui.js.map +1 -1
- package/dist/src/tui/skill-manager.js +1 -1
- package/dist/src/tui/skill-manager.js.map +1 -1
- package/dist/src/updates/update.js +9 -3
- package/dist/src/updates/update.js.map +1 -1
- package/package.json +15 -3
- package/scripts/rebrand-pi.mjs +6 -0
- package/skills/recommended/catalog.json +2 -11
- package/vendor/ars/.claude/CLAUDE.md +371 -0
- package/vendor/ars/.command-invariants.toml +24 -0
- package/vendor/ars/CITATION.cff +35 -0
- package/vendor/ars/LICENSE +417 -0
- package/vendor/ars/MODE_REGISTRY.md +76 -0
- package/vendor/ars/NOTICE.md +26 -0
- package/vendor/ars/POSITIONING.md +99 -0
- package/vendor/ars/PSYCLAW_SOURCE.json +10 -0
- package/vendor/ars/README.md +751 -0
- package/vendor/ars/SECURITY.md +52 -0
- package/vendor/ars/THIRD_PARTY.md +70 -0
- package/vendor/ars/academic-paper/SKILL.md +542 -0
- package/vendor/ars/academic-paper/agents/abstract_bilingual_agent.md +171 -0
- package/vendor/ars/academic-paper/agents/argument_builder_agent.md +276 -0
- package/vendor/ars/academic-paper/agents/citation_compliance_agent.md +422 -0
- package/vendor/ars/academic-paper/agents/draft_writer_agent.md +656 -0
- package/vendor/ars/academic-paper/agents/formatter_agent.md +999 -0
- package/vendor/ars/academic-paper/agents/intake_agent.md +393 -0
- package/vendor/ars/academic-paper/agents/literature_strategist_agent.md +626 -0
- package/vendor/ars/academic-paper/agents/peer_reviewer_agent.md +516 -0
- package/vendor/ars/academic-paper/agents/revision_coach_agent.md +334 -0
- package/vendor/ars/academic-paper/agents/socratic_mentor_agent.md +527 -0
- package/vendor/ars/academic-paper/agents/structure_architect_agent.md +401 -0
- package/vendor/ars/academic-paper/agents/visualization_agent.md +441 -0
- package/vendor/ars/academic-paper/examples/chinese_paper_example.md +278 -0
- package/vendor/ars/academic-paper/examples/clinical_citation_verification_checklist.md +95 -0
- package/vendor/ars/academic-paper/examples/clinical_epistemic_status_example.md +100 -0
- package/vendor/ars/academic-paper/examples/commitment_ledger_example.md +147 -0
- package/vendor/ars/academic-paper/examples/imrad_hei_example.md +234 -0
- package/vendor/ars/academic-paper/examples/literature_review_example.md +260 -0
- package/vendor/ars/academic-paper/examples/plan_mode_guided_writing.md +600 -0
- package/vendor/ars/academic-paper/examples/revision_mode_example.md +344 -0
- package/vendor/ars/academic-paper/examples/revision_recovery_example.md +506 -0
- package/vendor/ars/academic-paper/examples/version_family_reconciliation_example.md +89 -0
- package/vendor/ars/academic-paper/references/abstract_writing_guide.md +169 -0
- package/vendor/ars/academic-paper/references/academic_writing_style.md +188 -0
- package/vendor/ars/academic-paper/references/anti_leakage_protocol.md +83 -0
- package/vendor/ars/academic-paper/references/apa7_chinese_citation_guide.md +364 -0
- package/vendor/ars/academic-paper/references/apa7_extended_guide.md +198 -0
- package/vendor/ars/academic-paper/references/changelog.md +11 -0
- package/vendor/ars/academic-paper/references/citation_format_switcher.md +228 -0
- package/vendor/ars/academic-paper/references/committee_correspondence_protocol.md +158 -0
- package/vendor/ars/academic-paper/references/credit_authorship_guide.md +308 -0
- package/vendor/ars/academic-paper/references/disclosure_mode_protocol.md +478 -0
- package/vendor/ars/academic-paper/references/domain_evidence_profiles.md +38 -0
- package/vendor/ars/academic-paper/references/failure_paths.md +349 -0
- package/vendor/ars/academic-paper/references/funding_statement_guide.md +319 -0
- package/vendor/ars/academic-paper/references/hei_domain_glossary.md +169 -0
- package/vendor/ars/academic-paper/references/intro_title_rhetoric_guide.md +114 -0
- package/vendor/ars/academic-paper/references/journal_submission_guide.md +249 -0
- package/vendor/ars/academic-paper/references/latex_template_reference.md +378 -0
- package/vendor/ars/academic-paper/references/mode_selection_guide.md +378 -0
- package/vendor/ars/academic-paper/references/paper_structure_patterns.md +330 -0
- package/vendor/ars/academic-paper/references/plan_mode_protocol.md +112 -0
- package/vendor/ars/academic-paper/references/policy_anchor_disclosure_protocol.md +200 -0
- package/vendor/ars/academic-paper/references/policy_anchor_table.md +157 -0
- package/vendor/ars/academic-paper/references/revision_patch_protocol.md +173 -0
- package/vendor/ars/academic-paper/references/statistical_visualization_standards.md +750 -0
- package/vendor/ars/academic-paper/references/venue_disclosure_policies.md +259 -0
- package/vendor/ars/academic-paper/references/vlm_figure_verification.md +126 -0
- package/vendor/ars/academic-paper/references/workflow_phase_details.md +135 -0
- package/vendor/ars/academic-paper/references/writing_judgment_framework.md +59 -0
- package/vendor/ars/academic-paper/references/writing_quality_check.md +173 -0
- package/vendor/ars/academic-paper/templates/bilingual_abstract_template.md +78 -0
- package/vendor/ars/academic-paper/templates/case_study_template.md +129 -0
- package/vendor/ars/academic-paper/templates/conference_paper_template.md +108 -0
- package/vendor/ars/academic-paper/templates/credit_statement_template.md +132 -0
- package/vendor/ars/academic-paper/templates/funding_statement_template.md +290 -0
- package/vendor/ars/academic-paper/templates/imrad_template.md +183 -0
- package/vendor/ars/academic-paper/templates/latex_article_template.tex +199 -0
- package/vendor/ars/academic-paper/templates/literature_review_template.md +135 -0
- package/vendor/ars/academic-paper/templates/policy_brief_template.md +139 -0
- package/vendor/ars/academic-paper/templates/revision_tracking_template.md +199 -0
- package/vendor/ars/academic-paper/templates/theoretical_paper_template.md +119 -0
- package/vendor/ars/academic-paper-reviewer/SKILL.md +491 -0
- package/vendor/ars/academic-paper-reviewer/agents/devils_advocate_reviewer_agent.md +443 -0
- package/vendor/ars/academic-paper-reviewer/agents/domain_reviewer_agent.md +412 -0
- package/vendor/ars/academic-paper-reviewer/agents/editorial_synthesizer_agent.md +478 -0
- package/vendor/ars/academic-paper-reviewer/agents/eic_agent.md +339 -0
- package/vendor/ars/academic-paper-reviewer/agents/field_analyst_agent.md +221 -0
- package/vendor/ars/academic-paper-reviewer/agents/methodology_reviewer_agent.md +449 -0
- package/vendor/ars/academic-paper-reviewer/agents/perspective_reviewer_agent.md +427 -0
- package/vendor/ars/academic-paper-reviewer/examples/hei_paper_review_example.md +391 -0
- package/vendor/ars/academic-paper-reviewer/examples/interdisciplinary_review_example.md +299 -0
- package/vendor/ars/academic-paper-reviewer/examples/subclaim_decomposition_example.md +80 -0
- package/vendor/ars/academic-paper-reviewer/references/calibration_mode_protocol.md +256 -0
- package/vendor/ars/academic-paper-reviewer/references/changelog.md +10 -0
- package/vendor/ars/academic-paper-reviewer/references/editorial_decision_standards.md +236 -0
- package/vendor/ars/academic-paper-reviewer/references/guided_mode_protocol.md +34 -0
- package/vendor/ars/academic-paper-reviewer/references/integration_guide.md +15 -0
- package/vendor/ars/academic-paper-reviewer/references/quality_rubrics.md +84 -0
- package/vendor/ars/academic-paper-reviewer/references/re_review_mode_protocol.md +340 -0
- package/vendor/ars/academic-paper-reviewer/references/review_criteria_framework.md +98 -0
- package/vendor/ars/academic-paper-reviewer/references/review_panel_provenance_protocol.md +197 -0
- package/vendor/ars/academic-paper-reviewer/references/review_quality_thinking.md +58 -0
- package/vendor/ars/academic-paper-reviewer/references/reviewer_sprint_prompt_source.md +324 -0
- package/vendor/ars/academic-paper-reviewer/references/sprint_contract_protocol.md +296 -0
- package/vendor/ars/academic-paper-reviewer/references/statistical_reporting_standards.md +505 -0
- package/vendor/ars/academic-paper-reviewer/references/top_journals_by_field.md +206 -0
- package/vendor/ars/academic-paper-reviewer/templates/editorial_decision_template.md +235 -0
- package/vendor/ars/academic-paper-reviewer/templates/peer_review_report_template.md +305 -0
- package/vendor/ars/academic-paper-reviewer/templates/revision_response_template.md +248 -0
- package/vendor/ars/academic-pipeline/SKILL.md +736 -0
- package/vendor/ars/academic-pipeline/agents/claim_ref_alignment_audit_agent.md +382 -0
- package/vendor/ars/academic-pipeline/agents/collaboration_depth_agent.md +164 -0
- package/vendor/ars/academic-pipeline/agents/integrity_verification_agent.md +870 -0
- package/vendor/ars/academic-pipeline/agents/pipeline_orchestrator_agent.md +1379 -0
- package/vendor/ars/academic-pipeline/agents/state_tracker_agent.md +622 -0
- package/vendor/ars/academic-pipeline/examples/full_pipeline_example.md +482 -0
- package/vendor/ars/academic-pipeline/examples/integrity_failure_recovery.md +389 -0
- package/vendor/ars/academic-pipeline/examples/mid_entry_example.md +414 -0
- package/vendor/ars/academic-pipeline/references/adapters/.gitkeep +0 -0
- package/vendor/ars/academic-pipeline/references/adapters/overview.md +153 -0
- package/vendor/ars/academic-pipeline/references/ai_research_failure_modes.md +185 -0
- package/vendor/ars/academic-pipeline/references/changelog.md +14 -0
- package/vendor/ars/academic-pipeline/references/claim_audit_calibration_protocol.md +175 -0
- package/vendor/ars/academic-pipeline/references/claim_verification_protocol.md +282 -0
- package/vendor/ars/academic-pipeline/references/external_review_protocol.md +131 -0
- package/vendor/ars/academic-pipeline/references/integrity_review_protocol.md +110 -0
- package/vendor/ars/academic-pipeline/references/literature_corpus_consumers.md +193 -0
- package/vendor/ars/academic-pipeline/references/mode_advisor.md +135 -0
- package/vendor/ars/academic-pipeline/references/passport_as_reset_boundary.md +132 -0
- package/vendor/ars/academic-pipeline/references/pipeline_state_machine.md +405 -0
- package/vendor/ars/academic-pipeline/references/plagiarism_detection_protocol.md +239 -0
- package/vendor/ars/academic-pipeline/references/process_summary_protocol.md +209 -0
- package/vendor/ars/academic-pipeline/references/progress_dashboard_template.md +38 -0
- package/vendor/ars/academic-pipeline/references/reinforcement_content.md +15 -0
- package/vendor/ars/academic-pipeline/references/reproducibility_audit.md +55 -0
- package/vendor/ars/academic-pipeline/references/score_trajectory_protocol.md +78 -0
- package/vendor/ars/academic-pipeline/references/team_collaboration_protocol.md +261 -0
- package/vendor/ars/academic-pipeline/references/two_stage_review_protocol.md +27 -0
- package/vendor/ars/academic-pipeline/templates/pipeline_status_template.md +146 -0
- package/vendor/ars/agents/report_compiler_agent.md +341 -0
- package/vendor/ars/agents/research_architect_agent.md +298 -0
- package/vendor/ars/agents/synthesis_agent.md +356 -0
- package/vendor/ars/commands/ars-3w.md +10 -0
- package/vendor/ars/commands/ars-abstract.md +10 -0
- package/vendor/ars/commands/ars-cache-invalidate.md +20 -0
- package/vendor/ars/commands/ars-citation-check.md +10 -0
- package/vendor/ars/commands/ars-disclosure.md +10 -0
- package/vendor/ars/commands/ars-format-convert.md +10 -0
- package/vendor/ars/commands/ars-full.md +9 -0
- package/vendor/ars/commands/ars-lit-review.md +12 -0
- package/vendor/ars/commands/ars-mark-read.md +18 -0
- package/vendor/ars/commands/ars-outline.md +10 -0
- package/vendor/ars/commands/ars-plan.md +10 -0
- package/vendor/ars/commands/ars-rebuttal-audit.md +12 -0
- package/vendor/ars/commands/ars-reviewer.md +9 -0
- package/vendor/ars/commands/ars-revision-coach.md +9 -0
- package/vendor/ars/commands/ars-revision.md +10 -0
- package/vendor/ars/commands/ars-unmark-read.md +16 -0
- package/vendor/ars/deep-research/SKILL.md +600 -0
- package/vendor/ars/deep-research/agents/bibliography_agent.md +473 -0
- package/vendor/ars/deep-research/agents/devils_advocate_agent.md +192 -0
- package/vendor/ars/deep-research/agents/editor_in_chief_agent.md +167 -0
- package/vendor/ars/deep-research/agents/ethics_review_agent.md +267 -0
- package/vendor/ars/deep-research/agents/meta_analysis_agent.md +325 -0
- package/vendor/ars/deep-research/agents/monitoring_agent.md +209 -0
- package/vendor/ars/deep-research/agents/report_compiler_agent.md +341 -0
- package/vendor/ars/deep-research/agents/research_architect_agent.md +298 -0
- package/vendor/ars/deep-research/agents/research_question_agent.md +216 -0
- package/vendor/ars/deep-research/agents/risk_of_bias_agent.md +231 -0
- package/vendor/ars/deep-research/agents/socratic_mentor_agent.md +764 -0
- package/vendor/ars/deep-research/agents/source_verification_agent.md +219 -0
- package/vendor/ars/deep-research/agents/synthesis_agent.md +356 -0
- package/vendor/ars/deep-research/agents/timeline_extraction_agent.md +99 -0
- package/vendor/ars/deep-research/examples/exploratory_research.md +157 -0
- package/vendor/ars/deep-research/examples/fact_check_mode.md +173 -0
- package/vendor/ars/deep-research/examples/handoff_to_paper.md +318 -0
- package/vendor/ars/deep-research/examples/idea_diversity_coverage_gap_advisory.md +64 -0
- package/vendor/ars/deep-research/examples/policy_analysis.md +161 -0
- package/vendor/ars/deep-research/examples/review_mode.md +253 -0
- package/vendor/ars/deep-research/examples/socratic_guided_research.md +331 -0
- package/vendor/ars/deep-research/examples/systematic_review.md +133 -0
- package/vendor/ars/deep-research/references/apa7_style_guide.md +162 -0
- package/vendor/ars/deep-research/references/argumentation_reasoning_framework.md +68 -0
- package/vendor/ars/deep-research/references/arxiv_api_protocol.md +76 -0
- package/vendor/ars/deep-research/references/changelog.md +22 -0
- package/vendor/ars/deep-research/references/chinese_literature_api_protocol.md +317 -0
- package/vendor/ars/deep-research/references/cross_agent_quality_definitions.md +14 -0
- package/vendor/ars/deep-research/references/crossref_api_protocol.md +84 -0
- package/vendor/ars/deep-research/references/equator_reporting_guidelines.md +482 -0
- package/vendor/ars/deep-research/references/ethics_checklist.md +282 -0
- package/vendor/ars/deep-research/references/failure_paths.md +355 -0
- package/vendor/ars/deep-research/references/interdisciplinary_bridges.md +292 -0
- package/vendor/ars/deep-research/references/irb_decision_tree.md +315 -0
- package/vendor/ars/deep-research/references/literature_monitoring_strategies.md +263 -0
- package/vendor/ars/deep-research/references/logical_fallacies.md +192 -0
- package/vendor/ars/deep-research/references/methodology_patterns.md +462 -0
- package/vendor/ars/deep-research/references/mode_selection_guide.md +331 -0
- package/vendor/ars/deep-research/references/openalex_api_protocol.md +82 -0
- package/vendor/ars/deep-research/references/preregistration_guide.md +324 -0
- package/vendor/ars/deep-research/references/semantic_scholar_api_protocol.md +107 -0
- package/vendor/ars/deep-research/references/socratic_mode_protocol.md +99 -0
- package/vendor/ars/deep-research/references/socratic_questioning_framework.md +232 -0
- package/vendor/ars/deep-research/references/source_quality_hierarchy.md +188 -0
- package/vendor/ars/deep-research/references/systematic_review_protocol.md +95 -0
- package/vendor/ars/deep-research/references/systematic_review_toolkit.md +353 -0
- package/vendor/ars/deep-research/templates/evidence_assessment_template.md +127 -0
- package/vendor/ars/deep-research/templates/literature_matrix_template.md +85 -0
- package/vendor/ars/deep-research/templates/preregistration_template.md +318 -0
- package/vendor/ars/deep-research/templates/prisma_protocol_template.md +248 -0
- package/vendor/ars/deep-research/templates/prisma_report_template.md +415 -0
- package/vendor/ars/deep-research/templates/research_brief_template.md +93 -0
- package/vendor/ars/package.json +24 -0
- package/vendor/ars/pi/README.md +161 -0
- package/vendor/ars/pi/package.json +26 -0
- package/vendor/ars/pi/wrapper.js +193 -0
- package/vendor/ars/pi/wrapper.test.mjs +201 -0
- package/vendor/ars/pyproject.toml +2 -0
- package/vendor/ars/requirements-pdf-content-classifier.txt +5 -0
- package/vendor/ars/scripts/_block_parser.py +396 -0
- package/vendor/ars/scripts/_ci_pytest_manifest.toml +661 -0
- package/vendor/ars/scripts/_claim_audit_constants.py +268 -0
- package/vendor/ars/scripts/_e4_evidence.py +110 -0
- package/vendor/ars/scripts/_eval_threshold_gate.py +73 -0
- package/vendor/ars/scripts/_markdown_lint_util.py +224 -0
- package/vendor/ars/scripts/_next_verified_at_ms.py +176 -0
- package/vendor/ars/scripts/_passport_yaml.py +53 -0
- package/vendor/ars/scripts/_skill_lint.py +254 -0
- package/vendor/ars/scripts/_text_similarity.py +141 -0
- package/vendor/ars/scripts/adapters/README.md +89 -0
- package/vendor/ars/scripts/adapters/_common.py +209 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/expected_passport.yaml +25 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/expected_rejection_log.yaml +18 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Chen2024_AIAssessment.pdf +0 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Wang_2023_formative_feedback.pdf +0 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/paper1.pdf +0 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture//344/270/255/346/226/207/346/252/224/345/220/215_2024.pdf +0 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/expected_passport.yaml +40 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/expected_rejection_log.yaml +13 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.gitkeep +0 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.obsidian/app.json +1 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/_templates/tmpl.md +7 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/chen2024ai.md +13 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/invalid.md +1 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/wang2023formative.md +10 -0
- package/vendor/ars/scripts/adapters/examples/zotero/expected_passport.yaml +34 -0
- package/vendor/ars/scripts/adapters/examples/zotero/expected_rejection_log.yaml +34 -0
- package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/.gitkeep +0 -0
- package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/export.json +43 -0
- package/vendor/ars/scripts/adapters/folder_scan.py +214 -0
- package/vendor/ars/scripts/adapters/obsidian.py +336 -0
- package/vendor/ars/scripts/adapters/tests/.gitkeep +0 -0
- package/vendor/ars/scripts/adapters/tests/conftest.py +77 -0
- package/vendor/ars/scripts/adapters/tests/test_check_corpus_consumer_protocol.py +632 -0
- package/vendor/ars/scripts/adapters/tests/test_check_literature_corpus_schema.py +631 -0
- package/vendor/ars/scripts/adapters/tests/test_common.py +365 -0
- package/vendor/ars/scripts/adapters/tests/test_conftest.py +96 -0
- package/vendor/ars/scripts/adapters/tests/test_folder_scan.py +260 -0
- package/vendor/ars/scripts/adapters/tests/test_literature_corpus_entry_schema.py +745 -0
- package/vendor/ars/scripts/adapters/tests/test_obsidian.py +357 -0
- package/vendor/ars/scripts/adapters/tests/test_rejection_log_schema.py +271 -0
- package/vendor/ars/scripts/adapters/tests/test_sync_adapter_docs.py +88 -0
- package/vendor/ars/scripts/adapters/tests/test_zotero.py +454 -0
- package/vendor/ars/scripts/adapters/zotero.py +318 -0
- package/vendor/ars/scripts/adjudication_activity.py +1592 -0
- package/vendor/ars/scripts/announce-ars-loaded.sh +144 -0
- package/vendor/ars/scripts/ars_anchorize_draft.py +170 -0
- package/vendor/ars/scripts/ars_apply_revision_patch.py +912 -0
- package/vendor/ars/scripts/ars_cache_invalidate.py +40 -0
- package/vendor/ars/scripts/ars_mark_read.py +521 -0
- package/vendor/ars/scripts/ars_phase_scope_manifest.json +33 -0
- package/vendor/ars/scripts/ars_update_check.sh +215 -0
- package/vendor/ars/scripts/ars_write_scope_guard.py +506 -0
- package/vendor/ars/scripts/arxiv_client.py +222 -0
- package/vendor/ars/scripts/audit_snapshot.py +572 -0
- package/vendor/ars/scripts/bibliographic_integrity_signals.py +800 -0
- package/vendor/ars/scripts/bootstrap_timeline_yaml.py +146 -0
- package/vendor/ars/scripts/build_claim_standing_candidate_ledger.py +1238 -0
- package/vendor/ars/scripts/build_claim_standing_query_plan.py +643 -0
- package/vendor/ars/scripts/build_content_coverage_advisory.py +1205 -0
- package/vendor/ars/scripts/build_cross_document_consistency_advisory.py +2332 -0
- package/vendor/ars/scripts/build_review_pathway_rule_trace.py +830 -0
- package/vendor/ars/scripts/build_submission_packet_manifest.py +2310 -0
- package/vendor/ars/scripts/check_215_field_norm.py +173 -0
- package/vendor/ars/scripts/check_216_surface_form.py +250 -0
- package/vendor/ars/scripts/check_268_nested_commitment_ledger.py +180 -0
- package/vendor/ars/scripts/check_390_revision_patch_discipline.py +296 -0
- package/vendor/ars/scripts/check_392_citation_verification_intake.py +126 -0
- package/vendor/ars/scripts/check_394_submission_policy.py +178 -0
- package/vendor/ars/scripts/check_439_format_profile.py +307 -0
- package/vendor/ars/scripts/check_619_disclosure_closeout.py +237 -0
- package/vendor/ars/scripts/check_630_codex_subscription_transport.py +456 -0
- package/vendor/ars/scripts/check_669_review_pathway_rule_trace.py +662 -0
- package/vendor/ars/scripts/check_670_revision_roadmap_integration.py +518 -0
- package/vendor/ars/scripts/check_673_adjudication_activity.py +684 -0
- package/vendor/ars/scripts/check_684_review_criteria_binding.py +557 -0
- package/vendor/ars/scripts/check_agents_mirror_sync.py +115 -0
- package/vendor/ars/scripts/check_audit_artifact_consistency.py +2313 -0
- package/vendor/ars/scripts/check_benchmark_report.py +79 -0
- package/vendor/ars/scripts/check_bibliographic_integrity_signals.py +831 -0
- package/vendor/ars/scripts/check_calibration_tiers.py +235 -0
- package/vendor/ars/scripts/check_changelog_covers_merges.py +289 -0
- package/vendor/ars/scripts/check_ci_pytest_manifest.py +204 -0
- package/vendor/ars/scripts/check_claim_audit_consistency.py +1664 -0
- package/vendor/ars/scripts/check_claim_standing_candidate_ledger_integration.py +500 -0
- package/vendor/ars/scripts/check_claim_standing_freshness.py +253 -0
- package/vendor/ars/scripts/check_claim_standing_transmissions.py +449 -0
- package/vendor/ars/scripts/check_collaboration_depth_rubric.py +180 -0
- package/vendor/ars/scripts/check_command_frontmatter_name.py +116 -0
- package/vendor/ars/scripts/check_committee_correspondence.py +333 -0
- package/vendor/ars/scripts/check_compliance_report.py +108 -0
- package/vendor/ars/scripts/check_content_coverage_advisory_integration.py +796 -0
- package/vendor/ars/scripts/check_control_availability.py +172 -0
- package/vendor/ars/scripts/check_corpus_consumer_protocol.py +404 -0
- package/vendor/ars/scripts/check_cross_document_consistency_advisory_integration.py +1191 -0
- package/vendor/ars/scripts/check_cross_model_handoff_contract.py +234 -0
- package/vendor/ars/scripts/check_cross_model_verification_sync.py +261 -0
- package/vendor/ars/scripts/check_data_access_level.py +131 -0
- package/vendor/ars/scripts/check_data_flows.py +252 -0
- package/vendor/ars/scripts/check_decision_contract.py +464 -0
- package/vendor/ars/scripts/check_degradation_registry.py +326 -0
- package/vendor/ars/scripts/check_distribution_surface_claims.py +226 -0
- package/vendor/ars/scripts/check_domain_evidence_profile.py +538 -0
- package/vendor/ars/scripts/check_e4_promotion.py +195 -0
- package/vendor/ars/scripts/check_evals_gold_set.py +279 -0
- package/vendor/ars/scripts/check_evidence_row_integration.py +396 -0
- package/vendor/ars/scripts/check_experiment_provenance.py +117 -0
- package/vendor/ars/scripts/check_field_norm_severity.py +144 -0
- package/vendor/ars/scripts/check_firm_rules_sync.py +375 -0
- package/vendor/ars/scripts/check_heldout_measurement_report.py +1179 -0
- package/vendor/ars/scripts/check_human_subjects_output_contract.py +139 -0
- package/vendor/ars/scripts/check_human_subjects_reference_migration.py +844 -0
- package/vendor/ars/scripts/check_indirect_prompt_injection_no_call.py +328 -0
- package/vendor/ars/scripts/check_instruction_data_boundary.py +236 -0
- package/vendor/ars/scripts/check_judge_prompt_version.py +125 -0
- package/vendor/ars/scripts/check_literature_corpus_schema.py +402 -0
- package/vendor/ars/scripts/check_model_tiering.py +223 -0
- package/vendor/ars/scripts/check_panel_synthesis.py +1331 -0
- package/vendor/ars/scripts/check_passport_reset_contract.py +214 -0
- package/vendor/ars/scripts/check_pattern_eval_manifest.py +422 -0
- package/vendor/ars/scripts/check_persuasion_invariance_fixtures.py +637 -0
- package/vendor/ars/scripts/check_phase_conformance.py +2180 -0
- package/vendor/ars/scripts/check_pipeline_boundary_semantics.py +600 -0
- package/vendor/ars/scripts/check_pipeline_integrity.py +340 -0
- package/vendor/ars/scripts/check_policy_anchor_protocol.py +159 -0
- package/vendor/ars/scripts/check_policy_anchor_table.py +286 -0
- package/vendor/ars/scripts/check_preprint_venues_consistency.py +123 -0
- package/vendor/ars/scripts/check_prisma_trAIce_freshness.py +84 -0
- package/vendor/ars/scripts/check_promotion_bakeoff_preregistration.py +1303 -0
- package/vendor/ars/scripts/check_ranking_lift.py +323 -0
- package/vendor/ars/scripts/check_re_review_synthesis.py +2719 -0
- package/vendor/ars/scripts/check_receipt_enum_sync.py +203 -0
- package/vendor/ars/scripts/check_repro_lock.py +85 -0
- package/vendor/ars/scripts/check_review_pathway_output.py +276 -0
- package/vendor/ars/scripts/check_reviewer_data_fences.py +224 -0
- package/vendor/ars/scripts/check_reviewer_finding_contract.py +783 -0
- package/vendor/ars/scripts/check_reviewer_role_label.py +447 -0
- package/vendor/ars/scripts/check_reviewer_scoring_honesty.py +292 -0
- package/vendor/ars/scripts/check_reviewer_sprint_prompt_sync.py +409 -0
- package/vendor/ars/scripts/check_revision_claim_drift_suite_v2.py +1622 -0
- package/vendor/ars/scripts/check_revision_token_conservation.py +263 -0
- package/vendor/ars/scripts/check_risk_register.py +280 -0
- package/vendor/ars/scripts/check_role_scoped_contract.py +702 -0
- package/vendor/ars/scripts/check_rq_framing_patterns.py +184 -0
- package/vendor/ars/scripts/check_rubric_weight_consistency.py +32 -0
- package/vendor/ars/scripts/check_seeded_defect_fixtures.py +425 -0
- package/vendor/ars/scripts/check_setup_cross_model_parity.py +139 -0
- package/vendor/ars/scripts/check_spec_consistency.py +1249 -0
- package/vendor/ars/scripts/check_sprint_contract.py +371 -0
- package/vendor/ars/scripts/check_stage_capability_matrix.py +784 -0
- package/vendor/ars/scripts/check_submission_packet_manifest_integration.py +358 -0
- package/vendor/ars/scripts/check_surface_form_parity.py +445 -0
- package/vendor/ars/scripts/check_task_type.py +22 -0
- package/vendor/ars/scripts/check_tools_allowlist.py +538 -0
- package/vendor/ars/scripts/check_tortured_phrase_screening_integration.py +1974 -0
- package/vendor/ars/scripts/check_v3_10_134_write_scope.py +286 -0
- package/vendor/ars/scripts/check_v3_10_policy.py +656 -0
- package/vendor/ars/scripts/check_v3_6_6_ab_manifest.py +364 -0
- package/vendor/ars/scripts/check_v3_6_7_pattern_protection.py +1366 -0
- package/vendor/ars/scripts/check_v3_6_8_audit_scope_block.py +464 -0
- package/vendor/ars/scripts/check_v3_6_8_cite_provenance_pipeline.py +231 -0
- package/vendor/ars/scripts/check_v3_6_8_frontmatter_trust_schema.py +248 -0
- package/vendor/ars/scripts/check_v3_6_8_mark_read_commands.py +79 -0
- package/vendor/ars/scripts/check_v3_6_8_pattern_protection.py +941 -0
- package/vendor/ars/scripts/check_v3_7_3_three_layer_citation.py +318 -0
- package/vendor/ars/scripts/check_v3_8_annotation_literal_sync.py +228 -0
- package/vendor/ars/scripts/check_v3_9_0_triangulation.py +366 -0
- package/vendor/ars/scripts/check_v3_9_2_phase_boundary.py +270 -0
- package/vendor/ars/scripts/check_v3_9_4_temporal_verification.py +139 -0
- package/vendor/ars/scripts/check_venue_disclosure_policies.py +63 -0
- package/vendor/ars/scripts/check_version_consistency.py +826 -0
- package/vendor/ars/scripts/check_workflow_classification.py +223 -0
- package/vendor/ars/scripts/chinese_literature_client.py +1938 -0
- package/vendor/ars/scripts/citation_verification_summary.py +85 -0
- package/vendor/ars/scripts/claim_audit_calibration.py +517 -0
- package/vendor/ars/scripts/claim_audit_finalizer.py +456 -0
- package/vendor/ars/scripts/claim_audit_pipeline.py +1594 -0
- package/vendor/ars/scripts/claim_registry_coverage.py +493 -0
- package/vendor/ars/scripts/claim_standing_discovery.py +784 -0
- package/vendor/ars/scripts/claim_standing_stance_runner.py +758 -0
- package/vendor/ars/scripts/claim_standing_stance_scorer.py +239 -0
- package/vendor/ars/scripts/claim_strength_drift_disposition.py +666 -0
- package/vendor/ars/scripts/contamination_signals.py +689 -0
- package/vendor/ars/scripts/corpus_consumer_manifest.json +19 -0
- package/vendor/ars/scripts/cross_model_codex_transport.py +1374 -0
- package/vendor/ars/scripts/cross_model_codex_verify.sh +6 -0
- package/vendor/ars/scripts/cross_model_handoff.py +359 -0
- package/vendor/ars/scripts/cross_model_smoke_test.sh +183 -0
- package/vendor/ars/scripts/cross_model_smoke_test_codex.sh +35 -0
- package/vendor/ars/scripts/cross_model_verification/gemini_is_grounded.jq +45 -0
- package/vendor/ars/scripts/cross_model_verification/gemini_sources.jq +45 -0
- package/vendor/ars/scripts/cross_model_verification/normalize_compat_verdict.py +57 -0
- package/vendor/ars/scripts/cross_model_verification/openai_has_completed_web_search.jq +15 -0
- package/vendor/ars/scripts/cross_model_verification/openai_sources.jq +17 -0
- package/vendor/ars/scripts/cross_model_verification/openai_text.jq +11 -0
- package/vendor/ars/scripts/crossref_client.py +225 -0
- package/vendor/ars/scripts/dispatch_e4_panel.py +2731 -0
- package/vendor/ars/scripts/evidence_rows.py +2043 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3.json +32 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_input.json +27 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime.json +32 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime_input.json +27 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override.json +50 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override_action.json +14 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_pass.json +44 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/explicit_user_request_log.json +26 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/mandatory_checkpoint_log.json +116 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/README.md +56 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +24 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a7_orphan_completion/2026-04-30T15-22-04Z-d8f3.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +26 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +24 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.verdict.yaml +12 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction.json +46 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction_check_attestation.json +44 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase.json +44 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_abstract_missing.json +107 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_detected.json +124 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/expected_outcomes.json +77 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/manifest.yaml +37 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/001-valid-doi-test.json +20 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/002-valid-arxiv-test.json +19 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/003-fabricated-test.json +20 -0
- package/vendor/ars/scripts/fixtures/claim_audit_calibration/gold_set.json +344 -0
- package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/query_plan.json +108 -0
- package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/retrieval_input.json +179 -0
- package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/concern_tracker.json +129 -0
- package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/response_skeleton.md +19 -0
- package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/source_letter.txt +9 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_draft.json +48 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_inventory.json +38 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/packet/consent.txt +1 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/session_sources.json +3 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/README.md +6 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/accepted_draft.md +35 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/cases.json +75 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/preregistration.md +5 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/forbidden_event.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/grounded_verified.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/malformed.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/missing_search.jsonl +2 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/multiple_finals.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/not_found.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/unbound_source.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/wrong_search_shape.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/evidence_rows/phase_e_inputs.json +114 -0
- package/vendor/ars/scripts/fixtures/evidence_rows/session_sources.json +5 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/cross-border-us-tw-gdpr.json +71 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/gdpr-member-state-unresolved.json +62 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/missing-data-axis.json +45 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/no-profile.json +33 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/tw-gdpr-two-axis.json +74 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/us-gdpr-two-axis.json +74 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/lint-near-misses.json +72 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/no-profile-request.json +18 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/tw-candidates-request.json +63 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/us-candidates-request.json +77 -0
- package/vendor/ars/scripts/fixtures/review_target_context/exact-declaration.json +24 -0
- package/vendor/ars/scripts/fixtures/review_target_context/field-general-declaration.json +24 -0
- package/vendor/ars/scripts/fixtures/review_target_context/msr-2027-technical-full-declaration.json +24 -0
- package/vendor/ars/scripts/fixtures/review_target_context/synthetic-registry.json +92 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/README.md +21 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_attested_only.json +55 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_machine_supported.json +49 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_not_isolated.json +56 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_unknown.json +49 -0
- package/vendor/ars/scripts/fixtures/submission_package/clean/paper.md +16 -0
- package/vendor/ars/scripts/fixtures/submission_package/clean/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/paper.md +11 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/paper.tex +9 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/marker_no_join/paper.md +7 -0
- package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/paper.md +8 -0
- package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/references.bib +6 -0
- package/vendor/ars/scripts/fixtures/submission_package/passports/corpus_only.yaml +9 -0
- package/vendor/ars/scripts/fixtures/submission_package/passports/summary_join.yaml +16 -0
- package/vendor/ars/scripts/fixtures/submission_package/profiles/full.yaml +15 -0
- package/vendor/ars/scripts/fixtures/submission_package/profiles/tight.yaml +15 -0
- package/vendor/ars/scripts/fixtures/submission_package/summary_join/paper.md +7 -0
- package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/paper.md +9 -0
- package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_clean/paper.md +26 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_clean/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_violations/paper.md +20 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_violations/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/base_inventory.json +101 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/consent-materials.txt +3 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/training-certificate.txt +2 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/tw-consent-materials.txt +3 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/corpus_input.yaml +30 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.md +36 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.tex +21 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/seed_expectations.json +218 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot.json +164 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot_manifest.json +29 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/README.md +55 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/empty_feed.xml +10 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/error_5xx.html +7 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/id_hit.xml +26 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/README.md +69 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/cnki_landing_page.html +13 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/error_5xx.html +2 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_ambiguous.json +16 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_hit.json +16 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_zero.json +14 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_hit.json +15 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_zero.json +13 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_hit.json +43 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_issn_mismatch.json +31 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_no_doi.json +31 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_unknown_ra.json +34 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_year_mismatch.json +31 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_absent.json +4 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_exists.json +16 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_internal_error.json +4 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_hit.json +25 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_other_title.json +18 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_cnki.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_crossref.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_istic.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_unknown_prefix.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/crossref/doi_hit.json +24 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/crossref/error_5xx.html +7 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/crossref/title_search_miss.json +15 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/openalex/doi_hit.json +29 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/openalex/error_5xx.json +4 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/openalex/title_search_miss.json +11 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/doi_hit.json +14 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/error_5xx.json +3 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/title_search_miss.json +5 -0
- package/vendor/ars/scripts/human_read_attestation_resolver.py +467 -0
- package/vendor/ars/scripts/ideation_diversity_assignment_gate.py +430 -0
- package/vendor/ars/scripts/inquiry_branch_ledger.py +2540 -0
- package/vendor/ars/scripts/legacy/ars_apply_revision_patch_v1_0.py +715 -0
- package/vendor/ars/scripts/legacy/check_re_review_synthesis_v1_0.py +2215 -0
- package/vendor/ars/scripts/migrate_literature_corpus_to_v3_10.py +223 -0
- package/vendor/ars/scripts/migrate_literature_corpus_to_v3_7_3.py +277 -0
- package/vendor/ars/scripts/migrate_literature_corpus_to_v3_9_0.py +304 -0
- package/vendor/ars/scripts/model_tiering_manifest.json +46 -0
- package/vendor/ars/scripts/openalex_client.py +232 -0
- package/vendor/ars/scripts/parse_audit_verdict.py +802 -0
- package/vendor/ars/scripts/pdf_content_classifier_worker.py +176 -0
- package/vendor/ars/scripts/pdf_read_preflight.py +1455 -0
- package/vendor/ars/scripts/policy_anchor_disclosure_referee.py +354 -0
- package/vendor/ars/scripts/recompute_receipts.py +1414 -0
- package/vendor/ars/scripts/render_claim_standing_view.py +374 -0
- package/vendor/ars/scripts/render_eval_comment.py +130 -0
- package/vendor/ars/scripts/render_harness_retirement_issue.py +167 -0
- package/vendor/ars/scripts/repro_lock_validation.py +90 -0
- package/vendor/ars/scripts/research_workflow_profile.py +1079 -0
- package/vendor/ars/scripts/resolve_human_subjects_authority.py +1158 -0
- package/vendor/ars/scripts/resolve_review_target_context.py +683 -0
- package/vendor/ars/scripts/resume_e4_record.py +509 -0
- package/vendor/ars/scripts/retraction_status.py +484 -0
- package/vendor/ars/scripts/review_criteria_binding.py +889 -0
- package/vendor/ars/scripts/review_panel_provenance.py +744 -0
- package/vendor/ars/scripts/revision_roadmap.py +1967 -0
- package/vendor/ars/scripts/run_ci_pytest_manifest.py +116 -0
- package/vendor/ars/scripts/run_codex_audit.sh +1191 -0
- package/vendor/ars/scripts/run_evals.py +513 -0
- package/vendor/ars/scripts/run_ideation_diversity_no_call.py +3505 -0
- package/vendor/ars/scripts/run_indirect_prompt_injection_no_call.py +3330 -0
- package/vendor/ars/scripts/run_indirect_prompt_injection_probe.py +399 -0
- package/vendor/ars/scripts/run_review_criteria_constructive_value.py +1895 -0
- package/vendor/ars/scripts/run_role_topology_utility_dry_run.py +606 -0
- package/vendor/ars/scripts/score_review_criteria_constructive_value.py +617 -0
- package/vendor/ars/scripts/semantic_scholar_client.py +291 -0
- package/vendor/ars/scripts/slr_lineage.py +59 -0
- package/vendor/ars/scripts/sync_adapter_docs.py +118 -0
- package/vendor/ars/scripts/temporal_integrity_audit.py +840 -0
- package/vendor/ars/scripts/test_431_exact_or_bust.py +253 -0
- package/vendor/ars/scripts/test__eval_threshold_gate.py +125 -0
- package/vendor/ars/scripts/test__markdown_lint_util.py +129 -0
- package/vendor/ars/scripts/test__next_verified_at_ms.py +256 -0
- package/vendor/ars/scripts/test_adjacent_framing_probe_lint.py +171 -0
- package/vendor/ars/scripts/test_adjudication_activity.py +1515 -0
- package/vendor/ars/scripts/test_ars_anchorize_draft.py +178 -0
- package/vendor/ars/scripts/test_ars_apply_revision_patch.py +1335 -0
- package/vendor/ars/scripts/test_ars_cache_invalidate.py +51 -0
- package/vendor/ars/scripts/test_ars_mark_read.py +910 -0
- package/vendor/ars/scripts/test_ars_update_check.py +816 -0
- package/vendor/ars/scripts/test_ars_write_scope_guard.py +790 -0
- package/vendor/ars/scripts/test_arxiv_client.py +374 -0
- package/vendor/ars/scripts/test_audit_schemas.py +560 -0
- package/vendor/ars/scripts/test_audit_snapshot_render_section_0.py +105 -0
- package/vendor/ars/scripts/test_block_parser.py +259 -0
- package/vendor/ars/scripts/test_bootstrap_timeline_yaml.py +148 -0
- package/vendor/ars/scripts/test_build_claim_standing_candidate_ledger.py +1203 -0
- package/vendor/ars/scripts/test_build_claim_standing_query_plan.py +791 -0
- package/vendor/ars/scripts/test_build_submission_packet_manifest.py +2644 -0
- package/vendor/ars/scripts/test_check_215_field_norm.py +238 -0
- package/vendor/ars/scripts/test_check_216_surface_form.py +341 -0
- package/vendor/ars/scripts/test_check_268_nested_commitment_ledger.py +194 -0
- package/vendor/ars/scripts/test_check_390_revision_patch_discipline.py +279 -0
- package/vendor/ars/scripts/test_check_392_citation_verification_intake.py +144 -0
- package/vendor/ars/scripts/test_check_394_submission_policy.py +200 -0
- package/vendor/ars/scripts/test_check_439_format_profile.py +252 -0
- package/vendor/ars/scripts/test_check_619_disclosure_closeout.py +158 -0
- package/vendor/ars/scripts/test_check_630_codex_subscription_transport.py +245 -0
- package/vendor/ars/scripts/test_check_669_review_pathway_rule_trace.py +304 -0
- package/vendor/ars/scripts/test_check_670_revision_roadmap_integration.py +260 -0
- package/vendor/ars/scripts/test_check_673_adjudication_activity.py +367 -0
- package/vendor/ars/scripts/test_check_684_review_criteria_binding.py +274 -0
- package/vendor/ars/scripts/test_check_agents_mirror_sync.py +137 -0
- package/vendor/ars/scripts/test_check_audit_artifact_consistency.py +2133 -0
- package/vendor/ars/scripts/test_check_benchmark_report.py +117 -0
- package/vendor/ars/scripts/test_check_bibliographic_integrity_signals.py +609 -0
- package/vendor/ars/scripts/test_check_calibration_tiers.py +376 -0
- package/vendor/ars/scripts/test_check_changelog_covers_merges.py +509 -0
- package/vendor/ars/scripts/test_check_ci_pytest_manifest.py +425 -0
- package/vendor/ars/scripts/test_check_claim_standing_candidate_ledger_integration.py +256 -0
- package/vendor/ars/scripts/test_check_claim_standing_freshness.py +277 -0
- package/vendor/ars/scripts/test_check_collaboration_depth_rubric.py +239 -0
- package/vendor/ars/scripts/test_check_command_frontmatter_name.py +178 -0
- package/vendor/ars/scripts/test_check_committee_correspondence.py +298 -0
- package/vendor/ars/scripts/test_check_compliance_report.py +381 -0
- package/vendor/ars/scripts/test_check_content_coverage_advisory_integration.py +308 -0
- package/vendor/ars/scripts/test_check_control_availability.py +361 -0
- package/vendor/ars/scripts/test_check_cross_document_consistency_advisory_integration.py +698 -0
- package/vendor/ars/scripts/test_check_cross_model_handoff_contract.py +301 -0
- package/vendor/ars/scripts/test_check_cross_model_verification_sync.py +203 -0
- package/vendor/ars/scripts/test_check_data_access_level.py +227 -0
- package/vendor/ars/scripts/test_check_data_flows.py +382 -0
- package/vendor/ars/scripts/test_check_decision_contract.py +387 -0
- package/vendor/ars/scripts/test_check_degradation_registry.py +270 -0
- package/vendor/ars/scripts/test_check_distribution_surface_claims.py +225 -0
- package/vendor/ars/scripts/test_check_domain_evidence_profile.py +439 -0
- package/vendor/ars/scripts/test_check_e4_promotion.py +163 -0
- package/vendor/ars/scripts/test_check_evals_gold_set.py +312 -0
- package/vendor/ars/scripts/test_check_evidence_row_integration.py +187 -0
- package/vendor/ars/scripts/test_check_field_norm_severity.py +173 -0
- package/vendor/ars/scripts/test_check_firm_rules_sync.py +342 -0
- package/vendor/ars/scripts/test_check_heldout_measurement_report.py +1508 -0
- package/vendor/ars/scripts/test_check_human_subjects_output_contract.py +129 -0
- package/vendor/ars/scripts/test_check_human_subjects_reference_migration.py +756 -0
- package/vendor/ars/scripts/test_check_instruction_data_boundary.py +204 -0
- package/vendor/ars/scripts/test_check_judge_prompt_version.py +90 -0
- package/vendor/ars/scripts/test_check_model_tiering.py +236 -0
- package/vendor/ars/scripts/test_check_panel_synthesis.py +1658 -0
- package/vendor/ars/scripts/test_check_passport_reset_contract.py +249 -0
- package/vendor/ars/scripts/test_check_pattern_eval_manifest.py +381 -0
- package/vendor/ars/scripts/test_check_persuasion_invariance_fixtures.py +591 -0
- package/vendor/ars/scripts/test_check_phase_conformance.py +4366 -0
- package/vendor/ars/scripts/test_check_pipeline_boundary_semantics.py +931 -0
- package/vendor/ars/scripts/test_check_pipeline_integrity.py +243 -0
- package/vendor/ars/scripts/test_check_policy_anchor_protocol.py +236 -0
- package/vendor/ars/scripts/test_check_policy_anchor_table.py +295 -0
- package/vendor/ars/scripts/test_check_prisma_trAIce_freshness.py +68 -0
- package/vendor/ars/scripts/test_check_promotion_bakeoff_preregistration.py +799 -0
- package/vendor/ars/scripts/test_check_ranking_lift.py +377 -0
- package/vendor/ars/scripts/test_check_re_review_synthesis.py +3398 -0
- package/vendor/ars/scripts/test_check_receipt_enum_sync.py +176 -0
- package/vendor/ars/scripts/test_check_repro_lock.py +107 -0
- package/vendor/ars/scripts/test_check_reviewer_data_fences.py +334 -0
- package/vendor/ars/scripts/test_check_reviewer_finding_contract.py +1107 -0
- package/vendor/ars/scripts/test_check_reviewer_role_label.py +491 -0
- package/vendor/ars/scripts/test_check_reviewer_scoring_honesty.py +222 -0
- package/vendor/ars/scripts/test_check_reviewer_sprint_prompt_sync.py +373 -0
- package/vendor/ars/scripts/test_check_revision_claim_drift_suite_v2.py +1925 -0
- package/vendor/ars/scripts/test_check_revision_token_conservation.py +398 -0
- package/vendor/ars/scripts/test_check_risk_register.py +336 -0
- package/vendor/ars/scripts/test_check_role_scoped_contract.py +1104 -0
- package/vendor/ars/scripts/test_check_rq_framing_patterns.py +110 -0
- package/vendor/ars/scripts/test_check_rubric_weight_consistency.py +32 -0
- package/vendor/ars/scripts/test_check_seeded_defect_fixtures.py +392 -0
- package/vendor/ars/scripts/test_check_setup_cross_model_parity.py +121 -0
- package/vendor/ars/scripts/test_check_spec_consistency.py +1081 -0
- package/vendor/ars/scripts/test_check_sprint_contract.py +458 -0
- package/vendor/ars/scripts/test_check_stage_capability_matrix.py +698 -0
- package/vendor/ars/scripts/test_check_submission_packet_manifest_integration.py +177 -0
- package/vendor/ars/scripts/test_check_surface_form_parity.py +417 -0
- package/vendor/ars/scripts/test_check_task_type.py +116 -0
- package/vendor/ars/scripts/test_check_tools_allowlist.py +835 -0
- package/vendor/ars/scripts/test_check_tortured_phrase_screening_integration.py +1306 -0
- package/vendor/ars/scripts/test_check_v3_10_134_write_scope.py +251 -0
- package/vendor/ars/scripts/test_check_v3_10_policy.py +547 -0
- package/vendor/ars/scripts/test_check_v3_6_7_pattern_protection.py +960 -0
- package/vendor/ars/scripts/test_check_v3_6_8_audit_scope_block.py +1000 -0
- package/vendor/ars/scripts/test_check_v3_6_8_cite_provenance_pipeline.py +454 -0
- package/vendor/ars/scripts/test_check_v3_6_8_frontmatter_trust_schema.py +581 -0
- package/vendor/ars/scripts/test_check_v3_6_8_mark_read_commands.py +120 -0
- package/vendor/ars/scripts/test_check_v3_6_8_pattern_protection.py +1138 -0
- package/vendor/ars/scripts/test_check_v3_7_3_three_layer_citation.py +566 -0
- package/vendor/ars/scripts/test_check_v3_8_annotation_literal_sync.py +262 -0
- package/vendor/ars/scripts/test_check_v3_9_0_triangulation.py +321 -0
- package/vendor/ars/scripts/test_check_v3_9_2_phase_boundary.py +190 -0
- package/vendor/ars/scripts/test_check_v3_9_4_temporal_verification.py +471 -0
- package/vendor/ars/scripts/test_check_version_consistency.py +1462 -0
- package/vendor/ars/scripts/test_check_workflow_classification.py +225 -0
- package/vendor/ars/scripts/test_chinese_literature_client.py +1889 -0
- package/vendor/ars/scripts/test_citation_existence_policy.py +480 -0
- package/vendor/ars/scripts/test_citation_verification_summary.py +342 -0
- package/vendor/ars/scripts/test_claim_audit_calibration.py +882 -0
- package/vendor/ars/scripts/test_claim_audit_finalizer.py +979 -0
- package/vendor/ars/scripts/test_claim_audit_pipeline.py +2398 -0
- package/vendor/ars/scripts/test_claim_audit_schema.py +1778 -0
- package/vendor/ars/scripts/test_claim_intent_manifest.py +666 -0
- package/vendor/ars/scripts/test_claim_registry_coverage.py +246 -0
- package/vendor/ars/scripts/test_claim_standing_discovery.py +579 -0
- package/vendor/ars/scripts/test_claim_standing_pipeline_wiring.py +290 -0
- package/vendor/ars/scripts/test_claim_standing_stance_assets.py +399 -0
- package/vendor/ars/scripts/test_claim_standing_stance_contracts.py +383 -0
- package/vendor/ars/scripts/test_claim_standing_stance_runner.py +349 -0
- package/vendor/ars/scripts/test_claim_standing_transmissions.py +605 -0
- package/vendor/ars/scripts/test_claim_strength_drift_disposition.py +517 -0
- package/vendor/ars/scripts/test_claim_verification_coverage_contract.py +104 -0
- package/vendor/ars/scripts/test_contamination_signals.py +1086 -0
- package/vendor/ars/scripts/test_content_coverage_advisory.py +1767 -0
- package/vendor/ars/scripts/test_cross_document_consistency_advisory.py +1270 -0
- package/vendor/ars/scripts/test_cross_model_codex_transport.py +1152 -0
- package/vendor/ars/scripts/test_cross_model_handoff.py +571 -0
- package/vendor/ars/scripts/test_cross_model_verification_guards.py +792 -0
- package/vendor/ars/scripts/test_crossref_client.py +393 -0
- package/vendor/ars/scripts/test_dispatch_e4_panel.py +4235 -0
- package/vendor/ars/scripts/test_e2e_claim_audit.py +540 -0
- package/vendor/ars/scripts/test_eval_harness_workflow.py +140 -0
- package/vendor/ars/scripts/test_evals_citation_extraction.py +150 -0
- package/vendor/ars/scripts/test_evals_lift_report_schema.py +108 -0
- package/vendor/ars/scripts/test_evidence_rows.py +2593 -0
- package/vendor/ars/scripts/test_experiment_provenance.py +915 -0
- package/vendor/ars/scripts/test_human_read_attestation_resolver.py +477 -0
- package/vendor/ars/scripts/test_ideation_diversity_assignment_gate.py +615 -0
- package/vendor/ars/scripts/test_indirect_prompt_injection_behavior_probe.py +250 -0
- package/vendor/ars/scripts/test_inquiry_branch_ledger.py +2296 -0
- package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_10.py +248 -0
- package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_7_3.py +545 -0
- package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_9_0.py +497 -0
- package/vendor/ars/scripts/test_normalize_compat_verdict.py +149 -0
- package/vendor/ars/scripts/test_openalex_client.py +490 -0
- package/vendor/ars/scripts/test_passport_yaml.py +104 -0
- package/vendor/ars/scripts/test_pattern_eval_runtime.py +1295 -0
- package/vendor/ars/scripts/test_pdf_read_preflight.py +1943 -0
- package/vendor/ars/scripts/test_policy_anchor_disclosure.py +666 -0
- package/vendor/ars/scripts/test_reading_probe_lint.py +218 -0
- package/vendor/ars/scripts/test_recompute_receipts.py +778 -0
- package/vendor/ars/scripts/test_render_claim_standing_view.py +192 -0
- package/vendor/ars/scripts/test_render_eval_comment.py +162 -0
- package/vendor/ars/scripts/test_render_harness_retirement_issue.py +110 -0
- package/vendor/ars/scripts/test_repro_lock_validation_drift.py +100 -0
- package/vendor/ars/scripts/test_research_workflow_profile.py +734 -0
- package/vendor/ars/scripts/test_resolve_human_subjects_authority.py +1219 -0
- package/vendor/ars/scripts/test_resolve_review_target_context.py +703 -0
- package/vendor/ars/scripts/test_resume_e4_record.py +315 -0
- package/vendor/ars/scripts/test_retraction_status.py +456 -0
- package/vendor/ars/scripts/test_review_criteria_binding.py +629 -0
- package/vendor/ars/scripts/test_review_panel_provenance.py +565 -0
- package/vendor/ars/scripts/test_review_pathway_rule_trace.py +821 -0
- package/vendor/ars/scripts/test_revision_roadmap.py +1255 -0
- package/vendor/ars/scripts/test_run_ci_pytest_manifest.py +186 -0
- package/vendor/ars/scripts/test_run_codex_audit_e2e.py +369 -0
- package/vendor/ars/scripts/test_run_evals.py +430 -0
- package/vendor/ars/scripts/test_run_guard_launcher.py +500 -0
- package/vendor/ars/scripts/test_run_ideation_diversity_no_call.py +1833 -0
- package/vendor/ars/scripts/test_run_indirect_prompt_injection_no_call.py +1889 -0
- package/vendor/ars/scripts/test_run_review_criteria_constructive_value.py +586 -0
- package/vendor/ars/scripts/test_run_role_topology_utility_dry_run.py +428 -0
- package/vendor/ars/scripts/test_score_review_criteria_constructive_value.py +340 -0
- package/vendor/ars/scripts/test_semantic_scholar_client.py +554 -0
- package/vendor/ars/scripts/test_slr_lineage_emission.py +230 -0
- package/vendor/ars/scripts/test_socratic_rq_non_generation_contract.py +173 -0
- package/vendor/ars/scripts/test_temporal_integrity_audit.py +438 -0
- package/vendor/ars/scripts/test_text_similarity.py +95 -0
- package/vendor/ars/scripts/test_title_fuzzy_false_positive.py +111 -0
- package/vendor/ars/scripts/test_tortured_phrase_screening.py +2959 -0
- package/vendor/ars/scripts/test_transport_fixture_citation_gate.py +338 -0
- package/vendor/ars/scripts/test_uncited_assertion.py +558 -0
- package/vendor/ars/scripts/test_v3_6_7_phase_6_6.py +1279 -0
- package/vendor/ars/scripts/test_validate_compliance_fixtures.py +36 -0
- package/vendor/ars/scripts/test_validate_ideation_diversity_assets.py +230 -0
- package/vendor/ars/scripts/test_venue_disclosure_contract.py +755 -0
- package/vendor/ars/scripts/test_verification_cache.py +280 -0
- package/vendor/ars/scripts/test_verification_gate.py +461 -0
- package/vendor/ars/scripts/test_verify_passport_cli.py +123 -0
- package/vendor/ars/scripts/test_verify_submission_package.py +1407 -0
- package/vendor/ars/scripts/test_version_records_schema.py +211 -0
- package/vendor/ars/scripts/tortured_phrase_screening.py +3502 -0
- package/vendor/ars/scripts/uncited_assertion_detector.py +254 -0
- package/vendor/ars/scripts/v3_6_7_inversion_manifest.json +9 -0
- package/vendor/ars/scripts/v3_6_8_inversion_manifest.json +10 -0
- package/vendor/ars/scripts/validate_claim_standing_stance_assets.py +249 -0
- package/vendor/ars/scripts/validate_compliance_fixtures.py +56 -0
- package/vendor/ars/scripts/validate_ideation_diversity_assets.py +303 -0
- package/vendor/ars/scripts/venue_disclosure_contract_harness.py +837 -0
- package/vendor/ars/scripts/verification_cache.py +276 -0
- package/vendor/ars/scripts/verification_gate/__init__.py +345 -0
- package/vendor/ars/scripts/verify_passport.py +133 -0
- package/vendor/ars/scripts/verify_submission_package.py +1657 -0
- package/vendor/ars/shared/agents/compliance_agent.md +136 -0
- package/vendor/ars/shared/artifact_reproducibility_pattern.md +173 -0
- package/vendor/ars/shared/benchmark_report.schema.json +81 -0
- package/vendor/ars/shared/benchmark_report_pattern.md +180 -0
- package/vendor/ars/shared/bibliographic_integrity_signals.md +142 -0
- package/vendor/ars/shared/collaboration_depth_rubric.md +154 -0
- package/vendor/ars/shared/compliance_checkpoint_protocol.md +162 -0
- package/vendor/ars/shared/compliance_report.schema.json +187 -0
- package/vendor/ars/shared/contracts/README.md +938 -0
- package/vendor/ars/shared/contracts/activity/adjudication_activity_input.schema.json +555 -0
- package/vendor/ars/shared/contracts/activity/adjudication_activity_store.schema.json +560 -0
- package/vendor/ars/shared/contracts/audit/audit_jsonl.schema.json +128 -0
- package/vendor/ars/shared/contracts/audit/audit_sidecar.schema.json +169 -0
- package/vendor/ars/shared/contracts/audit/audit_verdict.schema.json +133 -0
- package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory.schema.json +480 -0
- package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory_draft.schema.json +564 -0
- package/vendor/ars/shared/contracts/audit/cross_document_source_manifest.schema.json +202 -0
- package/vendor/ars/shared/contracts/audit/tortured_phrase_advisory.schema.json +1362 -0
- package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot.schema.json +208 -0
- package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot_manifest.schema.json +335 -0
- package/vendor/ars/shared/contracts/capability/stage_capability_matrix.json +434 -0
- package/vendor/ars/shared/contracts/claim_standing/candidate_ledger.schema.json +705 -0
- package/vendor/ars/shared/contracts/claim_standing/query_plan.schema.json +389 -0
- package/vendor/ars/shared/contracts/claim_standing/query_plan_v1_1.schema.json +782 -0
- package/vendor/ars/shared/contracts/claim_standing/retrieval_input.schema.json +440 -0
- package/vendor/ars/shared/contracts/claim_standing/stance_record.schema.json +460 -0
- package/vendor/ars/shared/contracts/claim_standing/transmission_ledger.schema.json +282 -0
- package/vendor/ars/shared/contracts/cross_model/codex_citation_receipt.schema.json +157 -0
- package/vendor/ars/shared/contracts/cross_model/codex_citation_request.schema.json +23 -0
- package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_commitment.schema.json +55 -0
- package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_reveal.schema.json +50 -0
- package/vendor/ars/shared/contracts/degradation_registry.json +428 -0
- package/vendor/ars/shared/contracts/evaluator/full.json +126 -0
- package/vendor/ars/shared/contracts/evidence/claim_registry.schema.json +50 -0
- package/vendor/ars/shared/contracts/evidence/claim_registry_coverage_report.schema.json +79 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row.schema.json +504 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row_v1_1.schema.json +364 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row_v1_2.schema.json +714 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row_v1_3.schema.json +524 -0
- package/vendor/ars/shared/contracts/human_subjects/authority_profile_registry.schema.json +497 -0
- package/vendor/ars/shared/contracts/human_subjects/committee_correspondence.schema.json +253 -0
- package/vendor/ars/shared/contracts/human_subjects/content_coverage_advisory.schema.json +620 -0
- package/vendor/ars/shared/contracts/human_subjects/irb_context_record.schema.json +329 -0
- package/vendor/ars/shared/contracts/human_subjects/resolved_authority_context.schema.json +347 -0
- package/vendor/ars/shared/contracts/human_subjects/review_pathway_rule_trace.schema.json +265 -0
- package/vendor/ars/shared/contracts/human_subjects/review_pathway_trace_request.schema.json +127 -0
- package/vendor/ars/shared/contracts/human_subjects/submission_packet_inventory.schema.json +240 -0
- package/vendor/ars/shared/contracts/human_subjects/submission_packet_manifest.schema.json +999 -0
- package/vendor/ars/shared/contracts/passport/audit_artifact_entry.schema.json +266 -0
- package/vendor/ars/shared/contracts/passport/bibliographic_integrity_signal.schema.json +1676 -0
- package/vendor/ars/shared/contracts/passport/citation_provenance.schema.json +107 -0
- package/vendor/ars/shared/contracts/passport/citation_verification_summary.schema.json +164 -0
- package/vendor/ars/shared/contracts/passport/claim_audit_result.schema.json +124 -0
- package/vendor/ars/shared/contracts/passport/claim_drift.schema.json +58 -0
- package/vendor/ars/shared/contracts/passport/claim_intent_manifest.schema.json +107 -0
- package/vendor/ars/shared/contracts/passport/constraint_violation.schema.json +65 -0
- package/vendor/ars/shared/contracts/passport/experiment_alignment_result.schema.json +69 -0
- package/vendor/ars/shared/contracts/passport/experiment_provenance_entry.schema.json +169 -0
- package/vendor/ars/shared/contracts/passport/human_read_log.schema.json +86 -0
- package/vendor/ars/shared/contracts/passport/inquiry_ledger_ref.schema.json +22 -0
- package/vendor/ars/shared/contracts/passport/literature_corpus_entry.schema.json +650 -0
- package/vendor/ars/shared/contracts/passport/preregistration_artifact.schema.json +132 -0
- package/vendor/ars/shared/contracts/passport/rejection_log.schema.json +89 -0
- package/vendor/ars/shared/contracts/passport/reset_ledger_entry.schema.json +158 -0
- package/vendor/ars/shared/contracts/passport/temporal_audit_results.schema.json +208 -0
- package/vendor/ars/shared/contracts/passport/terminal_policies.schema.json +50 -0
- package/vendor/ars/shared/contracts/passport/timeline.schema.json +102 -0
- package/vendor/ars/shared/contracts/passport/uncited_assertion.schema.json +56 -0
- package/vendor/ars/shared/contracts/passport/uncited_audit_failure.schema.json +72 -0
- package/vendor/ars/shared/contracts/passport/user_attested_read_resolution.schema.json +87 -0
- package/vendor/ars/shared/contracts/passport/version_records.schema.json +138 -0
- package/vendor/ars/shared/contracts/patch/block_manifest.schema.json +45 -0
- package/vendor/ars/shared/contracts/patch/legacy/v1_0/revision_patch.schema.json +110 -0
- package/vendor/ars/shared/contracts/patch/revision_patch.schema.json +250 -0
- package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_diagnostic.schema.json +39 -0
- package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_worker.schema.json +88 -0
- package/vendor/ars/shared/contracts/pdf/pdf_read_preflight.schema.json +227 -0
- package/vendor/ars/shared/contracts/re_review/input_manifest.schema.json +161 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/input_manifest.schema.json +134 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/precommitment.schema.json +193 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/traceability.schema.json +841 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/verdict_record.schema.json +244 -0
- package/vendor/ars/shared/contracts/re_review/precommitment.schema.json +193 -0
- package/vendor/ars/shared/contracts/re_review/traceability.schema.json +916 -0
- package/vendor/ars/shared/contracts/re_review/verdict_record.schema.json +245 -0
- package/vendor/ars/shared/contracts/research_workflow/inquiry_branch_ledger.schema.json +449 -0
- package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile.schema.json +222 -0
- package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile_selection_receipt.schema.json +111 -0
- package/vendor/ars/shared/contracts/review_target/constructive_review_findings.schema.json +172 -0
- package/vendor/ars/shared/contracts/review_target/criteria_registry.schema.json +97 -0
- package/vendor/ars/shared/contracts/review_target/review_criteria_binding_manifest.schema.json +235 -0
- package/vendor/ars/shared/contracts/review_target/review_criteria_source_receipt.schema.json +158 -0
- package/vendor/ars/shared/contracts/review_target/review_target_context.schema.json +106 -0
- package/vendor/ars/shared/contracts/review_target/review_target_declaration.schema.json +118 -0
- package/vendor/ars/shared/contracts/reviewer/full.json +114 -0
- package/vendor/ars/shared/contracts/reviewer/methodology_focus.json +75 -0
- package/vendor/ars/shared/contracts/reviewer/review_panel_provenance.schema.json +263 -0
- package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_carrier.schema.json +122 -0
- package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_input.schema.json +120 -0
- package/vendor/ars/shared/contracts/revision/author_adjudication.schema.json +251 -0
- package/vendor/ars/shared/contracts/revision/author_adjudication_input.schema.json +25 -0
- package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition.schema.json +97 -0
- package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition_input.schema.json +80 -0
- package/vendor/ars/shared/contracts/revision/claim_strength_drift_findings.schema.json +139 -0
- package/vendor/ars/shared/contracts/revision/claim_surface_manifest.schema.json +99 -0
- package/vendor/ars/shared/contracts/revision/integrity_correction_authorization.schema.json +140 -0
- package/vendor/ars/shared/contracts/revision/integrity_correction_authorization_input.schema.json +28 -0
- package/vendor/ars/shared/contracts/revision/integrity_correction_list.schema.json +48 -0
- package/vendor/ars/shared/contracts/revision/integrity_pass_receipt.schema.json +16 -0
- package/vendor/ars/shared/contracts/revision/revision_evidence_bundle.schema.json +134 -0
- package/vendor/ars/shared/contracts/revision/revision_roadmap.schema.json +334 -0
- package/vendor/ars/shared/contracts/submission/format_profile.example.yaml +33 -0
- package/vendor/ars/shared/contracts/submission/format_profile.schema.json +102 -0
- package/vendor/ars/shared/contracts/submission/submission_verification_report.schema.json +233 -0
- package/vendor/ars/shared/contracts/submission/venue_profile.schema.json +114 -0
- package/vendor/ars/shared/contracts/writer/full.json +87 -0
- package/vendor/ars/shared/cross_model_verification.md +714 -0
- package/vendor/ars/shared/evals_lift_report.schema.json +141 -0
- package/vendor/ars/shared/ground_truth_isolation_pattern.md +275 -0
- package/vendor/ars/shared/handoff_schemas.md +1209 -0
- package/vendor/ars/shared/human_subjects_authority_registry.json +1278 -0
- package/vendor/ars/shared/mode_spectrum.md +57 -0
- package/vendor/ars/shared/model_tiering.md +83 -0
- package/vendor/ars/shared/policy_data/nature_policy.md +56 -0
- package/vendor/ars/shared/prisma_trAIce_protocol.md +157 -0
- package/vendor/ars/shared/raise_framework.md +129 -0
- package/vendor/ars/shared/references/authority_content_coverage_advisory_protocol.md +275 -0
- package/vendor/ars/shared/references/claim_standing_candidate_ledger_protocol.md +66 -0
- package/vendor/ars/shared/references/claim_strength_ladder.md +93 -0
- package/vendor/ars/shared/references/cross_document_consistency_advisory_protocol.md +263 -0
- package/vendor/ars/shared/references/evidence_row_protocol.md +260 -0
- package/vendor/ars/shared/references/firm_rules.md +90 -0
- package/vendor/ars/shared/references/human_subjects_authority_protocol.md +274 -0
- package/vendor/ars/shared/references/intent_clarification_protocol.md +168 -0
- package/vendor/ars/shared/references/irb_terminology_glossary.md +229 -0
- package/vendor/ars/shared/references/protected_hedging_phrases.md +118 -0
- package/vendor/ars/shared/references/psychometric_terminology_glossary.md +109 -0
- package/vendor/ars/shared/references/review_criteria_consumer_protocol.md +238 -0
- package/vendor/ars/shared/references/review_pathway_rule_trace_protocol.md +166 -0
- package/vendor/ars/shared/references/submission_packet_manifest_protocol.md +292 -0
- package/vendor/ars/shared/references/word_count_conventions.md +124 -0
- package/vendor/ars/shared/research_workflow_profiles/field_general.json +1 -0
- package/vendor/ars/shared/review_criteria_registry.json +207 -0
- package/vendor/ars/shared/review_criteria_sources/msr-2027-technical-papers.2026-08-24.json +46 -0
- package/vendor/ars/shared/review_criteria_sources/sigsoft-empirical-standards.2026-08-24.json +31 -0
- package/vendor/ars/shared/sprint_contract.schema.json +482 -0
- package/vendor/ars/shared/style_calibration_protocol.md +151 -0
- package/vendor/ars/shared/templates/codex_audit_multifile_template.md +263 -0
- package/vendor/ars/tools/release-discipline/.toolkit-version +1 -0
- package/vendor/ars/tools/release-discipline/README.md +4 -0
- package/vendor/ars/tools/release-discipline/scripts/_release_doc_alignment_schema.py +1011 -0
- package/vendor/ars/tools/release-discipline/scripts/check_command_invariants.py +497 -0
- package/vendor/ars/tools/release-discipline/scripts/check_release_doc_alignment.py +263 -0
- package/vendor/ars/tools/release-discipline/scripts/sync-toolkit.sh +147 -0
- package/vendor/windows/NOTICE.md +12 -0
- package/vendor/windows/arm64/fd.exe +0 -0
- package/vendor/windows/arm64/licenses/fd/LICENSE-APACHE +201 -0
- package/vendor/windows/arm64/licenses/fd/LICENSE-MIT +21 -0
- package/vendor/windows/arm64/licenses/ripgrep/COPYING +3 -0
- package/vendor/windows/arm64/licenses/ripgrep/LICENSE-MIT +21 -0
- package/vendor/windows/arm64/licenses/ripgrep/UNLICENSE +24 -0
- package/vendor/windows/arm64/rg.exe +0 -0
- package/vendor/windows/x64/fd.exe +0 -0
- package/vendor/windows/x64/licenses/fd/LICENSE-APACHE +201 -0
- package/vendor/windows/x64/licenses/fd/LICENSE-MIT +21 -0
- package/vendor/windows/x64/licenses/ripgrep/COPYING +3 -0
- package/vendor/windows/x64/licenses/ripgrep/LICENSE-MIT +21 -0
- package/vendor/windows/x64/licenses/ripgrep/UNLICENSE +24 -0
- package/vendor/windows/x64/rg.exe +0 -0
|
@@ -0,0 +1,2180 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fail-closed Phase 1 -> Phase 2 conformance checker for reviewer Schema 13.2.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python scripts/check_phase_conformance.py --contract C.json --role eic \
|
|
6
|
+
--phase1 eic.phase1.md --phase2 eic.phase2.md \
|
|
7
|
+
--manuscript manuscript.md --metadata metadata.json
|
|
8
|
+
|
|
9
|
+
Exit 0 pass, 2 contract/infra failure, 3 reviewer conformance failure.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import html
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
import unicodedata
|
|
19
|
+
from collections import Counter
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
24
|
+
import check_panel_synthesis as panel # noqa: E402
|
|
25
|
+
import recompute_receipts as recompute # noqa: E402
|
|
26
|
+
|
|
27
|
+
EXIT_PASS = 0
|
|
28
|
+
EXIT_CONTRACT = 2
|
|
29
|
+
EXIT_CONFORMANCE = 3
|
|
30
|
+
_METADATA_KEYS = frozenset({"title", "field", "word_count"})
|
|
31
|
+
_FIELD_PATTERNS = {
|
|
32
|
+
"dimension_id": re.compile(r"^dimension_id: (?P<value>D\d+)$"),
|
|
33
|
+
"what_to_look_for": re.compile(r"^what_to_look_for: (?P<value>\S.*)$"),
|
|
34
|
+
"what_triggers_block": re.compile(
|
|
35
|
+
r"^what_triggers_block: (?P<value>\S.*)$"
|
|
36
|
+
),
|
|
37
|
+
"what_triggers_warn": re.compile(
|
|
38
|
+
r"^what_triggers_warn: (?P<value>\S.*)$"
|
|
39
|
+
),
|
|
40
|
+
"what_triggers_fatal": re.compile(
|
|
41
|
+
r"^what_triggers_fatal: (?P<value>\S.*)$"
|
|
42
|
+
),
|
|
43
|
+
}
|
|
44
|
+
_DISSENT_DIM_RE = re.compile(r"^dimension_id: (?P<dim>D\d+)\s*$")
|
|
45
|
+
_DISSENT_RATIONALE_RE = re.compile(r"^rationale: (?P<text>\S.*)\s*$")
|
|
46
|
+
_SEVERITY_RE = re.compile(
|
|
47
|
+
r"(?:^|\|\s*)\s*(?:[-*]\s*)?\*\*Severity\*\*:\s*"
|
|
48
|
+
r"(?P<severity>Critical|Major|Minor)\b"
|
|
49
|
+
)
|
|
50
|
+
_ANCHOR_RE = re.compile(
|
|
51
|
+
r"(?:^|\|\s*)\s*(?:[-*]\s*)?\*\*Evidence Anchor\*\*:\s*"
|
|
52
|
+
r"(?P<value>[^|]+)"
|
|
53
|
+
)
|
|
54
|
+
_SEVERITY_DECL_RE = re.compile(
|
|
55
|
+
r"\*\*Severity(?:\*\*)?\s*:",
|
|
56
|
+
re.IGNORECASE,
|
|
57
|
+
)
|
|
58
|
+
_ANCHOR_DECL_RE = re.compile(
|
|
59
|
+
r"\*\*Evidence Anchor(?:\*\*)?\s*:",
|
|
60
|
+
re.IGNORECASE,
|
|
61
|
+
)
|
|
62
|
+
_FINDING_H3_RE = re.compile(r"^W[1-9]\d*: \S.*$")
|
|
63
|
+
# --- #610 methodology arithmetic-receipt grammar -------------------------
|
|
64
|
+
# Receipt machine lines are lowercase snake_case on purpose: they can never
|
|
65
|
+
# collide with the finding grammar's bold `**Severity**:` / `**Evidence
|
|
66
|
+
# Anchor**:` declarations, so the receipt section cannot trip the
|
|
67
|
+
# outside-Review-Body severity gate and a finding cannot satisfy a receipt
|
|
68
|
+
# field. Decoration tolerance is bounded to the two shapes the finding
|
|
69
|
+
# grammar already accepts (one leading list marker, bold around the key).
|
|
70
|
+
# The section is read fence-transparently (#637/#638 display-form
|
|
71
|
+
# discipline): a fenced receipt line is still content, so a stylistic fence
|
|
72
|
+
# neither hides a receipt nor turns one into a false abort, and any other
|
|
73
|
+
# decoration of a field-shaped line is detected and aborts loudly instead of
|
|
74
|
+
# silently passing a forbidden-field guard or failing a required count.
|
|
75
|
+
_RECEIPT_SECTION = "Arithmetic Receipts"
|
|
76
|
+
_RECEIPT_H3_RE = re.compile(r"^AR[1-9]\d*$")
|
|
77
|
+
_RECEIPT_PROCEDURES = frozenset(
|
|
78
|
+
{"p_from_test_statistic", "grim", "grimmer", "n_from_df"}
|
|
79
|
+
)
|
|
80
|
+
_RECEIPT_STATUSES = frozenset(
|
|
81
|
+
{"consistent", "mismatch", "not_computable", "not_applicable"}
|
|
82
|
+
)
|
|
83
|
+
_NOT_COMPUTABLE_REASONS = frozenset({
|
|
84
|
+
"missing_reported_value",
|
|
85
|
+
"test_family_ambiguous",
|
|
86
|
+
"tail_ambiguous",
|
|
87
|
+
"nonstandard_p_procedure",
|
|
88
|
+
"inequality_unresolvable",
|
|
89
|
+
"rounding_rule_ambiguous",
|
|
90
|
+
"rounding_boundary_ambiguous",
|
|
91
|
+
"scale_granularity_unknown",
|
|
92
|
+
"scale_support_unknown",
|
|
93
|
+
"analytic_n_ambiguous",
|
|
94
|
+
"aggregation_or_weighting_unknown",
|
|
95
|
+
"sd_convention_unknown",
|
|
96
|
+
"mean_grim_inconsistent",
|
|
97
|
+
"df_identity_ambiguous",
|
|
98
|
+
"model_correction_or_pooling",
|
|
99
|
+
"reachability_not_completed",
|
|
100
|
+
})
|
|
101
|
+
_TAIL_CONVENTIONS = frozenset(
|
|
102
|
+
{"two-tailed", "one-tailed", "upper-tail", "unstated"}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _receipt_field_re(key: str) -> re.Pattern[str]:
|
|
107
|
+
# Bold is balanced-or-absent: a half-bold `**key:` is a decorated line
|
|
108
|
+
# for the shape guard to abort, never a canonical field (#610 round-2).
|
|
109
|
+
return re.compile(
|
|
110
|
+
rf"^(?:[-*] )?(?:{key}|\*\*{key}\*\*): (?P<value>\S.*?)\s*$"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
_RECEIPT_FIELD_RES = {
|
|
115
|
+
key: _receipt_field_re(key)
|
|
116
|
+
for key in (
|
|
117
|
+
"procedure_id",
|
|
118
|
+
"evidence_anchor",
|
|
119
|
+
"reported_inputs",
|
|
120
|
+
"assumptions",
|
|
121
|
+
"derivation",
|
|
122
|
+
"derived_value_or_range",
|
|
123
|
+
"comparison_rule",
|
|
124
|
+
"status",
|
|
125
|
+
"not_computable_reason",
|
|
126
|
+
"finding_ref",
|
|
127
|
+
"tail_convention",
|
|
128
|
+
"rounding_interval",
|
|
129
|
+
"nearest_achievable",
|
|
130
|
+
"df_identity",
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
_RECEIPT_ATTESTATION_RE = _receipt_field_re("no_recomputable_statistics")
|
|
134
|
+
_RECEIPT_ATTESTATION_ADVISORY = (
|
|
135
|
+
"[RECEIPT-ATTESTATION: declaration-only — applicability not "
|
|
136
|
+
"machine-verified; adjudication judges the attestation]"
|
|
137
|
+
)
|
|
138
|
+
# Folded field-name alphabets for the decoration-agnostic shape test, the
|
|
139
|
+
# same construction as `_is_dissent_field_shaped`: a line is receipt-field-
|
|
140
|
+
# shaped when the letters before its first colon spell a field name after
|
|
141
|
+
# markup-span strip + NFKC fold. A shaped line that the canonical grammar
|
|
142
|
+
# does not parse is a loud abort, never a silently ignored decoration.
|
|
143
|
+
_RECEIPT_FIELD_SHAPE_NAMES = frozenset(
|
|
144
|
+
"".join(char for char in key if char.isalpha())
|
|
145
|
+
for key in (*_RECEIPT_FIELD_RES, "no_recomputable_statistics")
|
|
146
|
+
)
|
|
147
|
+
_FINDING_REF_VALUE_RE = re.compile(r"^W[1-9]\d*$")
|
|
148
|
+
# The back-reference value is exact: `AR<n>` up to end-of-line or the next
|
|
149
|
+
# table pipe. `AR1, AR2` and trailing prose fail this parse and then abort
|
|
150
|
+
# via the lenient declaration count below rather than half-reading `AR1`.
|
|
151
|
+
_RECEIPT_BACKREF_RE = re.compile(
|
|
152
|
+
r"(?:^|\|\s*)\s*(?:[-*]\s*)?\*\*Arithmetic Receipt\*\*:\s*"
|
|
153
|
+
r"(?P<value>AR[1-9]\d*)\s*(?=\||$)"
|
|
154
|
+
)
|
|
155
|
+
# Lenient declaration shape for the back-reference, checked against the
|
|
156
|
+
# canonical parse per line (`_is_dissent_field_shaped` construction): a line
|
|
157
|
+
# whose letters before the first colon spell exactly `arithmeticreceipt` is
|
|
158
|
+
# a declaration the seat made, so a decorated, fenced, or prose-trailed
|
|
159
|
+
# back-reference the canonical regex refuses aborts instead of vanishing.
|
|
160
|
+
# Prose that merely mentions the section (`the Arithmetic Receipts
|
|
161
|
+
# section: ...`) carries extra letters in its head and stays prose.
|
|
162
|
+
_RECEIPT_BACKREF_SHAPE_NAME = "arithmeticreceipt"
|
|
163
|
+
# Both-tails value rule (#610 rounds 1-2): under an unstated tail, each tail
|
|
164
|
+
# label must share its own `;`-delimited segment of `derived_value_or_range`
|
|
165
|
+
# with a digit — either side of the label, so `p = .192 (two-tailed)` is a
|
|
166
|
+
# shown value while a bare label in a digitless segment is not. Hyphen
|
|
167
|
+
# variants (space, ASCII hyphen, U+2010-U+2015) are folded into the label
|
|
168
|
+
# match, the value text is NFKC-folded first, and the label needs letter
|
|
169
|
+
# boundaries so `notwo-tailed` prose cannot satisfy the rule; a fused
|
|
170
|
+
# `twotailed` typo still counts as the label — it carries its value, and
|
|
171
|
+
# rejecting it would be a false abort on an unretryable phase.
|
|
172
|
+
_TAIL_LABEL_RES = {
|
|
173
|
+
label: re.compile(rf"(?<![a-z]){label}[\s‐-―-]?tailed(?![a-z])")
|
|
174
|
+
for label in ("two", "one")
|
|
175
|
+
}
|
|
176
|
+
# Conditional-field matrix, spec §4/§5 (2026-08-02 #610 spec): which
|
|
177
|
+
# procedure-specific lines each procedure may carry. `tail_convention` is
|
|
178
|
+
# required on every p_from_test_statistic receipt (what the paper states is
|
|
179
|
+
# always statable — `unstated` exists for exactly that case); the completed-
|
|
180
|
+
# procedure fields (`rounding_interval`, `nearest_achievable`, `df_identity`)
|
|
181
|
+
# are required only under a verdict status, because `not_computable` may
|
|
182
|
+
# legitimately stop before the field can exist, and remain permitted there
|
|
183
|
+
# for a partial attempt that stopped late.
|
|
184
|
+
_RECEIPT_VERDICT_STATUSES = frozenset({"consistent", "mismatch"})
|
|
185
|
+
_PROCEDURE_FIELDS = {
|
|
186
|
+
"p_from_test_statistic": ("tail_convention",),
|
|
187
|
+
"grim": ("rounding_interval", "nearest_achievable"),
|
|
188
|
+
"grimmer": ("rounding_interval", "nearest_achievable"),
|
|
189
|
+
"n_from_df": ("df_identity",),
|
|
190
|
+
}
|
|
191
|
+
# Inverted view driving the conditional-field loop, so the matrix above is
|
|
192
|
+
# the single authority: field -> the procedures that carry it.
|
|
193
|
+
_PROCEDURE_FIELD_OWNERS = {
|
|
194
|
+
field: tuple(
|
|
195
|
+
procedure
|
|
196
|
+
for procedure, fields in _PROCEDURE_FIELDS.items()
|
|
197
|
+
if field in fields
|
|
198
|
+
)
|
|
199
|
+
for fields in _PROCEDURE_FIELDS.values()
|
|
200
|
+
for field in fields
|
|
201
|
+
}
|
|
202
|
+
_ALWAYS_REQUIRED_PROCEDURE_FIELDS = frozenset({"tail_convention"})
|
|
203
|
+
# --- end #610 receipt grammar --------------------------------------------
|
|
204
|
+
_DISSENT_FIELD_NAMES = frozenset({"dimensionid", "rationale"})
|
|
205
|
+
_MARKUP_SPAN_RE = re.compile(
|
|
206
|
+
r"<[^>]*>|\]\((?:[^()]|\([^()]*\))*\)|\]\[[^\]]*\]|\[[ xX]?\]"
|
|
207
|
+
)
|
|
208
|
+
# Non-comment raw HTML is forbidden only inside the dissent span (#682).
|
|
209
|
+
# Match the opening delimiter rather than requiring a complete tag: CommonMark
|
|
210
|
+
# raw blocks such as a line ending in ``<script`` can still hide the canonical
|
|
211
|
+
# fields below it, and a malformed claimed dissent must abort rather than fall
|
|
212
|
+
# through to the empty-section advisory. Autolinks such as
|
|
213
|
+
# ``<https://example.test>`` do not match because ``:`` is not a tag-name
|
|
214
|
+
# boundary. Comment delimiters remain owned by the #613 state machine so its
|
|
215
|
+
# more specific ``[DISSENT-HIDDEN]`` diagnostic keeps precedence.
|
|
216
|
+
_DISSENT_RAW_HTML_RE = re.compile(
|
|
217
|
+
r"<(?:(?:/?[A-Za-z][A-Za-z0-9-]*)(?=[\s/>]|$)|![A-Za-z]|!\[CDATA\[|\?)",
|
|
218
|
+
re.IGNORECASE,
|
|
219
|
+
)
|
|
220
|
+
# A block opener, including one behind list or blockquote markers: rendered
|
|
221
|
+
# through CommonMark, `- <!--` opens raw HTML just as a bare `<!--` does, and
|
|
222
|
+
# needs no closer to swallow the rest of the item. The indentation allowances
|
|
223
|
+
# must not add up to four, which would make the whole line indented code: a
|
|
224
|
+
# list marker takes one to four following spaces before its content, a block
|
|
225
|
+
# quote at most one, and a block start may still be indented up to three
|
|
226
|
+
# inside the quote it opens. Erring narrow costs a miss; erring wide aborts a
|
|
227
|
+
# valid card. Only an ordered list beginning at 1 may interrupt an open
|
|
228
|
+
# paragraph, so `2. <!--` under a paragraph line is text, not a list opening
|
|
229
|
+
# a raw-HTML block, and the fields below it stay on the page.
|
|
230
|
+
def _container_prefix(ordered: str) -> str:
|
|
231
|
+
"""Matched against a tab-expanded line, so spaces are the only gap.
|
|
232
|
+
|
|
233
|
+
Every gap is one unambiguous run: letting a single space be claimed by
|
|
234
|
+
either of two adjacent optional groups gave two ways to match each marker
|
|
235
|
+
and doubled the work per nesting level, so a short nested quote stalled
|
|
236
|
+
the checker rather than returning a verdict.
|
|
237
|
+
"""
|
|
238
|
+
return rf"(?:(?:[-*+]|{ordered}) {{1,4}}|> {{0,4}})*"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# Lines that end the paragraph above them, so the NEXT line sits at a real
|
|
242
|
+
# block start where any ordered marker may open a list. An ATX heading of any
|
|
243
|
+
# level, a thematic break, and a lone `-` do that from either state, the last
|
|
244
|
+
# because it reads as a setext underline under a paragraph and as an empty
|
|
245
|
+
# list item without one. A bare `>` starts a container rather than closing a
|
|
246
|
+
# paragraph and is left to #613 with the rest of the container family.
|
|
247
|
+
_CLOSES_PARAGRAPH_RE = re.compile(
|
|
248
|
+
r"^ {0,3}(?:"
|
|
249
|
+
r"#{1,6}(?:[ \t].*)?$"
|
|
250
|
+
r"|(?:\*[ \t]*){3,}$|(?:_[ \t]*){3,}$|(?:-[ \t]*){3,}$"
|
|
251
|
+
r"|-[ \t]*$"
|
|
252
|
+
r")"
|
|
253
|
+
)
|
|
254
|
+
# A `=` run or two-or-more hyphens underlines a paragraph, so it closes one
|
|
255
|
+
# only when there is a paragraph to underline; with none, it is ordinary
|
|
256
|
+
# paragraph text and OPENS one.
|
|
257
|
+
_SETEXT_UNDERLINE_RE = re.compile(r"^ {0,3}(?:=+|-{2,})[ \t]*$")
|
|
258
|
+
# An empty list item has a blank first line, so it cannot interrupt an open
|
|
259
|
+
# paragraph. It only holds the state down where none is open. Reading every
|
|
260
|
+
# lone marker as a closer looked symmetrical and aborted valid cards.
|
|
261
|
+
_EMPTY_LIST_ITEM_RE = re.compile(r"^ {0,3}(?:[*+]|\d{1,9}[.)])[ \t]*$")
|
|
262
|
+
_ANY_ORDERED_MARKER = r"\d{1,9}[.)]"
|
|
263
|
+
_PARAGRAPH_INTERRUPTING_MARKER = r"1[.)]"
|
|
264
|
+
_COMMENT_OPENER_RE = re.compile(
|
|
265
|
+
rf"^ {{0,3}}{_container_prefix(_ANY_ORDERED_MARKER)}<!--"
|
|
266
|
+
)
|
|
267
|
+
_PARAGRAPH_OPENER_RE = re.compile(
|
|
268
|
+
rf"^ {{0,3}}{_container_prefix(_PARAGRAPH_INTERRUPTING_MARKER)}<!--"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class ConformanceError(Exception):
|
|
273
|
+
"""Reviewer conformance failure -> exit 3."""
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@dataclass
|
|
277
|
+
class PhaseOnePlan:
|
|
278
|
+
commitments: dict[str, dict[str, str]]
|
|
279
|
+
warnings: list[str]
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@dataclass
|
|
283
|
+
class DissentSpan:
|
|
284
|
+
"""The dissent section as written: every line, plus the commented ones.
|
|
285
|
+
|
|
286
|
+
`strip_fences` leaves HTML comments in place, so a canonical field inside
|
|
287
|
+
`<!-- ... -->` otherwise parses as a real dissent and collects the
|
|
288
|
+
trigger-binding exemption while the visible card claims nothing.
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
lines: list[str]
|
|
292
|
+
hidden_by_comment: list[str]
|
|
293
|
+
raw_html: list[str]
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@dataclass
|
|
297
|
+
class DissentParse:
|
|
298
|
+
dimensions: set[str]
|
|
299
|
+
diagnostics: list[str]
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _normalise(text: str) -> str:
|
|
303
|
+
return " ".join(text.casefold().split())
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _is_dissent_field_shaped(line: str) -> bool:
|
|
307
|
+
"""True when a line spells a dissent field, canonically or decorated.
|
|
308
|
+
|
|
309
|
+
Decoration-agnostic by construction: a line is field-shaped when the
|
|
310
|
+
letters preceding its first colon spell exactly a field name, whatever
|
|
311
|
+
non-letters surround them. Enumerating Markdown wrappers instead would
|
|
312
|
+
leave the next unenumerated wrapper (task item, table cell, link label)
|
|
313
|
+
reading as prose. Folded first so a fullwidth re-spelling cannot pass.
|
|
314
|
+
|
|
315
|
+
Markup spans carrying letters of their own (HTML tags, reference targets,
|
|
316
|
+
task-list markers, and link destinations up to one nesting level deep) are
|
|
317
|
+
dropped BEFORE the colon is located, not after: an absolute link target
|
|
318
|
+
carries its own `https:` and would otherwise win the partition.
|
|
319
|
+
|
|
320
|
+
Decoration beyond those — a deeper nested destination, a quoted HTML
|
|
321
|
+
attribute containing `>`, exotic custom syntax — is deliberately out of
|
|
322
|
+
scope, because absorbing it costs one advisory-flagged record while a
|
|
323
|
+
broader rule costs false aborts, which is the failure this tolerance
|
|
324
|
+
exists to remove. `test_a_nested_paren_link_destination_is_a_declared_
|
|
325
|
+
limit` pins that boundary so it cannot move by accident.
|
|
326
|
+
|
|
327
|
+
`str.isalpha` keeps every script's letters, so CJK prose that happens to
|
|
328
|
+
name a field stays prose rather than collapsing onto the field name and
|
|
329
|
+
aborting a panel it should tolerate.
|
|
330
|
+
"""
|
|
331
|
+
stripped = _MARKUP_SPAN_RE.sub(
|
|
332
|
+
"", unicodedata.normalize("NFKC", line).casefold()
|
|
333
|
+
)
|
|
334
|
+
head, separator, _ = stripped.partition(":")
|
|
335
|
+
label = "".join(char for char in head if char.isalpha())
|
|
336
|
+
return bool(separator) and label in _DISSENT_FIELD_NAMES
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _display_fold(line: str) -> str:
|
|
340
|
+
"""Unescape HTML entities FIRST, then NFKC-fold and casefold.
|
|
341
|
+
|
|
342
|
+
Order matters (#610 round-3 P1): a fullwidth-colon entity `:`
|
|
343
|
+
decodes to a fullwidth colon that only the SUBSEQUENT NFKC fold maps to
|
|
344
|
+
`:`, so folding before unescaping left the decoded colon unfolded and
|
|
345
|
+
the declaration unseen. A doubly-encoded or fullwidth-ampersand entity
|
|
346
|
+
stays literal, which matches what the renderer shows.
|
|
347
|
+
"""
|
|
348
|
+
return unicodedata.normalize("NFKC", html.unescape(line)).casefold()
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
_ESCAPED_PIPE_RE = re.compile(r"\\\|")
|
|
352
|
+
# CommonMark §2.4: a backslash escapes the next punctuation character, so
|
|
353
|
+
# `\\` is a literal backslash and ``\` `` a literal backtick that can
|
|
354
|
+
# neither open nor close a code span. One left-to-right pass sequences the
|
|
355
|
+
# two correctly (`\\` consumes its backslash before a following backtick is
|
|
356
|
+
# considered). Ignoring this let an escaped-backtick "span" blank a live
|
|
357
|
+
# `<!--` and credit a hidden dissent field (#613 security round 1, P1).
|
|
358
|
+
_ESCAPED_BACKTICK_RE = re.compile(r"\\[\\`]")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _blank_code_spans(line: str) -> str:
|
|
362
|
+
"""Blank CommonMark inline code spans, matching runs of EQUAL length.
|
|
363
|
+
|
|
364
|
+
A regex accepting unequal delimiter runs (#610 round-4 P1) blanked
|
|
365
|
+
from a single-backtick opener to a double-backtick closer — a stretch
|
|
366
|
+
the renderer does NOT treat as code, re-opening the later-cell attack
|
|
367
|
+
— and conversely swallowed legitimate prose. This scanner pairs an
|
|
368
|
+
opening run only with the next run of exactly its length, as the
|
|
369
|
+
renderer does; an unmatched run stays literal. Backslash-escaped
|
|
370
|
+
backticks are blanked FIRST: they are literal to the renderer and must
|
|
371
|
+
not participate in pairing.
|
|
372
|
+
"""
|
|
373
|
+
line = _ESCAPED_BACKTICK_RE.sub(" ", line)
|
|
374
|
+
out: list[str] = []
|
|
375
|
+
index, length = 0, len(line)
|
|
376
|
+
while index < length:
|
|
377
|
+
if line[index] != "`":
|
|
378
|
+
out.append(line[index])
|
|
379
|
+
index += 1
|
|
380
|
+
continue
|
|
381
|
+
run_end = index
|
|
382
|
+
while run_end < length and line[run_end] == "`":
|
|
383
|
+
run_end += 1
|
|
384
|
+
run = run_end - index
|
|
385
|
+
scan, close = run_end, -1
|
|
386
|
+
while scan < length:
|
|
387
|
+
if line[scan] != "`":
|
|
388
|
+
scan += 1
|
|
389
|
+
continue
|
|
390
|
+
candidate_end = scan
|
|
391
|
+
while candidate_end < length and line[candidate_end] == "`":
|
|
392
|
+
candidate_end += 1
|
|
393
|
+
if candidate_end - scan == run:
|
|
394
|
+
close = scan
|
|
395
|
+
break
|
|
396
|
+
scan = candidate_end
|
|
397
|
+
if close >= 0:
|
|
398
|
+
out.append(" " * (close + run - index))
|
|
399
|
+
index = close + run
|
|
400
|
+
else:
|
|
401
|
+
out.append(line[index:run_end])
|
|
402
|
+
index = run_end
|
|
403
|
+
return "".join(out)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _cell_split_form(line: str) -> str:
|
|
407
|
+
"""The line with pipe-bearing non-cell constructs blanked for splitting.
|
|
408
|
+
|
|
409
|
+
A naive `split("|")` read the pipe inside a Markdown link destination,
|
|
410
|
+
an escaped `\\|`, or an inline code span as a GFM cell boundary and
|
|
411
|
+
minted a phantom field-shaped cell out of legitimate prose — a false
|
|
412
|
+
abort on an unretryable phase (#610 round-3). Those spans are literal
|
|
413
|
+
or non-tabular to the renderer, so they are blanked before the split;
|
|
414
|
+
the whole-line candidate is always tested unmodified.
|
|
415
|
+
"""
|
|
416
|
+
blanked = _MARKUP_SPAN_RE.sub(" ", line)
|
|
417
|
+
blanked = _blank_code_spans(blanked)
|
|
418
|
+
return _ESCAPED_PIPE_RE.sub(" ", blanked)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _is_receipt_field_shaped(line: str) -> bool:
|
|
422
|
+
"""True when a line spells a receipt machine field, however decorated.
|
|
423
|
+
|
|
424
|
+
Same construction as `_is_dissent_field_shaped` (#610 round-1 fix 4):
|
|
425
|
+
markup spans are stripped, the line is NFKC-folded and HTML-unescaped
|
|
426
|
+
(an entity colon `:` renders as a colon and must read as one), and
|
|
427
|
+
the letters before the first colon must spell exactly a receipt field
|
|
428
|
+
name. An unenumerated decoration — `**status:** mismatch`, a table
|
|
429
|
+
cell, an indented, ordered, or blockquoted marker — is therefore a
|
|
430
|
+
declaration the seat made, and a shaped line the canonical grammar
|
|
431
|
+
refuses aborts loudly instead of silently passing a forbidden-field
|
|
432
|
+
guard or starving a required-field count.
|
|
433
|
+
"""
|
|
434
|
+
stripped = _MARKUP_SPAN_RE.sub("", _display_fold(line))
|
|
435
|
+
head, separator, _ = stripped.partition(":")
|
|
436
|
+
label = "".join(char for char in head if char.isalpha())
|
|
437
|
+
return bool(separator) and label in _RECEIPT_FIELD_SHAPE_NAMES
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _receipt_shape_candidates(line: str) -> list[str]:
|
|
441
|
+
"""The line plus, when it carries pipes, each `|`-delimited cell.
|
|
442
|
+
|
|
443
|
+
The head-of-line shape test alone lets a machine declaration hide in a
|
|
444
|
+
LATER table cell (`| note | **tail_convention:** two-tailed |`), where
|
|
445
|
+
the head letters of the whole line spell nothing (#610 round-2). Cells
|
|
446
|
+
are scanned only when the line does not canonically parse, so a
|
|
447
|
+
canonical field whose free-text value happens to contain a pipe is
|
|
448
|
+
never re-partitioned against itself.
|
|
449
|
+
"""
|
|
450
|
+
if "|" not in line:
|
|
451
|
+
return [line]
|
|
452
|
+
return [line, *_cell_split_form(line).split("|")]
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _tail_value_shown(derived_fold: str, label_re: re.Pattern[str]) -> bool:
|
|
456
|
+
"""Whether a tail label shares a `;`-delimited segment with a digit.
|
|
457
|
+
|
|
458
|
+
Segments are the `;`-delimited pieces of the (NFKC-folded, casefolded)
|
|
459
|
+
`derived_value_or_range` value: a bare label whose number sits in some
|
|
460
|
+
other segment — or nowhere — is a label, not a shown value, and cannot
|
|
461
|
+
satisfy the both-tails display rule. Digit-adjacency is direction-free
|
|
462
|
+
within the segment (`two-tailed p ≈ .192` and `p = .192 (two-tailed)`
|
|
463
|
+
both show the value); the one-pass any() also keeps the scan linear.
|
|
464
|
+
"""
|
|
465
|
+
return any(
|
|
466
|
+
label_re.search(segment)
|
|
467
|
+
and any(char.isdigit() for char in segment)
|
|
468
|
+
for segment in derived_fold.split(";")
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _is_backref_shaped(line: str) -> bool:
|
|
473
|
+
"""True when a line spells the `**Arithmetic Receipt**:` field, however
|
|
474
|
+
decorated — the same head shape test as the receipt fields."""
|
|
475
|
+
stripped = _MARKUP_SPAN_RE.sub("", _display_fold(line))
|
|
476
|
+
head, separator, _ = stripped.partition(":")
|
|
477
|
+
label = "".join(char for char in head if char.isalpha())
|
|
478
|
+
return bool(separator) and label == _RECEIPT_BACKREF_SHAPE_NAME
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def _strip_inline_comment_spans(
|
|
482
|
+
line: str, open_: bool
|
|
483
|
+
) -> tuple[str, list[str], bool]:
|
|
484
|
+
"""(rendered remainder, hidden segments, state) for inline spans.
|
|
485
|
+
|
|
486
|
+
The block visibility model deliberately refuses to read a mid-paragraph
|
|
487
|
+
`prose <!--` as an opener (see `_raw_dissent_span`), but CommonMark
|
|
488
|
+
treats it as raw inline HTML whose comment hides everything until
|
|
489
|
+
`-->`. The Review Body backref walk therefore strips those spans and
|
|
490
|
+
parses only what the renderer shows (#610 rounds 3-5): content after
|
|
491
|
+
the opener on the SAME line is stripped too, and content after a
|
|
492
|
+
closing `-->` on the same line is kept and parsed — so a hidden
|
|
493
|
+
back-reference is never credited and a rendered one is never dropped.
|
|
494
|
+
The hidden segments are returned as well, because a declaration inside
|
|
495
|
+
a span must abort even when the line also carries visible content —
|
|
496
|
+
silently dropping it re-opened the hidden-declaration channel one
|
|
497
|
+
partial-visibility case at a time (#610 round-5). They stay one entry
|
|
498
|
+
PER SPAN, never joined (#610 round-6): joining let a prose span prefix
|
|
499
|
+
shield a declaration in the next span, and conversely synthesized a
|
|
500
|
+
phantom declaration out of harmless fragments. Callers pass a
|
|
501
|
+
code-span-blanked line, matching the renderer's precedence of code
|
|
502
|
+
spans over raw HTML.
|
|
503
|
+
"""
|
|
504
|
+
visible_parts: list[str] = []
|
|
505
|
+
hidden_segments: list[str] = []
|
|
506
|
+
index, state = 0, open_
|
|
507
|
+
while True:
|
|
508
|
+
token = "-->" if state else "<!--"
|
|
509
|
+
position = line.find(token, index)
|
|
510
|
+
if position < 0:
|
|
511
|
+
if state:
|
|
512
|
+
hidden_segments.append(line[index:])
|
|
513
|
+
else:
|
|
514
|
+
visible_parts.append(line[index:])
|
|
515
|
+
return "".join(visible_parts), hidden_segments, state
|
|
516
|
+
if state:
|
|
517
|
+
hidden_segments.append(line[index:position])
|
|
518
|
+
else:
|
|
519
|
+
visible_parts.append(line[index:position])
|
|
520
|
+
state, index = not state, position + len(token)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _backref_declared_count(line: str) -> int:
|
|
524
|
+
"""Back-reference declarations on the line, counted per table cell.
|
|
525
|
+
|
|
526
|
+
Counting the whole line once let a malformed second declaration ride
|
|
527
|
+
beside a canonical one (`… AR1 | **Arithmetic Receipt:** see AR2`) —
|
|
528
|
+
the canonical parse found a match, the shape test never ran, and the
|
|
529
|
+
visible second declaration vanished (#610 round-2). Cells are the
|
|
530
|
+
declaration slots the canonical regex itself recognises (its
|
|
531
|
+
start-or-pipe prefix), so declared-vs-parsed equality is exact.
|
|
532
|
+
"""
|
|
533
|
+
if "|" not in line:
|
|
534
|
+
return 1 if _is_backref_shaped(line) else 0
|
|
535
|
+
return sum(
|
|
536
|
+
1 for cell in _cell_split_form(line).split("|")
|
|
537
|
+
if _is_backref_shaped(cell)
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _lines_with_fence_state(text: str):
|
|
542
|
+
"""Yield every line with whether it sits inside a fenced block.
|
|
543
|
+
|
|
544
|
+
Mirrors the fence bookkeeping of `panel.strip_fences`, which drops those
|
|
545
|
+
lines rather than reporting them. The dissent scan needs both facts: the
|
|
546
|
+
content, so a fenced field cannot hide, and the state, so a fenced heading
|
|
547
|
+
is not mistaken for a section boundary.
|
|
548
|
+
|
|
549
|
+
Fenced lines are yielded in DISPLAY form: CommonMark strips up to the
|
|
550
|
+
opener's indentation from every content line of an indented fence, so a
|
|
551
|
+
consumer reading fenced content (the receipt gate) sees what the page
|
|
552
|
+
shows rather than false-aborting on the indent (#610 round-2).
|
|
553
|
+
"""
|
|
554
|
+
fence_char, fence_len, fence_indent = None, 0, 0
|
|
555
|
+
for line in panel._COMMONMARK_LINE_END_RE.split(text):
|
|
556
|
+
if fence_char is not None:
|
|
557
|
+
if match := panel._FENCE_CLOSE_RE.fullmatch(line):
|
|
558
|
+
token = match.group("fence")
|
|
559
|
+
if token[0] == fence_char and len(token) >= fence_len:
|
|
560
|
+
fence_char, fence_len, fence_indent = None, 0, 0
|
|
561
|
+
continue
|
|
562
|
+
leading = len(line) - len(line.lstrip(" "))
|
|
563
|
+
yield line[min(fence_indent, leading):], True
|
|
564
|
+
continue
|
|
565
|
+
if match := panel._FENCE_OPEN_RE.fullmatch(line):
|
|
566
|
+
token, info = match.group("fence"), match.group("info")
|
|
567
|
+
if token[0] != "`" or "`" not in info:
|
|
568
|
+
fence_char, fence_len = token[0], len(token)
|
|
569
|
+
fence_indent = len(line) - len(line.lstrip(" "))
|
|
570
|
+
continue
|
|
571
|
+
yield line, False
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _opens_comment(line: str, *, paragraph_open: bool) -> bool:
|
|
575
|
+
"""Whether the line starts an HTML comment at a block position.
|
|
576
|
+
|
|
577
|
+
Tabs are measured to the next four-column stop rather than counted as one
|
|
578
|
+
character, the way CommonMark reads them wherever indentation defines
|
|
579
|
+
block structure. Counting characters put `> \\t<!--` (column four, a live
|
|
580
|
+
comment) and ` - \\t<!--` (column eight, indented code) on the wrong
|
|
581
|
+
sides of the boundary in opposite directions.
|
|
582
|
+
|
|
583
|
+
With a paragraph open, an ordered marker other than 1 cannot start a list,
|
|
584
|
+
so it cannot open a comment either.
|
|
585
|
+
"""
|
|
586
|
+
pattern = _PARAGRAPH_OPENER_RE if paragraph_open else _COMMENT_OPENER_RE
|
|
587
|
+
return bool(pattern.match(line.expandtabs(4)))
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _comment_state_after(
|
|
591
|
+
line: str, *, commented: bool, paragraph_open: bool = False
|
|
592
|
+
) -> bool:
|
|
593
|
+
"""Whether the line ends inside an HTML comment.
|
|
594
|
+
|
|
595
|
+
Resolved by delimiter ORDER, not by presence: `<!-- a --> <!--` closes
|
|
596
|
+
and reopens on one line, and reading that as closed credited a comment
|
|
597
|
+
carrying canonical fields as a real dissent. Only a block opener starts
|
|
598
|
+
one, list and blockquote markers included, so a marker discussed mid-line
|
|
599
|
+
is still not a comment; once a line has opened one, its later delimiters
|
|
600
|
+
are that same block's raw HTML.
|
|
601
|
+
|
|
602
|
+
The closer may reuse the opener's own last two dashes, which is how
|
|
603
|
+
CommonMark closes `<!-->` and `<!--->`. Ordering the scan without that
|
|
604
|
+
overlap would read them as unterminated and abort a card that presence-
|
|
605
|
+
testing for a closer had passed.
|
|
606
|
+
"""
|
|
607
|
+
index = 0
|
|
608
|
+
if not commented:
|
|
609
|
+
if not _opens_comment(line, paragraph_open=paragraph_open):
|
|
610
|
+
return False
|
|
611
|
+
# Located in the ORIGINAL line: the prefix carries no `<!--`, so the
|
|
612
|
+
# first occurrence is the opener wherever tab expansion moved it.
|
|
613
|
+
commented, index = True, line.find("<!--") + 2
|
|
614
|
+
while True:
|
|
615
|
+
token = "-->" if commented else "<!--"
|
|
616
|
+
position = line.find(token, index)
|
|
617
|
+
if position < 0:
|
|
618
|
+
return commented
|
|
619
|
+
commented, index = not commented, position + len(token)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _inline_comment_state_after(line: str, *, commented: bool) -> bool:
|
|
623
|
+
"""Delimiter-order comment state with NO block-position requirement.
|
|
624
|
+
|
|
625
|
+
Span-scoped (#613): inside the dissent span the output grammar makes a
|
|
626
|
+
bare ``<!--`` out-of-grammar prose — the delivered Phase 2 prompts and
|
|
627
|
+
the protocol now require inline code for any mention — so every
|
|
628
|
+
occurrence is an opener, including the two CommonMark shapes the block
|
|
629
|
+
visibility model deliberately does not read (a marker following text on
|
|
630
|
+
its own line; a marker indented as a lazy paragraph continuation).
|
|
631
|
+
Callers blank code spans first, so the sanctioned inline-code mention
|
|
632
|
+
never opens.
|
|
633
|
+
"""
|
|
634
|
+
index = 0
|
|
635
|
+
while True:
|
|
636
|
+
token = "-->" if commented else "<!--"
|
|
637
|
+
position = line.find(token, index)
|
|
638
|
+
if position < 0:
|
|
639
|
+
return commented
|
|
640
|
+
commented = not commented
|
|
641
|
+
# After an opener, resume at +2 rather than +4: the closer may
|
|
642
|
+
# reuse the opener's own last two dashes (`<!-->`, `<!--->`) —
|
|
643
|
+
# the same overlap rule the block scanner applies (codex #650
|
|
644
|
+
# round 1, P2: skipping it left the empty comment "open" and
|
|
645
|
+
# false-aborted the rendered fields below it).
|
|
646
|
+
index = position + (2 if commented else len(token))
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def _lines_with_hidden_state(text: str):
|
|
650
|
+
"""Yield (line, fenced, hidden): fence plus HTML-comment visibility.
|
|
651
|
+
|
|
652
|
+
The comment and paragraph bookkeeping mirror `_raw_dissent_span` line
|
|
653
|
+
for line, so the receipt gate and the dissent gate share one visibility
|
|
654
|
+
model (#610 round-2, both tracks): `hidden` is True when the line sits
|
|
655
|
+
inside — or itself opens — an HTML comment at a block position, i.e.
|
|
656
|
+
when a CommonMark renderer would not display it. `code_indented` is
|
|
657
|
+
True when the line starts an indented code block (four columns at a
|
|
658
|
+
block position, no paragraph open), i.e. when the renderer shows it as
|
|
659
|
+
literal text rather than a field line (#610 round-3). Fence state is
|
|
660
|
+
layered first, exactly as in `_raw_dissent_span`: a comment marker
|
|
661
|
+
inside a fence is literal text and advances no comment state.
|
|
662
|
+
"""
|
|
663
|
+
commented = False
|
|
664
|
+
paragraph_open = False
|
|
665
|
+
for line, fenced in _lines_with_fence_state(text):
|
|
666
|
+
entered_commented = commented
|
|
667
|
+
opens_comment = not fenced and _opens_comment(
|
|
668
|
+
line, paragraph_open=paragraph_open
|
|
669
|
+
)
|
|
670
|
+
if not fenced:
|
|
671
|
+
commented = _comment_state_after(
|
|
672
|
+
line, commented=commented, paragraph_open=paragraph_open
|
|
673
|
+
)
|
|
674
|
+
expanded = line.expandtabs(4)
|
|
675
|
+
code_indented = (
|
|
676
|
+
not fenced
|
|
677
|
+
and not paragraph_open
|
|
678
|
+
and bool(line.strip(" \t"))
|
|
679
|
+
and expanded.startswith(" ")
|
|
680
|
+
)
|
|
681
|
+
state_dependent = (
|
|
682
|
+
_SETEXT_UNDERLINE_RE if paragraph_open else _EMPTY_LIST_ITEM_RE
|
|
683
|
+
)
|
|
684
|
+
closes_paragraph = bool(
|
|
685
|
+
_CLOSES_PARAGRAPH_RE.match(expanded)
|
|
686
|
+
or state_dependent.match(expanded)
|
|
687
|
+
)
|
|
688
|
+
paragraph_open = (
|
|
689
|
+
not fenced
|
|
690
|
+
and bool(line.strip(" \t"))
|
|
691
|
+
and not closes_paragraph
|
|
692
|
+
and not (entered_commented or opens_comment)
|
|
693
|
+
# An indented-code line is literal, never a paragraph: letting
|
|
694
|
+
# it open one made the NEXT code line read as a live paragraph
|
|
695
|
+
# continuation and earn credit the renderer does not show
|
|
696
|
+
# (#610 round-4).
|
|
697
|
+
and not code_indented
|
|
698
|
+
)
|
|
699
|
+
yield line, fenced, entered_commented or opens_comment, code_indented
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
def _raw_dissent_span(text: str) -> DissentSpan:
|
|
703
|
+
"""Dissent-section lines as written, before any sanitizer runs.
|
|
704
|
+
|
|
705
|
+
Comment delimiters are opened up rather than dropped, so a field inside an
|
|
706
|
+
HTML comment is scanned instead of vanishing with the comment. A
|
|
707
|
+
field-shaped H2 immediately inside the span rides along, because a field
|
|
708
|
+
spelled as its own `## dimension_id: D1` heading leaves the section body
|
|
709
|
+
empty and would otherwise be invisible; a field-shaped heading anywhere
|
|
710
|
+
else is an ordinary extra section, which the report grammar permits.
|
|
711
|
+
|
|
712
|
+
Only an unfenced heading delimits the span, so a heading written inside a
|
|
713
|
+
fenced block cannot end it early and hide the fields that follow — not
|
|
714
|
+
even one repeating a title that exists structurally elsewhere. A COMMENTED
|
|
715
|
+
heading still delimits, agreeing with `split_sections` rather than second-
|
|
716
|
+
guessing it: disagreeing cost four false aborts across review rounds and
|
|
717
|
+
bought only a miss that credits the seat nothing, while agreeing keeps a
|
|
718
|
+
comment opened above the heading from laundering the fields below it.
|
|
719
|
+
"""
|
|
720
|
+
span, hidden_by_comment, raw_html = [], [], []
|
|
721
|
+
inside, commented = False, False
|
|
722
|
+
paragraph_open = False
|
|
723
|
+
# #613: span-scoped inline comment state. Outside the span, only a
|
|
724
|
+
# block-position opener counts (the #612 model, unchanged, because
|
|
725
|
+
# prose there may legitimately mention a bare marker). INSIDE the span
|
|
726
|
+
# the delivered output grammar requires inline code for any mention, so
|
|
727
|
+
# a bare `<!--` is an opener wherever it appears — closing the two
|
|
728
|
+
# residual shapes (#613): a marker following text on its own line, and
|
|
729
|
+
# a marker indented as a lazy paragraph continuation.
|
|
730
|
+
span_inline = False
|
|
731
|
+
# Code spans pair by equal-length runs ACROSS soft line breaks within a
|
|
732
|
+
# paragraph, which a per-line blanker cannot see: a trailing unpaired
|
|
733
|
+
# run on one line can pair into the next line and pull a `<!--` out of
|
|
734
|
+
# (or into) code (#613 security round 1, P1). Once a span line leaves
|
|
735
|
+
# an odd number of backtick runs, local blanking is untrustworthy for
|
|
736
|
+
# the REST of that paragraph: stop blanking and read every `<!--` as an
|
|
737
|
+
# opener — abort-direction, since the sanctioned mention is a
|
|
738
|
+
# same-line inline-code span in a paragraph with balanced runs.
|
|
739
|
+
code_parity_suspect = False
|
|
740
|
+
for line, fenced in _lines_with_fence_state(text):
|
|
741
|
+
entered_commented = commented
|
|
742
|
+
entered_inline = span_inline
|
|
743
|
+
opens_comment = not fenced and _opens_comment(
|
|
744
|
+
line, paragraph_open=paragraph_open
|
|
745
|
+
)
|
|
746
|
+
if not fenced:
|
|
747
|
+
# A block opener only, list and blockquote markers included:
|
|
748
|
+
# four columns STARTING a block makes indented code, and a fence
|
|
749
|
+
# makes every marker inert. Not read as an opener HERE: a marker
|
|
750
|
+
# mid-line, one in an inline-code span, or one indented as a lazy
|
|
751
|
+
# paragraph continuation — inside the dissent span those are the
|
|
752
|
+
# #613 inline state's job, now that the output grammar makes a
|
|
753
|
+
# bare marker out-of-grammar prose. #613 tracks, and leaves
|
|
754
|
+
# OPEN, the wider hiding channel this visibility model does not
|
|
755
|
+
# cover at all: raw HTML that is not a comment, such as a
|
|
756
|
+
# `<script>` or `<template>` block — the shipped closure is the
|
|
757
|
+
# comment channel only.
|
|
758
|
+
commented = _comment_state_after(
|
|
759
|
+
line, commented=commented, paragraph_open=paragraph_open
|
|
760
|
+
)
|
|
761
|
+
if not fenced and (match := panel._H2_RE.fullmatch(line)):
|
|
762
|
+
title = match.group(1)
|
|
763
|
+
if inside and _is_dissent_field_shaped(title):
|
|
764
|
+
span.append(title)
|
|
765
|
+
else:
|
|
766
|
+
inside = title == "Scoring Plan Dissent"
|
|
767
|
+
paragraph_open = False
|
|
768
|
+
span_inline = False
|
|
769
|
+
code_parity_suspect = False
|
|
770
|
+
continue
|
|
771
|
+
# CommonMark counts only spaces and tabs as blank, so a line holding
|
|
772
|
+
# an ideographic space is a paragraph. Calling it blank put the next
|
|
773
|
+
# line at a block start and aborted a valid zh-TW card.
|
|
774
|
+
expanded = line.expandtabs(4)
|
|
775
|
+
state_dependent = (
|
|
776
|
+
_SETEXT_UNDERLINE_RE if paragraph_open else _EMPTY_LIST_ITEM_RE
|
|
777
|
+
)
|
|
778
|
+
closes_paragraph = bool(
|
|
779
|
+
_CLOSES_PARAGRAPH_RE.match(expanded)
|
|
780
|
+
or state_dependent.match(expanded)
|
|
781
|
+
)
|
|
782
|
+
paragraph_open = (
|
|
783
|
+
not fenced
|
|
784
|
+
and bool(line.strip(" \t"))
|
|
785
|
+
and not closes_paragraph
|
|
786
|
+
# A recognized comment block is raw HTML, never a paragraph, so
|
|
787
|
+
# the line after one is at a block start again. Recording its own
|
|
788
|
+
# lines as an open paragraph read the next `2. <!--` with the
|
|
789
|
+
# restricted pattern and credited the fields it hides.
|
|
790
|
+
and not (entered_commented or opens_comment)
|
|
791
|
+
)
|
|
792
|
+
if inside:
|
|
793
|
+
if not line.strip(" \t"):
|
|
794
|
+
# A blank line closes the paragraph, and with it any
|
|
795
|
+
# cross-line code-span ambiguity.
|
|
796
|
+
code_parity_suspect = False
|
|
797
|
+
opens_inline = False
|
|
798
|
+
blanked = None
|
|
799
|
+
if (not fenced and not entered_commented and not entered_inline
|
|
800
|
+
and not opens_comment):
|
|
801
|
+
# #613: only lines the block model does NOT already own can
|
|
802
|
+
# open the inline state; code spans are blanked first so the
|
|
803
|
+
# grammar's sanctioned `` `<!--` `` mention stays prose —
|
|
804
|
+
# unless this paragraph's runs stopped pairing locally, in
|
|
805
|
+
# which case blanking is off and every marker opens.
|
|
806
|
+
escaped = _ESCAPED_BACKTICK_RE.sub(" ", line)
|
|
807
|
+
blanked = (
|
|
808
|
+
escaped if code_parity_suspect
|
|
809
|
+
else _blank_code_spans(line)
|
|
810
|
+
)
|
|
811
|
+
opens_inline = "<!--" in blanked
|
|
812
|
+
if len(re.findall(r"`+", escaped)) % 2:
|
|
813
|
+
code_parity_suspect = True
|
|
814
|
+
# #682: any non-comment raw-HTML tag/delimiter in the dissent
|
|
815
|
+
# span is out of grammar even when it hides no field. Scan the
|
|
816
|
+
# display form after inline-code blanking; fenced examples retain
|
|
817
|
+
# their previous semantics, and the exact H2 boundaries above
|
|
818
|
+
# keep the rule span-scoped. Under cross-line code ambiguity we
|
|
819
|
+
# take the same abort-direction as the #613 comment guard rather
|
|
820
|
+
# than letting a malformed tag earn a dissent exemption.
|
|
821
|
+
if not fenced:
|
|
822
|
+
html_scan = (
|
|
823
|
+
_ESCAPED_BACKTICK_RE.sub(" ", line)
|
|
824
|
+
if code_parity_suspect
|
|
825
|
+
else _blank_code_spans(line)
|
|
826
|
+
)
|
|
827
|
+
if _DISSENT_RAW_HTML_RE.search(html_scan):
|
|
828
|
+
raw_html.append(line)
|
|
829
|
+
# Opened up only where a comment actually is. Rewriting every line
|
|
830
|
+
# carrying the tokens would break a canonical `rationale:` that
|
|
831
|
+
# merely mentions them in inline code from matching the
|
|
832
|
+
# canonical parse, aborting an unretryable Phase 2 on a valid
|
|
833
|
+
# card.
|
|
834
|
+
if (entered_commented or opens_comment or entered_inline
|
|
835
|
+
or opens_inline):
|
|
836
|
+
span.append(line.replace("<!--", " ").replace("-->", " "))
|
|
837
|
+
else:
|
|
838
|
+
span.append(line)
|
|
839
|
+
if entered_commented or entered_inline:
|
|
840
|
+
hidden_by_comment.append(line)
|
|
841
|
+
if not fenced and (entered_inline or opens_inline):
|
|
842
|
+
span_inline = _inline_comment_state_after(
|
|
843
|
+
blanked if blanked is not None
|
|
844
|
+
else _blank_code_spans(line),
|
|
845
|
+
commented=entered_inline,
|
|
846
|
+
)
|
|
847
|
+
return DissentSpan(span, hidden_by_comment, raw_html)
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def _empty_dissent_section_diagnostic(raw_span: list[str]) -> str:
|
|
851
|
+
"""Counted on the raw span: fenced prose is archived content too."""
|
|
852
|
+
non_blank = sum(1 for line in raw_span if line.strip())
|
|
853
|
+
return (
|
|
854
|
+
"[DISSENT-EMPTY-SECTION: ## Scoring Plan Dissent spells no dissent "
|
|
855
|
+
"field; read as no dissent, with full Phase 1 trigger binding "
|
|
856
|
+
f"enforced on every dimension; {non_blank} non-blank line(s) present "
|
|
857
|
+
"— read the archived response if any narrate a deviation]"
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def _one_field(
|
|
862
|
+
lines: list[str],
|
|
863
|
+
field: str,
|
|
864
|
+
path: str,
|
|
865
|
+
*,
|
|
866
|
+
required: bool,
|
|
867
|
+
dimension_id: str,
|
|
868
|
+
) -> str | None:
|
|
869
|
+
hits = [match.group("value") for line in lines
|
|
870
|
+
if (match := _FIELD_PATTERNS[field].fullmatch(line))]
|
|
871
|
+
expected = "exactly one" if required else "at most one"
|
|
872
|
+
if (required and len(hits) != 1) or (not required and len(hits) > 1):
|
|
873
|
+
raise ConformanceError(
|
|
874
|
+
f"[PHASE1-GRAMMAR: {path}: expected {expected} canonical "
|
|
875
|
+
f"{field}: line for dimension {dimension_id}, found {len(hits)}]"
|
|
876
|
+
)
|
|
877
|
+
return hits[0] if hits else None
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
def parse_phase1(
|
|
881
|
+
path: str, text: str, contract: dict, role: str
|
|
882
|
+
) -> PhaseOnePlan:
|
|
883
|
+
lines = panel.strip_fences(text)
|
|
884
|
+
sections, dupes = panel.split_sections(lines)
|
|
885
|
+
if "Scoring Plan" in dupes or "Scoring Plan" not in sections:
|
|
886
|
+
raise ConformanceError(
|
|
887
|
+
f"[PHASE1-GRAMMAR: {path}: exactly one ## Scoring Plan required]"
|
|
888
|
+
)
|
|
889
|
+
# §4 names three requirements, not one: the paraphrase section and the
|
|
890
|
+
# terminal acknowledgement are as mandatory as the plan, and a plan
|
|
891
|
+
# alone passing would let the dispatcher retry -- or accept -- a
|
|
892
|
+
# protocol-invalid Phase 1.
|
|
893
|
+
if "Contract Paraphrase" in dupes or "Contract Paraphrase" \
|
|
894
|
+
not in sections:
|
|
895
|
+
raise ConformanceError(
|
|
896
|
+
f"[PHASE1-GRAMMAR: {path}: exactly one ## Contract Paraphrase "
|
|
897
|
+
"required]"
|
|
898
|
+
)
|
|
899
|
+
# BOTH tails must be the marker. The raw tail catches output after
|
|
900
|
+
# the acknowledgement (a trailing fenced block would vanish from the
|
|
901
|
+
# structural view before the tail is computed); the fence-aware tail
|
|
902
|
+
# catches an acknowledgement that exists only as fenced code.
|
|
903
|
+
# Located by nonblankness but compared UNSTRIPPED (bar the line
|
|
904
|
+
# ending): an indented ` [CONTRACT-ACKNOWLEDGED]` renders as a
|
|
905
|
+
# code block, and stripping before comparison let it pass the exact
|
|
906
|
+
# terminal-line requirement.
|
|
907
|
+
raw_tail = next(
|
|
908
|
+
(line for line in reversed(text.splitlines())
|
|
909
|
+
if line.strip()), "")
|
|
910
|
+
fenced_tail = next(
|
|
911
|
+
(line for line in reversed(lines) if line.strip()), "")
|
|
912
|
+
if raw_tail.rstrip() != "[CONTRACT-ACKNOWLEDGED]" or \
|
|
913
|
+
fenced_tail.rstrip() != "[CONTRACT-ACKNOWLEDGED]":
|
|
914
|
+
raise ConformanceError(
|
|
915
|
+
f"[PHASE1-GRAMMAR: {path}: final nonblank line must be "
|
|
916
|
+
"[CONTRACT-ACKNOWLEDGED]]"
|
|
917
|
+
)
|
|
918
|
+
# And in §4's exact order, with nothing else at H2: presence alone
|
|
919
|
+
# would let a reordered or extra-sectioned precommitment pass. All
|
|
920
|
+
# four real dispatch outputs carry exactly this sequence.
|
|
921
|
+
h2_titles = [line[3:].strip() for line in lines
|
|
922
|
+
if line.startswith("## ")]
|
|
923
|
+
if h2_titles != ["Contract Paraphrase", "Scoring Plan"]:
|
|
924
|
+
raise ConformanceError(
|
|
925
|
+
f"[PHASE1-GRAMMAR: {path}: H2 sections must be exactly "
|
|
926
|
+
"## Contract Paraphrase then ## Scoring Plan, found "
|
|
927
|
+
f"{h2_titles!r}]"
|
|
928
|
+
)
|
|
929
|
+
dimensions = {d["id"]: d for d in contract["acceptance_dimensions"]}
|
|
930
|
+
# §4's paragraph floor: a bare heading over an empty or one-line body
|
|
931
|
+
# is not the paraphrase the contract's `paraphrase_minimum_dimensions`
|
|
932
|
+
# names ("all" = one paragraph per dimension). The count is the
|
|
933
|
+
# machine-checkable lower bound; whether each paragraph is TIED to a
|
|
934
|
+
# distinct dimension stays with the seat's own §4 preflight.
|
|
935
|
+
# A CLOSED list of zero-content lines that separate and never count:
|
|
936
|
+
# ATX headings, thematic breaks, single-line HTML comments -- six
|
|
937
|
+
# bare `### Dn` headings (or six `---` rules) used to count as six
|
|
938
|
+
# paragraphs, satisfying the floor with no paraphrase prose at all.
|
|
939
|
+
# Deliberately NOT a full CommonMark block classifier: a list item
|
|
940
|
+
# still counts, because a bulleted six-point paraphrase is real
|
|
941
|
+
# content and refusing it would abort a panel over formatting (the
|
|
942
|
+
# false-abort channel #609 exists to remove). Blocks are still
|
|
943
|
+
# blank-line separated -- a TIGHT six-item list is one block and
|
|
944
|
+
# does not meet a six-paragraph floor, same as before this list.
|
|
945
|
+
# TERMINATION BOUND: zero-content variants beyond this list
|
|
946
|
+
# (container-prefixed comments like `- <!-- -->`, malformed comments
|
|
947
|
+
# like `<!-->`, entity/whitespace tricks) are out of scope by
|
|
948
|
+
# declared design -- the variant space is unbounded, the observed
|
|
949
|
+
# base rate in committed panels is zero, and §4's substantive
|
|
950
|
+
# judgment sits with the seat's preflight, not this floor.
|
|
951
|
+
separator = re.compile(
|
|
952
|
+
r"#{1,6}(\s|$)" # ATX heading
|
|
953
|
+
r"|([-*_])(\s*\2){2,}$" # thematic break
|
|
954
|
+
r"|<!--.*-->$" # single-line HTML comment
|
|
955
|
+
r"|[-*+]$" # lone list marker, no item text
|
|
956
|
+
)
|
|
957
|
+
paragraphs, in_paragraph, in_comment = 0, False, False
|
|
958
|
+
for line in sections["Contract Paraphrase"]:
|
|
959
|
+
stripped = line.strip()
|
|
960
|
+
if in_comment:
|
|
961
|
+
# Hidden until the closing `-->`, closer line included: six
|
|
962
|
+
# multi-line comment blocks are as unrendered as six
|
|
963
|
+
# single-line ones.
|
|
964
|
+
if "-->" in stripped:
|
|
965
|
+
in_comment = False
|
|
966
|
+
in_paragraph = False
|
|
967
|
+
continue
|
|
968
|
+
if stripped.startswith("<!--") and "-->" not in stripped:
|
|
969
|
+
# Conservative entry -- a line-LEADING opener with no closer
|
|
970
|
+
# on the same line. Prose that merely mentions `<!--`
|
|
971
|
+
# mid-line stays countable content.
|
|
972
|
+
in_comment = True
|
|
973
|
+
in_paragraph = False
|
|
974
|
+
continue
|
|
975
|
+
if stripped and not separator.match(stripped):
|
|
976
|
+
if not in_paragraph:
|
|
977
|
+
paragraphs += 1
|
|
978
|
+
in_paragraph = True
|
|
979
|
+
else:
|
|
980
|
+
in_paragraph = False
|
|
981
|
+
minimum = contract.get("measurement_procedure", {}).get(
|
|
982
|
+
"paraphrase_minimum_dimensions")
|
|
983
|
+
required = len(dimensions) if minimum == "all" else (
|
|
984
|
+
minimum if isinstance(minimum, int) else 0)
|
|
985
|
+
if paragraphs < required:
|
|
986
|
+
raise ConformanceError(
|
|
987
|
+
f"[PHASE1-GRAMMAR: {path}: Contract Paraphrase has "
|
|
988
|
+
f"{paragraphs} paragraph(s), fewer than the {required} "
|
|
989
|
+
"required]"
|
|
990
|
+
)
|
|
991
|
+
eligible = {
|
|
992
|
+
did for did, dim in dimensions.items()
|
|
993
|
+
if role in dim["eligible_roles"]
|
|
994
|
+
}
|
|
995
|
+
subsections, subsection_dupes = panel.split_subsections(
|
|
996
|
+
sections["Scoring Plan"]
|
|
997
|
+
)
|
|
998
|
+
if subsection_dupes:
|
|
999
|
+
raise ConformanceError(
|
|
1000
|
+
f"[PHASE1-GRAMMAR: {path}: duplicate scoring-plan subsection: "
|
|
1001
|
+
f"{', '.join(sorted(subsection_dupes))}]"
|
|
1002
|
+
)
|
|
1003
|
+
commitments: dict[str, dict[str, str]] = {}
|
|
1004
|
+
warnings: list[str] = []
|
|
1005
|
+
for title, sublines in subsections.items():
|
|
1006
|
+
match = panel._DIM_H3_RE.fullmatch(title)
|
|
1007
|
+
if not match or match.group("dim") not in dimensions:
|
|
1008
|
+
raise ConformanceError(
|
|
1009
|
+
f"[PHASE1-GRAMMAR: {path}: invalid subsection '### {title}']"
|
|
1010
|
+
)
|
|
1011
|
+
did = match.group("dim")
|
|
1012
|
+
if did not in eligible:
|
|
1013
|
+
raise ConformanceError(
|
|
1014
|
+
f"[PHASE1-OUT-OF-ROLE: {path}: role {role} planned {did}]"
|
|
1015
|
+
)
|
|
1016
|
+
if match.group("name") != dimensions[did]["name"]:
|
|
1017
|
+
raise ConformanceError(
|
|
1018
|
+
f"[PHASE1-GRAMMAR: {path}: {did} name mismatch]"
|
|
1019
|
+
)
|
|
1020
|
+
fields = {
|
|
1021
|
+
field: _one_field(
|
|
1022
|
+
sublines,
|
|
1023
|
+
field,
|
|
1024
|
+
path,
|
|
1025
|
+
required=True,
|
|
1026
|
+
dimension_id=did,
|
|
1027
|
+
)
|
|
1028
|
+
for field in (
|
|
1029
|
+
"dimension_id", "what_to_look_for",
|
|
1030
|
+
"what_triggers_block", "what_triggers_warn",
|
|
1031
|
+
)
|
|
1032
|
+
}
|
|
1033
|
+
mandatory = dimensions[did]["priority"] == "mandatory"
|
|
1034
|
+
fields["what_triggers_fatal"] = _one_field(
|
|
1035
|
+
sublines,
|
|
1036
|
+
"what_triggers_fatal",
|
|
1037
|
+
path,
|
|
1038
|
+
required=mandatory,
|
|
1039
|
+
dimension_id=did,
|
|
1040
|
+
)
|
|
1041
|
+
if not mandatory and fields["what_triggers_fatal"] is not None:
|
|
1042
|
+
raise ConformanceError(
|
|
1043
|
+
f"[PHASE1-GRAMMAR: {path}: what_triggers_fatal is forbidden "
|
|
1044
|
+
f"on non-mandatory dimension {did}]"
|
|
1045
|
+
)
|
|
1046
|
+
if fields["dimension_id"] != did:
|
|
1047
|
+
raise ConformanceError(
|
|
1048
|
+
f"[PHASE1-GRAMMAR: {path}: heading {did} disagrees with "
|
|
1049
|
+
f"dimension_id={fields['dimension_id']}]"
|
|
1050
|
+
)
|
|
1051
|
+
triggers = [
|
|
1052
|
+
fields["what_triggers_block"],
|
|
1053
|
+
fields["what_triggers_warn"],
|
|
1054
|
+
]
|
|
1055
|
+
if mandatory:
|
|
1056
|
+
triggers.append(fields["what_triggers_fatal"])
|
|
1057
|
+
if len({_normalise(value) for value in triggers}) != len(triggers):
|
|
1058
|
+
raise ConformanceError(
|
|
1059
|
+
f"[PHASE1-TRIGGER-COLLISION: {path}: {did} trigger "
|
|
1060
|
+
"commitments must be pairwise distinct]"
|
|
1061
|
+
)
|
|
1062
|
+
for field in (
|
|
1063
|
+
"what_triggers_block", "what_triggers_warn",
|
|
1064
|
+
"what_triggers_fatal",
|
|
1065
|
+
):
|
|
1066
|
+
value = fields[field]
|
|
1067
|
+
if value is not None and len(value.split()) < 8:
|
|
1068
|
+
warnings.append(
|
|
1069
|
+
f"[PHASE1-TRIGGER-SHORT: {path}: {did} {field} "
|
|
1070
|
+
"has fewer than 8 words]"
|
|
1071
|
+
)
|
|
1072
|
+
commitments[did] = fields
|
|
1073
|
+
if set(commitments) != eligible:
|
|
1074
|
+
raise ConformanceError(
|
|
1075
|
+
f"[PHASE1-SCOPE: {path}: planned={sorted(commitments)}, "
|
|
1076
|
+
f"eligible={sorted(eligible)}]"
|
|
1077
|
+
)
|
|
1078
|
+
return PhaseOnePlan(commitments, warnings)
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
def _flatten_metadata_values(value) -> list[str]:
|
|
1082
|
+
if isinstance(value, dict):
|
|
1083
|
+
return [
|
|
1084
|
+
item
|
|
1085
|
+
for nested in value.values()
|
|
1086
|
+
for item in _flatten_metadata_values(nested)
|
|
1087
|
+
]
|
|
1088
|
+
if isinstance(value, list):
|
|
1089
|
+
return [
|
|
1090
|
+
item
|
|
1091
|
+
for nested in value
|
|
1092
|
+
for item in _flatten_metadata_values(nested)
|
|
1093
|
+
]
|
|
1094
|
+
if isinstance(value, (str, int, float, bool)):
|
|
1095
|
+
return [str(value)]
|
|
1096
|
+
return []
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
def validate_metadata_envelope(metadata) -> None:
|
|
1100
|
+
if not isinstance(metadata, dict) or set(metadata) != _METADATA_KEYS:
|
|
1101
|
+
actual = (
|
|
1102
|
+
sorted(metadata)
|
|
1103
|
+
if isinstance(metadata, dict)
|
|
1104
|
+
else type(metadata).__name__
|
|
1105
|
+
)
|
|
1106
|
+
raise panel.ContractError(
|
|
1107
|
+
"[METADATA-INVALID: expected exact title/field/word_count "
|
|
1108
|
+
f"envelope, got {actual}]"
|
|
1109
|
+
)
|
|
1110
|
+
if (
|
|
1111
|
+
not isinstance(metadata["title"], str)
|
|
1112
|
+
or not metadata["title"].strip()
|
|
1113
|
+
or not isinstance(metadata["field"], str)
|
|
1114
|
+
or not metadata["field"].strip()
|
|
1115
|
+
or isinstance(metadata["word_count"], bool)
|
|
1116
|
+
or not isinstance(metadata["word_count"], int)
|
|
1117
|
+
or metadata["word_count"] < 0
|
|
1118
|
+
):
|
|
1119
|
+
raise panel.ContractError(
|
|
1120
|
+
"[METADATA-INVALID: title and field must be non-empty strings; "
|
|
1121
|
+
"word_count must be a non-negative integer]"
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
def check_manuscript_leakage(
|
|
1126
|
+
phase1_text: str, manuscript_text: str, metadata: dict, contract: dict
|
|
1127
|
+
) -> None:
|
|
1128
|
+
validate_metadata_envelope(metadata)
|
|
1129
|
+
phase1_norm = _normalise(phase1_text)
|
|
1130
|
+
words = _normalise(manuscript_text).split()
|
|
1131
|
+
exemption_haystacks = [
|
|
1132
|
+
_normalise(value) for value in _flatten_metadata_values(metadata)
|
|
1133
|
+
]
|
|
1134
|
+
exemption_haystacks.append(_normalise(json.dumps(
|
|
1135
|
+
contract, ensure_ascii=False, sort_keys=True
|
|
1136
|
+
)))
|
|
1137
|
+
for index in range(max(0, len(words) - 11)):
|
|
1138
|
+
shingle = " ".join(words[index:index + 12])
|
|
1139
|
+
if shingle not in phase1_norm:
|
|
1140
|
+
continue
|
|
1141
|
+
if any(shingle in haystack for haystack in exemption_haystacks):
|
|
1142
|
+
continue
|
|
1143
|
+
raise ConformanceError(
|
|
1144
|
+
"[PHASE1-MANUSCRIPT-LEAK: 12-word manuscript shingle appears "
|
|
1145
|
+
"in Phase 1 outside metadata/contract exemptions]"
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
def parse_dissent_dimensions(text: str) -> DissentParse:
|
|
1150
|
+
lines = panel.strip_fences(text)
|
|
1151
|
+
sections, dupes = panel.split_sections(lines)
|
|
1152
|
+
if "Scoring Plan Dissent" in dupes:
|
|
1153
|
+
raise ConformanceError(
|
|
1154
|
+
"[DISSENT-GRAMMAR: duplicate ## Scoring Plan Dissent]"
|
|
1155
|
+
)
|
|
1156
|
+
if "Scoring Plan Dissent" not in sections:
|
|
1157
|
+
return DissentParse(set(), [])
|
|
1158
|
+
body = sections["Scoring Plan Dissent"]
|
|
1159
|
+
span = _raw_dissent_span(text)
|
|
1160
|
+
raw_span = span.lines
|
|
1161
|
+
# A commented-out field is not a claim the seat made, so it is struck from
|
|
1162
|
+
# the canonical parse and left to fail below as an unparsed occurrence.
|
|
1163
|
+
outstanding = Counter(span.hidden_by_comment)
|
|
1164
|
+
visible = []
|
|
1165
|
+
for line in body:
|
|
1166
|
+
if outstanding.get(line):
|
|
1167
|
+
outstanding[line] -= 1
|
|
1168
|
+
continue
|
|
1169
|
+
visible.append(line)
|
|
1170
|
+
parsed = Counter(
|
|
1171
|
+
line for line in visible
|
|
1172
|
+
if _DISSENT_DIM_RE.fullmatch(line)
|
|
1173
|
+
or _DISSENT_RATIONALE_RE.fullmatch(line)
|
|
1174
|
+
)
|
|
1175
|
+
dims = [match.group("dim") for line in visible
|
|
1176
|
+
if (match := _DISSENT_DIM_RE.fullmatch(line))]
|
|
1177
|
+
rationales = [
|
|
1178
|
+
match.group("text") for line in visible
|
|
1179
|
+
if (match := _DISSENT_RATIONALE_RE.fullmatch(line))
|
|
1180
|
+
]
|
|
1181
|
+
# Scanned on the RAW span, not the sanitized body: a field the sanitizers
|
|
1182
|
+
# delete (fence, comment) or relocate (its own H2) would otherwise reach
|
|
1183
|
+
# the tolerance branch as an empty section. A field line the canonical
|
|
1184
|
+
# parse never saw is a dissent this seat cannot be credited with, so it
|
|
1185
|
+
# fails whether or not it is canonically spelled. Counted rather than
|
|
1186
|
+
# matched by value, so a hidden copy of a canonical field is still one
|
|
1187
|
+
# unparsed occurrence and cannot ride in on its twin's identity.
|
|
1188
|
+
hidden = Counter(
|
|
1189
|
+
candidate for candidate in raw_span
|
|
1190
|
+
if _is_dissent_field_shaped(candidate)
|
|
1191
|
+
)
|
|
1192
|
+
# Raw HTML owns its own diagnostic even when stripping the tag leaves a
|
|
1193
|
+
# field-shaped line. Checking it first prevents a ``<span>dimension_id``
|
|
1194
|
+
# wrapper from being mislabeled as comment hiding by the older generic
|
|
1195
|
+
# raw-span occurrence guard. Comment-only cards never populate this list
|
|
1196
|
+
# and retain the #613 ``[DISSENT-HIDDEN]`` path below.
|
|
1197
|
+
if span.raw_html:
|
|
1198
|
+
raise ConformanceError(
|
|
1199
|
+
"[DISSENT-RAW-HTML: raw HTML tags or delimiters are forbidden "
|
|
1200
|
+
"inside ## Scoring Plan Dissent; put markup mentions in inline "
|
|
1201
|
+
"code]"
|
|
1202
|
+
)
|
|
1203
|
+
if any(count > parsed[value] for value, count in hidden.items()):
|
|
1204
|
+
# Distinct marker (#613 security round 1, P3): these fields ARE
|
|
1205
|
+
# canonical — the failure is that comment markup hides them from
|
|
1206
|
+
# the rendered card, and pointing the operator at line grammar
|
|
1207
|
+
# misattributes an unretryable abort.
|
|
1208
|
+
raise ConformanceError(
|
|
1209
|
+
"[DISSENT-HIDDEN: a canonical dissent field is hidden from the "
|
|
1210
|
+
"rendered card by comment markup; write dissent fields in the "
|
|
1211
|
+
"clear and mention comment syntax only in inline code]"
|
|
1212
|
+
)
|
|
1213
|
+
if any(
|
|
1214
|
+
_is_dissent_field_shaped(candidate) and candidate not in parsed
|
|
1215
|
+
for candidate in visible
|
|
1216
|
+
):
|
|
1217
|
+
raise ConformanceError(
|
|
1218
|
+
"[DISSENT-GRAMMAR: dissent fields must be canonical unbulleted "
|
|
1219
|
+
"dimension_id: and rationale: lines]"
|
|
1220
|
+
)
|
|
1221
|
+
if not dims and not rationales:
|
|
1222
|
+
# A section that spells no dissent field carries the same information
|
|
1223
|
+
# as an absent section, so it is read as no dissent instead of
|
|
1224
|
+
# aborting an unretryable Phase 2. The occurrence stays auditable in
|
|
1225
|
+
# the run record, and full Phase 1 trigger binding still applies to
|
|
1226
|
+
# every dimension.
|
|
1227
|
+
return DissentParse(
|
|
1228
|
+
set(), [_empty_dissent_section_diagnostic(raw_span)]
|
|
1229
|
+
)
|
|
1230
|
+
h2_positions = {
|
|
1231
|
+
match.group(1): index
|
|
1232
|
+
for index, line in enumerate(lines)
|
|
1233
|
+
if (match := panel._H2_RE.fullmatch(line))
|
|
1234
|
+
}
|
|
1235
|
+
if h2_positions["Scoring Plan Dissent"] > h2_positions.get(
|
|
1236
|
+
"Dimension Scores", -1
|
|
1237
|
+
):
|
|
1238
|
+
raise ConformanceError(
|
|
1239
|
+
"[DISSENT-GRAMMAR: ## Scoring Plan Dissent must precede "
|
|
1240
|
+
"## Dimension Scores]"
|
|
1241
|
+
)
|
|
1242
|
+
if not dims:
|
|
1243
|
+
raise ConformanceError(
|
|
1244
|
+
"[DISSENT-GRAMMAR: dissent section must name dimension_id]"
|
|
1245
|
+
)
|
|
1246
|
+
if len(dims) != len(set(dims)):
|
|
1247
|
+
raise ConformanceError("[DISSENT-GRAMMAR: duplicate dimension_id]")
|
|
1248
|
+
if len(rationales) != len(dims):
|
|
1249
|
+
raise ConformanceError(
|
|
1250
|
+
"[DISSENT-GRAMMAR: each dissent requires one rationale: line]"
|
|
1251
|
+
)
|
|
1252
|
+
return DissentParse(set(dims), [])
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
def check_trigger_binding(
|
|
1256
|
+
report: panel.ReviewerReport,
|
|
1257
|
+
plan: PhaseOnePlan,
|
|
1258
|
+
dimensions: dict[str, dict],
|
|
1259
|
+
dissent: set[str],
|
|
1260
|
+
) -> None:
|
|
1261
|
+
if len(dissent) >= 2:
|
|
1262
|
+
raise ConformanceError(
|
|
1263
|
+
f"[PROTOCOL-VIOLATION: multi_dissent=true, "
|
|
1264
|
+
f"dimensions={sorted(dissent)}]"
|
|
1265
|
+
)
|
|
1266
|
+
unknown = dissent - set(dimensions)
|
|
1267
|
+
if unknown:
|
|
1268
|
+
raise ConformanceError(
|
|
1269
|
+
f"[DISSENT-GRAMMAR: unknown dimensions {sorted(unknown)}]"
|
|
1270
|
+
)
|
|
1271
|
+
uncommitted = dissent - set(plan.commitments)
|
|
1272
|
+
if uncommitted:
|
|
1273
|
+
raise ConformanceError(
|
|
1274
|
+
f"[DISSENT-GRAMMAR: dissent dimensions were not committed by "
|
|
1275
|
+
f"this seat {sorted(uncommitted)}]"
|
|
1276
|
+
)
|
|
1277
|
+
for did, value in report.scores.items():
|
|
1278
|
+
eligible = report.role in dimensions[did]["eligible_roles"]
|
|
1279
|
+
needs_trigger = eligible and value.score in {"block", "warn"}
|
|
1280
|
+
if needs_trigger != bool(value.trigger):
|
|
1281
|
+
raise ConformanceError(
|
|
1282
|
+
f"[TRIGGER-GRAMMAR: {report.path}: {did} trigger is required "
|
|
1283
|
+
"iff an eligible dimension scores block or warn]"
|
|
1284
|
+
)
|
|
1285
|
+
if did in dissent:
|
|
1286
|
+
if value.block_class == "fatal":
|
|
1287
|
+
raise ConformanceError(
|
|
1288
|
+
f"[DISSENT-FATALITY: {did} dissent may not mint fatality]"
|
|
1289
|
+
)
|
|
1290
|
+
continue
|
|
1291
|
+
if not value.trigger:
|
|
1292
|
+
continue
|
|
1293
|
+
if value.score == "warn":
|
|
1294
|
+
field = "what_triggers_warn"
|
|
1295
|
+
elif value.block_class == "fatal":
|
|
1296
|
+
field = "what_triggers_fatal"
|
|
1297
|
+
else:
|
|
1298
|
+
field = "what_triggers_block"
|
|
1299
|
+
committed = plan.commitments.get(did, {}).get(field)
|
|
1300
|
+
if not committed or _normalise(value.trigger) not in _normalise(committed):
|
|
1301
|
+
raise ConformanceError(
|
|
1302
|
+
f"[TRIGGER-DRIFT: {did} {field} does not contain emitted "
|
|
1303
|
+
"trigger text]"
|
|
1304
|
+
)
|
|
1305
|
+
matching_fields = {
|
|
1306
|
+
candidate
|
|
1307
|
+
for candidate in (
|
|
1308
|
+
"what_triggers_block", "what_triggers_warn",
|
|
1309
|
+
"what_triggers_fatal",
|
|
1310
|
+
)
|
|
1311
|
+
if plan.commitments.get(did, {}).get(candidate)
|
|
1312
|
+
and _normalise(value.trigger) in _normalise(
|
|
1313
|
+
plan.commitments[did][candidate]
|
|
1314
|
+
)
|
|
1315
|
+
}
|
|
1316
|
+
if matching_fields != {field}:
|
|
1317
|
+
raise ConformanceError(
|
|
1318
|
+
f"[TRIGGER-AMBIGUOUS: {did} emitted trigger matches "
|
|
1319
|
+
f"{sorted(matching_fields)}, expected only {field}]"
|
|
1320
|
+
)
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
def _validate_anchor(anchor: str, context: str) -> None:
|
|
1324
|
+
try:
|
|
1325
|
+
panel.validate_evidence_anchor(anchor, context)
|
|
1326
|
+
except panel.ReportError as exc:
|
|
1327
|
+
raise ConformanceError(str(exc)) from exc
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
def check_scoring_seat_anchors(report: panel.ReviewerReport) -> None:
|
|
1331
|
+
lines = panel.strip_fences(report.text)
|
|
1332
|
+
sections, dupes = panel.split_sections(lines)
|
|
1333
|
+
if "Review Body" in dupes or "Review Body" not in sections:
|
|
1334
|
+
raise ConformanceError(
|
|
1335
|
+
f"[REVIEW-BODY-MISSING: {report.path}]"
|
|
1336
|
+
)
|
|
1337
|
+
current_h2 = None
|
|
1338
|
+
for line in lines:
|
|
1339
|
+
if match := panel._H2_RE.fullmatch(line):
|
|
1340
|
+
current_h2 = match.group(1)
|
|
1341
|
+
elif _SEVERITY_DECL_RE.search(line) and current_h2 != "Review Body":
|
|
1342
|
+
raise ConformanceError(
|
|
1343
|
+
f"[FINDING-GRAMMAR: {report.path}: Severity outside "
|
|
1344
|
+
"## Review Body]"
|
|
1345
|
+
)
|
|
1346
|
+
review_lines = sections["Review Body"]
|
|
1347
|
+
blocks, subsection_dupes = panel.split_subsections(review_lines)
|
|
1348
|
+
if subsection_dupes:
|
|
1349
|
+
raise ConformanceError(
|
|
1350
|
+
f"[FINDING-GRAMMAR: {report.path}: duplicate finding heading]"
|
|
1351
|
+
)
|
|
1352
|
+
preamble = []
|
|
1353
|
+
for line in review_lines:
|
|
1354
|
+
if panel._H3_RE.fullmatch(line):
|
|
1355
|
+
break
|
|
1356
|
+
preamble.append(line)
|
|
1357
|
+
if any(_SEVERITY_DECL_RE.search(line) for line in preamble):
|
|
1358
|
+
raise ConformanceError(
|
|
1359
|
+
f"[FINDING-GRAMMAR: {report.path}: every finding with "
|
|
1360
|
+
"Severity must have its own ### finding heading]"
|
|
1361
|
+
)
|
|
1362
|
+
for title, block in blocks.items():
|
|
1363
|
+
severity_declarations = sum(
|
|
1364
|
+
len(_SEVERITY_DECL_RE.findall(line)) for line in block
|
|
1365
|
+
)
|
|
1366
|
+
severities = [
|
|
1367
|
+
match.group("severity") for line in block
|
|
1368
|
+
for match in _SEVERITY_RE.finditer(line)
|
|
1369
|
+
]
|
|
1370
|
+
is_finding = _FINDING_H3_RE.fullmatch(title) is not None
|
|
1371
|
+
if severity_declarations and not is_finding:
|
|
1372
|
+
raise ConformanceError(
|
|
1373
|
+
f"[FINDING-GRAMMAR: {report.path}: every finding with "
|
|
1374
|
+
"Severity must have its own ### W<n>: <title> heading]"
|
|
1375
|
+
)
|
|
1376
|
+
if is_finding and any(panel._H4_RE.fullmatch(line) for line in block):
|
|
1377
|
+
raise ConformanceError(
|
|
1378
|
+
f"[FINDING-GRAMMAR: {report.path}: {title} may not nest "
|
|
1379
|
+
"a Severity finding under H4]"
|
|
1380
|
+
)
|
|
1381
|
+
if not is_finding:
|
|
1382
|
+
continue
|
|
1383
|
+
# A finding needs at least one parseable Severity declaration, and
|
|
1384
|
+
# every declaration must parse. When a card declares more than one
|
|
1385
|
+
# ACROSS lines and the chain strictly ESCALATES (Minor < Major <
|
|
1386
|
+
# Critical), the LAST in reading order is operative — the current
|
|
1387
|
+
# model generation self-corrects mid-card with explicit supersession
|
|
1388
|
+
# prose ("See the Severity line below, which supersedes the line
|
|
1389
|
+
# above", #637 ms01_quant r1: Major -> Critical), and Phase 2 permits
|
|
1390
|
+
# no retry for this class, so a strict exactly-one rule turns a
|
|
1391
|
+
# visible, reader-unambiguous correction into a whole-panel abort.
|
|
1392
|
+
# Every other multi-declaration shape keeps the loud abort:
|
|
1393
|
+
# de-escalation could waive the Critical/Major Evidence-Anchor
|
|
1394
|
+
# requirement by appending one weaker line; a non-monotone or
|
|
1395
|
+
# repeated-value chain signals several findings bundled under one W
|
|
1396
|
+
# heading (the one-finding-per-heading accounting feeds the severity
|
|
1397
|
+
# ladder); and two parseable declarations on ONE line are not a
|
|
1398
|
+
# reading-order correction at all. The advisory line below keeps the
|
|
1399
|
+
# full declaration trail in the gate log for adjudication.
|
|
1400
|
+
parseable_per_line = [
|
|
1401
|
+
sum(1 for _ in _SEVERITY_RE.finditer(line)) for line in block
|
|
1402
|
+
]
|
|
1403
|
+
if (not severities or len(severities) != severity_declarations
|
|
1404
|
+
or any(count > 1 for count in parseable_per_line)):
|
|
1405
|
+
raise ConformanceError(
|
|
1406
|
+
f"[FINDING-GRAMMAR: {report.path}: {title} must contain "
|
|
1407
|
+
"exactly one parseable Severity declaration]"
|
|
1408
|
+
)
|
|
1409
|
+
severity_rank = {"Minor": 0, "Major": 1, "Critical": 2}
|
|
1410
|
+
if any(severity_rank[later] <= severity_rank[earlier]
|
|
1411
|
+
for earlier, later in zip(severities, severities[1:])):
|
|
1412
|
+
raise ConformanceError(
|
|
1413
|
+
f"[FINDING-GRAMMAR: {report.path}: {title}: multiple "
|
|
1414
|
+
"Severity declarations must form a strictly escalating "
|
|
1415
|
+
"self-correction chain]"
|
|
1416
|
+
)
|
|
1417
|
+
if len(severities) > 1:
|
|
1418
|
+
print(
|
|
1419
|
+
f"[SEVERITY-SUPERSEDED: {report.path}: {title}: "
|
|
1420
|
+
+ " -> ".join(severities) + "]"
|
|
1421
|
+
)
|
|
1422
|
+
operative_severity = severities[-1]
|
|
1423
|
+
anchor_declarations = sum(
|
|
1424
|
+
len(_ANCHOR_DECL_RE.findall(line)) for line in block
|
|
1425
|
+
)
|
|
1426
|
+
anchors = [
|
|
1427
|
+
match.group("value") for line in block
|
|
1428
|
+
for match in _ANCHOR_RE.finditer(line)
|
|
1429
|
+
]
|
|
1430
|
+
if operative_severity not in {"Critical", "Major"}:
|
|
1431
|
+
if anchor_declarations > 1 or len(anchors) != anchor_declarations:
|
|
1432
|
+
raise ConformanceError(
|
|
1433
|
+
f"[FINDING-GRAMMAR: {report.path}: {title} may contain "
|
|
1434
|
+
"at most one parseable Evidence Anchor declaration]"
|
|
1435
|
+
)
|
|
1436
|
+
if anchors:
|
|
1437
|
+
_validate_anchor(anchors[0], f"{report.path}:{title}")
|
|
1438
|
+
continue
|
|
1439
|
+
if len(anchors) != 1 or anchor_declarations != 1:
|
|
1440
|
+
raise ConformanceError(
|
|
1441
|
+
f"[ANCHOR-MISSING: {report.path}: {title} "
|
|
1442
|
+
f"{operative_severity} finding needs exactly one "
|
|
1443
|
+
"Evidence Anchor]"
|
|
1444
|
+
)
|
|
1445
|
+
_validate_anchor(anchors[0], f"{report.path}:{title}")
|
|
1446
|
+
|
|
1447
|
+
|
|
1448
|
+
def check_da_anchors(report: panel.ReviewerReport) -> None:
|
|
1449
|
+
try:
|
|
1450
|
+
rows, major_anchors = panel.parse_da_tables(report.text, report.path)
|
|
1451
|
+
except panel.ReportError as exc:
|
|
1452
|
+
raise ConformanceError(str(exc)) from exc
|
|
1453
|
+
expected = [f"C{index}" for index in range(1, len(rows) + 1)]
|
|
1454
|
+
if list(rows) != expected:
|
|
1455
|
+
raise ConformanceError(
|
|
1456
|
+
f"[DA-CRITICAL-ID: {report.path}: IDs must be dense C1..Cn; "
|
|
1457
|
+
f"got={list(rows)}]"
|
|
1458
|
+
)
|
|
1459
|
+
for finding_id, anchor in rows.items():
|
|
1460
|
+
if not anchor:
|
|
1461
|
+
raise ConformanceError(
|
|
1462
|
+
f"[ANCHOR-MISSING: {report.path}: {finding_id}]"
|
|
1463
|
+
)
|
|
1464
|
+
_validate_anchor(anchor, f"{report.path}:{finding_id}")
|
|
1465
|
+
|
|
1466
|
+
for anchor in major_anchors:
|
|
1467
|
+
if not anchor:
|
|
1468
|
+
raise ConformanceError(
|
|
1469
|
+
f"[ANCHOR-MISSING: {report.path}: DA MAJOR row]"
|
|
1470
|
+
)
|
|
1471
|
+
_validate_anchor(anchor, f"{report.path}:DA MAJOR")
|
|
1472
|
+
|
|
1473
|
+
|
|
1474
|
+
def _one_receipt_field(
|
|
1475
|
+
lines: list[str], key: str, path: str, receipt_id: str, *, required: bool
|
|
1476
|
+
) -> str | None:
|
|
1477
|
+
hits = [
|
|
1478
|
+
match.group("value")
|
|
1479
|
+
for line in lines
|
|
1480
|
+
if (match := _RECEIPT_FIELD_RES[key].fullmatch(line))
|
|
1481
|
+
]
|
|
1482
|
+
expected = "exactly one" if required else "at most one"
|
|
1483
|
+
if (required and len(hits) != 1) or (not required and len(hits) > 1):
|
|
1484
|
+
raise ConformanceError(
|
|
1485
|
+
f"[RECEIPT-GRAMMAR: {path}: {receipt_id} expected {expected} "
|
|
1486
|
+
f"canonical {key}: line, found {len(hits)}]"
|
|
1487
|
+
)
|
|
1488
|
+
return hits[0] if hits else None
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
def _review_body_receipt_backrefs(
|
|
1492
|
+
report: panel.ReviewerReport,
|
|
1493
|
+
) -> tuple[set[str], dict[str, list[str]]]:
|
|
1494
|
+
"""W-finding ids and per-block `**Arithmetic Receipt**:` back-references.
|
|
1495
|
+
|
|
1496
|
+
Back-references are collected across the WHOLE Review Body — preamble and
|
|
1497
|
+
non-W subsections included — so a back-reference stranded outside its
|
|
1498
|
+
weakness block is a linkage failure, not invisible. The walk is
|
|
1499
|
+
fence-aware: only unfenced headings delimit (agreeing with
|
|
1500
|
+
`split_sections`), a fenced or decorated back-reference declaration is
|
|
1501
|
+
counted by the lenient detector, and a declaration count exceeding the
|
|
1502
|
+
canonical parse aborts — a back-reference the seat spelled but the
|
|
1503
|
+
grammar refused is a linkage failure, never a silent drop.
|
|
1504
|
+
"""
|
|
1505
|
+
lines = panel.strip_fences(report.text)
|
|
1506
|
+
sections, _ = panel.split_sections(lines)
|
|
1507
|
+
review_lines = sections.get("Review Body", [])
|
|
1508
|
+
blocks, _ = panel.split_subsections(review_lines)
|
|
1509
|
+
finding_ids = {
|
|
1510
|
+
title.split(":", 1)[0]
|
|
1511
|
+
for title in blocks
|
|
1512
|
+
if _FINDING_H3_RE.fullmatch(title)
|
|
1513
|
+
}
|
|
1514
|
+
backrefs: dict[str, list[str]] = {}
|
|
1515
|
+
current = "(preamble)"
|
|
1516
|
+
inside = False
|
|
1517
|
+
inline_commented = False
|
|
1518
|
+
for line, fenced, hidden, code_indented in _lines_with_hidden_state(
|
|
1519
|
+
report.text
|
|
1520
|
+
):
|
|
1521
|
+
if not fenced and (match := panel._H2_RE.fullmatch(line)):
|
|
1522
|
+
inside = match.group(1) == "Review Body"
|
|
1523
|
+
current = "(preamble)"
|
|
1524
|
+
inline_commented = False
|
|
1525
|
+
continue
|
|
1526
|
+
if not inside:
|
|
1527
|
+
continue
|
|
1528
|
+
parse_line = line
|
|
1529
|
+
if not fenced and not hidden and not code_indented:
|
|
1530
|
+
# A paragraph-inline comment span cannot cross a paragraph
|
|
1531
|
+
# boundary: a blank line or an interrupting ATX heading (any
|
|
1532
|
+
# level) ends the paragraph and with it the raw-HTML span, so
|
|
1533
|
+
# the state resets there instead of leaking a false abort into
|
|
1534
|
+
# later prose. Code spans are blanked first, matching the
|
|
1535
|
+
# renderer's precedence: a literal `<!--` inside inline code
|
|
1536
|
+
# opens nothing (#610 round-4).
|
|
1537
|
+
if (
|
|
1538
|
+
not line.strip(" \t")
|
|
1539
|
+
or _ATX_HEADING_RE.match(line)
|
|
1540
|
+
):
|
|
1541
|
+
inline_commented = False
|
|
1542
|
+
else:
|
|
1543
|
+
code_blanked = _blank_code_spans(line)
|
|
1544
|
+
visible, hidden_segments, inline_commented = (
|
|
1545
|
+
_strip_inline_comment_spans(
|
|
1546
|
+
code_blanked, inline_commented
|
|
1547
|
+
)
|
|
1548
|
+
)
|
|
1549
|
+
if visible != code_blanked:
|
|
1550
|
+
# A declaration inside a hidden span aborts whether or
|
|
1551
|
+
# not the line also shows visible content (#610 round
|
|
1552
|
+
# 5): hidden prose is skippable, a hidden machine
|
|
1553
|
+
# declaration never is. Each span is checked on its
|
|
1554
|
+
# own (#610 round-6).
|
|
1555
|
+
if any(
|
|
1556
|
+
_backref_declared_count(segment)
|
|
1557
|
+
for segment in hidden_segments
|
|
1558
|
+
):
|
|
1559
|
+
raise ConformanceError(
|
|
1560
|
+
f"[RECEIPT-LINKAGE: {report.path}: an "
|
|
1561
|
+
"Arithmetic Receipt back-reference "
|
|
1562
|
+
"declaration sits inside a paragraph-"
|
|
1563
|
+
"inline HTML comment span and does not "
|
|
1564
|
+
"render; it is non-conforming]"
|
|
1565
|
+
)
|
|
1566
|
+
if not visible.strip(" \t"):
|
|
1567
|
+
continue
|
|
1568
|
+
# Only the rendered remainder is parsed: content after
|
|
1569
|
+
# a closing `-->` stays live, content behind an opener
|
|
1570
|
+
# is gone.
|
|
1571
|
+
parse_line = visible
|
|
1572
|
+
if fenced or hidden or code_indented:
|
|
1573
|
+
# Never credited: a machine declaration the page does not show
|
|
1574
|
+
# as a field line — fenced, commented, or rendered as indented
|
|
1575
|
+
# code — is a loud failure, not a silent drop or a silent
|
|
1576
|
+
# credit (#610 rounds 2-3).
|
|
1577
|
+
if _backref_declared_count(line):
|
|
1578
|
+
kind = (
|
|
1579
|
+
"fenced" if fenced
|
|
1580
|
+
else "commented-out" if hidden
|
|
1581
|
+
else "indented-code"
|
|
1582
|
+
)
|
|
1583
|
+
raise ConformanceError(
|
|
1584
|
+
f"[RECEIPT-LINKAGE: {report.path}: a {kind} Arithmetic "
|
|
1585
|
+
"Receipt back-reference declaration does not render as "
|
|
1586
|
+
"a field line and is non-conforming]"
|
|
1587
|
+
)
|
|
1588
|
+
continue
|
|
1589
|
+
if match := panel._H3_RE.fullmatch(line):
|
|
1590
|
+
title = match.group(1)
|
|
1591
|
+
current = (
|
|
1592
|
+
title.split(":", 1)[0]
|
|
1593
|
+
if _FINDING_H3_RE.fullmatch(title)
|
|
1594
|
+
else f"(non-finding: {title})"
|
|
1595
|
+
)
|
|
1596
|
+
continue
|
|
1597
|
+
# Canonical parsing and the declaration count run on the SAME
|
|
1598
|
+
# display form (#610 round-4 P1): parsing the raw line while
|
|
1599
|
+
# counting on a blanked one let a code-span declaration be
|
|
1600
|
+
# credited over the rendered field beside it.
|
|
1601
|
+
display = _cell_split_form(parse_line)
|
|
1602
|
+
matches = list(_RECEIPT_BACKREF_RE.finditer(display))
|
|
1603
|
+
if _backref_declared_count(display) != len(matches):
|
|
1604
|
+
raise ConformanceError(
|
|
1605
|
+
f"[RECEIPT-LINKAGE: {report.path}: back-reference "
|
|
1606
|
+
f"declaration(s) on {line.strip()!r} do not all parse "
|
|
1607
|
+
"canonically — each value must be exactly "
|
|
1608
|
+
"**Arithmetic Receipt**: AR<n> with no trailing text]"
|
|
1609
|
+
)
|
|
1610
|
+
for match in matches:
|
|
1611
|
+
backrefs.setdefault(current, []).append(match.group("value"))
|
|
1612
|
+
return finding_ids, backrefs
|
|
1613
|
+
|
|
1614
|
+
|
|
1615
|
+
_INDENTED_H2_RE = re.compile(r"^ {1,3}##\s+\S")
|
|
1616
|
+
# Any ATX heading interrupts a paragraph (CommonMark), so any level ends a
|
|
1617
|
+
# paragraph-inline comment span — resetting only on H3 false-aborted a
|
|
1618
|
+
# rendered backref after an interrupting H4 (#610 round-4).
|
|
1619
|
+
_ATX_HEADING_RE = re.compile(r"^ {0,3}#{1,6}(?:[ \t]|$)")
|
|
1620
|
+
|
|
1621
|
+
|
|
1622
|
+
def _receipt_section_view(
|
|
1623
|
+
text: str,
|
|
1624
|
+
) -> tuple[list[tuple[str, bool]], list[str], bool]:
|
|
1625
|
+
"""Receipt-section lines with visibility, the unfenced H2 order, and
|
|
1626
|
+
whether an indented H2 renders after the section opened.
|
|
1627
|
+
|
|
1628
|
+
The section body is read through `_lines_with_hidden_state` rather than
|
|
1629
|
+
`strip_fences` (#610 rounds 1-2, the #637/#609 convergence): a model
|
|
1630
|
+
that fences its receipt block still WROTE the receipts, so the fenced
|
|
1631
|
+
lines are read as content — no false abort — while a fenced `### AR<n>`
|
|
1632
|
+
block sitting beside an unfenced attestation is SEEN and aborts as
|
|
1633
|
+
receipts-plus-attestation instead of hiding. Each content line carries
|
|
1634
|
+
its hidden flag so a machine line inside an HTML comment — which the
|
|
1635
|
+
rendered card does not show — can abort rather than be credited. Only
|
|
1636
|
+
an unfenced H2 delimits, agreeing with `split_sections` (a COMMENTED
|
|
1637
|
+
heading therefore still delimits, as in `_raw_dissent_span`), so a
|
|
1638
|
+
fenced heading can neither open nor close the section. The H2 title is
|
|
1639
|
+
matched exactly, case-sensitively, consistent with every other section
|
|
1640
|
+
grammar. A 1-3-space-indented `##` line is not a section to this
|
|
1641
|
+
grammar but still renders as a heading, so one appearing after the
|
|
1642
|
+
receipt section opens is reported for the terminal-section rule.
|
|
1643
|
+
"""
|
|
1644
|
+
h2_titles: list[str] = []
|
|
1645
|
+
section: list[tuple[str, bool, bool]] = []
|
|
1646
|
+
inside = False
|
|
1647
|
+
indented_h2_after = False
|
|
1648
|
+
for line, fenced, hidden, _ in _lines_with_hidden_state(text):
|
|
1649
|
+
if not fenced and (match := panel._H2_RE.fullmatch(line)):
|
|
1650
|
+
h2_titles.append(match.group(1))
|
|
1651
|
+
inside = match.group(1) == _RECEIPT_SECTION
|
|
1652
|
+
continue
|
|
1653
|
+
if inside:
|
|
1654
|
+
if not fenced and not hidden and _INDENTED_H2_RE.match(line):
|
|
1655
|
+
indented_h2_after = True
|
|
1656
|
+
section.append((line, hidden, fenced))
|
|
1657
|
+
return section, h2_titles, indented_h2_after
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
def check_methodology_receipts(report: panel.ReviewerReport) -> None:
|
|
1661
|
+
"""#610 arithmetic-receipt gate, methodology seat only.
|
|
1662
|
+
|
|
1663
|
+
Auditability, not truth: this gate proves the receipt fields exist, use
|
|
1664
|
+
the closed enums, and link mismatches to weaknesses bidirectionally. It
|
|
1665
|
+
never attests that the arithmetic is correct — that judgment stays with
|
|
1666
|
+
human adjudication (`VERIFIED` / `MISCOMPUTED`), per the spec's invariant
|
|
1667
|
+
6 and the receipt block's leading epistemic-status note.
|
|
1668
|
+
"""
|
|
1669
|
+
view, h2_titles, indented_h2_after = _receipt_section_view(report.text)
|
|
1670
|
+
occurrences = h2_titles.count(_RECEIPT_SECTION)
|
|
1671
|
+
if occurrences > 1:
|
|
1672
|
+
raise ConformanceError(
|
|
1673
|
+
f"[RECEIPT-GRAMMAR: {report.path}: duplicate "
|
|
1674
|
+
f"## {_RECEIPT_SECTION}]"
|
|
1675
|
+
)
|
|
1676
|
+
if occurrences == 0:
|
|
1677
|
+
raise ConformanceError(
|
|
1678
|
+
f"[RECEIPT-MISSING: {report.path}: methodology card requires "
|
|
1679
|
+
f"exactly one ## {_RECEIPT_SECTION} section]"
|
|
1680
|
+
)
|
|
1681
|
+
if h2_titles[-1] != _RECEIPT_SECTION or indented_h2_after:
|
|
1682
|
+
raise ConformanceError(
|
|
1683
|
+
f"[RECEIPT-GRAMMAR: {report.path}: ## {_RECEIPT_SECTION} must "
|
|
1684
|
+
"be the final section of the card]"
|
|
1685
|
+
)
|
|
1686
|
+
in_subsection = False
|
|
1687
|
+
for line, hidden, fenced in view:
|
|
1688
|
+
if not fenced and ("<!--" in line or "-->" in line):
|
|
1689
|
+
# Comment-free zone (#610 round-3 P1): the receipt section is
|
|
1690
|
+
# machine lines, so a paragraph-inline `prose <!--` opener —
|
|
1691
|
+
# which the block-position visibility model deliberately does
|
|
1692
|
+
# not read — could otherwise launder the receipts below it out
|
|
1693
|
+
# of the rendered card. Banning the markup outright is
|
|
1694
|
+
# deterministic and costs no legitimate content; fenced lines
|
|
1695
|
+
# render the markup literally and stay exempt.
|
|
1696
|
+
raise ConformanceError(
|
|
1697
|
+
f"[RECEIPT-GRAMMAR: {report.path}: HTML comment markup "
|
|
1698
|
+
f"{line.strip()!r} is not allowed in the receipt section]"
|
|
1699
|
+
)
|
|
1700
|
+
if panel._H3_RE.fullmatch(line):
|
|
1701
|
+
if hidden:
|
|
1702
|
+
raise ConformanceError(
|
|
1703
|
+
f"[RECEIPT-GRAMMAR: {report.path}: receipt heading "
|
|
1704
|
+
f"{line.strip()!r} is inside an HTML comment; the "
|
|
1705
|
+
"rendered card does not show it]"
|
|
1706
|
+
)
|
|
1707
|
+
in_subsection = True
|
|
1708
|
+
continue
|
|
1709
|
+
canonical = bool(
|
|
1710
|
+
_RECEIPT_ATTESTATION_RE.fullmatch(line)
|
|
1711
|
+
or any(
|
|
1712
|
+
pattern.fullmatch(line)
|
|
1713
|
+
for pattern in _RECEIPT_FIELD_RES.values()
|
|
1714
|
+
)
|
|
1715
|
+
)
|
|
1716
|
+
if canonical:
|
|
1717
|
+
if hidden:
|
|
1718
|
+
raise ConformanceError(
|
|
1719
|
+
f"[RECEIPT-GRAMMAR: {report.path}: receipt machine "
|
|
1720
|
+
f"line {line.strip()!r} is inside an HTML comment; the "
|
|
1721
|
+
"rendered card does not show it]"
|
|
1722
|
+
)
|
|
1723
|
+
if (
|
|
1724
|
+
not in_subsection
|
|
1725
|
+
and not _RECEIPT_ATTESTATION_RE.fullmatch(line)
|
|
1726
|
+
):
|
|
1727
|
+
raise ConformanceError(
|
|
1728
|
+
f"[RECEIPT-GRAMMAR: {report.path}: receipt machine "
|
|
1729
|
+
f"line {line.strip()!r} sits outside every ### AR<n> "
|
|
1730
|
+
"subsection; the enum and linkage gates never inspect "
|
|
1731
|
+
"it there]"
|
|
1732
|
+
)
|
|
1733
|
+
continue
|
|
1734
|
+
if any(
|
|
1735
|
+
_is_receipt_field_shaped(candidate)
|
|
1736
|
+
for candidate in _receipt_shape_candidates(line)
|
|
1737
|
+
):
|
|
1738
|
+
# One neutral message for both visibility states: an opener
|
|
1739
|
+
# line like `<!-- c --> key: value` is hidden to the model but
|
|
1740
|
+
# partially renders, so claiming "commented-out" would misread
|
|
1741
|
+
# the page to the seat (#610 round-3 P3).
|
|
1742
|
+
raise ConformanceError(
|
|
1743
|
+
f"[RECEIPT-GRAMMAR: {report.path}: receipt machine line "
|
|
1744
|
+
f"{line.strip()!r} is decorated or non-canonical; write "
|
|
1745
|
+
"plain unbulleted key: value text (tolerated: one "
|
|
1746
|
+
"leading list marker, balanced bold around the key)]"
|
|
1747
|
+
)
|
|
1748
|
+
body = [line for line, _, _ in view]
|
|
1749
|
+
attestations = [
|
|
1750
|
+
line for line in body if _RECEIPT_ATTESTATION_RE.fullmatch(line)
|
|
1751
|
+
]
|
|
1752
|
+
subsections, sub_dupes = panel.split_subsections(body)
|
|
1753
|
+
if sub_dupes:
|
|
1754
|
+
raise ConformanceError(
|
|
1755
|
+
f"[RECEIPT-GRAMMAR: {report.path}: duplicate receipt heading(s) "
|
|
1756
|
+
f"{sorted(sub_dupes)}]"
|
|
1757
|
+
)
|
|
1758
|
+
for title in subsections:
|
|
1759
|
+
if not _RECEIPT_H3_RE.fullmatch(title):
|
|
1760
|
+
raise ConformanceError(
|
|
1761
|
+
f"[RECEIPT-GRAMMAR: {report.path}: invalid receipt "
|
|
1762
|
+
f"subsection '### {title}'; expected ### AR<n>]"
|
|
1763
|
+
)
|
|
1764
|
+
if not subsections:
|
|
1765
|
+
if len(attestations) != 1:
|
|
1766
|
+
raise ConformanceError(
|
|
1767
|
+
f"[RECEIPT-GRAMMAR: {report.path}: a receipt section with "
|
|
1768
|
+
"no ### AR<n> subsection requires exactly one "
|
|
1769
|
+
f"no_recomputable_statistics: line, found {len(attestations)}]"
|
|
1770
|
+
)
|
|
1771
|
+
# Honest-claim boundary (#610 round-1 fix 2): the gate proved only
|
|
1772
|
+
# that the declaration EXISTS. Whether the manuscript really has
|
|
1773
|
+
# nothing recomputable is judged at adjudication, so the pass is
|
|
1774
|
+
# annotated rather than silent.
|
|
1775
|
+
print(_RECEIPT_ATTESTATION_ADVISORY)
|
|
1776
|
+
return
|
|
1777
|
+
if attestations:
|
|
1778
|
+
raise ConformanceError(
|
|
1779
|
+
f"[RECEIPT-GRAMMAR: {report.path}: no_recomputable_statistics: "
|
|
1780
|
+
"is forbidden when ### AR<n> receipts exist]"
|
|
1781
|
+
)
|
|
1782
|
+
expected_ids = [f"AR{index}" for index in range(1, len(subsections) + 1)]
|
|
1783
|
+
if list(subsections) != expected_ids:
|
|
1784
|
+
raise ConformanceError(
|
|
1785
|
+
f"[RECEIPT-GRAMMAR: {report.path}: receipt IDs must be dense "
|
|
1786
|
+
f"AR1..ARn in order; got={list(subsections)}]"
|
|
1787
|
+
)
|
|
1788
|
+
finding_ids, backrefs = _review_body_receipt_backrefs(report)
|
|
1789
|
+
mismatch_refs: dict[str, str] = {}
|
|
1790
|
+
for receipt_id, sublines in subsections.items():
|
|
1791
|
+
procedure = _one_receipt_field(
|
|
1792
|
+
sublines, "procedure_id", report.path, receipt_id, required=True
|
|
1793
|
+
)
|
|
1794
|
+
if procedure not in _RECEIPT_PROCEDURES:
|
|
1795
|
+
raise ConformanceError(
|
|
1796
|
+
f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} "
|
|
1797
|
+
f"procedure_id '{procedure}' is not a bounded procedure]"
|
|
1798
|
+
)
|
|
1799
|
+
anchor = _one_receipt_field(
|
|
1800
|
+
sublines, "evidence_anchor", report.path, receipt_id,
|
|
1801
|
+
required=True,
|
|
1802
|
+
)
|
|
1803
|
+
_validate_anchor(anchor, f"{report.path}:{receipt_id}")
|
|
1804
|
+
for key in (
|
|
1805
|
+
"reported_inputs", "assumptions", "derivation",
|
|
1806
|
+
"comparison_rule",
|
|
1807
|
+
):
|
|
1808
|
+
_one_receipt_field(
|
|
1809
|
+
sublines, key, report.path, receipt_id, required=True
|
|
1810
|
+
)
|
|
1811
|
+
derived = _one_receipt_field(
|
|
1812
|
+
sublines, "derived_value_or_range", report.path, receipt_id,
|
|
1813
|
+
required=True,
|
|
1814
|
+
)
|
|
1815
|
+
status = _one_receipt_field(
|
|
1816
|
+
sublines, "status", report.path, receipt_id, required=True
|
|
1817
|
+
)
|
|
1818
|
+
if status not in _RECEIPT_STATUSES:
|
|
1819
|
+
raise ConformanceError(
|
|
1820
|
+
f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} status "
|
|
1821
|
+
f"'{status}' is not in the closed status enum]"
|
|
1822
|
+
)
|
|
1823
|
+
reason = _one_receipt_field(
|
|
1824
|
+
sublines, "not_computable_reason", report.path, receipt_id,
|
|
1825
|
+
required=(status == "not_computable"),
|
|
1826
|
+
)
|
|
1827
|
+
if status != "not_computable" and reason is not None:
|
|
1828
|
+
raise ConformanceError(
|
|
1829
|
+
f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} "
|
|
1830
|
+
"not_computable_reason is forbidden unless status is "
|
|
1831
|
+
"not_computable]"
|
|
1832
|
+
)
|
|
1833
|
+
if reason is not None and reason not in _NOT_COMPUTABLE_REASONS:
|
|
1834
|
+
raise ConformanceError(
|
|
1835
|
+
f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} "
|
|
1836
|
+
f"not_computable_reason '{reason}' is not in the closed v1 "
|
|
1837
|
+
"enum]"
|
|
1838
|
+
)
|
|
1839
|
+
tail = None
|
|
1840
|
+
for key, procedures in _PROCEDURE_FIELD_OWNERS.items():
|
|
1841
|
+
if procedure in procedures:
|
|
1842
|
+
required = (
|
|
1843
|
+
key in _ALWAYS_REQUIRED_PROCEDURE_FIELDS
|
|
1844
|
+
or status in _RECEIPT_VERDICT_STATUSES
|
|
1845
|
+
)
|
|
1846
|
+
value = _one_receipt_field(
|
|
1847
|
+
sublines, key, report.path, receipt_id, required=required
|
|
1848
|
+
)
|
|
1849
|
+
if key == "tail_convention":
|
|
1850
|
+
tail = value
|
|
1851
|
+
else:
|
|
1852
|
+
if _one_receipt_field(
|
|
1853
|
+
sublines, key, report.path, receipt_id, required=False
|
|
1854
|
+
) is not None:
|
|
1855
|
+
raise ConformanceError(
|
|
1856
|
+
f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} "
|
|
1857
|
+
f"{key} is forbidden for procedure {procedure}]"
|
|
1858
|
+
)
|
|
1859
|
+
if procedure == "p_from_test_statistic":
|
|
1860
|
+
if tail is not None and tail not in _TAIL_CONVENTIONS:
|
|
1861
|
+
raise ConformanceError(
|
|
1862
|
+
f"[RECEIPT-GRAMMAR: {report.path}: {receipt_id} "
|
|
1863
|
+
f"tail_convention '{tail}' is not in the closed enum]"
|
|
1864
|
+
)
|
|
1865
|
+
if tail == "unstated" and status in _RECEIPT_VERDICT_STATUSES:
|
|
1866
|
+
derived_fold = unicodedata.normalize(
|
|
1867
|
+
"NFKC", derived
|
|
1868
|
+
).casefold()
|
|
1869
|
+
if not all(
|
|
1870
|
+
_tail_value_shown(derived_fold, label_re)
|
|
1871
|
+
for label_re in _TAIL_LABEL_RES.values()
|
|
1872
|
+
):
|
|
1873
|
+
raise ConformanceError(
|
|
1874
|
+
f"[RECEIPT-TAILS: {report.path}: {receipt_id} "
|
|
1875
|
+
"unstated tail requires derived_value_or_range to "
|
|
1876
|
+
"show BOTH labeled VALUES — each of the two-tailed "
|
|
1877
|
+
"and one-tailed labels sharing its own ;-segment "
|
|
1878
|
+
"with its derived number, either order]"
|
|
1879
|
+
)
|
|
1880
|
+
finding_ref = _one_receipt_field(
|
|
1881
|
+
sublines, "finding_ref", report.path, receipt_id,
|
|
1882
|
+
required=(status == "mismatch"),
|
|
1883
|
+
)
|
|
1884
|
+
if status != "mismatch":
|
|
1885
|
+
if finding_ref is not None:
|
|
1886
|
+
raise ConformanceError(
|
|
1887
|
+
f"[RECEIPT-LINKAGE: {report.path}: {receipt_id} "
|
|
1888
|
+
"finding_ref is forbidden unless status is mismatch]"
|
|
1889
|
+
)
|
|
1890
|
+
continue
|
|
1891
|
+
if not _FINDING_REF_VALUE_RE.fullmatch(finding_ref):
|
|
1892
|
+
raise ConformanceError(
|
|
1893
|
+
f"[RECEIPT-LINKAGE: {report.path}: {receipt_id} finding_ref "
|
|
1894
|
+
f"'{finding_ref}' must name one W<n> weakness]"
|
|
1895
|
+
)
|
|
1896
|
+
if finding_ref not in finding_ids:
|
|
1897
|
+
raise ConformanceError(
|
|
1898
|
+
f"[RECEIPT-LINKAGE: {report.path}: {receipt_id} finding_ref "
|
|
1899
|
+
f"{finding_ref} has no matching ### {finding_ref} weakness "
|
|
1900
|
+
"in ## Review Body]"
|
|
1901
|
+
)
|
|
1902
|
+
if finding_ref in mismatch_refs:
|
|
1903
|
+
raise ConformanceError(
|
|
1904
|
+
f"[RECEIPT-LINKAGE: {report.path}: {finding_ref} is claimed "
|
|
1905
|
+
f"by both {mismatch_refs[finding_ref]} and {receipt_id}; "
|
|
1906
|
+
"no two receipts share a finding_ref]"
|
|
1907
|
+
)
|
|
1908
|
+
mismatch_refs[finding_ref] = receipt_id
|
|
1909
|
+
for finding_id, receipt_id in mismatch_refs.items():
|
|
1910
|
+
if backrefs.get(finding_id) != [receipt_id]:
|
|
1911
|
+
raise ConformanceError(
|
|
1912
|
+
f"[RECEIPT-LINKAGE: {report.path}: weakness {finding_id} "
|
|
1913
|
+
"must carry exactly one **Arithmetic Receipt**: "
|
|
1914
|
+
f"{receipt_id} back-reference, found "
|
|
1915
|
+
f"{backrefs.get(finding_id, [])}]"
|
|
1916
|
+
)
|
|
1917
|
+
for location, values in backrefs.items():
|
|
1918
|
+
for value in values:
|
|
1919
|
+
if mismatch_refs.get(location) != value:
|
|
1920
|
+
raise ConformanceError(
|
|
1921
|
+
f"[RECEIPT-LINKAGE: {report.path}: back-reference "
|
|
1922
|
+
f"{value} in {location} does not correspond to a "
|
|
1923
|
+
"mismatch receipt naming that weakness]"
|
|
1924
|
+
)
|
|
1925
|
+
|
|
1926
|
+
|
|
1927
|
+
def check_recompute_extraction(path: str, text: str) -> None:
|
|
1928
|
+
"""#610 step-5 extraction gate, methodology seat only.
|
|
1929
|
+
|
|
1930
|
+
The isolated numeric input surface: the response must be exactly one
|
|
1931
|
+
``## Recompute Extraction`` section of machine lines. The grammar
|
|
1932
|
+
authority is the calculator's own parser (`recompute_receipts`), so the
|
|
1933
|
+
gate and the consumer can never disagree about what an extraction says;
|
|
1934
|
+
this gate adds the response-level structure rules and the anchor-grammar
|
|
1935
|
+
check the calculator deliberately does not own.
|
|
1936
|
+
"""
|
|
1937
|
+
lines = panel.strip_fences(text)
|
|
1938
|
+
sections, dupes = panel.split_sections(lines)
|
|
1939
|
+
if recompute.EXTRACTION_SECTION in dupes:
|
|
1940
|
+
raise ConformanceError(
|
|
1941
|
+
f"[EXTRACTION-GRAMMAR: {path}: duplicate "
|
|
1942
|
+
f"## {recompute.EXTRACTION_SECTION}]"
|
|
1943
|
+
)
|
|
1944
|
+
if recompute.EXTRACTION_SECTION not in sections:
|
|
1945
|
+
raise ConformanceError(
|
|
1946
|
+
f"[EXTRACTION-GRAMMAR: {path}: exactly one "
|
|
1947
|
+
f"## {recompute.EXTRACTION_SECTION} section is required]"
|
|
1948
|
+
)
|
|
1949
|
+
extra = [
|
|
1950
|
+
title for title in list(sections) + list(dupes)
|
|
1951
|
+
if title != recompute.EXTRACTION_SECTION
|
|
1952
|
+
]
|
|
1953
|
+
if extra:
|
|
1954
|
+
raise ConformanceError(
|
|
1955
|
+
f"[EXTRACTION-GRAMMAR: {path}: the response may carry no "
|
|
1956
|
+
f"section other than ## {recompute.EXTRACTION_SECTION}; "
|
|
1957
|
+
f"found {extra}]"
|
|
1958
|
+
)
|
|
1959
|
+
preamble = []
|
|
1960
|
+
for line in lines:
|
|
1961
|
+
if panel._H2_RE.fullmatch(line):
|
|
1962
|
+
break
|
|
1963
|
+
preamble.append(line)
|
|
1964
|
+
if any(line.strip() for line in preamble):
|
|
1965
|
+
raise ConformanceError(
|
|
1966
|
+
f"[EXTRACTION-GRAMMAR: {path}: no content is allowed before "
|
|
1967
|
+
f"## {recompute.EXTRACTION_SECTION}]"
|
|
1968
|
+
)
|
|
1969
|
+
try:
|
|
1970
|
+
extraction = recompute.parse_extraction(text)
|
|
1971
|
+
except recompute.ExtractionError as exc:
|
|
1972
|
+
raise ConformanceError(
|
|
1973
|
+
f"[EXTRACTION-GRAMMAR: {path}: {exc}]"
|
|
1974
|
+
) from exc
|
|
1975
|
+
for request in extraction.requests:
|
|
1976
|
+
_validate_anchor(
|
|
1977
|
+
request["evidence_anchor"], f"{path}:{request.rr_id}"
|
|
1978
|
+
)
|
|
1979
|
+
if extraction.attestation is not None:
|
|
1980
|
+
# Same declaration-only honesty boundary as the receipt attestation.
|
|
1981
|
+
print(_RECEIPT_ATTESTATION_ADVISORY)
|
|
1982
|
+
|
|
1983
|
+
|
|
1984
|
+
def check_injected_receipts(
|
|
1985
|
+
report: panel.ReviewerReport, injected_text: str
|
|
1986
|
+
) -> None:
|
|
1987
|
+
"""#610 step-5 identity gate: the card's receipt section must be the
|
|
1988
|
+
dispatcher-injected receipts verbatim, plus only the canonical
|
|
1989
|
+
``finding_ref:`` lines the mismatch receipts require.
|
|
1990
|
+
|
|
1991
|
+
Runs AFTER `check_methodology_receipts`, so grammar, enum, linkage, and
|
|
1992
|
+
per-subsection `finding_ref` placement are already proven; this gate
|
|
1993
|
+
proves nothing else changed. The comparison ignores blank lines and
|
|
1994
|
+
reads the card fence-transparently — the same view the receipt gate
|
|
1995
|
+
uses — so a decorated or re-spelled injected line fails identity loudly
|
|
1996
|
+
rather than being silently re-read.
|
|
1997
|
+
"""
|
|
1998
|
+
injected_lines = [
|
|
1999
|
+
line.rstrip("\r") for line in injected_text.split("\n")
|
|
2000
|
+
]
|
|
2001
|
+
heading = f"## {recompute.RECEIPT_SECTION}"
|
|
2002
|
+
if not injected_lines or injected_lines[0] != heading:
|
|
2003
|
+
raise panel.ContractError(
|
|
2004
|
+
f"[INJECTED-RECEIPTS-INVALID: the injected file must begin "
|
|
2005
|
+
f"with {heading!r}]"
|
|
2006
|
+
)
|
|
2007
|
+
expected = [line for line in injected_lines[1:] if line.strip()]
|
|
2008
|
+
view, _, _ = _receipt_section_view(report.text)
|
|
2009
|
+
# Only the PLAIN spelling is the permitted addition (codex round 1,
|
|
2010
|
+
# P2-4): the receipt grammar tolerates a decorated finding_ref, but
|
|
2011
|
+
# under injection "add exactly one finding_ref: line" means the
|
|
2012
|
+
# undecorated canonical form — a bolded or bulleted spelling stays in
|
|
2013
|
+
# the comparison and fails identity loudly.
|
|
2014
|
+
plain_finding_ref = re.compile(r"^finding_ref: W[1-9]\d*$")
|
|
2015
|
+
actual = [
|
|
2016
|
+
line for line, _, _ in view
|
|
2017
|
+
if line.strip() and not plain_finding_ref.fullmatch(line)
|
|
2018
|
+
]
|
|
2019
|
+
if actual != expected:
|
|
2020
|
+
divergence = next(
|
|
2021
|
+
(
|
|
2022
|
+
f"card={card!r} vs injected={wanted!r}"
|
|
2023
|
+
for card, wanted in zip(actual, expected)
|
|
2024
|
+
if card != wanted
|
|
2025
|
+
),
|
|
2026
|
+
f"card has {len(actual)} content lines, injected has "
|
|
2027
|
+
f"{len(expected)}",
|
|
2028
|
+
)
|
|
2029
|
+
raise ConformanceError(
|
|
2030
|
+
f"[RECEIPT-IDENTITY: {report.path}: the ## "
|
|
2031
|
+
f"{recompute.RECEIPT_SECTION} section must reproduce the "
|
|
2032
|
+
"dispatcher-computed receipts verbatim, adding only "
|
|
2033
|
+
f"finding_ref: lines on mismatch receipts; first divergence: "
|
|
2034
|
+
f"{divergence}]"
|
|
2035
|
+
)
|
|
2036
|
+
|
|
2037
|
+
|
|
2038
|
+
def check_receipt_section_forbidden(report: panel.ReviewerReport) -> None:
|
|
2039
|
+
# Declared boundary: this guard sees what `split_sections` sees, so a
|
|
2040
|
+
# fenced or indented `## Arithmetic Receipts` on a non-methodology seat
|
|
2041
|
+
# is an inert displayed block, not a section — it earns the seat
|
|
2042
|
+
# nothing (no consumer reads it) and is deliberately not chased here.
|
|
2043
|
+
lines = panel.strip_fences(report.text)
|
|
2044
|
+
sections, dupes = panel.split_sections(lines)
|
|
2045
|
+
if _RECEIPT_SECTION in sections or _RECEIPT_SECTION in dupes:
|
|
2046
|
+
raise ConformanceError(
|
|
2047
|
+
f"[RECEIPT-SECTION-FORBIDDEN: {report.path}: role "
|
|
2048
|
+
f"{report.role} may not emit ## {_RECEIPT_SECTION}; the #610 "
|
|
2049
|
+
"receipt gate is methodology-only]"
|
|
2050
|
+
)
|
|
2051
|
+
|
|
2052
|
+
|
|
2053
|
+
def _parse_args(argv):
|
|
2054
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
2055
|
+
parser.add_argument("--contract", required=True, type=Path)
|
|
2056
|
+
parser.add_argument("--role", required=True)
|
|
2057
|
+
parser.add_argument("--phase1", required=True, type=Path)
|
|
2058
|
+
# The retry decision is taken while Phase 2 has not been requested yet, so
|
|
2059
|
+
# the gate has to be answerable on Phase 1 alone. Mirrors the
|
|
2060
|
+
# --synthesis / --layer1-only split in check_panel_synthesis.py.
|
|
2061
|
+
stage = parser.add_mutually_exclusive_group(required=True)
|
|
2062
|
+
stage.add_argument("--phase2", type=Path)
|
|
2063
|
+
stage.add_argument("--phase1-only", action="store_true")
|
|
2064
|
+
# #610 step 5: the methodology extraction call is its own gated stage,
|
|
2065
|
+
# answerable between Phase 1 and Phase 2 like --phase1-only is before
|
|
2066
|
+
# Phase 2.
|
|
2067
|
+
stage.add_argument("--extraction", type=Path)
|
|
2068
|
+
parser.add_argument("--injected-receipts", type=Path)
|
|
2069
|
+
parser.add_argument("--manuscript", required=True, type=Path)
|
|
2070
|
+
parser.add_argument("--metadata", required=True, type=Path)
|
|
2071
|
+
args = parser.parse_args(argv)
|
|
2072
|
+
if args.injected_receipts is not None and args.phase2 is None:
|
|
2073
|
+
parser.error("--injected-receipts requires --phase2")
|
|
2074
|
+
return args
|
|
2075
|
+
|
|
2076
|
+
|
|
2077
|
+
def main(argv=None) -> int:
|
|
2078
|
+
args = _parse_args(argv)
|
|
2079
|
+
try:
|
|
2080
|
+
contract, _ = panel.load_contract(args.contract)
|
|
2081
|
+
if args.role not in panel.ROLE_SETS[contract["mode"]]:
|
|
2082
|
+
raise panel.ContractError(
|
|
2083
|
+
f"[ROLE-BINDING: --role {args.role} is invalid for "
|
|
2084
|
+
f"{contract['mode']}]"
|
|
2085
|
+
)
|
|
2086
|
+
phase1_text = panel._read_text(args.phase1)
|
|
2087
|
+
phase2_text = (
|
|
2088
|
+
panel._read_text(args.phase2) if args.phase2 is not None
|
|
2089
|
+
else None
|
|
2090
|
+
)
|
|
2091
|
+
manuscript_text = panel._read_text(args.manuscript)
|
|
2092
|
+
try:
|
|
2093
|
+
metadata = json.loads(panel._read_text(args.metadata))
|
|
2094
|
+
except json.JSONDecodeError as exc:
|
|
2095
|
+
raise panel.ContractError(
|
|
2096
|
+
f"[METADATA-INVALID: {args.metadata}: {exc}]"
|
|
2097
|
+
) from exc
|
|
2098
|
+
if args.phase1_only:
|
|
2099
|
+
# Blindness FIRST, before structural parsing: a response both
|
|
2100
|
+
# malformed and leaking would otherwise report only the grammar
|
|
2101
|
+
# failure, and the dispatcher would grant the retry a proven
|
|
2102
|
+
# leak must never receive. It is the half a retry must not be
|
|
2103
|
+
# granted in spite of.
|
|
2104
|
+
check_manuscript_leakage(
|
|
2105
|
+
phase1_text, manuscript_text, metadata, contract
|
|
2106
|
+
)
|
|
2107
|
+
plan = parse_phase1(str(args.phase1), phase1_text, contract, args.role)
|
|
2108
|
+
for warning in plan.warnings:
|
|
2109
|
+
print(warning)
|
|
2110
|
+
if args.phase1_only:
|
|
2111
|
+
print("PHASE1-CONFORMANCE: PASS")
|
|
2112
|
+
return EXIT_PASS
|
|
2113
|
+
if args.extraction is not None:
|
|
2114
|
+
# #610 step 5. The extraction call is methodology-only by
|
|
2115
|
+
# design: no other seat has an extraction stage to gate.
|
|
2116
|
+
if args.role != "methodology":
|
|
2117
|
+
raise panel.ContractError(
|
|
2118
|
+
f"[ROLE-BINDING: --extraction is methodology-only, "
|
|
2119
|
+
f"dispatched as {args.role}]"
|
|
2120
|
+
)
|
|
2121
|
+
check_manuscript_leakage(
|
|
2122
|
+
phase1_text, manuscript_text, metadata, contract
|
|
2123
|
+
)
|
|
2124
|
+
check_recompute_extraction(
|
|
2125
|
+
str(args.extraction), panel._read_text(args.extraction)
|
|
2126
|
+
)
|
|
2127
|
+
print("EXTRACTION-CONFORMANCE: PASS")
|
|
2128
|
+
return EXIT_PASS
|
|
2129
|
+
report = panel.parse_report(
|
|
2130
|
+
str(args.phase2), phase2_text, contract
|
|
2131
|
+
)
|
|
2132
|
+
if report.role != args.role:
|
|
2133
|
+
raise ConformanceError(
|
|
2134
|
+
f"[ROLE-BINDING: report declares {report.role}, dispatched "
|
|
2135
|
+
f"as {args.role}]"
|
|
2136
|
+
)
|
|
2137
|
+
check_manuscript_leakage(
|
|
2138
|
+
phase1_text, manuscript_text, metadata, contract
|
|
2139
|
+
)
|
|
2140
|
+
dissent = parse_dissent_dimensions(phase2_text)
|
|
2141
|
+
for diagnostic in dissent.diagnostics:
|
|
2142
|
+
print(diagnostic)
|
|
2143
|
+
dimensions = {
|
|
2144
|
+
dim["id"]: dim for dim in contract["acceptance_dimensions"]
|
|
2145
|
+
}
|
|
2146
|
+
check_trigger_binding(report, plan, dimensions, dissent.dimensions)
|
|
2147
|
+
if report.role == "da":
|
|
2148
|
+
check_da_anchors(report)
|
|
2149
|
+
else:
|
|
2150
|
+
check_scoring_seat_anchors(report)
|
|
2151
|
+
if report.role == "methodology":
|
|
2152
|
+
check_methodology_receipts(report)
|
|
2153
|
+
if args.injected_receipts is not None:
|
|
2154
|
+
check_injected_receipts(
|
|
2155
|
+
report, panel._read_text(args.injected_receipts)
|
|
2156
|
+
)
|
|
2157
|
+
# A distinct witness line (security round 1, P2-5): the
|
|
2158
|
+
# evidence contract names this gate as contract content, so
|
|
2159
|
+
# a Phase 2 gated WITHOUT it must be distinguishable in the
|
|
2160
|
+
# preserved gate log.
|
|
2161
|
+
print("RECEIPT-IDENTITY: PASS")
|
|
2162
|
+
else:
|
|
2163
|
+
if args.injected_receipts is not None:
|
|
2164
|
+
raise panel.ContractError(
|
|
2165
|
+
f"[ROLE-BINDING: --injected-receipts is "
|
|
2166
|
+
f"methodology-only, dispatched as {args.role}]"
|
|
2167
|
+
)
|
|
2168
|
+
check_receipt_section_forbidden(report)
|
|
2169
|
+
except panel.ContractError as exc:
|
|
2170
|
+
print(exc)
|
|
2171
|
+
return EXIT_CONTRACT
|
|
2172
|
+
except (panel.ReportError, ConformanceError) as exc:
|
|
2173
|
+
print(exc)
|
|
2174
|
+
return EXIT_CONFORMANCE
|
|
2175
|
+
print("PHASE-CONFORMANCE: PASS")
|
|
2176
|
+
return EXIT_PASS
|
|
2177
|
+
|
|
2178
|
+
|
|
2179
|
+
if __name__ == "__main__":
|
|
2180
|
+
sys.exit(main())
|