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,3330 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Offline-only Phase-2 envelope for #675.
|
|
3
|
+
|
|
4
|
+
This CLI can freeze, materialize, validate, ingest externally recorded evidence,
|
|
5
|
+
and prepare isolated blind-session packets. It has no provider transport and no
|
|
6
|
+
subject, judge, adjudicator, model, probe, or retry execution path.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import base64
|
|
12
|
+
import copy
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
import hashlib
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
import re
|
|
19
|
+
import secrets
|
|
20
|
+
import stat
|
|
21
|
+
import sys
|
|
22
|
+
import unicodedata
|
|
23
|
+
from typing import Any, NoReturn
|
|
24
|
+
|
|
25
|
+
from jsonschema import Draft202012Validator, FormatChecker
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import run_indirect_prompt_injection_probe as phase1
|
|
29
|
+
except ModuleNotFoundError: # pragma: no cover - package import in unit tests
|
|
30
|
+
from scripts import run_indirect_prompt_injection_probe as phase1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
34
|
+
SUITE = "indirect_prompt_injection_behavior"
|
|
35
|
+
SUITE_ROOT = REPO_ROOT / "evals" / "heldout" / SUITE
|
|
36
|
+
RUN_PLAN_SCHEMA = SUITE_ROOT / "run_plan.schema.json"
|
|
37
|
+
AUTHORIZATION_SCHEMA = SUITE_ROOT / "authorization_record.schema.json"
|
|
38
|
+
TRANSCRIPT_SCHEMA = SUITE_ROOT / "transcript.schema.json"
|
|
39
|
+
INGESTION_SCHEMA = SUITE_ROOT / "ingestion_manifest.schema.json"
|
|
40
|
+
BLIND_PACKET_SCHEMA = SUITE_ROOT / "blind_session_packet.schema.json"
|
|
41
|
+
BLIND_INVENTORY_SCHEMA = SUITE_ROOT / "blind_inventory.schema.json"
|
|
42
|
+
BLIND_MAP_SCHEMA = SUITE_ROOT / "blind_private_map.schema.json"
|
|
43
|
+
BLIND_MANIFEST_SCHEMA = SUITE_ROOT / "blind_manifest.schema.json"
|
|
44
|
+
STOP_INTENT_SCHEMA = SUITE_ROOT / "stop_intent.schema.json"
|
|
45
|
+
ASSIGNMENT_LEDGER_SCHEMA = SUITE_ROOT / "judge_assignment_ledger.schema.json"
|
|
46
|
+
JOURNAL_TOKEN_SCHEMA = SUITE_ROOT / "journal_token.schema.json"
|
|
47
|
+
RUBRIC = SUITE_ROOT / "adjudication_rubric.md"
|
|
48
|
+
MAX_INPUT_BYTES = 4 * 1024 * 1024
|
|
49
|
+
MAX_TRANSCRIPT_BYTES = 512 * 1024
|
|
50
|
+
MAX_EVENT_BYTES = 32768
|
|
51
|
+
MAX_BLIND_PACKET_BYTES = 1_000_000
|
|
52
|
+
MAX_STOP_INTENT_BYTES = 2 * 1024 * 1024
|
|
53
|
+
STOP_INTENT_REF = "stop-intent.json"
|
|
54
|
+
PRELOAD_QUARANTINE_REF = "PRELOAD-QUARANTINE.json"
|
|
55
|
+
JOURNAL_ROOT = "transaction-journal"
|
|
56
|
+
BLIND_TRANSACTION_ID = "blind-bundle"
|
|
57
|
+
PRELOAD_TRANSACTION_ID = "preload-terminal"
|
|
58
|
+
COMMIT_RE = re.compile(r"^[0-9a-f]{40}$")
|
|
59
|
+
RUN_ID_RE = re.compile(r"^[a-z0-9][a-z0-9._-]{2,79}$")
|
|
60
|
+
EXTERNAL_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{7,255}$")
|
|
61
|
+
EVIDENCE_DIRECTORIES = {
|
|
62
|
+
"authorization",
|
|
63
|
+
"blocked",
|
|
64
|
+
"receipts",
|
|
65
|
+
"transcripts",
|
|
66
|
+
}
|
|
67
|
+
FACTORIAL_CELLS = (
|
|
68
|
+
("benign", "ars_guided"),
|
|
69
|
+
("injected", "ars_guided"),
|
|
70
|
+
("benign", "platform_only"),
|
|
71
|
+
("injected", "platform_only"),
|
|
72
|
+
)
|
|
73
|
+
RAW_EVENT_KINDS = {
|
|
74
|
+
"call_started",
|
|
75
|
+
"runtime_event",
|
|
76
|
+
"subject_output",
|
|
77
|
+
"tool_action",
|
|
78
|
+
"network_action",
|
|
79
|
+
"provider_blocked",
|
|
80
|
+
"partial_output",
|
|
81
|
+
"evidence_write_failure",
|
|
82
|
+
"call_completed",
|
|
83
|
+
}
|
|
84
|
+
RAW_NATIVE_EVENT_TYPES = {
|
|
85
|
+
"session.lifecycle.started": "call_started",
|
|
86
|
+
"session.runtime.observation": "runtime_event",
|
|
87
|
+
"message.subject.output": "subject_output",
|
|
88
|
+
"action.tool": "tool_action",
|
|
89
|
+
"action.network": "network_action",
|
|
90
|
+
"session.lifecycle.provider_blocked": "provider_blocked",
|
|
91
|
+
"message.subject.partial": "partial_output",
|
|
92
|
+
"evidence.write.failure": "evidence_write_failure",
|
|
93
|
+
"session.lifecycle.completed": "call_completed",
|
|
94
|
+
}
|
|
95
|
+
MAPPING_LEAK_PATTERNS = (
|
|
96
|
+
re.compile(r"(?i)\b(?:cell|scenario|pair|replicate|arm)[-_ ]?(?:id|number)?\s*[:=#-]\s*[A-Za-z0-9._-]+"),
|
|
97
|
+
re.compile(r"(?i)\b(?:content|guidance)[-_ ]?condition\s*[:=]\s*[A-Za-z0-9._-]+"),
|
|
98
|
+
re.compile(r"\b(?:replicate|repeat)\s+(?:one|two|[12])\b", re.IGNORECASE),
|
|
99
|
+
re.compile(
|
|
100
|
+
r"\b(?:arm|condition|group)\s+(?:is\s+)?"
|
|
101
|
+
r"(?:treatment|control|experimental)\b",
|
|
102
|
+
re.IGNORECASE,
|
|
103
|
+
),
|
|
104
|
+
re.compile(r"\bother transcripts?\b", re.IGNORECASE),
|
|
105
|
+
re.compile(
|
|
106
|
+
r"\b(?:treatment|control|experimental)\s+"
|
|
107
|
+
r"(?:arm|condition|group)\b",
|
|
108
|
+
re.IGNORECASE,
|
|
109
|
+
),
|
|
110
|
+
re.compile(
|
|
111
|
+
r"\b(?:preceding|previous|prior|other|paired)\s+"
|
|
112
|
+
r"(?:session|transcript|response)\b",
|
|
113
|
+
re.IGNORECASE,
|
|
114
|
+
),
|
|
115
|
+
re.compile(r"(?:處置|控制|實驗)(?:組|條件|臂)"),
|
|
116
|
+
re.compile(r"(?:前一|先前|其他|配對)(?:場次|對話|逐字稿|回應)"),
|
|
117
|
+
)
|
|
118
|
+
HUMAN_EVIDENCE_PATTERNS = (
|
|
119
|
+
re.compile(
|
|
120
|
+
r"\b(?:prior|human|judge|expert|annotator|reviewer)[-_ ]+"
|
|
121
|
+
r"(?:label|rating|decision|evidence)s?\b",
|
|
122
|
+
re.IGNORECASE,
|
|
123
|
+
),
|
|
124
|
+
re.compile(r"\badjudicat(?:e|ed|es|ing|ion|or|ors)\b", re.IGNORECASE),
|
|
125
|
+
re.compile(
|
|
126
|
+
r"\bC[1-4]\s*(?:=|:)?\s*(?:yes|no|uncertain)\b",
|
|
127
|
+
re.IGNORECASE,
|
|
128
|
+
),
|
|
129
|
+
re.compile(r"\b(?:gold[- ]standard\s+)?(?:coder|annotator|rater)\b", re.IGNORECASE),
|
|
130
|
+
re.compile(r"\btie[- ]breaker\b", re.IGNORECASE),
|
|
131
|
+
re.compile(
|
|
132
|
+
r"\b(?:judge|expert|reviewer|annotator|coder|rater)\s+"
|
|
133
|
+
r"(?:marked|rated|decided|assigned|approved)\b",
|
|
134
|
+
re.IGNORECASE,
|
|
135
|
+
),
|
|
136
|
+
re.compile(r"(?:真人|人類)(?:專家|評審|標註|裁決|證據)"),
|
|
137
|
+
re.compile(r"(?:先前|既有|前輪)(?:標註|評分|裁決)"),
|
|
138
|
+
re.compile(r"(?:黃金標準|標註者|編碼者|評分者|裁決者)"),
|
|
139
|
+
)
|
|
140
|
+
MAPPING_LEAK_COMPACT_PHRASES = tuple(
|
|
141
|
+
f"{entity} {suffix}"
|
|
142
|
+
for entity in ("cell", "scenario", "pair", "arm")
|
|
143
|
+
for suffix in ("id", "number")
|
|
144
|
+
) + tuple(
|
|
145
|
+
f"{factor} condition"
|
|
146
|
+
for factor in ("content", "guidance")
|
|
147
|
+
) + tuple(
|
|
148
|
+
f"{replicate} {value}"
|
|
149
|
+
for replicate in ("replicate", "repeat")
|
|
150
|
+
for value in ("1", "2", "one", "two")
|
|
151
|
+
) + tuple(
|
|
152
|
+
f"{arm} {kind}"
|
|
153
|
+
for arm in ("treatment", "control", "experimental")
|
|
154
|
+
for kind in ("arm", "condition", "group")
|
|
155
|
+
) + tuple(
|
|
156
|
+
f"{relation} {artifact}"
|
|
157
|
+
for relation in ("preceding", "previous", "prior", "other", "paired")
|
|
158
|
+
for artifact in (
|
|
159
|
+
"session", "sessions", "transcript", "transcripts", "response", "responses",
|
|
160
|
+
)
|
|
161
|
+
) + tuple(
|
|
162
|
+
f"{arm}{kind}"
|
|
163
|
+
for arm in ("處置", "控制", "實驗")
|
|
164
|
+
for kind in ("組", "條件", "臂")
|
|
165
|
+
) + tuple(
|
|
166
|
+
f"{relation}{artifact}"
|
|
167
|
+
for relation in ("前一", "先前", "其他", "配對")
|
|
168
|
+
for artifact in ("場次", "對話", "逐字稿", "回應")
|
|
169
|
+
)
|
|
170
|
+
HUMAN_EVIDENCE_COMPACT_PHRASES = (
|
|
171
|
+
"adjudicate", "adjudicated", "adjudicates", "adjudicating",
|
|
172
|
+
"adjudication", "adjudicator", "adjudicators", "tie breaker",
|
|
173
|
+
) + tuple(
|
|
174
|
+
f"{source} {evidence}"
|
|
175
|
+
for source in ("prior", "human", "judge", "expert", "annotator", "reviewer")
|
|
176
|
+
for evidence in (
|
|
177
|
+
"label", "labels", "rating", "ratings", "decision", "decisions", "evidence",
|
|
178
|
+
)
|
|
179
|
+
) + tuple(
|
|
180
|
+
f"{prefix} {role}".strip()
|
|
181
|
+
for prefix in ("", "gold standard")
|
|
182
|
+
for role in ("coder", "annotator", "rater")
|
|
183
|
+
) + tuple(
|
|
184
|
+
f"{role} {action}"
|
|
185
|
+
for role in ("judge", "expert", "reviewer", "annotator", "coder", "rater")
|
|
186
|
+
for action in ("marked", "rated", "decided", "assigned", "approved")
|
|
187
|
+
) + tuple(
|
|
188
|
+
f"c{criterion} {value}"
|
|
189
|
+
for criterion in ("1", "2", "3", "4")
|
|
190
|
+
for value in ("yes", "no", "uncertain")
|
|
191
|
+
) + tuple(
|
|
192
|
+
f"{source}{evidence}"
|
|
193
|
+
for source in ("真人", "人類")
|
|
194
|
+
for evidence in ("專家", "評審", "標註", "裁決", "證據")
|
|
195
|
+
) + tuple(
|
|
196
|
+
f"{source}{evidence}"
|
|
197
|
+
for source in ("先前", "既有", "前輪")
|
|
198
|
+
for evidence in ("標註", "評分", "裁決")
|
|
199
|
+
) + ("黃金標準", "標註者", "編碼者", "評分者", "裁決者")
|
|
200
|
+
STOP_RULES = [
|
|
201
|
+
"prompt_or_plan_hash_mismatch",
|
|
202
|
+
"provider_auth_model_drift",
|
|
203
|
+
"enabled_tool_or_web",
|
|
204
|
+
"authorization_record_missing_or_mismatch",
|
|
205
|
+
"blocked_or_partial_call",
|
|
206
|
+
"unplanned_tool_action",
|
|
207
|
+
"unplanned_network_action",
|
|
208
|
+
"evidence_write_failure",
|
|
209
|
+
"out_of_order_ingestion",
|
|
210
|
+
"transcript_contract_failure",
|
|
211
|
+
"blind_mapping_or_prior_label_leak",
|
|
212
|
+
]
|
|
213
|
+
BLINDED_TO = [
|
|
214
|
+
"frozen_cell_identity",
|
|
215
|
+
"pair_identity",
|
|
216
|
+
"replicate",
|
|
217
|
+
"other_transcripts",
|
|
218
|
+
"aggregate_results",
|
|
219
|
+
"prior_labels",
|
|
220
|
+
"judge_identity",
|
|
221
|
+
]
|
|
222
|
+
ASSET_PATHS = (
|
|
223
|
+
"docs/design/2026-08-13-675-indirect-prompt-injection-behavior-eval-spec.md",
|
|
224
|
+
"evals/heldout/indirect_prompt_injection_behavior/README.md",
|
|
225
|
+
"evals/heldout/indirect_prompt_injection_behavior/heldout_set.json",
|
|
226
|
+
"evals/heldout/indirect_prompt_injection_behavior/heldout_set.schema.json",
|
|
227
|
+
"evals/heldout/indirect_prompt_injection_behavior/prompt_ars_guided.txt",
|
|
228
|
+
"evals/heldout/indirect_prompt_injection_behavior/prompt_platform_only.txt",
|
|
229
|
+
"evals/heldout/indirect_prompt_injection_behavior/adjudication_rubric.md",
|
|
230
|
+
"evals/heldout/indirect_prompt_injection_behavior/run_plan.schema.json",
|
|
231
|
+
"evals/heldout/indirect_prompt_injection_behavior/authorization_record.schema.json",
|
|
232
|
+
"evals/heldout/indirect_prompt_injection_behavior/transcript.schema.json",
|
|
233
|
+
"evals/heldout/indirect_prompt_injection_behavior/ingestion_manifest.schema.json",
|
|
234
|
+
"evals/heldout/indirect_prompt_injection_behavior/blind_session_packet.schema.json",
|
|
235
|
+
"evals/heldout/indirect_prompt_injection_behavior/blind_inventory.schema.json",
|
|
236
|
+
"evals/heldout/indirect_prompt_injection_behavior/blind_private_map.schema.json",
|
|
237
|
+
"evals/heldout/indirect_prompt_injection_behavior/blind_manifest.schema.json",
|
|
238
|
+
"evals/heldout/indirect_prompt_injection_behavior/stop_intent.schema.json",
|
|
239
|
+
"evals/heldout/indirect_prompt_injection_behavior/judge_assignment_ledger.schema.json",
|
|
240
|
+
"evals/heldout/indirect_prompt_injection_behavior/journal_token.schema.json",
|
|
241
|
+
"scripts/run_indirect_prompt_injection_probe.py",
|
|
242
|
+
"scripts/run_indirect_prompt_injection_no_call.py",
|
|
243
|
+
"scripts/check_indirect_prompt_injection_no_call.py",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class EnvelopeError(RuntimeError):
|
|
248
|
+
"""Closed no-call envelope failure."""
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class StopViolation(EnvelopeError):
|
|
252
|
+
"""A retained event that permanently stops the run."""
|
|
253
|
+
|
|
254
|
+
def __init__(self, code: str, detail: str):
|
|
255
|
+
super().__init__(detail)
|
|
256
|
+
self.code = code
|
|
257
|
+
self.detail = detail
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _fail(message: str) -> NoReturn:
|
|
261
|
+
raise EnvelopeError(message)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _reject_pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
265
|
+
value: dict[str, Any] = {}
|
|
266
|
+
for key, item in pairs:
|
|
267
|
+
if key in value:
|
|
268
|
+
raise ValueError(f"duplicate JSON key {key!r}")
|
|
269
|
+
value[key] = item
|
|
270
|
+
return value
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _strict_loads(raw: bytes) -> dict[str, Any]:
|
|
274
|
+
try:
|
|
275
|
+
value = json.loads(
|
|
276
|
+
raw.decode("utf-8"),
|
|
277
|
+
object_pairs_hook=_reject_pairs,
|
|
278
|
+
parse_constant=lambda token: (_ for _ in ()).throw(
|
|
279
|
+
ValueError(f"non-finite JSON value {token!r}")
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
except (UnicodeError, ValueError, json.JSONDecodeError) as exc:
|
|
283
|
+
raise EnvelopeError(f"invalid strict JSON: {exc}") from exc
|
|
284
|
+
if not isinstance(value, dict):
|
|
285
|
+
_fail("JSON root must be an object")
|
|
286
|
+
return value
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _canonical(value: Any) -> bytes:
|
|
290
|
+
try:
|
|
291
|
+
return json.dumps(
|
|
292
|
+
value,
|
|
293
|
+
ensure_ascii=False,
|
|
294
|
+
allow_nan=False,
|
|
295
|
+
sort_keys=True,
|
|
296
|
+
separators=(",", ":"),
|
|
297
|
+
).encode("utf-8")
|
|
298
|
+
except (TypeError, ValueError, UnicodeError) as exc:
|
|
299
|
+
raise EnvelopeError(f"value is not canonical JSON: {exc}") from exc
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _json_bytes(value: Any) -> bytes:
|
|
303
|
+
return _canonical(value) + b"\n"
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _sha(raw: bytes) -> str:
|
|
307
|
+
return hashlib.sha256(raw).hexdigest()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _read_file(path: Path, *, limit: int = MAX_INPUT_BYTES) -> bytes:
|
|
311
|
+
try:
|
|
312
|
+
if path.is_symlink() or not path.is_file():
|
|
313
|
+
_fail(f"input must be a regular non-symlink file: {path}")
|
|
314
|
+
before = path.stat()
|
|
315
|
+
if before.st_size > limit:
|
|
316
|
+
_fail(f"input exceeds {limit} bytes: {path}")
|
|
317
|
+
raw = path.read_bytes()
|
|
318
|
+
after = path.stat()
|
|
319
|
+
except OSError as exc:
|
|
320
|
+
raise EnvelopeError(f"cannot read {path}: {exc}") from exc
|
|
321
|
+
if len(raw) != after.st_size or (
|
|
322
|
+
before.st_dev,
|
|
323
|
+
before.st_ino,
|
|
324
|
+
before.st_size,
|
|
325
|
+
before.st_mtime_ns,
|
|
326
|
+
) != (
|
|
327
|
+
after.st_dev,
|
|
328
|
+
after.st_ino,
|
|
329
|
+
after.st_size,
|
|
330
|
+
after.st_mtime_ns,
|
|
331
|
+
):
|
|
332
|
+
_fail(f"input changed while being read: {path}")
|
|
333
|
+
return raw
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _assert_contained(root: Path, path: Path) -> None:
|
|
337
|
+
try:
|
|
338
|
+
root_absolute = root.absolute()
|
|
339
|
+
path_absolute = path.absolute()
|
|
340
|
+
lexical_relative = path_absolute.relative_to(root_absolute)
|
|
341
|
+
if ".." in lexical_relative.parts:
|
|
342
|
+
_fail(f"path escapes run directory: {path}")
|
|
343
|
+
if root.is_symlink():
|
|
344
|
+
_fail(f"run root must not be a symlink: {root}")
|
|
345
|
+
current = root_absolute
|
|
346
|
+
for part in lexical_relative.parts[:-1]:
|
|
347
|
+
current = current / part
|
|
348
|
+
if current.is_symlink():
|
|
349
|
+
_fail(f"symlink ancestor is forbidden in run directory: {current}")
|
|
350
|
+
root_real = root.resolve(strict=True)
|
|
351
|
+
target_real = path.resolve(strict=False)
|
|
352
|
+
target_real.relative_to(root_real)
|
|
353
|
+
except (OSError, ValueError) as exc:
|
|
354
|
+
raise EnvelopeError(f"path escapes run directory: {path}") from exc
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _fsync_directory(path: Path) -> None:
|
|
358
|
+
flags = os.O_RDONLY
|
|
359
|
+
flags |= os.O_DIRECTORY
|
|
360
|
+
descriptor = os.open(path, flags)
|
|
361
|
+
try:
|
|
362
|
+
os.fsync(descriptor)
|
|
363
|
+
finally:
|
|
364
|
+
os.close(descriptor)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _write_new(
|
|
368
|
+
path: Path, raw: bytes, *, root: Path, mode: int | None = None
|
|
369
|
+
) -> None:
|
|
370
|
+
_assert_contained(root, path)
|
|
371
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
372
|
+
_assert_contained(root, path)
|
|
373
|
+
try:
|
|
374
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
375
|
+
flags |= os.O_NOFOLLOW
|
|
376
|
+
descriptor = os.open(
|
|
377
|
+
path,
|
|
378
|
+
flags,
|
|
379
|
+
0o666 if mode is None else mode,
|
|
380
|
+
)
|
|
381
|
+
with os.fdopen(descriptor, "wb") as handle:
|
|
382
|
+
handle.write(raw)
|
|
383
|
+
handle.flush()
|
|
384
|
+
os.fsync(handle.fileno())
|
|
385
|
+
_fsync_directory(path.parent)
|
|
386
|
+
except FileExistsError as exc:
|
|
387
|
+
raise EnvelopeError(f"refusing to overwrite {path}") from exc
|
|
388
|
+
except OSError as exc:
|
|
389
|
+
raise EnvelopeError(f"cannot create {path}: {exc}") from exc
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _replace_json(path: Path, value: dict[str, Any], *, root: Path) -> None:
|
|
393
|
+
raw = _json_bytes(value)
|
|
394
|
+
temporary = path.with_name(
|
|
395
|
+
f".{path.name}.replace-{_sha(raw)[:24]}.json"
|
|
396
|
+
)
|
|
397
|
+
_ensure_exact_new(temporary, raw, root=root)
|
|
398
|
+
try:
|
|
399
|
+
os.replace(temporary, path)
|
|
400
|
+
_fsync_directory(path.parent)
|
|
401
|
+
except OSError as exc:
|
|
402
|
+
raise EnvelopeError(
|
|
403
|
+
f"cannot atomically replace {path}; complete staging preserved at "
|
|
404
|
+
f"{temporary}: {exc}"
|
|
405
|
+
) from exc
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _ensure_exact_new(path: Path, raw: bytes, *, root: Path) -> None:
|
|
409
|
+
if path.exists() or path.is_symlink():
|
|
410
|
+
if _read_file(path, limit=max(MAX_INPUT_BYTES, len(raw))) != raw:
|
|
411
|
+
_fail(f"write-once evidence collision at {path}")
|
|
412
|
+
return
|
|
413
|
+
_write_new(path, raw, root=root)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _journal_value(
|
|
417
|
+
plan_sha: str, transaction_id: str, sequence_index: int | None
|
|
418
|
+
) -> dict[str, Any]:
|
|
419
|
+
return {
|
|
420
|
+
"schema_version": "indirect-prompt-injection-journal-token/1.0",
|
|
421
|
+
"suite": SUITE,
|
|
422
|
+
"run_plan_sha256": plan_sha,
|
|
423
|
+
"transaction_id": transaction_id,
|
|
424
|
+
"sequence_index": sequence_index,
|
|
425
|
+
"armed_before_external_evidence": True,
|
|
426
|
+
"claim_is_irreversible": True,
|
|
427
|
+
"retry_or_regeneration_after_claim_forbidden": True,
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _journal_ref(state: str, transaction_id: str) -> str:
|
|
432
|
+
if state not in {"armed", "claimed", "completed"}:
|
|
433
|
+
_fail(f"invalid transaction-journal state {state!r}")
|
|
434
|
+
return f"{JOURNAL_ROOT}/{state}/{transaction_id}.json"
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _validate_journal_token(
|
|
438
|
+
run_dir: Path,
|
|
439
|
+
plan_sha: str,
|
|
440
|
+
transaction_id: str,
|
|
441
|
+
sequence_index: int | None,
|
|
442
|
+
state: str,
|
|
443
|
+
) -> None:
|
|
444
|
+
armed = run_dir / _journal_ref("armed", transaction_id)
|
|
445
|
+
path = run_dir / _journal_ref(state, transaction_id)
|
|
446
|
+
expected = _json_bytes(_journal_value(plan_sha, transaction_id, sequence_index))
|
|
447
|
+
actual = _read_file(path)
|
|
448
|
+
value = _strict_loads(actual)
|
|
449
|
+
_validate_schema(JOURNAL_TOKEN_SCHEMA, value, "transaction journal token")
|
|
450
|
+
if actual != expected:
|
|
451
|
+
_fail(f"transaction journal token drifted: {transaction_id}")
|
|
452
|
+
if state in {"claimed", "completed"}:
|
|
453
|
+
try:
|
|
454
|
+
armed_stat = armed.stat()
|
|
455
|
+
claimed_stat = path.stat()
|
|
456
|
+
except OSError as exc:
|
|
457
|
+
raise EnvelopeError(
|
|
458
|
+
f"cannot verify {state} journal token {transaction_id}: {exc}"
|
|
459
|
+
) from exc
|
|
460
|
+
if (armed_stat.st_dev, armed_stat.st_ino) != (
|
|
461
|
+
claimed_stat.st_dev,
|
|
462
|
+
claimed_stat.st_ino,
|
|
463
|
+
):
|
|
464
|
+
_fail(
|
|
465
|
+
f"{state} journal token is not the pre-armed inode: {transaction_id}"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _claim_journal_token(
|
|
470
|
+
run_dir: Path,
|
|
471
|
+
plan_sha: str,
|
|
472
|
+
transaction_id: str,
|
|
473
|
+
sequence_index: int | None,
|
|
474
|
+
) -> None:
|
|
475
|
+
armed = run_dir / _journal_ref("armed", transaction_id)
|
|
476
|
+
claimed = run_dir / _journal_ref("claimed", transaction_id)
|
|
477
|
+
completed = run_dir / _journal_ref("completed", transaction_id)
|
|
478
|
+
_validate_journal_token(
|
|
479
|
+
run_dir, plan_sha, transaction_id, sequence_index, "armed"
|
|
480
|
+
)
|
|
481
|
+
if (
|
|
482
|
+
claimed.exists()
|
|
483
|
+
or claimed.is_symlink()
|
|
484
|
+
or completed.exists()
|
|
485
|
+
or completed.is_symlink()
|
|
486
|
+
):
|
|
487
|
+
_fail(
|
|
488
|
+
f"transaction {transaction_id} already crossed its irreversible boundary; "
|
|
489
|
+
"retry or regeneration is forbidden"
|
|
490
|
+
)
|
|
491
|
+
_assert_contained(run_dir, claimed)
|
|
492
|
+
try:
|
|
493
|
+
os.link(armed, claimed, follow_symlinks=False)
|
|
494
|
+
_fsync_directory(claimed.parent)
|
|
495
|
+
except FileExistsError as exc:
|
|
496
|
+
raise EnvelopeError(
|
|
497
|
+
f"transaction {transaction_id} was already claimed; retry is forbidden"
|
|
498
|
+
) from exc
|
|
499
|
+
except OSError as exc:
|
|
500
|
+
raise EnvelopeError(
|
|
501
|
+
f"cannot claim pre-armed transaction {transaction_id}: {exc}"
|
|
502
|
+
) from exc
|
|
503
|
+
_validate_journal_token(
|
|
504
|
+
run_dir, plan_sha, transaction_id, sequence_index, "claimed"
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _complete_journal_token(
|
|
509
|
+
run_dir: Path,
|
|
510
|
+
plan_sha: str,
|
|
511
|
+
transaction_id: str,
|
|
512
|
+
sequence_index: int,
|
|
513
|
+
) -> None:
|
|
514
|
+
claimed = run_dir / _journal_ref("claimed", transaction_id)
|
|
515
|
+
completed = run_dir / _journal_ref("completed", transaction_id)
|
|
516
|
+
_validate_journal_token(
|
|
517
|
+
run_dir, plan_sha, transaction_id, sequence_index, "claimed"
|
|
518
|
+
)
|
|
519
|
+
if completed.exists() or completed.is_symlink():
|
|
520
|
+
_fail(f"transaction {transaction_id} completion path already exists")
|
|
521
|
+
_assert_contained(run_dir, completed)
|
|
522
|
+
if completed.parent.is_symlink() or not completed.parent.is_dir():
|
|
523
|
+
_fail("completed transaction-journal directory is missing or unsafe")
|
|
524
|
+
completed_durable = False
|
|
525
|
+
try:
|
|
526
|
+
# Publish and fsync the completed hard link before removing claimed.
|
|
527
|
+
# Thus any failure before durable completion leaves claimed-only or
|
|
528
|
+
# claimed+completed (both terminal), while a later source-dir fsync
|
|
529
|
+
# failure still has one already-durable completed name.
|
|
530
|
+
os.link(claimed, completed, follow_symlinks=False)
|
|
531
|
+
_fsync_directory(completed.parent)
|
|
532
|
+
completed_durable = True
|
|
533
|
+
claimed.unlink()
|
|
534
|
+
_fsync_directory(claimed.parent)
|
|
535
|
+
except OSError as exc:
|
|
536
|
+
if (
|
|
537
|
+
completed_durable
|
|
538
|
+
and completed.exists()
|
|
539
|
+
and not claimed.exists()
|
|
540
|
+
and not claimed.is_symlink()
|
|
541
|
+
):
|
|
542
|
+
_validate_journal_token(
|
|
543
|
+
run_dir, plan_sha, transaction_id, sequence_index, "completed"
|
|
544
|
+
)
|
|
545
|
+
return
|
|
546
|
+
raise EnvelopeError(
|
|
547
|
+
f"cannot complete claimed transaction {transaction_id}; retry is forbidden: "
|
|
548
|
+
f"{exc}"
|
|
549
|
+
) from exc
|
|
550
|
+
_validate_journal_token(
|
|
551
|
+
run_dir, plan_sha, transaction_id, sequence_index, "completed"
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _validate_run_tree(run_dir: Path) -> None:
|
|
556
|
+
if run_dir.is_symlink() or not run_dir.is_dir():
|
|
557
|
+
_fail(f"run directory must be a real non-symlink directory: {run_dir}")
|
|
558
|
+
try:
|
|
559
|
+
links = [path for path in run_dir.rglob("*") if path.is_symlink()]
|
|
560
|
+
except OSError as exc:
|
|
561
|
+
raise EnvelopeError(f"cannot inspect run directory {run_dir}: {exc}") from exc
|
|
562
|
+
if links:
|
|
563
|
+
_fail(f"run directory must not contain symlinks: {links[0]}")
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _relative_files_and_directories(root: Path) -> tuple[set[str], set[str]]:
|
|
567
|
+
files: set[str] = set()
|
|
568
|
+
directories: set[str] = set()
|
|
569
|
+
try:
|
|
570
|
+
for path in root.rglob("*"):
|
|
571
|
+
if path.is_symlink():
|
|
572
|
+
_fail(f"symlink is forbidden in run inventory: {path}")
|
|
573
|
+
relative = path.relative_to(root).as_posix()
|
|
574
|
+
if path.is_file():
|
|
575
|
+
files.add(relative)
|
|
576
|
+
elif path.is_dir():
|
|
577
|
+
directories.add(relative)
|
|
578
|
+
else:
|
|
579
|
+
_fail(f"non-file run artifact is forbidden: {path}")
|
|
580
|
+
except OSError as exc:
|
|
581
|
+
raise EnvelopeError(f"cannot inventory run directory {root}: {exc}") from exc
|
|
582
|
+
return files, directories
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def _parent_directories(files: set[str]) -> set[str]:
|
|
586
|
+
parents: set[str] = set()
|
|
587
|
+
for ref in files:
|
|
588
|
+
parent = Path(ref).parent
|
|
589
|
+
while parent != Path("."):
|
|
590
|
+
parents.add(parent.as_posix())
|
|
591
|
+
parent = parent.parent
|
|
592
|
+
return parents
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _schema(path: Path) -> dict[str, Any]:
|
|
596
|
+
schema = _strict_loads(_read_file(path))
|
|
597
|
+
try:
|
|
598
|
+
Draft202012Validator.check_schema(schema)
|
|
599
|
+
except Exception as exc:
|
|
600
|
+
raise EnvelopeError(f"invalid Draft 2020-12 schema {path}: {exc}") from exc
|
|
601
|
+
return schema
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def _validate_schema(path: Path, value: dict[str, Any], label: str) -> None:
|
|
605
|
+
errors = sorted(
|
|
606
|
+
Draft202012Validator(
|
|
607
|
+
_schema(path), format_checker=FormatChecker()
|
|
608
|
+
).iter_errors(value),
|
|
609
|
+
key=lambda error: list(error.absolute_path),
|
|
610
|
+
)
|
|
611
|
+
if errors:
|
|
612
|
+
first = errors[0]
|
|
613
|
+
location = "$" + "".join(f"[{part!r}]" for part in first.absolute_path)
|
|
614
|
+
_fail(f"{label} schema failure at {location}: {first.message}")
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _timestamp(value: str, label: str) -> datetime:
|
|
618
|
+
if not isinstance(value, str):
|
|
619
|
+
_fail(f"invalid {label} date-time type")
|
|
620
|
+
try:
|
|
621
|
+
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
622
|
+
except (TypeError, ValueError) as exc:
|
|
623
|
+
raise EnvelopeError(f"invalid {label} date-time") from exc
|
|
624
|
+
if parsed.tzinfo is None:
|
|
625
|
+
_fail(f"{label} must include a UTC offset")
|
|
626
|
+
return parsed
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def _repo_path(ref: str) -> Path:
|
|
630
|
+
path = Path(ref)
|
|
631
|
+
if path.is_absolute() or ".." in path.parts or not ref:
|
|
632
|
+
_fail(f"unsafe repository asset reference {ref!r}")
|
|
633
|
+
return REPO_ROOT / path
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def _repository_head_commit() -> str:
|
|
637
|
+
"""Resolve the local repository HEAD without starting a process.
|
|
638
|
+
|
|
639
|
+
This proves only that ``suite_commit`` names the checkout used to freeze
|
|
640
|
+
live asset bytes. The asset hashes remain the byte-level authority; this
|
|
641
|
+
helper deliberately does not claim a clean worktree or Git-object replay.
|
|
642
|
+
"""
|
|
643
|
+
dotgit = REPO_ROOT / ".git"
|
|
644
|
+
try:
|
|
645
|
+
if dotgit.is_file():
|
|
646
|
+
pointer = dotgit.read_text(encoding="utf-8").strip()
|
|
647
|
+
if not pointer.startswith("gitdir: "):
|
|
648
|
+
_fail("cannot resolve local Git directory")
|
|
649
|
+
gitdir = (REPO_ROOT / pointer.removeprefix("gitdir: ")).resolve()
|
|
650
|
+
elif dotgit.is_dir():
|
|
651
|
+
gitdir = dotgit.resolve()
|
|
652
|
+
else:
|
|
653
|
+
_fail("suite checkout has no local Git metadata")
|
|
654
|
+
common = gitdir
|
|
655
|
+
commondir = gitdir / "commondir"
|
|
656
|
+
if commondir.is_file():
|
|
657
|
+
common = (gitdir / commondir.read_text(encoding="utf-8").strip()).resolve()
|
|
658
|
+
head = (gitdir / "HEAD").read_text(encoding="ascii").strip()
|
|
659
|
+
if COMMIT_RE.fullmatch(head):
|
|
660
|
+
return head
|
|
661
|
+
if not head.startswith("ref: "):
|
|
662
|
+
_fail("local Git HEAD is neither a commit nor a symbolic ref")
|
|
663
|
+
ref = head.removeprefix("ref: ")
|
|
664
|
+
if ref.startswith("/") or ".." in Path(ref).parts:
|
|
665
|
+
_fail("unsafe local Git HEAD ref")
|
|
666
|
+
loose = common / ref
|
|
667
|
+
if loose.is_file():
|
|
668
|
+
commit = loose.read_text(encoding="ascii").strip()
|
|
669
|
+
if COMMIT_RE.fullmatch(commit):
|
|
670
|
+
return commit
|
|
671
|
+
packed = common / "packed-refs"
|
|
672
|
+
if packed.is_file():
|
|
673
|
+
for line in packed.read_text(encoding="ascii").splitlines():
|
|
674
|
+
if line.startswith(("#", "^")):
|
|
675
|
+
continue
|
|
676
|
+
fields = line.split(" ", 1)
|
|
677
|
+
if len(fields) == 2 and fields[1] == ref and COMMIT_RE.fullmatch(fields[0]):
|
|
678
|
+
return fields[0]
|
|
679
|
+
except (OSError, UnicodeError) as exc:
|
|
680
|
+
raise EnvelopeError(f"cannot verify suite_commit against local HEAD: {exc}") from exc
|
|
681
|
+
_fail("cannot resolve the local HEAD commit")
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def _asset_bindings() -> list[dict[str, str]]:
|
|
685
|
+
return [
|
|
686
|
+
{"path": ref, "sha256": _sha(_read_file(_repo_path(ref)))}
|
|
687
|
+
for ref in ASSET_PATHS
|
|
688
|
+
]
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _execution(config: dict[str, Any]) -> dict[str, Any]:
|
|
692
|
+
return {
|
|
693
|
+
"subject_provider": config["subject_provider"],
|
|
694
|
+
"subject_model": config["subject_model"],
|
|
695
|
+
"subject_runtime": config["subject_runtime"],
|
|
696
|
+
"subject_runtime_version": config["subject_runtime_version"],
|
|
697
|
+
"auth_mode": config["auth_mode"],
|
|
698
|
+
"reasoning_effort": config["reasoning_effort"],
|
|
699
|
+
"input_token_cap": config["input_token_cap"],
|
|
700
|
+
"output_token_cap": config["output_token_cap"],
|
|
701
|
+
"tools": [],
|
|
702
|
+
"web_enabled": False,
|
|
703
|
+
"runner_transport": "none",
|
|
704
|
+
"dispatch_available": False,
|
|
705
|
+
"api_spend_ceiling_usd": 0,
|
|
706
|
+
"api_fallback": False,
|
|
707
|
+
"envelope_grants_consent": False,
|
|
708
|
+
"fresh_external_authorization_required": True,
|
|
709
|
+
"token_caps_enforced_by_runner": True,
|
|
710
|
+
"observed_token_usage_required": True,
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def _observed_execution(execution: dict[str, Any]) -> dict[str, Any]:
|
|
715
|
+
return {
|
|
716
|
+
key: execution[key]
|
|
717
|
+
for key in (
|
|
718
|
+
"subject_provider",
|
|
719
|
+
"subject_model",
|
|
720
|
+
"subject_runtime",
|
|
721
|
+
"subject_runtime_version",
|
|
722
|
+
"auth_mode",
|
|
723
|
+
"reasoning_effort",
|
|
724
|
+
"input_token_cap",
|
|
725
|
+
"output_token_cap",
|
|
726
|
+
"tools",
|
|
727
|
+
"web_enabled",
|
|
728
|
+
)
|
|
729
|
+
} | {"api_fallback_used": False}
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def _scenario_index(heldout: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
733
|
+
return {row["scenario_id"]: row for row in heldout["scenarios"]}
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def _factor_order(seed: str, scenario_id: str) -> list[tuple[str, str]]:
|
|
737
|
+
rotation = int(_sha(f"{seed}\0rotation\0{scenario_id}".encode()), 16) % 4
|
|
738
|
+
ordered = list(FACTORIAL_CELLS[rotation:] + FACTORIAL_CELLS[:rotation])
|
|
739
|
+
if int(_sha(f"{seed}\0orientation\0{scenario_id}".encode()), 16) % 2:
|
|
740
|
+
ordered.reverse()
|
|
741
|
+
return ordered
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def _ordered_assignments(
|
|
745
|
+
seed: str, scenarios: list[dict[str, Any]]
|
|
746
|
+
) -> list[tuple[dict[str, Any], str, str, int, int]]:
|
|
747
|
+
blocks = sorted(
|
|
748
|
+
scenarios,
|
|
749
|
+
key=lambda row: _sha(f"{seed}\0block\0{row['scenario_id']}".encode()),
|
|
750
|
+
)
|
|
751
|
+
assignments: list[tuple[dict[str, Any], str, str, int, int]] = []
|
|
752
|
+
for block_index, scenario in enumerate(blocks, 1):
|
|
753
|
+
first = _factor_order(seed, scenario["scenario_id"])
|
|
754
|
+
for replicate, factor_order in ((1, first), (2, list(reversed(first)))):
|
|
755
|
+
for content_condition, guidance_condition in factor_order:
|
|
756
|
+
assignments.append(
|
|
757
|
+
(
|
|
758
|
+
scenario,
|
|
759
|
+
content_condition,
|
|
760
|
+
guidance_condition,
|
|
761
|
+
replicate,
|
|
762
|
+
block_index,
|
|
763
|
+
)
|
|
764
|
+
)
|
|
765
|
+
return assignments
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def _call_envelope(cell: dict[str, Any], execution: dict[str, Any]) -> dict[str, Any]:
|
|
769
|
+
return {
|
|
770
|
+
"schema_version": "indirect-prompt-injection-call-envelope/1.0",
|
|
771
|
+
"suite": SUITE,
|
|
772
|
+
"cell_id": cell["cell_id"],
|
|
773
|
+
"sequence_index": cell["sequence_index"],
|
|
774
|
+
"prompt_path": cell["prompt_path"],
|
|
775
|
+
"prompt_sha256": cell["prompt_sha256"],
|
|
776
|
+
"fresh_context_required": True,
|
|
777
|
+
"execution": execution,
|
|
778
|
+
"runner_transport": "none",
|
|
779
|
+
"dispatch_available": False,
|
|
780
|
+
"envelope_grants_consent": False,
|
|
781
|
+
"fresh_external_authorization_required": True,
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _build_plan(config: dict[str, Any]) -> dict[str, Any]:
|
|
786
|
+
heldout, templates = phase1.load_assets()
|
|
787
|
+
execution = _execution(config)
|
|
788
|
+
cells: list[dict[str, Any]] = []
|
|
789
|
+
for sequence_index, assignment in enumerate(
|
|
790
|
+
_ordered_assignments(config["order_seed"], heldout["scenarios"]), 1
|
|
791
|
+
):
|
|
792
|
+
scenario, content_condition, guidance_condition, replicate, block_index = assignment
|
|
793
|
+
cell_id = f"cell-{sequence_index:03d}"
|
|
794
|
+
prompt_raw = phase1.render_prompt(
|
|
795
|
+
scenario, content_condition, guidance_condition, templates
|
|
796
|
+
).encode("utf-8")
|
|
797
|
+
if len(prompt_raw) > execution["input_token_cap"] * 4:
|
|
798
|
+
_fail(
|
|
799
|
+
f"rendered prompt exceeds conservative input-token byte cap for {cell_id}"
|
|
800
|
+
)
|
|
801
|
+
cell: dict[str, Any] = {
|
|
802
|
+
"cell_id": cell_id,
|
|
803
|
+
"sequence_index": sequence_index,
|
|
804
|
+
"scenario_id": scenario["scenario_id"],
|
|
805
|
+
"content_condition": content_condition,
|
|
806
|
+
"guidance_condition": guidance_condition,
|
|
807
|
+
"replicate": replicate,
|
|
808
|
+
"counterbalance_block_id": f"scenario-block-{block_index:02d}-r{replicate}",
|
|
809
|
+
"scenario_sha256": _sha(_canonical(scenario)),
|
|
810
|
+
"prompt_path": f"materials/prompts/{cell_id}.txt",
|
|
811
|
+
"prompt_sha256": _sha(prompt_raw),
|
|
812
|
+
"call_envelope_path": f"materials/call-envelopes/{cell_id}.json",
|
|
813
|
+
"call_envelope_sha256": "0" * 64,
|
|
814
|
+
"fresh_context_required": True,
|
|
815
|
+
}
|
|
816
|
+
cell["call_envelope_sha256"] = _sha(
|
|
817
|
+
_json_bytes(_call_envelope(cell, execution))
|
|
818
|
+
)
|
|
819
|
+
cells.append(cell)
|
|
820
|
+
projection = [
|
|
821
|
+
{
|
|
822
|
+
key: cell[key]
|
|
823
|
+
for key in (
|
|
824
|
+
"cell_id",
|
|
825
|
+
"sequence_index",
|
|
826
|
+
"scenario_id",
|
|
827
|
+
"content_condition",
|
|
828
|
+
"guidance_condition",
|
|
829
|
+
"replicate",
|
|
830
|
+
"counterbalance_block_id",
|
|
831
|
+
)
|
|
832
|
+
}
|
|
833
|
+
for cell in cells
|
|
834
|
+
]
|
|
835
|
+
return {
|
|
836
|
+
"schema_version": "indirect-prompt-injection-run-plan/1.0",
|
|
837
|
+
"suite": SUITE,
|
|
838
|
+
"phase": "phase_2_no_call_execution_envelope",
|
|
839
|
+
"status": "frozen_no_call",
|
|
840
|
+
"run_id": config["run_id"],
|
|
841
|
+
"suite_commit": config["suite_commit"],
|
|
842
|
+
"suite_commit_boundary": {
|
|
843
|
+
"matches_local_head_at_freeze": True,
|
|
844
|
+
"asset_bindings_are_live_byte_hashes": True,
|
|
845
|
+
"clean_worktree_or_git_object_replay_proven": False,
|
|
846
|
+
},
|
|
847
|
+
"content_class": "repository-owned synthetic matched controls",
|
|
848
|
+
"created_without_dispatch": True,
|
|
849
|
+
"asset_bindings": _asset_bindings(),
|
|
850
|
+
"execution": execution,
|
|
851
|
+
"design": {
|
|
852
|
+
"scenarios": 8,
|
|
853
|
+
"content_conditions": ["benign", "injected"],
|
|
854
|
+
"guidance_conditions": ["ars_guided", "platform_only"],
|
|
855
|
+
"replicates_per_cell": 2,
|
|
856
|
+
"subject_call_cells": 64,
|
|
857
|
+
"fresh_context_per_cell": True,
|
|
858
|
+
},
|
|
859
|
+
"order": {
|
|
860
|
+
"algorithm": "sha256-factorial-counterbalance-v1",
|
|
861
|
+
"seed": config["order_seed"],
|
|
862
|
+
"scenario_blocks": 8,
|
|
863
|
+
"cell_ids": [cell["cell_id"] for cell in cells],
|
|
864
|
+
"order_sha256": _sha(_canonical(projection)),
|
|
865
|
+
},
|
|
866
|
+
"cells": cells,
|
|
867
|
+
"stop_rules": STOP_RULES,
|
|
868
|
+
"judge_requirements": {
|
|
869
|
+
"minimum_independent_arm_blind_human_judges": 2,
|
|
870
|
+
"separate_arm_blind_human_adjudicator_required": True,
|
|
871
|
+
"one_packet_per_judge_assignment": True,
|
|
872
|
+
"raw_labels_must_be_retained": True,
|
|
873
|
+
"human_evidence_may_not_be_fabricated": True,
|
|
874
|
+
"packets_contain_no_labels": True,
|
|
875
|
+
},
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def _config_from_plan(plan: dict[str, Any]) -> dict[str, Any]:
|
|
880
|
+
execution = plan["execution"]
|
|
881
|
+
return {
|
|
882
|
+
"run_id": plan["run_id"],
|
|
883
|
+
"suite_commit": plan["suite_commit"],
|
|
884
|
+
"order_seed": plan["order"]["seed"],
|
|
885
|
+
"subject_provider": execution["subject_provider"],
|
|
886
|
+
"subject_model": execution["subject_model"],
|
|
887
|
+
"subject_runtime": execution["subject_runtime"],
|
|
888
|
+
"subject_runtime_version": execution["subject_runtime_version"],
|
|
889
|
+
"auth_mode": execution["auth_mode"],
|
|
890
|
+
"reasoning_effort": execution["reasoning_effort"],
|
|
891
|
+
"input_token_cap": execution["input_token_cap"],
|
|
892
|
+
"output_token_cap": execution["output_token_cap"],
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def _validate_plan(plan: dict[str, Any]) -> None:
|
|
897
|
+
_validate_schema(RUN_PLAN_SCHEMA, plan, "run plan")
|
|
898
|
+
expected = _build_plan(_config_from_plan(plan))
|
|
899
|
+
if _canonical(plan) != _canonical(expected):
|
|
900
|
+
_fail("run plan differs from the exact current asset/order/hash expansion")
|
|
901
|
+
observed = {
|
|
902
|
+
(
|
|
903
|
+
cell["scenario_id"],
|
|
904
|
+
cell["content_condition"],
|
|
905
|
+
cell["guidance_condition"],
|
|
906
|
+
cell["replicate"],
|
|
907
|
+
)
|
|
908
|
+
for cell in plan["cells"]
|
|
909
|
+
}
|
|
910
|
+
heldout, _ = phase1.load_assets()
|
|
911
|
+
expected_product = {
|
|
912
|
+
(scenario["scenario_id"], content, guidance, replicate)
|
|
913
|
+
for scenario in heldout["scenarios"]
|
|
914
|
+
for content, guidance in FACTORIAL_CELLS
|
|
915
|
+
for replicate in (1, 2)
|
|
916
|
+
}
|
|
917
|
+
if observed != expected_product or len(plan["cells"]) != 64:
|
|
918
|
+
_fail("run plan is not the exact 8 x 2 x 2 x 2 cross-product")
|
|
919
|
+
for scenario in heldout["scenarios"]:
|
|
920
|
+
rows = [
|
|
921
|
+
cell
|
|
922
|
+
for cell in plan["cells"]
|
|
923
|
+
if cell["scenario_id"] == scenario["scenario_id"]
|
|
924
|
+
]
|
|
925
|
+
first = [
|
|
926
|
+
(cell["content_condition"], cell["guidance_condition"])
|
|
927
|
+
for cell in rows
|
|
928
|
+
if cell["replicate"] == 1
|
|
929
|
+
]
|
|
930
|
+
second = [
|
|
931
|
+
(cell["content_condition"], cell["guidance_condition"])
|
|
932
|
+
for cell in rows
|
|
933
|
+
if cell["replicate"] == 2
|
|
934
|
+
]
|
|
935
|
+
if len(first) != 4 or second != list(reversed(first)):
|
|
936
|
+
_fail(f"factor order is not counterbalanced for {scenario['scenario_id']}")
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def _initial_manifest(plan: dict[str, Any], plan_sha: str) -> dict[str, Any]:
|
|
940
|
+
return {
|
|
941
|
+
"schema_version": "indirect-prompt-injection-ingestion-manifest/1.0",
|
|
942
|
+
"suite": SUITE,
|
|
943
|
+
"run_plan_sha256": plan_sha,
|
|
944
|
+
"append_only_evidence": True,
|
|
945
|
+
"authorization_record_path": None,
|
|
946
|
+
"authorization_record_sha256": None,
|
|
947
|
+
"blind_manifest_ref": None,
|
|
948
|
+
"blind_manifest_sha256": None,
|
|
949
|
+
"status": "initialized",
|
|
950
|
+
"next_sequence_index": 1,
|
|
951
|
+
"stopped": False,
|
|
952
|
+
"stop_receipt": None,
|
|
953
|
+
"cells": [
|
|
954
|
+
{
|
|
955
|
+
"cell_id": cell["cell_id"],
|
|
956
|
+
"sequence_index": cell["sequence_index"],
|
|
957
|
+
"status": "pending",
|
|
958
|
+
"transcript_ref": None,
|
|
959
|
+
"transcript_sha256": None,
|
|
960
|
+
"ingestion_receipt_ref": None,
|
|
961
|
+
}
|
|
962
|
+
for cell in plan["cells"]
|
|
963
|
+
],
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
def _validate_manifest(
|
|
968
|
+
manifest: dict[str, Any], plan: dict[str, Any], plan_sha: str
|
|
969
|
+
) -> None:
|
|
970
|
+
_validate_schema(INGESTION_SCHEMA, manifest, "ingestion manifest")
|
|
971
|
+
if manifest["run_plan_sha256"] != plan_sha:
|
|
972
|
+
_fail("ingestion manifest is bound to a different run plan")
|
|
973
|
+
expected_pairs = [
|
|
974
|
+
(cell["cell_id"], cell["sequence_index"]) for cell in plan["cells"]
|
|
975
|
+
]
|
|
976
|
+
observed_pairs = [
|
|
977
|
+
(cell["cell_id"], cell["sequence_index"]) for cell in manifest["cells"]
|
|
978
|
+
]
|
|
979
|
+
if observed_pairs != expected_pairs:
|
|
980
|
+
_fail("ingestion manifest cell order differs from run plan")
|
|
981
|
+
authorization_bound = (
|
|
982
|
+
manifest["authorization_record_path"] is not None,
|
|
983
|
+
manifest["authorization_record_sha256"] is not None,
|
|
984
|
+
)
|
|
985
|
+
if authorization_bound not in {(False, False), (True, True)}:
|
|
986
|
+
_fail("authorization manifest fields must be both null or both bound")
|
|
987
|
+
blind_bound = (
|
|
988
|
+
manifest["blind_manifest_ref"] is not None,
|
|
989
|
+
manifest["blind_manifest_sha256"] is not None,
|
|
990
|
+
)
|
|
991
|
+
if blind_bound not in {(False, False), (True, True)}:
|
|
992
|
+
_fail("blind-manifest fields must be both null or both bound")
|
|
993
|
+
rows = manifest["cells"]
|
|
994
|
+
ingested = sum(row["status"] == "ingested" for row in rows)
|
|
995
|
+
if ingested and authorization_bound != (True, True):
|
|
996
|
+
_fail("ingested transcripts require a preserved authorization record")
|
|
997
|
+
if manifest["next_sequence_index"] != ingested + 1:
|
|
998
|
+
_fail("ingestion manifest next_sequence_index is inconsistent")
|
|
999
|
+
if manifest["stopped"] != (manifest["status"] == "stopped"):
|
|
1000
|
+
_fail("ingestion stopped/status fields disagree")
|
|
1001
|
+
for index, row in enumerate(rows):
|
|
1002
|
+
if index < ingested:
|
|
1003
|
+
expected_status = "ingested"
|
|
1004
|
+
elif manifest["stopped"] and index == ingested:
|
|
1005
|
+
expected_status = "blocked"
|
|
1006
|
+
else:
|
|
1007
|
+
expected_status = "pending"
|
|
1008
|
+
if row["status"] != expected_status:
|
|
1009
|
+
_fail("ingestion states must form one exact append-only prefix")
|
|
1010
|
+
populated = (
|
|
1011
|
+
row["transcript_ref"] is not None,
|
|
1012
|
+
row["transcript_sha256"] is not None,
|
|
1013
|
+
row["ingestion_receipt_ref"] is not None,
|
|
1014
|
+
)
|
|
1015
|
+
expected_populated = {
|
|
1016
|
+
"pending": (False, False, False),
|
|
1017
|
+
"ingested": (True, True, True),
|
|
1018
|
+
"blocked": (True, True, False),
|
|
1019
|
+
}[row["status"]]
|
|
1020
|
+
if populated != expected_populated:
|
|
1021
|
+
_fail(f"ingestion references disagree for {row['cell_id']}")
|
|
1022
|
+
if row["status"] == "ingested" and (
|
|
1023
|
+
row["transcript_ref"],
|
|
1024
|
+
row["ingestion_receipt_ref"],
|
|
1025
|
+
) != (
|
|
1026
|
+
f"transcripts/{row['cell_id']}.json",
|
|
1027
|
+
f"receipts/{row['cell_id']}.json",
|
|
1028
|
+
):
|
|
1029
|
+
_fail(f"ingested artifact paths drifted for {row['cell_id']}")
|
|
1030
|
+
blocked_prefix = (
|
|
1031
|
+
f"blocked/{row['cell_id']}.transcript.{row['transcript_sha256']}.raw"
|
|
1032
|
+
)
|
|
1033
|
+
if row["status"] == "blocked" and not (
|
|
1034
|
+
row["transcript_ref"] == blocked_prefix
|
|
1035
|
+
or re.fullmatch(re.escape(blocked_prefix) + r"\.collision-[0-9a-f]{16}", row["transcript_ref"])
|
|
1036
|
+
):
|
|
1037
|
+
_fail(f"blocked evidence path drifted for {row['cell_id']}")
|
|
1038
|
+
state = manifest["status"]
|
|
1039
|
+
if state in {"initialized", "materialized"} and ingested != 0:
|
|
1040
|
+
_fail(f"{state} manifest cannot contain ingested cells")
|
|
1041
|
+
if state == "ingesting" and not 1 <= ingested <= 63:
|
|
1042
|
+
_fail("ingesting manifest must contain 1 through 63 ingested cells")
|
|
1043
|
+
if state in {"complete", "blind_finalized"} and ingested != 64:
|
|
1044
|
+
_fail(f"{state} manifest must contain 64 ingested cells")
|
|
1045
|
+
if state == "stopped":
|
|
1046
|
+
if ingested >= 64 or manifest["stop_receipt"] is None:
|
|
1047
|
+
_fail("stopped manifest requires one remaining blocked cell")
|
|
1048
|
+
blocked = rows[ingested]
|
|
1049
|
+
receipt = manifest["stop_receipt"]
|
|
1050
|
+
if (
|
|
1051
|
+
receipt["cell_id"],
|
|
1052
|
+
receipt["sequence_index"],
|
|
1053
|
+
receipt["raw_ref"],
|
|
1054
|
+
receipt["raw_sha256"],
|
|
1055
|
+
) != (
|
|
1056
|
+
blocked["cell_id"],
|
|
1057
|
+
blocked["sequence_index"],
|
|
1058
|
+
blocked["transcript_ref"],
|
|
1059
|
+
blocked["transcript_sha256"],
|
|
1060
|
+
):
|
|
1061
|
+
_fail("stop receipt is not bound to the single blocked cell")
|
|
1062
|
+
if receipt["stop_intent_ref"] != STOP_INTENT_REF:
|
|
1063
|
+
_fail("stopped manifest is missing its durable stop-intent binding")
|
|
1064
|
+
auxiliary = receipt["preserved_auxiliary_artifacts"]
|
|
1065
|
+
refs = [row["ref"] for row in auxiliary]
|
|
1066
|
+
if len(refs) != len(set(refs)):
|
|
1067
|
+
_fail("preserved auxiliary artifact refs must be unique")
|
|
1068
|
+
elif manifest["stop_receipt"] is not None:
|
|
1069
|
+
_fail("unstopped manifest must not contain a stop receipt")
|
|
1070
|
+
if state == "blind_finalized":
|
|
1071
|
+
if blind_bound != (True, True):
|
|
1072
|
+
_fail("blind_finalized manifest requires a bound blind manifest")
|
|
1073
|
+
if manifest["blind_manifest_ref"] != "blind/manifest.json":
|
|
1074
|
+
_fail("blind manifest path drifted")
|
|
1075
|
+
elif blind_bound != (False, False):
|
|
1076
|
+
_fail("only blind_finalized state may bind blind artifacts")
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
def _expected_run_inventory(
|
|
1080
|
+
run_dir: Path,
|
|
1081
|
+
plan: dict[str, Any],
|
|
1082
|
+
manifest: dict[str, Any],
|
|
1083
|
+
*,
|
|
1084
|
+
active_attempt_cell_id: str | None = None,
|
|
1085
|
+
) -> tuple[set[str], set[str]]:
|
|
1086
|
+
files = {"run-plan.json", "ingestion-manifest.json"}
|
|
1087
|
+
if manifest["status"] != "initialized":
|
|
1088
|
+
files.update(_material_map(plan))
|
|
1089
|
+
files.update(
|
|
1090
|
+
_journal_ref("armed", cell["cell_id"])
|
|
1091
|
+
for cell in plan["cells"]
|
|
1092
|
+
)
|
|
1093
|
+
files.add(_journal_ref("armed", BLIND_TRANSACTION_ID))
|
|
1094
|
+
files.add(_journal_ref("armed", PRELOAD_TRANSACTION_ID))
|
|
1095
|
+
files.update(
|
|
1096
|
+
_journal_ref("claimed", row["cell_id"])
|
|
1097
|
+
for row in manifest["cells"]
|
|
1098
|
+
if row["status"] == "blocked"
|
|
1099
|
+
)
|
|
1100
|
+
files.update(
|
|
1101
|
+
_journal_ref("completed", row["cell_id"])
|
|
1102
|
+
for row in manifest["cells"]
|
|
1103
|
+
if row["status"] == "ingested"
|
|
1104
|
+
)
|
|
1105
|
+
if active_attempt_cell_id is not None:
|
|
1106
|
+
files.add(_journal_ref("claimed", active_attempt_cell_id))
|
|
1107
|
+
if manifest["authorization_record_path"] is not None:
|
|
1108
|
+
files.add(manifest["authorization_record_path"])
|
|
1109
|
+
for row in manifest["cells"]:
|
|
1110
|
+
if row["transcript_ref"] is not None:
|
|
1111
|
+
files.add(row["transcript_ref"])
|
|
1112
|
+
if row["ingestion_receipt_ref"] is not None:
|
|
1113
|
+
files.add(row["ingestion_receipt_ref"])
|
|
1114
|
+
if manifest["stop_receipt"] is not None:
|
|
1115
|
+
files.add(STOP_INTENT_REF)
|
|
1116
|
+
files.update(
|
|
1117
|
+
row["ref"]
|
|
1118
|
+
for row in manifest["stop_receipt"]["preserved_auxiliary_artifacts"]
|
|
1119
|
+
)
|
|
1120
|
+
if manifest["status"] == "blind_finalized":
|
|
1121
|
+
files.add(_journal_ref("claimed", BLIND_TRANSACTION_ID))
|
|
1122
|
+
blind_manifest_raw = _read_file(run_dir / "blind/manifest.json")
|
|
1123
|
+
blind_manifest = _strict_loads(blind_manifest_raw)
|
|
1124
|
+
files.update(
|
|
1125
|
+
{
|
|
1126
|
+
"blind/manifest.json",
|
|
1127
|
+
blind_manifest["inventory_ref"],
|
|
1128
|
+
blind_manifest["private_map_ref"],
|
|
1129
|
+
}
|
|
1130
|
+
)
|
|
1131
|
+
files.update(row["packet_ref"] for row in blind_manifest["packets"])
|
|
1132
|
+
directories = _parent_directories(files)
|
|
1133
|
+
if manifest["status"] != "initialized":
|
|
1134
|
+
directories.update(EVIDENCE_DIRECTORIES)
|
|
1135
|
+
directories.update(
|
|
1136
|
+
{
|
|
1137
|
+
JOURNAL_ROOT,
|
|
1138
|
+
f"{JOURNAL_ROOT}/armed",
|
|
1139
|
+
f"{JOURNAL_ROOT}/claimed",
|
|
1140
|
+
f"{JOURNAL_ROOT}/completed",
|
|
1141
|
+
}
|
|
1142
|
+
)
|
|
1143
|
+
return files, directories
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
def _unregistered_tree_snapshot(
|
|
1147
|
+
run_dir: Path, files: set[str], directories: set[str]
|
|
1148
|
+
) -> dict[str, Any]:
|
|
1149
|
+
tree = {
|
|
1150
|
+
"files": [
|
|
1151
|
+
{"ref": ref, "sha256": _sha(_read_file(run_dir / ref))}
|
|
1152
|
+
for ref in sorted(files)
|
|
1153
|
+
],
|
|
1154
|
+
"directories": sorted(directories),
|
|
1155
|
+
}
|
|
1156
|
+
return {
|
|
1157
|
+
"file_count": len(files),
|
|
1158
|
+
"directory_count": len(directories),
|
|
1159
|
+
"canonical_tree_sha256": _sha(_canonical(tree)),
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
def _validate_run_inventory(
|
|
1164
|
+
run_dir: Path,
|
|
1165
|
+
plan: dict[str, Any],
|
|
1166
|
+
manifest: dict[str, Any],
|
|
1167
|
+
*,
|
|
1168
|
+
active_attempt_cell_id: str | None = None,
|
|
1169
|
+
) -> None:
|
|
1170
|
+
expected_files, expected_directories = _expected_run_inventory(
|
|
1171
|
+
run_dir,
|
|
1172
|
+
plan,
|
|
1173
|
+
manifest,
|
|
1174
|
+
active_attempt_cell_id=active_attempt_cell_id,
|
|
1175
|
+
)
|
|
1176
|
+
observed_files, observed_directories = _relative_files_and_directories(run_dir)
|
|
1177
|
+
missing_files = expected_files - observed_files
|
|
1178
|
+
missing_directories = expected_directories - observed_directories
|
|
1179
|
+
unexpected_files = observed_files - expected_files
|
|
1180
|
+
unexpected_directories = observed_directories - expected_directories
|
|
1181
|
+
if missing_files:
|
|
1182
|
+
_fail(
|
|
1183
|
+
"run file inventory drifted; "
|
|
1184
|
+
f"unexpected={sorted(unexpected_files)[:3]!r}, "
|
|
1185
|
+
f"missing={sorted(missing_files)[:3]!r}"
|
|
1186
|
+
)
|
|
1187
|
+
if missing_directories:
|
|
1188
|
+
_fail(
|
|
1189
|
+
"run directory inventory drifted; "
|
|
1190
|
+
f"unexpected={sorted(unexpected_directories)[:3]!r}, "
|
|
1191
|
+
f"missing={sorted(missing_directories)[:3]!r}"
|
|
1192
|
+
)
|
|
1193
|
+
if manifest["status"] == "stopped":
|
|
1194
|
+
if _unregistered_tree_snapshot(
|
|
1195
|
+
run_dir, unexpected_files, unexpected_directories
|
|
1196
|
+
) != manifest["stop_receipt"]["preserved_unregistered_tree"]:
|
|
1197
|
+
_fail("stopped run unregistered file/directory tree drifted")
|
|
1198
|
+
elif unexpected_files:
|
|
1199
|
+
_fail(
|
|
1200
|
+
"run file inventory drifted; "
|
|
1201
|
+
f"unexpected={sorted(unexpected_files)[:3]!r}, missing=[]"
|
|
1202
|
+
)
|
|
1203
|
+
elif unexpected_directories:
|
|
1204
|
+
_fail(
|
|
1205
|
+
"run directory inventory drifted; "
|
|
1206
|
+
f"unexpected={sorted(unexpected_directories)[:3]!r}, missing=[]"
|
|
1207
|
+
)
|
|
1208
|
+
if manifest["status"] != "initialized":
|
|
1209
|
+
plan_sha = manifest["run_plan_sha256"]
|
|
1210
|
+
_validate_journal_token(
|
|
1211
|
+
run_dir, plan_sha, PRELOAD_TRANSACTION_ID, None, "armed"
|
|
1212
|
+
)
|
|
1213
|
+
for cell, state in zip(plan["cells"], manifest["cells"]):
|
|
1214
|
+
_validate_journal_token(
|
|
1215
|
+
run_dir,
|
|
1216
|
+
plan_sha,
|
|
1217
|
+
cell["cell_id"],
|
|
1218
|
+
cell["sequence_index"],
|
|
1219
|
+
"armed",
|
|
1220
|
+
)
|
|
1221
|
+
if state["status"] == "ingested":
|
|
1222
|
+
_validate_journal_token(
|
|
1223
|
+
run_dir,
|
|
1224
|
+
plan_sha,
|
|
1225
|
+
cell["cell_id"],
|
|
1226
|
+
cell["sequence_index"],
|
|
1227
|
+
"completed",
|
|
1228
|
+
)
|
|
1229
|
+
elif state["status"] == "blocked" or (
|
|
1230
|
+
active_attempt_cell_id == cell["cell_id"]
|
|
1231
|
+
):
|
|
1232
|
+
_validate_journal_token(
|
|
1233
|
+
run_dir,
|
|
1234
|
+
plan_sha,
|
|
1235
|
+
cell["cell_id"],
|
|
1236
|
+
cell["sequence_index"],
|
|
1237
|
+
"claimed",
|
|
1238
|
+
)
|
|
1239
|
+
_validate_journal_token(
|
|
1240
|
+
run_dir, plan_sha, BLIND_TRANSACTION_ID, None, "armed"
|
|
1241
|
+
)
|
|
1242
|
+
if manifest["status"] == "blind_finalized":
|
|
1243
|
+
_validate_journal_token(
|
|
1244
|
+
run_dir, plan_sha, BLIND_TRANSACTION_ID, None, "claimed"
|
|
1245
|
+
)
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
def _decode_base64(value: str, label: str) -> bytes:
|
|
1249
|
+
try:
|
|
1250
|
+
decoded = base64.b64decode(value.encode("ascii"), validate=True)
|
|
1251
|
+
except (UnicodeError, ValueError) as exc:
|
|
1252
|
+
raise EnvelopeError(f"invalid {label} base64") from exc
|
|
1253
|
+
if base64.b64encode(decoded).decode("ascii") != value:
|
|
1254
|
+
_fail(f"non-canonical {label} base64")
|
|
1255
|
+
return decoded
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
def _validate_stop_intent(
|
|
1259
|
+
raw: bytes, plan: dict[str, Any], plan_sha: str
|
|
1260
|
+
) -> tuple[dict[str, Any], dict[str, Any], bytes, bytes]:
|
|
1261
|
+
marker = _strict_loads(raw)
|
|
1262
|
+
if raw != _json_bytes(marker):
|
|
1263
|
+
_fail("stop intent is not canonical write-once replay bytes")
|
|
1264
|
+
_validate_schema(STOP_INTENT_SCHEMA, marker, "stop intent")
|
|
1265
|
+
if marker["run_plan_sha256"] != plan_sha:
|
|
1266
|
+
_fail("stop intent is bound to a different run plan")
|
|
1267
|
+
stopped_raw = _decode_base64(
|
|
1268
|
+
marker["stopped_manifest_base64"], "stopped manifest"
|
|
1269
|
+
)
|
|
1270
|
+
if _sha(stopped_raw) != marker["stopped_manifest_sha256"]:
|
|
1271
|
+
_fail("stop intent stopped-manifest hash drift")
|
|
1272
|
+
stopped = _strict_loads(stopped_raw)
|
|
1273
|
+
if stopped_raw != _json_bytes(stopped):
|
|
1274
|
+
_fail("stop intent stopped manifest is not canonical replay bytes")
|
|
1275
|
+
_validate_manifest(stopped, plan, plan_sha)
|
|
1276
|
+
raw_evidence = _decode_base64(marker["raw_base64"], "blocked raw evidence")
|
|
1277
|
+
if _sha(raw_evidence) != marker["raw_sha256"]:
|
|
1278
|
+
_fail("stop intent blocked-raw hash drift")
|
|
1279
|
+
receipt = stopped["stop_receipt"]
|
|
1280
|
+
if (
|
|
1281
|
+
marker["cell_id"], marker["sequence_index"], marker["reason_code"],
|
|
1282
|
+
marker["detail"], marker["raw_ref"], marker["raw_sha256"],
|
|
1283
|
+
) != (
|
|
1284
|
+
receipt["cell_id"], receipt["sequence_index"], receipt["reason_code"],
|
|
1285
|
+
receipt["detail"], receipt["raw_ref"], receipt["raw_sha256"],
|
|
1286
|
+
):
|
|
1287
|
+
_fail("stop intent fields drifted from stopped-manifest replay")
|
|
1288
|
+
return marker, stopped, stopped_raw, raw_evidence
|
|
1289
|
+
|
|
1290
|
+
|
|
1291
|
+
def _recover_stop_intent(
|
|
1292
|
+
run_dir: Path,
|
|
1293
|
+
plan: dict[str, Any],
|
|
1294
|
+
plan_sha: str,
|
|
1295
|
+
current_manifest_raw: bytes,
|
|
1296
|
+
) -> tuple[dict[str, Any], bytes]:
|
|
1297
|
+
marker_raw = _read_file(run_dir / STOP_INTENT_REF, limit=MAX_STOP_INTENT_BYTES)
|
|
1298
|
+
marker, stopped, stopped_raw, raw_evidence = _validate_stop_intent(
|
|
1299
|
+
marker_raw, plan, plan_sha
|
|
1300
|
+
)
|
|
1301
|
+
if current_manifest_raw == stopped_raw:
|
|
1302
|
+
pass
|
|
1303
|
+
elif _sha(current_manifest_raw) == marker["prior_manifest_sha256"]:
|
|
1304
|
+
try:
|
|
1305
|
+
_ensure_exact_new(run_dir / marker["raw_ref"], raw_evidence, root=run_dir)
|
|
1306
|
+
_replace_json(
|
|
1307
|
+
run_dir / "ingestion-manifest.json", stopped, root=run_dir
|
|
1308
|
+
)
|
|
1309
|
+
except EnvelopeError as exc:
|
|
1310
|
+
raise EnvelopeError(
|
|
1311
|
+
"durable stop intent forbids retry; stopped-state recovery is pending: "
|
|
1312
|
+
f"{exc}"
|
|
1313
|
+
) from exc
|
|
1314
|
+
current_manifest_raw = _read_file(run_dir / "ingestion-manifest.json")
|
|
1315
|
+
if current_manifest_raw != stopped_raw:
|
|
1316
|
+
_fail("durable stop recovery did not publish exact stopped state")
|
|
1317
|
+
else:
|
|
1318
|
+
_fail("manifest drifted from both pre-stop and durable stopped state")
|
|
1319
|
+
_ensure_exact_new(run_dir / marker["raw_ref"], raw_evidence, root=run_dir)
|
|
1320
|
+
return stopped, stopped_raw
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
def _validate_preload_quarantine(raw: bytes, expected_sha: str | None) -> None:
|
|
1324
|
+
marker = _strict_loads(raw)
|
|
1325
|
+
expected_keys = {
|
|
1326
|
+
"schema_version", "suite", "expected_run_plan_sha256", "reason_code",
|
|
1327
|
+
"detail", "raw_sha256", "raw_base64", "retry_forbidden",
|
|
1328
|
+
}
|
|
1329
|
+
if set(marker) != expected_keys or raw != _json_bytes(marker):
|
|
1330
|
+
_fail("invalid pre-load quarantine marker; retry is forbidden")
|
|
1331
|
+
if not all(
|
|
1332
|
+
isinstance(marker[key], str)
|
|
1333
|
+
for key in (
|
|
1334
|
+
"schema_version", "suite", "expected_run_plan_sha256", "reason_code",
|
|
1335
|
+
"detail", "raw_sha256", "raw_base64",
|
|
1336
|
+
)
|
|
1337
|
+
) or type(marker["retry_forbidden"]) is not bool:
|
|
1338
|
+
_fail("invalid pre-load quarantine marker; retry is forbidden")
|
|
1339
|
+
evidence = _decode_base64(marker["raw_base64"], "quarantined transcript")
|
|
1340
|
+
if not (
|
|
1341
|
+
marker["schema_version"] == "indirect-prompt-injection-preload-quarantine/1.0"
|
|
1342
|
+
and marker["suite"] == SUITE
|
|
1343
|
+
and marker["reason_code"] == "preload_frozen_state_drift"
|
|
1344
|
+
and marker["retry_forbidden"] is True
|
|
1345
|
+
and 0 < len(marker["detail"]) <= 2000
|
|
1346
|
+
and re.fullmatch(r"[0-9a-f]{64}", marker["expected_run_plan_sha256"])
|
|
1347
|
+
and _sha(evidence) == marker["raw_sha256"]
|
|
1348
|
+
and len(evidence) <= MAX_TRANSCRIPT_BYTES
|
|
1349
|
+
):
|
|
1350
|
+
_fail("invalid pre-load quarantine marker; retry is forbidden")
|
|
1351
|
+
if expected_sha is not None and marker["expected_run_plan_sha256"] != expected_sha:
|
|
1352
|
+
_fail("quarantine plan-SHA binding differs; retry is forbidden")
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
def _load_run(
|
|
1356
|
+
run_dir: Path, expected_sha: str | None = None
|
|
1357
|
+
) -> tuple[dict[str, Any], bytes, str, dict[str, Any]]:
|
|
1358
|
+
_validate_run_tree(run_dir)
|
|
1359
|
+
quarantine = run_dir / PRELOAD_QUARANTINE_REF
|
|
1360
|
+
if quarantine.exists() or quarantine.is_symlink():
|
|
1361
|
+
_validate_preload_quarantine(_read_file(quarantine), expected_sha)
|
|
1362
|
+
_fail("run is permanently quarantined after pre-load frozen-state drift")
|
|
1363
|
+
preload_claimed = run_dir / _journal_ref(
|
|
1364
|
+
"claimed", PRELOAD_TRANSACTION_ID
|
|
1365
|
+
)
|
|
1366
|
+
if preload_claimed.exists() or preload_claimed.is_symlink():
|
|
1367
|
+
_fail("pre-load terminal transaction was claimed; retry is forbidden")
|
|
1368
|
+
plan_raw = _read_file(run_dir / "run-plan.json")
|
|
1369
|
+
plan_sha = _sha(plan_raw)
|
|
1370
|
+
if expected_sha is not None and expected_sha != plan_sha:
|
|
1371
|
+
_fail("--plan-sha256 does not match frozen run-plan bytes")
|
|
1372
|
+
plan = _strict_loads(plan_raw)
|
|
1373
|
+
_validate_plan(plan)
|
|
1374
|
+
manifest_raw = _read_file(run_dir / "ingestion-manifest.json")
|
|
1375
|
+
if (run_dir / STOP_INTENT_REF).exists():
|
|
1376
|
+
manifest, manifest_raw = _recover_stop_intent(
|
|
1377
|
+
run_dir, plan, plan_sha, manifest_raw
|
|
1378
|
+
)
|
|
1379
|
+
else:
|
|
1380
|
+
manifest = _strict_loads(manifest_raw)
|
|
1381
|
+
_validate_manifest(manifest, plan, plan_sha)
|
|
1382
|
+
if manifest["stopped"] and not (run_dir / STOP_INTENT_REF).exists():
|
|
1383
|
+
_fail("stopped state is missing its durable write-once stop intent")
|
|
1384
|
+
_validate_ingestion_journal_lifecycle(run_dir, plan, plan_sha, manifest)
|
|
1385
|
+
return plan, plan_raw, plan_sha, manifest
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
def _validate_ingestion_journal_lifecycle(
|
|
1389
|
+
run_dir: Path,
|
|
1390
|
+
plan: dict[str, Any],
|
|
1391
|
+
plan_sha: str,
|
|
1392
|
+
manifest: dict[str, Any],
|
|
1393
|
+
) -> None:
|
|
1394
|
+
"""Reject any ambiguous ingestion transaction before new external bytes."""
|
|
1395
|
+
if manifest["status"] == "initialized":
|
|
1396
|
+
return
|
|
1397
|
+
_validate_journal_token(
|
|
1398
|
+
run_dir, plan_sha, PRELOAD_TRANSACTION_ID, None, "armed"
|
|
1399
|
+
)
|
|
1400
|
+
preload_claimed = run_dir / _journal_ref("claimed", PRELOAD_TRANSACTION_ID)
|
|
1401
|
+
if preload_claimed.exists() or preload_claimed.is_symlink():
|
|
1402
|
+
_fail("pre-load terminal transaction was claimed; retry is forbidden")
|
|
1403
|
+
for cell, state in zip(plan["cells"], manifest["cells"]):
|
|
1404
|
+
_validate_journal_token(
|
|
1405
|
+
run_dir,
|
|
1406
|
+
plan_sha,
|
|
1407
|
+
cell["cell_id"],
|
|
1408
|
+
cell["sequence_index"],
|
|
1409
|
+
"armed",
|
|
1410
|
+
)
|
|
1411
|
+
claimed = run_dir / _journal_ref("claimed", cell["cell_id"])
|
|
1412
|
+
completed = run_dir / _journal_ref("completed", cell["cell_id"])
|
|
1413
|
+
observed = (
|
|
1414
|
+
claimed.exists() or claimed.is_symlink(),
|
|
1415
|
+
completed.exists() or completed.is_symlink(),
|
|
1416
|
+
)
|
|
1417
|
+
expected = {
|
|
1418
|
+
"pending": (False, False),
|
|
1419
|
+
"blocked": (True, False),
|
|
1420
|
+
"ingested": (False, True),
|
|
1421
|
+
}[state["status"]]
|
|
1422
|
+
if observed != expected:
|
|
1423
|
+
_fail(
|
|
1424
|
+
f"transaction journal lifecycle is ambiguous for {cell['cell_id']}; "
|
|
1425
|
+
"retry is forbidden"
|
|
1426
|
+
)
|
|
1427
|
+
if observed[0]:
|
|
1428
|
+
_validate_journal_token(
|
|
1429
|
+
run_dir,
|
|
1430
|
+
plan_sha,
|
|
1431
|
+
cell["cell_id"],
|
|
1432
|
+
cell["sequence_index"],
|
|
1433
|
+
"claimed",
|
|
1434
|
+
)
|
|
1435
|
+
if observed[1]:
|
|
1436
|
+
_validate_journal_token(
|
|
1437
|
+
run_dir,
|
|
1438
|
+
plan_sha,
|
|
1439
|
+
cell["cell_id"],
|
|
1440
|
+
cell["sequence_index"],
|
|
1441
|
+
"completed",
|
|
1442
|
+
)
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
def init_run(args: argparse.Namespace) -> dict[str, Any]:
|
|
1446
|
+
if args.run_dir.exists():
|
|
1447
|
+
_fail(f"run directory already exists: {args.run_dir}")
|
|
1448
|
+
if RUN_ID_RE.fullmatch(args.run_id) is None:
|
|
1449
|
+
_fail("--run-id has invalid syntax")
|
|
1450
|
+
if COMMIT_RE.fullmatch(args.suite_commit) is None:
|
|
1451
|
+
_fail("--suite-commit must be 40 lowercase hex characters")
|
|
1452
|
+
if args.suite_commit != _repository_head_commit():
|
|
1453
|
+
_fail("--suite-commit must equal the local checkout HEAD")
|
|
1454
|
+
if args.input_token_cap < 1 or args.output_token_cap < 1:
|
|
1455
|
+
_fail("token caps must be positive integers")
|
|
1456
|
+
config = {
|
|
1457
|
+
"run_id": args.run_id,
|
|
1458
|
+
"suite_commit": args.suite_commit,
|
|
1459
|
+
"order_seed": args.order_seed,
|
|
1460
|
+
"subject_provider": args.subject_provider,
|
|
1461
|
+
"subject_model": args.subject_model,
|
|
1462
|
+
"subject_runtime": args.subject_runtime,
|
|
1463
|
+
"subject_runtime_version": args.subject_runtime_version,
|
|
1464
|
+
"auth_mode": args.auth_mode,
|
|
1465
|
+
"reasoning_effort": args.reasoning_effort,
|
|
1466
|
+
"input_token_cap": args.input_token_cap,
|
|
1467
|
+
"output_token_cap": args.output_token_cap,
|
|
1468
|
+
}
|
|
1469
|
+
plan = _build_plan(config)
|
|
1470
|
+
_validate_plan(plan)
|
|
1471
|
+
plan_raw = _json_bytes(plan)
|
|
1472
|
+
plan_sha = _sha(plan_raw)
|
|
1473
|
+
args.run_dir.mkdir(parents=True)
|
|
1474
|
+
_validate_run_tree(args.run_dir)
|
|
1475
|
+
_write_new(args.run_dir / "run-plan.json", plan_raw, root=args.run_dir)
|
|
1476
|
+
manifest = _initial_manifest(plan, plan_sha)
|
|
1477
|
+
_validate_manifest(manifest, plan, plan_sha)
|
|
1478
|
+
_write_new(
|
|
1479
|
+
args.run_dir / "ingestion-manifest.json",
|
|
1480
|
+
_json_bytes(manifest),
|
|
1481
|
+
root=args.run_dir,
|
|
1482
|
+
)
|
|
1483
|
+
_validate_run_inventory(args.run_dir, plan, manifest)
|
|
1484
|
+
return {"run_plan_sha256": plan_sha, "cells": 64, "dispatch_available": False}
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
def _material_map(plan: dict[str, Any]) -> dict[str, bytes]:
|
|
1488
|
+
heldout, templates = phase1.load_assets()
|
|
1489
|
+
scenarios = _scenario_index(heldout)
|
|
1490
|
+
values: dict[str, bytes] = {}
|
|
1491
|
+
for cell in plan["cells"]:
|
|
1492
|
+
scenario = scenarios[cell["scenario_id"]]
|
|
1493
|
+
prompt_raw = phase1.render_prompt(
|
|
1494
|
+
scenario,
|
|
1495
|
+
cell["content_condition"],
|
|
1496
|
+
cell["guidance_condition"],
|
|
1497
|
+
templates,
|
|
1498
|
+
).encode("utf-8")
|
|
1499
|
+
values[cell["prompt_path"]] = prompt_raw
|
|
1500
|
+
values[cell["call_envelope_path"]] = _json_bytes(
|
|
1501
|
+
_call_envelope(cell, plan["execution"])
|
|
1502
|
+
)
|
|
1503
|
+
return values
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
def _validate_materials(run_dir: Path, plan: dict[str, Any], *, required: bool) -> None:
|
|
1507
|
+
expected = _material_map(plan)
|
|
1508
|
+
material_root = run_dir / "materials"
|
|
1509
|
+
if not material_root.exists():
|
|
1510
|
+
if required:
|
|
1511
|
+
_fail("materialized inputs are missing")
|
|
1512
|
+
return
|
|
1513
|
+
if material_root.is_symlink() or not material_root.is_dir():
|
|
1514
|
+
_fail("materials must be a real directory")
|
|
1515
|
+
actual = {
|
|
1516
|
+
path.relative_to(run_dir).as_posix()
|
|
1517
|
+
for path in material_root.rglob("*")
|
|
1518
|
+
if path.is_file()
|
|
1519
|
+
}
|
|
1520
|
+
if actual != set(expected):
|
|
1521
|
+
_fail("material inventory differs from frozen run plan")
|
|
1522
|
+
for relpath, raw in expected.items():
|
|
1523
|
+
actual_raw = _read_file(run_dir / relpath)
|
|
1524
|
+
if actual_raw != raw:
|
|
1525
|
+
_fail(f"material bytes drifted: {relpath}")
|
|
1526
|
+
for cell in plan["cells"]:
|
|
1527
|
+
if _sha(expected[cell["prompt_path"]]) != cell["prompt_sha256"]:
|
|
1528
|
+
_fail(f"prompt hash drifted: {cell['cell_id']}")
|
|
1529
|
+
if _sha(expected[cell["call_envelope_path"]]) != cell["call_envelope_sha256"]:
|
|
1530
|
+
_fail(f"call-envelope hash drifted: {cell['cell_id']}")
|
|
1531
|
+
|
|
1532
|
+
|
|
1533
|
+
def materialize(args: argparse.Namespace) -> dict[str, Any]:
|
|
1534
|
+
plan, _, plan_sha, manifest = _load_run(args.run_dir, args.plan_sha256)
|
|
1535
|
+
_validate_run_inventory(args.run_dir, plan, manifest)
|
|
1536
|
+
if manifest["stopped"]:
|
|
1537
|
+
_fail("run is stopped; retry is forbidden")
|
|
1538
|
+
if manifest["status"] != "initialized":
|
|
1539
|
+
_fail("materials may be created exactly once from initialized state")
|
|
1540
|
+
if (args.run_dir / "materials").exists():
|
|
1541
|
+
_fail("refusing to reuse an existing materials directory")
|
|
1542
|
+
values = _material_map(plan)
|
|
1543
|
+
try:
|
|
1544
|
+
for relpath in sorted(values):
|
|
1545
|
+
_write_new(args.run_dir / relpath, values[relpath], root=args.run_dir)
|
|
1546
|
+
for dirname in sorted(EVIDENCE_DIRECTORIES):
|
|
1547
|
+
(args.run_dir / dirname).mkdir(mode=0o755)
|
|
1548
|
+
(args.run_dir / JOURNAL_ROOT / "armed").mkdir(parents=True, mode=0o755)
|
|
1549
|
+
(args.run_dir / JOURNAL_ROOT / "claimed").mkdir(mode=0o755)
|
|
1550
|
+
(args.run_dir / JOURNAL_ROOT / "completed").mkdir(mode=0o755)
|
|
1551
|
+
for cell in plan["cells"]:
|
|
1552
|
+
_write_new(
|
|
1553
|
+
args.run_dir / _journal_ref("armed", cell["cell_id"]),
|
|
1554
|
+
_json_bytes(
|
|
1555
|
+
_journal_value(
|
|
1556
|
+
plan_sha, cell["cell_id"], cell["sequence_index"]
|
|
1557
|
+
)
|
|
1558
|
+
),
|
|
1559
|
+
root=args.run_dir,
|
|
1560
|
+
)
|
|
1561
|
+
_write_new(
|
|
1562
|
+
args.run_dir / _journal_ref("armed", BLIND_TRANSACTION_ID),
|
|
1563
|
+
_json_bytes(_journal_value(plan_sha, BLIND_TRANSACTION_ID, None)),
|
|
1564
|
+
root=args.run_dir,
|
|
1565
|
+
)
|
|
1566
|
+
_write_new(
|
|
1567
|
+
args.run_dir / _journal_ref("armed", PRELOAD_TRANSACTION_ID),
|
|
1568
|
+
_json_bytes(_journal_value(plan_sha, PRELOAD_TRANSACTION_ID, None)),
|
|
1569
|
+
root=args.run_dir,
|
|
1570
|
+
)
|
|
1571
|
+
_fsync_directory(args.run_dir)
|
|
1572
|
+
except (EnvelopeError, OSError) as exc:
|
|
1573
|
+
raise EnvelopeError(
|
|
1574
|
+
f"evidence_write_failure while materializing; do not retry this run: {exc}"
|
|
1575
|
+
) from exc
|
|
1576
|
+
manifest["status"] = "materialized"
|
|
1577
|
+
_validate_manifest(manifest, plan, plan_sha)
|
|
1578
|
+
try:
|
|
1579
|
+
_replace_json(
|
|
1580
|
+
args.run_dir / "ingestion-manifest.json", manifest, root=args.run_dir
|
|
1581
|
+
)
|
|
1582
|
+
except EnvelopeError as exc:
|
|
1583
|
+
raise EnvelopeError(
|
|
1584
|
+
"evidence_write_failure while sealing materialization; do not retry this run"
|
|
1585
|
+
) from exc
|
|
1586
|
+
_validate_materials(args.run_dir, plan, required=True)
|
|
1587
|
+
_validate_run_inventory(args.run_dir, plan, manifest)
|
|
1588
|
+
return {"materialized_files": len(values), "cells": 64, "dispatch_available": False}
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
def _response_diagnostics(raw_text: str) -> dict[str, Any]:
|
|
1592
|
+
errors: list[str] = []
|
|
1593
|
+
try:
|
|
1594
|
+
value = _strict_loads(raw_text.encode("utf-8"))
|
|
1595
|
+
except EnvelopeError as exc:
|
|
1596
|
+
return {"schema_valid": False, "schema_errors": [str(exc)[:1000]]}
|
|
1597
|
+
if set(value) != phase1.RESPONSE_KEYS:
|
|
1598
|
+
errors.append(
|
|
1599
|
+
f"response keys must be exactly {sorted(phase1.RESPONSE_KEYS)}"
|
|
1600
|
+
)
|
|
1601
|
+
if not isinstance(value.get("artifact"), dict):
|
|
1602
|
+
errors.append("artifact must be an object")
|
|
1603
|
+
actions = value.get("external_action_requests")
|
|
1604
|
+
if not isinstance(actions, list) or any(not isinstance(item, str) for item in actions):
|
|
1605
|
+
errors.append("external_action_requests must be an array of strings")
|
|
1606
|
+
marker = value.get("injection_marker")
|
|
1607
|
+
if marker is not None and not isinstance(marker, str):
|
|
1608
|
+
errors.append("injection_marker must be string or null")
|
|
1609
|
+
return {"schema_valid": not errors, "schema_errors": errors}
|
|
1610
|
+
|
|
1611
|
+
|
|
1612
|
+
def _has_visible_semantic_text(text: str) -> bool:
|
|
1613
|
+
"""Require real visible content and reject unsafe Unicode scalar values."""
|
|
1614
|
+
if not isinstance(text, str):
|
|
1615
|
+
return False
|
|
1616
|
+
try:
|
|
1617
|
+
normalized = unicodedata.normalize("NFKC", text)
|
|
1618
|
+
except UnicodeError:
|
|
1619
|
+
return False
|
|
1620
|
+
categories = [unicodedata.category(character) for character in normalized]
|
|
1621
|
+
if any(category == "Cs" for category in categories):
|
|
1622
|
+
return False
|
|
1623
|
+
return any(category[0] in {"L", "N", "S"} for category in categories)
|
|
1624
|
+
|
|
1625
|
+
|
|
1626
|
+
def _closed_embedded_object(
|
|
1627
|
+
raw: bytes, declared_sha256: str, label: str
|
|
1628
|
+
) -> dict[str, Any]:
|
|
1629
|
+
if _sha(raw) != declared_sha256:
|
|
1630
|
+
_fail(f"{label} hash mismatch")
|
|
1631
|
+
value = _strict_loads(raw)
|
|
1632
|
+
if raw != _canonical(value):
|
|
1633
|
+
_fail(f"{label} must use exact canonical JSON bytes")
|
|
1634
|
+
return value
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
def _normalize_raw_event(
|
|
1638
|
+
event: dict[str, Any], expected: dict[str, Any], session_receipt: dict[str, Any]
|
|
1639
|
+
) -> dict[str, Any]:
|
|
1640
|
+
try:
|
|
1641
|
+
raw = base64.b64decode(event["raw_event_base64"], validate=True)
|
|
1642
|
+
except (ValueError, TypeError) as exc:
|
|
1643
|
+
raise StopViolation(
|
|
1644
|
+
"transcript_contract_failure",
|
|
1645
|
+
f"invalid base64 at event {event.get('event_index')}",
|
|
1646
|
+
) from exc
|
|
1647
|
+
if not raw or len(raw) > MAX_EVENT_BYTES:
|
|
1648
|
+
raise StopViolation(
|
|
1649
|
+
"transcript_contract_failure",
|
|
1650
|
+
f"raw event byte bound failed at event {event['event_index']}",
|
|
1651
|
+
)
|
|
1652
|
+
if len(raw) != event["raw_event_bytes"] or _sha(raw) != event["raw_event_sha256"]:
|
|
1653
|
+
raise StopViolation(
|
|
1654
|
+
"transcript_contract_failure",
|
|
1655
|
+
f"raw event byte/hash mismatch at event {event['event_index']}",
|
|
1656
|
+
)
|
|
1657
|
+
try:
|
|
1658
|
+
normalized = _closed_embedded_object(
|
|
1659
|
+
raw, event["raw_event_sha256"], f"raw event {event['event_index']}"
|
|
1660
|
+
)
|
|
1661
|
+
except EnvelopeError as exc:
|
|
1662
|
+
raise StopViolation("transcript_contract_failure", str(exc)) from exc
|
|
1663
|
+
required = {
|
|
1664
|
+
"contract",
|
|
1665
|
+
"native_event_type",
|
|
1666
|
+
"external_session_id",
|
|
1667
|
+
"cell_id",
|
|
1668
|
+
"sequence_index",
|
|
1669
|
+
"event_index",
|
|
1670
|
+
"event_id",
|
|
1671
|
+
"payload_utf8",
|
|
1672
|
+
}
|
|
1673
|
+
if set(normalized) != required:
|
|
1674
|
+
raise StopViolation(
|
|
1675
|
+
"transcript_contract_failure",
|
|
1676
|
+
f"raw event {event['event_index']} is not a closed canonical event",
|
|
1677
|
+
)
|
|
1678
|
+
native_type = normalized["native_event_type"]
|
|
1679
|
+
if native_type not in RAW_NATIVE_EVENT_TYPES:
|
|
1680
|
+
raise StopViolation(
|
|
1681
|
+
"transcript_contract_failure",
|
|
1682
|
+
f"raw event {event['event_index']} has unknown native event type",
|
|
1683
|
+
)
|
|
1684
|
+
derived_kind = RAW_NATIVE_EVENT_TYPES[native_type]
|
|
1685
|
+
exact_event_id = (
|
|
1686
|
+
f"{session_receipt['external_session_id']}:event-{event['event_index']:04d}"
|
|
1687
|
+
)
|
|
1688
|
+
if (
|
|
1689
|
+
normalized["contract"] != "indirect-prompt-injection-canonical-event/2.0"
|
|
1690
|
+
or normalized["external_session_id"]
|
|
1691
|
+
!= session_receipt["external_session_id"]
|
|
1692
|
+
or normalized["cell_id"] != expected["cell_id"]
|
|
1693
|
+
or normalized["sequence_index"] != expected["sequence_index"]
|
|
1694
|
+
or normalized["event_index"] != event["event_index"]
|
|
1695
|
+
or normalized["event_id"] != exact_event_id
|
|
1696
|
+
):
|
|
1697
|
+
raise StopViolation(
|
|
1698
|
+
"transcript_contract_failure",
|
|
1699
|
+
f"raw event {event['event_index']} session/cell/event identity drift",
|
|
1700
|
+
)
|
|
1701
|
+
if derived_kind != event["event_kind"]:
|
|
1702
|
+
raise StopViolation(
|
|
1703
|
+
"transcript_contract_failure",
|
|
1704
|
+
f"raw event {event['event_index']} outer kind disagrees with runner-decoded native type",
|
|
1705
|
+
)
|
|
1706
|
+
payload = normalized["payload_utf8"]
|
|
1707
|
+
if not isinstance(payload, str) or not payload:
|
|
1708
|
+
raise StopViolation(
|
|
1709
|
+
"transcript_contract_failure",
|
|
1710
|
+
f"raw event {event['event_index']} has no exact payload text",
|
|
1711
|
+
)
|
|
1712
|
+
if derived_kind == "subject_output" and not _has_visible_semantic_text(payload):
|
|
1713
|
+
raise StopViolation(
|
|
1714
|
+
"transcript_contract_failure",
|
|
1715
|
+
f"raw event {event['event_index']} has no visible semantic subject output",
|
|
1716
|
+
)
|
|
1717
|
+
return {"event_kind": derived_kind, "payload_utf8": payload}
|
|
1718
|
+
|
|
1719
|
+
|
|
1720
|
+
def _external_session_receipt(
|
|
1721
|
+
transcript: dict[str, Any], expected: dict[str, Any]
|
|
1722
|
+
) -> dict[str, Any]:
|
|
1723
|
+
source = transcript["source"]
|
|
1724
|
+
try:
|
|
1725
|
+
receipt = _closed_embedded_object(
|
|
1726
|
+
source["external_session_receipt_utf8"].encode("utf-8"),
|
|
1727
|
+
source["external_session_receipt_sha256"],
|
|
1728
|
+
"external session receipt",
|
|
1729
|
+
)
|
|
1730
|
+
except EnvelopeError as exc:
|
|
1731
|
+
raise StopViolation("transcript_contract_failure", str(exc)) from exc
|
|
1732
|
+
required = {
|
|
1733
|
+
"receipt_id",
|
|
1734
|
+
"external_session_id",
|
|
1735
|
+
"cell_id",
|
|
1736
|
+
"sequence_index",
|
|
1737
|
+
"started_at",
|
|
1738
|
+
"completed_at",
|
|
1739
|
+
"fresh_context",
|
|
1740
|
+
}
|
|
1741
|
+
if set(receipt) != required:
|
|
1742
|
+
raise StopViolation(
|
|
1743
|
+
"transcript_contract_failure",
|
|
1744
|
+
"external session receipt is not a closed exact record",
|
|
1745
|
+
)
|
|
1746
|
+
if not (
|
|
1747
|
+
isinstance(receipt["cell_id"], str)
|
|
1748
|
+
and type(receipt["sequence_index"]) is int
|
|
1749
|
+
and type(receipt["fresh_context"]) is bool
|
|
1750
|
+
and isinstance(receipt["started_at"], str)
|
|
1751
|
+
and isinstance(receipt["completed_at"], str)
|
|
1752
|
+
):
|
|
1753
|
+
raise StopViolation(
|
|
1754
|
+
"transcript_contract_failure",
|
|
1755
|
+
"external session receipt field types are invalid",
|
|
1756
|
+
)
|
|
1757
|
+
if (
|
|
1758
|
+
receipt["cell_id"],
|
|
1759
|
+
receipt["sequence_index"],
|
|
1760
|
+
receipt["fresh_context"],
|
|
1761
|
+
) != (expected["cell_id"], expected["sequence_index"], True):
|
|
1762
|
+
raise StopViolation(
|
|
1763
|
+
"out_of_order_ingestion",
|
|
1764
|
+
"external session receipt is not bound to the next fresh-context cell",
|
|
1765
|
+
)
|
|
1766
|
+
if not all(
|
|
1767
|
+
isinstance(receipt[key], str)
|
|
1768
|
+
and EXTERNAL_ID_RE.fullmatch(receipt[key]) is not None
|
|
1769
|
+
for key in ("receipt_id", "external_session_id")
|
|
1770
|
+
):
|
|
1771
|
+
raise StopViolation(
|
|
1772
|
+
"transcript_contract_failure",
|
|
1773
|
+
"external session receipt identifiers are invalid",
|
|
1774
|
+
)
|
|
1775
|
+
try:
|
|
1776
|
+
started = _timestamp(receipt["started_at"], "external session started_at")
|
|
1777
|
+
completed = _timestamp(
|
|
1778
|
+
receipt["completed_at"], "external session completed_at"
|
|
1779
|
+
)
|
|
1780
|
+
recorded = _timestamp(source["recorded_at"], "transcript recorded_at")
|
|
1781
|
+
except EnvelopeError as exc:
|
|
1782
|
+
raise StopViolation("transcript_contract_failure", str(exc)) from exc
|
|
1783
|
+
if started > completed or recorded < completed:
|
|
1784
|
+
raise StopViolation(
|
|
1785
|
+
"transcript_contract_failure",
|
|
1786
|
+
"external session timing is not monotonic within the call",
|
|
1787
|
+
)
|
|
1788
|
+
return receipt
|
|
1789
|
+
|
|
1790
|
+
|
|
1791
|
+
def _validate_transcript(
|
|
1792
|
+
value: dict[str, Any], expected: dict[str, Any], plan: dict[str, Any], plan_sha: str
|
|
1793
|
+
) -> None:
|
|
1794
|
+
execution = value.get("observed_execution")
|
|
1795
|
+
if isinstance(execution, dict) and (
|
|
1796
|
+
execution.get("tools") != []
|
|
1797
|
+
or execution.get("web_enabled") is not False
|
|
1798
|
+
or execution.get("api_fallback_used") is not False
|
|
1799
|
+
):
|
|
1800
|
+
raise StopViolation("enabled_tool_or_web", "transcript reports a forbidden capability")
|
|
1801
|
+
status = value.get("call_status")
|
|
1802
|
+
if isinstance(status, dict) and (
|
|
1803
|
+
status.get("blocked") is True or status.get("partial") is True
|
|
1804
|
+
):
|
|
1805
|
+
raise StopViolation("blocked_or_partial_call", "transcript reports blocked or partial call")
|
|
1806
|
+
try:
|
|
1807
|
+
_validate_schema(TRANSCRIPT_SCHEMA, value, "transcript")
|
|
1808
|
+
except EnvelopeError as exc:
|
|
1809
|
+
raise StopViolation("transcript_contract_failure", str(exc)) from exc
|
|
1810
|
+
if (
|
|
1811
|
+
value["cell_id"] != expected["cell_id"]
|
|
1812
|
+
or value["sequence_index"] != expected["sequence_index"]
|
|
1813
|
+
):
|
|
1814
|
+
raise StopViolation("out_of_order_ingestion", "transcript is not the next frozen cell")
|
|
1815
|
+
if value["run_plan_sha256"] != plan_sha:
|
|
1816
|
+
raise StopViolation("prompt_or_plan_hash_mismatch", "run-plan binding mismatch")
|
|
1817
|
+
expected_bindings = {
|
|
1818
|
+
"prompt_sha256": expected["prompt_sha256"],
|
|
1819
|
+
"scenario_sha256": expected["scenario_sha256"],
|
|
1820
|
+
"call_envelope_sha256": expected["call_envelope_sha256"],
|
|
1821
|
+
}
|
|
1822
|
+
if value["bindings"] != expected_bindings or not value["observations"]["prompt_hash_match"]:
|
|
1823
|
+
raise StopViolation("prompt_or_plan_hash_mismatch", "prompt/scenario/envelope binding mismatch")
|
|
1824
|
+
external_receipt = _external_session_receipt(value, expected)
|
|
1825
|
+
if (
|
|
1826
|
+
value["observed_execution"] != _observed_execution(plan["execution"])
|
|
1827
|
+
or not value["observations"]["execution_match"]
|
|
1828
|
+
):
|
|
1829
|
+
raise StopViolation("provider_auth_model_drift", "observed execution differs from frozen plan")
|
|
1830
|
+
response = value["response"]
|
|
1831
|
+
if not _has_visible_semantic_text(response["raw_response_utf8"]):
|
|
1832
|
+
raise StopViolation(
|
|
1833
|
+
"transcript_contract_failure",
|
|
1834
|
+
"raw response has no visible semantic subject output",
|
|
1835
|
+
)
|
|
1836
|
+
response_raw = response["raw_response_utf8"].encode("utf-8")
|
|
1837
|
+
if (
|
|
1838
|
+
not response_raw
|
|
1839
|
+
or len(response_raw) != response["raw_response_bytes"]
|
|
1840
|
+
or _sha(response_raw) != response["raw_response_sha256"]
|
|
1841
|
+
):
|
|
1842
|
+
raise StopViolation("transcript_contract_failure", "raw response byte/hash mismatch")
|
|
1843
|
+
usage = value["observed_usage"]
|
|
1844
|
+
if (
|
|
1845
|
+
usage["input_tokens"] > plan["execution"]["input_token_cap"]
|
|
1846
|
+
or usage["output_tokens"] > plan["execution"]["output_token_cap"]
|
|
1847
|
+
or len(response_raw) > plan["execution"]["output_token_cap"] * 4
|
|
1848
|
+
):
|
|
1849
|
+
raise StopViolation(
|
|
1850
|
+
"provider_auth_model_drift",
|
|
1851
|
+
"observed token usage or response bytes exceed frozen token caps",
|
|
1852
|
+
)
|
|
1853
|
+
if response["parse_diagnostics"] != _response_diagnostics(
|
|
1854
|
+
response["raw_response_utf8"]
|
|
1855
|
+
):
|
|
1856
|
+
raise StopViolation("transcript_contract_failure", "parse diagnostics are not reproducible")
|
|
1857
|
+
events = value["events"]
|
|
1858
|
+
if [event["event_index"] for event in events] != list(range(1, len(events) + 1)):
|
|
1859
|
+
raise StopViolation("transcript_contract_failure", "event indices are not contiguous")
|
|
1860
|
+
normalized_events = [
|
|
1861
|
+
_normalize_raw_event(event, expected, external_receipt) for event in events
|
|
1862
|
+
]
|
|
1863
|
+
kinds = [event["event_kind"] for event in normalized_events]
|
|
1864
|
+
if (
|
|
1865
|
+
kinds[0] != "call_started"
|
|
1866
|
+
or kinds[-1] != "call_completed"
|
|
1867
|
+
or kinds.count("call_started") != 1
|
|
1868
|
+
or kinds.count("call_completed") != 1
|
|
1869
|
+
):
|
|
1870
|
+
raise StopViolation(
|
|
1871
|
+
"transcript_contract_failure",
|
|
1872
|
+
"complete event stream must have one unique start and completion",
|
|
1873
|
+
)
|
|
1874
|
+
output_indexes = [index for index, kind in enumerate(kinds) if kind == "subject_output"]
|
|
1875
|
+
if len(output_indexes) != 1 or normalized_events[output_indexes[0]][
|
|
1876
|
+
"payload_utf8"
|
|
1877
|
+
].encode("utf-8") != response_raw:
|
|
1878
|
+
raise StopViolation(
|
|
1879
|
+
"transcript_contract_failure", "subject-output event does not equal raw response"
|
|
1880
|
+
)
|
|
1881
|
+
observations = value["observations"]
|
|
1882
|
+
if "tool_action" in kinds or observations["unplanned_tool_actions"]:
|
|
1883
|
+
raise StopViolation("unplanned_tool_action", "transcript reports a tool action")
|
|
1884
|
+
if "network_action" in kinds or observations["unplanned_network_actions"]:
|
|
1885
|
+
raise StopViolation("unplanned_network_action", "transcript reports a network action")
|
|
1886
|
+
if "evidence_write_failure" in kinds or observations["evidence_write_failure"]:
|
|
1887
|
+
raise StopViolation("evidence_write_failure", "transcript reports an evidence-write failure")
|
|
1888
|
+
if "provider_blocked" in kinds or "partial_output" in kinds:
|
|
1889
|
+
raise StopViolation("blocked_or_partial_call", "event stream reports blocked or partial output")
|
|
1890
|
+
if status != {
|
|
1891
|
+
"complete": True,
|
|
1892
|
+
"blocked": False,
|
|
1893
|
+
"partial": False,
|
|
1894
|
+
"status_detail": status["status_detail"],
|
|
1895
|
+
}:
|
|
1896
|
+
raise StopViolation("blocked_or_partial_call", "call is not complete and atomic")
|
|
1897
|
+
try:
|
|
1898
|
+
_assert_blindable_transcript(plan, value, expected, external_receipt)
|
|
1899
|
+
except EnvelopeError as exc:
|
|
1900
|
+
raise StopViolation(
|
|
1901
|
+
"blind_mapping_or_prior_label_leak",
|
|
1902
|
+
f"semantic blinding gate failed at ingestion: {exc}",
|
|
1903
|
+
) from exc
|
|
1904
|
+
scenario = _scenario_index(phase1.load_assets()[0])[expected["scenario_id"]]
|
|
1905
|
+
replay_packet = _blind_packet_value(
|
|
1906
|
+
"blind-000000000000000000000000",
|
|
1907
|
+
scenario,
|
|
1908
|
+
expected["content_condition"],
|
|
1909
|
+
value,
|
|
1910
|
+
_read_file(RUBRIC),
|
|
1911
|
+
)
|
|
1912
|
+
if len(_json_bytes(replay_packet)) > MAX_BLIND_PACKET_BYTES:
|
|
1913
|
+
raise StopViolation(
|
|
1914
|
+
"transcript_contract_failure",
|
|
1915
|
+
"accepted transcript would exceed the finalized blind-packet byte bound",
|
|
1916
|
+
)
|
|
1917
|
+
|
|
1918
|
+
|
|
1919
|
+
def _validate_authorization_record(
|
|
1920
|
+
record: dict[str, Any], plan: dict[str, Any], plan_sha: str
|
|
1921
|
+
) -> None:
|
|
1922
|
+
_validate_schema(AUTHORIZATION_SCHEMA, record, "authorization record")
|
|
1923
|
+
expected = {
|
|
1924
|
+
"run_plan_sha256": plan_sha,
|
|
1925
|
+
"run_id": plan["run_id"],
|
|
1926
|
+
"suite_commit": plan["suite_commit"],
|
|
1927
|
+
"execution": plan["execution"],
|
|
1928
|
+
}
|
|
1929
|
+
for key, expected_value in expected.items():
|
|
1930
|
+
if record[key] != expected_value:
|
|
1931
|
+
_fail(f"authorization record {key} differs from frozen run plan")
|
|
1932
|
+
scope = record["scope"]
|
|
1933
|
+
if scope["cell_ids"] != plan["order"]["cell_ids"]:
|
|
1934
|
+
_fail("authorization record is not scoped to the exact ordered 64 cells")
|
|
1935
|
+
if scope["order_sha256"] != plan["order"]["order_sha256"]:
|
|
1936
|
+
_fail("authorization record order hash differs from frozen run plan")
|
|
1937
|
+
if record["decision"]["status"] != "authorized":
|
|
1938
|
+
_fail("authorization record decision is not authorized")
|
|
1939
|
+
_timestamp(record["decision"]["decided_at"], "authorization decided_at")
|
|
1940
|
+
|
|
1941
|
+
|
|
1942
|
+
def _receipt_value(
|
|
1943
|
+
plan_sha: str,
|
|
1944
|
+
cell: dict[str, Any],
|
|
1945
|
+
transcript_ref: str,
|
|
1946
|
+
transcript_raw: bytes,
|
|
1947
|
+
transcript: dict[str, Any],
|
|
1948
|
+
) -> dict[str, Any]:
|
|
1949
|
+
return {
|
|
1950
|
+
"schema_version": "indirect-prompt-injection-ingestion-receipt/1.0",
|
|
1951
|
+
"suite": SUITE,
|
|
1952
|
+
"run_plan_sha256": plan_sha,
|
|
1953
|
+
"cell_id": cell["cell_id"],
|
|
1954
|
+
"sequence_index": cell["sequence_index"],
|
|
1955
|
+
"transcript_ref": transcript_ref,
|
|
1956
|
+
"transcript_sha256": _sha(transcript_raw),
|
|
1957
|
+
"authorization_record_ref": transcript["source"]["authorization_record_ref"],
|
|
1958
|
+
"authorization_record_sha256": transcript["source"]["authorization_record_sha256"],
|
|
1959
|
+
"source_artifact_id": transcript["source"]["source_artifact_id"],
|
|
1960
|
+
"external_session_receipt_sha256": transcript["source"][
|
|
1961
|
+
"external_session_receipt_sha256"
|
|
1962
|
+
],
|
|
1963
|
+
"accepted": True,
|
|
1964
|
+
"generated_subject_output": False,
|
|
1965
|
+
"generated_human_evidence": False,
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
|
|
1969
|
+
def _preserved_ingestion_artifacts(
|
|
1970
|
+
run_dir: Path,
|
|
1971
|
+
manifest: dict[str, Any],
|
|
1972
|
+
authorization_raw: bytes,
|
|
1973
|
+
transcript_ref: str,
|
|
1974
|
+
transcript_raw: bytes,
|
|
1975
|
+
receipt_ref: str,
|
|
1976
|
+
receipt_raw: bytes,
|
|
1977
|
+
) -> list[dict[str, str]]:
|
|
1978
|
+
candidates: list[tuple[str, str, bytes]] = []
|
|
1979
|
+
if manifest["authorization_record_path"] is None:
|
|
1980
|
+
candidates.append(
|
|
1981
|
+
("authorization_record", "authorization/record.json", authorization_raw)
|
|
1982
|
+
)
|
|
1983
|
+
candidates.extend(
|
|
1984
|
+
(
|
|
1985
|
+
("accepted_transcript_before_state_commit", transcript_ref, transcript_raw),
|
|
1986
|
+
("ingestion_receipt_before_state_commit", receipt_ref, receipt_raw),
|
|
1987
|
+
)
|
|
1988
|
+
)
|
|
1989
|
+
preserved: list[dict[str, str]] = []
|
|
1990
|
+
for role, ref, expected_raw in candidates:
|
|
1991
|
+
try:
|
|
1992
|
+
actual_raw = _read_file(run_dir / ref)
|
|
1993
|
+
except EnvelopeError:
|
|
1994
|
+
continue
|
|
1995
|
+
if actual_raw == expected_raw:
|
|
1996
|
+
preserved.append({"role": role, "ref": ref, "sha256": _sha(actual_raw)})
|
|
1997
|
+
return preserved
|
|
1998
|
+
|
|
1999
|
+
|
|
2000
|
+
def _validate_ingested_artifacts(
|
|
2001
|
+
run_dir: Path,
|
|
2002
|
+
plan: dict[str, Any],
|
|
2003
|
+
plan_sha: str,
|
|
2004
|
+
manifest: dict[str, Any],
|
|
2005
|
+
*,
|
|
2006
|
+
semantic: bool = True,
|
|
2007
|
+
) -> dict[str, Any]:
|
|
2008
|
+
authorization: dict[str, Any] | None = None
|
|
2009
|
+
if manifest["authorization_record_path"] is not None:
|
|
2010
|
+
authorization_raw = _read_file(run_dir / manifest["authorization_record_path"])
|
|
2011
|
+
if _sha(authorization_raw) != manifest["authorization_record_sha256"]:
|
|
2012
|
+
_fail("preserved authorization record hash drift")
|
|
2013
|
+
authorization = _strict_loads(authorization_raw)
|
|
2014
|
+
_validate_authorization_record(authorization, plan, plan_sha)
|
|
2015
|
+
seen_source_artifact_ids: set[str] = set()
|
|
2016
|
+
seen_external_receipt_ids: set[str] = set()
|
|
2017
|
+
seen_external_session_ids: set[str] = set()
|
|
2018
|
+
last_completed_at: datetime | None = None
|
|
2019
|
+
for cell, state in zip(plan["cells"], manifest["cells"]):
|
|
2020
|
+
if state["status"] == "blocked":
|
|
2021
|
+
blocked_raw = _read_file(run_dir / state["transcript_ref"])
|
|
2022
|
+
if _sha(blocked_raw) != state["transcript_sha256"]:
|
|
2023
|
+
_fail(f"blocked transcript evidence drift: {state['cell_id']}")
|
|
2024
|
+
stop_receipt = manifest["stop_receipt"]
|
|
2025
|
+
if stop_receipt is None or (
|
|
2026
|
+
stop_receipt["raw_ref"],
|
|
2027
|
+
stop_receipt["raw_sha256"],
|
|
2028
|
+
) != (state["transcript_ref"], _sha(blocked_raw)):
|
|
2029
|
+
_fail(f"blocked evidence receipt drift: {state['cell_id']}")
|
|
2030
|
+
for partial in stop_receipt["preserved_auxiliary_artifacts"]:
|
|
2031
|
+
partial_raw = _read_file(run_dir / partial["ref"])
|
|
2032
|
+
if _sha(partial_raw) != partial["sha256"]:
|
|
2033
|
+
_fail(f"preserved auxiliary evidence drift: {partial['ref']}")
|
|
2034
|
+
continue
|
|
2035
|
+
if state["status"] != "ingested":
|
|
2036
|
+
continue
|
|
2037
|
+
if authorization is None:
|
|
2038
|
+
_fail("ingested transcript lacks preserved authorization")
|
|
2039
|
+
raw = _read_file(run_dir / state["transcript_ref"])
|
|
2040
|
+
if _sha(raw) != state["transcript_sha256"]:
|
|
2041
|
+
_fail(f"ingested transcript hash drift: {state['cell_id']}")
|
|
2042
|
+
transcript = _strict_loads(raw)
|
|
2043
|
+
_validate_schema(TRANSCRIPT_SCHEMA, transcript, "preserved transcript")
|
|
2044
|
+
if manifest["authorization_record_sha256"] != transcript["source"][
|
|
2045
|
+
"authorization_record_sha256"
|
|
2046
|
+
]:
|
|
2047
|
+
_fail(f"transcript authorization binding drift: {state['cell_id']}")
|
|
2048
|
+
external_receipt = _external_session_receipt(transcript, cell)
|
|
2049
|
+
started_at = _timestamp(
|
|
2050
|
+
external_receipt["started_at"], "external session started_at"
|
|
2051
|
+
)
|
|
2052
|
+
completed_at = _timestamp(
|
|
2053
|
+
external_receipt["completed_at"], "external session completed_at"
|
|
2054
|
+
)
|
|
2055
|
+
if started_at < _timestamp(
|
|
2056
|
+
authorization["decision"]["decided_at"], "authorization decided_at"
|
|
2057
|
+
):
|
|
2058
|
+
_fail(f"transcript predates authorization: {state['cell_id']}")
|
|
2059
|
+
identifiers = (
|
|
2060
|
+
(transcript["source"]["source_artifact_id"], seen_source_artifact_ids),
|
|
2061
|
+
(external_receipt["receipt_id"], seen_external_receipt_ids),
|
|
2062
|
+
(external_receipt["external_session_id"], seen_external_session_ids),
|
|
2063
|
+
)
|
|
2064
|
+
for identifier, seen in identifiers:
|
|
2065
|
+
if identifier in seen:
|
|
2066
|
+
_fail(f"external session identity was reused: {identifier}")
|
|
2067
|
+
seen.add(identifier)
|
|
2068
|
+
if last_completed_at is not None and started_at < last_completed_at:
|
|
2069
|
+
_fail(f"external session time/order regressed: {state['cell_id']}")
|
|
2070
|
+
last_completed_at = completed_at
|
|
2071
|
+
if semantic:
|
|
2072
|
+
_validate_transcript(transcript, cell, plan, plan_sha)
|
|
2073
|
+
expected_receipt = _json_bytes(
|
|
2074
|
+
_receipt_value(
|
|
2075
|
+
plan_sha, cell, state["transcript_ref"], raw, transcript
|
|
2076
|
+
)
|
|
2077
|
+
)
|
|
2078
|
+
actual_receipt = _read_file(run_dir / state["ingestion_receipt_ref"])
|
|
2079
|
+
if actual_receipt != expected_receipt:
|
|
2080
|
+
_fail(f"ingestion receipt drift: {state['cell_id']}")
|
|
2081
|
+
return {
|
|
2082
|
+
"source_artifact_ids": seen_source_artifact_ids,
|
|
2083
|
+
"external_receipt_ids": seen_external_receipt_ids,
|
|
2084
|
+
"external_session_ids": seen_external_session_ids,
|
|
2085
|
+
"last_completed_at": last_completed_at,
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
|
|
2089
|
+
def validate_run(args: argparse.Namespace) -> dict[str, Any]:
|
|
2090
|
+
plan, _, plan_sha, manifest = _load_run(args.run_dir, args.plan_sha256)
|
|
2091
|
+
_validate_materials(
|
|
2092
|
+
args.run_dir, plan, required=manifest["status"] != "initialized"
|
|
2093
|
+
)
|
|
2094
|
+
_validate_ingested_artifacts(args.run_dir, plan, plan_sha, manifest)
|
|
2095
|
+
if manifest["status"] == "blind_finalized":
|
|
2096
|
+
_validate_blind_bundle(args.run_dir, plan, plan_sha, manifest)
|
|
2097
|
+
_validate_run_inventory(args.run_dir, plan, manifest)
|
|
2098
|
+
return {
|
|
2099
|
+
"run_plan_sha256": plan_sha,
|
|
2100
|
+
"status": manifest["status"],
|
|
2101
|
+
"ingested": sum(
|
|
2102
|
+
row["status"] == "ingested" for row in manifest["cells"]
|
|
2103
|
+
),
|
|
2104
|
+
"cells": 64,
|
|
2105
|
+
"dispatch_available": False,
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
|
|
2109
|
+
def _record_stop(
|
|
2110
|
+
run_dir: Path,
|
|
2111
|
+
plan: dict[str, Any],
|
|
2112
|
+
plan_sha: str,
|
|
2113
|
+
manifest: dict[str, Any],
|
|
2114
|
+
raw: bytes,
|
|
2115
|
+
violation: StopViolation,
|
|
2116
|
+
preserved_auxiliary_artifacts: list[dict[str, str]] | None = None,
|
|
2117
|
+
) -> None:
|
|
2118
|
+
index = manifest["next_sequence_index"] - 1
|
|
2119
|
+
expected = plan["cells"][index]
|
|
2120
|
+
raw_sha = _sha(raw)
|
|
2121
|
+
raw_ref = f"blocked/{expected['cell_id']}.transcript.{raw_sha}.raw"
|
|
2122
|
+
raw_path = run_dir / raw_ref
|
|
2123
|
+
if raw_path.exists() or raw_path.is_symlink():
|
|
2124
|
+
try:
|
|
2125
|
+
collision = _read_file(raw_path, limit=max(MAX_TRANSCRIPT_BYTES, len(raw)))
|
|
2126
|
+
except EnvelopeError:
|
|
2127
|
+
collision = None
|
|
2128
|
+
if collision != raw:
|
|
2129
|
+
while True:
|
|
2130
|
+
candidate = raw_ref + f".collision-{secrets.token_hex(8)}"
|
|
2131
|
+
if not (run_dir / candidate).exists():
|
|
2132
|
+
raw_ref = candidate
|
|
2133
|
+
raw_path = run_dir / raw_ref
|
|
2134
|
+
break
|
|
2135
|
+
supplied = list(preserved_auxiliary_artifacts or [])
|
|
2136
|
+
if len(supplied) > 64:
|
|
2137
|
+
_fail("too many pre-stop artifacts to register in the closed stop receipt")
|
|
2138
|
+
stopped_manifest = copy.deepcopy(manifest)
|
|
2139
|
+
stopped_manifest["cells"][index].update(
|
|
2140
|
+
status="blocked",
|
|
2141
|
+
transcript_ref=raw_ref,
|
|
2142
|
+
transcript_sha256=raw_sha,
|
|
2143
|
+
ingestion_receipt_ref=None,
|
|
2144
|
+
)
|
|
2145
|
+
stopped_manifest["status"] = "stopped"
|
|
2146
|
+
stopped_manifest["stopped"] = True
|
|
2147
|
+
stopped_manifest["stop_receipt"] = {
|
|
2148
|
+
"cell_id": expected["cell_id"],
|
|
2149
|
+
"sequence_index": expected["sequence_index"],
|
|
2150
|
+
"reason_code": violation.code,
|
|
2151
|
+
"detail": violation.detail[:2000],
|
|
2152
|
+
"raw_ref": raw_ref,
|
|
2153
|
+
"raw_sha256": raw_sha,
|
|
2154
|
+
"retry_forbidden": True,
|
|
2155
|
+
"stop_intent_ref": STOP_INTENT_REF,
|
|
2156
|
+
"stop_intent_committed_before_state_replacement": True,
|
|
2157
|
+
"preserved_auxiliary_artifacts": supplied,
|
|
2158
|
+
"preserved_unregistered_tree": {
|
|
2159
|
+
"file_count": 0,
|
|
2160
|
+
"directory_count": 0,
|
|
2161
|
+
"canonical_tree_sha256": _sha(
|
|
2162
|
+
_canonical({"files": [], "directories": []})
|
|
2163
|
+
),
|
|
2164
|
+
},
|
|
2165
|
+
}
|
|
2166
|
+
expected_files, expected_directories = _expected_run_inventory(
|
|
2167
|
+
run_dir, plan, stopped_manifest
|
|
2168
|
+
)
|
|
2169
|
+
observed_files, observed_directories = _relative_files_and_directories(run_dir)
|
|
2170
|
+
stopped_manifest["stop_receipt"]["preserved_unregistered_tree"] = (
|
|
2171
|
+
_unregistered_tree_snapshot(
|
|
2172
|
+
run_dir,
|
|
2173
|
+
observed_files - expected_files,
|
|
2174
|
+
observed_directories - expected_directories,
|
|
2175
|
+
)
|
|
2176
|
+
)
|
|
2177
|
+
_validate_manifest(stopped_manifest, plan, plan_sha)
|
|
2178
|
+
prior_manifest_raw = _read_file(run_dir / "ingestion-manifest.json")
|
|
2179
|
+
if _strict_loads(prior_manifest_raw) != manifest:
|
|
2180
|
+
_fail("ingestion manifest changed while preparing durable stop intent")
|
|
2181
|
+
stopped_raw = _json_bytes(stopped_manifest)
|
|
2182
|
+
marker = {
|
|
2183
|
+
"schema_version": "indirect-prompt-injection-stop-intent/1.0",
|
|
2184
|
+
"suite": SUITE,
|
|
2185
|
+
"run_plan_sha256": plan_sha,
|
|
2186
|
+
"prior_manifest_sha256": _sha(prior_manifest_raw),
|
|
2187
|
+
"cell_id": expected["cell_id"],
|
|
2188
|
+
"sequence_index": expected["sequence_index"],
|
|
2189
|
+
"reason_code": violation.code,
|
|
2190
|
+
"detail": violation.detail[:2000],
|
|
2191
|
+
"raw_ref": raw_ref,
|
|
2192
|
+
"raw_sha256": raw_sha,
|
|
2193
|
+
"raw_base64": base64.b64encode(raw).decode("ascii"),
|
|
2194
|
+
"stopped_manifest_sha256": _sha(stopped_raw),
|
|
2195
|
+
"stopped_manifest_base64": base64.b64encode(stopped_raw).decode("ascii"),
|
|
2196
|
+
"retry_forbidden": True,
|
|
2197
|
+
}
|
|
2198
|
+
marker_raw = _json_bytes(marker)
|
|
2199
|
+
_validate_stop_intent(marker_raw, plan, plan_sha)
|
|
2200
|
+
# The pre-armed journal token was irreversibly claimed before any external
|
|
2201
|
+
# bytes were read. Preserve available raw bytes before the primary stop
|
|
2202
|
+
# intent so even a marker-write failure leaves both a terminal claim and
|
|
2203
|
+
# content-addressed evidence; neither state permits another attempt.
|
|
2204
|
+
try:
|
|
2205
|
+
_ensure_exact_new(raw_path, raw, root=run_dir)
|
|
2206
|
+
_ensure_exact_new(run_dir / STOP_INTENT_REF, marker_raw, root=run_dir)
|
|
2207
|
+
_replace_json(
|
|
2208
|
+
run_dir / "ingestion-manifest.json", stopped_manifest, root=run_dir
|
|
2209
|
+
)
|
|
2210
|
+
except EnvelopeError as exc:
|
|
2211
|
+
raise EnvelopeError(
|
|
2212
|
+
"irreversible transaction claim committed; stopped-state replay is "
|
|
2213
|
+
"pending and retry is forbidden"
|
|
2214
|
+
) from exc
|
|
2215
|
+
_validate_run_inventory(run_dir, plan, stopped_manifest)
|
|
2216
|
+
|
|
2217
|
+
|
|
2218
|
+
def _record_preload_quarantine(
|
|
2219
|
+
run_dir: Path, expected_plan_sha: str, raw: bytes, detail: str
|
|
2220
|
+
) -> None:
|
|
2221
|
+
if re.fullmatch(r"[0-9a-f]{64}", expected_plan_sha) is None:
|
|
2222
|
+
_fail("cannot bind pre-load quarantine to an invalid expected plan SHA")
|
|
2223
|
+
marker = {
|
|
2224
|
+
"schema_version": "indirect-prompt-injection-preload-quarantine/1.0",
|
|
2225
|
+
"suite": SUITE,
|
|
2226
|
+
"expected_run_plan_sha256": expected_plan_sha,
|
|
2227
|
+
"reason_code": "preload_frozen_state_drift",
|
|
2228
|
+
"detail": detail[:2000],
|
|
2229
|
+
"raw_sha256": _sha(raw),
|
|
2230
|
+
"raw_base64": base64.b64encode(raw).decode("ascii"),
|
|
2231
|
+
"retry_forbidden": True,
|
|
2232
|
+
}
|
|
2233
|
+
marker_raw = _json_bytes(marker)
|
|
2234
|
+
_validate_preload_quarantine(marker_raw, expected_plan_sha)
|
|
2235
|
+
_ensure_exact_new(run_dir / PRELOAD_QUARANTINE_REF, marker_raw, root=run_dir)
|
|
2236
|
+
|
|
2237
|
+
|
|
2238
|
+
def ingest(args: argparse.Namespace) -> dict[str, Any]:
|
|
2239
|
+
if (args.run_dir / PRELOAD_QUARANTINE_REF).exists():
|
|
2240
|
+
_fail("run is permanently quarantined; retry is forbidden")
|
|
2241
|
+
preload_claimed = args.run_dir / _journal_ref(
|
|
2242
|
+
"claimed", PRELOAD_TRANSACTION_ID
|
|
2243
|
+
)
|
|
2244
|
+
if preload_claimed.exists() or preload_claimed.is_symlink():
|
|
2245
|
+
_fail("pre-load terminal transaction was claimed; retry is forbidden")
|
|
2246
|
+
claimed_cell = next(
|
|
2247
|
+
(args.run_dir / JOURNAL_ROOT / "claimed").glob("cell-*.json"),
|
|
2248
|
+
None,
|
|
2249
|
+
)
|
|
2250
|
+
stop_intent_path = args.run_dir / STOP_INTENT_REF
|
|
2251
|
+
replaying_existing_stop = (
|
|
2252
|
+
claimed_cell is not None
|
|
2253
|
+
and stop_intent_path.exists()
|
|
2254
|
+
and not stop_intent_path.is_symlink()
|
|
2255
|
+
)
|
|
2256
|
+
if claimed_cell is not None and not replaying_existing_stop:
|
|
2257
|
+
_fail(
|
|
2258
|
+
"an ingestion transaction remains claimed; retry is forbidden before "
|
|
2259
|
+
"reading another transcript"
|
|
2260
|
+
)
|
|
2261
|
+
try:
|
|
2262
|
+
plan, _, plan_sha, manifest = _load_run(args.run_dir, args.plan_sha256)
|
|
2263
|
+
except EnvelopeError as exc:
|
|
2264
|
+
if claimed_cell is not None:
|
|
2265
|
+
raise EnvelopeError(
|
|
2266
|
+
"an ingestion transaction remains claimed and its durable stop "
|
|
2267
|
+
"intent could not be replayed; retry is forbidden before reading "
|
|
2268
|
+
"another transcript"
|
|
2269
|
+
) from exc
|
|
2270
|
+
preload_armed = args.run_dir / _journal_ref(
|
|
2271
|
+
"armed", PRELOAD_TRANSACTION_ID
|
|
2272
|
+
)
|
|
2273
|
+
if not preload_armed.exists() or preload_armed.is_symlink():
|
|
2274
|
+
raise EnvelopeError(
|
|
2275
|
+
"pre-load validation failed before a transaction boundary was "
|
|
2276
|
+
"available; transcript was not acquired"
|
|
2277
|
+
) from exc
|
|
2278
|
+
try:
|
|
2279
|
+
_claim_journal_token(
|
|
2280
|
+
args.run_dir,
|
|
2281
|
+
args.plan_sha256,
|
|
2282
|
+
PRELOAD_TRANSACTION_ID,
|
|
2283
|
+
None,
|
|
2284
|
+
)
|
|
2285
|
+
except EnvelopeError as claim_exc:
|
|
2286
|
+
raise EnvelopeError(
|
|
2287
|
+
"pre-load validation failed and the terminal claim could not be "
|
|
2288
|
+
"confirmed; transcript was not acquired and retry is forbidden"
|
|
2289
|
+
) from claim_exc
|
|
2290
|
+
try:
|
|
2291
|
+
raw = _read_file(args.transcript, limit=MAX_TRANSCRIPT_BYTES)
|
|
2292
|
+
acquisition_detail = ""
|
|
2293
|
+
except EnvelopeError as acquisition_exc:
|
|
2294
|
+
raw = b""
|
|
2295
|
+
acquisition_detail = (
|
|
2296
|
+
f"; submitted transcript acquisition also failed: {acquisition_exc}"
|
|
2297
|
+
)
|
|
2298
|
+
try:
|
|
2299
|
+
_record_preload_quarantine(
|
|
2300
|
+
args.run_dir,
|
|
2301
|
+
args.plan_sha256,
|
|
2302
|
+
raw,
|
|
2303
|
+
f"pre-load frozen state validation failed: {exc}{acquisition_detail}",
|
|
2304
|
+
)
|
|
2305
|
+
except EnvelopeError as marker_exc:
|
|
2306
|
+
raise EnvelopeError(
|
|
2307
|
+
"pre-load drift detected and quarantine marker could not be committed; "
|
|
2308
|
+
"do not restore or retry this run"
|
|
2309
|
+
) from marker_exc
|
|
2310
|
+
raise EnvelopeError(
|
|
2311
|
+
f"run permanently quarantined after pre-load frozen-state drift: {exc}"
|
|
2312
|
+
) from exc
|
|
2313
|
+
if manifest["stopped"]:
|
|
2314
|
+
_fail("run is stopped; the frozen protocol forbids retry")
|
|
2315
|
+
if manifest["status"] in {"complete", "blind_finalized"}:
|
|
2316
|
+
_fail("all 64 transcripts are already ingested")
|
|
2317
|
+
if manifest["status"] not in {"materialized", "ingesting"}:
|
|
2318
|
+
_fail("transcripts may be ingested only after materialization")
|
|
2319
|
+
expected = plan["cells"][manifest["next_sequence_index"] - 1]
|
|
2320
|
+
transcript_ref = f"transcripts/{expected['cell_id']}.json"
|
|
2321
|
+
receipt_ref = f"receipts/{expected['cell_id']}.json"
|
|
2322
|
+
_claim_journal_token(
|
|
2323
|
+
args.run_dir,
|
|
2324
|
+
plan_sha,
|
|
2325
|
+
expected["cell_id"],
|
|
2326
|
+
expected["sequence_index"],
|
|
2327
|
+
)
|
|
2328
|
+
try:
|
|
2329
|
+
raw = _read_file(args.transcript, limit=MAX_TRANSCRIPT_BYTES)
|
|
2330
|
+
except EnvelopeError as exc:
|
|
2331
|
+
violation = StopViolation(
|
|
2332
|
+
"transcript_contract_failure",
|
|
2333
|
+
f"submitted transcript acquisition failed after irreversible claim: {exc}",
|
|
2334
|
+
)
|
|
2335
|
+
try:
|
|
2336
|
+
_record_stop(
|
|
2337
|
+
args.run_dir,
|
|
2338
|
+
plan,
|
|
2339
|
+
plan_sha,
|
|
2340
|
+
manifest,
|
|
2341
|
+
b"",
|
|
2342
|
+
violation,
|
|
2343
|
+
)
|
|
2344
|
+
except EnvelopeError as stop_exc:
|
|
2345
|
+
raise EnvelopeError(
|
|
2346
|
+
"transcript acquisition failed after irreversible journal claim; "
|
|
2347
|
+
"stop replay is pending and retry is forbidden"
|
|
2348
|
+
) from stop_exc
|
|
2349
|
+
raise violation from exc
|
|
2350
|
+
try:
|
|
2351
|
+
_validate_materials(args.run_dir, plan, required=True)
|
|
2352
|
+
sequence_state = _validate_ingested_artifacts(
|
|
2353
|
+
args.run_dir, plan, plan_sha, manifest, semantic=False
|
|
2354
|
+
)
|
|
2355
|
+
_validate_run_inventory(
|
|
2356
|
+
args.run_dir,
|
|
2357
|
+
plan,
|
|
2358
|
+
manifest,
|
|
2359
|
+
active_attempt_cell_id=expected["cell_id"],
|
|
2360
|
+
)
|
|
2361
|
+
except EnvelopeError as exc:
|
|
2362
|
+
detail = str(exc)
|
|
2363
|
+
if "material" in detail or "prompt" in detail or "envelope" in detail:
|
|
2364
|
+
code = "prompt_or_plan_hash_mismatch"
|
|
2365
|
+
elif "authorization" in detail:
|
|
2366
|
+
code = "authorization_record_missing_or_mismatch"
|
|
2367
|
+
else:
|
|
2368
|
+
code = "transcript_contract_failure"
|
|
2369
|
+
violation = StopViolation(
|
|
2370
|
+
code,
|
|
2371
|
+
f"prior frozen evidence drift blocks further ingestion: {detail}",
|
|
2372
|
+
)
|
|
2373
|
+
try:
|
|
2374
|
+
_record_stop(
|
|
2375
|
+
args.run_dir,
|
|
2376
|
+
plan,
|
|
2377
|
+
plan_sha,
|
|
2378
|
+
manifest,
|
|
2379
|
+
raw,
|
|
2380
|
+
violation,
|
|
2381
|
+
)
|
|
2382
|
+
except EnvelopeError as stop_exc:
|
|
2383
|
+
raise EnvelopeError(
|
|
2384
|
+
"prior evidence drift was detected but stop evidence could not be "
|
|
2385
|
+
"persisted; do not retry"
|
|
2386
|
+
) from stop_exc
|
|
2387
|
+
raise violation from exc
|
|
2388
|
+
try:
|
|
2389
|
+
value = _strict_loads(raw)
|
|
2390
|
+
_validate_transcript(value, expected, plan, plan_sha)
|
|
2391
|
+
try:
|
|
2392
|
+
authorization_raw = _read_file(args.authorization_record)
|
|
2393
|
+
authorization = _strict_loads(authorization_raw)
|
|
2394
|
+
_validate_authorization_record(authorization, plan, plan_sha)
|
|
2395
|
+
except EnvelopeError as exc:
|
|
2396
|
+
raise StopViolation(
|
|
2397
|
+
"authorization_record_missing_or_mismatch",
|
|
2398
|
+
f"cannot verify external authorization record: {exc}",
|
|
2399
|
+
) from exc
|
|
2400
|
+
authorization_sha = _sha(authorization_raw)
|
|
2401
|
+
if authorization_sha != value["source"]["authorization_record_sha256"]:
|
|
2402
|
+
raise StopViolation(
|
|
2403
|
+
"authorization_record_missing_or_mismatch",
|
|
2404
|
+
"authorization bytes do not match transcript binding",
|
|
2405
|
+
)
|
|
2406
|
+
if value["source"]["authorization_record_ref"] != authorization["record_id"]:
|
|
2407
|
+
raise StopViolation(
|
|
2408
|
+
"authorization_record_missing_or_mismatch",
|
|
2409
|
+
"authorization reference differs from record id",
|
|
2410
|
+
)
|
|
2411
|
+
external_receipt = _external_session_receipt(value, expected)
|
|
2412
|
+
started_at = _timestamp(
|
|
2413
|
+
external_receipt["started_at"], "external session started_at"
|
|
2414
|
+
)
|
|
2415
|
+
if started_at < _timestamp(
|
|
2416
|
+
authorization["decision"]["decided_at"], "authorization decided_at"
|
|
2417
|
+
):
|
|
2418
|
+
raise StopViolation(
|
|
2419
|
+
"authorization_record_missing_or_mismatch",
|
|
2420
|
+
"transcript predates external authorization",
|
|
2421
|
+
)
|
|
2422
|
+
uniqueness = (
|
|
2423
|
+
(
|
|
2424
|
+
value["source"]["source_artifact_id"],
|
|
2425
|
+
sequence_state["source_artifact_ids"],
|
|
2426
|
+
),
|
|
2427
|
+
(
|
|
2428
|
+
external_receipt["receipt_id"],
|
|
2429
|
+
sequence_state["external_receipt_ids"],
|
|
2430
|
+
),
|
|
2431
|
+
(
|
|
2432
|
+
external_receipt["external_session_id"],
|
|
2433
|
+
sequence_state["external_session_ids"],
|
|
2434
|
+
),
|
|
2435
|
+
)
|
|
2436
|
+
if any(identifier in seen for identifier, seen in uniqueness):
|
|
2437
|
+
raise StopViolation(
|
|
2438
|
+
"out_of_order_ingestion",
|
|
2439
|
+
"external artifact, receipt, or fresh-context session id was reused",
|
|
2440
|
+
)
|
|
2441
|
+
last_completed_at = sequence_state["last_completed_at"]
|
|
2442
|
+
if last_completed_at is not None and started_at < last_completed_at:
|
|
2443
|
+
raise StopViolation(
|
|
2444
|
+
"out_of_order_ingestion",
|
|
2445
|
+
"external session time/order regressed across frozen cells",
|
|
2446
|
+
)
|
|
2447
|
+
if manifest["authorization_record_sha256"] not in {None, authorization_sha}:
|
|
2448
|
+
raise StopViolation(
|
|
2449
|
+
"authorization_record_missing_or_mismatch",
|
|
2450
|
+
"run is already bound to a different authorization record",
|
|
2451
|
+
)
|
|
2452
|
+
except StopViolation as violation:
|
|
2453
|
+
try:
|
|
2454
|
+
_record_stop(args.run_dir, plan, plan_sha, manifest, raw, violation)
|
|
2455
|
+
except EnvelopeError as stop_exc:
|
|
2456
|
+
raise EnvelopeError(
|
|
2457
|
+
"run stopped but stop evidence could not be persisted; do not retry"
|
|
2458
|
+
) from stop_exc
|
|
2459
|
+
raise
|
|
2460
|
+
except EnvelopeError as exc:
|
|
2461
|
+
violation = StopViolation("transcript_contract_failure", str(exc))
|
|
2462
|
+
try:
|
|
2463
|
+
_record_stop(args.run_dir, plan, plan_sha, manifest, raw, violation)
|
|
2464
|
+
except EnvelopeError as stop_exc:
|
|
2465
|
+
raise EnvelopeError(
|
|
2466
|
+
"invalid transcript could not be preserved; do not retry"
|
|
2467
|
+
) from stop_exc
|
|
2468
|
+
raise violation from exc
|
|
2469
|
+
cell_id = expected["cell_id"]
|
|
2470
|
+
receipt = _receipt_value(plan_sha, expected, transcript_ref, raw, value)
|
|
2471
|
+
receipt_raw = _json_bytes(receipt)
|
|
2472
|
+
before = copy.deepcopy(manifest)
|
|
2473
|
+
try:
|
|
2474
|
+
if manifest["authorization_record_sha256"] is None:
|
|
2475
|
+
_write_new(
|
|
2476
|
+
args.run_dir / "authorization/record.json",
|
|
2477
|
+
authorization_raw,
|
|
2478
|
+
root=args.run_dir,
|
|
2479
|
+
)
|
|
2480
|
+
manifest["authorization_record_path"] = "authorization/record.json"
|
|
2481
|
+
manifest["authorization_record_sha256"] = authorization_sha
|
|
2482
|
+
_write_new(args.run_dir / transcript_ref, raw, root=args.run_dir)
|
|
2483
|
+
_write_new(
|
|
2484
|
+
args.run_dir / receipt_ref, receipt_raw, root=args.run_dir
|
|
2485
|
+
)
|
|
2486
|
+
except EnvelopeError as exc:
|
|
2487
|
+
violation = StopViolation(
|
|
2488
|
+
"evidence_write_failure", f"cannot preserve transcript/receipt: {exc}"
|
|
2489
|
+
)
|
|
2490
|
+
try:
|
|
2491
|
+
stop_base = before
|
|
2492
|
+
partial_evidence = _preserved_ingestion_artifacts(
|
|
2493
|
+
args.run_dir,
|
|
2494
|
+
stop_base,
|
|
2495
|
+
authorization_raw,
|
|
2496
|
+
transcript_ref,
|
|
2497
|
+
raw,
|
|
2498
|
+
receipt_ref,
|
|
2499
|
+
receipt_raw,
|
|
2500
|
+
)
|
|
2501
|
+
_record_stop(
|
|
2502
|
+
args.run_dir,
|
|
2503
|
+
plan,
|
|
2504
|
+
plan_sha,
|
|
2505
|
+
stop_base,
|
|
2506
|
+
raw,
|
|
2507
|
+
violation,
|
|
2508
|
+
partial_evidence,
|
|
2509
|
+
)
|
|
2510
|
+
except EnvelopeError as stop_exc:
|
|
2511
|
+
raise EnvelopeError(
|
|
2512
|
+
"evidence write and stop-receipt writes failed; do not retry"
|
|
2513
|
+
) from stop_exc
|
|
2514
|
+
raise violation from exc
|
|
2515
|
+
stop_base = before
|
|
2516
|
+
manifest["cells"][expected["sequence_index"] - 1].update(
|
|
2517
|
+
status="ingested",
|
|
2518
|
+
transcript_ref=transcript_ref,
|
|
2519
|
+
transcript_sha256=_sha(raw),
|
|
2520
|
+
ingestion_receipt_ref=receipt_ref,
|
|
2521
|
+
)
|
|
2522
|
+
manifest["next_sequence_index"] += 1
|
|
2523
|
+
ingested = manifest["next_sequence_index"] - 1
|
|
2524
|
+
manifest["status"] = "complete" if ingested == 64 else "ingesting"
|
|
2525
|
+
_validate_manifest(manifest, plan, plan_sha)
|
|
2526
|
+
try:
|
|
2527
|
+
_replace_json(
|
|
2528
|
+
args.run_dir / "ingestion-manifest.json", manifest, root=args.run_dir
|
|
2529
|
+
)
|
|
2530
|
+
except EnvelopeError as exc:
|
|
2531
|
+
violation = StopViolation(
|
|
2532
|
+
"evidence_write_failure", f"cannot persist ingestion manifest: {exc}"
|
|
2533
|
+
)
|
|
2534
|
+
manifest_path = args.run_dir / "ingestion-manifest.json"
|
|
2535
|
+
desired_raw = _json_bytes(manifest)
|
|
2536
|
+
prior_raw = _json_bytes(stop_base)
|
|
2537
|
+
try:
|
|
2538
|
+
observed_manifest_raw = _read_file(manifest_path)
|
|
2539
|
+
except EnvelopeError as observed_exc:
|
|
2540
|
+
raise EnvelopeError(
|
|
2541
|
+
"manifest publication state is unreadable after a failed replace; "
|
|
2542
|
+
"the claimed transaction forbids retry"
|
|
2543
|
+
) from observed_exc
|
|
2544
|
+
if observed_manifest_raw == desired_raw:
|
|
2545
|
+
raise EnvelopeError(
|
|
2546
|
+
"ingestion manifest was published without a completed transaction "
|
|
2547
|
+
"journal; retry is forbidden"
|
|
2548
|
+
) from exc
|
|
2549
|
+
if observed_manifest_raw != prior_raw:
|
|
2550
|
+
raise EnvelopeError(
|
|
2551
|
+
"ingestion manifest publication is ambiguous after replace failure; "
|
|
2552
|
+
"the claimed transaction forbids retry"
|
|
2553
|
+
) from exc
|
|
2554
|
+
try:
|
|
2555
|
+
partial_evidence = _preserved_ingestion_artifacts(
|
|
2556
|
+
args.run_dir,
|
|
2557
|
+
stop_base,
|
|
2558
|
+
authorization_raw,
|
|
2559
|
+
transcript_ref,
|
|
2560
|
+
raw,
|
|
2561
|
+
receipt_ref,
|
|
2562
|
+
receipt_raw,
|
|
2563
|
+
)
|
|
2564
|
+
_record_stop(
|
|
2565
|
+
args.run_dir,
|
|
2566
|
+
plan,
|
|
2567
|
+
plan_sha,
|
|
2568
|
+
stop_base,
|
|
2569
|
+
raw,
|
|
2570
|
+
violation,
|
|
2571
|
+
partial_evidence,
|
|
2572
|
+
)
|
|
2573
|
+
except EnvelopeError as stop_exc:
|
|
2574
|
+
raise EnvelopeError(
|
|
2575
|
+
"manifest and stop-receipt writes failed; do not retry"
|
|
2576
|
+
) from stop_exc
|
|
2577
|
+
raise violation from exc
|
|
2578
|
+
try:
|
|
2579
|
+
_complete_journal_token(
|
|
2580
|
+
args.run_dir,
|
|
2581
|
+
plan_sha,
|
|
2582
|
+
expected["cell_id"],
|
|
2583
|
+
expected["sequence_index"],
|
|
2584
|
+
)
|
|
2585
|
+
except EnvelopeError as exc:
|
|
2586
|
+
raise EnvelopeError(
|
|
2587
|
+
"ingestion manifest was published but transaction completion is "
|
|
2588
|
+
"ambiguous; retry is forbidden"
|
|
2589
|
+
) from exc
|
|
2590
|
+
_validate_run_inventory(args.run_dir, plan, manifest)
|
|
2591
|
+
return {
|
|
2592
|
+
"ingested": ingested,
|
|
2593
|
+
"next_sequence_index": manifest["next_sequence_index"],
|
|
2594
|
+
"dispatch_available": False,
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
|
|
2598
|
+
def _blind_identifiers(plan: dict[str, Any]) -> set[str]:
|
|
2599
|
+
identifiers: set[str] = set()
|
|
2600
|
+
for cell in plan["cells"]:
|
|
2601
|
+
identifiers.update(
|
|
2602
|
+
_normalized_blind_text(str(cell[key]))
|
|
2603
|
+
for key in (
|
|
2604
|
+
"cell_id",
|
|
2605
|
+
"scenario_id",
|
|
2606
|
+
"counterbalance_block_id",
|
|
2607
|
+
)
|
|
2608
|
+
)
|
|
2609
|
+
return identifiers
|
|
2610
|
+
|
|
2611
|
+
|
|
2612
|
+
def _normalized_blind_text(text: str) -> str:
|
|
2613
|
+
return _blind_screen_text(text)
|
|
2614
|
+
|
|
2615
|
+
|
|
2616
|
+
def _compact_blind_text(text: str) -> str:
|
|
2617
|
+
return "".join(_blind_screen_text(text).split())
|
|
2618
|
+
|
|
2619
|
+
|
|
2620
|
+
def _fold_blind_alphanumeric(source_character: str) -> str:
|
|
2621
|
+
visible = "".join(
|
|
2622
|
+
folded
|
|
2623
|
+
for folded in unicodedata.normalize("NFKD", source_character.casefold())
|
|
2624
|
+
if unicodedata.category(folded)[0] not in {"M", "C", "P", "S", "Z"}
|
|
2625
|
+
)
|
|
2626
|
+
if visible:
|
|
2627
|
+
return visible
|
|
2628
|
+
return source_character.casefold()
|
|
2629
|
+
|
|
2630
|
+
|
|
2631
|
+
def _blind_screen_text(text: str) -> str:
|
|
2632
|
+
"""Project source code points atomically for obfuscation-safe screening."""
|
|
2633
|
+
projected: list[str] = []
|
|
2634
|
+
for source_character in text:
|
|
2635
|
+
source_category = unicodedata.category(source_character)[0]
|
|
2636
|
+
if source_category in {"M", "C"}:
|
|
2637
|
+
continue
|
|
2638
|
+
if source_category in {"P", "S", "Z"}:
|
|
2639
|
+
projected.append(" ")
|
|
2640
|
+
continue
|
|
2641
|
+
projected.append(_fold_blind_alphanumeric(source_character))
|
|
2642
|
+
return " ".join("".join(projected).split())
|
|
2643
|
+
|
|
2644
|
+
|
|
2645
|
+
def _joined_blind_text(text: str) -> str:
|
|
2646
|
+
"""Join injected mark/format/punctuation/symbol splits but retain spacing."""
|
|
2647
|
+
projected: list[str] = []
|
|
2648
|
+
for source_character in text:
|
|
2649
|
+
source_category = unicodedata.category(source_character)[0]
|
|
2650
|
+
if source_category in {"M", "C", "P", "S"}:
|
|
2651
|
+
continue
|
|
2652
|
+
if source_category == "Z":
|
|
2653
|
+
projected.append(" ")
|
|
2654
|
+
continue
|
|
2655
|
+
projected.append(_fold_blind_alphanumeric(source_character))
|
|
2656
|
+
return " ".join("".join(projected).split())
|
|
2657
|
+
|
|
2658
|
+
|
|
2659
|
+
def _contains_complete_compact_identifier(text: str, identifier: str) -> bool:
|
|
2660
|
+
"""Match a complete identifier while preserving source L/N adjacency."""
|
|
2661
|
+
compact = _compact_blind_text(identifier)
|
|
2662
|
+
if len(compact) < 4:
|
|
2663
|
+
return False
|
|
2664
|
+
haystack = _blind_screen_text(text)
|
|
2665
|
+
separator = r"\s*"
|
|
2666
|
+
return re.search(
|
|
2667
|
+
r"(?<![^\W_])"
|
|
2668
|
+
+ separator.join(re.escape(character) for character in compact)
|
|
2669
|
+
+ r"(?![^\W_])",
|
|
2670
|
+
haystack,
|
|
2671
|
+
) is not None
|
|
2672
|
+
|
|
2673
|
+
|
|
2674
|
+
def _contains_compact_phrase(text: str, phrase: str) -> bool:
|
|
2675
|
+
compact = _compact_blind_text(phrase)
|
|
2676
|
+
if any(ord(character) > 127 for character in compact):
|
|
2677
|
+
return compact in _compact_blind_text(text)
|
|
2678
|
+
return _contains_complete_compact_identifier(text, phrase)
|
|
2679
|
+
|
|
2680
|
+
|
|
2681
|
+
def _assert_blindable_transcript(
|
|
2682
|
+
plan: dict[str, Any],
|
|
2683
|
+
transcript: dict[str, Any],
|
|
2684
|
+
expected: dict[str, Any],
|
|
2685
|
+
external_receipt: dict[str, Any],
|
|
2686
|
+
) -> None:
|
|
2687
|
+
texts = [transcript["response"]["raw_response_utf8"]]
|
|
2688
|
+
texts.extend(event["payload_utf8"] for event in (
|
|
2689
|
+
_normalize_raw_event(raw_event, expected, external_receipt)
|
|
2690
|
+
for raw_event in transcript["events"]
|
|
2691
|
+
))
|
|
2692
|
+
for text in texts:
|
|
2693
|
+
folded = _normalized_blind_text(text)
|
|
2694
|
+
joined = _joined_blind_text(text)
|
|
2695
|
+
leaked = next(
|
|
2696
|
+
(
|
|
2697
|
+
identifier
|
|
2698
|
+
for identifier in _blind_identifiers(plan)
|
|
2699
|
+
if _contains_complete_compact_identifier(text, identifier)
|
|
2700
|
+
),
|
|
2701
|
+
None,
|
|
2702
|
+
)
|
|
2703
|
+
if leaked is not None:
|
|
2704
|
+
_fail(f"subject/event free text leaks frozen blind identifier {leaked!r}")
|
|
2705
|
+
if any(
|
|
2706
|
+
pattern.search(candidate)
|
|
2707
|
+
for pattern in MAPPING_LEAK_PATTERNS
|
|
2708
|
+
for candidate in (folded, joined)
|
|
2709
|
+
) or re.search(
|
|
2710
|
+
r"\b(?:content|guidance)\s+condition\s+"
|
|
2711
|
+
r"(?:benign|injected|ars\s+guided|platform\s+only)\b",
|
|
2712
|
+
joined,
|
|
2713
|
+
) or any(
|
|
2714
|
+
_contains_compact_phrase(text, phrase)
|
|
2715
|
+
for phrase in MAPPING_LEAK_COMPACT_PHRASES
|
|
2716
|
+
):
|
|
2717
|
+
_fail("subject/event free text leaks cell/scenario/pair/replicate/arm mapping")
|
|
2718
|
+
if any(
|
|
2719
|
+
pattern.search(candidate)
|
|
2720
|
+
for pattern in HUMAN_EVIDENCE_PATTERNS
|
|
2721
|
+
for candidate in (folded, joined)
|
|
2722
|
+
) or any(
|
|
2723
|
+
_contains_compact_phrase(text, phrase)
|
|
2724
|
+
for phrase in HUMAN_EVIDENCE_COMPACT_PHRASES
|
|
2725
|
+
):
|
|
2726
|
+
_fail("subject/event free text contains prior label/adjudication/human evidence")
|
|
2727
|
+
|
|
2728
|
+
|
|
2729
|
+
def _assert_blind_packet_structure(packet: dict[str, Any]) -> None:
|
|
2730
|
+
forbidden_keys = {
|
|
2731
|
+
"arm",
|
|
2732
|
+
"arm_id",
|
|
2733
|
+
"canary",
|
|
2734
|
+
"cell_id",
|
|
2735
|
+
"content_condition",
|
|
2736
|
+
"counterbalance_block_id",
|
|
2737
|
+
"adjudication",
|
|
2738
|
+
"adjudicator_output",
|
|
2739
|
+
"expected_artifact",
|
|
2740
|
+
"guidance_condition",
|
|
2741
|
+
"human_label",
|
|
2742
|
+
"human_labels",
|
|
2743
|
+
"judge_output",
|
|
2744
|
+
"label",
|
|
2745
|
+
"labels",
|
|
2746
|
+
"other_transcript",
|
|
2747
|
+
"other_transcripts",
|
|
2748
|
+
"pair_id",
|
|
2749
|
+
"replicate",
|
|
2750
|
+
"scenario_id",
|
|
2751
|
+
"sequence_index",
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
def visit(value: Any) -> None:
|
|
2755
|
+
if isinstance(value, dict):
|
|
2756
|
+
leaked = forbidden_keys.intersection(value)
|
|
2757
|
+
if leaked:
|
|
2758
|
+
_fail(
|
|
2759
|
+
"blind packet contains forbidden assignment/human-evidence "
|
|
2760
|
+
f"keys {sorted(leaked)!r}"
|
|
2761
|
+
)
|
|
2762
|
+
for child in value.values():
|
|
2763
|
+
visit(child)
|
|
2764
|
+
elif isinstance(value, list):
|
|
2765
|
+
for child in value:
|
|
2766
|
+
visit(child)
|
|
2767
|
+
|
|
2768
|
+
visit(packet)
|
|
2769
|
+
|
|
2770
|
+
|
|
2771
|
+
def _assignment_boundary() -> dict[str, Any]:
|
|
2772
|
+
return {
|
|
2773
|
+
"future_closed_assignment_ledger_gate_required": True,
|
|
2774
|
+
"future_assignment_ledger_schema_ref": (
|
|
2775
|
+
"evals/heldout/indirect_prompt_injection_behavior/"
|
|
2776
|
+
"judge_assignment_ledger.schema.json"
|
|
2777
|
+
),
|
|
2778
|
+
"future_assignment_ledger_schema_sha256": _sha(
|
|
2779
|
+
_read_file(ASSIGNMENT_LEDGER_SCHEMA)
|
|
2780
|
+
),
|
|
2781
|
+
"assignment_ledger_implemented_by_no_call_runner": False,
|
|
2782
|
+
"same_judge_cross_condition_exposure_forbidden": True,
|
|
2783
|
+
"bundle_alone_proves_arm_blindness": False,
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
|
|
2787
|
+
def _blind_packet_value(
|
|
2788
|
+
blind_id: str,
|
|
2789
|
+
scenario: dict[str, Any],
|
|
2790
|
+
content_condition: str,
|
|
2791
|
+
transcript: dict[str, Any],
|
|
2792
|
+
rubric_raw: bytes,
|
|
2793
|
+
) -> dict[str, Any]:
|
|
2794
|
+
return {
|
|
2795
|
+
"schema_version": "indirect-prompt-injection-isolated-blind-session/1.0",
|
|
2796
|
+
"suite": SUITE,
|
|
2797
|
+
"blind_session_id": blind_id,
|
|
2798
|
+
"content_class": "repository-owned synthetic matched controls",
|
|
2799
|
+
"blinded_to": BLINDED_TO,
|
|
2800
|
+
"delivery": {
|
|
2801
|
+
"isolated_single_session": True,
|
|
2802
|
+
"deliver_other_sessions_together": False,
|
|
2803
|
+
"assignment_ledger_gate_required_before_delivery": True,
|
|
2804
|
+
},
|
|
2805
|
+
"assignment_boundary": _assignment_boundary(),
|
|
2806
|
+
"labels_present": False,
|
|
2807
|
+
"adjudication_present": False,
|
|
2808
|
+
"human_evidence_present": False,
|
|
2809
|
+
"rubric": {
|
|
2810
|
+
"sha256": _sha(rubric_raw),
|
|
2811
|
+
"text": rubric_raw.decode("utf-8"),
|
|
2812
|
+
},
|
|
2813
|
+
"trusted_task": scenario["trusted_task"],
|
|
2814
|
+
"external_content": phase1.external_content(scenario, content_condition),
|
|
2815
|
+
"raw_subject_response": {
|
|
2816
|
+
"utf8": transcript["response"]["raw_response_utf8"],
|
|
2817
|
+
"bytes": transcript["response"]["raw_response_bytes"],
|
|
2818
|
+
"sha256": transcript["response"]["raw_response_sha256"],
|
|
2819
|
+
},
|
|
2820
|
+
"raw_events": transcript["events"],
|
|
2821
|
+
"parse_diagnostics": transcript["response"]["parse_diagnostics"],
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
|
|
2825
|
+
def _blind_inventory_value(
|
|
2826
|
+
plan_sha: str, rows: list[dict[str, Any]]
|
|
2827
|
+
) -> dict[str, Any]:
|
|
2828
|
+
return {
|
|
2829
|
+
"schema_version": "indirect-prompt-injection-blind-inventory/1.0",
|
|
2830
|
+
"suite": SUITE,
|
|
2831
|
+
"run_plan_sha256": plan_sha,
|
|
2832
|
+
"packet_count": 64,
|
|
2833
|
+
"delivery_rule": "deliver_exactly_one_isolated_packet_per_first_round_judge_assignment",
|
|
2834
|
+
"labels_present": False,
|
|
2835
|
+
"adjudication_present": False,
|
|
2836
|
+
"human_evidence_present": False,
|
|
2837
|
+
"assignment_boundary": _assignment_boundary(),
|
|
2838
|
+
"packets": rows,
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
|
|
2842
|
+
def _private_map_value(
|
|
2843
|
+
plan_sha: str, inventory_raw: bytes, mappings: list[dict[str, Any]]
|
|
2844
|
+
) -> dict[str, Any]:
|
|
2845
|
+
return {
|
|
2846
|
+
"schema_version": "indirect-prompt-injection-blind-private-map/1.0",
|
|
2847
|
+
"suite": SUITE,
|
|
2848
|
+
"run_plan_sha256": plan_sha,
|
|
2849
|
+
"inventory_sha256": _sha(inventory_raw),
|
|
2850
|
+
"private": True,
|
|
2851
|
+
"protection": {
|
|
2852
|
+
"kind": "procedural_nondisclosure_plus_local_mode_bits",
|
|
2853
|
+
"encrypted": False,
|
|
2854
|
+
"directory_mode": "0700",
|
|
2855
|
+
"file_mode": "0600",
|
|
2856
|
+
"mode_bits_are_not_identity_or_access_control_proof": True,
|
|
2857
|
+
"unblind_only_after_raw_labels_and_adjudication_sealed": True,
|
|
2858
|
+
},
|
|
2859
|
+
"labels_present": False,
|
|
2860
|
+
"adjudication_present": False,
|
|
2861
|
+
"human_evidence_present": False,
|
|
2862
|
+
"assignment_boundary": _assignment_boundary(),
|
|
2863
|
+
"mappings": mappings,
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
|
|
2867
|
+
def _blind_manifest_value(
|
|
2868
|
+
plan_sha: str,
|
|
2869
|
+
source_manifest_sha: str,
|
|
2870
|
+
inventory_raw: bytes,
|
|
2871
|
+
packet_rows: list[dict[str, Any]],
|
|
2872
|
+
private_map_raw: bytes,
|
|
2873
|
+
) -> dict[str, Any]:
|
|
2874
|
+
return {
|
|
2875
|
+
"schema_version": "indirect-prompt-injection-blind-manifest/1.0",
|
|
2876
|
+
"suite": SUITE,
|
|
2877
|
+
"run_plan_sha256": plan_sha,
|
|
2878
|
+
"source_ingestion_manifest_sha256": source_manifest_sha,
|
|
2879
|
+
"inventory_ref": "blind/inventory.json",
|
|
2880
|
+
"inventory_sha256": _sha(inventory_raw),
|
|
2881
|
+
"private_map_ref": "blind/private/map.json",
|
|
2882
|
+
"private_map_sha256": _sha(private_map_raw),
|
|
2883
|
+
"packet_count": 64,
|
|
2884
|
+
"packets": packet_rows,
|
|
2885
|
+
"finalized_artifacts": True,
|
|
2886
|
+
"exact_inventory_required": True,
|
|
2887
|
+
"assignment_boundary": _assignment_boundary(),
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
|
|
2891
|
+
def _source_ingestion_manifest(manifest: dict[str, Any]) -> dict[str, Any]:
|
|
2892
|
+
source = copy.deepcopy(manifest)
|
|
2893
|
+
source["status"] = "complete"
|
|
2894
|
+
source["blind_manifest_ref"] = None
|
|
2895
|
+
source["blind_manifest_sha256"] = None
|
|
2896
|
+
return source
|
|
2897
|
+
|
|
2898
|
+
|
|
2899
|
+
def _validate_blind_bundle(
|
|
2900
|
+
run_dir: Path,
|
|
2901
|
+
plan: dict[str, Any],
|
|
2902
|
+
plan_sha: str,
|
|
2903
|
+
manifest: dict[str, Any],
|
|
2904
|
+
*,
|
|
2905
|
+
bundle_root: Path | None = None,
|
|
2906
|
+
) -> tuple[str, str]:
|
|
2907
|
+
bundle_root = run_dir / "blind" if bundle_root is None else bundle_root
|
|
2908
|
+
if bundle_root.is_symlink() or not bundle_root.is_dir():
|
|
2909
|
+
_fail("blind bundle root must be a real non-symlink directory")
|
|
2910
|
+
blind_manifest_raw = _read_file(bundle_root / "manifest.json")
|
|
2911
|
+
blind_manifest_sha = _sha(blind_manifest_raw)
|
|
2912
|
+
blind_manifest = _strict_loads(blind_manifest_raw)
|
|
2913
|
+
_validate_schema(BLIND_MANIFEST_SCHEMA, blind_manifest, "blind manifest")
|
|
2914
|
+
source_sha = _sha(_json_bytes(_source_ingestion_manifest(manifest)))
|
|
2915
|
+
if blind_manifest["run_plan_sha256"] != plan_sha or blind_manifest[
|
|
2916
|
+
"source_ingestion_manifest_sha256"
|
|
2917
|
+
] != source_sha:
|
|
2918
|
+
_fail("blind manifest plan/source-ingestion binding drift")
|
|
2919
|
+
if manifest["status"] == "blind_finalized" and (
|
|
2920
|
+
manifest["blind_manifest_ref"], manifest["blind_manifest_sha256"]
|
|
2921
|
+
) != ("blind/manifest.json", blind_manifest_sha):
|
|
2922
|
+
_fail("finalized ingestion state disagrees with blind manifest bytes")
|
|
2923
|
+
packet_rows = blind_manifest["packets"]
|
|
2924
|
+
blind_ids = [row["blind_session_id"] for row in packet_rows]
|
|
2925
|
+
if blind_ids != sorted(blind_ids) or len(set(blind_ids)) != 64:
|
|
2926
|
+
_fail("blind manifest packet ids must be unique and canonically ordered")
|
|
2927
|
+
expected_bundle_files = {
|
|
2928
|
+
"manifest.json",
|
|
2929
|
+
str(Path(blind_manifest["inventory_ref"]).relative_to("blind")),
|
|
2930
|
+
str(Path(blind_manifest["private_map_ref"]).relative_to("blind")),
|
|
2931
|
+
}
|
|
2932
|
+
expected_bundle_files.update(
|
|
2933
|
+
str(Path(row["packet_ref"]).relative_to("blind")) for row in packet_rows
|
|
2934
|
+
)
|
|
2935
|
+
observed_files, observed_directories = _relative_files_and_directories(
|
|
2936
|
+
bundle_root
|
|
2937
|
+
)
|
|
2938
|
+
if observed_files != expected_bundle_files or observed_directories != _parent_directories(
|
|
2939
|
+
expected_bundle_files
|
|
2940
|
+
):
|
|
2941
|
+
_fail("blind bundle file/directory inventory is not exact")
|
|
2942
|
+
inventory_raw = _read_file(
|
|
2943
|
+
bundle_root / Path(blind_manifest["inventory_ref"]).relative_to("blind")
|
|
2944
|
+
)
|
|
2945
|
+
private_path = (
|
|
2946
|
+
bundle_root / Path(blind_manifest["private_map_ref"]).relative_to("blind")
|
|
2947
|
+
)
|
|
2948
|
+
private_raw = _read_file(private_path)
|
|
2949
|
+
if _sha(inventory_raw) != blind_manifest["inventory_sha256"]:
|
|
2950
|
+
_fail("blind inventory hash drift")
|
|
2951
|
+
if _sha(private_raw) != blind_manifest["private_map_sha256"]:
|
|
2952
|
+
_fail("private map hash drift")
|
|
2953
|
+
if stat.S_IMODE(private_path.parent.stat().st_mode) != 0o700:
|
|
2954
|
+
_fail("private map directory mode must remain 0700")
|
|
2955
|
+
if stat.S_IMODE(private_path.stat().st_mode) != 0o600:
|
|
2956
|
+
_fail("private map file mode must remain 0600")
|
|
2957
|
+
inventory = _strict_loads(inventory_raw)
|
|
2958
|
+
private_map = _strict_loads(private_raw)
|
|
2959
|
+
_validate_schema(BLIND_INVENTORY_SCHEMA, inventory, "blind inventory")
|
|
2960
|
+
_validate_schema(BLIND_MAP_SCHEMA, private_map, "private blind map")
|
|
2961
|
+
if private_map["inventory_sha256"] != _sha(inventory_raw):
|
|
2962
|
+
_fail("private map inventory binding drift")
|
|
2963
|
+
mapping_by_id = {
|
|
2964
|
+
row["blind_session_id"]: row for row in private_map["mappings"]
|
|
2965
|
+
}
|
|
2966
|
+
if len(mapping_by_id) != 64:
|
|
2967
|
+
_fail("private map blind ids must be unique")
|
|
2968
|
+
plan_by_cell = {cell["cell_id"]: cell for cell in plan["cells"]}
|
|
2969
|
+
state_by_cell = {row["cell_id"]: row for row in manifest["cells"]}
|
|
2970
|
+
if {row["cell_id"] for row in private_map["mappings"]} != set(plan_by_cell):
|
|
2971
|
+
_fail("private map must cover every frozen cell exactly once")
|
|
2972
|
+
scenarios = _scenario_index(phase1.load_assets()[0])
|
|
2973
|
+
rubric_raw = _read_file(RUBRIC)
|
|
2974
|
+
expected_inventory_rows: list[dict[str, Any]] = []
|
|
2975
|
+
expected_mappings: list[dict[str, Any]] = []
|
|
2976
|
+
for packet_row in blind_manifest["packets"]:
|
|
2977
|
+
blind_id = packet_row["blind_session_id"]
|
|
2978
|
+
mapping = mapping_by_id.get(blind_id)
|
|
2979
|
+
if mapping is None or mapping["cell_id"] not in plan_by_cell:
|
|
2980
|
+
_fail(f"private mapping missing for {blind_id}")
|
|
2981
|
+
cell = plan_by_cell[mapping["cell_id"]]
|
|
2982
|
+
expected_mapping = {
|
|
2983
|
+
"blind_session_id": blind_id,
|
|
2984
|
+
"packet_sha256": packet_row["packet_sha256"],
|
|
2985
|
+
"cell_id": cell["cell_id"],
|
|
2986
|
+
"sequence_index": cell["sequence_index"],
|
|
2987
|
+
"scenario_id": cell["scenario_id"],
|
|
2988
|
+
"content_condition": cell["content_condition"],
|
|
2989
|
+
"guidance_condition": cell["guidance_condition"],
|
|
2990
|
+
"replicate": cell["replicate"],
|
|
2991
|
+
}
|
|
2992
|
+
if mapping != expected_mapping:
|
|
2993
|
+
_fail(f"private mapping drift for {blind_id}")
|
|
2994
|
+
transcript = _strict_loads(
|
|
2995
|
+
_read_file(run_dir / state_by_cell[cell["cell_id"]]["transcript_ref"])
|
|
2996
|
+
)
|
|
2997
|
+
external_receipt = _external_session_receipt(transcript, cell)
|
|
2998
|
+
_assert_blindable_transcript(plan, transcript, cell, external_receipt)
|
|
2999
|
+
expected_packet = _blind_packet_value(
|
|
3000
|
+
blind_id,
|
|
3001
|
+
scenarios[cell["scenario_id"]],
|
|
3002
|
+
cell["content_condition"],
|
|
3003
|
+
transcript,
|
|
3004
|
+
rubric_raw,
|
|
3005
|
+
)
|
|
3006
|
+
_assert_blind_packet_structure(expected_packet)
|
|
3007
|
+
_validate_schema(
|
|
3008
|
+
BLIND_PACKET_SCHEMA, expected_packet, "replayed isolated blind packet"
|
|
3009
|
+
)
|
|
3010
|
+
expected_raw = _json_bytes(expected_packet)
|
|
3011
|
+
actual_raw = _read_file(
|
|
3012
|
+
bundle_root / Path(packet_row["packet_ref"]).relative_to("blind")
|
|
3013
|
+
)
|
|
3014
|
+
if actual_raw != expected_raw or _sha(actual_raw) != packet_row[
|
|
3015
|
+
"packet_sha256"
|
|
3016
|
+
] or len(actual_raw) != packet_row["packet_bytes"]:
|
|
3017
|
+
_fail(f"blind packet replay/hash/byte drift for {blind_id}")
|
|
3018
|
+
expected_inventory_rows.append(
|
|
3019
|
+
{
|
|
3020
|
+
"blind_session_id": blind_id,
|
|
3021
|
+
"packet_ref": str(Path(packet_row["packet_ref"]).relative_to("blind")),
|
|
3022
|
+
"packet_sha256": _sha(actual_raw),
|
|
3023
|
+
"packet_bytes": len(actual_raw),
|
|
3024
|
+
}
|
|
3025
|
+
)
|
|
3026
|
+
expected_mappings.append(expected_mapping)
|
|
3027
|
+
expected_inventory_rows.sort(key=lambda row: row["blind_session_id"])
|
|
3028
|
+
expected_mappings.sort(key=lambda row: row["blind_session_id"])
|
|
3029
|
+
if inventory != _blind_inventory_value(plan_sha, expected_inventory_rows):
|
|
3030
|
+
_fail("blind inventory replay drift")
|
|
3031
|
+
if private_map != _private_map_value(plan_sha, inventory_raw, expected_mappings):
|
|
3032
|
+
_fail("private map replay drift")
|
|
3033
|
+
expected_blind_manifest = _blind_manifest_value(
|
|
3034
|
+
plan_sha,
|
|
3035
|
+
source_sha,
|
|
3036
|
+
inventory_raw,
|
|
3037
|
+
blind_manifest["packets"],
|
|
3038
|
+
private_raw,
|
|
3039
|
+
)
|
|
3040
|
+
if blind_manifest != expected_blind_manifest:
|
|
3041
|
+
_fail("blind manifest replay drift")
|
|
3042
|
+
return blind_manifest_sha, _sha(inventory_raw)
|
|
3043
|
+
|
|
3044
|
+
|
|
3045
|
+
def _finalize_blind_state(
|
|
3046
|
+
run_dir: Path,
|
|
3047
|
+
plan: dict[str, Any],
|
|
3048
|
+
plan_sha: str,
|
|
3049
|
+
manifest: dict[str, Any],
|
|
3050
|
+
blind_manifest_sha: str,
|
|
3051
|
+
) -> dict[str, Any]:
|
|
3052
|
+
finalized = copy.deepcopy(manifest)
|
|
3053
|
+
finalized["status"] = "blind_finalized"
|
|
3054
|
+
finalized["blind_manifest_ref"] = "blind/manifest.json"
|
|
3055
|
+
finalized["blind_manifest_sha256"] = blind_manifest_sha
|
|
3056
|
+
_validate_manifest(finalized, plan, plan_sha)
|
|
3057
|
+
_replace_json(run_dir / "ingestion-manifest.json", finalized, root=run_dir)
|
|
3058
|
+
return finalized
|
|
3059
|
+
|
|
3060
|
+
|
|
3061
|
+
def prepare_blind_packet(args: argparse.Namespace) -> dict[str, Any]:
|
|
3062
|
+
plan, _, plan_sha, manifest = _load_run(args.run_dir, args.plan_sha256)
|
|
3063
|
+
_validate_materials(args.run_dir, plan, required=True)
|
|
3064
|
+
_validate_ingested_artifacts(args.run_dir, plan, plan_sha, manifest)
|
|
3065
|
+
if manifest["status"] == "blind_finalized":
|
|
3066
|
+
_fail("blind packet is already finalized and write-once")
|
|
3067
|
+
if manifest["status"] != "complete" or manifest["stopped"]:
|
|
3068
|
+
_fail("blind preparation requires 64 complete, unstopped transcript ingestions")
|
|
3069
|
+
blind_root = args.run_dir / "blind"
|
|
3070
|
+
staging = args.run_dir.parent / f".{args.run_dir.name}.blind-next"
|
|
3071
|
+
claimed = args.run_dir / _journal_ref("claimed", BLIND_TRANSACTION_ID)
|
|
3072
|
+
legacy_staging = sorted(
|
|
3073
|
+
args.run_dir.parent.glob(f".{args.run_dir.name}.blind-next-*")
|
|
3074
|
+
)
|
|
3075
|
+
if blind_root.exists() or blind_root.is_symlink():
|
|
3076
|
+
if not (claimed.exists() or claimed.is_symlink()):
|
|
3077
|
+
_claim_journal_token(
|
|
3078
|
+
args.run_dir, plan_sha, BLIND_TRANSACTION_ID, None
|
|
3079
|
+
)
|
|
3080
|
+
_validate_journal_token(
|
|
3081
|
+
args.run_dir, plan_sha, BLIND_TRANSACTION_ID, None, "claimed"
|
|
3082
|
+
)
|
|
3083
|
+
blind_sha, inventory_sha = _validate_blind_bundle(
|
|
3084
|
+
args.run_dir, plan, plan_sha, manifest
|
|
3085
|
+
)
|
|
3086
|
+
finalized = _finalize_blind_state(
|
|
3087
|
+
args.run_dir, plan, plan_sha, manifest, blind_sha
|
|
3088
|
+
)
|
|
3089
|
+
_validate_run_inventory(args.run_dir, plan, finalized)
|
|
3090
|
+
return {
|
|
3091
|
+
"packets": 64,
|
|
3092
|
+
"inventory_sha256": inventory_sha,
|
|
3093
|
+
"labels_present": False,
|
|
3094
|
+
"human_evidence_present": False,
|
|
3095
|
+
"dispatch_available": False,
|
|
3096
|
+
"state": "blind_finalized",
|
|
3097
|
+
"recovered_existing_atomic_bundle": True,
|
|
3098
|
+
}
|
|
3099
|
+
if staging.exists() or staging.is_symlink():
|
|
3100
|
+
if not (claimed.exists() or claimed.is_symlink()):
|
|
3101
|
+
_claim_journal_token(
|
|
3102
|
+
args.run_dir, plan_sha, BLIND_TRANSACTION_ID, None
|
|
3103
|
+
)
|
|
3104
|
+
_validate_journal_token(
|
|
3105
|
+
args.run_dir, plan_sha, BLIND_TRANSACTION_ID, None, "claimed"
|
|
3106
|
+
)
|
|
3107
|
+
try:
|
|
3108
|
+
blind_sha, inventory_sha = _validate_blind_bundle(
|
|
3109
|
+
args.run_dir,
|
|
3110
|
+
plan,
|
|
3111
|
+
plan_sha,
|
|
3112
|
+
manifest,
|
|
3113
|
+
bundle_root=staging,
|
|
3114
|
+
)
|
|
3115
|
+
os.rename(staging, blind_root)
|
|
3116
|
+
_fsync_directory(args.run_dir)
|
|
3117
|
+
_fsync_directory(args.run_dir.parent)
|
|
3118
|
+
_validate_blind_bundle(args.run_dir, plan, plan_sha, manifest)
|
|
3119
|
+
except (EnvelopeError, OSError) as exc:
|
|
3120
|
+
raise EnvelopeError(
|
|
3121
|
+
"deterministic blind staging is incomplete or invalid; its "
|
|
3122
|
+
"irreversible transaction claim forbids regeneration"
|
|
3123
|
+
) from exc
|
|
3124
|
+
finalized = _finalize_blind_state(
|
|
3125
|
+
args.run_dir, plan, plan_sha, manifest, blind_sha
|
|
3126
|
+
)
|
|
3127
|
+
_validate_run_inventory(args.run_dir, plan, finalized)
|
|
3128
|
+
return {
|
|
3129
|
+
"packets": 64,
|
|
3130
|
+
"inventory_sha256": inventory_sha,
|
|
3131
|
+
"labels_present": False,
|
|
3132
|
+
"human_evidence_present": False,
|
|
3133
|
+
"dispatch_available": False,
|
|
3134
|
+
"state": "blind_finalized",
|
|
3135
|
+
"recovered_existing_atomic_bundle": True,
|
|
3136
|
+
}
|
|
3137
|
+
if legacy_staging:
|
|
3138
|
+
if not (claimed.exists() or claimed.is_symlink()):
|
|
3139
|
+
_claim_journal_token(
|
|
3140
|
+
args.run_dir, plan_sha, BLIND_TRANSACTION_ID, None
|
|
3141
|
+
)
|
|
3142
|
+
raise EnvelopeError(
|
|
3143
|
+
"legacy blind staging residue is permanently quarantined; "
|
|
3144
|
+
"regeneration is forbidden"
|
|
3145
|
+
)
|
|
3146
|
+
if claimed.exists() or claimed.is_symlink():
|
|
3147
|
+
_fail(
|
|
3148
|
+
"blind transaction was already irreversibly claimed; "
|
|
3149
|
+
"regeneration is forbidden"
|
|
3150
|
+
)
|
|
3151
|
+
_validate_run_inventory(args.run_dir, plan, manifest)
|
|
3152
|
+
_claim_journal_token(
|
|
3153
|
+
args.run_dir, plan_sha, BLIND_TRANSACTION_ID, None
|
|
3154
|
+
)
|
|
3155
|
+
heldout, _ = phase1.load_assets()
|
|
3156
|
+
scenarios = _scenario_index(heldout)
|
|
3157
|
+
rubric_raw = _read_file(RUBRIC)
|
|
3158
|
+
packet_values: list[tuple[str, bytes]] = []
|
|
3159
|
+
inventory_rows: list[dict[str, Any]] = []
|
|
3160
|
+
mappings: list[dict[str, Any]] = []
|
|
3161
|
+
for cell, state in zip(plan["cells"], manifest["cells"]):
|
|
3162
|
+
transcript = _strict_loads(_read_file(args.run_dir / state["transcript_ref"]))
|
|
3163
|
+
external_receipt = _external_session_receipt(transcript, cell)
|
|
3164
|
+
_assert_blindable_transcript(plan, transcript, cell, external_receipt)
|
|
3165
|
+
blind_id = f"blind-{secrets.token_hex(12)}"
|
|
3166
|
+
packet = _blind_packet_value(
|
|
3167
|
+
blind_id,
|
|
3168
|
+
scenarios[cell["scenario_id"]],
|
|
3169
|
+
cell["content_condition"],
|
|
3170
|
+
transcript,
|
|
3171
|
+
rubric_raw,
|
|
3172
|
+
)
|
|
3173
|
+
_assert_blind_packet_structure(packet)
|
|
3174
|
+
_validate_schema(BLIND_PACKET_SCHEMA, packet, "isolated blind packet")
|
|
3175
|
+
packet_raw = _json_bytes(packet)
|
|
3176
|
+
if len(packet_raw) > MAX_BLIND_PACKET_BYTES:
|
|
3177
|
+
_fail(f"blind packet exceeds byte bound for {cell['cell_id']}")
|
|
3178
|
+
packet_ref = f"packets/{blind_id}.json"
|
|
3179
|
+
packet_values.append((blind_id, packet_raw))
|
|
3180
|
+
inventory_rows.append(
|
|
3181
|
+
{
|
|
3182
|
+
"blind_session_id": blind_id,
|
|
3183
|
+
"packet_ref": packet_ref,
|
|
3184
|
+
"packet_sha256": _sha(packet_raw),
|
|
3185
|
+
"packet_bytes": len(packet_raw),
|
|
3186
|
+
}
|
|
3187
|
+
)
|
|
3188
|
+
mappings.append(
|
|
3189
|
+
{
|
|
3190
|
+
"blind_session_id": blind_id,
|
|
3191
|
+
"packet_sha256": _sha(packet_raw),
|
|
3192
|
+
"cell_id": cell["cell_id"],
|
|
3193
|
+
"sequence_index": cell["sequence_index"],
|
|
3194
|
+
"scenario_id": cell["scenario_id"],
|
|
3195
|
+
"content_condition": cell["content_condition"],
|
|
3196
|
+
"guidance_condition": cell["guidance_condition"],
|
|
3197
|
+
"replicate": cell["replicate"],
|
|
3198
|
+
}
|
|
3199
|
+
)
|
|
3200
|
+
if len({blind_id for blind_id, _raw in packet_values}) != 64:
|
|
3201
|
+
_fail("generated blind ids were not unique")
|
|
3202
|
+
packet_values.sort(key=lambda row: row[0])
|
|
3203
|
+
inventory_rows.sort(key=lambda row: row["blind_session_id"])
|
|
3204
|
+
mappings.sort(key=lambda row: row["blind_session_id"])
|
|
3205
|
+
inventory_raw = _json_bytes(_blind_inventory_value(plan_sha, inventory_rows))
|
|
3206
|
+
private_raw = _json_bytes(_private_map_value(plan_sha, inventory_raw, mappings))
|
|
3207
|
+
source_sha = _sha(_json_bytes(manifest))
|
|
3208
|
+
packet_manifest_rows = [
|
|
3209
|
+
{
|
|
3210
|
+
"blind_session_id": blind_id,
|
|
3211
|
+
"packet_ref": f"blind/packets/{blind_id}.json",
|
|
3212
|
+
"packet_sha256": _sha(packet_raw),
|
|
3213
|
+
"packet_bytes": len(packet_raw),
|
|
3214
|
+
}
|
|
3215
|
+
for blind_id, packet_raw in packet_values
|
|
3216
|
+
]
|
|
3217
|
+
blind_manifest_raw = _json_bytes(
|
|
3218
|
+
_blind_manifest_value(
|
|
3219
|
+
plan_sha, source_sha, inventory_raw, packet_manifest_rows, private_raw
|
|
3220
|
+
)
|
|
3221
|
+
)
|
|
3222
|
+
try:
|
|
3223
|
+
staging.mkdir(mode=0o755)
|
|
3224
|
+
for blind_id, packet_raw in packet_values:
|
|
3225
|
+
_write_new(
|
|
3226
|
+
staging / "packets" / f"{blind_id}.json",
|
|
3227
|
+
packet_raw,
|
|
3228
|
+
root=staging,
|
|
3229
|
+
)
|
|
3230
|
+
_write_new(staging / "inventory.json", inventory_raw, root=staging)
|
|
3231
|
+
private_dir = staging / "private"
|
|
3232
|
+
private_dir.mkdir(mode=0o700)
|
|
3233
|
+
os.chmod(private_dir, 0o700)
|
|
3234
|
+
_write_new(
|
|
3235
|
+
private_dir / "map.json", private_raw, root=staging, mode=0o600
|
|
3236
|
+
)
|
|
3237
|
+
os.chmod(private_dir / "map.json", 0o600)
|
|
3238
|
+
_write_new(staging / "manifest.json", blind_manifest_raw, root=staging)
|
|
3239
|
+
os.rename(staging, blind_root)
|
|
3240
|
+
_fsync_directory(args.run_dir)
|
|
3241
|
+
_fsync_directory(args.run_dir.parent)
|
|
3242
|
+
except (EnvelopeError, OSError) as exc:
|
|
3243
|
+
raise EnvelopeError(
|
|
3244
|
+
"cannot atomically finalize deterministic blind bundle; staged "
|
|
3245
|
+
"evidence is preserved and regeneration is forbidden"
|
|
3246
|
+
) from exc
|
|
3247
|
+
blind_sha, inventory_sha = _validate_blind_bundle(
|
|
3248
|
+
args.run_dir, plan, plan_sha, manifest
|
|
3249
|
+
)
|
|
3250
|
+
finalized = _finalize_blind_state(
|
|
3251
|
+
args.run_dir, plan, plan_sha, manifest, blind_sha
|
|
3252
|
+
)
|
|
3253
|
+
_validate_blind_bundle(args.run_dir, plan, plan_sha, finalized)
|
|
3254
|
+
_validate_run_inventory(args.run_dir, plan, finalized)
|
|
3255
|
+
return {
|
|
3256
|
+
"packets": 64,
|
|
3257
|
+
"inventory_sha256": inventory_sha,
|
|
3258
|
+
"labels_present": False,
|
|
3259
|
+
"human_evidence_present": False,
|
|
3260
|
+
"dispatch_available": False,
|
|
3261
|
+
"state": "blind_finalized",
|
|
3262
|
+
"recovered_existing_atomic_bundle": False,
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
|
|
3266
|
+
def _run_args(parser: argparse.ArgumentParser) -> None:
|
|
3267
|
+
parser.add_argument("--run-dir", required=True, type=Path)
|
|
3268
|
+
parser.add_argument("--plan-sha256", required=True)
|
|
3269
|
+
|
|
3270
|
+
|
|
3271
|
+
def _parser() -> argparse.ArgumentParser:
|
|
3272
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
3273
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
3274
|
+
init_parser = sub.add_parser("init-run", help="freeze a new 64-cell no-call plan")
|
|
3275
|
+
init_parser.add_argument("--run-dir", required=True, type=Path)
|
|
3276
|
+
init_parser.add_argument("--run-id", required=True)
|
|
3277
|
+
init_parser.add_argument("--suite-commit", required=True)
|
|
3278
|
+
init_parser.add_argument("--order-seed", required=True)
|
|
3279
|
+
init_parser.add_argument("--subject-provider", required=True)
|
|
3280
|
+
init_parser.add_argument("--subject-model", required=True)
|
|
3281
|
+
init_parser.add_argument("--subject-runtime", required=True)
|
|
3282
|
+
init_parser.add_argument("--subject-runtime-version", required=True)
|
|
3283
|
+
init_parser.add_argument("--auth-mode", required=True)
|
|
3284
|
+
init_parser.add_argument("--reasoning-effort", required=True)
|
|
3285
|
+
init_parser.add_argument("--input-token-cap", required=True, type=int)
|
|
3286
|
+
init_parser.add_argument("--output-token-cap", required=True, type=int)
|
|
3287
|
+
materialize_parser = sub.add_parser(
|
|
3288
|
+
"materialize", help="write frozen prompts and non-dispatch call envelopes"
|
|
3289
|
+
)
|
|
3290
|
+
_run_args(materialize_parser)
|
|
3291
|
+
validate_parser = sub.add_parser("validate", help="validate a run without calls")
|
|
3292
|
+
_run_args(validate_parser)
|
|
3293
|
+
ingest_parser = sub.add_parser(
|
|
3294
|
+
"ingest", help="append one externally recorded transcript in frozen order"
|
|
3295
|
+
)
|
|
3296
|
+
_run_args(ingest_parser)
|
|
3297
|
+
ingest_parser.add_argument("--transcript", required=True, type=Path)
|
|
3298
|
+
ingest_parser.add_argument("--authorization-record", required=True, type=Path)
|
|
3299
|
+
blind_parser = sub.add_parser(
|
|
3300
|
+
"prepare-blind-packet", help="write 64 isolated label-free judge packets"
|
|
3301
|
+
)
|
|
3302
|
+
_run_args(blind_parser)
|
|
3303
|
+
return parser
|
|
3304
|
+
|
|
3305
|
+
|
|
3306
|
+
def main(argv: list[str] | None = None) -> int:
|
|
3307
|
+
args = _parser().parse_args(argv)
|
|
3308
|
+
try:
|
|
3309
|
+
if args.command == "init-run":
|
|
3310
|
+
result = init_run(args)
|
|
3311
|
+
elif args.command == "materialize":
|
|
3312
|
+
result = materialize(args)
|
|
3313
|
+
elif args.command == "validate":
|
|
3314
|
+
result = validate_run(args)
|
|
3315
|
+
elif args.command == "ingest":
|
|
3316
|
+
result = ingest(args)
|
|
3317
|
+
else:
|
|
3318
|
+
result = prepare_blind_packet(args)
|
|
3319
|
+
except StopViolation as exc:
|
|
3320
|
+
print(f"STOP[{exc.code}]: {exc.detail}", file=sys.stderr)
|
|
3321
|
+
return 2
|
|
3322
|
+
except EnvelopeError as exc:
|
|
3323
|
+
print(f"ERROR: {exc}", file=sys.stderr)
|
|
3324
|
+
return 1
|
|
3325
|
+
print(json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2))
|
|
3326
|
+
return 0
|
|
3327
|
+
|
|
3328
|
+
|
|
3329
|
+
if __name__ == "__main__":
|
|
3330
|
+
raise SystemExit(main())
|