psyclaw 0.27.22 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/dist/apps/panel/index.html +2 -2
- package/dist/src/adapters/pi/extension.js +225 -92
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/agents/import.js +44 -32
- package/dist/src/agents/import.js.map +1 -1
- package/dist/src/analysis/hooks.d.ts +1 -0
- package/dist/src/analysis/hooks.js +20 -1
- package/dist/src/analysis/hooks.js.map +1 -1
- package/dist/src/ars/bridge.d.ts +20 -0
- package/dist/src/ars/bridge.js +132 -0
- package/dist/src/ars/bridge.js.map +1 -0
- package/dist/src/ars/contracts.d.ts +55 -0
- package/dist/src/ars/contracts.js +2 -0
- package/dist/src/ars/contracts.js.map +1 -0
- package/dist/src/ars/panel-plan.d.ts +4 -0
- package/dist/src/ars/panel-plan.js +11 -0
- package/dist/src/ars/panel-plan.js.map +1 -0
- package/dist/src/ars/pi-panel-executor.d.ts +30 -0
- package/dist/src/ars/pi-panel-executor.js +140 -0
- package/dist/src/ars/pi-panel-executor.js.map +1 -0
- package/dist/src/ars/profile.d.ts +36 -0
- package/dist/src/ars/profile.js +119 -0
- package/dist/src/ars/profile.js.map +1 -0
- package/dist/src/ars/re-review.d.ts +22 -0
- package/dist/src/ars/re-review.js +172 -0
- package/dist/src/ars/re-review.js.map +1 -0
- package/dist/src/branding.d.ts +2 -4
- package/dist/src/branding.js +3 -5
- package/dist/src/branding.js.map +1 -1
- package/dist/src/bundled-tools.d.ts +3 -0
- package/dist/src/bundled-tools.js +21 -0
- package/dist/src/bundled-tools.js.map +1 -0
- package/dist/src/chat.js +6 -5
- package/dist/src/chat.js.map +1 -1
- package/dist/src/creation/contracts.d.ts +38 -0
- package/dist/src/creation/contracts.js +2 -0
- package/dist/src/creation/contracts.js.map +1 -0
- package/dist/src/creation/service.d.ts +7 -0
- package/dist/src/creation/service.js +196 -0
- package/dist/src/creation/service.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +10 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/install/installer.js +32 -10
- package/dist/src/install/installer.js.map +1 -1
- package/dist/src/orchestration/personas.d.ts +15 -0
- package/dist/src/orchestration/personas.js +51 -0
- package/dist/src/orchestration/personas.js.map +1 -0
- package/dist/src/orchestration/pi-executor.d.ts +1 -0
- package/dist/src/orchestration/pi-executor.js +1 -1
- package/dist/src/orchestration/pi-executor.js.map +1 -1
- package/dist/src/panel/extension.js +29 -8
- package/dist/src/panel/extension.js.map +1 -1
- package/dist/src/panel/server.js +14 -5
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/project/paths.d.ts +3 -0
- package/dist/src/project/paths.js +7 -0
- package/dist/src/project/paths.js.map +1 -1
- package/dist/src/rules/user-rules.d.ts +8 -0
- package/dist/src/rules/user-rules.js +36 -0
- package/dist/src/rules/user-rules.js.map +1 -0
- package/dist/src/skills/contracts.d.ts +4 -4
- package/dist/src/skills/recommended.js +1 -1
- package/dist/src/skills/registry.js +49 -28
- package/dist/src/skills/registry.js.map +1 -1
- package/dist/src/style/cli-ui.js +0 -1
- package/dist/src/style/cli-ui.js.map +1 -1
- package/package.json +14 -3
- package/scripts/rebrand-pi.mjs +6 -0
- package/skills/recommended/catalog.json +2 -11
- package/vendor/ars/.claude/CLAUDE.md +371 -0
- package/vendor/ars/.command-invariants.toml +24 -0
- package/vendor/ars/CITATION.cff +35 -0
- package/vendor/ars/LICENSE +417 -0
- package/vendor/ars/MODE_REGISTRY.md +76 -0
- package/vendor/ars/NOTICE.md +26 -0
- package/vendor/ars/POSITIONING.md +99 -0
- package/vendor/ars/PSYCLAW_SOURCE.json +10 -0
- package/vendor/ars/README.md +751 -0
- package/vendor/ars/SECURITY.md +52 -0
- package/vendor/ars/THIRD_PARTY.md +70 -0
- package/vendor/ars/academic-paper/SKILL.md +542 -0
- package/vendor/ars/academic-paper/agents/abstract_bilingual_agent.md +171 -0
- package/vendor/ars/academic-paper/agents/argument_builder_agent.md +276 -0
- package/vendor/ars/academic-paper/agents/citation_compliance_agent.md +422 -0
- package/vendor/ars/academic-paper/agents/draft_writer_agent.md +656 -0
- package/vendor/ars/academic-paper/agents/formatter_agent.md +999 -0
- package/vendor/ars/academic-paper/agents/intake_agent.md +393 -0
- package/vendor/ars/academic-paper/agents/literature_strategist_agent.md +626 -0
- package/vendor/ars/academic-paper/agents/peer_reviewer_agent.md +516 -0
- package/vendor/ars/academic-paper/agents/revision_coach_agent.md +334 -0
- package/vendor/ars/academic-paper/agents/socratic_mentor_agent.md +527 -0
- package/vendor/ars/academic-paper/agents/structure_architect_agent.md +401 -0
- package/vendor/ars/academic-paper/agents/visualization_agent.md +441 -0
- package/vendor/ars/academic-paper/examples/chinese_paper_example.md +278 -0
- package/vendor/ars/academic-paper/examples/clinical_citation_verification_checklist.md +95 -0
- package/vendor/ars/academic-paper/examples/clinical_epistemic_status_example.md +100 -0
- package/vendor/ars/academic-paper/examples/commitment_ledger_example.md +147 -0
- package/vendor/ars/academic-paper/examples/imrad_hei_example.md +234 -0
- package/vendor/ars/academic-paper/examples/literature_review_example.md +260 -0
- package/vendor/ars/academic-paper/examples/plan_mode_guided_writing.md +600 -0
- package/vendor/ars/academic-paper/examples/revision_mode_example.md +344 -0
- package/vendor/ars/academic-paper/examples/revision_recovery_example.md +506 -0
- package/vendor/ars/academic-paper/examples/version_family_reconciliation_example.md +89 -0
- package/vendor/ars/academic-paper/references/abstract_writing_guide.md +169 -0
- package/vendor/ars/academic-paper/references/academic_writing_style.md +188 -0
- package/vendor/ars/academic-paper/references/anti_leakage_protocol.md +83 -0
- package/vendor/ars/academic-paper/references/apa7_chinese_citation_guide.md +364 -0
- package/vendor/ars/academic-paper/references/apa7_extended_guide.md +198 -0
- package/vendor/ars/academic-paper/references/changelog.md +11 -0
- package/vendor/ars/academic-paper/references/citation_format_switcher.md +228 -0
- package/vendor/ars/academic-paper/references/committee_correspondence_protocol.md +158 -0
- package/vendor/ars/academic-paper/references/credit_authorship_guide.md +308 -0
- package/vendor/ars/academic-paper/references/disclosure_mode_protocol.md +478 -0
- package/vendor/ars/academic-paper/references/domain_evidence_profiles.md +38 -0
- package/vendor/ars/academic-paper/references/failure_paths.md +349 -0
- package/vendor/ars/academic-paper/references/funding_statement_guide.md +319 -0
- package/vendor/ars/academic-paper/references/hei_domain_glossary.md +169 -0
- package/vendor/ars/academic-paper/references/intro_title_rhetoric_guide.md +114 -0
- package/vendor/ars/academic-paper/references/journal_submission_guide.md +249 -0
- package/vendor/ars/academic-paper/references/latex_template_reference.md +378 -0
- package/vendor/ars/academic-paper/references/mode_selection_guide.md +378 -0
- package/vendor/ars/academic-paper/references/paper_structure_patterns.md +330 -0
- package/vendor/ars/academic-paper/references/plan_mode_protocol.md +112 -0
- package/vendor/ars/academic-paper/references/policy_anchor_disclosure_protocol.md +200 -0
- package/vendor/ars/academic-paper/references/policy_anchor_table.md +157 -0
- package/vendor/ars/academic-paper/references/revision_patch_protocol.md +173 -0
- package/vendor/ars/academic-paper/references/statistical_visualization_standards.md +750 -0
- package/vendor/ars/academic-paper/references/venue_disclosure_policies.md +259 -0
- package/vendor/ars/academic-paper/references/vlm_figure_verification.md +126 -0
- package/vendor/ars/academic-paper/references/workflow_phase_details.md +135 -0
- package/vendor/ars/academic-paper/references/writing_judgment_framework.md +59 -0
- package/vendor/ars/academic-paper/references/writing_quality_check.md +173 -0
- package/vendor/ars/academic-paper/templates/bilingual_abstract_template.md +78 -0
- package/vendor/ars/academic-paper/templates/case_study_template.md +129 -0
- package/vendor/ars/academic-paper/templates/conference_paper_template.md +108 -0
- package/vendor/ars/academic-paper/templates/credit_statement_template.md +132 -0
- package/vendor/ars/academic-paper/templates/funding_statement_template.md +290 -0
- package/vendor/ars/academic-paper/templates/imrad_template.md +183 -0
- package/vendor/ars/academic-paper/templates/latex_article_template.tex +199 -0
- package/vendor/ars/academic-paper/templates/literature_review_template.md +135 -0
- package/vendor/ars/academic-paper/templates/policy_brief_template.md +139 -0
- package/vendor/ars/academic-paper/templates/revision_tracking_template.md +199 -0
- package/vendor/ars/academic-paper/templates/theoretical_paper_template.md +119 -0
- package/vendor/ars/academic-paper-reviewer/SKILL.md +491 -0
- package/vendor/ars/academic-paper-reviewer/agents/devils_advocate_reviewer_agent.md +443 -0
- package/vendor/ars/academic-paper-reviewer/agents/domain_reviewer_agent.md +412 -0
- package/vendor/ars/academic-paper-reviewer/agents/editorial_synthesizer_agent.md +478 -0
- package/vendor/ars/academic-paper-reviewer/agents/eic_agent.md +339 -0
- package/vendor/ars/academic-paper-reviewer/agents/field_analyst_agent.md +221 -0
- package/vendor/ars/academic-paper-reviewer/agents/methodology_reviewer_agent.md +449 -0
- package/vendor/ars/academic-paper-reviewer/agents/perspective_reviewer_agent.md +427 -0
- package/vendor/ars/academic-paper-reviewer/examples/hei_paper_review_example.md +391 -0
- package/vendor/ars/academic-paper-reviewer/examples/interdisciplinary_review_example.md +299 -0
- package/vendor/ars/academic-paper-reviewer/examples/subclaim_decomposition_example.md +80 -0
- package/vendor/ars/academic-paper-reviewer/references/calibration_mode_protocol.md +256 -0
- package/vendor/ars/academic-paper-reviewer/references/changelog.md +10 -0
- package/vendor/ars/academic-paper-reviewer/references/editorial_decision_standards.md +236 -0
- package/vendor/ars/academic-paper-reviewer/references/guided_mode_protocol.md +34 -0
- package/vendor/ars/academic-paper-reviewer/references/integration_guide.md +15 -0
- package/vendor/ars/academic-paper-reviewer/references/quality_rubrics.md +84 -0
- package/vendor/ars/academic-paper-reviewer/references/re_review_mode_protocol.md +340 -0
- package/vendor/ars/academic-paper-reviewer/references/review_criteria_framework.md +98 -0
- package/vendor/ars/academic-paper-reviewer/references/review_panel_provenance_protocol.md +197 -0
- package/vendor/ars/academic-paper-reviewer/references/review_quality_thinking.md +58 -0
- package/vendor/ars/academic-paper-reviewer/references/reviewer_sprint_prompt_source.md +324 -0
- package/vendor/ars/academic-paper-reviewer/references/sprint_contract_protocol.md +296 -0
- package/vendor/ars/academic-paper-reviewer/references/statistical_reporting_standards.md +505 -0
- package/vendor/ars/academic-paper-reviewer/references/top_journals_by_field.md +206 -0
- package/vendor/ars/academic-paper-reviewer/templates/editorial_decision_template.md +235 -0
- package/vendor/ars/academic-paper-reviewer/templates/peer_review_report_template.md +305 -0
- package/vendor/ars/academic-paper-reviewer/templates/revision_response_template.md +248 -0
- package/vendor/ars/academic-pipeline/SKILL.md +736 -0
- package/vendor/ars/academic-pipeline/agents/claim_ref_alignment_audit_agent.md +382 -0
- package/vendor/ars/academic-pipeline/agents/collaboration_depth_agent.md +164 -0
- package/vendor/ars/academic-pipeline/agents/integrity_verification_agent.md +870 -0
- package/vendor/ars/academic-pipeline/agents/pipeline_orchestrator_agent.md +1379 -0
- package/vendor/ars/academic-pipeline/agents/state_tracker_agent.md +622 -0
- package/vendor/ars/academic-pipeline/examples/full_pipeline_example.md +482 -0
- package/vendor/ars/academic-pipeline/examples/integrity_failure_recovery.md +389 -0
- package/vendor/ars/academic-pipeline/examples/mid_entry_example.md +414 -0
- package/vendor/ars/academic-pipeline/references/adapters/.gitkeep +0 -0
- package/vendor/ars/academic-pipeline/references/adapters/overview.md +153 -0
- package/vendor/ars/academic-pipeline/references/ai_research_failure_modes.md +185 -0
- package/vendor/ars/academic-pipeline/references/changelog.md +14 -0
- package/vendor/ars/academic-pipeline/references/claim_audit_calibration_protocol.md +175 -0
- package/vendor/ars/academic-pipeline/references/claim_verification_protocol.md +282 -0
- package/vendor/ars/academic-pipeline/references/external_review_protocol.md +131 -0
- package/vendor/ars/academic-pipeline/references/integrity_review_protocol.md +110 -0
- package/vendor/ars/academic-pipeline/references/literature_corpus_consumers.md +193 -0
- package/vendor/ars/academic-pipeline/references/mode_advisor.md +135 -0
- package/vendor/ars/academic-pipeline/references/passport_as_reset_boundary.md +132 -0
- package/vendor/ars/academic-pipeline/references/pipeline_state_machine.md +405 -0
- package/vendor/ars/academic-pipeline/references/plagiarism_detection_protocol.md +239 -0
- package/vendor/ars/academic-pipeline/references/process_summary_protocol.md +209 -0
- package/vendor/ars/academic-pipeline/references/progress_dashboard_template.md +38 -0
- package/vendor/ars/academic-pipeline/references/reinforcement_content.md +15 -0
- package/vendor/ars/academic-pipeline/references/reproducibility_audit.md +55 -0
- package/vendor/ars/academic-pipeline/references/score_trajectory_protocol.md +78 -0
- package/vendor/ars/academic-pipeline/references/team_collaboration_protocol.md +261 -0
- package/vendor/ars/academic-pipeline/references/two_stage_review_protocol.md +27 -0
- package/vendor/ars/academic-pipeline/templates/pipeline_status_template.md +146 -0
- package/vendor/ars/agents/report_compiler_agent.md +341 -0
- package/vendor/ars/agents/research_architect_agent.md +298 -0
- package/vendor/ars/agents/synthesis_agent.md +356 -0
- package/vendor/ars/commands/ars-3w.md +10 -0
- package/vendor/ars/commands/ars-abstract.md +10 -0
- package/vendor/ars/commands/ars-cache-invalidate.md +20 -0
- package/vendor/ars/commands/ars-citation-check.md +10 -0
- package/vendor/ars/commands/ars-disclosure.md +10 -0
- package/vendor/ars/commands/ars-format-convert.md +10 -0
- package/vendor/ars/commands/ars-full.md +9 -0
- package/vendor/ars/commands/ars-lit-review.md +12 -0
- package/vendor/ars/commands/ars-mark-read.md +18 -0
- package/vendor/ars/commands/ars-outline.md +10 -0
- package/vendor/ars/commands/ars-plan.md +10 -0
- package/vendor/ars/commands/ars-rebuttal-audit.md +12 -0
- package/vendor/ars/commands/ars-reviewer.md +9 -0
- package/vendor/ars/commands/ars-revision-coach.md +9 -0
- package/vendor/ars/commands/ars-revision.md +10 -0
- package/vendor/ars/commands/ars-unmark-read.md +16 -0
- package/vendor/ars/deep-research/SKILL.md +600 -0
- package/vendor/ars/deep-research/agents/bibliography_agent.md +473 -0
- package/vendor/ars/deep-research/agents/devils_advocate_agent.md +192 -0
- package/vendor/ars/deep-research/agents/editor_in_chief_agent.md +167 -0
- package/vendor/ars/deep-research/agents/ethics_review_agent.md +267 -0
- package/vendor/ars/deep-research/agents/meta_analysis_agent.md +325 -0
- package/vendor/ars/deep-research/agents/monitoring_agent.md +209 -0
- package/vendor/ars/deep-research/agents/report_compiler_agent.md +341 -0
- package/vendor/ars/deep-research/agents/research_architect_agent.md +298 -0
- package/vendor/ars/deep-research/agents/research_question_agent.md +216 -0
- package/vendor/ars/deep-research/agents/risk_of_bias_agent.md +231 -0
- package/vendor/ars/deep-research/agents/socratic_mentor_agent.md +764 -0
- package/vendor/ars/deep-research/agents/source_verification_agent.md +219 -0
- package/vendor/ars/deep-research/agents/synthesis_agent.md +356 -0
- package/vendor/ars/deep-research/agents/timeline_extraction_agent.md +99 -0
- package/vendor/ars/deep-research/examples/exploratory_research.md +157 -0
- package/vendor/ars/deep-research/examples/fact_check_mode.md +173 -0
- package/vendor/ars/deep-research/examples/handoff_to_paper.md +318 -0
- package/vendor/ars/deep-research/examples/idea_diversity_coverage_gap_advisory.md +64 -0
- package/vendor/ars/deep-research/examples/policy_analysis.md +161 -0
- package/vendor/ars/deep-research/examples/review_mode.md +253 -0
- package/vendor/ars/deep-research/examples/socratic_guided_research.md +331 -0
- package/vendor/ars/deep-research/examples/systematic_review.md +133 -0
- package/vendor/ars/deep-research/references/apa7_style_guide.md +162 -0
- package/vendor/ars/deep-research/references/argumentation_reasoning_framework.md +68 -0
- package/vendor/ars/deep-research/references/arxiv_api_protocol.md +76 -0
- package/vendor/ars/deep-research/references/changelog.md +22 -0
- package/vendor/ars/deep-research/references/chinese_literature_api_protocol.md +317 -0
- package/vendor/ars/deep-research/references/cross_agent_quality_definitions.md +14 -0
- package/vendor/ars/deep-research/references/crossref_api_protocol.md +84 -0
- package/vendor/ars/deep-research/references/equator_reporting_guidelines.md +482 -0
- package/vendor/ars/deep-research/references/ethics_checklist.md +282 -0
- package/vendor/ars/deep-research/references/failure_paths.md +355 -0
- package/vendor/ars/deep-research/references/interdisciplinary_bridges.md +292 -0
- package/vendor/ars/deep-research/references/irb_decision_tree.md +315 -0
- package/vendor/ars/deep-research/references/literature_monitoring_strategies.md +263 -0
- package/vendor/ars/deep-research/references/logical_fallacies.md +192 -0
- package/vendor/ars/deep-research/references/methodology_patterns.md +462 -0
- package/vendor/ars/deep-research/references/mode_selection_guide.md +331 -0
- package/vendor/ars/deep-research/references/openalex_api_protocol.md +82 -0
- package/vendor/ars/deep-research/references/preregistration_guide.md +324 -0
- package/vendor/ars/deep-research/references/semantic_scholar_api_protocol.md +107 -0
- package/vendor/ars/deep-research/references/socratic_mode_protocol.md +99 -0
- package/vendor/ars/deep-research/references/socratic_questioning_framework.md +232 -0
- package/vendor/ars/deep-research/references/source_quality_hierarchy.md +188 -0
- package/vendor/ars/deep-research/references/systematic_review_protocol.md +95 -0
- package/vendor/ars/deep-research/references/systematic_review_toolkit.md +353 -0
- package/vendor/ars/deep-research/templates/evidence_assessment_template.md +127 -0
- package/vendor/ars/deep-research/templates/literature_matrix_template.md +85 -0
- package/vendor/ars/deep-research/templates/preregistration_template.md +318 -0
- package/vendor/ars/deep-research/templates/prisma_protocol_template.md +248 -0
- package/vendor/ars/deep-research/templates/prisma_report_template.md +415 -0
- package/vendor/ars/deep-research/templates/research_brief_template.md +93 -0
- package/vendor/ars/package.json +24 -0
- package/vendor/ars/pi/README.md +161 -0
- package/vendor/ars/pi/package.json +26 -0
- package/vendor/ars/pi/wrapper.js +193 -0
- package/vendor/ars/pi/wrapper.test.mjs +201 -0
- package/vendor/ars/pyproject.toml +2 -0
- package/vendor/ars/requirements-pdf-content-classifier.txt +5 -0
- package/vendor/ars/scripts/_block_parser.py +396 -0
- package/vendor/ars/scripts/_ci_pytest_manifest.toml +661 -0
- package/vendor/ars/scripts/_claim_audit_constants.py +268 -0
- package/vendor/ars/scripts/_e4_evidence.py +110 -0
- package/vendor/ars/scripts/_eval_threshold_gate.py +73 -0
- package/vendor/ars/scripts/_markdown_lint_util.py +224 -0
- package/vendor/ars/scripts/_next_verified_at_ms.py +176 -0
- package/vendor/ars/scripts/_passport_yaml.py +53 -0
- package/vendor/ars/scripts/_skill_lint.py +254 -0
- package/vendor/ars/scripts/_text_similarity.py +141 -0
- package/vendor/ars/scripts/adapters/README.md +89 -0
- package/vendor/ars/scripts/adapters/_common.py +209 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/expected_passport.yaml +25 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/expected_rejection_log.yaml +18 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Chen2024_AIAssessment.pdf +0 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/Wang_2023_formative_feedback.pdf +0 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture/paper1.pdf +0 -0
- package/vendor/ars/scripts/adapters/examples/folder_scan/input_fixture//344/270/255/346/226/207/346/252/224/345/220/215_2024.pdf +0 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/expected_passport.yaml +40 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/expected_rejection_log.yaml +13 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.gitkeep +0 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/.obsidian/app.json +1 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/_templates/tmpl.md +7 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/chen2024ai.md +13 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/invalid.md +1 -0
- package/vendor/ars/scripts/adapters/examples/obsidian/input_fixture/vault/wang2023formative.md +10 -0
- package/vendor/ars/scripts/adapters/examples/zotero/expected_passport.yaml +34 -0
- package/vendor/ars/scripts/adapters/examples/zotero/expected_rejection_log.yaml +34 -0
- package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/.gitkeep +0 -0
- package/vendor/ars/scripts/adapters/examples/zotero/input_fixture/export.json +43 -0
- package/vendor/ars/scripts/adapters/folder_scan.py +214 -0
- package/vendor/ars/scripts/adapters/obsidian.py +336 -0
- package/vendor/ars/scripts/adapters/tests/.gitkeep +0 -0
- package/vendor/ars/scripts/adapters/tests/conftest.py +77 -0
- package/vendor/ars/scripts/adapters/tests/test_check_corpus_consumer_protocol.py +632 -0
- package/vendor/ars/scripts/adapters/tests/test_check_literature_corpus_schema.py +631 -0
- package/vendor/ars/scripts/adapters/tests/test_common.py +365 -0
- package/vendor/ars/scripts/adapters/tests/test_conftest.py +96 -0
- package/vendor/ars/scripts/adapters/tests/test_folder_scan.py +260 -0
- package/vendor/ars/scripts/adapters/tests/test_literature_corpus_entry_schema.py +745 -0
- package/vendor/ars/scripts/adapters/tests/test_obsidian.py +357 -0
- package/vendor/ars/scripts/adapters/tests/test_rejection_log_schema.py +271 -0
- package/vendor/ars/scripts/adapters/tests/test_sync_adapter_docs.py +88 -0
- package/vendor/ars/scripts/adapters/tests/test_zotero.py +454 -0
- package/vendor/ars/scripts/adapters/zotero.py +318 -0
- package/vendor/ars/scripts/adjudication_activity.py +1592 -0
- package/vendor/ars/scripts/announce-ars-loaded.sh +144 -0
- package/vendor/ars/scripts/ars_anchorize_draft.py +170 -0
- package/vendor/ars/scripts/ars_apply_revision_patch.py +912 -0
- package/vendor/ars/scripts/ars_cache_invalidate.py +40 -0
- package/vendor/ars/scripts/ars_mark_read.py +521 -0
- package/vendor/ars/scripts/ars_phase_scope_manifest.json +33 -0
- package/vendor/ars/scripts/ars_update_check.sh +215 -0
- package/vendor/ars/scripts/ars_write_scope_guard.py +506 -0
- package/vendor/ars/scripts/arxiv_client.py +222 -0
- package/vendor/ars/scripts/audit_snapshot.py +572 -0
- package/vendor/ars/scripts/bibliographic_integrity_signals.py +800 -0
- package/vendor/ars/scripts/bootstrap_timeline_yaml.py +146 -0
- package/vendor/ars/scripts/build_claim_standing_candidate_ledger.py +1238 -0
- package/vendor/ars/scripts/build_claim_standing_query_plan.py +643 -0
- package/vendor/ars/scripts/build_content_coverage_advisory.py +1205 -0
- package/vendor/ars/scripts/build_cross_document_consistency_advisory.py +2332 -0
- package/vendor/ars/scripts/build_review_pathway_rule_trace.py +830 -0
- package/vendor/ars/scripts/build_submission_packet_manifest.py +2310 -0
- package/vendor/ars/scripts/check_215_field_norm.py +173 -0
- package/vendor/ars/scripts/check_216_surface_form.py +250 -0
- package/vendor/ars/scripts/check_268_nested_commitment_ledger.py +180 -0
- package/vendor/ars/scripts/check_390_revision_patch_discipline.py +296 -0
- package/vendor/ars/scripts/check_392_citation_verification_intake.py +126 -0
- package/vendor/ars/scripts/check_394_submission_policy.py +178 -0
- package/vendor/ars/scripts/check_439_format_profile.py +307 -0
- package/vendor/ars/scripts/check_619_disclosure_closeout.py +237 -0
- package/vendor/ars/scripts/check_630_codex_subscription_transport.py +456 -0
- package/vendor/ars/scripts/check_669_review_pathway_rule_trace.py +662 -0
- package/vendor/ars/scripts/check_670_revision_roadmap_integration.py +518 -0
- package/vendor/ars/scripts/check_673_adjudication_activity.py +684 -0
- package/vendor/ars/scripts/check_684_review_criteria_binding.py +557 -0
- package/vendor/ars/scripts/check_agents_mirror_sync.py +115 -0
- package/vendor/ars/scripts/check_audit_artifact_consistency.py +2313 -0
- package/vendor/ars/scripts/check_benchmark_report.py +79 -0
- package/vendor/ars/scripts/check_bibliographic_integrity_signals.py +831 -0
- package/vendor/ars/scripts/check_calibration_tiers.py +235 -0
- package/vendor/ars/scripts/check_changelog_covers_merges.py +289 -0
- package/vendor/ars/scripts/check_ci_pytest_manifest.py +204 -0
- package/vendor/ars/scripts/check_claim_audit_consistency.py +1664 -0
- package/vendor/ars/scripts/check_claim_standing_candidate_ledger_integration.py +500 -0
- package/vendor/ars/scripts/check_claim_standing_freshness.py +253 -0
- package/vendor/ars/scripts/check_claim_standing_transmissions.py +449 -0
- package/vendor/ars/scripts/check_collaboration_depth_rubric.py +180 -0
- package/vendor/ars/scripts/check_command_frontmatter_name.py +116 -0
- package/vendor/ars/scripts/check_committee_correspondence.py +333 -0
- package/vendor/ars/scripts/check_compliance_report.py +108 -0
- package/vendor/ars/scripts/check_content_coverage_advisory_integration.py +796 -0
- package/vendor/ars/scripts/check_control_availability.py +172 -0
- package/vendor/ars/scripts/check_corpus_consumer_protocol.py +404 -0
- package/vendor/ars/scripts/check_cross_document_consistency_advisory_integration.py +1191 -0
- package/vendor/ars/scripts/check_cross_model_handoff_contract.py +234 -0
- package/vendor/ars/scripts/check_cross_model_verification_sync.py +261 -0
- package/vendor/ars/scripts/check_data_access_level.py +131 -0
- package/vendor/ars/scripts/check_data_flows.py +252 -0
- package/vendor/ars/scripts/check_decision_contract.py +464 -0
- package/vendor/ars/scripts/check_degradation_registry.py +326 -0
- package/vendor/ars/scripts/check_distribution_surface_claims.py +226 -0
- package/vendor/ars/scripts/check_domain_evidence_profile.py +538 -0
- package/vendor/ars/scripts/check_e4_promotion.py +195 -0
- package/vendor/ars/scripts/check_evals_gold_set.py +279 -0
- package/vendor/ars/scripts/check_evidence_row_integration.py +396 -0
- package/vendor/ars/scripts/check_experiment_provenance.py +117 -0
- package/vendor/ars/scripts/check_field_norm_severity.py +144 -0
- package/vendor/ars/scripts/check_firm_rules_sync.py +375 -0
- package/vendor/ars/scripts/check_heldout_measurement_report.py +1179 -0
- package/vendor/ars/scripts/check_human_subjects_output_contract.py +139 -0
- package/vendor/ars/scripts/check_human_subjects_reference_migration.py +844 -0
- package/vendor/ars/scripts/check_indirect_prompt_injection_no_call.py +328 -0
- package/vendor/ars/scripts/check_instruction_data_boundary.py +236 -0
- package/vendor/ars/scripts/check_judge_prompt_version.py +125 -0
- package/vendor/ars/scripts/check_literature_corpus_schema.py +402 -0
- package/vendor/ars/scripts/check_model_tiering.py +223 -0
- package/vendor/ars/scripts/check_panel_synthesis.py +1331 -0
- package/vendor/ars/scripts/check_passport_reset_contract.py +214 -0
- package/vendor/ars/scripts/check_pattern_eval_manifest.py +422 -0
- package/vendor/ars/scripts/check_persuasion_invariance_fixtures.py +637 -0
- package/vendor/ars/scripts/check_phase_conformance.py +2180 -0
- package/vendor/ars/scripts/check_pipeline_boundary_semantics.py +600 -0
- package/vendor/ars/scripts/check_pipeline_integrity.py +340 -0
- package/vendor/ars/scripts/check_policy_anchor_protocol.py +159 -0
- package/vendor/ars/scripts/check_policy_anchor_table.py +286 -0
- package/vendor/ars/scripts/check_preprint_venues_consistency.py +123 -0
- package/vendor/ars/scripts/check_prisma_trAIce_freshness.py +84 -0
- package/vendor/ars/scripts/check_promotion_bakeoff_preregistration.py +1303 -0
- package/vendor/ars/scripts/check_ranking_lift.py +323 -0
- package/vendor/ars/scripts/check_re_review_synthesis.py +2719 -0
- package/vendor/ars/scripts/check_receipt_enum_sync.py +203 -0
- package/vendor/ars/scripts/check_repro_lock.py +85 -0
- package/vendor/ars/scripts/check_review_pathway_output.py +276 -0
- package/vendor/ars/scripts/check_reviewer_data_fences.py +224 -0
- package/vendor/ars/scripts/check_reviewer_finding_contract.py +783 -0
- package/vendor/ars/scripts/check_reviewer_role_label.py +447 -0
- package/vendor/ars/scripts/check_reviewer_scoring_honesty.py +292 -0
- package/vendor/ars/scripts/check_reviewer_sprint_prompt_sync.py +409 -0
- package/vendor/ars/scripts/check_revision_claim_drift_suite_v2.py +1622 -0
- package/vendor/ars/scripts/check_revision_token_conservation.py +263 -0
- package/vendor/ars/scripts/check_risk_register.py +280 -0
- package/vendor/ars/scripts/check_role_scoped_contract.py +702 -0
- package/vendor/ars/scripts/check_rq_framing_patterns.py +184 -0
- package/vendor/ars/scripts/check_rubric_weight_consistency.py +32 -0
- package/vendor/ars/scripts/check_seeded_defect_fixtures.py +425 -0
- package/vendor/ars/scripts/check_setup_cross_model_parity.py +139 -0
- package/vendor/ars/scripts/check_spec_consistency.py +1249 -0
- package/vendor/ars/scripts/check_sprint_contract.py +371 -0
- package/vendor/ars/scripts/check_stage_capability_matrix.py +784 -0
- package/vendor/ars/scripts/check_submission_packet_manifest_integration.py +358 -0
- package/vendor/ars/scripts/check_surface_form_parity.py +445 -0
- package/vendor/ars/scripts/check_task_type.py +22 -0
- package/vendor/ars/scripts/check_tools_allowlist.py +538 -0
- package/vendor/ars/scripts/check_tortured_phrase_screening_integration.py +1974 -0
- package/vendor/ars/scripts/check_v3_10_134_write_scope.py +286 -0
- package/vendor/ars/scripts/check_v3_10_policy.py +656 -0
- package/vendor/ars/scripts/check_v3_6_6_ab_manifest.py +364 -0
- package/vendor/ars/scripts/check_v3_6_7_pattern_protection.py +1366 -0
- package/vendor/ars/scripts/check_v3_6_8_audit_scope_block.py +464 -0
- package/vendor/ars/scripts/check_v3_6_8_cite_provenance_pipeline.py +231 -0
- package/vendor/ars/scripts/check_v3_6_8_frontmatter_trust_schema.py +248 -0
- package/vendor/ars/scripts/check_v3_6_8_mark_read_commands.py +79 -0
- package/vendor/ars/scripts/check_v3_6_8_pattern_protection.py +941 -0
- package/vendor/ars/scripts/check_v3_7_3_three_layer_citation.py +318 -0
- package/vendor/ars/scripts/check_v3_8_annotation_literal_sync.py +228 -0
- package/vendor/ars/scripts/check_v3_9_0_triangulation.py +366 -0
- package/vendor/ars/scripts/check_v3_9_2_phase_boundary.py +270 -0
- package/vendor/ars/scripts/check_v3_9_4_temporal_verification.py +139 -0
- package/vendor/ars/scripts/check_venue_disclosure_policies.py +63 -0
- package/vendor/ars/scripts/check_version_consistency.py +826 -0
- package/vendor/ars/scripts/check_workflow_classification.py +223 -0
- package/vendor/ars/scripts/chinese_literature_client.py +1938 -0
- package/vendor/ars/scripts/citation_verification_summary.py +85 -0
- package/vendor/ars/scripts/claim_audit_calibration.py +517 -0
- package/vendor/ars/scripts/claim_audit_finalizer.py +456 -0
- package/vendor/ars/scripts/claim_audit_pipeline.py +1594 -0
- package/vendor/ars/scripts/claim_registry_coverage.py +493 -0
- package/vendor/ars/scripts/claim_standing_discovery.py +784 -0
- package/vendor/ars/scripts/claim_standing_stance_runner.py +758 -0
- package/vendor/ars/scripts/claim_standing_stance_scorer.py +239 -0
- package/vendor/ars/scripts/claim_strength_drift_disposition.py +666 -0
- package/vendor/ars/scripts/contamination_signals.py +689 -0
- package/vendor/ars/scripts/corpus_consumer_manifest.json +19 -0
- package/vendor/ars/scripts/cross_model_codex_transport.py +1374 -0
- package/vendor/ars/scripts/cross_model_codex_verify.sh +6 -0
- package/vendor/ars/scripts/cross_model_handoff.py +359 -0
- package/vendor/ars/scripts/cross_model_smoke_test.sh +183 -0
- package/vendor/ars/scripts/cross_model_smoke_test_codex.sh +35 -0
- package/vendor/ars/scripts/cross_model_verification/gemini_is_grounded.jq +45 -0
- package/vendor/ars/scripts/cross_model_verification/gemini_sources.jq +45 -0
- package/vendor/ars/scripts/cross_model_verification/normalize_compat_verdict.py +57 -0
- package/vendor/ars/scripts/cross_model_verification/openai_has_completed_web_search.jq +15 -0
- package/vendor/ars/scripts/cross_model_verification/openai_sources.jq +17 -0
- package/vendor/ars/scripts/cross_model_verification/openai_text.jq +11 -0
- package/vendor/ars/scripts/crossref_client.py +225 -0
- package/vendor/ars/scripts/dispatch_e4_panel.py +2731 -0
- package/vendor/ars/scripts/evidence_rows.py +2043 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3.json +32 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_input.json +27 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime.json +32 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/author_stage3_prime_input.json +27 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override.json +50 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_override_action.json +14 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/compliance_pass.json +44 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/explicit_user_request_log.json +26 -0
- package/vendor/ars/scripts/fixtures/adjudication_activity/mandatory_checkpoint_log.json +116 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/README.md +56 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +24 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a1_pass_with_p1/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/negative/a7_orphan_completion/2026-04-30T15-22-04Z-d8f3.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +26 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/persisted_minor/2026-04-30T15-22-04Z-d8f3.verdict.yaml +19 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.audit_artifact_entry.json +24 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.meta.json +43 -0
- package/vendor/ars/scripts/fixtures/audit_artifact_consistency/positive/proposal_pass/2026-04-30T15-22-04Z-d8f3.verdict.yaml +12 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction.json +46 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/retraction_check_attestation.json +44 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase.json +44 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_abstract_missing.json +107 -0
- package/vendor/ars/scripts/fixtures/bibliographic_integrity_signals/tortured_phrase_v1_2_detected.json +124 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/expected_outcomes.json +77 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/manifest.yaml +37 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/001-valid-doi-test.json +20 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/002-valid-arxiv-test.json +19 -0
- package/vendor/ars/scripts/fixtures/check_evals_gold_set/clean/tuples/003-fabricated-test.json +20 -0
- package/vendor/ars/scripts/fixtures/claim_audit_calibration/gold_set.json +344 -0
- package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/query_plan.json +108 -0
- package/vendor/ars/scripts/fixtures/claim_standing_candidate_ledger/retrieval_input.json +179 -0
- package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/concern_tracker.json +129 -0
- package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/response_skeleton.md +19 -0
- package/vendor/ars/scripts/fixtures/committee_correspondence/16fd83f6aec7/source_letter.txt +9 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_draft.json +48 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/base_inventory.json +38 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/packet/consent.txt +1 -0
- package/vendor/ars/scripts/fixtures/content_coverage_advisory/session_sources.json +3 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/README.md +6 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/accepted_draft.md +35 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/cases.json +75 -0
- package/vendor/ars/scripts/fixtures/cross_document_consistency/preregistration.md +5 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/forbidden_event.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/grounded_verified.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/malformed.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/missing_search.jsonl +2 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/multiple_finals.jsonl +4 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/not_found.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/unbound_source.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/cross_model_codex_transport/wrong_search_shape.jsonl +3 -0
- package/vendor/ars/scripts/fixtures/evidence_rows/phase_e_inputs.json +114 -0
- package/vendor/ars/scripts/fixtures/evidence_rows/session_sources.json +5 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/cross-border-us-tw-gdpr.json +71 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/gdpr-member-state-unresolved.json +62 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/missing-data-axis.json +45 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/no-profile.json +33 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/tw-gdpr-two-axis.json +74 -0
- package/vendor/ars/scripts/fixtures/human_subjects_authority/us-gdpr-two-axis.json +74 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/lint-near-misses.json +72 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/no-profile-request.json +18 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/tw-candidates-request.json +63 -0
- package/vendor/ars/scripts/fixtures/review_pathway_rule_trace/us-candidates-request.json +77 -0
- package/vendor/ars/scripts/fixtures/review_target_context/exact-declaration.json +24 -0
- package/vendor/ars/scripts/fixtures/review_target_context/field-general-declaration.json +24 -0
- package/vendor/ars/scripts/fixtures/review_target_context/msr-2027-technical-full-declaration.json +24 -0
- package/vendor/ars/scripts/fixtures/review_target_context/synthetic-registry.json +92 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/README.md +21 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_attested_only.json +55 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_machine_supported.json +49 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_not_isolated.json +56 -0
- package/vendor/ars/scripts/fixtures/revision_claim_drift_v2/subject_context_unknown.json +49 -0
- package/vendor/ars/scripts/fixtures/submission_package/clean/paper.md +16 -0
- package/vendor/ars/scripts/fixtures/submission_package/clean/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/paper.md +11 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_authoryear/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/paper.tex +9 -0
- package/vendor/ars/scripts/fixtures/submission_package/fallback_latex/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/marker_no_join/paper.md +7 -0
- package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/paper.md +8 -0
- package/vendor/ars/scripts/fixtures/submission_package/orphan_intext/references.bib +6 -0
- package/vendor/ars/scripts/fixtures/submission_package/passports/corpus_only.yaml +9 -0
- package/vendor/ars/scripts/fixtures/submission_package/passports/summary_join.yaml +16 -0
- package/vendor/ars/scripts/fixtures/submission_package/profiles/full.yaml +15 -0
- package/vendor/ars/scripts/fixtures/submission_package/profiles/tight.yaml +15 -0
- package/vendor/ars/scripts/fixtures/submission_package/summary_join/paper.md +7 -0
- package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/paper.md +9 -0
- package/vendor/ars/scripts/fixtures/submission_package/uncited_reference/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_clean/paper.md +26 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_clean/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_violations/paper.md +20 -0
- package/vendor/ars/scripts/fixtures/submission_package/venue_violations/references.bib +13 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/base_inventory.json +101 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/consent-materials.txt +3 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/training-certificate.txt +2 -0
- package/vendor/ars/scripts/fixtures/submission_packet_manifest/packet/tw-consent-materials.txt +3 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/corpus_input.yaml +30 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.md +36 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/own_draft.tex +21 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/seed_expectations.json +218 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot.json +164 -0
- package/vendor/ars/scripts/fixtures/tortured_phrase_screening/snapshot_manifest.json +29 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/README.md +55 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/empty_feed.xml +10 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/error_5xx.html +7 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/arxiv/id_hit.xml +26 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/README.md +69 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/cnki_landing_page.html +13 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/error_5xx.html +2 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_ambiguous.json +16 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_hit.json +16 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coordinate_zero.json +14 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_hit.json +15 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esearch_coverage_zero.json +13 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_hit.json +43 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_issn_mismatch.json +31 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_no_doi.json +31 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_unknown_ra.json +34 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/esummary_year_mismatch.json +31 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_absent.json +4 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_exists.json +16 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/handle_internal_error.json +4 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_hit.json +25 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/istic_csl_other_title.json +18 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_cnki.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_crossref.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_istic.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/chinese_literature/ra_unknown_prefix.json +6 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/crossref/doi_hit.json +24 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/crossref/error_5xx.html +7 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/crossref/title_search_miss.json +15 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/openalex/doi_hit.json +29 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/openalex/error_5xx.json +4 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/openalex/title_search_miss.json +11 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/doi_hit.json +14 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/error_5xx.json +3 -0
- package/vendor/ars/scripts/fixtures/transport_bodies/semantic_scholar/title_search_miss.json +5 -0
- package/vendor/ars/scripts/human_read_attestation_resolver.py +467 -0
- package/vendor/ars/scripts/ideation_diversity_assignment_gate.py +430 -0
- package/vendor/ars/scripts/inquiry_branch_ledger.py +2540 -0
- package/vendor/ars/scripts/legacy/ars_apply_revision_patch_v1_0.py +715 -0
- package/vendor/ars/scripts/legacy/check_re_review_synthesis_v1_0.py +2215 -0
- package/vendor/ars/scripts/migrate_literature_corpus_to_v3_10.py +223 -0
- package/vendor/ars/scripts/migrate_literature_corpus_to_v3_7_3.py +277 -0
- package/vendor/ars/scripts/migrate_literature_corpus_to_v3_9_0.py +304 -0
- package/vendor/ars/scripts/model_tiering_manifest.json +46 -0
- package/vendor/ars/scripts/openalex_client.py +232 -0
- package/vendor/ars/scripts/parse_audit_verdict.py +802 -0
- package/vendor/ars/scripts/pdf_content_classifier_worker.py +176 -0
- package/vendor/ars/scripts/pdf_read_preflight.py +1455 -0
- package/vendor/ars/scripts/policy_anchor_disclosure_referee.py +354 -0
- package/vendor/ars/scripts/recompute_receipts.py +1414 -0
- package/vendor/ars/scripts/render_claim_standing_view.py +374 -0
- package/vendor/ars/scripts/render_eval_comment.py +130 -0
- package/vendor/ars/scripts/render_harness_retirement_issue.py +167 -0
- package/vendor/ars/scripts/repro_lock_validation.py +90 -0
- package/vendor/ars/scripts/research_workflow_profile.py +1079 -0
- package/vendor/ars/scripts/resolve_human_subjects_authority.py +1158 -0
- package/vendor/ars/scripts/resolve_review_target_context.py +683 -0
- package/vendor/ars/scripts/resume_e4_record.py +509 -0
- package/vendor/ars/scripts/retraction_status.py +484 -0
- package/vendor/ars/scripts/review_criteria_binding.py +889 -0
- package/vendor/ars/scripts/review_panel_provenance.py +744 -0
- package/vendor/ars/scripts/revision_roadmap.py +1967 -0
- package/vendor/ars/scripts/run_ci_pytest_manifest.py +116 -0
- package/vendor/ars/scripts/run_codex_audit.sh +1191 -0
- package/vendor/ars/scripts/run_evals.py +513 -0
- package/vendor/ars/scripts/run_ideation_diversity_no_call.py +3505 -0
- package/vendor/ars/scripts/run_indirect_prompt_injection_no_call.py +3330 -0
- package/vendor/ars/scripts/run_indirect_prompt_injection_probe.py +399 -0
- package/vendor/ars/scripts/run_review_criteria_constructive_value.py +1895 -0
- package/vendor/ars/scripts/run_role_topology_utility_dry_run.py +606 -0
- package/vendor/ars/scripts/score_review_criteria_constructive_value.py +617 -0
- package/vendor/ars/scripts/semantic_scholar_client.py +291 -0
- package/vendor/ars/scripts/slr_lineage.py +59 -0
- package/vendor/ars/scripts/sync_adapter_docs.py +118 -0
- package/vendor/ars/scripts/temporal_integrity_audit.py +840 -0
- package/vendor/ars/scripts/test_431_exact_or_bust.py +253 -0
- package/vendor/ars/scripts/test__eval_threshold_gate.py +125 -0
- package/vendor/ars/scripts/test__markdown_lint_util.py +129 -0
- package/vendor/ars/scripts/test__next_verified_at_ms.py +256 -0
- package/vendor/ars/scripts/test_adjacent_framing_probe_lint.py +171 -0
- package/vendor/ars/scripts/test_adjudication_activity.py +1515 -0
- package/vendor/ars/scripts/test_ars_anchorize_draft.py +178 -0
- package/vendor/ars/scripts/test_ars_apply_revision_patch.py +1335 -0
- package/vendor/ars/scripts/test_ars_cache_invalidate.py +51 -0
- package/vendor/ars/scripts/test_ars_mark_read.py +910 -0
- package/vendor/ars/scripts/test_ars_update_check.py +816 -0
- package/vendor/ars/scripts/test_ars_write_scope_guard.py +790 -0
- package/vendor/ars/scripts/test_arxiv_client.py +374 -0
- package/vendor/ars/scripts/test_audit_schemas.py +560 -0
- package/vendor/ars/scripts/test_audit_snapshot_render_section_0.py +105 -0
- package/vendor/ars/scripts/test_block_parser.py +259 -0
- package/vendor/ars/scripts/test_bootstrap_timeline_yaml.py +148 -0
- package/vendor/ars/scripts/test_build_claim_standing_candidate_ledger.py +1203 -0
- package/vendor/ars/scripts/test_build_claim_standing_query_plan.py +791 -0
- package/vendor/ars/scripts/test_build_submission_packet_manifest.py +2644 -0
- package/vendor/ars/scripts/test_check_215_field_norm.py +238 -0
- package/vendor/ars/scripts/test_check_216_surface_form.py +341 -0
- package/vendor/ars/scripts/test_check_268_nested_commitment_ledger.py +194 -0
- package/vendor/ars/scripts/test_check_390_revision_patch_discipline.py +279 -0
- package/vendor/ars/scripts/test_check_392_citation_verification_intake.py +144 -0
- package/vendor/ars/scripts/test_check_394_submission_policy.py +200 -0
- package/vendor/ars/scripts/test_check_439_format_profile.py +252 -0
- package/vendor/ars/scripts/test_check_619_disclosure_closeout.py +158 -0
- package/vendor/ars/scripts/test_check_630_codex_subscription_transport.py +245 -0
- package/vendor/ars/scripts/test_check_669_review_pathway_rule_trace.py +304 -0
- package/vendor/ars/scripts/test_check_670_revision_roadmap_integration.py +260 -0
- package/vendor/ars/scripts/test_check_673_adjudication_activity.py +367 -0
- package/vendor/ars/scripts/test_check_684_review_criteria_binding.py +274 -0
- package/vendor/ars/scripts/test_check_agents_mirror_sync.py +137 -0
- package/vendor/ars/scripts/test_check_audit_artifact_consistency.py +2133 -0
- package/vendor/ars/scripts/test_check_benchmark_report.py +117 -0
- package/vendor/ars/scripts/test_check_bibliographic_integrity_signals.py +609 -0
- package/vendor/ars/scripts/test_check_calibration_tiers.py +376 -0
- package/vendor/ars/scripts/test_check_changelog_covers_merges.py +509 -0
- package/vendor/ars/scripts/test_check_ci_pytest_manifest.py +425 -0
- package/vendor/ars/scripts/test_check_claim_standing_candidate_ledger_integration.py +256 -0
- package/vendor/ars/scripts/test_check_claim_standing_freshness.py +277 -0
- package/vendor/ars/scripts/test_check_collaboration_depth_rubric.py +239 -0
- package/vendor/ars/scripts/test_check_command_frontmatter_name.py +178 -0
- package/vendor/ars/scripts/test_check_committee_correspondence.py +298 -0
- package/vendor/ars/scripts/test_check_compliance_report.py +381 -0
- package/vendor/ars/scripts/test_check_content_coverage_advisory_integration.py +308 -0
- package/vendor/ars/scripts/test_check_control_availability.py +361 -0
- package/vendor/ars/scripts/test_check_cross_document_consistency_advisory_integration.py +698 -0
- package/vendor/ars/scripts/test_check_cross_model_handoff_contract.py +301 -0
- package/vendor/ars/scripts/test_check_cross_model_verification_sync.py +203 -0
- package/vendor/ars/scripts/test_check_data_access_level.py +227 -0
- package/vendor/ars/scripts/test_check_data_flows.py +382 -0
- package/vendor/ars/scripts/test_check_decision_contract.py +387 -0
- package/vendor/ars/scripts/test_check_degradation_registry.py +270 -0
- package/vendor/ars/scripts/test_check_distribution_surface_claims.py +225 -0
- package/vendor/ars/scripts/test_check_domain_evidence_profile.py +439 -0
- package/vendor/ars/scripts/test_check_e4_promotion.py +163 -0
- package/vendor/ars/scripts/test_check_evals_gold_set.py +312 -0
- package/vendor/ars/scripts/test_check_evidence_row_integration.py +187 -0
- package/vendor/ars/scripts/test_check_field_norm_severity.py +173 -0
- package/vendor/ars/scripts/test_check_firm_rules_sync.py +342 -0
- package/vendor/ars/scripts/test_check_heldout_measurement_report.py +1508 -0
- package/vendor/ars/scripts/test_check_human_subjects_output_contract.py +129 -0
- package/vendor/ars/scripts/test_check_human_subjects_reference_migration.py +756 -0
- package/vendor/ars/scripts/test_check_instruction_data_boundary.py +204 -0
- package/vendor/ars/scripts/test_check_judge_prompt_version.py +90 -0
- package/vendor/ars/scripts/test_check_model_tiering.py +236 -0
- package/vendor/ars/scripts/test_check_panel_synthesis.py +1658 -0
- package/vendor/ars/scripts/test_check_passport_reset_contract.py +249 -0
- package/vendor/ars/scripts/test_check_pattern_eval_manifest.py +381 -0
- package/vendor/ars/scripts/test_check_persuasion_invariance_fixtures.py +591 -0
- package/vendor/ars/scripts/test_check_phase_conformance.py +4366 -0
- package/vendor/ars/scripts/test_check_pipeline_boundary_semantics.py +931 -0
- package/vendor/ars/scripts/test_check_pipeline_integrity.py +243 -0
- package/vendor/ars/scripts/test_check_policy_anchor_protocol.py +236 -0
- package/vendor/ars/scripts/test_check_policy_anchor_table.py +295 -0
- package/vendor/ars/scripts/test_check_prisma_trAIce_freshness.py +68 -0
- package/vendor/ars/scripts/test_check_promotion_bakeoff_preregistration.py +799 -0
- package/vendor/ars/scripts/test_check_ranking_lift.py +377 -0
- package/vendor/ars/scripts/test_check_re_review_synthesis.py +3398 -0
- package/vendor/ars/scripts/test_check_receipt_enum_sync.py +176 -0
- package/vendor/ars/scripts/test_check_repro_lock.py +107 -0
- package/vendor/ars/scripts/test_check_reviewer_data_fences.py +334 -0
- package/vendor/ars/scripts/test_check_reviewer_finding_contract.py +1107 -0
- package/vendor/ars/scripts/test_check_reviewer_role_label.py +491 -0
- package/vendor/ars/scripts/test_check_reviewer_scoring_honesty.py +222 -0
- package/vendor/ars/scripts/test_check_reviewer_sprint_prompt_sync.py +373 -0
- package/vendor/ars/scripts/test_check_revision_claim_drift_suite_v2.py +1925 -0
- package/vendor/ars/scripts/test_check_revision_token_conservation.py +398 -0
- package/vendor/ars/scripts/test_check_risk_register.py +336 -0
- package/vendor/ars/scripts/test_check_role_scoped_contract.py +1104 -0
- package/vendor/ars/scripts/test_check_rq_framing_patterns.py +110 -0
- package/vendor/ars/scripts/test_check_rubric_weight_consistency.py +32 -0
- package/vendor/ars/scripts/test_check_seeded_defect_fixtures.py +392 -0
- package/vendor/ars/scripts/test_check_setup_cross_model_parity.py +121 -0
- package/vendor/ars/scripts/test_check_spec_consistency.py +1081 -0
- package/vendor/ars/scripts/test_check_sprint_contract.py +458 -0
- package/vendor/ars/scripts/test_check_stage_capability_matrix.py +698 -0
- package/vendor/ars/scripts/test_check_submission_packet_manifest_integration.py +177 -0
- package/vendor/ars/scripts/test_check_surface_form_parity.py +417 -0
- package/vendor/ars/scripts/test_check_task_type.py +116 -0
- package/vendor/ars/scripts/test_check_tools_allowlist.py +835 -0
- package/vendor/ars/scripts/test_check_tortured_phrase_screening_integration.py +1306 -0
- package/vendor/ars/scripts/test_check_v3_10_134_write_scope.py +251 -0
- package/vendor/ars/scripts/test_check_v3_10_policy.py +547 -0
- package/vendor/ars/scripts/test_check_v3_6_7_pattern_protection.py +960 -0
- package/vendor/ars/scripts/test_check_v3_6_8_audit_scope_block.py +1000 -0
- package/vendor/ars/scripts/test_check_v3_6_8_cite_provenance_pipeline.py +454 -0
- package/vendor/ars/scripts/test_check_v3_6_8_frontmatter_trust_schema.py +581 -0
- package/vendor/ars/scripts/test_check_v3_6_8_mark_read_commands.py +120 -0
- package/vendor/ars/scripts/test_check_v3_6_8_pattern_protection.py +1138 -0
- package/vendor/ars/scripts/test_check_v3_7_3_three_layer_citation.py +566 -0
- package/vendor/ars/scripts/test_check_v3_8_annotation_literal_sync.py +262 -0
- package/vendor/ars/scripts/test_check_v3_9_0_triangulation.py +321 -0
- package/vendor/ars/scripts/test_check_v3_9_2_phase_boundary.py +190 -0
- package/vendor/ars/scripts/test_check_v3_9_4_temporal_verification.py +471 -0
- package/vendor/ars/scripts/test_check_version_consistency.py +1462 -0
- package/vendor/ars/scripts/test_check_workflow_classification.py +225 -0
- package/vendor/ars/scripts/test_chinese_literature_client.py +1889 -0
- package/vendor/ars/scripts/test_citation_existence_policy.py +480 -0
- package/vendor/ars/scripts/test_citation_verification_summary.py +342 -0
- package/vendor/ars/scripts/test_claim_audit_calibration.py +882 -0
- package/vendor/ars/scripts/test_claim_audit_finalizer.py +979 -0
- package/vendor/ars/scripts/test_claim_audit_pipeline.py +2398 -0
- package/vendor/ars/scripts/test_claim_audit_schema.py +1778 -0
- package/vendor/ars/scripts/test_claim_intent_manifest.py +666 -0
- package/vendor/ars/scripts/test_claim_registry_coverage.py +246 -0
- package/vendor/ars/scripts/test_claim_standing_discovery.py +579 -0
- package/vendor/ars/scripts/test_claim_standing_pipeline_wiring.py +290 -0
- package/vendor/ars/scripts/test_claim_standing_stance_assets.py +399 -0
- package/vendor/ars/scripts/test_claim_standing_stance_contracts.py +383 -0
- package/vendor/ars/scripts/test_claim_standing_stance_runner.py +349 -0
- package/vendor/ars/scripts/test_claim_standing_transmissions.py +605 -0
- package/vendor/ars/scripts/test_claim_strength_drift_disposition.py +517 -0
- package/vendor/ars/scripts/test_claim_verification_coverage_contract.py +104 -0
- package/vendor/ars/scripts/test_contamination_signals.py +1086 -0
- package/vendor/ars/scripts/test_content_coverage_advisory.py +1767 -0
- package/vendor/ars/scripts/test_cross_document_consistency_advisory.py +1270 -0
- package/vendor/ars/scripts/test_cross_model_codex_transport.py +1152 -0
- package/vendor/ars/scripts/test_cross_model_handoff.py +571 -0
- package/vendor/ars/scripts/test_cross_model_verification_guards.py +792 -0
- package/vendor/ars/scripts/test_crossref_client.py +393 -0
- package/vendor/ars/scripts/test_dispatch_e4_panel.py +4235 -0
- package/vendor/ars/scripts/test_e2e_claim_audit.py +540 -0
- package/vendor/ars/scripts/test_eval_harness_workflow.py +140 -0
- package/vendor/ars/scripts/test_evals_citation_extraction.py +150 -0
- package/vendor/ars/scripts/test_evals_lift_report_schema.py +108 -0
- package/vendor/ars/scripts/test_evidence_rows.py +2593 -0
- package/vendor/ars/scripts/test_experiment_provenance.py +915 -0
- package/vendor/ars/scripts/test_human_read_attestation_resolver.py +477 -0
- package/vendor/ars/scripts/test_ideation_diversity_assignment_gate.py +615 -0
- package/vendor/ars/scripts/test_indirect_prompt_injection_behavior_probe.py +250 -0
- package/vendor/ars/scripts/test_inquiry_branch_ledger.py +2296 -0
- package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_10.py +248 -0
- package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_7_3.py +545 -0
- package/vendor/ars/scripts/test_migrate_literature_corpus_to_v3_9_0.py +497 -0
- package/vendor/ars/scripts/test_normalize_compat_verdict.py +149 -0
- package/vendor/ars/scripts/test_openalex_client.py +490 -0
- package/vendor/ars/scripts/test_passport_yaml.py +104 -0
- package/vendor/ars/scripts/test_pattern_eval_runtime.py +1295 -0
- package/vendor/ars/scripts/test_pdf_read_preflight.py +1943 -0
- package/vendor/ars/scripts/test_policy_anchor_disclosure.py +666 -0
- package/vendor/ars/scripts/test_reading_probe_lint.py +218 -0
- package/vendor/ars/scripts/test_recompute_receipts.py +778 -0
- package/vendor/ars/scripts/test_render_claim_standing_view.py +192 -0
- package/vendor/ars/scripts/test_render_eval_comment.py +162 -0
- package/vendor/ars/scripts/test_render_harness_retirement_issue.py +110 -0
- package/vendor/ars/scripts/test_repro_lock_validation_drift.py +100 -0
- package/vendor/ars/scripts/test_research_workflow_profile.py +734 -0
- package/vendor/ars/scripts/test_resolve_human_subjects_authority.py +1219 -0
- package/vendor/ars/scripts/test_resolve_review_target_context.py +703 -0
- package/vendor/ars/scripts/test_resume_e4_record.py +315 -0
- package/vendor/ars/scripts/test_retraction_status.py +456 -0
- package/vendor/ars/scripts/test_review_criteria_binding.py +629 -0
- package/vendor/ars/scripts/test_review_panel_provenance.py +565 -0
- package/vendor/ars/scripts/test_review_pathway_rule_trace.py +821 -0
- package/vendor/ars/scripts/test_revision_roadmap.py +1255 -0
- package/vendor/ars/scripts/test_run_ci_pytest_manifest.py +186 -0
- package/vendor/ars/scripts/test_run_codex_audit_e2e.py +369 -0
- package/vendor/ars/scripts/test_run_evals.py +430 -0
- package/vendor/ars/scripts/test_run_guard_launcher.py +500 -0
- package/vendor/ars/scripts/test_run_ideation_diversity_no_call.py +1833 -0
- package/vendor/ars/scripts/test_run_indirect_prompt_injection_no_call.py +1889 -0
- package/vendor/ars/scripts/test_run_review_criteria_constructive_value.py +586 -0
- package/vendor/ars/scripts/test_run_role_topology_utility_dry_run.py +428 -0
- package/vendor/ars/scripts/test_score_review_criteria_constructive_value.py +340 -0
- package/vendor/ars/scripts/test_semantic_scholar_client.py +554 -0
- package/vendor/ars/scripts/test_slr_lineage_emission.py +230 -0
- package/vendor/ars/scripts/test_socratic_rq_non_generation_contract.py +173 -0
- package/vendor/ars/scripts/test_temporal_integrity_audit.py +438 -0
- package/vendor/ars/scripts/test_text_similarity.py +95 -0
- package/vendor/ars/scripts/test_title_fuzzy_false_positive.py +111 -0
- package/vendor/ars/scripts/test_tortured_phrase_screening.py +2959 -0
- package/vendor/ars/scripts/test_transport_fixture_citation_gate.py +338 -0
- package/vendor/ars/scripts/test_uncited_assertion.py +558 -0
- package/vendor/ars/scripts/test_v3_6_7_phase_6_6.py +1279 -0
- package/vendor/ars/scripts/test_validate_compliance_fixtures.py +36 -0
- package/vendor/ars/scripts/test_validate_ideation_diversity_assets.py +230 -0
- package/vendor/ars/scripts/test_venue_disclosure_contract.py +755 -0
- package/vendor/ars/scripts/test_verification_cache.py +280 -0
- package/vendor/ars/scripts/test_verification_gate.py +461 -0
- package/vendor/ars/scripts/test_verify_passport_cli.py +123 -0
- package/vendor/ars/scripts/test_verify_submission_package.py +1407 -0
- package/vendor/ars/scripts/test_version_records_schema.py +211 -0
- package/vendor/ars/scripts/tortured_phrase_screening.py +3502 -0
- package/vendor/ars/scripts/uncited_assertion_detector.py +254 -0
- package/vendor/ars/scripts/v3_6_7_inversion_manifest.json +9 -0
- package/vendor/ars/scripts/v3_6_8_inversion_manifest.json +10 -0
- package/vendor/ars/scripts/validate_claim_standing_stance_assets.py +249 -0
- package/vendor/ars/scripts/validate_compliance_fixtures.py +56 -0
- package/vendor/ars/scripts/validate_ideation_diversity_assets.py +303 -0
- package/vendor/ars/scripts/venue_disclosure_contract_harness.py +837 -0
- package/vendor/ars/scripts/verification_cache.py +276 -0
- package/vendor/ars/scripts/verification_gate/__init__.py +345 -0
- package/vendor/ars/scripts/verify_passport.py +133 -0
- package/vendor/ars/scripts/verify_submission_package.py +1657 -0
- package/vendor/ars/shared/agents/compliance_agent.md +136 -0
- package/vendor/ars/shared/artifact_reproducibility_pattern.md +173 -0
- package/vendor/ars/shared/benchmark_report.schema.json +81 -0
- package/vendor/ars/shared/benchmark_report_pattern.md +180 -0
- package/vendor/ars/shared/bibliographic_integrity_signals.md +142 -0
- package/vendor/ars/shared/collaboration_depth_rubric.md +154 -0
- package/vendor/ars/shared/compliance_checkpoint_protocol.md +162 -0
- package/vendor/ars/shared/compliance_report.schema.json +187 -0
- package/vendor/ars/shared/contracts/README.md +938 -0
- package/vendor/ars/shared/contracts/activity/adjudication_activity_input.schema.json +555 -0
- package/vendor/ars/shared/contracts/activity/adjudication_activity_store.schema.json +560 -0
- package/vendor/ars/shared/contracts/audit/audit_jsonl.schema.json +128 -0
- package/vendor/ars/shared/contracts/audit/audit_sidecar.schema.json +169 -0
- package/vendor/ars/shared/contracts/audit/audit_verdict.schema.json +133 -0
- package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory.schema.json +480 -0
- package/vendor/ars/shared/contracts/audit/cross_document_consistency_advisory_draft.schema.json +564 -0
- package/vendor/ars/shared/contracts/audit/cross_document_source_manifest.schema.json +202 -0
- package/vendor/ars/shared/contracts/audit/tortured_phrase_advisory.schema.json +1362 -0
- package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot.schema.json +208 -0
- package/vendor/ars/shared/contracts/audit/tortured_phrase_snapshot_manifest.schema.json +335 -0
- package/vendor/ars/shared/contracts/capability/stage_capability_matrix.json +434 -0
- package/vendor/ars/shared/contracts/claim_standing/candidate_ledger.schema.json +705 -0
- package/vendor/ars/shared/contracts/claim_standing/query_plan.schema.json +389 -0
- package/vendor/ars/shared/contracts/claim_standing/query_plan_v1_1.schema.json +782 -0
- package/vendor/ars/shared/contracts/claim_standing/retrieval_input.schema.json +440 -0
- package/vendor/ars/shared/contracts/claim_standing/stance_record.schema.json +460 -0
- package/vendor/ars/shared/contracts/claim_standing/transmission_ledger.schema.json +282 -0
- package/vendor/ars/shared/contracts/cross_model/codex_citation_receipt.schema.json +157 -0
- package/vendor/ars/shared/contracts/cross_model/codex_citation_request.schema.json +23 -0
- package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_commitment.schema.json +55 -0
- package/vendor/ars/shared/contracts/cross_model/promotion_bakeoff_sealed_reveal.schema.json +50 -0
- package/vendor/ars/shared/contracts/degradation_registry.json +428 -0
- package/vendor/ars/shared/contracts/evaluator/full.json +126 -0
- package/vendor/ars/shared/contracts/evidence/claim_registry.schema.json +50 -0
- package/vendor/ars/shared/contracts/evidence/claim_registry_coverage_report.schema.json +79 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row.schema.json +504 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row_v1_1.schema.json +364 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row_v1_2.schema.json +714 -0
- package/vendor/ars/shared/contracts/evidence/evidence_row_v1_3.schema.json +524 -0
- package/vendor/ars/shared/contracts/human_subjects/authority_profile_registry.schema.json +497 -0
- package/vendor/ars/shared/contracts/human_subjects/committee_correspondence.schema.json +253 -0
- package/vendor/ars/shared/contracts/human_subjects/content_coverage_advisory.schema.json +620 -0
- package/vendor/ars/shared/contracts/human_subjects/irb_context_record.schema.json +329 -0
- package/vendor/ars/shared/contracts/human_subjects/resolved_authority_context.schema.json +347 -0
- package/vendor/ars/shared/contracts/human_subjects/review_pathway_rule_trace.schema.json +265 -0
- package/vendor/ars/shared/contracts/human_subjects/review_pathway_trace_request.schema.json +127 -0
- package/vendor/ars/shared/contracts/human_subjects/submission_packet_inventory.schema.json +240 -0
- package/vendor/ars/shared/contracts/human_subjects/submission_packet_manifest.schema.json +999 -0
- package/vendor/ars/shared/contracts/passport/audit_artifact_entry.schema.json +266 -0
- package/vendor/ars/shared/contracts/passport/bibliographic_integrity_signal.schema.json +1676 -0
- package/vendor/ars/shared/contracts/passport/citation_provenance.schema.json +107 -0
- package/vendor/ars/shared/contracts/passport/citation_verification_summary.schema.json +164 -0
- package/vendor/ars/shared/contracts/passport/claim_audit_result.schema.json +124 -0
- package/vendor/ars/shared/contracts/passport/claim_drift.schema.json +58 -0
- package/vendor/ars/shared/contracts/passport/claim_intent_manifest.schema.json +107 -0
- package/vendor/ars/shared/contracts/passport/constraint_violation.schema.json +65 -0
- package/vendor/ars/shared/contracts/passport/experiment_alignment_result.schema.json +69 -0
- package/vendor/ars/shared/contracts/passport/experiment_provenance_entry.schema.json +169 -0
- package/vendor/ars/shared/contracts/passport/human_read_log.schema.json +86 -0
- package/vendor/ars/shared/contracts/passport/inquiry_ledger_ref.schema.json +22 -0
- package/vendor/ars/shared/contracts/passport/literature_corpus_entry.schema.json +650 -0
- package/vendor/ars/shared/contracts/passport/preregistration_artifact.schema.json +132 -0
- package/vendor/ars/shared/contracts/passport/rejection_log.schema.json +89 -0
- package/vendor/ars/shared/contracts/passport/reset_ledger_entry.schema.json +158 -0
- package/vendor/ars/shared/contracts/passport/temporal_audit_results.schema.json +208 -0
- package/vendor/ars/shared/contracts/passport/terminal_policies.schema.json +50 -0
- package/vendor/ars/shared/contracts/passport/timeline.schema.json +102 -0
- package/vendor/ars/shared/contracts/passport/uncited_assertion.schema.json +56 -0
- package/vendor/ars/shared/contracts/passport/uncited_audit_failure.schema.json +72 -0
- package/vendor/ars/shared/contracts/passport/user_attested_read_resolution.schema.json +87 -0
- package/vendor/ars/shared/contracts/passport/version_records.schema.json +138 -0
- package/vendor/ars/shared/contracts/patch/block_manifest.schema.json +45 -0
- package/vendor/ars/shared/contracts/patch/legacy/v1_0/revision_patch.schema.json +110 -0
- package/vendor/ars/shared/contracts/patch/revision_patch.schema.json +250 -0
- package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_diagnostic.schema.json +39 -0
- package/vendor/ars/shared/contracts/pdf/pdf_content_classifier_worker.schema.json +88 -0
- package/vendor/ars/shared/contracts/pdf/pdf_read_preflight.schema.json +227 -0
- package/vendor/ars/shared/contracts/re_review/input_manifest.schema.json +161 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/input_manifest.schema.json +134 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/precommitment.schema.json +193 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/traceability.schema.json +841 -0
- package/vendor/ars/shared/contracts/re_review/legacy/v1_0/verdict_record.schema.json +244 -0
- package/vendor/ars/shared/contracts/re_review/precommitment.schema.json +193 -0
- package/vendor/ars/shared/contracts/re_review/traceability.schema.json +916 -0
- package/vendor/ars/shared/contracts/re_review/verdict_record.schema.json +245 -0
- package/vendor/ars/shared/contracts/research_workflow/inquiry_branch_ledger.schema.json +449 -0
- package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile.schema.json +222 -0
- package/vendor/ars/shared/contracts/research_workflow/research_workflow_profile_selection_receipt.schema.json +111 -0
- package/vendor/ars/shared/contracts/review_target/constructive_review_findings.schema.json +172 -0
- package/vendor/ars/shared/contracts/review_target/criteria_registry.schema.json +97 -0
- package/vendor/ars/shared/contracts/review_target/review_criteria_binding_manifest.schema.json +235 -0
- package/vendor/ars/shared/contracts/review_target/review_criteria_source_receipt.schema.json +158 -0
- package/vendor/ars/shared/contracts/review_target/review_target_context.schema.json +106 -0
- package/vendor/ars/shared/contracts/review_target/review_target_declaration.schema.json +118 -0
- package/vendor/ars/shared/contracts/reviewer/full.json +114 -0
- package/vendor/ars/shared/contracts/reviewer/methodology_focus.json +75 -0
- package/vendor/ars/shared/contracts/reviewer/review_panel_provenance.schema.json +263 -0
- package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_carrier.schema.json +122 -0
- package/vendor/ars/shared/contracts/reviewer/review_panel_provenance_input.schema.json +120 -0
- package/vendor/ars/shared/contracts/revision/author_adjudication.schema.json +251 -0
- package/vendor/ars/shared/contracts/revision/author_adjudication_input.schema.json +25 -0
- package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition.schema.json +97 -0
- package/vendor/ars/shared/contracts/revision/claim_strength_drift_disposition_input.schema.json +80 -0
- package/vendor/ars/shared/contracts/revision/claim_strength_drift_findings.schema.json +139 -0
- package/vendor/ars/shared/contracts/revision/claim_surface_manifest.schema.json +99 -0
- package/vendor/ars/shared/contracts/revision/integrity_correction_authorization.schema.json +140 -0
- package/vendor/ars/shared/contracts/revision/integrity_correction_authorization_input.schema.json +28 -0
- package/vendor/ars/shared/contracts/revision/integrity_correction_list.schema.json +48 -0
- package/vendor/ars/shared/contracts/revision/integrity_pass_receipt.schema.json +16 -0
- package/vendor/ars/shared/contracts/revision/revision_evidence_bundle.schema.json +134 -0
- package/vendor/ars/shared/contracts/revision/revision_roadmap.schema.json +334 -0
- package/vendor/ars/shared/contracts/submission/format_profile.example.yaml +33 -0
- package/vendor/ars/shared/contracts/submission/format_profile.schema.json +102 -0
- package/vendor/ars/shared/contracts/submission/submission_verification_report.schema.json +233 -0
- package/vendor/ars/shared/contracts/submission/venue_profile.schema.json +114 -0
- package/vendor/ars/shared/contracts/writer/full.json +87 -0
- package/vendor/ars/shared/cross_model_verification.md +714 -0
- package/vendor/ars/shared/evals_lift_report.schema.json +141 -0
- package/vendor/ars/shared/ground_truth_isolation_pattern.md +275 -0
- package/vendor/ars/shared/handoff_schemas.md +1209 -0
- package/vendor/ars/shared/human_subjects_authority_registry.json +1278 -0
- package/vendor/ars/shared/mode_spectrum.md +57 -0
- package/vendor/ars/shared/model_tiering.md +83 -0
- package/vendor/ars/shared/policy_data/nature_policy.md +56 -0
- package/vendor/ars/shared/prisma_trAIce_protocol.md +157 -0
- package/vendor/ars/shared/raise_framework.md +129 -0
- package/vendor/ars/shared/references/authority_content_coverage_advisory_protocol.md +275 -0
- package/vendor/ars/shared/references/claim_standing_candidate_ledger_protocol.md +66 -0
- package/vendor/ars/shared/references/claim_strength_ladder.md +93 -0
- package/vendor/ars/shared/references/cross_document_consistency_advisory_protocol.md +263 -0
- package/vendor/ars/shared/references/evidence_row_protocol.md +260 -0
- package/vendor/ars/shared/references/firm_rules.md +90 -0
- package/vendor/ars/shared/references/human_subjects_authority_protocol.md +274 -0
- package/vendor/ars/shared/references/intent_clarification_protocol.md +168 -0
- package/vendor/ars/shared/references/irb_terminology_glossary.md +229 -0
- package/vendor/ars/shared/references/protected_hedging_phrases.md +118 -0
- package/vendor/ars/shared/references/psychometric_terminology_glossary.md +109 -0
- package/vendor/ars/shared/references/review_criteria_consumer_protocol.md +238 -0
- package/vendor/ars/shared/references/review_pathway_rule_trace_protocol.md +166 -0
- package/vendor/ars/shared/references/submission_packet_manifest_protocol.md +292 -0
- package/vendor/ars/shared/references/word_count_conventions.md +124 -0
- package/vendor/ars/shared/research_workflow_profiles/field_general.json +1 -0
- package/vendor/ars/shared/review_criteria_registry.json +207 -0
- package/vendor/ars/shared/review_criteria_sources/msr-2027-technical-papers.2026-08-24.json +46 -0
- package/vendor/ars/shared/review_criteria_sources/sigsoft-empirical-standards.2026-08-24.json +31 -0
- package/vendor/ars/shared/sprint_contract.schema.json +482 -0
- package/vendor/ars/shared/style_calibration_protocol.md +151 -0
- package/vendor/ars/shared/templates/codex_audit_multifile_template.md +263 -0
- package/vendor/ars/tools/release-discipline/.toolkit-version +1 -0
- package/vendor/ars/tools/release-discipline/README.md +4 -0
- package/vendor/ars/tools/release-discipline/scripts/_release_doc_alignment_schema.py +1011 -0
- package/vendor/ars/tools/release-discipline/scripts/check_command_invariants.py +497 -0
- package/vendor/ars/tools/release-discipline/scripts/check_release_doc_alignment.py +263 -0
- package/vendor/ars/tools/release-discipline/scripts/sync-toolkit.sh +147 -0
- package/vendor/windows/NOTICE.md +12 -0
- package/vendor/windows/arm64/fd.exe +0 -0
- package/vendor/windows/arm64/licenses/fd/LICENSE-APACHE +201 -0
- package/vendor/windows/arm64/licenses/fd/LICENSE-MIT +21 -0
- package/vendor/windows/arm64/licenses/ripgrep/COPYING +3 -0
- package/vendor/windows/arm64/licenses/ripgrep/LICENSE-MIT +21 -0
- package/vendor/windows/arm64/licenses/ripgrep/UNLICENSE +24 -0
- package/vendor/windows/arm64/rg.exe +0 -0
- package/vendor/windows/x64/fd.exe +0 -0
- package/vendor/windows/x64/licenses/fd/LICENSE-APACHE +201 -0
- package/vendor/windows/x64/licenses/fd/LICENSE-MIT +21 -0
- package/vendor/windows/x64/licenses/ripgrep/COPYING +3 -0
- package/vendor/windows/x64/licenses/ripgrep/LICENSE-MIT +21 -0
- package/vendor/windows/x64/licenses/ripgrep/UNLICENSE +24 -0
- package/vendor/windows/x64/rg.exe +0 -0
|
@@ -0,0 +1,2719 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Recompute the #576 Spec B three-gate re-review contract from its artifacts.
|
|
3
|
+
|
|
4
|
+
Stdlib-only, same architecture class as ``check_panel_synthesis.py`` (#510:
|
|
5
|
+
recompute every layer from the primary artifacts; any mismatch voids the
|
|
6
|
+
synthesis). Normative source:
|
|
7
|
+
``docs/design/2026-07-27-576-spec-b-re-review-precommitment-contract-spec.md``
|
|
8
|
+
§5 (artifact shapes), §6 (decision derivation), §10 (reviewer-label
|
|
9
|
+
normalization grammar), §11 (input manifest + apply-chain witness), §13
|
|
10
|
+
(checker responsibilities).
|
|
11
|
+
|
|
12
|
+
Responsibilities (spec §13):
|
|
13
|
+
|
|
14
|
+
1. Schema-validate the three phase artifacts + manifest (self-contained
|
|
15
|
+
validators), and replay the hash-bound #670 Revision-Evidence Bundle.
|
|
16
|
+
2. Hash-chain: ``input_manifest_hash`` -> ``precommitment_hash`` ->
|
|
17
|
+
``verdict_record_hash`` verbatim binding; criterion inheritance binding
|
|
18
|
+
(roadmap_text / letter_text byte comparison + R<n> ordinal recomputation
|
|
19
|
+
with the §5.1 contiguity degradation).
|
|
20
|
+
3. Consumer-side apply-chain witness (§11), including mandatory report-1.3
|
|
21
|
+
``patch_digest`` content-bound positional pairing. Older reports are
|
|
22
|
+
accepted only by the isolated archived loader.
|
|
23
|
+
4. Recompute invariants: coverage cardinalities, applied_criterion rules,
|
|
24
|
+
adjustment-chain grammar, deferral-loop referential integrity
|
|
25
|
+
(reapplications / resolutions / adjudications / acceptances / pending
|
|
26
|
+
rebuttal upgrades), the G2 deferral biconditional, DecisionInputs
|
|
27
|
+
operand equality, and the §6 Step 1-3 derivation (Steps 2-3 recomputed
|
|
28
|
+
BOTH from DecisionInputs AND independently from the raw records).
|
|
29
|
+
5. Goalpost witness: the frozen new-issue set is byte-identical across
|
|
30
|
+
§5.2/§5.3 and only ``regression`` attribution enters the decision path.
|
|
31
|
+
|
|
32
|
+
Exit codes (graded, spec §13):
|
|
33
|
+
|
|
34
|
+
0 pass
|
|
35
|
+
1 recomputation mismatch -> ``[RE-REVIEW-ABORT: synthesis_mismatch]``
|
|
36
|
+
(referential-integrity failures are recomputation mismatches and
|
|
37
|
+
always exit 1)
|
|
38
|
+
2 schema or manifest invalid -> the matching §3.5 abort reason
|
|
39
|
+
(``manifest_incomplete`` / ``manifest_hash_mismatch`` for the
|
|
40
|
+
manifest layer; ``phase1_lint_failed`` / ``phase2a_lint_failed`` /
|
|
41
|
+
``phase2b_lint_failed`` for a phase artifact that fails its schema)
|
|
42
|
+
|
|
43
|
+
Classification note (documented design decision): the three hash-chain
|
|
44
|
+
links and the manifest-side §11 rules (artifact byte hashes, freshness,
|
|
45
|
+
patch_digest pairing, apply-chain witness ``fail``) are the same-inputs
|
|
46
|
+
proof — their violation is graded exit 2 with ``manifest_hash_mismatch``,
|
|
47
|
+
mirroring §11's "-> manifest_hash_mismatch, G0". Content recomputation on
|
|
48
|
+
top of a sound binding (criterion quotes, letter ordinals, chains,
|
|
49
|
+
decision) is graded exit 1.
|
|
50
|
+
|
|
51
|
+
Hashing convention: sha256 hex over JSON Canonical Form (RFC 8785 / JCS,
|
|
52
|
+
the v3.6.3 passport convention). For this contract's value domain
|
|
53
|
+
(objects/arrays/strings/integers/booleans/null) Python's
|
|
54
|
+
``json.dumps(obj, sort_keys=True, separators=(",", ":"),
|
|
55
|
+
ensure_ascii=False)`` produces the JCS byte stream.
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
python scripts/check_re_review_synthesis.py \
|
|
59
|
+
--manifest M.json --precommitment P.json \
|
|
60
|
+
--verdict-record V.json --traceability T.json \
|
|
61
|
+
--roadmap roadmap.json --author-adjudication author.json \
|
|
62
|
+
--revision-evidence-bundle bundle.json \
|
|
63
|
+
[--revision-evidence-root bundle-root] \
|
|
64
|
+
[--letter letter.md] \
|
|
65
|
+
[--apply-report R1.json ...]
|
|
66
|
+
|
|
67
|
+
The roadmap, author sidecar, Revision-Evidence Bundle, optional letter, and
|
|
68
|
+
ordered apply reports are exact §11 files the checker reads. Bundle replay
|
|
69
|
+
uses only its explicitly declared local artifacts under the supplied root.
|
|
70
|
+
"""
|
|
71
|
+
from __future__ import annotations
|
|
72
|
+
|
|
73
|
+
import argparse
|
|
74
|
+
import hashlib
|
|
75
|
+
import json
|
|
76
|
+
import math
|
|
77
|
+
import re
|
|
78
|
+
import sys
|
|
79
|
+
import unicodedata
|
|
80
|
+
from pathlib import Path
|
|
81
|
+
|
|
82
|
+
if __package__ in (None, ""): # pragma: no cover - direct CLI invocation
|
|
83
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
84
|
+
|
|
85
|
+
from scripts.revision_roadmap import ContractError as RevisionContractError
|
|
86
|
+
from scripts.revision_roadmap import validate_bundle
|
|
87
|
+
|
|
88
|
+
EXIT_PASS = 0
|
|
89
|
+
EXIT_SYNTHESIS = 1
|
|
90
|
+
EXIT_INVALID = 2
|
|
91
|
+
|
|
92
|
+
VERDICTS = ("FULLY_ADDRESSED", "PARTIALLY_ADDRESSED", "NOT_ADDRESSED", "MADE_WORSE", "CANNOT_VERIFY")
|
|
93
|
+
JUDGE_VERDICTS = ("FULLY_ADDRESSED", "PARTIALLY_ADDRESSED", "NOT_ADDRESSED", "MADE_WORSE")
|
|
94
|
+
OBLIGATION_CLASSES = ("must_fix", "should_fix", "consider")
|
|
95
|
+
ROW_OBLIGATION_MAP = {"MUST_FIX": "must_fix", "SHOULD_FIX": "should_fix", "CONSIDER": "consider"}
|
|
96
|
+
SEVERITIES = ("critical", "major", "minor")
|
|
97
|
+
RESIDUAL_OBLIGATION_CLASSES = ("must_fix", "should_fix", "consider")
|
|
98
|
+
SEAT_LABELS = ("EIC", "R1", "R2", "R3", "DA")
|
|
99
|
+
VERIFIER_SEATS = ("EIC", "R1", "R2", "R3")
|
|
100
|
+
ADJUSTMENT_BASES = (
|
|
101
|
+
"author_pointer_located_evidence",
|
|
102
|
+
"valid_rebuttal",
|
|
103
|
+
"scope_correction",
|
|
104
|
+
"user_accepted_fail_closed",
|
|
105
|
+
"cross_model_adjudication",
|
|
106
|
+
)
|
|
107
|
+
ATTRIBUTIONS = ("regression", "previously_missed", "indeterminate")
|
|
108
|
+
DECISION_STATES = ("Accept", "Minor Revision", "Major Revision", "user_review_required", "aborted")
|
|
109
|
+
ABORT_REASONS = (
|
|
110
|
+
"phase1_lint_failed",
|
|
111
|
+
"phase2a_lint_failed",
|
|
112
|
+
"phase2b_lint_failed",
|
|
113
|
+
"manifest_incomplete",
|
|
114
|
+
"manifest_hash_mismatch",
|
|
115
|
+
"criteria_drift",
|
|
116
|
+
"synthesis_mismatch",
|
|
117
|
+
)
|
|
118
|
+
WITNESS_STATES = ("pass", "fail", "not_run_no_reports")
|
|
119
|
+
DECISION_ORDER = {"Accept": 0, "Minor Revision": 1, "Major Revision": 2}
|
|
120
|
+
# §5.3 mechanical 5->4 map (MADE_WORSE -> NO: the concern stands unresolved —
|
|
121
|
+
# verification happened, outcome negative).
|
|
122
|
+
VERIFIED_MAP = {
|
|
123
|
+
"FULLY_ADDRESSED": "YES",
|
|
124
|
+
"PARTIALLY_ADDRESSED": "PARTIAL",
|
|
125
|
+
"NOT_ADDRESSED": "NO",
|
|
126
|
+
"MADE_WORSE": "NO",
|
|
127
|
+
"CANNOT_VERIFY": "CANNOT_VERIFY",
|
|
128
|
+
}
|
|
129
|
+
ESCALATION_CLASSES = ("research_integrity", "ethics", "safety", "legal_compliance", "fatal_validity")
|
|
130
|
+
|
|
131
|
+
_SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
|
|
132
|
+
_HASH12_RE = re.compile(r"^[0-9a-f]{12}$")
|
|
133
|
+
_REPORT_VERSION_RE = re.compile(r"^[0-9]+(\.[0-9]+)*$")
|
|
134
|
+
_ITEM_ID_RE = re.compile(r"^REV-[A-Za-z0-9-]+$")
|
|
135
|
+
_CONCERN_ID_RE = re.compile(r"^[RSN][1-9][0-9]*$")
|
|
136
|
+
_LETTER_REF_RE = re.compile(r"^R([1-9][0-9]*)$")
|
|
137
|
+
_ANCHOR_RE = re.compile(r"^(text|table|figure|equation|dataset|absence): .+$")
|
|
138
|
+
_ARTIFACT_REF_RE = re.compile(r"^(passport|path):.+$")
|
|
139
|
+
_ANSWER_REF_RE = re.compile(r"^(adjudication:DIS-[1-9][0-9]*|intent:INT-[1-9][0-9]*)$")
|
|
140
|
+
_CRITERION_REF_RE = re.compile(r"^(phase1:REV-[A-Za-z0-9-]+|dissent:DIS-[1-9][0-9]*)$")
|
|
141
|
+
_SOURCE_REF_RE = re.compile(r"^(reapplication:RAP-[1-9][0-9]*|acceptance:ACC-[1-9][0-9]*)$")
|
|
142
|
+
_CHECK_ADJUDICATED_RE = re.compile(r"^adjudicated:(RADJ-[1-9][0-9]*)$")
|
|
143
|
+
_AUTHOR_EVENT_RE = re.compile(r"^AUTHOR-EVENT-[A-Za-z0-9._-]+$")
|
|
144
|
+
_BLOCK_ID_RE = re.compile(r"^(?:B[0-9]{4,}|DOC-BODY-START)$")
|
|
145
|
+
_CLAIM_AUTH_RE = re.compile(r"^CLAIM-AUTH-[A-Za-z0-9._-]+$")
|
|
146
|
+
_CLAIM_SURFACE_RE = re.compile(r"^CLAIM-SURFACE-[A-Za-z0-9._-]+$")
|
|
147
|
+
_CLAIM_ID_RE = re.compile(r"^C-[0-9]{3,}$")
|
|
148
|
+
_MANIFEST_ID_RE = re.compile(
|
|
149
|
+
r"^M-[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z-[0-9a-f]{4}$"
|
|
150
|
+
)
|
|
151
|
+
_ID_RES = {
|
|
152
|
+
"NS": re.compile(r"^NS-[1-9][0-9]*$"),
|
|
153
|
+
"NEW": re.compile(r"^NEW-[1-9][0-9]*$"),
|
|
154
|
+
"DIS": re.compile(r"^DIS-[1-9][0-9]*$"),
|
|
155
|
+
"ESC": re.compile(r"^ESC-[1-9][0-9]*$"),
|
|
156
|
+
"ADJ": re.compile(r"^ADJ-[1-9][0-9]*$"),
|
|
157
|
+
"INT": re.compile(r"^INT-[1-9][0-9]*$"),
|
|
158
|
+
"RES": re.compile(r"^RES-[1-9][0-9]*$"),
|
|
159
|
+
"RADJ": re.compile(r"^RADJ-[1-9][0-9]*$"),
|
|
160
|
+
"ACC": re.compile(r"^ACC-[1-9][0-9]*$"),
|
|
161
|
+
"RAP": re.compile(r"^RAP-[1-9][0-9]*$"),
|
|
162
|
+
"PRB": re.compile(r"^PRB-[1-9][0-9]*$"),
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ManifestError(Exception):
|
|
167
|
+
"""Manifest-layer failure -> exit 2 with a §3.5 G0 reason."""
|
|
168
|
+
|
|
169
|
+
def __init__(self, reason: str, message: str):
|
|
170
|
+
self.reason = reason
|
|
171
|
+
super().__init__(message)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class ArtifactSchemaError(Exception):
|
|
175
|
+
"""Phase-artifact schema failure -> exit 2 with the phase lint reason."""
|
|
176
|
+
|
|
177
|
+
def __init__(self, reason: str, message: str):
|
|
178
|
+
self.reason = reason
|
|
179
|
+
super().__init__(message)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def canonical_hash(obj) -> str:
|
|
183
|
+
"""sha256 hex over the object's JSON Canonical Form (RFC 8785 / JCS)."""
|
|
184
|
+
payload = json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
|
185
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _strict_json_bytes(raw: bytes, label: str):
|
|
189
|
+
"""Parse interoperable JSON: UTF-8, unique keys, no NaN/Infinity."""
|
|
190
|
+
def unique_object(pairs):
|
|
191
|
+
value = {}
|
|
192
|
+
for key, child in pairs:
|
|
193
|
+
if key in value:
|
|
194
|
+
raise ValueError(f"duplicate object key {key!r}")
|
|
195
|
+
value[key] = child
|
|
196
|
+
return value
|
|
197
|
+
|
|
198
|
+
def reject_constant(token):
|
|
199
|
+
raise ValueError(f"non-finite JSON constant {token!r}")
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
return json.loads(
|
|
203
|
+
raw.decode("utf-8"),
|
|
204
|
+
object_pairs_hook=unique_object,
|
|
205
|
+
parse_constant=reject_constant,
|
|
206
|
+
)
|
|
207
|
+
except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as exc:
|
|
208
|
+
raise ValueError(f"{label}: invalid interoperable JSON ({exc})") from exc
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# --- §10 normalization grammar -------------------------------------------------
|
|
212
|
+
|
|
213
|
+
_PAREN_RE = re.compile(r"\([^()]*\)")
|
|
214
|
+
_SEPARATOR_RE = re.compile(r",|/|;| and |&")
|
|
215
|
+
_R_TOKEN_RES = (
|
|
216
|
+
re.compile(r"^r([1-3])$"),
|
|
217
|
+
re.compile(r"^reviewer ([1-3])$"),
|
|
218
|
+
re.compile(r"^peer reviewer ([1-3])$"),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def normalize_reviewer_labels(raw: str):
|
|
223
|
+
"""§10 emitter-side normalization grammar, in declared order.
|
|
224
|
+
|
|
225
|
+
(1a) strip every parenthetical group FIRST; (1b) THEN strip any
|
|
226
|
+
" — <suffix>" tail from the residual (order normative and
|
|
227
|
+
non-commutative); (2) split on {",", "/", ";", " and ", "&"};
|
|
228
|
+
(3) trim whitespace; case-fold. A stripped token normalizes by
|
|
229
|
+
WHOLE-TOKEN EXACT MATCH (no substring extraction); unrecognized
|
|
230
|
+
tokens are DROPPED, never guessed. Returns ``(labels, parse_failure)``
|
|
231
|
+
where labels is the deduplicated, original-order result and
|
|
232
|
+
``parse_failure`` is True when a non-empty input produced zero
|
|
233
|
+
recognized tokens (§10: routes to EIC but counts as unmapped — a
|
|
234
|
+
distinct state from a genuinely empty list).
|
|
235
|
+
"""
|
|
236
|
+
text = unicodedata.normalize("NFC", raw)
|
|
237
|
+
prev = None
|
|
238
|
+
while prev != text: # nested groups collapse outside-in
|
|
239
|
+
prev = text
|
|
240
|
+
text = _PAREN_RE.sub("", text)
|
|
241
|
+
dash_idx = text.find(" — ")
|
|
242
|
+
if dash_idx != -1:
|
|
243
|
+
text = text[:dash_idx]
|
|
244
|
+
labels = []
|
|
245
|
+
for token in _SEPARATOR_RE.split(text):
|
|
246
|
+
token = token.strip().casefold()
|
|
247
|
+
if not token:
|
|
248
|
+
continue
|
|
249
|
+
label = None
|
|
250
|
+
if token in ("eic", "editor-in-chief"):
|
|
251
|
+
label = "EIC"
|
|
252
|
+
elif token in ("da", "devil's advocate", "devil’s advocate"):
|
|
253
|
+
label = "DA"
|
|
254
|
+
else:
|
|
255
|
+
for pattern in _R_TOKEN_RES:
|
|
256
|
+
match = pattern.match(token)
|
|
257
|
+
if match:
|
|
258
|
+
label = "R" + match.group(1)
|
|
259
|
+
break
|
|
260
|
+
if label is not None and label not in labels:
|
|
261
|
+
labels.append(label)
|
|
262
|
+
parse_failure = bool(raw.strip()) and not labels
|
|
263
|
+
return labels, parse_failure
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# --- validation helpers --------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _is_str(value, nonempty=True) -> bool:
|
|
270
|
+
return isinstance(value, str) and (not nonempty or len(value) > 0)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class _V:
|
|
274
|
+
"""Accumulating validator for one artifact; raises on first failure."""
|
|
275
|
+
|
|
276
|
+
def __init__(self, reason: str, artifact_name: str):
|
|
277
|
+
self.reason = reason
|
|
278
|
+
self.artifact = artifact_name
|
|
279
|
+
|
|
280
|
+
def fail(self, path: str, message: str):
|
|
281
|
+
raise ArtifactSchemaError(self.reason, f"{self.artifact}: {path}: {message}")
|
|
282
|
+
|
|
283
|
+
def obj(self, value, path, required, optional=()):
|
|
284
|
+
if not isinstance(value, dict):
|
|
285
|
+
self.fail(path, "must be an object")
|
|
286
|
+
allowed = set(required) | set(optional)
|
|
287
|
+
for key in value:
|
|
288
|
+
if key not in allowed:
|
|
289
|
+
self.fail(path, f"unexpected field {key!r}")
|
|
290
|
+
for key in required:
|
|
291
|
+
if key not in value:
|
|
292
|
+
self.fail(path, f"missing required field {key!r}")
|
|
293
|
+
|
|
294
|
+
def string(self, value, path, nonempty=True):
|
|
295
|
+
if not _is_str(value, nonempty):
|
|
296
|
+
self.fail(path, "must be a non-empty string" if nonempty else "must be a string")
|
|
297
|
+
|
|
298
|
+
def enum(self, value, path, allowed):
|
|
299
|
+
if value not in allowed:
|
|
300
|
+
self.fail(path, f"must be one of {sorted(allowed)!r}, got {value!r}")
|
|
301
|
+
|
|
302
|
+
def pattern(self, value, path, regex, label):
|
|
303
|
+
if not isinstance(value, str) or not regex.match(value):
|
|
304
|
+
self.fail(path, f"must match {label}, got {value!r}")
|
|
305
|
+
|
|
306
|
+
def integer(self, value, path, minimum=None, maximum=None):
|
|
307
|
+
if not isinstance(value, int) or isinstance(value, bool):
|
|
308
|
+
self.fail(path, "must be an integer")
|
|
309
|
+
if minimum is not None and value < minimum:
|
|
310
|
+
self.fail(path, f"must be >= {minimum}")
|
|
311
|
+
if maximum is not None and value > maximum:
|
|
312
|
+
self.fail(path, f"must be <= {maximum}")
|
|
313
|
+
|
|
314
|
+
def array(self, value, path):
|
|
315
|
+
if not isinstance(value, list):
|
|
316
|
+
self.fail(path, "must be an array")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _validate_residual_gap(v: _V, gap, path):
|
|
320
|
+
v.obj(gap, path, ["text", "residual_obligation_class"])
|
|
321
|
+
v.string(gap["text"], f"{path}.text")
|
|
322
|
+
v.enum(gap["residual_obligation_class"], f"{path}.residual_obligation_class", RESIDUAL_OBLIGATION_CLASSES)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _validate_author_targets(v: _V, targets, path):
|
|
326
|
+
v.array(targets, path)
|
|
327
|
+
seen = set()
|
|
328
|
+
operation_order = {"replace_block": 0, "insert_after": 1, "delete_block": 2}
|
|
329
|
+
prior_key = None
|
|
330
|
+
for i, target in enumerate(targets):
|
|
331
|
+
row_path = f"{path}[{i}]"
|
|
332
|
+
v.obj(target, row_path, ["block_id", "allowed_operations"])
|
|
333
|
+
v.pattern(target["block_id"], f"{row_path}.block_id", _BLOCK_ID_RE, "B<n> | DOC-BODY-START")
|
|
334
|
+
if target["block_id"] in seen:
|
|
335
|
+
v.fail(path, f"duplicate block_id {target['block_id']!r}")
|
|
336
|
+
seen.add(target["block_id"])
|
|
337
|
+
v.array(target["allowed_operations"], f"{row_path}.allowed_operations")
|
|
338
|
+
if not target["allowed_operations"]:
|
|
339
|
+
v.fail(f"{row_path}.allowed_operations", "must not be empty")
|
|
340
|
+
if len(target["allowed_operations"]) != len(set(target["allowed_operations"])):
|
|
341
|
+
v.fail(f"{row_path}.allowed_operations", "must be duplicate-free")
|
|
342
|
+
for op in target["allowed_operations"]:
|
|
343
|
+
v.enum(op, f"{row_path}.allowed_operations", tuple(operation_order))
|
|
344
|
+
if target["allowed_operations"] != sorted(
|
|
345
|
+
target["allowed_operations"], key=operation_order.__getitem__
|
|
346
|
+
):
|
|
347
|
+
v.fail(f"{row_path}.allowed_operations", "must use canonical operation order")
|
|
348
|
+
block_order = -1 if target["block_id"] == "DOC-BODY-START" else int(target["block_id"][1:])
|
|
349
|
+
key = (block_order, target["block_id"])
|
|
350
|
+
if prior_key is not None and key < prior_key:
|
|
351
|
+
v.fail(path, "must use canonical block order")
|
|
352
|
+
prior_key = key
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _validate_claim_authorizations(v: _V, authorizations, path):
|
|
356
|
+
v.array(authorizations, path)
|
|
357
|
+
seen = set()
|
|
358
|
+
required = [
|
|
359
|
+
"authorization_id",
|
|
360
|
+
"author_event_id",
|
|
361
|
+
"surface_id",
|
|
362
|
+
"scoped_manifest_id",
|
|
363
|
+
"claim_id",
|
|
364
|
+
"block_id",
|
|
365
|
+
"original_text_sha256",
|
|
366
|
+
"replacement_text",
|
|
367
|
+
"replacement_text_sha256",
|
|
368
|
+
"from_rung",
|
|
369
|
+
"to_rung",
|
|
370
|
+
"direction",
|
|
371
|
+
"reason",
|
|
372
|
+
]
|
|
373
|
+
for i, authorization in enumerate(authorizations):
|
|
374
|
+
row_path = f"{path}[{i}]"
|
|
375
|
+
v.obj(authorization, row_path, required)
|
|
376
|
+
v.pattern(authorization["authorization_id"], f"{row_path}.authorization_id", _CLAIM_AUTH_RE, "CLAIM-AUTH-<id>")
|
|
377
|
+
if authorization["authorization_id"] in seen:
|
|
378
|
+
v.fail(path, f"duplicate authorization_id {authorization['authorization_id']!r}")
|
|
379
|
+
seen.add(authorization["authorization_id"])
|
|
380
|
+
v.pattern(authorization["author_event_id"], f"{row_path}.author_event_id", _AUTHOR_EVENT_RE, "AUTHOR-EVENT-<id>")
|
|
381
|
+
v.pattern(authorization["surface_id"], f"{row_path}.surface_id", _CLAIM_SURFACE_RE, "CLAIM-SURFACE-<id>")
|
|
382
|
+
v.pattern(authorization["scoped_manifest_id"], f"{row_path}.scoped_manifest_id", _MANIFEST_ID_RE, "scoped manifest id")
|
|
383
|
+
v.pattern(authorization["claim_id"], f"{row_path}.claim_id", _CLAIM_ID_RE, "C-<n>")
|
|
384
|
+
if authorization["block_id"] == "DOC-BODY-START":
|
|
385
|
+
v.fail(f"{row_path}.block_id", "claim surfaces require an anchored B<n> block")
|
|
386
|
+
v.pattern(authorization["block_id"], f"{row_path}.block_id", _BLOCK_ID_RE, "B<n>")
|
|
387
|
+
for field in ("original_text_sha256", "replacement_text_sha256"):
|
|
388
|
+
v.pattern(authorization[field], f"{row_path}.{field}", _SHA256_RE, "sha256 hex")
|
|
389
|
+
v.string(authorization["replacement_text"], f"{row_path}.replacement_text", nonempty=False)
|
|
390
|
+
if hashlib.sha256(authorization["replacement_text"].encode("utf-8")).hexdigest() != authorization["replacement_text_sha256"]:
|
|
391
|
+
v.fail(row_path, "replacement_text_sha256 does not match replacement_text")
|
|
392
|
+
for field in ("from_rung", "to_rung", "reason"):
|
|
393
|
+
v.string(authorization[field], f"{row_path}.{field}")
|
|
394
|
+
if authorization["from_rung"] == authorization["to_rung"]:
|
|
395
|
+
v.fail(row_path, "from_rung and to_rung must differ")
|
|
396
|
+
v.enum(authorization["direction"], f"{row_path}.direction", ("strengthen", "weaken"))
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _validate_tagged_anchors(v: _V, anchors, path):
|
|
400
|
+
v.array(anchors, path)
|
|
401
|
+
if not anchors:
|
|
402
|
+
v.fail(path, "must carry at least one anchor")
|
|
403
|
+
for i, entry in enumerate(anchors):
|
|
404
|
+
v.obj(entry, f"{path}[{i}]", ["anchor", "anchor_artifact"])
|
|
405
|
+
v.pattern(entry["anchor"], f"{path}[{i}].anchor", _ANCHOR_RE, "the Schema 6 typed-anchor grammar")
|
|
406
|
+
v.enum(entry["anchor_artifact"], f"{path}[{i}].anchor_artifact", ("manuscript", "letter"))
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _validate_new_issue(v: _V, rec, path):
|
|
410
|
+
v.obj(rec, path, [
|
|
411
|
+
"new_issue_id", "description", "location_anchor", "severity", "found_by",
|
|
412
|
+
"confidence", "competence_basis", "attribution", "attribution_evidence",
|
|
413
|
+
"nearest_roadmap_item", "non_match_rationale",
|
|
414
|
+
])
|
|
415
|
+
v.pattern(rec["new_issue_id"], f"{path}.new_issue_id", _ID_RES["NEW"], "NEW-<n>")
|
|
416
|
+
v.string(rec["description"], f"{path}.description")
|
|
417
|
+
v.pattern(rec["location_anchor"], f"{path}.location_anchor", _ANCHOR_RE, "typed anchor")
|
|
418
|
+
v.enum(rec["severity"], f"{path}.severity", SEVERITIES)
|
|
419
|
+
v.enum(rec["found_by"], f"{path}.found_by", VERIFIER_SEATS)
|
|
420
|
+
v.integer(rec["confidence"], f"{path}.confidence", 1, 5)
|
|
421
|
+
v.string(rec["competence_basis"], f"{path}.competence_basis")
|
|
422
|
+
v.enum(rec["attribution"], f"{path}.attribution", ATTRIBUTIONS)
|
|
423
|
+
v.string(rec["attribution_evidence"], f"{path}.attribution_evidence")
|
|
424
|
+
nearest = rec["nearest_roadmap_item"]
|
|
425
|
+
if nearest is not None:
|
|
426
|
+
v.pattern(nearest, f"{path}.nearest_roadmap_item", _ITEM_ID_RE, "REV-<id> or null")
|
|
427
|
+
v.string(rec["non_match_rationale"], f"{path}.non_match_rationale")
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def validate_precommitment(art: dict):
|
|
431
|
+
v = _V("phase1_lint_failed", "precommitment")
|
|
432
|
+
v.obj(art, "$", ["contract_version", "round_id", "input_manifest_hash", "items", "new_standards"])
|
|
433
|
+
v.enum(art["contract_version"], "$.contract_version", ("1.1",))
|
|
434
|
+
v.string(art["round_id"], "$.round_id")
|
|
435
|
+
v.pattern(art["input_manifest_hash"], "$.input_manifest_hash", _SHA256_RE, "sha256 hex")
|
|
436
|
+
v.array(art["items"], "$.items")
|
|
437
|
+
for i, rec in enumerate(art["items"]):
|
|
438
|
+
path = f"$.items[{i}]"
|
|
439
|
+
v.obj(rec, path, [
|
|
440
|
+
"item_id", "obligation_class", "inherited_criterion", "operationalization",
|
|
441
|
+
"expected_change_surface", "equivalence_policy", "source_reviewer",
|
|
442
|
+
"source_reviewer_labels",
|
|
443
|
+
])
|
|
444
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
445
|
+
v.enum(rec["obligation_class"], f"{path}.obligation_class", ("must_fix", "should_fix"))
|
|
446
|
+
crit = rec["inherited_criterion"]
|
|
447
|
+
v.obj(crit, f"{path}.inherited_criterion", ["roadmap_text"], ["letter_text", "letter_item_ref"])
|
|
448
|
+
v.string(crit["roadmap_text"], f"{path}.inherited_criterion.roadmap_text")
|
|
449
|
+
has_text = "letter_text" in crit
|
|
450
|
+
has_ref = "letter_item_ref" in crit
|
|
451
|
+
if has_text != has_ref:
|
|
452
|
+
v.fail(f"{path}.inherited_criterion", "letter_text and letter_item_ref are biconditional (§5.1)")
|
|
453
|
+
if has_text:
|
|
454
|
+
v.string(crit["letter_text"], f"{path}.inherited_criterion.letter_text")
|
|
455
|
+
v.pattern(crit["letter_item_ref"], f"{path}.inherited_criterion.letter_item_ref", _LETTER_REF_RE, "R<n>")
|
|
456
|
+
if rec["obligation_class"] != "must_fix":
|
|
457
|
+
v.fail(f"{path}.inherited_criterion", "letter fields exist ONLY on must_fix items (§5.1)")
|
|
458
|
+
op = rec["operationalization"]
|
|
459
|
+
v.obj(op, f"{path}.operationalization", ["fully_addressed"], ["partially_addressed", "made_worse_discriminator"])
|
|
460
|
+
v.string(op["fully_addressed"], f"{path}.operationalization.fully_addressed")
|
|
461
|
+
if rec["obligation_class"] == "must_fix":
|
|
462
|
+
for field in ("partially_addressed", "made_worse_discriminator"):
|
|
463
|
+
if field not in op:
|
|
464
|
+
v.fail(f"{path}.operationalization", f"must_fix items require {field!r} (§5.1)")
|
|
465
|
+
v.string(op[field], f"{path}.operationalization.{field}")
|
|
466
|
+
else:
|
|
467
|
+
for field in ("partially_addressed", "made_worse_discriminator"):
|
|
468
|
+
if field in op:
|
|
469
|
+
v.fail(f"{path}.operationalization", f"should_fix lighter form forbids {field!r} (§5.1)")
|
|
470
|
+
v.string(rec["expected_change_surface"], f"{path}.expected_change_surface")
|
|
471
|
+
v.enum(rec["equivalence_policy"], f"{path}.equivalence_policy", ("allowed",))
|
|
472
|
+
v.string(rec["source_reviewer"], f"{path}.source_reviewer")
|
|
473
|
+
v.array(rec["source_reviewer_labels"], f"{path}.source_reviewer_labels")
|
|
474
|
+
seen = set()
|
|
475
|
+
for j, label in enumerate(rec["source_reviewer_labels"]):
|
|
476
|
+
v.enum(label, f"{path}.source_reviewer_labels[{j}]", SEAT_LABELS)
|
|
477
|
+
if label in seen:
|
|
478
|
+
v.fail(f"{path}.source_reviewer_labels", f"duplicate label {label!r}")
|
|
479
|
+
seen.add(label)
|
|
480
|
+
v.array(art["new_standards"], "$.new_standards")
|
|
481
|
+
ns_ids = set()
|
|
482
|
+
for i, rec in enumerate(art["new_standards"]):
|
|
483
|
+
path = f"$.new_standards[{i}]"
|
|
484
|
+
v.obj(rec, path, ["new_standard_id", "item_id", "standard_text", "why_not_in_round1", "classification"])
|
|
485
|
+
v.pattern(rec["new_standard_id"], f"{path}.new_standard_id", _ID_RES["NS"], "NS-<n>")
|
|
486
|
+
if rec["new_standard_id"] in ns_ids:
|
|
487
|
+
v.fail(path, f"duplicate new_standard_id {rec['new_standard_id']!r}")
|
|
488
|
+
ns_ids.add(rec["new_standard_id"])
|
|
489
|
+
if rec["item_id"] != "global":
|
|
490
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, 'REV-<id> or "global"')
|
|
491
|
+
v.string(rec["standard_text"], f"{path}.standard_text")
|
|
492
|
+
v.string(rec["why_not_in_round1"], f"{path}.why_not_in_round1")
|
|
493
|
+
v.enum(rec["classification"], f"{path}.classification", ("advisory", "escalation_requested"))
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def validate_verdict_record(art: dict):
|
|
497
|
+
v = _V("phase2a_lint_failed", "verdict_record")
|
|
498
|
+
v.obj(art, "$", ["contract_version", "round_id", "precommitment_hash", "items", "new_issues", "dissents", "escalation_exceptions"])
|
|
499
|
+
v.enum(art["contract_version"], "$.contract_version", ("1.1",))
|
|
500
|
+
v.string(art["round_id"], "$.round_id")
|
|
501
|
+
v.pattern(art["precommitment_hash"], "$.precommitment_hash", _SHA256_RE, "sha256 hex")
|
|
502
|
+
v.array(art["items"], "$.items")
|
|
503
|
+
for i, rec in enumerate(art["items"]):
|
|
504
|
+
path = f"$.items[{i}]"
|
|
505
|
+
v.obj(rec, path, ["item_id", "verdict", "change_summary", "verified_by", "applied_criterion"],
|
|
506
|
+
["evidence_anchor", "cannot_verify_reason", "residual_gap"])
|
|
507
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
508
|
+
v.enum(rec["verdict"], f"{path}.verdict", VERDICTS)
|
|
509
|
+
if rec["verdict"] == "CANNOT_VERIFY":
|
|
510
|
+
if "cannot_verify_reason" not in rec or "evidence_anchor" in rec:
|
|
511
|
+
v.fail(path, "CANNOT_VERIFY carries cannot_verify_reason and no evidence_anchor (§3.3)")
|
|
512
|
+
v.string(rec["cannot_verify_reason"], f"{path}.cannot_verify_reason")
|
|
513
|
+
else:
|
|
514
|
+
if "evidence_anchor" not in rec or "cannot_verify_reason" in rec:
|
|
515
|
+
v.fail(path, "non-CANNOT_VERIFY verdicts carry evidence_anchor and no cannot_verify_reason (§3.3)")
|
|
516
|
+
v.array(rec["evidence_anchor"], f"{path}.evidence_anchor")
|
|
517
|
+
if not rec["evidence_anchor"]:
|
|
518
|
+
v.fail(f"{path}.evidence_anchor", "must carry at least one anchor")
|
|
519
|
+
for j, anchor in enumerate(rec["evidence_anchor"]):
|
|
520
|
+
v.pattern(anchor, f"{path}.evidence_anchor[{j}]", _ANCHOR_RE, "typed anchor")
|
|
521
|
+
if rec["verdict"] == "PARTIALLY_ADDRESSED":
|
|
522
|
+
if "residual_gap" not in rec:
|
|
523
|
+
v.fail(path, "PARTIALLY_ADDRESSED requires residual_gap (§3.3)")
|
|
524
|
+
_validate_residual_gap(v, rec["residual_gap"], f"{path}.residual_gap")
|
|
525
|
+
elif "residual_gap" in rec:
|
|
526
|
+
v.fail(path, "residual_gap is required iff verdict = PARTIALLY_ADDRESSED")
|
|
527
|
+
v.string(rec["change_summary"], f"{path}.change_summary")
|
|
528
|
+
v.enum(rec["verified_by"], f"{path}.verified_by", VERIFIER_SEATS)
|
|
529
|
+
applied = rec["applied_criterion"]
|
|
530
|
+
if applied not in ("precommitted", "not_precommitted") and not (
|
|
531
|
+
isinstance(applied, str) and re.match(r"^dissented:DIS-[1-9][0-9]*$", applied)
|
|
532
|
+
):
|
|
533
|
+
v.fail(f"{path}.applied_criterion", f"invalid value {applied!r}")
|
|
534
|
+
v.array(art["new_issues"], "$.new_issues")
|
|
535
|
+
for i, rec in enumerate(art["new_issues"]):
|
|
536
|
+
_validate_new_issue(v, rec, f"$.new_issues[{i}]")
|
|
537
|
+
v.array(art["dissents"], "$.dissents")
|
|
538
|
+
for i, rec in enumerate(art["dissents"]):
|
|
539
|
+
path = f"$.dissents[{i}]"
|
|
540
|
+
v.obj(rec, path, [
|
|
541
|
+
"dissent_id", "item_id", "criterion_hash", "reason_code",
|
|
542
|
+
"original_operationalization", "replacement_operationalization",
|
|
543
|
+
"evidence", "decision_impact_note",
|
|
544
|
+
])
|
|
545
|
+
v.pattern(rec["dissent_id"], f"{path}.dissent_id", _ID_RES["DIS"], "DIS-<n>")
|
|
546
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
547
|
+
v.pattern(rec["criterion_hash"], f"{path}.criterion_hash", _SHA256_RE, "sha256 hex")
|
|
548
|
+
v.enum(rec["reason_code"], f"{path}.reason_code",
|
|
549
|
+
("criterion_ambiguous", "criterion_infeasible_as_written", "evidence_surface_moved", "criterion_error"))
|
|
550
|
+
for field in ("original_operationalization", "replacement_operationalization", "evidence", "decision_impact_note"):
|
|
551
|
+
v.string(rec[field], f"{path}.{field}")
|
|
552
|
+
v.array(art["escalation_exceptions"], "$.escalation_exceptions")
|
|
553
|
+
for i, rec in enumerate(art["escalation_exceptions"]):
|
|
554
|
+
path = f"$.escalation_exceptions[{i}]"
|
|
555
|
+
v.obj(rec, path, [
|
|
556
|
+
"exception_id", "escalation_class", "reason_code", "evidence_anchor",
|
|
557
|
+
"why_round1_missed_it", "mechanical_decision_impact", "approval_state",
|
|
558
|
+
], ["new_standard_ref"])
|
|
559
|
+
v.pattern(rec["exception_id"], f"{path}.exception_id", _ID_RES["ESC"], "ESC-<n>")
|
|
560
|
+
if "new_standard_ref" in rec:
|
|
561
|
+
v.pattern(rec["new_standard_ref"], f"{path}.new_standard_ref", _ID_RES["NS"], "NS-<n>")
|
|
562
|
+
v.enum(rec["escalation_class"], f"{path}.escalation_class", ESCALATION_CLASSES)
|
|
563
|
+
v.string(rec["reason_code"], f"{path}.reason_code")
|
|
564
|
+
v.pattern(rec["evidence_anchor"], f"{path}.evidence_anchor", _ANCHOR_RE, "typed anchor")
|
|
565
|
+
v.string(rec["why_round1_missed_it"], f"{path}.why_round1_missed_it")
|
|
566
|
+
v.enum(rec["mechanical_decision_impact"], f"{path}.mechanical_decision_impact", ("Minor Revision", "Major Revision"))
|
|
567
|
+
v.enum(rec["approval_state"], f"{path}.approval_state", ("pending",))
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _validate_adjustment_body(v: _V, rec, path, *, drafted: bool):
|
|
571
|
+
required = ["item_id", "from_verdict", "to_verdict", "basis", "rationale"]
|
|
572
|
+
optional = ["evidence_anchor", "cannot_verify_reason", "residual_gap"]
|
|
573
|
+
if drafted:
|
|
574
|
+
pass # drafted bodies carry no booking-assigned fields
|
|
575
|
+
else:
|
|
576
|
+
required.insert(0, "adjustment_id")
|
|
577
|
+
optional += ["critical_rebuttal_check", "source_ref", "supersedes_adjustment_id"]
|
|
578
|
+
v.obj(rec, path, required, optional)
|
|
579
|
+
if not drafted:
|
|
580
|
+
v.pattern(rec["adjustment_id"], f"{path}.adjustment_id", _ID_RES["ADJ"], "ADJ-<n>")
|
|
581
|
+
if "supersedes_adjustment_id" in rec:
|
|
582
|
+
v.pattern(rec["supersedes_adjustment_id"], f"{path}.supersedes_adjustment_id", _ID_RES["ADJ"], "ADJ-<n>")
|
|
583
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
584
|
+
v.enum(rec["from_verdict"], f"{path}.from_verdict", VERDICTS)
|
|
585
|
+
v.enum(rec["to_verdict"], f"{path}.to_verdict", VERDICTS)
|
|
586
|
+
if drafted:
|
|
587
|
+
v.enum(rec["basis"], f"{path}.basis", ("valid_rebuttal",))
|
|
588
|
+
else:
|
|
589
|
+
v.enum(rec["basis"], f"{path}.basis", ADJUSTMENT_BASES)
|
|
590
|
+
v.string(rec["rationale"], f"{path}.rationale")
|
|
591
|
+
if rec["to_verdict"] == "CANNOT_VERIFY":
|
|
592
|
+
if "cannot_verify_reason" not in rec or "evidence_anchor" in rec:
|
|
593
|
+
v.fail(path, "to_verdict CANNOT_VERIFY carries cannot_verify_reason and no evidence_anchor")
|
|
594
|
+
v.string(rec["cannot_verify_reason"], f"{path}.cannot_verify_reason")
|
|
595
|
+
if rec["basis"] != "user_accepted_fail_closed":
|
|
596
|
+
v.fail(path, "user_accepted_fail_closed is the ONLY basis that may land on CANNOT_VERIFY (§3.4)")
|
|
597
|
+
else:
|
|
598
|
+
if "evidence_anchor" not in rec or "cannot_verify_reason" in rec:
|
|
599
|
+
v.fail(path, "non-CANNOT_VERIFY adjustments carry evidence_anchor and no cannot_verify_reason")
|
|
600
|
+
_validate_tagged_anchors(v, rec["evidence_anchor"], f"{path}.evidence_anchor")
|
|
601
|
+
if rec["basis"] in ("author_pointer_located_evidence", "scope_correction"):
|
|
602
|
+
for j, anchor in enumerate(rec["evidence_anchor"]):
|
|
603
|
+
if anchor["anchor_artifact"] != "manuscript":
|
|
604
|
+
v.fail(f"{path}.evidence_anchor[{j}]",
|
|
605
|
+
f"basis {rec['basis']} requires manuscript-side anchors (§5.3)")
|
|
606
|
+
if rec["to_verdict"] == "PARTIALLY_ADDRESSED":
|
|
607
|
+
if "residual_gap" not in rec:
|
|
608
|
+
v.fail(path, "to_verdict PARTIALLY_ADDRESSED requires residual_gap (§5.3)")
|
|
609
|
+
_validate_residual_gap(v, rec["residual_gap"], f"{path}.residual_gap")
|
|
610
|
+
elif "residual_gap" in rec:
|
|
611
|
+
v.fail(path, "residual_gap is required iff to_verdict = PARTIALLY_ADDRESSED")
|
|
612
|
+
# §3.4 Direction column. valid_rebuttal — the only basis allowing
|
|
613
|
+
# letter-side anchors — is "upgrade to FULLY_ADDRESSED"; anything else
|
|
614
|
+
# would let a persuasive letter move a verdict sideways/downward, the
|
|
615
|
+
# exact channel §1 exists to close. author_pointer_located_evidence is
|
|
616
|
+
# an UPGRADE: located evidence satisfies the Phase-1 operationalization
|
|
617
|
+
# (fully or partially), so the target is PARTIALLY/FULLY and strictly
|
|
618
|
+
# better than the source (MADE_WORSE/CANNOT_VERIFY rank lowest).
|
|
619
|
+
if rec["basis"] == "valid_rebuttal":
|
|
620
|
+
if rec["to_verdict"] != "FULLY_ADDRESSED" or rec["from_verdict"] == "FULLY_ADDRESSED":
|
|
621
|
+
v.fail(path, "valid_rebuttal adjustments upgrade to FULLY_ADDRESSED (§3.4 Direction)")
|
|
622
|
+
elif rec["basis"] == "author_pointer_located_evidence":
|
|
623
|
+
rank = {"MADE_WORSE": 0, "CANNOT_VERIFY": 0, "NOT_ADDRESSED": 1, "PARTIALLY_ADDRESSED": 2, "FULLY_ADDRESSED": 3}
|
|
624
|
+
if rec["to_verdict"] not in ("PARTIALLY_ADDRESSED", "FULLY_ADDRESSED") or rank[rec["to_verdict"]] <= rank[rec["from_verdict"]]:
|
|
625
|
+
v.fail(path, "author_pointer_located_evidence adjustments are upgrades to PARTIALLY/FULLY_ADDRESSED (§3.4 Direction)")
|
|
626
|
+
if drafted:
|
|
627
|
+
return
|
|
628
|
+
source_ref = rec.get("source_ref")
|
|
629
|
+
if rec["basis"] == "cross_model_adjudication":
|
|
630
|
+
if not (isinstance(source_ref, str) and source_ref.startswith("reapplication:")):
|
|
631
|
+
v.fail(path, 'basis cross_model_adjudication requires source_ref "reapplication:<id>"')
|
|
632
|
+
elif rec["basis"] == "user_accepted_fail_closed":
|
|
633
|
+
if not (isinstance(source_ref, str) and source_ref.startswith("acceptance:")):
|
|
634
|
+
v.fail(path, 'basis user_accepted_fail_closed requires source_ref "acceptance:<id>"')
|
|
635
|
+
elif source_ref is not None:
|
|
636
|
+
v.fail(path, f"source_ref is FORBIDDEN on basis {rec['basis']} (§5.3)")
|
|
637
|
+
if source_ref is not None:
|
|
638
|
+
v.pattern(source_ref, f"{path}.source_ref", _SOURCE_REF_RE, "reapplication:RAP-<n> | acceptance:ACC-<n>")
|
|
639
|
+
check = rec.get("critical_rebuttal_check")
|
|
640
|
+
if check is not None:
|
|
641
|
+
if rec["basis"] != "valid_rebuttal":
|
|
642
|
+
v.fail(path, "critical_rebuttal_check exists only on valid_rebuttal adjustments (§3.4)")
|
|
643
|
+
if check not in ("single_family_disclosed", "pass_unavailable_disclosed") and not _CHECK_ADJUDICATED_RE.match(check):
|
|
644
|
+
v.fail(f"{path}.critical_rebuttal_check", f"invalid value {check!r}")
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def validate_traceability(art: dict):
|
|
648
|
+
v = _V("phase2b_lint_failed", "traceability")
|
|
649
|
+
v.obj(art, "$", [
|
|
650
|
+
"contract_version", "round_id", "revision", "verdict_record_hash", "rows", "adjustments",
|
|
651
|
+
"new_issues", "post_letter_observations", "dissent_adjudications",
|
|
652
|
+
"resolution_intents", "cross_model_resolutions", "rebuttal_adjudications",
|
|
653
|
+
"g2d_acceptances", "pending_rebuttal_upgrades", "escalation_approvals",
|
|
654
|
+
"reapplications", "decision_inputs", "decision_state",
|
|
655
|
+
], ["supersedes_hash", "abort_reason"])
|
|
656
|
+
v.enum(art["contract_version"], "$.contract_version", ("1.1",))
|
|
657
|
+
v.string(art["round_id"], "$.round_id")
|
|
658
|
+
v.integer(art["revision"], "$.revision", minimum=1)
|
|
659
|
+
if art["revision"] == 1:
|
|
660
|
+
if "supersedes_hash" in art:
|
|
661
|
+
v.fail("$", "revision 1 must not carry supersedes_hash")
|
|
662
|
+
else:
|
|
663
|
+
if "supersedes_hash" not in art:
|
|
664
|
+
v.fail("$", "revision >= 2 requires supersedes_hash")
|
|
665
|
+
v.pattern(art["supersedes_hash"], "$.supersedes_hash", _SHA256_RE, "sha256 hex")
|
|
666
|
+
v.pattern(art["verdict_record_hash"], "$.verdict_record_hash", _SHA256_RE, "sha256 hex")
|
|
667
|
+
v.enum(art["decision_state"], "$.decision_state", DECISION_STATES)
|
|
668
|
+
if art["decision_state"] == "aborted":
|
|
669
|
+
if "abort_reason" not in art:
|
|
670
|
+
v.fail("$", "decision_state aborted requires abort_reason")
|
|
671
|
+
v.enum(art["abort_reason"], "$.abort_reason", ABORT_REASONS)
|
|
672
|
+
elif "abort_reason" in art:
|
|
673
|
+
v.fail("$", "abort_reason is required iff decision_state = aborted")
|
|
674
|
+
|
|
675
|
+
v.array(art["rows"], "$.rows")
|
|
676
|
+
for i, row in enumerate(art["rows"]):
|
|
677
|
+
path = f"$.rows[{i}]"
|
|
678
|
+
v.obj(row, path, [
|
|
679
|
+
"item_id", "concern_id", "obligation_class", "original_comment", "authors_claim",
|
|
680
|
+
"revision_location", "verified", "status", "quality_assessment",
|
|
681
|
+
"final_verdict", "phase2a_verdict", "verified_by", "author_triage",
|
|
682
|
+
"authorized_targets", "claim_strength_authorizations",
|
|
683
|
+
], ["adjustment_id", "addressed_by_rebuttal", "cross_model_status", "cross_model_verdict", "author_reason"])
|
|
684
|
+
v.pattern(row["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
685
|
+
v.pattern(row["concern_id"], f"{path}.concern_id", _CONCERN_ID_RE, "R<n>/S<n>/N<n>")
|
|
686
|
+
v.enum(row["obligation_class"], f"{path}.obligation_class", tuple(ROW_OBLIGATION_MAP))
|
|
687
|
+
for field in ("original_comment", "authors_claim", "revision_location", "quality_assessment"):
|
|
688
|
+
v.string(row[field], f"{path}.{field}")
|
|
689
|
+
v.enum(row["verified"], f"{path}.verified", ("YES", "PARTIAL", "NO", "CANNOT_VERIFY"))
|
|
690
|
+
v.enum(row["status"], f"{path}.status", VERDICTS)
|
|
691
|
+
v.enum(row["final_verdict"], f"{path}.final_verdict", VERDICTS)
|
|
692
|
+
v.enum(row["phase2a_verdict"], f"{path}.phase2a_verdict", VERDICTS)
|
|
693
|
+
v.enum(row["verified_by"], f"{path}.verified_by", VERIFIER_SEATS)
|
|
694
|
+
v.enum(
|
|
695
|
+
row["author_triage"],
|
|
696
|
+
f"{path}.author_triage",
|
|
697
|
+
("will_address", "wont_address", "not_on_point"),
|
|
698
|
+
)
|
|
699
|
+
_validate_author_targets(v, row["authorized_targets"], f"{path}.authorized_targets")
|
|
700
|
+
_validate_claim_authorizations(
|
|
701
|
+
v,
|
|
702
|
+
row["claim_strength_authorizations"],
|
|
703
|
+
f"{path}.claim_strength_authorizations",
|
|
704
|
+
)
|
|
705
|
+
if row["author_triage"] in ("wont_address", "not_on_point"):
|
|
706
|
+
if "author_reason" not in row:
|
|
707
|
+
v.fail(path, "declined author triage requires author_reason")
|
|
708
|
+
v.string(row["author_reason"], f"{path}.author_reason")
|
|
709
|
+
if row["authorized_targets"] or row["claim_strength_authorizations"]:
|
|
710
|
+
v.fail(path, "declined author triage carries no work/claim authority")
|
|
711
|
+
elif "author_reason" in row:
|
|
712
|
+
v.fail(path, "will_address rows do not carry an inferred author_reason")
|
|
713
|
+
if "adjustment_id" in row:
|
|
714
|
+
v.pattern(row["adjustment_id"], f"{path}.adjustment_id", _ID_RES["ADJ"], "ADJ-<n>")
|
|
715
|
+
if "addressed_by_rebuttal" in row and row["addressed_by_rebuttal"] is not True:
|
|
716
|
+
v.fail(f"{path}.addressed_by_rebuttal", "when present the marker is the literal true")
|
|
717
|
+
has_status = "cross_model_status" in row
|
|
718
|
+
has_cmv = "cross_model_verdict" in row
|
|
719
|
+
if row["obligation_class"] == "MUST_FIX":
|
|
720
|
+
if not has_status:
|
|
721
|
+
v.fail(path, "MUST_FIX rows always carry cross_model_status (§5.3 / #539)")
|
|
722
|
+
elif has_status or has_cmv:
|
|
723
|
+
v.fail(path, "SHOULD_FIX/CONSIDER rows carry neither cross-model field (#539 scope)")
|
|
724
|
+
if has_status:
|
|
725
|
+
v.enum(row["cross_model_status"], f"{path}.cross_model_status",
|
|
726
|
+
("agree", "diverges", "unavailable", "not_configured"))
|
|
727
|
+
if row["cross_model_status"] in ("agree", "diverges"):
|
|
728
|
+
if not has_cmv:
|
|
729
|
+
v.fail(path, "evaluated (agree/diverges) rows carry cross_model_verdict")
|
|
730
|
+
v.enum(row["cross_model_verdict"], f"{path}.cross_model_verdict", JUDGE_VERDICTS)
|
|
731
|
+
elif has_cmv:
|
|
732
|
+
v.fail(path, "unavailable/not_configured rows carry no cross_model_verdict")
|
|
733
|
+
|
|
734
|
+
v.array(art["adjustments"], "$.adjustments")
|
|
735
|
+
for i, rec in enumerate(art["adjustments"]):
|
|
736
|
+
_validate_adjustment_body(v, rec, f"$.adjustments[{i}]", drafted=False)
|
|
737
|
+
|
|
738
|
+
v.array(art["new_issues"], "$.new_issues")
|
|
739
|
+
for i, rec in enumerate(art["new_issues"]):
|
|
740
|
+
_validate_new_issue(v, rec, f"$.new_issues[{i}]")
|
|
741
|
+
|
|
742
|
+
v.array(art["post_letter_observations"], "$.post_letter_observations")
|
|
743
|
+
for i, entry in enumerate(art["post_letter_observations"]):
|
|
744
|
+
v.string(entry, f"$.post_letter_observations[{i}]")
|
|
745
|
+
|
|
746
|
+
v.array(art["dissent_adjudications"], "$.dissent_adjudications")
|
|
747
|
+
for i, rec in enumerate(art["dissent_adjudications"]):
|
|
748
|
+
path = f"$.dissent_adjudications[{i}]"
|
|
749
|
+
v.obj(rec, path, ["dissent_id", "adjudicator", "outcome", "rationale"])
|
|
750
|
+
v.pattern(rec["dissent_id"], f"{path}.dissent_id", _ID_RES["DIS"], "DIS-<n>")
|
|
751
|
+
v.enum(rec["adjudicator"], f"{path}.adjudicator", ("cross_model", "user"))
|
|
752
|
+
v.enum(rec["outcome"], f"{path}.outcome", ("replacement_approved", "original_upheld"))
|
|
753
|
+
v.string(rec["rationale"], f"{path}.rationale")
|
|
754
|
+
|
|
755
|
+
v.array(art["resolution_intents"], "$.resolution_intents")
|
|
756
|
+
for i, rec in enumerate(art["resolution_intents"]):
|
|
757
|
+
path = f"$.resolution_intents[{i}]"
|
|
758
|
+
v.obj(rec, path, ["intent_id", "item_id", "answered_by"], ["guidance_note"])
|
|
759
|
+
v.pattern(rec["intent_id"], f"{path}.intent_id", _ID_RES["INT"], "INT-<n>")
|
|
760
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
761
|
+
v.enum(rec["answered_by"], f"{path}.answered_by", ("system", "user"))
|
|
762
|
+
if "guidance_note" in rec:
|
|
763
|
+
if rec["answered_by"] == "system":
|
|
764
|
+
v.fail(path, "guidance_note exists only on user intents (§5.3)")
|
|
765
|
+
v.string(rec["guidance_note"], f"{path}.guidance_note")
|
|
766
|
+
|
|
767
|
+
v.array(art["cross_model_resolutions"], "$.cross_model_resolutions")
|
|
768
|
+
for i, rec in enumerate(art["cross_model_resolutions"]):
|
|
769
|
+
path = f"$.cross_model_resolutions[{i}]"
|
|
770
|
+
v.obj(rec, path, ["resolution_id", "item_id", "intent_id", "reapplication_id", "state", "resolved_by", "rationale"])
|
|
771
|
+
v.pattern(rec["resolution_id"], f"{path}.resolution_id", _ID_RES["RES"], "RES-<n>")
|
|
772
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
773
|
+
v.pattern(rec["intent_id"], f"{path}.intent_id", _ID_RES["INT"], "INT-<n>")
|
|
774
|
+
v.pattern(rec["reapplication_id"], f"{path}.reapplication_id", _ID_RES["RAP"], "RAP-<n>")
|
|
775
|
+
v.enum(rec["state"], f"{path}.state", ("primary_upheld", "primary_revised"))
|
|
776
|
+
v.enum(rec["resolved_by"], f"{path}.resolved_by", ("system", "user"))
|
|
777
|
+
if rec["resolved_by"] == "user" and rec["state"] != "primary_upheld":
|
|
778
|
+
v.fail(path, "the user acceptance form is always primary_upheld — the user never hand-sets a verdict (§5.3)")
|
|
779
|
+
v.string(rec["rationale"], f"{path}.rationale")
|
|
780
|
+
|
|
781
|
+
v.array(art["rebuttal_adjudications"], "$.rebuttal_adjudications")
|
|
782
|
+
for i, rec in enumerate(art["rebuttal_adjudications"]):
|
|
783
|
+
path = f"$.rebuttal_adjudications[{i}]"
|
|
784
|
+
v.obj(rec, path, ["rebuttal_adjudication_id", "item_id", "verdict", "rationale"])
|
|
785
|
+
v.pattern(rec["rebuttal_adjudication_id"], f"{path}.rebuttal_adjudication_id", _ID_RES["RADJ"], "RADJ-<n>")
|
|
786
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
787
|
+
v.enum(rec["verdict"], f"{path}.verdict", ("upheld", "challenged"))
|
|
788
|
+
v.string(rec["rationale"], f"{path}.rationale")
|
|
789
|
+
|
|
790
|
+
v.array(art["g2d_acceptances"], "$.g2d_acceptances")
|
|
791
|
+
for i, rec in enumerate(art["g2d_acceptances"]):
|
|
792
|
+
path = f"$.g2d_acceptances[{i}]"
|
|
793
|
+
v.obj(rec, path, ["acceptance_id", "item_id", "reapplication_id", "accepted_by"])
|
|
794
|
+
v.pattern(rec["acceptance_id"], f"{path}.acceptance_id", _ID_RES["ACC"], "ACC-<n>")
|
|
795
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
796
|
+
v.pattern(rec["reapplication_id"], f"{path}.reapplication_id", _ID_RES["RAP"], "RAP-<n>")
|
|
797
|
+
v.enum(rec["accepted_by"], f"{path}.accepted_by", ("user",))
|
|
798
|
+
|
|
799
|
+
v.array(art["pending_rebuttal_upgrades"], "$.pending_rebuttal_upgrades")
|
|
800
|
+
for i, rec in enumerate(art["pending_rebuttal_upgrades"]):
|
|
801
|
+
path = f"$.pending_rebuttal_upgrades[{i}]"
|
|
802
|
+
v.obj(rec, path, ["proposal_id", "item_id", "drafted_adjustment", "disposition"])
|
|
803
|
+
v.pattern(rec["proposal_id"], f"{path}.proposal_id", _ID_RES["PRB"], "PRB-<n>")
|
|
804
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
805
|
+
_validate_adjustment_body(v, rec["drafted_adjustment"], f"{path}.drafted_adjustment", drafted=True)
|
|
806
|
+
disposition = rec["disposition"]
|
|
807
|
+
if not isinstance(disposition, str) or not re.match(
|
|
808
|
+
r"^(booked:ADJ-[1-9][0-9]*|challenged:RADJ-[1-9][0-9]*|booked_disclosed:ADJ-[1-9][0-9]*|booked_single_family:ADJ-[1-9][0-9]*)$",
|
|
809
|
+
disposition,
|
|
810
|
+
):
|
|
811
|
+
v.fail(f"{path}.disposition", f"invalid disposition {disposition!r}")
|
|
812
|
+
|
|
813
|
+
v.array(art["escalation_approvals"], "$.escalation_approvals")
|
|
814
|
+
for i, rec in enumerate(art["escalation_approvals"]):
|
|
815
|
+
path = f"$.escalation_approvals[{i}]"
|
|
816
|
+
v.obj(rec, path, ["exception_id", "approval_state", "approved_by"])
|
|
817
|
+
v.pattern(rec["exception_id"], f"{path}.exception_id", _ID_RES["ESC"], "ESC-<n>")
|
|
818
|
+
v.enum(rec["approval_state"], f"{path}.approval_state", ("approved", "rejected"))
|
|
819
|
+
v.enum(rec["approved_by"], f"{path}.approved_by", ("user",))
|
|
820
|
+
|
|
821
|
+
v.array(art["reapplications"], "$.reapplications")
|
|
822
|
+
for i, rec in enumerate(art["reapplications"]):
|
|
823
|
+
path = f"$.reapplications[{i}]"
|
|
824
|
+
v.obj(rec, path, [
|
|
825
|
+
"reapplication_id", "item_id", "answer_refs", "pre_reapplication_verdict",
|
|
826
|
+
"reapplied_verdict", "rationale", "criterion_ref",
|
|
827
|
+
], ["supersedes_reapplication_id", "evidence_anchor", "cannot_verify_reason", "residual_gap"])
|
|
828
|
+
v.pattern(rec["reapplication_id"], f"{path}.reapplication_id", _ID_RES["RAP"], "RAP-<n>")
|
|
829
|
+
v.pattern(rec["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
830
|
+
v.array(rec["answer_refs"], f"{path}.answer_refs")
|
|
831
|
+
if not rec["answer_refs"]:
|
|
832
|
+
v.fail(f"{path}.answer_refs", "must carry at least one tagged ref")
|
|
833
|
+
seen_refs = set()
|
|
834
|
+
for j, ref in enumerate(rec["answer_refs"]):
|
|
835
|
+
v.pattern(ref, f"{path}.answer_refs[{j}]", _ANSWER_REF_RE, "adjudication:DIS-<n> | intent:INT-<n>")
|
|
836
|
+
if ref in seen_refs:
|
|
837
|
+
v.fail(f"{path}.answer_refs", f"duplicate ref {ref!r}")
|
|
838
|
+
seen_refs.add(ref)
|
|
839
|
+
if "supersedes_reapplication_id" in rec:
|
|
840
|
+
v.pattern(rec["supersedes_reapplication_id"], f"{path}.supersedes_reapplication_id", _ID_RES["RAP"], "RAP-<n>")
|
|
841
|
+
v.enum(rec["pre_reapplication_verdict"], f"{path}.pre_reapplication_verdict", VERDICTS)
|
|
842
|
+
v.enum(rec["reapplied_verdict"], f"{path}.reapplied_verdict", VERDICTS)
|
|
843
|
+
if rec["reapplied_verdict"] == "CANNOT_VERIFY":
|
|
844
|
+
if "cannot_verify_reason" not in rec or "evidence_anchor" in rec:
|
|
845
|
+
v.fail(path, "CANNOT_VERIFY carries cannot_verify_reason and no evidence_anchor")
|
|
846
|
+
v.string(rec["cannot_verify_reason"], f"{path}.cannot_verify_reason")
|
|
847
|
+
else:
|
|
848
|
+
if "evidence_anchor" not in rec or "cannot_verify_reason" in rec:
|
|
849
|
+
v.fail(path, "non-CANNOT_VERIFY reapplications carry evidence_anchor and no cannot_verify_reason")
|
|
850
|
+
_validate_tagged_anchors(v, rec["evidence_anchor"], f"{path}.evidence_anchor")
|
|
851
|
+
if rec["reapplied_verdict"] == "PARTIALLY_ADDRESSED":
|
|
852
|
+
if "residual_gap" not in rec:
|
|
853
|
+
v.fail(path, "reapplied PARTIALLY_ADDRESSED requires residual_gap")
|
|
854
|
+
_validate_residual_gap(v, rec["residual_gap"], f"{path}.residual_gap")
|
|
855
|
+
elif "residual_gap" in rec:
|
|
856
|
+
v.fail(path, "residual_gap is required iff reapplied_verdict = PARTIALLY_ADDRESSED")
|
|
857
|
+
v.string(rec["rationale"], f"{path}.rationale")
|
|
858
|
+
v.pattern(rec["criterion_ref"], f"{path}.criterion_ref", _CRITERION_REF_RE, "phase1:REV-<id> | dissent:DIS-<n>")
|
|
859
|
+
|
|
860
|
+
di = art["decision_inputs"]
|
|
861
|
+
v.obj(di, "$.decision_inputs", [
|
|
862
|
+
"per_item", "verdict_counts", "residual_obligation_class_counts", "should_fix_addressed_rate",
|
|
863
|
+
"regressions", "non_regression_new_issue_ids", "escalations", "apply_chain_witness",
|
|
864
|
+
], ["reject_recommended"])
|
|
865
|
+
v.array(di["per_item"], "$.decision_inputs.per_item")
|
|
866
|
+
for i, entry in enumerate(di["per_item"]):
|
|
867
|
+
path = f"$.decision_inputs.per_item[{i}]"
|
|
868
|
+
v.obj(entry, path, ["item_id", "final_verdict", "driving_severity"], ["residual_obligation_class"])
|
|
869
|
+
v.pattern(entry["item_id"], f"{path}.item_id", _ITEM_ID_RE, "REV-<id>")
|
|
870
|
+
v.enum(entry["final_verdict"], f"{path}.final_verdict", VERDICTS)
|
|
871
|
+
if entry["driving_severity"] is not None:
|
|
872
|
+
v.enum(entry["driving_severity"], f"{path}.driving_severity", SEVERITIES)
|
|
873
|
+
if "residual_obligation_class" in entry:
|
|
874
|
+
v.enum(entry["residual_obligation_class"], f"{path}.residual_obligation_class", RESIDUAL_OBLIGATION_CLASSES)
|
|
875
|
+
for bucket_name in ("verdict_counts", "residual_obligation_class_counts"):
|
|
876
|
+
bucket = di[bucket_name]
|
|
877
|
+
v.obj(bucket, f"$.decision_inputs.{bucket_name}", list(OBLIGATION_CLASSES))
|
|
878
|
+
keys = VERDICTS if bucket_name == "verdict_counts" else RESIDUAL_OBLIGATION_CLASSES
|
|
879
|
+
for prio in OBLIGATION_CLASSES:
|
|
880
|
+
v.obj(bucket[prio], f"$.decision_inputs.{bucket_name}.{prio}", list(keys))
|
|
881
|
+
for key in keys:
|
|
882
|
+
v.integer(bucket[prio][key], f"$.decision_inputs.{bucket_name}.{prio}.{key}", minimum=0)
|
|
883
|
+
rate = di["should_fix_addressed_rate"]
|
|
884
|
+
v.obj(rate, "$.decision_inputs.should_fix_addressed_rate", ["numerator", "denominator"])
|
|
885
|
+
v.integer(rate["numerator"], "$.decision_inputs.should_fix_addressed_rate.numerator", minimum=0)
|
|
886
|
+
v.integer(rate["denominator"], "$.decision_inputs.should_fix_addressed_rate.denominator", minimum=0)
|
|
887
|
+
v.array(di["regressions"], "$.decision_inputs.regressions")
|
|
888
|
+
for i, entry in enumerate(di["regressions"]):
|
|
889
|
+
path = f"$.decision_inputs.regressions[{i}]"
|
|
890
|
+
v.obj(entry, path, ["new_issue_id", "severity"])
|
|
891
|
+
v.pattern(entry["new_issue_id"], f"{path}.new_issue_id", _ID_RES["NEW"], "NEW-<n>")
|
|
892
|
+
v.enum(entry["severity"], f"{path}.severity", SEVERITIES)
|
|
893
|
+
v.array(di["non_regression_new_issue_ids"], "$.decision_inputs.non_regression_new_issue_ids")
|
|
894
|
+
for i, entry in enumerate(di["non_regression_new_issue_ids"]):
|
|
895
|
+
v.pattern(entry, f"$.decision_inputs.non_regression_new_issue_ids[{i}]", _ID_RES["NEW"], "NEW-<n>")
|
|
896
|
+
v.array(di["escalations"], "$.decision_inputs.escalations")
|
|
897
|
+
for i, entry in enumerate(di["escalations"]):
|
|
898
|
+
path = f"$.decision_inputs.escalations[{i}]"
|
|
899
|
+
v.obj(entry, path, ["exception_id", "effective_approval_state", "escalation_class", "mechanical_decision_impact"])
|
|
900
|
+
v.pattern(entry["exception_id"], f"{path}.exception_id", _ID_RES["ESC"], "ESC-<n>")
|
|
901
|
+
v.enum(entry["effective_approval_state"], f"{path}.effective_approval_state", ("pending", "approved", "rejected"))
|
|
902
|
+
v.enum(entry["escalation_class"], f"{path}.escalation_class", ESCALATION_CLASSES)
|
|
903
|
+
v.enum(entry["mechanical_decision_impact"], f"{path}.mechanical_decision_impact", ("Minor Revision", "Major Revision"))
|
|
904
|
+
if "reject_recommended" in di and not isinstance(di["reject_recommended"], bool):
|
|
905
|
+
v.fail("$.decision_inputs.reject_recommended", "must be a boolean")
|
|
906
|
+
v.enum(di["apply_chain_witness"], "$.decision_inputs.apply_chain_witness", WITNESS_STATES)
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
# --- manifest ------------------------------------------------------------------
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
def validate_manifest(manifest: dict):
|
|
913
|
+
"""Structural manifest validation (§11). Failures -> manifest_incomplete."""
|
|
914
|
+
|
|
915
|
+
def fail(message: str):
|
|
916
|
+
raise ManifestError("manifest_incomplete", f"manifest: {message}")
|
|
917
|
+
|
|
918
|
+
if not isinstance(manifest, dict):
|
|
919
|
+
fail("must be an object")
|
|
920
|
+
expected_top = {"contract_version", "round_id", "cross_model_active", "artifacts"}
|
|
921
|
+
if set(manifest) != expected_top:
|
|
922
|
+
fail(f"top-level fields must be exactly {sorted(expected_top)}")
|
|
923
|
+
if manifest["contract_version"] != "1.1":
|
|
924
|
+
fail("contract_version must be current 1.1; legacy/mixed chains use the archived loader")
|
|
925
|
+
if not _is_str(manifest["round_id"]):
|
|
926
|
+
fail("round_id must be a non-empty string")
|
|
927
|
+
if not isinstance(manifest["cross_model_active"], bool):
|
|
928
|
+
fail("cross_model_active must be a boolean")
|
|
929
|
+
artifacts = manifest["artifacts"]
|
|
930
|
+
single_keys = (
|
|
931
|
+
"original_manuscript", "revised_manuscript", "revision_roadmap",
|
|
932
|
+
"author_adjudication", "revision_evidence_bundle",
|
|
933
|
+
"editorial_decision_letter", "response_to_reviewers",
|
|
934
|
+
"round1_findings", "round1_config_cards",
|
|
935
|
+
)
|
|
936
|
+
array_keys = ("revision_patches", "apply_reports")
|
|
937
|
+
if not isinstance(artifacts, dict) or set(artifacts) != set(single_keys) | set(array_keys):
|
|
938
|
+
fail("artifacts must carry exactly the eleven current §11 keys")
|
|
939
|
+
|
|
940
|
+
def check_entry_fields(entry, key, path):
|
|
941
|
+
ref = entry.get("path_or_passport_ref")
|
|
942
|
+
if not _is_str(ref) or not _ARTIFACT_REF_RE.match(ref):
|
|
943
|
+
fail(f"{path}: path_or_passport_ref must be passport:<ref> | path:<relative path>")
|
|
944
|
+
if ref.startswith("path:"):
|
|
945
|
+
rel = ref[len("path:"):]
|
|
946
|
+
segments = re.split(r"[/\\]", rel)
|
|
947
|
+
if (
|
|
948
|
+
rel.startswith(("/", "\\"))
|
|
949
|
+
or re.match(r"^[A-Za-z]:", rel)
|
|
950
|
+
or any(segment == ".." for segment in segments)
|
|
951
|
+
or not rel
|
|
952
|
+
):
|
|
953
|
+
fail(f"{path}: path: refs carry a RELATIVE path — no absolute, drive-letter, or traversal segments (§11)")
|
|
954
|
+
sha = entry.get("sha256")
|
|
955
|
+
if not isinstance(sha, str) or not _SHA256_RE.match(sha):
|
|
956
|
+
fail(f"{path}: sha256 must be 64-hex")
|
|
957
|
+
for field in ("version_label", "origin_date"):
|
|
958
|
+
value = entry.get(field, "MISSING")
|
|
959
|
+
if value == "MISSING":
|
|
960
|
+
fail(f"{path}: missing {field}")
|
|
961
|
+
if value is not None and not _is_str(value):
|
|
962
|
+
fail(f"{path}: {field} must be a non-empty string or null")
|
|
963
|
+
if value is None and entry["path_or_passport_ref"].startswith("passport:"):
|
|
964
|
+
fail(f"{path}: null {field} on a passport:-tagged entry (§11)")
|
|
965
|
+
|
|
966
|
+
for key in single_keys:
|
|
967
|
+
entry = artifacts[key]
|
|
968
|
+
if not isinstance(entry, dict) or not isinstance(entry.get("present"), bool):
|
|
969
|
+
fail(f"artifacts.{key}: present-discriminated union required")
|
|
970
|
+
if entry["present"]:
|
|
971
|
+
if set(entry) != {"present", "path_or_passport_ref", "sha256", "version_label", "origin_date"}:
|
|
972
|
+
fail(f"artifacts.{key}: present entry carries exactly ref/sha256/version_label/origin_date")
|
|
973
|
+
check_entry_fields(entry, key, f"artifacts.{key}")
|
|
974
|
+
elif set(entry) != {"present"}:
|
|
975
|
+
fail(f"artifacts.{key}: an absent artifact carries NO ref, hash, or freshness fields (§11)")
|
|
976
|
+
for key in array_keys:
|
|
977
|
+
entry = artifacts[key]
|
|
978
|
+
if not isinstance(entry, dict) or not isinstance(entry.get("present"), bool):
|
|
979
|
+
fail(f"artifacts.{key}: present-discriminated union required")
|
|
980
|
+
if entry["present"]:
|
|
981
|
+
if set(entry) != {"present", "items"}:
|
|
982
|
+
fail(f"artifacts.{key}: present array entry carries exactly items")
|
|
983
|
+
items = entry["items"]
|
|
984
|
+
if not isinstance(items, list) or not items:
|
|
985
|
+
fail(f"artifacts.{key}: present: false is the canonical empty encoding (§11)")
|
|
986
|
+
for i, element in enumerate(items):
|
|
987
|
+
if not isinstance(element, dict) or set(element) != {"path_or_passport_ref", "sha256", "version_label", "origin_date"}:
|
|
988
|
+
fail(f"artifacts.{key}[{i}]: every element FULLY populated (no per-element absence)")
|
|
989
|
+
check_entry_fields(element, key, f"artifacts.{key}[{i}]")
|
|
990
|
+
elif set(entry) != {"present"}:
|
|
991
|
+
fail(f"artifacts.{key}: an absent artifact carries NO ref, hash, or freshness fields (§11)")
|
|
992
|
+
|
|
993
|
+
# Required set + array pairing (G0).
|
|
994
|
+
for key in (
|
|
995
|
+
"original_manuscript",
|
|
996
|
+
"revised_manuscript",
|
|
997
|
+
"revision_roadmap",
|
|
998
|
+
"author_adjudication",
|
|
999
|
+
"revision_evidence_bundle",
|
|
1000
|
+
):
|
|
1001
|
+
if not artifacts[key]["present"]:
|
|
1002
|
+
raise ManifestError("manifest_incomplete", f"manifest: hard-required artifact {key} absent (§11)")
|
|
1003
|
+
patches = artifacts["revision_patches"]
|
|
1004
|
+
reports = artifacts["apply_reports"]
|
|
1005
|
+
if patches["present"] != reports["present"]:
|
|
1006
|
+
raise ManifestError(
|
|
1007
|
+
"manifest_incomplete",
|
|
1008
|
+
"manifest: revision_patches and apply_reports must travel together (§11)",
|
|
1009
|
+
)
|
|
1010
|
+
if reports["present"]:
|
|
1011
|
+
if len(reports["items"]) != len(patches["items"]):
|
|
1012
|
+
raise ManifestError("manifest_incomplete", "manifest: apply_reports / revision_patches length mismatch (§11)")
|
|
1013
|
+
|
|
1014
|
+
# Freshness: a roadmap older than the letter it should pair with fails
|
|
1015
|
+
# closed (§11). Comparable only when both origin_dates are non-null.
|
|
1016
|
+
roadmap_entry = artifacts["revision_roadmap"]
|
|
1017
|
+
letter_entry = artifacts["editorial_decision_letter"]
|
|
1018
|
+
if letter_entry["present"] and roadmap_entry["origin_date"] is not None and letter_entry["origin_date"] is not None:
|
|
1019
|
+
if roadmap_entry["origin_date"] < letter_entry["origin_date"]:
|
|
1020
|
+
raise ManifestError(
|
|
1021
|
+
"manifest_hash_mismatch",
|
|
1022
|
+
"manifest: revision_roadmap origin_date predates editorial_decision_letter (freshness, §11)",
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def _hash_prefix(sha256_hex: str) -> str:
|
|
1027
|
+
"""§11: apply reports use the 12-hex base_draft_hash format."""
|
|
1028
|
+
return sha256_hex[:12]
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
def compute_apply_chain_witness(manifest: dict, report_payloads):
|
|
1032
|
+
"""§11 apply-chain witness over the ordered report payloads.
|
|
1033
|
+
|
|
1034
|
+
Returns (witness, notes). A broken checked link raises ManifestError
|
|
1035
|
+
(manifest_hash_mismatch, G0) — positive breakage evidence outranks the
|
|
1036
|
+
absence markers, matching the declared precedence
|
|
1037
|
+
fail > not_run_no_reports > pass.
|
|
1038
|
+
"""
|
|
1039
|
+
artifacts = manifest["artifacts"]
|
|
1040
|
+
notes = []
|
|
1041
|
+
if not report_payloads:
|
|
1042
|
+
return "not_run_no_reports", notes
|
|
1043
|
+
patches = artifacts["revision_patches"]["items"]
|
|
1044
|
+
for i, report in enumerate(report_payloads):
|
|
1045
|
+
if not isinstance(report, dict):
|
|
1046
|
+
raise ManifestError("manifest_incomplete", f"apply report [{i}]: must be a JSON object")
|
|
1047
|
+
for field in ("report_format_version", "base_draft_hash", "output_draft_hash"):
|
|
1048
|
+
if not _is_str(report.get(field)):
|
|
1049
|
+
raise ManifestError("manifest_incomplete", f"apply report [{i}]: missing {field}")
|
|
1050
|
+
if not _REPORT_VERSION_RE.match(report["report_format_version"]):
|
|
1051
|
+
raise ManifestError(
|
|
1052
|
+
"manifest_incomplete",
|
|
1053
|
+
f"apply report [{i}]: report_format_version {report['report_format_version']!r} is not a numeric "
|
|
1054
|
+
"dotted version; current contract requires exact report format 1.3 (§11)",
|
|
1055
|
+
)
|
|
1056
|
+
for field in ("base_draft_hash", "output_draft_hash"):
|
|
1057
|
+
if not _HASH12_RE.match(report[field]):
|
|
1058
|
+
raise ManifestError(
|
|
1059
|
+
"manifest_incomplete",
|
|
1060
|
+
f"apply report [{i}]: {field} must be the 12-hex base_draft_hash format (§11)",
|
|
1061
|
+
)
|
|
1062
|
+
version = tuple(int(part) for part in report["report_format_version"].split("."))
|
|
1063
|
+
if version != (1, 3):
|
|
1064
|
+
raise ManifestError(
|
|
1065
|
+
"manifest_incomplete",
|
|
1066
|
+
f"apply report [{i}]: current contract requires report format 1.3; legacy reports use the archived loader",
|
|
1067
|
+
)
|
|
1068
|
+
digest = report.get("patch_digest")
|
|
1069
|
+
if not isinstance(digest, str) or not _SHA256_RE.match(digest):
|
|
1070
|
+
raise ManifestError("manifest_incomplete", f"apply report [{i}]: format 1.3 requires patch_digest")
|
|
1071
|
+
if digest != patches[i]["sha256"]:
|
|
1072
|
+
raise ManifestError(
|
|
1073
|
+
"manifest_hash_mismatch",
|
|
1074
|
+
f"apply report [{i}]: patch_digest does not equal the paired revision_patches[{i}].sha256 (§11 content-bound pairing)",
|
|
1075
|
+
)
|
|
1076
|
+
authorization = report.get("authorization_witness")
|
|
1077
|
+
if not isinstance(authorization, dict) or authorization.get("status") not in (
|
|
1078
|
+
"pass",
|
|
1079
|
+
"not_applicable_integrity_correction",
|
|
1080
|
+
):
|
|
1081
|
+
raise ManifestError(
|
|
1082
|
+
"manifest_incomplete",
|
|
1083
|
+
f"apply report [{i}]: format 1.3 requires a current replayed authorization_witness",
|
|
1084
|
+
)
|
|
1085
|
+
if authorization.get("unregistered_claim_drift_review_required") is not True:
|
|
1086
|
+
raise ManifestError(
|
|
1087
|
+
"manifest_incomplete",
|
|
1088
|
+
f"apply report [{i}]: authorization witness must surface the E6 unregistered-claim review boundary",
|
|
1089
|
+
)
|
|
1090
|
+
original = artifacts["original_manuscript"]
|
|
1091
|
+
if not original["present"]:
|
|
1092
|
+
raise ManifestError(
|
|
1093
|
+
"manifest_incomplete",
|
|
1094
|
+
"apply-chain witness: current contract hard-requires original_manuscript",
|
|
1095
|
+
)
|
|
1096
|
+
if report_payloads[0]["base_draft_hash"] != _hash_prefix(original["sha256"]):
|
|
1097
|
+
raise ManifestError(
|
|
1098
|
+
"manifest_hash_mismatch",
|
|
1099
|
+
"apply-chain witness: first report base_draft_hash does not equal the original_manuscript hash prefix (§11)",
|
|
1100
|
+
)
|
|
1101
|
+
for i in range(1, len(report_payloads)):
|
|
1102
|
+
if report_payloads[i]["base_draft_hash"] != report_payloads[i - 1]["output_draft_hash"]:
|
|
1103
|
+
raise ManifestError(
|
|
1104
|
+
"manifest_hash_mismatch",
|
|
1105
|
+
f"apply-chain witness: report [{i}] base_draft_hash does not equal report [{i - 1}] output_draft_hash (§11)",
|
|
1106
|
+
)
|
|
1107
|
+
revised = artifacts["revised_manuscript"]
|
|
1108
|
+
if report_payloads[-1]["output_draft_hash"] != _hash_prefix(revised["sha256"]):
|
|
1109
|
+
raise ManifestError(
|
|
1110
|
+
"manifest_hash_mismatch",
|
|
1111
|
+
"apply-chain witness: last report output_draft_hash does not equal the revised_manuscript hash prefix (§11)",
|
|
1112
|
+
)
|
|
1113
|
+
return "pass", notes
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
def validate_revision_bundle_binding(bundle: dict, manifest: dict) -> None:
|
|
1117
|
+
"""Bind the replayed bundle to the exact §11 current-round artifacts."""
|
|
1118
|
+
artifacts = manifest["artifacts"]
|
|
1119
|
+
revised_sha = artifacts["revised_manuscript"]["sha256"]
|
|
1120
|
+
if bundle["final_draft"]["sha256"] != revised_sha:
|
|
1121
|
+
raise ManifestError(
|
|
1122
|
+
"manifest_hash_mismatch",
|
|
1123
|
+
"revision_evidence_bundle.final_draft does not equal revised_manuscript",
|
|
1124
|
+
)
|
|
1125
|
+
|
|
1126
|
+
roadmap_sha = artifacts["revision_roadmap"]["sha256"]
|
|
1127
|
+
author_sha = artifacts["author_adjudication"]["sha256"]
|
|
1128
|
+
partial_matches = []
|
|
1129
|
+
exact_matches = []
|
|
1130
|
+
for index, row in enumerate(bundle["rounds"]):
|
|
1131
|
+
if row["kind"] not in ("review_roadmap", "review_noop"):
|
|
1132
|
+
continue
|
|
1133
|
+
roadmap_match = row["revision_roadmap"]["sha256"] == roadmap_sha
|
|
1134
|
+
author_match = row["author_adjudication"]["sha256"] == author_sha
|
|
1135
|
+
if roadmap_match != author_match:
|
|
1136
|
+
partial_matches.append(index)
|
|
1137
|
+
if roadmap_match and author_match:
|
|
1138
|
+
exact_matches.append((index, row))
|
|
1139
|
+
if partial_matches:
|
|
1140
|
+
raise ManifestError(
|
|
1141
|
+
"manifest_hash_mismatch",
|
|
1142
|
+
"revision_evidence_bundle splits the current roadmap/author pair across rounds",
|
|
1143
|
+
)
|
|
1144
|
+
if len(exact_matches) != 1:
|
|
1145
|
+
raise ManifestError(
|
|
1146
|
+
"manifest_hash_mismatch",
|
|
1147
|
+
"revision_evidence_bundle must contain exactly one round with the exact current roadmap/author pair",
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
_index, current = exact_matches[0]
|
|
1151
|
+
original = artifacts["original_manuscript"]
|
|
1152
|
+
if original["present"] and current["pre_round_draft"]["sha256"] != original["sha256"]:
|
|
1153
|
+
raise ManifestError(
|
|
1154
|
+
"manifest_hash_mismatch",
|
|
1155
|
+
"revision_evidence_bundle current round pre draft differs from original_manuscript",
|
|
1156
|
+
)
|
|
1157
|
+
bundle_pairs = [
|
|
1158
|
+
(row["revision_patch"]["sha256"], row["apply_report"]["sha256"])
|
|
1159
|
+
for row in bundle["rounds"]
|
|
1160
|
+
if row["kind"] in ("review_roadmap", "integrity_correction")
|
|
1161
|
+
]
|
|
1162
|
+
patches = artifacts["revision_patches"]
|
|
1163
|
+
reports = artifacts["apply_reports"]
|
|
1164
|
+
if patches["present"] != reports["present"]:
|
|
1165
|
+
raise ManifestError(
|
|
1166
|
+
"manifest_incomplete",
|
|
1167
|
+
"revision_evidence_bundle cannot bind asymmetric revision_patches/apply_reports state",
|
|
1168
|
+
)
|
|
1169
|
+
manifest_pairs = []
|
|
1170
|
+
if patches["present"] and reports["present"]:
|
|
1171
|
+
manifest_pairs = [
|
|
1172
|
+
(patch["sha256"], report["sha256"])
|
|
1173
|
+
for patch, report in zip(patches["items"], reports["items"])
|
|
1174
|
+
]
|
|
1175
|
+
if bundle_pairs != manifest_pairs:
|
|
1176
|
+
raise ManifestError(
|
|
1177
|
+
"manifest_hash_mismatch",
|
|
1178
|
+
"revision_evidence_bundle ordered write patch/report pairs differ from the input manifest",
|
|
1179
|
+
)
|
|
1180
|
+
|
|
1181
|
+
|
|
1182
|
+
# --- letter + roadmap parsing --------------------------------------------------
|
|
1183
|
+
|
|
1184
|
+
_LETTER_SECTION_RE = re.compile(r"^### Required Item Details\s*$")
|
|
1185
|
+
_LETTER_BLOCK_RE = re.compile(r"^\*\*(R[1-9][0-9]*): .*\*\*\s*$")
|
|
1186
|
+
_LETTER_CRITERIA_RE = re.compile(r"^- \*\*Acceptance criteria\*\*: (.*)$")
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
def parse_letter_blocks(text: str):
|
|
1190
|
+
"""Extract the letter's Required Item Details blocks in document order.
|
|
1191
|
+
|
|
1192
|
+
Returns an ordered list of (rid, acceptance_text) where acceptance_text
|
|
1193
|
+
is the single-line payload after the ``- **Acceptance criteria**:``
|
|
1194
|
+
bullet, or None when the block carries no such bullet (a block without
|
|
1195
|
+
an Acceptance-criteria field offers nothing to inherit — treated as
|
|
1196
|
+
no-block-at-ordinal for the §5.1 letter-fields rule; its id still
|
|
1197
|
+
participates in the contiguity check).
|
|
1198
|
+
"""
|
|
1199
|
+
lines = text.splitlines()
|
|
1200
|
+
blocks = []
|
|
1201
|
+
in_section = False
|
|
1202
|
+
current = None
|
|
1203
|
+
for line in lines:
|
|
1204
|
+
if _LETTER_SECTION_RE.match(line):
|
|
1205
|
+
in_section = True
|
|
1206
|
+
continue
|
|
1207
|
+
if not in_section:
|
|
1208
|
+
continue
|
|
1209
|
+
if line.startswith("## "):
|
|
1210
|
+
break
|
|
1211
|
+
header = _LETTER_BLOCK_RE.match(line)
|
|
1212
|
+
if header:
|
|
1213
|
+
current = [header.group(1), None]
|
|
1214
|
+
blocks.append(current)
|
|
1215
|
+
continue
|
|
1216
|
+
if current is not None and current[1] is None:
|
|
1217
|
+
criteria = _LETTER_CRITERIA_RE.match(line)
|
|
1218
|
+
if criteria:
|
|
1219
|
+
current[1] = criteria.group(1)
|
|
1220
|
+
return [(rid, text_value) for rid, text_value in blocks]
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
def letter_ordinals_sound(blocks, must_fix_count: int) -> bool:
|
|
1224
|
+
"""§5.1 contiguity: ids EXACTLY the contiguous sequence R1..Rk, k <= n.
|
|
1225
|
+
|
|
1226
|
+
A gap, duplicate, or extra id (beyond the must_fix count) makes the
|
|
1227
|
+
ordinal derivation unsound and degrades the WHOLE letter layer to
|
|
1228
|
+
absent; a mere count shortfall (contiguous prefix) does not.
|
|
1229
|
+
"""
|
|
1230
|
+
for i, (rid, _text) in enumerate(blocks):
|
|
1231
|
+
if rid != f"R{i + 1}":
|
|
1232
|
+
return False
|
|
1233
|
+
return len(blocks) <= must_fix_count
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
def load_roadmap(payload: dict):
|
|
1237
|
+
"""Minimal Schema 7 machine-form validation for checker consumption."""
|
|
1238
|
+
|
|
1239
|
+
def fail(message: str):
|
|
1240
|
+
raise ManifestError("manifest_incomplete", f"revision_roadmap: {message}")
|
|
1241
|
+
|
|
1242
|
+
if not isinstance(payload, dict) or not isinstance(payload.get("items"), list):
|
|
1243
|
+
fail("must be an object with an items array (Schema 7)")
|
|
1244
|
+
items = []
|
|
1245
|
+
seen = set()
|
|
1246
|
+
for i, item in enumerate(payload["items"]):
|
|
1247
|
+
if not isinstance(item, dict):
|
|
1248
|
+
fail(f"items[{i}] must be an object")
|
|
1249
|
+
for field in ("id", "obligation_class", "verification_criteria", "reviewer"):
|
|
1250
|
+
if not _is_str(item.get(field)):
|
|
1251
|
+
fail(f"items[{i}].{field} must be a non-empty string")
|
|
1252
|
+
if not _ITEM_ID_RE.match(item["id"]):
|
|
1253
|
+
fail(f"items[{i}].id must be REV-<id>")
|
|
1254
|
+
if item["id"] in seen:
|
|
1255
|
+
fail(f"duplicate roadmap id {item['id']!r}")
|
|
1256
|
+
seen.add(item["id"])
|
|
1257
|
+
if item["obligation_class"] not in OBLIGATION_CLASSES:
|
|
1258
|
+
fail(f"items[{i}].obligation_class must be one of {OBLIGATION_CLASSES}")
|
|
1259
|
+
severity = item.get("severity")
|
|
1260
|
+
if severity is not None and severity not in SEVERITIES:
|
|
1261
|
+
fail(f"items[{i}].severity must be one of {SEVERITIES} when present")
|
|
1262
|
+
source_kind = item.get("source_kind")
|
|
1263
|
+
if source_kind is not None and source_kind not in ("question", "editorial"):
|
|
1264
|
+
fail(f"items[{i}].source_kind must be question|editorial when present")
|
|
1265
|
+
transported = {
|
|
1266
|
+
"severity",
|
|
1267
|
+
"severity_source",
|
|
1268
|
+
"evidence_anchor",
|
|
1269
|
+
"confidence",
|
|
1270
|
+
"competence_basis",
|
|
1271
|
+
"confidence_source",
|
|
1272
|
+
"corroborating_sources",
|
|
1273
|
+
}
|
|
1274
|
+
if source_kind is None:
|
|
1275
|
+
for field in ("severity", "evidence_anchor", "confidence", "competence_basis"):
|
|
1276
|
+
if field not in item:
|
|
1277
|
+
fail(f"items[{i}] finding-driven row requires transported {field}")
|
|
1278
|
+
elif transported & set(item):
|
|
1279
|
+
fail(f"items[{i}] source_kind row must not carry transported finding fields")
|
|
1280
|
+
items.append(item)
|
|
1281
|
+
return items
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
def load_author_adjudication(
|
|
1285
|
+
payload: dict,
|
|
1286
|
+
*,
|
|
1287
|
+
roadmap_items: list[dict],
|
|
1288
|
+
roadmap_sha256: str,
|
|
1289
|
+
original_manuscript_sha256: str | None,
|
|
1290
|
+
):
|
|
1291
|
+
"""Validate the #670 sidecar projection this consumer copies.
|
|
1292
|
+
|
|
1293
|
+
Full target/claim-surface replay already occurs at patch apply and bundle
|
|
1294
|
+
validation. This consumer independently validates the closed sidecar shape,
|
|
1295
|
+
exact roadmap/base bindings, explicit user-event references, full item
|
|
1296
|
+
cardinality, and the author-owned fields Schema 11 must copy unchanged.
|
|
1297
|
+
"""
|
|
1298
|
+
|
|
1299
|
+
def fail(message: str):
|
|
1300
|
+
raise ManifestError("manifest_incomplete", f"author_adjudication: {message}")
|
|
1301
|
+
|
|
1302
|
+
expected_top = {
|
|
1303
|
+
"schema_version",
|
|
1304
|
+
"revision_round",
|
|
1305
|
+
"roadmap_sha256",
|
|
1306
|
+
"base_draft_sha256",
|
|
1307
|
+
"claim_surface_manifest_sha256",
|
|
1308
|
+
"adjudication_status",
|
|
1309
|
+
"author_events",
|
|
1310
|
+
"display_order",
|
|
1311
|
+
"author_adjudications",
|
|
1312
|
+
"collateral_authorizations",
|
|
1313
|
+
}
|
|
1314
|
+
if not isinstance(payload, dict) or set(payload) != expected_top:
|
|
1315
|
+
fail(f"top-level fields must be exactly {sorted(expected_top)}")
|
|
1316
|
+
if payload["schema_version"] != "author-adjudication/1.0":
|
|
1317
|
+
fail("schema_version must be author-adjudication/1.0")
|
|
1318
|
+
if not isinstance(payload["revision_round"], int) or isinstance(payload["revision_round"], bool) or payload["revision_round"] < 1:
|
|
1319
|
+
fail("revision_round must be an integer >= 1")
|
|
1320
|
+
for field in ("roadmap_sha256", "base_draft_sha256", "claim_surface_manifest_sha256"):
|
|
1321
|
+
if not isinstance(payload[field], str) or not _SHA256_RE.match(payload[field]):
|
|
1322
|
+
fail(f"{field} must be 64-hex")
|
|
1323
|
+
if payload["roadmap_sha256"] != roadmap_sha256:
|
|
1324
|
+
raise ManifestError(
|
|
1325
|
+
"manifest_hash_mismatch",
|
|
1326
|
+
"author_adjudication.roadmap_sha256 does not match the exact roadmap bytes",
|
|
1327
|
+
)
|
|
1328
|
+
if (
|
|
1329
|
+
original_manuscript_sha256 is not None
|
|
1330
|
+
and payload["base_draft_sha256"] != original_manuscript_sha256
|
|
1331
|
+
):
|
|
1332
|
+
raise ManifestError(
|
|
1333
|
+
"manifest_hash_mismatch",
|
|
1334
|
+
"author_adjudication.base_draft_sha256 does not match original_manuscript bytes",
|
|
1335
|
+
)
|
|
1336
|
+
if payload["adjudication_status"] != "complete":
|
|
1337
|
+
fail("adjudication_status must be complete")
|
|
1338
|
+
|
|
1339
|
+
events = payload["author_events"]
|
|
1340
|
+
if not isinstance(events, list):
|
|
1341
|
+
fail("author_events must be an array")
|
|
1342
|
+
event_ids = set()
|
|
1343
|
+
for index, event in enumerate(events):
|
|
1344
|
+
if not isinstance(event, dict) or set(event) != {
|
|
1345
|
+
"event_id",
|
|
1346
|
+
"source",
|
|
1347
|
+
"actor_role",
|
|
1348
|
+
"input_sha256",
|
|
1349
|
+
}:
|
|
1350
|
+
fail(f"author_events[{index}] has an invalid closed shape")
|
|
1351
|
+
if not _AUTHOR_EVENT_RE.match(event["event_id"]):
|
|
1352
|
+
fail(f"author_events[{index}].event_id is invalid")
|
|
1353
|
+
if event["event_id"] in event_ids:
|
|
1354
|
+
fail(f"duplicate author event {event['event_id']!r}")
|
|
1355
|
+
event_ids.add(event["event_id"])
|
|
1356
|
+
if event["source"] != "explicit_session_user_message" or event["actor_role"] != "author":
|
|
1357
|
+
fail(f"author_events[{index}] is not an explicit author session event")
|
|
1358
|
+
if not isinstance(event["input_sha256"], str) or not _SHA256_RE.match(event["input_sha256"]):
|
|
1359
|
+
fail(f"author_events[{index}].input_sha256 must be 64-hex")
|
|
1360
|
+
|
|
1361
|
+
roadmap_ids = [item["id"] for item in roadmap_items]
|
|
1362
|
+
display = payload["display_order"]
|
|
1363
|
+
if not isinstance(display, dict) or set(display) != {"mode", "item_ids", "author_event_id"}:
|
|
1364
|
+
fail("display_order has an invalid closed shape")
|
|
1365
|
+
if display["mode"] not in ("source_traceability", "user_selected"):
|
|
1366
|
+
fail("display_order.mode is invalid")
|
|
1367
|
+
if (
|
|
1368
|
+
not isinstance(display["item_ids"], list)
|
|
1369
|
+
or len(display["item_ids"]) != len(set(display["item_ids"]))
|
|
1370
|
+
or set(display["item_ids"]) != set(roadmap_ids)
|
|
1371
|
+
):
|
|
1372
|
+
fail("display_order.item_ids must be a full unique roadmap permutation")
|
|
1373
|
+
if display["mode"] == "source_traceability" and display["item_ids"] != roadmap_ids:
|
|
1374
|
+
fail("source_traceability display order must equal immutable roadmap order")
|
|
1375
|
+
if display["author_event_id"] not in event_ids:
|
|
1376
|
+
fail("display_order.author_event_id does not resolve")
|
|
1377
|
+
|
|
1378
|
+
records = payload["author_adjudications"]
|
|
1379
|
+
if not isinstance(records, list):
|
|
1380
|
+
fail("author_adjudications must be an array")
|
|
1381
|
+
by_item = {}
|
|
1382
|
+
for index, record in enumerate(records):
|
|
1383
|
+
required = {
|
|
1384
|
+
"item_id",
|
|
1385
|
+
"author_event_id",
|
|
1386
|
+
"author_triage",
|
|
1387
|
+
"authorized_targets",
|
|
1388
|
+
"claim_strength_authorizations",
|
|
1389
|
+
}
|
|
1390
|
+
allowed = required | {"author_reason"}
|
|
1391
|
+
if not isinstance(record, dict) or not required.issubset(record) or not set(record).issubset(allowed):
|
|
1392
|
+
fail(f"author_adjudications[{index}] has an invalid closed shape")
|
|
1393
|
+
item_id = record["item_id"]
|
|
1394
|
+
if item_id in by_item:
|
|
1395
|
+
fail(f"duplicate author adjudication for {item_id!r}")
|
|
1396
|
+
by_item[item_id] = record
|
|
1397
|
+
if record["author_event_id"] not in event_ids:
|
|
1398
|
+
fail(f"author adjudication {item_id}: author_event_id does not resolve")
|
|
1399
|
+
if record["author_triage"] not in ("will_address", "wont_address", "not_on_point"):
|
|
1400
|
+
fail(f"author adjudication {item_id}: invalid author_triage")
|
|
1401
|
+
try:
|
|
1402
|
+
validator = _V("manifest_incomplete", f"author adjudication {item_id}")
|
|
1403
|
+
_validate_author_targets(validator, record["authorized_targets"], "$.authorized_targets")
|
|
1404
|
+
_validate_claim_authorizations(
|
|
1405
|
+
validator,
|
|
1406
|
+
record["claim_strength_authorizations"],
|
|
1407
|
+
"$.claim_strength_authorizations",
|
|
1408
|
+
)
|
|
1409
|
+
except ArtifactSchemaError as exc:
|
|
1410
|
+
fail(str(exc))
|
|
1411
|
+
if record["author_triage"] == "will_address":
|
|
1412
|
+
if not record["authorized_targets"]:
|
|
1413
|
+
fail(f"author adjudication {item_id}: will_address requires exact targets")
|
|
1414
|
+
if "author_reason" in record:
|
|
1415
|
+
fail(f"author adjudication {item_id}: will_address must not invent author_reason")
|
|
1416
|
+
else:
|
|
1417
|
+
if not _is_str(record.get("author_reason")):
|
|
1418
|
+
fail(f"author adjudication {item_id}: declined choice requires author_reason")
|
|
1419
|
+
if record["authorized_targets"] or record["claim_strength_authorizations"]:
|
|
1420
|
+
fail(f"author adjudication {item_id}: declined choice carries no authority")
|
|
1421
|
+
if set(by_item) != set(roadmap_ids):
|
|
1422
|
+
fail("complete sidecar must carry exactly one author adjudication per roadmap item")
|
|
1423
|
+
|
|
1424
|
+
collateral = payload["collateral_authorizations"]
|
|
1425
|
+
if not isinstance(collateral, list):
|
|
1426
|
+
fail("collateral_authorizations must be an array")
|
|
1427
|
+
for index, record in enumerate(collateral):
|
|
1428
|
+
required = {
|
|
1429
|
+
"authorization_id",
|
|
1430
|
+
"author_event_id",
|
|
1431
|
+
"authorizing_item_id",
|
|
1432
|
+
"constrained_item_id",
|
|
1433
|
+
"block_id",
|
|
1434
|
+
"operation",
|
|
1435
|
+
"reason",
|
|
1436
|
+
}
|
|
1437
|
+
if not isinstance(record, dict) or set(record) != required:
|
|
1438
|
+
fail(f"collateral_authorizations[{index}] has an invalid closed shape")
|
|
1439
|
+
if record["author_event_id"] not in event_ids:
|
|
1440
|
+
fail(f"collateral_authorizations[{index}].author_event_id does not resolve")
|
|
1441
|
+
if record["authorizing_item_id"] not in by_item or record["constrained_item_id"] not in by_item:
|
|
1442
|
+
fail(f"collateral_authorizations[{index}] item reference does not resolve")
|
|
1443
|
+
if by_item[record["authorizing_item_id"]]["author_triage"] != "will_address":
|
|
1444
|
+
fail(f"collateral_authorizations[{index}] authorizing item is not will_address")
|
|
1445
|
+
if by_item[record["constrained_item_id"]]["author_triage"] not in (
|
|
1446
|
+
"wont_address",
|
|
1447
|
+
"not_on_point",
|
|
1448
|
+
):
|
|
1449
|
+
fail(f"collateral_authorizations[{index}] constrained item is not declined")
|
|
1450
|
+
if not _BLOCK_ID_RE.match(record["block_id"]):
|
|
1451
|
+
fail(f"collateral_authorizations[{index}].block_id is invalid")
|
|
1452
|
+
if record["operation"] not in ("replace_block", "insert_after", "delete_block"):
|
|
1453
|
+
fail(f"collateral_authorizations[{index}].operation is invalid")
|
|
1454
|
+
if not _is_str(record["reason"]):
|
|
1455
|
+
fail(f"collateral_authorizations[{index}].reason must be non-empty")
|
|
1456
|
+
return by_item
|
|
1457
|
+
|
|
1458
|
+
|
|
1459
|
+
# --- synthesis recomputation ---------------------------------------------------
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
class Failures:
|
|
1463
|
+
def __init__(self):
|
|
1464
|
+
self.messages = []
|
|
1465
|
+
|
|
1466
|
+
def add(self, message: str):
|
|
1467
|
+
self.messages.append(message)
|
|
1468
|
+
|
|
1469
|
+
|
|
1470
|
+
def _decision_max(*decisions):
|
|
1471
|
+
return max(decisions, key=lambda d: DECISION_ORDER[d])
|
|
1472
|
+
|
|
1473
|
+
|
|
1474
|
+
def derive_decision(p1_items, p2_partial_magnitudes, p2_made_worse, rate_num, rate_den, regression_severities, escalations):
|
|
1475
|
+
"""§6 Steps 2-3. ``p1_items`` = [(final_verdict, driving_severity, residual_obligation_class)].
|
|
1476
|
+
|
|
1477
|
+
Returns (decision, reject_recommended, base_rule).
|
|
1478
|
+
"""
|
|
1479
|
+
p1_count = len(p1_items)
|
|
1480
|
+
rate_below_80 = rate_den > 0 and rate_num * 5 < rate_den * 4 # < 80%, integer-exact
|
|
1481
|
+
base = None
|
|
1482
|
+
rule = None
|
|
1483
|
+
if any(v == "MADE_WORSE" and sev == "critical" for v, sev, _m in p1_items) or ("critical" in regression_severities):
|
|
1484
|
+
base, rule = "Major Revision", "B1"
|
|
1485
|
+
elif p1_count > 0 and 2 * sum(1 for v, _s, _m in p1_items if v in ("NOT_ADDRESSED", "MADE_WORSE")) >= p1_count:
|
|
1486
|
+
base, rule = "Major Revision", "B2"
|
|
1487
|
+
elif any(v in ("NOT_ADDRESSED", "MADE_WORSE", "CANNOT_VERIFY") for v, _s, _m in p1_items) or ("major" in regression_severities):
|
|
1488
|
+
base, rule = "Major Revision", "B3"
|
|
1489
|
+
elif any(v == "PARTIALLY_ADDRESSED" and m == "must_fix" for v, _s, m in p1_items) or ("must_fix" in p2_partial_magnitudes):
|
|
1490
|
+
base, rule = "Major Revision", "B4"
|
|
1491
|
+
elif (
|
|
1492
|
+
any(v == "PARTIALLY_ADDRESSED" and m in ("should_fix", "consider") for v, _s, m in p1_items)
|
|
1493
|
+
or rate_below_80
|
|
1494
|
+
or p2_made_worse
|
|
1495
|
+
or ("minor" in regression_severities)
|
|
1496
|
+
):
|
|
1497
|
+
base, rule = "Minor Revision", "B5"
|
|
1498
|
+
else:
|
|
1499
|
+
base, rule = "Accept", "B6"
|
|
1500
|
+
decision = base
|
|
1501
|
+
reject = rule in ("B1", "B2")
|
|
1502
|
+
for esc in escalations:
|
|
1503
|
+
if esc["effective_approval_state"] == "approved":
|
|
1504
|
+
decision = _decision_max(decision, esc["mechanical_decision_impact"])
|
|
1505
|
+
if esc["escalation_class"] == "research_integrity":
|
|
1506
|
+
reject = True
|
|
1507
|
+
return decision, reject, rule
|
|
1508
|
+
|
|
1509
|
+
|
|
1510
|
+
def _adjustment_chains(traceability, fails: Failures):
|
|
1511
|
+
"""Build per-item ordered adjustment chains (§5.3 grammar).
|
|
1512
|
+
|
|
1513
|
+
Returns {item_id: [records head..tail]} (empty dict entries omitted);
|
|
1514
|
+
reports every grammar violation into ``fails``.
|
|
1515
|
+
"""
|
|
1516
|
+
by_id = {}
|
|
1517
|
+
for rec in traceability["adjustments"]:
|
|
1518
|
+
if rec["adjustment_id"] in by_id:
|
|
1519
|
+
fails.add(f"adjustment ids must be unique: {rec['adjustment_id']}")
|
|
1520
|
+
return {}
|
|
1521
|
+
by_id[rec["adjustment_id"]] = rec
|
|
1522
|
+
chains = {}
|
|
1523
|
+
by_item = {}
|
|
1524
|
+
for rec in traceability["adjustments"]:
|
|
1525
|
+
by_item.setdefault(rec["item_id"], []).append(rec)
|
|
1526
|
+
for item_id, records in by_item.items():
|
|
1527
|
+
superseded = {}
|
|
1528
|
+
heads = []
|
|
1529
|
+
for rec in records:
|
|
1530
|
+
prev = rec.get("supersedes_adjustment_id")
|
|
1531
|
+
if prev is None:
|
|
1532
|
+
heads.append(rec)
|
|
1533
|
+
else:
|
|
1534
|
+
prev_rec = by_id.get(prev)
|
|
1535
|
+
if prev_rec is None or prev_rec["item_id"] != item_id:
|
|
1536
|
+
fails.add(f"adjustment {rec['adjustment_id']}: supersedes_adjustment_id {prev!r} does not resolve on item {item_id}")
|
|
1537
|
+
return {}
|
|
1538
|
+
if prev in superseded:
|
|
1539
|
+
fails.add(f"adjustment chain on {item_id}: {prev} superseded twice")
|
|
1540
|
+
return {}
|
|
1541
|
+
superseded[prev] = rec
|
|
1542
|
+
if len(heads) != 1:
|
|
1543
|
+
fails.add(f"adjustment chain on {item_id}: exactly one head required, found {len(heads)}")
|
|
1544
|
+
return {}
|
|
1545
|
+
chain = [heads[0]]
|
|
1546
|
+
while chain[-1]["adjustment_id"] in superseded:
|
|
1547
|
+
chain.append(superseded[chain[-1]["adjustment_id"]])
|
|
1548
|
+
if len(chain) != len(records):
|
|
1549
|
+
fails.add(f"adjustment chain on {item_id}: records outside the linear chain (cycle or fork)")
|
|
1550
|
+
return {}
|
|
1551
|
+
for prev_rec, next_rec in zip(chain, chain[1:]):
|
|
1552
|
+
if next_rec["from_verdict"] != prev_rec["to_verdict"]:
|
|
1553
|
+
fails.add(
|
|
1554
|
+
f"adjustment chain on {item_id}: {next_rec['adjustment_id']}.from_verdict "
|
|
1555
|
+
f"!= {prev_rec['adjustment_id']}.to_verdict (head-to-tail join)"
|
|
1556
|
+
)
|
|
1557
|
+
chains[item_id] = chain
|
|
1558
|
+
return chains
|
|
1559
|
+
|
|
1560
|
+
|
|
1561
|
+
def check(
|
|
1562
|
+
manifest,
|
|
1563
|
+
precommitment,
|
|
1564
|
+
verdict_record,
|
|
1565
|
+
traceability,
|
|
1566
|
+
roadmap_items,
|
|
1567
|
+
author_by_item,
|
|
1568
|
+
letter_blocks,
|
|
1569
|
+
witness,
|
|
1570
|
+
witness_notes,
|
|
1571
|
+
):
|
|
1572
|
+
"""All §13 recomputation on schema-valid, hash-bound inputs.
|
|
1573
|
+
|
|
1574
|
+
Returns (failures, warnings, recomputed_outcome) where
|
|
1575
|
+
recomputed_outcome is the Step 1-3 emission expectation.
|
|
1576
|
+
"""
|
|
1577
|
+
fails = Failures()
|
|
1578
|
+
warnings = list(witness_notes)
|
|
1579
|
+
|
|
1580
|
+
roadmap_by_id = {item["id"]: item for item in roadmap_items}
|
|
1581
|
+
must_fix_order = [item["id"] for item in roadmap_items if item["obligation_class"] == "must_fix"]
|
|
1582
|
+
p12_ids = {item["id"] for item in roadmap_items if item["obligation_class"] in ("must_fix", "should_fix")}
|
|
1583
|
+
cross_model_active = manifest["cross_model_active"]
|
|
1584
|
+
|
|
1585
|
+
# round_id equality across all artifacts.
|
|
1586
|
+
for name, art in (("precommitment", precommitment), ("verdict_record", verdict_record), ("traceability", traceability)):
|
|
1587
|
+
if art["round_id"] != manifest["round_id"]:
|
|
1588
|
+
fails.add(f"{name}.round_id != manifest.round_id")
|
|
1589
|
+
if art["contract_version"] != manifest["contract_version"]:
|
|
1590
|
+
fails.add(f"{name}.contract_version != manifest.contract_version")
|
|
1591
|
+
|
|
1592
|
+
# --- Phase 1 coverage + criterion binding (§4/§5.1) ---
|
|
1593
|
+
pre_by_item = {}
|
|
1594
|
+
for rec in precommitment["items"]:
|
|
1595
|
+
if rec["item_id"] in pre_by_item:
|
|
1596
|
+
fails.add(f"precommitment: duplicate record for {rec['item_id']}")
|
|
1597
|
+
pre_by_item[rec["item_id"]] = rec
|
|
1598
|
+
if set(pre_by_item) != p12_ids:
|
|
1599
|
+
fails.add(
|
|
1600
|
+
"precommitment coverage: records must exist for exactly the must_fix/should_fix roadmap items "
|
|
1601
|
+
f"(missing {sorted(p12_ids - set(pre_by_item))}, extra {sorted(set(pre_by_item) - p12_ids)})"
|
|
1602
|
+
)
|
|
1603
|
+
letter_present = manifest["artifacts"]["editorial_decision_letter"]["present"]
|
|
1604
|
+
letter_sound = letter_blocks is not None and letter_ordinals_sound(letter_blocks, len(must_fix_order))
|
|
1605
|
+
if letter_present and letter_blocks is not None and not letter_sound:
|
|
1606
|
+
warnings.append("[CRITERIA-LAYER-ABSENT: letter/roadmap ordinal mismatch]")
|
|
1607
|
+
if letter_present and letter_sound and not letter_blocks:
|
|
1608
|
+
warnings.append(
|
|
1609
|
+
"NOTE: editorial decision letter present but no Required Item Details blocks parsed — the level-2 "
|
|
1610
|
+
"criteria layer is empty (template drift or a genuinely block-less letter)"
|
|
1611
|
+
)
|
|
1612
|
+
block_by_rid = {}
|
|
1613
|
+
if letter_sound and letter_blocks:
|
|
1614
|
+
block_by_rid = {rid: text for rid, text in letter_blocks}
|
|
1615
|
+
for item_id, rec in pre_by_item.items():
|
|
1616
|
+
item = roadmap_by_id.get(item_id)
|
|
1617
|
+
if item is None:
|
|
1618
|
+
continue
|
|
1619
|
+
if rec["obligation_class"] != item["obligation_class"]:
|
|
1620
|
+
fails.add(f"precommitment {item_id}: obligation_class {rec['obligation_class']} != roadmap {item['obligation_class']}")
|
|
1621
|
+
if rec["inherited_criterion"]["roadmap_text"] != item["verification_criteria"]:
|
|
1622
|
+
fails.add(f"precommitment {item_id}: inherited_criterion.roadmap_text does not match the roadmap verbatim (§4)")
|
|
1623
|
+
if rec["source_reviewer"] != item["reviewer"]:
|
|
1624
|
+
fails.add(
|
|
1625
|
+
f"precommitment {item_id}: source_reviewer is a VERBATIM copy of the Schema 7 reviewer field "
|
|
1626
|
+
f"({rec['source_reviewer']!r} != {item['reviewer']!r}, §5.1)"
|
|
1627
|
+
)
|
|
1628
|
+
labels, _parse_failure = normalize_reviewer_labels(rec["source_reviewer"])
|
|
1629
|
+
if rec["source_reviewer_labels"] != labels:
|
|
1630
|
+
fails.add(
|
|
1631
|
+
f"precommitment {item_id}: source_reviewer_labels {rec['source_reviewer_labels']!r} "
|
|
1632
|
+
f"!= §10 normalization of source_reviewer ({labels!r})"
|
|
1633
|
+
)
|
|
1634
|
+
has_letter_fields = "letter_text" in rec["inherited_criterion"]
|
|
1635
|
+
if item["obligation_class"] == "must_fix":
|
|
1636
|
+
ordinal = must_fix_order.index(item_id) + 1
|
|
1637
|
+
derived_ref = f"R{ordinal}"
|
|
1638
|
+
block_text = block_by_rid.get(derived_ref) if letter_present and letter_sound else None
|
|
1639
|
+
if block_text is not None:
|
|
1640
|
+
if not has_letter_fields:
|
|
1641
|
+
fails.add(f"precommitment {item_id}: letter carries block {derived_ref} — letter_text/letter_item_ref REQUIRED (§5.1)")
|
|
1642
|
+
else:
|
|
1643
|
+
if rec["inherited_criterion"]["letter_item_ref"] != derived_ref:
|
|
1644
|
+
fails.add(
|
|
1645
|
+
f"precommitment {item_id}: letter_item_ref {rec['inherited_criterion']['letter_item_ref']!r} "
|
|
1646
|
+
f"!= derived ordinal {derived_ref!r} (§5.1 — the ref is DERIVED, never chosen)"
|
|
1647
|
+
)
|
|
1648
|
+
if rec["inherited_criterion"]["letter_text"] != block_text:
|
|
1649
|
+
fails.add(f"precommitment {item_id}: letter_text does not match the letter's Acceptance criteria byte-for-byte (§13)")
|
|
1650
|
+
elif has_letter_fields:
|
|
1651
|
+
fails.add(f"precommitment {item_id}: letter layer absent at this item — letter fields must be absent (§5.1/§13)")
|
|
1652
|
+
elif has_letter_fields:
|
|
1653
|
+
fails.add(f"precommitment {item_id}: letter fields on a non-must_fix item (§5.1)")
|
|
1654
|
+
|
|
1655
|
+
# --- Phase 2A coverage + applied_criterion (§5.2) ---
|
|
1656
|
+
verdict_by_item = {}
|
|
1657
|
+
for rec in verdict_record["items"]:
|
|
1658
|
+
if rec["item_id"] in verdict_by_item:
|
|
1659
|
+
fails.add(f"verdict_record: duplicate record for {rec['item_id']}")
|
|
1660
|
+
verdict_by_item[rec["item_id"]] = rec
|
|
1661
|
+
if set(verdict_by_item) != set(roadmap_by_id):
|
|
1662
|
+
fails.add(
|
|
1663
|
+
"verdict_record coverage: exactly one record per roadmap item, ALL priorities "
|
|
1664
|
+
f"(missing {sorted(set(roadmap_by_id) - set(verdict_by_item))}, extra {sorted(set(verdict_by_item) - set(roadmap_by_id))})"
|
|
1665
|
+
)
|
|
1666
|
+
dissent_by_id = {}
|
|
1667
|
+
for rec in verdict_record["dissents"]:
|
|
1668
|
+
if rec["dissent_id"] in dissent_by_id:
|
|
1669
|
+
fails.add(f"verdict_record: duplicate dissent_id {rec['dissent_id']}")
|
|
1670
|
+
dissent_by_id[rec["dissent_id"]] = rec
|
|
1671
|
+
pre = pre_by_item.get(rec["item_id"])
|
|
1672
|
+
if pre is None:
|
|
1673
|
+
fails.add(f"dissent {rec['dissent_id']}: item {rec['item_id']} has no pre-commitment record")
|
|
1674
|
+
elif rec["criterion_hash"] != canonical_hash(pre):
|
|
1675
|
+
fails.add(f"dissent {rec['dissent_id']}: criterion_hash does not recompute from the Phase-1 record (§7)")
|
|
1676
|
+
# Reverse witness (§7): a dissent swaps the criterion BEFORE the
|
|
1677
|
+
# verdict — "The item's verdict record then carries
|
|
1678
|
+
# applied_criterion: dissented:<dissent_id>". A ghost dissent that
|
|
1679
|
+
# was never applied cannot exist, trip the §7 bound, or authorize
|
|
1680
|
+
# an original_upheld re-application of a criterion that was in
|
|
1681
|
+
# fact applied at 2A.
|
|
1682
|
+
applied = verdict_by_item.get(rec["item_id"], {}).get("applied_criterion")
|
|
1683
|
+
if applied != f"dissented:{rec['dissent_id']}":
|
|
1684
|
+
fails.add(
|
|
1685
|
+
f"dissent {rec['dissent_id']}: the item's verdict record must carry "
|
|
1686
|
+
f"applied_criterion dissented:{rec['dissent_id']} — a dissent that was never applied is a ghost (§7)"
|
|
1687
|
+
)
|
|
1688
|
+
# §7 bounds (SD-5): dissent on a must_fix item, or dissents on > ceil(N/3) of
|
|
1689
|
+
# all items, trips independent adjudication of EVERY dissent record.
|
|
1690
|
+
total_items = len(roadmap_items)
|
|
1691
|
+
bound_tripped = any(
|
|
1692
|
+
roadmap_by_id.get(dissent["item_id"], {}).get("obligation_class") == "must_fix"
|
|
1693
|
+
for dissent in dissent_by_id.values()
|
|
1694
|
+
) or (len(dissent_by_id) > math.ceil(total_items / 3))
|
|
1695
|
+
for item_id, rec in verdict_by_item.items():
|
|
1696
|
+
item = roadmap_by_id.get(item_id)
|
|
1697
|
+
if item is None:
|
|
1698
|
+
continue
|
|
1699
|
+
applied = rec["applied_criterion"]
|
|
1700
|
+
if item["obligation_class"] == "consider":
|
|
1701
|
+
if applied != "not_precommitted":
|
|
1702
|
+
fails.add(f"verdict {item_id}: consider items carry applied_criterion not_precommitted (§5.2)")
|
|
1703
|
+
else:
|
|
1704
|
+
if applied == "not_precommitted":
|
|
1705
|
+
fails.add(f"verdict {item_id}: not_precommitted is valid ONLY for consider items (§5.2)")
|
|
1706
|
+
elif applied.startswith("dissented:"):
|
|
1707
|
+
dissent = dissent_by_id.get(applied.split(":", 1)[1])
|
|
1708
|
+
if dissent is None or dissent["item_id"] != item_id:
|
|
1709
|
+
fails.add(f"verdict {item_id}: applied_criterion {applied!r} does not resolve to a dissent on this item")
|
|
1710
|
+
|
|
1711
|
+
ns_by_id = {rec["new_standard_id"]: rec for rec in precommitment["new_standards"]}
|
|
1712
|
+
exceptions_by_id = {}
|
|
1713
|
+
for rec in verdict_record["escalation_exceptions"]:
|
|
1714
|
+
if rec["exception_id"] in exceptions_by_id:
|
|
1715
|
+
fails.add(f"verdict_record: duplicate exception_id {rec['exception_id']}")
|
|
1716
|
+
exceptions_by_id[rec["exception_id"]] = rec
|
|
1717
|
+
ref = rec.get("new_standard_ref")
|
|
1718
|
+
if ref is not None:
|
|
1719
|
+
target = ns_by_id.get(ref)
|
|
1720
|
+
if target is None:
|
|
1721
|
+
fails.add(f"escalation exception {rec['exception_id']}: new_standard_ref {ref!r} does not resolve")
|
|
1722
|
+
elif target["classification"] != "escalation_requested":
|
|
1723
|
+
fails.add(f"escalation exception {rec['exception_id']}: new_standard_ref target is not escalation_requested (§5.1)")
|
|
1724
|
+
|
|
1725
|
+
# --- new-issue freeze witness (§13.5) ---
|
|
1726
|
+
if traceability["new_issues"] != verdict_record["new_issues"]:
|
|
1727
|
+
fails.add("new_issues: §5.3 set is not WHOLE-RECORD byte-identical to the frozen §5.2 set (§3.3 freeze witness)")
|
|
1728
|
+
new_issue_by_id = {}
|
|
1729
|
+
for rec in verdict_record["new_issues"]:
|
|
1730
|
+
if rec["new_issue_id"] in new_issue_by_id:
|
|
1731
|
+
fails.add(f"verdict_record: duplicate new_issue_id {rec['new_issue_id']}")
|
|
1732
|
+
new_issue_by_id[rec["new_issue_id"]] = rec
|
|
1733
|
+
nearest = rec["nearest_roadmap_item"]
|
|
1734
|
+
if nearest is not None and nearest not in roadmap_by_id:
|
|
1735
|
+
fails.add(f"new issue {rec['new_issue_id']}: nearest_roadmap_item {nearest!r} does not resolve")
|
|
1736
|
+
if rec["attribution"] == "regression" and nearest is not None:
|
|
1737
|
+
warnings.append(
|
|
1738
|
+
f"ADVISORY: regression {rec['new_issue_id']} names nearest_roadmap_item {nearest} — "
|
|
1739
|
+
"review for roadmap-item overlap (§8)"
|
|
1740
|
+
)
|
|
1741
|
+
# --- rows (§5.3) ---
|
|
1742
|
+
row_by_item = {}
|
|
1743
|
+
for row in traceability["rows"]:
|
|
1744
|
+
if row["item_id"] in row_by_item:
|
|
1745
|
+
fails.add(f"traceability: duplicate row for {row['item_id']}")
|
|
1746
|
+
row_by_item[row["item_id"]] = row
|
|
1747
|
+
if set(row_by_item) != set(roadmap_by_id):
|
|
1748
|
+
fails.add(
|
|
1749
|
+
"row coverage: EVERY roadmap item, all priorities, has exactly one row "
|
|
1750
|
+
f"(missing {sorted(set(roadmap_by_id) - set(row_by_item))}, extra {sorted(set(row_by_item) - set(roadmap_by_id))})"
|
|
1751
|
+
)
|
|
1752
|
+
for item_id, row in row_by_item.items():
|
|
1753
|
+
item = roadmap_by_id.get(item_id)
|
|
1754
|
+
if item is None:
|
|
1755
|
+
continue
|
|
1756
|
+
if ROW_OBLIGATION_MAP[row["obligation_class"]] != item["obligation_class"]:
|
|
1757
|
+
fails.add(f"row {item_id}: obligation_class {row['obligation_class']} does not match the roadmap ({item['obligation_class']})")
|
|
1758
|
+
author = author_by_item.get(item_id)
|
|
1759
|
+
if author is None:
|
|
1760
|
+
fails.add(f"row {item_id}: no hash-bound author adjudication exists")
|
|
1761
|
+
else:
|
|
1762
|
+
for field in (
|
|
1763
|
+
"author_triage",
|
|
1764
|
+
"authorized_targets",
|
|
1765
|
+
"claim_strength_authorizations",
|
|
1766
|
+
):
|
|
1767
|
+
if row[field] != author[field]:
|
|
1768
|
+
fails.add(
|
|
1769
|
+
f"row {item_id}: {field} is not an exact copy of the hash-bound author adjudication"
|
|
1770
|
+
)
|
|
1771
|
+
row_has_reason = "author_reason" in row
|
|
1772
|
+
author_has_reason = "author_reason" in author
|
|
1773
|
+
if row_has_reason != author_has_reason or (
|
|
1774
|
+
row_has_reason and row["author_reason"] != author["author_reason"]
|
|
1775
|
+
):
|
|
1776
|
+
fails.add(
|
|
1777
|
+
f"row {item_id}: author_reason presence/value is not an exact author-adjudication copy"
|
|
1778
|
+
)
|
|
1779
|
+
vrec = verdict_by_item.get(item_id)
|
|
1780
|
+
if vrec is not None:
|
|
1781
|
+
if row["phase2a_verdict"] != vrec["verdict"]:
|
|
1782
|
+
fails.add(f"row {item_id}: phase2a_verdict != committed §5.2 verdict")
|
|
1783
|
+
if row["verified_by"] != vrec["verified_by"]:
|
|
1784
|
+
fails.add(f"row {item_id}: verified_by not copied from the §5.2 verdict record")
|
|
1785
|
+
if row["status"] != row["final_verdict"]:
|
|
1786
|
+
fails.add(f"row {item_id}: status must map 1:1 from final_verdict (§5.3)")
|
|
1787
|
+
if row["verified"] != VERIFIED_MAP[row["final_verdict"]]:
|
|
1788
|
+
fails.add(f"row {item_id}: verified must follow the mechanical 5->4 map (§5.3)")
|
|
1789
|
+
if "cross_model_verdict" in row:
|
|
1790
|
+
derived = "agree" if row["cross_model_verdict"] == row["final_verdict"] else "diverges"
|
|
1791
|
+
if row["cross_model_status"] != derived:
|
|
1792
|
+
fails.add(f"row {item_id}: cross_model_status {row['cross_model_status']!r} != per-emission derivation {derived!r} (§5.3)")
|
|
1793
|
+
|
|
1794
|
+
# --- adjustment chains (§5.3) ---
|
|
1795
|
+
chains = _adjustment_chains(traceability, fails)
|
|
1796
|
+
for item_id, row in row_by_item.items():
|
|
1797
|
+
chain = chains.get(item_id)
|
|
1798
|
+
if chain:
|
|
1799
|
+
if row.get("adjustment_id") != chain[-1]["adjustment_id"]:
|
|
1800
|
+
fails.add(f"row {item_id}: adjustment_id must name the chain's LATEST record")
|
|
1801
|
+
if chain[0]["from_verdict"] != row["phase2a_verdict"]:
|
|
1802
|
+
fails.add(f"adjustment chain on {item_id}: head from_verdict != phase2a_verdict")
|
|
1803
|
+
if chain[-1]["to_verdict"] != row["final_verdict"]:
|
|
1804
|
+
fails.add(f"adjustment chain on {item_id}: tail to_verdict != final_verdict")
|
|
1805
|
+
else:
|
|
1806
|
+
if "adjustment_id" in row:
|
|
1807
|
+
fails.add(f"row {item_id}: adjustment_id present with no adjustment chain")
|
|
1808
|
+
tail_rebuttal = bool(chain) and chain[-1]["basis"] == "valid_rebuttal" and chain[-1]["to_verdict"] == "FULLY_ADDRESSED"
|
|
1809
|
+
if bool(row.get("addressed_by_rebuttal")) != tail_rebuttal:
|
|
1810
|
+
fails.add(f"row {item_id}: addressed_by_rebuttal marker must reflect a valid_rebuttal chain-tail upgrade to FULLY_ADDRESSED (§3.4)")
|
|
1811
|
+
for item_id in chains:
|
|
1812
|
+
if item_id not in row_by_item:
|
|
1813
|
+
fails.add(f"adjustment chain on {item_id}: no matrix row exists")
|
|
1814
|
+
|
|
1815
|
+
# --- deferral-loop referential integrity (§5.3/§6) ---
|
|
1816
|
+
intents_by_id = {}
|
|
1817
|
+
for rec in traceability["resolution_intents"]:
|
|
1818
|
+
if rec["intent_id"] in intents_by_id:
|
|
1819
|
+
fails.add(f"duplicate intent_id {rec['intent_id']}")
|
|
1820
|
+
intents_by_id[rec["intent_id"]] = rec
|
|
1821
|
+
if rec["answered_by"] == "system":
|
|
1822
|
+
# §5.3: system intents are emitted for every must_fix diverges row —
|
|
1823
|
+
# the row must be an evaluated must_fix row (cross_model_verdict
|
|
1824
|
+
# present implies an active configuration).
|
|
1825
|
+
intent_row = row_by_item.get(rec["item_id"])
|
|
1826
|
+
if intent_row is None or intent_row["obligation_class"] != "MUST_FIX" or "cross_model_verdict" not in intent_row:
|
|
1827
|
+
fails.add(
|
|
1828
|
+
f"resolution intent {rec['intent_id']}: system intents exist only for evaluated must_fix diverges "
|
|
1829
|
+
"rows under ACTIVE cross-model (§5.3)"
|
|
1830
|
+
)
|
|
1831
|
+
adjudications_by_dissent = {}
|
|
1832
|
+
for rec in traceability["dissent_adjudications"]:
|
|
1833
|
+
if rec["dissent_id"] in adjudications_by_dissent:
|
|
1834
|
+
fails.add(f"duplicate dissent adjudication for {rec['dissent_id']}")
|
|
1835
|
+
adjudications_by_dissent[rec["dissent_id"]] = rec
|
|
1836
|
+
if rec["dissent_id"] not in dissent_by_id:
|
|
1837
|
+
fails.add(f"dissent adjudication {rec['dissent_id']}: no such dissent record")
|
|
1838
|
+
if rec["adjudicator"] == "cross_model" and not cross_model_active:
|
|
1839
|
+
fails.add(f"dissent adjudication {rec['dissent_id']}: cross_model adjudicator on an inactive configuration (§11)")
|
|
1840
|
+
if not bound_tripped:
|
|
1841
|
+
fails.add(
|
|
1842
|
+
f"dissent adjudication {rec['dissent_id']}: no §7 bound tripped — dissents below the bound stand "
|
|
1843
|
+
"unadjudicated by design (§7)"
|
|
1844
|
+
)
|
|
1845
|
+
dissent = dissent_by_id.get(rec["dissent_id"])
|
|
1846
|
+
dissent_item = roadmap_by_id.get(dissent["item_id"]) if dissent else None
|
|
1847
|
+
if rec["adjudicator"] == "cross_model":
|
|
1848
|
+
if dissent_item is None or dissent_item["obligation_class"] != "must_fix":
|
|
1849
|
+
fails.add(
|
|
1850
|
+
f"dissent adjudication {rec['dissent_id']}: the judge's adjudication scope EQUALS the §9 pass's "
|
|
1851
|
+
"must_fix coverage — dissents on non-must_fix items always take the G2(a) user path, even on an active "
|
|
1852
|
+
"setup (§7)"
|
|
1853
|
+
)
|
|
1854
|
+
# A `user` adjudication on an active-setup must_fix dissent stays legal:
|
|
1855
|
+
# the §6 deferral loop records "a user-adjudicated DissentAdjudication
|
|
1856
|
+
# ... DIRECTLY" without an activity qualifier, and the §9 pass can be
|
|
1857
|
+
# per-row unavailable — rejecting the user path would make a
|
|
1858
|
+
# judge-transport failure unrecoverable (adjudicated against the
|
|
1859
|
+
# stricter one-way reading; see PR #605 round-4/5 record).
|
|
1860
|
+
reapp_by_id = {}
|
|
1861
|
+
for rec in traceability["reapplications"]:
|
|
1862
|
+
if rec["reapplication_id"] in reapp_by_id:
|
|
1863
|
+
fails.add(f"duplicate reapplication_id {rec['reapplication_id']}")
|
|
1864
|
+
reapp_by_id[rec["reapplication_id"]] = rec
|
|
1865
|
+
superseded_reapps = {}
|
|
1866
|
+
for rec in traceability["reapplications"]:
|
|
1867
|
+
prev = rec.get("supersedes_reapplication_id")
|
|
1868
|
+
if prev is not None:
|
|
1869
|
+
prev_rec = reapp_by_id.get(prev)
|
|
1870
|
+
if prev_rec is None or prev_rec["item_id"] != rec["item_id"]:
|
|
1871
|
+
fails.add(f"reapplication {rec['reapplication_id']}: supersedes_reapplication_id {prev!r} does not resolve on the same item")
|
|
1872
|
+
elif prev in superseded_reapps:
|
|
1873
|
+
fails.add(f"reapplication supersession: {prev} superseded twice")
|
|
1874
|
+
else:
|
|
1875
|
+
superseded_reapps[prev] = rec["reapplication_id"]
|
|
1876
|
+
# acyclicity of supersession
|
|
1877
|
+
for rec in traceability["reapplications"]:
|
|
1878
|
+
seen_ids = set()
|
|
1879
|
+
cursor = rec
|
|
1880
|
+
while cursor is not None:
|
|
1881
|
+
if cursor["reapplication_id"] in seen_ids:
|
|
1882
|
+
fails.add(f"reapplication supersession cycle at {cursor['reapplication_id']}")
|
|
1883
|
+
break
|
|
1884
|
+
seen_ids.add(cursor["reapplication_id"])
|
|
1885
|
+
prev = cursor.get("supersedes_reapplication_id")
|
|
1886
|
+
cursor = reapp_by_id.get(prev) if prev is not None else None
|
|
1887
|
+
current_reapps = [rec for rec in traceability["reapplications"] if rec["reapplication_id"] not in superseded_reapps]
|
|
1888
|
+
|
|
1889
|
+
def _chain_tail_verdict(item_id):
|
|
1890
|
+
chain = chains.get(item_id)
|
|
1891
|
+
if chain:
|
|
1892
|
+
return chain[-1]["to_verdict"]
|
|
1893
|
+
row = row_by_item.get(item_id)
|
|
1894
|
+
return row["phase2a_verdict"] if row else None
|
|
1895
|
+
|
|
1896
|
+
for rec in traceability["reapplications"]:
|
|
1897
|
+
item_id = rec["item_id"]
|
|
1898
|
+
has_adjudication_ref = False
|
|
1899
|
+
has_intent_ref = False
|
|
1900
|
+
for ref in rec["answer_refs"]:
|
|
1901
|
+
kind, ref_id = ref.split(":", 1)
|
|
1902
|
+
if kind == "intent":
|
|
1903
|
+
has_intent_ref = True
|
|
1904
|
+
if kind == "adjudication":
|
|
1905
|
+
has_adjudication_ref = True
|
|
1906
|
+
adjudication = adjudications_by_dissent.get(ref_id)
|
|
1907
|
+
dissent = dissent_by_id.get(ref_id)
|
|
1908
|
+
if adjudication is None or dissent is None:
|
|
1909
|
+
fails.add(f"reapplication {rec['reapplication_id']}: answer ref {ref!r} does not resolve")
|
|
1910
|
+
continue
|
|
1911
|
+
if adjudication["outcome"] != "original_upheld":
|
|
1912
|
+
fails.add(f"reapplication {rec['reapplication_id']}: adjudication ref {ref!r} must resolve to original_upheld (§6)")
|
|
1913
|
+
if dissent["item_id"] != item_id:
|
|
1914
|
+
fails.add(f"reapplication {rec['reapplication_id']}: adjudication ref {ref!r} is on a different item")
|
|
1915
|
+
else:
|
|
1916
|
+
intent = intents_by_id.get(ref_id)
|
|
1917
|
+
if intent is None:
|
|
1918
|
+
fails.add(f"reapplication {rec['reapplication_id']}: answer ref {ref!r} does not resolve")
|
|
1919
|
+
elif intent["item_id"] != item_id:
|
|
1920
|
+
fails.add(f"reapplication {rec['reapplication_id']}: intent ref {ref!r} is on a different item")
|
|
1921
|
+
# effective-criterion rule (§6)
|
|
1922
|
+
replacement_dissents = [
|
|
1923
|
+
dissent_id for dissent_id, adjudication in adjudications_by_dissent.items()
|
|
1924
|
+
if adjudication["outcome"] == "replacement_approved"
|
|
1925
|
+
and dissent_by_id.get(dissent_id, {}).get("item_id") == item_id
|
|
1926
|
+
]
|
|
1927
|
+
if has_adjudication_ref:
|
|
1928
|
+
expected_ref = f"phase1:{item_id}"
|
|
1929
|
+
elif replacement_dissents:
|
|
1930
|
+
expected_ref = f"dissent:{replacement_dissents[0]}"
|
|
1931
|
+
else:
|
|
1932
|
+
expected_ref = f"phase1:{item_id}"
|
|
1933
|
+
if rec["criterion_ref"] != expected_ref:
|
|
1934
|
+
fails.add(
|
|
1935
|
+
f"reapplication {rec['reapplication_id']}: criterion_ref {rec['criterion_ref']!r} "
|
|
1936
|
+
f"violates the §6 effective-criterion rule (expected {expected_ref!r})"
|
|
1937
|
+
)
|
|
1938
|
+
# derived adjustment (§6 loop step 2)
|
|
1939
|
+
derived = [
|
|
1940
|
+
adj for adj in traceability["adjustments"]
|
|
1941
|
+
if adj.get("source_ref") == f"reapplication:{rec['reapplication_id']}"
|
|
1942
|
+
]
|
|
1943
|
+
# §6 trigger binding: a reapplication whose answer_refs carry ONLY
|
|
1944
|
+
# intent refs is a DIVERGENCE re-verification — it exists only for
|
|
1945
|
+
# an evaluated must_fix row (one carrying cross_model_verdict, which
|
|
1946
|
+
# itself implies an active configuration): "diverges rows exist
|
|
1947
|
+
# only under ACTIVE cross-model (a not_configured run has none)".
|
|
1948
|
+
# A G2(d) retry is unaffected — its refs are CUMULATIVE and always
|
|
1949
|
+
# include the mandating adjudication.
|
|
1950
|
+
if has_intent_ref and not has_adjudication_ref:
|
|
1951
|
+
row = row_by_item.get(item_id)
|
|
1952
|
+
if row is None or row["obligation_class"] != "MUST_FIX" or "cross_model_verdict" not in row:
|
|
1953
|
+
fails.add(
|
|
1954
|
+
f"reapplication {rec['reapplication_id']}: a divergence-only re-application exists only for an "
|
|
1955
|
+
"evaluated must_fix row under active cross-model (§6/§5.3) — no committed verdict moves without its "
|
|
1956
|
+
"triggering divergence"
|
|
1957
|
+
)
|
|
1958
|
+
elif row["cross_model_verdict"] == rec["pre_reapplication_verdict"]:
|
|
1959
|
+
# §6 dispatch order is normative: diverges is identified
|
|
1960
|
+
# FIRST (judge verdict vs the row's verdict), THEN the
|
|
1961
|
+
# system intent is emitted and the 2B' call dispatched. A
|
|
1962
|
+
# judge verdict equal to the dispatch-time pre-value means
|
|
1963
|
+
# no divergence existed to resolve — the chain manufactures
|
|
1964
|
+
# its own diverges status after the fact.
|
|
1965
|
+
fails.add(
|
|
1966
|
+
f"reapplication {rec['reapplication_id']}: no divergence existed at dispatch — the judge's "
|
|
1967
|
+
"verdict equals the dispatch-time pre_reapplication_verdict (§6)"
|
|
1968
|
+
)
|
|
1969
|
+
if not any(
|
|
1970
|
+
intents_by_id.get(ref.split(":", 1)[1], {}).get("answered_by") == "system"
|
|
1971
|
+
for ref in rec["answer_refs"]
|
|
1972
|
+
if ref.startswith("intent:")
|
|
1973
|
+
):
|
|
1974
|
+
fails.add(
|
|
1975
|
+
f"reapplication {rec['reapplication_id']}: a divergence re-application chain carries the "
|
|
1976
|
+
"ORIGINAL mandating system intent (§6 — divergence resolution is MECHANICAL by default; user "
|
|
1977
|
+
"intents exist only on the retry paths, cumulatively alongside it)"
|
|
1978
|
+
)
|
|
1979
|
+
# §5.3 letter-tag condition: a letter-tagged anchor on a
|
|
1980
|
+
# reapplication (and hence on its mechanically-copied
|
|
1981
|
+
# cross_model_adjudication adjustment) is valid EXACTLY when the
|
|
1982
|
+
# re-examined chain projection carried a booked valid_rebuttal
|
|
1983
|
+
# record — the §3.4 machine witness that an assertion in the letter
|
|
1984
|
+
# with no locatable manuscript evidence changes nothing. The 2B'
|
|
1985
|
+
# input set withholds the Response Letter itself, so a booked
|
|
1986
|
+
# rebuttal anchor is the only legal letter-side source.
|
|
1987
|
+
if rec["reapplied_verdict"] != "CANNOT_VERIFY" and any(
|
|
1988
|
+
anchor["anchor_artifact"] == "letter" for anchor in rec["evidence_anchor"]
|
|
1989
|
+
):
|
|
1990
|
+
letter_tag_allowed = False
|
|
1991
|
+
for chain_adj in chains.get(item_id, []):
|
|
1992
|
+
if len(derived) == 1 and chain_adj["adjustment_id"] == derived[0]["adjustment_id"]:
|
|
1993
|
+
break
|
|
1994
|
+
if chain_adj["basis"] == "valid_rebuttal":
|
|
1995
|
+
letter_tag_allowed = True
|
|
1996
|
+
break
|
|
1997
|
+
if not letter_tag_allowed:
|
|
1998
|
+
fails.add(
|
|
1999
|
+
f"reapplication {rec['reapplication_id']}: letter-tagged anchors are valid exactly when the "
|
|
2000
|
+
"re-examined chain carries a booked valid_rebuttal record (§5.3/§3.4)"
|
|
2001
|
+
)
|
|
2002
|
+
changing = rec["reapplied_verdict"] != "CANNOT_VERIFY" and rec["reapplied_verdict"] != rec["pre_reapplication_verdict"]
|
|
2003
|
+
if rec["reapplied_verdict"] == "CANNOT_VERIFY":
|
|
2004
|
+
if derived:
|
|
2005
|
+
fails.add(f"reapplication {rec['reapplication_id']}: CANNOT_VERIFY appends NOTHING mechanically (§6)")
|
|
2006
|
+
elif changing:
|
|
2007
|
+
if len(derived) != 1:
|
|
2008
|
+
fails.add(f"reapplication {rec['reapplication_id']}: a verdict-changing reapplication requires exactly one derived adjustment (§13)")
|
|
2009
|
+
else:
|
|
2010
|
+
adj = derived[0]
|
|
2011
|
+
if adj["item_id"] != item_id or adj["basis"] != "cross_model_adjudication":
|
|
2012
|
+
fails.add(f"reapplication {rec['reapplication_id']}: derived adjustment must be a cross_model_adjudication on the same item")
|
|
2013
|
+
if adj["from_verdict"] != rec["pre_reapplication_verdict"] or adj["to_verdict"] != rec["reapplied_verdict"]:
|
|
2014
|
+
fails.add(f"reapplication {rec['reapplication_id']}: derived adjustment endpoints must bind to the RECORDED pre-value (§6)")
|
|
2015
|
+
if adj["rationale"] != rec["rationale"]:
|
|
2016
|
+
fails.add(f"reapplication {rec['reapplication_id']}: derived adjustment rationale must be MECHANICALLY COPIED (§6)")
|
|
2017
|
+
if adj.get("residual_gap") != rec.get("residual_gap"):
|
|
2018
|
+
fails.add(f"reapplication {rec['reapplication_id']}: derived adjustment residual_gap must be MECHANICALLY COPIED (§6)")
|
|
2019
|
+
if adj.get("evidence_anchor") != rec.get("evidence_anchor"):
|
|
2020
|
+
fails.add(f"reapplication {rec['reapplication_id']}: derived adjustment anchors must be MECHANICALLY COPIED, tags included (§5.3)")
|
|
2021
|
+
else:
|
|
2022
|
+
if derived:
|
|
2023
|
+
fails.add(f"reapplication {rec['reapplication_id']}: no verdict change, so no derived adjustment may exist (§6)")
|
|
2024
|
+
|
|
2025
|
+
# every original_upheld adjudication + every intent appears in exactly one CURRENT record's answer_refs
|
|
2026
|
+
def _answers_in_current(ref: str):
|
|
2027
|
+
return [rec for rec in current_reapps if ref in rec["answer_refs"]]
|
|
2028
|
+
|
|
2029
|
+
for dissent_id, adjudication in adjudications_by_dissent.items():
|
|
2030
|
+
if adjudication["outcome"] == "original_upheld":
|
|
2031
|
+
holders = _answers_in_current(f"adjudication:{dissent_id}")
|
|
2032
|
+
if len(holders) != 1:
|
|
2033
|
+
fails.add(
|
|
2034
|
+
f"dissent adjudication {dissent_id}: original_upheld must appear in exactly one CURRENT reapplication's "
|
|
2035
|
+
f"answer_refs (found {len(holders)}) — silent non-reapplication cannot pass (§6)"
|
|
2036
|
+
)
|
|
2037
|
+
for intent_id in intents_by_id:
|
|
2038
|
+
holders = _answers_in_current(f"intent:{intent_id}")
|
|
2039
|
+
if len(holders) != 1:
|
|
2040
|
+
fails.add(
|
|
2041
|
+
f"resolution intent {intent_id}: must appear in exactly one CURRENT reapplication's answer_refs "
|
|
2042
|
+
f"(found {len(holders)}) (§6)"
|
|
2043
|
+
)
|
|
2044
|
+
|
|
2045
|
+
# --- cross-model resolutions (§5.3/§9) ---
|
|
2046
|
+
resolutions_by_reapp = {}
|
|
2047
|
+
for rec in traceability["cross_model_resolutions"]:
|
|
2048
|
+
if rec["reapplication_id"] in resolutions_by_reapp:
|
|
2049
|
+
fails.add(f"reapplication {rec['reapplication_id']}: referenced by more than one CrossModelResolution")
|
|
2050
|
+
resolutions_by_reapp[rec["reapplication_id"]] = rec
|
|
2051
|
+
# §6 loop step 2: when an intent-mandated re-application derives a state
|
|
2052
|
+
# (reapplied_verdict != CANNOT_VERIFY) the resolution is created
|
|
2053
|
+
# MECHANICALLY — its absence means the pipeline skipped a mechanical step.
|
|
2054
|
+
for rec in current_reapps:
|
|
2055
|
+
has_intent_ref = any(ref.startswith("intent:") for ref in rec["answer_refs"])
|
|
2056
|
+
if has_intent_ref and rec["reapplied_verdict"] != "CANNOT_VERIFY":
|
|
2057
|
+
resolution = resolutions_by_reapp.get(rec["reapplication_id"])
|
|
2058
|
+
if resolution is None or resolution["resolved_by"] != "system":
|
|
2059
|
+
fails.add(
|
|
2060
|
+
f"reapplication {rec['reapplication_id']}: a derived state mandates a mechanically-created "
|
|
2061
|
+
"system CrossModelResolution (§6 loop step 2)"
|
|
2062
|
+
)
|
|
2063
|
+
for rec in traceability["cross_model_resolutions"]:
|
|
2064
|
+
intent = intents_by_id.get(rec["intent_id"])
|
|
2065
|
+
reapp = reapp_by_id.get(rec["reapplication_id"])
|
|
2066
|
+
if intent is None or intent["item_id"] != rec["item_id"]:
|
|
2067
|
+
fails.add(f"resolution {rec['resolution_id']}: intent_id does not resolve on the same item")
|
|
2068
|
+
if reapp is None or reapp["item_id"] != rec["item_id"]:
|
|
2069
|
+
fails.add(f"resolution {rec['resolution_id']}: reapplication_id does not resolve on the same item")
|
|
2070
|
+
continue
|
|
2071
|
+
if f"intent:{rec['intent_id']}" not in reapp["answer_refs"]:
|
|
2072
|
+
fails.add(
|
|
2073
|
+
f"resolution {rec['resolution_id']}: reapplication {rec['reapplication_id']} did not answer "
|
|
2074
|
+
f"intent {rec['intent_id']} — the resolution derives FROM the mandated re-application, never from "
|
|
2075
|
+
"an unrelated same-item record (§6 loop step 2)"
|
|
2076
|
+
)
|
|
2077
|
+
if rec["resolved_by"] == "system":
|
|
2078
|
+
if reapp["reapplied_verdict"] == "CANNOT_VERIFY":
|
|
2079
|
+
fails.add(f"resolution {rec['resolution_id']}: a CANNOT_VERIFY reapplication derives no system resolution (§5.3)")
|
|
2080
|
+
else:
|
|
2081
|
+
expected_state = "primary_upheld" if reapp["reapplied_verdict"] == reapp["pre_reapplication_verdict"] else "primary_revised"
|
|
2082
|
+
if rec["state"] != expected_state:
|
|
2083
|
+
fails.add(f"resolution {rec['resolution_id']}: state {rec['state']!r} != §9 derivation ({expected_state!r})")
|
|
2084
|
+
else:
|
|
2085
|
+
if reapp["reapplied_verdict"] != "CANNOT_VERIFY":
|
|
2086
|
+
fails.add(f"resolution {rec['resolution_id']}: the user acceptance form requires a CANNOT_VERIFY reapplication (§5.3)")
|
|
2087
|
+
|
|
2088
|
+
# --- G2(d) acceptances ---
|
|
2089
|
+
acceptances_by_id = {}
|
|
2090
|
+
for rec in traceability["g2d_acceptances"]:
|
|
2091
|
+
if rec["acceptance_id"] in acceptances_by_id:
|
|
2092
|
+
fails.add(f"duplicate acceptance_id {rec['acceptance_id']}")
|
|
2093
|
+
acceptances_by_id[rec["acceptance_id"]] = rec
|
|
2094
|
+
reapp = reapp_by_id.get(rec["reapplication_id"])
|
|
2095
|
+
if reapp is None or reapp["item_id"] != rec["item_id"]:
|
|
2096
|
+
fails.add(f"acceptance {rec['acceptance_id']}: reapplication_id does not resolve on the same item")
|
|
2097
|
+
elif reapp["reapplied_verdict"] != "CANNOT_VERIFY":
|
|
2098
|
+
fails.add(f"acceptance {rec['acceptance_id']}: the accepted reapplication must be CANNOT_VERIFY (§6 G2(d))")
|
|
2099
|
+
if rec["reapplication_id"] in superseded_reapps:
|
|
2100
|
+
fails.add(
|
|
2101
|
+
f"acceptance {rec['acceptance_id']}: references a SUPERSEDED reapplication — an acceptance binds "
|
|
2102
|
+
"to the CURRENT failed attempt, never a stale one (§6 G2(d))"
|
|
2103
|
+
)
|
|
2104
|
+
row = row_by_item.get(rec["item_id"])
|
|
2105
|
+
if row is not None and row["final_verdict"] != "CANNOT_VERIFY":
|
|
2106
|
+
fails.add(f"acceptance {rec['acceptance_id']}: the accepted item's final_verdict must be CANNOT_VERIFY (§6 G2(d))")
|
|
2107
|
+
backing = [
|
|
2108
|
+
adj for adj in traceability["adjustments"]
|
|
2109
|
+
if adj["basis"] == "user_accepted_fail_closed"
|
|
2110
|
+
and adj.get("source_ref") == f"acceptance:{rec['acceptance_id']}"
|
|
2111
|
+
]
|
|
2112
|
+
if len(backing) != 1:
|
|
2113
|
+
fails.add(
|
|
2114
|
+
f"acceptance {rec['acceptance_id']}: a G2(d) acceptance backs exactly ONE user_accepted_fail_closed "
|
|
2115
|
+
f"adjustment (found {len(backing)}) — the one legal CANNOT_VERIFY append; an orphan acceptance cannot "
|
|
2116
|
+
"clear the deferral (§6 G2(d)/§3.4)"
|
|
2117
|
+
)
|
|
2118
|
+
acceptance_adj_by_reapp = {}
|
|
2119
|
+
for adj in traceability["adjustments"]:
|
|
2120
|
+
if adj["basis"] == "user_accepted_fail_closed":
|
|
2121
|
+
acc_id = adj["source_ref"].split(":", 1)[1]
|
|
2122
|
+
acceptance = acceptances_by_id.get(acc_id)
|
|
2123
|
+
if acceptance is None or acceptance["item_id"] != adj["item_id"]:
|
|
2124
|
+
fails.add(f"adjustment {adj['adjustment_id']}: acceptance ref does not resolve on the same item")
|
|
2125
|
+
continue
|
|
2126
|
+
acceptance_adj_by_reapp[acceptance["reapplication_id"]] = adj
|
|
2127
|
+
reapp = reapp_by_id.get(acceptance["reapplication_id"])
|
|
2128
|
+
if reapp is not None and reapp.get("cannot_verify_reason") != adj.get("cannot_verify_reason"):
|
|
2129
|
+
fails.add(f"adjustment {adj['adjustment_id']}: cannot_verify_reason must be copied from the accepted reapplication (§3.4)")
|
|
2130
|
+
elif adj["basis"] == "cross_model_adjudication":
|
|
2131
|
+
rap_id = adj["source_ref"].split(":", 1)[1]
|
|
2132
|
+
if rap_id not in reapp_by_id:
|
|
2133
|
+
fails.add(f"adjustment {adj['adjustment_id']}: reapplication ref does not resolve")
|
|
2134
|
+
|
|
2135
|
+
# pre_reapplication_verdict consistency (§13): the recorded pre-value is
|
|
2136
|
+
# the TIME ANCHOR — it equals this reapplication's own derived
|
|
2137
|
+
# adjustment's from_verdict when one exists; on the G2(d) path it equals
|
|
2138
|
+
# the acceptance-backed adjustment's from_verdict (that adjustment
|
|
2139
|
+
# consumed this reapplication, so the tail moved AFTER dispatch); else
|
|
2140
|
+
# the current chain tail; else phase2a_verdict when the item has no chain.
|
|
2141
|
+
for rec in traceability["reapplications"]:
|
|
2142
|
+
derived = [
|
|
2143
|
+
adj for adj in traceability["adjustments"]
|
|
2144
|
+
if adj.get("source_ref") == f"reapplication:{rec['reapplication_id']}"
|
|
2145
|
+
]
|
|
2146
|
+
if rec["reapplication_id"] in superseded_reapps:
|
|
2147
|
+
# UNCONDITIONAL for every superseded record — a derived
|
|
2148
|
+
# adjustment must not bypass this branch. Supersession is
|
|
2149
|
+
# defined for FAILED attempts only (§6 — "a retry names the
|
|
2150
|
+
# failed attempt it supersedes"), and a failed attempt appends
|
|
2151
|
+
# nothing, so its dispatch tail equals its direct retry's
|
|
2152
|
+
# recorded pre-value. The current-tail fallback never applies
|
|
2153
|
+
# to a historical record (the tail may have moved since).
|
|
2154
|
+
successor = reapp_by_id[superseded_reapps[rec["reapplication_id"]]]
|
|
2155
|
+
if rec["reapplied_verdict"] != "CANNOT_VERIFY":
|
|
2156
|
+
fails.add(
|
|
2157
|
+
f"reapplication {rec['reapplication_id']}: only FAILED (CANNOT_VERIFY) attempts are superseded "
|
|
2158
|
+
"by a retry (§6)"
|
|
2159
|
+
)
|
|
2160
|
+
if rec["pre_reapplication_verdict"] != successor["pre_reapplication_verdict"]:
|
|
2161
|
+
fails.add(
|
|
2162
|
+
f"reapplication {rec['reapplication_id']}: a failed attempt appends nothing, so its dispatch "
|
|
2163
|
+
f"tail equals its direct retry's pre_reapplication_verdict "
|
|
2164
|
+
f"({rec['pre_reapplication_verdict']!r} != {successor['pre_reapplication_verdict']!r}, §6/§13)"
|
|
2165
|
+
)
|
|
2166
|
+
continue
|
|
2167
|
+
if len(derived) == 1:
|
|
2168
|
+
expected_pre = derived[0]["from_verdict"]
|
|
2169
|
+
elif rec["reapplication_id"] in acceptance_adj_by_reapp:
|
|
2170
|
+
expected_pre = acceptance_adj_by_reapp[rec["reapplication_id"]]["from_verdict"]
|
|
2171
|
+
else:
|
|
2172
|
+
expected_pre = _chain_tail_verdict(rec["item_id"])
|
|
2173
|
+
if expected_pre is not None and rec["pre_reapplication_verdict"] != expected_pre:
|
|
2174
|
+
fails.add(
|
|
2175
|
+
f"reapplication {rec['reapplication_id']}: pre_reapplication_verdict {rec['pre_reapplication_verdict']!r} "
|
|
2176
|
+
f"!= the chain tail as it stood at dispatch ({expected_pre!r}, §13)"
|
|
2177
|
+
)
|
|
2178
|
+
|
|
2179
|
+
# --- critical-rebuttal machinery (§3.4/§5.3) ---
|
|
2180
|
+
radj_by_id = {}
|
|
2181
|
+
for rec in traceability["rebuttal_adjudications"]:
|
|
2182
|
+
if rec["rebuttal_adjudication_id"] in radj_by_id:
|
|
2183
|
+
fails.add(f"duplicate rebuttal_adjudication_id {rec['rebuttal_adjudication_id']}")
|
|
2184
|
+
radj_by_id[rec["rebuttal_adjudication_id"]] = rec
|
|
2185
|
+
if not cross_model_active:
|
|
2186
|
+
fails.add(f"rebuttal adjudication {rec['rebuttal_adjudication_id']}: the §3.4 pass exists only under active cross-model (§11)")
|
|
2187
|
+
adjustments_by_id = {adj["adjustment_id"]: adj for adj in traceability["adjustments"]}
|
|
2188
|
+
|
|
2189
|
+
def _is_critical(item_id):
|
|
2190
|
+
item = roadmap_by_id.get(item_id)
|
|
2191
|
+
return item is not None and item.get("severity") == "critical"
|
|
2192
|
+
|
|
2193
|
+
for adj in traceability["adjustments"]:
|
|
2194
|
+
should_have_check = adj["basis"] == "valid_rebuttal" and _is_critical(adj["item_id"])
|
|
2195
|
+
has_check = "critical_rebuttal_check" in adj
|
|
2196
|
+
if should_have_check != has_check:
|
|
2197
|
+
fails.add(
|
|
2198
|
+
f"adjustment {adj['adjustment_id']}: critical_rebuttal_check present iff the adjustment is a critical "
|
|
2199
|
+
f"valid_rebuttal (§3.4)"
|
|
2200
|
+
)
|
|
2201
|
+
if has_check:
|
|
2202
|
+
check_value = adj["critical_rebuttal_check"]
|
|
2203
|
+
match = _CHECK_ADJUDICATED_RE.match(check_value) if isinstance(check_value, str) else None
|
|
2204
|
+
if match:
|
|
2205
|
+
radj = radj_by_id.get(match.group(1))
|
|
2206
|
+
if radj is None or radj["verdict"] != "upheld" or radj["item_id"] != adj["item_id"]:
|
|
2207
|
+
fails.add(f"adjustment {adj['adjustment_id']}: adjudicated:* must resolve to an upheld RebuttalAdjudication on the same item (§3.4)")
|
|
2208
|
+
elif check_value == "single_family_disclosed" and cross_model_active:
|
|
2209
|
+
fails.add(f"adjustment {adj['adjustment_id']}: single_family_disclosed is invalid on an active cross-model run (§3.4)")
|
|
2210
|
+
|
|
2211
|
+
proposals_by_id = {}
|
|
2212
|
+
booked_by_adjustment = {}
|
|
2213
|
+
seen_drafted_bodies = {}
|
|
2214
|
+
challenged_bodies = []
|
|
2215
|
+
for rec in traceability["pending_rebuttal_upgrades"]:
|
|
2216
|
+
if rec["proposal_id"] in proposals_by_id:
|
|
2217
|
+
fails.add(f"duplicate proposal_id {rec['proposal_id']}")
|
|
2218
|
+
proposals_by_id[rec["proposal_id"]] = rec
|
|
2219
|
+
kind, ref_id = rec["disposition"].split(":", 1)
|
|
2220
|
+
if rec["drafted_adjustment"]["item_id"] != rec["item_id"]:
|
|
2221
|
+
fails.add(f"proposal {rec['proposal_id']}: drafted_adjustment.item_id must equal the proposal's item_id (§5.3)")
|
|
2222
|
+
body_key = (rec["item_id"], canonical_hash(rec["drafted_adjustment"]))
|
|
2223
|
+
if body_key in seen_drafted_bodies:
|
|
2224
|
+
fails.add(
|
|
2225
|
+
f"proposal {rec['proposal_id']}: duplicate PendingRebuttalUpgrade drafted body on item {rec['item_id']} "
|
|
2226
|
+
f"(same canonical body as {seen_drafted_bodies[body_key]}) — one proposal per drafted upgrade (§5.3)"
|
|
2227
|
+
)
|
|
2228
|
+
seen_drafted_bodies[body_key] = rec["proposal_id"]
|
|
2229
|
+
if kind == "challenged":
|
|
2230
|
+
challenged_bodies.append((rec["proposal_id"], rec["item_id"], rec["drafted_adjustment"]))
|
|
2231
|
+
if kind == "challenged":
|
|
2232
|
+
radj = radj_by_id.get(ref_id)
|
|
2233
|
+
if radj is None or radj["item_id"] != rec["item_id"]:
|
|
2234
|
+
fails.add(f"proposal {rec['proposal_id']}: challenged ref does not resolve on the same item")
|
|
2235
|
+
elif radj["verdict"] != "challenged":
|
|
2236
|
+
fails.add(f"proposal {rec['proposal_id']}: challenged disposition must reference a challenged adjudication")
|
|
2237
|
+
else:
|
|
2238
|
+
adj = adjustments_by_id.get(ref_id)
|
|
2239
|
+
if adj is None or adj["item_id"] != rec["item_id"] or adj["basis"] != "valid_rebuttal":
|
|
2240
|
+
fails.add(f"proposal {rec['proposal_id']}: booked ref must resolve to a valid_rebuttal adjustment on the same item")
|
|
2241
|
+
continue
|
|
2242
|
+
if ref_id in booked_by_adjustment:
|
|
2243
|
+
fails.add(f"proposal {rec['proposal_id']}: adjustment {ref_id} already booked by {booked_by_adjustment[ref_id]}")
|
|
2244
|
+
booked_by_adjustment[ref_id] = rec["proposal_id"]
|
|
2245
|
+
drafted = dict(rec["drafted_adjustment"])
|
|
2246
|
+
booked = {
|
|
2247
|
+
key: value for key, value in adj.items()
|
|
2248
|
+
if key not in ("adjustment_id", "supersedes_adjustment_id", "critical_rebuttal_check")
|
|
2249
|
+
}
|
|
2250
|
+
if drafted != booked:
|
|
2251
|
+
fails.add(
|
|
2252
|
+
f"proposal {rec['proposal_id']}: booked adjustment must content-equal the drafted body "
|
|
2253
|
+
"field-for-field, excluding EXACTLY {adjustment_id, supersedes_adjustment_id, critical_rebuttal_check} (§5.3)"
|
|
2254
|
+
)
|
|
2255
|
+
check_value = adj.get("critical_rebuttal_check")
|
|
2256
|
+
if kind == "booked":
|
|
2257
|
+
match = _CHECK_ADJUDICATED_RE.match(check_value) if isinstance(check_value, str) else None
|
|
2258
|
+
if not match or radj_by_id.get(match.group(1), {}).get("verdict") != "upheld":
|
|
2259
|
+
fails.add(f"proposal {rec['proposal_id']}: booked disposition requires an upheld adjudicated:* check (§3.4)")
|
|
2260
|
+
elif kind == "booked_disclosed":
|
|
2261
|
+
if check_value != "pass_unavailable_disclosed":
|
|
2262
|
+
fails.add(f"proposal {rec['proposal_id']}: booked_disclosed requires critical_rebuttal_check pass_unavailable_disclosed")
|
|
2263
|
+
if not cross_model_active:
|
|
2264
|
+
fails.add(f"proposal {rec['proposal_id']}: booked_disclosed exists only under active cross-model (§11)")
|
|
2265
|
+
elif kind == "booked_single_family":
|
|
2266
|
+
if check_value != "single_family_disclosed":
|
|
2267
|
+
fails.add(f"proposal {rec['proposal_id']}: booked_single_family requires critical_rebuttal_check single_family_disclosed")
|
|
2268
|
+
if cross_model_active:
|
|
2269
|
+
fails.add(f"proposal {rec['proposal_id']}: booked_single_family is invalid under active cross-model (§3.4)")
|
|
2270
|
+
for adj in traceability["adjustments"]:
|
|
2271
|
+
if adj["basis"] == "valid_rebuttal" and _is_critical(adj["item_id"]) and adj["adjustment_id"] not in booked_by_adjustment:
|
|
2272
|
+
fails.add(
|
|
2273
|
+
f"adjustment {adj['adjustment_id']}: every critical valid_rebuttal adjustment traces to exactly one "
|
|
2274
|
+
"booked* PendingRebuttalUpgrade (§5.3)"
|
|
2275
|
+
)
|
|
2276
|
+
if adj["basis"] == "valid_rebuttal":
|
|
2277
|
+
booked_view = {
|
|
2278
|
+
key: value for key, value in adj.items()
|
|
2279
|
+
if key not in ("adjustment_id", "supersedes_adjustment_id", "critical_rebuttal_check")
|
|
2280
|
+
}
|
|
2281
|
+
for proposal_id, item_id, drafted in challenged_bodies:
|
|
2282
|
+
if adj["item_id"] == item_id and booked_view == drafted:
|
|
2283
|
+
fails.add(
|
|
2284
|
+
f"adjustment {adj['adjustment_id']}: content-equals CHALLENGED proposal {proposal_id}'s "
|
|
2285
|
+
"drafted body — a challenged upgrade is NEVER booked (§3.4)"
|
|
2286
|
+
)
|
|
2287
|
+
|
|
2288
|
+
# --- escalation approvals ---
|
|
2289
|
+
approvals_by_exception = {}
|
|
2290
|
+
for rec in traceability["escalation_approvals"]:
|
|
2291
|
+
if rec["exception_id"] in approvals_by_exception:
|
|
2292
|
+
fails.add(f"duplicate escalation approval for {rec['exception_id']}")
|
|
2293
|
+
approvals_by_exception[rec["exception_id"]] = rec
|
|
2294
|
+
if rec["exception_id"] not in exceptions_by_id:
|
|
2295
|
+
fails.add(f"escalation approval {rec['exception_id']}: no such exception record")
|
|
2296
|
+
|
|
2297
|
+
# --- cross-model activity consistency (§11) ---
|
|
2298
|
+
if not cross_model_active:
|
|
2299
|
+
for item_id, row in row_by_item.items():
|
|
2300
|
+
if row.get("cross_model_status") in ("agree", "diverges", "unavailable"):
|
|
2301
|
+
fails.add(f"row {item_id}: cross_model_status {row['cross_model_status']!r} implies an active configuration (§11)")
|
|
2302
|
+
|
|
2303
|
+
# --- DecisionInputs recomputation (§13) ---
|
|
2304
|
+
di = traceability["decision_inputs"]
|
|
2305
|
+
|
|
2306
|
+
def _final_residual_obligation_class(item_id):
|
|
2307
|
+
row = row_by_item.get(item_id)
|
|
2308
|
+
if row is None or row["final_verdict"] != "PARTIALLY_ADDRESSED":
|
|
2309
|
+
return None
|
|
2310
|
+
chain = chains.get(item_id)
|
|
2311
|
+
record = chain[-1] if chain else verdict_by_item.get(item_id)
|
|
2312
|
+
if record is None:
|
|
2313
|
+
return None
|
|
2314
|
+
gap = record.get("residual_gap")
|
|
2315
|
+
return gap["residual_obligation_class"] if gap else None
|
|
2316
|
+
|
|
2317
|
+
expected_per_item = []
|
|
2318
|
+
for item_id in must_fix_order:
|
|
2319
|
+
row = row_by_item.get(item_id)
|
|
2320
|
+
if row is None:
|
|
2321
|
+
continue
|
|
2322
|
+
item = roadmap_by_id[item_id]
|
|
2323
|
+
severity = item.get("severity")
|
|
2324
|
+
# §5.3: a null driving_severity is legal ONLY for source_kind items
|
|
2325
|
+
# and legacy roadmaps. A must_fix item carrying other transported markers
|
|
2326
|
+
# (#574 A2/A3) but no severity is a half-transported finding-driven
|
|
2327
|
+
# item, not a legacy one — B1's critical join would be silently
|
|
2328
|
+
# suppressed, so it fails here.
|
|
2329
|
+
if (
|
|
2330
|
+
severity is None
|
|
2331
|
+
and item.get("source_kind") is None
|
|
2332
|
+
and any(item.get(field) is not None for field in ("evidence_anchor", "confidence", "competence_basis"))
|
|
2333
|
+
):
|
|
2334
|
+
fails.add(
|
|
2335
|
+
f"roadmap item {item_id}: transported fields present but severity absent — a non-legacy "
|
|
2336
|
+
"finding-driven must_fix item cannot carry driving_severity null (§5.3)"
|
|
2337
|
+
)
|
|
2338
|
+
entry = {
|
|
2339
|
+
"item_id": item_id,
|
|
2340
|
+
"final_verdict": row["final_verdict"],
|
|
2341
|
+
"driving_severity": severity if severity in SEVERITIES else None,
|
|
2342
|
+
}
|
|
2343
|
+
magnitude = _final_residual_obligation_class(item_id)
|
|
2344
|
+
if magnitude is not None:
|
|
2345
|
+
entry["residual_obligation_class"] = magnitude
|
|
2346
|
+
expected_per_item.append(entry)
|
|
2347
|
+
if di["per_item"] != expected_per_item:
|
|
2348
|
+
fails.add("decision_inputs.per_item does not equal the recomputed per-must_fix operand list (immutable roadmap order, §13)")
|
|
2349
|
+
|
|
2350
|
+
expected_counts = {prio: {verdict: 0 for verdict in VERDICTS} for prio in OBLIGATION_CLASSES}
|
|
2351
|
+
expected_magnitudes = {prio: {mag: 0 for mag in RESIDUAL_OBLIGATION_CLASSES} for prio in OBLIGATION_CLASSES}
|
|
2352
|
+
for item_id, row in row_by_item.items():
|
|
2353
|
+
item = roadmap_by_id.get(item_id)
|
|
2354
|
+
if item is None:
|
|
2355
|
+
continue
|
|
2356
|
+
expected_counts[item["obligation_class"]][row["final_verdict"]] += 1
|
|
2357
|
+
if row["final_verdict"] == "PARTIALLY_ADDRESSED":
|
|
2358
|
+
magnitude = _final_residual_obligation_class(item_id)
|
|
2359
|
+
if magnitude is not None:
|
|
2360
|
+
expected_magnitudes[item["obligation_class"]][magnitude] += 1
|
|
2361
|
+
if di["verdict_counts"] != expected_counts:
|
|
2362
|
+
fails.add("decision_inputs.verdict_counts does not recompute from the final row verdicts (§13)")
|
|
2363
|
+
if di["residual_obligation_class_counts"] != expected_magnitudes:
|
|
2364
|
+
fails.add("decision_inputs.residual_obligation_class_counts does not recompute from the PARTIALLY_ADDRESSED rows (§13)")
|
|
2365
|
+
|
|
2366
|
+
p2_ids = [item["id"] for item in roadmap_items if item["obligation_class"] == "should_fix"]
|
|
2367
|
+
expected_num = sum(
|
|
2368
|
+
1 for item_id in p2_ids
|
|
2369
|
+
if row_by_item.get(item_id, {}).get("final_verdict") in ("FULLY_ADDRESSED", "PARTIALLY_ADDRESSED")
|
|
2370
|
+
)
|
|
2371
|
+
if di["should_fix_addressed_rate"] != {"numerator": expected_num, "denominator": len(p2_ids)}:
|
|
2372
|
+
fails.add("decision_inputs.should_fix_addressed_rate does not recompute over final should_fix verdicts (§6)")
|
|
2373
|
+
|
|
2374
|
+
expected_regressions = [
|
|
2375
|
+
{"new_issue_id": rec["new_issue_id"], "severity": rec["severity"]}
|
|
2376
|
+
for rec in verdict_record["new_issues"] if rec["attribution"] == "regression"
|
|
2377
|
+
]
|
|
2378
|
+
expected_non_regression = [
|
|
2379
|
+
rec["new_issue_id"] for rec in verdict_record["new_issues"] if rec["attribution"] != "regression"
|
|
2380
|
+
]
|
|
2381
|
+
if di["regressions"] != expected_regressions:
|
|
2382
|
+
fails.add("decision_inputs.regressions does not equal the regression-attributed frozen new issues (§8/§13)")
|
|
2383
|
+
if di["non_regression_new_issue_ids"] != expected_non_regression:
|
|
2384
|
+
fails.add("decision_inputs.non_regression_new_issue_ids does not equal the frozen previously_missed/indeterminate ids (§8/§13)")
|
|
2385
|
+
|
|
2386
|
+
expected_escalations = []
|
|
2387
|
+
for rec in verdict_record["escalation_exceptions"]:
|
|
2388
|
+
approval = approvals_by_exception.get(rec["exception_id"])
|
|
2389
|
+
expected_escalations.append({
|
|
2390
|
+
"exception_id": rec["exception_id"],
|
|
2391
|
+
"effective_approval_state": approval["approval_state"] if approval else "pending",
|
|
2392
|
+
"escalation_class": rec["escalation_class"],
|
|
2393
|
+
"mechanical_decision_impact": rec["mechanical_decision_impact"],
|
|
2394
|
+
})
|
|
2395
|
+
if di["escalations"] != expected_escalations:
|
|
2396
|
+
fails.add("decision_inputs.escalations does not equal the joined exception/approval summary (§5.3)")
|
|
2397
|
+
|
|
2398
|
+
if di["apply_chain_witness"] != witness:
|
|
2399
|
+
fails.add(f"decision_inputs.apply_chain_witness {di['apply_chain_witness']!r} != recomputed witness {witness!r} (§11)")
|
|
2400
|
+
|
|
2401
|
+
# --- §6 Step 1 recomputation (gates) ---
|
|
2402
|
+
g1 = any(
|
|
2403
|
+
row["final_verdict"] != row["phase2a_verdict"] and row["item_id"] not in chains
|
|
2404
|
+
for row in traceability["rows"]
|
|
2405
|
+
)
|
|
2406
|
+
pending = []
|
|
2407
|
+
# bound_tripped computed once, next to dissent_by_id (§7 SD-5).
|
|
2408
|
+
if bound_tripped:
|
|
2409
|
+
for dissent_id in dissent_by_id:
|
|
2410
|
+
if dissent_id not in adjudications_by_dissent:
|
|
2411
|
+
pending.append(f"G2(a): dissent {dissent_id} lacks its DissentAdjudication (bound tripped — covering cardinality)")
|
|
2412
|
+
|
|
2413
|
+
def _covering_resolution(item_id, final_verdict):
|
|
2414
|
+
for rec in traceability["cross_model_resolutions"]:
|
|
2415
|
+
if rec["item_id"] != item_id:
|
|
2416
|
+
continue
|
|
2417
|
+
reapp = reapp_by_id.get(rec["reapplication_id"])
|
|
2418
|
+
if reapp is None:
|
|
2419
|
+
continue
|
|
2420
|
+
anchor = reapp["reapplied_verdict"] if rec["resolved_by"] == "system" else reapp["pre_reapplication_verdict"]
|
|
2421
|
+
if anchor == final_verdict:
|
|
2422
|
+
return True
|
|
2423
|
+
return False
|
|
2424
|
+
|
|
2425
|
+
for item_id, row in row_by_item.items():
|
|
2426
|
+
if row.get("cross_model_status") == "diverges" and ROW_OBLIGATION_MAP[row["obligation_class"]] == "must_fix":
|
|
2427
|
+
if not _covering_resolution(item_id, row["final_verdict"]):
|
|
2428
|
+
pending.append(f"G2(b): must_fix diverges row {item_id} has no covering CrossModelResolution")
|
|
2429
|
+
for rec in verdict_record["escalation_exceptions"]:
|
|
2430
|
+
if rec["exception_id"] not in approvals_by_exception:
|
|
2431
|
+
pending.append(f"G2(c): escalation exception {rec['exception_id']} is pending user approval")
|
|
2432
|
+
accepted_reapps = {rec["reapplication_id"] for rec in traceability["g2d_acceptances"]}
|
|
2433
|
+
for dissent_id, adjudication in adjudications_by_dissent.items():
|
|
2434
|
+
if adjudication["outcome"] != "original_upheld":
|
|
2435
|
+
continue
|
|
2436
|
+
holders = _answers_in_current(f"adjudication:{dissent_id}")
|
|
2437
|
+
if len(holders) != 1:
|
|
2438
|
+
continue # already a synthesis failure above
|
|
2439
|
+
current = holders[0]
|
|
2440
|
+
if current["reapplied_verdict"] == "CANNOT_VERIFY" and current["reapplication_id"] not in accepted_reapps:
|
|
2441
|
+
pending.append(f"G2(d): original_upheld dissent {dissent_id} fail-closed reapplication awaits user resolution")
|
|
2442
|
+
|
|
2443
|
+
# --- emission-kind-scoped equality set (§13) ---
|
|
2444
|
+
recomputed = {"pending": pending, "g1": g1}
|
|
2445
|
+
emitted_state = traceability["decision_state"]
|
|
2446
|
+
if emitted_state == "aborted":
|
|
2447
|
+
# §13: an aborted emission carries its true root-cause abort_reason
|
|
2448
|
+
# and is EXEMPT from the deferral biconditional and Steps 2-3 (abort
|
|
2449
|
+
# precedence over deferral). The only checker-recomputable reason is
|
|
2450
|
+
# criteria_drift; the phase-lint reasons are fenced-call facts the
|
|
2451
|
+
# checker cannot re-derive and are accepted as recorded (the
|
|
2452
|
+
# manifest-class reasons would already have exited 2 above).
|
|
2453
|
+
reason = traceability.get("abort_reason")
|
|
2454
|
+
recomputed["expect"] = ("aborted", reason)
|
|
2455
|
+
if "reject_recommended" in di:
|
|
2456
|
+
fails.add("decision_inputs.reject_recommended must be ABSENT on a gated emission (§5.3 presence biconditional)")
|
|
2457
|
+
if reason in ("manifest_incomplete", "manifest_hash_mismatch", "synthesis_mismatch"):
|
|
2458
|
+
fails.add(
|
|
2459
|
+
f"aborted emission claims {reason!r} as root cause, but the checker reached recomputation — the "
|
|
2460
|
+
"§11 manifest layer validated against these same hash-bound inputs, so the claimed abort cannot be "
|
|
2461
|
+
"the true root cause (§13: an aborted emission carries its TRUE root-cause abort_reason)"
|
|
2462
|
+
)
|
|
2463
|
+
if g1 and reason != "criteria_drift":
|
|
2464
|
+
fails.add("Step 1 recomputes G1 (silent verdict change) — the aborted emission's true root cause is criteria_drift (§13)")
|
|
2465
|
+
if reason == "criteria_drift" and not g1:
|
|
2466
|
+
fails.add("the emission claims [RE-REVIEW-ABORT: criteria_drift] but no silent verdict change recomputes (§13)")
|
|
2467
|
+
elif g1:
|
|
2468
|
+
recomputed["expect"] = ("aborted", "criteria_drift")
|
|
2469
|
+
fails.add("Step 1 recomputes G1 (silent verdict change) — the emission must be [RE-REVIEW-ABORT: criteria_drift]")
|
|
2470
|
+
elif pending:
|
|
2471
|
+
recomputed["expect"] = ("user_review_required", None)
|
|
2472
|
+
if emitted_state != "user_review_required":
|
|
2473
|
+
fails.add(
|
|
2474
|
+
"G2 deferral biconditional: pending user-input state(s) exist, so user_review_required is the only "
|
|
2475
|
+
"checker-valid decision_state — " + "; ".join(pending)
|
|
2476
|
+
)
|
|
2477
|
+
if "reject_recommended" in di:
|
|
2478
|
+
fails.add("decision_inputs.reject_recommended must be ABSENT on a gated emission (§5.3 presence biconditional)")
|
|
2479
|
+
else:
|
|
2480
|
+
if emitted_state == "user_review_required":
|
|
2481
|
+
fails.add(
|
|
2482
|
+
"Step 1 recomputes no gate, but the emission carries decision_state 'user_review_required' "
|
|
2483
|
+
"(the G2 biconditional runs both directions)"
|
|
2484
|
+
)
|
|
2485
|
+
else:
|
|
2486
|
+
# Steps 2-3, recomputed independently from the raw records...
|
|
2487
|
+
p1_operands = []
|
|
2488
|
+
for item_id in must_fix_order:
|
|
2489
|
+
row = row_by_item.get(item_id)
|
|
2490
|
+
if row is None:
|
|
2491
|
+
continue
|
|
2492
|
+
item = roadmap_by_id[item_id]
|
|
2493
|
+
severity = item.get("severity") if item.get("severity") in SEVERITIES else None
|
|
2494
|
+
p1_operands.append((row["final_verdict"], severity, _final_residual_obligation_class(item_id)))
|
|
2495
|
+
p2_partial_magnitudes = [
|
|
2496
|
+
_final_residual_obligation_class(item_id) for item_id in p2_ids
|
|
2497
|
+
if row_by_item.get(item_id, {}).get("final_verdict") == "PARTIALLY_ADDRESSED"
|
|
2498
|
+
]
|
|
2499
|
+
p2_made_worse = any(
|
|
2500
|
+
row_by_item.get(item_id, {}).get("final_verdict") == "MADE_WORSE" for item_id in p2_ids
|
|
2501
|
+
)
|
|
2502
|
+
regression_severities = {entry["severity"] for entry in expected_regressions}
|
|
2503
|
+
raw_decision, raw_reject, raw_rule = derive_decision(
|
|
2504
|
+
p1_operands, p2_partial_magnitudes, p2_made_worse,
|
|
2505
|
+
expected_num, len(p2_ids), regression_severities, expected_escalations,
|
|
2506
|
+
)
|
|
2507
|
+
# ...AND from the emitted DecisionInputs copies.
|
|
2508
|
+
di_p1 = [
|
|
2509
|
+
(entry["final_verdict"], entry["driving_severity"], entry.get("residual_obligation_class"))
|
|
2510
|
+
for entry in di["per_item"]
|
|
2511
|
+
]
|
|
2512
|
+
di_p2_partials = [
|
|
2513
|
+
mag for mag in RESIDUAL_OBLIGATION_CLASSES
|
|
2514
|
+
for _ in range(di["residual_obligation_class_counts"]["should_fix"][mag])
|
|
2515
|
+
]
|
|
2516
|
+
di_decision, di_reject, _di_rule = derive_decision(
|
|
2517
|
+
di_p1,
|
|
2518
|
+
di_p2_partials,
|
|
2519
|
+
di["verdict_counts"]["should_fix"]["MADE_WORSE"] > 0,
|
|
2520
|
+
di["should_fix_addressed_rate"]["numerator"],
|
|
2521
|
+
di["should_fix_addressed_rate"]["denominator"],
|
|
2522
|
+
{entry["severity"] for entry in di["regressions"]},
|
|
2523
|
+
di["escalations"],
|
|
2524
|
+
)
|
|
2525
|
+
recomputed["expect"] = (raw_decision, None)
|
|
2526
|
+
recomputed["rule"] = raw_rule
|
|
2527
|
+
if emitted_state != raw_decision:
|
|
2528
|
+
fails.add(f"decision_state {emitted_state!r} != Steps 2-3 recomputed from the raw records ({raw_decision!r}, base {raw_rule})")
|
|
2529
|
+
if di_decision != raw_decision:
|
|
2530
|
+
fails.add(f"Steps 2-3 from DecisionInputs ({di_decision!r}) diverge from the raw-record recomputation ({raw_decision!r})")
|
|
2531
|
+
if "reject_recommended" not in di:
|
|
2532
|
+
fails.add("decision_inputs.reject_recommended must be PRESENT on a non-gated emission (§5.3 presence biconditional)")
|
|
2533
|
+
else:
|
|
2534
|
+
if di["reject_recommended"] != raw_reject:
|
|
2535
|
+
fails.add(f"reject_recommended {di['reject_recommended']!r} != recomputed {raw_reject!r} (§6)")
|
|
2536
|
+
if di_reject != raw_reject:
|
|
2537
|
+
fails.add("reject_recommended recomputed from DecisionInputs diverges from the raw-record recomputation")
|
|
2538
|
+
|
|
2539
|
+
return fails, warnings, recomputed
|
|
2540
|
+
|
|
2541
|
+
|
|
2542
|
+
# --- CLI -----------------------------------------------------------------------
|
|
2543
|
+
|
|
2544
|
+
|
|
2545
|
+
def _load_json(path: Path, reason: str, label: str):
|
|
2546
|
+
try:
|
|
2547
|
+
return _strict_json_bytes(path.read_bytes(), label)
|
|
2548
|
+
except (OSError, ValueError) as exc:
|
|
2549
|
+
if reason.startswith("manifest"):
|
|
2550
|
+
raise ManifestError(reason, f"{label}: unreadable or invalid JSON ({exc})")
|
|
2551
|
+
raise ArtifactSchemaError(reason, f"{label}: unreadable or invalid JSON ({exc})")
|
|
2552
|
+
|
|
2553
|
+
|
|
2554
|
+
def _verify_file_hash(path: Path, expected_sha256: str, label: str) -> bytes:
|
|
2555
|
+
try:
|
|
2556
|
+
raw = path.read_bytes()
|
|
2557
|
+
except OSError as exc:
|
|
2558
|
+
raise ManifestError("manifest_incomplete", f"{label}: unreadable ({exc})")
|
|
2559
|
+
actual = hashlib.sha256(raw).hexdigest()
|
|
2560
|
+
if actual != expected_sha256:
|
|
2561
|
+
raise ManifestError("manifest_hash_mismatch", f"{label}: file bytes do not match the manifest sha256 (§11)")
|
|
2562
|
+
return raw
|
|
2563
|
+
|
|
2564
|
+
|
|
2565
|
+
def run(argv=None) -> int:
|
|
2566
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
2567
|
+
parser.add_argument("--manifest", type=Path, required=True)
|
|
2568
|
+
parser.add_argument("--precommitment", type=Path, required=True)
|
|
2569
|
+
parser.add_argument("--verdict-record", type=Path, required=True)
|
|
2570
|
+
parser.add_argument("--traceability", type=Path, required=True)
|
|
2571
|
+
parser.add_argument("--roadmap", type=Path, required=True,
|
|
2572
|
+
help="the file the manifest's revision_roadmap entry hash-binds (Schema 7 machine form)")
|
|
2573
|
+
parser.add_argument(
|
|
2574
|
+
"--author-adjudication",
|
|
2575
|
+
type=Path,
|
|
2576
|
+
required=True,
|
|
2577
|
+
help="the exact #670 author sidecar hash-bound by the current input manifest",
|
|
2578
|
+
)
|
|
2579
|
+
parser.add_argument(
|
|
2580
|
+
"--revision-evidence-bundle",
|
|
2581
|
+
type=Path,
|
|
2582
|
+
required=True,
|
|
2583
|
+
help="the exact #670 bundle hash-bound by the current input manifest",
|
|
2584
|
+
)
|
|
2585
|
+
parser.add_argument(
|
|
2586
|
+
"--revision-evidence-root",
|
|
2587
|
+
type=Path,
|
|
2588
|
+
default=None,
|
|
2589
|
+
help="root for paths declared inside the bundle (default: bundle parent)",
|
|
2590
|
+
)
|
|
2591
|
+
parser.add_argument("--letter", type=Path, default=None,
|
|
2592
|
+
help="the file the manifest's editorial_decision_letter entry hash-binds (markdown)")
|
|
2593
|
+
parser.add_argument("--apply-report", type=Path, action="append", default=[], dest="apply_reports",
|
|
2594
|
+
help="apply-report file(s), in the manifest's apply_reports[] order")
|
|
2595
|
+
args = parser.parse_args(argv)
|
|
2596
|
+
|
|
2597
|
+
try:
|
|
2598
|
+
manifest = _load_json(args.manifest, "manifest_incomplete", "manifest")
|
|
2599
|
+
validate_manifest(manifest)
|
|
2600
|
+
artifacts = manifest["artifacts"]
|
|
2601
|
+
|
|
2602
|
+
roadmap_raw = _verify_file_hash(args.roadmap, artifacts["revision_roadmap"]["sha256"], "revision_roadmap file")
|
|
2603
|
+
try:
|
|
2604
|
+
roadmap_payload = _strict_json_bytes(roadmap_raw, "revision_roadmap file")
|
|
2605
|
+
except ValueError as exc:
|
|
2606
|
+
raise ManifestError("manifest_incomplete", str(exc)) from exc
|
|
2607
|
+
roadmap_items = load_roadmap(roadmap_payload)
|
|
2608
|
+
author_raw = _verify_file_hash(
|
|
2609
|
+
args.author_adjudication,
|
|
2610
|
+
artifacts["author_adjudication"]["sha256"],
|
|
2611
|
+
"author_adjudication file",
|
|
2612
|
+
)
|
|
2613
|
+
try:
|
|
2614
|
+
author_payload = _strict_json_bytes(author_raw, "author_adjudication file")
|
|
2615
|
+
except ValueError as exc:
|
|
2616
|
+
raise ManifestError("manifest_incomplete", str(exc)) from exc
|
|
2617
|
+
original_sha = (
|
|
2618
|
+
artifacts["original_manuscript"]["sha256"]
|
|
2619
|
+
if artifacts["original_manuscript"]["present"]
|
|
2620
|
+
else None
|
|
2621
|
+
)
|
|
2622
|
+
author_by_item = load_author_adjudication(
|
|
2623
|
+
author_payload,
|
|
2624
|
+
roadmap_items=roadmap_items,
|
|
2625
|
+
roadmap_sha256=hashlib.sha256(roadmap_raw).hexdigest(),
|
|
2626
|
+
original_manuscript_sha256=original_sha,
|
|
2627
|
+
)
|
|
2628
|
+
|
|
2629
|
+
letter_blocks = None
|
|
2630
|
+
if artifacts["editorial_decision_letter"]["present"]:
|
|
2631
|
+
if args.letter is None:
|
|
2632
|
+
raise ManifestError("manifest_incomplete", "manifest declares editorial_decision_letter present — --letter is required")
|
|
2633
|
+
letter_raw = _verify_file_hash(args.letter, artifacts["editorial_decision_letter"]["sha256"], "editorial_decision_letter file")
|
|
2634
|
+
letter_blocks = parse_letter_blocks(letter_raw.decode("utf-8"))
|
|
2635
|
+
elif args.letter is not None:
|
|
2636
|
+
raise ManifestError("manifest_incomplete", "--letter provided but the manifest declares the letter absent")
|
|
2637
|
+
|
|
2638
|
+
report_entries = artifacts["apply_reports"]["items"] if artifacts["apply_reports"]["present"] else []
|
|
2639
|
+
if len(args.apply_reports) != len(report_entries):
|
|
2640
|
+
raise ManifestError(
|
|
2641
|
+
"manifest_incomplete",
|
|
2642
|
+
f"manifest declares {len(report_entries)} apply report(s); {len(args.apply_reports)} provided via --apply-report",
|
|
2643
|
+
)
|
|
2644
|
+
report_payloads = []
|
|
2645
|
+
for i, (path, entry) in enumerate(zip(args.apply_reports, report_entries)):
|
|
2646
|
+
raw = _verify_file_hash(path, entry["sha256"], f"apply report [{i}]")
|
|
2647
|
+
try:
|
|
2648
|
+
report_payloads.append(_strict_json_bytes(raw, f"apply report [{i}]"))
|
|
2649
|
+
except ValueError as exc:
|
|
2650
|
+
raise ManifestError("manifest_incomplete", str(exc)) from exc
|
|
2651
|
+
witness, witness_notes = compute_apply_chain_witness(manifest, report_payloads)
|
|
2652
|
+
|
|
2653
|
+
bundle_entry = artifacts["revision_evidence_bundle"]
|
|
2654
|
+
bundle_raw = _verify_file_hash(
|
|
2655
|
+
args.revision_evidence_bundle,
|
|
2656
|
+
bundle_entry["sha256"],
|
|
2657
|
+
"revision_evidence_bundle file",
|
|
2658
|
+
)
|
|
2659
|
+
try:
|
|
2660
|
+
bundle = _strict_json_bytes(bundle_raw, "revision_evidence_bundle file")
|
|
2661
|
+
except ValueError as exc:
|
|
2662
|
+
raise ManifestError("manifest_incomplete", str(exc)) from exc
|
|
2663
|
+
try:
|
|
2664
|
+
validate_bundle(
|
|
2665
|
+
bundle,
|
|
2666
|
+
root=args.revision_evidence_root or args.revision_evidence_bundle.parent,
|
|
2667
|
+
)
|
|
2668
|
+
except RevisionContractError as exc:
|
|
2669
|
+
raise ManifestError(
|
|
2670
|
+
"manifest_hash_mismatch",
|
|
2671
|
+
"revision_evidence_bundle replay failed: " + "; ".join(exc.failures),
|
|
2672
|
+
) from exc
|
|
2673
|
+
validate_revision_bundle_binding(bundle, manifest)
|
|
2674
|
+
|
|
2675
|
+
precommitment = _load_json(args.precommitment, "phase1_lint_failed", "precommitment")
|
|
2676
|
+
validate_precommitment(precommitment)
|
|
2677
|
+
verdict_record = _load_json(args.verdict_record, "phase2a_lint_failed", "verdict_record")
|
|
2678
|
+
validate_verdict_record(verdict_record)
|
|
2679
|
+
traceability = _load_json(args.traceability, "phase2b_lint_failed", "traceability")
|
|
2680
|
+
validate_traceability(traceability)
|
|
2681
|
+
|
|
2682
|
+
# Hash chain (§11): the same-inputs proof.
|
|
2683
|
+
if precommitment["input_manifest_hash"] != canonical_hash(manifest):
|
|
2684
|
+
raise ManifestError("manifest_hash_mismatch", "precommitment.input_manifest_hash does not recompute from the manifest (§11 chain)")
|
|
2685
|
+
if verdict_record["precommitment_hash"] != canonical_hash(precommitment):
|
|
2686
|
+
raise ManifestError("manifest_hash_mismatch", "verdict_record.precommitment_hash does not recompute from the precommitment artifact (§11 chain)")
|
|
2687
|
+
if traceability["verdict_record_hash"] != canonical_hash(verdict_record):
|
|
2688
|
+
raise ManifestError("manifest_hash_mismatch", "traceability.verdict_record_hash does not recompute from the verdict_record artifact (§11 chain)")
|
|
2689
|
+
except ManifestError as exc:
|
|
2690
|
+
print(f"[RE-REVIEW-ABORT: {exc.reason}]")
|
|
2691
|
+
print(f"FAIL: {exc}")
|
|
2692
|
+
return EXIT_INVALID
|
|
2693
|
+
except ArtifactSchemaError as exc:
|
|
2694
|
+
print(f"[RE-REVIEW-ABORT: {exc.reason}]")
|
|
2695
|
+
print(f"FAIL: {exc}")
|
|
2696
|
+
return EXIT_INVALID
|
|
2697
|
+
|
|
2698
|
+
fails, warnings, recomputed = check(
|
|
2699
|
+
manifest, precommitment, verdict_record, traceability,
|
|
2700
|
+
roadmap_items, author_by_item, letter_blocks, witness, witness_notes,
|
|
2701
|
+
)
|
|
2702
|
+
for note in warnings:
|
|
2703
|
+
print(note, file=sys.stderr)
|
|
2704
|
+
if fails.messages:
|
|
2705
|
+
print("[RE-REVIEW-ABORT: synthesis_mismatch]")
|
|
2706
|
+
for message in fails.messages:
|
|
2707
|
+
print(f"MISMATCH: {message}")
|
|
2708
|
+
return EXIT_SYNTHESIS
|
|
2709
|
+
expect = recomputed.get("expect")
|
|
2710
|
+
outcome = expect[0] if expect else traceability["decision_state"]
|
|
2711
|
+
print(
|
|
2712
|
+
f"re-review synthesis ok: round {manifest['round_id']!r}, revision {traceability['revision']}, "
|
|
2713
|
+
f"decision_state {outcome!r}, apply_chain_witness {witness!r}"
|
|
2714
|
+
)
|
|
2715
|
+
return EXIT_PASS
|
|
2716
|
+
|
|
2717
|
+
|
|
2718
|
+
if __name__ == "__main__":
|
|
2719
|
+
raise SystemExit(run())
|