diff --git a/.github/workflows/application.yml b/.github/workflows/application.yml index 90a858d3..bdd02d48 100644 --- a/.github/workflows/application.yml +++ b/.github/workflows/application.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/e1-approx-quotient-smoke.yml b/.github/workflows/e1-approx-quotient-smoke.yml index 60a52528..11bba315 100644 --- a/.github/workflows/e1-approx-quotient-smoke.yml +++ b/.github/workflows/e1-approx-quotient-smoke.yml @@ -16,6 +16,10 @@ on: - '.github/workflows/e1-approx-quotient-smoke.yml' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/f1-multiagent-protocols-smoke.yml b/.github/workflows/f1-multiagent-protocols-smoke.yml index 4fccdae1..b87a8c08 100644 --- a/.github/workflows/f1-multiagent-protocols-smoke.yml +++ b/.github/workflows/f1-multiagent-protocols-smoke.yml @@ -16,6 +16,10 @@ on: - '.github/workflows/f1-multiagent-protocols-smoke.yml' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/lean.yml b/.github/workflows/lean.yml index 6a2fca20..65aa095a 100644 --- a/.github/workflows/lean.yml +++ b/.github/workflows/lean.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/pr113-all-case-compiler.yml b/.github/workflows/pr113-all-case-compiler.yml new file mode 100644 index 00000000..07791f65 --- /dev/null +++ b/.github/workflows/pr113-all-case-compiler.yml @@ -0,0 +1,113 @@ +name: PR113 All-Case Compiler Candidate + +on: + push: + branches: [research/final-mathematical-closure] + paths: + - '.github/workflows/pr113-all-case-compiler.yml' + - 'PermanssonResearch/**' + - 'PermanssonResearch.lean' + - 'verification/research/final_closure_complete_lean_evidence.py' + - 'verification/research/closure_elaborated_type.py' + - 'verification/research/final_closure_merge_compiler_shards.py' + - 'research/paper_closures/post_merge_112/acceptance_case_source_map.json' + - 'research/paper_closures/post_merge_112/claim_declaration_map.json' + workflow_dispatch: + +concurrency: + group: pr113-all-case-compiler-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + inventory-preflight: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Bind genuine branch checkout and resolve ALL historical theorem names + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + mkdir -p "$RUNNER_TEMP/all-case-inventory" + python3 verification/research/final_closure_complete_lean_evidence.py \ + --head "$GITHUB_SHA" \ + --output "$RUNNER_TEMP/not-written" \ + --inventory-only \ + > "$RUNNER_TEMP/all-case-inventory/inventory.json" + python3 - <<'PY' + import json, os + from pathlib import Path + inv = json.loads((Path(os.environ["RUNNER_TEMP"]) / + "all-case-inventory/inventory.json").read_text()) + assert inv["cases"] == 45 and inv["claims"] == 11 + assert inv["proof_targets"] > 11 + assert len(inv["declarations"]) == inv["proof_targets"] + assert inv["status"] == "SOURCE_CANDIDATE_UNREVIEWED" + print("All-case compile candidates:", inv["proof_targets"]) + PY + - name: Preserve source inventory candidate + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-all-case-owner-inventory + path: ${{ runner.temp }}/all-case-inventory/ + retention-days: 30 + if-no-files-found: warn + + compiler-shard: + needs: inventory-preflight + runs-on: ubuntu-latest + timeout-minutes: 110 + strategy: + fail-fast: false + max-parallel: 2 + matrix: + shard: [0, 1, 2, 3] + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Build pinned research library before theorem probes + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Execute original 45-case and 11-claim assigned compiler shard + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + python3 verification/research/final_closure_complete_lean_evidence.py \ + --head "$GITHUB_SHA" \ + --output "$RUNNER_TEMP/complete-shard" \ + --shard-index "${{ matrix.shard }}" --shard-count 4 + - name: Preserve unreviewed theorem type and axiom logs + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-compiler-shard-${{ matrix.shard }} + path: ${{ runner.temp }}/complete-shard/ + retention-days: 30 + if-no-files-found: warn diff --git a/.github/workflows/pr113-all-original-semantic-candidates.yml b/.github/workflows/pr113-all-original-semantic-candidates.yml new file mode 100644 index 00000000..2027a05a --- /dev/null +++ b/.github/workflows/pr113-all-original-semantic-candidates.yml @@ -0,0 +1,76 @@ +name: PR113 All Original Semantic Candidate Evidence + +on: + push: + branches: + - research/final-mathematical-closure + paths: + - '.github/workflows/pr113-all-original-semantic-candidates.yml' + - 'PermanssonResearch/**' + - 'PermanssonResearch.lean' + - 'verification/research/final_closure_*' + - 'verification/research/test_pr113_semantic_*.py' + - 'verification/research/test_final_closure_all_original_semantics.py' + - 'research/paper_closures/post_merge_112/acceptance_case_source_map.json' + workflow_dispatch: + +concurrency: + group: pr113-original-semantic-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + semantic-candidate-shard: + runs-on: ubuntu-latest + timeout-minutes: 110 + strategy: + fail-fast: false + max-parallel: 3 + matrix: + shard: [0, 1, 2, 3, 4, 5, 6, 7, 8] + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Check exact Git checkout before mathematical work + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + test -z "$(git status --porcelain=v1 --untracked-files=all -- PermanssonResearch PermanssonLean verification/research)" + - name: Build pinned Lean research tree and audit axioms + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Run exact source/mutant candidates (not approval) + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + python3 verification/research/final_closure_all_original_semantics.py \ + --head "$GITHUB_SHA" \ + --shard-index "${{ matrix.shard }}" --shard-count 9 \ + --out "$RUNNER_TEMP/pr113-semantic-source-${{ matrix.shard }}" + - name: Preserve unreviewed source/mutant compiler evidence + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-all-original-semantic-candidates-${{ matrix.shard }} + path: ${{ runner.temp }}/pr113-semantic-source-${{ matrix.shard }}/ + retention-days: 30 + if-no-files-found: error diff --git a/.github/workflows/pr113-clean-paper-rebuild.yml b/.github/workflows/pr113-clean-paper-rebuild.yml new file mode 100644 index 00000000..a89fdc99 --- /dev/null +++ b/.github/workflows/pr113-clean-paper-rebuild.yml @@ -0,0 +1,79 @@ +name: PR113 Clean Paper Rebuild Candidate + +on: + push: + branches: + - research/final-mathematical-closure + paths: + - 'release/mathematical-closure-1/**' + - 'research_papers/mathematical_closure/**' + - '.github/workflows/pr113-clean-paper-rebuild.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + clean-paper-rebuild: + runs-on: ubuntu-latest + timeout-minutes: 100 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Install independent TeX/PDF render dependencies + run: | + sudo apt-get update -qq + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ + texlive-latex-base texlive-latex-recommended \ + texlive-latex-extra texlive-pictures \ + texlive-fonts-recommended poppler-utils + - name: Rebuild exact H from two separate no-hardlink source clones + shell: bash + run: | + set -euo pipefail + python3 release/mathematical-closure-1/reproduce_clean_sources.py \ + --source-head "$GITHUB_SHA" \ + --out "$RUNNER_TEMP/perm-clean-rebuild" + - name: Attach honest execution identity, NOT approval + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + import json, os + from pathlib import Path + out = Path(os.environ["RUNNER_TEMP"]) / "perm-clean-rebuild" + report = json.loads((out/"reproduction_receipt.json").read_text()) + assert report["status"] == "REPRODUCIBLE_DRAFT_ONLY" + assert report["source_commit_sha"] == os.environ["GITHUB_SHA"] + assert report["independent_source_roots"] == 2 + identity = { + "format": "Permansson.CICleanRebuildCandidate.v1", + "status": "EXECUTED_DRAFT_ONLY_NO_SCHOLARLY_REVIEW", + "source_head": os.environ["GITHUB_SHA"], + "github_repository": os.environ["GITHUB_REPOSITORY"], + "github_workflow": os.environ["GITHUB_WORKFLOW"], + "github_run_id": int(os.environ["GITHUB_RUN_ID"]), + "github_run_attempt": int(os.environ["GITHUB_RUN_ATTEMPT"]), + "github_job": os.environ["GITHUB_JOB"], + "pdf_sha256": report["pdf_sha256"], + "archive_sha256": report["archive_sha256"], + "authenticated_ci_artifact": False, + "scientific_release_approved": False, + } + assert identity["github_repository"] == "HMarcusWH/PermanssonLean" + assert identity["github_job"] == "clean-paper-rebuild" + (out/"ci_execution_identity.json").write_text( + json.dumps(identity, sort_keys=True, indent=2)+"\n") + PY + - name: Retain both genuine draft source-root outputs for independent verification + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-clean-paper-rebuild-draft + path: ${{ runner.temp }}/perm-clean-rebuild/ + retention-days: 30 + if-no-files-found: error diff --git a/.github/workflows/pr113-compiler-evidence-candidate.yml b/.github/workflows/pr113-compiler-evidence-candidate.yml new file mode 100644 index 00000000..8f762813 --- /dev/null +++ b/.github/workflows/pr113-compiler-evidence-candidate.yml @@ -0,0 +1,92 @@ +name: PR113 Compiler Evidence Candidate + +on: + push: + branches: + - research/final-mathematical-closure + workflow_dispatch: + +permissions: + contents: read + +jobs: + compiler-candidate: + runs-on: ubuntu-latest + timeout-minutes: 110 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Test and inspect immutable historical tooling migration + shell: bash + run: | + set -euo pipefail + python3 -m unittest discover -s verification/research \ + -p test_pr113_tooling_migration.py -v + python3 verification/research/pr113_tooling_migration.py \ + --head "$GITHUB_SHA" \ + --output "$RUNNER_TEMP/pr113-tooling-migration-proposal.json" + - name: Retain candidate tooling migration proposal + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-tooling-migration-candidate + path: ${{ runner.temp }}/pr113-tooling-migration-proposal.json + retention-days: 30 + if-no-files-found: warn + - name: Build exact source and audit transitive axioms + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Execute all 11 mapped compiler-declaration probes + shell: bash + run: | + set -euo pipefail + python3 verification/research/final_closure_lean_evidence.py \ + --head "$GITHUB_SHA" \ + --output "$RUNNER_TEMP/perm-compiler-candidate" + - name: Resolve all historical case and claim theorem targets + shell: bash + run: | + set -euo pipefail + python3 verification/research/final_closure_complete_lean_evidence.py \ + --head "$GITHUB_SHA" \ + --output "$RUNNER_TEMP/perm-compiler-candidate/complete" \ + --inventory-only \ + > "$RUNNER_TEMP/perm-compiler-candidate/complete_obligation_inventory.json" + python3 - <<'PY' + import json, os + from pathlib import Path + output = Path(os.environ["RUNNER_TEMP"]) / "perm-compiler-candidate" + manifest = json.loads((output/"complete_obligation_inventory.json").read_text()) + assert manifest["cases"] == 45 and manifest["claims"] == 11 + assert manifest["proof_targets"] >= 45 + assert manifest["status"] == "SOURCE_CANDIDATE_UNREVIEWED" + PY + - name: Bind evidence to actual source/job/run/attempt + shell: bash + run: | + set -euo pipefail + python3 verification/research/final_closure_execution_stamp.py \ + --head "$GITHUB_SHA" \ + --evidence-dir "$RUNNER_TEMP/perm-compiler-candidate" \ + --out "$RUNNER_TEMP/perm-compiler-candidate/execution_provenance.json" + - name: Retain raw executed compiler logs and unreviewed inventory + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-executed-compiler-candidate + path: ${{ runner.temp }}/perm-compiler-candidate/ + retention-days: 30 + if-no-files-found: error diff --git a/.github/workflows/pr113-legacy-semantic-evidence.yml b/.github/workflows/pr113-legacy-semantic-evidence.yml new file mode 100644 index 00000000..6e65891d --- /dev/null +++ b/.github/workflows/pr113-legacy-semantic-evidence.yml @@ -0,0 +1,78 @@ +name: PR113 Legacy Original Semantic Compiler Candidates + +on: + push: + branches: [research/final-mathematical-closure] + paths: + - '.github/workflows/pr113-legacy-semantic-evidence.yml' + - 'verification/research/pr113_compiler_trace_hook.py' + - 'verification/research/final_closure_legacy_semantic_evidence.py' + - 'verification/research/test_final_closure_legacy_semantic_evidence.py' + - 'verification/research/test_*.py' + - 'PermanssonResearch/**' + - 'research/paper_closures/post_merge_112/acceptance_case_source_map.json' + workflow_dispatch: + +concurrency: + group: pr113-legacy-semantic-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + legacy-original-shard: + runs-on: ubuntu-latest + timeout-minutes: 110 + strategy: + fail-fast: false + max-parallel: 2 + matrix: + shard: [0, 1, 2, 3, 4, 5, 6, 7] + env: + PYTHONDONTWRITEBYTECODE: '1' + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Verify checkout identity and original policy + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + test -z "$(git status --porcelain=v1 --untracked-files=all -- PermanssonResearch PermanssonLean verification/research research/paper_closures/post_merge_112)" + python3 -m unittest discover -s verification/research -p test_final_closure_legacy_semantic_evidence.py -v + - name: Build and audit pinned research source + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Execute and retain original negative unittest compiler observations + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + python3 verification/research/final_closure_legacy_semantic_evidence.py \ + --head "$GITHUB_SHA" \ + --shard-index "${{ matrix.shard }}" --shard-count 8 \ + --out "$RUNNER_TEMP/pr113-legacy-semantic-${{ matrix.shard }}" + - name: Retain source bytes, compiler logs, and explicitly unreviewed manifests + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-legacy-semantic-candidates-${{ matrix.shard }} + path: ${{ runner.temp }}/pr113-legacy-semantic-${{ matrix.shard }}/ + retention-days: 30 + if-no-files-found: warn diff --git a/.github/workflows/pr113-original-semantic-candidates.yml b/.github/workflows/pr113-original-semantic-candidates.yml new file mode 100644 index 00000000..ab509cdb --- /dev/null +++ b/.github/workflows/pr113-original-semantic-candidates.yml @@ -0,0 +1,105 @@ +name: PR113 Original Semantic Mutant Candidates + +on: + push: + branches: + - research/final-mathematical-closure + paths: + - '.github/workflows/pr113-original-semantic-candidates.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + original-semantic-shard: + runs-on: ubuntu-latest + timeout-minutes: 110 + strategy: + fail-fast: false + max-parallel: 2 + matrix: + shard: [0, 1, 2] + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Build and audit pinned research source + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Execute frozen original semantic-positive and false mutations + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + case "${{ matrix.shard }}" in + 0) CASES=(F-03 D-02 D-03) ;; + 1) CASES=(D-04 D-05 D-06) ;; + 2) CASES=(Q-03 Q-04 E-05) ;; + *) exit 2 ;; + esac + mkdir -p "$RUNNER_TEMP/original-semantic-candidates" + for CASE in "${CASES[@]}"; do + python3 verification/research/final_closure_original_semantic_batch.py \ + --head "$GITHUB_SHA" --case-id "$CASE" \ + --out "$RUNNER_TEMP/original-semantic-candidates/$CASE" + done + - name: Bind retained candidate to real job execution without approval + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + import json, os, subprocess + from pathlib import Path + source = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip() + tree = subprocess.check_output(["git", "rev-parse", "HEAD^{tree}"], text=True).strip() + assert source == os.environ["GITHUB_SHA"] + assert os.environ["GITHUB_REPOSITORY"] == "HMarcusWH/PermanssonLean" + out = Path(os.environ["RUNNER_TEMP"]) / "original-semantic-candidates" + cases = sorted(p.name for p in out.iterdir() if p.is_dir()) + assert len(cases) == 3 + for case in cases: + data = json.loads((out/case/"unreviewed_candidate.json").read_text()) + assert data["status"] == "DETACHED_UNREVIEWED_UNATTRIBUTED" + assert data["source_head"] == source and data["case_id"] == case + assert data["ci_check_run_id"] is None + assert data["independent_review"] is False + record = { + "format": "Permansson.CIOriginalSemanticCandidateRun.v1", + "status": "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED_RECEIPT", + "source_head": source, + "source_tree": tree, + "github_repository": os.environ["GITHUB_REPOSITORY"], + "github_workflow": os.environ["GITHUB_WORKFLOW"], + "github_job": os.environ["GITHUB_JOB"], + "github_run_id": int(os.environ["GITHUB_RUN_ID"]), + "github_run_attempt": int(os.environ["GITHUB_RUN_ATTEMPT"]), + "case_ids": cases, + "independent_review": False, + "check_run_id": None, + "release_approved": False + } + (out/"execution_identity.json").write_text( + json.dumps(record, sort_keys=True, indent=2)+"\n") + PY + - name: Retain detached original source and mutant logs + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-original-semantic-candidates-${{ matrix.shard }} + path: ${{ runner.temp }}/original-semantic-candidates/ + retention-days: 30 + if-no-files-found: error diff --git a/.github/workflows/research-integrity-portability.yml b/.github/workflows/research-integrity-portability.yml index 3acb8284..380a5920 100644 --- a/.github/workflows/research-integrity-portability.yml +++ b/.github/workflows/research-integrity-portability.yml @@ -6,6 +6,10 @@ on: push: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/research-integrity.yml b/.github/workflows/research-integrity.yml index 52c50993..19bab922 100644 --- a/.github/workflows/research-integrity.yml +++ b/.github/workflows/research-integrity.yml @@ -6,6 +6,10 @@ on: push: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/research-lean.yml b/.github/workflows/research-lean.yml index 48d5a3d7..06842a2d 100644 --- a/.github/workflows/research-lean.yml +++ b/.github/workflows/research-lean.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/research-release-verification.yml b/.github/workflows/research-release-verification.yml index 79aad774..46ea7ebe 100644 --- a/.github/workflows/research-release-verification.yml +++ b/.github/workflows/research-release-verification.yml @@ -9,6 +9,10 @@ on: paths: ['release/**', '.github/workflows/research-release-verification.yml'] workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml index 91839099..c016aa6e 100644 --- a/.github/workflows/verification.yml +++ b/.github/workflows/verification.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/PermanssonResearch.lean b/PermanssonResearch.lean index 64771fc6..bcb1b6a2 100644 --- a/PermanssonResearch.lean +++ b/PermanssonResearch.lean @@ -468,6 +468,7 @@ import PermanssonResearch.MultiAgent.AutonomousSupportClosure import PermanssonResearch.MultiAgent.BehavioralAutonomy import PermanssonResearch.MultiAgent.CorrelatedMediator import PermanssonResearch.MultiAgent.FeasibleAnchors +import PermanssonResearch.MultiAgent.F03ActualInfeasibleAnchor import PermanssonResearch.MultiAgent.PlayerSwapConstitution import PermanssonResearch.MultiAgent.PlayerSwapTransport import PermanssonResearch.MultiAgent.PlayerTwoProtocols @@ -684,6 +685,7 @@ import PermanssonResearch.Probability.RegenerativeRenewalMass import PermanssonResearch.ReverseSolver.ThroughHorizonRiskFixture import PermanssonResearch.ReverseSolver.ThroughHorizonRiskMonitor import PermanssonResearch.Specialized.BoundaryWitnesses +import PermanssonResearch.Specialized.E05FullGramMaskingCountermodel import PermanssonResearch.StrategicWorld.InformationBoundary import PermanssonResearch.X1.ExactMatrixTables import PermanssonResearch.X1.TiedStartDomainFixture @@ -696,7 +698,11 @@ import PermanssonResearch.MultiAgent.PositiveMediationWitness import PermanssonResearch.MultiAgent.AsymmetricCommonSwapFixture import PermanssonResearch.Probability.DependentThreeStageSaturation import PermanssonResearch.MultiAgent.MarkedCouplingCountermodel +import PermanssonResearch.MultiAgent.MarkedStrategicPathCountermodel +import PermanssonResearch.MultiAgent.M02ActualMarkedHistoryFixture import PermanssonResearch.GrammarRobust.LocalOrderPositiveFixture +import PermanssonResearch.GrammarRobust.B02ActualOriginalMarginFixture +import PermanssonResearch.GrammarRobust.B02LocalOriginalMarginClosure import PermanssonResearch.GrammarRobust.BooleanOverlapFixture import PermanssonResearch.ReverseSolver.ReachableCompletionBeliefFixture import PermanssonResearch.ReverseSolver.NonlinearOccupationBoundaryFixture @@ -717,6 +723,29 @@ import PermanssonResearch.ReverseSolver.NonlinearPeriodThreePathLaw import PermanssonResearch.Quotient.DiscountedCanonicalBridge import PermanssonResearch.Quotient.DiscountedOriginalMargin import PermanssonResearch.Quotient.DiscountedOriginalMarginExamples +import PermanssonResearch.MultiAgent.MeasurableProductTwoMatchedProtocols +import PermanssonResearch.MultiAgent.MeasurableProductTwoOriginalMarginClosure +import PermanssonResearch.Quotient.DiscountedCanonicalSourceWitness +import PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure +import PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples +import PermanssonResearch.Quotient.Q05ActualOriginalMarginWitnessFixture +import PermanssonResearch.Quotient.Q05ActualOriginalMarginAdapter +import PermanssonResearch.Quotient.Q05ActualAtomClosure +import PermanssonResearch.MultiAgent.MeasurableProductTwoMatchedExamples +import PermanssonResearch.ReverseSolver.NonlinearPeriodThreeStationary +import PermanssonResearch.ReverseSolver.NonlinearPeriodThreeExpectedScore +import PermanssonResearch.ReverseSolver.NonlinearBooleanClassEvaluator +import PermanssonResearch.ReverseSolver.AdaptiveGrammarAdmissionFixture +import PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture +import PermanssonResearch.ReverseSolver.NonlinearPeriodThreeClassGeometry +import PermanssonResearch.ReverseSolver.NonlinearPhaseBlindClassGeometry + +import PermanssonResearch.Games.S02InformationBoundaryCountermodel +import PermanssonResearch.Quotient.Q01NonLumpableProjectionCountermodel +import PermanssonResearch.Probability.A01SupnormClassCountermodel + +import PermanssonResearch.Perturbation.A04FourOutputNecessityCountermodel +import PermanssonResearch.Perturbation.A05SurvivalOccupationCountermodel /-! # PermanssonResearch diff --git a/PermanssonResearch/Games/S02InformationBoundaryCountermodel.lean b/PermanssonResearch/Games/S02InformationBoundaryCountermodel.lean new file mode 100644 index 00000000..f23c9590 --- /dev/null +++ b/PermanssonResearch/Games/S02InformationBoundaryCountermodel.lean @@ -0,0 +1,54 @@ +import PermanssonResearch.StrategicWorld.ActionTrace +import Mathlib.Tactic + +/-! +# S-02: nonanticipating first-stage action cannot read an independent coin + +A current own-action row in the original action-trace semantics is drawn +BEFORE the opponent's fresh independent uniform Boolean action. Any such +row has match success exactly 1/2. The hypothetical rule a := b after +observing that current draw instead succeeds with probability one and is +not an admissible original behavioral policy. This is a finite information +boundary, not an unrestricted full-strategy verification theorem. +-/ + +namespace PermanssonResearch.ActionTrace.S02InformationBoundary +open PermanssonResearch.ActionTrace +set_option autoImplicit false + +def matchingPayoff (a b : Bool) : ℝ := + if a = b then 1 else 0 + +noncomputable def independentFairMatch (a : Bool) : ℝ := + (matchingPayoff a false + matchingPayoff a true) / 2 + +theorem each_fixed_action_half (a : Bool) : + independentFairMatch a = (1 / 2 : ℝ) := by + cases a <;> norm_num [independentFairMatch, matchingPayoff] + +/-- Every history-conditioned (but current-noise-blind) own probability row +has exactly one-half chance of matching the new independent opponent coin. -/ +theorem every_nonanticipating_row_half (p : Row Bool) : + p.expect independentFairMatch = (1 / 2 : ℝ) := by + have he : independentFairMatch = (fun _ : Bool => (1 / 2 : ℝ)) := by + funext a + exact each_fixed_action_half a + rw [he] + exact Row.expect_const p (1 / 2 : ℝ) + +/-- Looking at the independent draw first changes the information class. -/ +theorem clairvoyant_match_one (b : Bool) : + matchingPayoff b b = (1 : ℝ) := by + simp [matchingPayoff] + +theorem clairvoyant_not_a_nonanticipating_row (p : Row Bool) : + p.expect independentFairMatch ≠ (1 : ℝ) := by + rw [every_nonanticipating_row_half] + norm_num + +theorem explicit_information_advantage (p : Row Bool) : + p.expect independentFairMatch < matchingPayoff true true := by + rw [every_nonanticipating_row_half, clairvoyant_match_one] + norm_num + +end PermanssonResearch.ActionTrace.S02InformationBoundary diff --git a/PermanssonResearch/GrammarRobust/B02ActualOriginalMarginFixture.lean b/PermanssonResearch/GrammarRobust/B02ActualOriginalMarginFixture.lean new file mode 100644 index 00000000..23e5f5b9 --- /dev/null +++ b/PermanssonResearch/GrammarRobust/B02ActualOriginalMarginFixture.lean @@ -0,0 +1,66 @@ +import PermanssonResearch.GrammarRobust.BooleanMinimalityCases +import PermanssonResearch.GrammarRobust.FullRelabelingTransport +import Mathlib.Tactic + +/-! +B-02 original finite uniform constitutive margin and local minimality. + +Unlike Nat-order-only illustrations, the predicate is the genuine original +blockMargin of the fixed path-law property on the declared comparison set. +The relabeling comes from a real two-atom bank, nonidentity components and +an exactly identical typed strategic-world process. Coarse aggregation +does NOT inherit local minimality, by the existing B7 witness. +No unrestricted grammar/order/monotonicity inference is asserted. +-/ + +namespace PermanssonResearch.GrammarRobust.B02ActualOriginalMarginFixture + +open PermanssonLean +open PermanssonLean.RegimeSpecification +open PermanssonResearch.GrammarRobust +open BooleanModel BooleanBaseline BooleanOneStepEvents +open BooleanMinimalityCases RelabelingOrderWitness +open FullRelabelingTransport +open RelabelingWitness + +theorem two_actual_minimal_original_blocks : + IsMinimalAllowed fine andPos ({0} : Finset (Fin 2)) ∧ + IsMinimalAllowed fine andPos ({1} : Finset (Fin 2)) := by + exact ⟨and_action_minimal, and_update_minimal⟩ + +theorem nonidentity_relabeling_preserves_actual_margin : + 0 < blockMargin baseline bank fine (fineBlock {0}) + spec (propertyAtOne andP) comparison ∧ + blockMargin baseline bank fine (fineBlock {0}) + spec (propertyAtOne andP) comparison = + blockMargin baseline bank RelabelingWitness.relabeled + (RelabelingOrderWitness.relabeledBlock (relabelEquiv {0})) + spec (propertyAtOne andP) comparison := by + constructor + · exact andPos_action + · exact relabeled_margin_all ({0} : Finset (Fin 2)) + (propertyAtOne andP) + +theorem actual_local_minimality_transport_not_global_identity : + (IsMinimalAllowed fine andPos ({0} : Finset (Fin 2)) ∧ + IsMinimalAllowed RelabelingWitness.relabeled relabeledAndPos + (relabelEquiv ({0} : Finset (Fin 2)))) ∧ + relabelEquiv ({0} : Finset (Fin 2)) ≠ ({0} : Finset (Fin 2)) := by + constructor + · exact ⟨and_action_minimal, + (relabeled_AND_minimal_iff ({0} : Finset (Fin 2))).mp + and_action_minimal⟩ + · exact relabelEquiv_is_nonidentity + +theorem actual_coarsening_does_not_preserve_minimality : + IsMinimalAllowed coarse coarseAndPos ({0} : Finset (Fin 1)) ∧ + ¬ IsMinimalAllowed fine andPos + (coarsening.lift ({0} : Finset (Fin 1))) := + B7_minimality_not_preserved_by_refinement + +theorem and_joint_is_positive_but_not_minimal : + andPos ({0,1} : Finset (Fin 2)) ∧ + ¬ IsMinimalAllowed fine andPos ({0,1} : Finset (Fin 2)) := + ⟨andPos_joint, and_joint_not_minimal⟩ + +end PermanssonResearch.GrammarRobust.B02ActualOriginalMarginFixture diff --git a/PermanssonResearch/GrammarRobust/B02LocalOriginalMarginClosure.lean b/PermanssonResearch/GrammarRobust/B02LocalOriginalMarginClosure.lean new file mode 100644 index 00000000..5d7fba11 --- /dev/null +++ b/PermanssonResearch/GrammarRobust/B02LocalOriginalMarginClosure.lean @@ -0,0 +1,185 @@ +import PermanssonResearch.GrammarRobust.BooleanMinimalityCases +import PermanssonResearch.GrammarRobust.OriginalLowerSetTransport +import Mathlib.Tactic + +/-! +# B-02: original constitutive minimality with local lower-set coverage + +The actual two-atom bank, typed alpha-P-U block interventions, frozen OR +whole-path property, and original comparison set are unchanged. The fine +grammar admits EMPTY and the COMPLETE joint block only; this admission +constraint is frozen ex ante. The coarsening Fin 2 -> Fin 1 is genuinely +noninjective, so there is no global component-level grammar isomorphism. +Every *admitted* strict lower block of the chosen positive fine intervention +has a matching coarse preimage. No unjustified downward closure or +global subblock equivalence is asserted. +-/ + +open MeasureTheory ProbabilityTheory + +namespace PermanssonResearch.GrammarRobust.B02LocalOriginalMarginClosure + +open PermanssonLean +open PermanssonLean.RegimeSpecification +open PermanssonResearch.GrammarRobust +open BooleanModel BooleanBaseline BooleanDynamics BooleanPersistence +open BooleanOneStepEvents BooleanEffects BooleanMinimalityCases + +set_option autoImplicit false + +/-- Source-frozen admissibility excludes partial patches. Because B-02 is +about an admitted explanatory grammar, it must never treat syntactically +valid but inadmissible subsets as actual interventions. -/ +noncomputable def fullOrEmptyFine : + PartitionGrammar (Fin 1) (Fin 1) (Fin 2) where + atomsOf := fine.atomsOf + nonempty := fine.nonempty + disjoint := fine.disjoint + cover := fine.cover + allowed := fun D => D = ∅ ∨ D = Finset.univ + allowed_empty := Or.inl rfl + +theorem fullOrEmptyFine_joint_allowed : + fullOrEmptyFine.allowed ({0,1} : Finset (Fin 2)) := by + right + decide + +theorem fullOrEmptyFine_empty_allowed : + fullOrEmptyFine.allowed (∅ : Finset (Fin 2)) := by + left + rfl + +theorem fullOrEmptyFine_rejects_singleton_zero : + ¬ fullOrEmptyFine.allowed ({0} : Finset (Fin 2)) := by + simp [fullOrEmptyFine] + +theorem fullOrEmptyFine_rejects_singleton_one : + ¬ fullOrEmptyFine.allowed ({1} : Finset (Fin 2)) := by + simp [fullOrEmptyFine] + +/-- The original coarse grammar accepts its empty and full blocks. -/ +noncomputable def actualCoarsening : Coarsening fullOrEmptyFine coarse where + map := fun _ => 0 + onto := by + intro c + refine ⟨0, ?_⟩ + exact Subsingleton.elim _ _ + coarse_atoms := by + intro c + ext a + have hc : c = (0 : Fin 1) := Subsingleton.elim _ _ + subst c + cases a with + | inl i => + have hi : i = (0 : Fin 1) := Subsingleton.elim _ _ + subst i + simp [coarse, fullOrEmptyFine, fine] + | inr i => + have hi : i = (0 : Fin 1) := Subsingleton.elim _ _ + subst i + simp [coarse, fullOrEmptyFine, fine] + respects_allowed := by + intro D _ + by_cases h : (0 : Fin 1) ∈ D + · right + change (Finset.univ.filter + (fun _ : Fin 2 => (0 : Fin 1) ∈ D)) = Finset.univ + simp [h] + · left + change (Finset.univ.filter + (fun _ : Fin 2 => (0 : Fin 1) ∈ D)) = ∅ + simp [h] + +theorem actualCoarsening_not_global_injective : + ¬ Function.Injective actualCoarsening.map := by + intro hinj + have he : actualCoarsening.map (0 : Fin 2) = + actualCoarsening.map (1 : Fin 2) := rfl + have hh : (0 : Fin 2) = 1 := hinj he + exact absurd hh (by decide) + +theorem actualCoarsening_lift_full : + actualCoarsening.lift ({0} : Finset (Fin 1)) = + (Finset.univ : Finset (Fin 2)) := by + ext f + simp [Coarsening.lift, actualCoarsening] + +theorem actualCoarsening_lift_empty : + actualCoarsening.lift (∅ : Finset (Fin 1)) = + (∅ : Finset (Fin 2)) := by + ext f + simp [Coarsening.lift, actualCoarsening] + +/-- The genuine local strict-lower-set coverage, established for the +original admitted-block poset, not postulated as a general isomorphism. -/ +theorem original_admitted_lower_coverage : + ∀ E : Finset (Fin 2), fullOrEmptyFine.allowed E → + E ⊂ actualCoarsening.lift ({0} : Finset (Fin 1)) → + ∃ C : Finset (Fin 1), + coarse.allowed C ∧ C ⊂ ({0} : Finset (Fin 1)) ∧ + actualCoarsening.lift C = E := by + intro E hE hstrict + change E = ∅ ∨ E = Finset.univ at hE + rcases hE with he | he + · refine ⟨∅, trivial, (by decide), ?_⟩ + rw [actualCoarsening_lift_empty, he] + · subst E + rw [actualCoarsening_lift_full] at hstrict + exact False.elim (lt_irrefl _ hstrict) + +/-- The coarse OR intervention has the actual ORIGINAL uniform margin +one, not merely a syntactically named positive predicate. -/ +def originalCoarsePositive (D : Finset (Fin 1)) : Prop := + ∃ hD : coarse.allowed D, + 0 < blockMargin baseline bank coarse ⟨D, hD⟩ spec + (propertyAtOne orP) comparison + +theorem coarse_original_OR_positive : + originalCoarsePositive ({0} : Finset (Fin 1)) := by + refine ⟨trivial, ?_⟩ + change 0 < blockMargin baseline bank coarse (coarseBlock {0}) + spec (propertyAtOne orP) comparison + rw [coarse_margin_eq_fine_joint, or_joint_margin_one] + norm_num + +theorem coarse_original_empty_not_positive : + ¬ originalCoarsePositive (∅ : Finset (Fin 1)) := by + rintro ⟨h, hp⟩ + change 0 < blockMargin baseline bank coarse (coarseBlock ∅) + spec (propertyAtOne orP) comparison at hp + rw [coarse_empty_margin_zero] at hp + norm_num at hp + +theorem coarse_original_OR_minimal : + IsMinimalAllowed coarse originalCoarsePositive + ({0} : Finset (Fin 1)) := by + refine ⟨trivial, coarse_original_OR_positive, ?_⟩ + intro E _ hproper hpositive + have hE : E = ∅ := Finset.eq_empty_of_ssubset_singleton hproper + exact coarse_original_empty_not_positive (hE ▸ hpositive) + +/-- The existing general original-margin local coverage theorem now +INSTantiates nontrivially with the actual finite bank/grammar and +noninjective component map. -/ +theorem original_local_minimality_without_global_isomorphism : + IsMinimalAllowed fullOrEmptyFine + (fun E => ∃ hE : fullOrEmptyFine.allowed E, + 0 < blockMargin baseline bank fullOrEmptyFine ⟨E,hE⟩ spec + (propertyAtOne orP) comparison) + (actualCoarsening.lift ({0} : Finset (Fin 1))) ∧ + ¬ Function.Injective actualCoarsening.map := by + constructor + · have htransport := + actualCoarsening.original_margin_minimal_iff_of_lower_coverage + baseline bank spec (propertyAtOne orP) comparison + ({0} : Finset (Fin 1)) trivial original_admitted_lower_coverage + exact htransport.mp coarse_original_OR_minimal + · exact actualCoarsening_not_global_injective + +/-- This result is local and does not authorize including the +excluded singleton patches or an unrestricted minimality transfer. -/ +theorem false_extra_admission_rejected : + ¬ fullOrEmptyFine.allowed ({0} : Finset (Fin 2)) := + fullOrEmptyFine_rejects_singleton_zero + +end PermanssonResearch.GrammarRobust.B02LocalOriginalMarginClosure diff --git a/PermanssonResearch/MultiAgent/AsymmetricCommonSwapFixture.lean b/PermanssonResearch/MultiAgent/AsymmetricCommonSwapFixture.lean index 579ac8dd..b947393d 100644 --- a/PermanssonResearch/MultiAgent/AsymmetricCommonSwapFixture.lean +++ b/PermanssonResearch/MultiAgent/AsymmetricCommonSwapFixture.lean @@ -1,4 +1,5 @@ import PermanssonResearch.MultiAgent.PlayerSwapTransport +import PermanssonResearch.X1.TypedModels import Mathlib.Tactic /-! @@ -98,4 +99,80 @@ theorem asymmetric_original_path_law_transport : exact PlayerSwap.actual_pathLaw generator physicalFromFirstAction (Measure.dirac initial) + +/-- The initial source is deliberately NON-DIRAC. Both physical world +values and both common-state values occur before the first action; no +single favorable start is substituted for the original all-initial theorem. -/ +def mixedInitialState (b : Bool) : + JointState (GameState Bool Unit Unit) Bool := + ((b, ((), ())), b) + +noncomputable def mixedInitialLaw : + Measure (JointState (GameState Bool Unit Unit) Bool) := + (PermanssonResearch.X1.coin (1/2)).map mixedInitialState + +instance mixedInitialLaw_isProbability : IsProbabilityMeasure mixedInitialLaw := by + unfold mixedInitialLaw + haveI : IsProbabilityMeasure (PermanssonResearch.X1.coin (1/2)) := + PermanssonResearch.X1.coin_probability (1/2) (by norm_num) + infer_instance + +theorem mixed_starts_distinct : + mixedInitialState false ≠ mixedInitialState true := by decide + +theorem mixedPreimage_false : + mixedInitialState ⁻¹' ({mixedInitialState false} : + Set (JointState (GameState Bool Unit Unit) Bool)) = + ({false} : Set Bool) := by + ext b + cases b <;> simp [mixedInitialState] + +theorem mixedPreimage_true : + mixedInitialState ⁻¹' ({mixedInitialState true} : + Set (JointState (GameState Bool Unit Unit) Bool)) = + ({true} : Set Bool) := by + ext b + cases b <;> simp [mixedInitialState] + +theorem mixed_initial_positive_each_state : + mixedInitialLaw {mixedInitialState false} = + ENNReal.ofReal (1/2 : ℝ) ∧ + mixedInitialLaw {mixedInitialState true} = + ENNReal.ofReal (1/2 : ℝ) := by + constructor + · rw [mixedInitialLaw, + Measure.map_apply (by fun_prop) + (measurableSet_singleton (mixedInitialState false))] + rw [mixedPreimage_false, PermanssonResearch.X1.coin_false] + norm_num + · rw [mixedInitialLaw, + Measure.map_apply (by fun_prop) + (measurableSet_singleton (mixedInitialState true))] + rw [mixedPreimage_true, PermanssonResearch.X1.coin_true] + norm_num + +/-- A path distribution with two positive distinct time-zero atoms +cannot equal the earlier point-started Dirac initial distribution. -/ +theorem mixed_initial_not_point_mass : + mixedInitialLaw ≠ Measure.dirac (mixedInitialState false) := by + intro h + have hm := congrArg + (fun μ : Measure (JointState (GameState Bool Unit Unit) Bool) => + μ {mixedInitialState true}) h + rw [mixed_initial_positive_each_state.2] at hm + simp [Measure.dirac_apply', mixed_starts_distinct] at hm + +/-- Genuine asymmetric source transport for a mixed ORIGINAL initial law. +It is an instance of the existing all-measurable-event whole-path theorem; +the physical/world input is reindexed, not assumed symmetric. -/ +theorem asymmetric_mixed_original_path_law_transport : + ((generator.toModel physicalFromFirstAction).pathLaw mixedInitialLaw).map + (PlayerSwap.compression (S0 := Bool) (S1 := Unit) + (S2 := Unit) (X := Bool)).pathMap = + ((PlayerSwap.generator generator).toModel + (PlayerSwap.physical physicalFromFirstAction)).pathLaw + (mixedInitialLaw.map PlayerSwap.state) := by + exact PlayerSwap.actual_pathLaw generator physicalFromFirstAction + mixedInitialLaw + end PermanssonResearch.MultiAgent.AsymmetricCommonSwapFixture diff --git a/PermanssonResearch/MultiAgent/F03ActualInfeasibleAnchor.lean b/PermanssonResearch/MultiAgent/F03ActualInfeasibleAnchor.lean new file mode 100644 index 00000000..3e12d5df --- /dev/null +++ b/PermanssonResearch/MultiAgent/F03ActualInfeasibleAnchor.lean @@ -0,0 +1,76 @@ +import PermanssonResearch.MultiAgent.FeasibleAnchors +import Mathlib.Tactic + +/-! +# F-03: genuine finite strict-opponent legal-fibre obstruction + +All original states have NONEMPTY legal opponent menus. Two states in one +strict own-key fibre demand opposite Boolean actions. Their fibre intersection +is empty and the original finite existence theorem therefore rules out every +Markov strictly non-reactive opponent mechanism assigning full legal mass. + +This is the original typed PlayerTwoMechanism / anchorKey construction. +No reduction to a single sampled history or singleton menu is assumed. +-/ + +open MeasureTheory ProbabilityTheory Set +namespace PermanssonResearch.MultiAgent.F03ActualInfeasibleAnchor + +open PermanssonLean +open PermanssonResearch.MultiAgent +set_option autoImplicit false + +abbrev Y := JointState (GameState Unit Bool Unit) Unit +abbrev OwnKey := Unit × Unit + +def oldFalse : Y := (((), (false, ())), ()) +def oldTrue : Y := (((), (true, ())), ()) + +def legal (y : Y) : Set Bool := {y.1.2.1} + +theorem each_original_menu_nonempty (y : Y) : + (legal y).Nonempty := by + exact ⟨y.1.2.1, rfl⟩ + +theorem both_starts_have_same_original_strict_key : + anchorKey oldFalse = anchorKey oldTrue := rfl + +theorem legal_oldFalse (a : Bool) : + a ∈ legal oldFalse ↔ a = false := by + simp [legal, oldFalse] + +theorem legal_oldTrue (a : Bool) : + a ∈ legal oldTrue ↔ a = true := by + simp [legal, oldTrue] + +theorem legal_fibre_empty_at_genuine_key : + fibreLegal anchorKey legal (((), ()) : OwnKey) = ∅ := by + ext a + constructor + · intro h + have hf : a = false := (legal_oldFalse a).mp (h oldFalse rfl) + have ht : a = true := (legal_oldTrue a).mp (h oldTrue rfl) + simp [hf] at ht + · intro h + simp at h + +theorem no_feasible_original_strict_anchor : + ¬ ∃ a : PlayerTwoMechanism Unit Bool Unit Unit Unit Bool, + IsStrictlyNonReactive a ∧ + ∀ y : Y, a.action y (legal y) = 1 := by + intro h + have hf := (exists_feasible_strictAnchor_iff + (S0 := Unit) (S1 := Bool) (S2 := Unit) + (X := Unit) (A1 := Unit) (A2 := Bool) legal).mp h + have hnonempty := hf (((), ()) : OwnKey) + rw [legal_fibre_empty_at_genuine_key] at hnonempty + simpa using hnonempty + +theorem all_pointwise_menus_nonempty_but_global_anchor_impossible : + (∀ y : Y, (legal y).Nonempty) ∧ + ¬ ∃ a : PlayerTwoMechanism Unit Bool Unit Unit Unit Bool, + IsStrictlyNonReactive a ∧ + ∀ y : Y, a.action y (legal y) = 1 := by + exact ⟨each_original_menu_nonempty, no_feasible_original_strict_anchor⟩ + +end PermanssonResearch.MultiAgent.F03ActualInfeasibleAnchor diff --git a/PermanssonResearch/MultiAgent/M02ActualMarkedHistoryFixture.lean b/PermanssonResearch/MultiAgent/M02ActualMarkedHistoryFixture.lean new file mode 100644 index 00000000..5c51e495 --- /dev/null +++ b/PermanssonResearch/MultiAgent/M02ActualMarkedHistoryFixture.lean @@ -0,0 +1,137 @@ +import PermanssonResearch.MultiAgent.MarkedStrategicPathCountermodel +import PermanssonResearch.MultiAgent.AutonomousMarkedKernel +import Mathlib.Tactic + +/-! +# M-02 actual conditional-product marked history source + +Two genuinely admitted strict opponent update mechanisms have fair sampled +opponent actions and the same unmarked next-key distribution. One records +a₂; the other records ¬a₂. We instantiate the ORIGINAL alpha -> P -> U +oneStepHistoryKernel and its opponentMark pushforward, rather than assuming +that an arbitrary independent key lottery describes the source. + +This does NOT refute the strict MA2 path-law obstruction: the two anchors +do not satisfy baseline mechanism agreement, which remains mandatory. +-/ + +open MeasureTheory ProbabilityTheory Set +open scoped ENNReal ProbabilityTheory + +namespace PermanssonResearch.MultiAgent.M02ActualMarkedHistoryFixture + +open PermanssonLean +open PermanssonResearch.MultiAgent +open PermanssonResearch.MultiAgent.Autonomous +open PermanssonResearch.MultiAgent.MarkedStrategicPathCountermodel +open PermanssonResearch.X1 (coin coin_probability coin_true coin_false) + +set_option autoImplicit false + +noncomputable def fairActionFactor : Kernel (Unit × Bool) Bool := + Kernel.const _ (coin (1/2)) + +noncomputable def markedUpdateFactor (twist : Bool) : + Kernel ((Unit × Bool) × Bool) Bool := + Kernel.deterministic + (fun i => if twist then !i.2 else i.2) Measurable.of_discrete + +/-- Actual source opponent action/update rows factor through strictly the +original anchorKey/anchorUpdateKey. They never read player one's action. -/ +noncomputable def strictAnchor (twist : Bool) : + PlayerTwoMechanism Unit Unit Bool Unit Bool Bool where + action := (fairActionFactor).comap anchorKey measurable_anchorKey + update := (markedUpdateFactor twist).comap + anchorUpdateKey measurable_anchorUpdateKey + action_isMarkov := by + letI : IsProbabilityMeasure (coin (1/2)) := + coin_probability _ (by norm_num) + unfold fairActionFactor + infer_instance + update_isMarkov := by + unfold markedUpdateFactor + infer_instance + +theorem strictAnchor_isStrict (twist : Bool) : + IsStrictlyNonReactive (strictAnchor twist) := by + exact ⟨⟨fairActionFactor, rfl⟩, + ⟨markedUpdateFactor twist, rfl⟩⟩ + +/-- Unlike an assumed arbitrary probability coupling, this profile records +the exact factor kernels that reconstruct the actual typed source. -/ +noncomputable def sourceProfile (twist : Bool) : + Profile game (strictAnchor twist) where + common := Kernel.const Unit (Measure.dirac ()) + action := fairActionFactor + update := markedUpdateFactor twist + common_eq := rfl + action_eq := rfl + update_eq := rfl + +/-- This marked probability is ACTUALLY the sampled opponent action and the +next common/private opponent state of the original one-step history law. -/ +noncomputable def markedLaw (twist : Bool) (y : Y) : + Measure (Bool × (Unit × Bool)) := + ((((game.openLoop (player false) (strictAnchor twist)).toModel world).oneStepHistoryKernel y).map + (opponentMark (S0 := Unit) (S1 := Unit) (S2 := Bool) + (X := Unit) (A1 := Bool) (A2 := Bool))) + +theorem markedLaw_actual_source_atom (twist : Bool) (y : Y) (b k : Bool) : + markedLaw twist y {(b, ((), k))} = + (coin (1/2)) {b} * + (if k = (if twist then !b else b) then 1 else 0) := by + classical + have h := autonomous_marked_atom game world + (strictAnchor twist) (sourceProfile twist) (player false) + y b ((), k) + -- In the finite Boolean source the only residual after the actual marked + -- atom theorem is Pi.single's indicator. Resolve the eight exact source + -- atoms rather than replacing the original history kernel with a table. + cases twist <;> cases b <;> cases k <;> + simpa [markedLaw, sourceProfile, fairActionFactor, markedUpdateFactor, + Kernel.const_apply, Kernel.deterministic_apply, + Measure.dirac_apply', Kernel.comap_apply, + markedNextKey, Pi.single_apply] using h + +/-- Both marked laws have exactly the SAME (fair) unmarked next-key +marginal, shown by summing their actual marked source atoms. -/ +theorem actual_unmarked_next_key_fair (twist : Bool) (y : Y) (k : Bool) : + (∑ b : Bool, markedLaw twist y {(b, ((), k))}) = + ENNReal.ofReal (1/2 : ℝ) := by + simp_rw [markedLaw_actual_source_atom] + cases twist <;> cases k <;> + norm_num [Fintype.sum_bool, coin_true, coin_false] + +theorem actual_unmarked_next_key_laws_equal (y : Y) (k : Bool) : + (∑ b : Bool, markedLaw false y {(b, ((), k))}) = + (∑ b : Bool, markedLaw true y {(b, ((), k))}) := by + rw [actual_unmarked_next_key_fair, actual_unmarked_next_key_fair] + +/-- Complete original action-marked diagonal event, in the ACTUAL original +one-step history law, separates the two legal strict anchors. -/ +private theorem fair_halves_add_one : + ENNReal.ofReal (1/2 : ℝ) + ENNReal.ofReal (1/2 : ℝ) = 1 := by + rw [← ENNReal.ofReal_add + (by norm_num : (0 : ℝ) ≤ 1/2) + (by norm_num : (0 : ℝ) ≤ 1/2)] + norm_num + +theorem actual_marked_diagonal_separation (y : Y) : + (∑ b : Bool, markedLaw false y {(b, ((), b))}) = 1 ∧ + (∑ b : Bool, markedLaw true y {(b, ((), b))}) = 0 := by + constructor <;> simp_rw [markedLaw_actual_source_atom] <;> + norm_num [Fintype.sum_bool, coin_true, coin_false, fair_halves_add_one] + +/-- The assumptions of the strict MA2 no-separation theorem are not silently +inherited from equality of unmarked key marginals: these anchors have +different private-update kernels at the same original opponent key/action. -/ +theorem update_mechanisms_differ : + (strictAnchor false).update (sourceInput false false false) ≠ + (strictAnchor true).update (sourceInput false false false) := by + intro h + have he := congrArg (fun μ : Measure Bool => μ {true}) h + simp [strictAnchor, markedUpdateFactor, fairActionFactor, + sourceInput, anchorUpdateKey, Kernel.comap_apply, + Kernel.deterministic_apply, Measure.dirac_apply'] at he + +end PermanssonResearch.MultiAgent.M02ActualMarkedHistoryFixture diff --git a/PermanssonResearch/MultiAgent/MarkedStrategicPathCountermodel.lean b/PermanssonResearch/MultiAgent/MarkedStrategicPathCountermodel.lean new file mode 100644 index 00000000..913cdb37 --- /dev/null +++ b/PermanssonResearch/MultiAgent/MarkedStrategicPathCountermodel.lean @@ -0,0 +1,158 @@ +import PermanssonResearch.MultiAgent.AutonomousMarkedKernel +import PermanssonResearch.MultiAgent.MarkedCouplingCountermodel +import PermanssonResearch.X1.TypedModels +import Mathlib.Tactic + +/-! +# M-02: typed opponent-mark countermodel, bounded source bridge + +The opponent's sampled action is fair under the actual conditional-product +generator. Its private update records the sampled action when player one acts +false, and its negation when player one acts true. This module connects the +finite aligned/crossed arithmetic to actual source action/update rows and +proves the strict opponent-locked premise fails. + +The entire one-step history kernel pushed through opponentMark still needs a +separate compiled atom identity before full historical M-02 acceptance. +-/ + +open MeasureTheory ProbabilityTheory Set +open scoped ENNReal +namespace PermanssonResearch.MultiAgent.MarkedStrategicPathCountermodel + +open PermanssonLean +open PermanssonResearch.MultiAgent +open PermanssonResearch.MultiAgent.Autonomous +open PermanssonResearch.X1 (coin coin_probability coin_true coin_false) + +set_option autoImplicit false + +abbrev Y := State Unit Unit Bool Unit +abbrev UI := UpdateInput (GameState Unit Unit Bool) Unit (Bool × Bool) +abbrev WI := WorldInput (GameState Unit Unit Bool) Unit (Bool × Bool) + +noncomputable def player (t : Bool) : + PlayerOneMechanism Unit Unit Bool Unit Bool Bool where + action := Kernel.deterministic (fun _ : Y => t) Measurable.of_discrete + update := Kernel.deterministic (fun _ : UI => ()) Measurable.of_discrete + action_isMarkov := by infer_instance + update_isMarkov := by infer_instance + +noncomputable def opponent : PlayerTwoMechanism Unit Unit Bool Unit Bool Bool where + action := Kernel.const Y (coin (1/2)) + update := Kernel.deterministic + (fun i : UI => if i.1.2.1 then !i.1.2.2 else i.1.2.2) + Measurable.of_discrete + action_isMarkov := by + letI : IsProbabilityMeasure (coin (1/2)) := + coin_probability _ (by norm_num) + infer_instance + update_isMarkov := by infer_instance + +noncomputable def world : Kernel WI Unit := + Kernel.const WI (Measure.dirac ()) + +instance world_isMarkov : IsMarkovKernel world := by + unfold world + infer_instance + +noncomputable def game : TwoPlayerGenerator Unit Unit Bool Unit Bool Bool where + player1 := player false + player2 := opponent + common := Kernel.const UI (Measure.dirac ()) + common_isMarkov := by infer_instance + +theorem same_physical_P_for_both_actual_protocols : + ((game.openLoop (player false) opponent).toModel world).world = + ((game.openLoop (player true) opponent).toModel world).world := by + change world = world + rfl + +def markedNextKey (t b : Bool) : Bool := + if t then !b else b + +def sourceInput (t b old : Bool) : UI := + (((((), ((), old)), ()), (t,b)), ()) + +/-- The opponent's actual private update uses both sampled actions, in the +actual update-input type of the original TwoPlayerGenerator. -/ +theorem actual_opponent_private_update (t b old : Bool) : + opponent.update (sourceInput t b old) = + Measure.dirac (markedNextKey t b) := by + change Kernel.deterministic + (fun i : UI => if i.1.2.1 then !i.1.2.2 else i.1.2.2) + Measurable.of_discrete (sourceInput t b old) = _ + rw [Kernel.deterministic_apply] + rfl + +def markerInput (t : Bool) : UI := sourceInput t false false + +theorem same_allowed_anchor_update_key : + anchorUpdateKey (markerInput false) = + anchorUpdateKey (markerInput true) := rfl + +theorem unequal_actual_update_rows : + opponent.update (markerInput false) ≠ + opponent.update (markerInput true) := by + intro heq + have h := congrArg (fun μ : Measure Bool => μ {true}) heq + simp [opponent, markerInput, sourceInput, + Kernel.deterministic_apply, Measure.dirac_apply'] at h + +/-- The genuine original strict factorization premise is false because the +update law depends on player one's current action at identical opponent keys. -/ +theorem original_strict_anchor_premise_rejected : + ¬ IsStrictlyNonReactive opponent := by + intro h + exact unequal_actual_update_rows + (h.update_eq same_allowed_anchor_update_key) + +theorem actual_opponent_action_fair (y : Y) : + opponent.action y {true} = ENNReal.ofReal (1/2 : ℝ) ∧ + opponent.action y {false} = ENNReal.ofReal (1/2 : ℝ) := by + constructor + · change (coin (1/2)) {true} = _ + simpa using coin_true (1/2) + · change (coin (1/2)) {false} = _ + rw [coin_false] + norm_num + +/-- Finite exact marked rows obtained from the source's measured action law +and the deterministic opponent update, not an independent key lottery. -/ +def actualMarkedAtom (t opponentAction nextKey : Bool) : ℚ := + if nextKey = markedNextKey t opponentAction then 1/2 else 0 + +theorem source_aligned_atom (a k : Bool) : + actualMarkedAtom false a k = + (if k = a then (1/2 : ℚ) else 0) := by + cases a <;> cases k <;> norm_num [actualMarkedAtom, markedNextKey] + +theorem source_crossed_atom (a k : Bool) : + actualMarkedAtom true a k = + (if k ≠ a then (1/2 : ℚ) else 0) := by + cases a <;> cases k <;> norm_num [actualMarkedAtom, markedNextKey] + +def nextKeyMass (t k : Bool) : ℚ := + ∑ a : Bool, actualMarkedAtom t a k + +theorem unmarked_key_laws_identical (k : Bool) : + nextKeyMass false k = nextKeyMass true k ∧ + nextKeyMass false k = (1/2 : ℚ) := by + cases k <;> norm_num [nextKeyMass, actualMarkedAtom, + markedNextKey, Fintype.sum_bool] + +def diagonalMass (t : Bool) : ℚ := + ∑ a : Bool, actualMarkedAtom t a a + +theorem marked_diagonal_separates : + diagonalMass false = 1 ∧ diagonalMass true = 0 := by + norm_num [diagonalMass, actualMarkedAtom, + markedNextKey, Fintype.sum_bool] + +theorem unmarked_not_strict_certificate : + (∀ k : Bool, nextKeyMass false k = nextKeyMass true k) ∧ + ¬ IsStrictlyNonReactive opponent := by + exact ⟨fun k => (unmarked_key_laws_identical k).1, + original_strict_anchor_premise_rejected⟩ + +end PermanssonResearch.MultiAgent.MarkedStrategicPathCountermodel diff --git a/PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean b/PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean new file mode 100644 index 00000000..b5d3a252 --- /dev/null +++ b/PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean @@ -0,0 +1,68 @@ +import PermanssonResearch.MultiAgent.MeasurableProductTwoOriginalMarginClosure +import PermanssonResearch.MultiAgent.AsymmetricCommonSwapFixture +import Mathlib.Tactic + +/-! +Mixed-direction admissibility regression: the same frozen physical kernel +persists on both sides of any Fin 2 treated-player protocol. Existing +asymmetric swap fixture independently catches physical/action and common +update input mistakes; its use does not identify a correlated generator. +-/ +open MeasureTheory ProbabilityTheory +namespace PermanssonResearch.MultiAgent.MeasurableProduct.Two +open PermanssonLean +open PermanssonResearch.MultiAgent +open PermanssonResearch.MultiAgent.FiniteProduct +open PermanssonResearch.MultiAgent.MeasurableProduct + +set_option autoImplicit false +universe u0 uS uX uA +variable {S0 : Type u0} {S : Fin 2 → Type uS} +variable {X : Type uX} {A : Fin 2 → Type uA} +variable [MeasurableSpace S0] [∀ i, MeasurableSpace (S i)] +variable [MeasurableSpace X] [∀ i, MeasurableSpace (A i)] + +theorem matched_closed_worlds_both_frozen + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] + (i : Fin 2) (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) : + (MeasurableProduct.closedLoopIntervention G P i r anchors).intervention.apply.world = P ∧ + (matchedBinaryClosed G P i r anchors).intervention.apply.world = + physical P := by + constructor + · exact closed_loop_world_frozen G P i r anchors + · exact matched_binary_closed_world_frozen G P i r anchors + +theorem matched_open_worlds_both_frozen + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] + (i : Fin 2) (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) + (a : ∀ j, FiniteProduct.Mechanism S0 S X A j) + (ha : a ∈ anchors) : + (MeasurableProduct.openLoopIntervention G P i r anchors a ha).intervention.apply.world = P ∧ + (matchedBinaryOpen G P i r anchors a ha).intervention.apply.world = + physical P := by + constructor + · exact open_loop_world_frozen G P i r anchors a ha + · exact matched_binary_open_world_frozen G P i r anchors a ha + +end PermanssonResearch.MultiAgent.MeasurableProduct.Two + +namespace PermanssonResearch.MultiAgent.FinalMatchedAsymmetry + +open PermanssonResearch.MultiAgent.AsymmetricCommonSwapFixture +open PermanssonResearch.MultiAgent.PlayerSwap + +theorem wrong_asymmetric_input_change_is_detectable : + ((PlayerSwap.physical physicalFromFirstAction) liveWorldInput {true} = 1 ∧ + physicalFromFirstAction liveWorldInput {true} = 0) ∧ + ((PlayerSwap.generator generator).common liveUpdateInput {true} = 1 ∧ + generator.common liveUpdateInput {true} = 0) := + ⟨real_physical_pullback_changes_asymmetric_input, + real_common_update_pullback_changes_input⟩ + +end PermanssonResearch.MultiAgent.FinalMatchedAsymmetry diff --git a/PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean b/PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean new file mode 100644 index 00000000..e7c1a1e8 --- /dev/null +++ b/PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean @@ -0,0 +1,145 @@ +import PermanssonResearch.MultiAgent.MeasurableProductTwoPathLaw +import PermanssonResearch.MultiAgent.MeasurableProductProtocols +import Mathlib.Tactic + +/-! +# Final closure: matched binary two-player interventions + +The binary family is constructed from the *same* source generator, treated +player and admitted original opponent anchors. It is not an independently +chosen binary intervention. All physical changes are within-model forbidden: +the source model retains P, and the binary model retains physical P. +-/ + +open MeasureTheory ProbabilityTheory Set +namespace PermanssonResearch.MultiAgent.MeasurableProduct.Two + +open PermanssonLean +open PermanssonResearch.MultiAgent +open PermanssonResearch.MultiAgent.FiniteProduct +open PermanssonResearch.MultiAgent.MeasurableProduct + +set_option autoImplicit false +universe u0 uS uX uA +variable {S0 : Type u0} {S : Fin 2 → Type uS} +variable {X : Type uX} {A : Fin 2 → Type uA} +variable [MeasurableSpace S0] [∀ i, MeasurableSpace (S i)] +variable [MeasurableSpace X] [∀ i, MeasurableSpace (A i)] + +/-- Exact binary-side family induced by a single frozen source protocol. -/ +noncomputable def matchedBinaryFamily + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] (i : Fin 2) + (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) : + InterventionFamily ((binary G).toModel (physical P)) Protocol where + targetOf _ := .jointStrategic + admissible + | .closedLoop, gen => + gen = (binary (MeasurableProduct.closedLoop G i r)).toGenerator + | .openLoop, gen => + ∃ a ∈ anchors, + gen = (binary (MeasurableProduct.openLoop G i r a)).toGenerator + +/-- Closed loop on the binary side: exactly the transported original choice. -/ +noncomputable def matchedBinaryClosed + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] (i : Fin 2) + (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) : + AdmissibleStrategicIntervention (matchedBinaryFamily G P i r anchors) where + intervention := + ⟨.closedLoop, (binary (MeasurableProduct.closedLoop G i r)).toGenerator⟩ + accepted := rfl + strategic := InterventionTarget.isStrategic_jointStrategic + +/-- Open loop on the binary side: the anchor is the actual admitted source anchor. -/ +noncomputable def matchedBinaryOpen + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] (i : Fin 2) + (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) + (a : ∀ j, FiniteProduct.Mechanism S0 S X A j) + (ha : a ∈ anchors) : + AdmissibleStrategicIntervention (matchedBinaryFamily G P i r anchors) where + intervention := + ⟨.openLoop, (binary (MeasurableProduct.openLoop G i r a)).toGenerator⟩ + accepted := ⟨a, ha, rfl⟩ + strategic := InterventionTarget.isStrategic_jointStrategic + +theorem matchedBinaryClosed_apply + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] (i : Fin 2) + (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) : + (matchedBinaryClosed G P i r anchors).intervention.apply = + ((binary (MeasurableProduct.closedLoop G i r)).toModel (physical P)) := rfl + +theorem matchedBinaryOpen_apply + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] (i : Fin 2) + (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) + (a : ∀ j, FiniteProduct.Mechanism S0 S X A j) + (ha : a ∈ anchors) : + (matchedBinaryOpen G P i r anchors a ha).intervention.apply = + ((binary (MeasurableProduct.openLoop G i r a)).toModel (physical P)) := rfl + +/-- The complete original source protocol and the admitted binary choice +commute with the same typed state compression, for either treated player. -/ +theorem matched_closed_kernel_two + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] (i : Fin 2) + (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) : + KernelIntertwines + (compression_two (S0 := S0) (S := S) (X := X)) + (MeasurableProduct.closedLoopIntervention G P i r anchors).intervention.apply.inducedKernel + (matchedBinaryClosed G P i r anchors).intervention.apply.inducedKernel := by + rw [matchedBinaryClosed_apply] + exact closed_loop_kernel_two G P i r anchors + +theorem matched_open_kernel_two + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] (i : Fin 2) + (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) + (a : ∀ j, FiniteProduct.Mechanism S0 S X A j) + (ha : a ∈ anchors) : + KernelIntertwines + (compression_two (S0 := S0) (S := S) (X := X)) + (MeasurableProduct.openLoopIntervention G P i r anchors a ha).intervention.apply.inducedKernel + (matchedBinaryOpen G P i r anchors a ha).intervention.apply.inducedKernel := by + rw [matchedBinaryOpen_apply] + exact open_loop_kernel_two G P i r anchors a ha + +theorem matched_binary_closed_world_frozen + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] (i : Fin 2) + (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) : + (matchedBinaryClosed G P i r anchors).intervention.apply.world = physical P := by + rw [matchedBinaryClosed_apply] + rfl + +theorem matched_binary_open_world_frozen + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] (i : Fin 2) + (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) + (a : ∀ j, FiniteProduct.Mechanism S0 S X A j) + (ha : a ∈ anchors) : + (matchedBinaryOpen G P i r anchors a ha).intervention.apply.world = physical P := by + rw [matchedBinaryOpen_apply] + rfl + +end PermanssonResearch.MultiAgent.MeasurableProduct.Two diff --git a/PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean b/PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean new file mode 100644 index 00000000..d3ce047d --- /dev/null +++ b/PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean @@ -0,0 +1,131 @@ +import PermanssonResearch.MultiAgent.MeasurableProductTwoMatchedProtocols +import PermanssonResearch.MultiAgent.MeasurableProductTwoMargin +import Mathlib.Tactic + +/-! +# Original constitutive-margin closure for both matched two-player protocols + +The admissible binary intervention and kernel intertwining are constructed +from the source generator, player, replacement mechanism and the frozen source +anchor set. The entire original B₁ and a single pulled-back whole-path property +are retained. The original formal Exact-GR/PR gate is NOT inferred here. +-/ + +open MeasureTheory ProbabilityTheory Set +namespace PermanssonResearch.MultiAgent.MeasurableProduct.Two + +open PermanssonLean +open PermanssonLean.RegimeSpecification +open PermanssonResearch.MultiAgent +open PermanssonResearch.MultiAgent.FiniteProduct +open PermanssonResearch.MultiAgent.MeasurableProduct + +set_option autoImplicit false +universe u0 uS uX uA uH uHb uZ +variable {S0 : Type u0} {S : Fin 2 → Type uS} +variable {X : Type uX} {A : Fin 2 → Type uA} +variable [MeasurableSpace S0] [∀ i, MeasurableSpace (S i)] +variable [MeasurableSpace X] [∀ i, MeasurableSpace (A i)] +variable {H : Type uH} {Hb : Type uHb} {Z : Type uZ} +variable [MeasurableSpace H] [MeasurableSpace Hb] [MetricSpace Z] + +theorem matched_closed_loop_original_margin + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] + (i : Fin 2) (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) + (spec : RegimeSpecification (JointState (FiniteProduct.StrategicState S0 S) X) H) + (specBar : RegimeSpecification (JointState (GameState S0 (S 0) (S 1)) X) Hb) + (B₁ : ConstitutiveComparisonSet (MeasurableProduct.model G P) spec) + (Bbar₁ : ConstitutiveComparisonSet ((binary G).toModel (physical P)) specBar) + (hB : Bbar₁.states = + (compression_two (S0 := S0) (S := S) (X := X)).stateMap '' B₁.states) + (ψbar : RegimePropertyMap (JointState (GameState S0 (S 0) (S 1)) X) Z) : + constitutiveMargin (MeasurableProduct.model G P) spec + (MeasurableProduct.protocolFamily G P i r anchors) + (pullbackWholePathProperty + (compression_two (S0 := S0) (S := S) (X := X)) ψbar) + B₁ (MeasurableProduct.closedLoopIntervention G P i r anchors) = + constitutiveMargin ((binary G).toModel (physical P)) specBar + (matchedBinaryFamily G P i r anchors) ψbar Bbar₁ + (matchedBinaryClosed G P i r anchors) := by + exact original_margin_transport_two + G P spec specBar + (MeasurableProduct.protocolFamily G P i r anchors) + (matchedBinaryFamily G P i r anchors) + B₁ Bbar₁ hB + (MeasurableProduct.closedLoopIntervention G P i r anchors) + (matchedBinaryClosed G P i r anchors) + (matched_closed_kernel_two G P i r anchors) + ψbar + +theorem matched_open_loop_original_margin + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] + (i : Fin 2) (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) + (a : ∀ j, FiniteProduct.Mechanism S0 S X A j) + (ha : a ∈ anchors) + (spec : RegimeSpecification (JointState (FiniteProduct.StrategicState S0 S) X) H) + (specBar : RegimeSpecification (JointState (GameState S0 (S 0) (S 1)) X) Hb) + (B₁ : ConstitutiveComparisonSet (MeasurableProduct.model G P) spec) + (Bbar₁ : ConstitutiveComparisonSet ((binary G).toModel (physical P)) specBar) + (hB : Bbar₁.states = + (compression_two (S0 := S0) (S := S) (X := X)).stateMap '' B₁.states) + (ψbar : RegimePropertyMap (JointState (GameState S0 (S 0) (S 1)) X) Z) : + constitutiveMargin (MeasurableProduct.model G P) spec + (MeasurableProduct.protocolFamily G P i r anchors) + (pullbackWholePathProperty + (compression_two (S0 := S0) (S := S) (X := X)) ψbar) + B₁ (MeasurableProduct.openLoopIntervention G P i r anchors a ha) = + constitutiveMargin ((binary G).toModel (physical P)) specBar + (matchedBinaryFamily G P i r anchors) ψbar Bbar₁ + (matchedBinaryOpen G P i r anchors a ha) := by + exact original_margin_transport_two + G P spec specBar + (MeasurableProduct.protocolFamily G P i r anchors) + (matchedBinaryFamily G P i r anchors) + B₁ Bbar₁ hB + (MeasurableProduct.openLoopIntervention G P i r anchors a ha) + (matchedBinaryOpen G P i r anchors a ha) + (matched_open_kernel_two G P i r anchors a ha) + ψbar + +/-- Actual all-initial canonical entire-path equality after transport of a +closed-loop intervention, with the admitted source side, not a fresh binary +choice selected after observing the effect. -/ +theorem matched_closed_loop_all_initial_paths + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] + (i : Fin 2) (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) + (μ : Measure (JointState (FiniteProduct.StrategicState S0 S) X)) + [IsProbabilityMeasure μ] : + (((MeasurableProduct.closedLoopIntervention G P i r anchors).intervention.apply).pathLaw μ).map + (compression_two (S0 := S0) (S := S) (X := X)).pathMap = + ((matchedBinaryClosed G P i r anchors).intervention.apply).pathLaw + (μ.map (compression_two (S0 := S0) (S := S) (X := X)).stateMap) := by + rw [matchedBinaryClosed_apply] + exact closed_loop_path_law_two G P i r anchors μ + +theorem matched_open_loop_all_initial_paths + (G : FiniteProduct.Generator S0 S X A) + (P : Kernel (WorldInput (FiniteProduct.StrategicState S0 S) X (∀ i, A i)) X) + [IsMarkovKernel P] + (i : Fin 2) (r : FiniteProduct.Mechanism S0 S X A i) + (anchors : Set (∀ j, FiniteProduct.Mechanism S0 S X A j)) + (a : ∀ j, FiniteProduct.Mechanism S0 S X A j) + (ha : a ∈ anchors) + (μ : Measure (JointState (FiniteProduct.StrategicState S0 S) X)) + [IsProbabilityMeasure μ] : + (((MeasurableProduct.openLoopIntervention G P i r anchors a ha).intervention.apply).pathLaw μ).map + (compression_two (S0 := S0) (S := S) (X := X)).pathMap = + ((matchedBinaryOpen G P i r anchors a ha).intervention.apply).pathLaw + (μ.map (compression_two (S0 := S0) (S := S) (X := X)).stateMap) := by + rw [matchedBinaryOpen_apply] + exact open_loop_path_law_two G P i r anchors a ha μ + +end PermanssonResearch.MultiAgent.MeasurableProduct.Two diff --git a/PermanssonResearch/Perturbation/A04FourOutputNecessityCountermodel.lean b/PermanssonResearch/Perturbation/A04FourOutputNecessityCountermodel.lean new file mode 100644 index 00000000..540eac9a --- /dev/null +++ b/PermanssonResearch/Perturbation/A04FourOutputNecessityCountermodel.lean @@ -0,0 +1,42 @@ +import PermanssonResearch.Perturbation.FiniteOccupationFactorization +import Mathlib.Tactic + +/-! +# A-04: no general three-output substitute for a four-process margin + +For an original constitutive comparison, the baseline and intervention each +have an actual property output. Perturbing the model adds both of those +outputs separately. If one suppresses the perturbed intervention output, +three scalar numbers do not determine the margin difference. + +This is a necessary-input countermodel to the *form* of the original +four-process wrapper; it is not an instantiation of the full typed canonical +path-law factorization theorem. That still requires its own source receipt. +-/ + +namespace PermanssonResearch.Perturbation.A04FourOutputNecessity +set_option autoImplicit false + +def marginDifference (base treated perturbedBase perturbedTreated : ℝ) : ℝ := + dist (|perturbedBase - perturbedTreated|) (|base - treated|) + +theorem same_three_outputs_different_true_margins : + marginDifference 0 1 0 0 = (1 : ℝ) ∧ + marginDifference 0 1 0 1 = (0 : ℝ) := by + constructor <;> norm_num [marginDifference, Real.dist_eq] + +/-- No function of only the original baseline, original treatment, and +perturbed baseline outputs can return the correct margin difference for +every possible perturbed treatment output. -/ +theorem no_general_three_output_margin_formula : + ¬ ∃ f : ℝ → ℝ → ℝ → ℝ, + ∀ base treated perturbedBase perturbedTreated : ℝ, + f base treated perturbedBase = + marginDifference base treated perturbedBase perturbedTreated := by + rintro ⟨f, hf⟩ + have h0 := hf 0 1 0 0 + have h1 := hf 0 1 0 1 + norm_num [marginDifference, Real.dist_eq] at h0 h1 + linarith + +end PermanssonResearch.Perturbation.A04FourOutputNecessity diff --git a/PermanssonResearch/Perturbation/A05SurvivalOccupationCountermodel.lean b/PermanssonResearch/Perturbation/A05SurvivalOccupationCountermodel.lean new file mode 100644 index 00000000..6af99d37 --- /dev/null +++ b/PermanssonResearch/Perturbation/A05SurvivalOccupationCountermodel.lean @@ -0,0 +1,89 @@ +import PermanssonResearch.Perturbation.FiniteOccupationFactorization +import PermanssonResearch.ReverseSolver.InfiniteSafetyLeakage +import Mathlib.Analysis.SpecificLimits.Basic +import Mathlib.Tactic + +/-! +# A-05: invariant occupation weights are not a general full-path property + +A finite *constant-row* rational Markov model has stationary weight 99/100 +on the safe physical state, yet the probability of never leaving that state +is zero. Both computations concern the same explicitly declared transition +law. Thus the original whole-path survival property cannot silently be +replaced by an arbitrary functional reading the stationary marginal. + +This is a model-relative counterexample to unlicensed property factorization, +not a four-model perturbation theorem or general long-run certification. +-/ + +open Filter MeasureTheory ProbabilityTheory +open scoped Topology +namespace PermanssonResearch.ReverseSolver.InfiniteSafety.A05OccupationBoundary +open ControlledKernel FiniteMonitor + +def constantMatrix : RationalMarkovMatrix 2 where + entry _ z := if z = 0 then 99/100 else 1/100 + entry_nonneg _ z := by split_ifs <;> norm_num + row_sum_one y := by fin_cases y <;> norm_num [Fin.sum_univ_two] + +def constantSystem : FiniteControlSystem Unit 2 where + states_nonempty := by decide + baseline := constantMatrix + options _ := {()} + options_nonempty _ := Finset.singleton_nonempty () + matrix _ := constantMatrix + +def safe : Finset (Fin 2) := {0} + +def stationaryWeight (z : Fin 2) : ℚ := + if z = 0 then 99/100 else 1/100 + +theorem actual_rational_stationary_weights : + (∑ z : Fin 2, stationaryWeight z) = 1 ∧ + ∀ z : Fin 2, (∑ y : Fin 2, + stationaryWeight y * constantMatrix.entry y z) = + stationaryWeight z := by + constructor + · norm_num [stationaryWeight, Fin.sum_univ_two] + · intro z + fin_cases z <;> + norm_num [stationaryWeight, constantMatrix, Fin.sum_univ_two] + +noncomputable section + +theorem outside_horizon (n : ℕ) : + horizonValue constantSystem safe n 1 = 0 := by + cases n <;> simp [horizonValue, step, safe] + +theorem inside_horizon (n : ℕ) : + horizonValue constantSystem safe n 0 = ((99 : ℝ)/100)^n := by + induction n with + | zero => simp [horizonValue, safe] + | succ n ih => + have hm (v : Fin 2 → ℝ) : + maximum constantSystem v 0 = rowScore constantSystem v 0 () := by + simp [maximum, constantSystem] + rw [horizonValue, step, + if_pos (show (0 : Fin 2) ∈ safe by decide), + hm, rowScore, Fin.sum_univ_two, ih, outside_horizon] + norm_num [constantSystem, constantMatrix, pow_succ] <;> ring + +theorem actual_full_path_survival_zero : + limitValue constantSystem safe 0 = 0 := by + have hv := horizonValue_tendsto constantSystem safe 0 + simp_rw [inside_horizon] at hv + have hz : Tendsto (fun k : ℕ => ((99 : ℝ)/100)^k) + atTop (𝓝 0) := + tendsto_pow_atTop_nhds_zero_of_lt_one (by norm_num) (by norm_num) + exact tendsto_nhds_unique hv hz + +/-- A true invariant-state mass does not equal the infinite survival event +of this very same fixed finite rational chain. -/ +theorem cannot_replace_full_path_survival_by_stationary_mass : + limitValue constantSystem safe 0 ≠ + ((stationaryWeight (0 : Fin 2) : ℚ) : ℝ) := by + rw [actual_full_path_survival_zero] + norm_num [stationaryWeight] + +end +end PermanssonResearch.ReverseSolver.InfiniteSafety.A05OccupationBoundary diff --git a/PermanssonResearch/Probability/A01SupnormClassCountermodel.lean b/PermanssonResearch/Probability/A01SupnormClassCountermodel.lean new file mode 100644 index 00000000..d24a524f --- /dev/null +++ b/PermanssonResearch/Probability/A01SupnormClassCountermodel.lean @@ -0,0 +1,53 @@ +import PermanssonResearch.Probability.FiniteKantorovichDuality +import Mathlib.Tactic + +/-! +# A-01: unit-supnorm is NOT the full bounded one-Lipschitz dual class + +The two probability atoms 0 and 4 in the real line have a unit-Lipschitz +witness with value difference four, e.g. clamp(x,0,4), which is globally +bounded but NOT bounded in absolute value by one. Any test additionally +restricted to absolute values <= 1 at both atoms has a difference <= 2. +The original finite KR result uses the correctly typed test class; this +countermodel does not replace its actual coupling/duality theorem. +-/ + +namespace PermanssonResearch.Probability.A01SupnormClass +set_option autoImplicit false + +def clipped (x : ℝ) : ℝ := max 0 (min x 4) + +theorem clipped_lipschitz : + LipschitzWith 1 clipped := by + change LipschitzWith 1 (fun x : ℝ => max 0 (min x 4)) + exact (LipschitzWith.id.min_const (4 : ℝ)).const_max 0 + +theorem clipped_gap_four : + |clipped 4 - clipped 0| = (4 : ℝ) := by + norm_num [clipped] + +theorem supnorm_one_gap_le_two (f : ℝ → ℝ) + (h0 : |f 0| ≤ 1) (h4 : |f 4| ≤ 1) : + |f 4 - f 0| ≤ (2 : ℝ) := by + rcases abs_le.mp h0 with ⟨h0lo, h0hi⟩ + rcases abs_le.mp h4 with ⟨h4lo, h4hi⟩ + apply abs_le.mpr + constructor <;> linarith + +theorem no_unit_supnorm_witness_for_gap_four : + ¬ ∃ f : ℝ → ℝ, + |f 0| ≤ 1 ∧ |f 4| ≤ 1 ∧ (4 : ℝ) ≤ |f 4 - f 0| := by + rintro ⟨f, h0, h4, hgap⟩ + have hb := supnorm_one_gap_le_two f h0 h4 + linarith + +theorem legitimate_bounded_lipschitz_witness_not_unit_supnorm : + LipschitzWith 1 clipped ∧ + |clipped 4 - clipped 0| = (4 : ℝ) ∧ + ¬ (∀ x : ℝ, |clipped x| ≤ 1) := by + refine ⟨clipped_lipschitz, clipped_gap_four, ?_⟩ + intro hb + have hh := hb 4 + norm_num [clipped] at hh + +end PermanssonResearch.Probability.A01SupnormClass diff --git a/PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean b/PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean new file mode 100644 index 00000000..96f86a24 --- /dev/null +++ b/PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean @@ -0,0 +1,147 @@ +import PermanssonResearch.Quotient.DiscountedCanonicalSourceWitness +import PermanssonResearch.Quotient.DiscountedOriginalMargin +import Mathlib.Tactic + +/-! +# Real source-to-original constitutive-margin closure + +Uses genuine typed baseline/treatment canonical laws to construct the two +premises of original Proposition 5.2. A bound over every original B₁ start is +mandatory. Positive approximate margins never imply Exact GR preservation. +-/ + +open MeasureTheory ProbabilityTheory Set +open scoped ENNReal ProbabilityTheory + +namespace PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure +open PermanssonLean +open PermanssonLean.RegimeSpecification +open PermanssonResearch.Probability +open PermanssonResearch.Probability.DependentHistory +open PermanssonResearch.Quotient.DiscountedCanonicalSourceWitness +open PermanssonResearch.Quotient.DiscountedOriginalMargin + +set_option autoImplicit false +variable {S X A Sb Xb Ab H Hb : Type*} +variable [MeasurableSpace S] [MeasurableSpace X] [MeasurableSpace A] +variable [MeasurableSpace Sb] [MeasurableSpace Xb] [MeasurableSpace Ab] +variable [MeasurableSpace H] [MeasurableSpace Hb] +variable [MeasurableSingletonClass (JointState S X)] +variable [MeasurableSingletonClass (JointState Sb Xb)] + +/-- A source-bound, point-initialized common-mass certificate. +The initial submeasure is exactly the matched Dirac law; its defect is zero. +The row domination covers intervention-reachable states as well. -/ +structure PointWitness + (Qc : TypeRespectingStateCompression S X Sb Xb) + (M : StrategicWorldModel S X A) + (Mbar : StrategicWorldModel Sb Xb Ab) where + R : Kernel (JointState S X) (JointState S X) + R_finite : IsFiniteKernel R + R_sub : R ≤ M.inducedKernel + R_projected : R.map Qc.stateMap ≤ + Mbar.inducedKernel.comap Qc.stateMap Qc.stateMap_measurable + eta : ℕ → ℝ + eta_nonneg : ∀ n, 0 ≤ eta n + eta_le_one : ∀ n, eta n ≤ 1 + retention : ∀ n y, ENNReal.ofReal (1-eta n) ≤ R y Set.univ + +/-- Actual canonical discounted output, from *each* genuine source and +coarse model at the same fixed comparison start. -/ +theorem point_witness_output + (Qc : TypeRespectingStateCompression S X Sb Xb) + (M : StrategicWorldModel S X A) + (Mbar : StrategicWorldModel Sb Xb Ab) + (W : PointWitness Qc M Mbar) + (y : JointState S X) + (g : ℕ → JointState Sb Xb → ℝ) + (hg : ∀ n, Measurable (g n)) + (hg0 : ∀ n z, 0 ≤ g n z) (hg1 : ∀ n z, g n z ≤ 1) + {β : ℝ} (hβ0 : 0 ≤ β) (hβ1 : β < 1) : + |(∫ w, summablePathScore (fun n => (1-β)*β^n) + (fun n w => g n (w n)) w + ∂((M.pathLaw (Measure.dirac y)).map Qc.pathMap)) - + ∫ w, summablePathScore (fun n => (1-β)*β^n) + (fun n w => g n (w n)) w + ∂(Mbar.pathLaw (Measure.dirac (Qc.stateMap y)))| ≤ + ∑' n, (1-β)*β^n*(1-retained W.eta n) := by + letI : IsFiniteKernel W.R := W.R_finite + have hmap : (Measure.dirac y).map Qc.stateMap = + Measure.dirac (Qc.stateMap y) := by + rw [Measure.map_dirac' Qc.stateMap_measurable] + have h := canonical_discounted_output_error Qc M Mbar + (Measure.dirac y) (Measure.dirac y) + (Measure.dirac (Qc.stateMap y)) W.R + le_rfl (le_of_eq hmap) W.R_sub W.R_projected + W.eta W.eta_nonneg W.eta_le_one W.retention + (by norm_num : 0 ≤ (0 : ℝ)) (by norm_num : (0 : ℝ) ≤ 1) + (by simp : ENNReal.ofReal (1-(0:ℝ)) ≤ (Measure.dirac y) Set.univ) + g hg hg0 hg1 hβ0 hβ1 + simpa only [sub_zero, one_mul] using h + +/-- All original comparison starts receive their own certified baseline and +intervention source witnesses; the same B₁ and coarse image Bbar₁ are used. -/ +theorem actual_canonical_discounted_original_margin + (Qc : TypeRespectingStateCompression S X Sb Xb) + {M : StrategicWorldModel S X A} + {Mbar : StrategicWorldModel Sb Xb Ab} + (spec : RegimeSpecification (JointState S X) H) + (specBar : RegimeSpecification (JointState Sb Xb) Hb) + {Component ComponentBar : Type*} + (F : InterventionFamily M Component) + (Fbar : InterventionFamily Mbar ComponentBar) + (B₁ : ConstitutiveComparisonSet M spec) + (Bbar₁ : ConstitutiveComparisonSet Mbar specBar) + (hB : Bbar₁.states = Qc.stateMap '' B₁.states) + (J : AdmissibleStrategicIntervention F) + (Jbar : AdmissibleStrategicIntervention Fbar) + (W0 : ∀ y ∈ B₁.states, PointWitness Qc M Mbar) + (WJ : ∀ y ∈ B₁.states, + PointWitness Qc J.intervention.apply Jbar.intervention.apply) + (β : ℝ) (g : ℕ → JointState Sb Xb → ℝ) + (hg : ∀ n, Measurable (g n)) + (hg0 : ∀ n z, 0 ≤ g n z) (hg1 : ∀ n z, g n z ≤ 1) + (hβ0 : 0 ≤ β) (hβ1 : β < 1) + (e0 eJ : ℝ) (he0 : 0 ≤ e0) (heJ : 0 ≤ eJ) + (huniform0 : ∀ y (hy : y ∈ B₁.states), + (∑' n, (1-β)*β^n*(1-retained (W0 y hy).eta n)) ≤ e0) + (huniformJ : ∀ y (hy : y ∈ B₁.states), + (∑' n, (1-β)*β^n*(1-retained (WJ y hy).eta n)) ≤ eJ) : + |constitutiveMargin M spec F (projectedDiscountedProperty Qc β g) B₁ J - + constitutiveMargin Mbar specBar Fbar + (discountedWholePathProperty β g) Bbar₁ Jbar| ≤ e0+eJ := by + apply discounted_original_margin_abs_le Qc spec specBar F Fbar + B₁ Bbar₁ hB J Jbar β g e0 eJ he0 heJ + · intro y hy + have hw := point_witness_output Qc M Mbar (W0 y hy) y g + hg hg0 hg1 hβ0 hβ1 + have hb := huniform0 y hy + unfold baselinePropertyValue projectedDiscountedProperty + discountedWholePathProperty + rw [Real.dist_eq] + change + |(∫ w, summablePathScore (fun n => (1-β)*β^n) + (fun n w => g n (w n)) w + ∂((M.pathLaw (Measure.dirac y)).map Qc.pathMap)) - + ∫ w, summablePathScore (fun n => (1-β)*β^n) + (fun n w => g n (w n)) w + ∂(Mbar.pathLaw (Measure.dirac (Qc.stateMap y)))| ≤ e0 + exact hw.trans hb + · intro y hy + have hw := point_witness_output Qc J.intervention.apply + Jbar.intervention.apply (WJ y hy) y g + hg hg0 hg1 hβ0 hβ1 + have hb := huniformJ y hy + unfold intervenedPropertyValue projectedDiscountedProperty + discountedWholePathProperty + rw [Real.dist_eq] + change + |(∫ w, summablePathScore (fun n => (1-β)*β^n) + (fun n w => g n (w n)) w + ∂((J.intervention.apply.pathLaw (Measure.dirac y)).map Qc.pathMap)) - + ∫ w, summablePathScore (fun n => (1-β)*β^n) + (fun n w => g n (w n)) w + ∂(Jbar.intervention.apply.pathLaw (Measure.dirac (Qc.stateMap y)))| ≤ eJ + exact hw.trans hb + +end PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure diff --git a/PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean b/PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean new file mode 100644 index 00000000..5bc6096a --- /dev/null +++ b/PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean @@ -0,0 +1,89 @@ +import PermanssonResearch.Quotient.DiscountedCanonicalBridge +import PermanssonLean.Quotient.PathLaw +import PermanssonResearch.Quotient.DiscountedProjectedOutputs +import Mathlib.Tactic + +/-! +# Actual-source canonical discounted-output transport + +The source/target path laws here are not freely supplied measures. They are +the existing *typed* strategic-world canonical laws. All common-subkernel +premises, retention bounds and legal primitive correspondence remain visible. +-/ + +open MeasureTheory ProbabilityTheory +open scoped ENNReal ProbabilityTheory + +namespace PermanssonResearch.Quotient.DiscountedCanonicalSourceWitness +open PermanssonLean +open PermanssonLean.RegimeSpecification +open PermanssonResearch.Probability +open PermanssonResearch.Probability.DependentHistory +open PermanssonResearch.Quotient.DiscountedCanonicalBridge +open PermanssonResearch.Quotient.DiscountedProjected + +set_option autoImplicit false +variable {S X A Sb Xb Ab : Type*} +variable [MeasurableSpace S] [MeasurableSpace X] [MeasurableSpace A] +variable [MeasurableSpace Sb] [MeasurableSpace Xb] [MeasurableSpace Ab] + +/-- Baseline or treatment, with source and target both the *original* +canonical Markov law. Retention witnesses are assumptions on ACTUAL kernels, +not a fabricated TV bound, and are charged separately for each protocol. -/ +theorem canonical_discounted_output_error + (Qc : TypeRespectingStateCompression S X Sb Xb) + (M : StrategicWorldModel S X A) + (Mbar : StrategicWorldModel Sb Xb Ab) + (μ ξ : Measure (JointState S X)) [IsProbabilityMeasure μ] + [IsProbabilityMeasure ξ] + (ν : Measure (JointState Sb Xb)) [IsProbabilityMeasure ν] + (R : Kernel (JointState S X) (JointState S X)) [IsFiniteKernel R] + (hξμ : ξ ≤ μ) + (hξν : ξ.map Qc.stateMap ≤ ν) + (hRK : R ≤ M.inducedKernel) + (hproj : R.map Qc.stateMap ≤ + Mbar.inducedKernel.comap Qc.stateMap Qc.stateMap_measurable) + (η : ℕ → ℝ) (hη0 : ∀ n, 0 ≤ η n) (hη1 : ∀ n, η n ≤ 1) + (hmass : ∀ n y, ENNReal.ofReal (1-η n) ≤ R y Set.univ) + {ε : ℝ} (hε0 : 0 ≤ ε) (hε1 : ε ≤ 1) + (hξmass : ENNReal.ofReal (1-ε) ≤ ξ Set.univ) + (g : ℕ → JointState Sb Xb → ℝ) + (hg : ∀ n, Measurable (g n)) + (hg0 : ∀ n y, 0 ≤ g n y) + (hg1 : ∀ n y, g n y ≤ 1) + {β : ℝ} (hβ0 : 0 ≤ β) (hβ1 : β < 1) : + |(∫ w, summablePathScore (fun n => (1-β)*β^n) + (fun n w => g n (w n)) w + ∂((M.pathLaw μ).map Qc.pathMap)) - + ∫ w, summablePathScore (fun n => (1-β)*β^n) + (fun n w => g n (w n)) w ∂(Mbar.pathLaw ν)| ≤ + ∑' n, (1-β)*β^n*(1-(1-ε)*retained η n) := by + letI : IsMarkovKernel M.inducedKernel := + StrategicWorldModel.inducedKernel_isMarkov M + letI : IsMarkovKernel Mbar.inducedKernel := + StrategicWorldModel.inducedKernel_isMarkov Mbar + have hbase (n : ℕ) : + (M.pathLaw μ).map (fun w => w n) = + evolution μ (fun _ => M.inducedKernel) n := + canonical_nth_marginal_eq_evolution M μ n + have htarget (n : ℕ) : + (Mbar.pathLaw ν).map (fun w => w n) = + evolution ν (fun _ => Mbar.inducedKernel) n := + canonical_nth_marginal_eq_evolution Mbar ν n + have h := discounted_projected_output_error + (M.pathLaw μ) (Mbar.pathLaw ν) μ ξ ν + (fun _ => M.inducedKernel) (fun _ => R) + (fun _ => Mbar.inducedKernel) + (fun _ => Qc.stateMap) (fun _ => Qc.stateMap_measurable) + hξμ hξν (fun _ => hRK) (fun _ => hproj) + η hη0 hη1 hmass hε0 hε1 hξmass + hbase htarget g hg hg0 hg1 hβ0 hβ1 + have hmap : + (DiscountedProjected.projectedPath (fun _ : ℕ => Qc.stateMap) : + (ℕ → JointState S X) → (ℕ → JointState Sb Xb)) = Qc.pathMap := by + funext w n + rfl + rw [hmap] at h + exact h + +end PermanssonResearch.Quotient.DiscountedCanonicalSourceWitness diff --git a/PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean b/PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean new file mode 100644 index 00000000..e79843e6 --- /dev/null +++ b/PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean @@ -0,0 +1,99 @@ +import PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure +import PermanssonResearch.Quotient.DiscountedOriginalMarginExamples +import Mathlib.Tactic + +/-! +Exact discounted arithmetic adversaries. These facts inspect literal +closed-form model-output envelopes. They do not establish Exact GR or +identify the physical source kernels for an arbitrary outside model. +-/ +namespace PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples +open PermanssonResearch.Quotient.DiscountedOriginalMargin +open PermanssonResearch.Probability + +theorem correct_frozen_discounted_budget : + (∑' n : ℕ, (1-(1/3:ℝ))*(1/3:ℝ)^n* + (1-(1-(1/5:ℝ))*(1-(1/4:ℝ))^n)) = 13/45 := + exact_thirteen_fortyfive_budget + +/-- A well-formed but wrong rational literal cannot be substituted for the +true stationary discounted defect bound. -/ +theorem wrong_frozen_discounted_budget : + (∑' n : ℕ, (1-(1/3:ℝ))*(1/3:ℝ)^n* + (1-(1-(1/5:ℝ))*(1-(1/4:ℝ))^n)) ≠ 14/45 := by + rw [correct_frozen_discounted_budget] + norm_num + + + +namespace SourceBoundExactWitness +open PermanssonLean +open MeasureTheory ProbabilityTheory +open scoped ENNReal +open PermanssonResearch.Probability.DependentHistory +open PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure + +variable {S X A Sb Xb Ab : Type*} +variable [MeasurableSpace S] [MeasurableSpace X] [MeasurableSpace A] +variable [MeasurableSpace Sb] [MeasurableSpace Xb] [MeasurableSpace Ab] + +/-- The zero-leakage case is constructed from the REAL source Markov kernel +and a proved exact projection intertwining, not from an assumed TV bound. -/ +noncomputable def exactKernelPointWitness + (Qc : TypeRespectingStateCompression S X Sb Xb) + (M : StrategicWorldModel S X A) + (Mbar : StrategicWorldModel Sb Xb Ab) + (hK : KernelIntertwines Qc M.inducedKernel Mbar.inducedKernel) : + PointWitness Qc M Mbar where + R := M.inducedKernel + R_finite := by + letI : IsMarkovKernel M.inducedKernel := + StrategicWorldModel.inducedKernel_isMarkov M + infer_instance + R_sub := le_rfl + R_projected := le_of_eq hK + eta := fun _ => 0 + eta_nonneg := by intro n; norm_num + eta_le_one := by intro n; norm_num + retention := by + letI : IsMarkovKernel M.inducedKernel := + StrategicWorldModel.inducedKernel_isMarkov M + intro n y + simpa using (show (1 : ℝ≥0∞) ≤ M.inducedKernel y Set.univ from by simp) + +/-- Every horizon of this source-derived witness retains full mass. -/ +theorem exactKernelPointWitness_retained_one + (Qc : TypeRespectingStateCompression S X Sb Xb) + (M : StrategicWorldModel S X A) + (Mbar : StrategicWorldModel Sb Xb Ab) + (hK : KernelIntertwines Qc M.inducedKernel Mbar.inducedKernel) + (n : ℕ) : + retained (exactKernelPointWitness Qc M Mbar hK).eta n = 1 := by + simp [exactKernelPointWitness, retained] + +/-- A genuinely type-respecting identity projection, used to regression-test +that no fictitious error can be inserted at a matched point start. -/ +def identityStateCompression (S X : Type*) [MeasurableSpace S] + [MeasurableSpace X] : TypeRespectingStateCompression S X S X where + strategic := id + world := id + strategic_measurable := measurable_id + world_measurable := measurable_id + strategic_surjective := Function.surjective_id + world_surjective := Function.surjective_id + +theorem identity_canonical_full_path + (M : StrategicWorldModel S X A) + (μ : Measure (JointState S X)) [IsProbabilityMeasure μ] : + (M.pathLaw μ).map (identityStateCompression S X).pathMap = + M.pathLaw μ := by + have hmap : (identityStateCompression S X).pathMap = + (id : (ℕ → JointState S X) → (ℕ → JointState S X)) := by + funext w n + rfl + rw [hmap] + simp + +end SourceBoundExactWitness + +end PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples diff --git a/PermanssonResearch/Quotient/Q01NonLumpableProjectionCountermodel.lean b/PermanssonResearch/Quotient/Q01NonLumpableProjectionCountermodel.lean new file mode 100644 index 00000000..23f73a80 --- /dev/null +++ b/PermanssonResearch/Quotient/Q01NonLumpableProjectionCountermodel.lean @@ -0,0 +1,44 @@ +import PermanssonResearch.Quotient.GeometricPrefix +import Mathlib.Tactic + +/-! +# Q-01: a deterministic fine transition need not descend to its projection + +Two genuine fine states share their coarse visible state while their next +coarse states differ. Hence no uniform autonomous deterministic coarse +transition exists. This is a sufficient finite non-lumpability witness, +NOT a claim that every particular projected path law is non-Markov or that +the geometric-prefix theorem requires a projected Markov hypothesis. +-/ + +namespace PermanssonResearch.Quotient.Q01NonLumpable +set_option autoImplicit false + +def observed (x : Fin 3) : Bool := x == 1 + +def fineStep (x : Fin 3) : Fin 3 := + if x = 0 then 1 else 0 + +theorem equal_current_observation : + observed (0 : Fin 3) = observed (2 : Fin 3) := by + decide + +theorem incompatible_next_observations : + observed (fineStep (0 : Fin 3)) ≠ observed (fineStep (2 : Fin 3)) := by + decide + +/-- There is no state-only coarse evolution map that represents this +fine deterministic transition for every allowed fine starting state. -/ +theorem no_autonomous_coarse_transition : + ¬ ∃ g : Bool → Bool, ∀ x : Fin 3, + observed (fineStep x) = g (observed x) := by + rintro ⟨g, hg⟩ + have h0 := hg 0 + have h2 := hg 2 + apply incompatible_next_observations + calc + observed (fineStep (0 : Fin 3)) = g (observed 0) := h0 + _ = g (observed 2) := congrArg g equal_current_observation + _ = observed (fineStep (2 : Fin 3)) := h2.symm + +end PermanssonResearch.Quotient.Q01NonLumpable diff --git a/PermanssonResearch/Quotient/Q05ActualAtomClosure.lean b/PermanssonResearch/Quotient/Q05ActualAtomClosure.lean new file mode 100644 index 00000000..e4ff8e00 --- /dev/null +++ b/PermanssonResearch/Quotient/Q05ActualAtomClosure.lean @@ -0,0 +1,161 @@ +import PermanssonResearch.Quotient.Q05ActualOriginalMarginAdapter +import PermanssonResearch.X1.TypedModels +import Mathlib.Tactic + +/-! +# Q-05 exact original alpha -> P -> U kernel domination candidate + +The following lemmas identify genuine typed induced-kernel singleton atoms, +not a postulated Markov table. Once both actual atom identities are checked, +finite atom dominance supplies the missing original PointWitness premises. +All statements are scoped proofs; no axioms are introduced, and no randomized-policy or Exact GR result is asserted. +This module is a SOURCE CANDIDATE until a pinned Lean/compiler receipt passes. +-/ + +open MeasureTheory ProbabilityTheory Set +open scoped ENNReal + +namespace PermanssonResearch.Quotient.Q05ActualAtomClosure + +open PermanssonLean +open PermanssonResearch.X1 (coin coin_true coin_false coin_lintegral) +open PermanssonResearch.Quotient.Q05ActualOriginalMarginWitnessFixture +open PermanssonResearch.Quotient.Q05ActualOriginalMarginAdapter + +set_option autoImplicit false + +/-- The actual typed fine source has only the new strategic coordinate `t`, +and its physical world is the sampled action. -/ +theorem fine_induced_singleton (t : Bool) (y z : FineState) : + (fineModel t).inducedKernel y {z} = + if z.1 = t then (coin (fineActionRate t y.1)) {z.2} else 0 := by + classical + rw [StrategicWorldModel.inducedKernel_apply + (fineModel t) y (measurableSet_singleton z)] + change (∫⁻ a, ∫⁻ x', (Measure.dirac t) + {s : Bool | (s, x') ∈ ({z} : Set FineState)} + ∂(Measure.dirac a) ∂coin (fineActionRate t y.1)) = _ + rcases z with ⟨sz, xz⟩ + simp only [lintegral_dirac] + rw [coin_lintegral] + cases t <;> cases sz <;> cases xz <;> + simp [Measure.dirac_apply', coin_true, coin_false, + Set.indicator, Set.mem_singleton_iff] + +/-- Same actual alpha -> P -> U identity for the coarse Unit-strategic +model, where the physical P is still literally x' = a. -/ +theorem coarse_induced_singleton (t : Bool) (y z : CoarseState) : + (coarseModel t).inducedKernel y {z} = + (coin (coarseActionRate t)) {z.2} := by + classical + rw [StrategicWorldModel.inducedKernel_apply + (coarseModel t) y (measurableSet_singleton z)] + change (∫⁻ a, ∫⁻ x', (Measure.dirac ()) + {s : Unit | (s, x') ∈ ({z} : Set CoarseState)} + ∂(Measure.dirac a) ∂coin (coarseActionRate t)) = _ + rcases z with ⟨sz, xz⟩ + cases sz + simp only [lintegral_dirac] + rw [coin_lintegral] + cases xz <;> simp [Measure.dirac_apply', coin_true, coin_false, + Set.indicator, Set.mem_singleton_iff] + +/-- Finite common subkernel: only the two physically reachable next +atoms carry mass, and the weights are the entrywise Bernoulli minima. -/ +theorem common_source_singleton (t : Bool) (y z : FineState) : + (commonSourceKernel t) y {z} = + if z.1 = t then + ENNReal.ofReal + ((if z.2 then commonTrueMass t y.1 + else commonFalseMass t y.1 : ℚ) : ℝ) + else 0 := by + rcases z with ⟨sz, xz⟩ + cases t <;> cases sz <;> cases xz <;> + simp [commonSourceKernel, commonSourceRow, Measure.add_apply, + Measure.smul_apply, Measure.dirac_apply', commonTrueMass, + commonFalseMass, fineActionRate, coarseActionRate] + +/-- Genuine compressed common row, with all fine-memory dependence +still retained in the probability weights. -/ +theorem projected_common_singleton (t : Bool) (y : FineState) + (z : CoarseState) : + ((commonSourceKernel t) y).map compression.stateMap {z} = + ENNReal.ofReal + ((if z.2 then commonTrueMass t y.1 + else commonFalseMass t y.1 : ℚ) : ℝ) := by + rcases z with ⟨sz, xz⟩ + cases sz + rw [Measure.map_apply compression.stateMap_measurable + (measurableSet_singleton ((), xz))] + cases t <;> cases xz <;> cases y with + | mk s x => + cases s <;> cases x <;> + simp [commonSourceKernel, commonSourceRow, compression, + TypeRespectingStateCompression.stateMap, + Measure.add_apply, Measure.smul_apply, + Measure.dirac_apply', Set.indicator, Set.mem_singleton_iff, + commonTrueMass, commonFalseMass, + fineActionRate, coarseActionRate] + +/-- The two original input-dependent singleton atoms dominate the actual +common subkernel on every fine strategic/physical state. -/ +theorem fine_actual_atom_domination + (t : Bool) (y z : FineState) : + (commonSourceKernel t) y {z} ≤ + (fineModel t).inducedKernel y {z} := by + rw [common_source_singleton, fine_induced_singleton] + by_cases h : z.1 = t + · simp only [h, ite_true] + cases hz : z.2 + · rw [coin_false] + apply ENNReal.ofReal_le_ofReal + exact_mod_cast commonFalseMass_le_fine t y.1 + · rw [coin_true] + apply ENNReal.ofReal_le_ofReal + exact_mod_cast commonTrueMass_le_fine t y.1 + · simp [h] + +/-- The compressed row is dominated by the REAL coarse induced kernel at +every original fine source state, not just at baseline-reachable states. -/ +theorem coarse_actual_atom_domination + (t : Bool) (y : FineState) (z : CoarseState) : + ((commonSourceKernel t) y).map compression.stateMap {z} ≤ + (coarseModel t).inducedKernel (compression.stateMap y) {z} := by + rw [projected_common_singleton, coarse_induced_singleton] + cases hz : z.2 + · rw [coin_false] + apply ENNReal.ofReal_le_ofReal + exact_mod_cast commonFalseMass_le_coarse t y.1 + · rw [coin_true] + apply ENNReal.ofReal_le_ofReal + exact_mod_cast commonTrueMass_le_coarse t y.1 + +/-- Full actual all-state source-domination certificate, with no +pointwise/initial-law assumption hidden in the constructor. -/ +noncomputable def original_verified_atom_domination : + VerifiedSourceAtomDomination where + fine := fine_actual_atom_domination + projected := coarse_actual_atom_domination + +noncomputable def original_point_witness (t : Bool) : + PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure.PointWitness + compression (fineModel t) (coarseModel t) := + sourcePointWitnessOfAtoms original_verified_atom_domination t + +/-- Full ORIGINAL constitutive-margin conclusion once source atom identities +and finite all-row dominance above have compiled. -/ +theorem actual_original_B1_margin_bound : + |PermanssonLean.RegimeSpecification.constitutiveMargin + (fineModel false) fineSpec fineFamily + (PermanssonResearch.Quotient.DiscountedOriginalMargin.projectedDiscountedProperty + compression (1/3 : ℝ) observedWorldScore) + fineComparisonSet fineJ - + PermanssonLean.RegimeSpecification.constitutiveMargin + (coarseModel false) coarseSpec coarseFamily + (PermanssonResearch.Quotient.DiscountedOriginalMargin.discountedWholePathProperty + (1/3 : ℝ) observedWorldScore) + coarseComparisonSet coarseJ| ≤ (2/41 : ℝ) := + original_margin_of_verified_source_domination + (sourceDominationOfAtoms original_verified_atom_domination) + +end PermanssonResearch.Quotient.Q05ActualAtomClosure diff --git a/PermanssonResearch/Quotient/Q05ActualOriginalMarginAdapter.lean b/PermanssonResearch/Quotient/Q05ActualOriginalMarginAdapter.lean new file mode 100644 index 00000000..35b21a1e --- /dev/null +++ b/PermanssonResearch/Quotient/Q05ActualOriginalMarginAdapter.lean @@ -0,0 +1,207 @@ +import PermanssonResearch.Quotient.Q05ActualOriginalMarginWitnessFixture +import PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure +import PermanssonResearch.Quotient.DiscountedOriginalMargin +import Mathlib.Tactic + +/-! +# Q-05: actual-source PointWitness and original constitutive-margin interface + +The previous typed source has a real common finite subkernel for both actual +baseline and intervention. Its exact mass, eta and geometric budget are proved +there. The *remaining* obligations are the two genuine kernel domination +statements. They are explicit hypotheses here, not numerical substitutes, +unreviewed axioms, or fabricated proof data. + +Under those full all-state inequalities, this theorem constructs actual +PointWitness objects for both legal strategic interventions and invokes the +existing original full-B₁ constitutiveMargin theorem with the exact image of +the predeclared, path-law-nontrivial two-start comparison set. It does NOT prove +the two domination hypotheses, canonical-score numerical equalities, Exact +GR/PR preservation, or empirical identification. +-/ + +open MeasureTheory ProbabilityTheory Set +open scoped ENNReal ProbabilityTheory + +namespace PermanssonResearch.Quotient.Q05ActualOriginalMarginAdapter + +open PermanssonLean +open PermanssonLean.RegimeSpecification +open PermanssonResearch.Quotient.Q05ActualOriginalMarginWitnessFixture +open PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure +open PermanssonResearch.Quotient.DiscountedOriginalMargin +open PermanssonResearch.Probability.DependentHistory + +set_option autoImplicit false + +/-- The independently mandatory, ACTUAL induced-kernel domination contracts +for all four source models, not just the two frozen comparison starts. -/ +structure VerifiedSourceDomination where + fine : ∀ t : Bool, + commonSourceKernel t ≤ (fineModel t).inducedKernel + projected : ∀ t : Bool, + (commonSourceKernel t).map compression.stateMap ≤ + (coarseModel t).inducedKernel.comap + compression.stateMap compression.stateMap_measurable + +/-- For a genuinely finite state carrier, eventwise domination is entailed +by domination of EVERY singleton atom; no special event is skipped. -/ +theorem finiteMeasureLeOfAtoms + {Z : Type*} [Fintype Z] [MeasurableSpace Z] + [MeasurableSingletonClass Z] + (μ ν : Measure Z) + (hatom : ∀ z : Z, μ {z} ≤ ν {z}) : μ ≤ ν := by + classical + apply Measure.le_iff.mpr + intro E hE + let selected : Finset Z := Finset.univ.filter (fun z => z ∈ E) + have hselected : (selected : Set Z) = E := by + ext z + simp [selected] + calc + μ E = μ (selected : Set Z) := by rw [hselected] + _ = ∑ z ∈ selected, μ {z} := + (sum_measure_singleton).symm + _ ≤ ∑ z ∈ selected, ν {z} := + Finset.sum_le_sum (fun z _ => hatom z) + _ = ν (selected : Set Z) := + sum_measure_singleton + _ = ν E := by rw [hselected] + +/-- An exact original-kernel atom contract: every fine atom AND every +projected coarse atom must dominate its actual common source row. Unlike a +norm bound, it is an all-state, all-atom, both-protocol specification. -/ +structure VerifiedSourceAtomDomination where + fine : ∀ (t : Bool) (y z : FineState), + (commonSourceKernel t) y {z} ≤ + (fineModel t).inducedKernel y {z} + projected : ∀ (t : Bool) (y : FineState) (z : CoarseState), + ((commonSourceKernel t) y).map compression.stateMap {z} ≤ + (coarseModel t).inducedKernel (compression.stateMap y) {z} + +/-- Complete row domination follows from the source's actual finite atom +inequalities. This avoids treating a few sampled histories or a positive +reported total-variation estimate as full kernel order. -/ +theorem sourceDominationOfAtoms + (h : VerifiedSourceAtomDomination) : VerifiedSourceDomination := by + refine ⟨?_, ?_⟩ + · intro t + change ∀ y, (commonSourceKernel t) y ≤ (fineModel t).inducedKernel y + intro y + exact finiteMeasureLeOfAtoms _ _ (h.fine t y) + · intro t + change ∀ y, + ((commonSourceKernel t).map compression.stateMap) y ≤ + ((coarseModel t).inducedKernel.comap + compression.stateMap compression.stateMap_measurable) y + intro y + rw [Kernel.map_apply _ compression.stateMap_measurable, + Kernel.comap_apply] + exact finiteMeasureLeOfAtoms _ _ (h.projected t y) + +/-- All the other PointWitness fields are derived from the source, not assumed. +The two domination premises above are the precise open mathematical tasks. -/ +noncomputable def sourcePointWitness + (h : VerifiedSourceDomination) (t : Bool) : + PointWitness compression (fineModel t) (coarseModel t) where + R := commonSourceKernel t + R_finite := commonSourceKernel_isFinite t + R_sub := h.fine t + R_projected := h.projected t + eta := commonEta + eta_nonneg := by intro n; norm_num [commonEta] + eta_le_one := by intro n; norm_num [commonEta] + retention := fun n y => commonSourceKernel_retention t n y + +/-- Once the eight finite-row atom obligations are discharged, their +ALL-event consequences provide the genuine original PointWitness. -/ +noncomputable def sourcePointWitnessOfAtoms + (h : VerifiedSourceAtomDomination) (t : Bool) : + PointWitness compression (fineModel t) (coarseModel t) := + sourcePointWitness (sourceDominationOfAtoms h) t + +/-- This is the literal *admitted* joint strategic intervention, not a +separate model silently substituted for the intervention. -/ +theorem fineJ_is_treated_source : + fineJ.intervention.apply = fineModel true := rfl + +theorem coarseJ_is_treated_source : + coarseJ.intervention.apply = coarseModel true := rfl + +/-- The score is a genuine fixed, bounded, measurable full-path integrand. +It reads the physical coordinate and never accesses latent fine memory. -/ +def observedWorldScore : ℕ → CoarseState → ℝ := + fun _ z => if z.2 then 1 else 0 + +theorem observedWorldScore_measurable (n : ℕ) : + Measurable (observedWorldScore n) := Measurable.of_discrete + +theorem observedWorldScore_bounded (n : ℕ) (z : CoarseState) : + 0 ≤ observedWorldScore n z ∧ observedWorldScore n z ≤ 1 := by + rcases z with ⟨s, x⟩ + cases x <;> norm_num [observedWorldScore] + +/-- The real source subkernel retains exactly the discounted mass needed +for both baseline and intervention, at *all* original comparison states. -/ +theorem sourcePointWitness_budget + (h : VerifiedSourceDomination) (t : Bool) : + (∑' n : ℕ, (1-(1/3 : ℝ))*(1/3 : ℝ)^n* + (1-retained (sourcePointWitness h t).eta n)) = 1/41 := by + change (∑' n : ℕ, (1-(1/3 : ℝ))*(1/3 : ℝ)^n* + (1-retained commonEta n)) = 1/41 + exact commonEta_discounted_budget + +/-- Conditional source-derived full original B₁ margin. +The only unresolved *hypotheses* are genuine all-row kernel domination, +which are declared in VerifiedSourceDomination. Neither manuscript approval +nor Exact GR follows from this quantitative margin bound. -/ +theorem original_margin_of_verified_source_domination + (h : VerifiedSourceDomination) : + |constitutiveMargin (fineModel false) fineSpec fineFamily + (projectedDiscountedProperty compression (1/3 : ℝ) observedWorldScore) + fineComparisonSet fineJ - + constitutiveMargin (coarseModel false) coarseSpec coarseFamily + (discountedWholePathProperty (1/3 : ℝ) observedWorldScore) + coarseComparisonSet coarseJ| ≤ (2/41 : ℝ) := by + let W0 : ∀ y ∈ fineComparisonSet.states, + PointWitness compression (fineModel false) (coarseModel false) := + fun _ _ => sourcePointWitness h false + let WJ : ∀ y ∈ fineComparisonSet.states, + PointWitness compression fineJ.intervention.apply + coarseJ.intervention.apply := fun _ _ => by + rw [fineJ_is_treated_source, coarseJ_is_treated_source] + exact sourcePointWitness h true + have hg : ∀ n, Measurable (observedWorldScore n) := + observedWorldScore_measurable + have hg0 : ∀ n z, 0 ≤ observedWorldScore n z := + fun n z => (observedWorldScore_bounded n z).1 + have hg1 : ∀ n z, observedWorldScore n z ≤ 1 := + fun n z => (observedWorldScore_bounded n z).2 + have hb0 : ∀ y (hy : y ∈ fineComparisonSet.states), + (∑' n, (1-(1/3 : ℝ))*(1/3 : ℝ)^n* + (1-retained (W0 y hy).eta n)) ≤ 1/41 := by + intro y hy + change (∑' n : ℕ, (1-(1/3 : ℝ))*(1/3 : ℝ)^n* + (1-retained commonEta n)) ≤ 1/41 + rw [commonEta_discounted_budget] + have hbJ : ∀ y (hy : y ∈ fineComparisonSet.states), + (∑' n, (1-(1/3 : ℝ))*(1/3 : ℝ)^n* + (1-retained (WJ y hy).eta n)) ≤ 1/41 := by + intro y hy + change (∑' n : ℕ, (1-(1/3 : ℝ))*(1/3 : ℝ)^n* + (1-retained commonEta n)) ≤ 1/41 + rw [commonEta_discounted_budget] + have main := actual_canonical_discounted_original_margin + compression fineSpec coarseSpec fineFamily coarseFamily + fineComparisonSet coarseComparisonSet entire_original_comparison_domain + fineJ coarseJ W0 WJ (1/3 : ℝ) observedWorldScore hg hg0 hg1 + (by norm_num : 0 ≤ (1/3 : ℝ)) + (by norm_num : (1/3 : ℝ) < 1) + (1/41 : ℝ) (1/41 : ℝ) + (by norm_num : 0 ≤ (1/41 : ℝ)) + (by norm_num : 0 ≤ (1/41 : ℝ)) + hb0 hbJ + norm_num at main ⊢ + exact main + +end PermanssonResearch.Quotient.Q05ActualOriginalMarginAdapter diff --git a/PermanssonResearch/Quotient/Q05ActualOriginalMarginWitnessFixture.lean b/PermanssonResearch/Quotient/Q05ActualOriginalMarginWitnessFixture.lean new file mode 100644 index 00000000..e6d75b91 --- /dev/null +++ b/PermanssonResearch/Quotient/Q05ActualOriginalMarginWitnessFixture.lean @@ -0,0 +1,442 @@ +import PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure +import PermanssonResearch.X1.TypedModels +import PermanssonResearch.ConstitutiveQuasi.ComparisonTransport +import PermanssonResearch.Probability.DiscountedInitialBudget +import Mathlib.Tactic + +/-! +# Q-05 typed finite source witness (not the final original-margin certificate) +Genuine alpha -> P -> U models with fixed physical transition P. +The exact rational score calculations are a separate finite calculation; +PointWitness, full canonical paths and original comparison-set margin remain +additional required obligations before full Q-05 acceptance. +-/ + +open MeasureTheory ProbabilityTheory +open scoped ENNReal + +namespace PermanssonResearch.Quotient.Q05ActualOriginalMarginWitnessFixture +open PermanssonLean +open PermanssonResearch.X1 (coin coin_probability) +set_option autoImplicit false + +abbrev FineState := JointState Bool Bool +abbrev CoarseState := JointState Unit Bool + +def fineActionRate (treated strategic : Bool) : ℚ := + if treated then (if strategic then 4/5 else 7/10) + else (if strategic then 1/5 else 1/10) + +def coarseActionRate (treated : Bool) : ℚ := + if treated then 3/4 else 3/20 + +theorem fineActionRate_bounds (t s : Bool) : + 0 ≤ fineActionRate t s ∧ fineActionRate t s ≤ 1 := by + cases t <;> cases s <;> norm_num [fineActionRate] + +theorem coarseActionRate_bounds (t : Bool) : + 0 ≤ coarseActionRate t ∧ coarseActionRate t ≤ 1 := by + cases t <;> norm_num [coarseActionRate] + +noncomputable def fineAction (t : Bool) : Kernel FineState Bool where + toFun y := coin (fineActionRate t y.1) + measurable' := Measurable.of_discrete + +instance fineAction_isMarkov (t : Bool) : IsMarkovKernel (fineAction t) := + ⟨fun y => coin_probability _ (fineActionRate_bounds t y.1)⟩ + +noncomputable def coarseAction (t : Bool) : Kernel CoarseState Bool where + toFun _ := coin (coarseActionRate t) + measurable' := Measurable.of_discrete + +instance coarseAction_isMarkov (t : Bool) : IsMarkovKernel (coarseAction t) := + ⟨fun _ => coin_probability _ (coarseActionRate_bounds t)⟩ + +noncomputable def fineWorld : Kernel (WorldInput Bool Bool Bool) Bool := + Kernel.deterministic (fun z => z.2) Measurable.of_discrete + +noncomputable def coarseWorld : Kernel (WorldInput Unit Bool Bool) Bool := + Kernel.deterministic (fun z => z.2) Measurable.of_discrete + +noncomputable def fineUpdate (t : Bool) : + Kernel (UpdateInput Bool Bool Bool) Bool := + Kernel.deterministic (fun _ => t) Measurable.of_discrete + +noncomputable def coarseUpdate : + Kernel (UpdateInput Unit Bool Bool) Unit := + Kernel.deterministic (fun _ => ()) Measurable.of_discrete + +noncomputable def fineModel (t : Bool) : StrategicWorldModel Bool Bool Bool where + generator := { + action := fineAction t + update := fineUpdate t + action_isMarkov := inferInstance + update_isMarkov := by unfold fineUpdate; infer_instance + } + world := fineWorld + world_isMarkov := by unfold fineWorld; infer_instance + +noncomputable def coarseModel (t : Bool) : StrategicWorldModel Unit Bool Bool where + generator := { + action := coarseAction t + update := coarseUpdate + action_isMarkov := inferInstance + update_isMarkov := by unfold coarseUpdate; infer_instance + } + world := coarseWorld + world_isMarkov := by unfold coarseWorld; infer_instance + +theorem fine_world_fixed : (fineModel false).world = (fineModel true).world := rfl +theorem coarse_world_fixed : (coarseModel false).world = (coarseModel true).world := rfl + +def compression : TypeRespectingStateCompression Bool Bool Unit Bool where + strategic := fun _ => () + world := id + strategic_measurable := Measurable.of_discrete + world_measurable := measurable_id + strategic_surjective := by + intro z + exact ⟨false, by cases z; rfl⟩ + world_surjective := Function.surjective_id + +/-- Frozen admitted joint strategic intervention, not a model-swapping label. -/ +inductive FamilyComponent | joint + +noncomputable def fineFamily : + InterventionFamily (fineModel false) FamilyComponent where + targetOf + | .joint => .jointStrategic + admissible + | .joint, r => r = (fineModel true).generator + +noncomputable def coarseFamily : + InterventionFamily (coarseModel false) FamilyComponent where + targetOf + | .joint => .jointStrategic + admissible + | .joint, r => r = (coarseModel true).generator + +noncomputable def fineJ : AdmissibleStrategicIntervention fineFamily where + intervention := ⟨.joint, (fineModel true).generator⟩ + accepted := rfl + strategic := InterventionTarget.isStrategic_jointStrategic + +noncomputable def coarseJ : AdmissibleStrategicIntervention coarseFamily where + intervention := ⟨.joint, (coarseModel true).generator⟩ + accepted := rfl + strategic := InterventionTarget.isStrategic_jointStrategic + +/-- This is the actual intervention grammar's world preservation lemma. -/ +theorem admitted_fine_world_fixed : + fineJ.intervention.apply.world = (fineModel false).world := fineJ.world_eq + +theorem admitted_coarse_world_fixed : + coarseJ.intervention.apply.world = (coarseModel false).world := coarseJ.world_eq + +/-- The original predeclared two-state comparison domain, not one random +singleton path law. A genuine full ConstitutiveComparisonSet must additionally +carry a chosen regime specification and the corresponding source law. -/ +def twoStartDomain : Set FineState := {(false, false), (false, true)} + +theorem twoStartDomain_contains_both : + (false, false) ∈ twoStartDomain ∧ + (false, true) ∈ twoStartDomain := by + simp [twoStartDomain] + +theorem twoStartDomain_has_distinct_starts : + (false, false) ≠ (false, true) := by decide + +/-- Frozen original source regime specification: no parameter is optimized +after the witness is calculated. All finite states are declared in the basin. +This does NOT assert Exact GR. -/ +noncomputable def fineSpec : RegimeSpecification FineState Bool where + region := Set.univ + region_measurable := MeasurableSet.univ + basin := Set.univ + basin_measurable := MeasurableSet.univ + basin_subset_region := Set.Subset.rfl + descriptor := Prod.snd + descriptor_measurable := measurable_snd + target := ⟨coin (1/2), coin_probability _ (by constructor <;> norm_num)⟩ + convergenceMode := ConvergenceMode.almostSureWeak FineState Bool + +noncomputable def coarseSpec : RegimeSpecification CoarseState Bool where + region := Set.univ + region_measurable := MeasurableSet.univ + basin := Set.univ + basin_measurable := MeasurableSet.univ + basin_subset_region := Set.Subset.rfl + descriptor := Prod.snd + descriptor_measurable := measurable_snd + target := ⟨coin (1/2), coin_probability _ (by constructor <;> norm_num)⟩ + convergenceMode := ConvergenceMode.almostSureWeak CoarseState Bool + +/-- The original B₁ object has TWO distinct baseline point-started +whole-path laws. Randomness in one point-started law is not substituted for +the required nontriviality premise. -/ +noncomputable def fineComparisonSet : + RegimeSpecification.ConstitutiveComparisonSet (fineModel false) fineSpec where + states := twoStartDomain + states_measurable := MeasurableSet.of_discrete + states_subset_basin := by intro y hy; trivial + pathLawNontrivial := by + refine ⟨(false, false), ?_, (false, true), ?_, ?_⟩ + · simp [twoStartDomain] + · simp [twoStartDomain] + · exact PermanssonResearch.ConstitutiveQuasi.pointStarted_pathLaw_ne_of_ne + (fineModel false) (by decide) + +noncomputable def coarseComparisonSet : + RegimeSpecification.ConstitutiveComparisonSet (coarseModel false) coarseSpec where + states := Set.univ + states_measurable := MeasurableSet.univ + states_subset_basin := by intro y hy; trivial + pathLawNontrivial := by + refine ⟨((), false), Set.mem_univ _, ((), true), Set.mem_univ _, ?_⟩ + exact PermanssonResearch.ConstitutiveQuasi.pointStarted_pathLaw_ne_of_ne + (coarseModel false) (by decide) + +/-- The *entire* original two-start domain maps to the coarse domain. +No post-hoc smaller comparison subset or weaker surrogate margin. -/ +theorem entire_original_comparison_domain : + coarseComparisonSet.states = + compression.stateMap '' fineComparisonSet.states := by + change Set.univ = compression.stateMap '' twoStartDomain + ext z + constructor + · intro _ + refine ⟨(false, z.2), ?_, ?_⟩ + · cases z.2 <;> simp [twoStartDomain] + · cases z.1; rfl + · intro _ + exact Set.mem_univ _ + +theorem compression_preserves_world (y : FineState) : + (compression.stateMap y).2 = y.2 := rfl + +theorem compression_collapses_distinct_states : + compression.stateMap (false, true) = + compression.stateMap (true, true) ∧ + (false, true) ≠ (true, true) := by + constructor + · rfl + · decide + +theorem compression_noninjective : + ¬ Function.Injective compression.stateMap := by + intro h + have eqmap : compression.stateMap (false, true) = + compression.stateMap (true, true) := rfl + have heq := h eqmap + have impossible : (false : Bool) = true := congrArg Prod.fst heq + simp at impossible + +def fineDiscountedOutput (treated s x : Bool) : ℚ := + (2/3 : ℚ) * (if x then 1 else 0) + + (2/9 : ℚ) * fineActionRate treated s + + (1/9 : ℚ) * fineActionRate treated treated + +def coarseDiscountedOutput (treated x : Bool) : ℚ := + (2/3 : ℚ) * (if x then 1 else 0) + + (1/3 : ℚ) * coarseActionRate treated + +def fineOriginalEffect (x : Bool) : ℚ := + fineDiscountedOutput true false x - fineDiscountedOutput false false x + +def coarseOriginalEffect (x : Bool) : ℚ := + coarseDiscountedOutput true x - coarseDiscountedOutput false x + +theorem exact_four_outputs_false : + fineDiscountedOutput false false false = 1/30 ∧ + fineDiscountedOutput true false false = 11/45 ∧ + coarseDiscountedOutput false false = 1/20 ∧ + coarseDiscountedOutput true false = 1/4 := by + norm_num [fineDiscountedOutput, fineActionRate, + coarseDiscountedOutput, coarseActionRate] + +theorem exact_four_outputs_true : + fineDiscountedOutput false false true = 7/10 ∧ + fineDiscountedOutput true false true = 41/45 ∧ + coarseDiscountedOutput false true = 43/60 ∧ + coarseDiscountedOutput true true = 11/12 := by + norm_num [fineDiscountedOutput, fineActionRate, + coarseDiscountedOutput, coarseActionRate] + +theorem actual_two_start_effects (x : Bool) : + fineOriginalEffect x = (19/90 : ℚ) ∧ + coarseOriginalEffect x = (1/5 : ℚ) := by + cases x <;> norm_num [fineOriginalEffect, coarseOriginalEffect, + fineDiscountedOutput, coarseDiscountedOutput, + fineActionRate, coarseActionRate] + +theorem exact_margin_gap (x : Bool) : + |fineOriginalEffect x - coarseOriginalEffect x| = (1/90 : ℚ) := by + cases x <;> norm_num [fineOriginalEffect, coarseOriginalEffect, + fineDiscountedOutput, coarseDiscountedOutput, + fineActionRate, coarseActionRate] + +def commonMass (p q : ℚ) : ℚ := + min p q + min (1-p) (1-q) + +theorem both_sides_every_strategic_row (t s : Bool) : + commonMass (fineActionRate t s) (coarseActionRate t) = 19/20 := by + cases t <;> cases s <;> + norm_num [commonMass, fineActionRate, coarseActionRate, min_def] + +theorem discounted_defect_budget : + ((1/3 : ℚ) * (1/20) / (1-(1/3)+(1/3)*(1/20))) = 1/41 := by + norm_num + +theorem two_sided_original_margin_bound (x : Bool) : + |fineOriginalEffect x - coarseOriginalEffect x| ≤ (2/41 : ℚ) := by + cases x <;> norm_num [fineOriginalEffect, coarseOriginalEffect, + fineDiscountedOutput, coarseDiscountedOutput, + fineActionRate, coarseActionRate] + +theorem positive_gap_survives_budget : + (0 : ℚ) < 19/90 - 2/41 := by norm_num + +/-- The common true/false atom masses are the EXACT finite Bernoulli +intersection, defined from the original typed action rates on every row. -/ +def commonTrueMass (treated strategic : Bool) : ℚ := + min (fineActionRate treated strategic) (coarseActionRate treated) + +def commonFalseMass (treated strategic : Bool) : ℚ := + min (1-fineActionRate treated strategic) (1-coarseActionRate treated) + +/-- The actual finite Bernoulli atomwise common mass is pointwise dominated +by BOTH source action rows, for BOTH protocols and EVERY fine strategic row. -/ +theorem commonTrueMass_le_fine (t s : Bool) : + commonTrueMass t s ≤ fineActionRate t s := by + exact min_le_left _ _ + +theorem commonTrueMass_le_coarse (t s : Bool) : + commonTrueMass t s ≤ coarseActionRate t := by + exact min_le_right _ _ + +theorem commonFalseMass_le_fine (t s : Bool) : + commonFalseMass t s ≤ 1-fineActionRate t s := by + exact min_le_left _ _ + +theorem commonFalseMass_le_coarse (t s : Bool) : + commonFalseMass t s ≤ 1-coarseActionRate t := by + exact min_le_right _ _ + +/-- This is the exact algebraic component of the all-event kernel +domination; source-specific induced-kernel atom identities are still +required for the final verified PointWitness. -/ +theorem commonMass_dominated_every_source (t s : Bool) : + commonTrueMass t s ≤ fineActionRate t s ∧ + commonTrueMass t s ≤ coarseActionRate t ∧ + commonFalseMass t s ≤ 1-fineActionRate t s ∧ + commonFalseMass t s ≤ 1-coarseActionRate t := by + exact ⟨commonTrueMass_le_fine t s, + commonTrueMass_le_coarse t s, + commonFalseMass_le_fine t s, + commonFalseMass_le_coarse t s⟩ + +theorem commonTrueMass_nonneg (t s : Bool) : + (0 : ℝ) ≤ (commonTrueMass t s : ℝ) := by + cases t <;> cases s <;> + norm_num [commonTrueMass, fineActionRate, coarseActionRate] + +theorem commonFalseMass_nonneg (t s : Bool) : + (0 : ℝ) ≤ (commonFalseMass t s : ℝ) := by + cases t <;> cases s <;> + norm_num [commonFalseMass, fineActionRate, coarseActionRate] + +theorem commonMass_normalization (t s : Bool) : + commonTrueMass t s + commonFalseMass t s = (19/20 : ℚ) := by + simpa [commonMass, commonTrueMass, commonFalseMass] using + both_sides_every_strategic_row t s + +/-- A real FINITE subprobability measure on actual fine next states. +The next strategic coordinate is the original baseline/treatment update; +the physical coordinate remains the actual sampled action. -/ +noncomputable def commonSourceRow (t : Bool) (y : FineState) : + Measure FineState := + ENNReal.ofReal (commonTrueMass t y.1 : ℝ) • + Measure.dirac (t, true) + + ENNReal.ofReal (commonFalseMass t y.1 : ℝ) • + Measure.dirac (t, false) + +noncomputable def commonSourceKernel (t : Bool) : + Kernel FineState FineState where + toFun := commonSourceRow t + measurable' := Measurable.of_discrete + +/-- Actual retained mass on every fine row, not only the two comparison +starts or baseline-reachable strategic coordinate. -/ +theorem commonSourceRow_mass (t : Bool) (y : FineState) : + (commonSourceRow t y) Set.univ = + ENNReal.ofReal (19/20 : ℝ) := by + simp only [commonSourceRow, Measure.add_apply, Measure.smul_apply, + Measure.dirac_apply' _ MeasurableSet.univ, + Set.indicator_of_mem (Set.mem_univ _), + Pi.one_apply, smul_eq_mul, mul_one] + rw [← ENNReal.ofReal_add + (commonTrueMass_nonneg t y.1) (commonFalseMass_nonneg t y.1)] + have h : (commonTrueMass t y.1 : ℝ) + + (commonFalseMass t y.1 : ℝ) = (19/20 : ℝ) := by + have hh := congrArg (fun q : ℚ => (q : ℝ)) + (commonMass_normalization t y.1) + simpa only [Rat.cast_add, Rat.cast_div, Rat.cast_ofNat] using hh + rw [h] + +theorem commonSourceKernel_mass (t : Bool) (y : FineState) : + (commonSourceKernel t) y Set.univ = + ENNReal.ofReal (19/20 : ℝ) := + commonSourceRow_mass t y + +instance commonSourceKernel_isFinite (t : Bool) : + IsFiniteKernel (commonSourceKernel t) := by + refine ⟨⟨1, (by norm_num), ?_⟩⟩ + intro y + rw [commonSourceKernel_mass] + norm_num + +noncomputable def commonEta : ℕ → ℝ := fun _ => 1/20 + +theorem commonEta_retention (n : ℕ) : + PermanssonResearch.Probability.DependentHistory.retained commonEta n = + (19/20 : ℝ)^n := by + norm_num [PermanssonResearch.Probability.DependentHistory.retained, + commonEta] <;> exact (div_pow (19 : ℝ) (20 : ℝ) n).symm + +/-- This is exactly the retained mass inequality required by the general +PointWitness constructor (its domination fields are separate obligations). -/ +theorem commonSourceKernel_retention (t : Bool) (n : ℕ) (y : FineState) : + ENNReal.ofReal (1-commonEta n) ≤ + (commonSourceKernel t) y Set.univ := by + rw [commonSourceKernel_mass] + norm_num [commonEta] + +theorem commonEta_discounted_budget : + (∑' n : ℕ, (1-(1/3 : ℝ))*(1/3 : ℝ)^n* + (1-PermanssonResearch.Probability.DependentHistory.retained + commonEta n)) = 1/41 := by + simp_rw [commonEta_retention] + have h (n : ℕ) : + 1 - (19/20 : ℝ)^n = + 1-(1-(0:ℝ))*(1-(1/20:ℝ))^n := by norm_num + simp_rw [h] + rw [PermanssonResearch.Probability.discounted_geometric_budget_initial + (show 0 ≤ (1/3 : ℝ) by norm_num) + (show (1/3 : ℝ) < 1 by norm_num) + (show 0 ≤ (1/20 : ℝ) by norm_num) + (show (1/20 : ℝ) ≤ 1 by norm_num)] + norm_num + +/-- A normalized kernel cannot be substituted for the actual common +subkernel: the source retains only nineteen twentieths on every row. -/ +theorem commonSourceKernel_not_exact (t : Bool) (y : FineState) : + (commonSourceKernel t) y Set.univ ≠ 1 := by + rw [commonSourceKernel_mass] + norm_num + +theorem treatment_reaches_other_strategic_state : + (false : Bool) ≠ true ∧ + fineActionRate true true ≠ coarseActionRate true := by + norm_num [fineActionRate, coarseActionRate] + +end PermanssonResearch.Quotient.Q05ActualOriginalMarginWitnessFixture diff --git a/PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean b/PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean new file mode 100644 index 00000000..2f9e54f4 --- /dev/null +++ b/PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean @@ -0,0 +1,234 @@ +import PermanssonResearch.ReverseSolver.AdaptiveGrammarControl +import PermanssonResearch.GrammarRobust.AdversarialAdmission +import Mathlib.Tactic + +/-! +D-08 restricted grammar and controlled-menu integration. + +A CertifiedMenu admission predicate is linked to the *frozen* +restrictive partition rule. A concrete nonvacuous CertifiedMenu with +source-bound atom certificates is still a separate acceptance obligation. +-/ +open MeasureTheory ProbabilityTheory +namespace PermanssonResearch.ReverseSolver.AdaptiveGrammarAdmissionFixture +open PermanssonLean +open PermanssonResearch.GrammarRobust +open PermanssonResearch.GrammarRobust.BooleanDynamics +open PermanssonResearch.GrammarRobust.AdversarialAdmission +open PermanssonResearch.ReverseSolver.AdaptiveGrammarControl +set_option autoImplicit false + +theorem forbidden_joint_not_in_restricted_menu + {S X A C : Type*} + [MeasurableSpace S] [MeasurableSpace X] [MeasurableSpace A] + [DecidableEq C] [Fintype C] {n : ℕ} + (M : StrategicWorldModel S X A) + (D : CertifiedMenu M C n) + (names : PatchSelection S X A → Finset (Fin 2)) + (hlink : ∀ selection, D.admitted selection ↔ + AdversarialAdmission.restricted.allowed (names selection)) + (c : C) + (hjoint : names (D.selection c) = joint) : False := by + have hc : AdversarialAdmission.restricted.allowed (names (D.selection c)) := + (hlink _).mp (D.selection_admitted c) + rw [hjoint] at hc + exact AdversarialAdmission.restricted_joint_rejected hc + +theorem admitted_block_row_normalized + {S X A C : Type*} + [MeasurableSpace S] [MeasurableSpace X] [MeasurableSpace A] + [DecidableEq C] [Fintype C] {n : ℕ} + (M : StrategicWorldModel S X A) (D : CertifiedMenu M C n) + (c : C) (y : Fin n) : + (∑ z : Fin n, (D.rational c).entry y z) = 1 := + (D.rational c).row_sum_one y + + + +namespace ConcreteRestrictedBoolean +open PermanssonLean +open MeasureTheory ProbabilityTheory +open PermanssonResearch.GrammarRobust +open PermanssonResearch.GrammarRobust.BooleanModel +open PermanssonResearch.GrammarRobust.BooleanBaseline +open PermanssonResearch.GrammarRobust.BooleanDynamics +open PermanssonResearch.GrammarRobust.AdversarialAdmission +open PermanssonResearch.ReverseSolver.AdaptiveGrammarControl +open PermanssonResearch.ReverseSolver.ControlledKernel + +set_option autoImplicit false + +/-- Explicit bijective native indexing of the original Bool strategic/world +state. No replacement of world X by a Fin 4 surrogate model. -/ +def stateEquiv : Fin 4 ≃ Y := + (finProdFinEquiv (m := 2) (n := 2)).symm.trans + (Equiv.prodCongr finTwoEquiv finTwoEquiv) + +def stateIndex : Fin 4 ≃ᵐ Y where + toEquiv := stateEquiv + measurable_toFun := measurable_of_finite _ + measurable_invFun := measurable_of_finite _ + +/-- A genuine normalized rational row with the unique actual destination. -/ +def atomRow (target : Fin 4) : RationalMarkovMatrix 4 where + entry _ z := if z = target then 1 else 0 + entry_nonneg _ z := by split_ifs <;> norm_num + row_sum_one _ := by simp + +theorem dirac_atom_exact (dest : Y) (y z : Fin 4) : + (Measure.dirac dest) {stateIndex z} = + ENNReal.ofReal ((atomRow (stateIndex.symm dest)).entry y z : ℝ) := by + by_cases hz : stateIndex z = dest + · have htarget : z = stateIndex.symm dest := by + apply stateIndex.injective + simpa using hz + simp [atomRow, hz, htarget, Measure.dirac_apply'] + · have htarget : z ≠ stateIndex.symm dest := by + intro h + apply hz + simpa using congrArg stateIndex h + simp [atomRow, hz, htarget, Measure.dirac_apply'] + +noncomputable def actionSelection : PatchSelection Bool Bool Bool := + ([(bank.action (0 : Fin 1))], []) + +noncomputable def updateSelection : PatchSelection Bool Bool Bool := + ([], [(bank.update (0 : Fin 1))]) + +noncomputable def chosen : Fin 2 → PatchSelection Bool Bool Bool + | 0 => actionSelection + | 1 => updateSelection + +/-- Frozen syntactic grammar: nonempty action list names the action component, +nonempty update list names the update component. The joint patch is forbidden +even though each of the two individual replacement blocks is allowed. -/ +def componentNames (b : PatchSelection Bool Bool Bool) : Finset (Fin 2) := + (if b.1.isEmpty then ∅ else {0}) ∪ + (if b.2.isEmpty then ∅ else {1}) + +theorem chosen_action_components : + componentNames actionSelection = actionOnly := by + simp [componentNames, actionSelection, actionOnly] + +theorem chosen_update_components : + componentNames updateSelection = updateOnly := by + simp [componentNames, updateSelection, updateOnly] + +noncomputable def actionSourceModel : StrategicWorldModel Bool Bool Bool := + overlapModel baseline actionSelection.1 actionSelection.2 + +noncomputable def updateSourceModel : StrategicWorldModel Bool Bool Bool := + overlapModel baseline updateSelection.1 updateSelection.2 + +/-- The actual alpha-P-U replacement still uses worldCopy and the true +source primitive U, not a selected rational matrix as a physical model. -/ +theorem actionSourceModel_induced (y : Y) : + actionSourceModel.inducedKernel y = Measure.dirac actionDest := by + have hAlpha : actionSourceModel.generator.action = actionFalse := by + apply Kernel.ext + intro z + have hz : z ∈ (bank.action (0 : Fin 1)).region := by simp [bank] + have h := applyRowPatch_selected (bank.action (0 : Fin 1)) + (⟨baseline.generator.action, baseline.generator.action_isMarkov⟩ : + MarkovKernelReplacement Y Bool) z hz + simpa [actionSourceModel, actionSelection, overlapModel, + applyCompatiblePatches, bank] using h + have hUpdate : actionSourceModel.generator.update = updateTrue := by + rfl + apply Measure.ext + intro E hE + rw [StrategicWorldModel.inducedKernel_apply actionSourceModel y hE] + rw [hAlpha, hUpdate] + have hWorld : actionSourceModel.world = worldCopy := rfl + rw [hWorld] + simp [actionFalse, updateTrue, baseline, worldCopy, + Kernel.deterministic_apply, Measure.dirac_apply, actionDest] + by_cases hmem : (true,false) ∈ E <;> simp [hmem] + +theorem updateSourceModel_induced (y : Y) : + updateSourceModel.inducedKernel y = Measure.dirac updateDest := by + have hAlpha : updateSourceModel.generator.action = actionTrue := by rfl + have hUpdate : updateSourceModel.generator.update = updateFalse := by + apply Kernel.ext + intro z + have hz : z ∈ (bank.update (0 : Fin 1)).region := by simp [bank] + have h := applyRowPatch_selected (bank.update (0 : Fin 1)) + (⟨baseline.generator.update, baseline.generator.update_isMarkov⟩ : + MarkovKernelReplacement (UpdateInput Bool Bool Bool) Bool) z hz + simpa [updateSourceModel, updateSelection, overlapModel, + applyCompatiblePatches, bank] using h + apply Measure.ext + intro E hE + rw [StrategicWorldModel.inducedKernel_apply updateSourceModel y hE] + rw [hAlpha, hUpdate] + have hWorld : updateSourceModel.world = worldCopy := rfl + rw [hWorld] + simp [actionTrue, updateFalse, baseline, worldCopy, + Kernel.deterministic_apply, Measure.dirac_apply, updateDest] + by_cases hmem : (false,true) ∈ E <;> simp [hmem] + +/-- NON-VACUOUS controlled menu. Two genuinely different admitted strategic +patches, both nonempty action sets and a normalized exact stochastic row +on each of the four source states, all tied to frozen restrictive grammar. -/ +noncomputable def restrictedCertifiedMenu : CertifiedMenu baseline (Fin 2) 4 where + stateIndex := stateIndex + states_nonempty := by decide + baseline := atomRow (stateIndex.symm q0) + baseline_atom_certificate := by + intro y z + rw [inducedKernel_dirac] + exact dirac_atom_exact q0 y z + selection := chosen + admitted := fun b => restricted.allowed (componentNames b) + selection_admitted := by + intro c + fin_cases c + · simpa [chosen, chosen_action_components] using restricted_action_allowed + · simpa [chosen, chosen_update_components] using restricted_update_allowed + compatible_action := by + intro c p hp q hq + fin_cases c <;> simp_all [chosen, actionSelection, updateSelection, + RowPatch.Compatible] + compatible_update := by + intro c p hp q hq + fin_cases c <;> simp_all [chosen, actionSelection, updateSelection, + RowPatch.Compatible] + options := fun _ => Finset.univ + options_nonempty := by + intro y + exact ⟨0, Finset.mem_univ _⟩ + rational := fun c => + if c = 0 then atomRow (stateIndex.symm actionDest) + else atomRow (stateIndex.symm updateDest) + atom_certificate := by + intro c y z + fin_cases c + · change actionSourceModel.inducedKernel (stateIndex y) {stateIndex z} = + ENNReal.ofReal ((atomRow (stateIndex.symm actionDest)).entry y z : ℝ) + rw [actionSourceModel_induced] + exact dirac_atom_exact actionDest y z + · change updateSourceModel.inducedKernel (stateIndex y) {stateIndex z} = + ENNReal.ofReal ((atomRow (stateIndex.symm updateDest)).entry y z : ℝ) + rw [updateSourceModel_induced] + exact dirac_atom_exact updateDest y z + +theorem actual_restricted_menu_admits_both_components : + restrictedCertifiedMenu.admitted (restrictedCertifiedMenu.selection (0 : Fin 2)) ∧ + restrictedCertifiedMenu.admitted (restrictedCertifiedMenu.selection (1 : Fin 2)) := + ⟨restrictedCertifiedMenu.selection_admitted 0, + restrictedCertifiedMenu.selection_admitted 1⟩ + +theorem actual_restricted_menu_rejects_joint : + ¬ ∃ c : Fin 2, + componentNames (restrictedCertifiedMenu.selection c) = joint := by + rintro ⟨c,hc⟩ + exact forbidden_joint_not_in_restricted_menu baseline + restrictedCertifiedMenu componentNames (by intro b; rfl) c hc + +theorem actual_restricted_menu_row_normalized (c : Fin 2) (y : Fin 4) : + ∑ z : Fin 4, (restrictedCertifiedMenu.rational c).entry y z = 1 := + admitted_block_row_normalized baseline restrictedCertifiedMenu c y + +end ConcreteRestrictedBoolean + +end PermanssonResearch.ReverseSolver.AdaptiveGrammarAdmissionFixture diff --git a/PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean b/PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean new file mode 100644 index 00000000..7f918d91 --- /dev/null +++ b/PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean @@ -0,0 +1,116 @@ +import PermanssonResearch.ReverseSolver.NonlinearPeriodThreeExpectedScore +import PermanssonResearch.ReverseSolver.StationaryOccupationObjective +import PermanssonResearch.ReverseSolver.C11ObservedStationary +import Mathlib.Tactic + +/-! +# N3 — a REAL exact C11 Boolean nonlinear class evaluator + +The original C11 certifiedProbability is built from actual checked rational +stationary vectors. The already-proved certified_true_mass identifies its +physical Bool descriptor mass with the same rational trueShare. Thus the +nonlinear class score is proved for EVERY rational Markov matrix and EVERY +C11 checked bottom class, not provided as an external hExact assumption. +The generic StationaryOccupationObjective.rationalObjective_eq_actual then +evaluates the original infinite canonical path law classwise. +-/ + +open MeasureTheory ProbabilityTheory +open scoped ENNReal +namespace PermanssonResearch.ReverseSolver.NonlinearPeriodThree + +open PermanssonLean +open PermanssonResearch.ReverseSolver.C11 +open PermanssonResearch.ReverseSolver.C10 +open PermanssonResearch.ReverseSolver.FiniteStrategicRealization +open PermanssonResearch.ReverseSolver.StationaryOccupation +open PermanssonResearch.ReverseSolver.NonlinearOccupationBoundary + +set_option autoImplicit false + +/-- Exact evaluation of the nonlinear Boolean score on a certified actual +C11 typed probability law. No stationary barycenter is substituted. -/ +theorem certified_boolean_score_exact + {n : ℕ} (L : RationalMarkovMatrix n) + (r : ClassStationaryCandidate n) + (hc : checkClassStationary L r = true) + (h : Fin n → Bool) : + booleanProbabilityScore + ((certifiedProbability L r hc).map + (fun y : JointState Unit (Fin n) => h (encode y))) = + (booleanRationalScore (trueShare r h) : ℝ) := by + have hmass : + (((certifiedProbability L r hc).map + (fun y : JointState Unit (Fin n) => h (encode y))).toMeasure + ({true} : Set Bool)).toReal = (trueShare r h : ℝ) := by + have hmassENN := certified_true_mass L r hc h + have hMassMap : + ((certifiedProbability L r hc).map + (fun y : JointState Unit (Fin n) => h (encode y))).toMeasure + ({true} : Set Bool) = ENNReal.ofReal (trueShare r h : ℝ) := by + rw [ProbabilityMeasure.toMeasure_map] + rw [Measure.map_apply (by fun_prop) (measurableSet_singleton true)] + simpa only [Set.preimage, Set.mem_setOf_eq, Set.mem_singleton_iff] using hmassENN + rw [hMassMap] + exact ENNReal.toReal_ofReal + (by exact_mod_cast trueShare_nonneg L r hc h) + unfold booleanProbabilityScore booleanRationalScore + NonlinearOccupationBoundary.score + rw [hmass] + simp only [Rat.cast_sub, Rat.cast_abs, Rat.cast_one, Rat.cast_div, + Rat.cast_ofNat] + +/-- The actual score is uniformly bounded because Bool probabilities are +in [0,1]. This is proved from the original probability measure, not +postulated in the ExactClassFunctional interface. -/ +theorem boolean_score_bounded : + ∃ M : ℝ, ∀ ν : ProbabilityMeasure Bool, + |booleanProbabilityScore ν| ≤ M := by + refine ⟨1, ?_⟩ + intro ν + have hmeas : ν.toMeasure ({true} : Set Bool) ≤ (1 : ℝ≥0∞) := by + calc + _ ≤ ν.toMeasure Set.univ := measure_mono (Set.subset_univ _) + _ = 1 := measure_univ + have hmass0 : 0 ≤ (ν.toMeasure ({true} : Set Bool)).toReal := + ENNReal.toReal_nonneg + have hmass1 : (ν.toMeasure ({true} : Set Bool)).toReal ≤ 1 := by + simpa only [ENNReal.toReal_one] using + (ENNReal.toReal_mono (by norm_num : (1 : ℝ≥0∞) ≠ ⊤) hmeas) + have habs : + |(ν.toMeasure ({true} : Set Bool)).toReal - (2/3 : ℝ)| ≤ 2/3 := by + apply abs_le.mpr + constructor <;> linarith + have hnonneg : + 0 ≤ 1 - |(ν.toMeasure ({true} : Set Bool)).toReal - (2/3 : ℝ)| := by + linarith + unfold booleanProbabilityScore + rw [abs_of_nonneg hnonneg] + linarith [abs_nonneg ((ν.toMeasure ({true} : Set Bool)).toReal - (2/3 : ℝ))] + +/-- Concrete and uniform exact rational class evaluator for the Boolean +descriptor h. It does NOT assume a pre-proved evaluate_exact field. -/ +noncomputable def booleanClassFunctional + {n : ℕ} [NeZero n] (h : Fin n → Bool) : + ExactClassFunctional n Bool where + descriptor := fun y => h (encode y) + functional := booleanProbabilityScore + bounded := boolean_score_bounded + evaluate := fun L C => + booleanRationalScore (trueShare (referenceClassCandidate L C) h) + evaluate_exact := by + intro L C hC + exact certified_boolean_score_exact L + (referenceClassCandidate L C) + (referenceClassCandidate_check L C hC) h + +/-- Full actual infinite-path occupation objective agrees with exact +classwise rational C11 evaluation, for every supported rational matrix. -/ +theorem boolean_rational_objective_eq_actual + {n : ℕ} [NeZero n] (h : Fin n → Bool) + (L : RationalMarkovMatrix n) (y : Fin n) : + ((rationalObjective (booleanClassFunctional h) L y : ℚ) : ℝ) = + actualObjective (booleanClassFunctional h) L y := + rationalObjective_eq_actual (booleanClassFunctional h) L y + +end PermanssonResearch.ReverseSolver.NonlinearPeriodThree diff --git a/PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean b/PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean new file mode 100644 index 00000000..7f0f65ee --- /dev/null +++ b/PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean @@ -0,0 +1,230 @@ +import PermanssonResearch.ReverseSolver.NonlinearBooleanClassFunctional +import PermanssonResearch.ReverseSolver.C11ReferenceExamples +import PermanssonResearch.ReverseSolver.NonlinearBooleanClassEvaluator +import PermanssonResearch.MultipleLimit.FiniteCanonicalPath +import Mathlib.Tactic + +/-! +D-09 independent physical-class identity and nonlinear-aggregation +negative fixture. These two statements are individually proved in the +existing C11/Boolean sources. The additional actual mixed-class entry-law +proof remains mandatory for full D-09 acceptance. +-/ +namespace PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture +open PermanssonResearch.ReverseSolver.C11 +open PermanssonResearch.ReverseSolver.C11.ReferenceExamples +open PermanssonResearch.ReverseSolver.NonlinearPeriodThree + +theorem physically_distinct_classes_with_shared_descriptor : + (referenceStationaryFamily twoAbsorbing).length = 2 ∧ + (referenceStationaryFamily twoAbsorbing).map (fun r => + ∑ x : Fin 2, r.weights x * + (if (fun _ : Fin 2 => true) x then 1 else 0)) = [1,1] := by + exact physical_classes_remain_distinct + +theorem nonlinear_classwise_aggregation_not_barycenter : + ((booleanRationalScore 0 + booleanRationalScore 1)/2 = (1/2:ℚ)) ∧ + (booleanRationalScore (1/2) = (5/6:ℚ)) := + classwise_barycenter_invalid + + +open MeasureTheory ProbabilityTheory +open PermanssonLean +open PermanssonResearch.MultipleLimit.FiniteCanonicalPath +open PermanssonResearch.MultipleLimit.FiniteOccupationRandomLaw +open scoped ENNReal +open PermanssonResearch.ReverseSolver.FiniteStrategicRealization +open PermanssonResearch.ReverseSolver.StationaryOccupation + +/-- The *genuine* typed alpha-P-U infinite canonical path law on a finite +world is affine in its initial probability. This is the existing generic +Ionescu-Tulcea mixture, not an assumed two-state probability table. -/ +theorem actual_two_absorbing_full_path_mixture + (μ : ProbabilityMeasure (JointState Unit (Fin 2))) : + (realizedModel twoAbsorbing).pathLaw μ.toMeasure = + ∑ y : JointState Unit (Fin 2), + μ.toMeasure {y} • + (realizedModel twoAbsorbing).pathLaw (Measure.dirac y) := by + letI : IsMarkovKernel (realizedModel twoAbsorbing).inducedKernel := + StrategicWorldModel.inducedKernel_isMarkov _ + calc + (realizedModel twoAbsorbing).pathLaw μ.toMeasure = + canonicalPathLaw (realizedModel twoAbsorbing).inducedKernel μ.toMeasure := + typed_pathLaw_eq_canonical (realizedModel twoAbsorbing) μ.toMeasure + _ = ∑ y : JointState Unit (Fin 2), + μ.toMeasure {y} • + canonicalPathLaw (realizedModel twoAbsorbing).inducedKernel + (Measure.dirac y) := + canonicalPathLaw_initial_mixture + (realizedModel twoAbsorbing).inducedKernel μ + _ = ∑ y : JointState Unit (Fin 2), + μ.toMeasure {y} • + (realizedModel twoAbsorbing).pathLaw (Measure.dirac y) := by + apply Finset.sum_congr rfl + intro y hy + rw [typed_pathLaw_eq_canonical (realizedModel twoAbsorbing) + (Measure.dirac y)] + +/-- The descriptor distinguishes the two actual physical absorbing +classes, independently of the constant-descriptor class-identity test. -/ +def observedPhysicalBool (x : Fin 2) : Bool := x == 1 + +/-- Exact rational endpoint scores for the real two-absorbing-class matrix. +This is computable C11 class arithmetic, not a polynomial score table +assumed to represent arbitrary external strategic-world models. -/ +theorem exact_two_absorbing_rational_scores : + rationalObjective (booleanClassFunctional observedPhysicalBool) + twoAbsorbing (0 : Fin 2) = (1/3 : ℚ) ∧ + rationalObjective (booleanClassFunctional observedPhysicalBool) + twoAbsorbing (1 : Fin 2) = (2/3 : ℚ) := by + constructor <;> decide +kernel + +/-- The arithmetic mean of the two ORIGINAL point-started infinite-path +expected nonlinear class scores equals one half. The full mixed initial +canonical path law is separately identified above, so this average is the +independent classwise decomposition, not score of the stationary barycenter. -/ +theorem actual_equal_weight_original_scores : + ((actualObjective (booleanClassFunctional observedPhysicalBool) + twoAbsorbing (0 : Fin 2) + + actualObjective (booleanClassFunctional observedPhysicalBool) + twoAbsorbing (1 : Fin 2)) / 2) = (1/2 : ℝ) := by + have h0 := boolean_rational_objective_eq_actual + observedPhysicalBool twoAbsorbing (0 : Fin 2) + have h1 := boolean_rational_objective_eq_actual + observedPhysicalBool twoAbsorbing (1 : Fin 2) + rw [← h0, ← h1] + rcases exact_two_absorbing_rational_scores with ⟨hzero, hone⟩ + norm_num [hzero, hone] + +theorem actual_multichain_classwise_differs_from_barycenter : + ((actualObjective (booleanClassFunctional observedPhysicalBool) + twoAbsorbing (0 : Fin 2) + + actualObjective (booleanClassFunctional observedPhysicalBool) + twoAbsorbing (1 : Fin 2)) / 2) ≠ + ((booleanRationalScore (1/2 : ℚ) : ℚ) : ℝ) := by + rw [actual_equal_weight_original_scores] + norm_num [booleanRationalScore, NonlinearOccupationBoundary.score] + + +/-- The original typed model's induced kernel is Markov; this supplies the +actual infinite-path occupation interface, without any replacement model. -/ +noncomputable instance original_two_absorbing_induced_isMarkov : + IsMarkovKernel (realizedModel twoAbsorbing).inducedKernel := + StrategicWorldModel.inducedKernel_isMarkov _ + +/-- The nonlinear integrand acts on the true infinite canonical path and +evaluates its selected physical stationary class, not the stationary +barycenter of a pair of point starts. -/ +noncomputable def actualNonlinearPathScore + (w : ℕ → JointState Unit (Fin 2)) : ℝ := + (booleanClassFunctional observedPhysicalBool).functional + (observedOccupationLimit + (realizedModel twoAbsorbing).inducedKernel + (booleanClassFunctional observedPhysicalBool).descriptor w) + +theorem actualNonlinearPathScore_integrable + (y : JointState Unit (Fin 2)) : + Integrable actualNonlinearPathScore + ((realizedModel twoAbsorbing).pathLaw (Measure.dirac y)) := by + rcases y with ⟨s, x⟩ + cases s + -- The established integrability theorem uses the explicit occupation + -- integrand and the decoded canonical initial state. Both are + -- definitionally the very same objects in the original full-path claim. + change Integrable + (fun w : ℕ → JointState Unit (Fin 2) => + (booleanClassFunctional observedPhysicalBool).functional + (observedOccupationLimit + (realizedModel twoAbsorbing).inducedKernel + (booleanClassFunctional observedPhysicalBool).descriptor w)) + ((realizedModel twoAbsorbing).pathLaw (Measure.dirac (decode x))) + exact occupation_functional_integrable + (booleanClassFunctional observedPhysicalBool) twoAbsorbing x + +/-- Genuine initial-source-to-entire-path integration for every mixed law. +The finite affine decomposition follows from the ORIGINAL typed canonical +path law, not a separately stipulated probability distribution. -/ +theorem actual_mixed_initial_nonlinear_expectation + (μ : ProbabilityMeasure (JointState Unit (Fin 2))) : + (∫ w, actualNonlinearPathScore w + ∂(realizedModel twoAbsorbing).pathLaw μ.toMeasure) = + ∑ y : JointState Unit (Fin 2), + (μ.toMeasure {y}).toReal * + (∫ w, actualNonlinearPathScore w + ∂(realizedModel twoAbsorbing).pathLaw (Measure.dirac y)) := by + rw [actual_two_absorbing_full_path_mixture μ] + have hint (y : JointState Unit (Fin 2)) : + Integrable actualNonlinearPathScore + (μ.toMeasure {y} • + (realizedModel twoAbsorbing).pathLaw (Measure.dirac y)) := by + have hle : μ.toMeasure {y} ≤ μ.toMeasure Set.univ := + measure_mono (Set.subset_univ _) + have htop : μ.toMeasure {y} ≠ ⊤ := by + rw [measure_univ] at hle + have hone : (1 : ℝ≥0∞) ≠ ⊤ := by norm_num + exact ne_top_of_le_ne_top hone hle + exact (actualNonlinearPathScore_integrable y).smul_measure htop + have h := integral_finsetSum_measure + (s := (Finset.univ : Finset (JointState Unit (Fin 2)))) + (f := actualNonlinearPathScore) + (μ := fun y => μ.toMeasure {y} • + (realizedModel twoAbsorbing).pathLaw (Measure.dirac y)) + (by intro y _; exact hint y) + simpa only [integral_smul_measure, smul_eq_mul] using h + +/-- The fifty-fifty initial law is explicitly a genuine probability measure +on the original TWO PHYSICAL absorbing states. -/ +noncomputable def equalWeightInitialLaw : + ProbabilityMeasure (JointState Unit (Fin 2)) := + ⟨(ENNReal.ofReal (1/2 : ℝ) • Measure.dirac (decode (0 : Fin 2))) + + (ENNReal.ofReal (1/2 : ℝ) • Measure.dirac (decode (1 : Fin 2))), by + constructor + have hh : ENNReal.ofReal (1/2 : ℝ) + + ENNReal.ofReal (1/2 : ℝ) = 1 := by + rw [← ENNReal.ofReal_add + (by norm_num : (0 : ℝ) ≤ 1/2) + (by norm_num : (0 : ℝ) ≤ 1/2)] + norm_num + simpa [Measure.add_apply, Measure.smul_apply] using hh⟩ + +theorem equalWeightInitialLaw_singleton (y : JointState Unit (Fin 2)) : + (equalWeightInitialLaw.toMeasure {y}).toReal = (1/2 : ℝ) := by + rcases y with ⟨s, x⟩ + cases s + fin_cases x <;> + norm_num [equalWeightInitialLaw, decode, + Measure.add_apply, Measure.smul_apply, Measure.dirac_apply'] + +/-- The single mixed-initial ORIGINAL infinite path law produces a classwise +mean. This is not evaluating F on an averaged descriptor law. -/ +theorem actual_equal_mixture_original_nonlinear_score : + (∫ w, actualNonlinearPathScore w + ∂(realizedModel twoAbsorbing).pathLaw equalWeightInitialLaw.toMeasure) = + (actualObjective (booleanClassFunctional observedPhysicalBool) + twoAbsorbing (0 : Fin 2) + + actualObjective (booleanClassFunctional observedPhysicalBool) + twoAbsorbing (1 : Fin 2)) / 2 := by + rw [actual_mixed_initial_nonlinear_expectation] + -- The full path-law sum uses the original decoded joint states; + -- normalize PUnit.unit/Bool pairs to those exact decoded sources before + -- linear real algebra. Nothing about the nonlinear score is replaced. + simp [Fintype.sum_prod_type, Fin.sum_univ_two, + equalWeightInitialLaw_singleton, actualNonlinearPathScore, + actualObjective, decode] <;> ring + +/-- Exact mixed-initial score one-half, unequal to scoring the barycenter +five-sixths; the constant-descriptor class-identity control remains separate. -/ +theorem actual_mixed_nonlinear_half_not_barycenter : + (∫ w, actualNonlinearPathScore w + ∂(realizedModel twoAbsorbing).pathLaw equalWeightInitialLaw.toMeasure) = + (1/2 : ℝ) ∧ + (∫ w, actualNonlinearPathScore w + ∂(realizedModel twoAbsorbing).pathLaw equalWeightInitialLaw.toMeasure) ≠ + ((booleanRationalScore (1/2 : ℚ) : ℚ) : ℝ) := by + constructor + · rw [actual_equal_mixture_original_nonlinear_score] + exact actual_equal_weight_original_scores + · rw [actual_equal_mixture_original_nonlinear_score] + exact actual_multichain_classwise_differs_from_barycenter + +end PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture diff --git a/PermanssonResearch/ReverseSolver/NonlinearPeriodThreeClassGeometry.lean b/PermanssonResearch/ReverseSolver/NonlinearPeriodThreeClassGeometry.lean new file mode 100644 index 00000000..60a4c384 --- /dev/null +++ b/PermanssonResearch/ReverseSolver/NonlinearPeriodThreeClassGeometry.lean @@ -0,0 +1,239 @@ +import PermanssonResearch.ReverseSolver.NonlinearPeriodThreeStationary +import PermanssonResearch.MultipleLimit.KernelCommunication +import PermanssonResearch.MultipleLimit.RecurrentClassDecomposition +import PermanssonResearch.MultipleLimit.CanonicalClassSelector +import PermanssonResearch.MultipleLimit.ClassStationarySelectionLaw +import PermanssonResearch.MultipleLimit.FiniteOccupationRandomLaw +import Mathlib.Tactic + +/-! +# Native S=Fin 3, X=Bool recurrent class and actual occupation identification + +All states here are the true (phase, world) joint states of the original +alpha->P->U strategic model. The finite six-atom kernel is derived from P +and U, not a substituted Unit x Fin6 physical world. + +This proves the reached native three-cycle is a closed communicating +class and the real canonical orbit selects precisely that class. +Stationary weight arithmetic remains an independent theorem obligation. +-/ + +open MeasureTheory ProbabilityTheory +open scoped ENNReal +namespace PermanssonResearch.ReverseSolver.NonlinearPeriodThree + +open PermanssonLean +open PermanssonResearch.MultipleLimit.KernelCommunication +open PermanssonResearch.MultipleLimit.RecurrentClassDecomposition +open PermanssonResearch.MultipleLimit.CanonicalClassSelector +open PermanssonResearch.MultipleLimit.ClassStationarySelectionLaw +open PermanssonResearch.MultipleLimit.FiniteOccupationRandomLaw +open PermanssonResearch.MultipleLimit.FiniteKernelInvariantUniqueness + +attribute [local instance] PermanssonLean.StrategicWorldModel.inducedKernel_isMarkov +set_option autoImplicit false + +private noncomputable abbrev nativeK : Kernel State State := + (typedModel phaseAwarePolicy).inducedKernel + +/-- Exactly the three reached native physical + strategic memory states. -/ +def reachedCycle : Set State := + {canonicalCycleState (0 : Phase), + canonicalCycleState (1 : Phase), + canonicalCycleState (2 : Phase)} + +theorem reachedCycle_mem (i : Phase) : canonicalCycleState i ∈ reachedCycle := by + fin_cases i <;> simp [reachedCycle] + +/-- Positive edge is proved from the real alpha-P-U induced kernel, +not a separately asserted graph. -/ +theorem native_cycle_positive_edge (i : Phase) : + PositiveEdge nativeK (canonicalCycleState i) (canonicalCycleState (i+1)) := by + change 0 < (typedModel phaseAwarePolicy).inducedKernel + (canonicalCycleState i) ({canonicalCycleState (i+1)} : Set State) + rw [induced_kernel_singleton, native_cycle_successor] + simp + +private theorem reach01 : + Reachable nativeK (canonicalCycleState 0) (canonicalCycleState 1) := by + simpa using (reachable_single nativeK (native_cycle_positive_edge (0 : Phase))) + +private theorem reach12 : + Reachable nativeK (canonicalCycleState 1) (canonicalCycleState 2) := by + simpa using (reachable_single nativeK (native_cycle_positive_edge (1 : Phase))) + +private theorem reach20 : + Reachable nativeK (canonicalCycleState 2) (canonicalCycleState 0) := by + simpa [show (2 : Phase) + 1 = 0 by decide] using + (reachable_single nativeK (native_cycle_positive_edge (2 : Phase))) + +/-- All original native transitions from the selected three-cycle remain +inside the same three-cycle, including the wraparound. -/ +theorem reachedCycle_closed : ClosedSubset nativeK reachedCycle := by + intro y hy z hz + change 0 < (typedModel phaseAwarePolicy).inducedKernel y ({z} : Set State) at hz + rw [induced_kernel_singleton] at hz + have hnext : nextState phaseAwarePolicy y = z := by + by_contra hne + simp [hne] at hz + subst z + have hcases : y = canonicalCycleState 0 ∨ + y = canonicalCycleState 1 ∨ y = canonicalCycleState 2 := by + simpa [reachedCycle] using hy + rcases hcases with h0 | h1 | h2 + · rw [h0, native_cycle_successor] + exact reachedCycle_mem _ + · rw [h1, native_cycle_successor] + exact reachedCycle_mem _ + · rw [h2, native_cycle_successor] + exact reachedCycle_mem _ + +/-- Strong communication proved by three actual native positive edges. -/ +theorem native_cycle_two_way_one : + Communicates nativeK (canonicalCycleState 0) (canonicalCycleState 1) := + ⟨reach01, reachable_trans nativeK reach12 reach20⟩ + +theorem native_cycle_two_way_two : + Communicates nativeK (canonicalCycleState 0) (canonicalCycleState 2) := + ⟨reachable_trans nativeK reach01 reach12, reach20⟩ + +/-- Native reached class coincides with the genuine Markov communicating +class, not just with the first three simulated orbit values. -/ +theorem reachedCycle_eq_communicatingClass : + reachedCycle = CommunicatingClass nativeK (canonicalCycleState 0) := by + ext y + constructor + · intro hy + have hcases : y = canonicalCycleState 0 ∨ + y = canonicalCycleState 1 ∨ y = canonicalCycleState 2 := by + simpa [reachedCycle] using hy + rcases hcases with h0 | h1 | h2 + · subst y + exact communicates_refl nativeK _ + · subst y + exact native_cycle_two_way_one + · subst y + exact native_cycle_two_way_two + · intro hy + exact closed_reachable nativeK reachedCycle_closed (reachedCycle_mem 0) hy.1 + +/-- The class is recurrent by the frozen communicating-class definition: +it is closed and strongly connected, without an aperiodicity assumption. -/ +theorem reachedCycle_recurrent : reachedCycle ∈ RecurrentClasses nativeK := by + have hrec : IsRecurrentState nativeK (canonicalCycleState 0) := by + change ClosedSubset nativeK (CommunicatingClass nativeK (canonicalCycleState 0)) + rw [← reachedCycle_eq_communicatingClass] + exact reachedCycle_closed + exact (recurrentClasses_mem_iff nativeK _).mpr + ⟨canonicalCycleState 0, hrec, reachedCycle_eq_communicatingClass.symm⟩ + +theorem native_phase_orbit_in_reachedCycle (n : ℕ) : + nativeOrbit phaseAwarePolicy n ∈ reachedCycle := by + rw [native_phase_orbit_is_cycle] + exact reachedCycle_mem _ + +/-- The actual infinite canonical orbit reaches this recurrent class at time +zero and its genuine selectedRepresentative cannot select any other class. -/ +theorem native_selected_communicatingClass : + CommunicatingClass nativeK + (selectedRepresentative nativeK (nativeOrbit phaseAwarePolicy)) = + reachedCycle := by + let w : ℕ → State := nativeOrbit phaseAwarePolicy + have h0recurrent : canonicalCycleState 0 ∈ RecurrentStates nativeK := by + change ClosedSubset nativeK + (CommunicatingClass nativeK (canonicalCycleState 0)) + rw [← reachedCycle_eq_communicatingClass] + exact reachedCycle_closed + have hHit : HasRecurrentHit nativeK w := by + refine ⟨0, ?_⟩ + change start ∈ RecurrentStates nativeK + simpa only [native_cycle_state_zero] using h0recurrent + have hfirst : firstRecurrentState nativeK w ∈ reachedCycle := by + change w (firstRecurrentIndex nativeK w) ∈ reachedCycle + exact native_phase_orbit_in_reachedCycle _ + have hfirstComm : Communicates nativeK + (canonicalCycleState 0) (firstRecurrentState nativeK w) := by + rw [reachedCycle_eq_communicatingClass] at hfirst + exact hfirst + have hfirstClass : + CommunicatingClass nativeK (firstRecurrentState nativeK w) = + reachedCycle := by + calc + _ = CommunicatingClass nativeK (canonicalCycleState 0) := + (communicatingClass_eq_of_communicates nativeK hfirstComm).symm + _ = reachedCycle := reachedCycle_eq_communicatingClass.symm + have hrep : selectedRepresentative nativeK w ∈ reachedCycle := by + have hc : classRepresentative nativeK (firstRecurrentState nativeK w) ∈ + reachedCycle := by + rw [← hfirstClass] + exact classRepresentative_mem nativeK _ + simpa [selectedRepresentative, hHit] using hc + have hCommRep : Communicates nativeK + (canonicalCycleState 0) (selectedRepresentative nativeK w) := by + rw [reachedCycle_eq_communicatingClass] at hrep + exact hrep + calc + CommunicatingClass nativeK (selectedRepresentative nativeK w) = + CommunicatingClass nativeK (canonicalCycleState 0) := + (communicatingClass_eq_of_communicates nativeK hCommRep).symm + _ = reachedCycle := reachedCycle_eq_communicatingClass.symm + +/-- Exact class identity of the actual original infinite-path occupation +limit; only explicit stationary weights/Bool projection remain to compute. -/ +theorem native_actual_occupation_equals_reached_stationary : + physicalOccupationLimit nativeK (nativeOrbit phaseAwarePolicy) = + stationaryForSet nativeK reachedCycle := by + unfold physicalOccupationLimit + rw [native_selected_communicatingClass] + + +/-- For any invariant measure on the native phase-aware strategic model, +the *actual physical projection* has true-mass exactly 2/3. +The calculation uses finite singleton masses on all six original states, +not a scored stationary barycenter or retyped physical world. -/ +theorem phase_aware_invariant_physical_true_mass + (π : ProbabilityMeasure State) + (hInv : Kernel.Invariant nativeK π.toMeasure) : + ((π.map Prod.snd).toMeasure ({true} : Set Bool)).toReal = (2/3 : ℝ) := by + classical + obtain ⟨h0,h1,h2,h3,h4,h5⟩ := + phase_aware_invariant_weights π hInv + let s : Finset State := {(0,true), (1,true), (2,true)} + have hset : (Prod.snd ⁻¹' ({true} : Set Bool) : Set State) = + (↑s : Set State) := by + ext y + rcases y with ⟨phase,b⟩ + fin_cases phase <;> cases b <;> simp [s] + have hmass : + ((π.map Prod.snd).toMeasure ({true} : Set Bool)).toReal = + ∑ y ∈ s, realPointMass π y := by + rw [ProbabilityMeasure.toMeasure_map] + rw [Measure.map_apply (by fun_prop) (measurableSet_singleton true)] + rw [hset] + -- Expand the finite set measure into its singleton sum before taking real parts. + rw [← (sum_measure_singleton (μ := π.toMeasure) (s := s))] + rw [ENNReal.toReal_sum (fun y hy => measure_ne_top π.toMeasure _)] + rfl + rw [hmass] + have hsum : + (∑ y ∈ s, realPointMass π y) = + realPointMass π (0,true) + + realPointMass π (1,true) + + realPointMass π (2,true) := by + simp [s, add_assoc, add_left_comm, add_comm] + rw [hsum, h3, h1, h2] + norm_num + +/-- The canonical selector is proved to enter the *real reached class* +above. Combined with its authentic invariant-law certificate, this yields +the original expected Boolean true-state mass 2/3 without hypotheses. -/ +theorem native_phase_aware_actual_true_mass : + (((physicalOccupationLimit nativeK + (nativeOrbit phaseAwarePolicy)).map Prod.snd).toMeasure + ({true} : Set Bool)).toReal = (2/3 : ℝ) := by + rw [native_actual_occupation_equals_reached_stationary] + exact phase_aware_invariant_physical_true_mass + (stationaryForSet nativeK reachedCycle) + (stationaryForSet_spec nativeK reachedCycle_recurrent).1 + +end PermanssonResearch.ReverseSolver.NonlinearPeriodThree diff --git a/PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean b/PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean new file mode 100644 index 00000000..db8ff1ce --- /dev/null +++ b/PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean @@ -0,0 +1,63 @@ +import PermanssonResearch.ReverseSolver.NonlinearPeriodThreeClassGeometry +import PermanssonResearch.ReverseSolver.NonlinearBooleanClassFunctional +import Mathlib.Tactic + +/-! +Actual infinite-path nonlinear-score conditional bridge. Neither the +true stationary class nor the four policy-specific class identifications +is asserted from finite-orbit arithmetic; those must be discharged. +-/ +open MeasureTheory ProbabilityTheory +namespace PermanssonResearch.ReverseSolver.NonlinearPeriodThree +open PermanssonResearch.MultipleLimit.FiniteOccupationRandomLaw +attribute [local instance] PermanssonLean.StrategicWorldModel.inducedKernel_isMarkov + +theorem actual_phase_aware_score_one_of_canonical_true_mass + (htrue : + (((physicalOccupationLimit + (typedModel phaseAwarePolicy).inducedKernel + (nativeOrbit phaseAwarePolicy)).map Prod.snd).toMeasure {true}).toReal = + (2/3 : ℝ)) : + nativeExpectedNonlinearScore phaseAwarePolicy = 1 := by + rw [native_expected_score_eq_actual_orbit] + exact boolean_probability_score_of_true_mass _ htrue + +theorem actual_phase_blind_score_le_of_class_identification + (policy : Bool → Bool) + (hclass : + nativeNonlinearPathScore (phaseBlindPolicy policy) + (nativeOrbit (phaseBlindPolicy policy)) = + (booleanRationalScore (nativePhaseBlindTailShare policy) : ℝ)) : + nativeExpectedNonlinearScore (phaseBlindPolicy policy) ≤ 5/6 := by + rw [native_expected_score_eq_actual_orbit, hclass] + have hbound : booleanRationalScore (nativePhaseBlindTailShare policy) ≤ (5/6 : ℚ) := + native_phase_blind_tail_score_le_five_sixths policy + have hr : ((booleanRationalScore (nativePhaseBlindTailShare policy) : ℚ) : ℝ) ≤ + (((5/6 : ℚ) : ℝ)) := by + exact_mod_cast hbound + simpa only [show (((5/6 : ℚ) : ℝ)) = (5/6 : ℝ) by norm_num] using hr + +theorem actual_restricted_gap_of_identified_classes + (htrue : + (((physicalOccupationLimit + (typedModel phaseAwarePolicy).inducedKernel + (nativeOrbit phaseAwarePolicy)).map Prod.snd).toMeasure {true}).toReal = + (2/3 : ℝ)) + (hclasses : ∀ policy : Bool → Bool, + nativeNonlinearPathScore (phaseBlindPolicy policy) + (nativeOrbit (phaseBlindPolicy policy)) = + (booleanRationalScore (nativePhaseBlindTailShare policy) : ℝ)) + (policy : Bool → Bool) : + nativeExpectedNonlinearScore (phaseBlindPolicy policy) < + nativeExpectedNonlinearScore phaseAwarePolicy := by + rw [actual_phase_aware_score_one_of_canonical_true_mass htrue] + have hb := actual_phase_blind_score_le_of_class_identification policy (hclasses policy) + linarith + +theorem native_phase_aware_actual_score_one : + nativeExpectedNonlinearScore phaseAwarePolicy = 1 := + actual_phase_aware_score_one_of_canonical_true_mass + native_phase_aware_actual_true_mass + + +end PermanssonResearch.ReverseSolver.NonlinearPeriodThree diff --git a/PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean b/PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean new file mode 100644 index 00000000..f786c9b8 --- /dev/null +++ b/PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean @@ -0,0 +1,120 @@ +import PermanssonResearch.ReverseSolver.NonlinearPeriodThreePathLaw +import PermanssonLean.Regime.Occupation +import PermanssonResearch.MultipleLimit.FiniteKernelInvariantUniqueness +import Mathlib.Tactic + +/-! +Native three-phase occupation witness. This certifies the real alpha-P-U +orbit and an explicit normalized candidate cycle law. Equality to the +canonical selected stationary law is NOT silently inferred. +-/ +open MeasureTheory ProbabilityTheory +namespace PermanssonResearch.ReverseSolver.NonlinearPeriodThree +open PermanssonLean +open PermanssonLean.RegimeSpecification + +def canonicalCycleState (i : Phase) : State := (i, i != 0) + +theorem native_cycle_state_zero : canonicalCycleState 0 = start := by decide +theorem native_cycle_state_one : canonicalCycleState 1 = (1,true) := by decide +theorem native_cycle_state_two : canonicalCycleState 2 = (2,true) := by decide + +theorem native_cycle_successor (i : Phase) : + nextState phaseAwarePolicy (canonicalCycleState i) = + canonicalCycleState (i+1) := by + fin_cases i <;> decide + +def phaseIndex : ℕ → Phase + | 0 => 0 + | n+1 => phaseIndex n + 1 + +theorem native_phase_orbit_is_cycle (n : ℕ) : + nativeOrbit phaseAwarePolicy n = canonicalCycleState (phaseIndex n) := by + induction n with + | zero => exact native_cycle_state_zero.symm + | succ n ih => + rw [nativeOrbit, phaseIndex, ih] + exact native_cycle_successor (phaseIndex n) + +noncomputable def exactCycleLaw : ProbabilityMeasure State := + (uniformFinProbability 3 (by norm_num)).map canonicalCycleState + +theorem native_three_phase_true_share : + ((if (canonicalCycleState 0).2 then (1:ℚ) else 0) + + (if (canonicalCycleState 1).2 then (1:ℚ) else 0) + + (if (canonicalCycleState 2).2 then (1:ℚ) else 0))/3 = 2/3 := by + norm_num [canonicalCycleState] + + +/-! +## Exact invariant weights on the genuine native six-state Markov chain + +This is the genuine original alpha-P-U kernel. Any invariant probability +must concentrate on the unique physically reached three-phase cycle; +its three nonzero stationary point masses are all one third. No C11 +Unit/Fin6 strategic-world re-encoding or assumed numerical table appears. +-/ + +open PermanssonResearch.MultipleLimit.FiniteKernelInvariantUniqueness +open PermanssonResearch.MultipleLimit.FiniteKernelRealBridge +open PermanssonResearch.MultipleLimit.FiniteCesaroAlgebra +attribute [local instance] PermanssonLean.StrategicWorldModel.inducedKernel_isMarkov + +/-- Every real singleton transition probability is exactly the genuine +native alpha-P-U deterministic row, not an externally asserted matrix. -/ +theorem phase_aware_real_transition (y z : State) : + realTransition (typedModel phaseAwarePolicy).inducedKernel y z = + if nextState phaseAwarePolicy y = z then (1 : ℝ) else 0 := by + change (((typedModel phaseAwarePolicy).inducedKernel y + ({z} : Set State)).toReal) = _ + rw [induced_kernel_singleton] + split_ifs <;> simp + +/-- Finite stationary equations for the real native model force *all six* +point masses. In particular the three off-cycle states have probability +zero; the period-three class has equal point weights 1/3. -/ +theorem phase_aware_invariant_weights + (π : ProbabilityMeasure State) + (hInv : Kernel.Invariant + (typedModel phaseAwarePolicy).inducedKernel π.toMeasure) : + realPointMass π start = (1/3 : ℝ) ∧ + realPointMass π (1,true) = (1/3 : ℝ) ∧ + realPointMass π (2,true) = (1/3 : ℝ) ∧ + realPointMass π (0,true) = 0 ∧ + realPointMass π (1,false) = 0 ∧ + realPointMass π (2,false) = 0 := by + classical + let v : State → ℝ := realPointMass π + have hsum : (∑ y : State, v y) = 1 := + realPointMass_total π + have hstat (z : State) : + (∑ y : State, v y * + (if nextState phaseAwarePolicy y = z then (1:ℝ) else 0)) = v z := by + have h := realPointMass_stationary + (typedModel phaseAwarePolicy).inducedKernel π hInv z + change (∑ y : State, + v y * realTransition (typedModel phaseAwarePolicy).inducedKernel y z) = v z at h + simpa only [phase_aware_real_transition] using h + have h0 := hstat (0,false) + have h1 := hstat (1,true) + have h2 := hstat (2,true) + have h3 := hstat (0,true) + have h4 := hstat (1,false) + have h5 := hstat (2,false) + simp [Fintype.sum_prod_type, Fin.sum_univ_three, Fintype.sum_bool, + nextState, phaseAwarePolicy, nextPhase, start] at h0 h1 h2 h3 h4 h5 + simp only [Fintype.sum_prod_type, Fin.sum_univ_three, + Fintype.sum_bool] at hsum + -- Normalize the designated starting state before solving the six equations. + change v (0, false) = _ ∧ v (1,true) = _ ∧ v (2,true) = _ ∧ + v (0,true) = _ ∧ v (1,false) = _ ∧ v (2,false) = _ + refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩ + · linarith [h0, h1, h2, h3, h4, h5, hsum] + · linarith [h0, h1, h2, h3, h4, h5, hsum] + · linarith [h0, h1, h2, h3, h4, h5, hsum] + · linarith [h3] + · linarith [h4] + · linarith [h5] + + +end PermanssonResearch.ReverseSolver.NonlinearPeriodThree diff --git a/PermanssonResearch/ReverseSolver/NonlinearPhaseBlindClassGeometry.lean b/PermanssonResearch/ReverseSolver/NonlinearPhaseBlindClassGeometry.lean new file mode 100644 index 00000000..22c44d3c --- /dev/null +++ b/PermanssonResearch/ReverseSolver/NonlinearPhaseBlindClassGeometry.lean @@ -0,0 +1,328 @@ +import PermanssonResearch.ReverseSolver.NonlinearPeriodThreeExpectedScore +import PermanssonResearch.MultipleLimit.FiniteKernelInvariantUniqueness +import Mathlib.Tactic + +/-! +# N2: Complete physical-only deterministic feedback comparison + +The original native strategic memory is Fin 3; the physical state/action +remain Bool and its P/U kernels are unchanged across policies. These lemmas +discharge the actual original-infinite-path objective for each physical-only +deterministic policy. The false-preserving case uses the *reached* bottom +class, not an arbitrary stationary mixture; the other cases use the exact +native invariant equations. This does not compare arbitrary randomized or +phase-aware stationary controllers. +-/ + +open MeasureTheory ProbabilityTheory +open scoped ENNReal +namespace PermanssonResearch.ReverseSolver.NonlinearPeriodThree + +open PermanssonLean +open PermanssonResearch.MultipleLimit.FiniteKernelInvariantUniqueness +open PermanssonResearch.MultipleLimit.FiniteKernelRealBridge +open PermanssonResearch.MultipleLimit.FiniteCesaroAlgebra +open PermanssonResearch.MultipleLimit.KernelCommunication +open PermanssonResearch.MultipleLimit.RecurrentClassDecomposition +open PermanssonResearch.MultipleLimit.CanonicalClassSelector +open PermanssonResearch.MultipleLimit.ClassStationarySelectionLaw +open PermanssonResearch.MultipleLimit.FiniteOccupationRandomLaw +open PermanssonResearch.ReverseSolver.NonlinearOccupationBoundary + +attribute [local instance] PermanssonLean.StrategicWorldModel.inducedKernel_isMarkov +set_option autoImplicit false + +private noncomputable abbrev blindKernel (p : Bool → Bool) : Kernel State State := + (typedModel (phaseBlindPolicy p)).inducedKernel + +private def falseState (i : Phase) : State := (i,false) +private def falseCycle : Set State := {y | y.2 = false} + +/-- In the original six-state source, every phase-independent feedback +which keeps false at false rotates within the physical-false phase cycle. -/ +private theorem false_next (p : Bool → Bool) (hf : p false = false) + (i : Phase) : + nextState (phaseBlindPolicy p) (falseState i) = falseState (i+1) := by + simp [falseState, nextState, phaseBlindPolicy, hf] + -- The remaining phase increment is a finite identity in Fin 3. + fin_cases i <;> decide + +private theorem false_cycle_edge (p : Bool → Bool) (hf : p false = false) + (i : Phase) : + PositiveEdge (blindKernel p) (falseState i) (falseState (i+1)) := by + change 0 < (typedModel (phaseBlindPolicy p)).inducedKernel + (falseState i) ({falseState (i+1)} : Set State) + rw [induced_kernel_singleton, false_next p hf] + simp + +private theorem false_cycle_closed (p : Bool → Bool) (hf : p false = false) : + ClosedSubset (blindKernel p) falseCycle := by + intro y hy z hz + change 0 < (typedModel (phaseBlindPolicy p)).inducedKernel + y ({z} : Set State) at hz + rw [induced_kernel_singleton] at hz + have heq : nextState (phaseBlindPolicy p) y = z := by + by_contra hn + simp [hn] at hz + subst z + rcases y with ⟨i,b⟩ + have hb : b = false := by simpa [falseCycle] using hy + subst b + change (nextState (phaseBlindPolicy p) (falseState i)).2 = false + rw [false_next p hf i] <;> rfl + +private theorem false_cycle_comm (p : Bool → Bool) (hf : p false = false) + (i : Phase) : + Communicates (blindKernel p) (falseState 0) (falseState i) := by + have h01 : Reachable (blindKernel p) (falseState 0) (falseState 1) := by + simpa using reachable_single (blindKernel p) (false_cycle_edge p hf 0) + have h12 : Reachable (blindKernel p) (falseState 1) (falseState 2) := by + simpa using reachable_single (blindKernel p) (false_cycle_edge p hf 1) + have h20 : Reachable (blindKernel p) (falseState 2) (falseState 0) := by + simpa [show (2 : Phase) + 1 = 0 by decide] using + reachable_single (blindKernel p) (false_cycle_edge p hf 2) + fin_cases i + · exact communicates_refl (blindKernel p) _ + · exact ⟨h01,reachable_trans (blindKernel p) h12 h20⟩ + · exact ⟨reachable_trans (blindKernel p) h01 h12,h20⟩ + +private theorem false_cycle_eq_class (p : Bool → Bool) (hf : p false = false) : + falseCycle = CommunicatingClass (blindKernel p) (falseState 0) := by + ext y + constructor + · intro hy + rcases y with ⟨i,b⟩ + have hb : b = false := by simpa [falseCycle] using hy + subst b + exact false_cycle_comm p hf i + · intro hy + exact closed_reachable (blindKernel p) (false_cycle_closed p hf) + (by simp [falseCycle, falseState]) hy.1 + +private theorem false_cycle_recurrent (p : Bool → Bool) (hf : p false = false) : + falseCycle ∈ RecurrentClasses (blindKernel p) := by + have hrec : IsRecurrentState (blindKernel p) (falseState 0) := by + change ClosedSubset (blindKernel p) + (CommunicatingClass (blindKernel p) (falseState 0)) + rw [← false_cycle_eq_class p hf] + exact false_cycle_closed p hf + exact (recurrentClasses_mem_iff (blindKernel p) _).mpr + ⟨falseState 0, hrec,(false_cycle_eq_class p hf).symm⟩ + +private theorem blind_orbit_false (p : Bool → Bool) (hf : p false = false) + (n : ℕ) : + (nativeOrbit (phaseBlindPolicy p) n).2 = false := by + induction n with + | zero => rfl + | succ n ih => + change p (nativeOrbit (phaseBlindPolicy p) n).2 = false + rw [ih] + exact hf + +/-- The chosen native Markov class remains the physical-false 3-cycle +for BOTH false-preserving stationary Bool-only feedback controllers. -/ +private theorem false_cycle_selected (p : Bool → Bool) (hf : p false = false) : + CommunicatingClass (blindKernel p) + (selectedRepresentative (blindKernel p) + (nativeOrbit (phaseBlindPolicy p))) = falseCycle := by + let w : ℕ → State := nativeOrbit (phaseBlindPolicy p) + have h0recurrent : falseState 0 ∈ RecurrentStates (blindKernel p) := by + change ClosedSubset (blindKernel p) + (CommunicatingClass (blindKernel p) (falseState 0)) + rw [← false_cycle_eq_class p hf] + exact false_cycle_closed p hf + have hHit : HasRecurrentHit (blindKernel p) w := by + refine ⟨0,?_⟩ + change start ∈ RecurrentStates (blindKernel p) + exact h0recurrent + have hfirst : firstRecurrentState (blindKernel p) w ∈ falseCycle := by + change (w (firstRecurrentIndex (blindKernel p) w)).2 = false + exact blind_orbit_false p hf _ + have hfirstComm : Communicates (blindKernel p) + (falseState 0) (firstRecurrentState (blindKernel p) w) := by + rw [false_cycle_eq_class p hf] at hfirst + exact hfirst + have hfirstClass : + CommunicatingClass (blindKernel p) + (firstRecurrentState (blindKernel p) w) = falseCycle := by + calc + _ = CommunicatingClass (blindKernel p) (falseState 0) := + (communicatingClass_eq_of_communicates (blindKernel p) hfirstComm).symm + _ = falseCycle := (false_cycle_eq_class p hf).symm + have hrep : selectedRepresentative (blindKernel p) w ∈ falseCycle := by + have hc : classRepresentative (blindKernel p) + (firstRecurrentState (blindKernel p) w) ∈ falseCycle := by + rw [← hfirstClass] + exact classRepresentative_mem (blindKernel p) _ + simpa [selectedRepresentative, hHit] using hc + have hCommRep : Communicates (blindKernel p) (falseState 0) + (selectedRepresentative (blindKernel p) w) := by + rw [false_cycle_eq_class p hf] at hrep + exact hrep + calc + CommunicatingClass (blindKernel p) + (selectedRepresentative (blindKernel p) w) = + CommunicatingClass (blindKernel p) (falseState 0) := + (communicatingClass_eq_of_communicates (blindKernel p) hCommRep).symm + _ = falseCycle := (false_cycle_eq_class p hf).symm + +/-- Actual physical true-state mass is ZERO for the real original point +start when the deterministic policy does not leave physical false. -/ +theorem native_false_preserving_true_mass_zero + (p : Bool → Bool) (hf : p false = false) : + (((physicalOccupationLimit (blindKernel p) + (nativeOrbit (phaseBlindPolicy p))).map Prod.snd).toMeasure + ({true} : Set Bool)).toReal = 0 := by + have hclass : + physicalOccupationLimit (blindKernel p) + (nativeOrbit (phaseBlindPolicy p)) = + stationaryForSet (blindKernel p) falseCycle := by + unfold physicalOccupationLimit + rw [false_cycle_selected p hf] + rw [hclass] + let π := stationaryForSet (blindKernel p) falseCycle + have hsupp : π.toMeasure falseCycle = 1 := + (stationaryForSet_spec (blindKernel p) (false_cycle_recurrent p hf)).2 + have hpre : + (Prod.snd ⁻¹' ({true} : Set Bool) : Set State) = falseCycleᶜ := by + ext y + cases y.2 <;> simp [falseCycle] + have hmeas : MeasurableSet falseCycle := + (Set.toFinite falseCycle).measurableSet + rw [ProbabilityMeasure.toMeasure_map, + Measure.map_apply (by fun_prop) (measurableSet_singleton true), hpre] + rw [prob_compl_eq_one_sub hmeas, hsupp] + norm_num + +/-- An original stationary physical-true marginal equals the sum of the +three genuine native-state singleton masses. -/ +private theorem physical_true_mass_eq_sum (π : ProbabilityMeasure State) : + ((π.map Prod.snd).toMeasure ({true} : Set Bool)).toReal = + ∑ i : Phase, realPointMass π (i,true) := by + classical + let s : Finset State := Finset.univ.filter (fun y => y.2 = true) + have hs : (Prod.snd ⁻¹' ({true} : Set Bool) : Set State) = + (↑s : Set State) := by + ext y + simp [s] + rw [ProbabilityMeasure.toMeasure_map] + rw [Measure.map_apply (by fun_prop) (measurableSet_singleton true), hs] + rw [← (sum_measure_singleton (μ := π.toMeasure) (s := s))] + rw [ENNReal.toReal_sum (fun i hi => measure_ne_top π.toMeasure _)] + change (∑ y ∈ s, realPointMass π y) = ∑ i : Phase, realPointMass π (i,true) + -- A filtered product sum must be expanded before product summation. + -- Use the same finite-product identity as C11ObservedStationary. + unfold s + rw [Finset.sum_filter] + rw [Fintype.sum_prod_type] + simp [Fintype.sum_bool] + +private theorem blind_real_transition (p : Bool → Bool) (y z : State) : + realTransition (blindKernel p) y z = + if nextState (phaseBlindPolicy p) y = z then (1 : ℝ) else 0 := by + change (((typedModel (phaseBlindPolicy p)).inducedKernel y + ({z} : Set State)).toReal) = _ + rw [induced_kernel_singleton] + split_ifs <;> simp + +/-- The two policies with p(false)=true have either a constant true world +(p(true)=true) or an alternating Bool world (p(true)=false). Any native +invariant law consequently has projected physical true mass 1 or 1/2. +This uses the ORIGINAL six-state stochastic rows and stationary equations. -/ +private theorem blind_invariant_true_mass + (p : Bool → Bool) (hf : p false = true) + (π : ProbabilityMeasure State) + (hInv : Kernel.Invariant (blindKernel p) π.toMeasure) : + ((π.map Prod.snd).toMeasure ({true} : Set Bool)).toReal = + if p true then (1 : ℝ) else (1/2 : ℝ) := by + classical + let v : State → ℝ := realPointMass π + have hsum : (∑ y : State, v y) = 1 := realPointMass_total π + have hs (z : State) : + (∑ y : State, v y * + (if nextState (phaseBlindPolicy p) y = z then (1:ℝ) else 0)) = v z := by + have h := realPointMass_stationary (blindKernel p) π hInv z + change (∑ y : State, + v y * realTransition (blindKernel p) y z) = v z at h + simpa only [blind_real_transition] using h + have h0 := hs (0,false) + have h1 := hs (1,false) + have h2 := hs (2,false) + have h3 := hs (0,true) + have h4 := hs (1,true) + have h5 := hs (2,true) + rw [physical_true_mass_eq_sum] + change (∑ i : Phase, v (i,true)) = _ + simp only [Fin.sum_univ_three] + simp only [Fintype.sum_prod_type,Fin.sum_univ_three, + Fintype.sum_bool] at hsum + cases ht : p true + · simp [Fintype.sum_prod_type,Fin.sum_univ_three,Fintype.sum_bool, + nextState,phaseBlindPolicy,nextPhase,hf,ht] at h0 h1 h2 h3 h4 h5 + simp only [ht, Bool.false_eq_true, ite_false] + linarith [hsum,h0,h1,h2,h3,h4,h5] + · simp [Fintype.sum_prod_type,Fin.sum_univ_three,Fintype.sum_bool, + nextState,phaseBlindPolicy,nextPhase,hf,ht] at h0 h1 h2 h3 h4 h5 + simp [ht] + linarith [hsum,h0,h1,h2,h3,h4,h5] + +/-- Actual original canonical expected nonlinear score for the +false-preserving physical-only policy class. -/ +theorem native_false_preserving_score + (p : Bool → Bool) (hf : p false = false) : + nativeExpectedNonlinearScore (phaseBlindPolicy p) = (1/3 : ℝ) := by + rw [native_expected_score_eq_actual_orbit] + unfold nativeNonlinearPathScore booleanProbabilityScore + rw [native_false_preserving_true_mass_zero p hf] + norm_num + +/-- Actual original canonical expected score for the other two Bool-only +feedback policies: constant true gives 2/3, alternation gives 5/6. -/ +theorem native_true_after_false_score + (p : Bool → Bool) (hf : p false = true) : + nativeExpectedNonlinearScore (phaseBlindPolicy p) = + if p true then (2/3 : ℝ) else (5/6 : ℝ) := by + rw [native_expected_score_eq_actual_orbit] + unfold nativeNonlinearPathScore booleanProbabilityScore + have hInv : Kernel.Invariant (blindKernel p) + (physicalOccupationLimit (blindKernel p) + (nativeOrbit (phaseBlindPolicy p))).toMeasure := + (selectedStationary_spec (blindKernel p) + (nativeOrbit (phaseBlindPolicy p))).1 + have hmass := blind_invariant_true_mass p hf + (physicalOccupationLimit (blindKernel p) + (nativeOrbit (phaseBlindPolicy p))) hInv + rw [hmass] + cases ht : p true <;> norm_num [ht] + +/-- All four deterministic phase-blind stationary physical-feedback +policies have genuine expected nonlinear scores at most 5/6. -/ +theorem native_phase_blind_actual_le_five_sixths + (p : Bool → Bool) : + nativeExpectedNonlinearScore (phaseBlindPolicy p) ≤ (5/6 : ℝ) := by + cases h0 : p false + · rw [native_false_preserving_score p h0] + norm_num + · rw [native_true_after_false_score p h0] + cases h1 : p true <;> norm_num [h1] + +/-- Alternating physical-only deterministic stationary feedback actually +attains the restricted optimum 5/6 in the original canonical model. -/ +theorem native_phase_blind_actual_attained : + nativeExpectedNonlinearScore + (phaseBlindPolicy alternatingFeedback) = (5/6 : ℝ) := by + rw [native_true_after_false_score alternatingFeedback (by decide)] + norm_num [alternatingFeedback] + +/-- The phase-aware ORIGINAL native-policy objective equals one and +strictly beats the original expected nonlinear objective of every +deterministic Bool-only stationary feedback on exactly the same P/U. -/ +theorem native_actual_phase_aware_strictly_beats_phase_blind + (p : Bool → Bool) : + nativeExpectedNonlinearScore (phaseBlindPolicy p) < + nativeExpectedNonlinearScore phaseAwarePolicy := by + rw [native_phase_aware_actual_score_one] + exact lt_of_le_of_lt (native_phase_blind_actual_le_five_sixths p) + (by norm_num) + +end PermanssonResearch.ReverseSolver.NonlinearPeriodThree diff --git a/PermanssonResearch/Specialized/E05FullGramMaskingCountermodel.lean b/PermanssonResearch/Specialized/E05FullGramMaskingCountermodel.lean new file mode 100644 index 00000000..36dbaa0b --- /dev/null +++ b/PermanssonResearch/Specialized/E05FullGramMaskingCountermodel.lean @@ -0,0 +1,62 @@ +import PermanssonResearch.Specialized.GramSeparation +import Mathlib.Tactic + +/-! +# E-05: source-bound Gram negative witness with a masking positive component + +The frozen Gram witness of the ORIGINAL inner-product-space construction has +negative signed component when c>0 and Gram gap>0. This does not imply the +entire form remains negative after a separately budgeted summand is added. + +An additional positive component 2*c*gap² exceeds the negative +-c*gap² signed term. The resulting total is STRICTLY POSITIVE on the +same genuine witness and with the same nondegenerate positive-gap premise. +No unqualified full-form negativity or empirical identification is claimed. +-/ + +namespace PermanssonResearch.Specialized.E05FullGramMaskingCountermodel + +open PermanssonResearch.Specialized +open scoped InnerProductSpace +set_option autoImplicit false + +variable {E : Type*} [NormedAddCommGroup E] [InnerProductSpace ℝ E] + +/-- A positive-gap source direction is exactly where the signed Gram +summand is strictly negative, yet its admissible additive budget matters. -/ +theorem added_component_masks_actual_negative_gram + {c : ℝ} (hc : 0 < c) (x y : E) + (hgap : 0 < gramGap x y) : + 0 < + c * (⟪x, gramWitness x y⟫_ℝ ^ 2 - + ⟪y, gramWitness x y⟫_ℝ ^ 2) + + 2 * c * (gramGap x y) ^ 2 := by + rw [signedGram_witness_value] + have hpos : 0 < c * gramGap x y ^ 2 := + mul_pos hc (sq_pos_of_pos hgap) + nlinarith + +/-- The same hypotheses imply the negative signed Gram term. Both facts +coexist: the signed summand is negative but the displayed total is positive. -/ +theorem original_signed_negative_but_total_positive + {c : ℝ} (hc : 0 < c) (x y : E) + (hgap : 0 < gramGap x y) : + (c * (⟪x, gramWitness x y⟫_ℝ ^ 2 - + ⟪y, gramWitness x y⟫_ℝ ^ 2) < 0) ∧ + (0 < + c * (⟪x, gramWitness x y⟫_ℝ ^ 2 - + ⟪y, gramWitness x y⟫_ℝ ^ 2) + + 2 * c * gramGap x y ^ 2) := by + exact ⟨signedGram_witness_negative hc x y hgap, + added_component_masks_actual_negative_gram hc x y hgap⟩ + +/-- The original quantitative budget cannot simply be dropped. -/ +theorem extra_component_exceeds_negative_budget + {c : ℝ} (hc : 0 < c) (x y : E) + (hgap : 0 < gramGap x y) : + ¬ (2 * c * gramGap x y ^ 2 < c * gramGap x y ^ 2) := by + have hp : 0 < c * gramGap x y ^ 2 := + mul_pos hc (sq_pos_of_pos hgap) + nlinarith + +end PermanssonResearch.Specialized.E05FullGramMaskingCountermodel diff --git a/docs/operations/PR113_MERGE_COMPLETION_2026-10-28.md b/docs/operations/PR113_MERGE_COMPLETION_2026-10-28.md new file mode 100644 index 00000000..13b5bf77 --- /dev/null +++ b/docs/operations/PR113_MERGE_COMPLETION_2026-10-28.md @@ -0,0 +1,120 @@ +# PR #113: one completion sequence + +Baseline inspected: `4764861bf1384f3e7cd13840f4bd7fcdcc795064`. +This plan distinguishes deployed code, actual execution, and external authority. +It is not a review receipt, activation authorization, merge, or release. + +## Findings and the source repair in this change + +The baseline Research run (40608689212) completed 1,883 tests with only the +original historical A2 source-contract rejection remaining. The archive run +(40608689053) passed its 35 adversarial tests and rejected current tooling with +`E_TOOLING_CLOSURE`. Those historical verifiers must not be rewritten. + +The reviewed candidate has one additional **post-activation** integration defect: +`test_pr113_a2_successor_compiler.py` unconditionally imported the v1 compiler, +whose identity checker pins the current 25-workflow source and original A2 test. +The 30-workflow cutover intentionally changes those pins. The existing eight-suite +disposable rehearsal did not execute this separate actual-Lean test. + +This patch keeps both native compilers unchanged and routes that mandatory test +through `pr113_a2_compiler_route_v3.select_compiler`. The complete exact-Git +workflow audit, not a filename count or environment variable, selects v1 for +staged-25 and v2 for active-30. A failed audit or compiler never falls back to +another profile. Both paths retain the original 40 declarations, positive +fixture, all four semantic mutations, source checks and nonacceptance flags. +The receipt format must match the selected compiler. Thirteen routing regressions +are added to Research discovery and the fast preflight now lists 53 suites. + +## Ordered work to make this infrastructure PR merge-ready + +| Step | Work and completion evidence | Responsibility / status | +|---|---|---| +| 1 | Deploy the phase-selected actual-Lean consumer, routing regression suite and preflight entry together. Preserve the native v1/v2 and historical A2 verifiers. | Source repair in this patch | +| 2 | Run exact-new-head fast QA, disposable direct-C and ordered-S rehearsal, and the actual Lean compiler tests. Retain failure-level logs. The original historical checks may remain red before the authorized cutover; do not call that a green required run. | Engineering execution | +| 3 | Reproduce original T in its exact isolated checkout; reproduce original A2 in its exact checkout; compile the successor separately. Review the full source/test/manuscript implementation matrix and clean draft reproduction. Source discovery is not independent 45/11 certification. | Genuine preactivation evidence | +| 4 | Obtain the actual independent security reviewer's GitHub identity, disclosure and owner authorization. Configure `research/certification/infrastructure_reviewer_authority.json` before final source review. An empty roster, self-review, or invented approval must block. | Owner/reviewer input; currently absent | +| 5 | Freeze the revised proposal and independently review all candidate workflow/guard bytes and called verifiers. Obtain a separate owner decision to install that exact proposal. This source review precedes installation; final artifact attestation follows producer execution. | External source review and installation authority | +| 6 | Install exactly ten source paths atomically: two workflow replacements, five workflow additions and three verification guards. Verify 30 installed workflow blobs, with 23 unchanged retained workflows. Do not change proposal metadata to pretend it is an approval. | Blocked until step 5 | +| 7 | Execute the real branch-push historical producer at the final installed head. Require successful preflight and distinct original-T/current-H and original-A2/current-H jobs in one successful run attempt. Preserve the original ZIP bytes, IDs, digests and source tree. | Real GitHub execution, not a local receipt | +| 8 | Obtain an actual latest independent APPROVED review for the final head and original producer artifacts. Bind the existing five-field attestation contract exactly; do not add guessed IDs or unsupported fields. Supply genuine producer/review IDs through repository variables. | External artifact attestation and administrator inputs | +| 9 | Rerun `research` and `archive-reproducibility` at the **same final head** after inputs exist. Require every applicable downstream step plus all twelve exact-source required contexts. Any source edit restarts affected review and evidence. | Final CI, no skipped-required-step acceptance | +| 10 | Verify effective main protection enforces the intended checks/review. Mark ready only after the applicable criteria are met. Obtain a separate owner merge decision; perform and authenticate ordered `[B113,H113]` merge and main CI. | Owner/admin; not performed by this patch | +| 11 | Open the actual successor CERT PR. Independent 45-case/11-claim certification, scholarly approval, final seal and publication stay in CERT. | Deferred, not waived | + +These are dependencies, not independent tasks that can all be completed by a +source-code commit. No source patch can supply a non-author's genuine review. + +## Verification commands before the source freeze + +Use a clean, complete checkout, run Python with `-B`, and keep outputs outside it. +`H` is obtained from the checkout being tested; do not reuse the baseline SHA. + +```sh +H="$(git rev-parse HEAD)" +OUT="$(mktemp -d)" +python3 -B -m unittest discover -s verification/research -p test_pr113_a2_compiler_route_v3.py -v +python3 -B verification/research/pr113_fast_engineering_preflight.py --head "$H" --out "$OUT/fast" +python3 -B verification/research/pr113_disposable_transition_rehearsal.py --head "$H" --out "$OUT/disposable.json" +# Requires the pinned Lean installation and clean, pinned dependencies: +python3 -B -m unittest discover -s verification/research -p test_pr113_a2_successor_compiler.py -v +``` + +The eight-suite disposable rehearsal is still an engineering rehearsal, not +original T replay, original A2 compilation, or an authenticated required check. +During the reviewed cutover, execute the routing test and the real compiler test +again in the actual 30-workflow checkout. Full Research discovery must not omit +these tests. The new routing tests exercise synthetic profile boundaries; +they are not themselves proof of Lean compilation or security review. + +## Exact installation surface + +Use `pr113_disposable_transition_rehearsal.planned_sources` and the source-pinned +manifests to obtain target/source/blob triples. The ten targets are: + +- `.github/workflows/research-lean.yml` +- `.github/workflows/research-integrity.yml` +- `.github/workflows/pr113-historical-migration-candidates.yml` +- `.github/workflows/pr113-original-positive-controls-candidate.yml` +- `.github/workflows/pr113-postseal-replay.yml` +- `.github/workflows/pr113-source-controls-v3_1.yml` +- `.github/workflows/pr113-preseal-fullscope.yml` +- `verification/integrity/workflow_contract.py` +- `verification/integrity/tests/test_workflow.py` +- `verification/research/test_localized_prefix_compiler.py` + +Their staged sources remain under `docs/operations/`. This patch does not copy +those sources into protected active paths. Before installation, compare the +entire intended diff and source modes to the independently reviewed proposal; +reject a changed PR head, dirty source, an extra path or altered historical byte. + +## Artifact attestation and final required checks + +The existing review parser accepts one `PERMANSSON-INFRA-SECURITY-V1:` JSON +attestation with exactly `head_sha`, `workflow_sha256`, `T_artifact_sha256`, +`A2_artifact_sha256`, and `original_base_sha`. The final head binds the complete +reviewed source profile. Generate all values from real final-source and original +artifact bytes; do not commit external run/review IDs to source. + +Runtime inputs are `PR113_HISTORICAL_PRODUCER_RUN_ID` and `PR113_SECURITY_REVIEW_ID`. +Successful producer execution does not itself authorize the security review. +The initial required runs can fail while those inputs are absent; authenticate +and rerun them after the approved inputs exist, without changing source. + +All twelve required contexts are: `research`, `build`, +`frozen-source-preservation`, `multiagent-protocols`, `approx-quotient`, +`release-export-integrity`, `archive-reproducibility`, `executable-verification`, +`research-release`, `integrity-portability-windows`, +`Application contract (ubuntu-latest)`, and `Application contract (windows-latest)`. + +For the PR event, require C10 generation/verification, D0 reports, the default +build, import coverage and source firewall to execute successfully. Do not +confuse seven successful workflows with all twelve required job contexts. +Do not borrow old-head successes or call an intended historical rejection a pass. + +## Explicit non-actions + +No original archive, original A2 proof provider, native compiler, protected +workflow, reviewer roster, repository setting, merge or publication was altered +by the source repair in this change. Activation and final merge remain blocked +until their respective genuine approvals and execution evidence exist. diff --git a/docs/operations/PR113_R1_R6_ENGINEERING_HANDOFF_UNAPPROVED.md b/docs/operations/PR113_R1_R6_ENGINEERING_HANDOFF_UNAPPROVED.md new file mode 100644 index 00000000..70802a04 --- /dev/null +++ b/docs/operations/PR113_R1_R6_ENGINEERING_HANDOFF_UNAPPROVED.md @@ -0,0 +1,76 @@ +# PR #113 — R1–R6 engineering implementation handoff (unapproved) + +**Status:** candidate implementation and execution contract only. **Not** a security, mathematical, scholarly, merge or publication certificate. Always refresh exact `H113` before running a producer or requesting review. + +**Original immutable Git anchors:** T `71cc34fa0bc4b639c05d2ec5f39f0b88afd56388`; historical A2 `f71a76032b169aac7bfe0694aa715339a94d4e05`; original B113 `17c315f7df7abf457eb94d393bd526d46da6f6f4`. + +## R1 — T → T2 source preservation + +- `pr113_dual_tooling_candidate.py` runs the unmodified T archive verifier in its own exact-T checkout, without inherited CI credentials, with true offline namespace isolation. Separately runs the current H original workflow test suite as a **sanitized local source run**, never as a fabricated main-branch/PR check. +- Requires original T restoration PASS, `network_isolation=ENFORCED`, zero capabilities, exact current H local source, CI-06/CI-07 coverage and no skipped/error/failing tests. +- `pr113_tooling_migration.py` still protects the original archived supplement bytes and the exact **two** documented canonical integrity-tooling changes. +- Staged producing workflow: `docs/operations/pr113_unapproved_workflows/pr113-historical-migration-candidates.yml`. Remains **inactive** until independently reviewed and separately owner-authorized. + +## R2 — real historical and current A2 compiler sources + +- `pr113_dual_a2_compiler.py` requires the real old verifier checkout and the successor source, full positive compiler/source/signature legs and all four meaningful mathematical negatives **on each**. Intermediate status cannot mask a positive command failure or a false-mutant compilation success. +- The staged workflow reuses only identical, independently Git-pinned **dependency source checkouts**, not compiled project proof objects, and retains `historical_dependency_bridge.json` alongside both original A2 reports/logs. +- `infrastructure_acceptance.verify_A2_dependency_bridge` authenticates that receipt against exact Git-H `lake-manifest.json`, including the original audited mathlib revision. No changed A2 historical verifier or rewritten expected hash. + +## R3 — noncircular required-check transition (NOT ACTIVATED) + +- `infrastructure_acceptance.check_historical_only` authenticates **both** actual successful historical GitHub producer ZIPs, their original T/T2 and A2 semantics, the true source H/B/S target, and a real independent, exact-H security approval. It **does not query any of the twelve required source checks**, so a required check cannot circularly demand its own success. +- Only the **separate** `infrastructure_acceptance.check_evidence` can aggregate **12/12 authentic latest-H required checks** after they are independently green. +- `pr113_required_dual_gate.py` is fail-closed on local calls or other event identities. It is intended for reviewed replacements in the real `Research Lean` `research` and `Research integrity` `archive-reproducibility` original PR113-merge event jobs. Even on valid provenance it claims neither all-CI completion nor mathematical approval. +- Exact replacement proposals are staged and source-pinned in `docs/operations/pr113_required_gate_replacement_unapproved/manifest.json`: the required A2 test module, archive job fragment and original protected code hashes. **Do not silently install** them. +- **Additional mandatory review surface before installation:** `pr113_a2_successor_identity.py` currently pins `verification/research/test_localized_prefix_compiler.py` by original blob SHA. A reviewed future replacement must also update the **versioned exact-blob identity profile** and every affected `verification/integrity/workflow_contract.py` approved workflow digest and 25+5 review manifest. Until that coherent atomic source review is complete, the original test and original archive job stay red by design. +- Future reviewed workflow input delivery must retrieve original GitHub artifact ZIPs and source-bound external reviewer/owner attestations at actual H, no source-owned self-approval. Changing source H invalidates prior producer artifacts and reviews; refreeze/reexecute/review final H. + +## R4 — mathematical source closure remains genuine research + +No receipt, staged status or original 21-producer execution can substitute for a missing mathematical theorem. The **45 original cases, 11 new claims, and 259 baseline declaration identities** are preserved. Q-05 actual domination and full original-canonical-margin rational identities, D-09 original classwise nonlinear expected-functional identity, F-01 measurable matched protocols and the other recorded 15 pending/30 partial original cases still require actual source-level verification or an explicit owner-documented deferral. Deferred certification obligations remain blockers to full CERT. + +## R5 — reviewer-choice transport + +`pr113_legacy20_choice_delivery.py` continues to require a successful exact-H producing legacy survey and independent owner-authorized commenter, all 20 case-wise observed candidate events and original artifact hashes. It now rejects a GitHub issue comment edited after creation and retains the selected comment-body SHA-256 to support downstream provenance. No automated event selection or mathematical relevance approval is inferred. + +## R6 — reproducibility rehearsal + +`pr113_draft_rehearsal_auth.py` reopens the real two-clean-source-root GitHub artifact ZIP without extracting it to disk, verifies both actual PDFs, draft nested ZIPs, 13 original manuscript source members, corresponding SHA256SUMS, original PDF page render bytes and absence of independent or postmerge approval. Both outer and inner archives are bounded against traversal, symlinks, duplicate entries and excessive expansion. The detached report is `REPRODUCIBLE_DRAFT_BYTES_AUDITED_NOT_A_RELEASE`. A scholarly reader still needs to examine **every actual page and claim**. + +## Commands after authentic historical producing jobs + +```bash +# After the original T and A2 producing jobs finish successfully: +python3 -B verification/research/pr113_dual_review_packet.py \ + --head "$H113" \ + --T-artifact-zip "$REAL_ORIGINAL_T_ZIP" \ + --A2-artifact-zip "$REAL_ORIGINAL_A2_ZIP" \ + --out "$REVIEW_FOLDER/dual_review_packet.json" + +# The historical-only required gate can run ONLY after the source profile, +# independent reviewer authority, genuine GitHub approval and artifact +# sources have been externally authenticated. +python3 -B verification/research/infrastructure_acceptance.py \ + --historical-only \ + --evidence "$REAL_EXTERNAL_ENVELOPE" \ + --historical-T-zip "$REAL_ORIGINAL_T_ZIP" \ + --historical-A2-zip "$REAL_ORIGINAL_A2_ZIP" \ + --out "$REVIEW_FOLDER/dual_authentication.json" + +# After two clean-source manuscript builds (draft is not accepted): +python3 -B verification/research/pr113_draft_rehearsal_auth.py \ + --head "$ACTUAL_DRAFT_H" \ + --artifact-zip "$REAL_CLEAN_DRAFT_ARTIFACT_ZIP" \ + --out "$REVIEW_FOLDER/draft_rehearsal.json" +``` + +## Stop conditions / next decisions + +1. No existing historical verifier may be skipped, replaced as if equivalent, or made to accept a changed T/C closure by updating a self-owned digest. +2. No staged workflow or protected original A2 test may be activated before **independent security review of exact combined source** and **separate explicit owner authorization**. The current repository reviewer-authority rosters are unconfigured. +3. Required CI may be considered green only when all **12 exact-H check-run identities** genuinely conclude success with complete Research Lean C10/D0/default/import/firewall stages. Two old required checks fail until the reviewed transition is activated and rerun. +4. Main-branch protection/rules must be independently checked by an administrator before any authorized merge. No current source commit or comment grants authorization. +5. **PR #113 merge** requires separate owner approval and actual merge M113. **CERT** independently handles 45/45 and 11/11 mathematical decisions, full manuscript approval, final archive bytes and publication authorization. + +This document intentionally does not embed any future PR number, invented M113, fake reviewer, fabricated current CI identity, activation permission, or scholarly acceptance. diff --git a/docs/operations/PR113_RESEARCH_CI_SCOPE_REPAIR_2026-10-28.md b/docs/operations/PR113_RESEARCH_CI_SCOPE_REPAIR_2026-10-28.md new file mode 100644 index 00000000..fdbe8eaa --- /dev/null +++ b/docs/operations/PR113_RESEARCH_CI_SCOPE_REPAIR_2026-10-28.md @@ -0,0 +1,84 @@ +# PR113 Research CI: separate historical and current source obligations + +Baseline inspected: `19d2f29d36bd426e19298a2e59b175affb928df0`. +The completed Research job ran 1,896 tests with one failure: the unchanged +historical A2 verifier was applied to a current checkout containing the +known twelve modified baseline providers. Its rejection preceded compilation. + +## Deployed engineering correction + +Keep the 25 installed workflow definitions unchanged. Replace the current +unittest integration adapter, not either historical executor, so the +registered `test_actual_final_source_and_semantic_mutations` method does both: + +1. Create a fresh detached checkout of original A2 + `f71a76032b169aac7bfe0694aa715339a94d4e05`. Check its original integration + blob and both executor blobs against their frozen identities. Launch that + checkout's own `localized_prefix_gate.py` in a separate process. +2. Compile CURRENT source with the source-audited native successor selected + by `pr113_a2_compiler_route_v3`. Historical success never substitutes for + current-source compilation. Both outputs must pass the existing complete + 40-declaration/four-mutation receipt and semantic-diagnostic validators. + +The second leg is essential: the original P-02 source map references this +same unittest method as compiler evidence. Converting it to a historical-only +check would silently change the case's meaning. The standalone current-source +A2 compiler test also remains mandatory; no source map or acceptance criterion +is deleted or marked approved. + +## Dependency and source isolation + +The historical worktree starts with no project `.lake/build`. Only dependency +packages may be shared after exact equality of committed `lean-toolchain`, +`lake-manifest.json` and `lakefile.toml`, followed by the original dependency +revision/cleanliness verifier. Every dependency is checked again after both +executions. Current project proof objects are never imported into history. + +Both source checkouts must remain clean and at their bound commits. The worktree +is removed on success or failure; original raw receipts and command logs are +emitted by the integration test before its external evidence directory closes. +Missing tools, failed compilers, stale source, dirty dependencies, invalid logs, +missing declarations, missing mutants or wrong receipt identity all fail. + +## Source guards and the later 30-workflow transition + +The v1 current-source identity profile pins the new integration adapter exactly. +The original integration blob `c0f49038ea9b590720c78be85968eb63f5ac820f` stays +pinned at its historical introducer and preactivation revision. The original +`localized_prefix_gate.py`, `localized_prefix_support.py`, four accepted A2 +proof providers, toolchain and dependency pins are not edited. + +Preactivation and disposable-active30 guard tests distinguish three real sources: +original historical integration, the current engineering replay adapter, and +the separately staged security-reviewed successor consumer. They never relabel +one as another. The staged 30-workflow manifests and their artifact-authenticated +consumer remain unchanged. This patch does not install or authorize that profile. + +## Downstream Research execution + +No workflow condition, test skip, expected failure, or continue-on-error is +introduced. Once the genuine dual-source replay and the remaining regression +suite pass, the existing job proceeds to C10 generation and independent checking, +D0 golden reports, the default build, research import coverage, and the source +firewall. C10 must still bind the actual PR event and synthetic commit; historical +checkout activity must not leave tracked/untracked source dirt behind. + +A green regression summary alone is insufficient: confirm every applicable +subsequent step in the exact-head Research job, including retained artifacts. + +## Verification and authority boundaries + +The focused replay test suite exercises real temporary Git/worktree identities, +byte pinning, dependency symlinks and cleanup with explicitly synthetic compiler +and receipt-library adapters. It is registered in fast preflight. Its passing +results are protocol evidence, not actual historical Lean execution. + +Actual historical and current Lean results must come from the CI integration. +The new combined report is `HistoricalA2EngineeringReplay.v1`, states both source +identities explicitly, and cannot be used as the separate historical producer ZIP +or an independent security approval. Every approval/acceptance flag remains false. + +Archive reproducibility, independent reviewer authority, reviewed workflow +activation, all twelve final checks, merge authorization and CERT certification +are separate obligations. In particular, this repair does not make the historical +T/current-tooling comparison green and does not authorize merging PR113. diff --git a/docs/operations/PR113_TT2_A2_V2_IMPLEMENTATION_CHECKPOINT_2026-10-25.md b/docs/operations/PR113_TT2_A2_V2_IMPLEMENTATION_CHECKPOINT_2026-10-25.md new file mode 100644 index 00000000..a44f8acb --- /dev/null +++ b/docs/operations/PR113_TT2_A2_V2_IMPLEMENTATION_CHECKPOINT_2026-10-25.md @@ -0,0 +1,41 @@ +# PR #113 — staged T/T2+A2 v2 implementation checkpoint + +**Status:** source implementation candidate, not independently reviewed or activated. This is a historical/engineering migration, not mathematical acceptance, scholarly publication, or merge authorization. + +## Intent and immutable authority + +The existing 25 active workflows remain byte-identical to their pre-activation Git source at `5189f0c4a0db7535d57a9ff2a7aef6a3e76191b6`. The accepted T historical archive stays `71cc34fa0bc4b639c05d2ec5f39f0b88afd56388`; historical A2 stays `f71a76032b169aac7bfe0694aa715339a94d4e05`. PR #113's original base and 45-case, 11-claim, 259-declaration requirements are not replaced. Successor CERT receives independent mathematical acceptance, not inherited approval flags. + +## Implemented pre-activation sources + +| Function | Candidate source | Distinction | +| --- | --- | --- | +| Exact workflow profile | `verification/research/pr113_workflow_profile_v2.py` | Exact old 25 Git source; proposed 23 retained + 2 replaced + 5 added = 30; isolated staged vs active guards | +| T-to-T2 tooling profile | `verification/research/pr113_tooling_migration_v2.py` | Original T package byte closure and two expected integrity-tool changes remain bounded | +| Genuine dual T runner | `verification/research/pr113_dual_tooling_candidate_v2.py` | Historical T runs isolated in exact-T checkout; current source runs separately; profile must be actual 30 | +| A2 current-source identity | `verification/research/pr113_a2_successor_identity_v2.py` | Exactly pinned allowed changes, original A2 proof bytes and tools retained | +| Genuine dual A2 runner | `verification/research/pr113_dual_a2_compiler_v2.py` | Original real A2 versus new current source, positive and four semantic-negative runs on each | +| Artifact intake | `verification/research/pr113_historical_intake_v2.py` | Old v1 25-workflow artifacts cannot be accepted as current 30 | +| Detached dossier | `verification/research/pr113_dual_review_packet_v2.py` | Compiles unapproved dual reports and retained digests without inventing external provenance | +| GitHub intake + gate | `verification/research/pr113_historical_artifact_delivery_v2.py`, `infrastructure_acceptance_v2.py`, `pr113_required_dual_gate_v2.py`, `pr113_historical_postmerge_v2.py` | Real run/attempt/job/check/artifact, H/S/M, original artifacts and reviewer authority are separately authenticated | + +The proposed source-controlled T/A2 producer, full required Research and Integrity workflows, A2 test, v2 integrity contract, CI-06/CI-07 suite and all staged source-blob manifests were updated together in `docs/operations/`. The active `.github/workflows/` is unchanged. Older v1 verification sources remain accessible under their original identities. + +## Two necessary states + +1. **Pre-activation QA:** workflow inventory exactly 25, proposed virtual profile exactly 30, historical T/A2 verifiers stay unchanged, candidate reports unapproved. A successful source-only test does not authorize installation. +2. **Post-activation after separate permission:** actual installed profile is exactly 30, 23 legacy workflow files retain exact Git OIDs, two required jobs and five additions exactly match reviewed source, CI-06/CI-07 cases continue to execute, historical old verifier and current successor execute separately, successful producer jobs deliver authenticated artifacts. An explicit genuine independent security review of exact source/artifact bytes must be supplied. No downstream required check may depend on its own result. + +## Required external actions still NOT performed + +- Independent security/engineering review of the exact atomic 30-workflow replacement with inherited mutable action refs disclosed. +- Separate owner-authorized activation and verification of main branch-protection/rulesets. +- Installation of staged workflow/test/guard sources, real T/T2 + A2 producer CI, genuine reviewer attestation, and recheck of exact final H/S and applicable M. +- Complete pinned Research Lean run (including C10/D0/default/import/firewall), all 12 required contexts, source freeze, owner-authorized PR #113 merge. +- Remaining promised mathematics (including Q-05, F-01 and D-09 obligations as qualified by exact statements), independent CERT 45/45 + 11/11 acceptance, and release/owner publication authorization. + +**Fail closed:** current archive `E_TOOLING_CLOSURE` and original A2 12-provider drift remain real blockers until their authorized versioned replacement. Do not skip, xfail, rewrite historical archive hashes, or mark an unapproved receipt as accepted. + +## Validation evidence boundary + +The staged versioned source graph and fixed Git blob identities have been checked in the repository. Previous immediate engineering regressions passed at `5189f0c`. Fresh full CI for the staged migration must be judged only at its exact final commit, not at an earlier successful or cancelled run. No new independent Lean proof audit or historical replay result is asserted by this document. diff --git a/docs/operations/closure_readiness.md b/docs/operations/closure_readiness.md new file mode 100644 index 00000000..2130f601 --- /dev/null +++ b/docs/operations/closure_readiness.md @@ -0,0 +1,169 @@ +# Mathematical closure operational readiness + +**Draft; no deployment authorization.** Baseline main was unprotected at commit `17c315f7df7abf457eb94d393bd526d46da6f6f4`. No ruleset, original source or dependency pin is changed by this PR. + +Engineering compilation, theorem approval, independent scholarly review, PDF inspection, empirical identification, archive publication and operations are separate gates. The generated candidate remains unaccepted until actual post-merge M and CI identities and source/reviewer receipts have been authenticated. No release, tag, branch-setting change or production rollout is authorized. + + +## Current required-check policy (candidate, review pending) + +The PR113 v2 source authenticator requires all twelve named, applicable +exact-head checks: Research Lean, original Lean build, frozen-source preservation, +F1, E1, release-export integrity, historical archive reproducibility, executable +verification, research-release, Windows integrity portability and both application +contract OS jobs. Their identities must be authenticated on H and again on genuine +merged M. A global green Research job cannot mask a failing standalone verifier. +The historical verifier and its original T tooling remain unchanged and may still +fail pending a separately authorized successor trust-boundary migration. + +## PR #113 candidate execution — not an acceptance receipt + +Original acceptance routing now identifies exactly 45 original positive routes +and 45 original semantic-negative routes, but registered routes are not +executed tests or independent review. The detached evidence machinery has +two explicitly different input classes: + +| Input class | Cases | Candidate execution mechanism | +|---|---:|---| +| Original-source-preserving appended false Lean assertions | 25 | `final_closure_all_original_semantics.py` and nine-shard candidate workflow | +| Pre-existing original-source unittest false-probe methods | 20 | `final_closure_legacy_semantic_evidence.py`, passive Lean-input/log capture and eight-shard candidate workflow | + +The first class preserves original Lean provider bytes, appends a designated +false statement, and requires both compilations to be inspected. +The second class runs the historically registered unittest methods and +captures any real Lean invocations, including those in nested audit drivers, +**before their temporary input files are deleted**. It verifies the designated +test-method source against the frozen Git blob, preserves compiler command, +input and output hashes, rejects missing diagnostic observations, and records +unreviewed case/source metadata. A successful Python test does not certify +that a captured diagnostic belongs to the intended false theorem. + +**Never cast the second class directly into the canonical +`semantic_negative_receipts` field.** The current +`final_closure_case_receipts.validate_test_receipt` contract expects an +appended-source mutant with equal import lists and a source-bound positive +control. A standalone imported false probe has different semantics and +requires a separate reviewed, typed validation/receipt adapter before its +result may participate in effective mathematical acceptance. The legacy +observation records are intentionally incompatible with that accepted receipt. +A candidate-only bridge `final_closure_legacy_replay.py` now accepts one +individually attributable captured false method, demands a provider from its +original frozen theorem map, appends the false assertion to *unchanged tracked* +Lean provider bytes, recompiles the positive and negative sides with real pinned +Lean, and retains both sources and logs. Failed or ambiguous replays stay open +obligations. The replay output deliberately has no accepted CI check-run ID, +no independent review and no release authority. + +The detached negative receipt schema now requires +`original_lean_source_member` in addition to the original source digest. +The strict source validator checks that the archived original matches the +checkout, that the mutation is an append-only extension, and that no import, +axiom, `sorry`, or `admit` has been injected. A hash-only reference to the +mutable checkout no longer suffices. + +The separate four-shard `pr113-all-case-compiler.yml` workflow enumerates +the selected original 45-case and 11-claim theorem support. It must compile +the exact final source, export full elaborated Lean types and transitive axioms, +and retain the actual logs. All candidate workflows are read-only, shardable, +and cancel superseded branch-head runs. CI artifact retention is temporary +(30 days), so an authenticated final release must carry its own complete +source-bound evidence rather than relying on expiring GitHub artifacts. + +### Operational progression, in order + +1. Freeze a final candidate commit H. Verify original B, H, actual synthetic + merge S, source-tree equality, dependency pins, protected paths and review + policy. Do not treat an earlier green commit as evidence for a later H. +2. Execute all nine appended-source and eight legacy-compiler shards and all + four theorem-export shards on the same authenticated source. Inspect + every actual compiler diagnostic, positive control, source identity and + missing/partial observation. No artificial check-run IDs are permitted. +3. Resolve standalone-probe versus appended-mutant typed evidence contracts. + Authenticate the producing GitHub job, run attempt, checkout tree, uploaded + artifact member inventory and digest; require the original case-to-test + source map and the independent exact-H case/claim scope reviewer. +4. Only after effective 45/45 original cases and 11/11 new claims are accepted + may the premerge full-source predicate be evaluated. The operation must + still keep the release unapproved and not claim a merge. +5. A separately authorized merge M, exact-M CI, reproducible manuscript and + archive builds, every rendered PDF page, and independent scholarly approval + are indispensable to the full-release seal. No empirical Exact GR/PR or + unbounded randomized-control assertion follows automatically. + +PR #113 remains a draft until these gates actually succeed. No automation in +these candidate producer workflows authorizes a merge, release, review +approval, repository-setting change or deployment. + + +## Source-bound GitHub producing-artifact attestation (candidate only) + +`verification/research/final_closure_candidate_artifact_auth.py` is the +read-only consumer for a downloaded GitHub Actions ZIP. It requires the actual +successful producing workflow/run, the precise shard job and its own check-run +ID, the recorded head and run attempt, the artifact ID/name and the authoritative +GitHub `sha256:` artifact digest. It verifies the downloaded ZIP against that +digest and checks the expected, explicitly unreviewed candidate manifest. +If GitHub does not provide the SHA-256 digest, this attestor **fails closed**. +A successful attestation **does not** certify its inner mathematical logs, test +semantics, completeness of the final 45/11 graph, or independent case approval. + +For a genuine, already completed run, the operator may separately supply +the *actual* IDs and ZIP from GitHub (illustrative placeholders only): + +```sh +python3 verification/research/final_closure_candidate_artifact_auth.py \ + --head "$H" --kind append --shard 0 \ + --run-id "$REAL_RUN_ID" --artifact-id "$REAL_ARTIFACT_ID" \ + --artifact-zip /external/pr113-append-shard-0.zip \ + --output /external/verified-producer-shard-0.json +``` + +The attestor records the independently queried producing check-run ID as +metadata but **never writes it into a mathematical acceptance receipt**. +Current strict `final_closure_case_receipts.validate_test_receipt` still +requires a research check binding and a reviewed append-only false theorem; +before G3, the final source gate must be further reconciled with authenticated +per-producing-job artifacts and every complete original criterion. Never copy +a green general research CI ID onto locally produced mutant logs. + +The repository's current draft branch has no permission to self-approve +evidence, merge, tag, release or override these missing authentic dependencies. + + +## Immutable historical tooling gate — unresolved new release dependency + +The archival v0.2.0 offline verifier binds the complete canonical +`verification/integrity/`, `verification/archive/`, and historical +reproduction-guide byte closure between its preserved tooling commit T and +candidate C. It deliberately rejects a changed checker at candidate HEAD +with `E_TOOLING_CLOSURE`, even when the new checker's own unit tests pass. + +PR #113's updated 25-workflow source-profile checker remains **new, draft +tooling**, not a silently substituted historical verifier. Its workflow-pin +fix may pass frozen-source preservation while the historical offline-replay +job correctly refuses the altered code/guide closure. Resolving this +requires an explicitly authorized, independently reviewed tooling-version +migration or a reviewed release strategy that restores immutable T-compatible +tooling; the archived T snapshot must never be retroactively rewritten, +and source checks must not be skipped to obtain a green release check. + +A green draft paper build or an updated workflow hash does not supersede +this distinct archive-reproducibility gate. + + +## PR113 successor-tooling preflight (candidate, not historical certification) + +`verification/research/pr113_tooling_migration.py` compares the immutable +historical tooling T, release base B, current candidate H and every packaged +v0.2.0 tooling byte. It fails if the historical archive snapshot changes or +if any canonical verifier beyond the two named integrity profile files differs. +The 25-workflow successor inventory is bound to H; a distinct read-only test +exercises extra-tooling, archive-tampering, missing-workflow and wrong-HEAD +adversaries. + +The resulting `Permansson.PR113.ToolingMigrationProposal.v1` is deliberately +`DRAFT_NEEDS_OWNER_AND_INDEPENDENT_SECURITY_REVIEW`. Its GitHub artifact is +not a signed migration, an approved new T, a reason to alter historical v0.2.0 +evidence, or a mathematical release authorization. The old v0.2.0 offline +job remains an independently enforced gate until an owner and an independent +security reviewer authorize the successor release pathway. diff --git a/docs/operations/pr113_R01_R05_repair_handoff_20261024.md b/docs/operations/pr113_R01_R05_repair_handoff_20261024.md new file mode 100644 index 00000000..12f4c2ae --- /dev/null +++ b/docs/operations/pr113_R01_R05_repair_handoff_20261024.md @@ -0,0 +1,118 @@ + +# PR113 R01-R05 repair handoff — source candidates, not acceptance + +Date: 2026-10-24 +Repository: HMarcusWH/PermanssonLean +PR: https://github.com/HMarcusWH/PermanssonLean/pull/113 + +All five repair work packages are coded as review-pending candidates. +Original 45 acceptance criteria, eleven new claims, 259 kernel source targets, +Lean 4.34.0, historical T archive/supplement and original 25-workflow +inventory were NOT altered by this batch. + +## R01 — Fast engineering QA + +Use a genuine clean checkout of final H: + + python3 verification/research/pr113_fast_engineering_preflight.py --head "$H" --out /external/pr113/fast-qa + +The QA checks Python source ASTs and 17 focused regression suites, including +the new source-control producer v3.1 tests. This is not the original required +Research Lean suite or historical archive approval. + +## R02 — Stable TeX stage and no cache contamination + +The original two source roots are still separately cloned with no hardlinks. +The sequential builder now creates the same absolute fresh _canonical_tex_stage +path for each run. After draft verification the complete generated directory +is moved intact to first/ or second/, and a remaining stage is rejected. +This avoids path-dependent TeX line wrapping without stripping log bytes. + + python3 release/mathematical-closure-1/reproduce_clean_sources.py --source-head "$H" --out /external/pr113/clean-paper + +A standalone local pdfTeX smoke passed identical PDF/log bytes, but final-H +and postmerge-M manuscript SHA256, ZIP, manifest and page review remain pending. +This README modification intentionally triggers the existing clean-paper +workflow path filter at the new exact H. No workflow file was modified. + +## R03 — Actual historical A2 Python execution profile + +The original A2 compiler script was introduced at commit +f71a76032b169aac7bfe0694aa715339a94d4e05 (not at the earlier +674c1b55 inherited source baseline). The dual-run candidate now: +- authenticates historical source and support Git blobs and clean checkout; +- requires the original historical executable blob from the introducing commit; +- launches the executable and support from the HISTORICAL checkout in its + own Python process, with source PYTHONPATH contamination removed; +- verifies that execution's own actual source/commit/tree receipt; +- then separately runs current H A2 successor with real positive and four + Lean semantic false mutants. + + python3 verification/research/pr113_dual_a2_compiler.py --historical-source /real-A2-original --historical-head "$A2_HISTORICAL_SHA" --current-source /real-H --current-head "$H" --out /external/pr113/dual-A2 + +A successful successor remains unreviewed; it cannot disable the mandatory +inherited original A2 Research Lean check. Versioned CI migration requires +owner and independent security authorization. + +## R04 — Original T/H dual archive security report + +Historical unmodified T is replayed from its own real clean checkout in an +actual isolated nonroot network namespace. Current H separately runs every +original workflow-integrity adversarial method. No Python source import path +can substitute H's verifier for the historical one. + + python3 verification/research/pr113_dual_tooling_candidate.py --historical-T-source /real-T --current-source /real-H --head "$H" --output /external/pr113/dual-T-H + +The detached T2_security_review_candidate.json now retains immutable T/H +Git source blobs for two known changed canonical tooling files, the frozen +historical manifest hash, both complete original report SHA256 digests and +25 original workflow inventory entries. All approval fields remain false. +The current required archive E_TOOLING_CLOSURE is not silently waived. + +## R05 — Extra authenticated source-control CI producers v3.1 + +New files: +- verification/research/final_closure_extra_producers_v31.py +- verification/research/test_final_closure_extra_producers_v31.py +- research/paper_closures/post_merge_112/additional_control_producers_v3_1.schema.json + +The original 21 GitHub producing artifacts (4 compiler, 9 appended-source +semantic, 8 legacy observation) remain mandatory without rewriting v2 or v3. +The additional versioned v3.1 contract covers **76 source-control owners**: +45 original role-selected positives, 20 original legacy source-appended +negatives and 11 claim-specific positive-and-negative PAIRS (22 directions). + +Each new producer must be a genuinely reviewed source-controlled workflow +with authentic GitHub run attempt, job ID, check-run ID, original artifact +SHA256 and byte-for-byte owner index and receipt in the original ZIP. Wrong +head, borrowed artifact, missing mathematical owner, invented review or +changed source/workflow Git blob must fail. This contract does NOT install +a new workflow or accept any local declaration of independent approval. + +After owner-approved producing workflows genuinely run, provide original +strict-v3 receipt JSON/ZIP, additional v3.1 contract JSON, original artifact-ID +ZIP files, and exact proposed workflow Git blob SHA JSON: + + python3 verification/research/final_closure_extra_producers_v31.py --head "$H" --original-v3-receipts /external/strict-v3.json --original-v3-zip /external/strict-v3.zip --additional-contract /external/v3_1-controls.json --additional-zips /external/control-artifacts --reviewed-workflow-blob-candidates /external/workflow-blobs.json --output /external/v3_1-unreviewed.json + +Even a genuinely source-attributed output remains strictly unreviewed: +security approval false, 0/45 cases accepted, 0/11 claims accepted, +no merge or mathematical release. + +## R06-R11 still require external execution/authorization + +First get required Research/Archive green through genuinely reviewed +historical trust migration. Freeze B/H/S, get twelve exact-H required checks, +and all 4+9+8 original CI artifacts and 259 Lean type/axiom receipts. +Execute and authenticate original 45 positive test routes and 45 negatives +(including twenty source-appended legacy replays), plus eleven genuine +claim-specific positive/false semantic controls. Independent nonauthor +mathematical reviewers must accept 45/45 and 11/11 against EXACT H and +original criterion wording. No claims can be inferred from generic CI. + +Only owner-authorized genuine merge M permits 12 exact-M checks, independent +PDF/page scholarly review, matching two-root PDF/ZIP manifests, candidate +seal, independent authorized external postseal replay and explicit publication. + +Stop if any required check/source SHA differs; never substitute a successful +mock, unapproved status flag, borrowed old-head artifact or synthetic review. diff --git a/docs/operations/pr113_implementation_scope_and_certification_handoff.md b/docs/operations/pr113_implementation_scope_and_certification_handoff.md new file mode 100644 index 00000000..5bb8bdf2 --- /dev/null +++ b/docs/operations/pr113_implementation_scope_and_certification_handoff.md @@ -0,0 +1,73 @@ +# PR #113 implementation scope and certification successor handoff + +**Status:** implementation contract / work in progress; **not** a mathematical, security, merge, or publication certificate. +**Repository:** `HMarcusWH/PermanssonLean` · **PR:** [#113](https://github.com/HMarcusWH/PermanssonLean/pull/113) +**Decision recorded:** 25 October 2026. This is a sequencing/scope decision, **not** an approval of final source or modified workflows. +**Starting reviewed head:** `52f00c9ecb60219da31e7ee6780f6c5debfb5b87`; refresh GitHub identity for every later phase. +**Original base:** `17c315f7df7abf457eb94d393bd526d46da6f6f4`. + +## The boundary + +> **#113 finishes proofs, implementation and verification infrastructure. A successor PR (CERT) runs genuine full mathematical acceptance and an independently reviewed publication process.** + +A missing proof is **not** merely a missing acceptance vote. If it is promised in #113, implement and label it with its exact Lean hypotheses, or obtain an explicit owner decision identifying an unresolved research deferral. A deferred required theorem remains a blocker for complete certification. + +Immutable historical authority includes the original 45 cases, 11 claims, 259 baseline declaration inventory (additions must be versioned, not substituted), accepted supplement/archive bytes, Lean/pins and original `PermanssonLean/` definitions. Historical T and A2 identities, checks and reports are never silently rewritten. + +## Separate gates and roles + +| Gate | Meaning | Cannot imply | +| --- | --- | --- | +| Implementation and infrastructure readiness | All claimed source implementations compile; integration, required CI, historical dual verifiers and independent security/engineering review pass | Independently certified mathematical results or right to publish | +| CERT mathematical acceptance | Actual source-bound proof/test receipts + authenticated independent 45/45 and 11/11 decisions | Permission to merge or publish | +| Release acceptance | Real approved CERT merge + two clean builds + scholarly/PDF review + exact archive bytes and independent postseal replay | Permission to publish without separate owner authorization | + +The builder or coding agents implement. The independent engineering/security reviewer reviews the final combined trust surface. Genuine nonauthor mathematical and scholarly reviewers make corresponding source-bound decisions. Marcus retains separate authority for scope, workflow activation, each merge and publication. Source-owned `approved:true` flags and synthetic tests never create an external decision. + +## #113: ordered implementation checklist (all unchecked until evidenced) + +- [ ] **113-01 — Scope and handoff:** PR title/body and this document agree; preserve the 45/11 obligations, old provenance and draft paper. This step documents scope only. +- [ ] **113-02 — Deterministic integration repairs:** pass `original_producers=authentic_original` and `original_producers=from_original` to the two public `validate_executed_controls` callers; correct the staged compiler `expected_job` to `compiler-shard`; prevent inner transport status overwriting preseal's outer status; test through real local validators and real committed manifest. +- [ ] **113-03 — Distinct infrastructure acceptance:** new typed engineering acceptance must require source identity, historical/security results and genuine review; every mathematical/seal/release gate rejects this receipt as certification. +- [ ] **113-04 — Versioned successor target:** separate original corpus, infrastructure baseline and CERT target. Verify actual PR/base/branch/source/run IDs without self-referential future hashes in frozen source. Old receipts stay historical. +- [ ] **113-05 — Mathematical source crosswalk:** all 45 original cases and 11 claims map to exact compiled theorem, hypotheses, positive/negative controls and separately recorded implementation/acceptance status. Inspect Q-05 real domination versus any still-unproved exact rational equality; preserve 259 baseline proof requirements. +- [ ] **113-06 — T→T2 and A2 security migration:** run untouched historical verifiers in their original isolated historical checkouts and successor verifiers separately, including both A2 positive controls and four genuine semantic mutations. Retain reports and immutable baselines. +- [ ] **113-07 — Runtime reviewer-input delivery:** producers → authenticated collection → observation survey → independently reviewed 20 legacy-event choices → replay. Choices must be detached, source-bound and authenticated; missing reviewer input must block. +- [ ] **113-08 — Complete workflow configuration:** prepare coherent pinned actions, permissions, job names, workflow inventory, dependencies, source-target semantics, and distinct dev versus explicit certification execution. Do not activate protected changes yet. +- [ ] **113-09 — Integrated release dry run:** source-faithful draft → complete synthetic evidence → strict fail-closed checks → candidate ZIP + SHA256 sidecar → independent synthetic replay. No fake release credentials or approvals. +- [ ] **113-10 — Reproducible truthful draft:** align manuscript to theorem crosswalk, build twice from clean source roots, compare required artifacts and inspect every page; no scholarly signoff claim. +- [ ] **113-11 — Actual security approval and activation:** present final combined T/T2+A2+CI/release profile to authorized independent security/engineering reviewer; obtain separate owner activation authorization and verify protected rules with admin access. Activate only exact approved bytes. +- [ ] **113-12 — Final-source freeze and full checks:** focused Python/regressions, full pinned Lean/axioms, historical/engineering 12 required contexts, entire Research workflow including C10, D0, default build, import coverage and firewall. No skipped/queued/cancelled run counts as success. +- [ ] **113-13 — Authorized merge and handoff:** request separate owner merge approval. Merge real #113, record `M113`, verify applicable merged-source CI, retain source/tree/receipts/limitations. **Not a mathematical release.** + +Implementation dependency guidance: 113-02 follows scope; 113-03→04 follows repairs; 113-05 can proceed independently; 113-06/07 after target and provenance design; 113-08 after migrations/transport; 113-09 after executable inputs/workflows; 113-11 only after combined implementation and independent security review; 113-12/13 only after activation/freeze/owner decisions. + +### Critical 113-02 source locations + +- `verification/research/pr113_mandatory_v31.py` → `authenticate_all_76` +- `verification/research/final_closure_extra_producers_v31.py` → `assemble_review_pending` +- `verification/research/pr113_v31_execution_integrity.py` → real callee `validate_executed_controls` +- `docs/operations/pr113_unapproved_workflows/trigger_candidates/manifest.json` → staged `compiler-shard` +- `verification/research/pr113_original_producer_trigger_candidate.py` → check real manifest and original pinned blobs +- `verification/research/pr113_preseal_input_carrier.py` and staged `pr113-preseal-fullscope.yml` → outer status and transport diagnostic agreement + +## CERT: ordered successor checklist (do not open or pre-approve early) + +- [ ] **CERT-01:** After genuine `M113`, open a *new draft* PR based on main containing it; register GitHub's assigned PR number, not #114 (already closed/merged). +- [ ] **CERT-02:** Authorize genuine mathematical and scholarly reviewers, all 45+11 exact scopes, providers, assumptions and positive/negative selections. +- [ ] **CERT-03:** Freeze actual `Hcert`; prove target/base/source identity; store runtime identities only in detached records. +- [ ] **CERT-04:** Execute/authenticate **21 original producers** (4 compiler + 9 direct semantics + 8 legacy observations); collect **259 baseline** declaration receipts plus approved additions for `Hcert`. +- [ ] **CERT-05:** Obtain 20 authentic independent event selections from the real legacy survey; bind to producing observations and source. +- [ ] **CERT-06:** Run and authenticate **45 original positives, 45 original semantic negatives (25 direct + 20 legacy), 11 new claim positive/negative pairs and 76 additional control owners** with genuine run/job/check/artifact provenance. +- [ ] **CERT-07:** Obtain case-by-case independent mathematical acceptance for **45/45 and 11/11**; pass strict source-bound mathematics gate. +- [ ] **CERT-08:** Obtain separate owner approval to merge CERT; record actual `Mcert`, verify reviewed source ancestry/tree and required merged CI. +- [ ] **CERT-09:** Rebuild complete final paper twice from independent clean `Mcert` source roots; independently inspect and approve every PDF page, equation, reference and limitation. +- [ ] **CERT-10:** Produce actual full-scope preseal, candidate archive, checksum sidecar and independently replay the **exact candidate ZIP bytes**. Synthetic replay is insufficient. +- [ ] **CERT-11:** Obtain independent **owner publication authorization for that exact archive**, then publish precisely the checked bytes and preserved signed/attributed source records. + +## Status bookkeeping + +- `B113` is the pinned original base. `H113` changes until final infrastructure freeze. `M113` does not exist until authorized merge. +- `CERT` is a placeholder name. The next GitHub number, `Bcert`, `Hcert` and `Mcert` cannot be invented in #113. +- Requirements, source implementation, local test pass, CI evidence, independent acceptance, merge permission, and publication are separate columns in any generated ledger. +- **This document adds no independent review, security signoff, mathematical acceptance, merge authorization or publication authorization.** diff --git a/docs/operations/pr113_original21_frozen_head_dispatch_contract.md b/docs/operations/pr113_original21_frozen_head_dispatch_contract.md new file mode 100644 index 00000000..86e5fb8b --- /dev/null +++ b/docs/operations/pr113_original21_frozen_head_dispatch_contract.md @@ -0,0 +1,16 @@ +# PR #113: original 21-producer frozen-head dispatch compatibility + +Status: **source-side engineering candidate, not activated or approved**. + +The three unchanged protected original producer workflows (4 compiler, 9 original semantic and 8 legacy semantic shards) declare `workflow_dispatch` alongside path-filtered `push`. The reviewed 25→30 migration intentionally retains their original Git workflow bytes, so its exact 10-path activation cannot guarantee that all 21 producer jobs are naturally triggered at the final H. + +The original source collector previously required `run.event == "push"`, which rejected a genuine explicit frozen-H dispatch even though the original workflow provided that interface. The corrected contract accepts only `push` or `workflow_dispatch`, and **both** must be authentic GitHub Actions results bound to the exact PR113 branch/head, expected original workflow, completed successful run and selected attempt/job/check/artifact IDs and ZIP digests. Other event types, stale H, wrong workflow and failed/partial evidence remain rejected. + +For PR #113, run nonauthorizing trigger/collector/consumer rehearsals, not purported original21 acceptance. GitHub manual dispatch requires the workflow to exist on the default branch; at original base B the retained compiler workflow is absent from main, so feature-branch-only dispatch cannot work. After a separately authorized M113 registers the protected producers on main, the successor CERT may dispatch them at its frozen head, checking actual returned branch/ref, SHA, run/attempt, jobs, checks and all 4+9+8 artifacts. Path-filtered push may not trigger. Do not manufacture commits/IDs or conflate original21 with historical T/A2. + +Independent R08 review and separate owner R09b workflow activation remain prerequisites. This implementation does not dispatch workflows or perform an activation. + + +## Successor source identity contract + +At the genuine frozen Hcert run, the versioned producer consumers read `PERMANSSON_CERT_PR_NUMBER` as an **untrusted runtime lookup hint**. Set it to the actually assigned post-M113 successor PR number only after main registers the retained producer workflows. The consumer checks the exact GitHub producer event (`GITHUB_SHA`, `GITHUB_REF`, `GITHUB_REPOSITORY`), the live CERT PR head/base/branch, and the actual ordered M113 ancestry. Missing, stale, guessed or old #114 PR numbers fail. This variable is neither independent review nor authorization for mathematical acceptance. diff --git a/docs/operations/pr113_post2fa_scope_reconciliation_2026-10-26.md b/docs/operations/pr113_post2fa_scope_reconciliation_2026-10-26.md new file mode 100644 index 00000000..0e3e59bf --- /dev/null +++ b/docs/operations/pr113_post2fa_scope_reconciliation_2026-10-26.md @@ -0,0 +1,13 @@ +# PR113 source-side scope reconciliation (26 October 2026) + +**Evidence head:** `2fa4901cf8e2efd254f753f7dc277eca8f60f471` (read-only audit). **Not** an activation, reviewer approval, accepted release or merge authorization. + +The deterministic R01-R04 repairs, 21-original-producer argument forwarding, `compiler-shard` manifest and outer preseal status repair already existed at the audit head. Do not reapply them without a current reproduced defect. + +The manuscript now distinguishes compiled Q--05 all-state atom domination and original-margin upper bound from unproved exact candidate margin values; the D--09 mixed-initial integral and false-barycenter tests are similarly distinguished from independent 45/11 acceptance. + +R05 and R07 still require a complete connected public producer→schema→carrier→consumer→candidate ZIP/sidecar→independent-replay rehearsal. Synthetic data and mocked GitHub only prove engineering compatibility; genuine CI/artifact provenance and actual independent review are different evidence. Never turn an infrastructure receipt into mathematical or publication acceptance. + +Both `infrastructure_reviewer_authority.json` and `legacy_event_reviewer_authority.json` were `UNCONFIGURED` at the audit head. Committing genuine owner-approved reviewer rosters changes H: either configure them *before* definitive R08 security review or repeat that review after the roster commit. Do not invent reviewer identity or authorization. + +Keep 25 active protected workflows unchanged until separately authorized R08–R10 security review and atomic activation. Authentic historical T/T2/A2 evidence, exact 12 required contexts, owner merge decision, postmerge CI, and later mathematical CERT remain separate. diff --git a/docs/operations/pr113_postlog_implementation.md b/docs/operations/pr113_postlog_implementation.md new file mode 100644 index 00000000..82726f7d --- /dev/null +++ b/docs/operations/pr113_postlog_implementation.md @@ -0,0 +1,317 @@ +# PR113 post-log implementation handoff + +**State:** source-engineering candidates implemented. Historical security +authorization, exact-H CI evidence and independent mathematical/scholarly +approval are NOT complete. Do not merge or publish. + +The original 45 cases, eleven claim IDs (Q1 Q2 Q3 M1 M2 M3 N1 N2 N3 D8 +D9), 259 compiler targets, four A2 proof blobs and historical T supplement +remain the controlling scope. None of the code below creates review receipts. + +## Implemented entrypoints + +| Work | Source entrypoint | Result | +|---|---|---| +| W0 | verification/research/pr113_release_preflight.py | Detached B/H/T, 45/11/259 source identity snapshot, unreviewed | +| W1 | verification/research/pr113_dual_tooling_candidate.py | Actual original-T isolated offline restore and separate H workflow integrity candidate; owner/security signoff pending | +| W2 | verification/research/pr113_dual_a2_compiler.py | Separate real historical A2 and PR113 successor Lean, signatures, positive/false mutants; not substituted into original required test | +| W3 | verification/research/final_closure_assemble_v3.py | Original 21 producing ZIPs, actual 259 proof logs, 25 append-source true/false controls; 20 legacy/11 claim-control gaps reported | +| W3 | verification/research/final_closure_batch_legacy_replay.py | Batch actual source-appended Lean replays for 20 explicitly selected original legacy cases; new CI attribution still required | +| W4 | verification/research/final_closure_producer_bytes.py + final_closure_case_receipts.py | Authentic original job/check/archive/member SHA, typed v3 source evidence, no self-approval | +| W5 | research_papers/mathematical_closure/preamble.tex + reproduce_clean_sources.py | pdfTeX trailer determinism and honest mismatched-PDF diagnostics; independent final-M check pending | +| W6 | verification/research/final_closure_reproduction_auth.py | Live source-M GitHub clean build + retained two-root PDF, pages, manifest and archive byte authentication | +| W6 | verification/research/final_closure_postseal_auth.py | A future independently security-approved postseal job and original candidate ZIP verifier; workflow not yet installed | +| W7 | verification/research/pr113_final_source_readiness.py | Fail closed on less than 12 checks, 21 producing artifacts, 259 types or 45/11 individually owned controls | +| W8 | Actual independent reviewer authority and real M | Still outstanding; cannot be simulated by CI or author-created JSON | + +The original historical verifier, release supplement, dependency pins and +accepted mathematical source modules have not been modified in this series. + +## Step 1 — Source checkpoint and original T/H historical boundary + +In an exact-H, clean Git checkout, write baseline outside the repository: + + python3 verification/research/pr113_release_preflight.py \ + --head "$H" --output /external/pr113/source-baseline.json + +On an eligible nonroot isolated Linux host with original clean historical T +checkout and distinct clean H checkout, the two-leg archival candidate is: + + python3 verification/research/pr113_dual_tooling_candidate.py \ + --historical-T-source /checked-out-T \ + --current-source /checked-out-H --head "$H" \ + --output /external/pr113/dual-tooling + +Running original T without genuine network isolation fails; running the +standalone historical replay inside GitHub Actions without source/event +binding is expressly prohibited. A green local candidate is not +authorization to replace the historical archive workflow. + +Original historical A2 source checkout with original unchanged verifier +must also be run separately from the current PR113 successor checker: + + python3 verification/research/pr113_dual_a2_compiler.py \ + --historical-source /checked-out-valid-original-A2 \ + --historical-head "$HISTORICAL_A2_SHA" \ + --current-source /checked-out-H --current-head "$H" \ + --out /external/pr113/dual-A2 + +Both paths run actual pinned Lean theorem signatures, axiom checks, positive +fixture and all four false semantic mutants. Security authorization is +pending; the existing required old A2 test and T/C archive gate must never +be hidden, skipped or marked green by local diagnostic JSON. + +## Step 2 — Only after code stabilizes: 4+9+8 exact-H producing jobs + +A final H requires all twelve exact-H required contexts genuinely green. +The four all-case compiler shards may require explicit manual +workflow_dispatch due restrictive push-path filters. + +Download all 21 *original* GitHub Actions ZIPs before expiry and authenticate +actual run attempts, job/check IDs and GitHub authoritative artifact SHA256. +Provide final_closure_producer_catalog.py an external JSON with format +Permansson.PR113.ExternalProducerCatalog.v1, head_sha H and 21 artifacts +entries containing kind, shard, run_id, artifact_id, zip_file. + + python3 verification/research/final_closure_producer_catalog.py \ + --head "$H" --catalog /external/pr113/catalog.json \ + --output /external/pr113/catalog-output + +For the versioned v3 assembler, retain the identical ZIP bytes in a +directory as exact artifact_id.zip filenames: + + python3 verification/research/final_closure_assemble_v3.py \ + --head "$H" --synthetic "$S" \ + --catalog-dir /external/pr113/catalog-output \ + --producer-zips /external/pr113/producer-ids \ + --output /external/pr113/v3-preparation + +The output consists of unreviewed_v3_receipts.json, unreviewed_evidence.zip +and remaining_work.json. The v3 statement SHA means the complete +kernel-elaborated Lean type digest, not an invented lexical source-header +hash. Only 25 truly observed append-case controls can be prefilled; +20 legacy cases and all eleven claim-specific controls still need genuine +new positive/negative evidence. + +## Step 3 — Twenty actual original legacy source replays + +Supply an externally reviewed selection JSON with format +Permansson.PR113.LegacyReplaySelection.v1 and exactly twenty cases entries. +Each must contain case_id, test_index, test_method, original_provider, +observation_id and producing_artifact_id, drawn from the authentic +legacy-shard recorded named-method observations. No model or test may guess +the relevant mathematical original source provider. + + python3 verification/research/final_closure_batch_legacy_replay.py \ + --head "$H" --catalog-dir /external/pr113/catalog-output \ + --producer-zips /external/pr113/producer-ids \ + --selection /external/pr113/legacy-selection.json \ + --out /external/pr113/legacy-real-lean + +Each actual compile/mutation bundle is retained. The batch result has no +new CI check-run ID, acceptance decision or reviewer; it requires separate +authentic producing CI execution and independent mathematical evaluation. + +## Step 4 — Exact-H readiness, independent reviews, genuine M + + python3 verification/research/pr113_final_source_readiness.py \ + --head "$H" --synthetic "$S" \ + --receipts /external/pr113/v3-preparation/unreviewed_v3_receipts.json \ + --bundle /external/pr113/v3-preparation/unreviewed_evidence.zip \ + --out /external/pr113/readiness.json + +Expected to FAIL while any original case or claim lacks its own positive and +negative control, or any required check is red/missing. A green readiness +result is unreviewed source evidence, NOT mathematical acceptance. + +After real owner authorization and independent reviewer disclosures: +review 45/45 historical case premises, all eleven claim statements, +theorem source types, axioms and semantic negative relevance. Preserve the +P-06 deterministic phase-blind limitation and D-09 mixed-initial +nonlinear-expectation versus barycenter distinction. Bind actual independent +GitHub approvals to H, evidence bundle SHA and reviewed payload SHA. + +Only after premerge-full is truly satisfied can the owner authorize a +genuine ordered [B,H] Git merge M with identical synthetic tree. Then rerun +all twelve checks on M, rebuild PDF twice, inspect every PDF page and obtain +independent scholarly signoff for exact M/PDF/claim wording. + +## Step 5 — Real M reproduction and final postseal authentication + +The merged-M clean paper artifact must come from a real successful +clean-paper-rebuild job on main with name pr113-clean-paper-rebuild-draft. +The strict preseal gate consumes its original ZIP and an external JSON of +run_id, run_attempt, job_id, check_run_id and artifact_id. Provide the pair: + + --reproduction-zip /external/pr113/real-M-clean.zip \ + --reproduction-ids /external/pr113/real-M-clean-ids.json + +final_closure_reproduction_auth.py checks original GH artifact SHA256, +run/job/check, both clean source-root PDF, log, archive, manifest and all +rendered pages. With true independent mathematical/scholarly review this +may establish POSTMERGE_PRESEAL_READY. seal_approved.py --full emits only +Permansson_Mathematical_Closure_CANDIDATE.zip. + +Full-release-strict additionally requires a separately authorized exact-M +pr113-postseal-replay.yml workflow_dispatch, job postseal-archive-replay, +and GitHub artifact pr113-complete-postseal-candidate: + + --postseal-zip /external/pr113/real-M-postseal.zip \ + --postseal-ids /external/pr113/real-M-postseal-ids.json + +The future producer workflow is intentionally **not added** to the +security-pinned 25-workflow inventory; it needs independent security +authorization first. The postseal authenticator replays all nested source, +PDF, page, archive and sidecar bytes. A local replay cannot self-award a +COMPLETE_ARCHIVE_REPLAY receipt. No current code may fabricate reviewers, +security approval, actual M, publication/tag or empirical Exact GR/PR proof. + +## Stop conditions + +Do NOT merge/tag/release while historical E_TOOLING_CLOSURE or original A2 +source gate fails; code head H changes after evidence or review; main has +advanced; any of 12 CI checks fail; any of 21 original producing ZIPs is +missing/stale; any of 259 Lean declarations or 45+11 controls is missing; +historical source/premise boundaries change without review; independent +45+11 mathematical approval, exact M, PDF page review, two-clean-build +evidence or authenticated postseal replay is absent. + + +## October 24 complete engineering implementation: final coding additions + +**All changes below are in the draft PR only.** Security authorization, +independent mathematical acceptance, scholarly publication and merge permission +are not supplied by coding or a green candidate workflow. The original A2 +and T archived trust check remain fail-closed until reviewed migrations. + +### Immediate resolved source/test defects + +- test_pr113_a2_successor_identity.py now models the actual + git_text(repo, *argv) API, tests valid exact H and synthetic S, reversed + parents and wrong source tree. Original verifier untouched. +- build.py now sets relative TEXINPUTS and BIBINPUTS and copies byte-identical + references.bib to the BibTeX working directory. The real earlier PDF-byte + comparison found both PDFs and all eight page PNGs equal; only the TeX log + hash differed. The old absolute TEXINPUTS caused path strings to be split + at the TeX output line width, defeating naive replacement. The new input + configuration avoids producing unstable absolute input paths. +- reproduce_clean_sources.py reports exact field-level manifest differences, + first differing normalized/raw log byte and line, raw ZIP member SHA and + metadata, page PNG digest changes and SHA256SUMS. It does **not** normalize + scientific diagnostics or waive any raw output difference. The actual new + two-root clean-paper CI pass still must be verified at final H and M. + +### New source-bound execution inputs for all 45 original cases + +Code: verification/research/final_closure_positive_route_candidate.py + +The caller must supply an external JSON document of format +Permansson.PR113.OriginalPositiveSelection.v1 containing **45** cases. +Each entry uses exactly: + + case_id + test_path + test_class + test_method + proof_source_path + +The selected named path and method must appear in the immutable original +case registry as COMPILER_SOURCE or EXACT_CHECK. A semantic-negative method +cannot impersonate an admissible positive. The proof provider must appear in +that case's historical declarations. The runner checks the actual Git source +bytes, executes exactly one real unskipped named test, compiles the provider +with pinned Lean 4.34.0, records raw diagnostics and rechecks both sources. + + python3 verification/research/final_closure_positive_route_candidate.py \ + --head "$H" --selection /external/positive-45.json \ + --out /external/pr113/actual-positive-45 + +A local success is an **unreviewed execution candidate**, with NO real +producing check-run ID, no independent case review and no acceptance result. +It must subsequently be executed and attributed via a genuinely approved +CI producer before producing strict original-case v3 receipts. + +### New source-bound controls for all eleven claims + +Code: verification/research/final_closure_claim_controls_candidate.py + +Provide a detached JSON with format +Permansson.PR113.ClaimControlSelection.v1 containing exactly eleven +claim entries, each with: + + claim_id + proof_source_path + positive_test_path + positive_test_class + positive_test_method + negative_test_path + negative_test_class + negative_test_method + +Every selected path must be in the corresponding immutable claim's source +registry; positive and negative named methods are distinct. Separately +provide **one explicit, claim-relevant append-only false Lean file** per ID +under an external directory, exactly Q1.lean, Q2.lean, Q3.lean, M1.lean, +M2.lean, M3.lean, N1.lean, N2.lean, N3.lean, D8.lean and D9.lean. + + python3 verification/research/final_closure_claim_controls_candidate.py \ + --head "$H" --selection /external/claims-11.json \ + --mutants-root /external/claim-false-sources \ + --out /external/pr113/actual-claim-controls + +The runner uses the genuine exact-H theorem provider bytes, executes both +selected named tests, compiles true and false Lean sources, rejects nonsemantic +parser/import/resource errors, retained additions of imports/axioms/sorry, +and stores every original input and log digest. No mathematical false theorem +is guessed, and no case label or reviewer/CI stamp is fabricated. + +### Strengthened source-acceptance readiness + +Code: verification/research/final_closure_v3_source_readiness.py and +pr113_final_source_readiness.py. + +The former rechecks all 259 exact original Lean theorem types, real producing +check/ZIP member bytes, positive and negative compiler outcomes, all source +test paths and exact original case method roles. Every case must have a +distinct COMPILER_SOURCE/EXACT_CHECK positive and SEMANTIC_NEGATIVE negative +test, and every claim must satisfy its complete path/declaration set and its +selected positive and negative test-source paths. Unrelated extra methods +remain invalid even alongside a legitimate route. + +The readiness predicate is NOW explicitly separated into +label_coverage_only and candidate_controls_complete. A label-only complete +mock NEVER creates full source readiness. A complete source candidate remains +review pending and cannot set mathematical approval. + +### Local fast engineering feedback + +Code: verification/research/pr113_fast_engineering_preflight.py + +When at an exact clean checkout H, run: + + python3 verification/research/pr113_fast_engineering_preflight.py \ + --head "$H" --out /external/pr113/fast-qa + +It checks actual Python AST for every research and release module and runs +sixteen separate targeted adversarial test modules (not the real source +compiler integration). Reports/logs are detached with hashes. This is +**fast engineering QA only** and never substitutes for required Research +Lean, A2, original historical archive, 21 source jobs, mathematical reviews +or postseal CI. No protected workflow changes were made. + +### Current execution/approval blockers + +- The required original A2 test still enforces the historical twelve + protected provider blobs; new successor compilation does not supersede it. +- The historical T/C archive verifier still requires owner-approved and + independently security-reviewed versioned T2 migration. +- Final H is not frozen for independent reviews; all twelve required check + contexts and all 4+9+8 original producer artifacts must be green at the + SAME final H before evidence is assembled. +- The 45 positive and 20 legacy negative executions and the eleven + claim-specific false sources need real choices, real execution, authorized + producing job identities and independent semantic relevance reviews. +- reviewer_authority.json remains unconfigured until actual owner + authorization, and no independent 45/11 mathematical reviews have been + recorded. No real M, final PDF page approval or postseal workflow has + been authorized. No merge, tag or publication. diff --git a/docs/operations/pr113_required_gate_replacement_unapproved/full_workflow_manifest.json b/docs/operations/pr113_required_gate_replacement_unapproved/full_workflow_manifest.json new file mode 100644 index 00000000..45321ae6 --- /dev/null +++ b/docs/operations/pr113_required_gate_replacement_unapproved/full_workflow_manifest.json @@ -0,0 +1,39 @@ +{ + "format": "Permansson.PR113.FullRequiredWorkflowPreactivation.v1", + "status": "SOURCE_STAGED_NONEXECUTABLE_AWAITING_COMPLETE_SECURITY_REVIEW", + "original_repository": "HMarcusWH/PermanssonLean", + "original_workflow_count": 25, + "proposed_final_workflow_count": 30, + "old_workflows": [ + { + "path": ".github/workflows/research-lean.yml", + "git_blob_oid": "06842a2da34d6ff06434f202a7630edd57e8cb20" + }, + { + "path": ".github/workflows/research-integrity.yml", + "git_blob_oid": "19bab92285ab2c981b0a1200c03024f7d022e31c" + } + ], + "full_replacements": [ + { + "target": ".github/workflows/research-lean.yml", + "staged_source": "docs/operations/pr113_required_gate_replacement_unapproved/research_lean_full.yml", + "git_blob_oid": "8e993121fdd0e278a2b9585f90dd73795058d938", + "installed": false, + "independent_reviewed": false + }, + { + "target": ".github/workflows/research-integrity.yml", + "staged_source": "docs/operations/pr113_required_gate_replacement_unapproved/research_integrity_full.yml", + "git_blob_oid": "76ab437ef4781e534b0bf7de79c646890dae1be7", + "installed": false, + "independent_reviewed": false + } + ], + "requires_main_PR_and_ordered_merge": true, + "independent_security_review": false, + "owner_activation_authorized": false, + "mathematical_acceptance": false, + "merge_authorized": false, + "publication_authorized": false +} diff --git a/docs/operations/pr113_required_gate_replacement_unapproved/manifest.json b/docs/operations/pr113_required_gate_replacement_unapproved/manifest.json new file mode 100644 index 00000000..4f208a93 --- /dev/null +++ b/docs/operations/pr113_required_gate_replacement_unapproved/manifest.json @@ -0,0 +1,42 @@ +{ + "format": "Permansson.PR113.UnapprovedRequiredGateTransition.v1", + "status": "PROPOSED_EXACT_SOURCE_REPLACEMENTS_PENDING_INDEPENDENT_SECURITY_REVIEW", + "target_pr": 113, + "baseline_original_workflow_count": 25, + "original_unchanged_sources": [ + { + "path": "verification/research/test_localized_prefix_compiler.py", + "git_blob_oid": "c0f49038ea9b590720c78be85968eb63f5ac820f" + }, + { + "path": ".github/workflows/research-integrity.yml", + "git_blob_oid": "19bab92285ab2c981b0a1200c03024f7d022e31c" + }, + { + "path": ".github/workflows/research-lean.yml", + "git_blob_oid": "06842a2da34d6ff06434f202a7630edd57e8cb20" + } + ], + "staged_replacements": [ + { + "target": "verification/research/test_localized_prefix_compiler.py", + "source": "docs/operations/pr113_required_gate_replacement_unapproved/test_localized_prefix_compiler.py", + "git_blob_oid": "89d1772038e0d6e07d095e44ddf4a2bb93c86ee4", + "installed": false, + "independent_reviewed": false + }, + { + "target": ".github/workflows/research-integrity.yml#archive-reproducibility", + "source": "docs/operations/pr113_required_gate_replacement_unapproved/research_integrity_archive_step.yml.inc", + "git_blob_oid": "476dc9764eb73a8a065c2c1a9be42c8c9d2850a5", + "installed": false, + "independent_reviewed": false + } + ], + "original_T_A2_unmodified": true, + "requires_independent_security_review": true, + "owner_activation_authorized": false, + "mathematical_acceptance": false, + "merge_authorized": false, + "publication_authorized": false +} diff --git a/docs/operations/pr113_required_gate_replacement_unapproved/research_integrity_archive_step.yml.inc b/docs/operations/pr113_required_gate_replacement_unapproved/research_integrity_archive_step.yml.inc new file mode 100644 index 00000000..476dc976 --- /dev/null +++ b/docs/operations/pr113_required_gate_replacement_unapproved/research_integrity_archive_step.yml.inc @@ -0,0 +1,19 @@ +# UNAPPROVED R3 archive job replacement step, for independent review only. +# Original archive adversaries and separate source-preservation/export jobs stay. +# The original historical T verifier still executes in its own unchanged +# historical Git checkout in an independently checked producing job. +# The event inputs must be collected from authentic GitHub run/job/check and +# artifact bytes. An unreviewed/absent reviewer is a hard failure. + - name: Verify reviewed original dual T/T2 + A2 source + shell: bash + run: | + set -euo pipefail + test -s "$PERMANSSON_DUAL_HISTORICAL_EVIDENCE" + test -s "$PERMANSSON_DUAL_ORIGINAL_T_ZIP" + test -s "$PERMANSSON_DUAL_ORIGINAL_A2_ZIP" + python3 -B verification/research/pr113_required_dual_gate_v2.py \ + --evidence "$PERMANSSON_DUAL_HISTORICAL_EVIDENCE" \ + --historical-T-zip "$PERMANSSON_DUAL_ORIGINAL_T_ZIP" \ + --historical-A2-zip "$PERMANSSON_DUAL_ORIGINAL_A2_ZIP" \ + --out "$RUNNER_TEMP/pr113-historical-required-unreviewed.json" + test -s "$RUNNER_TEMP/pr113-historical-required-unreviewed.json" diff --git a/docs/operations/pr113_required_gate_replacement_unapproved/research_integrity_full.yml b/docs/operations/pr113_required_gate_replacement_unapproved/research_integrity_full.yml new file mode 100644 index 00000000..76ab437e --- /dev/null +++ b/docs/operations/pr113_required_gate_replacement_unapproved/research_integrity_full.yml @@ -0,0 +1,145 @@ +name: Research integrity + +on: + pull_request: + branches: [main] + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + frozen-source-preservation: + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Bind actual source context + run: python -B -m verification.integrity.ci_context --repo . --event-file "$GITHUB_EVENT_PATH" --report "$RUNNER_TEMP/context.json" + - name: Verify every protected Git object + run: python -B -m verification.integrity.preservation --repo . --candidate "$GITHUB_SHA" --report "$RUNNER_TEMP/preservation.json" + - name: Execute reference and preservation adversaries + run: | + python -B -m verification.integrity.run_tests --suite reference --report "$RUNNER_TEMP/reference-tests.json" + python -B -m verification.integrity.run_tests --suite preservation --report "$RUNNER_TEMP/preservation-tests.json" + python -B -m verification.integrity.run_tests --suite context --report "$RUNNER_TEMP/context-tests.json" + python -B -m verification.integrity.run_tests --suite workflow --report "$RUNNER_TEMP/workflow-tests.json" + - name: Preserve source and test reports + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: hardening-preservation-reports + path: | + ${{ runner.temp }}/context.json + ${{ runner.temp }}/preservation.json + ${{ runner.temp }}/*-tests.json + if-no-files-found: error + retention-days: 30 + release-export-integrity: + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Bind actual source context + run: python -B -m verification.integrity.ci_context --repo . --event-file "$GITHUB_EVENT_PATH" --report "$RUNNER_TEMP/context.json" + - name: Compare the complete isolated TeX export + run: python -B -m verification.integrity.tex_export --repo . --candidate "$GITHUB_SHA" --report "$RUNNER_TEMP/tex-export.json" + - name: Execute independent equation and import-isolation adversaries + run: python -B -m verification.integrity.run_tests --suite tex --report "$RUNNER_TEMP/tex-tests.json" + - name: Preserve export and test reports + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: hardening-export-reports + path: | + ${{ runner.temp }}/context.json + ${{ runner.temp }}/tex-export.json + ${{ runner.temp }}/tex-tests.json + if-no-files-found: error + retention-days: 30 + archive-reproducibility: + # Only this job receives additional READ permissions for artifact intake. + permissions: + actions: read + checks: read + contents: read + pull-requests: read + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Bind actual source context + run: python -B -m verification.integrity.ci_context --repo . --event-file "$GITHUB_EVENT_PATH" --report "$RUNNER_TEMP/context.json" + - name: Prepare the pinned replay dependency before isolation + run: python -m pip install -r verification/integrity/requirements.txt + - name: Execute archive adversaries + run: python -B -m verification.integrity.run_tests --suite archive --report "$RUNNER_TEMP/archive-tests.json" + # REVIEW PENDING: source IDs are supplied by an authorized administrator. + # The actual original GitHub producer must be successful and reviewed. + - name: Deliver genuine source-bound historical T and A2 artifacts + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + ORIGINAL_RUN_ID: ${{ vars.PR113_HISTORICAL_PRODUCER_RUN_ID }} + INDEPENDENT_REVIEW_ID: ${{ vars.PR113_SECURITY_REVIEW_ID }} + run: | + set -euo pipefail + [[ "$ORIGINAL_RUN_ID" =~ ^[1-9][0-9]*$ ]] + [[ "$INDEPENDENT_REVIEW_ID" =~ ^[1-9][0-9]*$ ]] + python3 -B verification/research/pr113_historical_runtime_v3.py \ + --producer-run-id "$ORIGINAL_RUN_ID" \ + --security-review-id "$INDEPENDENT_REVIEW_ID" \ + --out "$RUNNER_TEMP/pr113-historical-input" + test -s "$RUNNER_TEMP/pr113-historical-input/historical_required.json" + # Original T restoration remains mandatory in the separate historical + # producer; the original archive adversaries above still execute here. + - name: Verify genuine reviewed original T/T2 and A2 source + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + ORIGINAL_RUN_ID: ${{ vars.PR113_HISTORICAL_PRODUCER_RUN_ID }} + INDEPENDENT_REVIEW_ID: ${{ vars.PR113_SECURITY_REVIEW_ID }} + run: | + set -euo pipefail + python3 -B verification/research/pr113_historical_runtime_v3.py \ + --producer-run-id "$ORIGINAL_RUN_ID" \ + --security-review-id "$INDEPENDENT_REVIEW_ID" \ + --out "$RUNNER_TEMP/pr113-historical-input/historical_required.json" \ + --check-existing + cp "$RUNNER_TEMP/pr113-historical-input/historical_required.json" \ + "$RUNNER_TEMP/pr113-historical-required.json" + test -s "$RUNNER_TEMP/pr113-historical-required.json" + - name: Preserve archival restoration reports + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: hardening-archive-reports + path: | + ${{ runner.temp }}/context.json + ${{ runner.temp }}/archive-tests.json + ${{ runner.temp }}/pr113-historical-required.json + if-no-files-found: error + retention-days: 30 diff --git a/docs/operations/pr113_required_gate_replacement_unapproved/research_lean_full.yml b/docs/operations/pr113_required_gate_replacement_unapproved/research_lean_full.yml new file mode 100644 index 00000000..8e993121 --- /dev/null +++ b/docs/operations/pr113_required_gate_replacement_unapproved/research_lean_full.yml @@ -0,0 +1,141 @@ +name: Research Lean + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + actions: read + checks: read + contents: read + pull-requests: read + +jobs: + research: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Bind genuine required-check source + shell: bash + run: python -B -m verification.integrity.ci_context --repo . --event-file "$GITHUB_EVENT_PATH" --report "$RUNNER_TEMP/required-source.json" + - name: Build research library and audit its transitive axioms + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + # REVIEW PENDING: source IDs are supplied by an authorized administrator. + # The actual original GitHub producer must be successful and reviewed. + - name: Deliver genuine source-bound historical T and A2 artifacts + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + ORIGINAL_RUN_ID: ${{ vars.PR113_HISTORICAL_PRODUCER_RUN_ID }} + INDEPENDENT_REVIEW_ID: ${{ vars.PR113_SECURITY_REVIEW_ID }} + run: | + set -euo pipefail + [[ "$ORIGINAL_RUN_ID" =~ ^[1-9][0-9]*$ ]] + [[ "$INDEPENDENT_REVIEW_ID" =~ ^[1-9][0-9]*$ ]] + python3 -B verification/research/pr113_historical_runtime_v3.py \ + --producer-run-id "$ORIGINAL_RUN_ID" \ + --security-review-id "$INDEPENDENT_REVIEW_ID" \ + --out "$RUNNER_TEMP/pr113-historical-input" + test -s "$RUNNER_TEMP/pr113-historical-input/historical_required.json" + - name: Run research regressions and retain test-level diagnostics + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + python3 -B verification/research/pr113_regression_diagnostics.py --out "$RUNNER_TEMP/pr113-research-diagnostics.json" + - name: Preserve diagnostics even when regressions fail + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-regression-diagnostics + path: ${{ runner.temp }}/pr113-research-diagnostics.json + if-no-files-found: warn + retention-days: 30 + - name: Generate and independently verify C10 release proof evidence + if: github.event_name == 'pull_request' + run: | + python3 verification/research/c10_release_evidence.py "$RUNNER_TEMP/c10-release-evidence" --event-file "$GITHUB_EVENT_PATH" --repository "$GITHUB_REPOSITORY" + python3 verification/research/c10_verify_release_evidence.py "$RUNNER_TEMP/c10-release-evidence" --event-file "$GITHUB_EVENT_PATH" --repository "$GITHUB_REPOSITORY" + - name: Upload source-bound C10 proof evidence + if: github.event_name == 'pull_request' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: c10-release-evidence + path: ${{ runner.temp }}/c10-release-evidence/ + retention-days: 30 + if-no-files-found: error + - name: Generate and independently recheck D0-F golden reports + run: python3 verification/research/d0_golden.py "$RUNNER_TEMP/d0-finite-certificates" + - name: Upload ephemeral D0-F calculation evidence + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: d0-finite-calculation-evidence + path: ${{ runner.temp }}/d0-finite-certificates/ + retention-days: 7 + if-no-files-found: error + - name: Verify original default target still builds + run: lake build + - name: Ensure every research module is imported by the research root + run: | + python3 - <<'PY' + from pathlib import Path + + root = Path("PermanssonResearch.lean") + if not root.is_file(): + raise SystemExit("Missing research root module") + imports = set() + for line in root.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if line.startswith("import "): + imports.update(line[7:].split()) + modules = sorted(Path("PermanssonResearch").rglob("*.lean")) + if not modules: + raise SystemExit("No research modules discovered") + missing = [ + ".".join(p.with_suffix("").parts) + for p in modules + if ".".join(p.with_suffix("").parts) not in imports + ] + if missing: + raise SystemExit( + "Research modules must be imported directly by the root: " + + ", ".join(missing) + ) + print(f"Research import coverage PASS ({len(modules)} modules)") + PY + - name: Reject proof placeholders and custom axiom declarations + run: | + if grep -R -n --include='*.lean' -E '(^|[^[:alnum:]_])(sorry|admit)([^[:alnum:]_]|$)|^[[:space:]]*((private|protected|noncomputable|unsafe)[[:space:]]+)*axiom[[:space:]]' PermanssonResearch PermanssonResearch.lean; then + echo 'Forbidden proof placeholder or custom axiom in research library' + exit 1 + fi + echo 'Research source firewall PASS' + - name: Preserve required-check source context + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: legacy-research-source-context + path: ${{ runner.temp }}/required-source.json + if-no-files-found: error + retention-days: 30 diff --git a/docs/operations/pr113_required_gate_replacement_unapproved/test_localized_prefix_compiler.py b/docs/operations/pr113_required_gate_replacement_unapproved/test_localized_prefix_compiler.py new file mode 100644 index 00000000..89d17720 --- /dev/null +++ b/docs/operations/pr113_required_gate_replacement_unapproved/test_localized_prefix_compiler.py @@ -0,0 +1,73 @@ +"""Review-pending versioned A2 required-check consumer (not installed). + +The authentic v3 historical runtime already executed and bound the phase- +appropriate original T/A2 or inherited M113 check. Revalidate its detached +receipt against the *current* real CI event before accepting this test: +original PR/main and later CERT/main descendants have different sources. +A candidate result is never independent review or mathematical acceptance. +""" +import json +import os +from pathlib import Path +import unittest + +from pr113_boundary_io import strict_json_object +from pr113_historical_runtime_v3 import FORMAT, check_stored + +RECEIPT_ENV = "PERMANSSON_HISTORICAL_REQUIRED_RECEIPT" +PHASE_STATUSES = { + "initial_113_pr": "REQUIRED_HISTORICAL_SOURCE_AND_SECURITY_REVIEW_VERIFIED", + "initial_113_main": "MERGED_HISTORICAL_T_AND_A2_REVERIFIED_UNDER_REVIEWED_SOURCE", + "main_descendant_pr": "PR113_HISTORICAL_SOURCE_REVERIFIED_FOR_AUTHENTIC_DESCENDANT", + "main_descendant_push": "PR113_HISTORICAL_SOURCE_REVERIFIED_FOR_AUTHENTIC_DESCENDANT", +} +MAX_RECEIPT_BYTES = 4 * 1024 * 1024 + + +class LocalizedCompilerIntegration(unittest.TestCase): + def test_actual_final_source_and_semantic_mutations(self): + value = os.environ.get(RECEIPT_ENV) + self.assertTrue(value, "missing phase-neutral authenticated historical receipt") + candidate = Path(value) + self.assertTrue(candidate.is_file() and not candidate.is_symlink()) + self.assertLessEqual(candidate.stat().st_size, MAX_RECEIPT_BYTES) + payload = strict_json_object( + candidate.read_bytes(), max_bytes=MAX_RECEIPT_BYTES) + self.assertEqual(payload.get("format"), FORMAT) + phase = payload.get("phase") + self.assertIn(phase, PHASE_STATUSES) + producer_id = payload.get("historical_producer_run_id") + reviewer_id = payload.get("historical_security_review_id") + self.assertIs(type(producer_id), int) + self.assertGreater(producer_id, 0) + self.assertIs(type(reviewer_id), int) + self.assertGreater(reviewer_id, 0) + + # This is the real fail-closed validator, not a status-only parse: + # it binds actual_source() to this job and independently re-executes + # the historical source+artifact+independent-review authentication. + # Only the initial PR/main phases carry original T/A2 ZIP sidecars; + # descendants must revalidate M113 without borrowing those ZIPs. + verified = check_stored(candidate, producer_id, reviewer_id) + self.assertEqual(verified, payload) + result = verified["historical_required_result"] + self.assertEqual(result.get("status"), PHASE_STATUSES[phase]) + if phase.startswith("initial_113"): + self.assertIs(result.get("independent_security_review_authenticated"), True) + else: + self.assertRegex( + result.get("historical_merged_main_sha", ""), r"^[0-9a-f]{40}$") + self.assertIs(result.get("current_required_ci_accepted"), False) + self.assertIs(verified.get("accepted_current_mathematics"), False) + self.assertIs(verified.get("owner_merge_authorized"), False) + self.assertIs(verified.get("publication_authorized"), False) + print("PR113_PHASE_AWARE_HISTORICAL_RECEIPT_BEGIN") + print(json.dumps({ + "phase": phase, + "historical_status": result["status"], + "source_commit_sha": verified["source_commit_sha"], + "mathematical_acceptance": False, + "required_ci_acceptance_inferred": False, + "publication_authorized": False, + }, sort_keys=True)) + print("PR113_PHASE_AWARE_HISTORICAL_RECEIPT_END") diff --git a/docs/operations/pr113_required_gate_replacement_unapproved/test_workflow_v2.py b/docs/operations/pr113_required_gate_replacement_unapproved/test_workflow_v2.py new file mode 100644 index 00000000..d49819f0 --- /dev/null +++ b/docs/operations/pr113_required_gate_replacement_unapproved/test_workflow_v2.py @@ -0,0 +1,147 @@ +"""Versioned T2 successor workflow tests -- NOT INSTALLED OR APPROVED. + +The original 25-workflow verifier remains in its immutable T checkout and +continues to verify the historical archive. These current-H tests validate +the reviewed 30-workflow source set, preserving original CI-06/CI-07 method +IDs and fail-closed discovery mapping. Requires independent security review +of the entire atomic upgrade to become an applicable required check. +""" +import io +import json +from pathlib import Path +import unittest + +from verification.integrity.reports import HardeningError +from verification.integrity.run_tests import ( + RecordedResult, validate_mapping, _validate_mapping, validate_result, +) +from verification.integrity.workflow_contract import ( + APPROVED_WORKFLOW_BLOBS, load_workflows, + validate_workflow, validate_workflow_set, +) + +class WorkflowTests(unittest.TestCase): + def setUp(self): + self.root=Path(__file__).resolve().parents[3] + self.workflows=load_workflows() + self.integrity=self.workflows["research-integrity.yml"] + + def rejected(self, replacement): + with self.assertRaises(HardeningError) as caught: + validate_workflow(replacement.get("research-integrity.yml",""), + replacement) + self.assertEqual(caught.exception.code,"E_WORKFLOW_CONTRACT") + + def altered(self, file, before, after): + source=self.workflows[file] + self.assertIn(before,source) + modified=dict(self.workflows) + modified[file]=source.replace(before,after,1) + return modified + + def test_ci_06_filters_and_skip_paths_forbidden(self): + output=validate_workflow(self.integrity,self.workflows) + self.assertEqual(output["workflows"],30) + self.assertEqual(len(output["contexts"]),3) + for before,after in ( + (" branches: [main]"," branches: [changed]"), + (" - name: Execute archive adversaries", + " - name: Execute archive adversaries\n if: false"), + (" - name: Verify genuine reviewed original T/T2 and A2 source", + " - name: Verify genuine reviewed original T/T2 and A2 source\n continue-on-error: true"), + ): + with self.subTest(altered=before): + self.rejected(self.altered( + "research-integrity.yml",before,after)) + + def test_ci_07_discovery_reports_and_skips_fail_closed(self): + registry={"cases":[{"id":"CI-07","suite":"workflow", + "level":"unit","methods":["x.test"]}]} + self.assertEqual({"x.test":["CI-07"]}, + _validate_mapping(registry,"workflow", + ["x.test"],{"CI-07":("workflow","unit")})) + with self.assertRaises(HardeningError): + validate_mapping(registry,"workflow",["x.test"]) + with self.assertRaises(HardeningError): + validate_mapping(registry,"workflow",[]) + class Skipped(unittest.TestCase): + @unittest.skip("deliberate negative control") + def test_case(self):pass + test=Skipped("test_case") + runner=unittest.TextTestRunner(stream=io.StringIO(), + resultclass=RecordedResult) + result=runner.run(unittest.TestSuite([test])) + with self.assertRaises(HardeningError): + validate_result(result,[test.id()],{test.id():["CI-07"]}) + + def test_read_only_credentials_review_control(self): + for name in ("research-integrity.yml","research-lean.yml"): + self.rejected(self.altered(name,"contents: read","contents: write")) + self.rejected(self.altered(name,"actions: read","actions: write")) + self.rejected(self.altered(name,"pull-requests: read", + "pull-requests: write")) + + def test_disabled_ruleset_is_only_a_proposal(self): + path=self.root/".github/repository-rules/main.proposed.json" + proposal=json.loads(path.read_bytes()) + self.assertEqual(proposal["enforcement"],"disabled") + self.assertEqual(proposal["bypass_actors"],[]) + + def test_job_permission_overrides_rejected(self): + for value in (" permissions: {contents: write}\n", + " permissions:\n contents: write\n", + " env:\n GITHUB_TOKEN: overridden\n", + " runs-on: self-hosted\n"): + self.rejected(self.altered( + "research-integrity.yml", + " archive-reproducibility:\n", + " archive-reproducibility:\n"+value)) + + def test_shell_background_and_redirection_rejected(self): + original="python3 -B verification/research/pr113_historical_runtime_v3.py" + source=self.integrity + # Both the producer and --check-existing verifier must be guarded. + self.assertEqual(source.count(original),2) + first=source.find(original) + second=source.find(original,first+len(original)) + self.assertGreater(second,first) + self.assertIn("--check-existing",source[second:second+400]) + for site,offset in (("produce",first),("check-existing",second)): + for changed in ("true || "+original, + original+" > /dev/null", + original+" & true", + original+" || true", + "echo "+original): + with self.subTest(site=site,mutation=changed): + mutated=dict(self.workflows) + mutated["research-integrity.yml"]=( + source[:offset]+changed+source[offset+len(original):]) + self.rejected(mutated) + + def test_execution_overrides_rejected(self): + for source in ("research-integrity.yml","research-lean.yml"): + for before,after in ( + ("persist-credentials: false","persist-credentials: true"), + ("fetch-depth: 0","fetch-depth: 1"), + ("cancel-in-progress: true","cancel-in-progress: false"), + ("branches: [main]","branches: [other]"), + ): + with self.subTest(source=source,edited=before): + self.rejected(self.altered(source,before,after)) + + def test_required_workflow_rejects_dispatch(self): + for path in ("research-lean.yml","research-integrity.yml"): + self.rejected(self.altered(path,"on:\n", + "on:\n workflow_dispatch:\n")) + self.rejected(self.altered(path,"GITHUB_TOKEN:", + "IGNORED_TOKEN:")) + extra=dict(self.workflows) + extra["duplicate-required.yml"]="name: duplicate\non: push\n" + self.rejected(extra) + missing=dict(self.workflows) + missing.pop("research-lean.yml") + self.rejected(missing) + + +if __name__=="__main__": + unittest.main() diff --git a/docs/operations/pr113_required_gate_replacement_unapproved/test_workflow_v2_manifest.json b/docs/operations/pr113_required_gate_replacement_unapproved/test_workflow_v2_manifest.json new file mode 100644 index 00000000..6f90c103 --- /dev/null +++ b/docs/operations/pr113_required_gate_replacement_unapproved/test_workflow_v2_manifest.json @@ -0,0 +1,16 @@ +{ + "format": "Permansson.PR113.VersionedWorkflowSuiteCandidate.v2", + "status": "ORIGINAL_CI06_CI07_METHODS_PRESERVED_IN_INACTIVE_STAGE", + "staged_test_source": "docs/operations/pr113_required_gate_replacement_unapproved/test_workflow_v2.py", + "staged_test_blob_oid": "d49819f064a4da330d4ba8fcc1456232fdc27fb6", + "target_test_path": "verification/integrity/tests/test_workflow.py", + "target_contract_path": "verification/integrity/workflow_contract.py", + "old_test_source_untouched": true, + "old_contract_source_untouched": true, + "original_CI06_CI07_case_mapping_preserved": true, + "independent_security_review": false, + "owner_activation_authorized": false, + "installed": false, + "mathematical_acceptance": false, + "publication_authorized": false +} diff --git a/docs/operations/pr113_required_gate_replacement_unapproved/workflow_contract_v2.py b/docs/operations/pr113_required_gate_replacement_unapproved/workflow_contract_v2.py new file mode 100644 index 00000000..cf8b8911 --- /dev/null +++ b/docs/operations/pr113_required_gate_replacement_unapproved/workflow_contract_v2.py @@ -0,0 +1,204 @@ +"""VERSIONED T2 reviewed workflow contract CANDIDATE, NOT installed/approved. + +The original historical T integrity verifier and its historical 25-file +workflow source remain immutable in T. This successor contract is a distinct +versioned allowlist of the intended exact 30-workflow active configuration. +A SHA-1 Git blob ID is a content commitment, NOT independent authorization. + +Install only with an approved atomic workflow+contract+test source change. +""" +from __future__ import annotations +import hashlib +from pathlib import Path +import re +import subprocess +from verification.integrity.reports import HardeningError + +FORMAT="Permansson.PR113.VersionedWorkflowContract.v2" +APPROVED_WORKFLOW_BLOBS={ + "a2-smoke.yml": "b1a3c2001614ac873ec015d05d2dfd7e7e04344f", + "a3-contractive-smoke.yml": "d17cb4a6eecf5083f0eb92dd8437719c752c6e4f", + "application.yml": "bdd02d48265e08f8cb94b7402da56da9c8e3ec9d", + "b2-smoke.yml": "c56c37df8f52d1ed09a05b4d81789bca88b0b659", + "c-exit-random-entry-smoke.yml": "b3523038985e0997177b69578ea77d981e850593", + "c-exit-restart-d-smoke.yml": "0e5e0028498eaf8802fe5f923b20d38bf62f623b", + "c-exit-restart-smoke.yml": "e8a2cf304de04e3ccd08edfd0096733a358b6266", + "c-exit-smoke.yml": "a4a24c40463492998dab761cf06b32ecebb39e0f", + "c-exit-switch-smoke.yml": "e10e334ed0b3fd327e6c341d851639a5a696accd", + "d2-residence-smoke.yml": "8bbe39361fea58a7c6717363341f09898d2a94dd", + "e1-approx-quotient-smoke.yml": "11bba31567059d968133a513c78b9103e43418fc", + "f1-multiagent-protocols-smoke.yml": "b87a8c082f51f24dd7d533147e676813d1a78b85", + "lean.yml": "65aa095a9564cbef0bdca035c95b6d8f568d2cf4", + "pr113-all-case-compiler.yml": "07791f65c6572ca29ada9a8150a643c51958602f", + "pr113-all-original-semantic-candidates.yml": "2027a05a225dd49e92415bcbd81d73755111d5ef", + "pr113-clean-paper-rebuild.yml": "a89fdc99e7daa8b418deed2f09898a08677140a8", + "pr113-compiler-evidence-candidate.yml": "8f762813360a43e344bc328d54d53c2726bf3879", + "pr113-historical-migration-candidates.yml": "41c9dc082db3fc51110f72a98b1f1a41af9b0361", + "pr113-legacy-semantic-evidence.yml": "6e65891db1239bf87250ff3420e9ea96407353a4", + "pr113-original-positive-controls-candidate.yml": "16245b42cbfea16ade0cce24bb0c345c2b251a14", + "pr113-original-semantic-candidates.yml": "ab509cdb5097fb3c1a9e04dbba192adfe304a940", + "pr113-postseal-replay.yml": "76777249a3a66312fde3b7ec35cd32bdb961e671", + "pr113-preseal-fullscope.yml": "b849a1492badfffcc18e40759c1c78c67f60b824", + "pr113-source-controls-v3_1.yml": "ac41a782bb8a795c2509cfe5cc219b50b61fcf08", + "research-integrity-portability.yml": "380a592039dd9e50b87bb2891771e76b071a2aaa", + "research-integrity.yml": "76ab437ef4781e534b0bf7de79c646890dae1be7", + "research-lean.yml": "8e993121fdd0e278a2b9585f90dd73795058d938", + "research-release-verification.yml": "46ea7ebeae142f794394179154385cf1762b78d3", + "verification.yml": "c016aa6efcc42e19d81cb148f5db6bc6a439d7d3", + "x1-commons-smoke.yml": "554966b640d5f4d4ad284f3b5bdd0005c992dd80" +} +REQUIRED_JOBS=frozenset(( + "frozen-source-preservation","release-export-integrity", + "archive-reproducibility")) +APPROVED_ACTIONS={ + "actions/checkout":"11d5960a326750d5838078e36cf38b85af677262", + "actions/setup-python":"a26af69be951a213d495a4c3e4e4022e16d87065", + "actions/upload-artifact":"ea165f8d65b6e75b540449e92b4886f43607fa02", +} +READ_PERMISSIONS=frozenset(("actions","checks","contents","pull-requests")) +# These 23 objects retain the exact already-active historical Git blobs. +# Their mutable action tags are PRE-EXISTING SECURITY DEBT, not newly +# approved action pins. Only the two replacements and five additions can +# introduce a new workflow source, and every such action must be SHA pinned. +REPLACEMENT_WORKFLOWS=frozenset(("research-lean.yml","research-integrity.yml")) +ADDITION_WORKFLOWS=frozenset(( + "pr113-historical-migration-candidates.yml", + "pr113-original-positive-controls-candidate.yml", + "pr113-postseal-replay.yml", + "pr113-source-controls-v3_1.yml", + "pr113-preseal-fullscope.yml", +)) +RETAINED_HISTORICAL_WORKFLOWS=(frozenset(APPROVED_WORKFLOW_BLOBS) + - REPLACEMENT_WORKFLOWS - ADDITION_WORKFLOWS) +assert len(RETAINED_HISTORICAL_WORKFLOWS)==23 + + +def need(ok,msg): + if not ok: + raise HardeningError("E_WORKFLOW_CONTRACT",msg) + + +def git_blob_oid(data): + return hashlib.sha1(b"blob "+str(len(data)).encode()+b"\0"+data).hexdigest() + + +def load_workflows(root=None): + """Load real HEAD Git blobs, never checkout shadow or untracked files.""" + root=Path(root).resolve() if root is not None else Path(__file__).resolve().parents[2] + def git(*args): + try: + return subprocess.check_output(["git",*args],cwd=root, + stderr=subprocess.DEVNULL,timeout=30) + except (OSError,subprocess.CalledProcessError, + subprocess.TimeoutExpired) as exc: + raise HardeningError("E_WORKFLOW_CONTRACT", + "reviewed Git tree not available") from exc + head=git("rev-parse","HEAD").decode().strip() + need(re.fullmatch(r"[a-f0-9]{40}",head) is not None, + "reviewed versioned source HEAD missing") + entries={} + for row in git("ls-tree","-r",head,"--",".github/workflows").splitlines(): + try: + meta,path=row.split(b"\t",1) + mode,kind,oid=meta.decode().split() + name=path.decode() + except (ValueError,UnicodeError) as exc: + raise HardeningError("E_WORKFLOW_CONTRACT", + "unknown workflow Git tree record") from exc + need(name.startswith(".github/workflows/") + and "/" not in name.removeprefix(".github/workflows/") + and name.endswith((".yml",".yaml")) + and mode=="100644" and kind=="blob" + and len(oid)==40, + "unexpected workflow pathname/type/mode") + label=name.removeprefix(".github/workflows/") + need(label in APPROVED_WORKFLOW_BLOBS + and oid==APPROVED_WORKFLOW_BLOBS[label], + "unreviewed successor Git workflow blob") + blob=git("show",head+":"+name) + need(git_blob_oid(blob)==oid, + "Git blob bytes differ from exact workflow identity") + entries[label]=blob.decode("utf-8") + need(set(entries)==set(APPROVED_WORKFLOW_BLOBS), + "missing or extra reviewed successor workflow") + return entries + + +def validate_workflow_set(workflows): + need(isinstance(workflows,dict) and + set(workflows)==set(APPROVED_WORKFLOW_BLOBS), + "entire 30-workflow review set must match exactly") + inherited_mutable_actions={} + for name,oid in APPROVED_WORKFLOW_BLOBS.items(): + text=workflows[name] + need(isinstance(text,str) and + git_blob_oid(text.encode("utf-8"))==oid, + "reviewed source changed: "+name) + need("pull_request_target" not in text and + "id-token: write" not in text and + "contents: write" not in text and + "actions: write" not in text and + "pull-requests: write" not in text, + "workflow source is privileged beyond read-only profile") + inherited=[] + for token in re.findall(r"(?m)^\s*- uses:\s*(\S+)",text): + need(token.count("@")==1 and not token.startswith("@"), + "unrecognized action source syntax: "+name) + action,pin=token.rsplit("@",1) + if re.fullmatch(r"[a-f0-9]{40}",pin) is None: + # Exact Git blob commitment above is mandatory in either case. + # This exception ONLY reports unchanged, inherited action tags; + # it never labels those tags immutable, reviewed, or approved. + need(name in RETAINED_HISTORICAL_WORKFLOWS, + "new/replaced workflow action must be full-SHA pinned: "+name) + inherited.append(token) + elif action in APPROVED_ACTIONS: + need(APPROVED_ACTIONS[action]==pin, + "workflow action source is not the approved pin") + if inherited: + inherited_mutable_actions[name]=sorted(inherited) + research=workflows["research-lean.yml"] + arch=workflows["research-integrity.yml"] + need(research.count("\n research:\n")==1 and + arch.count("\n archive-reproducibility:\n")==1 and + arch.count("\n frozen-source-preservation:\n")==1 and + arch.count("\n release-export-integrity:\n")==1, + "original required-check job identities changed") + need("verification.integrity.ci_context" in research and + "verification.integrity.ci_context" in arch and + "pr113_historical_runtime_v3.py" in research and + "pr113_historical_runtime_v3.py" in arch and + "--check-existing" in arch and + "pr113_regression_diagnostics.py" in research and + "Preserve diagnostics even when regressions fail" in research and + "Generate and independently verify C10" in research and + "Generate and independently recheck D0" in research and + "Reject proof placeholders" in research and + "verification.integrity.run_tests --suite archive" in arch, + "versioned mandatory historical security and scientific legs missing") + return { + "format":FORMAT,"workflows":30, + "original_historical_unchanged":False, + "retains_historical_T_and_A2_through_external_original_checkouts":True, + "original_required_context_names":sorted(REQUIRED_JOBS), + "mathematical_release_approved":False, + "independent_security_review":False, + "owner_activation_authorized":False, + "all_workflow_actions_commit_pinned":not bool(inherited_mutable_actions), + "retained_historical_mutable_action_refs":inherited_mutable_actions, + "mutable_action_refs_pending_security_review":bool(inherited_mutable_actions), + } + + +def validate_workflow(text,workflows=None): + if workflows is None: + workflows=load_workflows() + results=validate_workflow_set(workflows) + need(isinstance(text,str) and + git_blob_oid(text.encode("utf-8"))== + APPROVED_WORKFLOW_BLOBS["research-integrity.yml"], + "main reviewed Research integrity source not exact") + return { + **results,"contexts":sorted(REQUIRED_JOBS), + "required_context_names_unique":"exact full 30-file Git blob allowlist", + } diff --git a/docs/operations/pr113_required_gate_replacement_unapproved/workflow_contract_v2_manifest.json b/docs/operations/pr113_required_gate_replacement_unapproved/workflow_contract_v2_manifest.json new file mode 100644 index 00000000..daa56e27 --- /dev/null +++ b/docs/operations/pr113_required_gate_replacement_unapproved/workflow_contract_v2_manifest.json @@ -0,0 +1,49 @@ +{ + "format": "Permansson.PR113.ReviewPendingVersionedWorkflowContract.v2", + "status": "EXACT_30_WORKFLOW_GIT_BLOBS_SOURCE_STAGED_NOT_ACTIVE", + "source_head_at_proposal": "2cd93bc42aa2584328e47af5ccdae19ac08e586a", + "current_original_count": 25, + "proposed_active_count": 30, + "activation_target": "verification/integrity/workflow_contract.py", + "staged_source": "docs/operations/pr113_required_gate_replacement_unapproved/workflow_contract_v2.py", + "staged_git_blob_oid": "cf8b89114adc280dbb2bb68ff6037a0822e8c1d2", + "expected_workflow_git_blobs": { + "a2-smoke.yml": "b1a3c2001614ac873ec015d05d2dfd7e7e04344f", + "a3-contractive-smoke.yml": "d17cb4a6eecf5083f0eb92dd8437719c752c6e4f", + "application.yml": "bdd02d48265e08f8cb94b7402da56da9c8e3ec9d", + "b2-smoke.yml": "c56c37df8f52d1ed09a05b4d81789bca88b0b659", + "c-exit-random-entry-smoke.yml": "b3523038985e0997177b69578ea77d981e850593", + "c-exit-restart-d-smoke.yml": "0e5e0028498eaf8802fe5f923b20d38bf62f623b", + "c-exit-restart-smoke.yml": "e8a2cf304de04e3ccd08edfd0096733a358b6266", + "c-exit-smoke.yml": "a4a24c40463492998dab761cf06b32ecebb39e0f", + "c-exit-switch-smoke.yml": "e10e334ed0b3fd327e6c341d851639a5a696accd", + "d2-residence-smoke.yml": "8bbe39361fea58a7c6717363341f09898d2a94dd", + "e1-approx-quotient-smoke.yml": "11bba31567059d968133a513c78b9103e43418fc", + "f1-multiagent-protocols-smoke.yml": "b87a8c082f51f24dd7d533147e676813d1a78b85", + "lean.yml": "65aa095a9564cbef0bdca035c95b6d8f568d2cf4", + "pr113-all-case-compiler.yml": "07791f65c6572ca29ada9a8150a643c51958602f", + "pr113-all-original-semantic-candidates.yml": "2027a05a225dd49e92415bcbd81d73755111d5ef", + "pr113-clean-paper-rebuild.yml": "a89fdc99e7daa8b418deed2f09898a08677140a8", + "pr113-compiler-evidence-candidate.yml": "8f762813360a43e344bc328d54d53c2726bf3879", + "pr113-historical-migration-candidates.yml": "41c9dc082db3fc51110f72a98b1f1a41af9b0361", + "pr113-legacy-semantic-evidence.yml": "6e65891db1239bf87250ff3420e9ea96407353a4", + "pr113-original-positive-controls-candidate.yml": "16245b42cbfea16ade0cce24bb0c345c2b251a14", + "pr113-original-semantic-candidates.yml": "ab509cdb5097fb3c1a9e04dbba192adfe304a940", + "pr113-postseal-replay.yml": "76777249a3a66312fde3b7ec35cd32bdb961e671", + "pr113-preseal-fullscope.yml": "b849a1492badfffcc18e40759c1c78c67f60b824", + "pr113-source-controls-v3_1.yml": "ac41a782bb8a795c2509cfe5cc219b50b61fcf08", + "research-integrity-portability.yml": "380a592039dd9e50b87bb2891771e76b071a2aaa", + "research-integrity.yml": "76ab437ef4781e534b0bf7de79c646890dae1be7", + "research-lean.yml": "8e993121fdd0e278a2b9585f90dd73795058d938", + "research-release-verification.yml": "46ea7ebeae142f794394179154385cf1762b78d3", + "verification.yml": "c016aa6efcc42e19d81cb148f5db6bc6a439d7d3", + "x1-commons-smoke.yml": "554966b640d5f4d4ad284f3b5bdd0005c992dd80" + }, + "historical_T_contract_unchanged": true, + "requires_original_historical_verification": true, + "independent_security_review": false, + "owner_activation_authorized": false, + "installed": false, + "mathematical_acceptance": false, + "publication_authorized": false +} diff --git a/docs/operations/pr113_selection_candidate_survey.md b/docs/operations/pr113_selection_candidate_survey.md new file mode 100644 index 00000000..7c5132dc --- /dev/null +++ b/docs/operations/pr113_selection_candidate_survey.md @@ -0,0 +1,82 @@ +# PR113 selection candidate survey (P1-06 / P1-08) + +**Status: UNREVIEWED tooling. This survey is not a selection, a review or an +acceptance.** It lists the choices a nonauthor reviewer must still make before +`reviewed_positive45_selection_v31.json` and `reviewed_claim11_selection_v31.json` +(plus the eleven claim false sources) can exist. It never writes those files. + +## Command + +From a clean checkout of the exact head being surveyed: + +```sh +PYTHONPATH=.:verification/research python3 -B \ + verification/research/pr113_selection_candidate_survey.py \ + --head "$(git rev-parse HEAD)" \ + --out /path/outside/the/checkout/pr113-selection-survey +``` + +- Reads only the committed exact-H blobs of + `research/paper_closures/post_merge_112/acceptance_case_source_map.json`, + `claim_declaration_map.json` and the test/Lean sources they name + (`git cat-file`, not the working tree). +- Writes `selection_candidate_survey.json` and `selection_candidate_survey.md` + into a new directory outside the checkout. A directory named `reviewed_*` is + refused. +- Exit `0`: coverage complete. Exit `1`: report written, but some case/claim + has no eligible route, provider or two test methods. Exit `2`: blocked. + Malformed maps, missing, duplicate or foreign owners, provider paths outside + `PermanssonLean/`/`PermanssonResearch/`, foreign test paths, unknown route + roles and unparseable test sources all block, and nothing is written. +- Output is deterministic: two runs at the same head are byte-identical. + +A positive route is `ELIGIBLE_UNREVIEWED` only when its role is +`COMPILER_SOURCE`/`EXACT_CHECK`, its test file exists at H and AST inspection +finds the method in exactly one class. Otherwise it is `ABSENT_TEST_SOURCE`, +`ABSENT_METHOD` or `AMBIGUOUS_CLASS`. Eligibility is a well-formedness fact +only. Whether the method tests the original criterion is the reviewer's +decision. + +Tests: `PYTHONPATH=.:verification/research python3 -B -m unittest test_pr113_selection_candidate_survey`. + +## Findings at `1ae74fc130c8e44fc3534e44c1d1dac1cefbf0a7` + +Coverage is complete: 45/45 cases have at least one eligible positive route +and a Lean provider present at H. 11/11 claims have a provider and at least +two discovered test methods. None of this is review. + +## Reviewer decisions still open + +**Original cases (P1-06).** + +1. **Choose one positive route** for each of the 16 cases with several + eligible routes: A-01, B-02, D-01, D-03, D-04, D-07, D-09, E-05, F-01, + F-03, M-01, M-02, Q-03, Q-04, Q-05, X-03 (Q-05 has five, D-09 three). +2. **Choose the Lean provider** for the 33 cases declaring more than one + provider file. The JSON `summary.cases_with_multiple_lean_providers` lists + them. +3. **Confirm criterion relevance** of the chosen route for all 45 cases, + including the 29 with a single eligible route. +4. **Pick the relevant semantic negative** (P1-07) where several exist: + B-02, M-02, Q-05, X-02, X-05. + +**Claims (P1-08).** + +5. **Choose distinct positive and negative test class/method** for all 11 + claims. The claim map names test files only, not methods. +6. **Keep tests claim-specific.** M1/M2 share `test_final05_measurable_lean.py` + and Q1/Q2 share `test_final04_source_lean.py`, each file having only two + test methods, so one claim must not borrow the other's controls. +7. **Choose the provider** for N1 and N2, which have two provider files each. +8. **Author a false Lean statement for each claim.** It must be + claim-specific, criterion-relevant and append-only to the chosen provider, + with no import, axiom, option or `sorry` change. The survey deliberately + provides none (`negative_lean_statement: null`). + +## Boundaries + +This tool does not modify canonical maps, Lean sources, workflows, manifests, +release gates or reviewer policy. It sets no accepted/approved flag, and every +report carries `independent_review: false`, `accepted_cases: 0` and +`accepted_claims: 0`. The reviewed selection files stay absent until a +configured nonauthor reviewer (P1-19) produces them. diff --git a/docs/operations/pr113_t2_security_review_dossier_unapproved.md b/docs/operations/pr113_t2_security_review_dossier_unapproved.md new file mode 100644 index 00000000..af44288f --- /dev/null +++ b/docs/operations/pr113_t2_security_review_dossier_unapproved.md @@ -0,0 +1,107 @@ +# PR113 — historical T → T2 security review dossier (candidate; not approval) + +**Current baseline for this dossier:** Historical T +`71cc34fa0bc4b639c05d2ec5f39f0b88afd56388`; +PR base B `17c315f7df7abf457eb94d393bd526d46da6f6f4`. +The run must bind **the actual latest source head H at execution**; any +source-changing commit resets its source identity and all downstream +review/execution artifacts. + +## Scope + +The original `archive-reproducibility` CI job currently runs an unmodified +`verification.archive.offline_smoke` against candidate C. It fails closed +with `E_TOOLING_CLOSURE` because **two** current canonical integrity-tooling +files differ from the frozen T package: + +| Source path | T Git blob | H pre-dossier Git blob | +|---|---|---| +| `verification/integrity/workflow_contract.py` | `7c1b26559dc30c2f37e8165983ce71094b205cde` | `f8fa8196a7a0e33d2f218faa0ba570c09aac9a3a` | +| `verification/integrity/tests/test_workflow.py` | `66ba674710cae286605baf3eee570ee1d7fcf282` | `1fa90e0dce90518b8052e42199867f11c77e1010` | + +Verify these actual objects again at **final H**; the pinned example H objects +are a record, not permission to reinterpret a later change. All other +`verification/integrity/`, `verification/archive/`, and +`archival/v0.2.0/README.md` Git members are to remain unchanged as proved +by `pr113_tooling_migration.audit`. Original packaged T source and the +v0.2.0 archive/expected hash must never be modified. + +The H workflow set contains 25 protected GitHub workflow files; new +`pr113-*` producing workflows, concurrency, job identities, action versions, +privilege scope and events must be reviewed **as a complete source set**. +Staged future producer workflows outside `.github/workflows` are +**not active**, and a prospective 26+ workflow set is a NEW review target. + +## Dual execution before approval + +The source-controlled **candidate** entrypoint is +`verification/research/pr113_dual_tooling_candidate.py`, and the +historical candidate workflow source remains +`docs/operations/pr113_unapproved_workflows/pr113-historical-migration-candidates.yml`. +The proposed workflow is not installed. Its GitHub execution is allowed only +as a specifically named exact-branch **push candidate** after the +workflow has been independently authorized for activation. Never interpret +its result as the required archived check. + +1. Obtain actual complete-history checkouts: historical T and candidate H + with clean Git status. Keep different source roots, output outside either. +2. Install exactly Python 3.13 and `pypdf==6.10.0`; verify availability of + `sudo`, `unshare`, `setpriv`, nonroot runner, and real network isolation. + No offline-to-online fallback. +3. Run original historical T's **unaltered** `offline_smoke` standalone, + with GitHub event and secret environment entirely excluded. Verify the + historical artifact is restored and the original check reports PASS, + `acceptance_scope=accepted_artifact_replay_only`, + `network_isolation=ENFORCED` and `capabilities=all_zero`. + This is NOT execution of the original gate against today's C. +4. In the separate actual H checkout run original source-controlled workflow + regression suite and record all methods/failures/errors/skips and the + current 25-workflow source binding. +5. Independently audit every packaged T file versus its immutable T + Git object, the two changed T/H blob pairs, exact 25-file workflow + membership and current H tree with `pr113_tooling_migration.audit`. +6. Retain both original unedited JSON reports, digest them, and generate + `T2_security_review_candidate.json`. Status MUST remain + `COMPLETE_DUAL_EXECUTION_CANDIDATE_SECURITY_REVIEW_PENDING` and + independent approvals MUST be false. + +**No approval from a green candidate.** A historical PASS alongside a +current-H workflow suite PASS only demonstrates two distinct execution legs, +not authorization to redefine the required T/C archival closure. + +## Required human decisions + +A genuine **independent security reviewer** must examine all source objects, +especially two changed integrity files, all current and proposed workflows, +event/permission restrictions, old and successor verifier behavior, +no-change guarantees for T, negative-adversary coverage and every +source trust transition. The repository owner must explicitly authorize the +precise versioned T2 profile **after** that review. Do not author approval +JSON, independent GitHub reviews, check-run identities, or merge commits in +this implementation work. + +Only after explicit owner plus nonauthor security authorization: + +- implement the versioned, active mandatory `archive-reproducibility` gate + so it **executes both historical T and reviewed T2**, each with legitimate + separate source binding; never short-circuit the historical leg; +- update the protected workflow contract **as one reviewed atomic source + revision**, restart final H provenance, and rerun all 12 required GitHub + contexts at exact final H; +- retain both original execution reports/ZIPs and approval provenance. + Any new workflow, action, test selection or source change makes the + prior security review stale. + +## Stop/failure conditions + +- Original T still fails, lacks true namespace isolation, or gets labeled + current C; historical archive/expected digest modified. +- Current H's 25 protected workflows do not pass its own original suite. +- T/H source drift beyond the explicitly reviewed two files. +- A candidate tries to run as `research` or `archive-reproducibility`. +- No authentic owner + independent security signoff or a stale final H. +- Missing review of proposed future workflow producer additions. +- Original mandatory `archive-reproducibility` continues to fail. + +All T2 receipts remain **candidates**; no mathematical acceptance, +independent reviewer certification or publication is implied. diff --git a/docs/operations/pr113_two_stage_historical_cutover_v1.md b/docs/operations/pr113_two_stage_historical_cutover_v1.md new file mode 100644 index 00000000..6495facd --- /dev/null +++ b/docs/operations/pr113_two_stage_historical_cutover_v1.md @@ -0,0 +1,47 @@ +# PR #113 — two-stage historical verification cutover + +**Status:** implementation runbook only; not security approval, workflow activation, release certification or a merge decision. Current base and accepted historical artifacts are never rewritten. + +## Immutable authority and scope + +- Original historical T: `71cc34fa0bc4b639c05d2ec5f39f0b88afd56388`. +- Original A2 introducer: `f71a76032b169aac7bfe0694aa715339a94d4e05`. +- Source-pinned preactivation workflow inventory: 25 exact files at `5189f0c4a0db7535d57a9ff2a7aef6a3e76191b6`. +- The candidate is exactly **23 retained + 2 replaced + 5 added workflows**, plus three source guards. The ten-path cutover is atomic and needs separate approval. +- `main` PR #113 source baseline is `17c315f7df7abf457eb94d393bd526d46da6f6f4`. Always authenticate real later H/S/M from GitHub; do not write predicted SHAs or runtime IDs to committed source. + +## Stage 0 — Engineering, without authority + +1. Keep the installed 25 workflows untouched. Reproduce historical T in a genuine detached T checkout with Python 3.13, pypdf 6.10.0 and proven nonroot/offline network namespace. Separately verify the current 25-workflow suite. Neither replay is current source approval. +2. Compile original A2 from its exact historical checkout under pinned Lean 4.34.0 and source-pinned dependencies. Independently run successor A2 on a genuine disposable 30-workflow checkout. Require real positives, transitive-axiom evidence and all four intended semantic negatives per leg; parser/import/timeout failure is not evidence. +3. Run `pr113_disposable_transition_rehearsal.py` for the exact ten-path candidate and synthetic [B,C] tree/parent checks. Require current CI-06/07 and eight active30 regression suites. It remains local/nonauthorizing. +4. Prepare one immutable source-review dossier: exact Git objects and file modes, all relevant called verifiers, 25/30 inventory, actions/permissions/events, source-mutation controls, historical T/A2 execution records, dependency hashes, producer scheduling, response to failure and every retained workflow's risk. + +## Stage 1 — Source security review and installation decision + +1. An authorized *independent* security/engineering reviewer assesses the entire proposed source configuration and the Stage-0 evidence. A builder's synthetic proof, code comment, JSON boolean, or current CI status is not independent review. +2. The owner separately authorizes **installing the exact reviewed source on the unmerged PR branch for evidence production**. This is not final artifact attestation, a merge or mathematical acceptance. Record external decisions and exact source identity, not future artifact IDs, in the relevant detached review evidence. +3. If either permission is absent, do not install or silently skip original required checks. Do not demand the producer's future GitHub artifacts before the workflow has been authorized to run. + +## Stage 2 — Actual producer artifacts and final source-bound attestation + +1. Install exactly the 23 retained / 2 replacement / 5 additional workflows and three guard files as one reviewed source change on PR #113. Verify all 30 actual Git blobs and updated manifest/test OIDs. Preserve every historical T archive and original A2 verifier source. +2. The new historical producer is registered for **PR-branch push**; this is different from manual workflow dispatch on the default branch. At the actual install H, confirm the GitHub push event launched both real `historical-T-and-H-unreviewed` and `historical-A2-and-H-unreviewed` producing jobs. Both must succeed at the exact same run attempt before collection. Rerun or reschedule the producer at the same H if required, using a real supported GitHub trigger—never a fabricated commit/run or a borrowed CI check. +3. Obtain the genuine T/A2 artifact ZIPs and their SHA-256 digests. Authenticate repository, branch, H, workflow source, run/attempt, job, check-run, artifact identity, ZIP members, original T isolated restoration and genuine A2 positive/four-negative source results. Transport and attest these outside Git source. +4. The configured independent reviewer then submits an actual current GitHub PR review binding exact **final H, 30-workflow fingerprint, original base B and both T/A2 ZIP digests**. Reviewer independence and dismissal/latest-review state are authenticated through the owner-approved reviewer policy. Any subsequent source edit makes the attestation stale. +5. Admin provides the genuine original producer run ID and approved review ID through GitHub variables/authorized inputs. Rerun the same final source H's original `research` and `archive-reproducibility` contexts. The phase-aware runtime exports `PERMANSSON_HISTORICAL_REQUIRED_RECEIPT` for every phase; initial #113 alone may export the original T/A2 sidecars. +6. Require the entire Research suite plus C10, D0, default target, import coverage and firewall to actually execute. Require all 12 exact H/S required check contexts and the archive's original adversarial coverage. A source success flag without executed job evidence is insufficient. + +## Descendants and final main + +- After separately owner-authorized ordered merge, record real M113 and verify parents `[B113,H113]` and identical H/S/M tree. Reauthenticate the first actual main-push historical gate, not the premerge PR event. +- For later CERT and main descendants, use `pr113_historical_inheritance_v3` against the immutable genuine M113 source and real current descendant event. The staged A2 test consumes the **phase-neutral receipt** and calls the same fail-closed `check_stored` verifier; it never asks CERT to fabricate PR113 event or borrow initial ZIP sidecars. +- The original 4+9+8 mathematical producer workflows are a distinct CERT pipeline, requiring actual default-branch registration and authenticated frozen Hcert dispatch after M113. A path-filtered push or docs-only YAML is not proof of a GitHub producer run. +- GitHub's *effective* main rules and required-status-check set must be inspected separately: a source-side 12-context checklist does not automatically configure branch protection. +- Infrastructure acceptance never means independent 45/45 or 11/11 mathematical certification, scholarly approval, sealing or publication. + +## Mandatory rejection cases + +Wrong H/B/S/M, reversed merge parents, missing historical isolation, altered T archive, stale/foreign/doubled run or artifact, source/blob drift, unauthorized or stale review, duplicate/nonfinite JSON, missing provenance, any skipped required Research step, failing original A2, false-mutant parser failure, a 25-as-30 upgrade, a 31st workflow, and inherited ZIP evidence instead of revalidation all fail closed. + +No unauthorized approval, workflow installation, GitHub ruleset change, merge, certification or publication is implemented by this runbook. diff --git a/docs/operations/pr113_unapproved_workflows/README.md b/docs/operations/pr113_unapproved_workflows/README.md new file mode 100644 index 00000000..164c5e14 --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/README.md @@ -0,0 +1,120 @@ +# PR113 workflow proposal staging (not an approval) + +The three new GitHub Actions workflow files introduced during the PR114 repair batch were added to the *active* `.github/workflows/` set before the 25-workflow security contract was independently reviewed. The original review-sensitive workflow contract correctly rejected the 28-file tree. This commit relocates the three candidates **byte-identically** into this non-executing proposal directory, restoring the original 25 active workflow files without changing, overriding or suppressing the historical verifier. + +The `manifest.json` records their original Git blob identifiers. The regression suite checks exact Git object equality, not just names or counts. No active trusted workflow is modified. The original archive T, the historical A2 verifier, the original 45 acceptance criteria and Lean sources remain unchanged. + +**Review/activation rule:** The workflow candidates must not be placed under `.github/workflows/` or used as a producer of accepted CI evidence until an independent security reviewer and owner authorize the exact complete versioned workflow set (including all three) and the associated historical T→T2 and A2 successor trust migrations. Once approved, activation is an explicit source-changing commit which restarts the exact-head CI/evidence boundary. A passing candidate unit test, a GitHub run, or this manifest does NOT grant such authority. + +* `pr113-historical-migration-candidates.yml`: proposed T/A2 separate-history dual-execution candidate. +* `pr113-original-positive-controls-candidate.yml`: proposed authentic 45 original case-positive CI producer. +* `pr113-postseal-replay.yml`: proposed post-M full-candidate archive replay producer. + +Until those migrations are approved, the research and archive mandatory gates may still legitimately fail; those failures must not be suppressed. Existing authenticated 4+9+8 original producers remain separately mandatory. The original current Research Lean run is not replaced by the staged candidate. + +Acceptance of this staging commit proves only that the **unreviewed workflow files are not active and have not been modified**. It neither executes nor approves any pending mathematical/archival work. + +## Additive v3.1 complete-control producer proposal — not installed + +The fourth staged file, `pr113-source-controls-v3_1.yml`, is **new proposed +source**, not one of the three preserved earlier-branch workflow blobs. It +consolidates three authentic producing job families under a single versioned +workflow: 45 original source-positive Lean/unittest controls, 20 genuine +original-source-appended legacy negatives using all 21 authenticated original +ZIPs, and 11 claim-specific positive/false Lean pairs. The job names, exact +GitHub source head, artifact run attempt, original member hashes and +reviewed source method selections become explicit CI execution inputs. + +The proposed `push` trigger uses the **actual PR #113 research branch** +rather than the closed former #114 branch, and `workflow_dispatch` is only an +optional manually authorized path. This file is intentionally **outside** +`.github/workflows/`. Required reviewed selections under +`research/paper_closures/post_merge_112/` do not yet exist and every job +fails closed until those distinct case/claim choices and false-source files +are legitimately prepared. Do not replace them with generated +`False` fixtures or unreviewed role tags. + +Before activation, the owner and a nonauthor security reviewer must authorize +the complete 25+successor workflow set (including triggering semantics, +source OIDs, job/check/artifact identity, script dependencies and original +historical T/A2 trust migrations). Installing these bytes changes final H +and requires **new** exact-H source CI and evidence. This manifest neither +grants approval nor publishes a workflow. + +## P1-12: staged exact-M full-scope preseal producer + +`pr113-preseal-fullscope.yml` is a **fifth, entirely inactive** workflow +proposal. Its exact approved workflow/job/artifact identifiers must remain +`.github/workflows/pr113-preseal-fullscope.yml`, +`preseal-fullscope-candidate` and +`pr113-preseal-fullscope-candidate`, as expected by the independently +executed postseal authenticator. + +It can only run on `main` at an owner-authorized real merge M. It downloads +an original GitHub successful exact-M input carrier artifact named +`pr113-preseal-review-inputs` and reauthenticates its run, attempt, job, +check, source head and ZIP SHA before extracting reviewed evidence. **That +carrier is not approval**. The strict full-scope sealing entrypoint +independently checks all current-source and merged-M required CI, genuine +source/scholarly reviews, all original 21 producer ZIPs, the 76 additional +controls and mathematical scope. The pipeline must fail closed if any +review/evidence input is absent or inauthentic, and only uploads the exact +candidate ZIP and original SHA256 sidecar after successful independent +archive replay. + +The input carrier production and reviewed source, method selections, +false statements, independent security approval of these YAML bytes, +successful review of historical T/A2 migrations, final M and publication +authorization are all STILL OPEN. No full candidate artifact or active +workflow is created simply by adding this staged file. + +## Historical T/T2 dual verifier CI candidate (P0-01) + +The additional T2 candidate `pr113-historical-migration-candidates.yml` +is a **branch-scoped push** proposal and NOT installed or a required check. Its T/H dual-verifier job is constrained to the exact +original research branch and the named `original-T-and-current-H` job. +The original T checkout runs its *unmodified* offline restoration in an +actual Linux isolated standalone source context with all GitHub event and +credential variables removed, while the current H workflow verifier runs in +the genuine current CI context. This separation avoids falsely presenting an +old T artifact restoration as the current C archive verification. + +The two original source paths, immutable T archive, historical checksums, +accepted 25 current active workflow inventory, and required +`archive-reproducibility` context have **not** been edited. A successful +candidate run is a dossier for owner and independent security review only. +The mandatory check remains red until a separately authorized successor +contract is approved and installed with fresh exact-H CI. The review must +examine all 25 workflow objects, the two changed integrity files and the +source execution profile, not just hashes and status labels. + +## Corrected v3.1 legacy provenance ordering (source cannot hold future IDs) + +A source-controlled exact-H file **cannot contain** the genuine GitHub +`artifact_id`, `run_id`, or compiler `event-*` that only exist AFTER the +same frozen-H producer executes. Previous v3.1 proposal erroneously required +`reviewed_original21_catalog_v31.json` and dynamic +`reviewed_legacy20_selection_v31.json` as committed inputs. That creates +an impossible H/source-binding cycle. + +The latest staged v3.1 proposal instead: +1. Requires a source-frozen, explicitly selected **20-case static intent** + `legacy20_source_intent_v31.json` (case, test path/method and original + provider only), with no future runtime identities. +2. Runs `pr113_original21_live_catalog.py` AFTER the original 4+9+8 GH + checks complete at the SAME H. It authenticates latest successful + run/job/check/attempt/artifact SHA and obtains original ZIPs into a detached + runtime directory, not the repository. +3. Runs `pr113_legacy20_runtime_resolution.py` to enumerate actual + criterion-owned semantic compiler observations. Several original + tests generate more than one false event, so this stage NEVER guesses + the first negative automatically. It retains the unreviewed choice report. +4. **Deliberately fails closed** before the 20 source replays until an + independently justified, authenticated DETACHED event-choice input exists. + No producer of that reviewed choice has been authorized or implemented, + and mathematical review is still required before release. + +The first three stages are reviewable source engineering; step four remains +an explicit open external review/evidence dependency. This proposal itself +is still completely inactive and cannot satisfy required Research/Archive +checks until the separate security review/activation contract is fulfilled. diff --git a/docs/operations/pr113_unapproved_workflows/manifest.json b/docs/operations/pr113_unapproved_workflows/manifest.json new file mode 100644 index 00000000..2c517634 --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/manifest.json @@ -0,0 +1,54 @@ +{ + "format": "Permansson.PR113.UnreviewedWorkflowProposals.v1", + "baseline_pr_head": "7bd09407f0670f73943689b4c8da21a7156857aa", + "original_approved_workflow_count": 25, + "status": "SOURCE_STAGED_NONEXECUTABLE_PENDING_INDEPENDENT_SECURITY_REVIEW", + "historic_T": "71cc34fa0bc4b639c05d2ec5f39f0b88afd56388", + "base_B": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "proposals": [ + { + "workflow_path": ".github/workflows/pr113-historical-migration-candidates.yml", + "staged_source_path": "docs/operations/pr113_unapproved_workflows/pr113-historical-migration-candidates.yml", + "git_blob_oid": "41c9dc082db3fc51110f72a98b1f1a41af9b0361", + "source_unchanged": false, + "approved": false, + "installed": false + }, + { + "workflow_path": ".github/workflows/pr113-original-positive-controls-candidate.yml", + "staged_source_path": "docs/operations/pr113_unapproved_workflows/pr113-original-positive-controls-candidate.yml", + "git_blob_oid": "16245b42cbfea16ade0cce24bb0c345c2b251a14", + "source_unchanged": true, + "approved": false, + "installed": false + }, + { + "workflow_path": ".github/workflows/pr113-postseal-replay.yml", + "staged_source_path": "docs/operations/pr113_unapproved_workflows/pr113-postseal-replay.yml", + "git_blob_oid": "76777249a3a66312fde3b7ec35cd32bdb961e671", + "source_unchanged": false, + "approved": false, + "installed": false + }, + { + "workflow_path": ".github/workflows/pr113-source-controls-v3_1.yml", + "staged_source_path": "docs/operations/pr113_unapproved_workflows/pr113-source-controls-v3_1.yml", + "git_blob_oid": "ac41a782bb8a795c2509cfe5cc219b50b61fcf08", + "source_unchanged": false, + "approved": false, + "installed": false + }, + { + "workflow_path": ".github/workflows/pr113-preseal-fullscope.yml", + "staged_source_path": "docs/operations/pr113_unapproved_workflows/pr113-preseal-fullscope.yml", + "git_blob_oid": "b849a1492badfffcc18e40759c1c78c67f60b824", + "source_unchanged": false, + "approved": false, + "installed": false + } + ], + "independent_security_review": false, + "owner_authorized_activation": false, + "mathematical_acceptance": false, + "release_authorized": false +} diff --git a/docs/operations/pr113_unapproved_workflows/pr113-historical-migration-candidates.yml b/docs/operations/pr113_unapproved_workflows/pr113-historical-migration-candidates.yml new file mode 100644 index 00000000..41c9dc08 --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/pr113-historical-migration-candidates.yml @@ -0,0 +1,176 @@ +# Review-pending source-controlled historical execution candidate. +# This workflow is NOT the required Research Lean or archive-reproducibility gate. +# It cannot approve a T2/A2 trust migration, PR merge, or mathematical release. +name: PR113 Historical T and A2 candidates + +# Review/activation prerequisite: source-controlled branch push only. +# This file remains inactive outside .github/workflows until explicitly approved. +on: + push: + branches: [research/final-mathematical-closure] + +permissions: + contents: read + +jobs: + engineering-preflight: + name: historical-engineering-preflight + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Verify real checkout and run source-controlled engineering preflight + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test -z "$(git status --porcelain=v1 --untracked-files=all)" + python3 -B verification/research/pr113_fast_engineering_preflight.py \ + --head "$GITHUB_SHA" \ + --out "$RUNNER_TEMP/pr113-fast-engineering" + - name: Execute new producer and selection adversarial tests + shell: bash + run: | + set -euo pipefail + python3 -B -m unittest discover -s verification/research -p test_pr113_prepare_positive_selection.py -v + python3 -B -m unittest discover -s verification/research -p test_pr113_pack_ci_positive_owners.py -v + - name: Resolve 45 genuine original case routes without acceptance + shell: bash + run: | + set -euo pipefail + python3 -B verification/research/pr113_prepare_positive_selection.py --head "$GITHUB_SHA" --out "$RUNNER_TEMP/pr113-selection-smoke" + - name: Retain candidate original-case route selection + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-positive-selection-unreviewed + path: ${{ runner.temp }}/pr113-selection-smoke/ + if-no-files-found: warn + retention-days: 30 + - name: Retain unapproved exact-source QA evidence + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-fast-qa-candidate + path: ${{ runner.temp }}/pr113-fast-engineering/ + if-no-files-found: warn + retention-days: 30 + + original-T-and-current-H: + name: historical-T-and-H-unreviewed + if: github.event_name == 'push' + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Pin dependencies and isolate original history + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test -z "$(git status --porcelain=v1 --untracked-files=all)" + python3 -m pip install -r verification/integrity/requirements.txt + git cat-file -e "71cc34fa0bc4b639c05d2ec5f39f0b88afd56388^{commit}" + git worktree add --detach "$RUNNER_TEMP/pr113-source-T" \ + 71cc34fa0bc4b639c05d2ec5f39f0b88afd56388 + test "$(git -C "$RUNNER_TEMP/pr113-source-T" rev-parse HEAD)" = \ + 71cc34fa0bc4b639c05d2ec5f39f0b88afd56388 + - name: Execute unmodified T and current H as distinct historical legs + shell: bash + run: | + set -euo pipefail + python3 -B verification/research/pr113_dual_tooling_candidate_v2.py \ + --historical-T-source "$RUNNER_TEMP/pr113-source-T" \ + --current-source "$GITHUB_WORKSPACE" \ + --head "$GITHUB_SHA" \ + --output "$RUNNER_TEMP/pr113-dual-tooling" + - name: Retain T and H original bytes and candidate reports + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-T-H-historical-candidate + path: ${{ runner.temp }}/pr113-dual-tooling/ + if-no-files-found: warn + retention-days: 30 + + original-A2-and-current-H: + name: historical-A2-and-H-unreviewed + if: github.event_name == 'push' + runs-on: ubuntu-latest + timeout-minutes: 110 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Pin genuine historical A2 executor source + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test -z "$(git status --porcelain=v1 --untracked-files=all)" + git cat-file -e "55f872abddfbbd8eaa996f3c95f91e15a5ea421a^{commit}" + git worktree add --detach "$RUNNER_TEMP/pr113-source-A2" \ + 55f872abddfbbd8eaa996f3c95f91e15a5ea421a + test "$(git -C "$RUNNER_TEMP/pr113-source-A2" rev-parse HEAD)" = \ + 55f872abddfbbd8eaa996f3c95f91e15a5ea421a + - name: Build pinned Lean and audit current mathematical dependencies + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Bind historical A2 to the exact pinned dependency source (not H build objects) + shell: bash + run: | + set -euo pipefail + python3 -B verification/research/pr113_a2_dependency_bridge_v2.py \ + --historical-source "$RUNNER_TEMP/pr113-source-A2" \ + --current-source "$GITHUB_WORKSPACE" \ + --current-head "$GITHUB_SHA" \ + --out "$RUNNER_TEMP/pr113-A2-dependency-bridge.json" + - name: Execute both genuine compiler gates and four semantic negatives + shell: bash + run: | + set -euo pipefail + python3 -B verification/research/pr113_dual_a2_compiler_v2.py \ + --historical-source "$RUNNER_TEMP/pr113-source-A2" \ + --historical-head 55f872abddfbbd8eaa996f3c95f91e15a5ea421a \ + --current-source "$GITHUB_WORKSPACE" \ + --current-head "$GITHUB_SHA" \ + --out "$RUNNER_TEMP/pr113-dual-A2" + # Carry forward the actual original dependency bridge bytes. The + # output producer cannot claim full A2 provenance without them. + test -s "$RUNNER_TEMP/pr113-A2-dependency-bridge.json" + test -s "$RUNNER_TEMP/pr113-dual-A2/unreviewed_dual_A2_candidate.json" + cp -- "$RUNNER_TEMP/pr113-A2-dependency-bridge.json" \ + "$RUNNER_TEMP/pr113-dual-A2/historical_dependency_bridge.json" + - name: Retain historical and successor source-bound proof logs + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-A2-historical-candidate + path: ${{ runner.temp }}/pr113-dual-A2/ + if-no-files-found: warn + retention-days: 30 diff --git a/docs/operations/pr113_unapproved_workflows/pr113-original-positive-controls-candidate.yml b/docs/operations/pr113_unapproved_workflows/pr113-original-positive-controls-candidate.yml new file mode 100644 index 00000000..16245b42 --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/pr113-original-positive-controls-candidate.yml @@ -0,0 +1,83 @@ +# Exact-source positive-test producer. Execution alone is not independent review. +# Original case decisions, 20 legacy negatives and 11 claim-pairs remain separate. +name: PR113 Original 45 Positive Source Controls + +on: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pr113-original-positives-${{ github.ref }} + cancel-in-progress: true + +jobs: + original-45-positive-controls: + runs-on: ubuntu-latest + timeout-minutes: 360 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Reject stale or dirty source checkout + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + test -z "$(git status --porcelain=v1 --untracked-files=all)" + - name: Build and axiom-audit the original pinned Lean research source + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Resolve all 45 original source-positive routes, without approval + shell: bash + run: | + set -euo pipefail + python3 -B verification/research/pr113_prepare_positive_selection.py \ + --head "$GITHUB_SHA" \ + --out "$RUNNER_TEMP/pr113-selection" + test -s "$RUNNER_TEMP/pr113-selection/selection_review_pending.json" + - name: Actually execute each named unittest and original Lean provider + shell: bash + run: | + set -euo pipefail + python3 -B verification/research/final_closure_positive_route_candidate.py \ + --head "$GITHUB_SHA" \ + --selection "$RUNNER_TEMP/pr113-selection/selection.json" \ + --out "$RUNNER_TEMP/pr113-positive-run" + - name: Revalidate log bytes and package 45 source-control owner records + shell: bash + run: | + set -euo pipefail + python3 -B verification/research/pr113_pack_ci_positive_owners.py \ + --head "$GITHUB_SHA" \ + --run-id "$GITHUB_RUN_ID" \ + --run-attempt "$GITHUB_RUN_ATTEMPT" \ + --source "$RUNNER_TEMP/pr113-positive-run" \ + --output "$RUNNER_TEMP/pr113-positive-artifact" + cp "$RUNNER_TEMP/pr113-selection/selection.json" \ + "$RUNNER_TEMP/pr113-positive-artifact/selection_candidate.json" + cp "$RUNNER_TEMP/pr113-selection/selection_review_pending.json" \ + "$RUNNER_TEMP/pr113-positive-artifact/selection_review_pending.json" + - name: Preserve true original evidence, explicitly unreviewed + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-source-positive45-unreviewed + path: ${{ runner.temp }}/pr113-positive-artifact/ + if-no-files-found: warn + retention-days: 30 diff --git a/docs/operations/pr113_unapproved_workflows/pr113-postseal-replay.yml b/docs/operations/pr113_unapproved_workflows/pr113-postseal-replay.yml new file mode 100644 index 00000000..76777249 --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/pr113-postseal-replay.yml @@ -0,0 +1,175 @@ +# SECURITY REVIEW REQUIRED BEFORE MERGE. +# This workflow is an independently inspectable producer candidate; it is +# not an authorization to seal, tag, publish or declare 45/11 accepted. +name: PR113 Full Candidate Postseal Replay + +on: + workflow_dispatch: + inputs: + merged_sha: + description: Exact already-authorized main merge M + required: true + type: string + candidate_run_id: + description: Original exact-M producing GitHub run ID + required: true + type: string + candidate_artifact_id: + description: Original exact-M preseal artifact ID + required: true + type: string + +permissions: + contents: read + actions: read + +jobs: + postseal-archive-replay: + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Enforce actual authorized main M and numeric source identity + shell: bash + env: + MERGED_SHA: ${{ inputs.merged_sha }} + SOURCE_RUN: ${{ inputs.candidate_run_id }} + ARTIFACT_ID: ${{ inputs.candidate_artifact_id }} + run: | + set -euo pipefail + test "$GITHUB_REF" = "refs/heads/main" + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$GITHUB_SHA" = "$MERGED_SHA" + test -z "$(git status --porcelain=v1 --untracked-files=all)" + [[ "$MERGED_SHA" =~ ^[0-9a-f]{40}$ ]] + [[ "$SOURCE_RUN" =~ ^[1-9][0-9]*$ ]] + [[ "$ARTIFACT_ID" =~ ^[1-9][0-9]*$ ]] + test "$SOURCE_RUN" != "$GITHUB_RUN_ID" + - name: Fetch genuine exact-M source artifact by immutable GitHub identity + shell: bash + env: + GH_TOKEN: ${{ github.token }} + MERGED_SHA: ${{ inputs.merged_sha }} + SOURCE_RUN: ${{ inputs.candidate_run_id }} + ARTIFACT_ID: ${{ inputs.candidate_artifact_id }} + run: | + set -euo pipefail + gh api "repos/$GITHUB_REPOSITORY/actions/artifacts/$ARTIFACT_ID" \ + > "$RUNNER_TEMP/pr113-upstream-artifact-metadata.json" + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$SOURCE_RUN" \ + > "$RUNNER_TEMP/pr113-upstream-run.json" + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$SOURCE_RUN/jobs?per_page=100" \ + > "$RUNNER_TEMP/pr113-upstream-jobs.json" + gh api "repos/$GITHUB_REPOSITORY/actions/artifacts/$ARTIFACT_ID/zip" \ + > "$RUNNER_TEMP/pr113-upstream-artifact.zip" + python3 - <<'PY' + import hashlib, json, os, stat, zipfile + from pathlib import Path + + root = Path(os.environ["RUNNER_TEMP"]) + meta = json.loads((root/"pr113-upstream-artifact-metadata.json").read_text()) + upstream = root/"pr113-upstream-artifact.zip" + assert 0 < upstream.stat().st_size <= 400*1024*1024 + raw = upstream.read_bytes() + merged = os.environ["MERGED_SHA"] + source_run = int(os.environ["SOURCE_RUN"]) + artifact_id = int(os.environ["ARTIFACT_ID"]) + assert meta.get("id") == artifact_id + assert meta.get("name") == "pr113-preseal-fullscope-candidate" + assert meta.get("expired") is False + assert meta.get("digest") == "sha256:" + hashlib.sha256(raw).hexdigest() + context = meta.get("workflow_run") or {} + assert context.get("id") == source_run + assert context.get("head_sha") == merged + + # An authentic digest is not a positive preseal result: an + # `if: always()` upload from a failed or superseded attempt has one. + run = json.loads((root/"pr113-upstream-run.json").read_text()) + assert run.get("id") == source_run + assert run.get("head_sha") == merged + assert run.get("head_branch") == "main" + assert run.get("status") == "completed" + assert run.get("conclusion") == "success" + assert str(run.get("path", "")).endswith( + ".github/workflows/pr113-preseal-fullscope.yml") + jobs = [job for job in json.loads( + (root/"pr113-upstream-jobs.json").read_text())["jobs"] + if job.get("name") == "preseal-fullscope-candidate"] + assert len(jobs) == 1 + job = jobs[0] + assert job.get("run_attempt") == run.get("run_attempt") + assert job.get("status") == "completed" + assert job.get("conclusion") == "success" + assert job["started_at"] <= meta["created_at"] <= job["completed_at"] + + allowed = { + "Permansson_Mathematical_Closure_CANDIDATE.zip", + "SHA256SUMS.txt", + } + target = root/"pr113-original-candidate" + target.mkdir(mode=0o700) + with zipfile.ZipFile(root/"pr113-upstream-artifact.zip") as package: + members = package.infolist() + assert len(members) == 2 + assert {m.filename for m in members} == allowed + assert sum(m.file_size for m in members) <= 384*1024*1024 + 8192 + for item in members: + assert not item.is_dir() + assert 0 < item.file_size <= 384*1024*1024 + assert item.compress_size > 0 + assert item.file_size <= 200*item.compress_size + mode = (item.external_attr >> 16) & 0o170000 + assert mode not in (stat.S_IFLNK, stat.S_IFDIR) + name = target/item.filename + with package.open(item) as stream: + bytes_ = stream.read(item.file_size + 1) + assert len(bytes_) == item.file_size + name.write_bytes(bytes_) + PY + - name: Independently replay exact candidate archive and sidecar bytes + shell: bash + run: | + set -euo pipefail + python3 -B release/mathematical-closure-1/postseal_replay.py \ + --archive "$RUNNER_TEMP/pr113-original-candidate/Permansson_Mathematical_Closure_CANDIDATE.zip" \ + --sidecar "$RUNNER_TEMP/pr113-original-candidate/SHA256SUMS.txt" \ + --output "$RUNNER_TEMP/postseal_replay_receipt.json" + python3 - <<'PY' + import json, os + from pathlib import Path + receipt = json.loads( + (Path(os.environ["RUNNER_TEMP"]) / + "postseal_replay_receipt.json").read_bytes()) + assert receipt["status"] == "CANDIDATE_ARCHIVE_REPLAYED_UNREVIEWED" + assert receipt["source_commit_sha"] == os.environ["GITHUB_SHA"] + assert receipt["authenticated_ci_artifact"] is False + assert receipt["independent_review"] is False + assert receipt["full_release_approved"] is False + PY + - name: Package the original candidate bytes, not reconstructed surrogates + shell: bash + run: | + set -euo pipefail + mkdir -p "$RUNNER_TEMP/pr113-postseal-artifact/candidate" + cp "$RUNNER_TEMP/pr113-original-candidate/Permansson_Mathematical_Closure_CANDIDATE.zip" \ + "$RUNNER_TEMP/pr113-postseal-artifact/candidate/" + cp "$RUNNER_TEMP/pr113-original-candidate/SHA256SUMS.txt" \ + "$RUNNER_TEMP/pr113-postseal-artifact/candidate/" + cp "$RUNNER_TEMP/postseal_replay_receipt.json" \ + "$RUNNER_TEMP/pr113-postseal-artifact/postseal_replay_receipt.json" + cp "$RUNNER_TEMP/pr113-upstream-artifact-metadata.json" \ + "$RUNNER_TEMP/pr113-postseal-artifact/upstream_source_artifact.json" + - name: Retain exact-M unreviewed postseal producing artifact + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-complete-postseal-candidate + path: ${{ runner.temp }}/pr113-postseal-artifact/ + if-no-files-found: warn + retention-days: 30 diff --git a/docs/operations/pr113_unapproved_workflows/pr113-preseal-fullscope.yml b/docs/operations/pr113_unapproved_workflows/pr113-preseal-fullscope.yml new file mode 100644 index 00000000..b849a149 --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/pr113-preseal-fullscope.yml @@ -0,0 +1,182 @@ +# REVIEW-PENDING ONLY. This is NOT an active Github Actions workflow. +# Independent security authorization of the full workflow set and genuine +# 45/11 mathematical/scholarly acceptance are prerequisites for activation. +# The transported input artifact is untrusted; seal_approved.py --full +# independently revalidates all source, GitHub, receipt and review evidence. +name: PR113 Complete Mathematical Preseal Candidate + +on: + workflow_dispatch: + inputs: + merged_sha: + description: Exact owner-authorized already-merged main M + required: true + type: string + carrier_run_id: + description: Successful exact-M upstream review-input carrier run ID + required: true + type: string + carrier_run_attempt: + description: Exact successful carrier run attempt + required: true + type: string + carrier_job_id: + description: Original successful carrier producer job ID + required: true + type: string + carrier_check_id: + description: Original successful producer check-run ID + required: true + type: string + carrier_artifact_id: + description: Original carrier ZIP artifact ID + required: true + type: string + +permissions: + contents: read + actions: read + checks: read + pull-requests: read + +jobs: + preseal-fullscope-candidate: + name: preseal-fullscope-candidate + runs-on: ubuntu-latest + timeout-minutes: 240 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Fail closed unless checkout is authorized genuine main merge M + shell: bash + env: + MERGED_SHA: ${{ inputs.merged_sha }} + CARRIER_RUN: ${{ inputs.carrier_run_id }} + CARRIER_ATTEMPT: ${{ inputs.carrier_run_attempt }} + CARRIER_JOB: ${{ inputs.carrier_job_id }} + CARRIER_CHECK: ${{ inputs.carrier_check_id }} + CARRIER_ARTIFACT: ${{ inputs.carrier_artifact_id }} + run: | + set -euo pipefail + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + test "$GITHUB_REF" = "refs/heads/main" + test "$GITHUB_SHA" = "$MERGED_SHA" + test "$(git rev-parse HEAD)" = "$MERGED_SHA" + test -z "$(git status --porcelain=v1 --untracked-files=all)" + [[ "$MERGED_SHA" =~ ^[0-9a-f]{40}$ ]] + for field in "$CARRIER_RUN" "$CARRIER_ATTEMPT" "$CARRIER_JOB" "$CARRIER_CHECK" "$CARRIER_ARTIFACT"; do + [[ "$field" =~ ^[1-9][0-9]*$ ]] + done + test "$CARRIER_RUN" != "$GITHUB_RUN_ID" + - name: Fetch genuine upstream input artifact and immutable job/check identity + shell: bash + env: + GH_TOKEN: ${{ github.token }} + MERGED_SHA: ${{ inputs.merged_sha }} + CARRIER_RUN: ${{ inputs.carrier_run_id }} + CARRIER_ATTEMPT: ${{ inputs.carrier_run_attempt }} + CARRIER_JOB: ${{ inputs.carrier_job_id }} + CARRIER_CHECK: ${{ inputs.carrier_check_id }} + CARRIER_ARTIFACT: ${{ inputs.carrier_artifact_id }} + run: | + set -euo pipefail + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$CARRIER_RUN" \ + > "$RUNNER_TEMP/pr113-review-run.json" + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$CARRIER_RUN/jobs?per_page=100" \ + > "$RUNNER_TEMP/pr113-review-jobs.json" + gh api "repos/$GITHUB_REPOSITORY/check-runs/$CARRIER_CHECK" \ + > "$RUNNER_TEMP/pr113-review-check.json" + gh api "repos/$GITHUB_REPOSITORY/actions/artifacts/$CARRIER_ARTIFACT" \ + > "$RUNNER_TEMP/pr113-review-artifact.json" + gh api "repos/$GITHUB_REPOSITORY/actions/artifacts/$CARRIER_ARTIFACT/zip" \ + > "$RUNNER_TEMP/pr113-review-input-original.zip" + python3 -B verification/research/pr113_preseal_input_carrier.py \ + --merged "$MERGED_SHA" \ + --run-id "$CARRIER_RUN" \ + --attempt "$CARRIER_ATTEMPT" \ + --job-id "$CARRIER_JOB" \ + --check-id "$CARRIER_CHECK" \ + --artifact-id "$CARRIER_ARTIFACT" \ + --run "$RUNNER_TEMP/pr113-review-run.json" \ + --jobs "$RUNNER_TEMP/pr113-review-jobs.json" \ + --check "$RUNNER_TEMP/pr113-review-check.json" \ + --metadata "$RUNNER_TEMP/pr113-review-artifact.json" \ + --zip "$RUNNER_TEMP/pr113-review-input-original.zip" \ + --out "$RUNNER_TEMP/pr113-reviewed-inputs" + - name: Install exact PDF source reproduction requirements + shell: bash + run: | + set -euo pipefail + sudo apt-get update -qq + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ + texlive-latex-base texlive-latex-recommended \ + texlive-latex-extra texlive-pictures \ + texlive-fonts-recommended poppler-utils + - name: Build the actual M source paper and all rendered pages + shell: bash + run: | + set -euo pipefail + python3 -B release/mathematical-closure-1/build.py \ + --mode draft --render --out "$RUNNER_TEMP/pr113-paper-draft" + - name: Independently authenticate full 45/11 scholarly/review/CI source and seal + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + INPUT="$RUNNER_TEMP/pr113-reviewed-inputs" + python3 -B release/mathematical-closure-1/seal_approved.py \ + --draft-dir "$RUNNER_TEMP/pr113-paper-draft" \ + --approved-claim-manifest "$INPUT/approved_claim_manifest.json" \ + --source-receipts "$INPUT/source_receipts.json" \ + --bundle "$INPUT/evidence_bundle.zip" \ + --reproduction-zip "$INPUT/reproduction.zip" \ + --reproduction-ids "$INPUT/reproduction_ids.json" \ + --additional-contract "$INPUT/additional_contract.json" \ + --additional-zips "$INPUT/additional_zips" \ + --workflow-blob-candidates "$INPUT/workflow_blob_candidates.json" \ + --out "$RUNNER_TEMP/pr113-preseal-candidate" --full + - name: Independently replay candidate ZIP, sidecar and complete source PDF + shell: bash + run: | + set -euo pipefail + python3 -B release/mathematical-closure-1/postseal_replay.py \ + --archive "$RUNNER_TEMP/pr113-preseal-candidate/Permansson_Mathematical_Closure_CANDIDATE.zip" \ + --sidecar "$RUNNER_TEMP/pr113-preseal-candidate/SHA256SUMS.txt" \ + --output "$RUNNER_TEMP/pr113-preseal-local-replay-unreviewed.json" + python3 - <<'PY' + import hashlib, json, os + from pathlib import Path + folder = Path(os.environ["RUNNER_TEMP"]) + actual = json.loads((folder/"pr113-preseal-local-replay-unreviewed.json").read_bytes()) + assert actual["status"] == "CANDIDATE_ARCHIVE_REPLAYED_UNREVIEWED" + assert actual["source_commit_sha"] == os.environ["GITHUB_SHA"] + assert actual["complete_original_members_verified"] is True + assert actual["independent_review"] is False + assert actual["authenticated_ci_artifact"] is False + assert actual["full_release_approved"] is False + input_report = json.loads( + (folder/"pr113-reviewed-inputs/transport_diagnostics.json").read_bytes()) + assert input_report["status"] == \ + "GH_SOURCE_TRANSPORT_VERIFIED__RELEASE_REVIEW_PENDING" + assert input_report["source_commit_sha"] == os.environ["GITHUB_SHA"] + assert input_report["transport_artifact_sha256"] == hashlib.sha256( + (folder/"pr113-review-input-original.zip").read_bytes()).hexdigest() + assert input_report["independent_review"] is False + assert input_report["mathematical_acceptance"] is False + assert input_report["publication_authorized"] is False + PY + - name: Upload real candidate ZIP + original SHA256 sidecar ONLY after success + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-preseal-fullscope-candidate + path: | + ${{ runner.temp }}/pr113-preseal-candidate/Permansson_Mathematical_Closure_CANDIDATE.zip + ${{ runner.temp }}/pr113-preseal-candidate/SHA256SUMS.txt + if-no-files-found: error + retention-days: 30 diff --git a/docs/operations/pr113_unapproved_workflows/pr113-source-controls-v3_1.yml b/docs/operations/pr113_unapproved_workflows/pr113-source-controls-v3_1.yml new file mode 100644 index 00000000..ac41a782 --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/pr113-source-controls-v3_1.yml @@ -0,0 +1,393 @@ +# PR113 v3.1 PROPOSED WORKFLOW ONLY. NEVER INSTALL BEFORE AN +# INDEPENDENT SECURITY REVIEW OF THE COMPLETE ORIGINAL 25 + SUCCESSOR SET. +name: PR113 Full v3.1 Original Source Controls + +on: + push: + branches: ['research/**'] + paths: + - ".github/workflows/pr113-source-controls-v3_1.yml" + - "verification/research/**" + - "research/paper_closures/post_merge_112/**" + - "PermanssonResearch/**" + # This staged source is not dispatchable until separately authorized + # default-branch installation. Every PR/head choice is authenticated live. + workflow_dispatch: + inputs: + run_mode: + description: Original source controls or independent legacy20 replay + required: true + type: choice + options: + - source_controls + - reviewed_legacy20 + source_head: + description: Exact current frozen PR source H + required: true + type: string + cert_pr_number: + description: Actual successor PR number; blank only for original PR113 + required: false + type: string + survey_run_id: + required: false + type: string + survey_run_attempt: + required: false + type: string + survey_job_id: + required: false + type: string + survey_check_run_id: + required: false + type: string + survey_artifact_id: + required: false + type: string + review_comment_id: + required: false + type: string + +permissions: + contents: read + actions: read + checks: read + pull-requests: read + +concurrency: + group: pr113-v31-${{ github.ref }} + cancel-in-progress: true + +jobs: + original-positive45: + if: (github.event_name == 'push' && github.ref == 'refs/heads/research/final-mathematical-closure') || (github.event_name == 'workflow_dispatch' && inputs.run_mode == 'source_controls') + env: + PERMANSSON_CERT_PR_NUMBER: ${{ inputs.cert_pr_number }} + PR113_REQUESTED_SOURCE_HEAD: ${{ inputs.source_head }} + PR113_REQUESTED_RUN_MODE: ${{ inputs.run_mode }} + runs-on: ubuntu-latest + timeout-minutes: 360 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: "3.13" + - name: Authenticate genuine original PR113 or successor CERT source + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + python3 -B verification/research/pr113_ci_producer_source_v3.py \ + --head "$GITHUB_SHA" --mode source_controls + - name: Require exact PR branch, clean source, reviewed selections + shell: bash + run: | + set -euo pipefail + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + [[ "$GITHUB_REF" == refs/heads/research/* ]] + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test -z "$(git status --porcelain=v1 --untracked-files=all)" + test -s research/paper_closures/post_merge_112/reviewed_positive45_selection_v31.json + - name: Build and axiom-audit pinned Lean research + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Execute all 45 source-reviewed original positives + shell: bash + run: | + set -euo pipefail + cp research/paper_closures/post_merge_112/reviewed_positive45_selection_v31.json "$RUNNER_TEMP/positive45.json" + python3 -B verification/research/final_closure_positive_route_candidate.py --head "$GITHUB_SHA" --selection "$RUNNER_TEMP/positive45.json" --out "$RUNNER_TEMP/positive45-real" + python3 -B verification/research/pr113_pack_ci_positive_owners.py --head "$GITHUB_SHA" --run-id "$GITHUB_RUN_ID" --run-attempt "$GITHUB_RUN_ATTEMPT" --source "$RUNNER_TEMP/positive45-real" --output "$RUNNER_TEMP/positive45-owner-index" + - name: Retain real original 45 positive controls without approval + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-v31-positive45 + path: ${{ runner.temp }}/positive45-owner-index/ + if-no-files-found: warn + retention-days: 30 + + original-legacy20: + if: (github.event_name == 'push' && github.ref == 'refs/heads/research/final-mathematical-closure') || (github.event_name == 'workflow_dispatch' && inputs.run_mode == 'source_controls') + env: + PERMANSSON_CERT_PR_NUMBER: ${{ inputs.cert_pr_number }} + PR113_REQUESTED_SOURCE_HEAD: ${{ inputs.source_head }} + PR113_REQUESTED_RUN_MODE: ${{ inputs.run_mode }} + runs-on: ubuntu-latest + timeout-minutes: 360 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: "3.13" + - name: Authenticate genuine original PR113 or successor CERT source + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + python3 -B verification/research/pr113_ci_producer_source_v3.py \ + --head "$GITHUB_SHA" --mode source_controls + - name: Require exactly frozen original criterion/test/provider intent + shell: bash + run: | + set -euo pipefail + [[ "$GITHUB_REF" == refs/heads/research/* ]] + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test -z "$(git status --porcelain=v1 --untracked-files=all)" + test -s research/paper_closures/post_merge_112/legacy20_source_intent_v31.json + - name: Compile research with pinned Lean/axiom audit + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Discover all actual 4+9+8 exact-H original GH producing artifacts + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + # Runtime IDs CANNOT appear in a source-frozen selection file. + # Fail closed until all original 21 source-bound jobs pass at H. + python3 -B verification/research/pr113_original21_live_catalog.py \ + --head "$GITHUB_SHA" --out "$RUNNER_TEMP/original21-input" + python3 -B verification/research/final_closure_producer_catalog.py \ + --head "$GITHUB_SHA" \ + --catalog "$RUNNER_TEMP/original21-input/original21_catalog.json" \ + --output "$RUNNER_TEMP/original21-catalog" + - name: Enumerate actual semantic-negative capture choices, not author-assigned approval + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + python3 -B verification/research/pr113_legacy20_runtime_resolution.py \ + --head "$GITHUB_SHA" \ + --intent research/paper_closures/post_merge_112/legacy20_source_intent_v31.json \ + --catalog-dir "$RUNNER_TEMP/original21-catalog" \ + --producer-zips "$RUNNER_TEMP/original21-input" \ + --out "$RUNNER_TEMP/legacy20-observed" + - name: Retain observed actual events for independent source-relevance review + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-v31-legacy20-observation-candidates + path: ${{ runner.temp }}/legacy20-observed/ + if-no-files-found: warn + retention-days: 30 + # A separate manually resumed run binds reviewer input AFTER original CI and + # AFTER a successful observation survey. No human input is awaited by a + # runner, and no source edit supplies runtime IDs. No self-approval. + original-legacy20-replay: + if: github.event_name == 'workflow_dispatch' && inputs.run_mode == 'reviewed_legacy20' + name: legacy20-source-review-replay + env: + PERMANSSON_CERT_PR_NUMBER: ${{ inputs.cert_pr_number }} + PR113_REQUESTED_SOURCE_HEAD: ${{ inputs.source_head }} + PR113_REQUESTED_RUN_MODE: ${{ inputs.run_mode }} + runs-on: ubuntu-latest + timeout-minutes: 360 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: "3.13" + - name: Authenticate genuine original PR113 or successor CERT source + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + python3 -B verification/research/pr113_ci_producer_source_v3.py \ + --head "$GITHUB_SHA" --mode reviewed_legacy20 + - name: Bind actual frozen Git checkout and external runtime IDs + shell: bash + env: + SOURCE_HEAD: ${{ inputs.source_head }} + SURVEY_RUN: ${{ inputs.survey_run_id }} + SURVEY_ATTEMPT: ${{ inputs.survey_run_attempt }} + SURVEY_JOB: ${{ inputs.survey_job_id }} + SURVEY_CHECK: ${{ inputs.survey_check_run_id }} + SURVEY_ARTIFACT: ${{ inputs.survey_artifact_id }} + REVIEW_COMMENT: ${{ inputs.review_comment_id }} + run: | + set -euo pipefail + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + [[ "$GITHUB_REF" == refs/heads/research/* ]] + test "$GITHUB_SHA" = "$SOURCE_HEAD" + test "$(git rev-parse HEAD)" = "$SOURCE_HEAD" + test -z "$(git status --porcelain=v1 --untracked-files=all)" + [[ "$SOURCE_HEAD" =~ ^[a-f0-9]{40}$ ]] + for id in "$SURVEY_RUN" "$SURVEY_ATTEMPT" "$SURVEY_JOB" "$SURVEY_CHECK" "$SURVEY_ARTIFACT" "$REVIEW_COMMENT"; do + [[ "$id" =~ ^[1-9][0-9]*$ ]] + done + test "$SURVEY_RUN" != "$GITHUB_RUN_ID" + - name: Build original research/axioms at frozen H + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Download exact original observed survey and authorized reviewer choices + shell: bash + env: + GH_TOKEN: ${{ github.token }} + GITHUB_TOKEN: ${{ github.token }} + SURVEY_RUN: ${{ inputs.survey_run_id }} + SURVEY_ATTEMPT: ${{ inputs.survey_run_attempt }} + SURVEY_JOB: ${{ inputs.survey_job_id }} + SURVEY_CHECK: ${{ inputs.survey_check_run_id }} + SURVEY_ARTIFACT: ${{ inputs.survey_artifact_id }} + REVIEW_COMMENT: ${{ inputs.review_comment_id }} + run: | + set -euo pipefail + gh api "repos/$GITHUB_REPOSITORY/actions/artifacts/$SURVEY_ARTIFACT/zip" \ + > "$RUNNER_TEMP/pr113-real-survey.zip" + test -s "$RUNNER_TEMP/pr113-real-survey.zip" + python3 -B verification/research/pr113_legacy20_choice_delivery.py \ + --head "$GITHUB_SHA" \ + --survey-zip "$RUNNER_TEMP/pr113-real-survey.zip" \ + --run-id "$SURVEY_RUN" --run-attempt "$SURVEY_ATTEMPT" \ + --job-id "$SURVEY_JOB" --check-run-id "$SURVEY_CHECK" \ + --artifact-id "$SURVEY_ARTIFACT" \ + --review-comment-id "$REVIEW_COMMENT" \ + --out "$RUNNER_TEMP/pr113-authenticated-legacy-event-choices" + - name: Reauthenticate all 21 original producers and resolve chosen captured events + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + python3 -B verification/research/pr113_original21_live_catalog.py \ + --head "$GITHUB_SHA" --out "$RUNNER_TEMP/pr113-resumed-original21" + python3 -B verification/research/final_closure_producer_catalog.py \ + --head "$GITHUB_SHA" \ + --catalog "$RUNNER_TEMP/pr113-resumed-original21/original21_catalog.json" \ + --output "$RUNNER_TEMP/pr113-resumed-catalog" + python3 -B verification/research/pr113_legacy20_runtime_resolution.py \ + --head "$GITHUB_SHA" \ + --intent research/paper_closures/post_merge_112/legacy20_source_intent_v31.json \ + --catalog-dir "$RUNNER_TEMP/pr113-resumed-catalog" \ + --producer-zips "$RUNNER_TEMP/pr113-resumed-original21" \ + --detached-event-choice "$RUNNER_TEMP/pr113-authenticated-legacy-event-choices/reviewed_legacy20_runtime_event_choices.json" \ + --out "$RUNNER_TEMP/pr113-reviewed-legacy20-route" + - name: Execute original-selected twenty append-only Lean semantic replays + shell: bash + run: | + set -euo pipefail + python3 -B verification/research/final_closure_batch_legacy_replay.py \ + --head "$GITHUB_SHA" \ + --catalog-dir "$RUNNER_TEMP/pr113-resumed-catalog" \ + --producer-zips "$RUNNER_TEMP/pr113-resumed-original21" \ + --selection "$RUNNER_TEMP/pr113-reviewed-legacy20-route/legacy20_runtime_selection.json" \ + --out "$RUNNER_TEMP/pr113-resumed-legacy20-real" + python3 -B verification/research/pr113_pack_ci_remaining_owners.py \ + --head "$GITHUB_SHA" --kind legacy_negative \ + --run-id "$GITHUB_RUN_ID" --run-attempt "$GITHUB_RUN_ATTEMPT" \ + --source "$RUNNER_TEMP/pr113-resumed-legacy20-real" \ + --output "$RUNNER_TEMP/pr113-resumed-legacy20-owner-index" + - name: Retain original source-owned twenty real replays without certification + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-v31-legacy20 + path: | + ${{ runner.temp }}/pr113-resumed-legacy20-owner-index/ + ${{ runner.temp }}/pr113-authenticated-legacy-event-choices/choice_transport.json + if-no-files-found: warn + retention-days: 30 + + claim-true-false11: + if: (github.event_name == 'push' && github.ref == 'refs/heads/research/final-mathematical-closure') || (github.event_name == 'workflow_dispatch' && inputs.run_mode == 'source_controls') + env: + PERMANSSON_CERT_PR_NUMBER: ${{ inputs.cert_pr_number }} + PR113_REQUESTED_SOURCE_HEAD: ${{ inputs.source_head }} + PR113_REQUESTED_RUN_MODE: ${{ inputs.run_mode }} + runs-on: ubuntu-latest + timeout-minutes: 360 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: "3.13" + - name: Authenticate genuine original PR113 or successor CERT source + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + python3 -B verification/research/pr113_ci_producer_source_v3.py \ + --head "$GITHUB_SHA" --mode source_controls + - name: Require exact original source and reviewed 11 claim false statements + shell: bash + run: | + set -euo pipefail + [[ "$GITHUB_REF" == refs/heads/research/* ]] + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test -z "$(git status --porcelain=v1 --untracked-files=all)" + test -s research/paper_closures/post_merge_112/reviewed_claim11_selection_v31.json + test -d research/paper_closures/post_merge_112/reviewed_claim11_false_sources + test "$(find research/paper_closures/post_merge_112/reviewed_claim11_false_sources -name '*.lean' | wc -l)" -eq 11 + - name: Build/axiom-audit original Lean research providers + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Execute and preserve all 11 true/false claim proof controls + shell: bash + run: | + set -euo pipefail + cp research/paper_closures/post_merge_112/reviewed_claim11_selection_v31.json "$RUNNER_TEMP/claim11.json" + cp -R research/paper_closures/post_merge_112/reviewed_claim11_false_sources "$RUNNER_TEMP/claim11-false-sources" + python3 -B verification/research/final_closure_claim_controls_candidate.py --head "$GITHUB_SHA" --selection "$RUNNER_TEMP/claim11.json" --mutants-root "$RUNNER_TEMP/claim11-false-sources" --out "$RUNNER_TEMP/claims11-real" + python3 -B verification/research/pr113_pack_ci_remaining_owners.py --head "$GITHUB_SHA" --kind claim_pair --run-id "$GITHUB_RUN_ID" --run-attempt "$GITHUB_RUN_ATTEMPT" --source "$RUNNER_TEMP/claims11-real" --output "$RUNNER_TEMP/claims11-owner-index" + - name: Retain original 11 claim true/false pair source checks + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-v31-claims11 + path: ${{ runner.temp }}/claims11-owner-index/ + if-no-files-found: warn + retention-days: 30 diff --git a/docs/operations/pr113_unapproved_workflows/trigger_candidates/README.md b/docs/operations/pr113_unapproved_workflows/trigger_candidates/README.md new file mode 100644 index 00000000..296469b6 --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/trigger_candidates/README.md @@ -0,0 +1,61 @@ +# PR #113 — review-pending exact-H original producer trigger repairs + +**These three YAML files are non-executing proposals.** They live under +`docs/operations/`, not `.github/workflows/`, and have no acceptance or +security authority. The original 25 active workflows remain untouched. + +## Problem addressed + +The original four full compiler shards, nine semantic shards and eight +legacy source-observation shards are necessary original exact-H GitHub +producing artifacts. Their current `push.paths` filters do not reliably run +on verification/release-only commits. The existing `workflow_dispatch` +triggers cannot be relied upon for new workflow files absent from the +default branch. Accordingly, a frozen source H cannot be assumed to have all +21 original producing jobs. + +## Review-candidate source transition + +Each staged proposed workflow retains the complete active source byte-for-byte +**except for its `on:` trigger**. The revised trigger is: + +```yaml +on: + push: + branches: [research/final-mathematical-closure] + workflow_dispatch: +``` + +This always produces original 4+9+8 source-bound candidate jobs on the exact +research-branch commit H when the replacement is independently authorized +and installed. The exact original matrix shards, job names, Lean build, test +method commands and artifact upload logic are otherwise unchanged. + +The versioned `manifest.json` explicitly lists the three active and staged +Git source identities and their 4/9/8 matrix counts. Use +`verification/research/pr113_original_producer_trigger_candidate.py` to +verify that the staged bytes are precisely the trigger-only patches of +**the committed active original source at the checked H** and are not +approved. No dynamic path or workflow hash is accepted. + +## Security review and activation boundary + +Before any staged YAML replaces a real `.github/workflows/` source: +- Original T→T2 and A2 historical verification transitions require genuine + independent security review and owner approval. +- Independently review the full old/new workflow set, trigger frequency, + `contents: read`, checkout/action pins, job/check identity, negative + controls, artifact provenance, and concurrency/cancellation semantics. +- Approve the complete *final proposed tree* and all other workflow + additions together; do not approve this in isolation and later mutate + security-sensitive workflow bytes. +- Install only through an explicitly owner-authorized source commit on the + same PR #113 branch. Activation changes H and resets source/CI/evidence + baselines. Re-run all mandatory 12 CI contexts and all 21 original producer + jobs at the resulting frozen H. +- Never treat compiler success, old-head proof log, source inventory, this + review document or absence of pending workflows as external mathematical + acceptance. + +**Current status:** candidate YAMLs staged; independent review absent; +not installed; mandatory historical verification unchanged. diff --git a/docs/operations/pr113_unapproved_workflows/trigger_candidates/manifest.json b/docs/operations/pr113_unapproved_workflows/trigger_candidates/manifest.json new file mode 100644 index 00000000..de0859db --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/trigger_candidates/manifest.json @@ -0,0 +1,38 @@ +{ + "format": "Permansson.PR113.StagedFullProducerTriggerReview.v1", + "original_T": "71cc34fa0bc4b639c05d2ec5f39f0b88afd56388", + "baseline_head": "fbbdb2968e18af5383b9871eb3bd89d36ad0f7fd", + "source_head": "FINAL_HEAD_MUST_BE_AUTHENTICATED_AT_REVIEW", + "status": "REVIEW_PENDING__ACTIVE_WORKFLOWS_UNCHANGED", + "original_requested_producer_jobs": 21, + "proposals": [ + { + "active": ".github/workflows/pr113-all-case-compiler.yml", + "proposal": "docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-all-case-compiler.yml", + "original_blob": "07791f65c6572ca29ada9a8150a643c51958602f", + "proposed_blob": "6b27928aac900274eca4f683b1e209572a3f3d92", + "producer_shards": 4, + "expected_job": "compiler-shard" + }, + { + "active": ".github/workflows/pr113-all-original-semantic-candidates.yml", + "proposal": "docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-all-original-semantic-candidates.yml", + "original_blob": "2027a05a225dd49e92415bcbd81d73755111d5ef", + "proposed_blob": "ef79a9c668ebdce2ea2688cdb66b0c02561a03d5", + "producer_shards": 9, + "expected_job": "semantic-candidate-shard" + }, + { + "active": ".github/workflows/pr113-legacy-semantic-evidence.yml", + "proposal": "docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-legacy-semantic-evidence.yml", + "original_blob": "6e65891db1239bf87250ff3420e9ea96407353a4", + "proposed_blob": "21d7c707d1e90b0b83c69e0402447e5419d8cc91", + "producer_shards": 8, + "expected_job": "legacy-original-shard" + } + ], + "independent_security_review": false, + "owner_approved_installation": false, + "mathematical_acceptance": false, + "publication_authorized": false +} diff --git a/docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-all-case-compiler.yml b/docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-all-case-compiler.yml new file mode 100644 index 00000000..6b27928a --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-all-case-compiler.yml @@ -0,0 +1,104 @@ +name: PR113 All-Case Compiler Candidate + +on: + push: + branches: [research/final-mathematical-closure] + workflow_dispatch: + +concurrency: + group: pr113-all-case-compiler-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + inventory-preflight: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Bind genuine branch checkout and resolve ALL historical theorem names + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + mkdir -p "$RUNNER_TEMP/all-case-inventory" + python3 verification/research/final_closure_complete_lean_evidence.py \ + --head "$GITHUB_SHA" \ + --output "$RUNNER_TEMP/not-written" \ + --inventory-only \ + > "$RUNNER_TEMP/all-case-inventory/inventory.json" + python3 - <<'PY' + import json, os + from pathlib import Path + inv = json.loads((Path(os.environ["RUNNER_TEMP"]) / + "all-case-inventory/inventory.json").read_text()) + assert inv["cases"] == 45 and inv["claims"] == 11 + assert inv["proof_targets"] > 11 + assert len(inv["declarations"]) == inv["proof_targets"] + assert inv["status"] == "SOURCE_CANDIDATE_UNREVIEWED" + print("All-case compile candidates:", inv["proof_targets"]) + PY + - name: Preserve source inventory candidate + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-all-case-owner-inventory + path: ${{ runner.temp }}/all-case-inventory/ + retention-days: 30 + if-no-files-found: warn + + compiler-shard: + needs: inventory-preflight + runs-on: ubuntu-latest + timeout-minutes: 110 + strategy: + fail-fast: false + max-parallel: 2 + matrix: + shard: [0, 1, 2, 3] + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Build pinned research library before theorem probes + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Execute original 45-case and 11-claim assigned compiler shard + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + python3 verification/research/final_closure_complete_lean_evidence.py \ + --head "$GITHUB_SHA" \ + --output "$RUNNER_TEMP/complete-shard" \ + --shard-index "${{ matrix.shard }}" --shard-count 4 + - name: Preserve unreviewed theorem type and axiom logs + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-compiler-shard-${{ matrix.shard }} + path: ${{ runner.temp }}/complete-shard/ + retention-days: 30 + if-no-files-found: warn diff --git a/docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-all-original-semantic-candidates.yml b/docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-all-original-semantic-candidates.yml new file mode 100644 index 00000000..ef79a9c6 --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-all-original-semantic-candidates.yml @@ -0,0 +1,67 @@ +name: PR113 All Original Semantic Candidate Evidence + +on: + push: + branches: [research/final-mathematical-closure] + workflow_dispatch: + +concurrency: + group: pr113-original-semantic-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + semantic-candidate-shard: + runs-on: ubuntu-latest + timeout-minutes: 110 + strategy: + fail-fast: false + max-parallel: 3 + matrix: + shard: [0, 1, 2, 3, 4, 5, 6, 7, 8] + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Check exact Git checkout before mathematical work + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + test -z "$(git status --porcelain=v1 --untracked-files=all -- PermanssonResearch PermanssonLean verification/research)" + - name: Build pinned Lean research tree and audit axioms + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Run exact source/mutant candidates (not approval) + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + python3 verification/research/final_closure_all_original_semantics.py \ + --head "$GITHUB_SHA" \ + --shard-index "${{ matrix.shard }}" --shard-count 9 \ + --out "$RUNNER_TEMP/pr113-semantic-source-${{ matrix.shard }}" + - name: Preserve unreviewed source/mutant compiler evidence + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-all-original-semantic-candidates-${{ matrix.shard }} + path: ${{ runner.temp }}/pr113-semantic-source-${{ matrix.shard }}/ + retention-days: 30 + if-no-files-found: error diff --git a/docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-legacy-semantic-evidence.yml b/docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-legacy-semantic-evidence.yml new file mode 100644 index 00000000..21d7c707 --- /dev/null +++ b/docs/operations/pr113_unapproved_workflows/trigger_candidates/pr113-legacy-semantic-evidence.yml @@ -0,0 +1,70 @@ +name: PR113 Legacy Original Semantic Compiler Candidates + +on: + push: + branches: [research/final-mathematical-closure] + workflow_dispatch: + +concurrency: + group: pr113-legacy-semantic-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + legacy-original-shard: + runs-on: ubuntu-latest + timeout-minutes: 110 + strategy: + fail-fast: false + max-parallel: 2 + matrix: + shard: [0, 1, 2, 3, 4, 5, 6, 7] + env: + PYTHONDONTWRITEBYTECODE: '1' + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.13' + - name: Verify checkout identity and original policy + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$GITHUB_REPOSITORY" = "HMarcusWH/PermanssonLean" + test -z "$(git status --porcelain=v1 --untracked-files=all -- PermanssonResearch PermanssonLean verification/research research/paper_closures/post_merge_112)" + python3 -m unittest discover -s verification/research -p test_final_closure_legacy_semantic_evidence.py -v + - name: Build and audit pinned research source + uses: leanprover/lean-action@24581d74d674803a25a81b10b059c5c0cdd344c6 + with: + auto-config: false + build: true + build-args: PermanssonResearch + test: false + lint: false + use-mathlib-cache: true + use-github-cache: false + axiom-audit: true + axiom-audit-root: PermanssonResearch + - name: Execute and retain original negative unittest compiler observations + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + python3 verification/research/final_closure_legacy_semantic_evidence.py \ + --head "$GITHUB_SHA" \ + --shard-index "${{ matrix.shard }}" --shard-count 8 \ + --out "$RUNNER_TEMP/pr113-legacy-semantic-${{ matrix.shard }}" + - name: Retain source bytes, compiler logs, and explicitly unreviewed manifests + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pr113-legacy-semantic-candidates-${{ matrix.shard }} + path: ${{ runner.temp }}/pr113-legacy-semantic-${{ matrix.shard }}/ + retention-days: 30 + if-no-files-found: warn diff --git a/docs/operations/pr113_v31_gate_usage_and_limits_20261024.md b/docs/operations/pr113_v31_gate_usage_and_limits_20261024.md new file mode 100644 index 00000000..8b4cb362 --- /dev/null +++ b/docs/operations/pr113_v31_gate_usage_and_limits_20261024.md @@ -0,0 +1,42 @@ +# PR113 full v3.1 execution entrypoints — source implementation note + +This note describes candidate code only. It is not a security review, mathematical acceptance, merger approval, or scholarly release. Everything remains on PR #113. + +## Full-source and release calling convention + +The read-only verification and release entrypoints now take three **required together** source-bound v3.1 inputs when claiming full 45-case/11-claim coverage: + +- `--additional-contract`: the original v3.1 producer registry bound to the exact reviewed H, not a local completion flag. +- `--additional-zips`: detached directory of *original* downloaded GitHub producer artifact ZIPs indexed by authenticated numeric artifact IDs, containing exactly the registered members. +- `--workflow-blob-candidates`: exact versioned workflow source Git object mapping. Mapping OIDs alone **never** establish independent security approval. + +Applies to `verification/research/final_closure_gate.py --phase premerge-full`, to `--phase postmerge-preseal-strict` and `--phase full-release-strict`, to `verification/research/final_closure_verify.py` corresponding modes, and to `release/mathematical-closure-1/seal_approved.py --full` / `verify_release.py --approved-seal` for the complete release. + +The gate reauthenticates 21 original producing ZIPs, all 76 v3.1 role-owned source controls, associated GH run/job/check/attempt/source-workflow/ZIP identities, and the actual positive/negative source/log bytes. This is distinct from approving mathematical relevance; 11 claim pairs produce 22 directional checks. Synthetic positive tests exercise predicates only. + +## Explicitly incomplete dependencies + +- The genuine historical T→T2 archive restoration and original A2 successor migrations remain fail-closed until real independently authorized verification. Do **not** modify historical T/C manifests, switch original expected hashes, skip legacy tests or weaken the preserved 25-workflow security policy. +- The original 3 staged historical workflow blobs remain unchanged. `docs/operations/pr113_unapproved_workflows/pr113-source-controls-v3_1.yml` is an **additional non-executing** reviewed-source candidate for all 45+20+11 original control families. Its missing reviewed selection files are a deliberate stop, not a reason to invent favorable false statements. +- The previous 259/259 Lean type and axiom candidate receipts originated from the earlier real source head and must not be relabeled as execution of a future H. Source identity alone does not grant GH producer/authenticated case acceptance. +- Independently submitted source/security and scholarly approval, genuine owner-authorized merge M with exact-M checks, original ZIP archive source and postseal replay, and explicit publication authority have not occurred. + +Any activation of a staged workflow or acceptance-map edit changes H and requires fresh exact-head CI, producer receipts, mathematical reviews and artifact verification. + +## Exact-H runtime dependency correction + +Never commit future GH artifact IDs or compiler event IDs into source H: +they are generated only when the original CI producers execute. The source +candidate workflow now keeps only +`legacy20_source_intent_v31.json` (static test/provider relevance intent) +under source control. It resolves all 21 producing artifacts at H through +`pr113_original21_live_catalog.py` into an external checked ZIP catalog; +then `pr113_legacy20_runtime_resolution.py` emits the unreviewed observed +event-choice inventory. + +Some cases have multiple actual semantic-negative observations. There is +currently NO authenticated detached reviewer event-choice producer, so +the staged workflow deliberately blocks before claiming completed 20 legacy +replays or 76 additional source owners. Human selection must be source-specific +and its authorization independently attributable—never guessed from a green +compiler exit. diff --git a/release/mathematical-closure-1/.gitignore b/release/mathematical-closure-1/.gitignore new file mode 100644 index 00000000..179b4868 --- /dev/null +++ b/release/mathematical-closure-1/.gitignore @@ -0,0 +1,3 @@ +output/ +__pycache__/ +*.pyc diff --git a/release/mathematical-closure-1/README.md b/release/mathematical-closure-1/README.md new file mode 100644 index 00000000..e6561013 --- /dev/null +++ b/release/mathematical-closure-1/README.md @@ -0,0 +1,56 @@ +# Mathematical-closure release candidate + +**DRAFT — NOT ACCEPTED — NOT PUBLISHED.** For full 45/11 scope the +versioned state machine is `POSTMERGE_PRESEAL_READY` -> +`CANDIDATE_SEAL_BUILT` -> independently authenticated +`POSTSEAL_ARCHIVE_VERIFIED` -> +`FULL_MATHEMATICAL_CLOSURE_RELEASE_READY`. Full seal mode emits only +a candidate ZIP; no source-controlled JSON or local ZIP replay authorizes +publication. Canonical manuscript source lives under `research_papers/mathematical_closure/`. The versioned `manifest.json` is a frozen *claim candidate*, not a future GitHub merge or review receipt. No generated PDF or archive is committed. See build.py and verify_release.py for draft tooling; release approval requires independent source-bound proof, CI and manuscript/visual review. + +## Exact merged-M reproduction + +The postmerge pre-seal gate accepts `--reproduction-zip` plus +`--reproduction-ids` only as a pair. The ZIP must be the **original** +GitHub Actions `pr113-clean-paper-rebuild-draft` archive from a real successful +`clean-paper-rebuild` job at merged **M** on `main`. The ID JSON has +`run_id`, `run_attempt`, `job_id`, `check_run_id`, and `artifact_id`. +`final_closure_reproduction_auth.py` independently rechecks GitHub metadata, +the authoritative artifact digest, and identical actual PDFs, manifests, +archives, logs and page PNGs from two source roots. Without this genuine +receipt the full pre-seal gate remains closed. Even a valid clean-build receipt +is **not** mathematical review, scholarly approval or release authorization. + +## Post-seal release authorization (not yet available) + +`--postseal-zip` and `--postseal-ids` are paired inputs to the strict +full-release verifier. `final_closure_postseal_auth.py` insists on a real +successful exact-M `pr113-postseal-replay.yml` workflow_dispatch, a successful +`postseal-archive-replay` producing check and the GitHub-authoritative +`pr113-complete-postseal-candidate` artifact digest. It re-verifies the exact +retained candidate ZIP and sidecar, complete archive members, and original +unreviewed postseal receipt before recognizing `COMPLETE_ARCHIVE_REPLAY`. +**The new workflow is NOT installed in the protected workflow inventory and +must not be added without independent security authorization.** Until that +reviewed producer exists the final release is necessarily blocked; local +postseal reports are not substitutes. + +## Two independent roots, one fresh TeX output pathname + +The actual October 24 clean-paper logs showed identical eight-page PDFs and +all eight identical rendered PNGs. Only the LaTeX output log differed, from +pdfTeX wrapping different absolute output paths across terminal lines. The +latex_log_sha256 field, source manifest and ZIP checksums then diverged. + +The two-root builder now creates two distinct no-hardlink exact-SHA Git source +clones, runs their TeX compilations sequentially through the same freshly +created absolute output staging pathname, then moves all generated bytes +unchanged to separate first/ and second/ directories. It requires the staging +location to be empty before both builds. Raw TeX logs, scientific diagnostics, +all PDF pages and the original SHA256SUMS comparison are preserved. + +A standalone local pdfTeX smoke with two different source directories yielded +identical PDF bytes and captured full logs. That is not a verified Permansson +manuscript run. The actual clean-paper GitHub action must pass again on exact +final H and later on a genuinely authorized merged M. Neither a local smoke +nor the BUILD success indicates independent scholarly or mathematical review. diff --git a/release/mathematical-closure-1/acceptance_snapshot.json b/release/mathematical-closure-1/acceptance_snapshot.json new file mode 100644 index 00000000..4ceaac02 --- /dev/null +++ b/release/mathematical-closure-1/acceptance_snapshot.json @@ -0,0 +1,7 @@ +{ + "format": "Permansson.MathClosure.AcceptanceSnapshot.v1", + "source_baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "source": "research/paper_closures/post_merge_112/acceptance_decisions.json", + "required_cases": 45, + "status": "PENDING_REVIEWED_FINAL_SNAPSHOT" +} diff --git a/release/mathematical-closure-1/archive_integrity.py b/release/mathematical-closure-1/archive_integrity.py new file mode 100644 index 00000000..b77fd5f6 --- /dev/null +++ b/release/mathematical-closure-1/archive_integrity.py @@ -0,0 +1,175 @@ +"""Independent byte-complete draft manuscript archive verification. + +The build and this verifier intentionally keep independently enumerated +source/output contracts. A checksum of selected files is not enough: a +release archive must contain ALL 13 canonical manuscript inputs and ALL +five generated draft receipt artifacts, with no extra/ambiguous members. +This is byte verification, NOT mathematical or scholarly acceptance. +""" +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +import re +import stat +import zipfile + +SOURCE_FILES = ( + "README.md", "main.tex", "preamble.tex", "references.bib", + "sections/01_scope_and_semantics.tex", + "sections/02_existing_foundations.tex", + "sections/03_discounted_original_margin.tex", + "sections/04_measurable_multiagent_recovery.tex", + "sections/05_nonlinear_occupation.tex", + "sections/06_counterexamples_and_limits.tex", + "sections/07_formal_verification.tex", + "appendices/A_theorem_crosswalk.tex", + "appendices/B_reproducibility.tex", +) +GENERATED = ( + "Permansson_Mathematical_Closure.pdf", + "Permansson_Mathematical_Closure.tex", + "manifest.lock.json", "source_ci_receipts.lock.json", + "latex_build.log", +) +SUMMED = ( + "Permansson_Mathematical_Closure.pdf", + "Permansson_Mathematical_Closure.tex", + "manifest.lock.json", "source_ci_receipts.lock.json", + "Permansson_Mathematical_Closure_E2E.zip", +) +SHA256 = re.compile(r"^[0-9a-f]{64}$") +MEMBER_LIMIT = 32 * 1024 * 1024 +TOTAL_LIMIT = 96 * 1024 * 1024 +ZIP_BYTES_LIMIT = 48 * 1024 * 1024 + + +class DraftArchiveError(ValueError): + pass + + +def demand(test, why): + if not test: + raise DraftArchiveError(why) + + +def sha_bytes(b): + return hashlib.sha256(b).hexdigest() + + +def secure_members(archive): + path = Path(archive) + demand(path.is_file() and not path.is_symlink() and + path.stat().st_size <= ZIP_BYTES_LIMIT, + "missing/unsafe/oversized draft ZIP") + expected = {"manuscript/" + name for name in SOURCE_FILES} + expected.update(GENERATED) + with zipfile.ZipFile(path) as z: + infos = z.infolist() + demand(len(infos) == len(expected), "extra/duplicate/missing draft ZIP member") + checked = set() + actual = set() + total = 0 + for i in infos: + path = i.filename + norm = path.casefold() + mode = i.external_attr >> 16 + demand(path in expected and norm not in checked and + not path.startswith("/") and "\\" not in path and + ".." not in path.split("/") and + not i.is_dir() and (mode == 0 or stat.S_ISREG(mode)), + "unsafe or unexpected draft ZIP member") + demand(not (i.flag_bits & 1) and + 0 <= i.file_size <= MEMBER_LIMIT, + "encrypted or oversized draft ZIP member") + checked.add(norm) + actual.add(path) + total += i.file_size + demand(actual == expected and total <= TOTAL_LIMIT, + "draft source/output archive not complete") + data = {} + for name in sorted(expected): + raw = z.read(name) + demand(len(raw) <= MEMBER_LIMIT, "member exceeded declared limit") + data[name] = raw + demand(z.testzip() is None, "corrupted ZIP member") + return data + + +def verify_draft_archive(directory: Path, manifest: dict): + directory = Path(directory) + sources = manifest.get("source_tree_files") + demand(isinstance(sources, dict) and + set(sources) == { + "research_papers/mathematical_closure/" + name + for name in SOURCE_FILES + }, "missing/unknown one of 13 canonical manuscript inputs") + for name, digest in sources.items(): + demand(isinstance(digest, str) and SHA256.fullmatch(digest), + "invalid manuscript source digest: " + name) + bundle = secure_members(directory / "Permansson_Mathematical_Closure_E2E.zip") + for src in SOURCE_FILES: + key = "research_papers/mathematical_closure/" + src + demand(sha_bytes(bundle["manuscript/" + src]) == sources[key], + "source content mismatch: " + src) + for name in GENERATED: + real = directory / name + demand(real.is_file() and not real.is_symlink() and + sha_bytes(real.read_bytes()) == sha_bytes(bundle[name]), + "archived artifact differs from draft byte: " + name) + expected_digests = { + "Permansson_Mathematical_Closure.pdf": manifest.get("pdf_sha256"), + "Permansson_Mathematical_Closure.tex": manifest.get("tex_sha256"), + "latex_build.log": manifest.get("latex_log_sha256"), + } + for name, expected in expected_digests.items(): + demand(isinstance(expected, str) and SHA256.fullmatch(expected) and + sha_bytes(bundle[name]) == expected, + "draft manifest digest differs: " + name) + demand(bundle["Permansson_Mathematical_Closure.pdf"].startswith(b"%PDF-"), + "draft PDF signature missing") + demand(bundle["manifest.lock.json"] == + (directory / "manifest.lock.json").read_bytes(), + "archived draft manifest was relabeled") + receipt = json.loads(bundle["source_ci_receipts.lock.json"]) + demand(isinstance(receipt, dict) and + receipt.get("status") == "MISSING_POST_MERGE_SOURCE_AND_REVIEWS" and + receipt.get("ci_receipts") == [] and + receipt.get("independent_reviews") == [], + "draft may not contain unauthenticated source approvals") + sums = directory / "SHA256SUMS.txt" + demand(sums.is_file() and not sums.is_symlink(), "missing SHA256SUMS.txt") + actual = {} + for line in sums.read_text(encoding="utf-8").splitlines(): + bits = line.split(" ") + demand(len(bits) == 2, "noncanonical checksum line") + h, name = bits + demand(isinstance(h, str) and SHA256.fullmatch(h) and + name in SUMMED and name not in actual, + "unknown/duplicate checksum input") + actual[name] = h + demand(set(actual) == set(SUMMED), "checksum inventory incomplete") + for name, expected in actual.items(): + p = directory / name + demand(p.is_file() and not p.is_symlink() and + sha_bytes(p.read_bytes()) == expected, + "checksum mismatch: " + name) + page_dir = directory / "pages" + if page_dir.exists(): + demand(page_dir.is_dir() and not page_dir.is_symlink(), + "invalid rendered page directory") + files = list(page_dir.iterdir()) + demand(all(p.is_file() and not p.is_symlink() and + re.fullmatch(r"page-[0-9]+[.]png", p.name) + for p in files), "unrelated/malformed page image") + count = manifest.get("pdf_page_count") + demand(type(count) is int and count > 0 and + sorted(int(p.stem[5:]) for p in files) == + list(range(1, count + 1)), + "PDF render has missing/extra/duplicate pages") + return {"status": "COMPLETE_UNAPPROVED_DRAFT", + "canonical_sources": len(SOURCE_FILES), + "archived_outputs": len(GENERATED), + "pdf_sha256": manifest["pdf_sha256"], + "source_commit_sha": manifest.get("source_commit_sha")} diff --git a/release/mathematical-closure-1/build.py b/release/mathematical-closure-1/build.py new file mode 100644 index 00000000..19644d1f --- /dev/null +++ b/release/mathematical-closure-1/build.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Reproducible draft PDF/TeX/manifest build; no source release or approval.""" +from __future__ import annotations +import argparse, hashlib, json, os, shutil, subprocess, sys, zipfile +from pathlib import Path + +ROOT=Path(__file__).resolve().parents[2] +PAPER=ROOT/"research_papers/mathematical_closure" +RELEASE=ROOT/"release/mathematical-closure-1" +SRC=["README.md","main.tex","preamble.tex","references.bib", + *[f"sections/{x}" for x in ( + "01_scope_and_semantics.tex","02_existing_foundations.tex", + "03_discounted_original_margin.tex","04_measurable_multiagent_recovery.tex", + "05_nonlinear_occupation.tex","06_counterexamples_and_limits.tex", + "07_formal_verification.tex")], + "appendices/A_theorem_crosswalk.tex","appendices/B_reproducibility.tex"] + +def sha(data: bytes)->str: return hashlib.sha256(data).hexdigest() + +def scoped_tex_environment(base: dict[str, str]) -> dict[str, str]: + """Use the current real manuscript/BibTeX input dir as a relative path. + + Absolute TEXINPUTS caused pdfTeX to echo absolute *wrapped* source paths. + A naive replacement could not recognize path strings split at 79 columns, + so two verified PDF-identical roots still had different logs/manifests. + This sets the standard Kpathsea relative current-directory search and + retains the optional existing system/default search path unchanged. + """ + environment = dict(base) + environment["TEXINPUTS"] = "." + os.pathsep + base.get("TEXINPUTS", "") + environment["BIBINPUTS"] = "." + os.pathsep + base.get("BIBINPUTS", "") + return environment +def run(cmd,cwd,env=None): + p=subprocess.run(cmd,cwd=cwd,env=env,check=False,capture_output=True,text=True,timeout=420) + if p.returncode: raise RuntimeError(f"{cmd}: {p.returncode}\n{(p.stdout+p.stderr)[-7000:]}") + return p.stdout+"\n"+p.stderr +def git_head(): + try:return run(["git","rev-parse","HEAD"],ROOT).strip() + except (OSError,RuntimeError):return None + +def build(out:Path,engine:str,render:bool)->dict: + if out.resolve().is_relative_to(ROOT.resolve()): + raise ValueError("use an out-of-repository output directory") + for f in SRC: + if not (PAPER/f).is_file(): raise ValueError(f"missing manuscript source {f}") + compiler=shutil.which(engine) + if not compiler: raise ValueError(f"TeX compiler unavailable: {engine}") + out.mkdir(parents=True,exist_ok=True) + work=out/"_texwork"; work.mkdir(exist_ok=True) + env=scoped_tex_environment(os.environ) + # Bind timestamps to the authentic source commit, not wall-clock build time. + source_date_epoch=run(["git","show","-s","--format=%ct","HEAD"],ROOT).strip() + if not source_date_epoch.isdigit(): + raise ValueError("authentic source commit epoch unavailable") + env["SOURCE_DATE_EPOCH"]=source_date_epoch + env["FORCE_SOURCE_DATE"]="1" + env["TZ"]="UTC" + env["LC_ALL"]="C" + # BibTeX runs from _texwork, unlike pdfTeX, which runs in PAPER. + # Retain an exact byte copy of the committed references.bib there so + # BibTeX can also report a stable relative filename in its stdout. + bib_source = PAPER/"references.bib" + bib_target = work/"references.bib" + shutil.copyfile(bib_source, bib_target) + if sha(bib_target.read_bytes()) != sha(bib_source.read_bytes()): + raise RuntimeError("BibTeX input copy differs from canonical source") + cmd=[compiler,"-halt-on-error","-interaction=nonstopmode","-file-line-error", + f"-output-directory={work}","main.tex"] + logs=[run(cmd,PAPER,env)] + if shutil.which("bibtex"): logs.append(run(["bibtex","main"],work,env)) + logs += [run(cmd,PAPER,env),run(cmd,PAPER,env)] + # The original LaTeX log contains transient output-root paths; preserve + # diagnostics while replacing only those root locations deterministically. + raw_merged="\n".join(logs) + # Retain a literal, non-published diagnostic transcript in each detached + # output root. It is NOT the hashed/scientific manuscript build receipt. + (out/"latex_build.raw.log").write_text(raw_merged) + normalized=raw_merged + for actual,label in ((str(work.resolve()),""), + (str(out.resolve()),""), + (str(PAPER.resolve()),"")): + normalized=normalized.replace(actual,label) + (out/"latex_build.log").write_text(normalized) + pdf=work/"main.pdf" + if not pdf.is_file() or not pdf.read_bytes().startswith(b"%PDF-"): + raise RuntimeError("no valid PDF from TeX") + shutil.copyfile(pdf,out/"Permansson_Mathematical_Closure.pdf") + shutil.copyfile(PAPER/"main.tex",out/"Permansson_Mathematical_Closure.tex") + pages=None + if shutil.which("pdfinfo"): + for line in run(["pdfinfo",str(pdf)],out).splitlines(): + if line.startswith("Pages:"): pages=int(line.partition(":")[2].strip()) + if render: + if not shutil.which("pdftoppm"):raise RuntimeError("pdftoppm unavailable") + (out/"pages").mkdir(exist_ok=True) + run(["pdftoppm","-png","-r","100",str(pdf),"page"],out/"pages") + sources={f"research_papers/mathematical_closure/{p}":sha((PAPER/p).read_bytes()) + for p in SRC} + m=json.loads((RELEASE/"manifest.json").read_text()) + data={"format":"Permansson.MathClosure.BuildReceipt.v1","status":"DRAFT_NOT_ACCEPTED", + "mode":"DRAFT","source_commit_sha":git_head(), + "source_date_epoch":source_date_epoch,"source_tree_files":sources, + "pdf_sha256":sha((out/"Permansson_Mathematical_Closure.pdf").read_bytes()), + "tex_sha256":sha((out/"Permansson_Mathematical_Closure.tex").read_bytes()), + "latex_log_sha256":sha((out/"latex_build.log").read_bytes()), + "manifest_source_sha256":sha((RELEASE/"manifest.json").read_bytes()), + "pdf_page_count":pages,"compiler":engine, + "compiler_version":run([compiler,"--version"],PAPER).splitlines()[0], + "visual_inspection":"NOT_PERFORMED","independent_review":"NOT_RECEIVED", + "source_ci_validation":"NOT_BOUND_TO_MERGED_MAIN","claims":m["claims"], + "empirical_identification_status":"NOT_ESTABLISHED"} + (out/"manifest.lock.json").write_text(json.dumps(data,sort_keys=True,indent=2)+"\n") + (out/"source_ci_receipts.lock.json").write_text(json.dumps( + {"status":"MISSING_POST_MERGE_SOURCE_AND_REVIEWS","ci_receipts":[], + "independent_reviews":[]},indent=2)+"\n") + members={"manuscript/"+p:(PAPER/p).read_bytes() for p in SRC} + members.update({p:(out/p).read_bytes() for p in + ("Permansson_Mathematical_Closure.pdf","Permansson_Mathematical_Closure.tex", + "manifest.lock.json","source_ci_receipts.lock.json","latex_build.log")}) + archive=out/"Permansson_Mathematical_Closure_E2E.zip" + with zipfile.ZipFile(archive,"w") as z: + for name,payload in sorted(members.items()): + entry=zipfile.ZipInfo(name,(1980,1,1,0,0,0)) + entry.compress_type=zipfile.ZIP_DEFLATED + entry.external_attr=0o100644<<16 + z.writestr(entry,payload,compress_type=zipfile.ZIP_DEFLATED,compresslevel=9) + names=["Permansson_Mathematical_Closure.pdf", + "Permansson_Mathematical_Closure.tex","manifest.lock.json", + "source_ci_receipts.lock.json","Permansson_Mathematical_Closure_E2E.zip"] + (out/"SHA256SUMS.txt").write_text( + "".join(f"{sha((out/p).read_bytes())} {p}\n" for p in names)) + return {"status":data["status"],"pdf_pages":pages,"archive":str(archive)} + +def main(argv=None): + ap=argparse.ArgumentParser() + ap.add_argument("--mode",choices=["draft"],default="draft") + ap.add_argument("--out",type=Path,required=True) + ap.add_argument("--engine",choices=["pdflatex","xelatex","lualatex"],default="pdflatex") + ap.add_argument("--render",action="store_true") + args=ap.parse_args(argv) + try: print(json.dumps(build(args.out,args.engine,args.render),indent=2));return 0 + except (RuntimeError,ValueError,OSError,subprocess.TimeoutExpired) as e: + print("BUILD BLOCKED:",e,file=sys.stderr);return 2 +if __name__=="__main__":raise SystemExit(main()) diff --git a/release/mathematical-closure-1/manifest.json b/release/mathematical-closure-1/manifest.json new file mode 100644 index 00000000..89084516 --- /dev/null +++ b/release/mathematical-closure-1/manifest.json @@ -0,0 +1,34 @@ +{ + "format": "Permansson.MathematicalClosure.Candidate.v1", + "status": "DRAFT_NOT_ACCEPTED", + "release_id": "PERMANSSON-MATH-CLOSURE-1", + "source_baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "source_commit_sha": null, + "claims": [ + "Q1", + "Q2", + "Q3", + "M1", + "M2", + "M3", + "N1", + "N2", + "N3", + "D8", + "D9" + ], + "approved_manuscript_claims": [], + "accepted_claim_receipts": [], + "source_ci_receipts": [], + "review_receipts": [], + "scientific_limitations": [ + "No empirical Exact GR/PR certification", + "No unrestricted policy optimality", + "No Exact GR transport from approximate quotient", + "No full 45-case claim absent strict acceptance" + ], + "empirical_identification_status": "NOT_ESTABLISHED", + "paper_source_directory": "research_papers/mathematical_closure", + "review_status": "NOT_INDEPENDENTLY_REVIEWED", + "build_status": "DRAFT" +} diff --git a/release/mathematical-closure-1/postseal_replay.py b/release/mathematical-closure-1/postseal_replay.py new file mode 100644 index 00000000..1ff4be04 --- /dev/null +++ b/release/mathematical-closure-1/postseal_replay.py @@ -0,0 +1,104 @@ +"""Fail-closed detached replay of an actual full-scope candidate seal. + +This re-opens real ZIP bytes and the original sidecar. It is not an +independently authenticated GitHub check, review or release approval. +""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path +import sys +import zipfile +from seal_approved import verify_approved_archive, SealError, ROOT as ROOT_DIR +from pr113_boundary_io import strict_json_object, detached_destination, BoundaryError + +FORMAT = "Permansson.MathClosure.PostsealReplayCandidate.v1" +# Intake must be bounded *before* z.read("approved_seal.json"). The full +# independently implemented archive verifier runs afterward. +MAX_ARCHIVE_BYTES = 384 * 1024 * 1024 +MAX_MANIFEST_BYTES = 2 * 1024 * 1024 + +def sha(raw: bytes) -> str: + return hashlib.sha256(raw).hexdigest() + +def no_symlink_input(raw: Path, *, exists: bool) -> Path: + """Check each unresolved path component before any symlink resolution.""" + try: + return detached_destination(raw, ROOT_DIR, must_exist=exists) + except BoundaryError as exc: + # Preserve the fail-closed path guard while exposing a stable + # seal-replay public error type to callers. + raise SealError("unsafe detached replay evidence path: " + str(exc)) from exc + + +def replay(archive: Path, sidecar: Path, output: Path) -> dict: + archive = no_symlink_input(archive, exists=True) + sidecar = no_symlink_input(sidecar, exists=True) + output = no_symlink_input(output, exists=False) + if (output.exists() or output.is_relative_to(archive.parent) or + output.is_relative_to(ROOT_DIR)): + raise SealError("replay output must be new and detached from source/candidate") + if archive.stat().st_size > MAX_ARCHIVE_BYTES: + raise SealError("unbounded candidate archive before seal intake") + with zipfile.ZipFile(archive) as z: + matching = [info for info in z.infolist() + if info.filename.casefold() == "approved_seal.json"] + if len(matching) != 1 or matching[0].filename != "approved_seal.json": + raise SealError("missing, duplicated or case-colliding seal member") + info = matching[0] + if (info.is_dir() or (info.flag_bits & 1) or + info.file_size <= 0 or info.file_size > MAX_MANIFEST_BYTES or + info.compress_size <= 0 or info.file_size > 300 * info.compress_size): + raise SealError("unbounded or unsafe original seal member") + with z.open(info) as reader: + seal_bytes = reader.read(MAX_MANIFEST_BYTES + 1) + if len(seal_bytes) != info.file_size: + raise SealError("partial or overexpanded seal member") + manifest = strict_json_object(seal_bytes, max_bytes=MAX_MANIFEST_BYTES) + if (manifest.get("format") != "Permansson.MathClosure.CandidateSeal.v3" or + manifest.get("status") != "CANDIDATE_SEAL_BUILT" or + manifest.get("full45_claimed") is not True or + manifest.get("accepted_case_count") != 45 or + manifest.get("accepted_claim_count") != 11): + raise SealError("full 45/11 preseal candidate not present") + verify_approved_archive(archive, manifest) + raw = archive.read_bytes() + expected = (sha(raw) + " " + archive.name + "\n" + + sha(seal_bytes) + " approved_seal.json\n") + if sidecar.read_text(encoding="utf-8") != expected: + raise SealError("actual archive and detached checksums disagree") + result = { + "format": FORMAT, + "status": "CANDIDATE_ARCHIVE_REPLAYED_UNREVIEWED", + "source_commit_sha": manifest["source_commit_sha"], + "head_sha": manifest["head_sha"], + "pdf_sha256": manifest["pdf_sha256"], + "archive_sha256": sha(raw), + "manifest_sha256": sha(seal_bytes), + "complete_original_members_verified": True, + "authenticated_ci_artifact": False, + "independent_review": False, + "full_release_approved": False, + } + output.parent.mkdir(parents=True, exist_ok=True) + with output.open("x", encoding="utf-8") as writer: + writer.write(json.dumps(result, sort_keys=True, indent=2) + "\n") + return result + +def main(argv=None): + p = argparse.ArgumentParser() + p.add_argument("--archive", required=True, type=Path) + p.add_argument("--sidecar", required=True, type=Path) + p.add_argument("--output", required=True, type=Path) + args = p.parse_args(argv) + try: + print(json.dumps(replay(args.archive, args.sidecar, args.output), + sort_keys=True)) + return 0 + except (OSError, ValueError, KeyError, zipfile.BadZipFile) as exc: + print("POSTSEAL REPLAY BLOCKED:", exc, file=sys.stderr) + return 2 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/release/mathematical-closure-1/reproduce.py b/release/mathematical-closure-1/reproduce.py new file mode 100644 index 00000000..44fbedf8 --- /dev/null +++ b/release/mathematical-closure-1/reproduce.py @@ -0,0 +1,89 @@ +"""Independent-root draft PDF/ZIP reproducibility audit, NOT publication. + +Two complete draft TeX runs are required. A mismatch is a reproducibility +failure, never a reason to rewrite expected checksums in an approval file. +""" +from __future__ import annotations + +import argparse +import hashlib +import importlib.util +import json +from pathlib import Path +import tempfile + +MODULE = Path(__file__).with_name("build.py") +spec = importlib.util.spec_from_file_location("permansson_draft_builder", MODULE) +builder = importlib.util.module_from_spec(spec) +assert spec.loader is not None +spec.loader.exec_module(builder) + +FILES = ("Permansson_Mathematical_Closure.pdf", + "Permansson_Mathematical_Closure.tex", + "Permansson_Mathematical_Closure_E2E.zip", + "manifest.lock.json", "latex_build.log", + "SHA256SUMS.txt", "source_ci_receipts.lock.json") + + +class ReproducibilityError(ValueError): + pass + + +def digest(p: Path) -> str: + return hashlib.sha256(p.read_bytes()).hexdigest() + + +def compare_artifacts(first: Path, second: Path) -> dict: + differences = {} + for name in FILES: + a, b = first/name, second/name + if not a.is_file() or not b.is_file(): + differences[name] = "MISSING_OUTPUT" + elif digest(a) != digest(b): + differences[name] = "NONDETERMINISTIC_BYTES" + pages_a = sorted(p for p in (first/"pages").glob("*.png")) + pages_b = sorted(p for p in (second/"pages").glob("*.png")) + if pages_a or pages_b: + left = {p.name: digest(p) for p in pages_a} + right = {p.name: digest(p) for p in pages_b} + if left != right: + differences["rendered_pages"] = "NONDETERMINISTIC_RENDER" + if differences: + raise ReproducibilityError("independent-root build differs: " + + json.dumps(differences, sort_keys=True)) + manifest = json.loads((first/"manifest.lock.json").read_text()) + if manifest.get("status") != "DRAFT_NOT_ACCEPTED": + raise ReproducibilityError("repeat build inadvertently claims release") + return {"status": "REPRODUCIBLE_DRAFT_ONLY", + "source_commit_sha": manifest.get("source_commit_sha"), + "pdf_sha256": digest(first/FILES[0]), + "zip_sha256": digest(first/FILES[2]), + "page_count": manifest.get("pdf_page_count")} + + +def reproduce(*, engine: str = "pdflatex", render: bool = True): + with tempfile.TemporaryDirectory(prefix="permansson-repro-a-") as a, \ + tempfile.TemporaryDirectory(prefix="permansson-repro-b-") as b: + one, two = Path(a), Path(b) + builder.build(one, engine, render) + builder.build(two, engine, render) + return compare_artifacts(one, two) + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument("--engine", choices=["pdflatex", "xelatex", "lualatex"], + default="pdflatex") + parser.add_argument("--no-render", action="store_true") + a = parser.parse_args(argv) + try: + result = reproduce(engine=a.engine, render=not a.no_render) + except (ValueError, OSError, RuntimeError) as exc: + print("REPRODUCIBILITY BLOCKED:", exc) + return 2 + print(json.dumps(result, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/release/mathematical-closure-1/reproduce_clean_sources.py b/release/mathematical-closure-1/reproduce_clean_sources.py new file mode 100644 index 00000000..d802d8dc --- /dev/null +++ b/release/mathematical-closure-1/reproduce_clean_sources.py @@ -0,0 +1,314 @@ +"""Two clean, independent Git source roots -> actual TeX PDF/ZIP byte replay. + +The older reproduce.py compares two output roots of ONE checkout. This script +makes two independent Git clones without hardlinked objects, detaches BOTH at +one exact commit, verifies their trees, builds both source roots, validates +all thirteen archived manuscript inputs and compares every resulting output. +No independent reviewer, M identity, artifact-attesting CI job, or release +approval is invented by a successful local repetition. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import zipfile +from pathlib import Path +import re +import shutil +import subprocess +import sys +import tempfile + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parents[1] +SHA40 = re.compile(r"^[0-9a-f]{40}$") + +import reproduce + + +class CleanReproductionError(ValueError): + pass + + +def need(flag, why): + if not flag: + raise CleanReproductionError(why) + + +def cmd(arguments, *, cwd=None, timeout=600): + try: + result = subprocess.run(arguments, cwd=cwd, text=True, + capture_output=True, timeout=timeout) + except (OSError, subprocess.TimeoutExpired) as exc: + raise CleanReproductionError("source/release command unavailable") from exc + if result.returncode: + raise CleanReproductionError( + "source/release command failed: " + " ".join(map(str, arguments)) + + "\n" + (result.stdout + result.stderr)[-2500:]) + return result.stdout.strip() + + +def git_object(args, cwd): + return cmd(["git", *args], cwd=cwd, timeout=30) + + + +def _sha_file(path: Path): + return hashlib.sha256(path.read_bytes()).hexdigest() if path.is_file() else None + + +def _text_delta(left: Path, right: Path) -> dict: + """Inspect genuine retained log bytes without suppressing any warning.""" + if not left.is_file() or not right.is_file(): + return {"first_exists": left.is_file(), "second_exists": right.is_file()} + first, second = left.read_bytes(), right.read_bytes() + offset = next((i for i, (a, b) in enumerate(zip(first, second)) if a != b), + min(len(first), len(second))) + one, two = first.decode("utf-8", errors="replace").splitlines(), \ + second.decode("utf-8", errors="replace").splitlines() + wrong = next((i for i in range(min(len(one), len(two))) + if one[i] != two[i]), min(len(one), len(two))) + lo, hi = max(0, wrong-2), wrong+3 + return { + "sha256_first": hashlib.sha256(first).hexdigest(), + "sha256_second": hashlib.sha256(second).hexdigest(), + "first_differing_byte_offset": offset, + "first_differing_line_index": wrong, + "line_count": [len(one), len(two)], + "lines_first": [x[:500] for x in one[lo:hi]], + "lines_second": [x[:500] for x in two[lo:hi]], + } + + +def _zip_delta(left: Path, right: Path) -> dict: + if not left.is_file() or not right.is_file(): + return {"first_exists": left.is_file(), "second_exists": right.is_file()} + def inventory(path): + with zipfile.ZipFile(path) as z: + return { + name: { + "sha256": hashlib.sha256(z.read(name)).hexdigest(), + "date_time": list(z.getinfo(name).date_time), + "compression": z.getinfo(name).compress_type, + "external_attr": z.getinfo(name).external_attr, + "raw_bytes": z.getinfo(name).file_size, + } for name in z.namelist() + }, z.namelist() + try: + a, ao = inventory(left) + b, bo = inventory(right) + except (OSError, zipfile.BadZipFile, RuntimeError) as exc: + return {"invalid_zip": type(exc).__name__} + names = sorted(set(a) | set(b)) + changed = { + name: {"first": a.get(name), "second": b.get(name)} + for name in names if a.get(name) != b.get(name) + } + return { + "member_order_matches": ao == bo, + "member_count": [len(ao), len(bo)], + "first_different_members": dict(list(changed.items())[:15]), + "all_changed_member_count": len(changed), + "first_order": ao[:10], "second_order": bo[:10], + } + + +def diagnose_root_differences(left: Path, right: Path) -> dict: + """Bounded failure diagnostics; NEVER normalize away a byte difference.""" + fields = {} + a = left/"manifest.lock.json" + b = right/"manifest.lock.json" + if a.is_file() and b.is_file(): + try: + x, y = json.loads(a.read_bytes()), json.loads(b.read_bytes()) + fields = {name: {"first": x.get(name), "second": y.get(name)} + for name in sorted(set(x) | set(y)) + if x.get(name) != y.get(name)} + except (ValueError, UnicodeError, TypeError): + fields = {"unparseable_manifest": True} + selected = ("Permansson_Mathematical_Closure.pdf", + "Permansson_Mathematical_Closure.tex", "latex_build.log", + "latex_build.raw.log", "manifest.lock.json", + "Permansson_Mathematical_Closure_E2E.zip", + "SHA256SUMS.txt", "source_ci_receipts.lock.json") + files = {name: {"first": _sha_file(left/name), "second": _sha_file(right/name)} + for name in selected} + logs = {} + for name in ("latex_build.log", "latex_build.raw.log"): + if files[name]["first"] != files[name]["second"]: + logs[name] = _text_delta(left/name, right/name) + pages_first = {p.name: _sha_file(p) for p in (left/"pages").glob("*.png")} + pages_second = {p.name: _sha_file(p) for p in (right/"pages").glob("*.png")} + return { + "manifest_changed_fields": fields, + "artifact_sha256": files, + "differing_logs": logs, + "draft_archive": _zip_delta( + left/"Permansson_Mathematical_Closure_E2E.zip", + right/"Permansson_Mathematical_Closure_E2E.zip"), + "rendered_pages": { + "count": [len(pages_first), len(pages_second)], + "changed": sorted(name for name in set(pages_first) | set(pages_second) + if pages_first.get(name) != pages_second.get(name))[:15], + }, + "byte_identity_waived": False, + "scientific_release_approved": False, + } + + +def reproduce_from_clean_source_roots(out: Path, *, head: str, + engine: str = "pdflatex", + render: bool = True) -> dict: + need(isinstance(head, str) and SHA40.fullmatch(head) is not None, + "exact source HEAD SHA required") + need(engine in ("pdflatex", "lualatex", "xelatex"), + "unapproved TeX engine") + out = out.resolve() + need(not out.is_relative_to(ROOT.resolve()) and + not ROOT.resolve().is_relative_to(out), + "source/output roots must be filesystem-disjoint") + need(not out.exists(), "replay output root must be new, not reused") + need(git_object(["rev-parse", "HEAD"], ROOT) == head, + "current checkout not at exact requested H/M") + tree = git_object(["rev-parse", "HEAD^{tree}"], ROOT) + need(SHA40.fullmatch(tree) is not None, + "missing authentic Git source tree") + out.mkdir(parents=True) + results = [] + # Keep independent Git source roots, but use ONE canonical TeX output + # pathname sequentially. pdfTeX inserts newlines according to the length + # of the displayed absolute output path, which cannot be fixed by + # replacing already-wrapped strings in compiler diagnostics. + # + # Rename the complete finished stage outside the reused pathname. + # Do not delete or edit one compiler log, nor share any output/cache + # between source passes. + stable_stage = out / "_canonical_tex_stage" + with tempfile.TemporaryDirectory(prefix="perm-source-roots-") as td: + temp = Path(td) + for index in ("first", "second"): + checkout = temp / ("source-" + index) + output = out / index + need(not output.exists() and not stable_stage.exists() and + not stable_stage.is_symlink(), + "shared TeX build stage or completed output was not fresh") + cmd(["git", "clone", "--local", "--no-hardlinks", "--no-checkout", + str(ROOT), str(checkout)], timeout=180) + git_object(["checkout", "--detach", head], checkout) + need(git_object(["rev-parse", "HEAD"], checkout) == head and + git_object(["rev-parse", "HEAD^{tree}"], checkout) == tree, + "replayed source root did not match H/tree") + need(not git_object( + ["status", "--porcelain", "--untracked-files=no"], checkout), + "replayed source root was dirty before build") + script = checkout / "release" / "mathematical-closure-1" + need(not stable_stage.exists() and not stable_stage.is_symlink(), + "shared TeX build stage was contaminated before build") + command = [sys.executable, str(script / "build.py"), + "--out", str(stable_stage), "--engine", engine] + if render: + command.append("--render") + cmd(command, cwd=checkout, timeout=1200) + need(stable_stage.is_dir() and not stable_stage.is_symlink(), + "genuine TeX run did not create a regular fresh stage") + cmd([sys.executable, str(script / "verify_release.py"), + "--manifest", str(stable_stage / "manifest.lock.json")], + cwd=checkout, timeout=120) + receipt = json.loads((stable_stage / "manifest.lock.json").read_bytes()) + need(receipt.get("source_commit_sha") == head and + receipt.get("status") == "DRAFT_NOT_ACCEPTED", + "clean replay manifest claims wrong commit or approval") + if render: + need((stable_stage / "pages").is_dir() and + receipt.get("pdf_page_count") is not None, + "clean replay lacks complete rendered pages") + # All outputs (including the unmodified raw compiler transcript) + # are moved as a whole; no normalization of diagnostics. + stable_stage.rename(output) + need(output.is_dir() and not stable_stage.exists(), + "TeX stage not detached/emptied between independent builds") + results.append({"source_head": head, "tree": tree, + "pdf_sha256": receipt["pdf_sha256"], + "archive_sha256": hashlib.sha256( + (output / "Permansson_Mathematical_Closure_E2E.zip") + .read_bytes()).hexdigest(), + "manifest_sha256": hashlib.sha256( + (output / "manifest.lock.json").read_bytes()).hexdigest()}) + need(not stable_stage.exists(), + "shared TeX output cache remained after independent source runs") + if not results or results[0] != results[1]: + # Report EXACT mismatched fields; do not downgrade byte identity, + # turn nondeterminism into a green receipt or erase either build root. + fields = sorted(set(results[0]) | set(results[1])) if len(results) == 2 else [] + mismatches = { + field: {"first": results[0].get(field), + "second": results[1].get(field)} + for field in fields if results[0].get(field) != results[1].get(field) + } + diagnostics = diagnose_root_differences(out/"first", out/"second") + failed = { + "format": "Permansson.CleanRootMismatchDiagnostic.v1", + "status": "TWO_CLEAN_SOURCE_BUILDS_BLOCKED", + "source_commit_sha": head, + "source_tree_sha": tree, + "differences": mismatches, + "source_root_byte_diagnostics": diagnostics, + "authenticated_ci_artifact": False, + "scientific_release_approved": False, + } + (out / "reproduction_failure.json").write_text( + json.dumps(failed, indent=2, sort_keys=True) + "\n", + encoding="utf-8") + raise CleanReproductionError( + "independent source-root manifests differ: " + + json.dumps(mismatches, sort_keys=True) + + "; byte-level diagnosis: " + + json.dumps(diagnostics, sort_keys=True)[:11000]) + comparison = reproduce.compare_artifacts(out / "first", out / "second") + need(comparison.get("pdf_sha256") == results[0]["pdf_sha256"] and + comparison.get("zip_sha256") == results[0]["archive_sha256"], + "byte comparison disconnected from source roots") + report = { + "format": "Permansson.TwoCleanSourceRebuilds.v1", + "status": "REPRODUCIBLE_DRAFT_ONLY", + "source_commit_sha": head, + "source_tree_sha": tree, + "independent_source_roots": 2, + "git_clone_mode": "local_no_hardlinks_no_checkout", + "output_staging_profile": "two_distinct_git_roots_sequential_fresh_identical_output_path", + "engine": engine, "rendered_pages": render, + "pdf_sha256": results[0]["pdf_sha256"], + "archive_sha256": results[0]["archive_sha256"], + "manifest_sha256": results[0]["manifest_sha256"], + "two_complete_archive_audits": True, + "byte_comparison": comparison, + "authenticated_ci_artifact": False, + "scientific_release_approved": False, + } + (out / "reproduction_receipt.json").write_text( + json.dumps(report, sort_keys=True, indent=2) + "\n") + return report + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--source-head", required=True) + ap.add_argument("--out", required=True, type=Path) + ap.add_argument("--engine", choices=["pdflatex", "xelatex", "lualatex"], + default="pdflatex") + ap.add_argument("--no-render", action="store_true") + opts = ap.parse_args(argv) + try: + result = reproduce_from_clean_source_roots( + opts.out, head=opts.source_head, engine=opts.engine, + render=not opts.no_render) + except (CleanReproductionError, OSError, ValueError, KeyError) as exc: + print("TWO CLEAN SOURCE BUILDS BLOCKED:", exc, file=sys.stderr) + return 2 + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/release/mathematical-closure-1/review_receipts.json b/release/mathematical-closure-1/review_receipts.json new file mode 100644 index 00000000..5d0f2907 --- /dev/null +++ b/release/mathematical-closure-1/review_receipts.json @@ -0,0 +1,7 @@ +{ + "format": "Permansson.MathClosure.ReviewTemplate.v1", + "review_receipts": [], + "independent_external_review": false, + "source_review_status": "NOT_RECEIVED", + "manuscript_review_status": "NOT_RECEIVED" +} diff --git a/release/mathematical-closure-1/seal_approved.py b/release/mathematical-closure-1/seal_approved.py new file mode 100644 index 00000000..866d9d8a --- /dev/null +++ b/release/mathematical-closure-1/seal_approved.py @@ -0,0 +1,561 @@ +"""Only externally authenticated postmerge case/claim and PDF approvals seal an archive. + +This never relabels the earlier DRAFT manifest. Approval requires: +real ordered Git merge M, integrated CI, detached source+mutation receipts, +reviewed claim scope and genuinely non-author scholarly/page-byte signoff. +""" +from __future__ import annotations + +import argparse +import hashlib +import importlib.util +import json +from pathlib import Path +import shutil +import stat +import sys +import zipfile + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "verification/research")) +from final_closure_verify import strict as strict_acceptance +from final_closure_full_contract import ORIGINAL_CLAIMS +from archive_integrity import SOURCE_FILES +from pr113_boundary_io import strict_json_object, detached_destination +from pr113_owner_partition_v3 import original_owner_artifacts, OwnerPartitionError + +THIS = Path(__file__).resolve().parent +spec = importlib.util.spec_from_file_location("permansson_draft_verifier", + THIS / "verify_release.py") +draft_verifier = importlib.util.module_from_spec(spec) +assert spec.loader is not None +spec.loader.exec_module(draft_verifier) + + +class SealError(ValueError): + pass + + +def sha(b: bytes) -> str: + return hashlib.sha256(b).hexdigest() + + +def render_page_digests(directory: Path, expected_count: int): + directory = directory / "pages" + if not directory.is_dir(): + raise SealError("all draft PDF pages must be rendered") + pages = sorted(p for p in directory.iterdir() if p.suffix == ".png") + if not pages or expected_count != len(pages): + raise SealError("missing or extra draft PDF rendered page") + if any(p.is_symlink() or not p.is_file() or + not p.name.startswith("page-") for p in pages): + raise SealError("unsafe rendered page record") + return {p.name: sha(p.read_bytes()) for p in pages} + + +def prepare_approved_manifest(draft_directory: Path, approved: dict): + draft = strict_json_object((draft_directory / "manifest.lock.json").read_bytes()) + if draft.get("status") != "DRAFT_NOT_ACCEPTED" or draft.get("mode") != "DRAFT": + raise SealError("input must be the genuine untouched draft artifact") + sha_pdf = sha((draft_directory/"Permansson_Mathematical_Closure.pdf").read_bytes()) + if draft["pdf_sha256"] != sha_pdf or approved.get("pdf_sha256") != sha_pdf: + raise SealError("reviewed PDF bytes differ from actual draft") + if approved.get("source_commit_sha") != draft.get("source_commit_sha"): + raise SealError("draft source is not approved merged main") + pages = render_page_digests(draft_directory, draft["pdf_page_count"]) + if approved.get("pdf_pages") != pages: + raise SealError("reviewed rendered page digests differ from PDF") + if approved.get("status") != "APPROVED_SOURCE_BOUND": + raise SealError("external approved claim manifest absent") + return draft, pages + + + +def external_evidence_children(reproduction_attachment, v31_attachment, *, + reproduction_ids_source): + """Retain unmodified full-scope reproduction and all 76 owner inputs.""" + if not isinstance(reproduction_attachment, dict) or not isinstance(v31_attachment, dict): + raise SealError("full seal requires original reproduction and owner evidence") + source_files = { + "external/reproduction.zip": reproduction_attachment.get("zip_file"), + "external/additional_contract.json": v31_attachment.get("additional_contract"), + "external/workflow_blob_candidates.json": v31_attachment.get("workflow_blob_candidates"), + } + identities = reproduction_attachment.get("identities") + if not isinstance(identities, dict): + raise SealError("original reproduction identities missing") + if reproduction_ids_source is None: + raise SealError("full candidate must retain original reproduction ID bytes") + ids_path = detached_destination(reproduction_ids_source, ROOT, must_exist=True) + if not 0 < ids_path.stat().st_size <= 4*1024*1024: + raise SealError("original reproduction identity JSON missing or oversized") + original_id_bytes = ids_path.read_bytes() + if strict_json_object(original_id_bytes) != identities: + raise SealError("original reproduction identity file differs from authenticated inputs") + children = {"external/reproduction_ids.json": original_id_bytes} + for key, file in source_files.items(): + if file is None: + raise SealError("missing full-scope source " + key) + p = detached_destination(file, ROOT, must_exist=True) + if not 0 < p.stat().st_size <= 64*1024*1024: + raise SealError("external input missing, oversized or symlinked") + children[key] = p.read_bytes() + directory = v31_attachment.get("additional_zips") + if directory is None: + raise SealError("missing original 76-owner directory") + requested = Path(directory) + if (".." in requested.parts or any(part.is_symlink() + for part in (requested.absolute(), *requested.absolute().parents))): + raise SealError("original owner ZIP directory traverses or uses symlink") + directory = requested.resolve(strict=True) + if (not directory.is_dir() or directory.is_relative_to(ROOT.resolve()) + or ROOT.resolve().is_relative_to(directory)): + raise SealError("original owner directory is not detached") + try: + expected_ids = original_owner_artifacts( + strict_json_object(children["external/additional_contract.json"])) + except (OwnerPartitionError, ValueError) as exc: + raise SealError("full-scope owner source registry does not cover 45+20+11") from exc + files = list(Path(directory).iterdir()) + expected_names = {str(i) + ".zip" for i in expected_ids} + if {p.name for p in files} != expected_names or len(files) != len(expected_ids): + raise SealError("actual original producer ZIP inventory differs from owner registry") + import re + total = 0 + for p in files: + if (not p.is_file() or p.is_symlink() + or not re.fullmatch(r"[1-9][0-9]*\.zip", p.name) + or not 0 < p.stat().st_size <= 64*1024*1024): + raise SealError("invalid original owner-producing artifact") + total += p.stat().st_size + if total > 256*1024*1024: + raise SealError("original owner-producing ZIP budget exceeded") + children["external/owners/" + p.name] = p.read_bytes() + return children + + +def bounded_zip_members(z, expected, *, total_limit, member_limit): + """Validate ZIP directory and budgets before any decompression.""" + infos = z.infolist() + if len(infos) != len(expected) or len(infos) > 256: + raise SealError("wrong ZIP member count") + seen = set() + total = 0 + for entry in infos: + name = entry.filename + mode = stat.S_IFMT(entry.external_attr >> 16) + if (name not in expected or name.casefold() in seen + or name.startswith("/") or "\\" in name or + any(x in ("", ".", "..") for x in name.split("/")) + or entry.is_dir() or entry.flag_bits & 1 + or mode not in (0, stat.S_IFREG) + or entry.file_size > member_limit or entry.compress_size <= 0 + or entry.file_size > max(entry.compress_size*500, 1024*1024)): + raise SealError("unsafe/duplicate/overexpanded ZIP member") + seen.add(name.casefold()) + total += entry.file_size + if total > total_limit: + raise SealError("excess nested ZIP expansion") + if {x.filename for x in infos} != set(expected): + raise SealError("missing expected ZIP member") + + +def read_bounded(z, name, max_bytes): + info = z.getinfo(name) + if info.file_size > max_bytes: + raise SealError("oversized ZIP member: " + name) + with z.open(info) as reader: + value = reader.read(max_bytes + 1) + if len(value) != info.file_size: + raise SealError("incomplete ZIP member: " + name) + return value + + +def seal(draft_directory: Path, approved: dict, receipts: dict, + evidence_bundle: Path, output: Path, *, full: bool = False, + reproduction_attachment: dict | None = None, + v31_attachment: dict | None = None, + reproduction_ids_source: Path | None = None): + output = detached_destination(output, ROOT, forbidden=(draft_directory,)) + # Baseline source integrity is always checked, even before authenticity. + draft_verifier.verify(draft_directory / "manifest.lock.json") + draft, pages = prepare_approved_manifest(draft_directory, approved) + accepted_scope = "postmerge-preseal-strict" if full else "scoped-release-strict" + authentic_input = ({"reproduction_attachment": reproduction_attachment} + if reproduction_attachment is not None else {}) + if v31_attachment is not None: + authentic_input["v31_attachment"] = v31_attachment + result = strict_acceptance(receipts, accepted_scope, approved, + bundle_path=evidence_bundle, + **authentic_input) + if full and (set(approved.get("claims", [])) != ORIGINAL_CLAIMS or + approved.get("approved_manuscript_claims") != + approved.get("claims")): + raise SealError("full seal must explicitly approve all original eleven claims") + if full and (result.get("accepted_case_count") != 45 or + result.get("accepted_claim_count") != 11 or + result.get("status") != + "POSTMERGE_PRESEAL_READY"): + raise SealError("candidate seal lacks authenticated preseal 45/11 certificate") + if draft.get("source_commit_sha") != result["source"]: + raise SealError("PDF was not built from genuine merged commit M") + # Complete source files are included in the *approved* artifact too. + # A detached reviewer must be able to reconstruct the candidate from + # the released archive without accessing the operator's checkout. + src_zip = draft_directory/"Permansson_Mathematical_Closure_E2E.zip" + if not src_zip.is_file(): + raise SealError("original 13-source draft archive missing") + complete_sources = {} + with zipfile.ZipFile(src_zip) as draft_zip: + expected_inner = {"manuscript/" + path for path in SOURCE_FILES} + expected_inner.update({"Permansson_Mathematical_Closure.pdf", + "Permansson_Mathematical_Closure.tex", + "manifest.lock.json", "source_ci_receipts.lock.json", + "latex_build.log"}) + bounded_zip_members(draft_zip, expected_inner, + total_limit=128*1024*1024, member_limit=48*1024*1024) + for relative in SOURCE_FILES: + original = "research_papers/mathematical_closure/" + relative + name = "manuscript/" + relative + expected = draft.get("source_tree_files", {}).get(original) + if not isinstance(expected, str): + raise SealError("draft omitted source input " + relative) + raw = read_bounded(draft_zip, name, 48*1024*1024) + if sha(raw) != expected: + raise SealError("draft source hash does not match " + relative) + complete_sources[name] = raw + original_ci = (draft_directory/"source_ci_receipts.lock.json").read_bytes() + original_log = (draft_directory/"latex_build.log").read_bytes() + original_sums = (draft_directory/"SHA256SUMS.txt").read_bytes() + if not evidence_bundle.is_file() or evidence_bundle.is_symlink(): + raise SealError("authenticated detached proof/mutation evidence bundle unavailable") + retained_bundle = evidence_bundle.read_bytes() + if not retained_bundle or len(retained_bundle) > 32*1024*1024: + raise SealError("detached proof/mutation bundle missing or oversized") + retained_receipts = (json.dumps( + receipts, indent=2, sort_keys=True, ensure_ascii=False)+"\n").encode("utf-8") + original_case_map = (ROOT/"research/paper_closures/post_merge_112/acceptance_case_source_map.json").read_bytes() + original_claim_map = (ROOT/"research/paper_closures/post_merge_112/claim_declaration_map.json").read_bytes() + effective_report = (json.dumps({ + "scope": accepted_scope, "frozen_head": result["frozen_head"], + "merged_source": result["source"], + "accepted_cases": result.get("effective_cases", {}), + "accepted_claims": result.get("effective_claims", []), + "accepted_case_count": result["accepted_case_count"], + "accepted_claim_count": result["accepted_claim_count"], + "receipt_contract": result.get("receipt_contract"), + "status": result.get("status", "SCOPED_SOURCE_ONLY") + }, indent=2, sort_keys=True)+"\n").encode("utf-8") + if sha(retained_bundle) != receipts.get("evidence_bundle_sha256"): + raise SealError("retained original proof bundle differs from strict acceptance") + if sha(original_log) != draft.get("latex_log_sha256"): + raise SealError("draft compiler log changed before sealing") + approved_bytes = (json.dumps(approved, indent=2, sort_keys=True)+"\n").encode() + children = (external_evidence_children( + reproduction_attachment, v31_attachment, + reproduction_ids_source=reproduction_ids_source) + if full else {}) + manifest = { + "format": ("Permansson.MathClosure.CandidateSeal.v3" if full + else "Permansson.MathClosure.ApprovedSeal.v3"), + "status": "CANDIDATE_SEAL_BUILT" if full else "APPROVED_SOURCE_BOUND", + "source_commit_sha": result["source"], + "head_sha": result["frozen_head"], + "draft_manifest_sha256": sha( + (draft_directory/"manifest.lock.json").read_bytes()), + "manuscript_sources": draft["source_tree_files"], + "original_draft_archive_sha256": sha(src_zip.read_bytes()), + "original_draft_ci_sha256": sha(original_ci), + "original_draft_log_sha256": sha(original_log), + "original_draft_checksums_sha256": sha(original_sums), + "evidence_bundle_sha256": sha(retained_bundle), + "source_receipts_sha256": sha(retained_receipts), + "approved_claims_manifest_sha256": sha(approved_bytes), + "external_evidence_members": + {key: sha(data) for key, data in sorted(children.items())}, + "original_case_map_sha256": sha(original_case_map), + "original_claim_map_sha256": sha(original_claim_map), + "effective_acceptance_report_sha256": sha(effective_report), + "pdf_sha256": draft["pdf_sha256"], + "pdf_pages": pages, + "claims": approved["claims"], + "scientific_limitations": approved["scientific_limitations"], + "empirical_identification_status": "NOT_ESTABLISHED", + "review_binding": "AUTHENTICATED_LIVE_EXACT_H_SOURCE_AND_POSTMERGE_SCHOLARLY", + "accepted_case_count": result["accepted_case_count"], + "accepted_claim_count": result["accepted_claim_count"], + "full45_claimed": full, + } + if output.exists(): + raise SealError("output must be a fresh detached directory") + output.mkdir(parents=True, exist_ok=False) + members = { + "Permansson_Mathematical_Closure.pdf": + (draft_directory/"Permansson_Mathematical_Closure.pdf").read_bytes(), + "Permansson_Mathematical_Closure.tex": + (draft_directory/"Permansson_Mathematical_Closure.tex").read_bytes(), + "original_draft_manifest.lock.json": + (draft_directory/"manifest.lock.json").read_bytes(), + "original_draft_E2E.zip": src_zip.read_bytes(), + "original_draft_source_ci_receipts.lock.json": original_ci, + "original_draft_latex_build.log": original_log, + "original_draft_SHA256SUMS.txt": original_sums, + "detached_proof_mutation_bundle.zip": retained_bundle, + "source_acceptance_receipts.json": retained_receipts, + "original_case_acceptance_map.json": original_case_map, + "original_claim_declaration_map.json": original_claim_map, + "effective_acceptance_report.json": effective_report, + "approved_claims_manifest.json": approved_bytes, + "approved_seal.json": + (json.dumps(manifest, indent=2, sort_keys=True)+"\n").encode(), + } + members.update(complete_sources) + members.update(children) + for page in pages: + members["pages/" + page] = (draft_directory/"pages"/page).read_bytes() + archive = output / ("Permansson_Mathematical_Closure_CANDIDATE.zip" + if full else "Permansson_Mathematical_Closure_APPROVED.zip") + with zipfile.ZipFile(archive, "w") as zipout: + for name, payload in sorted(members.items()): + entry = zipfile.ZipInfo(name, (1980,1,1,0,0,0)) + entry.external_attr = 0o100644 << 16 + entry.compress_type = zipfile.ZIP_DEFLATED + zipout.writestr(entry, payload, compress_type=zipfile.ZIP_DEFLATED, + compresslevel=9) + (output/"approved_seal.json").write_bytes(members["approved_seal.json"]) + (output/"SHA256SUMS.txt").write_text( + sha(archive.read_bytes()) + " " + archive.name + "\n" + + sha(members["approved_seal.json"]) + " approved_seal.json\n") + verify_approved_archive(archive, manifest) + return {"status": manifest["status"], "source": manifest["source_commit_sha"], + "approved_pdf_sha256": manifest["pdf_sha256"], + "archive_sha256": sha(archive.read_bytes())} + + +def verify_approved_archive(archive: Path, manifest: dict): + if not archive.is_file(): + raise SealError("approved archive missing") + if archive.is_symlink() or archive.stat().st_size > 384*1024*1024: + raise SealError("approved ZIP path or size unsafe") + with zipfile.ZipFile(archive) as z: + infos = z.infolist() + if len(infos) > 256 or sum(i.file_size for i in infos) > 512*1024*1024: + raise SealError("approved ZIP expanded member bound exceeded") + seen = set() + for info in infos: + mode = stat.S_IFMT(info.external_attr >> 16) + low = info.filename.casefold() + if (low in seen or + info.flag_bits & 1 or + info.filename.startswith("/") or + ".." in info.filename.split("/") or + "\\" in info.filename or + mode not in (0, stat.S_IFREG) or + info.is_dir() or + info.file_size > 64*1024*1024 or + info.flag_bits & 1): + raise SealError("unsafe or nonportable approved ZIP member") + seen.add(low) + if z.testzip() is not None: + raise SealError("approved archive CRC mismatch") + names = z.namelist() + expected = { + "Permansson_Mathematical_Closure.pdf", + "Permansson_Mathematical_Closure.tex", + "original_draft_manifest.lock.json", + "original_draft_E2E.zip", + "original_draft_source_ci_receipts.lock.json", + "original_draft_latex_build.log", + "original_draft_SHA256SUMS.txt", + "detached_proof_mutation_bundle.zip", + "source_acceptance_receipts.json", + "original_case_acceptance_map.json", + "original_claim_declaration_map.json", + "effective_acceptance_report.json", + "approved_claims_manifest.json", "approved_seal.json", + *["manuscript/" + source for source in SOURCE_FILES], + *["pages/" + p for p in manifest["pdf_pages"]], + } + extra = manifest.get("external_evidence_members") + if not isinstance(extra, dict): + raise SealError("missing versioned evidence inventory") + if manifest.get("format") == "Permansson.MathClosure.CandidateSeal.v3": + mandatory = { + "external/reproduction.zip", "external/reproduction_ids.json", + "external/additional_contract.json", + "external/workflow_blob_candidates.json", + } + if "external/additional_contract.json" not in names: + raise SealError("full source-controlled owner contract missing") + try: + raw_contract = read_bounded( + z, "external/additional_contract.json", 4*1024*1024) + producer_ids = original_owner_artifacts( + strict_json_object(raw_contract)) + except (OwnerPartitionError, ValueError, KeyError) as exc: + raise SealError( + "archived original owner contract not authenticated structurally") from exc + exact_members = mandatory | { + "external/owners/" + str(i) + ".zip" for i in producer_ids + } + if set(extra) != exact_members: + raise SealError("full candidate ZIP inventory differs from 45+20+11 owner registry") + elif manifest.get("format") == "Permansson.MathClosure.ApprovedSeal.v3": + if extra: + raise SealError("scoped release cannot assert full owner evidence") + else: + raise SealError("obsolete or unknown seal format") + expected.update(extra) + if len(names) != len(expected) or set(names) != expected: + raise SealError("approved archive has extra/missing members") + for child, digest in extra.items(): + if not isinstance(digest, str) or sha(read_bounded(z, child, 64*1024*1024)) != digest: + raise SealError("original full evidence bytes changed: " + child) + approved_bytes = read_bounded(z, "approved_claims_manifest.json", 4*1024*1024) + if sha(approved_bytes) != manifest.get("approved_claims_manifest_sha256"): + raise SealError("approved claims bytes not bound by seal") + approved = strict_json_object(approved_bytes) + if (approved.get("status") != "APPROVED_SOURCE_BOUND" + or approved.get("source_commit_sha") != manifest.get("source_commit_sha") + or approved.get("pdf_sha256") != manifest.get("pdf_sha256") + or approved.get("pdf_pages") != manifest.get("pdf_pages") + or approved.get("claims") != manifest.get("claims")): + raise SealError("approved claims contradict sealed review/source") + if manifest.get("full45_claimed") is True and ( + approved.get("approved_manuscript_claims") != manifest.get("claims")): + raise SealError("full paper claims differ from approved claim list") + if sha(z.read("Permansson_Mathematical_Closure.pdf")) != manifest["pdf_sha256"]: + raise SealError("approved archive replaced PDF") + for page, expected_sha in manifest["pdf_pages"].items(): + if sha(z.read("pages/"+page)) != expected_sha: + raise SealError("approved page bytes changed") + if strict_json_object(z.read("approved_seal.json")) != manifest: + raise SealError("approved source metadata changed") + source_index = manifest.get("manuscript_sources") + if not isinstance(source_index, dict) or len(source_index) != len(SOURCE_FILES): + raise SealError("approved seal lost canonical 13-source index") + for path in SOURCE_FILES: + name = "research_papers/mathematical_closure/" + path + expected_sha = source_index.get(name) + if not isinstance(expected_sha, str) or sha( + z.read("manuscript/" + path)) != expected_sha: + raise SealError("approved seal replaced manuscript: " + path) + originals = { + "original_draft_E2E.zip": "original_draft_archive_sha256", + "original_draft_source_ci_receipts.lock.json": "original_draft_ci_sha256", + "original_draft_latex_build.log": "original_draft_log_sha256", + "original_draft_SHA256SUMS.txt": "original_draft_checksums_sha256", + "original_draft_manifest.lock.json": "draft_manifest_sha256", + } + originals.update({ + "detached_proof_mutation_bundle.zip": "evidence_bundle_sha256", + "source_acceptance_receipts.json": "source_receipts_sha256", + "original_case_acceptance_map.json": "original_case_map_sha256", + "original_claim_declaration_map.json": "original_claim_map_sha256", + "effective_acceptance_report.json": "effective_acceptance_report_sha256", + }) + for name, field in originals.items(): + if sha(z.read(name)) != manifest.get(field): + raise SealError("approved archive replaced provenance bytes: " + name) + bound_receipts = strict_json_object(z.read("source_acceptance_receipts.json")) + if bound_receipts.get("evidence_bundle_sha256") != manifest["evidence_bundle_sha256"]: + raise SealError("detached compiler bundle no longer matches reviewed receipts") + effective = strict_json_object(z.read("effective_acceptance_report.json")) + if (effective.get("frozen_head") != manifest["head_sha"] or + effective.get("merged_source") != manifest["source_commit_sha"] or + effective.get("accepted_case_count") != manifest["accepted_case_count"] or + effective.get("accepted_claim_count") != manifest["accepted_claim_count"] or + manifest.get("full45_claimed") is True and + (effective.get("receipt_contract") != "ALL_45_HISTORICAL_AND_ALL_11_COMPLETE" or + effective.get("status") != ( + "POSTMERGE_PRESEAL_READY" + if manifest.get("status") == "CANDIDATE_SEAL_BUILT" + else "FULL_MATHEMATICAL_CLOSURE_RELEASE_READY"))): + raise SealError("effective acceptance receipt contradicts approved seal") + # A second complete retained copy must independently agree with all + # direct source members. Neither source set may disappear silently. + import io + with zipfile.ZipFile(io.BytesIO(z.read("original_draft_E2E.zip"))) as inner: + expected_inner = {"manuscript/" + p for p in SOURCE_FILES} + expected_inner.update({ + "Permansson_Mathematical_Closure.pdf", + "Permansson_Mathematical_Closure.tex", + "manifest.lock.json", "source_ci_receipts.lock.json", + "latex_build.log", + }) + bounded_zip_members(inner, expected_inner, + total_limit=128*1024*1024, member_limit=48*1024*1024) + for path in SOURCE_FILES: + name = "manuscript/" + path + if read_bounded(inner, name, 48*1024*1024) != z.read(name): + raise SealError("nested/detached manuscript bytes diverged: " + path) + if read_bounded(inner, "Permansson_Mathematical_Closure.pdf", 48*1024*1024) != z.read( + "Permansson_Mathematical_Closure.pdf"): + raise SealError("approved PDF differs from exact draft PDF") + if read_bounded(inner, "Permansson_Mathematical_Closure.tex", 48*1024*1024) != z.read( + "Permansson_Mathematical_Closure.tex"): + raise SealError("approved TeX differs from original draft") + if read_bounded(inner, "manifest.lock.json", 48*1024*1024) != z.read( + "original_draft_manifest.lock.json"): + raise SealError("nested draft manifest changed") + if read_bounded(inner, "source_ci_receipts.lock.json", 48*1024*1024) != z.read( + "original_draft_source_ci_receipts.lock.json"): + raise SealError("nested draft source CI receipts changed") + if read_bounded(inner, "latex_build.log", 48*1024*1024) != z.read( + "original_draft_latex_build.log"): + raise SealError("nested draft compiler log changed") + return True + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--draft-dir", required=True, type=Path) + ap.add_argument("--approved-claim-manifest", required=True, type=Path) + ap.add_argument("--source-receipts", required=True, type=Path) + ap.add_argument("--bundle", required=True, type=Path) + ap.add_argument("--out", required=True, type=Path) + ap.add_argument("--full", action="store_true", + help="require exact-M postmerge preseal 45/11 eligibility; candidate requires separate independent postseal review") + ap.add_argument("--reproduction-zip", type=Path) + ap.add_argument("--reproduction-ids", type=Path) + ap.add_argument("--additional-contract", type=Path) + ap.add_argument("--additional-zips", type=Path) + ap.add_argument("--workflow-blob-candidates", type=Path) + args = ap.parse_args(argv) + try: + approved = strict_json_object(args.approved_claim_manifest.read_bytes()) + receipts = strict_json_object(args.source_receipts.read_bytes()) + if bool(args.reproduction_zip) != bool(args.reproduction_ids): + raise SealError("reproduction ZIP and real producing IDs required together") + external = ({ + "zip_file": args.reproduction_zip, + "identities": strict_json_object(args.reproduction_ids.read_bytes())} + if args.reproduction_zip else None) + extra_inputs = (args.additional_contract, args.additional_zips, + args.workflow_blob_candidates) + if any(x is not None for x in extra_inputs) and not all( + x is not None for x in extra_inputs): + raise SealError( + "additional source-control evidence requires all three " + "contract, owner ZIP and workflow-source inputs") + additional = ({ + "additional_contract": args.additional_contract, + "additional_zips": args.additional_zips, + "workflow_blob_candidates": args.workflow_blob_candidates} + if all(x is not None for x in extra_inputs) else None) + if args.full and additional is None: + raise SealError("full candidate seal requires authenticated 76-owner v3.1 source inputs") + outcome = seal(args.draft_dir, approved, receipts, + args.bundle, args.out, full=args.full, + reproduction_attachment=external, + v31_attachment=additional, + reproduction_ids_source=args.reproduction_ids) + except (OSError, ValueError, KeyError, zipfile.BadZipFile) as exc: + print("APPROVED SEAL BLOCKED:", exc) + return 2 + print(json.dumps(outcome, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/release/mathematical-closure-1/source_receipts.json b/release/mathematical-closure-1/source_receipts.json new file mode 100644 index 00000000..d098d617 --- /dev/null +++ b/release/mathematical-closure-1/source_receipts.json @@ -0,0 +1,11 @@ +{ + "format": "Permansson.MathClosure.SourceReceiptTemplate.v1", + "base_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "head_sha": null, + "synthetic_sha": null, + "merged_main_sha": null, + "source_ci_receipts": [], + "axiom_receipts": [], + "source_hash_receipts": [], + "status": "UNVERIFIED_TEMPLATE" +} diff --git a/release/mathematical-closure-1/theorem_map.json b/release/mathematical-closure-1/theorem_map.json new file mode 100644 index 00000000..921000a0 --- /dev/null +++ b/release/mathematical-closure-1/theorem_map.json @@ -0,0 +1,61 @@ +{ + "format": "Permansson.MathClosure.TheoremMap.v1", + "source_baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "entries": [ + { + "id": "Q1", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + }, + { + "id": "Q2", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + }, + { + "id": "Q3", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + }, + { + "id": "M1", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + }, + { + "id": "M2", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + }, + { + "id": "M3", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + }, + { + "id": "N1", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + }, + { + "id": "N2", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + }, + { + "id": "N3", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + }, + { + "id": "D8", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + }, + { + "id": "D9", + "source": "research/paper_closures/post_merge_112/theorem_crosswalk.json", + "certification": "PENDING_SOURCE_BOUND_ACCEPTANCE" + } + ] +} diff --git a/release/mathematical-closure-1/verify_release.py b/release/mathematical-closure-1/verify_release.py new file mode 100644 index 00000000..b969c753 --- /dev/null +++ b/release/mathematical-closure-1/verify_release.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Byte and canonical-source validator; no implied scholarly approval.""" +from __future__ import annotations +import argparse,hashlib,json,sys,zipfile +from pathlib import Path +from archive_integrity import verify_draft_archive + +def sha(p:Path):return hashlib.sha256(p.read_bytes()).hexdigest() +def require(b,msg): + if not b: raise ValueError(msg) +def verify(manifest:Path,strict:bool=False,*,source_receipts:Path|None=None,approved_manifest:Path|None=None): + d=json.loads(manifest.read_text()); out=manifest.parent + require(d.get("format")=="Permansson.MathClosure.BuildReceipt.v1","wrong receipt format") + require(d.get("mode")=="DRAFT","mode mismatch") + require(d.get("empirical_identification_status")=="NOT_ESTABLISHED","false empirical claim") + names={"Permansson_Mathematical_Closure.pdf":d["pdf_sha256"], + "Permansson_Mathematical_Closure.tex":d["tex_sha256"], + "latex_build.log":d["latex_log_sha256"]} + for n,h in names.items():require((out/n).exists() and sha(out/n)==h,f"source/output corrupt {n}") + require((out/"Permansson_Mathematical_Closure.pdf").read_bytes().startswith(b"%PDF-"),"bad PDF") + audit = verify_draft_archive(out, d) + if strict: + raise ValueError("draft artifact cannot be approved; use --approved-seal and genuine external review") + return {"integrity":"PASS","accepted":False if not strict else True, + "pdf_pages":d.get("pdf_page_count"),"source_files":audit["canonical_sources"]} +def main(argv=None): + ap=argparse.ArgumentParser() + ap.add_argument("--manifest",required=True,type=Path) + ap.add_argument("--strict",action="store_true") + ap.add_argument("--source-receipts",type=Path) + ap.add_argument("--approved-manifest",type=Path) + ap.add_argument("--approved-seal",type=Path, + help="immutable approved ZIP, independently source/PDF authenticated") + ap.add_argument("--bundle",type=Path) + ap.add_argument("--reproduction-zip",type=Path) + ap.add_argument("--reproduction-ids",type=Path) + ap.add_argument("--postseal-zip",type=Path) + ap.add_argument("--postseal-ids",type=Path) + ap.add_argument("--additional-contract",type=Path) + ap.add_argument("--additional-zips",type=Path) + ap.add_argument("--workflow-blob-candidates",type=Path) + a=ap.parse_args(argv) + try: + if a.approved_seal: + require(a.source_receipts and a.approved_manifest and a.bundle, + "approved verification requires detached source/review and diagnostic bundle") + from seal_approved import verify_approved_archive + checkdir=Path(__file__).resolve().parents[2]/"verification/research" + sys.path.insert(0,str(checkdir)) + from final_closure_verify import strict as strict_closure + # The approved seal cannot rely solely on a cached green flag. + approved=json.loads(a.approved_manifest.read_bytes()) + receipts=json.loads(a.source_receipts.read_bytes()) + with zipfile.ZipFile(a.approved_seal) as z: + seal=json.loads(z.read("approved_seal.json")) + verify_approved_archive(a.approved_seal,seal) + requested_scope = ("full-release-strict" if + seal.get("full45_claimed") is True + else "scoped-release-strict") + require(bool(a.reproduction_zip)==bool(a.reproduction_ids) and + bool(a.postseal_zip)==bool(a.postseal_ids), + "real GitHub artifact identities must have their ZIPs") + reproduction=({"zip_file":a.reproduction_zip, + "identities":json.loads(a.reproduction_ids.read_bytes())} + if a.reproduction_zip else None) + postseal=({"zip_file":a.postseal_zip, + "identities":json.loads(a.postseal_ids.read_bytes())} + if a.postseal_zip else None) + additional=({ + "additional_contract":a.additional_contract, + "additional_zips":a.additional_zips, + "workflow_blob_candidates":a.workflow_blob_candidates} + if all(x is not None for x in ( + a.additional_contract,a.additional_zips, + a.workflow_blob_candidates)) else None) + if requested_scope == "full-release-strict": + require(additional is not None, + "full release requires all 76 authentic v3.1 producer inputs") + outcome=strict_closure(receipts,requested_scope,approved, + bundle_path=a.bundle, + reproduction_attachment=reproduction, + postseal_attachment=postseal, + v31_attachment=additional) + require(seal["source_commit_sha"]==outcome["source"] and + seal["head_sha"]==outcome["frozen_head"] and + seal["pdf_sha256"]==approved["pdf_sha256"] and + seal["pdf_pages"]==approved["pdf_pages"] and + seal["claims"]==approved["claims"], + "sealed reviewed claims or PDF source does not match live authentication") + if seal.get("full45_claimed") is True: + require(outcome.get("status") == + "FULL_MATHEMATICAL_CLOSURE_RELEASE_READY" and + outcome.get("accepted_case_count") == 45 and + outcome.get("accepted_claim_count") == 11, + "full claim contradicts effective acceptance gate") + print(json.dumps({"integrity":"PASS","accepted":True, + "source":outcome["source"], + "scope":"FULL_45_AND_11" if + seal.get("full45_claimed") is True + else "SCOPED_ONLY"},indent=2)) + return 0 + print(json.dumps(verify(a.manifest,a.strict, + source_receipts=a.source_receipts, + approved_manifest=a.approved_manifest),indent=2)) + return 0 + except (OSError,ValueError,KeyError,zipfile.BadZipFile) as e: + print("NOT ACCEPTED:",e,file=sys.stderr);return 2 +if __name__=="__main__":raise SystemExit(main()) diff --git a/research/certification/infrastructure_receipt.schema.json b/research/certification/infrastructure_receipt.schema.json new file mode 100644 index 00000000..2a9621ed --- /dev/null +++ b/research/certification/infrastructure_receipt.schema.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Permansson PR113 InfrastructureEvidence v1 - distinct from proof acceptance", + "type": "object", + "additionalProperties": false, + "required": [ + "format", + "target", + "source_bound_ci", + "historical_T", + "historical_A2", + "security_review_id" + ], + "properties": { + "format": { + "const": "Permansson.PR113.InfrastructureEvidence.v1" + }, + "target": { + "$ref": "target_contract.schema.json" + }, + "source_bound_ci": { + "type": "array", + "minItems": 12, + "maxItems": 12, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "id", + "head_sha" + ], + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[a-f0-9]{40}$" + } + } + } + }, + "historical_T": { + "$ref": "#/$defs/artifact" + }, + "historical_A2": { + "$ref": "#/$defs/artifact" + }, + "security_review_id": { + "type": "integer", + "minimum": 1 + } + }, + "$defs": { + "artifact": { + "type": "object", + "additionalProperties": false, + "required": [ + "run_id", + "run_attempt", + "job_id", + "check_run_id", + "artifact_id" + ], + "properties": { + "run_id": { + "type": "integer", + "minimum": 1 + }, + "run_attempt": { + "type": "integer", + "minimum": 1 + }, + "job_id": { + "type": "integer", + "minimum": 1 + }, + "check_run_id": { + "type": "integer", + "minimum": 1 + }, + "artifact_id": { + "type": "integer", + "minimum": 1 + } + } + } + } +} diff --git a/research/certification/infrastructure_receipt_v2.schema.json b/research/certification/infrastructure_receipt_v2.schema.json new file mode 100644 index 00000000..e2adae40 --- /dev/null +++ b/research/certification/infrastructure_receipt_v2.schema.json @@ -0,0 +1,116 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Permansson PR113 InfrastructureEvidence v2 — historical-only then complete 12-check aggregate", + "description": "Nonauthorizing external input. 0 contexts only for noncircular historical-only gate; exactly 12 for later infrastructure aggregate. V1 remains unchanged.", + "type": "object", + "additionalProperties": false, + "required": [ + "format", + "target", + "source_bound_ci", + "historical_T", + "historical_A2", + "security_review_id" + ], + "properties": { + "format": { + "const": "Permansson.PR113.InfrastructureEvidence.v2" + }, + "target": { + "$ref": "target_contract.schema.json" + }, + "source_bound_ci": { + "type": "array", + "oneOf": [ + { + "maxItems": 0 + }, + { + "minItems": 12, + "maxItems": 12 + } + ], + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "id", + "head_sha" + ], + "properties": { + "name": { + "type": "string", + "enum": [ + "research", + "build", + "frozen-source-preservation", + "multiagent-protocols", + "approx-quotient", + "release-export-integrity", + "archive-reproducibility", + "executable-verification", + "research-release", + "integrity-portability-windows", + "Application contract (ubuntu-latest)", + "Application contract (windows-latest)" + ] + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[a-f0-9]{40}$" + } + } + } + }, + "historical_T": { + "$ref": "#/$defs/artifact" + }, + "historical_A2": { + "$ref": "#/$defs/artifact" + }, + "security_review_id": { + "type": "integer", + "minimum": 1 + } + }, + "$defs": { + "artifact": { + "type": "object", + "additionalProperties": false, + "required": [ + "run_id", + "run_attempt", + "job_id", + "check_run_id", + "artifact_id" + ], + "properties": { + "run_id": { + "type": "integer", + "minimum": 1 + }, + "run_attempt": { + "type": "integer", + "minimum": 1 + }, + "job_id": { + "type": "integer", + "minimum": 1 + }, + "check_run_id": { + "type": "integer", + "minimum": 1 + }, + "artifact_id": { + "type": "integer", + "minimum": 1 + } + } + } + } +} diff --git a/research/certification/infrastructure_reviewer_authority.json b/research/certification/infrastructure_reviewer_authority.json new file mode 100644 index 00000000..e307b0d2 --- /dev/null +++ b/research/certification/infrastructure_reviewer_authority.json @@ -0,0 +1,6 @@ +{ + "format": "Permansson.InfrastructureReviewerAuthority.v1", + "status": "UNCONFIGURED", + "owner": "HMarcusWH", + "reviewers": [] +} diff --git a/research/certification/legacy_event_reviewer_authority.json b/research/certification/legacy_event_reviewer_authority.json new file mode 100644 index 00000000..d434442e --- /dev/null +++ b/research/certification/legacy_event_reviewer_authority.json @@ -0,0 +1,6 @@ +{ + "format": "Permansson.PR113.LegacyEventReviewerAuthority.v1", + "status": "UNCONFIGURED", + "owner": "HMarcusWH", + "reviewers": [] +} diff --git a/research/certification/target_contract.schema.json b/research/certification/target_contract.schema.json new file mode 100644 index 00000000..01950762 --- /dev/null +++ b/research/certification/target_contract.schema.json @@ -0,0 +1,106 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Permansson detached execution target v1", + "type": "object", + "required": [ + "format", + "phase", + "repository", + "pr_number", + "branch", + "base_sha", + "head_sha", + "synthetic_sha", + "infrastructure_merge_sha" + ], + "additionalProperties": false, + "properties": { + "format": { + "const": "Permansson.DetachedExecutionTarget.v1" + }, + "phase": { + "enum": [ + "infrastructure_premerge", + "certification_premerge" + ] + }, + "repository": { + "const": "HMarcusWH/PermanssonLean" + }, + "pr_number": { + "type": "integer", + "minimum": 1 + }, + "branch": { + "type": "string", + "pattern": "^research/[A-Za-z0-9_./-]+$", + "maxLength": 180 + }, + "base_sha": { + "type": "string", + "pattern": "^[a-f0-9]{40}$" + }, + "head_sha": { + "type": "string", + "pattern": "^[a-f0-9]{40}$" + }, + "synthetic_sha": { + "type": "string", + "pattern": "^[a-f0-9]{40}$" + }, + "infrastructure_merge_sha": { + "type": [ + "string", + "null" + ], + "pattern": "^[a-f0-9]{40}$" + } + }, + "allOf": [ + { + "if": { + "properties": { + "phase": { + "const": "infrastructure_premerge" + } + } + }, + "then": { + "properties": { + "pr_number": { + "const": 113 + }, + "branch": { + "const": "research/final-mathematical-closure" + }, + "base_sha": { + "const": "17c315f7df7abf457eb94d393bd526d46da6f6f4" + }, + "infrastructure_merge_sha": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "phase": { + "const": "certification_premerge" + } + } + }, + "then": { + "properties": { + "pr_number": { + "type": "integer", + "minimum": 115 + }, + "infrastructure_merge_sha": { + "type": "string" + } + } + } + } + ] +} diff --git a/research/certification/target_intent.json b/research/certification/target_intent.json new file mode 100644 index 00000000..0f0ccd47 --- /dev/null +++ b/research/certification/target_intent.json @@ -0,0 +1,12 @@ +{ + "format": "Permansson.CertificationTargetIntent.v1", + "repository": "HMarcusWH/PermanssonLean", + "original_base_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "historical_T_sha": "71cc34fa0bc4b639c05d2ec5f39f0b88afd56388", + "infrastructure_pr": 113, + "certification_pr": null, + "status": "CERTIFICATION_NOT_YET_BOUND", + "independent_security_review": false, + "independent_mathematical_review": false, + "publication_authorized": false +} diff --git a/research/paper_closures/post_merge_112/PREMERGE_CLAIM_CASE_EVIDENCE_2026-10-21.json b/research/paper_closures/post_merge_112/PREMERGE_CLAIM_CASE_EVIDENCE_2026-10-21.json new file mode 100644 index 00000000..9fa2ca59 --- /dev/null +++ b/research/paper_closures/post_merge_112/PREMERGE_CLAIM_CASE_EVIDENCE_2026-10-21.json @@ -0,0 +1,925 @@ +{ + "format": "Permansson.PreMergeCaseAndClaimObservation.v1", + "status": "PREMERGE_AUDIT_ONLY__NOT_ACCEPTED", + "observed_on": "2026-10-21", + "observation_boundary": "This records original compiled source H before the evidence-file commit. The commit changes Git HEAD. No final-source certification or reviewer approval is asserted.", + "pr": 113, + "source_head": "1ec91b78f423261a91670e5f66bbd5ccb78ed0eb", + "base_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "synthetic_sha": "2034fa27329d8de445268a64639b88039a1ccbd6", + "synthetic_parents_observed": [ + "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "1ec91b78f423261a91670e5f66bbd5ccb78ed0eb" + ], + "merged_main_sha": null, + "observed_workflows": [ + { + "name": "Executable verification", + "run_id": 39652715580, + "conclusion": "success" + }, + { + "name": "v0.2.0 research release verification", + "run_id": 39652715449, + "conclusion": "success" + }, + { + "name": "Application contract", + "run_id": 39652715491, + "conclusion": "success" + }, + { + "name": "Research integrity", + "run_id": 39652716098, + "conclusion": "success" + }, + { + "name": "Integrity portability", + "run_id": 39652715531, + "conclusion": "success" + }, + { + "name": "F1 multi-agent protocols focused smoke", + "run_id": 39652715434, + "conclusion": "success" + }, + { + "name": "Lean", + "run_id": 39652715550, + "conclusion": "success" + }, + { + "name": "E1 approximate-quotient focused smoke", + "run_id": 39652715536, + "conclusion": "success" + }, + { + "name": "Research Lean", + "run_id": 39652715436, + "conclusion": "success" + } + ], + "research_regressions_at_H": "1211/1211 SUCCESS", + "observed_research_job": 119404925339, + "cases": { + "count": 45, + "pending": 15, + "partial_scope": 30, + "accepted": 0, + "rows": [ + { + "case_id": "X-01", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 3, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "R2-A01: emitted-literal corruption is not stochasticity-preserving." + ], + "case_authenticated": false + }, + { + "case_id": "X-02", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 4, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "X-03", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 5, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "X-04", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 5, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "R2-A02: unique learner attainer removal is tested, not tied-equilibrium domain removal." + ], + "case_authenticated": false + }, + { + "case_id": "X-05", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 7, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "X-06", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 5, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "S-01", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 6, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "S-02", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 6, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "M-01", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 5, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "M-02", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 7, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "R2-A04: marked-versus-unmarked action-coupling distinction has no dedicated tracked countermodel." + ], + "case_authenticated": false + }, + { + "case_id": "F-01", + "prior_decision": "PENDING", + "closure_ids": [ + "M1", + "M2", + "M3" + ], + "recorded_proof_references": 8, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "R2-A03: asymmetric common/private-update swap fixture missing.", + "R2-I02: general measurable n=2 product identity is distinct from the finite full model/path recovery." + ], + "case_authenticated": false + }, + { + "case_id": "F-02", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 5, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "F-03", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 4, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "F-04", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 3, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "F-05", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 5, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "R2-A05: positive exact SCM test is in the full suite, but not this 48-check route; dedicated positive Lean instantiation is absent." + ], + "case_authenticated": false + }, + { + "case_id": "Q-01", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 6, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "Q-02", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 6, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "ANA-03: direct projected theorem uses matched starts/homogeneous kernels; expanded projected adapter remains open." + ], + "case_authenticated": false + }, + { + "case_id": "Q-03", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 3, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "ANA-01: routed executable tests the old additive primitive budget, not product saturation." + ], + "case_authenticated": false + }, + { + "case_id": "Q-04", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 2, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "ANA-02: selected localized visit/original-margin/reporting integration missing." + ], + "case_authenticated": false + }, + { + "case_id": "Q-05", + "prior_decision": "PENDING", + "closure_ids": [ + "Q1", + "Q2", + "Q3", + "M1", + "M2", + "M3" + ], + "recorded_proof_references": 10, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "ANA-04: generic discounted/whole-law results are proved; a quotient-native original-margin application must still supply its actual bindings." + ], + "case_authenticated": false + }, + { + "case_id": "A-01", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 6, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "A-02", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 13, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "A-03", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 7, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "A-04", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 8, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "A-05", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 8, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "D-01", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 11, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "D-02", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 10, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "D-03", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 16, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "D-04", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 10, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "D-05", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 9, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "SR-A04: 9/100 versus1/100 boundary is scalar arithmetic rather than an instantiated committed-model path-law fixture." + ], + "case_authenticated": false + }, + { + "case_id": "D-06", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 7, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "SR-A05: hidden-flag fixture uses different pure priors, not two reachable histories from one fixed initial law." + ], + "case_authenticated": false + }, + { + "case_id": "D-07", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 9, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "D-08", + "prior_decision": "PENDING", + "closure_ids": [ + "D8" + ], + "recorded_proof_references": 5, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "SR-A02: concrete typed compatible-overlap and joint-only certified block fixture missing." + ], + "case_authenticated": false + }, + { + "case_id": "D-09", + "prior_decision": "PENDING", + "closure_ids": [ + "N1", + "N2", + "N3", + "D9" + ], + "recorded_proof_references": 7, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "SR-A01: concrete ExactClassFunctional and two-state5/6-versus1 regression missing." + ], + "case_authenticated": false + }, + { + "case_id": "C11-01", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 7, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "C11-02", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 7, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "C-01", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 11, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "B-01", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 6, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "SR-A02: generic compatible overlap proved; concrete typed positive/negative overlap fixtures missing." + ], + "case_authenticated": false + }, + { + "case_id": "B-02", + "prior_decision": "PENDING", + "closure_ids": [], + "recorded_proof_references": 3, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [ + "SR-A03: non-global-isomorphism positive local-margin transport fixture missing.", + "R2-C01: preserved OR/AND labels are correct for ablation margins." + ], + "case_authenticated": false + }, + { + "case_id": "XPLUS-01", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 4, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "E-01", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 4, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "E-02", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 4, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "E-03", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 4, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "E-04", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 5, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + }, + { + "case_id": "E-05", + "prior_decision": "PARTIAL_SCOPE", + "closure_ids": [], + "recorded_proof_references": 4, + "recorded_positive_test_receipts": 0, + "recorded_negative_test_receipts": 0, + "recorded_ci_receipts": 0, + "recorded_review_receipts": 0, + "explicit_remaining_obligations": [], + "case_authenticated": false + } + ] + }, + "claims": { + "count": 11, + "accepted": 0, + "rows": [ + { + "claim_id": "Q1", + "path": "PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean", + "github_blob_sha": "5bc6096a983318a399d5fa5fd4196b1c55a73f50", + "root_import_occurrences": 1, + "source_declaration_names": [ + "canonical_discounted_output_error" + ], + "mapped_original_cases": [ + "Q-05" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + }, + { + "claim_id": "Q2", + "path": "PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean", + "github_blob_sha": "96f86a243a3b88211de9b0fe6b8a80145eff8cd3", + "root_import_occurrences": 1, + "source_declaration_names": [ + "point_witness_output", + "actual_canonical_discounted_original_margin" + ], + "mapped_original_cases": [ + "Q-05" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + }, + { + "claim_id": "Q3", + "path": "PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean", + "github_blob_sha": "e79843e60a7c776d8352a91c0b2498260a8a6430", + "root_import_occurrences": 1, + "source_declaration_names": [ + "correct_frozen_discounted_budget", + "wrong_frozen_discounted_budget", + "exactKernelPointWitness", + "exactKernelPointWitness_retained_one", + "identityStateCompression", + "identity_canonical_full_path" + ], + "mapped_original_cases": [ + "Q-05" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + }, + { + "claim_id": "M1", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean", + "github_blob_sha": "e7c1a1e84715ea869e0eeb7a2b023b7888ef8350", + "root_import_occurrences": 1, + "source_declaration_names": [ + "matchedBinaryFamily", + "matchedBinaryClosed", + "matchedBinaryOpen", + "matchedBinaryClosed_apply", + "matchedBinaryOpen_apply", + "matched_closed_kernel_two", + "matched_open_kernel_two", + "matched_binary_closed_world_frozen", + "matched_binary_open_world_frozen" + ], + "mapped_original_cases": [ + "F-01", + "Q-05" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + }, + { + "claim_id": "M2", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + "github_blob_sha": "d3ce047db6564d591610e5b0fe103eba4ea8f26a", + "root_import_occurrences": 1, + "source_declaration_names": [ + "matched_closed_loop_original_margin", + "matched_open_loop_original_margin", + "matched_closed_loop_all_initial_paths", + "matched_open_loop_all_initial_paths" + ], + "mapped_original_cases": [ + "F-01", + "Q-05" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + }, + { + "claim_id": "M3", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean", + "github_blob_sha": "b5d3a252be48ad2013b5614b76861ac89ed00209", + "root_import_occurrences": 1, + "source_declaration_names": [ + "matched_closed_worlds_both_frozen", + "matched_open_worlds_both_frozen", + "wrong_asymmetric_input_change_is_detectable" + ], + "mapped_original_cases": [ + "F-01", + "Q-05" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + }, + { + "claim_id": "N1", + "path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean", + "github_blob_sha": "f786c9b89812d658bab7dd5494163429b77ba4f8", + "root_import_occurrences": 1, + "source_declaration_names": [ + "canonicalCycleState", + "native_cycle_state_zero", + "native_cycle_state_one", + "native_cycle_state_two", + "native_cycle_successor", + "phaseIndex", + "native_phase_orbit_is_cycle", + "exactCycleLaw", + "native_three_phase_true_share", + "phase_aware_real_transition", + "phase_aware_invariant_weights" + ], + "mapped_original_cases": [ + "D-09" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + }, + { + "claim_id": "N2", + "path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean", + "github_blob_sha": "db8ff1cef8c34352950256c934545366ec294230", + "root_import_occurrences": 1, + "source_declaration_names": [ + "actual_phase_aware_score_one_of_canonical_true_mass", + "actual_phase_blind_score_le_of_class_identification", + "actual_restricted_gap_of_identified_classes", + "native_phase_aware_actual_score_one" + ], + "mapped_original_cases": [ + "D-09" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + }, + { + "claim_id": "N3", + "path": "PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean", + "github_blob_sha": "7f918d91cbc10dbfc9eba13ca406679faf6cdf87", + "root_import_occurrences": 1, + "source_declaration_names": [ + "certified_boolean_score_exact", + "boolean_score_bounded", + "booleanClassFunctional", + "boolean_rational_objective_eq_actual" + ], + "mapped_original_cases": [ + "D-09" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + }, + { + "claim_id": "D8", + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean", + "github_blob_sha": "2f9e54f40c59ca2d0b9ae9074c78c4ed738b2ba4", + "root_import_occurrences": 1, + "source_declaration_names": [ + "forbidden_joint_not_in_restricted_menu", + "admitted_block_row_normalized", + "stateEquiv", + "stateIndex", + "atomRow", + "dirac_atom_exact", + "actionSelection", + "updateSelection", + "chosen", + "componentNames", + "chosen_action_components", + "chosen_update_components", + "actionSourceModel", + "updateSourceModel", + "actionSourceModel_induced", + "updateSourceModel_induced", + "restrictedCertifiedMenu", + "actual_restricted_menu_admits_both_components", + "actual_restricted_menu_rejects_joint", + "actual_restricted_menu_row_normalized" + ], + "mapped_original_cases": [ + "D-08" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + }, + { + "claim_id": "D9", + "path": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "github_blob_sha": "10bcbfb888a268fe381b23f747ed22d8fb3fdc3c", + "root_import_occurrences": 1, + "source_declaration_names": [ + "physically_distinct_classes_with_shared_descriptor", + "nonlinear_classwise_aggregation_not_barycenter", + "actual_two_absorbing_full_path_mixture", + "observedPhysicalBool", + "exact_two_absorbing_rational_scores", + "actual_equal_weight_original_scores", + "actual_multichain_classwise_differs_from_barycenter" + ], + "mapped_original_cases": [ + "D-09" + ], + "observed_status": "SOURCE_PRESENT_AND_RESEARCH_LIBRARY_COMPILED", + "claim_acceptance": "PENDING_DECLARATION_LEVEL_REVIEW", + "transitive_axiom_receipts": [], + "independent_review_receipts": [] + } + ] + }, + "review": { + "github_reviews": 0, + "registered_independent_review": false, + "academic_reviews": 0, + "source_reviews": 0, + "signoff": "NOT_RECEIVED" + }, + "release": { + "status": "DRAFT_NOT_ACCEPTED", + "visual_pdf_review": "NOT_PERFORMED", + "independent_scholarly_review": false + }, + "important_boundaries": [ + "Observed source-only blob identities; source SHA256 and per-theorem statement receipts still require exact-source extraction", + "Green source head CI does not imply 45-case acceptance", + "M is unavailable until an authorized merge", + "Not a substitute for exact-head independent review or manuscript/PDF inspection" + ] +} diff --git a/research/paper_closures/post_merge_112/PREMERGE_EXECUTED_TEST_EVIDENCE_2026-10-21.json b/research/paper_closures/post_merge_112/PREMERGE_EXECUTED_TEST_EVIDENCE_2026-10-21.json new file mode 100644 index 00000000..02354f80 --- /dev/null +++ b/research/paper_closures/post_merge_112/PREMERGE_EXECUTED_TEST_EVIDENCE_2026-10-21.json @@ -0,0 +1,348 @@ +{ + "format": "Permansson.PreMergeExecutedTestObservation.v1", + "status": "TEST_SOURCE_AND_CI_OBSERVATION_ONLY_NOT_ACCEPTED", + "observed_head_sha": "1ec91b78f423261a91670e5f66bbd5ccb78ed0eb", + "observed_date": "2026-10-21", + "source_fingerprint": "Exact Git blob IDs of test files at H; no after-commit current-head promotion", + "research_lean_run_id": 39652715436, + "research_lean_job_id": 119404925339, + "regression_result": { + "observed_tests_passed": 1211, + "total_tests": 1211 + }, + "test_module_count": 15, + "new_final_test_functions": 47, + "new_final_test_functions_with_named_pass": 47, + "missing_named_success_records": [], + "tests": [ + { + "test_path": "verification/research/test_final04_canonical_output.py", + "blob_sha": "e2d80afc8e6ce0252fcdeba92763f24fe7e4eecd", + "test_count": 5, + "executions": [ + { + "name": "test_frozen_geometric_exact_thirteen_fortyfive", + "observed_pass_in_ci_log": true + }, + { + "name": "test_zero_row_error_keeps_initial_mismatch", + "observed_pass_in_ci_log": true + }, + { + "name": "test_chronological_defects_not_last_row", + "observed_pass_in_ci_log": true + }, + { + "name": "test_intervention_only_reachable_row_charged", + "observed_pass_in_ci_log": true + }, + { + "name": "test_positive_leakage_cannot_certify_exact_retention", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final04_source_lean.py", + "blob_sha": "ebe8eb486422367391658cd9071a6e7c227f7b3f", + "test_count": 2, + "executions": [ + { + "name": "test_canonical_source_and_full_margin", + "observed_pass_in_ci_log": true + }, + { + "name": "test_actual_kernel_witness_examples_and_axioms", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final05_matched_protocols.py", + "blob_sha": "6cc4d8fa60bd5117b2a48d5f609d9e803aca2b7d", + "test_count": 4, + "executions": [ + { + "name": "test_asymmetric_opponent_anchor_not_changed_by_treated_component", + "observed_pass_in_ci_log": true + }, + { + "name": "test_joint_action_order_changes_physical_law_when_asymmetric", + "observed_pass_in_ci_log": true + }, + { + "name": "test_correlated_actions_are_outside_product_subclass", + "observed_pass_in_ci_log": true + }, + { + "name": "test_mixed_initial_law_requires_both_atoms", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final05_measurable_lean.py", + "blob_sha": "1acee08e4d36f8be26a3271d7564212560eb1ac5", + "test_count": 2, + "executions": [ + { + "name": "test_full_original_margin_compiler_axioms", + "observed_pass_in_ci_log": true + }, + { + "name": "test_matched_asymmetric_source_witnesses", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final06_actual_score_lean.py", + "blob_sha": "b920b7a06ed7bbf66c7f9650470940d1c073bd73", + "test_count": 2, + "executions": [ + { + "name": "test_source_signatures_and_axioms", + "observed_pass_in_ci_log": true + }, + { + "name": "test_false_native_cycle_score_rejected_semantically", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final06_c11_evaluator.py", + "blob_sha": "1a45ccd707a52540445cdaeee9df138002eb478c", + "test_count": 2, + "executions": [ + { + "name": "test_named_compiler_and_transitive_axioms", + "observed_pass_in_ci_log": true + }, + { + "name": "test_false_stationarity_accepted_mutant_rejected", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final06_native_class_lean.py", + "blob_sha": "3bb65857813403e7b890354a1afc6c89124f7933", + "test_count": 2, + "executions": [ + { + "name": "test_real_native_class_source_signatures_and_axioms", + "observed_pass_in_ci_log": true + }, + { + "name": "test_false_transition_arc_rejected_semantically", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final06_phaseblind_original.py", + "blob_sha": "05373e7c521af0a9a67b253ab5a22a15cb8e5fe7", + "test_count": 3, + "executions": [ + { + "name": "test_exact_policy_scores_not_just_orbit_table", + "observed_pass_in_ci_log": true + }, + { + "name": "test_lean_original_canonical_expected_scores_and_axioms", + "observed_pass_in_ci_log": true + }, + { + "name": "test_false_unrestricted_optimum_is_rejected_mathematically", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final06_stationary_arithmetic.py", + "blob_sha": "80805052e477f33cdba3189f425eaa56c5bbc29c", + "test_count": 4, + "executions": [ + { + "name": "test_phase_aware_real_native_three_cycle", + "observed_pass_in_ci_log": true + }, + { + "name": "test_all_four_deterministic_physical_only_feedbacks", + "observed_pass_in_ci_log": true + }, + { + "name": "test_randomized_class_tie_not_unrestricted_dominance", + "observed_pass_in_ci_log": true + }, + { + "name": "test_cannot_replace_classwise_expectation_with_nonlinear_barycenter", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final_closure_acceptance.py", + "blob_sha": "40f58b17c879db3f8c2411a1b064bce451127106", + "test_count": 4, + "executions": [ + { + "name": "test_candidate_inventory_is_full_but_pending", + "observed_pass_in_ci_log": true + }, + { + "name": "test_full45_without_real_source_receipts_rejects", + "observed_pass_in_ci_log": true + }, + { + "name": "test_scoped_release_needs_authenticated_compiler_and_review", + "observed_pass_in_ci_log": true + }, + { + "name": "test_historical_expectations_remain_complete", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final_closure_authenticate.py", + "blob_sha": "cebfbd398a408166e14f97742da8b00d0ad84e51", + "test_count": 7, + "executions": [ + { + "name": "test_boolean_authenticated_flag_never_suffices", + "observed_pass_in_ci_log": true + }, + { + "name": "test_forged_non_git_synthetic_parents_rejected", + "observed_pass_in_ci_log": true + }, + { + "name": "test_source_head_ci_must_be_complete", + "observed_pass_in_ci_log": true + }, + { + "name": "test_author_approval_is_not_independent_review", + "observed_pass_in_ci_log": true + }, + { + "name": "test_stale_review_commit_is_rejected", + "observed_pass_in_ci_log": true + }, + { + "name": "test_claimed_ci_id_must_match_live_check", + "observed_pass_in_ci_log": true + }, + { + "name": "test_live_merge_and_read_only_checks_are_all_bound", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final_closure_generator.py", + "blob_sha": "eac8cd0d7bee674445f5c5611517ca36c4fef0a8", + "test_count": 2, + "executions": [ + { + "name": "test_all_eleven_closure_modules_present_and_directly_imported", + "observed_pass_in_ci_log": true + }, + { + "name": "test_statement_hashes_have_strict_byte_identity", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final_d08_admission.py", + "blob_sha": "7e8ad13b1cb3cbcd96df287c87ded35b602526ff", + "test_count": 2, + "executions": [ + { + "name": "test_positive_signatures_and_axioms", + "observed_pass_in_ci_log": true + }, + { + "name": "test_false_frozen_joint_admission_is_semantic_mutant", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final_d09_multichain.py", + "blob_sha": "97a02a3c552c98387b831bd59db7541040a58c02", + "test_count": 3, + "executions": [ + { + "name": "test_exact_expected_score_not_score_of_expected_law", + "observed_pass_in_ci_log": true + }, + { + "name": "test_compiled_distinct_physical_classes_and_nonlinear_lemma", + "observed_pass_in_ci_log": true + }, + { + "name": "test_wrong_barycenter_literal_fails_for_mathematical_reason", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + }, + { + "test_path": "verification/research/test_final_release_manifest.py", + "blob_sha": "4cae61a6bb4a625b21a160e692cfc80b1996ccc9", + "test_count": 3, + "executions": [ + { + "name": "test_unreviewed_manifest_is_still_draft", + "observed_pass_in_ci_log": true + }, + { + "name": "test_fake_receipt_with_no_pdf_is_rejected", + "observed_pass_in_ci_log": true + }, + { + "name": "test_strict_rejects_draft", + "observed_pass_in_ci_log": true + } + ], + "source_audit": "SOURCE_BOUND_TO_OBSERVED_HEAD_AND_SUCCESSFUL_WORKFLOW" + } + ], + "research_artifacts": [ + { + "name": "legacy-research-source-context", + "id": 12371867374 + }, + { + "name": "d0-finite-calculation-evidence", + "id": 12371416774 + }, + { + "name": "c10-release-evidence", + "id": 12371403038 + } + ], + "not_claimed": [ + "A passing test is not an independent scholarly approval", + "Passing code and mocks are not proof of case-specific generality", + "No new source-bound signed review receipts are fabricated", + "Test names and Git blob IDs do not replace exact theorem signature and axiom receipts" + ], + "next_gate": "Re-run and independently inspect final-source theorem signatures, genuine negative compiler logs and reviewer approval on eventual final H/S/M." +} diff --git a/research/paper_closures/post_merge_112/PREMERGE_REVIEW_PACKET_2026-10-21.md b/research/paper_closures/post_merge_112/PREMERGE_REVIEW_PACKET_2026-10-21.md new file mode 100644 index 00000000..6fb867f2 --- /dev/null +++ b/research/paper_closures/post_merge_112/PREMERGE_REVIEW_PACKET_2026-10-21.md @@ -0,0 +1,92 @@ +# PR #113 — pre-merge mathematical acceptance reviewer packet + +**Status:** PRE-REVIEW CANDIDATE. No independent source or scholarly approval; no release authorization. This document is a source-indexed work queue, **not** an acceptance receipt. + +## Verified observations at historical source head H + +- GitHub PR: https://github.com/HMarcusWH/PermanssonLean/pull/113 +- Exact compiled source `H`: `1ec91b78f423261a91670e5f66bbd5ccb78ed0eb` (before additive audit-document commits). +- Base `B`: `17c315f7df7abf457eb94d393bd526d46da6f6f4`. Synthetic `S`: `2034fa27329d8de445268a64639b88039a1ccbd6`. The observed synthetic commit has ordered parents `[B,H]` and an integrated tree matching H. **No merged main M exists.** +- At H, all 9 PR workflows passed, including Research Lean; its 1,211 regression tests passed, and source checks passed. The N2 false-unrestricted-optimum mutant was rejected as a mathematical error. +- `15` new final test modules with `47/47` named test functions observed reporting `... ok` in the exact Research Lean job log. These are test observations, not per-case scholarly approvals. +- All 11 closure source files were present and imported once by the Research root. Their actual GitHub blob IDs and local declaration names are indexed in [the claim-and-case JSON](PREMERGE_CLAIM_CASE_EVIDENCE_2026-10-21.json). +- Frozen original mathematical providers, pins and old release files were not edited by this PR, according to the protected-source CI and changed-file inspection. + +## Original 45-case disposition + +- **15 `PENDING`; 30 `PARTIAL_SCOPE`; 0 independently accepted in the current ledger.** No ledger decision is upgraded by this packet. +- Original 42-contract atlas stays intact. Every case retains its original expected result, recorded proof references and unresolved obligations in `acceptance_decisions.json`. +- All 45 rows currently have zero case-level positive, negative, CI and reviewer receipt entries in the new registry. A global 1,211-test pass does not automatically fill those slots. + +### Fifteen specifically pending cases + +| Case | Newly mapped closure IDs | Legacy substantive acceptance residual (re-evaluate against current proofs) | +|---|---|---| +| `X-01` | — | R2-A01: emitted-literal corruption is not stochasticity-preserving. | +| `X-04` | — | R2-A02: unique learner attainer removal is tested, not tied-equilibrium domain removal. | +| `M-02` | — | R2-A04: marked-versus-unmarked action-coupling distinction has no dedicated tracked countermodel. | +| `F-01` | `M1`, `M2`, `M3` | R2-A03: asymmetric common/private-update swap fixture missing. / R2-I02: general measurable n=2 product identity is distinct from the finite full model/path recovery. | +| `F-05` | — | R2-A05: positive exact SCM test is in the full suite, but not this 48-check route; dedicated positive Lean instantiation is absent. | +| `Q-02` | — | ANA-03: direct projected theorem uses matched starts/homogeneous kernels; expanded projected adapter remains open. | +| `Q-03` | — | ANA-01: routed executable tests the old additive primitive budget, not product saturation. | +| `Q-04` | — | ANA-02: selected localized visit/original-margin/reporting integration missing. | +| `Q-05` | `Q1`, `Q2`, `Q3`, `M1`, `M2`, `M3` | ANA-04: generic discounted/whole-law results are proved; a quotient-native original-margin application must still supply its actual bindings. | +| `D-05` | — | SR-A04: 9/100 versus1/100 boundary is scalar arithmetic rather than an instantiated committed-model path-law fixture. | +| `D-06` | — | SR-A05: hidden-flag fixture uses different pure priors, not two reachable histories from one fixed initial law. | +| `D-08` | `D8` | SR-A02: concrete typed compatible-overlap and joint-only certified block fixture missing. | +| `D-09` | `N1`, `N2`, `N3`, `D9` | SR-A01: concrete ExactClassFunctional and two-state5/6-versus1 regression missing. | +| `B-01` | — | SR-A02: generic compatible overlap proved; concrete typed positive/negative overlap fixtures missing. | +| `B-02` | — | SR-A03: non-global-isomorphism positive local-margin transport fixture missing. / R2-C01: preserved OR/AND labels are correct for ablation margins. | + +Some historical residuals say a fixture was missing when the earlier audit was written. The new modules might address some of them. **Only a reviewer comparing exact theorem signatures, assumptions, source/test bytes and required negative conditions may update that determination.** + +### Eleven new candidate-claim source owners + +| ID | Research source module | Observed declarations | Acceptance | +|---|---|---:|---| +| `Q1` | `PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean` | 1 | Source compiled; declaration review pending | +| `Q2` | `PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean` | 2 | Source compiled; declaration review pending | +| `Q3` | `PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean` | 6 | Source compiled; declaration review pending | +| `M1` | `PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean` | 9 | Source compiled; declaration review pending | +| `M2` | `PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean` | 4 | Source compiled; declaration review pending | +| `M3` | `PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean` | 3 | Source compiled; declaration review pending | +| `N1` | `PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean` | 11 | Source compiled; declaration review pending | +| `N2` | `PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean` | 4 | Source compiled; declaration review pending | +| `N3` | `PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean` | 4 | Source compiled; declaration review pending | +| `D8` | `PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean` | 20 | Source compiled; declaration review pending | +| `D9` | `PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean` | 7 | Source compiled; declaration review pending | + +## Targeted theorem-scope triage (review notes, not independent acceptance) + +- **Q-05 / P-04:** `actual_canonical_discounted_original_margin` in `DiscountedCanonicalMarginClosure.lean` still requires a real `PointWitness` on **both** original baseline and intervention for **every** `y ∈ B₁.states`, exact comparison-image hypothesis `hB`, and uniform error envelopes. Green compilation establishes this conditional theorem, not unrestricted witness existence. Compare the concrete examples in Q3 against the original mandatory Q-05 test. +- **F-01 / P-05:** `matched_closed_loop_original_margin` and `matched_open_loop_original_margin` transport full original margins for an admitted matched conditional-product two-player family, assuming exact `Bbar₁.states` compression-image equality. Do not reinterpret this as arbitrary correlated multiagent recovery or drop the admitted mechanism/anchor conditions. +- **D-08:** the current `AdaptiveGrammarAdmissionFixture.lean` now contains a **nonvacuous** `restrictedCertifiedMenu` whose `admitted` field is defined using `restricted.allowed (componentNames b)` and has two selected components, physical source-row certificates and a proof rejecting the forbidden joint patch. The inherited ledger's 'fixture missing' note is historically scoped; an independent reviewer must retest the original acceptance requirement before changing the decision. +- **D-09:** the current `NonlinearMultichainAggregationFixture.lean` includes distinct physical class and shared-descriptor evidence, an actual two-absorbing original whole-path mixture and an unequal classwise-versus-barycenter nonlinear score. The inherited missing-fixture allegation may be outdated, but the full original acceptance requirement and negative tests still need source-bound review. +- **N2 / P-06 crosswalk completeness:** `NonlinearPeriodThreeExpectedScore.lean` contains a conditional restricted-gap theorem and an unconditional phase-aware numeric score. The unconditional deterministic phase-blind `5/6` bound, its attained example and strict dominance over the declared deterministic class are exported from the **additional** `NonlinearPhaseBlindClassGeometry.lean` module. These supporting declarations must be attached explicitly to the final N2 claim-level crosswalk; the primary 11-row module mapping alone is insufficient. Do **not** claim domination over randomized stationary or history-dependent controls. + +All five triage observations are source-reading notes at original green H, **not** claim approvals, per-declaration axiom certificates, or independent reviewer submissions. +## Reproducible reviewer task sequence + +1. **Produce current-source hashes and lexical theorem headers:** in a clean pinned checkout on a single reviewed H, run `python3 verification/research/final_closure_generate.py --output /tmp/permansson-claim-observation.json`. Treat its SHA-256 outputs only as source identities; they are not compiler receipts. +2. **Review theorem scopes and assumptions:** for each advertised theorem, save exact compiled `#check` and `#print axioms` output, verify its transitive axioms are within `propext`, `Classical.choice`, `Quot.sound`, inspect the original `B_1` and both sides of P-04 and P-05, and preserve the restricted deterministic policy class in P-06. +3. **Replay positive and adversarial negative tests:** preserve separate source-hashed Lean compiler logs with explicit proof/type rejection. Forbid parser errors, unknown identifiers, heartbeat/resource timeouts, and numerical substitutions. Include current D-08 grammar admission, D-09 distinct physical classes and nonlinear classwise evaluation. +4. **Reconcile every historical acceptance case:** attach its original expected wording, reviewed source declarations, positive and negative receipts, real run/job IDs and an individual independent decision. A `PARTIAL_SCOPE` case stays partial if an original scope or premise is not actually covered. +5. **Separate approvals:** an independent source reviewer must submit a genuine GitHub `APPROVED` review on the exact final head, while manuscript/academic review and PDF visual inspection require their own authentic evidence. No owner-authored comment is an independent review. +6. **Build and inspect the draft manuscript:** from the genuine repository checkout run `python3 release/mathematical-closure-1/build.py --mode draft --engine pdflatex --render --out /tmp/permansson-math-closure`; then `python3 release/mathematical-closure-1/verify_release.py --manifest /tmp/permansson-math-closure/manifest.lock.json`. Inspect all rendered pages and citations; keep the `DRAFT_NOT_ACCEPTED` label. This packet performed **source-level structural lint only**: 13 canonical source files, 10 `\input` references and bibliography entries were found, with no missing cross-reference files; no TeX compilation or PDF visual signoff is claimed. +7. **Make a distinct pre-merge review decision and post-merge final release decision:** the repository's `final_closure_authenticate.py` deliberately requires a real merged `M` for strict verification. Therefore its post-merge strict gate **cannot be a pre-merge prerequisite without circularity**. Do not weaken it. Obtain independent pre-merge review and a narrowly scoped merge authorization first; only after an authorized merge authenticate real `M`, rerun integrated-main CI and use the strict release gate. Full 45-case acceptance is a separate and stronger result than a scoped paper. + +## Go/no-go + +**NO GO for claiming 45-case completion, scoped scholarly acceptance, tagging, PDF publication or merging PR #113 as a completed mathematical-closure release.** The exact source branch now contains additional audit-only commits after H; rerun exact-head CI and issue fresh H/S observations before any merge decision. + +Open requirements: per-theorem statement SHA-256/axiom/negative diagnostic receipts; individual 45-case review evidence; independent source and scholarly signoff; real rendered PDF inspection; approved manuscript claim crosswalk; eventual M and source-bound integrated CI. No empirical GR/PR identification or universal randomized-control dominance is inferred. + +## Evidence pointers + +- [45-case / 11-module source observations](PREMERGE_CLAIM_CASE_EVIDENCE_2026-10-21.json) +- [47 named final-test and Git blob observations](PREMERGE_EXECUTED_TEST_EVIDENCE_2026-10-21.json) +- [Original immutable requirements](acceptance_decisions.json) +- [11-claim crosswalk](theorem_crosswalk.json) +- [Observed successful Research Lean run](https://github.com/HMarcusWH/PermanssonLean/actions/runs/39652715436) +- [Current candidate release manifest](../../../release/mathematical-closure-1/manifest.json) + diff --git a/research/paper_closures/post_merge_112/Q05_NONIDENTITY_SOURCE_SCOPE.md b/research/paper_closures/post_merge_112/Q05_NONIDENTITY_SOURCE_SCOPE.md new file mode 100644 index 00000000..63c04f84 --- /dev/null +++ b/research/paper_closures/post_merge_112/Q05_NONIDENTITY_SOURCE_SCOPE.md @@ -0,0 +1,100 @@ +# Q-05 — nonidentity source witness: exact numerical candidate + +**Status:** `EXACT_RATIONAL_CANDIDATE_NOT_LEAN_CERTIFIED`. This is a +constructive numerical regression for the original Q-05 obligation, not a +replacement for the original whole-path `ConstitutiveComparisonSet`, +`RegimePropertyMap`, or original `constitutiveMargin` theorem. + +## Typed fixture (frozen numerical target) + +- Fine strategic/world/action spaces: `Bool × Bool` and Boolean action. +- Coarse strategic/world/action spaces: `Unit × Bool` and Boolean action. +- Compression `q(s,x)=((),x)` drops strategic memory and is non-injective. +- The physical kernel **in all four source models** is literally + `x_next = action`. No within-model strategic intervention alters it. +- Fine action true probabilities: baseline `1/10` for `s=false`, + `1/5` for `s=true`; intervention `7/10` and `4/5`. +- Coarse action true probabilities: baseline `3/20`, treatment `3/4`. +- Baseline fine strategic update writes `s_next=false); intervention + writes `s_next=true`; coarse update remains `()`. +- Original comparison candidate, fixed before evaluation: + `B₁={(false,false),(false,true)}`. +- Discount `β=1/3` and score + `Φ(w)=(1−β)∑ₙ βⁿ 1{world(wₙ)=true}`. + +## Executed-in-Python rational equalities + +The independent finite-chain resolvent regression +`verification/research/test_q05_nonidentity_source_fixture.py` +evaluates the actual typed finite transition order and yields: + +| Original start | Fine baseline | Fine treatment | Coarse baseline | Coarse treatment | +|---|---:|---:|---:|---:| +| `(false,false)` | `1/30` | `11/45` | `1/20` | `1/4` | +| `(false,true)` | `7/10` | `41/45` | `43/60` | `11/12` | + +The fine original effect is `19/90` at both starts; coarse effect +`1/5` at both compressed starts; margin difference `1/90`. +The baseline output error is `1/60`, treatment error `1/180`. +Every fine row has projected common mass `19/20`, with +`η=1/20`. The exact discounted envelope on each side is +`βη/(1−β+βη)=1/41`; hence `1/90 ≤ 2/41`. + +The treatment reaches `s=true` from either original comparison start; +the baseline never does. A baseline-only row audit therefore omits a +genuinely treatment-reachable strategic state. The negative regression +also modifies that row while preserving probability normalization and +verifies that its defect exceeds `η`. + +## Required Lean integration not claimed here + +1. Construct actual typed fine/coarse `alpha`, fixed physical `P`, + baseline/treatment `U`, and verify their induced atoms. +2. Prove type-respecting nonidentity compression, both legal strategic + intervention constructors, and the required source `PointWitness` + common-subkernel/row-domination hypotheses on all reachable rows. +3. Supply **original** predeclared `B₁` with nonempty measurable + two-start path-law nontriviality; a singleton random path is invalid. +4. Bind the score to the original whole-path `RegimePropertyMap`, + actual canonical path laws and both uniform original-margin + comparison sets (`B̄₁=q(B₁)` exactly). +5. Verify all exported theorem types/axioms and semantic false-mutant + tests against one genuine frozen H/S source and independent case + review. No empirical, exact-GR, or all-time invariance promotion. + +**Preservation:** Existing Q-04 localized bridges, Q-02 projected +common-mass machinery, frozen original `PermanssonLean/`, and the old +negative X1 exact-GR result are reused unchanged. This fixture neither +supersedes nor proves them. + + +## Conditional original-API integration + +The concrete research module +`PermanssonResearch/Quotient/Q05ActualOriginalMarginAdapter.lean` +now constructs `sourcePointWitness` for the genuinely typed baseline and +treatment models **conditional on** `VerifiedSourceDomination`. That record +states two required kernel inequalities for **each** side: the common +subkernel is dominated by the actual fine induced kernel, and its +type-respecting projection is dominated by the actual coarse induced kernel. + +The module also supplies a fixed measurable Bool-world score, separate +admitted intervention world-fixity, and a conditional +`original_margin_of_verified_source_domination` using the genuine original +`constitutiveMargin` on the entire two-start `B₁`. The exact +`1/41 + 1/41 = 2/41` envelope comes from the source finite-retention +kernel, not an assumed numerical TV error. + +**NOT CLOSED:** `VerifiedSourceDomination` must be proved from the +actual `alpha -> P -> U` induced kernels, and the standalone rational +values `19/90` and `1/5` must still be identified with the two +whole-canonical-path original constitutive margins. Until those proofs +compile with exact-head provenance and receive independent review, the +conditional theorem is not a complete Q-05 acceptance certificate. + + +## Current-source reconciliation (27 October 2026) + +The historical checklist predates PermanssonResearch/Quotient/Q05ActualAtomClosure.lean. The current source contains fine_induced_singleton, coarse_induced_singleton, fine_actual_atom_domination, coarse_actual_atom_domination, original_verified_atom_domination, original_point_witness, and actual_original_B1_margin_bound. These implement typed alpha -> P -> U singleton identities, all-state domination and a 2/41 bound for the original whole-path margin difference over the two-start B1. Thus actual atom domination is not merely postulated, although compilation and independent statement review remain distinct. + +The bound does NOT establish exact 19/90 or 1/5 whole-path constitutive margins. Those must be individually located in Lean and authenticated, or recorded as remaining mathematics. No empirical/Exact-GR or unrestricted policy-class claim follows. diff --git a/research/paper_closures/post_merge_112/README.md b/research/paper_closures/post_merge_112/README.md new file mode 100644 index 00000000..44444cb7 --- /dev/null +++ b/research/paper_closures/post_merge_112/README.md @@ -0,0 +1,9 @@ +# Post-PR112 final-closure implementation snapshot + +Source base: `17c315f7df7abf457eb94d393bd526d46da6f6f4`. Feature PR: [#113](https://github.com/HMarcusWH/PermanssonLean/pull/113). + +This is a **candidate**. Its 45 original mandatory case descriptions remain identical to the preserved Oct 18/20 historical records; new proof bindings and real CI/review receipts must be added only after actual compilation. The baseline `post_merge_79/` and `post_merge_102/` directories remain untouched. + +`acceptance_decisions.json` intentionally preserves the earlier PENDING/PARTIAL_SCOPE decisions; the new theorems do not silently promote their cases. `theorem_crosswalk.json` adds explicit remaining typed closures as proposed objects. `source_ci_index.template.json` has null unknown refs and never invents a future merge. + +Audit, scoped-release-strict and full45-strict are separate gates. No owner COMMENT is an independent scholarly review; no numerical table is a Lean proof; no green CI certifies empirical GR/PR. diff --git a/research/paper_closures/post_merge_112/acceptance_case_source_map.json b/research/paper_closures/post_merge_112/acceptance_case_source_map.json new file mode 100644 index 00000000..50aad61d --- /dev/null +++ b/research/paper_closures/post_merge_112/acceptance_case_source_map.json @@ -0,0 +1,4693 @@ +{ + "cases": [ + { + "case_id": "X-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "X1-N01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "fbc4c91fcf7379f0c12b0112213e04b9e6d0a378179807b758dc3f2e4bb48b4c", + "name": "protocol_atom_eq", + "path": "PermanssonResearch/X1/InterventionNumericalBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e9e74f52381068e5894537b9c621c9d50bf0566a141f511f0a1e9f77401e120f", + "name": "matrix_eq_entry", + "path": "PermanssonResearch/X1/ExactMatrixTables.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e9e74f52381068e5894537b9c621c9d50bf0566a141f511f0a1e9f77401e120f", + "name": "matrix_atom_eq", + "path": "PermanssonResearch/X1/ExactMatrixTables.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "R2-A01: emitted-literal corruption is not stochasticity-preserving.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Kernel-checked equality; changed world/update/action row rejected", + "test_method_candidates": [ + { + "method": "test_actual_kernel_equality_rejects_normalized_wrong_row", + "path": "verification/research/test_next03_stochastic_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_source_bound_transitive_audit", + "path": "verification/research/test_paper_x1_matrix_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_next03_stochastic_lean.py", + "verification/research/test_paper_x1_matrix_lean.py" + ] + }, + { + "case_id": "X-02", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "X1-N02" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "fbc4c91fcf7379f0c12b0112213e04b9e6d0a378179807b758dc3f2e4bb48b4c", + "name": "kernelTerminal_eq_rational", + "path": "PermanssonResearch/X1/InterventionNumericalBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "name": "table_zero", + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "name": "table_step", + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "name": "original_property_eq_table", + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Actual-kernel induction identifies each entry", + "test_method_candidates": [ + { + "method": "test_corrupt_terminal_literal", + "path": "verification/research/test_paper_x1_lean_closure.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_wrong_terminal_horizon", + "path": "verification/research/test_paper_x1_lean_closure.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_paths_match_matrix_properties_and_killed_recursion", + "path": "verification/research/test_x1_commons.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_x1_lean_closure.py", + "verification/research/test_x1_commons.py" + ] + }, + { + "case_id": "X-03", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "X1-N03" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "effects_eq_candidates", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "exactEffect_lower", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "exactEffect_attained", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "exactEffect_attainers", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "component_original_margin", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Original constitutiveMargin equalities and exact attainers", + "test_method_candidates": [ + { + "method": "test_exact_positive_controls", + "path": "verification/research/test_paper_x1_lean_closure.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_prospective_full_region_infima_check_every_start", + "path": "verification/research/test_x1_commons.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_omitted_minimizing_start", + "path": "verification/research/test_paper_x1_lean_closure.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_x1_lean_closure.py", + "verification/research/test_x1_commons.py" + ] + }, + { + "case_id": "X-04", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "X1-N03" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "effects_eq_candidates", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "exactEffect_lower", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "exactEffect_attained", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "exactEffect_attainers", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "component_original_margin", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "R2-A02: unique learner attainer removal is tested, not tied-equilibrium domain removal.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Lean proof fails at arithmetic/domain obligation; parser failure alone insufficient", + "test_method_candidates": [ + { + "method": "test_lean_false_original_domain_mutation", + "path": "verification/research/test_next03_tied_start_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_X_04_original_source_positive", + "path": "verification/research/test_final_closure_missing_original_positives.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_next03_tied_start_lean.py", + "verification/research/test_final_closure_missing_original_positives.py" + ] + }, + { + "case_id": "X-05", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "X1-N01", + "X1-N02" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "fbc4c91fcf7379f0c12b0112213e04b9e6d0a378179807b758dc3f2e4bb48b4c", + "name": "protocol_atom_eq", + "path": "PermanssonResearch/X1/InterventionNumericalBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e9e74f52381068e5894537b9c621c9d50bf0566a141f511f0a1e9f77401e120f", + "name": "matrix_eq_entry", + "path": "PermanssonResearch/X1/ExactMatrixTables.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e9e74f52381068e5894537b9c621c9d50bf0566a141f511f0a1e9f77401e120f", + "name": "matrix_atom_eq", + "path": "PermanssonResearch/X1/ExactMatrixTables.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "fbc4c91fcf7379f0c12b0112213e04b9e6d0a378179807b758dc3f2e4bb48b4c", + "name": "kernelTerminal_eq_rational", + "path": "PermanssonResearch/X1/InterventionNumericalBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "name": "table_zero", + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "name": "table_step", + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "name": "original_property_eq_table", + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Typed kernel equality fails", + "test_method_candidates": [ + { + "method": "test_reset_before_action", + "path": "verification/research/test_paper_x1_lean_closure.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_new_world_learning", + "path": "verification/research/test_paper_x1_lean_closure.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_swapped_update_protocol", + "path": "verification/research/test_paper_x1_lean_closure.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_X_05_original_source_positive", + "path": "verification/research/test_final_closure_missing_original_positives.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_x1_lean_closure.py", + "verification/research/test_final_closure_missing_original_positives.py" + ] + }, + { + "case_id": "X-06", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "X1-N03" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "effects_eq_candidates", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "exactEffect_lower", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "exactEffect_attained", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "exactEffect_attainers", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "name": "component_original_margin", + "path": "PermanssonResearch/X1/ExactMargins.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Rejected; leakage theorem remains unchanged", + "test_method_candidates": [ + { + "method": "test_leaking_region_not_exact_gr_and_persistence_decay", + "path": "verification/research/test_x1_commons.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_positive_quotient_margin_not_exact_gr", + "path": "verification/research/test_e1_approx_quotient.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_X_06_original_source_positive", + "path": "verification/research/test_final_closure_missing_original_positives.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_X_06_false_exact_gr_under_real_leakage", + "path": "verification/research/test_final_closure_existing_false_semantics_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_x1_commons.py", + "verification/research/test_e1_approx_quotient.py", + "verification/research/test_final_closure_missing_original_positives.py", + "verification/research/test_final_closure_existing_false_semantics_lean.py" + ] + }, + { + "case_id": "S-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "X1-S01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "18c8867029bf65b63bc3f02ca5c84d8837983d1767137c600c918f51055528b7", + "name": "finite_performance_difference", + "path": "PermanssonResearch/Games/DiscountedVerification.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "18c8867029bf65b63bc3f02ca5c84d8837983d1767137c600c918f51055528b7", + "name": "payoff_tail_bound", + "path": "PermanssonResearch/Games/DiscountedVerification.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "6252760219c99be64055c17b091059eba64ca1d4197dc6182a8c2ddb3288da3b", + "name": "payoff_eq_expected_path_sum", + "path": "PermanssonResearch/Games/DiscountedTraceExpectation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "ee27ac8502de1ffec64aebf900eeb38d9f3560c46ddafeebb6615097654ef034", + "name": "strategy_exact_advantage", + "path": "PermanssonResearch/X1/FullStrategyCertificate.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "675d4530df8d8bf8b697ee60ab7282e684c71afbe0f4d0e3dd0dcf299d49ffd7", + "name": "baseline_trace_stateLaw_eq_original", + "path": "PermanssonResearch/X1/StrategyOriginalBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b0b4909441cfd5f54622d11d87e2bc54737a801917a5e5e29c36a73ef4021416", + "name": "expected_exact_behavioral_performance_difference", + "path": "PermanssonResearch/X1/FullStrategyTrace.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Exact identity including terminal beta^N value", + "test_method_candidates": [ + { + "method": "test_source_bound_transitive_axioms", + "path": "verification/research/test_paper_strategy_economic_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_S_01_no_terminal_correction_is_false", + "path": "verification/research/test_pr113_semantic_second_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_strategy_economic_lean.py", + "verification/research/test_pr113_semantic_second_candidates.py" + ] + }, + { + "case_id": "S-02", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "X1-S01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "18c8867029bf65b63bc3f02ca5c84d8837983d1767137c600c918f51055528b7", + "name": "finite_performance_difference", + "path": "PermanssonResearch/Games/DiscountedVerification.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "18c8867029bf65b63bc3f02ca5c84d8837983d1767137c600c918f51055528b7", + "name": "payoff_tail_bound", + "path": "PermanssonResearch/Games/DiscountedVerification.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "6252760219c99be64055c17b091059eba64ca1d4197dc6182a8c2ddb3288da3b", + "name": "payoff_eq_expected_path_sum", + "path": "PermanssonResearch/Games/DiscountedTraceExpectation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "ee27ac8502de1ffec64aebf900eeb38d9f3560c46ddafeebb6615097654ef034", + "name": "strategy_exact_advantage", + "path": "PermanssonResearch/X1/FullStrategyCertificate.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "675d4530df8d8bf8b697ee60ab7282e684c71afbe0f4d0e3dd0dcf299d49ffd7", + "name": "baseline_trace_stateLaw_eq_original", + "path": "PermanssonResearch/X1/StrategyOriginalBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b0b4909441cfd5f54622d11d87e2bc54737a801917a5e5e29c36a73ef4021416", + "name": "expected_exact_behavioral_performance_difference", + "path": "PermanssonResearch/X1/FullStrategyTrace.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Outside admitted information class; no verification theorem", + "test_method_candidates": [ + { + "method": "test_original_information_class_and_transitive_axioms", + "path": "verification/research/test_paper_information_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_history_sensitive_policy_no_future_information", + "path": "verification/research/test_d2_residence.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_S_02_clairvoyant_action_is_not_admissible_history_row", + "path": "verification/research/test_pr113_semantic_structural_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_information_lean.py", + "verification/research/test_d2_residence.py", + "verification/research/test_pr113_semantic_structural_candidates.py" + ] + }, + { + "case_id": "M-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "F1-MA2" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "0d101feb3a9d8e080d411560c8af2d8b0350e7e7ca1d91dc29fe92116cf84ab2", + "name": "autonomous_marked_atom", + "path": "PermanssonResearch/MultiAgent/AutonomousMarkedKernel.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "668d6db254b00da02fc4d89b53ab5f459203de2e361d10513b9fb1940bdc427d", + "name": "supported_full_kernel_eq", + "path": "PermanssonResearch/MultiAgent/AutonomousSupportClosure.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c294448f00c5c79d66a17684836616878258be7d2f7c8772ab7281b214c56c2f", + "name": "strict_anchors_pathLaw_eq", + "path": "PermanssonResearch/MultiAgent/StrictMA2Obstruction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4bbece652236607d405af7d8cc03c536e16e3da04952c7abc90684776233f298", + "name": "ae_anchors_pathLaw_eq", + "path": "PermanssonResearch/MultiAgent/AutonomousAEMechanisms.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c36d8c2651f11d8c045b5a48e3b2fa033fa6f906cc4f6fbeac9c7a3e000e45a2", + "name": "nonatomic_treated_pathLaw_eq", + "path": "PermanssonResearch/MultiAgent/AutonomousAEContinuumFixture.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Full counterfactual path laws equal under stated premises", + "test_method_candidates": [ + { + "method": "test_actual_all_time_autonomy_and_transitive_axioms", + "path": "verification/research/test_paper_autonomy_ae_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_strict_autonomous_off_support_difference_stochastic_crosscheck", + "path": "verification/research/test_f1_multiagent_protocols.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_M_01_null_key_is_not_free_initial_support", + "path": "verification/research/test_pr113_semantic_second_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_autonomy_ae_lean.py", + "verification/research/test_f1_multiagent_protocols.py", + "verification/research/test_pr113_semantic_second_candidates.py" + ] + }, + { + "case_id": "M-02", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "F1-MA2", + "F1-P05" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "0d101feb3a9d8e080d411560c8af2d8b0350e7e7ca1d91dc29fe92116cf84ab2", + "name": "autonomous_marked_atom", + "path": "PermanssonResearch/MultiAgent/AutonomousMarkedKernel.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "668d6db254b00da02fc4d89b53ab5f459203de2e361d10513b9fb1940bdc427d", + "name": "supported_full_kernel_eq", + "path": "PermanssonResearch/MultiAgent/AutonomousSupportClosure.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c294448f00c5c79d66a17684836616878258be7d2f7c8772ab7281b214c56c2f", + "name": "strict_anchors_pathLaw_eq", + "path": "PermanssonResearch/MultiAgent/StrictMA2Obstruction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4bbece652236607d405af7d8cc03c536e16e3da04952c7abc90684776233f298", + "name": "ae_anchors_pathLaw_eq", + "path": "PermanssonResearch/MultiAgent/AutonomousAEMechanisms.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c36d8c2651f11d8c045b5a48e3b2fa033fa6f906cc4f6fbeac9c7a3e000e45a2", + "name": "nonatomic_treated_pathLaw_eq", + "path": "PermanssonResearch/MultiAgent/AutonomousAEContinuumFixture.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c7a58fbc86cebda84e3162a1cd09d229651fc4966121f891dd7cb76ca403aa48", + "name": "row_iff_all_histories", + "path": "PermanssonResearch/MultiAgent/BehavioralAutonomy.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c7a58fbc86cebda84e3162a1cd09d229651fc4966121f891dd7cb76ca403aa48", + "name": "exists_row_iff_all_histories", + "path": "PermanssonResearch/MultiAgent/BehavioralAutonomy.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "R2-A04: marked-versus-unmarked action-coupling distinction has no dedicated tracked countermodel.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Autonomy premise rejected; reactive witness not counted strict", + "test_method_candidates": [ + { + "method": "test_unmarked_key_laws_identical", + "path": "verification/research/test_next03_m02_marked_coupling.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_marked_diagonal_is_maximally_separating", + "path": "verification/research/test_next03_m02_marked_coupling.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_original_typed_generator_source_and_axioms", + "path": "verification/research/test_m02_marked_actual_source_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_invalid_strict_autonomy_promotion_fails_semantically", + "path": "verification/research/test_m02_marked_actual_source_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_real_marked_history_full_source_and_axioms", + "path": "verification/research/test_m02_actual_history_kernel_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_falsified_actual_marked_diagonal_is_rejected", + "path": "verification/research/test_m02_actual_history_kernel_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_next03_m02_marked_coupling.py", + "verification/research/test_m02_marked_actual_source_lean.py", + "verification/research/test_m02_actual_history_kernel_lean.py" + ] + }, + { + "case_id": "F-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "F1-P01", + "F1-P02" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "c35c5318c9f3e0459b22f1d4f946071c88fc4af37c4a8bf766b5015b0ec4252f", + "name": "generator", + "path": "PermanssonResearch/MultiAgent/PlayerSwapTransport.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c35c5318c9f3e0459b22f1d4f946071c88fc4af37c4a8bf766b5015b0ec4252f", + "name": "actual_pathLaw", + "path": "PermanssonResearch/MultiAgent/PlayerSwapTransport.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "83845f1f26eb7c09c14e8cecdbd1bbf886219da403a610a1d7b4794731bbc8d6", + "name": "original_effect", + "path": "PermanssonResearch/MultiAgent/PlayerSwapConstitution.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "83845f1f26eb7c09c14e8cecdbd1bbf886219da403a610a1d7b4794731bbc8d6", + "name": "original_margin", + "path": "PermanssonResearch/MultiAgent/PlayerSwapConstitution.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "36babffa63878708fb8289bedf06b617032bbfd2cba48c71bfb60a96358a7c7a", + "name": "actual_transition", + "path": "PermanssonResearch/MultiAgent/MeasurableFiniteProduct.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "9894c87e9c92af3b16cfd26b7776c51c45a7bbf29bebfa01a97c58756fcc7117", + "name": "original_margin_comparison", + "path": "PermanssonResearch/MultiAgent/MeasurableProductProtocols.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "9894c87e9c92af3b16cfd26b7776c51c45a7bbf29bebfa01a97c58756fcc7117", + "name": "productKernel_two", + "path": "PermanssonResearch/MultiAgent/MeasurableProductProtocols.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "f54d1c3a23714604298d38adc4518fbb1f9027941551a25a7ba3fa4f6b6ac6ec", + "name": "actual_pathLaw", + "path": "PermanssonResearch/MultiAgent/FiniteProductTwo.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [ + { + "id": "M1", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean" + }, + { + "id": "M2", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean" + }, + { + "id": "M3", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean" + } + ], + "remaining_obligations": [ + "R2-A03: asymmetric common/private-update swap fixture missing.", + "R2-I02: general measurable n=2 product identity is distinct from the finite full model/path recovery.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "P inputs reindexed correctly, no assumed symmetry", + "test_method_candidates": [ + { + "method": "test_actual_common_update_false_same_input_claim", + "path": "verification/research/test_next03_asymmetric_common_swap_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_matched_asymmetric_source_witnesses", + "path": "verification/research/test_final05_measurable_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_source_bound_non_dirac_asymmetric_whole_path", + "path": "verification/research/test_next03_asymmetric_common_swap_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_next03_asymmetric_common_swap_lean.py", + "verification/research/test_final05_measurable_lean.py" + ] + }, + { + "case_id": "F-02", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "F1-P03" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "3b95ae06a9eb3a6500cd6df23c291bbb865343a9fcb6508d0eca288bf74afb2a", + "name": "advancing_update", + "path": "PermanssonResearch/MultiAgent/MeasurableNatPrivateClock.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3b95ae06a9eb3a6500cd6df23c291bbb865343a9fcb6508d0eca288bf74afb2a", + "name": "inherited_strictness", + "path": "PermanssonResearch/MultiAgent/MeasurableNatPrivateClock.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "ad1b1eae9afeaca7ba334dd545b8d14864af9a5a8c67c796f5f487f90acbec42", + "name": "actual_row", + "path": "PermanssonResearch/MultiAgent/MeasurableNatClockTransport.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "ad1b1eae9afeaca7ba334dd545b8d14864af9a5a8c67c796f5f487f90acbec42", + "name": "natural_projection", + "path": "PermanssonResearch/MultiAgent/MeasurableNatClockTransport.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c793a3886ecc064e455b951c17c55d82699ef371ca4f2223dfaf6989a7a40bbe", + "name": "stalled_clock_rejected", + "path": "PermanssonResearch/MultiAgent/MeasurableNatClockFixture.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Projection/exogeneity fails", + "test_method_candidates": [ + { + "method": "test_actual_measurable_clock_projection_and_axioms", + "path": "verification/research/test_paper_measurable_clock_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_nonstationary_anchor_needs_clock_in_baseline", + "path": "verification/research/test_f1_multiagent_protocols.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_F_02_false_stalled_original_natural_clock", + "path": "verification/research/test_final_closure_more_false_semantics_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_measurable_clock_lean.py", + "verification/research/test_f1_multiagent_protocols.py", + "verification/research/test_final_closure_more_false_semantics_lean.py" + ] + }, + { + "case_id": "F-03", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "F1-P04" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "3bd960cd530773a2efac8482ea326c43d004c9181cfa763aa5651f7536608e64", + "name": "keyLaw_feasible_iff", + "path": "PermanssonResearch/MultiAgent/FeasibleAnchors.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3bd960cd530773a2efac8482ea326c43d004c9181cfa763aa5651f7536608e64", + "name": "exists_feasible_strictAnchor_iff", + "path": "PermanssonResearch/MultiAgent/FeasibleAnchors.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3bd960cd530773a2efac8482ea326c43d004c9181cfa763aa5651f7536608e64", + "name": "leastFibreAction_legal", + "path": "PermanssonResearch/MultiAgent/FeasibleAnchors.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3bd960cd530773a2efac8482ea326c43d004c9181cfa763aa5651f7536608e64", + "name": "leastFibreAction_le", + "path": "PermanssonResearch/MultiAgent/FeasibleAnchors.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "No feasible strict probability anchor", + "test_method_candidates": [ + { + "method": "test_nonempty_state_menus_can_have_empty_key_intersection", + "path": "verification/research/test_paper_closure_boundaries.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_infeasible_anchor_excluded_separate_toy", + "path": "verification/research/test_f1_multiagent_protocols.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_F_03_original_source_positive", + "path": "verification/research/test_final_closure_missing_original_positives.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_actual_all_state_legal_fibre_and_original_existence_theorems", + "path": "verification/research/test_f03_actual_infeasible_strict_anchor_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_false_feasible_anchor_with_conflicting_own_fibre_rejected", + "path": "verification/research/test_f03_actual_infeasible_strict_anchor_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_closure_boundaries.py", + "verification/research/test_f1_multiagent_protocols.py", + "verification/research/test_final_closure_missing_original_positives.py", + "verification/research/test_f03_actual_infeasible_strict_anchor_lean.py" + ] + }, + { + "case_id": "F-04", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "F1-P06" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "b547e44664bf1ab08482fde276b4448398d05ffe6be77b41da29b3c14739e777", + "name": "mediator_mixture_eq", + "path": "PermanssonResearch/MultiAgent/CorrelatedMediator.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b547e44664bf1ab08482fde276b4448398d05ffe6be77b41da29b3c14739e777", + "name": "mediatedAction", + "path": "PermanssonResearch/MultiAgent/CorrelatedMediator.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b547e44664bf1ab08482fde276b4448398d05ffe6be77b41da29b3c14739e777", + "name": "withMediatedActions", + "path": "PermanssonResearch/MultiAgent/CorrelatedMediator.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Single-product claim fails; mediator mixture succeeds", + "test_method_candidates": [ + { + "method": "test_ma3_correlated_law_not_product_exact", + "path": "verification/research/test_f1_multiagent_protocols.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_ma3_shared_source_is_representable", + "path": "verification/research/test_f1_multiagent_protocols.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_F_04_false_product_representation_of_correlated_law", + "path": "verification/research/test_final_closure_more_false_semantics_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_f1_multiagent_protocols.py", + "verification/research/test_final_closure_more_false_semantics_lean.py" + ] + }, + { + "case_id": "F-05", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "F1-P07" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "2404c03891bb8912e73239e9b902ba76b7f02b656a659f7db8efeada63fabe3a", + "name": "mediation_identification", + "path": "PermanssonResearch/MultiAgent/FiniteMediation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "2404c03891bb8912e73239e9b902ba76b7f02b656a659f7db8efeada63fabe3a", + "name": "mediation_identification_on_support", + "path": "PermanssonResearch/MultiAgent/FiniteMediation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "2404c03891bb8912e73239e9b902ba76b7f02b656a659f7db8efeada63fabe3a", + "name": "signed_effect_decomposition", + "path": "PermanssonResearch/MultiAgent/FiniteMediation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "2404c03891bb8912e73239e9b902ba76b7f02b656a659f7db8efeada63fabe3a", + "name": "two_SCMs_same_observational_law", + "path": "PermanssonResearch/MultiAgent/FiniteMediation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "2404c03891bb8912e73239e9b902ba76b7f02b656a659f7db8efeada63fabe3a", + "name": "two_SCMs_positivity_failure", + "path": "PermanssonResearch/MultiAgent/FiniteMediation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "R2-A05: positive exact SCM test is in the full suite, but not this 48-check route; dedicated positive Lean instantiation is absent.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Mediation not identified without sufficient premises", + "test_method_candidates": [ + { + "method": "test_mediator_matters_not_only_direct_treatment", + "path": "verification/research/test_next03_positive_mediation.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_observed_mediation_does_not_identify_joint_intervention", + "path": "verification/research/test_paper_closure_boundaries.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_F_05_false_identification_of_original_joint_do", + "path": "verification/research/test_final_closure_more_false_semantics_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_next03_positive_mediation.py", + "verification/research/test_paper_closure_boundaries.py", + "verification/research/test_final_closure_more_false_semantics_lean.py" + ] + }, + { + "case_id": "Q-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "E1-G01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "07f20e818df7eb4362cad37d367eb9b63370f346c3c3721fd32b4544083d897c", + "name": "map_liftProjectedDensity", + "path": "PermanssonResearch/Probability/ProjectedCommonSubkernel.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "26f7d49df5bb7e083544a15a59d5392bad31aeeab5cab908e965a24a0b83bf4c", + "name": "projectedCommonKernel", + "path": "PermanssonResearch/Quotient/GeometricQuotient.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "28c69bccf4667c2aca09fc1294d9efe471d35a6a8ba7c376c32a2ce01ed54cba", + "name": "approxQuotient_prefix_tv_le_geometric_of_subkernel", + "path": "PermanssonResearch/Quotient/GeometricPrefix.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "26f7d49df5bb7e083544a15a59d5392bad31aeeab5cab908e965a24a0b83bf4c", + "name": "approxQuotient_prefix_tv_le_geometric", + "path": "PermanssonResearch/Quotient/GeometricQuotient.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b2929544c41fe5af75bb07c53f9a28f1b3d2ccc366430ec48716d756678a03e2", + "name": "geometricQuotient_margin_abs_le", + "path": "PermanssonResearch/Quotient/GeometricMargin.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "471a561f008715d8794533fd84d50794a2b9f9cbbd078cc516ea5de8231706f9", + "name": "prefix_tv_le_product", + "path": "PermanssonResearch/Probability/DependentHistoryCommonMass.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Geometric bound holds without claiming projection Markov", + "test_method_candidates": [ + { + "method": "test_projection_is_not_markov_from_one_start", + "path": "verification/research/test_e1_approx_quotient.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_geometric_bound_numeric_only", + "path": "verification/research/test_e1_approx_quotient.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_Q_01_fine_projection_does_not_always_lump", + "path": "verification/research/test_pr113_semantic_structural_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_e1_approx_quotient.py", + "verification/research/test_pr113_semantic_structural_candidates.py" + ] + }, + { + "case_id": "Q-02", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "E1-G01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "07f20e818df7eb4362cad37d367eb9b63370f346c3c3721fd32b4544083d897c", + "name": "map_liftProjectedDensity", + "path": "PermanssonResearch/Probability/ProjectedCommonSubkernel.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "26f7d49df5bb7e083544a15a59d5392bad31aeeab5cab908e965a24a0b83bf4c", + "name": "projectedCommonKernel", + "path": "PermanssonResearch/Quotient/GeometricQuotient.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "28c69bccf4667c2aca09fc1294d9efe471d35a6a8ba7c376c32a2ce01ed54cba", + "name": "approxQuotient_prefix_tv_le_geometric_of_subkernel", + "path": "PermanssonResearch/Quotient/GeometricPrefix.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "26f7d49df5bb7e083544a15a59d5392bad31aeeab5cab908e965a24a0b83bf4c", + "name": "approxQuotient_prefix_tv_le_geometric", + "path": "PermanssonResearch/Quotient/GeometricQuotient.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b2929544c41fe5af75bb07c53f9a28f1b3d2ccc366430ec48716d756678a03e2", + "name": "geometricQuotient_margin_abs_le", + "path": "PermanssonResearch/Quotient/GeometricMargin.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "471a561f008715d8794533fd84d50794a2b9f9cbbd078cc516ea5de8231706f9", + "name": "prefix_tv_le_product", + "path": "PermanssonResearch/Probability/DependentHistoryCommonMass.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "ANA-03: direct projected theorem uses matched starts/homogeneous kernels; expanded projected adapter remains open.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Correct boundaries; mismatched unbudgeted starts rejected", + "test_method_candidates": [ + { + "method": "test_zero_kernel_defect_does_not_delete_initial_mismatch", + "path": "verification/research/test_next04_projection_arithmetic.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_time_varying_initial_and_row_budget", + "path": "verification/research/test_next04_projection_arithmetic.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_Q_02_false_zero_initial_error_under_zero_row_defect", + "path": "verification/research/test_final_closure_more_false_semantics_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_next04_projection_arithmetic.py", + "verification/research/test_final_closure_more_false_semantics_lean.py" + ] + }, + { + "case_id": "Q-03", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "A2-P01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "142d1a885638c3621ee14aacbd0e5375bff33806662b1226178422b456b60aee", + "name": "primitiveCommon_mass_ge_product", + "path": "PermanssonResearch/Perturbation/PrimitiveProductTV.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "142d1a885638c3621ee14aacbd0e5375bff33806662b1226178422b456b60aee", + "name": "inducedKernel_tv_le_primitive_product", + "path": "PermanssonResearch/Perturbation/PrimitiveProductTV.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "ce5dbcc322706ed5b4027b3138078c3ceb8b191d82bc4f5304672012ebf6fec4", + "name": "inducedKernel_w1DualLE_of_primitive_product", + "path": "PermanssonResearch/Perturbation/PrimitiveMetricBudget.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "ANA-01: routed executable tests the old additive primitive budget, not product saturation.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Product mass proved, no independence assumption", + "test_method_candidates": [ + { + "method": "test_true_conditional_history_dependence", + "path": "verification/research/test_next03_dependent_three_stage.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_a2_15_seeded_exact_rational_primitive_rows", + "path": "verification/research/test_a2_primitive_tv.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_Q_03_false_independent_error_mass", + "path": "verification/research/test_final_actual_product_reporting_false_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_next03_dependent_three_stage.py", + "verification/research/test_a2_primitive_tv.py", + "verification/research/test_final_actual_product_reporting_false_lean.py" + ] + }, + { + "case_id": "Q-04", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "A2-P02" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "0d34452c4a2d571c5aa55960be13dd617b663bddaa76346c97ed778f22cf664c", + "name": "eventTV_compProd_averaged", + "path": "PermanssonResearch/Probability/LocalizedHistoryTV.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "0d34452c4a2d571c5aa55960be13dd617b663bddaa76346c97ed778f22cf664c", + "name": "historyEvolution_tv_le_sum", + "path": "PermanssonResearch/Probability/LocalizedHistoryTV.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "ANA-02: selected localized visit/original-margin/reporting integration missing.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Intervention defect charged rather than omitted", + "test_method_candidates": [ + { + "method": "test_actual_final_source_and_semantic_mutations", + "path": "verification/research/test_localized_prefix_compiler.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_both_error_sides", + "path": "verification/research/test_next04_original_margin.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_Q_04_false_zero_intervention_report_error", + "path": "verification/research/test_final_actual_product_reporting_false_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_localized_prefix_compiler.py", + "verification/research/test_next04_original_margin.py", + "verification/research/test_final_actual_product_reporting_false_lean.py" + ] + }, + { + "case_id": "Q-05", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "E1-P01", + "E1-P02" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "43dae187fef4703ef6f93a1086ed2f43b23f3e3d54be7b18236939c74d7f6baa", + "name": "integral_summablePathScore", + "path": "PermanssonResearch/Probability/SummablePathScores.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "43dae187fef4703ef6f93a1086ed2f43b23f3e3d54be7b18236939c74d7f6baa", + "name": "summable_output_score_error", + "path": "PermanssonResearch/Probability/SummablePathScores.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "43dae187fef4703ef6f93a1086ed2f43b23f3e3d54be7b18236939c74d7f6baa", + "name": "discounted_geometric_budget", + "path": "PermanssonResearch/Probability/SummablePathScores.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "55c9c9e26ca8ddbd81595ca5b47f6d5f72610d996c55316398b45922f3d30af6", + "name": "discounted_output_score_error_initial", + "path": "PermanssonResearch/Probability/DiscountedInitialBudget.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "55c9c9e26ca8ddbd81595ca5b47f6d5f72610d996c55316398b45922f3d30af6", + "name": "zero_row_defect_retains_initial_budget", + "path": "PermanssonResearch/Probability/DiscountedInitialBudget.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "7711c2f3ad7ce9d05f655211b1045d2f355cd50a674c1aebc0caa6ee17839c5b", + "name": "path_eventTotalVariation_le_of_prefixes", + "path": "PermanssonResearch/Probability/CylinderTotalVariation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "f802694a3d08aa1230bfbad95261358f0c236137f34f2eeb99651d6babd7be3e", + "name": "path_tv_le_product", + "path": "PermanssonResearch/Probability/FullPathProductTV.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b2929544c41fe5af75bb07c53f9a28f1b3d2ccc366430ec48716d756678a03e2", + "name": "geometricQuotient_uniformPR_pair", + "path": "PermanssonResearch/Quotient/GeometricMargin.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b2929544c41fe5af75bb07c53f9a28f1b3d2ccc366430ec48716d756678a03e2", + "name": "margin_positive_pair_of_budget", + "path": "PermanssonResearch/Quotient/GeometricMargin.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b2929544c41fe5af75bb07c53f9a28f1b3d2ccc366430ec48716d756678a03e2", + "name": "grounded_uniformPR_pair_of_margin_budget", + "path": "PermanssonResearch/Quotient/GeometricMargin.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [ + { + "id": "Q1", + "path": "PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean" + }, + { + "id": "Q2", + "path": "PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean" + }, + { + "id": "Q3", + "path": "PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean" + }, + { + "id": "M1", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean" + }, + { + "id": "M2", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean" + }, + { + "id": "M3", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean" + } + ], + "remaining_obligations": [ + "ANA-04: generic discounted/whole-law results are proved; a quotient-native original-margin application must still supply its actual bindings.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "No unqualified whole-path/Exact-GR transfer", + "test_method_candidates": [ + { + "method": "test_actual_kernel_witness_examples_and_axioms", + "path": "verification/research/test_final04_source_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_repeated_tiny_leaks_do_not_justify_exact_invariance", + "path": "verification/research/test_next04_projection_arithmetic.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_exact_canonical_discounted_values_for_both_original_starts", + "path": "verification/research/test_q05_nonidentity_source_fixture.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_baseline_only_witness_misses_treatment_reachable_state", + "path": "verification/research/test_q05_nonidentity_source_fixture.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_actual_typed_primitive_models_and_axioms", + "path": "verification/research/test_q05_actual_source_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_wrong_original_effect_cannot_compile", + "path": "verification/research/test_q05_actual_source_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_exact_original_margin_type_and_axiom_dependencies", + "path": "verification/research/test_q05_original_margin_adapter_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_unproved_source_domination_cannot_be_ignored", + "path": "verification/research/test_q05_original_margin_adapter_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_all_original_kernel_atoms_pointwitness_and_margin_compiler", + "path": "verification/research/test_q05_actual_atom_closure_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_actual_baseline_nonzero_atom_cannot_be_erased", + "path": "verification/research/test_q05_actual_atom_closure_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_final04_source_lean.py", + "verification/research/test_next04_original_margin_lean.py", + "verification/research/test_next04_projection_arithmetic.py", + "verification/research/test_q05_nonidentity_source_fixture.py", + "verification/research/test_q05_actual_source_lean.py", + "verification/research/test_q05_original_margin_adapter_lean.py", + "verification/research/test_q05_actual_atom_closure_lean.py" + ] + }, + { + "case_id": "A-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "A3-K01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "3bda14b9794e25d6eeb524657792ed8bb41612cd924a05d47a88d8794d3e1f80", + "name": "finite_w1DualLE_iff_couplingW1_le", + "path": "PermanssonResearch/Probability/FiniteKantorovichDuality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3bda14b9794e25d6eeb524657792ed8bb41612cd924a05d47a88d8794d3e1f80", + "name": "finite_exists_optimal_coupling", + "path": "PermanssonResearch/Probability/FiniteKantorovichDuality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3bda14b9794e25d6eeb524657792ed8bb41612cd924a05d47a88d8794d3e1f80", + "name": "finite_couplingW1_eq_dual_supremum", + "path": "PermanssonResearch/Probability/FiniteKantorovichDuality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "0fe9b650f209f09d6cdf040d5f8cede580bd15ce3d4478bf4d8811b73593da0e", + "name": "firstMoment_w1DualLE_iff_couplingW1_le", + "path": "PermanssonResearch/Probability/FirstMomentKantorovichDuality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "0fe9b650f209f09d6cdf040d5f8cede580bd15ce3d4478bf4d8811b73593da0e", + "name": "firstMoment_couplingW1_eq_dual_supremum", + "path": "PermanssonResearch/Probability/FirstMomentKantorovichDuality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "eb9bd0038c13a693c739101bba149135e128d2451ef69daa099b82ef27543246", + "name": "cauchySeq_has_limit", + "path": "PermanssonResearch/Probability/FirstMomentCompleteness.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Finite dual equality checked; wrong supnorm test class rejected", + "test_method_candidates": [ + { + "method": "test_actual_metric_certificates_and_transitive_axioms", + "path": "verification/research/test_paper_metric_coupling_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_dual_equals_coupling_on_the_line", + "path": "verification/research/test_a3_contractive.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_A_01_unit_supnorm_is_not_correct_KR_test_class", + "path": "verification/research/test_pr113_semantic_structural_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_metric_coupling_lean.py", + "verification/research/test_a3_contractive.py", + "verification/research/test_pr113_semantic_structural_candidates.py" + ] + }, + { + "case_id": "A-02", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "A3-I01", + "A3-C01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "06a40a18f0823f8325ca168caf2086f1fa7213732185a4c861173fba3198c504", + "name": "preservesFirstMoment_of_drift", + "path": "PermanssonResearch/Perturbation/FirstMomentKernel.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "76f1e2a6e7f0aae305e216da3c6c8242d84bce46d2479a2debbae623d53cf971", + "name": "exists_unique_invariant", + "path": "PermanssonResearch/Perturbation/FirstMomentInvariant.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "76f1e2a6e7f0aae305e216da3c6c8242d84bce46d2479a2debbae623d53cf971", + "name": "actual_iterate_coupling_bound", + "path": "PermanssonResearch/Perturbation/FirstMomentInvariant.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "76f1e2a6e7f0aae305e216da3c6c8242d84bce46d2479a2debbae623d53cf971", + "name": "automatic_invariant_and_convergence", + "path": "PermanssonResearch/Perturbation/FirstMomentInvariant.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3f3fdf7c13486b23e657b41dfbf9c1cbbfeddb8b4c881216c81a7c6cb19c585b", + "name": "generatedPerturbation", + "path": "PermanssonResearch/Perturbation/GeneratedInvariantPerturbation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "1f3a48e3c8e57d1f5d14a9f4041db98d2a8335f2862b44d9f78ff61ce7823a85", + "name": "exists_unique_invariant", + "path": "PermanssonResearch/Perturbation/FiniteInvariantConstructor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "93bb9b180a2061ac2d9f3989fa9e11aeabd64ad94607e6309bbaec7ba40709b7", + "name": "isW1Contraction_of_test_images", + "path": "PermanssonResearch/Perturbation/DirectDualContraction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "93bb9b180a2061ac2d9f3989fa9e11aeabd64ad94607e6309bbaec7ba40709b7", + "name": "constantKernel_isW1Contraction", + "path": "PermanssonResearch/Perturbation/DirectDualContraction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4b72c5c639d6c2035f5c9fbbfde6464ed5fc63eedbd558c62f2941fe26b0be8f", + "name": "isW1Contraction_of_tv_rows", + "path": "PermanssonResearch/Perturbation/DobrushinContraction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4b72c5c639d6c2035f5c9fbbfde6464ed5fc63eedbd558c62f2941fe26b0be8f", + "name": "isW1Contraction_dobrushin", + "path": "PermanssonResearch/Perturbation/DobrushinContraction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4b72c5c639d6c2035f5c9fbbfde6464ed5fc63eedbd558c62f2941fe26b0be8f", + "name": "isW1Contraction_doeblin", + "path": "PermanssonResearch/Perturbation/DobrushinContraction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "23c924dc109ad8176b97588e6bfe1047054463a091d1f29da56131bee0763105", + "name": "StateMetricBounds.original_isW1Contraction", + "path": "PermanssonResearch/Perturbation/FiniteStateMetricContraction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "da71060fa87aebe1e69d6af12fc59a1f76af49f1bc6c179ac774fb392a2b486d", + "name": "MetricProtocolBounds.original_occupationMargin", + "path": "PermanssonResearch/Perturbation/FiniteMetricProtocol.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Missing strict contraction/uniqueness exposed", + "test_method_candidates": [ + { + "method": "test_source_bound_transitive_axioms", + "path": "verification/research/test_paper_invariant_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_no_contraction_no_bound", + "path": "verification/research/test_a3_contractive.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_A_02_identity_kernel_is_not_strictly_contractive", + "path": "verification/research/test_pr113_semantic_second_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_invariant_lean.py", + "verification/research/test_a3_contractive.py", + "verification/research/test_pr113_semantic_second_candidates.py" + ] + }, + { + "case_id": "A-03", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "A3-P01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "89f05d3181fe7ec42514bea73a168c6b844468adc7ae0add1ece922f92a6c4f3", + "name": "typedRow_measure", + "path": "PermanssonResearch/Perturbation/FiniteTypedRows.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "158e0682132b3000c156a726cc0a699ed11e8348740aeddde39a15e58f10dd69", + "name": "sequential_weighted_cost", + "path": "PermanssonResearch/Perturbation/FiniteSequentialCoupling.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "1305ca4e3ef1f805b9e8e9221c24267276486e46986f17712e180d27e6317b51", + "name": "PrimitiveMetricBounds.exists_weighted_coupling", + "path": "PermanssonResearch/Perturbation/FinitePrimitiveMetric.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "1305ca4e3ef1f805b9e8e9221c24267276486e46986f17712e180d27e6317b51", + "name": "productDist_le_weighted", + "path": "PermanssonResearch/Perturbation/FinitePrimitiveMetric.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "1305ca4e3ef1f805b9e8e9221c24267276486e46986f17712e180d27e6317b51", + "name": "PrimitiveMetricBounds.original_induced_bound", + "path": "PermanssonResearch/Perturbation/FinitePrimitiveMetric.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "23c924dc109ad8176b97588e6bfe1047054463a091d1f29da56131bee0763105", + "name": "StateMetricBounds.original_isW1Contraction", + "path": "PermanssonResearch/Perturbation/FiniteStateMetricContraction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "da71060fa87aebe1e69d6af12fc59a1f76af49f1bc6c179ac774fb392a2b486d", + "name": "MetricProtocolBounds.original_occupationMargin", + "path": "PermanssonResearch/Perturbation/FiniteMetricProtocol.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Bounded-diameter conversion not applied", + "test_method_candidates": [ + { + "method": "test_bounded_diameter_conversion", + "path": "verification/research/test_a3_contractive.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_unbounded_no_conversion", + "path": "verification/research/test_a3_contractive.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_A_03_unbounded_TV_does_not_bound_W1", + "path": "verification/research/test_pr113_semantic_boundary_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_a3_contractive.py", + "verification/research/test_pr113_semantic_boundary_candidates.py" + ] + }, + { + "case_id": "A-04", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "A3-F01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "fd986fabeef6dd171d9d7fcd3b8a916145c5c437254165fda607dea1df040cf2", + "name": "four_actual_factorizations", + "path": "PermanssonResearch/Perturbation/FiniteOccupationFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "fd986fabeef6dd171d9d7fcd3b8a916145c5c437254165fda607dea1df040cf2", + "name": "generated_occupationMargin_robust", + "path": "PermanssonResearch/Perturbation/FiniteOccupationFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5dc066a9625a9db254b9c7faf50cdae67bbb4fbe432a640182bffcace1610530", + "name": "four_actual_factorizations", + "path": "PermanssonResearch/Perturbation/RegenerativeOccupationFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5dc066a9625a9db254b9c7faf50cdae67bbb4fbe432a640182bffcace1610530", + "name": "generated_occupationMargin_robust", + "path": "PermanssonResearch/Perturbation/RegenerativeOccupationFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "1b667d393432bc8bb019648b9d6aa049d9d582dc0c0759e0388e93ded4de8b24", + "name": "canonical_descriptor_empirical_tendsto", + "path": "PermanssonResearch/MultipleLimit/RegenerativeFirstMomentOccupation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "765e23d74c41394a4722b581b315a08460a3d14cb5aaa115aad87a918ccaafb8", + "name": "canonical_distance_tail_uniform", + "path": "PermanssonResearch/MultipleLimit/RegenerativeDescriptorTails.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "61cc390b87458994719bb28b759b8367c8f8dc5decebaf1e8d75c48e658ae268", + "name": "four_actual_factorizations", + "path": "PermanssonResearch/Perturbation/RegenerativeFirstMomentFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "61cc390b87458994719bb28b759b8367c8f8dc5decebaf1e8d75c48e658ae268", + "name": "generated_occupationMargin_robust", + "path": "PermanssonResearch/Perturbation/RegenerativeFirstMomentFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Four-process wrapper unavailable, not silently three-process", + "test_method_candidates": [ + { + "method": "test_four_process_factorization_cannot_be_deleted", + "path": "verification/research/test_a3_contractive.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_source_bound_transitive_axioms", + "path": "verification/research/test_paper_strategy_economic_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_A_04_missing_fourth_output_changes_margin", + "path": "verification/research/test_pr113_semantic_factorization_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_a3_contractive.py", + "verification/research/test_paper_strategy_economic_lean.py", + "verification/research/test_pr113_semantic_factorization_candidates.py" + ] + }, + { + "case_id": "A-05", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "A3-F01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "fd986fabeef6dd171d9d7fcd3b8a916145c5c437254165fda607dea1df040cf2", + "name": "four_actual_factorizations", + "path": "PermanssonResearch/Perturbation/FiniteOccupationFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "fd986fabeef6dd171d9d7fcd3b8a916145c5c437254165fda607dea1df040cf2", + "name": "generated_occupationMargin_robust", + "path": "PermanssonResearch/Perturbation/FiniteOccupationFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5dc066a9625a9db254b9c7faf50cdae67bbb4fbe432a640182bffcace1610530", + "name": "four_actual_factorizations", + "path": "PermanssonResearch/Perturbation/RegenerativeOccupationFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5dc066a9625a9db254b9c7faf50cdae67bbb4fbe432a640182bffcace1610530", + "name": "generated_occupationMargin_robust", + "path": "PermanssonResearch/Perturbation/RegenerativeOccupationFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "1b667d393432bc8bb019648b9d6aa049d9d582dc0c0759e0388e93ded4de8b24", + "name": "canonical_descriptor_empirical_tendsto", + "path": "PermanssonResearch/MultipleLimit/RegenerativeFirstMomentOccupation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "765e23d74c41394a4722b581b315a08460a3d14cb5aaa115aad87a918ccaafb8", + "name": "canonical_distance_tail_uniform", + "path": "PermanssonResearch/MultipleLimit/RegenerativeDescriptorTails.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "61cc390b87458994719bb28b759b8367c8f8dc5decebaf1e8d75c48e658ae268", + "name": "four_actual_factorizations", + "path": "PermanssonResearch/Perturbation/RegenerativeFirstMomentFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "61cc390b87458994719bb28b759b8367c8f8dc5decebaf1e8d75c48e658ae268", + "name": "generated_occupationMargin_robust", + "path": "PermanssonResearch/Perturbation/RegenerativeFirstMomentFactorization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Property identity fails rather than being assumed", + "test_method_candidates": [ + { + "method": "test_a3_3_survival_flips", + "path": "verification/research/test_a3_contractive.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_four_process_factorization_cannot_be_deleted", + "path": "verification/research/test_a3_contractive.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_A_05_original_source_positive", + "path": "verification/research/test_final_closure_missing_original_positives.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_A_05_stationary_mass_is_not_forever_survival", + "path": "verification/research/test_pr113_semantic_factorization_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_a3_contractive.py", + "verification/research/test_final_closure_missing_original_positives.py", + "verification/research/test_pr113_semantic_factorization_candidates.py" + ] + }, + { + "case_id": "D-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "D2-R01", + "D2-P01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "d09eb3931ee11cb4919e6019901f37e9e9b4e918df637c69c7b959702e9e1273", + "name": "residenceScan_invariant", + "path": "PermanssonResearch/ReverseSolver/CompactResidenceMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "d09eb3931ee11cb4919e6019901f37e9e9b4e918df637c69c7b959702e9e1273", + "name": "compactResidence_event_eq", + "path": "PermanssonResearch/ReverseSolver/CompactResidenceMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "28a1b65bf2f2b9ea102670666d39f3c49ec647a9af08d1ea45a84927ad7d53ea", + "name": "behavioralResidence_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/MonitorEventOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "663f5209d992b9944a700a6699671b47fde6e11a023e48a17b01be6f658ee310", + "name": "monitor_history_probability_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/MonitorHistoryBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "1faaaeaac81a14e8929be767cf96db2932de3983bdd38c5b3181c471d744bf1d", + "name": "rollingResidence_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/RollingResidenceTable.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "d2ec7e842f5c4cd0d60e08d7a3e29a3854b2f23a79cd6213a4f5fc1b06b8a5b3", + "name": "BehavioralPolicy", + "path": "PermanssonResearch/ReverseSolver/BehavioralFinitePolicy.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "6ac56a22a602aae8d1d34a4ac1657edaa1ff7b37f3c3305c782b70ef152ec486", + "name": "traceMass_total_one", + "path": "PermanssonResearch/ReverseSolver/ChronologicalFiniteLaw.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3330041fe9bbab41d43c6457bdf6b4e4a3bda9d4614b4fcebad21271a9a26903", + "name": "monitor_value_dominates_behavioral_history", + "path": "PermanssonResearch/ReverseSolver/MonitorOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3330041fe9bbab41d43c6457bdf6b4e4a3bda9d4614b4fcebad21271a9a26903", + "name": "realizedPolicy", + "path": "PermanssonResearch/ReverseSolver/MonitorOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3330041fe9bbab41d43c6457bdf6b4e4a3bda9d4614b4fcebad21271a9a26903", + "name": "monitor_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/MonitorOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "2c844fe5b4883e40e1e99c5ba6d43ce93f01ae91f7ddd818c35a0098b896af77", + "name": "monitor_initialLaw_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/InitialLawMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Same literal-event value; deterministic augmented maximizer attains", + "test_method_candidates": [ + { + "method": "test_bellman_equals_enumerated_deterministic_policies", + "path": "verification/research/test_d2_residence.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_product_clock_and_history_values_agree", + "path": "verification/research/test_d2_residence.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_D_01_hitting_is_not_residence", + "path": "verification/research/test_pr113_semantic_second_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_d2_residence.py", + "verification/research/test_pr113_semantic_second_candidates.py" + ] + }, + { + "case_id": "D-02", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "D2-R01", + "D2-P02" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "d09eb3931ee11cb4919e6019901f37e9e9b4e918df637c69c7b959702e9e1273", + "name": "residenceScan_invariant", + "path": "PermanssonResearch/ReverseSolver/CompactResidenceMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "d09eb3931ee11cb4919e6019901f37e9e9b4e918df637c69c7b959702e9e1273", + "name": "compactResidence_event_eq", + "path": "PermanssonResearch/ReverseSolver/CompactResidenceMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "28a1b65bf2f2b9ea102670666d39f3c49ec647a9af08d1ea45a84927ad7d53ea", + "name": "behavioralResidence_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/MonitorEventOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "663f5209d992b9944a700a6699671b47fde6e11a023e48a17b01be6f658ee310", + "name": "monitor_history_probability_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/MonitorHistoryBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "1faaaeaac81a14e8929be767cf96db2932de3983bdd38c5b3181c471d744bf1d", + "name": "rollingResidence_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/RollingResidenceTable.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "dc4645ecbd138060b924796b63c431b25fcf34ed1f1e424cc8cf9163df93567c", + "name": "firstEntryTest_iff_block", + "path": "PermanssonResearch/ReverseSolver/FirstEntryMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "dc4645ecbd138060b924796b63c431b25fcf34ed1f1e424cc8cf9163df93567c", + "name": "firstRun_correct", + "path": "PermanssonResearch/ReverseSolver/FirstEntryMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "dc4645ecbd138060b924796b63c431b25fcf34ed1f1e424cc8cf9163df93567c", + "name": "firstEntry_event_eq", + "path": "PermanssonResearch/ReverseSolver/FirstEntryMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "dc4645ecbd138060b924796b63c431b25fcf34ed1f1e424cc8cf9163df93567c", + "name": "first_entry_retry_counterexample", + "path": "PermanssonResearch/ReverseSolver/FirstEntryMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "fe13e9b08f490fd6d6fbb930e0a27b42010f93b388401bd0c9526c562f08d0ae", + "name": "behavioralFirstEntry_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/ResidenceSpecializationOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Exact event classification including word 1,0,1,1", + "test_method_candidates": [ + { + "method": "test_retry_is_distinct_from_first_ever_entry", + "path": "verification/research/test_d2_residence.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_all_short_physical_words", + "path": "verification/research/test_monitor_stationary_closures.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_D_02_false_retryable_residence_word", + "path": "verification/research/test_final_original_chronological_false_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_d2_residence.py", + "verification/research/test_monitor_stationary_closures.py", + "verification/research/test_final_original_chronological_false_lean.py" + ] + }, + { + "case_id": "D-03", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "D2-P03", + "D2-P04" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "2fe6ae7e4863ed165d1d348df6303cd4ae76eb6beeeb87f63e65ca6e8c9399e6", + "name": "avoidRun_safePrefix", + "path": "PermanssonResearch/ReverseSolver/AvoidResidenceMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "151530c963e47509e95a6f6f5abf4a040e12bc3befa73fe1f2664cf117b4987e", + "name": "beforeDanger_history_probability_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/AvoidResidenceHistoryBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "fe13e9b08f490fd6d6fbb930e0a27b42010f93b388401bd0c9526c562f08d0ae", + "name": "behavioralAvoidResidence_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/ResidenceSpecializationOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "756f60dfa2b48dedc5fc8363c8813b2c2417437391c99c3f3073dad32b400221", + "name": "initial_risk_iff_time", + "path": "PermanssonResearch/ReverseSolver/ThroughHorizonRiskMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "756f60dfa2b48dedc5fc8363c8813b2c2417437391c99c3f3073dad32b400221", + "name": "actual_risk_probability_eq_occupancy", + "path": "PermanssonResearch/ReverseSolver/ThroughHorizonRiskMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "756f60dfa2b48dedc5fc8363c8813b2c2417437391c99c3f3073dad32b400221", + "name": "actual_success_integral_eq_original", + "path": "PermanssonResearch/ReverseSolver/ThroughHorizonRiskMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "cca49d7b2c5cdd76953194017b6fcfccd5cbb5c63788b4979632f4f8ced2a974", + "name": "actual_history_flow_equivalence", + "path": "PermanssonResearch/ReverseSolver/FiniteOccupancyReconstruction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "cca49d7b2c5cdd76953194017b6fcfccd5cbb5c63788b4979632f4f8ced2a974", + "name": "actual_success_risk_range_eq_flow", + "path": "PermanssonResearch/ReverseSolver/FiniteOccupancyReconstruction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4985c7a94304b3f498c7a097107c162a43f381652fe411f2e3b73aeaa70ab5ec", + "name": "every_vertex_is_enumerated", + "path": "PermanssonResearch/ReverseSolver/PolytopeCompleteness.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4985c7a94304b3f498c7a097107c162a43f381652fe411f2e3b73aeaa70ab5ec", + "name": "exists_rational_optimal_candidate", + "path": "PermanssonResearch/ReverseSolver/PolytopeCompleteness.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "45c0b8b83ea91252fd1fe1b656d25a322551053871223ec7d8a635173d63f03b", + "name": "flowPolytope_bounded", + "path": "PermanssonResearch/ReverseSolver/FlowPolytope.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b443c8af7ee23a4f4a8dd837cb4481e42264186620d81b9ebc32ab44ef4997ea", + "name": "solveRiskFlow_none_iff_no_behavioral_policy", + "path": "PermanssonResearch/ReverseSolver/FlowPolicyOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b443c8af7ee23a4f4a8dd837cb4481e42264186620d81b9ebc32ab44ef4997ea", + "name": "solveRiskFlow_some_actual_optimal", + "path": "PermanssonResearch/ReverseSolver/FlowPolicyOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4b9a6ddaa60e29d97edbe0745058f1e8cdfb783e513d14d4609662d5969bfb1c", + "name": "actual_half_attains", + "path": "PermanssonResearch/ReverseSolver/FlowRiskWitness.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5674d9644d37fa6bbcb7fe23325a2a5c9876692fe1a17677fed32433b2360544", + "name": "dependent_equalities_singleton", + "path": "PermanssonResearch/ReverseSolver/PolytopeRegression.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5674d9644d37fa6bbcb7fe23325a2a5c9876692fe1a17677fed32433b2360544", + "name": "zero_dimensional_singleton", + "path": "PermanssonResearch/ReverseSolver/PolytopeRegression.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Declared precedence and separate risk monitor preserved", + "test_method_candidates": [ + { + "method": "test_retry_first_and_danger_boundaries", + "path": "verification/research/test_monitor_stationary_closures.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_actual_history_risk_flag_and_transitive_axioms", + "path": "verification/research/test_paper_through_risk_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_D_03_false_danger_after_success_revokes_it", + "path": "verification/research/test_final_original_chronological_false_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_monitor_stationary_closures.py", + "verification/research/test_paper_through_risk_lean.py", + "verification/research/test_final_original_chronological_false_lean.py" + ] + }, + { + "case_id": "D-04", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "D2-P04" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "cca49d7b2c5cdd76953194017b6fcfccd5cbb5c63788b4979632f4f8ced2a974", + "name": "actual_history_flow_equivalence", + "path": "PermanssonResearch/ReverseSolver/FiniteOccupancyReconstruction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "cca49d7b2c5cdd76953194017b6fcfccd5cbb5c63788b4979632f4f8ced2a974", + "name": "actual_success_risk_range_eq_flow", + "path": "PermanssonResearch/ReverseSolver/FiniteOccupancyReconstruction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4985c7a94304b3f498c7a097107c162a43f381652fe411f2e3b73aeaa70ab5ec", + "name": "every_vertex_is_enumerated", + "path": "PermanssonResearch/ReverseSolver/PolytopeCompleteness.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4985c7a94304b3f498c7a097107c162a43f381652fe411f2e3b73aeaa70ab5ec", + "name": "exists_rational_optimal_candidate", + "path": "PermanssonResearch/ReverseSolver/PolytopeCompleteness.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "45c0b8b83ea91252fd1fe1b656d25a322551053871223ec7d8a635173d63f03b", + "name": "flowPolytope_bounded", + "path": "PermanssonResearch/ReverseSolver/FlowPolytope.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b443c8af7ee23a4f4a8dd837cb4481e42264186620d81b9ebc32ab44ef4997ea", + "name": "solveRiskFlow_none_iff_no_behavioral_policy", + "path": "PermanssonResearch/ReverseSolver/FlowPolicyOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b443c8af7ee23a4f4a8dd837cb4481e42264186620d81b9ebc32ab44ef4997ea", + "name": "solveRiskFlow_some_actual_optimal", + "path": "PermanssonResearch/ReverseSolver/FlowPolicyOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4b9a6ddaa60e29d97edbe0745058f1e8cdfb783e513d14d4609662d5969bfb1c", + "name": "actual_half_attains", + "path": "PermanssonResearch/ReverseSolver/FlowRiskWitness.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5674d9644d37fa6bbcb7fe23325a2a5c9876692fe1a17677fed32433b2360544", + "name": "dependent_equalities_singleton", + "path": "PermanssonResearch/ReverseSolver/PolytopeRegression.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5674d9644d37fa6bbcb7fe23325a2a5c9876692fe1a17677fed32433b2360544", + "name": "zero_dimensional_singleton", + "path": "PermanssonResearch/ReverseSolver/PolytopeRegression.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Optimum17/20 atp1/2, unconstrained p1 infeasible", + "test_method_candidates": [ + { + "method": "test_risk_budget_changes_optimum", + "path": "verification/research/test_d2_residence.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_actual_solver_signatures_and_transitive_axioms", + "path": "verification/research/test_paper_risk_lp_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_D_04_false_hard_risk_budget_accepts_p_one", + "path": "verification/research/test_final_original_risk_belief_false_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_d2_residence.py", + "verification/research/test_paper_risk_lp_lean.py", + "verification/research/test_final_original_risk_belief_false_lean.py" + ] + }, + { + "case_id": "D-05", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "D2-P05" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "62f5b0771d14292f8271e97257bbae95b5ea884e112af845b14338f847f69e34", + "name": "Controller", + "path": "PermanssonResearch/ReverseSolver/RectangularFiniteGame.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "62f5b0771d14292f8271e97257bbae95b5ea884e112af845b14338f847f69e34", + "name": "Nature", + "path": "PermanssonResearch/ReverseSolver/RectangularFiniteGame.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "0d0940034bf0e05195c2726bc330b6094fcf88ac1d47060b873fff5ba5e95c61", + "name": "gameMass", + "path": "PermanssonResearch/ReverseSolver/RectangularGameLaw.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "0d0940034bf0e05195c2726bc330b6094fcf88ac1d47060b873fff5ba5e95c61", + "name": "rectangular_game_law_saddle", + "path": "PermanssonResearch/ReverseSolver/RectangularGameLaw.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5cc5fc7c353eaf568ecaddbaec36026444cfbef0168b21a5a7cd0874513ca6ab", + "name": "solvePrivateStage_returns", + "path": "PermanssonResearch/ReverseSolver/PrivateControlStageOptimizer.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5cc5fc7c353eaf568ecaddbaec36026444cfbef0168b21a5a7cd0874513ca6ab", + "name": "returned_dominates_all_real_mixtures", + "path": "PermanssonResearch/ReverseSolver/PrivateControlStageOptimizer.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5cc5fc7c353eaf568ecaddbaec36026444cfbef0168b21a5a7cd0874513ca6ab", + "name": "returned_actual_strategic_value", + "path": "PermanssonResearch/ReverseSolver/PrivateControlStageOptimizer.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "250b59afeb7650a833a775748fcb6c7ae546da8b33fa2981d581bd94067e0394", + "name": "timing_and_randomization_boundary", + "path": "PermanssonResearch/ReverseSolver/PrivateControlStageFixture.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "352703922717456d3fb42563b1fdf34bc3ba76699f20e51ab1ea31fc1100c450", + "name": "whole_model_commitment_strictly_exceeds_rectangular", + "path": "PermanssonResearch/ReverseSolver/SolverBoundaryWitnesses.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "SR-A04: 9/100 versus1/100 boundary is scalar arithmetic rather than an instantiated committed-model path-law fixture.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Committed value.09 differs from rectangular.01", + "test_method_candidates": [ + { + "method": "test_rectangular_reselection_is_a_different_path_law", + "path": "verification/research/test_next06_committed_path_law.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_committed_model_identity_persists_between_stages", + "path": "verification/research/test_next06_committed_path_law.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_D_05_false_committed_rectangular_path_law_equality", + "path": "verification/research/test_final_original_chronological_false_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_next06_committed_path_law.py", + "verification/research/test_final_original_chronological_false_lean.py" + ] + }, + { + "case_id": "D-06", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "D2-P06" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "3620313d0d0d7f8cd7b50b29081a88e3f56db0188bad8dfd5033c4971da14830", + "name": "ObservationPolicy", + "path": "PermanssonResearch/ReverseSolver/BeliefHistory.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "996f4cc294dae5807494cbbbb733ae246f4e79dcdba4febc8b1048e21a8cd946", + "name": "posterior_cancel", + "path": "PermanssonResearch/ReverseSolver/BeliefKernel.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "cc5ab3d5ed778995dfd610b3d2f5b94225e0569747f6d4b79d3606ab5d558d95", + "name": "actual_history_conditional", + "path": "PermanssonResearch/ReverseSolver/BeliefChronology.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "06cebf53aba94f9958a40dbbd8166c8172c4b6ac02831fba3de31072c3d7e469", + "name": "alpha_envelope", + "path": "PermanssonResearch/ReverseSolver/BeliefAlpha.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "06cebf53aba94f9958a40dbbd8166c8172c4b6ac02831fba3de31072c3d7e469", + "name": "value_convex", + "path": "PermanssonResearch/ReverseSolver/BeliefAlpha.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "25a111ad8566c6dda4967c165279d9d1949808f079908114f891baa06acf3eb1", + "name": "monitored_history_dominated", + "path": "PermanssonResearch/ReverseSolver/BeliefMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "25a111ad8566c6dda4967c165279d9d1949808f079908114f891baa06acf3eb1", + "name": "monitored_history_attained", + "path": "PermanssonResearch/ReverseSolver/BeliefMonitor.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "SR-A05: hidden-flag fixture uses different pure priors, not two reachable histories from one fixed initial law.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Full augmented belief required", + "test_method_candidates": [ + { + "method": "test_both_histories_have_mass_under_the_same_law", + "path": "verification/research/test_next06_reachable_completion_belief.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_same_physical_readout_does_not_identify_completion", + "path": "verification/research/test_next06_reachable_completion_belief.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_D_06_false_same_readout_identifies_completion", + "path": "verification/research/test_final_original_risk_belief_false_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_next06_reachable_completion_belief.py", + "verification/research/test_final_original_risk_belief_false_lean.py" + ] + }, + { + "case_id": "D-07", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "D2-P07" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "360bc29d1dcb49943a52def32845dd72c4cbb15ac9ebba69ee15de8836b33950", + "name": "pathLaw", + "path": "PermanssonResearch/ReverseSolver/BehavioralInfiniteLaw.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "6a0b4617f41c27786a93c303311406e9ffa7d7653224b2e6b6a84855b4d34611", + "name": "pathLaw_ae_chronological", + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyPathSemantics.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "6a0b4617f41c27786a93c303311406e9ffa7d7653224b2e6b6a84855b4d34611", + "name": "pathLaw_safeThrough", + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyPathSemantics.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b0b4b4e22d3b053f0d674adcb518fe0152ebe148401547a334b34ff51cff9b1b", + "name": "limitValue_fixed_point", + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyBellman.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "91e70713c92a44023e08397aca59b9d9e035213fcad6f977c1f6995e7311e8a7", + "name": "limitValue_le_stationary_survival", + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyFiniteEvaluation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c399aaab95f64c3a3d33d8f672815bd35c04ab96f99d35abe7984eaff2c75017", + "name": "deterministic_stationary_infinite_optimum", + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "3b54f57a995bef2947b226b4cc5cf35e71043a6183822dda212af749ef3df8ef", + "name": "invariantIterates_stable", + "path": "PermanssonResearch/ReverseSolver/GreatestControlledInvariant.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c399aaab95f64c3a3d33d8f672815bd35c04ab96f99d35abe7984eaff2c75017", + "name": "invariantCore_iff_almostSureSafety", + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyOptimality.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "4e4c34c224270881d20b904ca5177bbd811f37950d4f5184fbd6b191eba88785", + "name": "high_actual_finite_survival_zero_infinite", + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyLeakage.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Infinite optimum determined by limit theorem, not one finite bound", + "test_method_candidates": [ + { + "method": "test_actual_infinite_safety_audit", + "path": "verification/research/test_paper_infinite_safety_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_deterministic_robust_value_zero", + "path": "verification/research/test_d2_residence.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_D_07_finite_survival_does_not_imply_infinite_safety", + "path": "verification/research/test_pr113_semantic_boundary_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_infinite_safety_lean.py", + "verification/research/test_d2_residence.py", + "verification/research/test_pr113_semantic_boundary_candidates.py" + ] + }, + { + "case_id": "D-08", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "D2-P08" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "d16f5693027488555038686aabfd1c548e6dbe8665e8f19768a33eb60a403e5c", + "name": "CertifiedMenu", + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "d16f5693027488555038686aabfd1c548e6dbe8665e8f19768a33eb60a403e5c", + "name": "block_preserves_world", + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "d16f5693027488555038686aabfd1c548e6dbe8665e8f19768a33eb60a403e5c", + "name": "typedRow_eq", + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "d16f5693027488555038686aabfd1c548e6dbe8665e8f19768a33eb60a403e5c", + "name": "typedChronologicalLaw_eq", + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "d16f5693027488555038686aabfd1c548e6dbe8665e8f19768a33eb60a403e5c", + "name": "adaptive_block_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [ + { + "id": "D8", + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean" + } + ], + "remaining_obligations": [ + "SR-A02: concrete typed compatible-overlap and joint-only certified block fixture missing.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "No admissible typed controlled row", + "test_method_candidates": [ + { + "method": "test_false_frozen_joint_admission_is_semantic_mutant", + "path": "verification/research/test_final_d08_admission.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_actual_original_margin_control_and_false_mutant", + "path": "verification/research/test_next06_d08_joint_only_compiler.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_final_d08_admission.py", + "verification/research/test_next06_d08_joint_only_compiler.py" + ] + }, + { + "case_id": "D-09", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "D2-P09" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "4278ca143423a5d6e7ed6d9fb5c339e1c8b9cbb2a93a60a8a8f934aa443179cc", + "name": "observedOccupationLimit_ae_correct", + "path": "PermanssonResearch/MultipleLimit/FiniteOccupationRandomLaw.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b06460b24795cd14ccd4d4ef6ffa5989d77b5c1245efb16da18d0c22c2bac156", + "name": "ExactClassFunctional", + "path": "PermanssonResearch/ReverseSolver/StationaryOccupationObjective.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b06460b24795cd14ccd4d4ef6ffa5989d77b5c1245efb16da18d0c22c2bac156", + "name": "occupation_functional_integrable", + "path": "PermanssonResearch/ReverseSolver/StationaryOccupationObjective.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b06460b24795cd14ccd4d4ef6ffa5989d77b5c1245efb16da18d0c22c2bac156", + "name": "rationalObjective_eq_actual", + "path": "PermanssonResearch/ReverseSolver/StationaryOccupationObjective.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b06460b24795cd14ccd4d4ef6ffa5989d77b5c1245efb16da18d0c22c2bac156", + "name": "equal_descriptor_laws_equal_values", + "path": "PermanssonResearch/ReverseSolver/StationaryOccupationObjective.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "839dd7bc58a7b3bfe5b0a2145069e1947fbcfbbeb697d147b230373c69b25bb6", + "name": "computedStationaryPolicy", + "path": "PermanssonResearch/ReverseSolver/StationaryMenuOptimization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "839dd7bc58a7b3bfe5b0a2145069e1947fbcfbbeb697d147b230373c69b25bb6", + "name": "stationary_menu_actual_optimal_and_attained", + "path": "PermanssonResearch/ReverseSolver/StationaryMenuOptimization.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [ + { + "id": "N1", + "path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean" + }, + { + "id": "N2", + "path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean" + }, + { + "id": "N3", + "path": "PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean" + }, + { + "id": "D9", + "path": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean" + } + ], + "remaining_obligations": [ + "SR-A01: concrete ExactClassFunctional and two-state5/6-versus1 regression missing.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Class identity retained; descriptor score aggregates correctly", + "test_method_candidates": [ + { + "method": "test_exact_expected_score_not_score_of_expected_law", + "path": "verification/research/test_final_d09_multichain.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_exact_policy_scores_not_just_orbit_table", + "path": "verification/research/test_final06_phaseblind_original.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_compiled_distinct_physical_classes_and_nonlinear_lemma", + "path": "verification/research/test_final_d09_multichain.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_wrong_single_mixed_canonical_score_cannot_be_five_sixths", + "path": "verification/research/test_final_d09_multichain.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_final_d09_multichain.py", + "verification/research/test_final06_phaseblind_original.py" + ] + }, + { + "case_id": "C11-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "C11-P01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "0f370198eca4d5bed88c0301a8e61b13fa4e9b2bf4bedc0ebff3ac8c9c04ea3d", + "name": "referenceMatrix_det_ne_zero", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceMatrix.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "0f370198eca4d5bed88c0301a8e61b13fa4e9b2bf4bedc0ebff3ac8c9c04ea3d", + "name": "referenceWeights", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceMatrix.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "256cf50633615318e6376545da8d0f997cc5e96175524199493ac4185eb151b6", + "name": "referenceWeights_probability", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceProbability.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e2d4a77f3ca3c10fbf4a9fdd930248a41e286c22cbc4d210375db5e08afe31f7", + "name": "referenceClassCandidate_conditions", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceClasses.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "a1fda222a29a0863803cbc5c7174d35a07858956a623b4bebbf56e391c172c15", + "name": "canonicalBottomClassList_nodup", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceStationaryFamily.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "a1fda222a29a0863803cbc5c7174d35a07858956a623b4bebbf56e391c172c15", + "name": "referenceStationaryFamily_check", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceStationaryFamily.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "7183a67015c400f787f995ce7035fc83600200946b60fce8afcedfcd6b8b9e73", + "name": "referenceClassCandidate_eq_native", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceTypedBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Valid nonempty families accepted; n0 rejected", + "test_method_candidates": [ + { + "method": "test_02_periodic_alternation", + "path": "verification/research/test_lane_c11_stationary.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_13_zero_and_resource_limits", + "path": "verification/research/test_lane_c11_stationary.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_C11_01_zero_dimensional_family_is_rejected", + "path": "verification/research/test_pr113_semantic_second_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_lane_c11_stationary.py", + "verification/research/test_pr113_semantic_second_candidates.py" + ] + }, + { + "case_id": "C11-02", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "C11-P01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "0f370198eca4d5bed88c0301a8e61b13fa4e9b2bf4bedc0ebff3ac8c9c04ea3d", + "name": "referenceMatrix_det_ne_zero", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceMatrix.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "0f370198eca4d5bed88c0301a8e61b13fa4e9b2bf4bedc0ebff3ac8c9c04ea3d", + "name": "referenceWeights", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceMatrix.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "256cf50633615318e6376545da8d0f997cc5e96175524199493ac4185eb151b6", + "name": "referenceWeights_probability", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceProbability.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e2d4a77f3ca3c10fbf4a9fdd930248a41e286c22cbc4d210375db5e08afe31f7", + "name": "referenceClassCandidate_conditions", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceClasses.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "a1fda222a29a0863803cbc5c7174d35a07858956a623b4bebbf56e391c172c15", + "name": "canonicalBottomClassList_nodup", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceStationaryFamily.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "a1fda222a29a0863803cbc5c7174d35a07858956a623b4bebbf56e391c172c15", + "name": "referenceStationaryFamily_check", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceStationaryFamily.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "7183a67015c400f787f995ce7035fc83600200946b60fce8afcedfcd6b8b9e73", + "name": "referenceClassCandidate_eq_native", + "path": "PermanssonResearch/ReverseSolver/C11ReferenceTypedBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Existing checker rejects complete faulty output", + "test_method_candidates": [ + { + "method": "test_07_forged_share_after_hash_repair", + "path": "verification/research/test_lane_c11_stationary.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_09_missing_extra_and_duplicate_classes", + "path": "verification/research/test_lane_c11_stationary.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_C11_02_original_source_positive", + "path": "verification/research/test_final_closure_missing_original_positives.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_C11_02_corrupted_stationary_report_is_rejected", + "path": "verification/research/test_pr113_semantic_second_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_lane_c11_stationary.py", + "verification/research/test_final_closure_missing_original_positives.py", + "verification/research/test_pr113_semantic_second_candidates.py" + ] + }, + { + "case_id": "C-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "C-P01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "d05ed52a0f3dc947dec318a08a71b2647bea3a9ab34eea0ffac91fc717243039", + "name": "empirical_clusters_invariant_ae", + "path": "PermanssonResearch/MultipleLimit/CompactFellerEmpirical.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "d05ed52a0f3dc947dec318a08a71b2647bea3a9ab34eea0ffac91fc717243039", + "name": "empirical_unique_invariant_ae", + "path": "PermanssonResearch/MultipleLimit/CompactFellerEmpirical.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b0c70c23acd3908ef5905668c45536f45ad199a4ff0c774ebc8374b5e40edb94", + "name": "empirical_invariant_distance_ae", + "path": "PermanssonResearch/MultipleLimit/CompactFellerDistance.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "d05ed52a0f3dc947dec318a08a71b2647bea3a9ab34eea0ffac91fc717243039", + "name": "original_occupation_of_invariant_descriptor", + "path": "PermanssonResearch/MultipleLimit/CompactFellerEmpirical.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "0b362e8dfe715b029ad680c522ec95e4013813a9238a14122e0ded9c60e92805", + "name": "conditionalKernel", + "path": "PermanssonResearch/MultipleLimit/StationaryConditionalLaw.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b20e017023a642ad42321a794b0ea0857eb74447a01329c5a71d3b1c0bf8f506", + "name": "randomLaw_invariant_ae", + "path": "PermanssonResearch/MultipleLimit/StationaryConditionalInvariant.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "7a09b4036160b17ef24e364e451951ec01d37e5e654896bb100da75d8e18910a", + "name": "exists_measurable_random_limit", + "path": "PermanssonResearch/MultipleLimit/PolishStationaryRandomOccupation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e2bbcc60547fffa5e2f47a6df53f5c4045fc8fb66797c34a84d205dddfa00f75", + "name": "pathLaw_eq_withDensity", + "path": "PermanssonResearch/MultipleLimit/CanonicalInitialDensity.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e2bbcc60547fffa5e2f47a6df53f5c4045fc8fb66797c34a84d205dddfa00f75", + "name": "pathLaw_rnDeriv", + "path": "PermanssonResearch/MultipleLimit/CanonicalInitialDensity.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "fc0c1985520f06219b9ab32d96f21decbf2150125e9df6cb1f99819a385b71ee", + "name": "canonical_clock_no_weak_limit", + "path": "PermanssonResearch/MultipleLimit/NonfiniteClock.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "a9d23da8a799fb9c00d36c569e043a500f8158010ed5f2ae0e14df40968cd15e", + "name": "canonical_growing_block_occupations_fail", + "path": "PermanssonResearch/MultipleLimit/GrowingBlockCounterexample.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "No unrestricted nonfinite occupation theorem", + "test_method_candidates": [ + { + "method": "test_source_bound_transitive_axioms", + "path": "verification/research/test_paper_nonfinite_counterexample_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_C_01_bounded_descriptor_occupation_nonconvergence", + "path": "verification/research/test_pr113_semantic_boundary_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_nonfinite_counterexample_lean.py", + "verification/research/test_pr113_semantic_boundary_candidates.py" + ] + }, + { + "case_id": "B-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "B-P01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "c90bc6bf020f01f2bd515f458b0fdcf8c48a5acaa608003fae2ac11401e0fde6", + "name": "RowPatch.Compatible", + "path": "PermanssonResearch/GrammarRobust/CompatibleOverlap.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c90bc6bf020f01f2bd515f458b0fdcf8c48a5acaa608003fae2ac11401e0fde6", + "name": "applyCompatiblePatches_active", + "path": "PermanssonResearch/GrammarRobust/CompatibleOverlap.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c90bc6bf020f01f2bd515f458b0fdcf8c48a5acaa608003fae2ac11401e0fde6", + "name": "applyCompatiblePatches_perm", + "path": "PermanssonResearch/GrammarRobust/CompatibleOverlap.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c90bc6bf020f01f2bd515f458b0fdcf8c48a5acaa608003fae2ac11401e0fde6", + "name": "applyRowPatch_idempotent", + "path": "PermanssonResearch/GrammarRobust/CompatibleOverlap.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "b0a2a95b83a945eae4e7fb7d37a9110351c44256feb377eac7e861c357138e9e", + "name": "overlapModel_pathLaw_eq_of_membership", + "path": "PermanssonResearch/GrammarRobust/OverlapKernelAssembly.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "5961213b9e2744b0aa2f9d6103db579a4e7e46beb56a9012f1c8c831c6857ebe", + "name": "overlapIntervention_margin_eq", + "path": "PermanssonResearch/GrammarRobust/OverlapCanonicalBridge.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "SR-A02: generic compatible overlap proved; concrete typed positive/negative overlap fixtures missing.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Compatibility rejected; order dependence exposed", + "test_method_candidates": [ + { + "method": "test_positive_and_false_compatibility_compile", + "path": "verification/research/test_next06_overlap_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_14_invalid_order_or_noncommuting_patch", + "path": "verification/research/test_lane_b2_sequential_effects.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_B_01_original_source_positive", + "path": "verification/research/test_final_closure_missing_original_positives.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_next06_overlap_lean.py", + "verification/research/test_lane_b2_sequential_effects.py", + "verification/research/test_final_closure_missing_original_positives.py" + ] + }, + { + "case_id": "B-02", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PENDING", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "B-P02" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "07d6495112d3761118a6510026c878497d773270e10aee538f6e6e61385cb3cb", + "name": "minimalPositive_iff_of_lowerSet_lifting", + "path": "PermanssonResearch/GrammarRobust/LowerSetTransport.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e5b2bf9f8d48f25e83ad701a5e6b4b23286a2b2b944ba76143ca2b9abc45f8bd", + "name": "Coarsening.original_margin_minimal_iff_of_lower_coverage", + "path": "PermanssonResearch/GrammarRobust/OriginalLowerSetTransport.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "07d6495112d3761118a6510026c878497d773270e10aee538f6e6e61385cb3cb", + "name": "exists_least_positive_of_finite_meet_closed", + "path": "PermanssonResearch/GrammarRobust/LowerSetTransport.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "SR-A03: non-global-isomorphism positive local-margin transport fixture missing.", + "R2-C01: preserved OR/AND labels are correct for ablation margins.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "No unique-minimum or monotonicity inference", + "test_method_candidates": [ + { + "method": "test_09_AND_incomparable_singleton_supports", + "path": "verification/research/test_lane_b2_sequential_effects.py", + "role": "EXACT_CHECK", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_10_XOR_destroys_upward_monotonicity", + "path": "verification/research/test_lane_b2_sequential_effects.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_actual_original_margin_minimality_and_axioms", + "path": "verification/research/test_b02_actual_original_margin_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_false_joint_minimality_is_rejected_semantically", + "path": "verification/research/test_b02_actual_original_margin_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_original_nonisomorphic_local_margin_compiles", + "path": "verification/research/test_b02_local_nonisomorphic_margin_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_inadmissible_original_singleton_false_claim_rejected", + "path": "verification/research/test_b02_local_nonisomorphic_margin_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_lane_b2_sequential_effects.py", + "verification/research/test_b02_actual_original_margin_lean.py", + "verification/research/test_b02_local_nonisomorphic_margin_lean.py" + ] + }, + { + "case_id": "XPLUS-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "X1-P01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "21de62ad386d164a658886b67fa163d5c6e13855430346d8d6d4ed928f1b4746", + "name": "original_exactGR", + "path": "PermanssonResearch/EconomicWitness/ExactRegime.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "21de62ad386d164a658886b67fa163d5c6e13855430346d8d6d4ed928f1b4746", + "name": "original_confirmatoryGR", + "path": "PermanssonResearch/EconomicWitness/ExactRegime.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "ac4bc0808102a469e3ba9dfb728b6eb1d9fe3e5cfa0243b36b1ac22194f7974a", + "name": "original_grounded_confirmatory_uniformPR", + "path": "PermanssonResearch/EconomicWitness/Constitution.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "6c40feb1df8fe41fb47c2d621a882e4532052e2a66bdc9fac0a140ace7a78117", + "name": "baseline_attainment", + "path": "PermanssonResearch/EconomicWitness/Incentives.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Correct source constructors and kappa1; no edit to old X1", + "test_method_candidates": [ + { + "method": "test_source_bound_transitive_axioms", + "path": "verification/research/test_paper_strategy_economic_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_leaking_region_not_exact_gr_and_persistence_decay", + "path": "verification/research/test_x1_commons.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_XPLUS_01_constructed_original_margin_is_not_zero", + "path": "verification/research/test_pr113_semantic_second_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_strategy_economic_lean.py", + "verification/research/test_x1_commons.py", + "verification/research/test_pr113_semantic_second_candidates.py" + ] + }, + { + "case_id": "E-01", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "APP-E01" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "e09f3027088f643b7599c4a2861c42300e8bcf325982a66e93563a9fdb3d4787", + "name": "seam_rank_le_two", + "path": "PermanssonResearch/Specialized/SeamReduction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e09f3027088f643b7599c4a2861c42300e8bcf325982a66e93563a9fdb3d4787", + "name": "seam_constant_diagonal", + "path": "PermanssonResearch/Specialized/SeamReduction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e09f3027088f643b7599c4a2861c42300e8bcf325982a66e93563a9fdb3d4787", + "name": "constitutiveEffect_commonMode", + "path": "PermanssonResearch/Specialized/SeamReduction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "e09f3027088f643b7599c4a2861c42300e8bcf325982a66e93563a9fdb3d4787", + "name": "constitutiveMargin_commonMode", + "path": "PermanssonResearch/Specialized/SeamReduction.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "Rank-one/scalar promotion rejected", + "test_method_candidates": [ + { + "method": "test_seam_invisibility_can_have_rank_two", + "path": "verification/research/test_paper_closure_boundaries.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_negative_statements_and_transitive_axioms", + "path": "verification/research/test_paper_specialized_boundary_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_E_01_seam_invisibility_does_not_force_rank_one", + "path": "verification/research/test_pr113_semantic_boundary_candidates.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_closure_boundaries.py", + "verification/research/test_paper_specialized_boundary_lean.py", + "verification/research/test_pr113_semantic_boundary_candidates.py" + ] + }, + { + "case_id": "E-02", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "APP-E02" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "43f7f1b7842ab92c38f6e220dedbe9b449f713bd156f1013112f69c091b49336", + "name": "quadratic_approximation_bound", + "path": "PermanssonResearch/Specialized/FiniteDetection.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "43f7f1b7842ab92c38f6e220dedbe9b449f713bd156f1013112f69c091b49336", + "name": "exists_legal_negative_of_approximation", + "path": "PermanssonResearch/Specialized/FiniteDetection.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "43f7f1b7842ab92c38f6e220dedbe9b449f713bd156f1013112f69c091b49336", + "name": "negative_persists_under_embedding", + "path": "PermanssonResearch/Specialized/FiniteDetection.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "43f7f1b7842ab92c38f6e220dedbe9b449f713bd156f1013112f69c091b49336", + "name": "actual_effect_pos_of_legal_approximation", + "path": "PermanssonResearch/Specialized/FiniteDetection.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "No persistent witness transfer", + "test_method_candidates": [ + { + "method": "test_exact_approximant_can_violate_legal_carrier", + "path": "verification/research/test_paper_closure_boundaries.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_embedding_without_form_preservation_loses_negative_witness", + "path": "verification/research/test_paper_closure_boundaries.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_E_02_original_source_positive", + "path": "verification/research/test_final_closure_missing_original_positives.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_E_02_false_legal_negative_witness_from_illegal_approximation", + "path": "verification/research/test_final_closure_existing_false_semantics_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_closure_boundaries.py", + "verification/research/test_final_closure_missing_original_positives.py", + "verification/research/test_final_closure_existing_false_semantics_lean.py" + ] + }, + { + "case_id": "E-03", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "APP-E03" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "91976bd49739c02dc057be780ed4bfcb9eef3d692123a3cbcd203c4a43a43e54", + "name": "negativeShift_isUnit", + "path": "PermanssonResearch/Specialized/ShellSchur.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "91976bd49739c02dc057be780ed4bfcb9eef3d692123a3cbcd203c4a43a43e54", + "name": "shell_determinant", + "path": "PermanssonResearch/Specialized/ShellSchur.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "91976bd49739c02dc057be780ed4bfcb9eef3d692123a3cbcd203c4a43a43e54", + "name": "shell_equations_iff", + "path": "PermanssonResearch/Specialized/ShellSchur.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "c595d334a9049c85f404f5657d0e21eafee46b57b90879a6596cc40d5e980123", + "name": "scalarShellSecular_derivative_negative", + "path": "PermanssonResearch/Specialized/ShellResolvent.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "No scalar shell reduction or unjustified inverse", + "test_method_candidates": [ + { + "method": "test_two_dimensional_shell_needs_second_equation", + "path": "verification/research/test_paper_closure_boundaries.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_zero_shift_does_not_invert_psd_singular_core", + "path": "verification/research/test_paper_closure_boundaries.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_E_03_original_source_positive", + "path": "verification/research/test_final_closure_missing_original_positives.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_E_03_false_two_dimensional_shell_collapse", + "path": "verification/research/test_final_closure_existing_false_semantics_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_closure_boundaries.py", + "verification/research/test_final_closure_missing_original_positives.py", + "verification/research/test_final_closure_existing_false_semantics_lean.py" + ] + }, + { + "case_id": "E-04", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "APP-E04" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "39e607f76735f7d01c65e29aed4ecebd7bca0857f96b5be08bdfb0e7faedb950", + "name": "symmetric_preimage_iff", + "path": "PermanssonResearch/Specialized/SingularCompatibility.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "39e607f76735f7d01c65e29aed4ecebd7bca0857f96b5be08bdfb0e7faedb950", + "name": "singular_block_nonnegative_iff", + "path": "PermanssonResearch/Specialized/SingularCompatibility.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "39e607f76735f7d01c65e29aed4ecebd7bca0857f96b5be08bdfb0e7faedb950", + "name": "incompatible_kernel_negative", + "path": "PermanssonResearch/Specialized/SingularCompatibility.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "39e607f76735f7d01c65e29aed4ecebd7bca0857f96b5be08bdfb0e7faedb950", + "name": "denominator_free_kernel_rigidity", + "path": "PermanssonResearch/Specialized/SingularCompatibility.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "39e607f76735f7d01c65e29aed4ecebd7bca0857f96b5be08bdfb0e7faedb950", + "name": "zero_shift_source_response", + "path": "PermanssonResearch/Specialized/SingularCompatibility.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "No pseudoinverse positivity certificate or cancellation", + "test_method_candidates": [ + { + "method": "test_singular_missing_preimage_produces_negative_vector", + "path": "verification/research/test_paper_closure_boundaries.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_source_exceptional_branch_does_not_force_vanishing", + "path": "verification/research/test_paper_closure_boundaries.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_E_04_original_source_positive", + "path": "verification/research/test_final_closure_missing_original_positives.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_E_04_false_zero_shift_inverse", + "path": "verification/research/test_final_closure_existing_false_semantics_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_closure_boundaries.py", + "verification/research/test_final_closure_missing_original_positives.py", + "verification/research/test_final_closure_existing_false_semantics_lean.py" + ] + }, + { + "case_id": "E-05", + "case_specific_test_status": "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW", + "current_candidate_decision": "PARTIAL_SCOPE", + "effective_accepted": false, + "evidence": { + "check_runs": [], + "compiler": [], + "negative": [], + "positive": [], + "review": [] + }, + "historical_contract_ids": [ + "APP-E05" + ], + "historical_proof_declarations": [ + { + "historical_source_sha256": "9c3704950798c4d9a542dcecad802883f3aa52743d58b23618e011386cdc9396", + "name": "norm_gramWitness_sq", + "path": "PermanssonResearch/Specialized/GramSeparation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "9c3704950798c4d9a542dcecad802883f3aa52743d58b23618e011386cdc9396", + "name": "gram_unit_rayleigh_value", + "path": "PermanssonResearch/Specialized/GramSeparation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "9c3704950798c4d9a542dcecad802883f3aa52743d58b23618e011386cdc9396", + "name": "gram_operator_perturbation_negative", + "path": "PermanssonResearch/Specialized/GramSeparation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + }, + { + "historical_source_sha256": "9c3704950798c4d9a542dcecad802883f3aa52743d58b23618e011386cdc9396", + "name": "constitutiveEffect_gram_witness", + "path": "PermanssonResearch/Specialized/GramSeparation.lean", + "reviewed_head_sha": null, + "source_sha256": null, + "statement_sha256": null + } + ], + "new_claim_source_candidates": [], + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "required_expected_result": "No unqualified negative full-form conclusion", + "test_method_candidates": [ + { + "method": "test_positive_component_can_mask_gram_negative_direction", + "path": "verification/research/test_paper_closure_boundaries.py", + "role": "SCOPE_BOUNDARY", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_negative_statements_and_transitive_axioms", + "path": "verification/research/test_paper_specialized_boundary_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_original_signed_negative_and_masked_total_compiler", + "path": "verification/research/test_e05_full_gram_masking_lean.py", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }, + { + "method": "test_false_unqualified_full_gram_negativity_rejected", + "path": "verification/research/test_e05_full_gram_masking_lean.py", + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + } + ], + "test_source_candidates": [ + "verification/research/test_paper_closure_boundaries.py", + "verification/research/test_paper_specialized_boundary_lean.py", + "verification/research/test_e05_full_gram_masking_lean.py" + ] + } + ], + "format": "Permansson.OriginalAcceptanceCaseSourceMap.v1", + "new_closure_owners": 11, + "original_atlas_contracts": 42, + "original_cases": 45, + "qualification": "All 45 original cases now have concrete discovered test-method candidates; these are a review work queue, not criterion-level acceptance or executed CI receipts. Source/mutation identity, premise relevance and independent review remain mandatory.", + "source_baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "status": "UNREVIEWED_CANDIDATE_ONLY" +} diff --git a/research/paper_closures/post_merge_112/acceptance_decisions.json b/research/paper_closures/post_merge_112/acceptance_decisions.json new file mode 100644 index 00000000..5c4dfb24 --- /dev/null +++ b/research/paper_closures/post_merge_112/acceptance_decisions.json @@ -0,0 +1,3407 @@ +{ + "format": "Permansson.FinalClosureAcceptance.v1", + "source_baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "history_source_sha": "3931e4dccafb47b3cdf70177c0cf337102c90731", + "historical_case_count": 45, + "final_source_sha": null, + "full45_accepted": false, + "scoped_release_accepted": false, + "status": "CANDIDATE_UNREVIEWED", + "cases": [ + { + "case_id": "X-01", + "historical_contract_ids": [ + "X1-N01" + ], + "required_expected_result": "Kernel-checked equality; changed world/update/action row rejected", + "implemented_claim": "The exact accepted two modes and baseline/action/update/joint variants; every pair of eighteen states.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/X1/InterventionNumericalBridge.lean", + "name": "protocol_atom_eq", + "statement_sha256": null, + "historical_source_sha256": "fbc4c91fcf7379f0c12b0112213e04b9e6d0a378179807b758dc3f2e4bb48b4c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMatrixTables.lean", + "name": "matrix_eq_entry", + "statement_sha256": null, + "historical_source_sha256": "e9e74f52381068e5894537b9c621c9d50bf0566a141f511f0a1e9f77401e120f", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMatrixTables.lean", + "name": "matrix_atom_eq", + "statement_sha256": null, + "historical_source_sha256": "e9e74f52381068e5894537b9c621c9d50bf0566a141f511f0a1e9f77401e120f", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "R2-A01: emitted-literal corruption is not stochasticity-preserving.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "X-02", + "historical_contract_ids": [ + "X1-N02" + ], + "required_expected_result": "Actual-kernel induction identifies each entry", + "implemented_claim": "All-horizon rational-to-actual terminal evaluation; checked literal tables at horizons zero through three.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/X1/InterventionNumericalBridge.lean", + "name": "kernelTerminal_eq_rational", + "statement_sha256": null, + "historical_source_sha256": "fbc4c91fcf7379f0c12b0112213e04b9e6d0a378179807b758dc3f2e4bb48b4c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "name": "table_zero", + "statement_sha256": null, + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "name": "table_step", + "statement_sha256": null, + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "name": "original_property_eq_table", + "statement_sha256": null, + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "X-03", + "historical_contract_ids": [ + "X1-N03" + ], + "required_expected_result": "Original constitutiveMargin equalities and exact attainers", + "implemented_claim": "Six original constitutiveMargin equalities on all nine starts with lower bounds and attaining starts.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "effects_eq_candidates", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "exactEffect_lower", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "exactEffect_attained", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "exactEffect_attainers", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "component_original_margin", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "X-04", + "historical_contract_ids": [ + "X1-N03" + ], + "required_expected_result": "Lean proof fails at arithmetic/domain obligation; parser failure alone insufficient", + "implemented_claim": "Six original constitutiveMargin equalities on all nine starts with lower bounds and attaining starts.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "effects_eq_candidates", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "exactEffect_lower", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "exactEffect_attained", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "exactEffect_attainers", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "component_original_margin", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "R2-A02: unique learner attainer removal is tested, not tied-equilibrium domain removal.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "X-05", + "historical_contract_ids": [ + "X1-N01", + "X1-N02" + ], + "required_expected_result": "Typed kernel equality fails", + "implemented_claim": "The exact accepted two modes and baseline/action/update/joint variants; every pair of eighteen states. | All-horizon rational-to-actual terminal evaluation; checked literal tables at horizons zero through three.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/X1/InterventionNumericalBridge.lean", + "name": "protocol_atom_eq", + "statement_sha256": null, + "historical_source_sha256": "fbc4c91fcf7379f0c12b0112213e04b9e6d0a378179807b758dc3f2e4bb48b4c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMatrixTables.lean", + "name": "matrix_eq_entry", + "statement_sha256": null, + "historical_source_sha256": "e9e74f52381068e5894537b9c621c9d50bf0566a141f511f0a1e9f77401e120f", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMatrixTables.lean", + "name": "matrix_atom_eq", + "statement_sha256": null, + "historical_source_sha256": "e9e74f52381068e5894537b9c621c9d50bf0566a141f511f0a1e9f77401e120f", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/InterventionNumericalBridge.lean", + "name": "kernelTerminal_eq_rational", + "statement_sha256": null, + "historical_source_sha256": "fbc4c91fcf7379f0c12b0112213e04b9e6d0a378179807b758dc3f2e4bb48b4c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "name": "table_zero", + "statement_sha256": null, + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "name": "table_step", + "statement_sha256": null, + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactTerminalTables.lean", + "name": "original_property_eq_table", + "statement_sha256": null, + "historical_source_sha256": "50fbb2b833c5191631f2ec69c61e7f83b7c137c4af9b352465b85af6ea9a57dd", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "X-06", + "historical_contract_ids": [ + "X1-N03" + ], + "required_expected_result": "Rejected; leakage theorem remains unchanged", + "implemented_claim": "Six original constitutiveMargin equalities on all nine starts with lower bounds and attaining starts.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "effects_eq_candidates", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "exactEffect_lower", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "exactEffect_attained", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "exactEffect_attainers", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/ExactMargins.lean", + "name": "component_original_margin", + "statement_sha256": null, + "historical_source_sha256": "3a29e5a94da11489b6a920795d76cbdb9316b64002aaf04e6cf2442c9105d061", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "S-01", + "historical_contract_ids": [ + "X1-S01" + ], + "required_expected_result": "Exact identity including terminal beta^N value", + "implemented_claim": "Bounded finite discounted stochastic games, arbitrary nonanticipating behavioral unilateral deviations, independent current opponent draw.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Games/DiscountedVerification.lean", + "name": "finite_performance_difference", + "statement_sha256": null, + "historical_source_sha256": "18c8867029bf65b63bc3f02ca5c84d8837983d1767137c600c918f51055528b7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Games/DiscountedVerification.lean", + "name": "payoff_tail_bound", + "statement_sha256": null, + "historical_source_sha256": "18c8867029bf65b63bc3f02ca5c84d8837983d1767137c600c918f51055528b7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Games/DiscountedTraceExpectation.lean", + "name": "payoff_eq_expected_path_sum", + "statement_sha256": null, + "historical_source_sha256": "6252760219c99be64055c17b091059eba64ca1d4197dc6182a8c2ddb3288da3b", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/FullStrategyCertificate.lean", + "name": "strategy_exact_advantage", + "statement_sha256": null, + "historical_source_sha256": "ee27ac8502de1ffec64aebf900eeb38d9f3560c46ddafeebb6615097654ef034", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/StrategyOriginalBridge.lean", + "name": "baseline_trace_stateLaw_eq_original", + "statement_sha256": null, + "historical_source_sha256": "675d4530df8d8bf8b697ee60ab7282e684c71afbe0f4d0e3dd0dcf299d49ffd7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/FullStrategyTrace.lean", + "name": "expected_exact_behavioral_performance_difference", + "statement_sha256": null, + "historical_source_sha256": "b0b4909441cfd5f54622d11d87e2bc54737a801917a5e5e29c36a73ef4021416", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "S-02", + "historical_contract_ids": [ + "X1-S01" + ], + "required_expected_result": "Outside admitted information class; no verification theorem", + "implemented_claim": "Bounded finite discounted stochastic games, arbitrary nonanticipating behavioral unilateral deviations, independent current opponent draw.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Games/DiscountedVerification.lean", + "name": "finite_performance_difference", + "statement_sha256": null, + "historical_source_sha256": "18c8867029bf65b63bc3f02ca5c84d8837983d1767137c600c918f51055528b7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Games/DiscountedVerification.lean", + "name": "payoff_tail_bound", + "statement_sha256": null, + "historical_source_sha256": "18c8867029bf65b63bc3f02ca5c84d8837983d1767137c600c918f51055528b7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Games/DiscountedTraceExpectation.lean", + "name": "payoff_eq_expected_path_sum", + "statement_sha256": null, + "historical_source_sha256": "6252760219c99be64055c17b091059eba64ca1d4197dc6182a8c2ddb3288da3b", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/FullStrategyCertificate.lean", + "name": "strategy_exact_advantage", + "statement_sha256": null, + "historical_source_sha256": "ee27ac8502de1ffec64aebf900eeb38d9f3560c46ddafeebb6615097654ef034", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/StrategyOriginalBridge.lean", + "name": "baseline_trace_stateLaw_eq_original", + "statement_sha256": null, + "historical_source_sha256": "675d4530df8d8bf8b697ee60ab7282e684c71afbe0f4d0e3dd0dcf299d49ffd7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/X1/FullStrategyTrace.lean", + "name": "expected_exact_behavioral_performance_difference", + "statement_sha256": null, + "historical_source_sha256": "b0b4909441cfd5f54622d11d87e2bc54737a801917a5e5e29c36a73ef4021416", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "M-01", + "historical_contract_ids": [ + "F1-MA2" + ], + "required_expected_result": "Full counterfactual path laws equal under stated premises", + "implemented_claim": "Finite autonomous strict anchors with full baseline-supported action/update agreement; separately standard-Borel all-time AE extension.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/MultiAgent/AutonomousMarkedKernel.lean", + "name": "autonomous_marked_atom", + "statement_sha256": null, + "historical_source_sha256": "0d101feb3a9d8e080d411560c8af2d8b0350e7e7ca1d91dc29fe92116cf84ab2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/AutonomousSupportClosure.lean", + "name": "supported_full_kernel_eq", + "statement_sha256": null, + "historical_source_sha256": "668d6db254b00da02fc4d89b53ab5f459203de2e361d10513b9fb1940bdc427d", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/StrictMA2Obstruction.lean", + "name": "strict_anchors_pathLaw_eq", + "statement_sha256": null, + "historical_source_sha256": "c294448f00c5c79d66a17684836616878258be7d2f7c8772ab7281b214c56c2f", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/AutonomousAEMechanisms.lean", + "name": "ae_anchors_pathLaw_eq", + "statement_sha256": null, + "historical_source_sha256": "4bbece652236607d405af7d8cc03c536e16e3da04952c7abc90684776233f298", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/AutonomousAEContinuumFixture.lean", + "name": "nonatomic_treated_pathLaw_eq", + "statement_sha256": null, + "historical_source_sha256": "c36d8c2651f11d8c045b5a48e3b2fa033fa6f906cc4f6fbeac9c7a3e000e45a2", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "M-02", + "historical_contract_ids": [ + "F1-MA2", + "F1-P05" + ], + "required_expected_result": "Autonomy premise rejected; reactive witness not counted strict", + "implemented_claim": "Finite autonomous strict anchors with full baseline-supported action/update agreement; separately standard-Borel all-time AE extension. | All-point-start/all-control projected or marked kernel autonomy iff the corresponding row factorization.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/MultiAgent/AutonomousMarkedKernel.lean", + "name": "autonomous_marked_atom", + "statement_sha256": null, + "historical_source_sha256": "0d101feb3a9d8e080d411560c8af2d8b0350e7e7ca1d91dc29fe92116cf84ab2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/AutonomousSupportClosure.lean", + "name": "supported_full_kernel_eq", + "statement_sha256": null, + "historical_source_sha256": "668d6db254b00da02fc4d89b53ab5f459203de2e361d10513b9fb1940bdc427d", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/StrictMA2Obstruction.lean", + "name": "strict_anchors_pathLaw_eq", + "statement_sha256": null, + "historical_source_sha256": "c294448f00c5c79d66a17684836616878258be7d2f7c8772ab7281b214c56c2f", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/AutonomousAEMechanisms.lean", + "name": "ae_anchors_pathLaw_eq", + "statement_sha256": null, + "historical_source_sha256": "4bbece652236607d405af7d8cc03c536e16e3da04952c7abc90684776233f298", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/AutonomousAEContinuumFixture.lean", + "name": "nonatomic_treated_pathLaw_eq", + "statement_sha256": null, + "historical_source_sha256": "c36d8c2651f11d8c045b5a48e3b2fa033fa6f906cc4f6fbeac9c7a3e000e45a2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/BehavioralAutonomy.lean", + "name": "row_iff_all_histories", + "statement_sha256": null, + "historical_source_sha256": "c7a58fbc86cebda84e3162a1cd09d229651fc4966121f891dd7cb76ca403aa48", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/BehavioralAutonomy.lean", + "name": "exists_row_iff_all_histories", + "statement_sha256": null, + "historical_source_sha256": "c7a58fbc86cebda84e3162a1cd09d229651fc4966121f891dd7cb76ca403aa48", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "R2-A04: marked-versus-unmarked action-coupling distinction has no dedicated tracked countermodel.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "F-01", + "historical_contract_ids": [ + "F1-P01", + "F1-P02" + ], + "required_expected_result": "P inputs reindexed correctly, no assumed symmetry", + "implemented_claim": "Player-two construction and coordinate reindexing with physical kernel inputs explicitly transported. | Finite-index conditional-product action/update kernels and selected-player protocols.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/MultiAgent/PlayerSwapTransport.lean", + "name": "generator", + "statement_sha256": null, + "historical_source_sha256": "c35c5318c9f3e0459b22f1d4f946071c88fc4af37c4a8bf766b5015b0ec4252f", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/PlayerSwapTransport.lean", + "name": "actual_pathLaw", + "statement_sha256": null, + "historical_source_sha256": "c35c5318c9f3e0459b22f1d4f946071c88fc4af37c4a8bf766b5015b0ec4252f", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/PlayerSwapConstitution.lean", + "name": "original_effect", + "statement_sha256": null, + "historical_source_sha256": "83845f1f26eb7c09c14e8cecdbd1bbf886219da403a610a1d7b4794731bbc8d6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/PlayerSwapConstitution.lean", + "name": "original_margin", + "statement_sha256": null, + "historical_source_sha256": "83845f1f26eb7c09c14e8cecdbd1bbf886219da403a610a1d7b4794731bbc8d6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/MeasurableFiniteProduct.lean", + "name": "actual_transition", + "statement_sha256": null, + "historical_source_sha256": "36babffa63878708fb8289bedf06b617032bbfd2cba48c71bfb60a96358a7c7a", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/MeasurableProductProtocols.lean", + "name": "original_margin_comparison", + "statement_sha256": null, + "historical_source_sha256": "9894c87e9c92af3b16cfd26b7776c51c45a7bbf29bebfa01a97c58756fcc7117", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/MeasurableProductProtocols.lean", + "name": "productKernel_two", + "statement_sha256": null, + "historical_source_sha256": "9894c87e9c92af3b16cfd26b7776c51c45a7bbf29bebfa01a97c58756fcc7117", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/FiniteProductTwo.lean", + "name": "actual_pathLaw", + "statement_sha256": null, + "historical_source_sha256": "f54d1c3a23714604298d38adc4518fbb1f9027941551a25a7ba3fa4f6b6ac6ec", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "R2-A03: asymmetric common/private-update swap fixture missing.", + "R2-I02: general measurable n=2 product identity is distinct from the finite full model/path recovery.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "F-02", + "historical_contract_ids": [ + "F1-P03" + ], + "required_expected_result": "Projection/exogeneity fails", + "implemented_claim": "A new typed common/private clock-augmented baseline with actual clock update and projection bridge.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/MultiAgent/MeasurableNatPrivateClock.lean", + "name": "advancing_update", + "statement_sha256": null, + "historical_source_sha256": "3b95ae06a9eb3a6500cd6df23c291bbb865343a9fcb6508d0eca288bf74afb2a", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/MeasurableNatPrivateClock.lean", + "name": "inherited_strictness", + "statement_sha256": null, + "historical_source_sha256": "3b95ae06a9eb3a6500cd6df23c291bbb865343a9fcb6508d0eca288bf74afb2a", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/MeasurableNatClockTransport.lean", + "name": "actual_row", + "statement_sha256": null, + "historical_source_sha256": "ad1b1eae9afeaca7ba334dd545b8d14864af9a5a8c67c796f5f487f90acbec42", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/MeasurableNatClockTransport.lean", + "name": "natural_projection", + "statement_sha256": null, + "historical_source_sha256": "ad1b1eae9afeaca7ba334dd545b8d14864af9a5a8c67c796f5f487f90acbec42", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/MeasurableNatClockFixture.lean", + "name": "stalled_clock_rejected", + "statement_sha256": null, + "historical_source_sha256": "c793a3886ecc064e455b951c17c55d82699ef371ca4f2223dfaf6989a7a40bbe", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "F-03", + "historical_contract_ids": [ + "F1-P04" + ], + "required_expected_result": "No feasible strict probability anchor", + "implemented_claim": "Finite feasible menus; all-state strict feasibility iff support in each own-key fibre intersection.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/MultiAgent/FeasibleAnchors.lean", + "name": "keyLaw_feasible_iff", + "statement_sha256": null, + "historical_source_sha256": "3bd960cd530773a2efac8482ea326c43d004c9181cfa763aa5651f7536608e64", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/FeasibleAnchors.lean", + "name": "exists_feasible_strictAnchor_iff", + "statement_sha256": null, + "historical_source_sha256": "3bd960cd530773a2efac8482ea326c43d004c9181cfa763aa5651f7536608e64", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/FeasibleAnchors.lean", + "name": "leastFibreAction_legal", + "statement_sha256": null, + "historical_source_sha256": "3bd960cd530773a2efac8482ea326c43d004c9181cfa763aa5651f7536608e64", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/FeasibleAnchors.lean", + "name": "leastFibreAction_le", + "statement_sha256": null, + "historical_source_sha256": "3bd960cd530773a2efac8482ea326c43d004c9181cfa763aa5651f7536608e64", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "F-04", + "historical_contract_ids": [ + "F1-P06" + ], + "required_expected_result": "Single-product claim fails; mediator mixture succeeds", + "implemented_claim": "Finite joint actions represented by an explicitly sampled mediator with declared information timing.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/MultiAgent/CorrelatedMediator.lean", + "name": "mediator_mixture_eq", + "statement_sha256": null, + "historical_source_sha256": "b547e44664bf1ab08482fde276b4448398d05ffe6be77b41da29b3c14739e777", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/CorrelatedMediator.lean", + "name": "mediatedAction", + "statement_sha256": null, + "historical_source_sha256": "b547e44664bf1ab08482fde276b4448398d05ffe6be77b41da29b3c14739e777", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/CorrelatedMediator.lean", + "name": "withMediatedActions", + "statement_sha256": null, + "historical_source_sha256": "b547e44664bf1ab08482fde276b4448398d05ffe6be77b41da29b3c14739e777", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "F-05", + "historical_contract_ids": [ + "F1-P07" + ], + "required_expected_result": "Mediation not identified without sufficient premises", + "implemented_claim": "Finite single-mediator SCM with conditional independent noises, treatment ignorability, consistency and positivity; explicit nonidentification example.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/MultiAgent/FiniteMediation.lean", + "name": "mediation_identification", + "statement_sha256": null, + "historical_source_sha256": "2404c03891bb8912e73239e9b902ba76b7f02b656a659f7db8efeada63fabe3a", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/FiniteMediation.lean", + "name": "mediation_identification_on_support", + "statement_sha256": null, + "historical_source_sha256": "2404c03891bb8912e73239e9b902ba76b7f02b656a659f7db8efeada63fabe3a", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/FiniteMediation.lean", + "name": "signed_effect_decomposition", + "statement_sha256": null, + "historical_source_sha256": "2404c03891bb8912e73239e9b902ba76b7f02b656a659f7db8efeada63fabe3a", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/FiniteMediation.lean", + "name": "two_SCMs_same_observational_law", + "statement_sha256": null, + "historical_source_sha256": "2404c03891bb8912e73239e9b902ba76b7f02b656a659f7db8efeada63fabe3a", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultiAgent/FiniteMediation.lean", + "name": "two_SCMs_positivity_failure", + "statement_sha256": null, + "historical_source_sha256": "2404c03891bb8912e73239e9b902ba76b7f02b656a659f7db8efeada63fabe3a", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "R2-A05: positive exact SCM test is in the full suite, but not this 48-check route; dedicated positive Lean instantiation is absent.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "Q-01", + "historical_contract_ids": [ + "E1-G01" + ], + "required_expected_result": "Geometric bound holds without claiming projection Markov", + "implemented_claim": "Finite spaces or measurable kernel common-mass and jointly measurable Radon-Nikodym lift profile; every fine row.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Probability/ProjectedCommonSubkernel.lean", + "name": "map_liftProjectedDensity", + "statement_sha256": null, + "historical_source_sha256": "07f20e818df7eb4362cad37d367eb9b63370f346c3c3721fd32b4544083d897c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricQuotient.lean", + "name": "projectedCommonKernel", + "statement_sha256": null, + "historical_source_sha256": "26f7d49df5bb7e083544a15a59d5392bad31aeeab5cab908e965a24a0b83bf4c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricPrefix.lean", + "name": "approxQuotient_prefix_tv_le_geometric_of_subkernel", + "statement_sha256": null, + "historical_source_sha256": "28c69bccf4667c2aca09fc1294d9efe471d35a6a8ba7c376c32a2ce01ed54cba", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricQuotient.lean", + "name": "approxQuotient_prefix_tv_le_geometric", + "statement_sha256": null, + "historical_source_sha256": "26f7d49df5bb7e083544a15a59d5392bad31aeeab5cab908e965a24a0b83bf4c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricMargin.lean", + "name": "geometricQuotient_margin_abs_le", + "statement_sha256": null, + "historical_source_sha256": "b2929544c41fe5af75bb07c53f9a28f1b3d2ccc366430ec48716d756678a03e2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/DependentHistoryCommonMass.lean", + "name": "prefix_tv_le_product", + "statement_sha256": null, + "historical_source_sha256": "471a561f008715d8794533fd84d50794a2b9f9cbbd078cc516ea5de8231706f9", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "Q-02", + "historical_contract_ids": [ + "E1-G01" + ], + "required_expected_result": "Correct boundaries; mismatched unbudgeted starts rejected", + "implemented_claim": "Finite spaces or measurable kernel common-mass and jointly measurable Radon-Nikodym lift profile; every fine row.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/Probability/ProjectedCommonSubkernel.lean", + "name": "map_liftProjectedDensity", + "statement_sha256": null, + "historical_source_sha256": "07f20e818df7eb4362cad37d367eb9b63370f346c3c3721fd32b4544083d897c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricQuotient.lean", + "name": "projectedCommonKernel", + "statement_sha256": null, + "historical_source_sha256": "26f7d49df5bb7e083544a15a59d5392bad31aeeab5cab908e965a24a0b83bf4c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricPrefix.lean", + "name": "approxQuotient_prefix_tv_le_geometric_of_subkernel", + "statement_sha256": null, + "historical_source_sha256": "28c69bccf4667c2aca09fc1294d9efe471d35a6a8ba7c376c32a2ce01ed54cba", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricQuotient.lean", + "name": "approxQuotient_prefix_tv_le_geometric", + "statement_sha256": null, + "historical_source_sha256": "26f7d49df5bb7e083544a15a59d5392bad31aeeab5cab908e965a24a0b83bf4c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricMargin.lean", + "name": "geometricQuotient_margin_abs_le", + "statement_sha256": null, + "historical_source_sha256": "b2929544c41fe5af75bb07c53f9a28f1b3d2ccc366430ec48716d756678a03e2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/DependentHistoryCommonMass.lean", + "name": "prefix_tv_le_product", + "statement_sha256": null, + "historical_source_sha256": "471a561f008715d8794533fd84d50794a2b9f9cbbd078cc516ea5de8231706f9", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "ANA-03: direct projected theorem uses matched starts/homogeneous kernels; expanded projected adapter remains open.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "Q-03", + "historical_contract_ids": [ + "A2-P01" + ], + "required_expected_result": "Product mass proved, no independence assumption", + "implemented_claim": "Actual ordered primitive composition with measurable common kernels at each stage and uniform stage errors.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/Perturbation/PrimitiveProductTV.lean", + "name": "primitiveCommon_mass_ge_product", + "statement_sha256": null, + "historical_source_sha256": "142d1a885638c3621ee14aacbd0e5375bff33806662b1226178422b456b60aee", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/PrimitiveProductTV.lean", + "name": "inducedKernel_tv_le_primitive_product", + "statement_sha256": null, + "historical_source_sha256": "142d1a885638c3621ee14aacbd0e5375bff33806662b1226178422b456b60aee", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/PrimitiveMetricBudget.lean", + "name": "inducedKernel_w1DualLE_of_primitive_product", + "statement_sha256": null, + "historical_source_sha256": "ce5dbcc322706ed5b4027b3138078c3ceb8b191d82bc4f5304672012ebf6fec4", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "ANA-01: routed executable tests the old additive primitive budget, not product saturation.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "Q-04", + "historical_contract_ids": [ + "A2-P02" + ], + "required_expected_result": "Intervention defect charged rather than omitted", + "implemented_claim": "Nonstationary history-kernel hybrid with initial TV and expected every-step row defects; separate baseline/intervention visit budgets.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/Probability/LocalizedHistoryTV.lean", + "name": "eventTV_compProd_averaged", + "statement_sha256": null, + "historical_source_sha256": "0d34452c4a2d571c5aa55960be13dd617b663bddaa76346c97ed778f22cf664c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/LocalizedHistoryTV.lean", + "name": "historyEvolution_tv_le_sum", + "statement_sha256": null, + "historical_source_sha256": "0d34452c4a2d571c5aa55960be13dd617b663bddaa76346c97ed778f22cf664c", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "ANA-02: selected localized visit/original-margin/reporting integration missing.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "Q-05", + "historical_contract_ids": [ + "E1-P01", + "E1-P02" + ], + "required_expected_result": "No unqualified whole-path/Exact-GR transfer", + "implemented_claim": "Summable weighted bounded finite-prefix scores; full path TV only with summable sequential error or a separately proved common-mass limit. | Original-margin transport with independently proved Exact-GR, grounding, comparison and convergence prerequisites on both models.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/Probability/SummablePathScores.lean", + "name": "integral_summablePathScore", + "statement_sha256": null, + "historical_source_sha256": "43dae187fef4703ef6f93a1086ed2f43b23f3e3d54be7b18236939c74d7f6baa", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/SummablePathScores.lean", + "name": "summable_output_score_error", + "statement_sha256": null, + "historical_source_sha256": "43dae187fef4703ef6f93a1086ed2f43b23f3e3d54be7b18236939c74d7f6baa", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/SummablePathScores.lean", + "name": "discounted_geometric_budget", + "statement_sha256": null, + "historical_source_sha256": "43dae187fef4703ef6f93a1086ed2f43b23f3e3d54be7b18236939c74d7f6baa", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/DiscountedInitialBudget.lean", + "name": "discounted_output_score_error_initial", + "statement_sha256": null, + "historical_source_sha256": "55c9c9e26ca8ddbd81595ca5b47f6d5f72610d996c55316398b45922f3d30af6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/DiscountedInitialBudget.lean", + "name": "zero_row_defect_retains_initial_budget", + "statement_sha256": null, + "historical_source_sha256": "55c9c9e26ca8ddbd81595ca5b47f6d5f72610d996c55316398b45922f3d30af6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/CylinderTotalVariation.lean", + "name": "path_eventTotalVariation_le_of_prefixes", + "statement_sha256": null, + "historical_source_sha256": "7711c2f3ad7ce9d05f655211b1045d2f355cd50a674c1aebc0caa6ee17839c5b", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/FullPathProductTV.lean", + "name": "path_tv_le_product", + "statement_sha256": null, + "historical_source_sha256": "f802694a3d08aa1230bfbad95261358f0c236137f34f2eeb99651d6babd7be3e", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricMargin.lean", + "name": "geometricQuotient_uniformPR_pair", + "statement_sha256": null, + "historical_source_sha256": "b2929544c41fe5af75bb07c53f9a28f1b3d2ccc366430ec48716d756678a03e2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricMargin.lean", + "name": "margin_positive_pair_of_budget", + "statement_sha256": null, + "historical_source_sha256": "b2929544c41fe5af75bb07c53f9a28f1b3d2ccc366430ec48716d756678a03e2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Quotient/GeometricMargin.lean", + "name": "grounded_uniformPR_pair_of_margin_budget", + "statement_sha256": null, + "historical_source_sha256": "b2929544c41fe5af75bb07c53f9a28f1b3d2ccc366430ec48716d756678a03e2", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "ANA-04: generic discounted/whole-law results are proved; a quotient-native original-margin application must still supply its actual bindings.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "A-01", + "historical_contract_ids": [ + "A3-K01" + ], + "required_expected_result": "Finite dual equality checked; wrong supnorm test class rejected", + "implemented_claim": "Finite metric probabilities; larger complete separable Borel space with finite first moments and explicit finite quantization proof.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Probability/FiniteKantorovichDuality.lean", + "name": "finite_w1DualLE_iff_couplingW1_le", + "statement_sha256": null, + "historical_source_sha256": "3bda14b9794e25d6eeb524657792ed8bb41612cd924a05d47a88d8794d3e1f80", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/FiniteKantorovichDuality.lean", + "name": "finite_exists_optimal_coupling", + "statement_sha256": null, + "historical_source_sha256": "3bda14b9794e25d6eeb524657792ed8bb41612cd924a05d47a88d8794d3e1f80", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/FiniteKantorovichDuality.lean", + "name": "finite_couplingW1_eq_dual_supremum", + "statement_sha256": null, + "historical_source_sha256": "3bda14b9794e25d6eeb524657792ed8bb41612cd924a05d47a88d8794d3e1f80", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/FirstMomentKantorovichDuality.lean", + "name": "firstMoment_w1DualLE_iff_couplingW1_le", + "statement_sha256": null, + "historical_source_sha256": "0fe9b650f209f09d6cdf040d5f8cede580bd15ce3d4478bf4d8811b73593da0e", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/FirstMomentKantorovichDuality.lean", + "name": "firstMoment_couplingW1_eq_dual_supremum", + "statement_sha256": null, + "historical_source_sha256": "0fe9b650f209f09d6cdf040d5f8cede580bd15ce3d4478bf4d8811b73593da0e", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Probability/FirstMomentCompleteness.lean", + "name": "cauchySeq_has_limit", + "statement_sha256": null, + "historical_source_sha256": "eb9bd0038c13a693c739101bba149135e128d2451ef69daa099b82ef27543246", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "A-02", + "historical_contract_ids": [ + "A3-I01", + "A3-C01" + ], + "required_expected_result": "Missing strict contraction/uniqueness exposed", + "implemented_claim": "Contraction preserving first-moment probabilities on a complete separable metric space; finite shortcut also specified. | Direct bounded-Lipschitz row criterion giving the exact existing global dual predicate; finite Dobrushin and metric row-coupling certificates.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Perturbation/FirstMomentKernel.lean", + "name": "preservesFirstMoment_of_drift", + "statement_sha256": null, + "historical_source_sha256": "06a40a18f0823f8325ca168caf2086f1fa7213732185a4c861173fba3198c504", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FirstMomentInvariant.lean", + "name": "exists_unique_invariant", + "statement_sha256": null, + "historical_source_sha256": "76f1e2a6e7f0aae305e216da3c6c8242d84bce46d2479a2debbae623d53cf971", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FirstMomentInvariant.lean", + "name": "actual_iterate_coupling_bound", + "statement_sha256": null, + "historical_source_sha256": "76f1e2a6e7f0aae305e216da3c6c8242d84bce46d2479a2debbae623d53cf971", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FirstMomentInvariant.lean", + "name": "automatic_invariant_and_convergence", + "statement_sha256": null, + "historical_source_sha256": "76f1e2a6e7f0aae305e216da3c6c8242d84bce46d2479a2debbae623d53cf971", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/GeneratedInvariantPerturbation.lean", + "name": "generatedPerturbation", + "statement_sha256": null, + "historical_source_sha256": "3f3fdf7c13486b23e657b41dfbf9c1cbbfeddb8b4c881216c81a7c6cb19c585b", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FiniteInvariantConstructor.lean", + "name": "exists_unique_invariant", + "statement_sha256": null, + "historical_source_sha256": "1f3a48e3c8e57d1f5d14a9f4041db98d2a8335f2862b44d9f78ff61ce7823a85", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/DirectDualContraction.lean", + "name": "isW1Contraction_of_test_images", + "statement_sha256": null, + "historical_source_sha256": "93bb9b180a2061ac2d9f3989fa9e11aeabd64ad94607e6309bbaec7ba40709b7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/DirectDualContraction.lean", + "name": "constantKernel_isW1Contraction", + "statement_sha256": null, + "historical_source_sha256": "93bb9b180a2061ac2d9f3989fa9e11aeabd64ad94607e6309bbaec7ba40709b7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/DobrushinContraction.lean", + "name": "isW1Contraction_of_tv_rows", + "statement_sha256": null, + "historical_source_sha256": "4b72c5c639d6c2035f5c9fbbfde6464ed5fc63eedbd558c62f2941fe26b0be8f", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/DobrushinContraction.lean", + "name": "isW1Contraction_dobrushin", + "statement_sha256": null, + "historical_source_sha256": "4b72c5c639d6c2035f5c9fbbfde6464ed5fc63eedbd558c62f2941fe26b0be8f", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/DobrushinContraction.lean", + "name": "isW1Contraction_doeblin", + "statement_sha256": null, + "historical_source_sha256": "4b72c5c639d6c2035f5c9fbbfde6464ed5fc63eedbd558c62f2941fe26b0be8f", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FiniteStateMetricContraction.lean", + "name": "StateMetricBounds.original_isW1Contraction", + "statement_sha256": null, + "historical_source_sha256": "23c924dc109ad8176b97588e6bfe1047054463a091d1f29da56131bee0763105", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FiniteMetricProtocol.lean", + "name": "MetricProtocolBounds.original_occupationMargin", + "statement_sha256": null, + "historical_source_sha256": "da71060fa87aebe1e69d6af12fc59a1f76af49f1bc6c179ac774fb392a2b486d", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "A-03", + "historical_contract_ids": [ + "A3-P01" + ], + "required_expected_result": "Bounded-diameter conversion not applied", + "implemented_claim": "Weighted product metric and conditional stage couplings with declared action/world/update errors and Lipschitz sensitivities.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Perturbation/FiniteTypedRows.lean", + "name": "typedRow_measure", + "statement_sha256": null, + "historical_source_sha256": "89f05d3181fe7ec42514bea73a168c6b844468adc7ae0add1ece922f92a6c4f3", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FiniteSequentialCoupling.lean", + "name": "sequential_weighted_cost", + "statement_sha256": null, + "historical_source_sha256": "158e0682132b3000c156a726cc0a699ed11e8348740aeddde39a15e58f10dd69", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FinitePrimitiveMetric.lean", + "name": "PrimitiveMetricBounds.exists_weighted_coupling", + "statement_sha256": null, + "historical_source_sha256": "1305ca4e3ef1f805b9e8e9221c24267276486e46986f17712e180d27e6317b51", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FinitePrimitiveMetric.lean", + "name": "productDist_le_weighted", + "statement_sha256": null, + "historical_source_sha256": "1305ca4e3ef1f805b9e8e9221c24267276486e46986f17712e180d27e6317b51", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FinitePrimitiveMetric.lean", + "name": "PrimitiveMetricBounds.original_induced_bound", + "statement_sha256": null, + "historical_source_sha256": "1305ca4e3ef1f805b9e8e9221c24267276486e46986f17712e180d27e6317b51", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FiniteStateMetricContraction.lean", + "name": "StateMetricBounds.original_isW1Contraction", + "statement_sha256": null, + "historical_source_sha256": "23c924dc109ad8176b97588e6bfe1047054463a091d1f29da56131bee0763105", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FiniteMetricProtocol.lean", + "name": "MetricProtocolBounds.original_occupationMargin", + "statement_sha256": null, + "historical_source_sha256": "da71060fa87aebe1e69d6af12fc59a1f76af49f1bc6c179ac774fb392a2b486d", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "A-04", + "historical_contract_ids": [ + "A3-F01" + ], + "required_expected_result": "Four-process wrapper unavailable, not silently three-process", + "implemented_claim": "Explicit occupation-limit functional, four actual finite unique-class kernels; separate regenerative Polish profile with added moment conditions.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Perturbation/FiniteOccupationFactorization.lean", + "name": "four_actual_factorizations", + "statement_sha256": null, + "historical_source_sha256": "fd986fabeef6dd171d9d7fcd3b8a916145c5c437254165fda607dea1df040cf2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FiniteOccupationFactorization.lean", + "name": "generated_occupationMargin_robust", + "statement_sha256": null, + "historical_source_sha256": "fd986fabeef6dd171d9d7fcd3b8a916145c5c437254165fda607dea1df040cf2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/RegenerativeOccupationFactorization.lean", + "name": "four_actual_factorizations", + "statement_sha256": null, + "historical_source_sha256": "5dc066a9625a9db254b9c7faf50cdae67bbb4fbe432a640182bffcace1610530", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/RegenerativeOccupationFactorization.lean", + "name": "generated_occupationMargin_robust", + "statement_sha256": null, + "historical_source_sha256": "5dc066a9625a9db254b9c7faf50cdae67bbb4fbe432a640182bffcace1610530", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/RegenerativeFirstMomentOccupation.lean", + "name": "canonical_descriptor_empirical_tendsto", + "statement_sha256": null, + "historical_source_sha256": "1b667d393432bc8bb019648b9d6aa049d9d582dc0c0759e0388e93ded4de8b24", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/RegenerativeDescriptorTails.lean", + "name": "canonical_distance_tail_uniform", + "statement_sha256": null, + "historical_source_sha256": "765e23d74c41394a4722b581b315a08460a3d14cb5aaa115aad87a918ccaafb8", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/RegenerativeFirstMomentFactorization.lean", + "name": "four_actual_factorizations", + "statement_sha256": null, + "historical_source_sha256": "61cc390b87458994719bb28b759b8367c8f8dc5decebaf1e8d75c48e658ae268", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/RegenerativeFirstMomentFactorization.lean", + "name": "generated_occupationMargin_robust", + "statement_sha256": null, + "historical_source_sha256": "61cc390b87458994719bb28b759b8367c8f8dc5decebaf1e8d75c48e658ae268", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "A-05", + "historical_contract_ids": [ + "A3-F01" + ], + "required_expected_result": "Property identity fails rather than being assumed", + "implemented_claim": "Explicit occupation-limit functional, four actual finite unique-class kernels; separate regenerative Polish profile with added moment conditions.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Perturbation/FiniteOccupationFactorization.lean", + "name": "four_actual_factorizations", + "statement_sha256": null, + "historical_source_sha256": "fd986fabeef6dd171d9d7fcd3b8a916145c5c437254165fda607dea1df040cf2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/FiniteOccupationFactorization.lean", + "name": "generated_occupationMargin_robust", + "statement_sha256": null, + "historical_source_sha256": "fd986fabeef6dd171d9d7fcd3b8a916145c5c437254165fda607dea1df040cf2", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/RegenerativeOccupationFactorization.lean", + "name": "four_actual_factorizations", + "statement_sha256": null, + "historical_source_sha256": "5dc066a9625a9db254b9c7faf50cdae67bbb4fbe432a640182bffcace1610530", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/RegenerativeOccupationFactorization.lean", + "name": "generated_occupationMargin_robust", + "statement_sha256": null, + "historical_source_sha256": "5dc066a9625a9db254b9c7faf50cdae67bbb4fbe432a640182bffcace1610530", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/RegenerativeFirstMomentOccupation.lean", + "name": "canonical_descriptor_empirical_tendsto", + "statement_sha256": null, + "historical_source_sha256": "1b667d393432bc8bb019648b9d6aa049d9d582dc0c0759e0388e93ded4de8b24", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/RegenerativeDescriptorTails.lean", + "name": "canonical_distance_tail_uniform", + "statement_sha256": null, + "historical_source_sha256": "765e23d74c41394a4722b581b315a08460a3d14cb5aaa115aad87a918ccaafb8", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/RegenerativeFirstMomentFactorization.lean", + "name": "four_actual_factorizations", + "statement_sha256": null, + "historical_source_sha256": "61cc390b87458994719bb28b759b8367c8f8dc5decebaf1e8d75c48e658ae268", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Perturbation/RegenerativeFirstMomentFactorization.lean", + "name": "generated_occupationMargin_robust", + "statement_sha256": null, + "historical_source_sha256": "61cc390b87458994719bb28b759b8367c8f8dc5decebaf1e8d75c48e658ae268", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "D-01", + "historical_contract_ids": [ + "D2-R01", + "D2-P01" + ], + "required_expected_result": "Same literal-event value; deterministic augmented maximizer attains", + "implemented_claim": "Finite fully observed known rational controlled process and literal retryable residence event. | Known finite fully observed expected terminal monitor reward, real behavioral probabilities, deterministic time-state maximizer.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/ReverseSolver/CompactResidenceMonitor.lean", + "name": "residenceScan_invariant", + "statement_sha256": null, + "historical_source_sha256": "d09eb3931ee11cb4919e6019901f37e9e9b4e918df637c69c7b959702e9e1273", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/CompactResidenceMonitor.lean", + "name": "compactResidence_event_eq", + "statement_sha256": null, + "historical_source_sha256": "d09eb3931ee11cb4919e6019901f37e9e9b4e918df637c69c7b959702e9e1273", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/MonitorEventOptimality.lean", + "name": "behavioralResidence_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "28a1b65bf2f2b9ea102670666d39f3c49ec647a9af08d1ea45a84927ad7d53ea", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/MonitorHistoryBridge.lean", + "name": "monitor_history_probability_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "663f5209d992b9944a700a6699671b47fde6e11a023e48a17b01be6f658ee310", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/RollingResidenceTable.lean", + "name": "rollingResidence_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "1faaaeaac81a14e8929be767cf96db2932de3983bdd38c5b3181c471d744bf1d", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/BehavioralFinitePolicy.lean", + "name": "BehavioralPolicy", + "statement_sha256": null, + "historical_source_sha256": "d2ec7e842f5c4cd0d60e08d7a3e29a3854b2f23a79cd6213a4f5fc1b06b8a5b3", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/ChronologicalFiniteLaw.lean", + "name": "traceMass_total_one", + "statement_sha256": null, + "historical_source_sha256": "6ac56a22a602aae8d1d34a4ac1657edaa1ff7b37f3c3305c782b70ef152ec486", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/MonitorOptimality.lean", + "name": "monitor_value_dominates_behavioral_history", + "statement_sha256": null, + "historical_source_sha256": "3330041fe9bbab41d43c6457bdf6b4e4a3bda9d4614b4fcebad21271a9a26903", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/MonitorOptimality.lean", + "name": "realizedPolicy", + "statement_sha256": null, + "historical_source_sha256": "3330041fe9bbab41d43c6457bdf6b4e4a3bda9d4614b4fcebad21271a9a26903", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/MonitorOptimality.lean", + "name": "monitor_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "3330041fe9bbab41d43c6457bdf6b4e4a3bda9d4614b4fcebad21271a9a26903", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/InitialLawMonitor.lean", + "name": "monitor_initialLaw_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "2c844fe5b4883e40e1e99c5ba6d43ce93f01ae91f7ddd818c35a0098b896af77", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "D-02", + "historical_contract_ids": [ + "D2-R01", + "D2-P02" + ], + "required_expected_result": "Exact event classification including word 1,0,1,1", + "implemented_claim": "Finite fully observed known rational controlled process and literal retryable residence event. | Literal first-ever-hit-and-stay event, fixed entry deadline, irreversible failure after interrupted first run.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/ReverseSolver/CompactResidenceMonitor.lean", + "name": "residenceScan_invariant", + "statement_sha256": null, + "historical_source_sha256": "d09eb3931ee11cb4919e6019901f37e9e9b4e918df637c69c7b959702e9e1273", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/CompactResidenceMonitor.lean", + "name": "compactResidence_event_eq", + "statement_sha256": null, + "historical_source_sha256": "d09eb3931ee11cb4919e6019901f37e9e9b4e918df637c69c7b959702e9e1273", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/MonitorEventOptimality.lean", + "name": "behavioralResidence_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "28a1b65bf2f2b9ea102670666d39f3c49ec647a9af08d1ea45a84927ad7d53ea", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/MonitorHistoryBridge.lean", + "name": "monitor_history_probability_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "663f5209d992b9944a700a6699671b47fde6e11a023e48a17b01be6f658ee310", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/RollingResidenceTable.lean", + "name": "rollingResidence_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "1faaaeaac81a14e8929be767cf96db2932de3983bdd38c5b3181c471d744bf1d", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FirstEntryMonitor.lean", + "name": "firstEntryTest_iff_block", + "statement_sha256": null, + "historical_source_sha256": "dc4645ecbd138060b924796b63c431b25fcf34ed1f1e424cc8cf9163df93567c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FirstEntryMonitor.lean", + "name": "firstRun_correct", + "statement_sha256": null, + "historical_source_sha256": "dc4645ecbd138060b924796b63c431b25fcf34ed1f1e424cc8cf9163df93567c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FirstEntryMonitor.lean", + "name": "firstEntry_event_eq", + "statement_sha256": null, + "historical_source_sha256": "dc4645ecbd138060b924796b63c431b25fcf34ed1f1e424cc8cf9163df93567c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FirstEntryMonitor.lean", + "name": "first_entry_retry_counterexample", + "statement_sha256": null, + "historical_source_sha256": "dc4645ecbd138060b924796b63c431b25fcf34ed1f1e424cc8cf9163df93567c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/ResidenceSpecializationOptimality.lean", + "name": "behavioralFirstEntry_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "fe13e9b08f490fd6d6fbb930e0a27b42010f93b388401bd0c9526c562f08d0ae", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "D-03", + "historical_contract_ids": [ + "D2-P03", + "D2-P04" + ], + "required_expected_result": "Declared precedence and separate risk monitor preserved", + "implemented_claim": "Finish a target run before any danger; danger precedence at ties; separate through-horizon risk monitor. | Finite rational augmented occupancy polytope, behavioral policy reconstruction, exact vertex optimum and optional weak-dual certificate.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/ReverseSolver/AvoidResidenceMonitor.lean", + "name": "avoidRun_safePrefix", + "statement_sha256": null, + "historical_source_sha256": "2fe6ae7e4863ed165d1d348df6303cd4ae76eb6beeeb87f63e65ca6e8c9399e6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/AvoidResidenceHistoryBridge.lean", + "name": "beforeDanger_history_probability_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "151530c963e47509e95a6f6f5abf4a040e12bc3befa73fe1f2664cf117b4987e", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/ResidenceSpecializationOptimality.lean", + "name": "behavioralAvoidResidence_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "fe13e9b08f490fd6d6fbb930e0a27b42010f93b388401bd0c9526c562f08d0ae", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/ThroughHorizonRiskMonitor.lean", + "name": "initial_risk_iff_time", + "statement_sha256": null, + "historical_source_sha256": "756f60dfa2b48dedc5fc8363c8813b2c2417437391c99c3f3073dad32b400221", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/ThroughHorizonRiskMonitor.lean", + "name": "actual_risk_probability_eq_occupancy", + "statement_sha256": null, + "historical_source_sha256": "756f60dfa2b48dedc5fc8363c8813b2c2417437391c99c3f3073dad32b400221", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/ThroughHorizonRiskMonitor.lean", + "name": "actual_success_integral_eq_original", + "statement_sha256": null, + "historical_source_sha256": "756f60dfa2b48dedc5fc8363c8813b2c2417437391c99c3f3073dad32b400221", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FiniteOccupancyReconstruction.lean", + "name": "actual_history_flow_equivalence", + "statement_sha256": null, + "historical_source_sha256": "cca49d7b2c5cdd76953194017b6fcfccd5cbb5c63788b4979632f4f8ced2a974", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FiniteOccupancyReconstruction.lean", + "name": "actual_success_risk_range_eq_flow", + "statement_sha256": null, + "historical_source_sha256": "cca49d7b2c5cdd76953194017b6fcfccd5cbb5c63788b4979632f4f8ced2a974", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PolytopeCompleteness.lean", + "name": "every_vertex_is_enumerated", + "statement_sha256": null, + "historical_source_sha256": "4985c7a94304b3f498c7a097107c162a43f381652fe411f2e3b73aeaa70ab5ec", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PolytopeCompleteness.lean", + "name": "exists_rational_optimal_candidate", + "statement_sha256": null, + "historical_source_sha256": "4985c7a94304b3f498c7a097107c162a43f381652fe411f2e3b73aeaa70ab5ec", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FlowPolytope.lean", + "name": "flowPolytope_bounded", + "statement_sha256": null, + "historical_source_sha256": "45c0b8b83ea91252fd1fe1b656d25a322551053871223ec7d8a635173d63f03b", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FlowPolicyOptimality.lean", + "name": "solveRiskFlow_none_iff_no_behavioral_policy", + "statement_sha256": null, + "historical_source_sha256": "b443c8af7ee23a4f4a8dd837cb4481e42264186620d81b9ebc32ab44ef4997ea", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FlowPolicyOptimality.lean", + "name": "solveRiskFlow_some_actual_optimal", + "statement_sha256": null, + "historical_source_sha256": "b443c8af7ee23a4f4a8dd837cb4481e42264186620d81b9ebc32ab44ef4997ea", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FlowRiskWitness.lean", + "name": "actual_half_attains", + "statement_sha256": null, + "historical_source_sha256": "4b9a6ddaa60e29d97edbe0745058f1e8cdfb783e513d14d4609662d5969bfb1c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PolytopeRegression.lean", + "name": "dependent_equalities_singleton", + "statement_sha256": null, + "historical_source_sha256": "5674d9644d37fa6bbcb7fe23325a2a5c9876692fe1a17677fed32433b2360544", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PolytopeRegression.lean", + "name": "zero_dimensional_singleton", + "statement_sha256": null, + "historical_source_sha256": "5674d9644d37fa6bbcb7fe23325a2a5c9876692fe1a17677fed32433b2360544", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "D-04", + "historical_contract_ids": [ + "D2-P04" + ], + "required_expected_result": "Optimum17/20 atp1/2, unconstrained p1 infeasible", + "implemented_claim": "Finite rational augmented occupancy polytope, behavioral policy reconstruction, exact vertex optimum and optional weak-dual certificate.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/ReverseSolver/FiniteOccupancyReconstruction.lean", + "name": "actual_history_flow_equivalence", + "statement_sha256": null, + "historical_source_sha256": "cca49d7b2c5cdd76953194017b6fcfccd5cbb5c63788b4979632f4f8ced2a974", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FiniteOccupancyReconstruction.lean", + "name": "actual_success_risk_range_eq_flow", + "statement_sha256": null, + "historical_source_sha256": "cca49d7b2c5cdd76953194017b6fcfccd5cbb5c63788b4979632f4f8ced2a974", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PolytopeCompleteness.lean", + "name": "every_vertex_is_enumerated", + "statement_sha256": null, + "historical_source_sha256": "4985c7a94304b3f498c7a097107c162a43f381652fe411f2e3b73aeaa70ab5ec", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PolytopeCompleteness.lean", + "name": "exists_rational_optimal_candidate", + "statement_sha256": null, + "historical_source_sha256": "4985c7a94304b3f498c7a097107c162a43f381652fe411f2e3b73aeaa70ab5ec", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FlowPolytope.lean", + "name": "flowPolytope_bounded", + "statement_sha256": null, + "historical_source_sha256": "45c0b8b83ea91252fd1fe1b656d25a322551053871223ec7d8a635173d63f03b", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FlowPolicyOptimality.lean", + "name": "solveRiskFlow_none_iff_no_behavioral_policy", + "statement_sha256": null, + "historical_source_sha256": "b443c8af7ee23a4f4a8dd837cb4481e42264186620d81b9ebc32ab44ef4997ea", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FlowPolicyOptimality.lean", + "name": "solveRiskFlow_some_actual_optimal", + "statement_sha256": null, + "historical_source_sha256": "b443c8af7ee23a4f4a8dd837cb4481e42264186620d81b9ebc32ab44ef4997ea", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/FlowRiskWitness.lean", + "name": "actual_half_attains", + "statement_sha256": null, + "historical_source_sha256": "4b9a6ddaa60e29d97edbe0745058f1e8cdfb783e513d14d4609662d5969bfb1c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PolytopeRegression.lean", + "name": "dependent_equalities_singleton", + "statement_sha256": null, + "historical_source_sha256": "5674d9644d37fa6bbcb7fe23325a2a5c9876692fe1a17677fed32433b2360544", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PolytopeRegression.lean", + "name": "zero_dimensional_singleton", + "statement_sha256": null, + "historical_source_sha256": "5674d9644d37fa6bbcb7fe23325a2a5c9876692fe1a17677fed32433b2360544", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "D-05", + "historical_contract_ids": [ + "D2-P05" + ], + "required_expected_result": "Committed value.09 differs from rectangular.01", + "implemented_claim": "Finite rectangular perfect-information nature-after-control game; separately private-control-draw stage maximin LP.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/ReverseSolver/RectangularFiniteGame.lean", + "name": "Controller", + "statement_sha256": null, + "historical_source_sha256": "62f5b0771d14292f8271e97257bbae95b5ea884e112af845b14338f847f69e34", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/RectangularFiniteGame.lean", + "name": "Nature", + "statement_sha256": null, + "historical_source_sha256": "62f5b0771d14292f8271e97257bbae95b5ea884e112af845b14338f847f69e34", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/RectangularGameLaw.lean", + "name": "gameMass", + "statement_sha256": null, + "historical_source_sha256": "0d0940034bf0e05195c2726bc330b6094fcf88ac1d47060b873fff5ba5e95c61", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/RectangularGameLaw.lean", + "name": "rectangular_game_law_saddle", + "statement_sha256": null, + "historical_source_sha256": "0d0940034bf0e05195c2726bc330b6094fcf88ac1d47060b873fff5ba5e95c61", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PrivateControlStageOptimizer.lean", + "name": "solvePrivateStage_returns", + "statement_sha256": null, + "historical_source_sha256": "5cc5fc7c353eaf568ecaddbaec36026444cfbef0168b21a5a7cd0874513ca6ab", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PrivateControlStageOptimizer.lean", + "name": "returned_dominates_all_real_mixtures", + "statement_sha256": null, + "historical_source_sha256": "5cc5fc7c353eaf568ecaddbaec36026444cfbef0168b21a5a7cd0874513ca6ab", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PrivateControlStageOptimizer.lean", + "name": "returned_actual_strategic_value", + "statement_sha256": null, + "historical_source_sha256": "5cc5fc7c353eaf568ecaddbaec36026444cfbef0168b21a5a7cd0874513ca6ab", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/PrivateControlStageFixture.lean", + "name": "timing_and_randomization_boundary", + "statement_sha256": null, + "historical_source_sha256": "250b59afeb7650a833a775748fcb6c7ae546da8b33fa2981d581bd94067e0394", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/SolverBoundaryWitnesses.lean", + "name": "whole_model_commitment_strictly_exceeds_rectangular", + "statement_sha256": null, + "historical_source_sha256": "352703922717456d3fb42563b1fdf34bc3ba76699f20e51ab1ea31fc1100c450", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "SR-A04: 9/100 versus1/100 boundary is scalar arithmetic rather than an instantiated committed-model path-law fixture.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "D-06", + "historical_contract_ids": [ + "D2-P06" + ], + "required_expected_result": "Full augmented belief required", + "implemented_claim": "Finite hidden augmented state including event monitors, common finite control menu, finite observations, alpha-vector optimality.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/ReverseSolver/BeliefHistory.lean", + "name": "ObservationPolicy", + "statement_sha256": null, + "historical_source_sha256": "3620313d0d0d7f8cd7b50b29081a88e3f56db0188bad8dfd5033c4971da14830", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/BeliefKernel.lean", + "name": "posterior_cancel", + "statement_sha256": null, + "historical_source_sha256": "996f4cc294dae5807494cbbbb733ae246f4e79dcdba4febc8b1048e21a8cd946", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/BeliefChronology.lean", + "name": "actual_history_conditional", + "statement_sha256": null, + "historical_source_sha256": "cc5ab3d5ed778995dfd610b3d2f5b94225e0569747f6d4b79d3606ab5d558d95", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/BeliefAlpha.lean", + "name": "alpha_envelope", + "statement_sha256": null, + "historical_source_sha256": "06cebf53aba94f9958a40dbbd8166c8172c4b6ac02831fba3de31072c3d7e469", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/BeliefAlpha.lean", + "name": "value_convex", + "statement_sha256": null, + "historical_source_sha256": "06cebf53aba94f9958a40dbbd8166c8172c4b6ac02831fba3de31072c3d7e469", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/BeliefMonitor.lean", + "name": "monitored_history_dominated", + "statement_sha256": null, + "historical_source_sha256": "25a111ad8566c6dda4967c165279d9d1949808f079908114f891baa06acf3eb1", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/BeliefMonitor.lean", + "name": "monitored_history_attained", + "statement_sha256": null, + "historical_source_sha256": "25a111ad8566c6dda4967c165279d9d1949808f079908114f891baa06acf3eb1", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "SR-A05: hidden-flag fixture uses different pure priors, not two reachable histories from one fixed initial law.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "D-07", + "historical_contract_ids": [ + "D2-P07" + ], + "required_expected_result": "Infinite optimum determined by limit theorem, not one finite bound", + "implemented_claim": "Finite controlled infinite-safety optimum and deterministic stationary attainment; greatest controlled invariant set.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/ReverseSolver/BehavioralInfiniteLaw.lean", + "name": "pathLaw", + "statement_sha256": null, + "historical_source_sha256": "360bc29d1dcb49943a52def32845dd72c4cbb15ac9ebba69ee15de8836b33950", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyPathSemantics.lean", + "name": "pathLaw_ae_chronological", + "statement_sha256": null, + "historical_source_sha256": "6a0b4617f41c27786a93c303311406e9ffa7d7653224b2e6b6a84855b4d34611", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyPathSemantics.lean", + "name": "pathLaw_safeThrough", + "statement_sha256": null, + "historical_source_sha256": "6a0b4617f41c27786a93c303311406e9ffa7d7653224b2e6b6a84855b4d34611", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyBellman.lean", + "name": "limitValue_fixed_point", + "statement_sha256": null, + "historical_source_sha256": "b0b4b4e22d3b053f0d674adcb518fe0152ebe148401547a334b34ff51cff9b1b", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyFiniteEvaluation.lean", + "name": "limitValue_le_stationary_survival", + "statement_sha256": null, + "historical_source_sha256": "91e70713c92a44023e08397aca59b9d9e035213fcad6f977c1f6995e7311e8a7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyOptimality.lean", + "name": "deterministic_stationary_infinite_optimum", + "statement_sha256": null, + "historical_source_sha256": "c399aaab95f64c3a3d33d8f672815bd35c04ab96f99d35abe7984eaff2c75017", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/GreatestControlledInvariant.lean", + "name": "invariantIterates_stable", + "statement_sha256": null, + "historical_source_sha256": "3b54f57a995bef2947b226b4cc5cf35e71043a6183822dda212af749ef3df8ef", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyOptimality.lean", + "name": "invariantCore_iff_almostSureSafety", + "statement_sha256": null, + "historical_source_sha256": "c399aaab95f64c3a3d33d8f672815bd35c04ab96f99d35abe7984eaff2c75017", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/InfiniteSafetyLeakage.lean", + "name": "high_actual_finite_survival_zero_infinite", + "statement_sha256": null, + "historical_source_sha256": "4e4c34c224270881d20b904ca5177bbd811f37950d4f5184fbd6b191eba88785", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "D-08", + "historical_contract_ids": [ + "D2-P08" + ], + "required_expected_result": "No admissible typed controlled row", + "implemented_claim": "Finite certified block menus and genuine time-dependent alpha/P/U-controlled law with fixed physical P.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean", + "name": "CertifiedMenu", + "statement_sha256": null, + "historical_source_sha256": "d16f5693027488555038686aabfd1c548e6dbe8665e8f19768a33eb60a403e5c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean", + "name": "block_preserves_world", + "statement_sha256": null, + "historical_source_sha256": "d16f5693027488555038686aabfd1c548e6dbe8665e8f19768a33eb60a403e5c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean", + "name": "typedRow_eq", + "statement_sha256": null, + "historical_source_sha256": "d16f5693027488555038686aabfd1c548e6dbe8665e8f19768a33eb60a403e5c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean", + "name": "typedChronologicalLaw_eq", + "statement_sha256": null, + "historical_source_sha256": "d16f5693027488555038686aabfd1c548e6dbe8665e8f19768a33eb60a403e5c", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean", + "name": "adaptive_block_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "d16f5693027488555038686aabfd1c548e6dbe8665e8f19768a33eb60a403e5c", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "SR-A02: concrete typed compatible-overlap and joint-only certified block fixture missing.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "D-09", + "historical_contract_ids": [ + "D2-P09" + ], + "required_expected_result": "Class identity retained; descriptor score aggregates correctly", + "implemented_claim": "Finite menu of deterministic stationary policies, exact class absorption and stationary descriptor-limit scores.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/MultipleLimit/FiniteOccupationRandomLaw.lean", + "name": "observedOccupationLimit_ae_correct", + "statement_sha256": null, + "historical_source_sha256": "4278ca143423a5d6e7ed6d9fb5c339e1c8b9cbb2a93a60a8a8f934aa443179cc", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/StationaryOccupationObjective.lean", + "name": "ExactClassFunctional", + "statement_sha256": null, + "historical_source_sha256": "b06460b24795cd14ccd4d4ef6ffa5989d77b5c1245efb16da18d0c22c2bac156", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/StationaryOccupationObjective.lean", + "name": "occupation_functional_integrable", + "statement_sha256": null, + "historical_source_sha256": "b06460b24795cd14ccd4d4ef6ffa5989d77b5c1245efb16da18d0c22c2bac156", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/StationaryOccupationObjective.lean", + "name": "rationalObjective_eq_actual", + "statement_sha256": null, + "historical_source_sha256": "b06460b24795cd14ccd4d4ef6ffa5989d77b5c1245efb16da18d0c22c2bac156", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/StationaryOccupationObjective.lean", + "name": "equal_descriptor_laws_equal_values", + "statement_sha256": null, + "historical_source_sha256": "b06460b24795cd14ccd4d4ef6ffa5989d77b5c1245efb16da18d0c22c2bac156", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/StationaryMenuOptimization.lean", + "name": "computedStationaryPolicy", + "statement_sha256": null, + "historical_source_sha256": "839dd7bc58a7b3bfe5b0a2145069e1947fbcfbbeb697d147b230373c69b25bb6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/StationaryMenuOptimization.lean", + "name": "stationary_menu_actual_optimal_and_attained", + "statement_sha256": null, + "historical_source_sha256": "839dd7bc58a7b3bfe5b0a2145069e1947fbcfbbeb697d147b230373c69b25bb6", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "SR-A01: concrete ExactClassFunctional and two-state5/6-versus1 regression missing.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "C11-01", + "historical_contract_ids": [ + "C11-P01" + ], + "required_expected_result": "Valid nonempty families accepted; n0 rejected", + "implemented_claim": "All nonempty finite rational stochastic matrices; explicit Cramer stationary family for every genuine bottom class.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceMatrix.lean", + "name": "referenceMatrix_det_ne_zero", + "statement_sha256": null, + "historical_source_sha256": "0f370198eca4d5bed88c0301a8e61b13fa4e9b2bf4bedc0ebff3ac8c9c04ea3d", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceMatrix.lean", + "name": "referenceWeights", + "statement_sha256": null, + "historical_source_sha256": "0f370198eca4d5bed88c0301a8e61b13fa4e9b2bf4bedc0ebff3ac8c9c04ea3d", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceProbability.lean", + "name": "referenceWeights_probability", + "statement_sha256": null, + "historical_source_sha256": "256cf50633615318e6376545da8d0f997cc5e96175524199493ac4185eb151b6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceClasses.lean", + "name": "referenceClassCandidate_conditions", + "statement_sha256": null, + "historical_source_sha256": "e2d4a77f3ca3c10fbf4a9fdd930248a41e286c22cbc4d210375db5e08afe31f7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceStationaryFamily.lean", + "name": "canonicalBottomClassList_nodup", + "statement_sha256": null, + "historical_source_sha256": "a1fda222a29a0863803cbc5c7174d35a07858956a623b4bebbf56e391c172c15", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceStationaryFamily.lean", + "name": "referenceStationaryFamily_check", + "statement_sha256": null, + "historical_source_sha256": "a1fda222a29a0863803cbc5c7174d35a07858956a623b4bebbf56e391c172c15", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceTypedBridge.lean", + "name": "referenceClassCandidate_eq_native", + "statement_sha256": null, + "historical_source_sha256": "7183a67015c400f787f995ce7035fc83600200946b60fce8afcedfcd6b8b9e73", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "C11-02", + "historical_contract_ids": [ + "C11-P01" + ], + "required_expected_result": "Existing checker rejects complete faulty output", + "implemented_claim": "All nonempty finite rational stochastic matrices; explicit Cramer stationary family for every genuine bottom class.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceMatrix.lean", + "name": "referenceMatrix_det_ne_zero", + "statement_sha256": null, + "historical_source_sha256": "0f370198eca4d5bed88c0301a8e61b13fa4e9b2bf4bedc0ebff3ac8c9c04ea3d", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceMatrix.lean", + "name": "referenceWeights", + "statement_sha256": null, + "historical_source_sha256": "0f370198eca4d5bed88c0301a8e61b13fa4e9b2bf4bedc0ebff3ac8c9c04ea3d", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceProbability.lean", + "name": "referenceWeights_probability", + "statement_sha256": null, + "historical_source_sha256": "256cf50633615318e6376545da8d0f997cc5e96175524199493ac4185eb151b6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceClasses.lean", + "name": "referenceClassCandidate_conditions", + "statement_sha256": null, + "historical_source_sha256": "e2d4a77f3ca3c10fbf4a9fdd930248a41e286c22cbc4d210375db5e08afe31f7", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceStationaryFamily.lean", + "name": "canonicalBottomClassList_nodup", + "statement_sha256": null, + "historical_source_sha256": "a1fda222a29a0863803cbc5c7174d35a07858956a623b4bebbf56e391c172c15", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceStationaryFamily.lean", + "name": "referenceStationaryFamily_check", + "statement_sha256": null, + "historical_source_sha256": "a1fda222a29a0863803cbc5c7174d35a07858956a623b4bebbf56e391c172c15", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/ReverseSolver/C11ReferenceTypedBridge.lean", + "name": "referenceClassCandidate_eq_native", + "statement_sha256": null, + "historical_source_sha256": "7183a67015c400f787f995ce7035fc83600200946b60fce8afcedfcd6b8b9e73", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "C-01", + "historical_contract_ids": [ + "C-P01" + ], + "required_expected_result": "No unrestricted nonfinite occupation theorem", + "implemented_claim": "Compact metric Feller cluster theorem, unique-invariant convergence, stationary/absolutely-continuous random-law extension.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/MultipleLimit/CompactFellerEmpirical.lean", + "name": "empirical_clusters_invariant_ae", + "statement_sha256": null, + "historical_source_sha256": "d05ed52a0f3dc947dec318a08a71b2647bea3a9ab34eea0ffac91fc717243039", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/CompactFellerEmpirical.lean", + "name": "empirical_unique_invariant_ae", + "statement_sha256": null, + "historical_source_sha256": "d05ed52a0f3dc947dec318a08a71b2647bea3a9ab34eea0ffac91fc717243039", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/CompactFellerDistance.lean", + "name": "empirical_invariant_distance_ae", + "statement_sha256": null, + "historical_source_sha256": "b0c70c23acd3908ef5905668c45536f45ad199a4ff0c774ebc8374b5e40edb94", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/CompactFellerEmpirical.lean", + "name": "original_occupation_of_invariant_descriptor", + "statement_sha256": null, + "historical_source_sha256": "d05ed52a0f3dc947dec318a08a71b2647bea3a9ab34eea0ffac91fc717243039", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/StationaryConditionalLaw.lean", + "name": "conditionalKernel", + "statement_sha256": null, + "historical_source_sha256": "0b362e8dfe715b029ad680c522ec95e4013813a9238a14122e0ded9c60e92805", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/StationaryConditionalInvariant.lean", + "name": "randomLaw_invariant_ae", + "statement_sha256": null, + "historical_source_sha256": "b20e017023a642ad42321a794b0ea0857eb74447a01329c5a71d3b1c0bf8f506", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/PolishStationaryRandomOccupation.lean", + "name": "exists_measurable_random_limit", + "statement_sha256": null, + "historical_source_sha256": "7a09b4036160b17ef24e364e451951ec01d37e5e654896bb100da75d8e18910a", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/CanonicalInitialDensity.lean", + "name": "pathLaw_eq_withDensity", + "statement_sha256": null, + "historical_source_sha256": "e2bbcc60547fffa5e2f47a6df53f5c4045fc8fb66797c34a84d205dddfa00f75", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/CanonicalInitialDensity.lean", + "name": "pathLaw_rnDeriv", + "statement_sha256": null, + "historical_source_sha256": "e2bbcc60547fffa5e2f47a6df53f5c4045fc8fb66797c34a84d205dddfa00f75", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/NonfiniteClock.lean", + "name": "canonical_clock_no_weak_limit", + "statement_sha256": null, + "historical_source_sha256": "fc0c1985520f06219b9ab32d96f21decbf2150125e9df6cb1f99819a385b71ee", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/MultipleLimit/GrowingBlockCounterexample.lean", + "name": "canonical_growing_block_occupations_fail", + "statement_sha256": null, + "historical_source_sha256": "a9d23da8a799fb9c00d36c569e043a500f8158010ed5f2ae0e14df40968cd15e", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "B-01", + "historical_contract_ids": [ + "B-P01" + ], + "required_expected_result": "Compatibility rejected; order dependence exposed", + "implemented_claim": "Finite measurable row patches agreeing on every active overlap; fixed P and original typed law bridge.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/GrammarRobust/CompatibleOverlap.lean", + "name": "RowPatch.Compatible", + "statement_sha256": null, + "historical_source_sha256": "c90bc6bf020f01f2bd515f458b0fdcf8c48a5acaa608003fae2ac11401e0fde6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/GrammarRobust/CompatibleOverlap.lean", + "name": "applyCompatiblePatches_active", + "statement_sha256": null, + "historical_source_sha256": "c90bc6bf020f01f2bd515f458b0fdcf8c48a5acaa608003fae2ac11401e0fde6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/GrammarRobust/CompatibleOverlap.lean", + "name": "applyCompatiblePatches_perm", + "statement_sha256": null, + "historical_source_sha256": "c90bc6bf020f01f2bd515f458b0fdcf8c48a5acaa608003fae2ac11401e0fde6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/GrammarRobust/CompatibleOverlap.lean", + "name": "applyRowPatch_idempotent", + "statement_sha256": null, + "historical_source_sha256": "c90bc6bf020f01f2bd515f458b0fdcf8c48a5acaa608003fae2ac11401e0fde6", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/GrammarRobust/OverlapKernelAssembly.lean", + "name": "overlapModel_pathLaw_eq_of_membership", + "statement_sha256": null, + "historical_source_sha256": "b0a2a95b83a945eae4e7fb7d37a9110351c44256feb377eac7e861c357138e9e", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/GrammarRobust/OverlapCanonicalBridge.lean", + "name": "overlapIntervention_margin_eq", + "statement_sha256": null, + "historical_source_sha256": "5961213b9e2744b0aa2f9d6103db579a4e7e46beb56a9012f1c8c831c6857ebe", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "SR-A02: generic compatible overlap proved; concrete typed positive/negative overlap fixtures missing.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "B-02", + "historical_contract_ids": [ + "B-P02" + ], + "required_expected_result": "No unique-minimum or monotonicity inference", + "implemented_claim": "Local strict lower-set mapping/lifting with matched positivity; unique least block only under finite positive meet closure.", + "decision": "PENDING", + "historical_pr79_decision": "PENDING_STRENGTHENED_FIXTURE", + "proof_declarations": [ + { + "path": "PermanssonResearch/GrammarRobust/LowerSetTransport.lean", + "name": "minimalPositive_iff_of_lowerSet_lifting", + "statement_sha256": null, + "historical_source_sha256": "07d6495112d3761118a6510026c878497d773270e10aee538f6e6e61385cb3cb", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/GrammarRobust/OriginalLowerSetTransport.lean", + "name": "Coarsening.original_margin_minimal_iff_of_lower_coverage", + "statement_sha256": null, + "historical_source_sha256": "e5b2bf9f8d48f25e83ad701a5e6b4b23286a2b2b944ba76143ca2b9abc45f8bd", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/GrammarRobust/LowerSetTransport.lean", + "name": "exists_least_positive_of_finite_meet_closed", + "statement_sha256": null, + "historical_source_sha256": "07d6495112d3761118a6510026c878497d773270e10aee538f6e6e61385cb3cb", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "SR-A03: non-global-isomorphism positive local-margin transport fixture missing.", + "R2-C01: preserved OR/AND labels are correct for ablation margins.", + "Strengthened original acceptance fixture not independently certified at current head.", + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "XPLUS-01", + "historical_contract_ids": [ + "X1-P01" + ], + "required_expected_result": "Correct source constructors and kappa1; no edit to old X1", + "implemented_claim": "New three-world two-player constructed economic model with fair safe baseline, collapse under extraction, beta three quarters.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/EconomicWitness/ExactRegime.lean", + "name": "original_exactGR", + "statement_sha256": null, + "historical_source_sha256": "21de62ad386d164a658886b67fa163d5c6e13855430346d8d6d4ed928f1b4746", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/EconomicWitness/ExactRegime.lean", + "name": "original_confirmatoryGR", + "statement_sha256": null, + "historical_source_sha256": "21de62ad386d164a658886b67fa163d5c6e13855430346d8d6d4ed928f1b4746", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/EconomicWitness/Constitution.lean", + "name": "original_grounded_confirmatory_uniformPR", + "statement_sha256": null, + "historical_source_sha256": "ac4bc0808102a469e3ba9dfb728b6eb1d9fe3e5cfa0243b36b1ac22194f7974a", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/EconomicWitness/Incentives.lean", + "name": "baseline_attainment", + "statement_sha256": null, + "historical_source_sha256": "6c40feb1df8fe41fb47c2d621a882e4532052e2a66bdc9fac0a140ace7a78117", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "E-01", + "historical_contract_ids": [ + "APP-E01" + ], + "required_expected_result": "Rank-one/scalar promotion rejected", + "implemented_claim": "Finite real symmetric feature discrepancy with seam invisibility, plus constant diagonal for common rank one.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Specialized/SeamReduction.lean", + "name": "seam_rank_le_two", + "statement_sha256": null, + "historical_source_sha256": "e09f3027088f643b7599c4a2861c42300e8bcf325982a66e93563a9fdb3d4787", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/SeamReduction.lean", + "name": "seam_constant_diagonal", + "statement_sha256": null, + "historical_source_sha256": "e09f3027088f643b7599c4a2861c42300e8bcf325982a66e93563a9fdb3d4787", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/SeamReduction.lean", + "name": "constitutiveEffect_commonMode", + "statement_sha256": null, + "historical_source_sha256": "e09f3027088f643b7599c4a2861c42300e8bcf325982a66e93563a9fdb3d4787", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/SeamReduction.lean", + "name": "constitutiveMargin_commonMode", + "statement_sha256": null, + "historical_source_sha256": "e09f3027088f643b7599c4a2861c42300e8bcf325982a66e93563a9fdb3d4787", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "E-02", + "historical_contract_ids": [ + "APP-E02" + ], + "required_expected_result": "No persistent witness transfer", + "implemented_claim": "Continuous quadratic form, certified legal approximants and form-preserving nested embeddings.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Specialized/FiniteDetection.lean", + "name": "quadratic_approximation_bound", + "statement_sha256": null, + "historical_source_sha256": "43f7f1b7842ab92c38f6e220dedbe9b449f713bd156f1013112f69c091b49336", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/FiniteDetection.lean", + "name": "exists_legal_negative_of_approximation", + "statement_sha256": null, + "historical_source_sha256": "43f7f1b7842ab92c38f6e220dedbe9b449f713bd156f1013112f69c091b49336", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/FiniteDetection.lean", + "name": "negative_persists_under_embedding", + "statement_sha256": null, + "historical_source_sha256": "43f7f1b7842ab92c38f6e220dedbe9b449f713bd156f1013112f69c091b49336", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/FiniteDetection.lean", + "name": "actual_effect_pos_of_legal_approximation", + "statement_sha256": null, + "historical_source_sha256": "43f7f1b7842ab92c38f6e220dedbe9b449f713bd156f1013112f69c091b49336", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "E-03", + "historical_contract_ids": [ + "APP-E03" + ], + "required_expected_result": "No scalar shell reduction or unjustified inverse", + "implemented_claim": "Finite real self-adjoint predecessor PSD block, actual shell dimension, negative shift and Schur elimination.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Specialized/ShellSchur.lean", + "name": "negativeShift_isUnit", + "statement_sha256": null, + "historical_source_sha256": "91976bd49739c02dc057be780ed4bfcb9eef3d692123a3cbcd203c4a43a43e54", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/ShellSchur.lean", + "name": "shell_determinant", + "statement_sha256": null, + "historical_source_sha256": "91976bd49739c02dc057be780ed4bfcb9eef3d692123a3cbcd203c4a43a43e54", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/ShellSchur.lean", + "name": "shell_equations_iff", + "statement_sha256": null, + "historical_source_sha256": "91976bd49739c02dc057be780ed4bfcb9eef3d692123a3cbcd203c4a43a43e54", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/ShellResolvent.lean", + "name": "scalarShellSecular_derivative_negative", + "statement_sha256": null, + "historical_source_sha256": "c595d334a9049c85f404f5657d0e21eafee46b57b90879a6596cc40d5e980123", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "E-04", + "historical_contract_ids": [ + "APP-E04" + ], + "required_expected_result": "No pseudoinverse positivity certificate or cancellation", + "implemented_claim": "Finite symmetric range/kernel compatibility; explicit preimages and denominator-free source transport identities.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Specialized/SingularCompatibility.lean", + "name": "symmetric_preimage_iff", + "statement_sha256": null, + "historical_source_sha256": "39e607f76735f7d01c65e29aed4ecebd7bca0857f96b5be08bdfb0e7faedb950", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/SingularCompatibility.lean", + "name": "singular_block_nonnegative_iff", + "statement_sha256": null, + "historical_source_sha256": "39e607f76735f7d01c65e29aed4ecebd7bca0857f96b5be08bdfb0e7faedb950", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/SingularCompatibility.lean", + "name": "incompatible_kernel_negative", + "statement_sha256": null, + "historical_source_sha256": "39e607f76735f7d01c65e29aed4ecebd7bca0857f96b5be08bdfb0e7faedb950", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/SingularCompatibility.lean", + "name": "denominator_free_kernel_rigidity", + "statement_sha256": null, + "historical_source_sha256": "39e607f76735f7d01c65e29aed4ecebd7bca0857f96b5be08bdfb0e7faedb950", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/SingularCompatibility.lean", + "name": "zero_shift_source_response", + "statement_sha256": null, + "historical_source_sha256": "39e607f76735f7d01c65e29aed4ecebd7bca0857f96b5be08bdfb0e7faedb950", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + }, + { + "case_id": "E-05", + "historical_contract_ids": [ + "APP-E05" + ], + "required_expected_result": "No unqualified negative full-form conclusion", + "implemented_claim": "Signed rank-one quadratic block with positive Gram gap and an explicit operator-norm perturbation threshold.", + "decision": "PARTIAL_SCOPE", + "historical_pr79_decision": "ROUTED_EVIDENCE_SOURCE_CI_VERIFIED_NOT_INDEPENDENT_APPROVAL", + "proof_declarations": [ + { + "path": "PermanssonResearch/Specialized/GramSeparation.lean", + "name": "norm_gramWitness_sq", + "statement_sha256": null, + "historical_source_sha256": "9c3704950798c4d9a542dcecad802883f3aa52743d58b23618e011386cdc9396", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/GramSeparation.lean", + "name": "gram_unit_rayleigh_value", + "statement_sha256": null, + "historical_source_sha256": "9c3704950798c4d9a542dcecad802883f3aa52743d58b23618e011386cdc9396", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/GramSeparation.lean", + "name": "gram_operator_perturbation_negative", + "statement_sha256": null, + "historical_source_sha256": "9c3704950798c4d9a542dcecad802883f3aa52743d58b23618e011386cdc9396", + "source_sha256": null, + "reviewed_head_sha": null + }, + { + "path": "PermanssonResearch/Specialized/GramSeparation.lean", + "name": "constitutiveEffect_gram_witness", + "statement_sha256": null, + "historical_source_sha256": "9c3704950798c4d9a542dcecad802883f3aa52743d58b23618e011386cdc9396", + "source_sha256": null, + "reviewed_head_sha": null + } + ], + "positive_tests": [], + "negative_tests": [], + "ci_receipts": [], + "review_receipts": [], + "source_verification": "RECHECK_SOURCE_DIGEST_IN_AUDIT_MODE", + "remaining_obligations": [ + "Exact-head compiler and transitive-axiom receipts, source-bound semantic tests and independent case review are pending." + ], + "evaluated_against_sha": null, + "candidate_new_proofs": [], + "current_status_explanation": "Historical selected scope; re-evaluate against exact final branch head and authentic case-level CI/review receipts before acceptance." + } + ] +} diff --git a/research/paper_closures/post_merge_112/acceptance_obligations_report.md b/research/paper_closures/post_merge_112/acceptance_obligations_report.md new file mode 100644 index 00000000..6c500ddd --- /dev/null +++ b/research/paper_closures/post_merge_112/acceptance_obligations_report.md @@ -0,0 +1,156 @@ +# Permansson PR #113 — original acceptance obligation work queue + +**CANDIDATE / NOT ACCEPTED / NO INDEPENDENT REVIEW.** + +This report is derived from unchanged original criteria and source mappings. It is not an exact-head CI receipt or independent scholarly approval. + +- Original cases: 45; pending: 15; partial scope: 30; independently accepted: 0. +- New claim owners: 11; accepted: 0. +- Candidate test paths below are NOT verified as sufficient for their original criteria. + +## Original acceptance requirements (all 45) + +| Case | Candidate state | Original required result | Historical theorem pins | Candidate tests | +|---|---|---|---:|---| +| X-01 | PENDING | Kernel-checked equality; changed world/update/action row rejected | 3 | verification/research/test_next03_stochastic_lean.py, verification/research/test_paper_x1_matrix_lean.py | +| X-02 | PARTIAL_SCOPE | Actual-kernel induction identifies each entry | 4 | verification/research/test_paper_x1_lean_closure.py, verification/research/test_x1_commons.py | +| X-03 | PARTIAL_SCOPE | Original constitutiveMargin equalities and exact attainers | 5 | verification/research/test_paper_x1_lean_closure.py, verification/research/test_x1_commons.py | +| X-04 | PENDING | Lean proof fails at arithmetic/domain obligation; parser failure alone insufficient | 5 | verification/research/test_next03_tied_start_lean.py, verification/research/test_final_closure_missing_original_positives.py | +| X-05 | PARTIAL_SCOPE | Typed kernel equality fails | 7 | verification/research/test_paper_x1_lean_closure.py, verification/research/test_final_closure_missing_original_positives.py | +| X-06 | PARTIAL_SCOPE | Rejected; leakage theorem remains unchanged | 5 | verification/research/test_x1_commons.py, verification/research/test_e1_approx_quotient.py, verification/research/test_final_closure_missing_original_positives.py, verification/research/test_final_closure_existing_false_semantics_lean.py | +| S-01 | PARTIAL_SCOPE | Exact identity including terminal beta^N value | 6 | verification/research/test_paper_strategy_economic_lean.py, verification/research/test_pr113_semantic_second_candidates.py | +| S-02 | PARTIAL_SCOPE | Outside admitted information class; no verification theorem | 6 | verification/research/test_paper_information_lean.py, verification/research/test_d2_residence.py, verification/research/test_pr113_semantic_structural_candidates.py | +| M-01 | PARTIAL_SCOPE | Full counterfactual path laws equal under stated premises | 5 | verification/research/test_paper_autonomy_ae_lean.py, verification/research/test_f1_multiagent_protocols.py, verification/research/test_pr113_semantic_second_candidates.py | +| M-02 | PENDING | Autonomy premise rejected; reactive witness not counted strict | 7 | verification/research/test_next03_m02_marked_coupling.py, verification/research/test_m02_marked_actual_source_lean.py, verification/research/test_m02_actual_history_kernel_lean.py | +| F-01 | PENDING | P inputs reindexed correctly, no assumed symmetry | 8 | verification/research/test_next03_asymmetric_common_swap_lean.py, verification/research/test_final05_measurable_lean.py | +| F-02 | PARTIAL_SCOPE | Projection/exogeneity fails | 5 | verification/research/test_paper_measurable_clock_lean.py, verification/research/test_f1_multiagent_protocols.py, verification/research/test_final_closure_more_false_semantics_lean.py | +| F-03 | PARTIAL_SCOPE | No feasible strict probability anchor | 4 | verification/research/test_paper_closure_boundaries.py, verification/research/test_f1_multiagent_protocols.py, verification/research/test_final_closure_missing_original_positives.py, verification/research/test_f03_actual_infeasible_strict_anchor_lean.py | +| F-04 | PARTIAL_SCOPE | Single-product claim fails; mediator mixture succeeds | 3 | verification/research/test_f1_multiagent_protocols.py, verification/research/test_final_closure_more_false_semantics_lean.py | +| F-05 | PENDING | Mediation not identified without sufficient premises | 5 | verification/research/test_next03_positive_mediation.py, verification/research/test_paper_closure_boundaries.py, verification/research/test_final_closure_more_false_semantics_lean.py | +| Q-01 | PARTIAL_SCOPE | Geometric bound holds without claiming projection Markov | 6 | verification/research/test_e1_approx_quotient.py, verification/research/test_pr113_semantic_structural_candidates.py | +| Q-02 | PENDING | Correct boundaries; mismatched unbudgeted starts rejected | 6 | verification/research/test_next04_projection_arithmetic.py, verification/research/test_final_closure_more_false_semantics_lean.py | +| Q-03 | PENDING | Product mass proved, no independence assumption | 3 | verification/research/test_next03_dependent_three_stage.py, verification/research/test_a2_primitive_tv.py, verification/research/test_final_actual_product_reporting_false_lean.py | +| Q-04 | PENDING | Intervention defect charged rather than omitted | 2 | verification/research/test_localized_prefix_compiler.py, verification/research/test_next04_original_margin.py, verification/research/test_final_actual_product_reporting_false_lean.py | +| Q-05 | PENDING | No unqualified whole-path/Exact-GR transfer | 10 | verification/research/test_final04_source_lean.py, verification/research/test_next04_original_margin_lean.py, verification/research/test_next04_projection_arithmetic.py, verification/research/test_q05_nonidentity_source_fixture.py, verification/research/test_q05_actual_source_lean.py, verification/research/test_q05_original_margin_adapter_lean.py, verification/research/test_q05_actual_atom_closure_lean.py | +| A-01 | PARTIAL_SCOPE | Finite dual equality checked; wrong supnorm test class rejected | 6 | verification/research/test_paper_metric_coupling_lean.py, verification/research/test_a3_contractive.py, verification/research/test_pr113_semantic_structural_candidates.py | +| A-02 | PARTIAL_SCOPE | Missing strict contraction/uniqueness exposed | 13 | verification/research/test_paper_invariant_lean.py, verification/research/test_a3_contractive.py, verification/research/test_pr113_semantic_second_candidates.py | +| A-03 | PARTIAL_SCOPE | Bounded-diameter conversion not applied | 7 | verification/research/test_a3_contractive.py, verification/research/test_pr113_semantic_boundary_candidates.py | +| A-04 | PARTIAL_SCOPE | Four-process wrapper unavailable, not silently three-process | 8 | verification/research/test_a3_contractive.py, verification/research/test_paper_strategy_economic_lean.py, verification/research/test_pr113_semantic_factorization_candidates.py | +| A-05 | PARTIAL_SCOPE | Property identity fails rather than being assumed | 8 | verification/research/test_a3_contractive.py, verification/research/test_final_closure_missing_original_positives.py, verification/research/test_pr113_semantic_factorization_candidates.py | +| D-01 | PARTIAL_SCOPE | Same literal-event value; deterministic augmented maximizer attains | 11 | verification/research/test_d2_residence.py, verification/research/test_pr113_semantic_second_candidates.py | +| D-02 | PARTIAL_SCOPE | Exact event classification including word 1,0,1,1 | 10 | verification/research/test_d2_residence.py, verification/research/test_monitor_stationary_closures.py, verification/research/test_final_original_chronological_false_lean.py | +| D-03 | PARTIAL_SCOPE | Declared precedence and separate risk monitor preserved | 16 | verification/research/test_monitor_stationary_closures.py, verification/research/test_paper_through_risk_lean.py, verification/research/test_final_original_chronological_false_lean.py | +| D-04 | PARTIAL_SCOPE | Optimum17/20 atp1/2, unconstrained p1 infeasible | 10 | verification/research/test_d2_residence.py, verification/research/test_paper_risk_lp_lean.py, verification/research/test_final_original_risk_belief_false_lean.py | +| D-05 | PENDING | Committed value.09 differs from rectangular.01 | 9 | verification/research/test_next06_committed_path_law.py, verification/research/test_final_original_chronological_false_lean.py | +| D-06 | PENDING | Full augmented belief required | 7 | verification/research/test_next06_reachable_completion_belief.py, verification/research/test_final_original_risk_belief_false_lean.py | +| D-07 | PARTIAL_SCOPE | Infinite optimum determined by limit theorem, not one finite bound | 9 | verification/research/test_paper_infinite_safety_lean.py, verification/research/test_d2_residence.py, verification/research/test_pr113_semantic_boundary_candidates.py | +| D-08 | PENDING | No admissible typed controlled row | 5 | verification/research/test_final_d08_admission.py, verification/research/test_next06_d08_joint_only_compiler.py | +| D-09 | PENDING | Class identity retained; descriptor score aggregates correctly | 7 | verification/research/test_final_d09_multichain.py, verification/research/test_final06_phaseblind_original.py | +| C11-01 | PARTIAL_SCOPE | Valid nonempty families accepted; n0 rejected | 7 | verification/research/test_lane_c11_stationary.py, verification/research/test_pr113_semantic_second_candidates.py | +| C11-02 | PARTIAL_SCOPE | Existing checker rejects complete faulty output | 7 | verification/research/test_lane_c11_stationary.py, verification/research/test_final_closure_missing_original_positives.py, verification/research/test_pr113_semantic_second_candidates.py | +| C-01 | PARTIAL_SCOPE | No unrestricted nonfinite occupation theorem | 11 | verification/research/test_paper_nonfinite_counterexample_lean.py, verification/research/test_pr113_semantic_boundary_candidates.py | +| B-01 | PENDING | Compatibility rejected; order dependence exposed | 6 | verification/research/test_next06_overlap_lean.py, verification/research/test_lane_b2_sequential_effects.py, verification/research/test_final_closure_missing_original_positives.py | +| B-02 | PENDING | No unique-minimum or monotonicity inference | 3 | verification/research/test_lane_b2_sequential_effects.py, verification/research/test_b02_actual_original_margin_lean.py, verification/research/test_b02_local_nonisomorphic_margin_lean.py | +| XPLUS-01 | PARTIAL_SCOPE | Correct source constructors and kappa1; no edit to old X1 | 4 | verification/research/test_paper_strategy_economic_lean.py, verification/research/test_x1_commons.py, verification/research/test_pr113_semantic_second_candidates.py | +| E-01 | PARTIAL_SCOPE | Rank-one/scalar promotion rejected | 4 | verification/research/test_paper_closure_boundaries.py, verification/research/test_paper_specialized_boundary_lean.py, verification/research/test_pr113_semantic_boundary_candidates.py | +| E-02 | PARTIAL_SCOPE | No persistent witness transfer | 4 | verification/research/test_paper_closure_boundaries.py, verification/research/test_final_closure_missing_original_positives.py, verification/research/test_final_closure_existing_false_semantics_lean.py | +| E-03 | PARTIAL_SCOPE | No scalar shell reduction or unjustified inverse | 4 | verification/research/test_paper_closure_boundaries.py, verification/research/test_final_closure_missing_original_positives.py, verification/research/test_final_closure_existing_false_semantics_lean.py | +| E-04 | PARTIAL_SCOPE | No pseudoinverse positivity certificate or cancellation | 5 | verification/research/test_paper_closure_boundaries.py, verification/research/test_final_closure_missing_original_positives.py, verification/research/test_final_closure_existing_false_semantics_lean.py | +| E-05 | PARTIAL_SCOPE | No unqualified negative full-form conclusion | 4 | verification/research/test_paper_closure_boundaries.py, verification/research/test_paper_specialized_boundary_lean.py, verification/research/test_e05_full_gram_masking_lean.py | + +## Fifteen case-specific historical objections + +| Case | Still-to-be-reconciled obligation(s) | +|---|---| +| X-01 | R2-A01: emitted-literal corruption is not stochasticity-preserving. | +| X-04 | R2-A02: unique learner attainer removal is tested, not tied-equilibrium domain removal. | +| M-02 | R2-A04: marked-versus-unmarked action-coupling distinction has no dedicated tracked countermodel. | +| F-01 | R2-A03: asymmetric common/private-update swap fixture missing. / R2-I02: general measurable n=2 product identity is distinct from the finite full model/path recovery. | +| F-05 | R2-A05: positive exact SCM test is in the full suite, but not this 48-check route; dedicated positive Lean instantiation is absent. | +| Q-02 | ANA-03: direct projected theorem uses matched starts/homogeneous kernels; expanded projected adapter remains open. | +| Q-03 | ANA-01: routed executable tests the old additive primitive budget, not product saturation. | +| Q-04 | ANA-02: selected localized visit/original-margin/reporting integration missing. | +| Q-05 | ANA-04: generic discounted/whole-law results are proved; a quotient-native original-margin application must still supply its actual bindings. | +| D-05 | SR-A04: 9/100 versus1/100 boundary is scalar arithmetic rather than an instantiated committed-model path-law fixture. | +| D-06 | SR-A05: hidden-flag fixture uses different pure priors, not two reachable histories from one fixed initial law. | +| D-08 | SR-A02: concrete typed compatible-overlap and joint-only certified block fixture missing. | +| D-09 | SR-A01: concrete ExactClassFunctional and two-state5/6-versus1 regression missing. | +| B-01 | SR-A02: generic compatible overlap proved; concrete typed positive/negative overlap fixtures missing. | +| B-02 | SR-A03: non-global-isomorphism positive local-margin transport fixture missing. / R2-C01: preserved OR/AND labels are correct for ablation margins. | + +## Eleven candidate claim owners (not yet accepted) + +| Claim | Owner source | Fully qualified declaration count | Status | +|---|---|---:|---| +| Q1 | PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean | 1 | SOURCE_PRESENT_REVIEW_PENDING | +| Q2 | PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean | 2 | SOURCE_PRESENT_REVIEW_PENDING | +| Q3 | PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean | 3 | SOURCE_PRESENT_REVIEW_PENDING | +| M1 | PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean | 2 | SOURCE_PRESENT_REVIEW_PENDING | +| M2 | PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean | 4 | SOURCE_PRESENT_REVIEW_PENDING | +| M3 | PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean | 3 | SOURCE_PRESENT_REVIEW_PENDING | +| N1 | PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean | 2 | SOURCE_PRESENT_REVIEW_PENDING | +| N2 | PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean | 5 | SOURCE_PRESENT_REVIEW_PENDING | +| N3 | PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean | 4 | SOURCE_PRESENT_REVIEW_PENDING | +| D8 | PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean | 3 | SOURCE_PRESENT_REVIEW_PENDING | +| D9 | PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean | 6 | SOURCE_PRESENT_REVIEW_PENDING | + +## Executable-route gaps (unreviewed; not acceptance) + +- Missing selected positive controls: 0 of 45. +- Missing selected semantic false-mutant routes: 0 of 45. +- Passing scope-boundary checks do **not** discharge the mandatory compiler-negative evidence obligation. +- A selected method still requires source relevance, actual exact-head execution, intact logs, authenticated check-run attribution and independent approval. + +| Case | Positive method routes | Semantic-negative routes | Scope-boundary observations | +|---|---:|---:|---:| +| X-01 | 1 | 1 | 0 | +| X-02 | 1 | 2 | 0 | +| X-03 | 2 | 1 | 0 | +| X-04 | 1 | 1 | 0 | +| X-05 | 1 | 3 | 0 | +| X-06 | 1 | 1 | 2 | +| S-01 | 1 | 1 | 0 | +| S-02 | 1 | 1 | 1 | +| M-01 | 2 | 1 | 0 | +| M-02 | 3 | 2 | 1 | +| F-01 | 2 | 1 | 0 | +| F-02 | 1 | 1 | 1 | +| F-03 | 2 | 1 | 2 | +| F-04 | 1 | 1 | 1 | +| F-05 | 1 | 1 | 1 | +| Q-01 | 1 | 1 | 1 | +| Q-02 | 1 | 1 | 1 | +| Q-03 | 2 | 1 | 0 | +| Q-04 | 2 | 1 | 0 | +| Q-05 | 5 | 3 | 2 | +| A-01 | 2 | 1 | 0 | +| A-02 | 1 | 1 | 1 | +| A-03 | 1 | 1 | 1 | +| A-04 | 1 | 1 | 1 | +| A-05 | 1 | 1 | 2 | +| D-01 | 2 | 1 | 0 | +| D-02 | 1 | 1 | 1 | +| D-03 | 2 | 1 | 0 | +| D-04 | 2 | 1 | 0 | +| D-05 | 1 | 1 | 1 | +| D-06 | 1 | 1 | 1 | +| D-07 | 2 | 1 | 0 | +| D-08 | 1 | 1 | 0 | +| D-09 | 3 | 1 | 0 | +| C11-01 | 1 | 1 | 1 | +| C11-02 | 1 | 1 | 2 | +| C-01 | 1 | 1 | 0 | +| B-01 | 1 | 1 | 1 | +| B-02 | 3 | 2 | 1 | +| XPLUS-01 | 1 | 1 | 1 | +| E-01 | 1 | 1 | 1 | +| E-02 | 1 | 1 | 2 | +| E-03 | 1 | 1 | 2 | +| E-04 | 1 | 1 | 2 | +| E-05 | 2 | 1 | 1 | + +## Closure protocol + +Each original case requires a correct Lean source/premise binding, a positive control and semantically false mutant, genuine check-run IDs and a separately authenticated non-author exact-H case-specific review. Source-only PENDING/PARTIAL decisions cannot self-promote. + +After source H freezes, signed reviewer and effective acceptance overlays remain DETACHED. Any approved mathematical paper additionally requires genuine merged M, page-by-page PDF inspection and independent postmerge scholarly signoff. A compiler-green source does not certify empirical GR/PR or unrestricted randomized policy optimality. diff --git a/research/paper_closures/post_merge_112/additional_control_producers_v3_1.schema.json b/research/paper_closures/post_merge_112/additional_control_producers_v3_1.schema.json new file mode 100644 index 00000000..cdb611a5 --- /dev/null +++ b/research/paper_closures/post_merge_112/additional_control_producers_v3_1.schema.json @@ -0,0 +1,152 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/HMarcusWH/PermanssonLean/blob/research/final-mathematical-closure/research/paper_closures/post_merge_112/additional_control_producers_v3_1.schema.json", + "title": "PR113 additional producing CI source-control extension v3.1 (unreviewed)", + "description": "Requires separately authorized source workflow provenance. This schema alone never grants review or mathematical acceptance.", + "type": "object", + "additionalProperties": false, + "required": [ + "format", + "head_sha", + "original_21_producer_bundle_sha256", + "original_producing_artifact_count", + "source_controls_accepted", + "independent_security_review", + "mathematical_approval", + "merged_main_sha", + "producers" + ], + "properties": { + "format": { + "const": "Permansson.PR113.ControlProducerExtension.v3.1" + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "original_21_producer_bundle_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "original_producing_artifact_count": { + "const": 21 + }, + "source_controls_accepted": { + "const": false + }, + "independent_security_review": { + "const": false + }, + "mathematical_approval": { + "const": false + }, + "merged_main_sha": { + "type": "null" + }, + "producers": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "run_id", + "run_attempt", + "job_id", + "check_run_id", + "artifact_id", + "artifact_name", + "artifact_sha256", + "workflow_path", + "job_name", + "owner_index_member", + "owner_index_sha256", + "owners" + ], + "properties": { + "run_id": { + "type": "integer", + "minimum": 1 + }, + "run_attempt": { + "type": "integer", + "minimum": 1 + }, + "job_id": { + "type": "integer", + "minimum": 1 + }, + "check_run_id": { + "type": "integer", + "minimum": 1 + }, + "artifact_id": { + "type": "integer", + "minimum": 1 + }, + "artifact_name": { + "type": "string", + "minLength": 1 + }, + "artifact_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "workflow_path": { + "type": "string", + "pattern": "^\\.github/workflows/[A-Za-z0-9_.-]+\\.ya?ml$" + }, + "job_name": { + "type": "string", + "minLength": 1 + }, + "owner_index_member": { + "type": "string", + "minLength": 1 + }, + "owner_index_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "owners": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "owner_id", + "receipt_member", + "receipt_sha256" + ], + "properties": { + "kind": { + "enum": [ + "original_positive", + "legacy_negative", + "claim_pair" + ] + }, + "owner_id": { + "type": "string", + "minLength": 1 + }, + "receipt_member": { + "type": "string", + "minLength": 1 + }, + "receipt_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + } + } + } + } + } + } + } +} diff --git a/research/paper_closures/post_merge_112/claim_declaration_map.json b/research/paper_closures/post_merge_112/claim_declaration_map.json new file mode 100644 index 00000000..d7a9bae2 --- /dev/null +++ b/research/paper_closures/post_merge_112/claim_declaration_map.json @@ -0,0 +1,407 @@ +{ + "claims": [ + { + "accepted": false, + "claim_id": "Q1", + "declarations": [ + { + "declaration": "PermanssonResearch.Quotient.DiscountedCanonicalSourceWitness.canonical_discounted_output_error", + "header_present": true, + "path": "PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "Q-05" + ], + "primary_path": "PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean", + "scope_exclusions": [], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final04_source_lean.py" + ] + }, + { + "accepted": false, + "claim_id": "Q2", + "declarations": [ + { + "declaration": "PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure.actual_canonical_discounted_original_margin", + "header_present": true, + "path": "PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure.point_witness_output", + "header_present": true, + "path": "PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "Q-05" + ], + "primary_path": "PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean", + "scope_exclusions": [ + "Source PointWitness and full B1 premise must actually be discharged" + ], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final04_source_lean.py" + ] + }, + { + "accepted": false, + "claim_id": "Q3", + "declarations": [ + { + "declaration": "PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples.SourceBoundExactWitness.exactKernelPointWitness", + "header_present": true, + "path": "PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples.correct_frozen_discounted_budget", + "header_present": true, + "path": "PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples.wrong_frozen_discounted_budget", + "header_present": true, + "path": "PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "Q-05" + ], + "primary_path": "PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean", + "scope_exclusions": [], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final04_canonical_output.py" + ] + }, + { + "accepted": false, + "claim_id": "M1", + "declarations": [ + { + "declaration": "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_closed_kernel_two", + "header_present": true, + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_open_kernel_two", + "header_present": true, + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "F-01", + "Q-05" + ], + "primary_path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean", + "scope_exclusions": [ + "Conditional-product subclass only; no unrestricted correlated generator" + ], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final05_measurable_lean.py" + ] + }, + { + "accepted": false, + "claim_id": "M2", + "declarations": [ + { + "declaration": "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_closed_loop_original_margin", + "header_present": true, + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_open_loop_original_margin", + "header_present": true, + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_closed_loop_all_initial_paths", + "header_present": true, + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_open_loop_all_initial_paths", + "header_present": true, + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "F-01", + "Q-05" + ], + "primary_path": "PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + "scope_exclusions": [], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final05_measurable_lean.py" + ] + }, + { + "accepted": false, + "claim_id": "M3", + "declarations": [ + { + "declaration": "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_closed_worlds_both_frozen", + "header_present": true, + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.MultiAgent.FinalMatchedAsymmetry.wrong_asymmetric_input_change_is_detectable", + "header_present": true, + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_open_worlds_both_frozen", + "header_present": true, + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "F-01", + "Q-05" + ], + "primary_path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean", + "scope_exclusions": [], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final05_matched_protocols.py" + ] + }, + { + "accepted": false, + "claim_id": "N1", + "declarations": [ + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.phase_aware_invariant_weights", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.native_phase_aware_actual_true_mass", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeClassGeometry.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "D-09" + ], + "primary_path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean", + "scope_exclusions": [], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final06_native_class_lean.py" + ] + }, + { + "accepted": false, + "claim_id": "N2", + "declarations": [ + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.native_phase_aware_actual_score_one", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.native_phase_blind_actual_le_five_sixths", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearPhaseBlindClassGeometry.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.native_phase_blind_actual_attained", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearPhaseBlindClassGeometry.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.native_actual_phase_aware_strictly_beats_phase_blind", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearPhaseBlindClassGeometry.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.actual_restricted_gap_of_identified_classes", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "D-09" + ], + "primary_path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean", + "scope_exclusions": [ + "No unrestricted randomized/history-dependent policy optimality", + "Augmented-state stationary policy is not phase-blind" + ], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final06_phaseblind_original.py", + "verification/research/test_final06_actual_score_lean.py" + ] + }, + { + "accepted": false, + "claim_id": "N3", + "declarations": [ + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.booleanClassFunctional", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.boolean_rational_objective_eq_actual", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.certified_boolean_score_exact", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.boolean_score_bounded", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "D-09" + ], + "primary_path": "PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean", + "scope_exclusions": [], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final06_c11_evaluator.py" + ] + }, + { + "accepted": false, + "claim_id": "D8", + "declarations": [ + { + "declaration": "PermanssonResearch.ReverseSolver.AdaptiveGrammarAdmissionFixture.ConcreteRestrictedBoolean.actual_restricted_menu_admits_both_components", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.AdaptiveGrammarAdmissionFixture.ConcreteRestrictedBoolean.actual_restricted_menu_rejects_joint", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.AdaptiveGrammarAdmissionFixture.ConcreteRestrictedBoolean.actual_restricted_menu_row_normalized", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "D-08" + ], + "primary_path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean", + "scope_exclusions": [], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final_d08_admission.py" + ] + }, + { + "accepted": false, + "claim_id": "D9", + "declarations": [ + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture.actual_two_absorbing_full_path_mixture", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture.actual_multichain_classwise_differs_from_barycenter", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture.actual_mixed_nonlinear_half_not_barycenter", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture.physically_distinct_classes_with_shared_descriptor", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture.actual_mixed_initial_nonlinear_expectation", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + }, + { + "declaration": "PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture.exact_two_absorbing_rational_scores", + "header_present": true, + "path": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE" + } + ], + "independent_review_receipts": [], + "original_case_links": [ + "D-09" + ], + "primary_path": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "scope_exclusions": [ + "Current half-versus-five-sixths multichain witness cannot by itself discharge historical five-sixths-versus-one test" + ], + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "test_source_candidates": [ + "verification/research/test_final_d09_multichain.py" + ] + } + ], + "count": 11, + "format": "Permansson.FinalClosureQualifiedClaims.v1", + "source_baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "status": "UNREVIEWED_SOURCE_CANDIDATES" +} diff --git a/research/paper_closures/post_merge_112/claim_inventory.json b/research/paper_closures/post_merge_112/claim_inventory.json new file mode 100644 index 00000000..821ca52d --- /dev/null +++ b/research/paper_closures/post_merge_112/claim_inventory.json @@ -0,0 +1,92 @@ +{ + "format": "Permansson.FinalClosureClaimInventory.v1", + "status": "PLANNED_NOT_ACCEPTED", + "authority": "Actual Lean declarations and fully quantified premises govern claims. Do not infer empirical Exact GR/PR.", + "source_baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "claims": [ + { + "id": "Q1", + "title": "Canonical-source discounted output", + "source": "PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean", + "accepted": false, + "claimed_in_draft_only": true + }, + { + "id": "Q2", + "title": "Original full comparison margin", + "source": "PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean", + "accepted": false, + "claimed_in_draft_only": true + }, + { + "id": "Q3", + "title": "Typed quotient adversaries", + "source": "PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean", + "accepted": false, + "claimed_in_draft_only": true + }, + { + "id": "M1", + "title": "Admissible matched protocols", + "source": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean", + "accepted": false, + "claimed_in_draft_only": true + }, + { + "id": "M2", + "title": "Full B1 matched original margin", + "source": "PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + "accepted": false, + "claimed_in_draft_only": true + }, + { + "id": "M3", + "title": "Mixed-start and asymmetric adversaries", + "source": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean", + "accepted": false, + "claimed_in_draft_only": true + }, + { + "id": "N1", + "title": "Native recurrent stationary law", + "source": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean", + "accepted": false, + "claimed_in_draft_only": true + }, + { + "id": "N2", + "title": "Actual infinite-path nonlinear score", + "source": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean", + "accepted": false, + "claimed_in_draft_only": true + }, + { + "id": "N3", + "title": "Exact C11 general class functional", + "source": "PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean", + "accepted": false, + "claimed_in_draft_only": true + }, + { + "id": "D8", + "title": "Typed restricted admission", + "source": "PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean", + "accepted": false, + "claimed_in_draft_only": true + }, + { + "id": "D9", + "title": "Classwise expected nonlinear functional", + "source": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "accepted": false, + "claimed_in_draft_only": true + } + ], + "exclusions": [ + "No empirical GR/PR certification", + "No unconditional all-policy superiority", + "No Exact GR transport from approximate quotient", + "No source-independent Lean certificate", + "No automatic full45 acceptance" + ] +} diff --git a/research/paper_closures/post_merge_112/detached_receipts_v2.schema.json b/research/paper_closures/post_merge_112/detached_receipts_v2.schema.json new file mode 100644 index 00000000..69b98ced --- /dev/null +++ b/research/paper_closures/post_merge_112/detached_receipts_v2.schema.json @@ -0,0 +1,757 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/HMarcusWH/PermanssonLean/blob/research/final-mathematical-closure/research/paper_closures/post_merge_112/detached_receipts_v2.schema.json", + "title": "Permansson detached source/reviewer evidence v2", + "description": "External immutable overlay to be authenticated after frozen source H. JSON source flags alone cannot authorize acceptance.", + "type": "object", + "additionalProperties": false, + "required": [ + "format", + "phase", + "base_sha", + "head_sha", + "synthetic_sha", + "merged_main_sha", + "evidence_bundle_sha256", + "source_bound_ci", + "compiler_axiom_receipts", + "positive_test_receipts", + "semantic_negative_receipts", + "case_decisions", + "claim_decisions", + "independent_source_reviews", + "trusted_axioms" + ], + "properties": { + "format": { + "const": "Permansson.FinalClosureDetachedReceipts.v2" + }, + "phase": { + "enum": [ + "premerge_review", + "postmerge_acceptance" + ] + }, + "base_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "synthetic_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "merged_main_sha": { + "oneOf": [ + { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + { + "type": "null" + } + ] + }, + "evidence_bundle_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "source_bound_ci": { + "type": "array", + "minItems": 7, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "id", + "head_sha" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + } + } + } + }, + "merged_bound_ci": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "id", + "head_sha" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + } + } + } + }, + "compiler_axiom_receipts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "head_sha", + "path", + "declaration", + "source_sha256", + "statement_sha256", + "transitive_axioms", + "compiler_check", + "compiler_result", + "check_run_id", + "axiom_log_member", + "axiom_log_sha256", + "case_ids", + "claim_ids", + "elaborated_type_sha256", + "declaration_kind", + "universe_params_repr" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "check_run_id": { + "oneOf": [ + { + "type": "integer", + "minimum": 1 + }, + { + "type": "null" + } + ] + }, + "path": { + "type": "string", + "minLength": 1 + }, + "declaration": { + "type": "string", + "pattern": "^[A-Za-z0-9_'.]+\\.[A-Za-z0-9_']+$" + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "statement_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "transitive_axioms": { + "type": "array", + "uniqueItems": true, + "items": { + "enum": [ + "Classical.choice", + "Quot.sound", + "propext" + ] + } + }, + "compiler_check": { + "const": "research" + }, + "compiler_result": { + "const": "PASS" + }, + "axiom_log_member": { + "type": "string", + "minLength": 1 + }, + "axiom_log_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "case_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "claim_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "elaborated_type_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "declaration_kind": { + "enum": [ + "axiom", + "definition", + "theorem", + "opaque", + "quotient", + "inductive", + "constructor", + "recursor" + ] + }, + "universe_params_repr": { + "type": "string", + "minLength": 1 + } + } + } + }, + "positive_test_receipts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "head_sha", + "test_path", + "test_name", + "source_sha256", + "case_ids", + "positive_control", + "outcome", + "compiler_exit_code", + "log_member", + "log_sha256", + "check_run_id", + "claim_ids" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "check_run_id": { + "oneOf": [ + { + "type": "integer", + "minimum": 1 + }, + { + "type": "null" + } + ] + }, + "test_path": { + "type": "string", + "minLength": 1 + }, + "test_name": { + "type": "string", + "pattern": "^test_[A-Za-z0-9_]+$" + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "case_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "positive_control": { + "type": "object", + "additionalProperties": false, + "required": [ + "compiled", + "exit_code", + "lean_toolchain", + "log_member", + "log_sha256" + ], + "properties": { + "compiled": { + "const": true + }, + "exit_code": { + "const": 0 + }, + "lean_toolchain": { + "const": "4.34.0" + }, + "log_member": { + "type": "string", + "minLength": 1 + }, + "log_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + } + }, + "outcome": { + "enum": [ + "PASS", + "REJECTED_SEMANTIC" + ] + }, + "compiler_exit_code": { + "type": "integer" + }, + "log_member": { + "type": "string", + "minLength": 1 + }, + "log_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "mutant_source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "claim_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + } + } + } + }, + "semantic_negative_receipts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "head_sha", + "test_path", + "test_name", + "source_sha256", + "case_ids", + "positive_control", + "outcome", + "compiler_exit_code", + "log_member", + "log_sha256", + "check_run_id", + "mutant_source_member", + "mutant_source_sha256", + "original_lean_source_path", + "original_lean_source_sha256", + "original_lean_source_member", + "claim_ids" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "check_run_id": { + "oneOf": [ + { + "type": "integer", + "minimum": 1 + }, + { + "type": "null" + } + ] + }, + "test_path": { + "type": "string", + "minLength": 1 + }, + "test_name": { + "type": "string", + "pattern": "^test_[A-Za-z0-9_]+$" + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "case_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "positive_control": { + "type": "object", + "additionalProperties": false, + "required": [ + "compiled", + "exit_code", + "lean_toolchain", + "log_member", + "log_sha256" + ], + "properties": { + "compiled": { + "const": true + }, + "exit_code": { + "const": 0 + }, + "lean_toolchain": { + "const": "4.34.0" + }, + "log_member": { + "type": "string", + "minLength": 1 + }, + "log_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + } + }, + "outcome": { + "enum": [ + "PASS", + "REJECTED_SEMANTIC" + ] + }, + "compiler_exit_code": { + "type": "integer" + }, + "log_member": { + "type": "string", + "minLength": 1 + }, + "log_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "mutant_source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "mutant_source_member": { + "type": "string", + "minLength": 1 + }, + "original_lean_source_path": { + "type": "string", + "minLength": 1 + }, + "original_lean_source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "claim_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "original_lean_source_member": { + "type": "string", + "minLength": 1 + } + } + } + }, + "case_decisions": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "case_id", + "decision", + "required_expected_result_sha256", + "proof_ids", + "positive_ids", + "negative_ids", + "review_id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "case_id": { + "type": "string", + "minLength": 1 + }, + "decision": { + "enum": [ + "VERIFIED_SELECTED_SCOPE", + "VERIFIED_NEGATIVE" + ] + }, + "required_expected_result_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "proof_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "positive_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "negative_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "review_id": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "claim_decisions": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "claim_id", + "proof_ids", + "positive_ids", + "negative_ids", + "review_id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "claim_id": { + "type": "string", + "minLength": 1 + }, + "proof_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "review_id": { + "type": "integer", + "minimum": 1 + }, + "positive_ids": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "negative_ids": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + } + } + } + }, + "independent_source_reviews": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "minimum": 1 + } + }, + "additionalProperties": false + } + }, + "actual_synthetic_parents": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "actual_merged_parents": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "synthetic_tree_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "merged_tree_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "trusted_axioms": { + "type": "array", + "uniqueItems": true, + "minItems": 3, + "maxItems": 3, + "items": { + "enum": [ + "Classical.choice", + "Quot.sound", + "propext" + ] + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "phase": { + "const": "premerge_review" + } + } + }, + "then": { + "properties": { + "merged_main_sha": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "phase": { + "const": "postmerge_acceptance" + } + } + }, + "then": { + "properties": { + "merged_main_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "compiler_axiom_receipts": { + "items": { + "properties": { + "check_run_id": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "positive_test_receipts": { + "items": { + "properties": { + "check_run_id": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "semantic_negative_receipts": { + "items": { + "properties": { + "check_run_id": { + "type": "integer", + "minimum": 1 + } + } + } + } + } + } + } + ] +} diff --git a/research/paper_closures/post_merge_112/detached_receipts_v3.schema.json b/research/paper_closures/post_merge_112/detached_receipts_v3.schema.json new file mode 100644 index 00000000..be36ded8 --- /dev/null +++ b/research/paper_closures/post_merge_112/detached_receipts_v3.schema.json @@ -0,0 +1,925 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/HMarcusWH/PermanssonLean/blob/research/final-mathematical-closure/research/paper_closures/post_merge_112/detached_receipts_v3.schema.json", + "title": "Permansson exact-H producer-authenticated detached proof/test receipt v3", + "description": "Strict versioned producer-bound source/review schema. Shape validity alone NEVER authenticates GitHub or approves mathematics.", + "type": "object", + "additionalProperties": false, + "required": [ + "format", + "phase", + "base_sha", + "head_sha", + "synthetic_sha", + "merged_main_sha", + "evidence_bundle_sha256", + "source_bound_ci", + "compiler_axiom_receipts", + "positive_test_receipts", + "semantic_negative_receipts", + "case_decisions", + "claim_decisions", + "independent_source_reviews", + "trusted_axioms", + "producer_artifacts" + ], + "properties": { + "format": { + "const": "Permansson.FinalClosureDetachedReceipts.v3" + }, + "phase": { + "enum": [ + "premerge_review", + "postmerge_acceptance" + ] + }, + "base_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "synthetic_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "merged_main_sha": { + "oneOf": [ + { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + { + "type": "null" + } + ] + }, + "evidence_bundle_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "source_bound_ci": { + "type": "array", + "minItems": 12, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "id", + "head_sha" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + } + } + } + }, + "merged_bound_ci": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "id", + "head_sha" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + } + } + } + }, + "compiler_axiom_receipts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "head_sha", + "path", + "declaration", + "source_sha256", + "statement_sha256", + "transitive_axioms", + "compiler_check", + "compiler_result", + "check_run_id", + "axiom_log_member", + "axiom_log_sha256", + "case_ids", + "claim_ids", + "elaborated_type_sha256", + "declaration_kind", + "universe_params_repr", + "producer_artifact_id", + "producer_member_bindings" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "check_run_id": { + "type": "integer", + "minimum": 1 + }, + "path": { + "type": "string", + "minLength": 1 + }, + "declaration": { + "type": "string", + "pattern": "^[A-Za-z0-9_'.]+\\.[A-Za-z0-9_']+$" + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "statement_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "transitive_axioms": { + "type": "array", + "uniqueItems": true, + "items": { + "enum": [ + "Classical.choice", + "Quot.sound", + "propext" + ] + } + }, + "compiler_check": { + "const": "pr113-compiler-shard" + }, + "compiler_result": { + "const": "PASS" + }, + "axiom_log_member": { + "type": "string", + "minLength": 1 + }, + "axiom_log_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "case_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "claim_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "elaborated_type_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "declaration_kind": { + "enum": [ + "axiom", + "definition", + "theorem", + "opaque", + "quotient", + "inductive", + "constructor", + "recursor" + ] + }, + "universe_params_repr": { + "type": "string", + "minLength": 1 + }, + "producer_artifact_id": { + "type": "integer", + "minimum": 1 + }, + "producer_member_bindings": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "outer_member", + "source_member" + ], + "properties": { + "outer_member": { + "type": "string", + "minLength": 1 + }, + "source_member": { + "type": "string", + "minLength": 1 + } + } + } + } + } + } + }, + "positive_test_receipts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "head_sha", + "test_path", + "test_name", + "source_sha256", + "case_ids", + "positive_control", + "outcome", + "compiler_exit_code", + "log_member", + "log_sha256", + "check_run_id", + "claim_ids", + "producer_artifact_id", + "producer_member_bindings" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "check_run_id": { + "type": "integer", + "minimum": 1 + }, + "test_path": { + "type": "string", + "minLength": 1 + }, + "test_name": { + "type": "string", + "pattern": "^test_[A-Za-z0-9_]+$" + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "case_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "positive_control": { + "type": "object", + "additionalProperties": false, + "required": [ + "compiled", + "exit_code", + "lean_toolchain", + "log_member", + "log_sha256" + ], + "properties": { + "compiled": { + "const": true + }, + "exit_code": { + "const": 0 + }, + "lean_toolchain": { + "const": "4.34.0" + }, + "log_member": { + "type": "string", + "minLength": 1 + }, + "log_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + } + }, + "outcome": { + "enum": [ + "PASS", + "REJECTED_SEMANTIC" + ] + }, + "compiler_exit_code": { + "type": "integer" + }, + "log_member": { + "type": "string", + "minLength": 1 + }, + "log_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "mutant_source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "claim_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "producer_artifact_id": { + "type": "integer", + "minimum": 1 + }, + "producer_member_bindings": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "outer_member", + "source_member" + ], + "properties": { + "outer_member": { + "type": "string", + "minLength": 1 + }, + "source_member": { + "type": "string", + "minLength": 1 + } + } + } + } + } + } + }, + "semantic_negative_receipts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "head_sha", + "test_path", + "test_name", + "source_sha256", + "case_ids", + "positive_control", + "outcome", + "compiler_exit_code", + "log_member", + "log_sha256", + "check_run_id", + "mutant_source_member", + "mutant_source_sha256", + "original_lean_source_path", + "original_lean_source_sha256", + "original_lean_source_member", + "claim_ids", + "producer_artifact_id", + "producer_member_bindings" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "check_run_id": { + "type": "integer", + "minimum": 1 + }, + "test_path": { + "type": "string", + "minLength": 1 + }, + "test_name": { + "type": "string", + "pattern": "^test_[A-Za-z0-9_]+$" + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "case_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "positive_control": { + "type": "object", + "additionalProperties": false, + "required": [ + "compiled", + "exit_code", + "lean_toolchain", + "log_member", + "log_sha256" + ], + "properties": { + "compiled": { + "const": true + }, + "exit_code": { + "const": 0 + }, + "lean_toolchain": { + "const": "4.34.0" + }, + "log_member": { + "type": "string", + "minLength": 1 + }, + "log_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + } + }, + "outcome": { + "enum": [ + "PASS", + "REJECTED_SEMANTIC" + ] + }, + "compiler_exit_code": { + "type": "integer" + }, + "log_member": { + "type": "string", + "minLength": 1 + }, + "log_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "mutant_source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "mutant_source_member": { + "type": "string", + "minLength": 1 + }, + "original_lean_source_path": { + "type": "string", + "minLength": 1 + }, + "original_lean_source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "claim_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "original_lean_source_member": { + "type": "string", + "minLength": 1 + }, + "producer_artifact_id": { + "type": "integer", + "minimum": 1 + }, + "producer_member_bindings": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "outer_member", + "source_member" + ], + "properties": { + "outer_member": { + "type": "string", + "minLength": 1 + }, + "source_member": { + "type": "string", + "minLength": 1 + } + } + } + } + } + } + }, + "case_decisions": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "case_id", + "decision", + "required_expected_result_sha256", + "proof_ids", + "positive_ids", + "negative_ids", + "review_id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "case_id": { + "type": "string", + "minLength": 1 + }, + "decision": { + "enum": [ + "VERIFIED_SELECTED_SCOPE", + "VERIFIED_NEGATIVE" + ] + }, + "required_expected_result_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "proof_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "positive_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "negative_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "review_id": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "claim_decisions": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "claim_id", + "proof_ids", + "positive_ids", + "negative_ids", + "review_id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "claim_id": { + "type": "string", + "minLength": 1 + }, + "proof_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "review_id": { + "type": "integer", + "minimum": 1 + }, + "positive_ids": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "negative_ids": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + } + } + } + }, + "independent_source_reviews": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "minimum": 1 + } + }, + "additionalProperties": false + } + }, + "actual_synthetic_parents": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "actual_merged_parents": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "synthetic_tree_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "merged_tree_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "trusted_axioms": { + "type": "array", + "uniqueItems": true, + "minItems": 3, + "maxItems": 3, + "items": { + "enum": [ + "Classical.choice", + "Quot.sound", + "propext" + ] + } + }, + "producer_artifacts": { + "type": "array", + "minItems": 21, + "maxItems": 21, + "uniqueItems": true, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "format", + "status", + "head_sha", + "workflow_path", + "run_id", + "run_attempt", + "job_id", + "actual_check_run_id", + "artifact_id", + "artifact_name", + "artifact_sha256", + "manifest_sha256", + "mathematical_acceptance", + "independent_review", + "scientific_release_approved", + "merged_main_sha", + "evidence_zip_member" + ], + "properties": { + "format": { + "const": "Permansson.PR113.AuthenticatedProducerArtifact.v1" + }, + "status": { + "const": "CI_ARTIFACT_DIGEST_VERIFIED_UNREVIEWED" + }, + "head_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "workflow_path": { + "type": "string", + "minLength": 1 + }, + "run_id": { + "type": "integer", + "minimum": 1 + }, + "run_attempt": { + "type": "integer", + "minimum": 1 + }, + "job_id": { + "type": "integer", + "minimum": 1 + }, + "actual_check_run_id": { + "type": "integer", + "minimum": 1 + }, + "artifact_id": { + "type": "integer", + "minimum": 1 + }, + "artifact_name": { + "type": "string", + "minLength": 1 + }, + "artifact_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "manifest_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "case_ids_unreviewed": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "mathematical_acceptance": { + "const": false + }, + "independent_review": { + "const": false + }, + "scientific_release_approved": { + "const": false + }, + "merged_main_sha": { + "type": "null" + }, + "evidence_zip_member": { + "type": "string", + "minLength": 1 + } + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "phase": { + "const": "premerge_review" + } + } + }, + "then": { + "properties": { + "merged_main_sha": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "phase": { + "const": "postmerge_acceptance" + } + } + }, + "then": { + "properties": { + "merged_main_sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "compiler_axiom_receipts": { + "items": { + "properties": { + "check_run_id": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "positive_test_receipts": { + "items": { + "properties": { + "check_run_id": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "semantic_negative_receipts": { + "items": { + "properties": { + "check_run_id": { + "type": "integer", + "minimum": 1 + } + } + } + } + } + } + } + ] +} diff --git a/research/paper_closures/post_merge_112/residual_scope.json b/research/paper_closures/post_merge_112/residual_scope.json new file mode 100644 index 00000000..7d88060c --- /dev/null +++ b/research/paper_closures/post_merge_112/residual_scope.json @@ -0,0 +1,18 @@ +{ + "format": "Permansson.FinalClosureResidualScope.v1", + "source_baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "open_programmes": [ + "NEXT-08: general randomized/history-dependent asymptotic optimization", + "NEXT-09: specialized algebraic/causal identification", + "NEXT-10: prospective independently replicated empirical regime", + "General external-model JSON to alpha-P-U certification" + ], + "mandatory_scoped_gates": [ + "P-04 actual-source both-side bound", + "P-05 admitted binary CL/OL margin", + "P-06 native actual occupation score", + "D-08 true restrictive grammar menu", + "D-09 distinct physical bottom classes and nonlinear averaging" + ], + "disclosure": "All wider research questions remain open unless independently formalized; a single PR is not empirical validation." +} diff --git a/research/paper_closures/post_merge_112/review_receipts.json b/research/paper_closures/post_merge_112/review_receipts.json new file mode 100644 index 00000000..470ce229 --- /dev/null +++ b/research/paper_closures/post_merge_112/review_receipts.json @@ -0,0 +1,8 @@ +{ + "format": "Permansson.FinalClosureReviews.v1", + "baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "source_reviews": [], + "academic_reviews": [], + "reviewer_signoff": "NOT_RECEIVED", + "independent_review": false +} diff --git a/research/paper_closures/post_merge_112/reviewer_authority.json b/research/paper_closures/post_merge_112/reviewer_authority.json new file mode 100644 index 00000000..9f6b3f31 --- /dev/null +++ b/research/paper_closures/post_merge_112/reviewer_authority.json @@ -0,0 +1,7 @@ +{ + "format": "Permansson.ReviewerAuthority.v1", + "status": "UNCONFIGURED", + "source_reviewers": [], + "scholarly_reviewers": [], + "instruction": "Explicit owner authorization and independent reviewer disclosure are required before an acceptance review can count; this template grants no one authority." +} diff --git a/research/paper_closures/post_merge_112/source_ci_index.template.json b/research/paper_closures/post_merge_112/source_ci_index.template.json new file mode 100644 index 00000000..82aaa160 --- /dev/null +++ b/research/paper_closures/post_merge_112/source_ci_index.template.json @@ -0,0 +1,18 @@ +{ + "format": "Permansson.FinalClosureSourceReceiptTemplate.v1", + "source_baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "source_baseline_tree": "1bd1c31a67d28c97b82d6234d6c013b31336a0fd", + "branch": "research/final-mathematical-closure", + "pr_number": 113, + "branch_head_sha": null, + "pr_synthetic_merge_sha": null, + "post_merge_main_sha": null, + "ci_receipts": [], + "proof_statements": [], + "preservation_receipts": [], + "status": "TEMPLATE_AWAITING_REAL_HEAD_AND_REVIEWS", + "historical_receipt_limits": { + "original_17_679_census": "UNVERIFIED_DO_NOT_CITE", + "constitution_core": "RETIRED_UNRECONCILED_OWNER_RECEIPT_BINDING" + } +} diff --git a/research/paper_closures/post_merge_112/statement_receipts.schema.json b/research/paper_closures/post_merge_112/statement_receipts.schema.json new file mode 100644 index 00000000..eba2fa78 --- /dev/null +++ b/research/paper_closures/post_merge_112/statement_receipts.schema.json @@ -0,0 +1,44 @@ +{ + "schema_version": "1.0", + "fields": { + "declaration": { + "type": "string", + "required": true + }, + "path": { + "type": "string", + "required": true + }, + "source_sha256": { + "type": "sha256", + "required": true + }, + "statement_sha256": { + "type": "sha256", + "required": true + }, + "reviewed_head_sha": { + "type": "sha40", + "required": true + }, + "axiom_allowlist": { + "type": "array", + "required": true + }, + "compiler_run_url": { + "type": "url", + "required": true + }, + "compiler_result": { + "enum": [ + "PASS", + "FAIL", + "BLOCKED" + ] + }, + "negative_mutant_receipts": { + "type": "array", + "required": true + } + } +} diff --git a/research/paper_closures/post_merge_112/theorem_crosswalk.json b/research/paper_closures/post_merge_112/theorem_crosswalk.json new file mode 100644 index 00000000..f3328be6 --- /dev/null +++ b/research/paper_closures/post_merge_112/theorem_crosswalk.json @@ -0,0 +1,1129 @@ +{ + "format": "Permansson.FinalClosureCrosswalk.v1", + "evaluated_main_sha": "3931e4dccafb47b3cdf70177c0cf337102c90731", + "historical_contract_count": 42, + "contracts": [ + { + "atlas_id": "X1-N01", + "historical_selected_scope": "The exact accepted two modes and baseline/action/update/joint variants; every pair of eighteen states.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "The original finite X1 model, its state ordering and both modes are fixed.", + "The action uses the current memory; learning uses the old world state; resetting changes only the next memory." + ], + "historical_declared_residual": [ + "No new arbitrary intervention family or world model is certified by this fixed example." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/X1/InterventionNumericalBridge.lean", + "PermanssonResearch/X1/ExactMatrixTables.lean" + ], + "acceptance_case_ids": [ + "X-01", + "X-05" + ] + }, + { + "atlas_id": "X1-N02", + "historical_selected_scope": "All-horizon rational-to-actual terminal evaluation; checked literal tables at horizons zero through three.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "Finite original X1 state/action spaces and original chronological alpha-P-U semantics.", + "The table fixture is specifically horizons 0,1,2,3; the induction theorem is separately all-horizon." + ], + "historical_declared_residual": [ + "No certificate for arbitrary externally supplied terminal tables or changed properties is inferred." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/X1/InterventionNumericalBridge.lean", + "PermanssonResearch/X1/ExactTerminalTables.lean" + ], + "acceptance_case_ids": [ + "X-02", + "X-05" + ] + }, + { + "atlas_id": "X1-N03", + "historical_selected_scope": "Six original constitutiveMargin equalities on all nine starts with lower bounds and attaining starts.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "The original comparison set is retained, including every equilibrium tie.", + "Nonempty/bounded-below infimum hypotheses and the actual original interventions are used." + ], + "historical_declared_residual": [ + "The preserved replete_not_exactGR leakage theorem remains valid. Positive finite margins do not establish exact invariance." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/X1/ExactMargins.lean" + ], + "acceptance_case_ids": [ + "X-03", + "X-04", + "X-06" + ] + }, + { + "atlas_id": "X1-S01", + "historical_selected_scope": "Bounded finite discounted stochastic games, arbitrary nonanticipating behavioral unilateral deviations, independent current opponent draw.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "Finite state/actions; real-valued probability rows; 0 <= beta < 1 and bounded reward/value.", + "The deviator sees completed history, not the current opponent draw or future noise.", + "The opponent uses the fixed declared equilibrium action law; the learner mode changes memory updates, not that opponent action assumption." + ], + "historical_declared_residual": [ + "This does not prove clairvoyant, current-noise-correlated, changed-world or risk-sensitive optimality, nor unique optimality. The learner action policy is not silently declared an equilibrium." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Games/DiscountedVerification.lean", + "PermanssonResearch/Games/DiscountedTraceExpectation.lean", + "PermanssonResearch/X1/FullStrategyCertificate.lean", + "PermanssonResearch/X1/StrategyOriginalBridge.lean", + "PermanssonResearch/X1/FullStrategyTrace.lean" + ], + "acceptance_case_ids": [ + "S-01", + "S-02" + ] + }, + { + "atlas_id": "F1-MA2", + "historical_selected_scope": "Finite autonomous strict anchors with full baseline-supported action/update agreement; separately standard-Borel all-time AE extension.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Original strict nonreactivity and exogenous common updates yield actual factor kernels.", + "Finite support agreement includes the opponent update on actual realized action/world inputs.", + "The AE theorem uses explicit measurable factor kernels, the relevant standard-Borel spaces, and action plus update agreement under their different actual baseline laws.", + "The treated mechanism is the stationary PlayerOneMechanism type in these theorems." + ], + "historical_declared_residual": [ + "Weaker agreement, endogenous common channels, other information structures or history-dependent treated mechanisms need their own theorem; no broader strict-MA2 impossibility is asserted." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/MultiAgent/AutonomousMarkedKernel.lean", + "PermanssonResearch/MultiAgent/AutonomousSupportClosure.lean", + "PermanssonResearch/MultiAgent/StrictMA2Obstruction.lean", + "PermanssonResearch/MultiAgent/AutonomousAEMechanisms.lean", + "PermanssonResearch/MultiAgent/AutonomousAEContinuumFixture.lean" + ], + "acceptance_case_ids": [ + "M-01", + "M-02" + ] + }, + { + "atlas_id": "E1-G01", + "historical_selected_scope": "Finite spaces or measurable kernel common-mass and jointly measurable Radon-Nikodym lift profile; every fine row.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Actual homogeneous fine K and coarse Kbar, measurable q, every-fine-row HasQuotientDefect <= eta, 0<=eta<=1, and matched fine Dirac y/coarse Dirac q(y) starts for the exported quotient theorem.", + "The RN wrapper has the declared CountableOrCountablyGenerated profile. The common-subkernel wrapper accepts an actual jointly measurable R<=K with projected R dominated by Kbar(q(y)) and row mass at least 1-eta.", + "Original margin wrapper has a type-respecting state compression, paired actual baseline/intervention defects, source TV-Lipschitz finite-prefix property and the complete coarse comparison set as the image of the original fine set." + ], + "historical_declared_residual": [ + "No unrestricted measurable RN lifting claim.", + "Chapter04:49-51 also describes varying eta_t and an initial common mass. DependentHistoryCommonMass proves those retained products on a common dependent history type, but there is no exported varying-kernel/mismatched-start fine-hidden/coarse-projected adapter. Keep that expanded projected variant open rather than infer it from the generic product theorem." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Probability/ProjectedCommonSubkernel.lean", + "PermanssonResearch/Quotient/GeometricQuotient.lean", + "PermanssonResearch/Quotient/GeometricPrefix.lean", + "PermanssonResearch/Quotient/GeometricMargin.lean", + "PermanssonResearch/Probability/DependentHistoryCommonMass.lean" + ], + "acceptance_case_ids": [ + "Q-01", + "Q-02" + ] + }, + { + "atlas_id": "A3-K01", + "historical_selected_scope": "Finite metric probabilities; larger complete separable Borel space with finite first moments and explicit finite quantization proof.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite profile: finite pseudometric space with measurable singletons, probability measures, actual distance costs. Theorems retain arbitrary real atom weights and distances.", + "Larger KR profile: separable pseudometric Borel space; both probability laws integrate distance to an anchor. Completeness is unnecessary for the equality itself.", + "P1 completeness profile: complete separable metric Borel space and a chosen anchor; the metric is the constructed coupling W1.", + "IsKRTest means individually bounded measurable one-Lipschitz functions; it does not impose a shared supnorm-one restriction." + ], + "historical_declared_residual": [ + "General optimal coupling or optimal potential attainment on arbitrary Polish spaces is not exported; equality and approximate couplings are sufficient for the selected target.", + "There is no unrestricted sigma-algebra or infinite-first-moment KR assertion." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Probability/FiniteKantorovichDuality.lean", + "PermanssonResearch/Probability/FirstMomentKantorovichDuality.lean", + "PermanssonResearch/Probability/FirstMomentCompleteness.lean" + ], + "acceptance_case_ids": [ + "A-01" + ] + }, + { + "atlas_id": "A3-I01", + "historical_selected_scope": "Contraction preserving first-moment probabilities on a complete separable metric space; finite shortcut also specified.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Complete separable metric/Borel state space with anchor; actual Markov kernel; preservation of first moments; exact source IsW1Contraction K rho with 0 <= rho < 1.", + "FirstMomentKernel derives preservation from explicit row anchor-moment integrability and an affine drift upper bound. Supplying preservation directly is also permitted.", + "GeneratedInvariantPerturbation requires each of K and Kt to preserve P1 and have its own strict contraction coefficient. Its defect estimate uses the baseline coefficient.", + "Finite route requires a finite genuine metric space; it supplies existence from the finite invariant construction and uniqueness from contraction." + ], + "historical_declared_residual": [ + "Uniqueness is within the declared P1 class, not among all probability measures with no moment restrictions.", + "A small row defect alone does not establish perturbed existence or uniqueness." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Perturbation/FirstMomentKernel.lean", + "PermanssonResearch/Perturbation/FirstMomentInvariant.lean", + "PermanssonResearch/Perturbation/GeneratedInvariantPerturbation.lean", + "PermanssonResearch/Perturbation/FiniteInvariantConstructor.lean" + ], + "acceptance_case_ids": [ + "A-02" + ] + }, + { + "atlas_id": "A3-C01", + "historical_selected_scope": "Direct bounded-Lipschitz row criterion giving the exact existing global dual predicate; finite Dobrushin and metric row-coupling certificates.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "K is Markov; rho >= 0; for every original IsKRTest f and every x,y, |Kf(x)-Kf(y)| <= rho*dist(x,y).", + "The conclusion quantifies over every probability law and every real admissible input dual bound. W1DualLE.nonneg handles impossible negative input bounds.", + "TV conversion retains finite diameter D and positive separation ell, giving D*beta/ell; only the discrete unit metric has coefficient exactly the Dobrushin TV coefficient.", + "Finite typed conversion requires positive weights a,b and four separate factor/min(a,b)<1 gates before original-margin integration." + ], + "historical_declared_residual": [ + "The theorem proves a criterion; an application must supply genuine row or primitive bounds and prove strictness. A TV coefficient on an unbounded metric does not suffice." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Perturbation/DirectDualContraction.lean", + "PermanssonResearch/Perturbation/DobrushinContraction.lean", + "PermanssonResearch/Perturbation/FiniteStateMetricContraction.lean", + "PermanssonResearch/Perturbation/FiniteMetricProtocol.lean" + ], + "acceptance_case_ids": [ + "A-02" + ] + }, + { + "atlas_id": "A3-F01", + "historical_selected_scope": "Explicit occupation-limit functional, four actual finite unique-class kernels; separate regenerative Polish profile with added moment conditions.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "A single property is defined in advance from descriptor h, fallback, and observable Phi. It is measurable on all paths; nonconvergent paths use that fixed fallback.", + "Finite route: finite state and descriptor metrics; each of the four actual kernels has a unique recurrent class, or its own strict contraction from which that class is derived. The law functional is continuous and has the literal old global IsW1LipschitzFunctional bound.", + "Regenerative route: for every one of the four actual kernels, Data supplies an actual row decomposition K=(1-r)*seed+r*R with 0<=r<1, Markov R and probability seed. This independently constructs the stationary geometric residual-orbit mixture.", + "Bounded weak route uses continuous h and bounded weakly continuous Phi; compact descriptor route supplies a full probability-valued measurable occupation selector.", + "P1 route: complete separable metric/Borel descriptor; measurable h; genuine regenerative distance-reward summability for each descriptor target, and state moment budgets where invariant perturbation needs them. PhiB is bounded and continuous on P1 and identified with a Measure-level Phi satisfying the literal global dual-Lipschitz predicate.", + "Original margin comparison retains the original common comparison-state set, baseline/intervention contraction and defect budgets, and all four actual induced kernels." + ], + "historical_declared_residual": [ + "The result is for declared occupation functionals, not every arbitrary previously frozen survival or time-zero property.", + "On a general noncompact weak space, the bounded weak route uses a scalar Phi(empirical) limit; a universal full weak-law selector is supplied only in compact or complete P1 profiles.", + "Bounded continuous Phi is an explicit selected assumption in chapter05:110; arbitrary unbounded P1-Lipschitz observables are not asserted.", + "An explicit regeneration/moment certificate is still required for each of the four kernels; contraction alone does not prove the needed path factorization for arbitrary functionals." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Perturbation/FiniteOccupationFactorization.lean", + "PermanssonResearch/Perturbation/RegenerativeOccupationFactorization.lean", + "PermanssonResearch/MultipleLimit/RegenerativeFirstMomentOccupation.lean", + "PermanssonResearch/MultipleLimit/RegenerativeDescriptorTails.lean", + "PermanssonResearch/Perturbation/RegenerativeFirstMomentFactorization.lean" + ], + "acceptance_case_ids": [ + "A-04", + "A-05" + ] + }, + { + "atlas_id": "A3-P01", + "historical_selected_scope": "Weighted product metric and conditional stage couplings with declared action/world/update errors and Lipschitz sensitivities.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite nonempty S,X,A with supplied metrics and measurable singletons; actual typed StrategicWorldModel primitives.", + "PrimitiveMetricBounds gives uniform all-input primitive W1DualLE errors and reference action/next-world Lipschitz sensitivities; the comparison reference in the sequential proof is explicit.", + "a,b>0. Weighted budget is a*deltaU+(b+a*LUx)*deltaP+[a*LUa+(b+a*LUx)*LPa]*deltaAlpha.", + "The frozen product metric is max(dS,dX). Conversion divides the weighted output budget by min(a,b). Same-model input bounds must be stated relative to the intended incoming metric, and all four converted factors must be below one." + ], + "historical_declared_residual": [ + "The selected finite implementation is expressly allowed by chapter05:114. General measurable selection of optimal primitive couplings on arbitrary unbounded spaces is not exported.", + "The bounded-diameter TV-to-dual primitive wrapper remains a separate route and retains its D factor." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Perturbation/FiniteTypedRows.lean", + "PermanssonResearch/Perturbation/FiniteSequentialCoupling.lean", + "PermanssonResearch/Perturbation/FinitePrimitiveMetric.lean", + "PermanssonResearch/Perturbation/FiniteStateMetricContraction.lean", + "PermanssonResearch/Perturbation/FiniteMetricProtocol.lean" + ], + "acceptance_case_ids": [ + "A-03" + ] + }, + { + "atlas_id": "D2-R01", + "historical_selected_scope": "Finite fully observed known rational controlled process and literal retryable residence event.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "Finite fully observed known rational physical rows and nonempty state-dependent finite control menus.", + "Monitor reads Y0; m transitions means m+1 observations; entry T uses horizon T+m.", + "The physical row continues after the monitor success flag becomes sticky." + ], + "historical_declared_residual": [ + "No physical absorption or arbitrary nonlinear objective; entry and completion deadlines remain distinct. The stated complexity is arithmetic-operation complexity, not exact-rational bit complexity." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/ReverseSolver/CompactResidenceMonitor.lean", + "PermanssonResearch/ReverseSolver/MonitorEventOptimality.lean", + "PermanssonResearch/ReverseSolver/MonitorHistoryBridge.lean", + "PermanssonResearch/ReverseSolver/RollingResidenceTable.lean" + ], + "acceptance_case_ids": [ + "D-01", + "D-02" + ] + }, + { + "atlas_id": "A2-P01", + "historical_selected_scope": "Actual ordered primitive composition with measurable common kernels at each stage and uniform stage errors.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "Actual ordered alpha/P/U primitives from two typed models; every delta lies in [0,1].", + "The common-part construction requires the three explicit CountableOrCountablyGenerated source/target instances; finite spaces satisfy them.", + "All required primitive input rows, including off-graph and intervention-reachable rows, satisfy the uniform primitive bounds. World-law differences are between-model perturbations." + ], + "historical_declared_residual": [ + "Q-03 requires an actual correlated-stage/product-saturating semantic example. The generic theorem is correct, but that stronger acceptance fixture is not supplied at H." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Perturbation/PrimitiveProductTV.lean", + "PermanssonResearch/Perturbation/PrimitiveMetricBudget.lean" + ], + "acceptance_case_ids": [ + "Q-03" + ] + }, + { + "atlas_id": "A2-P02", + "historical_selected_scope": "Nonstationary history-kernel hybrid with initial TV and expected every-step row defects; separate baseline/intervention visit budgets.", + "historical_disposition": "CORE_THEOREM_FORMALIZED_INTEGRATION_OPEN", + "historical_declared_hypotheses": [ + "Actual probability initial laws mu,nu and Markov K_t,L_t on a common history/output type; measurable append maps. Histories evolve by the actual composition-product/map recurrence.", + "d_t is measurable and nonnegative and bounds actual row event TV at every history. e_t>=0 and the extended nonnegative integral of ENNReal.ofReal(d_t) under the actual FIRST history law is at most ENNReal.ofReal(e_t).", + "Initial event TV is at most epsilon. The conclusion is min(1, epsilon + sum e_t). The finite upper budgets ensure no nonintegrable real Bochner integral can silently evaluate to zero." + ], + "historical_declared_residual": [ + "Required selected integration remains: prove the good-history bound eta_t+mu_t(E_t^c), instantiate separate actual baseline/intervention visit budgets, and feed the resulting localized/initial-law property errors plus separately verified b0,bJ into the original margin interface.", + "This is required by the original A2-P02 title/selected scope and chapter04:81-84, not an optional unrestricted extension. The old ConstitutiveQuasi/PropertyErrors:45-98 accepts uniform kernel TV and geometric envelopes; it does not instantiate the new localized budget.", + "No other Lean source imports or applies historyEvolution_tv_le_sum or eventTV_compProd_averaged. A correct generic inequality alone does not establish this requested original-interface integration." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Probability/LocalizedHistoryTV.lean" + ], + "acceptance_case_ids": [ + "Q-04" + ] + }, + { + "atlas_id": "E1-P01", + "historical_selected_scope": "Summable weighted bounded finite-prefix scores; full path TV only with summable sequential error or a separately proved common-mass limit.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Actual probability laws mu,nu on a common measurable Omega; measurable finite-output maps and measurable scores in [0,1]; nonnegative summable weights; actual output TV bounds e_t in [0,1].", + "Normalized discount has 0<=beta<1, 0<=eta<=1. With initial mismatch also 0<=epsilon<=1 and actual output bounds 1-(1-epsilon)(1-eta)^t.", + "Full-product-law route: actual laws P,Q on the dependent countable product, actual prefix marginal identities with the dependent-history evolution, common initial submeasure, common transition subkernels with mass lower bounds, and summable nonnegative eta_t<=1." + ], + "historical_declared_residual": [ + "There is no exported quotient-native discounted RegimePropertyMap/original-margin adapter identifying fine and coarse path pushforwards. The stated chapter04:88-105 generic score inequality is proved; claiming automatic original quotient/margin integration would exceed the exports.", + "Recurring constant positive leakage is not summable and yields only the trivial full-path TV bound one. Infinite survival is outside the summable score class." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Probability/SummablePathScores.lean", + "PermanssonResearch/Probability/DiscountedInitialBudget.lean", + "PermanssonResearch/Probability/CylinderTotalVariation.lean", + "PermanssonResearch/Probability/FullPathProductTV.lean" + ], + "acceptance_case_ids": [ + "Q-05" + ] + }, + { + "atlas_id": "E1-P02", + "historical_selected_scope": "Original-margin transport with independently proved Exact-GR, grounding, comparison and convergence prerequisites on both models.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "The geometric quotient wrapper requires the actual type-respecting compression, paired fine/coarse original intervention models, source property factorization, matching actual comparison sets, both row-defect budgets and independent original Exact-GR certificates.", + "The explicit bidirectional margin lemma assumes e>=0, absolute margin error <=e, and either fine margin>e or coarse margin>e.", + "The grounded bidirectional wrapper separately requires the original confirmatory regime and grounded-property certificates on both sides." + ], + "historical_declared_residual": [ + "There is no bare-positivity equivalence at e>0 and no automatic Exact-GR, target-law identity, grounding, or intervention-label discovery from a small quotient defect." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Quotient/GeometricMargin.lean" + ], + "acceptance_case_ids": [ + "Q-05" + ] + }, + { + "atlas_id": "F1-P01", + "historical_selected_scope": "Player-two construction and coordinate reindexing with physical kernel inputs explicitly transported.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "Swapping acts on private-state, action, world-input and update-input coordinates.", + "Full actual atom/path transport is in the stated finite-coordinate profile.", + "The property and original comparison sets are transported by the declared equivalence." + ], + "historical_declared_residual": [ + "General measurable full path transport beyond the finite proof profile remains a possible extension; direct measurable player-two protocols themselves are present." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/MultiAgent/PlayerSwapTransport.lean", + "PermanssonResearch/MultiAgent/PlayerSwapConstitution.lean" + ], + "acceptance_case_ids": [ + "F-01" + ] + }, + { + "atlas_id": "F1-P02", + "historical_selected_scope": "Finite-index conditional-product action/update kernels and selected-player protocols.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite index set with a selected player; each component is a measurable Markov kernel.", + "Conditional independence is part of this subclass.", + "General binary productKernel_two is a measure identity; the full FiniteProductTwo bridge adds finite-coordinate assumptions." + ], + "historical_declared_residual": [ + "Countably infinite products and general correlated games are outside scope. To claim full recovery of the old arbitrary-measurable two-player interface, add the corresponding measurable model/path/intervention transport adapter." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/MultiAgent/MeasurableFiniteProduct.lean", + "PermanssonResearch/MultiAgent/MeasurableProductProtocols.lean", + "PermanssonResearch/MultiAgent/FiniteProductTwo.lean" + ], + "acceptance_case_ids": [ + "F-01" + ] + }, + { + "atlas_id": "F1-P03", + "historical_selected_scope": "A new typed common/private clock-augmented baseline with actual clock update and projection bridge.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Time index is natural; the initial clock value is fixed.", + "Each selected time kernel is measurable/Markov and each anchor satisfies its strictness/feasibility premise.", + "The lawful clock advances independently of the treated action." + ], + "historical_declared_residual": [ + "This constructs a new lifted baseline; it is not a silent alteration of the original frozen model. Arbitrary real-time or unobservable-clock implementations are not supplied." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/MultiAgent/MeasurableNatPrivateClock.lean", + "PermanssonResearch/MultiAgent/MeasurableNatClockTransport.lean", + "PermanssonResearch/MultiAgent/MeasurableNatClockFixture.lean" + ], + "acceptance_case_ids": [ + "F-02" + ] + }, + { + "atlas_id": "F1-P04", + "historical_selected_scope": "Finite feasible menus; all-state strict feasibility iff support in each own-key fibre intersection.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite menu/key/action profiles for the executable selector; the AE intersection theorem has its stated countability assumptions.", + "The key map is surjective or every represented fibre is explicitly handled.", + "Legality applies to the common anchor law over the whole key fibre." + ], + "historical_declared_residual": [ + "Nonrectangular joint feasibility and a general measurable-selection theorem remain separate." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/MultiAgent/FeasibleAnchors.lean" + ], + "acceptance_case_ids": [ + "F-03" + ] + }, + { + "atlas_id": "F1-P05", + "historical_selected_scope": "All-point-start/all-control projected or marked kernel autonomy iff the corresponding row factorization.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite control set; general measurable state and key spaces.", + "Legality holds with policy probability one and quantification includes all initial histories.", + "The theorem concerns the unmarked projected row unless a separate marked result is invoked." + ], + "historical_declared_residual": [ + "Equal unmarked key laws do not identify action coupling. A dedicated tracked regression for that distinction was not found; the audit gives an exact counterexample with the same constant key law and opposite realized actions." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/MultiAgent/BehavioralAutonomy.lean" + ], + "acceptance_case_ids": [ + "M-02" + ] + }, + { + "atlas_id": "F1-P06", + "historical_selected_scope": "Finite joint actions represented by an explicitly sampled mediator with declared information timing.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite joint action space for the explicit mixture identity.", + "A mediator may depend measurably on the current state; the full joint draw is the mediator variable." + ], + "historical_declared_residual": [ + "This is an explicit action-stage representation, not a hidden-state augmentation, public-signal identification or dynamic causal model." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/MultiAgent/CorrelatedMediator.lean" + ], + "acceptance_case_ids": [ + "F-04" + ] + }, + { + "atlas_id": "F1-P07", + "historical_selected_scope": "Finite single-mediator SCM with conditional independent noises, treatment ignorability, consistency and positivity; explicit nonidentification example.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Covariate law, treatment assignment and two conditional noise laws are normalized and nonnegative.", + "Consistency and modularity come from shared deterministic mediator/outcome maps and the same declared noises.", + "Treatment ignorability and conditional noise independence are structural product assumptions.", + "The on-support theorem requires positivity only on cells actually used." + ], + "historical_declared_residual": [ + "Dynamic/sequential mediation and actual empirical identification remain open. The positive example is not a dedicated Lean SCM instantiation and is absent from the 48-test acceptance routing even though it is in the 918-test suite." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/MultiAgent/FiniteMediation.lean" + ], + "acceptance_case_ids": [ + "F-05" + ] + }, + { + "atlas_id": "D2-P01", + "historical_selected_scope": "Known finite fully observed expected terminal monitor reward, real behavioral probabilities, deterministic time-state maximizer.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "Real behavioral probabilities are normalized/supported on each supplied past.", + "The controller sees completed realized states and controls, never a future draw.", + "One fixed finite horizon and rational terminal monitor reward; no constrained/robust/private/hidden-policy extension." + ], + "historical_declared_residual": [ + "Does not extend deterministic sufficiency to risk-constrained or hidden-model robust objectives. The conclusion is not imported into constrained or hidden-draw robust objectives." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/ReverseSolver/BehavioralFinitePolicy.lean", + "PermanssonResearch/ReverseSolver/ChronologicalFiniteLaw.lean", + "PermanssonResearch/ReverseSolver/MonitorOptimality.lean", + "PermanssonResearch/ReverseSolver/InitialLawMonitor.lean" + ], + "acceptance_case_ids": [ + "D-01" + ] + }, + { + "atlas_id": "D2-P02", + "historical_selected_scope": "Literal first-ever-hit-and-stay event, fixed entry deadline, irreversible failure after interrupted first run.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "First ever target hit, entry deadline T, m further transitions in target; time-zero entry counts.", + "Failure after interrupted first stay is irreversible; physical transitions remain live." + ], + "historical_declared_residual": [ + "No substitution for the existing retryable event." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/ReverseSolver/FirstEntryMonitor.lean", + "PermanssonResearch/ReverseSolver/ResidenceSpecializationOptimality.lean" + ], + "acceptance_case_ids": [ + "D-02" + ] + }, + { + "atlas_id": "D2-P03", + "historical_selected_scope": "Finish a target run before any danger; danger precedence at ties; separate through-horizon risk monitor.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "Danger precedes simultaneous unfinished success; a completed before-danger success remains true.", + "Ever-danger through horizon is an additional memory bit, not the complement of success.", + "Actual risk measure uses an initialized consistent monitor augmentation and the actual occupancy history law." + ], + "historical_declared_residual": [ + "Other post-success risk conventions require separately named events." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/ReverseSolver/AvoidResidenceMonitor.lean", + "PermanssonResearch/ReverseSolver/AvoidResidenceHistoryBridge.lean", + "PermanssonResearch/ReverseSolver/ResidenceSpecializationOptimality.lean", + "PermanssonResearch/ReverseSolver/ThroughHorizonRiskMonitor.lean" + ], + "acceptance_case_ids": [ + "D-03" + ] + }, + { + "atlas_id": "D2-P04", + "historical_selected_scope": "Finite rational augmented occupancy polytope, behavioral policy reconstruction, exact vertex optimum and optional weak-dual certificate.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite horizon, finite nonempty legal menus, rational initial law/rows/objective/risk/threshold.", + "Comparison is against all normalized supported real behavioral history policies.", + "x/d uses a legal default at zero state mass; one reconstructed policy realizes all coordinates and scores.", + "Compactness and rational vertex completeness are proved; no supplied optimal candidate, rank oracle or dual completeness premise." + ], + "historical_declared_residual": [ + "Polynomial efficiency is not proved; more general risk functionals require new state/convexity results. No polynomial runtime or general nonlinear/path-risk optimization claim." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/ReverseSolver/FiniteOccupancyReconstruction.lean", + "PermanssonResearch/ReverseSolver/PolytopeCompleteness.lean", + "PermanssonResearch/ReverseSolver/FlowPolytope.lean", + "PermanssonResearch/ReverseSolver/FlowPolicyOptimality.lean", + "PermanssonResearch/ReverseSolver/FlowRiskWitness.lean", + "PermanssonResearch/ReverseSolver/PolytopeRegression.lean" + ], + "acceptance_case_ids": [ + "D-03", + "D-04" + ] + }, + { + "atlas_id": "D2-P05", + "historical_selected_scope": "Finite rectangular perfect-information nature-after-control game; separately private-control-draw stage maximin LP.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite nonempty control/nature menus with perfect-information realized history.", + "Dynamic nature observes the current realized control and can choose anew at every node.", + "Private stage is a separate finite rational payoff table; nature sees distribution but not private draw.", + "No static model-commitment Bellman equivalence or generic minimax interchange." + ], + "historical_declared_residual": [ + "Static hidden model commitment is not rectangular; no generic max/min interchange. A single hidden model committed for the whole horizon requires different state/information treatment." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/ReverseSolver/RectangularFiniteGame.lean", + "PermanssonResearch/ReverseSolver/RectangularGameLaw.lean", + "PermanssonResearch/ReverseSolver/PrivateControlStageOptimizer.lean", + "PermanssonResearch/ReverseSolver/PrivateControlStageFixture.lean", + "PermanssonResearch/ReverseSolver/SolverBoundaryWitnesses.lean" + ], + "acceptance_case_ids": [ + "D-05" + ] + }, + { + "atlas_id": "D2-P06", + "historical_selected_scope": "Finite hidden augmented state including event monitors, common finite control menu, finite observations, alpha-vector optimality.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite full hidden augmented state, including event monitors; finite observations.", + "Common finite nonempty control menu is legal at every hidden physical state.", + "Control precedes hidden transition and successor observation; policy argument excludes hidden state.", + "Positive-word Bayes normalization; fixed fallback only on zero-probability branches.", + "Mathematical real alpha-vector maximum is noncomputable; no executable arbitrary-real optimization claim." + ], + "historical_declared_residual": [ + "Arbitrary belief-dependent menus, empirical identification and general infinite-horizon POMDP theory are excluded. Identifiability of the hidden model and general infinite-horizon POMDPs remain separate." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/ReverseSolver/BeliefHistory.lean", + "PermanssonResearch/ReverseSolver/BeliefKernel.lean", + "PermanssonResearch/ReverseSolver/BeliefChronology.lean", + "PermanssonResearch/ReverseSolver/BeliefAlpha.lean", + "PermanssonResearch/ReverseSolver/BeliefMonitor.lean" + ], + "acceptance_case_ids": [ + "D-06" + ] + }, + { + "atlas_id": "D2-P07", + "historical_selected_scope": "Finite controlled infinite-safety optimum and deterministic stationary attainment; greatest controlled invariant set.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite rational time-homogeneous controlled system; finite nonempty menus.", + "All real full-history behavioral policies use a genuine normalized countable-history canonical law.", + "Safety includes time zero and every physical successor.", + "Finite max/sum continuity yields fixed point; stationary lower-bound verification precedes limit.", + "Greatest invariant core is finite/rational; real limiting value selector is noncomputable." + ], + "historical_declared_residual": [ + "General nonfinite infinite retention or finite-horizon extrapolation is not supplied. A general reach-then-stay optimal switching theorem and original frozen-intervention bridge are additional targets." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/ReverseSolver/BehavioralInfiniteLaw.lean", + "PermanssonResearch/ReverseSolver/InfiniteSafetyPathSemantics.lean", + "PermanssonResearch/ReverseSolver/InfiniteSafetyBellman.lean", + "PermanssonResearch/ReverseSolver/InfiniteSafetyFiniteEvaluation.lean", + "PermanssonResearch/ReverseSolver/InfiniteSafetyOptimality.lean", + "PermanssonResearch/ReverseSolver/GreatestControlledInvariant.lean", + "PermanssonResearch/ReverseSolver/InfiniteSafetyLeakage.lean" + ], + "acceptance_case_ids": [ + "D-07" + ] + }, + { + "atlas_id": "D2-P08", + "historical_selected_scope": "Finite certified block menus and genuine time-dependent alpha/P/U-controlled law with fixed physical P.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "Certified finite measurable state indexing and exact rational actual block atom certificates.", + "Whole-block admission and both action/update overlap compatibility are explicit independent fields.", + "Block selected before its alpha/P/U stage; physical world P stays fixed.", + "Successive selections define an adaptive controlled law, not one frozen intervention." + ], + "historical_declared_residual": [ + "Overlapping incompatible patches and post-draw unauthorized control choice remain inadmissible. Compatible overlapping blocks are allowed; the inherited blanket wording “overlapping patches rejected” needs narrowing." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/ReverseSolver/AdaptiveGrammarControl.lean" + ], + "acceptance_case_ids": [ + "D-08" + ] + }, + { + "atlas_id": "D2-P09", + "historical_selected_scope": "Finite menu of deterministic stationary policies, exact class absorption and stationary descriptor-limit scores.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "All admissible deterministic stationary StateFeedback maps only.", + "Descriptor and globally bounded functional fixed before policy selection.", + "Exact rational functional evaluation on every constructed class law is certified input; existence of such an evaluator for arbitrary functionals is not claimed.", + "C10 absorption, C11 stationary laws, original C2 selector/occupation convergence supply actual-law bridge.", + "Expectation is of F(random occupation limit), not F(expected occupation law)." + ], + "historical_declared_residual": [ + "All-history asymptotic synthesis, MEC/mean-payoff objectives remain a broader unproved target. For continuous F(nu)=1-|nu(1)-2/3|, a two-state force-0/force-1 model has deterministic-stationary optimum 5/6 but a period-three history policy or randomized row policy attains 1." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/MultipleLimit/FiniteOccupationRandomLaw.lean", + "PermanssonResearch/ReverseSolver/StationaryOccupationObjective.lean", + "PermanssonResearch/ReverseSolver/StationaryMenuOptimization.lean" + ], + "acceptance_case_ids": [ + "D-09" + ] + }, + { + "atlas_id": "C11-P01", + "historical_selected_scope": "All nonempty finite rational stochastic matrices; explicit Cramer stationary family for every genuine bottom class.", + "historical_disposition": "SELECTED_STATEMENT_VERIFIED", + "historical_declared_hypotheses": [ + "Arbitrary nonempty finite rational stochastic matrix, no aperiodicity or resource-size hypothesis.", + "Computed genuine bottom classes give nonempty irreducible closed restrictions.", + "Nonsingularity, rational/real cast compatibility, positivity, mass one, stationarity and zero extension are proved.", + "Canonical class list is complete and duplicate-free; final checker acceptance is universal, not assumed." + ], + "historical_declared_residual": [ + "No verified external parser or runtime scalability claim; C10 total absorption is already proved. Existing C10 total Dirichlet/Cramer absorption must be reused; parser verification and runtime scalability are separate." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/ReverseSolver/C11ReferenceMatrix.lean", + "PermanssonResearch/ReverseSolver/C11ReferenceProbability.lean", + "PermanssonResearch/ReverseSolver/C11ReferenceClasses.lean", + "PermanssonResearch/ReverseSolver/C11ReferenceStationaryFamily.lean", + "PermanssonResearch/ReverseSolver/C11ReferenceTypedBridge.lean" + ], + "acceptance_case_ids": [ + "C11-01", + "C11-02" + ] + }, + { + "atlas_id": "C-P01", + "historical_selected_scope": "Compact metric Feller cluster theorem, unique-invariant convergence, stationary/absolutely-continuous random-law extension.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Compact-Feller route: nonempty compact metric Borel state space, actual Markov kernel, Feller continuity on C(Y,R), and any initial probability law.", + "Unique-invariant convergence requires uniqueness; general multi-invariant compact chains yield cluster containment and approach to the invariant set, not a single limit from this theorem alone.", + "Random-law route: second-countable metric Borel standard-Borel nonempty state space, actual stationary probability pi for K, and stationary start or an initial mu absolutely continuous with respect to pi. Feller and compactness are not needed for this separate route.", + "Weak pushforward descriptor conclusions use continuous h; a separate regenerative theorem can handle merely measurable h through direct averages. The original regime occupation bridge also needs every invariant descriptor image to equal the declared target." + ], + "historical_declared_residual": [ + "There is no unrestricted nonfinite all-initial weak occupation theorem. Noncompact marginal boundedness or weak cluster containment alone does not imply pathwise empirical convergence.", + "A non-atomic stationary pi does not dominate arbitrary Dirac starting laws; the absolute-continuity premise is material.", + "The general ergodic result formalized here is bounded Birkhoff. The regenerative route separately proves nonnegative integrable averages; it is not an unrestricted integrable Birkhoff theorem." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/MultipleLimit/CompactFellerEmpirical.lean", + "PermanssonResearch/MultipleLimit/CompactFellerDistance.lean", + "PermanssonResearch/MultipleLimit/StationaryConditionalLaw.lean", + "PermanssonResearch/MultipleLimit/StationaryConditionalInvariant.lean", + "PermanssonResearch/MultipleLimit/PolishStationaryRandomOccupation.lean", + "PermanssonResearch/MultipleLimit/CanonicalInitialDensity.lean", + "PermanssonResearch/MultipleLimit/NonfiniteClock.lean", + "PermanssonResearch/MultipleLimit/GrowingBlockCounterexample.lean" + ], + "acceptance_case_ids": [ + "C-01" + ] + }, + { + "atlas_id": "B-P01", + "historical_selected_scope": "Finite measurable row patches agreeing on every active overlap; fixed P and original typed law bridge.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite lists of measurable Markov row patches on exact alpha/update input types.", + "Pairwise equality of replacement measures at every active overlap.", + "Independent frozen whole-block admission; original P retained.", + "Permutation/idempotence/original-law/original-margin claims are under compatibility; raw fold may accept ordered conflicting data." + ], + "historical_declared_residual": [ + "Arbitrary conflicting patches require declared priority/order semantics rather than this commutative theorem. General nonlinear kernel transformations and conflict-priority semantics need distinct assumptions/theorems." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/GrammarRobust/CompatibleOverlap.lean", + "PermanssonResearch/GrammarRobust/OverlapKernelAssembly.lean", + "PermanssonResearch/GrammarRobust/OverlapCanonicalBridge.lean" + ], + "acceptance_case_ids": [ + "B-01" + ] + }, + { + "atlas_id": "B-P02", + "historical_selected_scope": "Local strict lower-set mapping/lifting with matched positivity; unique least block only under finite positive meet closure.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Matched positivity throughout chosen downset, strict preservation below chosen element, coverage of every strict lower target element.", + "Admission is in the poset carrier or explicitly retained in the actual wrapper.", + "Original-margin positivity match follows from typed physical coarsening transport.", + "Unique least positive element only when positive finite family is nonempty and meet-closed; uniqueness follows by antisymmetry." + ], + "historical_declared_residual": [ + "No arbitrary bijection or coarsening preserves minimality; positivity need not be monotone. The raw enabling-Boolean interpretation must be distinguished from the preserved ablation-margin examples, where AND correctly has incomparable singleton minima and OR a joint minimum." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/GrammarRobust/LowerSetTransport.lean", + "PermanssonResearch/GrammarRobust/OriginalLowerSetTransport.lean" + ], + "acceptance_case_ids": [ + "B-02" + ] + }, + { + "atlas_id": "X1-P01", + "historical_selected_scope": "New three-world two-player constructed economic model with fair safe baseline, collapse under extraction, beta three quarters.", + "historical_disposition": "SCOPED_THEOREM_FORMALIZED", + "historical_declared_hypotheses": [ + "Two safe world states and an absorbing collapse state; Unit strategic memory and Boolean extraction actions.", + "The baseline rests, discount is 3/4, safe rest reward is one and safe extraction reward is two.", + "The original descriptor, basin, comparison set, intervention family and property are explicitly constructed." + ], + "historical_declared_residual": [ + "This is a constructed mathematical witness, not empirical calibration or certification. It does not repair or reinterpret the older X1 leakage model." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/EconomicWitness/ExactRegime.lean", + "PermanssonResearch/EconomicWitness/Constitution.lean", + "PermanssonResearch/EconomicWitness/Incentives.lean" + ], + "acceptance_case_ids": [ + "XPLUS-01" + ] + }, + { + "atlas_id": "APP-E01", + "historical_selected_scope": "Finite real symmetric feature discrepancy with seam invisibility, plus constant diagonal for common rank one.", + "historical_disposition": "CONDITIONAL_ADAPTER_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite real symmetric matrices; a pivot; exact vanishing seam and, for rank one, constant diagonal.", + "A genuine path-law feature identity is an explicit premise of the original-model wrapper." + ], + "historical_declared_residual": [ + "The model-to-feature identity is not manufactured by the matrix algebra. Natural Weil/CCM or production-model transfer remains conditional." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Specialized/SeamReduction.lean" + ], + "acceptance_case_ids": [ + "E-01" + ] + }, + { + "atlas_id": "APP-E02", + "historical_selected_scope": "Continuous quadratic form, certified legal approximants and form-preserving nested embeddings.", + "historical_disposition": "CONDITIONAL_ADAPTER_FORMALIZED", + "historical_declared_hypotheses": [ + "Real normed spaces and bounded bilinear form with a declared bound.", + "A legal approximation property supplies arbitrarily close legal vectors.", + "Embedding must preserve the actual form; the original model wrapper requires its feature identity." + ], + "historical_declared_residual": [ + "This supplies no independent density theorem for an intended arithmetic/physical carrier and no unproved feature identification." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Specialized/FiniteDetection.lean" + ], + "acceptance_case_ids": [ + "E-02" + ] + }, + { + "atlas_id": "APP-E03", + "historical_selected_scope": "Finite real self-adjoint predecessor PSD block, actual shell dimension, negative shift and Schur elimination.", + "historical_disposition": "CONDITIONAL_ADAPTER_FORMALIZED", + "historical_declared_hypotheses": [ + "PSD core and strictly negative shift, finite dimensions and the actual off-diagonal blocks.", + "Scalar secular monotonicity is restricted to a one-dimensional shell.", + "Existence of a first bad index is conditional on some bad index existing." + ], + "historical_declared_residual": [ + "A general shell is a matrix equation, not an automatic scalar cubic. No actual source hierarchy or first failure is identified by these algebraic facts." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Specialized/ShellSchur.lean", + "PermanssonResearch/Specialized/ShellResolvent.lean" + ], + "acceptance_case_ids": [ + "E-03" + ] + }, + { + "atlas_id": "APP-E04", + "historical_selected_scope": "Finite symmetric range/kernel compatibility; explicit preimages and denominator-free source transport identities.", + "historical_disposition": "CONDITIONAL_ADAPTER_FORMALIZED", + "historical_declared_hypotheses": [ + "Finite real inner-product spaces and symmetric operators; the relevant PSD and source/preimage assumptions are explicit.", + "The denominator-free rigidity statement uses a correctly typed D:E->F and separate operators.", + "The zero-shift response uses an independent w:F with two explicit response identities." + ], + "historical_declared_residual": [ + "The actual construction identifying w with the intended source/hierarchy remains a premise. The Gamma=0 branch is unresolved by design, and complex/Hermitian source variants are not supplied." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Specialized/SingularCompatibility.lean" + ], + "acceptance_case_ids": [ + "E-04" + ] + }, + { + "atlas_id": "APP-E05", + "historical_selected_scope": "Signed rank-one quadratic block with positive Gram gap and an explicit operator-norm perturbation threshold.", + "historical_disposition": "CONDITIONAL_ADAPTER_FORMALIZED", + "historical_declared_hypotheses": [ + "Positive coefficient c and Gram gap Delta > 0.", + "An added PSD component is controlled by its actual value in the witness direction.", + "The original effect wrapper requires a declared operator feature representation." + ], + "historical_declared_residual": [ + "No zero-gap negative witness, unrestricted positive addition or natural model-to-operator identification follows. These remain conditional real algebraic adapters." + ], + "current_accepted_status": "PENDING_REAUDIT", + "source_paths": [ + "PermanssonResearch/Specialized/GramSeparation.lean" + ], + "acceptance_case_ids": [ + "E-05" + ] + } + ], + "planned_new_claims": [ + { + "id": "P-04", + "status": "NOT_LEAN_VERIFIED", + "accepted_declarations": [] + }, + { + "id": "P-05", + "status": "NOT_LEAN_VERIFIED", + "accepted_declarations": [] + }, + { + "id": "P-06", + "status": "NOT_LEAN_VERIFIED", + "accepted_declarations": [] + } + ], + "source_baseline_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "closure_additions": [ + { + "id": "Q1", + "title": "Canonical-source discounted output", + "path": "PermanssonResearch/Quotient/DiscountedCanonicalSourceWitness.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "Q-05" + ] + }, + { + "id": "Q2", + "title": "Original full comparison margin", + "path": "PermanssonResearch/Quotient/DiscountedCanonicalMarginClosure.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "Q-05" + ] + }, + { + "id": "Q3", + "title": "Typed quotient adversaries", + "path": "PermanssonResearch/Quotient/DiscountedCanonicalWitnessExamples.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "Q-05" + ] + }, + { + "id": "M1", + "title": "Admissible matched protocols", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "F-01", + "Q-05" + ] + }, + { + "id": "M2", + "title": "Full B1 matched original margin", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "F-01", + "Q-05" + ] + }, + { + "id": "M3", + "title": "Mixed-start and asymmetric adversaries", + "path": "PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedExamples.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "F-01", + "Q-05" + ] + }, + { + "id": "N1", + "title": "Native recurrent stationary law", + "path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeStationary.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "D-09" + ] + }, + { + "id": "N2", + "title": "Actual infinite-path nonlinear score", + "path": "PermanssonResearch/ReverseSolver/NonlinearPeriodThreeExpectedScore.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "D-09" + ] + }, + { + "id": "N3", + "title": "Exact C11 general class functional", + "path": "PermanssonResearch/ReverseSolver/NonlinearBooleanClassEvaluator.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "D-09" + ] + }, + { + "id": "D8", + "title": "Typed restricted admission", + "path": "PermanssonResearch/ReverseSolver/AdaptiveGrammarAdmissionFixture.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "D-08" + ] + }, + { + "id": "D9", + "title": "Classwise expected nonlinear functional", + "path": "PermanssonResearch/ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "status": "PLANNED_NOT_VERIFIED", + "actual_declarations": [], + "source_sha256": null, + "axiom_receipts": [], + "acceptance_case_ids": [ + "D-09" + ] + } + ], + "final_reviewed_head_sha": null +} diff --git a/research_papers/mathematical_closure/README.md b/research_papers/mathematical_closure/README.md index e0e20d3e..87acf1e6 100644 --- a/research_papers/mathematical_closure/README.md +++ b/research_papers/mathematical_closure/README.md @@ -1,31 +1,16 @@ -# Mathematical closure — canonical manuscript source +# Mathematical closure — final single-PR manuscript candidate -**DRAFT / NOT ACCEPTED / NOT PUBLISHED.** This directory contains the -13 canonical manuscript inputs (including this README), originally scaffolded -from main `3931e4dccafb47b3cdf70177c0cf337102c90731` -(tree `0ffd313dcd5efd1cf90a85ee458024b1b28cd3f8`). -The original v0.1.8 paper and historical accepted releases remain immutable. +**DRAFT / NOT ACCEPTED / NOT PUBLISHED.** Canonical source consists of these original thirteen TeX/bibliography/document files. This is the integrated continuation after PR #112, implemented in draft PR #113. Historical accepted v0.1.8 and v0.2.0 sources remain unchanged. -**Scoped mathematics integrated by the 20 October 2026 checkpoint -`main@6bc86eaa71fb26a5e86e2d7ffd88e4c1af4f0760`:** +Verified starting main: \`17c315f7df7abf457eb94d393bd526d46da6f6f4\`. +The merged earlier source already included actual full canonical paths and conditional original-margin adapters for P-04/P-05 plus the native P-06 finite phase-orbit/Dirac original-path identity. The new single-PR code constructs source-specific projected error/CL/OL adapters and attempts the remaining canonical nonlinear occupation and D-08/D-09 acceptance. -- [PR #103](https://github.com/HMarcusWH/PermanssonLean/pull/103): - NEXT-06.1 native Fin3/Bool strategic world and exact induced-kernel atoms; - compiler and negative finite/Lean checks passed. This does **not** prove the - actual infinite pathwise occupation objective or unrestricted optimality. -- [PR #106](https://github.com/HMarcusWH/PermanssonLean/pull/106): - NEXT-04.1 genuine canonical path-law time marginals equal - dependent-history evolution under the original typed induced kernel. - Pinned Lean compiler/axiom checks passed with 1,121 research tests on the - verified pre-merge synthetic tree. This does **not** yet connect complete - discounted whole-path output errors to the original constitutive margin. +All theorem statements in this manuscript MUST match accepted final Lean signatures and never be advertised as verified merely because compiled source is present. The complete acceptance status is computed separately, case-by-case, from preserved 45 original criteria; a scoped mathematical paper is not a blanket 45-case certificate. No independent academic or operational review or real-world GR/PR certification is asserted. -This manuscript is not itself a proof receipt or a full P-04/P-05/P-06 closure. -The 45-case post-PR102 ledger remains a *candidate audit* with strict release -gates pending; no independent academic or security review is asserted. +Draft PDF build (no release or tag): +\`\`\`bash +python3 release/mathematical-closure-1/build.py --mode draft --render --out /tmp/permansson-closure +python3 release/mathematical-closure-1/verify_release.py --manifest /tmp/permansson-closure/manifest.lock.json +\`\`\` -Before any paper release, freeze and independently verify exact theorem -signatures, source hashes, genuine Git B/H/S/M identities, all required -accepted claims, review provenance, complete LaTeX/PDF compilation and visual -page inspection. No publication, paper merge, tag, deployment, repository -security change, or external reviewer outreach is authorized by this draft. +An approved paper requires full claim-crosswalk/negative-test review, actual final source H/B/S/M CI, an authentic independent review, page-level PDF inspection and a separate publication decision. diff --git a/research_papers/mathematical_closure/appendices/A_theorem_crosswalk.tex b/research_papers/mathematical_closure/appendices/A_theorem_crosswalk.tex index ae802751..282016ad 100644 --- a/research_papers/mathematical_closure/appendices/A_theorem_crosswalk.tex +++ b/research_papers/mathematical_closure/appendices/A_theorem_crosswalk.tex @@ -1,16 +1,30 @@ -\section{Prospective theorem crosswalk (not an accepted certificate)} +\section{Implementation theorem-to-source crosswalk (uncertified draft)} \begin{longtable}{@{}p{22mm}p{94mm}p{22mm}@{}}\toprule Work&Required source-bound theorem surface&Status\\\midrule\endhead P--04.1&Canonical nth marginal equals actual dependent evolution&Scoped Lean\\ -P--04.2&Two-sided original full-$B_1$ constitutive margin and reporting&Open\\ -P--05.1&Measurable strategic update, action, physical and induced map&Open\\ -P--05.2&Both players' legal CL/OL and entire path-law recovery&Open\\ +P--04.2&Actual canonical source witnesses plus original full-$B_1$ margin&Pending review\\ +P--05.1&Measurable update, action, physical and induced map&Prior compiled\\ +P--05.2&Admitted matched binary CL/OL, entire path laws and original margin&Pending review\\ P--06.1&Native Fin3/Bool physical, update, 36 kernel atoms&Scoped Lean\\ -P--06.2&Genuine occupation expectation and restricted optimizer&Open\\ +P--06.2&Native actual occupation and restricted nonlinear policy score&Pending review\\ A--45&Original 45 acceptance cases, strengthened negatives&Open\\ \bottomrule\end{longtable} -These two scoped compiler results have not been promoted to full 45-case -acceptance; P--04.2 and P--06.2 are separate mathematical obligations. +P--04.1 and P--06.1 have source-level scoped predecessors, but neither +establishes independent acceptance of the original 45-case corpus. +Conditional original-margin or occupation adapters do not discharge every +source-specific hypothesis merely by compiling. In particular, Q--05 needs +actual fine/coarse kernel-domination and canonical-score identities; D--09 +needs the original classwise canonical expected-functional comparison. +The remaining full 45-case and 11-claim obligations require their exact +Lean types, assumptions, actual positive/negative source controls and +independent decisions on the final source head. Final original declaration names, reviewed hypothesis signatures, SHA-256, CI IDs, reviewer provenance and source identities must come from the separate canonical machine-readable evidence index, not these placeholders. + + +Implementation source candidates Q1--Q3, M1--M3, N1--N3, D8 and D9 +are inventoried under \texttt{research/paper\_closures/post\_merge\_112/}. +Their presence in the source tree is not a verification decision: the +current-source 45-case acceptance and manuscript claim manifests remain +separate, fail-closed records. diff --git a/research_papers/mathematical_closure/appendices/B_reproducibility.tex b/research_papers/mathematical_closure/appendices/B_reproducibility.tex index f99b400e..67bfeccb 100644 --- a/research_papers/mathematical_closure/appendices/B_reproducibility.tex +++ b/research_papers/mathematical_closure/appendices/B_reproducibility.tex @@ -15,4 +15,37 @@ \section{Pinned dependencies and release boundary} The LaTeX manuscript must compile, be rendered and visually inspected; a PDF-to-text extraction is insufficient. No paper outputs, release receipts, external peer review, source-bound archive, or publication -are certified by this working source. +are certified by this working source. The original archival T closure +and A2 source-preservation checks are retained. Changed successor +tooling requires separately audited historical/current execution and +genuine independent security review, not a rewritten original baseline. + + +\paragraph{Current working reproduction.} +Use the original pinned Lean 4.34.0 environment without dependency updates: +\begin{verbatim} +lake exe cache get +lake build PermanssonResearch +lake build +python3 -m unittest discover -s verification/research -p 'test_*.py' -v +python3 verification/research/final_closure_verify.py --mode audit +python3 release/mathematical-closure-1/build.py --mode draft \ + --render --out /tmp/permansson-math-closure +python3 release/mathematical-closure-1/verify_release.py \ + --manifest /tmp/permansson-math-closure/manifest.lock.json +\end{verbatim} +The two strict acceptance modes additionally require real authenticated +CI/review receipts and a final approved claim inventory. The build +script cannot predetermine the eventual GitHub merge SHA. + +\paragraph{Two-PR release provenance.} +The current \#113 source head $H_{113}$ remains mutable during implementation. +Its genuine merge $M_{113}$ does not exist until source freeze, successful +engineering/historical verification, independent security review and separate +owner merge authorization. The successor CERT source head $H_{\rm cert}$ must +be a new actual GitHub PR based on a main revision containing $M_{113}$. +Its 45 original cases, 11 claims, original 4+9+8 producing jobs, 259 +baseline declaration targets and added source controls need independent +source-bound authentication; earlier \#113 CI candidates cannot be +relabelled as freshly accepted CERT evidence. Clean-source draft +reproducibility is not scholarly or mathematical acceptance. diff --git a/research_papers/mathematical_closure/main.tex b/research_papers/mathematical_closure/main.tex index f4a15891..cc63bcf4 100644 --- a/research_papers/mathematical_closure/main.tex +++ b/research_papers/mathematical_closure/main.tex @@ -3,15 +3,18 @@ \title{Mathematical Closures in the Permansson Strategic--World Framework\\ \large Canonical Transport, Measurable Two-Player Recovery and Nonlinear Occupation} \author{Marcus Hermansson\\Independent researcher} -\date{Working source draft --- 20 October 2026} +\date{Implementation source draft --- 25 October 2026} \begin{document} \maketitle \begin{center}\fbox{\parbox{0.9\textwidth}{\centering\Warning\\ -This manuscript is a parallel-build draft. Selected P--04.1 and P--06.1 -theorems are Lean-checked, but full P--04/P--05/P--06 closure, the 45-case -programme, and paper-release acceptance remain unverified.}}\end{center} +This is the \#113 implementation working draft, not a certified manuscript. +The planned successor PR (CERT) performs independent case/claim acceptance, +scholarly PDF review and any authorized publication. Earlier canonical and +conditional providers compile, but the exact final P--04/P--05/P--06 scopes, +45 original-case decisions, 11 claim decisions, and release authority remain +unverified. Source existence and passing candidate jobs are not certification.}}\end{center} \begin{abstract} -A scoped build programme to connect canonical strategic--world path laws to +An explicitly scoped research and proof-integration programme connecting canonical strategic--world path laws to original constitutive margins, recover arbitrary-measurable conditional-product two-player models, and formalize a finite nonlinear occupation counterexample. We separate historical proved providers from work in progress, preserve the diff --git a/research_papers/mathematical_closure/preamble.tex b/research_papers/mathematical_closure/preamble.tex index 0ec21195..42008422 100644 --- a/research_papers/mathematical_closure/preamble.tex +++ b/research_papers/mathematical_closure/preamble.tex @@ -7,8 +7,21 @@ \usetikzlibrary{positioning,arrows.meta} \usepackage[hidelinks]{hyperref} \usepackage[round]{natbib} + +% pdfTeX derives its PDF trailer /ID from the absolute output-directory +% pathname even with SOURCE_DATE_EPOCH. Two exact Git source roots must not +% produce different PDF bytes solely because their temporary roots differ. +% This fixed release-profile PDF ID has NO scholarly or mathematical meaning; +% the complete PDF SHA-256 (not the trailer ID) identifies the actual paper. +% Other TeX engines require their own independently verified release profiles. +\ifdefined\pdftrailerid + \pdftrailerid{<5045524D414E53534F4E2D4D41544821> <5045524D414E53534F4E2D4D41544821>} +\fi + \newtheorem{remark}{Remark}[section] \newcommand{\code}[1]{\texttt{\detokenize{#1}}} \newcommand{\Warning}{\textbf{DRAFT --- NOT ACCEPTED}} \tikzset{obj/.style={draw,rounded corners,minimum width=17mm,minimum height=8mm,align=center}, arr/.style={-{Latex[length=2mm]},thick}} + +% Source-bound paper acceptance is not implied by LaTeX compilation. diff --git a/research_papers/mathematical_closure/references.bib b/research_papers/mathematical_closure/references.bib index 49fec8e2..68a2e83d 100644 --- a/research_papers/mathematical_closure/references.bib +++ b/research_papers/mathematical_closure/references.bib @@ -7,3 +7,5 @@ @misc{EGR2026 @misc{PRT2026,author={Hermansson, Marcus}, title={Permansson Regimes: A General Framework for Strategic Dynamics Beyond Equilibrium}, year={2026},note={Working paper v0.1.8, revised 23 September 2026}} + +% Bibliographic citation text remains subject to author and source verification. diff --git a/research_papers/mathematical_closure/sections/01_scope_and_semantics.tex b/research_papers/mathematical_closure/sections/01_scope_and_semantics.tex index e8ad506b..b9ad1fc9 100644 --- a/research_papers/mathematical_closure/sections/01_scope_and_semantics.tex +++ b/research_papers/mathematical_closure/sections/01_scope_and_semantics.tex @@ -24,3 +24,11 @@ \section{Original typed semantics} \caption{The actual $\alpha\to P\to U$ chronology; $P$ remains fixed under admissible within-model strategic intervention.} \end{figure} + + +\paragraph{Version control and claim authority.} +The original v0.1.8 regime definitions, constitutive comparison domain and +intervention grammar have not been rewritten by the mathematical-closure +work. Every theorem is interpreted with its actual quantified Lean +hypotheses and compiled final source. A paper formula, source checksum, +numerical output, or code-review note alone is not a new theorem. diff --git a/research_papers/mathematical_closure/sections/02_existing_foundations.tex b/research_papers/mathematical_closure/sections/02_existing_foundations.tex index 21fc01f5..005ed748 100644 --- a/research_papers/mathematical_closure/sections/02_existing_foundations.tex +++ b/research_papers/mathematical_closure/sections/02_existing_foundations.tex @@ -13,3 +13,13 @@ \section{Source foundations: what already exists} it does not imply an unrestricted generalization, a deployment safety certificate, or identified empirical GR/PR status. \end{remark} + + +\paragraph{Already integrated by PR \#112.} +Source-bound compiled providers include the actual canonical time-marginal +identity, full-path discounted property and \emph{conditional} original +margin bound, arbitrary-measurable conditional-product two-player induced +kernels and all-initial paths, plus the genuine native finite phase/world +kernel and original deterministic infinite path-law identity. The remaining +work concerns \emph{constructing} these conditional hypotheses for declared +source protocols and determining the true nonlinear occupation-limit values. diff --git a/research_papers/mathematical_closure/sections/03_discounted_original_margin.tex b/research_papers/mathematical_closure/sections/03_discounted_original_margin.tex index 88e943a6..7092a18a 100644 --- a/research_papers/mathematical_closure/sections/03_discounted_original_margin.tex +++ b/research_papers/mathematical_closure/sections/03_discounted_original_margin.tex @@ -1,5 +1,5 @@ \section{P--04: Discounted output to original constitutive margin} -\textbf{Status: P--04.1 canonical marginal bridge Lean-checked; original whole-path constitutive-margin closure still pending.} +\textbf{Status: canonical whole-path adapters and the actual Q--05 atom-domination/original-margin bound compile at the exact source head; exact margin-value identities and independent acceptance remain open.} The scoped theorem \code{canonical_nth_marginal_eq_evolution} concerns the genuine canonical time-$n$ marginal, not the full property or a closed original constitutive margin. @@ -33,3 +33,47 @@ \section{P--04: Discounted output to original constitutive margin} the original full-$B_1$ margin API; diagram is not a proof receipt.} \end{figure} A positive numerical margin does not by itself imply Exact GR/PR. + + +\paragraph{Source-specific witness.} +The final integration distinguishes the real baseline witness +$(K_0,\bar K_0,R_0,\eta_0)$ from the real treated witness +$(K_J,\bar K_J,R_J,\eta_J)$. Each subkernel must be dominated on all +required source states and its projected row must be dominated by the +proper \emph{coarse} kernel; neither is an assumed TV number. Each +point-initialized original comparison starts from its actual Dirac law, +so the initial mismatch vanishes only for that matching construction. +The two model-output bounds must then be used through the unchanged +original \texttt{HasConstitutiveOutputErrors} and full-$B_1$ +\texttt{constitutiveMargin} API. Positive leakage still defeats an Exact +GR transfer regardless of how small the discounted error becomes. + + +\paragraph{Nonidentity source witness: compiled bound, not independent acceptance (Q--05).} +The fixture uses $S=X=A=\mathrm{Bool}$, $\bar S=\mathrm{Unit}$, +$\bar X=\bar A=\mathrm{Bool}$, and $q(s,x)=((),x)$, with physical +$P(x'\mid s,x,a)=\delta_a(x')$ under both strategic interventions. +Its predeclared comparison set is $B_1=\{(F,F),(F,T)\}$. +The common subkernel takes the entrywise Bernoulli minimum across +all fine source states, including treatment-only rows. The current +\code{Q05ActualAtomClosure} source proves fine and projected-coarse +actual singleton domination on every state, constructs the actual +verified atom-domination and point-witness records, and establishes +\code{actual_original_B1_margin_bound}, an original full-$B_1$ +\emph{upper bound} $|\kappa-\bar\kappa|\le 2/41$. +At $\beta=1/3$ the common retained mass $19/20$ gives a geometric +discount envelope $1/41$ on each side. Exact-head CI executed the +Lean checks, transitive-axiom checks and a meaningful false atom +control. These are compiler-backed implementation observations, not +independent mathematical case acceptance. + +The separately calculated finite-model score candidates are +$V_0(F)=1/30$, $V_J(F)=11/45$, $\bar V_0(F)=1/20$ and +$\bar V_J(F)=1/4$; at the $x=T$ comparison start the scores are +$7/10$, $41/45$, $43/60$ and $11/12$. They suggest exact candidate +margins $19/90$ and $1/5$, with difference $1/90\le 2/41$. +The \emph{proved bound} does not establish those precise +entire-path margin equalities. Original Q--05 acceptance still +requires exact score-integral identities, the full quantified +premise/comparison-set crosswalk and independent criterion-specific +review. No Exact GR/PR conclusion follows from a discounted bound. diff --git a/research_papers/mathematical_closure/sections/04_measurable_multiagent_recovery.tex b/research_papers/mathematical_closure/sections/04_measurable_multiagent_recovery.tex index 83b61a85..8b7fe512 100644 --- a/research_papers/mathematical_closure/sections/04_measurable_multiagent_recovery.tex +++ b/research_papers/mathematical_closure/sections/04_measurable_multiagent_recovery.tex @@ -1,5 +1,5 @@ \section{P--05: Measurable two-player reconstruction} -\textbf{Status: complete source law recovery remains open.} +\textbf{Status: generic measurable kernel and canonical entire-path recovery compiled in prior PRs; concrete binary matched-intervention/original-margin closure is the final integration.} The original strategic state is $S_0\times\prod_{i\in\mathrm{Fin}\,2}S_i$. The binary target is $S_0\times(S_0^{\rm priv}\times S_1^{\rm priv})$, with joint states formed by adjoining $X$. Distinct measurable maps @@ -30,3 +30,14 @@ \section{P--05: Measurable two-player reconstruction} both action and realized next-world state.} \end{figure} This concerns the declared conditional-product subclass only. + + +\paragraph{Admissible matched protocols.} +For each treated player $i\in\mathrm{Fin}\,2$, the binary counterpart is +constructed by applying the \emph{same} source closed-loop or admitted +open-loop source mechanism before reindexing. The original and binary +physical kernels remain separately fixed. This replaces the earlier +conditional \texttt{Jbar}/\texttt{hJ} input with real admitted binary +interventions and original all-measurable-event kernel intertwining. +Only the conditional-product subclass is included; hidden common +correlation is an excluded model class. diff --git a/research_papers/mathematical_closure/sections/05_nonlinear_occupation.tex b/research_papers/mathematical_closure/sections/05_nonlinear_occupation.tex index 9ce9a6a8..da887661 100644 --- a/research_papers/mathematical_closure/sections/05_nonlinear_occupation.tex +++ b/research_papers/mathematical_closure/sections/05_nonlinear_occupation.tex @@ -1,6 +1,5 @@ \section{P--06: Native strategic phase and nonlinear occupation} -\textbf{Status: P--06.1 native induced-kernel atoms and cycle Lean-checked; -full canonical occupation theorem and policy-class objective pending.} +\textbf{Status: native paths, selected stationary-class/score providers and constructed mixed-initial nonlinear witnesses compile; exact original-scope review and D--09 / N1--N3 independent acceptance remain open.} One native strategic-world model uses phase memory $S=\mathrm{Fin}\,3$, physical world $X=\mathrm{Bool}$, action $A=\mathrm{Bool}$ and the \emph{same} primitives $P((p,x),a)=\delta_a$ and @@ -25,3 +24,31 @@ \section{P--06: Native strategic phase and nonlinear occupation} Bool-only controller with $\Pr(a=T)=2/3$ can also attain $1$, so no unrestricted stationary or all-history dominance is claimed. This time-aware controller is itself stationary on augmented $S\times X$. + + +\paragraph{Canonical score source and required review.} +Finite-cycle arithmetic alone cannot substitute for evaluating +$F$ on the \emph{actual} stationary class selected by the original +infinite canonical path law. Current source includes selected +phase-class, stationary-mass, actual-score and restricted-controller +results. Independent acceptance must still check their exact +hypotheses, physical-class identity and original-path interpretation +for all four deterministic phase-blind feedbacks. A randomized +phase-blind policy can attain one, so no general policy-optimality +theorem is claimed. + +\paragraph{Separate actual mixed-initial example (D--09).} +For two physical absorbing classes in a constructed source model, +the Boolean descriptor's classwise nonlinear values are $1/3$ and +$2/3$. Under an equal-weight initial law, the actual original +canonical path-law integral gives $\mathbb E[F(\nu_C)]=1/2$, +whereas applying $F$ to the \emph{averaged descriptor law} yields +$5/6$. This is distinct from the native three-phase comparison. +The actual Lean source now contains +\code{actual_mixed_initial_nonlinear_expectation}, +\code{actual_equal_mixture_original_nonlinear_score} and +\code{actual_mixed_nonlinear_half_not_barycenter}. The exact-head +\code{test_final_d09_multichain} verified declarations/axioms and +rejected a false canonical-score claim. Those facts concern this +scoped constructed model; they are not independent D--09 acceptance, +full N1--N3 acceptance or an arbitrary multichain theorem. diff --git a/research_papers/mathematical_closure/sections/06_counterexamples_and_limits.tex b/research_papers/mathematical_closure/sections/06_counterexamples_and_limits.tex index d09f6d60..8c26f80f 100644 --- a/research_papers/mathematical_closure/sections/06_counterexamples_and_limits.tex +++ b/research_papers/mathematical_closure/sections/06_counterexamples_and_limits.tex @@ -14,3 +14,27 @@ \section{Counterexamples and non-implications} $\mathbb E[F(\nu_C)]\ne F(\mathbb E[\nu_C])$ in general, requiring classwise aggregation. No statement in this construction empirically identifies a Hormuz, Russia--Ukraine, Iran, or MIDR Permansson regime. + + +\paragraph{Additional final acceptance adversaries.} +The proof programme separately requires that a caller-supplied +controlled-menu predicate actually equals the frozen grammar's admission +condition (D--08), and that distinct physical recurrent classes are not +merged merely because their observed descriptor laws coincide (D--09). +For nonlinear $F$, the correct classwise expectation can differ from +$F$ evaluated on an averaged occupation law. Neither case is resolved by +a favorable one-class orbit or a merely syntactically rejected fake proof. + + +\paragraph{Reactive marked-history boundary (M--02).} +A typed conditional-product source candidate draws a fair opponent +Boolean action, then records $s'_2=a_2$ under player one's false action, +or $s'_2=\neg a_2$ under player one's true action. Both unmarked key +laws are fair, while the marked action/next-key diagonal event has +finite-model candidate probabilities one and zero. The actual private +update changes when $a_1$ changes at the \emph{same} admitted +opponent key and opponent action. Hence this source does not +qualify as a strictly non-reactive opponent-locked anchor. +The entire marked one-step history-kernel pushforward still requires +a compiled source-to-event identity; neither these finite rows nor +a reactive fixture refute the strict theorem under its stated premises. diff --git a/research_papers/mathematical_closure/sections/07_formal_verification.tex b/research_papers/mathematical_closure/sections/07_formal_verification.tex index d57dc278..a6255499 100644 --- a/research_papers/mathematical_closure/sections/07_formal_verification.tex +++ b/research_papers/mathematical_closure/sections/07_formal_verification.tex @@ -8,12 +8,12 @@ \section{Compiler, acceptance and source provenance} \begin{table}[htbp]\centering\small \begin{tabularx}{\linewidth}{@{}lXl@{}}\toprule Lane&Completion gate&Manuscript status\\\midrule -P--04&Scoped time marginals checked; original margin open&Partial\\ -P--05&Measurable full protocol/path&Pending\\ -P--06&Native kernel checked; occupation theorem open&Partial\\ -P--AUDIT&45 case-by-case checks&Pending\\ -P--PAPER&Full release, source hashes, visual inspection&Pending\\ -P--INT&True H/B/S/M and independent review&Pending\\\bottomrule +P--04&Canonical source witness, original full-$B_1$ error&Pending review\\ +P--05&Matched admitted protocols and original full-path margin&Pending review\\ +P--06&Native class stationary law and original actual score&Pending review\\ +P--AUDIT&New 45-case preserved-criterion audit&Pending review\\ +P--PAPER&Draft TeX/PDF release, hashes and visual inspection&Draft\\ +P--INT&True B/H/S/M and independent review&Pending review\\\bottomrule \end{tabularx} \caption{Scoped Lean results do not constitute complete P--04/P--06 programme acceptance, manuscript approval, or empirical regime certification.} @@ -21,3 +21,50 @@ \section{Compiler, acceptance and source provenance} The historical PR79 source receipts cannot be silently rewritten. Draft manuscript, archival publication, operational release and independent academic review are different events. + + +The PR may pass draft artifact integrity while failing the full-45 +acceptance gate. Approval requires genuine compiler and negative-mutant +evidence on the exact final source, authentic Git objects and reviewers, +and an independently inspected real PDF. No historical receipt is +retroactively rewritten to create the appearance of a passed review. + + +\paragraph{Compiler versus accepted evidence.} +The current exact-type compiler exporter records the complete +elaborated declaration type, universe parameters and transitive axioms, +along with original source bytes; lexical declaration headers are only +non-authoritative display metadata. The original 45-case work map +records candidate test-method routes, not independently approved +criterion-level evidence. A separately retained run/attempt stamp +cannot obtain a genuine GitHub check-run identity solely from green CI. +Actual accepted case/claim decisions remain derived from detached, +authenticated execution and authorized independent review. The +two-clean-source-root draft PDF build remains a reproducibility +candidate, not publication approval. + + +\paragraph{Original-case semantic evidence is not route registration.} +The source-candidate acceptance ledger now selects at least one positive +and one semantic-negative test-method route for each of the original +forty-five criteria. This is a complete \emph{routing inventory}, not a +claim that forty-five positive and forty-five negative compilations have +succeeded or been independently accepted. Twenty-five case routes +add a false statement to the unchanged original Lean provider; the +other twenty run historical, individually named false-probe tests. +Raw compiler observations from the latter must not be relabelled as +original-source mutants. An optional append-only replay must identify +an imported original theorem provider, retain that exact source, +and recompile both sides on the frozen head before it can even be a +source-bound negative candidate. + +Every accepted negative receipt must retain the genuine original Lean +source bytes, the appended false source bytes, positive and negative +diagnostics, source/test hashes and exact source identity. A green +unittest, a correct-looking diagnostic or a self-reported GitHub run +number is not independent mathematical evidence. The detached +candidate workflows cannot issue the external CI-job authenticity +or non-author case/claim review required by the strict acceptance gate. +The exact eleven additional mathematical claims remain separately +subject to full elaborated-type, transitive-axiom and scope review. +No compiler candidate proves empirical Exact GR/PR identification. diff --git a/verification/integrity/tests/test_workflow.py b/verification/integrity/tests/test_workflow.py index 66ba6747..1fa90e0d 100644 --- a/verification/integrity/tests/test_workflow.py +++ b/verification/integrity/tests/test_workflow.py @@ -32,7 +32,7 @@ def rejected(self, callback, code): def test_ci_06_filters_and_skip_paths_forbidden(self): # This is the production gate, bound to canonical HEAD. A changed # profile must be committed before a full suite can certify it. - self.assertEqual(19, validate_workflow(self.text)["workflows"]) + self.assertEqual(25, validate_workflow(self.text)["workflows"]) self.assertEqual(3, len(self.checked(self.text)["contexts"])) for snippet in (" paths: ['docs/**']\n", " paths-ignore: ['release/**']\n", " if: false\n", " needs: absent\n", " continue-on-error: true\n"): @@ -123,6 +123,20 @@ def test_shell_background_and_redirection_rejected(self): def test_execution_overrides_rejected(self): self.checked(self.text) + # A profile change must not cancel checks on a different ref, + # silently disable superseded-run cleanup, or override its scope. + concurrency = ("concurrency:\n" + " group: ${{ github.workflow }}-${{ github.ref }}\n" + " cancel-in-progress: true\n") + self.assertIn(concurrency, self.text) + for drift in ( + concurrency.replace("cancel-in-progress: true", "cancel-in-progress: false"), + concurrency.replace("github.ref", "github.repository"), + concurrency.replace("github.workflow", "github.repository"), + concurrency + " max-parallel: 1\n", + ): + self.rejected(lambda: self.checked(self.text.replace( + concurrency, drift, 1)), "E_WORKFLOW_CONTRACT") for override in (" env:\n PATH: /tmp/shadow\n", " defaults:\n run:\n shell: bash -c 'true'\n", " shell: bash -c 'true'\n", @@ -163,7 +177,7 @@ def test_execution_overrides_rejected(self): def test_required_workflow_rejects_dispatch(self): checked = self.checked(self.text) - self.assertEqual(19, checked["workflows"]) + self.assertEqual(25, checked["workflows"]) self.assertEqual(5, len(checked["legacy_contexts"])) candidate = self.text.replace("on:\n", "on:\n workflow_dispatch:\n", 1) self.rejected(lambda: self.checked(candidate), "E_WORKFLOW_CONTRACT") @@ -220,7 +234,7 @@ def test_required_workflow_rejects_dispatch(self): (checkout / "duplicate-required.yaml").write_bytes(b"jobs: {build: {}}\n") canonical = load_workflows(fixture.root) self.assertEqual(self.workflows, canonical) - self.assertEqual(19, self.checked(self.text, canonical)["workflows"]) + self.assertEqual(25, self.checked(self.text, canonical)["workflows"]) path = b".github/workflows/lean.yml" variants = ( {path: ("100644", self.workflows["lean.yml"].replace("\n", "\r\n").encode())}, diff --git a/verification/integrity/workflow_contract.py b/verification/integrity/workflow_contract.py index 7c1b2655..f8fa8196 100644 --- a/verification/integrity/workflow_contract.py +++ b/verification/integrity/workflow_contract.py @@ -16,7 +16,7 @@ # effective execution. A profile change requires a separately reviewed update # to this source constant. There is no runtime override. The candidate still # owns this checker, so the pin does not authenticate its own human review. -APPROVED_WORKFLOW_SHA256 = "70cd7e8569beb995981be210369f57c34460289e6a5890c54153683a26939093" +APPROVED_WORKFLOW_SHA256 = "23bdb913dd2813dc9a3484bd40d142fa08ffb43da01efcda1f1b59284c1caed1" # Review the entire workflow set, not a grep of job names: another YAML file # can publish a colliding required context through quoted/flow/tagged or dynamic @@ -30,7 +30,7 @@ APPROVED_WORKFLOWS = { "a2-smoke.yml": "9ac3460a79cf861b43d5329d06a03650cc8c4de09d24cd677849eb1772c73a19", "a3-contractive-smoke.yml": "b7e3b723bb25e5287bdd60ee1470fabeeaac318617cca4f9ed1931c5a01c7931", - "application.yml": "356d62c5e8203ff9cc478f6d5f19d6bccb018eec5c3612b4d30d4b318c8db7e0", + "application.yml": "1abb20c60b1eeb7fd7eae51048aa7e3729a64a652772760bc4f272f57d3d5da4", "b2-smoke.yml": "3204afca3fb58bc72f6c90bfe6ca2b17e06b9b6c2b500b6087ee4c66ca7baebc", "c-exit-random-entry-smoke.yml": "499a5c76ecff1ab3b891df24477b249f9809b13201e9cee6b0a84250b3087536", "c-exit-restart-d-smoke.yml": "1ae741ce18c04d54bf3a25f82ef29041188551f8472757904b4675b2fbd443c3", @@ -38,14 +38,20 @@ "c-exit-smoke.yml": "a6f5fcbd0c087848c02538f2aebbf4c0bc7cf655dbf4197d366c88f67295eaf7", "c-exit-switch-smoke.yml": "5ebe0c3b24621ef2e2e4cfa518a48cca39f4e576e72405122730daca0985a7c4", "d2-residence-smoke.yml": "47b95f2477cccb552074eb9168bdc4f28d5d7d456892a9b2ac44959e519d7908", - "e1-approx-quotient-smoke.yml": "73e09702dd2eb99c5da85d7efa86e6bfdd05b936a71c67b0d8fb4981c3a29ac5", - "f1-multiagent-protocols-smoke.yml": "475242f86682e23f8924d0f9b5e26db7f35971ee6c7c682cebc24ed6ef62c0ba", - "lean.yml": "2dc2bc23f6013faed093fbff8238801b0dab1bc6a121c9ea12dc07c99fa20432", - "research-integrity-portability.yml": "23c0b16125590d19c017a191b1b4a0b41ea8b15063fc13b246352901f64ad43e", + "e1-approx-quotient-smoke.yml": "adb2a60518e1fbbebc897ba007c16f2a5455a5b2ffef69765e71b9de7ae80099", + "f1-multiagent-protocols-smoke.yml": "3c67b8cf4f18c43bf0af97d362e630e3aeb3ffe92bfbca5fc6d3e763b741f7b9", + "lean.yml": "d5a3123455c359356c755c4be7131c74122003d897c8f4100f1e01ade1e54a1e", + "pr113-all-case-compiler.yml": "6e7bf24815b5e5e42779b730633180d2bfa32602176a75eca74e1515f1a0c7ed", + "pr113-all-original-semantic-candidates.yml": "c021166eb27a10910be58b3c04515c59f5585c025b742355289fa282491cec02", + "pr113-clean-paper-rebuild.yml": "e744174c3c8f6c50fb4e404702601a35b53c2925517fa7a4210849edbcf3c0ed", + "pr113-compiler-evidence-candidate.yml": "f39066cd16a36860e44505f3b740f29e7169f4fb424897ac44010128b4592cf2", + "pr113-legacy-semantic-evidence.yml": "dddcc2c471cf13770e52dbb89329cc842ba83de73a3d1852edb7913f94067252", + "pr113-original-semantic-candidates.yml": "8a0e1abcd9ce4c574c95fa813bd27e8cf47ccf5d164541b9692d31db4be128b6", + "research-integrity-portability.yml": "8bdb9e5fe46a7f2bdf7f205cf4d537e07e9dcf37cecfe67f5a5ccc28064bc741", "research-integrity.yml": APPROVED_WORKFLOW_SHA256, - "research-lean.yml": "cb676566d9c0867b4b990ff83dffded6927972aca58068c6ab185cdf6af64eca", - "research-release-verification.yml": "6990c58ca64a260eab301ae022974b2642d80558817def87581087751a83821b", - "verification.yml": "8ec37551fc4a7b71e24fac0d5ead92ab308c40b4c35fe83f8befec39f14ce302", + "research-lean.yml": "5de749bf9deb22d4c9b0a3cd25db191f76a74d77ae668240f5adf9ea6f4eb6fd", + "research-release-verification.yml": "f66d6e1d7d46a16136314895456c62184ef496fbc9e4434edddaa2e2d4a67572", + "verification.yml": "ec40e169ecd9c5927731989b70f5aa4d1e220614b9aa21a4e498a19e7c5996cb", "x1-commons-smoke.yml": "606e8cf1b579f3012f534799e514c372a51e67cef990972718dc1724b206ea2d", } LEGACY_CONTEXTS = {"build", "research", "executable-verification", @@ -120,6 +126,12 @@ def reject(message): reject("Legacy source binding must propagate its real exit status") if " - name: Preserve required-check source context\n if: always()\n" not in text: reject("Legacy required contexts must retain their actual source-binding report") + for name in sorted(n for n in workflows if n.startswith("pr113-")): + optional = workflows[name] + if (optional.count("\npermissions:\n contents: read\n") != 1 or + "actions: write" in optional or + re.search(r"(?m)^ (?:frozen-source-preservation|release-export-integrity|archive-reproducibility|build|research|executable-verification|application-contract):", optional)): + reject("PR113 candidate workflow may not impersonate required jobs or elevate token permissions") for name, digest in APPROVED_WORKFLOWS.items(): text = workflows[name] if not isinstance(text, str) or hashlib.sha256(text.encode("utf-8")).hexdigest() != digest: @@ -146,8 +158,17 @@ def reject(message): or re.search(r"^[ \t]*<<[ \t]*:", text, re.M)): reject("Quoted/explicit keys, aliases and flow mappings are outside the reviewed workflow profile") top_keys = re.findall(r"^([a-z][a-z-]*):", text, re.M) - if len(top_keys) != 4 or set(top_keys) != {"name", "on", "permissions", "jobs"}: + if len(top_keys) != 5 or set(top_keys) != {"name", "on", "concurrency", "permissions", "jobs"}: reject("Unexpected or duplicated top-level workflow configuration") + # Every required job is allowed only this exact branch-scoped cancellation + # policy. Cross-branch cancellation or permission escalation remains barred. + fixed_concurrency = ( + "\nconcurrency:\n" + " group: ${{ github.workflow }}-${{ github.ref }}\n" + " cancel-in-progress: true\n" + ) + if text.count(fixed_concurrency) != 1: + reject("Unreviewed concurrency group or cancellation override") if re.search(r"^[ \t]*(?:shell|defaults|env|environment|working-directory|container|services|strategy)[ \t]*:", text, re.M): reject("Execution, environment and job-shell overrides are outside the reviewed profile") if re.search(r"^[ \t]*workflow_dispatch[ \t]*:", text, re.M): diff --git a/verification/research/closure_elaborated_type.py b/verification/research/closure_elaborated_type.py new file mode 100644 index 00000000..8ffb3f80 --- /dev/null +++ b/verification/research/closure_elaborated_type.py @@ -0,0 +1,94 @@ +"""Read complete elaborated declaration types from a genuine pinned Lean environment. + +This exporter uses the kernel Environment's ConstantInfo rather than slicing +source at the first textual ':=' (which may occur inside a named argument). +The Lean AST representation includes all elaborated binders, levels, and terms. +The byte digest is a deterministic *compiler evidence candidate*, not itself +an approved review, axiom audit, or final CI attestation. +""" +from __future__ import annotations + +import hashlib +import json +import re + +BEGIN = "PERMANSSON_ELABORATED_TYPE_V1_BEGIN" +END = "PERMANSSON_ELABORATED_TYPE_V1_END" +DECL = re.compile(r"^[A-Za-z0-9_'.]+\.[A-Za-z0-9_']+$") +MODULE = re.compile(r"^[A-Za-z0-9_'.]+$") +TYPES = frozenset(( + "axiom", "definition", "theorem", "opaque", + "quotient", "inductive", "constructor", "recursor", +)) + + +class TypeEvidenceError(ValueError): + pass + + +def lean_probe(module: str, declaration: str) -> str: + if not MODULE.fullmatch(module) or not DECL.fullmatch(declaration): + raise TypeEvidenceError("invalid compiler module or qualified declaration") + # The interpolated declaration is an exact checked identifier, not model text. + # The Repr Expr instance in pinned Lean 4 serializes the elaborated AST. + return ( + "import Lean\n" + f"import {module}\n" + "open Lean Elab Command\n" + "run_cmd do\n" + f" let nm : Name := `{declaration}\n" + " let some info := (← getEnv).find? nm |\n" + " throwError \"no kernel declaration for requested name\"\n" + " let kind : String := match info with\n" + " | .axiomInfo _ => \"axiom\"\n" + " | .defnInfo _ => \"definition\"\n" + " | .thmInfo _ => \"theorem\"\n" + " | .opaqueInfo _ => \"opaque\"\n" + " | .quotInfo _ => \"quotient\"\n" + " | .inductInfo _ => \"inductive\"\n" + " | .ctorInfo _ => \"constructor\"\n" + " | .recInfo _ => \"recursor\"\n" + " logInfo m!\"" + BEGIN + + "\\nNAME={info.name.toString}\\nKIND={kind}" + + "\\nUNIVERSES={reprStr info.levelParams}" + + "\\nTYPE={reprStr info.type}\\n" + END + "\"\n" + f"#print axioms {declaration}\n" + ) + + +def compiled_type_record(log: str, declaration: str) -> dict: + if not isinstance(log, str) or not DECL.fullmatch(declaration): + raise TypeEvidenceError("invalid Lean type export input") + if len(log.encode("utf-8")) > 4 * 1024 * 1024: + raise TypeEvidenceError("compiler type diagnostic exceeds bound") + pattern = re.compile( + re.escape(BEGIN) + r"\nNAME=(?P[^\n]+)" + + r"\nKIND=(?P[^\n]+)" + + r"\nUNIVERSES=(?P.*?)" + + r"\nTYPE=(?P.*?)\n" + re.escape(END), + re.DOTALL, + ) + matches = list(pattern.finditer(log)) + if len(matches) != 1: + raise TypeEvidenceError("missing/duplicate complete compiled type record") + item = matches[0].groupdict() + if item["name"] != declaration or item["kind"] not in TYPES or \ + not item["universes"].strip() or not item["type"].strip(): + raise TypeEvidenceError("wrong target, kind, universes or empty elaborated type") + return { + "declaration": item["name"], + "declaration_kind": item["kind"], + "universe_params_repr": item["universes"], + "type_expr_repr": item["type"], + } + + +def elaborated_type_sha256(record: dict) -> str: + if not isinstance(record, dict) or set(record) != { + "declaration", "declaration_kind", + "universe_params_repr", "type_expr_repr", + }: + raise TypeEvidenceError("invalid canonical type-record shape") + raw = json.dumps(record, sort_keys=True, ensure_ascii=False, + separators=(",", ":")).encode("utf-8") + return hashlib.sha256(raw).hexdigest() diff --git a/verification/research/final_closure_all_original_semantics.py b/verification/research/final_closure_all_original_semantics.py new file mode 100644 index 00000000..eae6ade7 --- /dev/null +++ b/verification/research/final_closure_all_original_semantics.py @@ -0,0 +1,186 @@ +"""Retain *candidate* source-bound Lean semantic negatives for all registered specs. + +This is NOT independent acceptance or an authenticated CI review receipt. +It runs the already-frozen per-case source+mutant compiler process, sharded +over the exact registered candidate IDs, and retains their byte hashes. +The actual producing GitHub run/job must be authenticated separately. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys + +import final_closure_original_semantic_batch as batch +from final_closure_mutation_evidence import MutationEvidenceError, git_head + +EXPECTED = frozenset(( + "F-03", "D-02", "D-03", "D-04", "D-05", "D-06", + "Q-03", "Q-04", "E-05", "A-03", "C-01", "D-07", + "E-01", "S-01", "M-01", "A-02", "D-01", + "C11-01", "C11-02", "XPLUS-01", + "S-02", "Q-01", "A-01", "A-04", "A-05", +)) + + +def validate_candidate_case(head: str, cid: str, record: dict) -> dict: + """A successful job must retain true/false execution, not just status. + + This checks the local producing payload before attaching any claimed CI + identity. The external v3 authenticated producer still validates actual + GitHub run/job/artifact and source/mutant ZIP bytes separately. + """ + entry = batch.SPECS.get(cid) + if not isinstance(entry, dict) or not isinstance(record, dict): + raise MutationEvidenceError("original semantic case/record absent") + expected = { + "format": "Permansson.OriginalSemanticMutantCandidate.v1", + "status": "DETACHED_UNREVIEWED_UNATTRIBUTED", + "source_head": head, + "case_id": cid, + "test_path": entry["test_path"], + "test_name": entry["method"], + "source_path": entry["source"], + "ci_check_run_id": None, + "independent_review": False, + "full_mathematical_acceptance": False, + } + if any(record.get(k) != v or type(record.get(k)) is not type(v) + for k, v in expected.items()): + raise MutationEvidenceError( + "original semantic evidence returned foreign source/role/approval") + hashes = ("mutant_sha256", "detached_bundle_sha256") + if not all(isinstance(record.get(k), str) and + re.fullmatch(r"[0-9a-f]{64}", record[k]) for k in hashes): + raise MutationEvidenceError("original semantic source/ZIP digests absent") + original = record.get("mutation_record") + if (not isinstance(original, dict) + or original.get("head_sha") != head + or original.get("case_ids") != [cid] + or original.get("claim_ids") != [] + or original.get("test_path") != entry["test_path"] + or original.get("test_name") != entry["method"] + or original.get("original_lean_source_path") != entry["source"] + or original.get("mutant_source_sha256") != record["mutant_sha256"] + or original.get("outcome") != "REJECTED_SEMANTIC" + or type(original.get("compiler_exit_code")) is not int + or original["compiler_exit_code"] == 0 + or original.get("check_run_id") is not None + or original.get("review_status") != "NOT_RECEIVED"): + raise MutationEvidenceError( + "original semantic case lacks actual bound false Lean execution") + positive = original.get("positive_control") + if (not isinstance(positive, dict) + or positive.get("compiled") is not True + or positive.get("exit_code") != 0 + or not isinstance(positive.get("lean_toolchain"), str) + or not re.fullmatch(r"[0-9a-f]{64}", + str(original.get("original_lean_source_sha256", ""))) + or not re.fullmatch(r"[0-9a-f]{64}", + str(original.get("source_sha256", "")))): + raise MutationEvidenceError( + "original positive Lean and exact source bytes were not retained") + return original + + +def select_cases(shard_index: int, shard_count: int) -> tuple[str, ...]: + if not 1 <= shard_count <= 25 or not 0 <= shard_index < shard_count: + raise MutationEvidenceError("invalid original-semantic shard specification") + if frozenset(batch.SPECS) != EXPECTED or len(batch.SPECS) != 25: + raise MutationEvidenceError("registered source/mutant inventory changed") + ordered = sorted(EXPECTED) + return tuple(case for j, case in enumerate(ordered) + if j % shard_count == shard_index) + + +def run_exact_shard(head: str, shard_index: int, shard_count: int, + output: Path) -> dict: + if not re.fullmatch(r"[0-9a-f]{40}", head) or git_head() != head: + raise MutationEvidenceError("source head is not the checked-out Git object") + output = output.resolve() + root = batch.ROOT.resolve() + if (output.exists() or output.is_relative_to(root) or + root.is_relative_to(output)): + raise MutationEvidenceError("untrusted/overlapping evidence output root") + ids = select_cases(shard_index, shard_count) + if not ids: + raise MutationEvidenceError("semantic evidence shard has no assigned case") + source_tree = subprocess.check_output( + ["git", "rev-parse", "HEAD^{tree}"], cwd=root, text=True).strip() + if not re.fullmatch(r"[0-9a-f]{40}", source_tree): + raise MutationEvidenceError("invalid checked-out Git tree") + output.mkdir(parents=True, exist_ok=False) + completed = [] + for cid in ids: + record = batch.run_one(head, cid, output / cid) + validate_candidate_case(head, cid, record) + (output / cid / "unreviewed_candidate.json").write_text( + json.dumps(record, indent=2, sort_keys=True) + "\n", + encoding="utf-8") + completed.append({ + "case_id": cid, + "source_sha256": record["mutation_record"]["original_lean_source_sha256"], + "mutant_sha256": record["mutant_sha256"], + "bundle_sha256": record["detached_bundle_sha256"], + "status": record["status"], + }) + # Rebind immutable source identity after the last real original-source + # mutation execution. An externally observed source transition is a + # failure, not a candidate with a borrowed old-H hash. + if git_head() != head: + raise MutationEvidenceError("Git HEAD changed during semantic execution") + after_tree = subprocess.check_output( + ["git", "rev-parse", "HEAD^{tree}"], cwd=root, text=True).strip() + if after_tree != source_tree: + raise MutationEvidenceError("Git source tree changed during semantic execution") + ci_keys = ("GITHUB_REPOSITORY", "GITHUB_WORKFLOW", "GITHUB_JOB", + "GITHUB_RUN_ID", "GITHUB_RUN_ATTEMPT", "GITHUB_SHA") + ci = {key: os.environ.get(key) for key in ci_keys} + # The environment is a self-report until authenticated against the actual + # GitHub job and uploaded artifact by the independent collector. + manifest = { + "format": "Permansson.AllOriginalSemanticCandidateShard.v1", + "status": "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED_RECEIPT", + "head": head, + "tree": source_tree, + "shard_index": shard_index, + "shard_count": shard_count, + "registered_candidate_count": len(EXPECTED), + "case_ids": list(ids), + "completed": completed, + "github_environment_unverified": ci, + "independent_review": False, + "ci_check_run_id": None, + "release_approved": False, + "mathematical_acceptance": False, + } + payload = (json.dumps(manifest, sort_keys=True, indent=2) + "\n").encode() + (output / "unreviewed_shard_manifest.json").write_bytes(payload) + return {"status": manifest["status"], "head": head, + "cases": list(ids), "manifest_sha256": hashlib.sha256(payload).hexdigest()} + + +def main(argv=None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--head", required=True) + parser.add_argument("--shard-index", type=int, required=True) + parser.add_argument("--shard-count", type=int, required=True) + parser.add_argument("--out", type=Path, required=True) + a = parser.parse_args(argv) + try: + result = run_exact_shard(a.head, a.shard_index, a.shard_count, a.out) + except (MutationEvidenceError, OSError, ValueError, + subprocess.CalledProcessError, KeyError) as e: + print("DETACHED UNREVIEWED EVIDENCE BLOCKED:", e, file=sys.stderr) + return 2 + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_assemble.py b/verification/research/final_closure_assemble.py new file mode 100644 index 00000000..dfd17e64 --- /dev/null +++ b/verification/research/final_closure_assemble.py @@ -0,0 +1,141 @@ +"""Assemble an UNREVIEWED external premerge receipt candidate from real H CI. + +This tool cannot manufacture case/claim decisions or reviewer IDs. Those +remain empty pending independent, exact-H scope review and signed payload +binding. Re-run on each final source H; never check future H into that H. +""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path +import sys + +from final_closure_authenticate import ( + BASE, REQUIRED, authenticated_checks, git, AuthenticationError, +) +from final_closure_case_receipts import ( + ROOT, FORMAT, EvidenceError, load_detached_bundle, +) +from final_closure_schema import validate_overlay + + +def sha(raw: bytes) -> str: + return hashlib.sha256(raw).hexdigest() + + +def assemble(head: str, synthetic: str, *, compiler_candidate: Path, + mutation_candidates: Path, bundle: Path): + if git("rev-parse", "HEAD") != head: + raise AuthenticationError("reviewed Git worktree differs from branch HEAD") + for h in (head, synthetic): + if git("cat-file", "-t", h) != "commit": + raise AuthenticationError("missing real source/synthetic Git object") + if git("show", "-s", "--format=%P", synthetic).split() != [BASE, head]: + raise AuthenticationError("wrong synthetic source B/H parent ordering") + tree = git("rev-parse", head+"^{tree}") + if git("rev-parse", synthetic+"^{tree}") != tree: + raise AuthenticationError("synthetic merge changes source tree") + checks = authenticated_checks(head) + evidence = load_detached_bundle(bundle, sha(bundle.read_bytes())) + source = json.loads(compiler_candidate.read_text()) + mutants = json.loads(mutation_candidates.read_text()) + if (source.get("head_sha") != head or + source.get("status") != "COMPILER_ONLY_UNREVIEWED" or + not isinstance(source.get("compiler_axiom_receipts"), list) or + not source["compiler_axiom_receipts"] or + not isinstance(mutants, list)): + raise EvidenceError("candidate compiler/mutant sources are incomplete") + compiler = [] + for row in source["compiler_axiom_receipts"]: + if row.get("head_sha") != head or row.get("compiler_result") != "PASS": + raise EvidenceError("wrong head or false proof candidate") + copy = dict(row) + copy.pop("CI_binding", None) + copy.pop("independent_review", None) + # CI passed at H, but did NOT necessarily execute this detached probe. + copy["check_run_id"] = None + compiler.append(copy) + negatives, positives = [], [] + for row in mutants: + if row.get("head_sha") != head or row.get("outcome") != "REJECTED_SEMANTIC": + raise EvidenceError("unverified mutation source or wrong head") + negative = dict(row) + negative.pop("CI_binding", None) + negative.pop("review_status", None) + # A matching CI check alone never authenticates local mutation logs. + negative["check_run_id"] = None + negatives.append(negative) + positive = dict(negative) + positive["id"] = "positive:" + negative["id"] + positive["outcome"] = "PASS" + positive["compiler_exit_code"] = 0 + positive["log_member"] = negative["positive_control"]["log_member"] + positive["log_sha256"] = negative["positive_control"]["log_sha256"] + for key in ("mutant_source_member", "mutant_source_sha256", + "original_lean_source_sha256", + "original_lean_source_member", + "original_lean_source_path"): + positive.pop(key, None) + positives.append(positive) + for row in compiler: + if row["axiom_log_member"] not in evidence: + raise EvidenceError("compiler axiom log not sealed into evidence bundle") + for row in negatives: + for key in ("log_member", "mutant_source_member", + "original_lean_source_member"): + if row[key] not in evidence: + raise EvidenceError("semantic mutant source/log missing from bundle") + out = { + "format": FORMAT, + "phase": "premerge_review", + "base_sha": BASE, + "head_sha": head, + "synthetic_sha": synthetic, + "merged_main_sha": None, + "actual_synthetic_parents": [BASE, head], + "synthetic_tree_sha": tree, + "evidence_bundle_sha256": sha(bundle.read_bytes()), + "source_bound_ci": [ + {"name": name, "id": checks[name]["id"], "head_sha": head} + for name in sorted(REQUIRED)], + "compiler_axiom_receipts": compiler, + "positive_test_receipts": positives, + "semantic_negative_receipts": negatives, + "case_decisions": [], + "claim_decisions": [], + "independent_source_reviews": [], + "trusted_axioms": ["Classical.choice", "Quot.sound", "propext"], + } + validate_overlay(out) + return out + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--head", required=True) + ap.add_argument("--synthetic", required=True) + ap.add_argument("--compiler-candidate", required=True, type=Path) + ap.add_argument("--mutation-candidates", required=True, type=Path) + ap.add_argument("--bundle", required=True, type=Path) + ap.add_argument("--output", required=True, type=Path) + args = ap.parse_args(argv) + try: + if args.output.resolve().is_relative_to(ROOT.resolve()): + raise EvidenceError("external reviewed data must not be committed into H") + payload = assemble(args.head, args.synthetic, + compiler_candidate=args.compiler_candidate, + mutation_candidates=args.mutation_candidates, + bundle=args.bundle) + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(payload, indent=2, sort_keys=True)+"\n") + except (OSError, KeyError, ValueError, AuthenticationError) as exc: + print("PREMERGE RECEIPT ASSEMBLY BLOCKED:", exc, file=sys.stderr) + return 2 + print(json.dumps({"source": args.head, "status": "UNREVIEWED_SOURCE_CANDIDATE", + "case_decisions": 0, "claim_decisions": 0})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_assemble_v3.py b/verification/research/final_closure_assemble_v3.py new file mode 100644 index 00000000..dd1d5b15 --- /dev/null +++ b/verification/research/final_closure_assemble_v3.py @@ -0,0 +1,332 @@ +"""Assemble an UNREVIEWED, producer-authenticated v3 source candidate. + +Input: a completed exact-H 21-shard catalog created by +final_closure_producer_catalog.py, its original ZIPs saved as .zip, +a real B/H synthetic merge S, and twelve successful exact-H checks. + +This does NOT manufacture missing twenty legacy append-only replays, positive +method-route proofs, eleven claim negative controls, any case/claim decision, +any GitHub reviewer, historical security authorization or scholarly release. +Only bytes actually present in the original authenticated producing ZIPs may +be entered as proof/test evidence. Never mutate old v2 receipt generation. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import sys +import zipfile + +from final_closure_authenticate import ( + BASE, REQUIRED, authenticated_checks, git, AuthenticationError) +from final_closure_case_receipts import ( + ROOT, FORMAT_V3, digest, EvidenceError, parse_target_axioms) +from final_closure_schema import validate_overlay +from final_closure_producer_bytes import authenticate, original_member +from final_closure_producer_v2_bridge import verified_producer_index +from final_closure_review_dossier import extracted_case_refs +from final_closure_mutation_evidence import committed_file_bytes +from closure_elaborated_type import compiled_type_record, elaborated_type_sha256 + + +class AssemblyError(ValueError): + pass + + +def require(condition, reason): + if not condition: + raise AssemblyError(reason) + + +def load_json(path): + return json.loads(Path(path).read_bytes()) + + +def check_external(path, *, folder=False): + value = Path(path) + require(not value.is_symlink(), "symlinked producer evidence forbidden") + value = value.resolve(strict=True) + root = ROOT.resolve() + require(not value.is_relative_to(root) and + not root.is_relative_to(value), + "candidate evidence must be external to source checkout") + require(value.is_dir() if folder else value.is_file(), + "missing producing evidence input") + return value + + +def exact_source_bytes(head, relative): + path = ROOT / relative + return committed_file_bytes(head, path, root=ROOT) + + +def pack_member(members, name, raw): + require(type(name) is str and type(raw) is bytes and + name not in members and + not name.startswith("/") and ".." not in Path(name).parts and + "\\" not in name and + len(raw) <= 4*1024*1024, "unsafe or duplicated detached member") + members[name] = raw + + +def proof_row(head, source, producer, raw_log, outer): + """Full elaborated Lean type, not a truncated lexical statement header.""" + idx = source.get("inventory_index") + require(type(idx) is int and 0 <= idx < 259 and + source.get("id") == f"all:{idx:04d}" and + source.get("head_sha") == head and + source.get("compiler_result") == "PASS" and + source.get("compiler_check") == "research" and + source.get("check_run_id") is None, + "invalid 259-target source theorem row") + decl = source["declaration"] + require(digest(raw_log) == source["axiom_log_sha256"] and + digest(exact_source_bytes(head, source["path"])) == + source["source_sha256"], + "compiler theorem source/log differs from exact H") + compiled = compiled_type_record(raw_log.decode("utf-8"), decl) + elaborated = elaborated_type_sha256(compiled) + require(elaborated == source["elaborated_type_sha256"] and + parse_target_axioms(raw_log.decode("utf-8"), decl) == + source["transitive_axioms"], "Lean type/axiom witness mismatch") + # v3 statement_sha256 is explicitly the complete kernel-elaborated type + # representation hash. It is never a guessed lexical declaration header. + keys = ("id", "head_sha", "path", "declaration", "source_sha256", + "transitive_axioms", "compiler_result", "axiom_log_sha256", + "case_ids", "claim_ids", "elaborated_type_sha256", + "declaration_kind", "universe_params_repr") + row = {key: source[key] for key in keys} + row.update({ + "statement_sha256": elaborated, + "compiler_check": "pr113-compiler-shard", + "check_run_id": producer["actual_check_run_id"], + "axiom_log_member": outer, + "producer_artifact_id": producer["artifact_id"], + "producer_member_bindings": [{ + "outer_member": outer, + "source_member": source["axiom_log_member"]}], + }) + return row + + +def append_case_rows(head, case, producer, original_members, result_members): + """Preserve source-owned false mutants and their *actual* positive logs.""" + candidate = json.loads(original_members[case+"/unreviewed_candidate.json"]) + source = candidate["mutation_record"] + require(candidate.get("case_id") == case and + candidate.get("source_head") == head and + source.get("head_sha") == head and + source.get("case_ids") == [case] and + source.get("check_run_id") is None and + source.get("outcome") == "REJECTED_SEMANTIC" and + candidate.get("independent_review") is False, + "case is not an unreviewed source-executed mutation") + require(digest(exact_source_bytes(head, source["test_path"])) == + source["source_sha256"], "original named test source does not match H") + controls = source["positive_control"] + names = (source["log_member"], controls["log_member"], + source["mutant_source_member"], + source["original_lean_source_member"]) + paths = {} + for name in set(names): + member = case+"/source_logs/"+name + require(member in original_members, "source-generated diagnostic missing") + outer = "append/"+member + raw = original_members[member] + pack_member(result_members, outer, raw) + paths[name] = (outer, member) + for key, name in (("log_sha256", source["log_member"]), + ("mutant_source_sha256", source["mutant_source_member"]), + ("original_lean_source_sha256", + source["original_lean_source_member"])): + require(digest(result_members[paths[name][0]]) == source[key], + "original positive/negative/source digest mismatch") + require(digest(result_members[paths[controls["log_member"]][0]]) == + controls["log_sha256"], "positive control bytes are not preserved") + require(digest(exact_source_bytes(head, source["original_lean_source_path"])) == + source["original_lean_source_sha256"], + "true original source changed at H") + def links(used): + return [{"outer_member":paths[name][0], + "source_member":paths[name][1]} + for name in sorted(set(used))] + base = {key:source[key] for key in ( + "head_sha","test_path","test_name","source_sha256","case_ids","claim_ids")} + base.update(check_run_id=producer["actual_check_run_id"], + producer_artifact_id=producer["artifact_id"]) + positive = { + **base, "id":"positive:"+source["id"], + "positive_control":dict(controls, log_member=paths[controls["log_member"]][0]), + "outcome":"PASS", "compiler_exit_code":0, + "log_member":paths[controls["log_member"]][0], + "log_sha256":controls["log_sha256"], + "producer_member_bindings":links([controls["log_member"]]), + } + negative = { + **base, "id":"negative:"+source["id"], + "positive_control":dict(controls, log_member=paths[controls["log_member"]][0]), + "outcome":"REJECTED_SEMANTIC", + "compiler_exit_code":source["compiler_exit_code"], + "log_member":paths[source["log_member"]][0], + "log_sha256":source["log_sha256"], + "mutant_source_member":paths[source["mutant_source_member"]][0], + "mutant_source_sha256":source["mutant_source_sha256"], + "original_lean_source_member":paths[source["original_lean_source_member"]][0], + "original_lean_source_sha256":source["original_lean_source_sha256"], + "original_lean_source_path":source["original_lean_source_path"], + "producer_member_bindings":links(names), + } + return positive, negative + + +def assemble(head, synthetic, catalog_dir, artifacts_dir, output): + catalog_dir = check_external(catalog_dir, folder=True) + artifacts_dir = check_external(artifacts_dir, folder=True) + output = Path(output).resolve() + require(not output.exists() and + not output.is_relative_to(ROOT.resolve()) and + not output.is_relative_to(catalog_dir) and + not output.is_relative_to(artifacts_dir), + "v3 output must be new and external to source/producer inputs") + require(git("rev-parse","HEAD") == head and + git("show","-s","--format=%P",synthetic).split() == [BASE,head] and + git("rev-parse",synthetic+"^{tree}") == + git("rev-parse",head+"^{tree}"), + "H or genuine ordered B/H source tree differs") + report = load_json(catalog_dir/"unreviewed_producer_catalog.json") + dossier = load_json(catalog_dir/"unreviewed_source_review_dossier.json") + compiled = load_json(catalog_dir/"compiler/complete_compiler_candidate.json") + require(report.get("head_sha") == head and + report.get("producing_artifact_count") == 21 and + dossier.get("head_sha") == head and + dossier.get("status") == + "UNREVIEWED_AUTHENTICATED_PRODUCERS_SCOPE_PENDING" and + compiled.get("head_sha") == head and + compiled.get("proof_count") == 259 and + len(compiled.get("compiler_axiom_receipts", [])) == 259, + "complete original 21/259 source review dossier missing") + producers = report["producer_artifact_checks"] + indexed = verified_producer_index(head, producers) + original_zip = {} + members = {} + for row in producers: + artifact = row["artifact_id"] + name = artifacts_dir/(str(artifact)+".zip") + require(name.is_file() and not name.is_symlink(), + "missing original producing ZIP ID "+str(artifact)) + raw = name.read_bytes() + require(digest(raw) == row["artifact_sha256"], + "producing ZIP differs from GitHub digest") + original_zip[artifact] = raw + pack_member(members, "producer/"+str(artifact)+".zip", raw) + verified = authenticate(head, producers, original_zip) + require(verified["producer_count"] == 21 and + verified["status"] == "REAL_PRODUCING_BYTES_UNREVIEWED", + "21 producers not independently authenticated") + proof_receipts = [] + for row in compiled["compiler_axiom_receipts"]: + idx = row["inventory_index"] + producer = indexed[("compiler", idx % 4)] + name = row["axiom_log_member"] + outer = "compiler/"+name + raw = verified["producers"][producer["artifact_id"]]["members"].get(name) + require(raw is not None, "original compiler shard log unavailable") + pack_member(members, outer, raw) + proof_receipts.append(proof_row(head, row, producer, raw, outer)) + require(len(proof_receipts) == 259 and + {p["id"] for p in proof_receipts} == + {f"all:{i:04d}" for i in range(259)}, + "incomplete exact 259 source theorem receipts") + positives, negatives = [], [] + cases = {} + for (kind, shard), row in indexed.items(): + if kind not in ("append","legacy"): + continue + raw = verified["producers"][row["artifact_id"]]["members"] + extracted = extracted_case_refs(kind, raw, head, row) + require(not cases.keys() & extracted.keys(), + "duplicated original case across semantic producers") + cases.update(extracted) + if kind == "append": + for cid in sorted(extracted): + pos, neg = append_case_rows(head, cid, row, raw, members) + positives.append(pos) + negatives.append(neg) + legacy = sorted(cid for cid,v in cases.items() if v["route"] == "legacy") + require(len(cases) == 45 and len(legacy) == 20 and + len(positives) == len(negatives) == 25, + "wrong 25+20 original-source candidate partition") + checks = authenticated_checks(head) + require(set(REQUIRED) <= set(checks), + "not all twelve source CI checks genuinely green") + stamped = [dict(row, evidence_zip_member="producer/"+str(row["artifact_id"])+".zip") + for row in producers] + overlay = { + "format":FORMAT_V3, "phase":"premerge_review", + "base_sha":BASE, "head_sha":head, "synthetic_sha":synthetic, + "merged_main_sha":None, "evidence_bundle_sha256":None, + "source_bound_ci":[ + {"name":name,"id":checks[name]["id"],"head_sha":head} + for name in sorted(REQUIRED)], + "producer_artifacts":stamped, + "compiler_axiom_receipts":proof_receipts, + "positive_test_receipts":positives, + "semantic_negative_receipts":negatives, + "case_decisions":[], "claim_decisions":[], + "independent_source_reviews":[], + "trusted_axioms":["Classical.choice","Quot.sound","propext"], + } + output.mkdir(parents=True, exist_ok=False) + bundle = output/"unreviewed_evidence.zip" + with zipfile.ZipFile(bundle,"w") as z: + for name,body in sorted(members.items()): + info = zipfile.ZipInfo(name,(1980,1,1,0,0,0)) + info.external_attr = 0o100644 << 16 + info.compress_type = zipfile.ZIP_DEFLATED + z.writestr(info,body,compress_type=zipfile.ZIP_DEFLATED, + compresslevel=9) + overlay["evidence_bundle_sha256"] = digest(bundle.read_bytes()) + validate_overlay(overlay) + (output/"unreviewed_v3_receipts.json").write_text( + json.dumps(overlay,sort_keys=True,indent=2)+"\n",encoding="utf-8") + gaps = { + "format":"Permansson.PR113.V3CandidateGaps.v1", + "status":"UNREVIEWED_INCOMPLETE_NOT_FULL_ACCEPTANCE", + "head_sha":head, "producers_authenticated":21, + "compiler_proofs_source_bound":259, + "append_cases_with_positive_negative_controls":25, + "legacy_cases_requiring_source_appended_replay":legacy, + "claims_requiring_individual_positive_negative_controls": + ["Q1","Q2","Q3","M1","M2","M3","N1","N2","N3","D8","D9"], + "original_positive_route_review_required":45, + "independent_case_reviews":0, "independent_claim_reviews":0, + "mathematical_release_approved":False, + } + (output/"remaining_work.json").write_text( + json.dumps(gaps,sort_keys=True,indent=2)+"\n") + return gaps + + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--head",required=True) + p.add_argument("--synthetic",required=True) + p.add_argument("--catalog-dir",type=Path,required=True) + p.add_argument("--producer-zips",type=Path,required=True) + p.add_argument("--output",type=Path,required=True) + args=p.parse_args(argv) + try: + result=assemble(args.head,args.synthetic,args.catalog_dir, + args.producer_zips,args.output) + except (AssemblyError,AuthenticationError,EvidenceError,ValueError,OSError, + KeyError,zipfile.BadZipFile) as exc: + print("V3 CANDIDATE ASSEMBLY BLOCKED:",exc,file=sys.stderr) + return 2 + print(json.dumps(result,sort_keys=True)) + return 0 + + +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_authenticate.py b/verification/research/final_closure_authenticate.py new file mode 100644 index 00000000..438599fa --- /dev/null +++ b/verification/research/final_closure_authenticate.py @@ -0,0 +1,453 @@ +"""Independent GitHub/Git-object authentication for final mathematical release. + +Never promote an arbitrary JSON flag, SHA-shaped string, reviewer name or +self-reported CI event. No network or missing objects -> strict rejection. +Offline draft/audit modes never use this module. +""" +from __future__ import annotations +import json +import os +from pathlib import Path +import re +import subprocess +import urllib.request +from pr113_boundary_io import strict_json_object + +ROOT = Path(__file__).resolve().parents[2] +PREFIX = "https://api.github.com/repos/HMarcusWH/PermanssonLean" +BASE = "17c315f7df7abf457eb94d393bd526d46da6f6f4" +SHA = re.compile(r"^[0-9a-f]{40}$") +REQUIRED = frozenset(( + "research", "build", "frozen-source-preservation", + "multiagent-protocols", "approx-quotient", + "release-export-integrity", "archive-reproducibility", + # These independent, applicable source-head checks MUST also be green. + # A green Research build cannot substitute for a failed executable, + # application or cross-platform integrity job. + "executable-verification", "research-release", + "integrity-portability-windows", + "Application contract (ubuntu-latest)", + "Application contract (windows-latest)", +)) + + +class AuthenticationError(ValueError): + pass + + +def demand(ok, message): + if not ok: + raise AuthenticationError(message) + + +def git(*args): + try: + return subprocess.check_output(["git", *args], cwd=ROOT, + text=True, stderr=subprocess.DEVNULL, timeout=20).strip() + except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc: + raise AuthenticationError("missing authentic Git object: " + " ".join(args)) from exc + + +def live(path): + """Only literal GitHub repo endpoints constructed by this module.""" + headers = {"User-Agent": "Permansson-Closure-Acceptance/1", + "Accept": "application/vnd.github+json"} + token = os.environ.get("GITHUB_TOKEN") + if token: + headers["Authorization"] = "Bearer " + token + req = urllib.request.Request(PREFIX+path, headers=headers) + try: + with urllib.request.urlopen(req, timeout=20) as response: + return json.load(response) + except Exception as exc: + raise AuthenticationError("live GitHub authentication unavailable: " + path) from exc + + +def verify_source(receipts): + b,h,s,m = (receipts.get(x) for x in + ("base_sha","head_sha","synthetic_sha","merged_main_sha")) + for name,value in zip("BHSM",(b,h,s,m)): + demand(isinstance(value,str) and SHA.fullmatch(value), + "missing real "+name+" SHA") + demand(git("cat-file","-t",value)=="commit",name+" is not a Git commit") + demand(b==BASE,"wrong original branch base") + demand(git("show","-s","--format=%P",s).split()==[b,h], + "synthetic Git parents differ from original B/H") + demand(git("rev-parse",s+"^{tree}")==git("rev-parse",m+"^{tree}"), + "synthetic and merged final trees differ") + git("merge-base","--is-ancestor",b,m) + git("merge-base","--is-ancestor",h,m) + pr=live("/pulls/113") + demand(pr.get("number")==113 and pr.get("merged") is True, + "the PR must be truly merged for final acceptance") + # GitHub may display a MOVED base SHA after the merge; B is immutable + # in the authentic synthetic and merged Git parent objects instead. + demand(pr["head"]["sha"]==h and pr.get("merge_commit_sha")==m, + "live GitHub PR H/M differs from claimed receipts") + demand(git("show","-s","--format=%P",m).split()==[b,h], + "merged M is not a true ordered [B,H] merge commit") + runs=live("/commits/"+h+"/check-runs?per_page=100")["check_runs"] + accepted={x["name"]:x for x in runs if + x.get("head_sha")==h and x.get("conclusion")=="success" + and x.get("status")=="completed"} + demand(REQUIRED <= accepted.keys(),"missing genuine source-bound CI checks") + given=receipts.get("source_bound_ci",[]) + demand(isinstance(given,list),"CI receipts must be source-bound entries") + for name in REQUIRED: + cert=accepted[name] + demand(any(isinstance(x,dict) and x.get("name")==name + and x.get("id")==cert.get("id") and x.get("head_sha")==h + for x in given), + "not a live source-bound CI receipt: "+name) + approvals=live("/pulls/113/reviews?per_page=100") + author=(pr.get("user") or {}).get("login") + reviewer_ids={x.get("id") for x in approvals if + x.get("state")=="APPROVED" and x.get("commit_id")==h + and x.get("submitted_at") and x.get("user") + and x["user"].get("login")!=author} + reviews=receipts.get("independent_source_reviews",[]) + demand(bool(reviewer_ids) and isinstance(reviews,list) and + any(isinstance(x,dict) and x.get("id") in reviewer_ids + for x in reviews), + "independent reviewer approval on exact H not authenticated") + return {"B":b,"H":h,"S":s,"M":m,"reviewer_ids":reviewer_ids} + + +# --- Source-bound premerge and merged-source authentication (v2) --- +# +# The old verify_source remains a postmerge strict compatibility entrypoint; +# neither legacy nor v2 acceptance ever accepts a self-reported review flag. + + +MAX_PAGES = 20 +REVIEWER_POLICY = ROOT / "research/paper_closures/post_merge_112/reviewer_authority.json" + + +def paginate(resource, *, key=None): + """Fetch complete GitHub pages; never certify the truncated first page.""" + rows = [] + for page in range(1, MAX_PAGES + 1): + suffix = "?per_page=100" + (f"&page={page}" if page > 1 else "") + response = live(resource + suffix) + batch = response.get(key) if key is not None and isinstance(response, dict) else response + demand(isinstance(batch, list), "malformed paginated GitHub response") + rows.extend(batch) + if len(batch) < 100: + return rows + raise AuthenticationError("GitHub evidence exceeds bounded pagination") + + +def reviewer_authorized(login, role): + """Explicit owner-versioned reviewer authority; non-author alone is insufficient.""" + demand(role in ("source", "scholarly"), "unknown reviewer role") + try: + policy = strict_json_object(REVIEWER_POLICY.read_bytes()) + except (OSError, ValueError) as exc: + raise AuthenticationError("reviewer authority policy unavailable") from exc + demand(isinstance(policy, dict) and + policy.get("format") == "Permansson.ReviewerAuthority.v1", + "invalid reviewer authority policy") + if policy.get("status") != "OWNER_AUTHORIZED": + return False + rows = policy.get(role + "_reviewers") + demand(isinstance(rows, list), "reviewer roster absent") + return any(isinstance(x, dict) and + x.get("login") == login and + x.get("independence_disclosed") is True and + x.get("approved_by") == "HMarcusWH" + for x in rows) + + +def current_reviews_by_user(reviews): + """One effective review per user, including dismissal and later changes requests.""" + latest = {} + for review in reviews: + if not isinstance(review, dict): + raise AuthenticationError("malformed GitHub review") + user = (review.get("user") or {}).get("login") + rid = review.get("id") + stamp = review.get("submitted_at") or review.get("dismissed_at") or "" + if not user or type(rid) is not int: + continue + key = (stamp, rid) + if user not in latest or key > latest[user][0]: + latest[user] = (key, review) + return {name: item[1] for name, item in latest.items()} + + +REVIEW_PREFIX = "PERMANSSON-REVIEW-V2:" +SHA256 = re.compile(r"^[0-9a-f]{64}$") + + +def receipt_payload_digest(receipts): + """Digest exact proof/case/claim overlay excluding unknown future review IDs. + + The reviewer can attest this digest before they submit the GitHub review; + adding its real review ID to the detached overlay afterward does not + change the reviewed mathematical/evidence decisions. + """ + import hashlib + fields = ("compiler_axiom_receipts", "positive_test_receipts", + "semantic_negative_receipts", "case_decisions", "claim_decisions") + payload = {} + for name in fields: + rows = receipts.get(name, []) + demand(isinstance(rows, list), "invalid case/claim review payload") + payload[name] = [ + {k: v for k, v in row.items() if k != "review_id"} + for row in rows if isinstance(row, dict)] + demand(len(payload[name]) == len(rows), "malformed review payload") + raw = json.dumps(payload, sort_keys=True, ensure_ascii=False, + separators=(",", ":")).encode("utf-8") + return hashlib.sha256(raw).hexdigest() + + +def authenticated_checks(head_sha): + """Select the latest trusted check attempt for every required job. + + Older success cannot mask a newer failed, cancelled, or queued run. + GitHub check-run IDs are not workflow-run IDs. + """ + demand(isinstance(head_sha, str) and SHA.fullmatch(head_sha), + "invalid checked source revision") + rows = paginate("/commits/" + head_sha + "/check-runs", key="check_runs") + by_name = {} + for check in rows: + if not isinstance(check, dict) or check.get("head_sha") != head_sha: + continue + name = check.get("name") + if name not in REQUIRED: + continue + if (check.get("app") or {}).get("slug") != "github-actions": + continue + rid = check.get("id") + demand(type(rid) is int and rid > 0, "bad GitHub check-run ID") + details = check.get("details_url") + if details is not None: + demand(isinstance(details, str) and details.startswith( + "https://github.com/HMarcusWH/PermanssonLean/actions/runs/"), + "required check has unexpected execution provenance") + if name not in by_name or rid > by_name[name]["id"]: + by_name[name] = check + demand(REQUIRED <= by_name.keys(), "required GitHub Actions checks absent") + for name in REQUIRED: + selected = by_name[name] + demand(selected.get("status") == "completed" and + selected.get("conclusion") == "success", + "latest required check is not successful: " + name) + return by_name + + +def authenticate_scoped_reviews(head_sha, author, bundle_sha256, refs, + expected_payload_sha256, *, pr_number=113): + """Parse per-case/claim decisions from exact-H non-author GitHub approvals. + + These scopes are authentic review-body content from GitHub, not the + caller-supplied JSON overlay. An approval must bind an immutable bundle + digest to the reviewed exact H. Missing scopes never imply acceptance. + """ + demand(isinstance(bundle_sha256, str) and SHA256.fullmatch(bundle_sha256), + "reviewed detached bundle digest required") + demand(isinstance(refs, list), "independent review receipts malformed") + registered_ids = {x.get("id") for x in refs if isinstance(x, dict)} + scopes = {} + reviews = paginate("/pulls/"+str(pr_number)+"/reviews") + for review in current_reviews_by_user(reviews).values(): + user = (review.get("user") or {}).get("login") + rid = review.get("id") + if (review.get("state") != "APPROVED" or + review.get("commit_id") != head_sha or + not review.get("submitted_at") or not user or user == author or + not reviewer_authorized(user, "source") or + rid not in registered_ids): + continue + lines = [x.strip() for x in (review.get("body") or "").splitlines() + if x.strip().startswith(REVIEW_PREFIX)] + demand(len(lines) <= 1, "ambiguous reviewer attestation lines") + if not lines: + continue + try: + claim = strict_json_object( + lines[0][len(REVIEW_PREFIX):].strip().encode("utf-8"), + max_bytes=64*1024) + except (ValueError, TypeError) as exc: + raise AuthenticationError("reviewer attestation is not valid JSON") from exc + demand(isinstance(claim, dict) and + claim.get("head_sha") == head_sha and + claim.get("evidence_bundle_sha256") == bundle_sha256 and + claim.get("receipt_payload_sha256") == expected_payload_sha256, + "reviewer's bundle/head/evidence payload does not match") + cases = claim.get("cases", {}) + ids = claim.get("claims", []) + demand(isinstance(cases, dict) and isinstance(ids, list) and + len(ids) == len(set(ids)) and + set(cases.values()) <= {"VERIFIED_SELECTED_SCOPE", "VERIFIED_NEGATIVE"}, + "malformed explicit case/claim decisions") + pdf_sha = claim.get("pdf_sha256") + demand(pdf_sha is None or (isinstance(pdf_sha, str) and + SHA256.fullmatch(pdf_sha)), "invalid signed paper digest") + scopes[rid] = {"cases": cases, "claims": ids, "reviewer": user, + "pdf_sha256": pdf_sha, + "manuscript_approved": claim.get("manuscript_approved") is True} + return scopes + + +def verify_premerge(receipts, *, require_independent_review=True): + """Fail closed before merging; DOES NOT fabricate or require future M.""" + b, h, s = (receipts.get(x) for x in ("base_sha", "head_sha", "synthetic_sha")) + for name, value in (("B", b), ("H", h), ("S", s)): + demand(isinstance(value, str) and SHA.fullmatch(value), "missing real " + name) + demand(git("cat-file", "-t", value) == "commit", name + " not a Git commit") + # Original PR113 must still use B113. A later genuine CERT target is + # separately source-bound to the actual ordered M113 ancestor. + demand(git("show", "-s", "--format=%P", s).split() == [b, h], + "synthetic S does not have ordered B/H parents") + demand(git("rev-parse", s + "^{tree}") == + git("rev-parse", h + "^{tree}"), + "synthetic merge changes the reviewed source tree") + demand(git("rev-parse", "main") == b, + "main advanced after branch freeze: rebase/re-review needed") + from pr113_cert_release_source_v3 import source_pr, ReleaseSourceError + try: + pr_number, pr = source_pr(b, h, git=git, live=live) + except ReleaseSourceError as exc: + raise AuthenticationError("original/CERT premerge source invalid") from exc + demand(pr.get("merged") is False and pr.get("state") == "open" + and pr["head"]["sha"] == h and pr["base"]["sha"] == b, + "PR no longer an authentic unmerged B/H review candidate") + checks = authenticated_checks(h) + required = receipts.get("source_bound_ci") + demand(isinstance(required, list), "source-bound check receipts absent") + for name in REQUIRED: + recorded = [x for x in required if + isinstance(x, dict) and x.get("name") == name] + demand(len(recorded) == 1 and + recorded[0].get("id") == checks[name]["id"] and + recorded[0].get("head_sha") == h, + "stale, duplicate or forged check-run: " + name) + scopes = {} + if require_independent_review: + scopes = authenticate_scoped_reviews( + h, (pr.get("user") or {}).get("login"), + receipts.get("evidence_bundle_sha256"), + receipts.get("independent_source_reviews", []), + receipt_payload_digest(receipts), pr_number=pr_number) + demand(bool(scopes), "independent exact-H GitHub review/scope absent") + return {"B": b, "H": h, "S": s, "M": None, + "checks": checks, "review_scopes": scopes} + + +SCHOLARLY_PREFIX = "PERMANSSON-SCHOLARLY-V2:" + + +def authentic_scholarly_attestations(pr, head_sha, merged_sha, *, + pr_number=113): + """Postmerge external scientific/PDF review from genuine GitHub comments. + + A machine can authenticate who authored this attestation, what they + explicitly signed off, and the exact bytes. It cannot independently + assess that the reviewer's mathematical reasoning was correct. + """ + merged_at = pr.get("merged_at") + demand(isinstance(merged_at, str) and bool(merged_at), + "scholarly approval requires completed genuine GitHub merge") + author = (pr.get("user") or {}).get("login") + comments = paginate("/issues/"+str(pr_number)+"/comments") + results = [] + for comment in comments: + reviewer = (comment.get("user") or {}).get("login") + stamp = comment.get("created_at") + if (not reviewer or reviewer == author or + not reviewer_authorized(reviewer, "scholarly") or + not stamp or stamp < merged_at): + continue + lines = [l.strip() for l in (comment.get("body") or "").splitlines() + if l.strip().startswith(SCHOLARLY_PREFIX)] + demand(len(lines) <= 1, "ambiguous publication attestations") + if not lines: + continue + # GitHub issue comments are editable. Do not accept a later-edited + # statement as the immutable scholarly attestation submitted after M. + demand(type(comment.get("id")) is int and comment["id"] > 0 + and comment.get("updated_at") == stamp, + "scholarly source comment was changed after its original submission") + try: + data = strict_json_object( + lines[0][len(SCHOLARLY_PREFIX):].strip().encode("utf-8"), + max_bytes=64*1024) + except (TypeError, ValueError) as exc: + raise AuthenticationError("invalid scholarly approval JSON") from exc + if (not isinstance(data, dict) or + data.get("head_sha") != head_sha or + data.get("merged_main_sha") != merged_sha): + continue + pdf = data.get("pdf_sha256") + pages = data.get("pages") + claim_ids = data.get("approved_claim_ids") + demand(isinstance(pdf, str) and SHA256.fullmatch(pdf) and + isinstance(pages, dict) and bool(pages) and + all(isinstance(n, str) and n.startswith("page-") and + isinstance(h, str) and SHA256.fullmatch(h) + for n, h in pages.items()) and + isinstance(claim_ids, list) and bool(claim_ids) and + all(isinstance(claim, str) and bool(claim) + for claim in claim_ids) and + len(claim_ids) == len(set(claim_ids)) and + data.get("scholarly_review") == "APPROVED" and + data.get("all_pages_visually_inspected") is True, + "scholarly PDF attestation lacks real page/claim scope") + results.append({"id": comment.get("id"), "reviewer": reviewer, + "pdf_sha256": pdf, "pages": pages, + "claim_ids": claim_ids, "merged_main_sha": merged_sha}) + return results + + +def verify_merged_v2(receipts): + """Genuine ordered merge M and integrated main CI; no squash/rebase.""" + b, h, s, m = (receipts.get(x) for x in + ("base_sha", "head_sha", "synthetic_sha", "merged_main_sha")) + for name, value in (("B", b), ("H", h), ("S", s), ("M", m)): + demand(isinstance(value, str) and SHA.fullmatch(value), "missing merged " + name) + demand(git("cat-file", "-t", value) == "commit", + "missing authentic " + name + " Git object") + demand(git("show", "-s", "--format=%P", s).split() == [b, h] and + git("show", "-s", "--format=%P", m).split() == [b, h], + "not an exact real [B,H] Git merge") + demand(git("rev-parse", m + "^{tree}") == + git("rev-parse", s + "^{tree}"), + "merged source tree differs from genuine synthetic merge") + git("merge-base", "--is-ancestor", b, m) + git("merge-base", "--is-ancestor", h, m) + from pr113_cert_release_source_v3 import source_pr, ReleaseSourceError + try: + pr_number, pr = source_pr(b, h, m, git=git, live=live) + except ReleaseSourceError as exc: + raise AuthenticationError("merged PR113/CERT source not genuine") from exc + demand(pr.get("merged") is True and pr.get("head", {}).get("sha") == h and + pr.get("merge_commit_sha") == m, + "live GitHub PR does not authenticate M") + frozen = authenticated_checks(h) + integrated = authenticated_checks(m) + for label, evidence, checks, checked_sha in ( + ("frozen", receipts.get("source_bound_ci"), frozen, h), + ("integrated", receipts.get("merged_bound_ci"), integrated, m), + ): + demand(isinstance(evidence, list), "missing " + label + " CI") + for name in REQUIRED: + matched = [x for x in evidence if + isinstance(x, dict) and x.get("name") == name and + x.get("id") == checks[name]["id"] and + x.get("head_sha") == checked_sha] + demand(len(matched) == 1, + label + " check is forged or stale: " + name) + scopes = authenticate_scoped_reviews( + h, (pr.get("user") or {}).get("login"), + receipts.get("evidence_bundle_sha256"), + receipts.get("independent_source_reviews", []), + receipt_payload_digest(receipts), pr_number=pr_number) + demand(bool(scopes), "missing authenticated independent exact-H scope reviews") + scholarly = authentic_scholarly_attestations( + pr, h, m, pr_number=pr_number) + return {"B": b, "H": h, "S": s, "M": m, + "checks": frozen, "integrated_checks": integrated, + "review_scopes": scopes, "scholarly_reviews": scholarly} diff --git a/verification/research/final_closure_batch_legacy_replay.py b/verification/research/final_closure_batch_legacy_replay.py new file mode 100644 index 00000000..b90d63f0 --- /dev/null +++ b/verification/research/final_closure_batch_legacy_replay.py @@ -0,0 +1,208 @@ +"""Batch execute twenty *real*, source-appended A2/legacy Lean replays. + +The eight old legacy shard ZIPs retain test/compiler observations; these are +not canonical append-only proof controls. A reviewer/operator must explicitly +supply the selected historical method, genuine observed false input and correct +original theorem provider per case. This program verifies those source choices +against original frozen case criteria and runs the existing actual Lean +legacy_replay.replay for each case. It never attaches arbitrary CI check IDs, +case decisions, scholarly approvals or release eligibility. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import re +import sys +import tempfile + +from final_closure_complete_lean_evidence import ROOT, git_head +from final_closure_mutation_evidence import committed_file_bytes +from final_closure_legacy_semantic_evidence import original_case_map +from final_closure_producer_v2_bridge import verified_producer_index +from final_closure_producer_bytes import authenticate +from final_closure_legacy_replay import replay as actual_replay +from final_closure_authenticate import AuthenticationError + +FORMAT="Permansson.PR113.LegacyBatchAppendOnlyCandidate.v1" +TEST=re.compile(r"^test_[A-Za-z0-9_]+$") +EVENT=re.compile(r"^[A-Za-z0-9_-]+$") + + +class LegacyBatchError(ValueError): + pass + +def require(ok,why): + if not ok:raise LegacyBatchError(why) + +def parse_mapping(data, expected): + require(isinstance(data,dict) and + data.get("format")=="Permansson.PR113.LegacyReplaySelection.v1" + and isinstance(data.get("cases"),list) and + isinstance(expected,set) and len(expected)==20, + "twenty original legacy case selections required") + records={} + keys={"case_id","test_index","test_method","original_provider", + "observation_id","producing_artifact_id"} + for row in data["cases"]: + require(isinstance(row,dict) and set(row)==keys, + "untypeable selected legacy provider/method/observation") + cid=row["case_id"] + require(cid in expected and cid not in records and + type(row["test_index"]) is int and row["test_index"]>=0 and + type(row["producing_artifact_id"]) is int and + row["producing_artifact_id"]>0 and + type(row["test_method"]) is str and + TEST.fullmatch(row["test_method"]) and + type(row["observation_id"]) is str and + EVENT.fullmatch(row["observation_id"]) and + type(row["original_provider"]) is str and + row["original_provider"].startswith("PermanssonResearch/") and + row["original_provider"].endswith(".lean") and + ".." not in Path(row["original_provider"]).parts, + "unknown, duplicated or unsafe source-negative replay selection") + records[cid]=row + require(set(records)==expected, + "selected legacy provider map misses original acceptance criteria") + return records + +def external_directory(path): + raw=Path(path) + require(not raw.is_symlink(),"symlinked legacy artifact path not allowed") + result=raw.resolve(strict=True) + require(result.is_dir() and + not result.is_relative_to(ROOT.resolve()) and + not ROOT.resolve().is_relative_to(result), + "all evidence inputs must be source-external directories") + return result + +def replay_twenty(head, catalog_root, producer_zip_root, mapping_path, out): + require(git_head()==head,"replays cannot borrow a historical source head") + catalog=external_directory(catalog_root) + producers=external_directory(producer_zip_root) + mapping_file=Path(mapping_path) + require(mapping_file.is_file() and not mapping_file.is_symlink() and + not mapping_file.resolve().is_relative_to(ROOT.resolve()), + "explicit legacy provider selection must be detached") + target=Path(out) + require(not target.is_symlink(),"symlink output forbidden") + target=target.resolve() + require(not target.exists() and + not target.is_relative_to(ROOT.resolve()) and + not target.is_relative_to(catalog) and + not target.is_relative_to(producers), + "replay output must be fresh and separate") + byid, legacy=original_case_map(head) + legacy_ids=set(legacy) + chosen=parse_mapping(json.loads(mapping_file.read_bytes()),legacy_ids) + report=json.loads( + (catalog/"unreviewed_producer_catalog.json").read_bytes()) + require(report.get("head_sha")==head and + report.get("producing_artifact_count")==21, + "original authenticated producer index unavailable") + attests=report["producer_artifact_checks"] + indexed=verified_producer_index(head,attests) + raw={} + for row in attests: + path=producers/(str(row["artifact_id"])+".zip") + require(path.is_file() and not path.is_symlink(), + "missing original GitHub producer ZIP") + data=path.read_bytes() + from hashlib import sha256 + require(sha256(data).hexdigest()==row["artifact_sha256"], + "producer ZIP changed since authenticated GitHub artifact") + raw[row["artifact_id"]]=data + authentic=authenticate(head,attests,raw) + require(authentic["producer_count"]==21, + "21 original compiler/append/legacy source jobs not authenticated") + legacy_artifacts={p["artifact_id"]:(kind,shard,p) + for (kind,shard),p in indexed.items() if kind=="legacy"} + for row in chosen.values(): + require(row["producing_artifact_id"] in legacy_artifacts, + "legacy replay borrowed append/compiler producing artifact") + target.mkdir(parents=True,exist_ok=False) + completed={} + for cid in sorted(chosen): + row=chosen[cid] + member=authentic["producers"][row["producing_artifact_id"]]["members"] + prefix=f'{cid}/test-{row["test_index"]:02d}/' + manifest_key=prefix+"unreviewed_test_manifest.json" + require(manifest_key in member, + "selected named historical test capture is missing") + original_manifest=json.loads(member[manifest_key]) + require(original_manifest.get("case_id")==cid and + row["test_method"] in original_manifest.get("selected_methods",[]) + and original_manifest.get("status")== + "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED", + "selected legacy test is not an actually observed source method") + with tempfile.TemporaryDirectory(prefix="pr113-legacy-source-") as td: + input_dir=Path(td)/"original_capture" + input_dir.mkdir() + for name,data in member.items(): + if name.startswith(prefix): + suffix=name[len(prefix):] + require(suffix and ".." not in Path(suffix).parts, + "unsafe legacy source capture input") + file=input_dir/suffix + file.parent.mkdir(parents=True,exist_ok=True) + file.write_bytes(data) + result=actual_replay( + head,case_id=cid,method=row["test_method"], + provider=row["original_provider"], + observation_id=row["observation_id"], + test_output=input_dir, + output=target/cid) + require(result.get("format")== + "Permansson.LegacyAppendOnlyReplayCandidate.v1" and + result.get("status")== + "REPLAYED_APPEND_ONLY_UNREVIEWED_UNATTRIBUTED" and + result.get("head_sha")==head and + result.get("case_id")==cid and + result.get("ci_check_run_id") is None and + result.get("independent_review") is False and + result.get("mathematical_acceptance") is False, + "actual original theorem replay failed to prove its source scope") + completed[cid]={ + "case_id":cid, + "original_legacy_producing_artifact_id":row["producing_artifact_id"], + "original_legacy_check_run_id_provenance_only": + legacy_artifacts[row["producing_artifact_id"]][2]["actual_check_run_id"], + "replay_bundle_sha256":result["detached_bundle_sha256"], + "new_replay_producer_check_run_id":None, + "status":"SOURCE_APPENDED_REPLAYED_UNREVIEWED_NOT_CI_ATTRIBUTED", + } + require(len(completed)==20,"not all original twenty cases replayed") + summary={ + "format":FORMAT, + "status":"TWENTY_REAL_LEAN_REPLAYS_UNREVIEWED_NOT_CI_BOUND", + "source_head":head,"replayed_case_count":20, + "replays":[completed[cid] for cid in sorted(completed)], + "new_source_bound_CI_acceptance":False, + "independent_case_reviews":0, + "independent_claim_reviews":0, + "mathematical_release_approved":False, + } + (target/"unreviewed_batch_replays.json").write_text( + json.dumps(summary,sort_keys=True,indent=2)+"\n") + return summary + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--head",required=True) + p.add_argument("--catalog-dir",required=True,type=Path) + p.add_argument("--producer-zips",required=True,type=Path) + p.add_argument("--selection",required=True,type=Path) + p.add_argument("--out",required=True,type=Path) + a=p.parse_args(argv) + try: + result=replay_twenty(a.head,a.catalog_dir,a.producer_zips, + a.selection,a.out) + print(json.dumps({"status":result["status"], + "cases":result["replayed_case_count"]})) + return 0 + except (LegacyBatchError,ValueError,KeyError,OSError) as exc: + print("LEGACY 20 REPLAY BLOCKED:",exc,file=sys.stderr) + return 2 +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_candidate_artifact_auth.py b/verification/research/final_closure_candidate_artifact_auth.py new file mode 100644 index 00000000..34f554b0 --- /dev/null +++ b/verification/research/final_closure_candidate_artifact_auth.py @@ -0,0 +1,262 @@ +"""Authenticate a PR113 candidate artifact to its REAL producing GitHub job. + +Do NOT attach ordinary green research check IDs to detached local logs. This +tool verifies an externally supplied artifact ZIP by GitHub's own SHA-256 +artifact digest and authentic workflow/run/job/check-run identities, then +checks its bounded candidate manifest. It cannot attest mathematical truth, +proof-scope sufficiency, a particular reviewer's work, or any release gate. + +Artifact authenticity is separate from the full 45/11 source obligations. +No record emitted here may be converted to accepted receipts automatically. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import sys + +from final_closure_authenticate import live, paginate, AuthenticationError +from final_closure_case_receipts import load_detached_bundle, EvidenceError +from final_closure_all_original_semantics import select_cases +from final_closure_legacy_semantic_evidence import shard_cases +from final_closure_mutation_evidence import git_head, MutationEvidenceError +from pr113_boundary_io import strict_json_object + +BRANCH = "research/final-mathematical-closure" +SHA40 = re.compile(r"^[0-9a-f]{40}$") +SHA256 = re.compile(r"^[0-9a-f]{64}$") +MAX_ARTIFACT_BYTES = 64 * 1024 * 1024 +KINDS = { + "append": { + "workflow": ".github/workflows/pr113-all-original-semantic-candidates.yml", + "job": "semantic-candidate-shard", + "artifact": "pr113-all-original-semantic-candidates-", + "count": 9, "manifest": "unreviewed_shard_manifest.json", + }, + "legacy": { + "workflow": ".github/workflows/pr113-legacy-semantic-evidence.yml", + "job": "legacy-original-shard", + "artifact": "pr113-legacy-semantic-candidates-", + "count": 8, "manifest": "unreviewed_shard_manifest.json", + }, + "compiler": { + "workflow": ".github/workflows/pr113-all-case-compiler.yml", + "job": "compiler-shard", + "artifact": "pr113-compiler-shard-", + "count": 4, "manifest": "complete_compiler_candidate.json", + }, +} + + +def demand(ok, why): + if not ok: + raise AuthenticationError(why) + + +def sha(data): + return hashlib.sha256(data).hexdigest() + + +def verify_manifest(members: dict, kind: str, shard: int, head: str): + spec = KINDS[kind] + name = spec["manifest"] + demand(name in members, "original candidate manifest missing from artifact") + try: + manifest = strict_json_object(members[name]) + except (TypeError, ValueError) as exc: + raise AuthenticationError("candidate manifest is invalid JSON") from exc + demand(isinstance(manifest, dict), "candidate manifest must be an object") + if kind == "append": + expected = list(select_cases(shard, spec["count"])) + demand( + manifest.get("format") == "Permansson.AllOriginalSemanticCandidateShard.v1" + and manifest.get("status") == + "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED_RECEIPT" + and manifest.get("head") == head + and manifest.get("shard_index") == shard + and manifest.get("shard_count") == spec["count"] + and manifest.get("case_ids") == expected + and [r.get("case_id") for r in manifest.get("completed", []) + if isinstance(r, dict)] == expected + and manifest.get("independent_review") is False + and manifest.get("ci_check_run_id") is None + and manifest.get("mathematical_acceptance") is False, + "append-only candidate manifest is incomplete, stale or promoted") + elif kind == "legacy": + _byid, expected = shard_cases(head, shard, spec["count"]) + expected = list(expected) + demand( + manifest.get("format") == "Permansson.OriginalLegacyCompilerShard.v1" + and manifest.get("status") == + "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED" + and manifest.get("source_head") == head + and manifest.get("case_ids") == expected + and [r.get("case_id") for r in manifest.get("case_reports", []) + if isinstance(r, dict)] == expected + and manifest.get("independent_review") is False + and manifest.get("ci_check_run_id") is None + and manifest.get("merged_main_sha") is None + and manifest.get("scientific_release_approved") is False, + "legacy candidate manifest incomplete, stale or promoted") + for entry in manifest["case_reports"]: + tests = entry.get("tests") + demand(isinstance(tests, list) and bool(tests) and + all(t.get("status") == + "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED" and + t.get("independent_review") is False and + t.get("ci_check_run_id") is None and + not t.get("semantic_methods_missing_candidate") + for t in tests if isinstance(t, dict)) and + all(isinstance(t, dict) for t in tests), + "legacy test did not cover all selected semantic negatives") + else: + demand(manifest.get("format") == "Permansson.AllCaseCompilerCandidate.v1" + and manifest.get("status") == "COMPILER_ONLY_UNREVIEWED" + and manifest.get("head_sha") == head + and manifest.get("case_count") == 45 + and manifest.get("claim_count") == 11 + and manifest.get("shard_index") == shard + and manifest.get("shard_count") == spec["count"] + and manifest.get("independent_review") is False + and manifest.get("authentic_ci_job_bound") is False + and manifest.get("merged_main_sha") is None + and isinstance(manifest.get("compiler_axiom_receipts"), list) + and len(manifest["compiler_axiom_receipts"]) == + manifest.get("proof_count"), + "theorem evidence manifest missing or falsely promoted") + return manifest + + +def verify_artifact(*, kind: str, shard: int, head: str, + run_id: int, artifact_id: int, artifact_zip: Path) -> dict: + demand(kind in KINDS and type(shard) is int and + 0 <= shard < KINDS[kind]["count"], "invalid shard type or index") + demand(isinstance(head, str) and SHA40.fullmatch(head), + "invalid exact source head") + demand(type(run_id) is int and run_id > 0 and + type(artifact_id) is int and artifact_id > 0, + "invalid producing GitHub run or artifact ID") + demand(git_head() == head, "checkout differs from producing source head") + demand(os.environ.get("GITHUB_TOKEN"), "live GitHub authentication token required") + from pr113_producer_target_v3 import authenticated_producer_branch + branch = authenticated_producer_branch(head) + spec = KINDS[kind] + run = live(f"/actions/runs/{run_id}") + demand(isinstance(run, dict) and run.get("id") == run_id + and run.get("head_sha") == head and run.get("head_branch") == branch + and run.get("event") in ("push", "workflow_dispatch") + and run.get("status") == "completed" + and run.get("conclusion") == "success", + "producing workflow is not successful at exact H") + workflow_path = str(run.get("path", "")) + demand(workflow_path == spec["workflow"] or + workflow_path.endswith("/" + spec["workflow"]), + "artifact comes from an unexpected workflow source") + job_rows = paginate(f"/actions/runs/{run_id}/jobs", key="jobs") + expected_name = f'{spec["job"]} ({shard})' + jobs = [j for j in job_rows if isinstance(j, dict) and + j.get("name") == expected_name] + demand(len(jobs) == 1, "producing matrix job not uniquely identified") + job = jobs[0] + demand(job.get("run_id") == run_id and + job.get("head_sha") == head and + job.get("status") == "completed" and + job.get("conclusion") == "success" and + job.get("run_attempt") == run.get("run_attempt"), + "producing job is not the current successful attempt") + check_run_url = job.get("check_run_url") + demand(isinstance(check_run_url, str) and + re.fullmatch( + r"https://api[.]github[.]com/repos/HMarcusWH/PermanssonLean/" + r"check-runs/[1-9][0-9]*", check_run_url), + "job is missing its real check-run URL") + check_id = int(check_run_url.rsplit("/", 1)[-1]) + check = live(f"/check-runs/{check_id}") + demand(check.get("id") == check_id and + check.get("head_sha") == head and + (check.get("app") or {}).get("slug") == "github-actions" and + check.get("status") == "completed" and + check.get("conclusion") == "success", + "real producer check-run does not match GitHub job") + artifact = live(f"/actions/artifacts/{artifact_id}") + expected_artifact_name = spec["artifact"] + str(shard) + digest = artifact.get("digest") + demand(artifact.get("id") == artifact_id and + artifact.get("name") == expected_artifact_name and + artifact.get("expired") is False and + (artifact.get("workflow_run") or {}).get("id") == run_id and + (artifact.get("workflow_run") or {}).get("head_sha") == head and + isinstance(digest, str) and digest.startswith("sha256:") and + SHA256.fullmatch(digest[7:]), + "GitHub artifact lacks verified exact-run SHA-256 metadata") + from pr113_boundary_io import detached_destination + from final_closure_case_receipts import ROOT as PROJECT_ROOT + artifact_zip = detached_destination(artifact_zip, PROJECT_ROOT, + must_exist=True) + demand(0 < artifact_zip.stat().st_size <= MAX_ARTIFACT_BYTES, + "detached uploaded artifact ZIP is unsafe or too large") + actual = sha(artifact_zip.read_bytes()) + demand(actual == digest[7:], + "artifact ZIP bytes differ from GitHub authoritative digest") + try: + members = load_detached_bundle(artifact_zip, actual) + manifest = verify_manifest(members, kind, shard, head) + except (EvidenceError, MutationEvidenceError) as exc: + raise AuthenticationError("artifact content is unsafe/incomplete") from exc + return { + "format": "Permansson.PR113.AuthenticatedProducerArtifact.v1", + "status": "CI_ARTIFACT_DIGEST_VERIFIED_UNREVIEWED", + "head_sha": head, + "workflow_path": spec["workflow"], + "run_id": run_id, + "run_attempt": run.get("run_attempt"), + "job_id": job.get("id"), + "actual_check_run_id": check_id, + "artifact_id": artifact_id, + "artifact_name": expected_artifact_name, + "artifact_sha256": actual, + "manifest_sha256": sha(members[spec["manifest"]]), + "case_ids_unreviewed": manifest.get("case_ids", []), + "mathematical_acceptance": False, + "independent_review": False, + "scientific_release_approved": False, + "merged_main_sha": None, + } + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--head", required=True) + ap.add_argument("--kind", required=True, choices=sorted(KINDS)) + ap.add_argument("--shard", required=True, type=int) + ap.add_argument("--run-id", required=True, type=int) + ap.add_argument("--artifact-id", required=True, type=int) + ap.add_argument("--artifact-zip", required=True, type=Path) + ap.add_argument("--output", required=True, type=Path) + a = ap.parse_args(argv) + try: + from pr113_boundary_io import detached_destination + from final_closure_case_receipts import ROOT + out = detached_destination(a.output, ROOT) + demand(not ROOT.resolve().is_relative_to(out), + "attestation must be written to a new detached location") + report = verify_artifact( + kind=a.kind, shard=a.shard, head=a.head, run_id=a.run_id, + artifact_id=a.artifact_id, artifact_zip=a.artifact_zip) + out.parent.mkdir(parents=True, exist_ok=True) + with out.open("x", encoding="utf-8") as writer: + writer.write(json.dumps(report, sort_keys=True, indent=2)+"\n") + except (OSError, ValueError, AuthenticationError, MutationEvidenceError) as exc: + print("CI ARTIFACT ATTESTATION BLOCKED:", exc, file=sys.stderr) + return 2 + print(json.dumps({k: report[k] for k in ( + "status", "head_sha", "artifact_sha256", "actual_check_run_id")})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_case_map.py b/verification/research/final_closure_case_map.py new file mode 100644 index 00000000..d556ac46 --- /dev/null +++ b/verification/research/final_closure_case_map.py @@ -0,0 +1,121 @@ +"""Deterministic original-criterion-to-source work map; NOT an acceptance gate. + +No current Git SHA, future reviewer, accepted bit, or fictional test outcome +is embedded in this source-candidate map. Original theorem pins and expected +wording are copied unchanged from the historical 45-case ledger. +""" +from __future__ import annotations +import argparse +import json +from pathlib import Path +import sys + +from final_closure_case_method_routes import validated_routes + +ROOT = Path(__file__).resolve().parents[2] +SNAP = ROOT / "research/paper_closures/post_merge_112" +OUTPUT = SNAP / "acceptance_case_source_map.json" + +# Every entry is only a candidate test for review, NEVER a passed receipt. +CANDIDATE_TESTS = { + "X-01": ["test_next03_stochastic_lean.py", "test_paper_x1_matrix_lean.py"], + "X-04": ["test_next03_tied_start_lean.py"], + "M-02": ["test_next03_m02_marked_coupling.py"], + "F-01": ["test_next03_asymmetric_common_swap_lean.py", + "test_final05_measurable_lean.py"], + "F-05": ["test_next03_positive_mediation.py"], + "Q-02": ["test_next04_projection_arithmetic.py"], + "Q-03": ["test_next03_dependent_three_stage.py", "test_a2_primitive_tv.py"], + "Q-04": ["test_localized_prefix_compiler.py", "test_next04_original_margin.py"], + "Q-05": ["test_final04_source_lean.py", "test_next04_original_margin_lean.py"], + "D-05": ["test_next06_committed_path_law.py"], + "D-06": ["test_next06_reachable_completion_belief.py"], + "D-08": ["test_final_d08_admission.py", "test_next06_d08_joint_only_compiler.py"], + "D-09": ["test_final_d09_multichain.py", "test_final06_phaseblind_original.py"], + "B-01": ["test_next06_overlap_lean.py"], + "B-02": ["test_lane_b2_sequential_effects.py"], +} + + +def generate() -> dict: + ledger = json.loads((SNAP / "acceptance_decisions.json").read_text()) + cross = json.loads((SNAP / "theorem_crosswalk.json").read_text()) + cases = ledger["cases"] + if len(cases) != 45 or len({x["case_id"] for x in cases}) != 45: + raise ValueError("original 45-case count/identity changed") + claims = cross["closure_additions"] + if len(claims) != 11: + raise ValueError("expected eleven distinct source closure owners") + routes = validated_routes([c["case_id"] for c in cases], root=ROOT) + results = [] + for c in cases: + cid = c["case_id"] + candidates = ["verification/research/" + n + for n in CANDIDATE_TESTS.get(cid, [])] + for route in routes[cid]: + if route["path"] not in candidates: + candidates.append(route["path"]) + for path in candidates: + if not (ROOT / path).is_file(): + raise ValueError("test candidate no longer exists: " + path) + results.append({ + "case_id": cid, + "current_candidate_decision": c["decision"], + "historical_contract_ids": c["historical_contract_ids"], + "required_expected_result": c["required_expected_result"], + "historical_proof_declarations": c["proof_declarations"], + "new_claim_source_candidates": [ + {"id": row["id"], "path": row["path"]} + for row in claims if cid in row["acceptance_case_ids"]], + "test_source_candidates": candidates, + "test_method_candidates": routes[cid], + "case_specific_test_status": ( + "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW" if candidates + else "MISSING_CASE_SPECIFIC_TEST_SELECTION"), + "remaining_obligations": c["remaining_obligations"], + "evidence": {"compiler": [], "positive": [], "negative": [], + "check_runs": [], "review": []}, + "effective_accepted": False, + }) + return { + "format": "Permansson.OriginalAcceptanceCaseSourceMap.v1", + "source_baseline_sha": ledger["source_baseline_sha"], + "status": "UNREVIEWED_CANDIDATE_ONLY", + "original_cases": 45, + "original_atlas_contracts": cross["historical_contract_count"], + "new_closure_owners": 11, + "cases": results, + "qualification": ("All 45 original cases now have concrete discovered test-method " + "candidates; these are a review work queue, not criterion-level " + "acceptance or executed CI receipts. Source/mutation identity, " + "premise relevance and independent review remain mandatory."), + } + + +def render() -> str: + return json.dumps(generate(), indent=2, sort_keys=True, + ensure_ascii=False) + "\n" + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--output", type=Path, default=OUTPUT) + ap.add_argument("--check", action="store_true") + args = ap.parse_args(argv) + value = render() + if args.check: + if not args.output.is_file() or args.output.read_text() != value: + print("case source map differs from exact original ledger", file=sys.stderr) + return 2 + else: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(value, encoding="utf-8") + print(json.dumps({"cases": 45, + "test_assigned": sum(bool(c["test_source_candidates"]) + for c in generate()["cases"]), + "status": "UNREVIEWED_CANDIDATE_ONLY"})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_case_method_routes.py b/verification/research/final_closure_case_method_routes.py new file mode 100644 index 00000000..632e95cb --- /dev/null +++ b/verification/research/final_closure_case_method_routes.py @@ -0,0 +1,922 @@ +"""Original 45-case test-method discovery routes for source review (ENG-04). + +Each route identifies an *existing test method*, not an executed CI receipt, +proved criterion, accepted negative mutation, or reviewer authorization. +Roles describe what the candidate test attempts to inspect. A case is not +accepted until the original expected result, full proof dependency graph, +required positive/negative controls, execution provenance, and independent +review have all been separately authenticated. +""" +from __future__ import annotations + +import ast +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +METHOD_ROUTES = { + "X-01": [ + [ + "test_next03_stochastic_lean.py", + "test_actual_kernel_equality_rejects_normalized_wrong_row", + "SEMANTIC_NEGATIVE" + ], + [ + "test_paper_x1_matrix_lean.py", + "test_source_bound_transitive_audit", + "COMPILER_SOURCE" + ] + ], + "X-02": [ + [ + "test_paper_x1_lean_closure.py", + "test_corrupt_terminal_literal", + "SEMANTIC_NEGATIVE" + ], + [ + "test_paper_x1_lean_closure.py", + "test_wrong_terminal_horizon", + "SEMANTIC_NEGATIVE" + ], + [ + "test_x1_commons.py", + "test_paths_match_matrix_properties_and_killed_recursion", + "EXACT_CHECK" + ] + ], + "X-03": [ + [ + "test_paper_x1_lean_closure.py", + "test_exact_positive_controls", + "COMPILER_SOURCE" + ], + [ + "test_x1_commons.py", + "test_prospective_full_region_infima_check_every_start", + "EXACT_CHECK" + ], + [ + "test_paper_x1_lean_closure.py", + "test_omitted_minimizing_start", + "SEMANTIC_NEGATIVE" + ] + ], + "X-04": [ + [ + "test_next03_tied_start_lean.py", + "test_lean_false_original_domain_mutation", + "SEMANTIC_NEGATIVE" + ], + [ + "test_final_closure_missing_original_positives.py", + "test_X_04_original_source_positive", + "COMPILER_SOURCE" + ] + ], + "X-05": [ + [ + "test_paper_x1_lean_closure.py", + "test_reset_before_action", + "SEMANTIC_NEGATIVE" + ], + [ + "test_paper_x1_lean_closure.py", + "test_new_world_learning", + "SEMANTIC_NEGATIVE" + ], + [ + "test_paper_x1_lean_closure.py", + "test_swapped_update_protocol", + "SEMANTIC_NEGATIVE" + ], + [ + "test_final_closure_missing_original_positives.py", + "test_X_05_original_source_positive", + "COMPILER_SOURCE" + ] + ], + "X-06": [ + [ + "test_x1_commons.py", + "test_leaking_region_not_exact_gr_and_persistence_decay", + "SCOPE_BOUNDARY" + ], + [ + "test_e1_approx_quotient.py", + "test_positive_quotient_margin_not_exact_gr", + "SCOPE_BOUNDARY" + ], + [ + "test_final_closure_missing_original_positives.py", + "test_X_06_original_source_positive", + "COMPILER_SOURCE" + ], + [ + "test_final_closure_existing_false_semantics_lean.py", + "test_X_06_false_exact_gr_under_real_leakage", + "SEMANTIC_NEGATIVE" + ] + ], + "S-01": [ + [ + "test_paper_strategy_economic_lean.py", + "test_source_bound_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_pr113_semantic_second_candidates.py", + "test_S_01_no_terminal_correction_is_false", + "SEMANTIC_NEGATIVE" + ] + ], + "S-02": [ + [ + "test_paper_information_lean.py", + "test_original_information_class_and_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_d2_residence.py", + "test_history_sensitive_policy_no_future_information", + "SCOPE_BOUNDARY" + ], + [ + "test_pr113_semantic_structural_candidates.py", + "test_S_02_clairvoyant_action_is_not_admissible_history_row", + "SEMANTIC_NEGATIVE" + ] + ], + "M-01": [ + [ + "test_paper_autonomy_ae_lean.py", + "test_actual_all_time_autonomy_and_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_f1_multiagent_protocols.py", + "test_strict_autonomous_off_support_difference_stochastic_crosscheck", + "EXACT_CHECK" + ], + [ + "test_pr113_semantic_second_candidates.py", + "test_M_01_null_key_is_not_free_initial_support", + "SEMANTIC_NEGATIVE" + ] + ], + "M-02": [ + [ + "test_next03_m02_marked_coupling.py", + "test_unmarked_key_laws_identical", + "EXACT_CHECK" + ], + [ + "test_next03_m02_marked_coupling.py", + "test_marked_diagonal_is_maximally_separating", + "SCOPE_BOUNDARY" + ], + [ + "test_m02_marked_actual_source_lean.py", + "test_original_typed_generator_source_and_axioms", + "COMPILER_SOURCE" + ], + [ + "test_m02_marked_actual_source_lean.py", + "test_invalid_strict_autonomy_promotion_fails_semantically", + "SEMANTIC_NEGATIVE" + ], + [ + "test_m02_actual_history_kernel_lean.py", + "test_real_marked_history_full_source_and_axioms", + "COMPILER_SOURCE" + ], + [ + "test_m02_actual_history_kernel_lean.py", + "test_falsified_actual_marked_diagonal_is_rejected", + "SEMANTIC_NEGATIVE" + ] + ], + "F-01": [ + [ + "test_next03_asymmetric_common_swap_lean.py", + "test_actual_common_update_false_same_input_claim", + "SEMANTIC_NEGATIVE" + ], + [ + "test_final05_measurable_lean.py", + "test_matched_asymmetric_source_witnesses", + "COMPILER_SOURCE" + ], + [ + "test_next03_asymmetric_common_swap_lean.py", + "test_source_bound_non_dirac_asymmetric_whole_path", + "COMPILER_SOURCE" + ] + ], + "F-02": [ + [ + "test_paper_measurable_clock_lean.py", + "test_actual_measurable_clock_projection_and_axioms", + "COMPILER_SOURCE" + ], + [ + "test_f1_multiagent_protocols.py", + "test_nonstationary_anchor_needs_clock_in_baseline", + "SCOPE_BOUNDARY" + ], + [ + "test_final_closure_more_false_semantics_lean.py", + "test_F_02_false_stalled_original_natural_clock", + "SEMANTIC_NEGATIVE" + ] + ], + "F-03": [ + [ + "test_paper_closure_boundaries.py", + "test_nonempty_state_menus_can_have_empty_key_intersection", + "SCOPE_BOUNDARY" + ], + [ + "test_f1_multiagent_protocols.py", + "test_infeasible_anchor_excluded_separate_toy", + "SCOPE_BOUNDARY" + ], + [ + "test_final_closure_missing_original_positives.py", + "test_F_03_original_source_positive", + "COMPILER_SOURCE" + ], + [ + "test_f03_actual_infeasible_strict_anchor_lean.py", + "test_actual_all_state_legal_fibre_and_original_existence_theorems", + "COMPILER_SOURCE" + ], + [ + "test_f03_actual_infeasible_strict_anchor_lean.py", + "test_false_feasible_anchor_with_conflicting_own_fibre_rejected", + "SEMANTIC_NEGATIVE" + ] + ], + "F-04": [ + [ + "test_f1_multiagent_protocols.py", + "test_ma3_correlated_law_not_product_exact", + "SCOPE_BOUNDARY" + ], + [ + "test_f1_multiagent_protocols.py", + "test_ma3_shared_source_is_representable", + "EXACT_CHECK" + ], + [ + "test_final_closure_more_false_semantics_lean.py", + "test_F_04_false_product_representation_of_correlated_law", + "SEMANTIC_NEGATIVE" + ] + ], + "F-05": [ + [ + "test_next03_positive_mediation.py", + "test_mediator_matters_not_only_direct_treatment", + "EXACT_CHECK" + ], + [ + "test_paper_closure_boundaries.py", + "test_observed_mediation_does_not_identify_joint_intervention", + "SCOPE_BOUNDARY" + ], + [ + "test_final_closure_more_false_semantics_lean.py", + "test_F_05_false_identification_of_original_joint_do", + "SEMANTIC_NEGATIVE" + ] + ], + "Q-01": [ + [ + "test_e1_approx_quotient.py", + "test_projection_is_not_markov_from_one_start", + "SCOPE_BOUNDARY" + ], + [ + "test_e1_approx_quotient.py", + "test_geometric_bound_numeric_only", + "EXACT_CHECK" + ], + [ + "test_pr113_semantic_structural_candidates.py", + "test_Q_01_fine_projection_does_not_always_lump", + "SEMANTIC_NEGATIVE" + ] + ], + "Q-02": [ + [ + "test_next04_projection_arithmetic.py", + "test_zero_kernel_defect_does_not_delete_initial_mismatch", + "SCOPE_BOUNDARY" + ], + [ + "test_next04_projection_arithmetic.py", + "test_time_varying_initial_and_row_budget", + "EXACT_CHECK" + ], + [ + "test_final_closure_more_false_semantics_lean.py", + "test_Q_02_false_zero_initial_error_under_zero_row_defect", + "SEMANTIC_NEGATIVE" + ] + ], + "Q-03": [ + [ + "test_next03_dependent_three_stage.py", + "test_true_conditional_history_dependence", + "EXACT_CHECK" + ], + [ + "test_a2_primitive_tv.py", + "test_a2_15_seeded_exact_rational_primitive_rows", + "EXACT_CHECK" + ], + [ + "test_final_actual_product_reporting_false_lean.py", + "test_Q_03_false_independent_error_mass", + "SEMANTIC_NEGATIVE" + ] + ], + "Q-04": [ + [ + "test_localized_prefix_compiler.py", + "test_actual_final_source_and_semantic_mutations", + "COMPILER_SOURCE" + ], + [ + "test_next04_original_margin.py", + "test_both_error_sides", + "EXACT_CHECK" + ], + [ + "test_final_actual_product_reporting_false_lean.py", + "test_Q_04_false_zero_intervention_report_error", + "SEMANTIC_NEGATIVE" + ] + ], + "Q-05": [ + [ + "test_final04_source_lean.py", + "test_actual_kernel_witness_examples_and_axioms", + "COMPILER_SOURCE" + ], + [ + "test_next04_projection_arithmetic.py", + "test_repeated_tiny_leaks_do_not_justify_exact_invariance", + "SCOPE_BOUNDARY" + ], + [ + "test_q05_nonidentity_source_fixture.py", + "test_exact_canonical_discounted_values_for_both_original_starts", + "EXACT_CHECK" + ], + [ + "test_q05_nonidentity_source_fixture.py", + "test_baseline_only_witness_misses_treatment_reachable_state", + "SCOPE_BOUNDARY" + ], + [ + "test_q05_actual_source_lean.py", + "test_actual_typed_primitive_models_and_axioms", + "COMPILER_SOURCE" + ], + [ + "test_q05_actual_source_lean.py", + "test_wrong_original_effect_cannot_compile", + "SEMANTIC_NEGATIVE" + ], + [ + "test_q05_original_margin_adapter_lean.py", + "test_exact_original_margin_type_and_axiom_dependencies", + "COMPILER_SOURCE" + ], + [ + "test_q05_original_margin_adapter_lean.py", + "test_unproved_source_domination_cannot_be_ignored", + "SEMANTIC_NEGATIVE" + ], + [ + "test_q05_actual_atom_closure_lean.py", + "test_all_original_kernel_atoms_pointwitness_and_margin_compiler", + "COMPILER_SOURCE" + ], + [ + "test_q05_actual_atom_closure_lean.py", + "test_actual_baseline_nonzero_atom_cannot_be_erased", + "SEMANTIC_NEGATIVE" + ] + ], + "A-01": [ + [ + "test_paper_metric_coupling_lean.py", + "test_actual_metric_certificates_and_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_a3_contractive.py", + "test_dual_equals_coupling_on_the_line", + "EXACT_CHECK" + ], + [ + "test_pr113_semantic_structural_candidates.py", + "test_A_01_unit_supnorm_is_not_correct_KR_test_class", + "SEMANTIC_NEGATIVE" + ] + ], + "A-02": [ + [ + "test_paper_invariant_lean.py", + "test_source_bound_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_a3_contractive.py", + "test_no_contraction_no_bound", + "SCOPE_BOUNDARY" + ], + [ + "test_pr113_semantic_second_candidates.py", + "test_A_02_identity_kernel_is_not_strictly_contractive", + "SEMANTIC_NEGATIVE" + ] + ], + "A-03": [ + [ + "test_a3_contractive.py", + "test_bounded_diameter_conversion", + "EXACT_CHECK" + ], + [ + "test_a3_contractive.py", + "test_unbounded_no_conversion", + "SCOPE_BOUNDARY" + ], + [ + "test_pr113_semantic_boundary_candidates.py", + "test_A_03_unbounded_TV_does_not_bound_W1", + "SEMANTIC_NEGATIVE" + ] + ], + "A-04": [ + [ + "test_a3_contractive.py", + "test_four_process_factorization_cannot_be_deleted", + "SCOPE_BOUNDARY" + ], + [ + "test_paper_strategy_economic_lean.py", + "test_source_bound_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_pr113_semantic_factorization_candidates.py", + "test_A_04_missing_fourth_output_changes_margin", + "SEMANTIC_NEGATIVE" + ] + ], + "A-05": [ + [ + "test_a3_contractive.py", + "test_a3_3_survival_flips", + "SCOPE_BOUNDARY" + ], + [ + "test_a3_contractive.py", + "test_four_process_factorization_cannot_be_deleted", + "SCOPE_BOUNDARY" + ], + [ + "test_final_closure_missing_original_positives.py", + "test_A_05_original_source_positive", + "COMPILER_SOURCE" + ], + [ + "test_pr113_semantic_factorization_candidates.py", + "test_A_05_stationary_mass_is_not_forever_survival", + "SEMANTIC_NEGATIVE" + ] + ], + "D-01": [ + [ + "test_d2_residence.py", + "test_bellman_equals_enumerated_deterministic_policies", + "EXACT_CHECK" + ], + [ + "test_d2_residence.py", + "test_product_clock_and_history_values_agree", + "EXACT_CHECK" + ], + [ + "test_pr113_semantic_second_candidates.py", + "test_D_01_hitting_is_not_residence", + "SEMANTIC_NEGATIVE" + ] + ], + "D-02": [ + [ + "test_d2_residence.py", + "test_retry_is_distinct_from_first_ever_entry", + "SCOPE_BOUNDARY" + ], + [ + "test_monitor_stationary_closures.py", + "test_all_short_physical_words", + "EXACT_CHECK" + ], + [ + "test_final_original_chronological_false_lean.py", + "test_D_02_false_retryable_residence_word", + "SEMANTIC_NEGATIVE" + ] + ], + "D-03": [ + [ + "test_monitor_stationary_closures.py", + "test_retry_first_and_danger_boundaries", + "EXACT_CHECK" + ], + [ + "test_paper_through_risk_lean.py", + "test_actual_history_risk_flag_and_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_final_original_chronological_false_lean.py", + "test_D_03_false_danger_after_success_revokes_it", + "SEMANTIC_NEGATIVE" + ] + ], + "D-04": [ + [ + "test_d2_residence.py", + "test_risk_budget_changes_optimum", + "EXACT_CHECK" + ], + [ + "test_paper_risk_lp_lean.py", + "test_actual_solver_signatures_and_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_final_original_risk_belief_false_lean.py", + "test_D_04_false_hard_risk_budget_accepts_p_one", + "SEMANTIC_NEGATIVE" + ] + ], + "D-05": [ + [ + "test_next06_committed_path_law.py", + "test_rectangular_reselection_is_a_different_path_law", + "SCOPE_BOUNDARY" + ], + [ + "test_next06_committed_path_law.py", + "test_committed_model_identity_persists_between_stages", + "EXACT_CHECK" + ], + [ + "test_final_original_chronological_false_lean.py", + "test_D_05_false_committed_rectangular_path_law_equality", + "SEMANTIC_NEGATIVE" + ] + ], + "D-06": [ + [ + "test_next06_reachable_completion_belief.py", + "test_both_histories_have_mass_under_the_same_law", + "EXACT_CHECK" + ], + [ + "test_next06_reachable_completion_belief.py", + "test_same_physical_readout_does_not_identify_completion", + "SCOPE_BOUNDARY" + ], + [ + "test_final_original_risk_belief_false_lean.py", + "test_D_06_false_same_readout_identifies_completion", + "SEMANTIC_NEGATIVE" + ] + ], + "D-07": [ + [ + "test_paper_infinite_safety_lean.py", + "test_actual_infinite_safety_audit", + "COMPILER_SOURCE" + ], + [ + "test_d2_residence.py", + "test_deterministic_robust_value_zero", + "EXACT_CHECK" + ], + [ + "test_pr113_semantic_boundary_candidates.py", + "test_D_07_finite_survival_does_not_imply_infinite_safety", + "SEMANTIC_NEGATIVE" + ] + ], + "D-08": [ + [ + "test_final_d08_admission.py", + "test_false_frozen_joint_admission_is_semantic_mutant", + "SEMANTIC_NEGATIVE" + ], + [ + "test_next06_d08_joint_only_compiler.py", + "test_actual_original_margin_control_and_false_mutant", + "COMPILER_SOURCE" + ] + ], + "D-09": [ + [ + "test_final_d09_multichain.py", + "test_exact_expected_score_not_score_of_expected_law", + "EXACT_CHECK" + ], + [ + "test_final06_phaseblind_original.py", + "test_exact_policy_scores_not_just_orbit_table", + "EXACT_CHECK" + ], + [ + "test_final_d09_multichain.py", + "test_compiled_distinct_physical_classes_and_nonlinear_lemma", + "COMPILER_SOURCE" + ], + [ + "test_final_d09_multichain.py", + "test_wrong_single_mixed_canonical_score_cannot_be_five_sixths", + "SEMANTIC_NEGATIVE" + ] + ], + "C11-01": [ + [ + "test_lane_c11_stationary.py", + "test_02_periodic_alternation", + "EXACT_CHECK" + ], + [ + "test_lane_c11_stationary.py", + "test_13_zero_and_resource_limits", + "SCOPE_BOUNDARY" + ], + [ + "test_pr113_semantic_second_candidates.py", + "test_C11_01_zero_dimensional_family_is_rejected", + "SEMANTIC_NEGATIVE" + ] + ], + "C11-02": [ + [ + "test_lane_c11_stationary.py", + "test_07_forged_share_after_hash_repair", + "SCOPE_BOUNDARY" + ], + [ + "test_lane_c11_stationary.py", + "test_09_missing_extra_and_duplicate_classes", + "SCOPE_BOUNDARY" + ], + [ + "test_final_closure_missing_original_positives.py", + "test_C11_02_original_source_positive", + "COMPILER_SOURCE" + ], + [ + "test_pr113_semantic_second_candidates.py", + "test_C11_02_corrupted_stationary_report_is_rejected", + "SEMANTIC_NEGATIVE" + ] + ], + "C-01": [ + [ + "test_paper_nonfinite_counterexample_lean.py", + "test_source_bound_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_pr113_semantic_boundary_candidates.py", + "test_C_01_bounded_descriptor_occupation_nonconvergence", + "SEMANTIC_NEGATIVE" + ] + ], + "B-01": [ + [ + "test_next06_overlap_lean.py", + "test_positive_and_false_compatibility_compile", + "SEMANTIC_NEGATIVE" + ], + [ + "test_lane_b2_sequential_effects.py", + "test_14_invalid_order_or_noncommuting_patch", + "SCOPE_BOUNDARY" + ], + [ + "test_final_closure_missing_original_positives.py", + "test_B_01_original_source_positive", + "COMPILER_SOURCE" + ] + ], + "B-02": [ + [ + "test_lane_b2_sequential_effects.py", + "test_09_AND_incomparable_singleton_supports", + "EXACT_CHECK" + ], + [ + "test_lane_b2_sequential_effects.py", + "test_10_XOR_destroys_upward_monotonicity", + "SCOPE_BOUNDARY" + ], + [ + "test_b02_actual_original_margin_lean.py", + "test_actual_original_margin_minimality_and_axioms", + "COMPILER_SOURCE" + ], + [ + "test_b02_actual_original_margin_lean.py", + "test_false_joint_minimality_is_rejected_semantically", + "SEMANTIC_NEGATIVE" + ], + [ + "test_b02_local_nonisomorphic_margin_lean.py", + "test_original_nonisomorphic_local_margin_compiles", + "COMPILER_SOURCE" + ], + [ + "test_b02_local_nonisomorphic_margin_lean.py", + "test_inadmissible_original_singleton_false_claim_rejected", + "SEMANTIC_NEGATIVE" + ] + ], + "XPLUS-01": [ + [ + "test_paper_strategy_economic_lean.py", + "test_source_bound_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_x1_commons.py", + "test_leaking_region_not_exact_gr_and_persistence_decay", + "SCOPE_BOUNDARY" + ], + [ + "test_pr113_semantic_second_candidates.py", + "test_XPLUS_01_constructed_original_margin_is_not_zero", + "SEMANTIC_NEGATIVE" + ] + ], + "E-01": [ + [ + "test_paper_closure_boundaries.py", + "test_seam_invisibility_can_have_rank_two", + "SCOPE_BOUNDARY" + ], + [ + "test_paper_specialized_boundary_lean.py", + "test_negative_statements_and_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_pr113_semantic_boundary_candidates.py", + "test_E_01_seam_invisibility_does_not_force_rank_one", + "SEMANTIC_NEGATIVE" + ] + ], + "E-02": [ + [ + "test_paper_closure_boundaries.py", + "test_exact_approximant_can_violate_legal_carrier", + "SCOPE_BOUNDARY" + ], + [ + "test_paper_closure_boundaries.py", + "test_embedding_without_form_preservation_loses_negative_witness", + "SCOPE_BOUNDARY" + ], + [ + "test_final_closure_missing_original_positives.py", + "test_E_02_original_source_positive", + "COMPILER_SOURCE" + ], + [ + "test_final_closure_existing_false_semantics_lean.py", + "test_E_02_false_legal_negative_witness_from_illegal_approximation", + "SEMANTIC_NEGATIVE" + ] + ], + "E-03": [ + [ + "test_paper_closure_boundaries.py", + "test_two_dimensional_shell_needs_second_equation", + "SCOPE_BOUNDARY" + ], + [ + "test_paper_closure_boundaries.py", + "test_zero_shift_does_not_invert_psd_singular_core", + "SCOPE_BOUNDARY" + ], + [ + "test_final_closure_missing_original_positives.py", + "test_E_03_original_source_positive", + "COMPILER_SOURCE" + ], + [ + "test_final_closure_existing_false_semantics_lean.py", + "test_E_03_false_two_dimensional_shell_collapse", + "SEMANTIC_NEGATIVE" + ] + ], + "E-04": [ + [ + "test_paper_closure_boundaries.py", + "test_singular_missing_preimage_produces_negative_vector", + "SCOPE_BOUNDARY" + ], + [ + "test_paper_closure_boundaries.py", + "test_source_exceptional_branch_does_not_force_vanishing", + "SCOPE_BOUNDARY" + ], + [ + "test_final_closure_missing_original_positives.py", + "test_E_04_original_source_positive", + "COMPILER_SOURCE" + ], + [ + "test_final_closure_existing_false_semantics_lean.py", + "test_E_04_false_zero_shift_inverse", + "SEMANTIC_NEGATIVE" + ] + ], + "E-05": [ + [ + "test_paper_closure_boundaries.py", + "test_positive_component_can_mask_gram_negative_direction", + "SCOPE_BOUNDARY" + ], + [ + "test_paper_specialized_boundary_lean.py", + "test_negative_statements_and_transitive_axioms", + "COMPILER_SOURCE" + ], + [ + "test_e05_full_gram_masking_lean.py", + "test_original_signed_negative_and_masked_total_compiler", + "COMPILER_SOURCE" + ], + [ + "test_e05_full_gram_masking_lean.py", + "test_false_unqualified_full_gram_negativity_rejected", + "SEMANTIC_NEGATIVE" + ] + ] +} +ROLES = frozenset({"COMPILER_SOURCE", "EXACT_CHECK", "SEMANTIC_NEGATIVE", "SCOPE_BOUNDARY"}) + + +def validated_routes(case_ids, *, root=ROOT, routes=METHOD_ROUTES): + """Fail before emitting a map if the exact original IDs/tests drift.""" + case_ids = tuple(case_ids) + if len(case_ids) != 45 or len(set(case_ids)) != 45 or set(routes) != set(case_ids): + raise ValueError("test selection does not cover the exact 45 original IDs") + cache = {} + selected = {} + for cid in case_ids: + rows = routes[cid] + if not isinstance(rows, list) or not rows: + raise ValueError("unassigned test-method selection: " + cid) + seen = set() + chosen = [] + for row in rows: + if not isinstance(row, list) or len(row) != 3: + raise ValueError("malformed test-method route: " + cid) + fname, method, role = row + if (not isinstance(fname, str) or not fname.startswith("test_") or + not fname.endswith(".py") or "/" in fname or "\\" in fname or + not isinstance(method, str) or not method.startswith("test_") or + role not in ROLES): + raise ValueError("unsupported test-method identity: " + cid) + if (fname, method) in seen: + raise ValueError("duplicate candidate test-method route: " + cid) + seen.add((fname, method)) + path = root / "verification" / "research" / fname + if fname not in cache: + if not path.is_file() or path.is_symlink(): + raise ValueError("candidate test source missing: " + str(path)) + syntax = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + cache[fname] = { + n.name for n in ast.walk(syntax) + if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) + and n.name.startswith("test_") + } + if method not in cache[fname]: + raise ValueError("candidate test-method absent: " + cid + "/" + fname + ":" + method) + chosen.append({"path": "verification/research/" + fname, + "method": method, "role": role, + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE"}) + selected[cid] = chosen + return selected diff --git a/verification/research/final_closure_case_receipts.py b/verification/research/final_closure_case_receipts.py new file mode 100644 index 00000000..3064d77a --- /dev/null +++ b/verification/research/final_closure_case_receipts.py @@ -0,0 +1,526 @@ +"""Detached exact-H case, claim, and compiler-evidence verification. + +Source-candidate decisions do not represent independent acceptance. +Only an overlay bound to live non-author exact-H GitHub review scopes, genuine +head-bound check runs and immutable off-repo evidence may yield effective +decisions. This module never mutates the historical 45-case ledger. +""" +from __future__ import annotations + +import hashlib +import io +import json +from pathlib import Path, PurePosixPath +import re +import stat +import zipfile +from closure_elaborated_type import ( + compiled_type_record, elaborated_type_sha256, TypeEvidenceError, +) + +ROOT = Path(__file__).resolve().parents[2] +FORMAT = "Permansson.FinalClosureDetachedReceipts.v2" +FORMAT_V3 = "Permansson.FinalClosureDetachedReceipts.v3" +TRUSTED_AXIOMS = frozenset({"Classical.choice", "Quot.sound", "propext"}) +DECISIONS = frozenset({"VERIFIED_SELECTED_SCOPE", "VERIFIED_NEGATIVE"}) +SHA256_RE = re.compile(r"^[0-9a-f]{64}$") +SHA40_RE = re.compile(r"^[0-9a-f]{40}$") +DECL_RE = re.compile( + r"(?m)^\s*(?:(?:private|protected|noncomputable|unsafe)\s+)*" + r"(?:theorem|lemma|def)\s+([A-Za-z0-9_']+)(?=\W)") +INVALID_DIAGNOSTICS = ( + "unknown module", "unknown identifier", "unknown constant", + "unknown tactic", "failed to import", "parser error", "syntax error", + "out of memory", "timed out", "timeout", "maximum number of heartbeats", + "failed to synthesize", "object file", "sorryax", + "declaration uses " + chr(96) + "sorry" + chr(96), +) +SEMANTIC_DIAGNOSTICS = ( + "type mismatch", "unsolved goals", "application type mismatch", +) +MAX_BUNDLE_BYTES = 32 * 1024 * 1024 +MAX_MEMBER_BYTES = 4 * 1024 * 1024 +MAX_EXPANDED_BYTES = 64 * 1024 * 1024 +MAX_MEMBERS = 512 +MAX_EXPANSION_RATIO = 1000 + + +class EvidenceError(ValueError): + pass + + +def demand(ok: bool, message: str): + if not ok: + raise EvidenceError(message) + + +def digest(raw: bytes) -> str: + return hashlib.sha256(raw).hexdigest() + + +def exact_source(path: str) -> Path: + demand(isinstance(path, str) and path and "\\" not in path, "invalid source path") + rel = PurePosixPath(path) + demand(not rel.is_absolute() and ".." not in rel.parts and + rel.as_posix() == path, "unsafe source path") + p = (ROOT / path).resolve() + demand(p.is_relative_to(ROOT.resolve()) and p.is_file() and + not (ROOT / path).is_symlink(), "source missing or escaped: " + path) + return p + + +def statement_header(source: str, qualified: str) -> str: + """Bind the actual nested Lean namespace, not merely a matching local name. + + This lexical calculation is not a substitute for compiler #check of the FQN. + """ + demand(isinstance(qualified, str) and "." in qualified and + re.fullmatch(r"[A-Za-z0-9_'.]+", qualified) is not None, + "declaration must be fully qualified") + name = qualified.rsplit(".", 1)[-1] + lines = source.splitlines(keepends=True) + stack = [] + offset = 0 + namespaces = [] + for line in lines: + stripped = line.strip() + if stripped.startswith("namespace "): + ns = stripped[len("namespace "):].strip() + demand(re.fullmatch(r"[A-Za-z0-9_.]+", ns) is not None, + "invalid Lean namespace") + stack.append(ns) + elif stripped.startswith("end "): + end = stripped[4:].strip() + if stack and (stack[-1] == end or + stack[-1].split(".")[-1] == end): + stack.pop() + elif stack and ".".join(stack) == end: + stack.clear() + if DECL_RE.match(line): + m = DECL_RE.match(line) + if m and m.group(1) == name: + prefix = ".".join(stack) + candidate = prefix + "." + name if prefix else name + namespaces.append((candidate, offset + m.start())) + offset += len(line) + chosen = [start for candidate, start in namespaces if candidate == qualified] + demand(len(chosen) == 1, "missing/ambiguous fully qualified declaration: " + qualified) + start = chosen[0] + match = DECL_RE.match(source, start) + demand(match is not None, "declaration scan inconsistent") + following = DECL_RE.search(source, match.end()) + limit = following.start() if following else len(source) + stop = source.find(":=", match.end(), limit) + demand(stop > match.end(), "declaration header separator absent") + return source[start:stop].strip() + + +def parse_target_axioms(log: str, declaration: str) -> list[str]: + """Read one genuine target-qualified Lean axiom record. + + Pinned Lean 4 prints quoted theorem names and multiline axiom lists. + Imported source can also print other records; a different declaration + must never be substituted for the requested theorem. + """ + demand(isinstance(log, str) and isinstance(declaration, str) and + re.fullmatch(r"[A-Za-z0-9_'.]+", declaration) is not None, + "invalid target axiom record") + record_pat = re.compile( + r"(?[A-Za-z0-9_'.]+)'|(?P[A-Za-z0-9_'.]+))" + r"\s+(?:depends on axioms:\s*\[(?P[^\]]*)\]|" + r"does not depend on any axioms)") + targets = [match for match in record_pat.finditer(log) + if (match.group("quoted") or match.group("plain")) == declaration] + demand(len(targets) == 1, + "missing, ambiguous or wrong-target compiler axiom output") + # Universal level suffixes do not change the underlying axiom identity. + raw = re.sub(r"\.\{[^{}]*\}", "", targets[0].group("axioms") or "") + names = [part.strip() for part in raw.split(",") if part.strip()] + demand(all(re.fullmatch(r"[A-Za-z_][A-Za-z0-9_'.]*", x) for x in names), + "malformed transitive axiom identity") + demand(len(set(names)) == len(names) and set(names) <= TRUSTED_AXIOMS, + "untrusted or duplicate transitive axiom") + return names + +def load_detached_bundle(path: str | Path, expected_sha256: str): + """Verify and parse the exact hashed ZIP bytes with aggregate resource bounds. + + All metadata are checked before decompression. Never reopen the potentially + mutable source path after verifying its digest; never extract members to disk. + """ + location = Path(path) + demand(location.is_file() and not location.is_symlink() and + not location.resolve().is_relative_to(ROOT.resolve()), + "evidence must be an external regular file") + demand(isinstance(expected_sha256, str) and SHA256_RE.fullmatch(expected_sha256), + "missing bundle SHA256") + demand(location.stat().st_size <= MAX_BUNDLE_BYTES, "evidence bundle too large") + raw = location.read_bytes() + demand(len(raw) <= MAX_BUNDLE_BYTES and digest(raw) == expected_sha256, + "detached evidence digest mismatch") + members = {} + try: + with zipfile.ZipFile(io.BytesIO(raw)) as archive: + entries = archive.infolist() + demand(len(entries) <= MAX_MEMBERS, "too many evidence members") + normalized = set() + expanded = 0 + for member in entries: + name = member.filename + rel = PurePosixPath(name) + demand(name and not member.is_dir() and + chr(92) not in name and not rel.is_absolute() and + ".." not in rel.parts and rel.as_posix() == name and + not any(part in ("", ".") for part in name.split("/")), + "unsafe evidence member") + folded = name.casefold() + demand(folded not in normalized, "duplicate evidence member") + normalized.add(folded) + mode = (member.external_attr >> 16) & 0o170000 + demand(mode in (0, stat.S_IFREG), "nonregular evidence member") + demand(member.file_size <= MAX_MEMBER_BYTES, "oversized evidence member") + expanded += member.file_size + demand(expanded <= MAX_EXPANDED_BYTES, + "expanded evidence bundle too large") + demand(member.compress_size >= 0 and + (member.file_size <= 1024 * 1024 or + member.file_size <= MAX_EXPANSION_RATIO * + max(1, member.compress_size)), + "suspicious evidence compression ratio") + for name in normalized: + parts = name.split("/") + demand(not any("/".join(parts[:i]) in normalized + for i in range(1, len(parts))), + "evidence member shadows a path ancestor") + for member in entries: + payload = archive.read(member) + demand(len(payload) == member.file_size, + "evidence member size mismatch") + members[member.filename] = payload + except (OSError, zipfile.BadZipFile, RuntimeError, EOFError) as exc: + raise EvidenceError("unreadable evidence archive") from exc + return members + + + +def member_bytes(members: dict[str, bytes], name: str, expected_hash: str) -> bytes: + demand(isinstance(name, str) and name in members, + "evidence log member absent") + demand(isinstance(expected_hash, str) and SHA256_RE.fullmatch(expected_hash) and + digest(members[name]) == expected_hash, "evidence log digest mismatch") + return members[name] + + + +def authenticate_v3_sources(overlay, head, members): + """Reauthenticate exact 21 producer ZIPs; never infer any math approval.""" + from final_closure_producer_bytes import authenticate + rows = overlay.get("producer_artifacts") + demand(isinstance(rows, list) and len(rows) == 21, + "all 21 exact-H producing artifacts required") + raw = {} + for item in rows: + demand(isinstance(item, dict) and + type(item.get("artifact_id")) is int and + isinstance(item.get("evidence_zip_member"), str) and + item["evidence_zip_member"] in members and + item["artifact_id"] not in raw, + "duplicate or missing producer ZIP") + raw[item["artifact_id"]] = members[item["evidence_zip_member"]] + try: + verified = authenticate(head, rows, raw) + except (ValueError, OSError, KeyError, TypeError) as exc: + raise EvidenceError("v3 producing CI job/ZIP authentication failed") from exc + demand(verified.get("format") == + "Permansson.PR113.ProducerBytesRevalidated.v1" and + verified.get("status") == "REAL_PRODUCING_BYTES_UNREVIEWED" and + verified.get("producer_count") == 21 and + verified.get("accepted_cases") == 0 and + verified.get("accepted_claims") == 0 and + verified.get("head_sha") == head, + "producer provenance cannot self-approve mathematical scope") + return verified + + +def verify_v3_member_links(row, members, producer, required, kind=None): + """All evidence member bytes must appear in the actual producer ZIP.""" + from final_closure_producer_bytes import original_member + artifact = row.get("producer_artifact_id") + check = row.get("check_run_id") + demand(type(artifact) is int and type(check) is int, + "producer must be referenced by real artifact and check IDs") + owned = producer.get("producers", {}).get(artifact) + demand(isinstance(owned, dict) and + owned.get("check_run_id") == check and + (kind is None or owned.get("kind") == kind), + "proof/test borrowed a different producer check") + links = row.get("producer_member_bindings") + demand(isinstance(links, list) and + len(links) == len(set(required)), + "not all original producer log/input bytes bound") + mapping = {} + for link in links: + demand(isinstance(link, dict) and + set(link) == {"outer_member", "source_member"} and + isinstance(link["outer_member"], str) and + isinstance(link["source_member"], str) and + link["outer_member"] not in mapping, + "malformed or duplicated producer member map") + mapping[link["outer_member"]] = link["source_member"] + demand(set(mapping) == set(required), + "producer member map does not cover exact retained evidence") + for outer in required: + demand(isinstance(outer, str) and outer in members, + "required source log not retained") + actual_digest = digest(members[outer]) + try: + raw = original_member( + producer, artifact, check, mapping[outer], actual_digest) + except ValueError as exc: + raise EvidenceError("original producer ZIP does not contain receipt") from exc + demand(raw == members[outer], "retained evidence differs from producer ZIP") + + +def validate_compiler_receipt(row: dict, head: str, members: dict[str, bytes], + accepted_checks: dict, *, producer_record=None): + demand(isinstance(row, dict) and row.get("head_sha") == head, + "stale/malformed compiler receipt") + path = row.get("path") + source = exact_source(path) + demand(source.suffix == ".lean" and + digest(source.read_bytes()) == row.get("source_sha256"), + "compiler receipt does not bind source bytes") + declaration = row.get("declaration") + # The source excerpt is display metadata only. It may be truncated at + # named arguments containing ':='; kernel elaboration is authoritative. + axioms = row.get("transitive_axioms") + demand(isinstance(axioms, list) and len(axioms) == len(set(axioms)) and + set(axioms) <= TRUSTED_AXIOMS, "bad or unreviewed transitive axiom") + demand(row.get("compiler_result") == "PASS", + "compiler success not established") + if producer_record is None: + demand(row.get("compiler_check") == "research", + "legacy compiler check missing") + check = accepted_checks.get("research") + demand(check and row.get("check_run_id") == check["id"] and + check["head_sha"] == head, "compiler check does not bind exact H") + else: + demand(row.get("compiler_check") == "pr113-compiler-shard", + "v3 compiler cannot borrow green global research check") + verify_v3_member_links( + row, members, producer_record, + [row.get("axiom_log_member")], kind="compiler") + data = member_bytes(members, row.get("axiom_log_member"), + row.get("axiom_log_sha256")) + stdout = data.decode("utf-8", errors="replace") + demand(declaration in stdout and "sorryAx" not in stdout and + "error:" not in stdout.lower(), "invalid axiom source diagnostic") + try: + compiled = compiled_type_record(stdout, declaration) + source_type_sha = elaborated_type_sha256(compiled) + except TypeEvidenceError as exc: + raise EvidenceError(str(exc)) from exc + demand(row.get("elaborated_type_sha256") == source_type_sha and + row.get("declaration_kind") == compiled["declaration_kind"] and + row.get("universe_params_repr") == compiled["universe_params_repr"], + "compiler elaborated-type identity mismatch") + observed = parse_target_axioms(stdout, declaration) + demand(observed == axioms, "target axioms do not match retained log") + if producer_record is not None: + # Kernel-elaborated full signature is authoritative, not a lexical + # header truncated at a named ':=' argument in Lean source. + demand(row.get("statement_sha256") == source_type_sha, + "v3 statement digest does not equal full elaborated type") + cases = row.get("case_ids") + claims = row.get("claim_ids") + demand(isinstance(cases, list) and isinstance(claims, list) and + (cases or claims), "theorem has no acceptance owner") + return row["id"] + + +def validate_test_receipt(row: dict, head: str, members: dict[str, bytes], + accepted_checks: dict, *, negative: bool, + producer_record=None): + demand(isinstance(row, dict) and row.get("head_sha") == head, + "stale/malformed test receipt") + p = exact_source(row.get("test_path")) + demand(p.suffix == ".py" and p.name.startswith("test_") and + digest(p.read_bytes()) == row.get("source_sha256"), + "test source identity not proven") + test_name = row.get("test_name") + demand(isinstance(test_name, str) and test_name.startswith("test_") and + re.search(r"(?m)^\s*def\s+" + re.escape(test_name) + r"\(", + p.read_text(encoding="utf-8")) is not None, + "named test does not exist") + if producer_record is None: + check = accepted_checks.get("research") + demand(check and row.get("check_run_id") == check["id"] and + check["head_sha"] == head, "test CI check not bound to H") + else: + pos = row.get("positive_control") + required = [row.get("log_member"), + pos.get("log_member") if isinstance(pos, dict) else None] + if negative: + required += [row.get("mutant_source_member"), + row.get("original_lean_source_member")] + verify_v3_member_links(row, members, producer_record, required) + pos = row.get("positive_control") + demand(isinstance(pos, dict) and pos.get("compiled") is True and + pos.get("exit_code") == 0 and pos.get("lean_toolchain") == "4.34.0", + "positive control missing or failed") + positive = member_bytes(members, pos.get("log_member"), pos.get("log_sha256")) + demand("error:" not in positive.decode("utf-8", errors="replace").lower(), + "positive test log contains compiler error") + log = member_bytes(members, row.get("log_member"), row.get("log_sha256")) + s = log.decode("utf-8", errors="replace").lower() + cases, claims = row.get("case_ids"), row.get("claim_ids") + demand(isinstance(cases, list) and isinstance(claims, list) and + (cases or claims), + "test is not linked to any original case or new claim") + if negative: + demand(row.get("outcome") == "REJECTED_SEMANTIC" and + row.get("compiler_exit_code") not in (None, 0) and + isinstance(row.get("mutant_source_sha256"), str) and + SHA256_RE.fullmatch(row["mutant_source_sha256"]), + "nonsemantic or missing false mutant") + mutated = member_bytes(members, row.get("mutant_source_member"), + row["mutant_source_sha256"]) + original_file = exact_source(row.get("original_lean_source_path")) + original = original_file.read_bytes() + # A complete, detached false-theorem receipt must retain both the + # exact original provider and the append-only false source bytes. + archived = member_bytes( + members, row.get("original_lean_source_member"), + row.get("original_lean_source_sha256")) + demand(archived == original and mutated.startswith(original) and + len(mutated) > len(original), + "original Lean bytes missing or mutation is not append-only") + appended = mutated[len(original):].decode("utf-8") + demand( + not re.search(r"(?mi)^\s*(?:import|axiom|constant|opaque)\b", appended) + and not re.search(r"(?i)\b(?:sorry|sorryAx|admit)\b", appended), + "false mutant appended an import, axiom, or admission") + demand(digest(original) == row.get("original_lean_source_sha256") and + digest(mutated) != digest(original) and + [line for line in mutated.decode("utf-8").splitlines() + if line.startswith("import ")] == + [line for line in original.decode("utf-8").splitlines() + if line.startswith("import ")], + "mutant bytes not a source-bound semantic variation") + demand(any(fragment in s for fragment in SEMANTIC_DIAGNOSTICS) and + "error:" in s and + not any(fragment in s for fragment in INVALID_DIAGNOSTICS), + "false theorem was not rejected semantically") + else: + demand(row.get("outcome") == "PASS" and + row.get("compiler_exit_code") == 0 and + not any(fragment in s for fragment in INVALID_DIAGNOSTICS) and + "error:" not in s, "positive test failed") + return row["id"] + + +def effective_acceptance(overlay: dict, *, head: str, case_ledger: dict, + claims: list[dict], members: dict[str, bytes], + accepted_checks: dict, approved_review_scopes: dict): + """Produce read-only decisions. All reviewer scopes come from live GitHub. + + approved_review_scopes is populated ONLY by authentication of non-author + GitHub APPROVED review bodies at the identical frozen H and bundle SHA. + """ + demand(isinstance(overlay, dict) and + overlay.get("format") in (FORMAT, FORMAT_V3) and + overlay.get("head_sha") == head and SHA40_RE.fullmatch(head), + "unsupported or stale detached overlay") + producer_record = (authenticate_v3_sources(overlay, head, members) + if overlay["format"] == FORMAT_V3 else None) + originals = {c["case_id"]: c for c in case_ledger["cases"]} + demand(len(originals) == len(case_ledger["cases"]) == 45, + "original 45-case identity mismatch") + inventory = {c["id"] for c in claims} + demand(len(inventory) == 11, "claim inventory mismatch") + def keyed(name): + rows = overlay.get(name, []) + demand(isinstance(rows, list), "invalid evidence collection: " + name) + records = {} + for row in rows: + demand(isinstance(row, dict) and + isinstance(row.get("id"), str) and + row["id"] and row["id"] not in records, + "duplicate or missing evidence identifier: " + name) + records[row["id"]] = row + return records + proof_rows = keyed("compiler_axiom_receipts") + pos_rows = keyed("positive_test_receipts") + neg_rows = keyed("semantic_negative_receipts") + case_rows = keyed("case_decisions") + claim_rows = keyed("claim_decisions") + proofs = {key: validate_compiler_receipt( + row, head, members, accepted_checks, + producer_record=producer_record) + for key, row in proof_rows.items()} + positives = {key: validate_test_receipt( + row, head, members, accepted_checks, + negative=False, producer_record=producer_record) + for key, row in pos_rows.items()} + negatives = {key: validate_test_receipt( + row, head, members, accepted_checks, + negative=True, producer_record=producer_record) + for key, row in neg_rows.items()} + effective_cases = {} + for row in case_rows.values(): + cid = row.get("case_id") + demand(cid in originals and cid not in effective_cases, + "unknown/duplicate accepted original case") + baseline = originals[cid] + demand(row.get("decision") in DECISIONS and + row.get("required_expected_result_sha256") == + digest(baseline["required_expected_result"].encode("utf-8")), + "altered original case expectation") + ids = row.get("proof_ids", []) + plus = row.get("positive_ids", []) + minus = row.get("negative_ids", []) + demand(all(isinstance(x, list) and bool(x) for x in (ids, plus, minus)), + "accepted case lacks individual positive/negative/proof evidence") + demand(all(i in proofs and cid in proof_rows[i]["case_ids"] for i in ids) and + all(i in positives and cid in pos_rows[i]["case_ids"] for i in plus) and + all(i in negatives and cid in neg_rows[i]["case_ids"] for i in minus), + "evidence borrowed from another case") + rid = row.get("review_id") + scope = approved_review_scopes.get(rid, {}) + demand(scope.get("cases", {}).get(cid) == row["decision"], + "no exact-H non-author reviewer approval for this case") + effective_cases[cid] = row["decision"] + effective_claims = set() + for row in claim_rows.values(): + cid = row.get("claim_id") + demand(cid in inventory and cid not in effective_claims, + "unknown or duplicate accepted claim") + ids = row.get("proof_ids", []) + demand(isinstance(ids, list) and bool(ids) and + all(i in proofs and cid in proof_rows[i]["claim_ids"] for i in ids), + "claim declaration not source bound") + for key, records, raw in ( + ("positive_ids", positives, pos_rows), + ("negative_ids", negatives, neg_rows), + ): + references = row.get(key) + demand(isinstance(references, list) and bool(references) and + len(references) == len(set(references)) and + all(ref in records and + cid in raw[ref].get("claim_ids", ()) + for ref in references), + "claim lacks individual executed positive/negative evidence: " + + str(cid) + "/" + key) + rid = row.get("review_id") + demand(cid in approved_review_scopes.get(rid, {}).get("claims", ()), + "claim lacks independent exact-H scope review") + effective_claims.add(cid) + return { + "effective_cases": effective_cases, + "effective_claims": sorted(effective_claims), + "accepted_case_count": len(effective_cases), + "pending_case_count": 45 - len(effective_cases), + "accepted_claim_count": len(effective_claims), + "status": "REVIEWED_SCOPE_ONLY_NOT_EMPIRICAL_CERTIFICATION", + } diff --git a/verification/research/final_closure_claim_controls_candidate.py b/verification/research/final_closure_claim_controls_candidate.py new file mode 100644 index 00000000..932c90ff --- /dev/null +++ b/verification/research/final_closure_claim_controls_candidate.py @@ -0,0 +1,239 @@ +"""Execute exactly 11 claim-owned positive and false Lean controls. + +Authoritative mathematical interpretation requires independent reviewers. +Selections and submitted append-only false source files MUST be explicitly +chosen; this script never invents a false statement or substitutes a +case-specific mutant for a claim-only obligation. It executes real named +unittest methods and pinned Lean 4.34.0, retaining original exact-H bytes. +Its outputs have NO producing GitHub check identity and NO accepted claims. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys + +from final_closure_mutation_evidence import ( + ROOT, committed_file_bytes, git_head, lean_result, + pinned_lean_version, verify_negative, seal_logs, + MutationEvidenceError, +) +from final_closure_positive_route_candidate import source_method +from final_closure_case_receipts import INVALID_DIAGNOSTICS +from final_closure_producer_v2_bridge import SHA40 + +FORMAT = "Permansson.PR113.ClaimControlsCandidate.v1" +SELECTION = "Permansson.PR113.ClaimControlSelection.v1" +CLAIMS = frozenset(("Q1","Q2","Q3","M1","M2","M3", + "N1","N2","N3","D8","D9")) +MAX_LOG_BYTES = 4*1024*1024 + + +class ClaimControlError(ValueError): + pass + + +def demand(ok, msg): + if not ok:raise ClaimControlError(msg) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def selection_map(payload, claims): + require_keys = { + "claim_id","proof_source_path","positive_test_path", + "positive_test_class","positive_test_method", + "negative_test_path","negative_test_class","negative_test_method", + } + demand(isinstance(payload,dict) and payload.get("format")==SELECTION and + isinstance(payload.get("claims"),list) and + isinstance(claims,dict) and set(claims)==CLAIMS, + "all eleven exact-source claim selections required") + result={} + for row in payload["claims"]: + demand(isinstance(row,dict) and set(row)==require_keys, + "unknown/forged claim control selection keys") + cid=row["claim_id"] + demand(cid in claims and cid not in result, + "missing, duplicated or foreign mathematical claim") + proof_path=row["proof_source_path"] + demand(isinstance(proof_path,str) and + any(s.get("path")==proof_path for s in + claims[cid].get("declarations",[])), + "claim selected a foreign original Lean provider") + candidates=claims[cid].get("test_source_candidates",[]) + demand(all(row[k] in candidates for k in + ("positive_test_path","negative_test_path")) and + row["positive_test_method"] != row["negative_test_method"], + "claim uses wrong selected source or reused one test method") + result[cid]=row + demand(set(result)==CLAIMS, + "at least one original new claim has no positive/negative selection") + return result + + +def named_method(head,path,cls,method): + return source_method(head,path,cls,method) + + +def run_test(path, cls, method): + environment=os.environ.copy() + environment["PYTHONDONTWRITEBYTECODE"]="1" + environment["PYTHONPATH"]=(str(ROOT/"verification/research") + + os.pathsep + str(ROOT) + + os.pathsep + environment.get("PYTHONPATH","")) + qualified=Path(path).stem+"."+cls+"."+method + try: + result=subprocess.run( + [sys.executable,"-B","-m","unittest",qualified,"-v"], + cwd=ROOT,env=environment,capture_output=True,timeout=900) + except (OSError,subprocess.TimeoutExpired) as exc: + raise ClaimControlError("named claim test was unavailable") from exc + demand(result.returncode==0 and + b"Ran 1 test" in result.stderr and b"OK" in result.stderr and + b"skipped=" not in result.stderr and + b"expected failures=" not in result.stderr and + len(result.stdout)+len(result.stderr) <= MAX_LOG_BYTES, + "named claim test failed or did not execute exactly one method") + return result + + +def execute(head, selections, mutant_root, output): + demand(isinstance(head,str) and SHA40.fullmatch(head) and + git_head()==head,"claim controls must use current genuine exact H") + select=Path(selections) + mutants=Path(mutant_root) + target=Path(output) + demand(select.is_file() and not select.is_symlink() and + not select.resolve().is_relative_to(ROOT.resolve()) and + mutants.is_dir() and not mutants.is_symlink() and + not mutants.resolve().is_relative_to(ROOT.resolve()) and + not target.is_symlink() and not target.resolve().exists() and + not target.resolve().is_relative_to(ROOT.resolve()), + "claim selection, original mutants and output must be disjoint external paths") + canonical=ROOT/"research/paper_closures/post_merge_112/claim_declaration_map.json" + manifest=json.loads(committed_file_bytes(head,canonical,root=ROOT)) + names={row["claim_id"]:row for row in manifest["claims"]} + chosen=selection_map(json.loads(select.read_bytes()),names) + version=pinned_lean_version(head) + target=target.resolve() + target.mkdir(parents=True,exist_ok=False) + executed=[] + for cid,row in sorted(chosen.items()): + original_path=row["proof_source_path"] + original=committed_file_bytes(head,ROOT/original_path,root=ROOT) + mutant_file=mutants/(cid+".lean") + demand(mutant_file.is_file() and not mutant_file.is_symlink() and + mutant_file.resolve().parent==mutants.resolve(), + "exact claim mutant source was not provided: "+cid) + altered=mutant_file.read_bytes() + demand(len(altered)>len(original) and altered.startswith(original) and + len(altered)<=MAX_LOG_BYTES and + b"\r" not in altered, + "claim mutant was not append-only to genuine original Lean source") + append=altered[len(original):].decode("utf-8") + demand(not re.search( + r"(?mi)^\s*(?:import|axiom|constant|opaque|set_option)\b",append) + and not re.search(r"(?i)\b(?:sorry|sorryAx|admit)\b",append), + "claim false assertion changed imports, axiom, options or admissions") + positive_test=named_method( + head,row["positive_test_path"],row["positive_test_class"], + row["positive_test_method"]) + negative_test=named_method( + head,row["negative_test_path"],row["negative_test_class"], + row["negative_test_method"]) + positive_test_result=run_test( + row["positive_test_path"],row["positive_test_class"], + row["positive_test_method"]) + negative_test_result=run_test( + row["negative_test_path"],row["negative_test_class"], + row["negative_test_method"]) + lean_positive=lean_result(ROOT/original_path) + lean_negative=lean_result(mutant_file) + verify_negative(lean_positive,lean_negative,original,altered) + demand(committed_file_bytes(head,ROOT/original_path,root=ROOT)==original + and committed_file_bytes(head,ROOT/row["positive_test_path"],root=ROOT) + ==positive_test and + committed_file_bytes(head,ROOT/row["negative_test_path"],root=ROOT) + ==negative_test and mutant_file.read_bytes()==altered, + "claim source/test or false assertion changed during execution") + path=target/cid + path.mkdir() + member={ + "original.lean":original, + "appended_false.lean":altered, + "positive_lean.log":(lean_positive.stdout+"\n"+lean_positive.stderr).encode(), + "negative_lean.log":(lean_negative.stdout+"\n"+lean_negative.stderr).encode(), + "positive_test.stdout":positive_test_result.stdout, + "positive_test.stderr":positive_test_result.stderr, + "negative_test.stdout":negative_test_result.stdout, + "negative_test.stderr":negative_test_result.stderr, + } + for name,raw in member.items(): + demand(len(raw)<=MAX_LOG_BYTES, + "captured original claim diagnostic exceeded bound") + (path/name).write_bytes(raw) + record={ + "format":"Permansson.PR113.ClaimOnlyControlCandidate.v1", + "status":"LEAN_POSITIVE_AND_FALSE_MUTANT_EXECUTED_UNREVIEWED", + "claim_id":cid,"case_ids":[],"claim_ids":[cid], + "head_sha":head,"original_lean_source_path":original_path, + "positive_test_path":row["positive_test_path"], + "positive_test_name":row["positive_test_method"], + "negative_test_path":row["negative_test_path"], + "negative_test_name":row["negative_test_method"], + "lean_version":version, + "lean_positive_exit_code":lean_positive.returncode, + "lean_negative_exit_code":lean_negative.returncode, + "source_test_hashes":{ + "positive":sha(positive_test),"negative":sha(negative_test)}, + "retained_member_sha256":{name:sha(raw) for name,raw in member.items()}, + "actual_ci_check_run_id":None, + "independent_mathematical_review":False, + "accepted_claim":False, + "scientific_release_approved":False, + } + (path/"unreviewed_claim_control.json").write_text( + json.dumps(record,sort_keys=True,indent=2)+"\n") + executed.append(record) + demand(len(executed)==11,"not all eleven claim false mutants ran") + summary={ + "format":FORMAT, + "status":"ALL_ELEVEN_EXECUTED_UNREVIEWED_NO_CI_ATTRIBUTION", + "source_head":head,"actual_new_claims_executed":11, + "claim_controls":executed, + "actual_producing_ci_check_run_id":None, + "independent_claim_reviews":0, + "mathematical_release_approved":False, + } + (target/"unreviewed_11_claim_control_summary.json").write_text( + json.dumps(summary,sort_keys=True,indent=2)+"\n") + return summary + + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--head",required=True) + p.add_argument("--selection",required=True,type=Path) + p.add_argument("--mutants-root",required=True,type=Path) + p.add_argument("--out",required=True,type=Path) + a=p.parse_args(argv) + try: + report=execute(a.head,a.selection,a.mutants_root,a.out) + except (ClaimControlError,MutationEvidenceError,OSError,ValueError, + KeyError,TypeError,UnicodeError) as exc: + print("PR113 CLAIM CONTROL EXECUTION BLOCKED:",exc,file=sys.stderr) + return 2 + print(json.dumps({"status":report["status"], + "claims":report["actual_new_claims_executed"]})) + return 0 + +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_claim_map.py b/verification/research/final_closure_claim_map.py new file mode 100644 index 00000000..e8e3f52a --- /dev/null +++ b/verification/research/final_closure_claim_map.py @@ -0,0 +1,177 @@ +"""Unreviewed fully-qualified eleven-claim declaration map. + +The primary owner is fixed by the existing theorem crosswalk. Supporting +proofs are explicit source obligations, NOT implied by importing one module. +All theorem FQNs and tested scopes are validated lexically, never accepted. +""" +import argparse +import json +from pathlib import Path +import sys + +from final_closure_case_receipts import statement_header + +ROOT = Path(__file__).resolve().parents[2] +SNAP = ROOT / "research/paper_closures/post_merge_112" +OUTPUT = SNAP / "claim_declaration_map.json" +NS_Q = "PermanssonResearch.Quotient." +NS_M = "PermanssonResearch.MultiAgent." +NS_N = "PermanssonResearch.ReverseSolver.NonlinearPeriodThree." +NS_D = "PermanssonResearch.ReverseSolver." + +# Every declaration here is a candidate requiring an actual current-source +# #check/#print axioms, literal statement hash, semantic mutant and human scope review. +SUPPORT = { + "Q1": [("Quotient/DiscountedCanonicalSourceWitness.lean", + NS_Q+"DiscountedCanonicalSourceWitness.canonical_discounted_output_error")], + "Q2": [("Quotient/DiscountedCanonicalMarginClosure.lean", + NS_Q+"DiscountedCanonicalMarginClosure.actual_canonical_discounted_original_margin"), + ("Quotient/DiscountedCanonicalMarginClosure.lean", + "PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure.point_witness_output")], + "Q3": [("Quotient/DiscountedCanonicalWitnessExamples.lean", + NS_Q+"DiscountedCanonicalWitnessExamples.SourceBoundExactWitness.exactKernelPointWitness"), + ("Quotient/DiscountedCanonicalWitnessExamples.lean", + "PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples.correct_frozen_discounted_budget"), + ("Quotient/DiscountedCanonicalWitnessExamples.lean", + "PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples.wrong_frozen_discounted_budget")], + "M1": [("MultiAgent/MeasurableProductTwoMatchedProtocols.lean", + NS_M+"MeasurableProduct.Two.matched_closed_kernel_two"), + ("MultiAgent/MeasurableProductTwoMatchedProtocols.lean", + NS_M+"MeasurableProduct.Two.matched_open_kernel_two")], + "M2": [("MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + NS_M+"MeasurableProduct.Two.matched_closed_loop_original_margin"), + ("MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + NS_M+"MeasurableProduct.Two.matched_open_loop_original_margin"), + ("MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_closed_loop_all_initial_paths"), + ("MultiAgent/MeasurableProductTwoOriginalMarginClosure.lean", + "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_open_loop_all_initial_paths")], + "M3": [("MultiAgent/MeasurableProductTwoMatchedExamples.lean", + NS_M+"MeasurableProduct.Two.matched_closed_worlds_both_frozen"), + ("MultiAgent/MeasurableProductTwoMatchedExamples.lean", + NS_M+"FinalMatchedAsymmetry.wrong_asymmetric_input_change_is_detectable"), + ("MultiAgent/MeasurableProductTwoMatchedExamples.lean", + "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_open_worlds_both_frozen")], + "N1": [("ReverseSolver/NonlinearPeriodThreeStationary.lean", + NS_N+"phase_aware_invariant_weights"), + ("ReverseSolver/NonlinearPeriodThreeClassGeometry.lean", + NS_N+"native_phase_aware_actual_true_mass")], + "N2": [("ReverseSolver/NonlinearPeriodThreeExpectedScore.lean", + NS_N+"native_phase_aware_actual_score_one"), + ("ReverseSolver/NonlinearPhaseBlindClassGeometry.lean", + NS_N+"native_phase_blind_actual_le_five_sixths"), + ("ReverseSolver/NonlinearPhaseBlindClassGeometry.lean", + NS_N+"native_phase_blind_actual_attained"), + ("ReverseSolver/NonlinearPhaseBlindClassGeometry.lean", + NS_N+"native_actual_phase_aware_strictly_beats_phase_blind"), + ("ReverseSolver/NonlinearPeriodThreeExpectedScore.lean", + "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.actual_restricted_gap_of_identified_classes")], + "N3": [("ReverseSolver/NonlinearBooleanClassEvaluator.lean", + NS_N+"booleanClassFunctional"), + ("ReverseSolver/NonlinearBooleanClassEvaluator.lean", + NS_N+"boolean_rational_objective_eq_actual"), + ("ReverseSolver/NonlinearBooleanClassEvaluator.lean", + "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.certified_boolean_score_exact"), + ("ReverseSolver/NonlinearBooleanClassEvaluator.lean", + "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.boolean_score_bounded")], + "D8": [("ReverseSolver/AdaptiveGrammarAdmissionFixture.lean", + NS_D+"AdaptiveGrammarAdmissionFixture.ConcreteRestrictedBoolean.actual_restricted_menu_admits_both_components"), + ("ReverseSolver/AdaptiveGrammarAdmissionFixture.lean", + NS_D+"AdaptiveGrammarAdmissionFixture.ConcreteRestrictedBoolean.actual_restricted_menu_rejects_joint"), + ("ReverseSolver/AdaptiveGrammarAdmissionFixture.lean", + NS_D+"AdaptiveGrammarAdmissionFixture.ConcreteRestrictedBoolean.actual_restricted_menu_row_normalized")], + "D9": [("ReverseSolver/NonlinearMultichainAggregationFixture.lean", + NS_D+"NonlinearMultichainAggregationFixture.actual_two_absorbing_full_path_mixture"), + ("ReverseSolver/NonlinearMultichainAggregationFixture.lean", + NS_D+"NonlinearMultichainAggregationFixture.actual_multichain_classwise_differs_from_barycenter"), + ("ReverseSolver/NonlinearMultichainAggregationFixture.lean", + NS_D+"NonlinearMultichainAggregationFixture.actual_mixed_nonlinear_half_not_barycenter"), + ("ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture.physically_distinct_classes_with_shared_descriptor"), + ("ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture.actual_mixed_initial_nonlinear_expectation"), + ("ReverseSolver/NonlinearMultichainAggregationFixture.lean", + "PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture.exact_two_absorbing_rational_scores")], +} +TEST_CANDIDATES = { + "Q1": ["test_final04_source_lean.py"], + "Q2": ["test_final04_source_lean.py"], + "Q3": ["test_final04_canonical_output.py"], + "M1": ["test_final05_measurable_lean.py"], + "M2": ["test_final05_measurable_lean.py"], + "M3": ["test_final05_matched_protocols.py"], + "N1": ["test_final06_native_class_lean.py"], + "N2": ["test_final06_phaseblind_original.py", "test_final06_actual_score_lean.py"], + "N3": ["test_final06_c11_evaluator.py"], + "D8": ["test_final_d08_admission.py"], + "D9": ["test_final_d09_multichain.py"], +} +EXCLUSIONS = { + "N2": ["No unrestricted randomized/history-dependent policy optimality", + "Augmented-state stationary policy is not phase-blind"], + "D9": ["Current half-versus-five-sixths multichain witness cannot by itself discharge historical five-sixths-versus-one test"], + "Q2": ["Source PointWitness and full B1 premise must actually be discharged"], + "M1": ["Conditional-product subclass only; no unrestricted correlated generator"], +} + + +def generate(): + cross = json.loads((SNAP / "theorem_crosswalk.json").read_text()) + rows = cross["closure_additions"] + if len(rows) != 11 or set(SUPPORT) != {x["id"] for x in rows}: + raise ValueError("closure owners changed") + claims = [] + for row in rows: + cid = row["id"] + decls = [] + for partial, fqn in SUPPORT[cid]: + path = "PermanssonResearch/" + partial + p = ROOT / path + if not p.is_file(): + raise ValueError("theorem provider missing: " + path) + header = statement_header(p.read_text(encoding="utf-8"), fqn) + decls.append({"path": path, "declaration": fqn, + "header_present": bool(header), + "review_status": "PENDING_AXIOMS_SEMANTIC_AND_INDEPENDENT_SCOPE"}) + tests = ["verification/research/" + t for t in TEST_CANDIDATES[cid]] + if any(not (ROOT / test).is_file() for test in tests): + raise ValueError("claim-level test candidates changed: " + cid) + claims.append({ + "claim_id": cid, "primary_path": row["path"], + "original_case_links": row["acceptance_case_ids"], + "declarations": decls, + "test_source_candidates": tests, + "scope_exclusions": EXCLUSIONS.get(cid, []), + "source_status": "SOURCE_PRESENT_REVIEW_PENDING", + "accepted": False, "independent_review_receipts": [], + }) + return {"format": "Permansson.FinalClosureQualifiedClaims.v1", + "source_baseline_sha": cross["source_baseline_sha"], + "count": 11, "status": "UNREVIEWED_SOURCE_CANDIDATES", + "claims": claims} + + +def render(): + return json.dumps(generate(), sort_keys=True, indent=2, + ensure_ascii=False) + "\n" + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, default=OUTPUT) + parser.add_argument("--check", action="store_true") + options = parser.parse_args(argv) + text = render() + if options.check: + if not options.output.is_file() or options.output.read_text() != text: + print("claim declaration map is stale or missing", file=sys.stderr) + return 2 + else: + options.output.parent.mkdir(parents=True, exist_ok=True) + options.output.write_text(text, encoding="utf-8") + print(json.dumps({"qualified_claims": 11, "status": "REVIEW_PENDING"})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_complete_lean_evidence.py b/verification/research/final_closure_complete_lean_evidence.py new file mode 100644 index 00000000..e311b38c --- /dev/null +++ b/verification/research/final_closure_complete_lean_evidence.py @@ -0,0 +1,553 @@ +"""Exact 45-case plus 11-claim theorem inventory and pinned-Lean exporter. + +The canonical 45-case ledger holds 290 historical declaration references +(typically short names); the 11-claim inventory holds fully qualified +supporting names. This module resolves them in their ACTUAL source files +before querying Lean's Environment for authoritative elaborated types and +transitive axioms. Lexical discovery can only propose a qualified name. +Compiler output is authoritative; any ambiguous/missing original declaration +is a BLOCKER, never silently dropped. + +Evidence remains detached, unreviewed and WITHOUT a fabricated CI check ID. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys +import tempfile + +from closure_elaborated_type import ( + lean_probe, compiled_type_record, elaborated_type_sha256, TypeEvidenceError, +) +from final_closure_case_receipts import parse_target_axioms, EvidenceError +from final_closure_lean_evidence import run_probe, parse_axioms +from final_closure_mutation_evidence import committed_file_bytes + +ROOT = Path(__file__).resolve().parents[2] +SNAP = ROOT / "research/paper_closures/post_merge_112" +SHA40 = re.compile(r"^[0-9a-f]{40}$") + + +class CompleteCompilerError(ValueError): + pass + + +def require(ok, message): + if not ok: + raise CompleteCompilerError(message) + + +def sha(raw: bytes): + return hashlib.sha256(raw).hexdigest() + + +def load(path: Path): + obj = json.loads(path.read_bytes()) + require(isinstance(obj, dict), "invalid source obligation JSON") + return obj + + + +LEAN_NAME = r"[A-Za-z_][A-Za-z0-9_']*" +# A declaration may be a public structure member written as +# "theorem Structure.member". Truncating the dotted name to "Structure" +# manufactures duplicate public names and drops the actual theorem owner. +QUALIFIED_LEAN_NAME = LEAN_NAME + r"(?:\." + LEAN_NAME + r")*" +DECLARATION = re.compile( + r"^[ \t]*(?:(?:protected|private|noncomputable|unsafe|partial|opaque)\s+)*" + r"(?:theorem|lemma|def|abbrev|structure|inductive|class|instance)\s+" + r"(" + QUALIFIED_LEAN_NAME + r")(?![A-Za-z0-9_'.])" +) +PREFIX = re.compile( + r"^[ \t]*(?P(?:(?:protected|private|noncomputable|unsafe|partial|opaque)\s+)*)" + r"(?:theorem|lemma|def|abbrev|structure|inductive|class|instance)\s+" +) +OPEN_NAMESPACE = re.compile(r"^[ \t]*namespace[ \t]+([A-Za-z0-9_.']+)(?:\s|$)") +CLOSE_SCOPE = re.compile(r"^[ \t]*end(?:[ \t]+([A-Za-z0-9_.']+))?(?:\s|$)") + + +def addressable_declarations(source: str): + """Find only ordinary qualified Lean owner candidates. + + Other modules may legitimately have private local lemmas, anonymous + instances or proof-local helpers. These must NOT invalidate an unrelated + pinned public declaration. Private names are excluded because Lean mangles + their exported identifiers; requests for such a source pin fail closed. + Comments are hidden before interpreting scope or theorem declarations. + Compiler elaboration, not this index, remains authoritative. + """ + if not isinstance(source, str): + raise CompleteCompilerError("invalid original source text") + stack = [] + found = [] + block_depth = 0 + for original in source.splitlines(): + # Lean permits nested block comments. Mask comments, preserving + # all noncomment text only when outside comment blocks. + line = [] + pos = 0 + while pos < len(original): + ahead = original[pos:pos+2] + if ahead == "/-": + block_depth += 1 + pos += 2 + elif block_depth and ahead == "-/": + block_depth -= 1 + pos += 2 + elif block_depth: + pos += 1 + elif ahead == "--": + break + else: + line.append(original[pos]) + pos += 1 + clean = "".join(line).strip() + if not clean: + continue + # Lean theorem attributes can prefix a genuine public declaration, + # e.g. "@[simp] theorem typedRow_measure". Strip only a complete + # same-line attribute wrapper after hiding comments; the compiler + # still authenticates the actual elaborated full declaration. + clean = re.sub(r"^(?:@\[[^\]\n]*\][ \t]*)+", "", clean) + if not clean: + continue + opened = OPEN_NAMESPACE.match(clean) + if opened: + stack.append(opened.group(1)) + continue + closed = CLOSE_SCOPE.match(clean) + if closed: + name = closed.group(1) + if stack and ( + name == stack[-1] or + name == stack[-1].split(".")[-1] or + name == ".".join(stack) + ): + if name == ".".join(stack): + stack.clear() + else: + stack.pop() + continue + declaration = DECLARATION.match(clean) + if declaration: + prefix = PREFIX.match(clean) + if prefix is not None and "private" in prefix.group("flags").split(): + continue + local = declaration.group(1) + found.append(".".join(stack + [local]) if stack else local) + if block_depth != 0: + raise CompleteCompilerError("unterminated Lean source comment") + if len(found) != len(set(found)): + repeated = sorted(name for name in set(found) if found.count(name) > 1) + raise CompleteCompilerError("duplicate public declaration source name: " + + repr(repeated)) + return found + + +def source_declarations(root: Path, relative: str, cache: dict): + if relative not in cache: + require(isinstance(relative, str) and + relative.startswith("PermanssonResearch/") and + relative.endswith(".lean") and + "\\" not in relative and + ".." not in Path(relative).parts, + "untrusted historical theorem path") + src = (root / relative).resolve() + require(src.is_relative_to(root.resolve()) and + src.is_file() and not src.is_symlink(), + "missing/hijacked original Lean module: " + relative) + try: + fqns = addressable_declarations(src.read_text(encoding="utf-8")) + except CompleteCompilerError as exc: + raise CompleteCompilerError(relative + ": " + str(exc)) from exc + cache[relative] = (src, fqns) + return cache[relative] + + +def resolved_name(root: Path, relative: str, short_name: str, cache: dict): + require(isinstance(short_name, str) and short_name and + not any(c.isspace() for c in short_name), + "invalid original declaration name") + _, known = source_declarations(root, relative, cache) + if short_name in known: + return short_name + options = [fqn for fqn in known if fqn.endswith("." + short_name)] + require(len(options) == 1, + "unresolved/ambiguous original Lean declaration: " + + relative + ":" + short_name + " -> " + repr(options)) + return options[0] + + +def required_inventory(*, root=ROOT, case_map=None, claim_map=None): + """Deterministic union of every original and supporting declaration.""" + case_map = load(SNAP / "acceptance_case_source_map.json") if case_map is None else case_map + claim_map = load(SNAP / "claim_declaration_map.json") if claim_map is None else claim_map + cases = case_map.get("cases") + claims = claim_map.get("claims") + require(isinstance(cases, list) and len(cases) == 45 and + isinstance(claims, list) and len(claims) == 11, + "original 45/11 inventory incomplete") + all_cases, all_claims = set(), set() + ownership = {} + cache = {} + def add(path, name, *, case=None, claim=None): + fqn = resolved_name(root, path, name, cache) + key = (path, fqn) + if key not in ownership: + ownership[key] = {"path": path, "declaration": fqn, + "case_ids": set(), "claim_ids": set()} + if case is not None: + ownership[key]["case_ids"].add(case) + if claim is not None: + ownership[key]["claim_ids"].add(claim) + for case in cases: + require(isinstance(case, dict), "malformed case obligation") + cid = case.get("case_id") + require(isinstance(cid, str) and cid not in all_cases, + "duplicate/invalid original case ID") + all_cases.add(cid) + entries = case.get("historical_proof_declarations") + require(isinstance(entries, list) and entries, + "case missing mandatory original theorems: " + cid) + for entry in entries: + require(isinstance(entry, dict), "malformed case theorem obligation") + add(entry.get("path"), entry.get("name"), case=cid) + claim_expected = {"Q1", "Q2", "Q3", "M1", "M2", "M3", "N1", "N2", "N3", "D8", "D9"} + for claim in claims: + require(isinstance(claim, dict), "malformed claim obligation") + cid = claim.get("claim_id") + require(isinstance(cid, str) and cid not in all_claims, + "invalid/duplicate claim identifier") + all_claims.add(cid) + entries = claim.get("declarations") + require(isinstance(entries, list) and entries, + "claim missing supporting theorem: " + str(cid)) + for entry in entries: + require(isinstance(entry, dict), "malformed claim theorem") + add(entry.get("path"), entry.get("declaration"), claim=cid) + require(all_claims == claim_expected and len(all_cases) == 45, + "exact 45/11 source-owner identities required") + return [ + {**entry, "case_ids": sorted(entry["case_ids"]), + "claim_ids": sorted(entry["claim_ids"])} + for key, entry in sorted(ownership.items()) + ] + + + +# This release's exact selected-scope union was independently inventoried at +# 259 distinct Lean declarations. A smaller successful subset cannot be +# promoted into the original 45-case/11-claim source-candidate producer. +FULL_TARGET_COUNT = 259 +# Frozen original criteria, independent of the claimed case count. A renamed +# or substituted historical case must not pass by keeping the total at 45. +FROZEN_ORIGINAL_CASE_IDS = frozenset(( + "A-01", + "A-02", + "A-03", + "A-04", + "A-05", + "B-01", + "B-02", + "C-01", + "C11-01", + "C11-02", + "D-01", + "D-02", + "D-03", + "D-04", + "D-05", + "D-06", + "D-07", + "D-08", + "D-09", + "E-01", + "E-02", + "E-03", + "E-04", + "E-05", + "F-01", + "F-02", + "F-03", + "F-04", + "F-05", + "M-01", + "M-02", + "Q-01", + "Q-02", + "Q-03", + "Q-04", + "Q-05", + "S-01", + "S-02", + "X-01", + "X-02", + "X-03", + "X-04", + "X-05", + "X-06", + "XPLUS-01", +)) + +FROZEN_CLAIM_IDS = frozenset(( + "Q1", "Q2", "Q3", "M1", "M2", "M3", + "N1", "N2", "N3", "D8", "D9", +)) + + +def validate_full_release_inventory(entries): + """Check the exact global evidence target, not a plausible small subset. + + This does NOT prove any theorem. Real full types, axiom receipts and + producer ZIP identities are verified separately by the Lean job and gate. + """ + require(isinstance(entries, list) and len(entries) == FULL_TARGET_COUNT, + "full closure compiler requires all 259 original proof targets") + keys, fqns, cases, claims = set(), set(), set(), set() + for row in entries: + require(isinstance(row, dict) and + set(row) == {"path", "declaration", "case_ids", "claim_ids"}, + "malformed original theorem owner") + path, fqn = row["path"], row["declaration"] + require(isinstance(path, str) and + path.startswith("PermanssonResearch/") and + path.endswith(".lean") and + ".." not in Path(path).parts and + isinstance(fqn, str) and "." in fqn, + "proof owner missing original source path or qualified Lean name") + require((path, fqn) not in keys and fqn not in fqns, + "duplicate or borrowed Lean theorem identity") + keys.add((path, fqn)) + fqns.add(fqn) + original, claim = row["case_ids"], row["claim_ids"] + require(isinstance(original, list) and + isinstance(claim, list) and + (original or claim) and + all(isinstance(x, str) and x for x in original + claim) and + len(original) == len(set(original)) and + len(claim) == len(set(claim)), + "unowned or duplicated theorem owner") + cases.update(original) + claims.update(claim) + require(cases == FROZEN_ORIGINAL_CASE_IDS and claims == FROZEN_CLAIM_IDS, + "full release theorem owner map substituted original 45/11 identities") + return entries + + +def git_head(): + try: + return subprocess.check_output(["git", "rev-parse", "HEAD"], + cwd=ROOT, text=True, + stderr=subprocess.DEVNULL, + timeout=30).strip() + except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc: + raise CompleteCompilerError("untrusted Git HEAD") from exc + + +def inventory_digest(entries: list[dict]) -> str: + """Bind all shards to exactly the same sorted source-owner map.""" + raw = json.dumps(entries, ensure_ascii=False, sort_keys=True, + separators=(",", ":")).encode("utf-8") + return sha(raw) + + +def shard_indices(total: int, shard_index: int, shard_count: int): + if (type(total) is not int or total < 1 or + type(shard_index) is not int or + type(shard_count) is not int or + not 1 <= shard_count <= 16 or + not 0 <= shard_index < shard_count or shard_count > total): + raise CompleteCompilerError("invalid compiler shard configuration") + return [idx for idx in range(total) if idx % shard_count == shard_index] + + +def source_bound_full_inventory(head: str): + """Bind BOTH original ledgers and all 259 theorem owners to exact Git H. + + Even --inventory-only is a source-bound *candidate*, not permission to + trust modified checkout JSON or pretend a status-only receipt was compiled. + We intentionally check the owner map before and after building the plan + so a mutable worktree cannot substitute its case/claim selection. + """ + require(isinstance(head, str) and SHA40.fullmatch(head) + and git_head() == head, + "original 45/11 inventory requested against foreign or stale Git H") + ledger_files = ( + SNAP / "acceptance_case_source_map.json", + SNAP / "claim_declaration_map.json", + ) + before = [ + committed_file_bytes(head, path, root=ROOT) + for path in ledger_files + ] + plan = validate_full_release_inventory(required_inventory()) + for path in sorted({row["path"] for row in plan}): + committed_file_bytes(head, ROOT / path, root=ROOT) + for path, bound in zip(ledger_files, before): + require(committed_file_bytes(head, path, root=ROOT) == bound, + "original 45/11 source-ledger changed during inventory") + require(git_head() == head, "Git HEAD moved during original inventory") + return plan + + +def export_all(head: str, output: Path, *, + shard_index: int = 0, shard_count: int = 1): + """Production-only 259-target release entrypoint; no inventory overrides. + + The mockable per-shard engine below is intentionally private. No CLI, + approved receipt, or full release path can reach it with a selected subset. + """ + manifest = source_bound_full_inventory(head) + return _export_checked_shard( + head, output, manifest, + shard_index=shard_index, shard_count=shard_count) + + +def _export_checked_shard(head: str, output: Path, manifest: list[dict], *, + shard_index: int = 0, shard_count: int = 1): + """Internal compiler diagnostic harness; NOT a full-release proof gate. + + Test fixtures exercise log-retention behavior with small source inventories. + Actual release runs must call export_all(), which first enforces all 259 + distinct 45-case / 11-claim source targets. + """ + require(isinstance(head, str) and SHA40.fullmatch(head) and + git_head() == head, "compiler checkout differs from requested H") + output = output.resolve() + require(not output.is_relative_to(ROOT.resolve()), + "compiler logs must be detached from the reviewed checkout") + require(isinstance(manifest, list) and manifest and + all(isinstance(row, dict) for row in manifest), + "internal diagnostic harness has no source inventory") + # Even a shard that does not compile a given original theorem must refuse + # a dirty/shadowed source-owner inventory. Authenticate ALL original paths + # at this H, not only the declarations assigned to the current worker. + for original_path in sorted({entry["path"] for entry in manifest}): + committed_file_bytes(head, ROOT / original_path, root=ROOT) + selected_indices = shard_indices(len(manifest), shard_index, shard_count) + manifest_digest = inventory_digest(manifest) + output.mkdir(parents=True, exist_ok=False) + (output / "axiom_logs").mkdir() + receipts = [] + for idx in selected_indices: + required = manifest[idx] + src = (ROOT / required["path"]).resolve() + # The requested H must supply these exact true theorem bytes; + # a dirty/shadowed checkout is not valid compiler evidence. + original_bytes = committed_file_bytes(head, src, root=ROOT) + module = required["path"][:-5].replace("/", ".") + declaration = required["declaration"] + code = lean_probe(module, declaration) + with tempfile.TemporaryDirectory(prefix="perm-case-compiler-", + dir=ROOT / ".lake") as td: + probe = Path(td) / "Proof.lean" + probe.write_text(code, encoding="utf-8") + result = run_probe(probe) + data = (result.stdout + "\n" + result.stderr).encode("utf-8") + require(0 < len(data) <= 4*1024*1024, + "compiler theorem log missing or too large") + # Preserve bounded RAW compiler bytes before interpreting exit codes, + # compiled types or axioms. Previously a failed parser exited before + # writing this log, leaving all-case failures unauditable in artifacts. + # This file is an UNREVIEWED diagnostic, never a proof receipt. + member = f"axiom_logs/all_{idx:04}.log" + log_path = output / member + require(not log_path.exists() and not log_path.is_symlink(), + "compiler log target already exists or is a symlink") + log_path.write_bytes(data) + require(log_path.read_bytes() == data, + "compiler log bytes changed after retention") + require(committed_file_bytes(head, src, root=ROOT) == original_bytes, + "source theorem bytes changed during compiler execution: " + + declaration) + require(result.returncode == 0, "compiler rejected mandatory source theorem: " + + declaration + "\n" + data[-500:].decode("utf-8", "replace")) + text = data.decode("utf-8") + try: + elaborated = compiled_type_record(text, declaration) + axioms = parse_axioms(text, declaration) + except (TypeEvidenceError, EvidenceError, RuntimeError) as exc: + # Keep each compiler shard fail-closed, but retain the exact + # parser/type reason so reviewers can distinguish a target-name + # mismatch, an invalid axiom or an absent elaborated type. + # No alternative theorem, skipped declaration or fabricated + # compiler success is permitted on this exception path. + raise CompleteCompilerError( + "unverifiable compiler declaration: " + declaration + + "; actual cause: " + type(exc).__name__ + ": " + str(exc)) from exc + # Raw member was retained before all authoritative validation. + receipts.append({ + "id": f"all:{idx:04}", "inventory_index": idx, + "head_sha": head, + "path": required["path"], + "declaration": declaration, + "case_ids": required["case_ids"], + "claim_ids": required["claim_ids"], + "source_sha256": sha(original_bytes), + "elaborated_type_sha256": elaborated_type_sha256(elaborated), + "declaration_kind": elaborated["declaration_kind"], + "universe_params_repr": elaborated["universe_params_repr"], + "transitive_axioms": axioms, + "compiler_result": "PASS", "compiler_check": "research", + "axiom_log_member": member, "axiom_log_sha256": sha(data), + "check_run_id": None, + }) + report = { + "format": "Permansson.AllCaseCompilerCandidate.v1", + "status": "COMPILER_ONLY_UNREVIEWED", + "head_sha": head, + "proof_count": len(receipts), + "inventory_count": len(manifest), + "inventory_sha256": manifest_digest, + "shard_index": shard_index, "shard_count": shard_count, + "case_count": 45, "claim_count": 11, + "compiler_axiom_receipts": receipts, + "independent_review": False, + "authentic_ci_job_bound": False, + "merged_main_sha": None, + } + (output / "complete_compiler_candidate.json").write_text( + json.dumps(report, sort_keys=True, indent=2) + "\n") + return report + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--head", required=True) + ap.add_argument("--output", required=True, type=Path) + ap.add_argument("--inventory-only", action="store_true") + ap.add_argument("--shard-index", type=int, default=0) + ap.add_argument("--shard-count", type=int, default=1) + args = ap.parse_args(argv) + try: + if args.inventory_only: + plan = source_bound_full_inventory(args.head) + output = { + "status": "SOURCE_CANDIDATE_UNREVIEWED", + "proof_targets": len(plan), + "cases": len({cid for row in plan for cid in row["case_ids"]}), + "claims": len({cid for row in plan for cid in row["claim_ids"]}), + "declarations": plan, + "inventory_sha256": inventory_digest(plan), + } + print(json.dumps(output, indent=2, sort_keys=True)) + else: + out = export_all(args.head, args.output, + shard_index=args.shard_index, + shard_count=args.shard_count) + print(json.dumps({"head": out["head_sha"], + "proof_targets": out["proof_count"], + "status": out["status"]})) + except (OSError, ValueError, RuntimeError) as exc: + print("ALL CASE LEAN EVIDENCE BLOCKED:", exc, file=sys.stderr) + return 2 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_execution_stamp.py b/verification/research/final_closure_execution_stamp.py new file mode 100644 index 00000000..1a5fb128 --- /dev/null +++ b/verification/research/final_closure_execution_stamp.py @@ -0,0 +1,147 @@ +"""Stamp genuine GitHub execution metadata on compiler *candidate* outputs. + +Runs inside the dedicated CI proof-evidence job, AFTER actual Lean execution. +This is provenance for a future externally checked GitHub artifact, not itself +a GitHub check-run ID, approval, authenticated evidence bundle, or release. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path, PurePosixPath +import re +import subprocess + +ROOT = Path(__file__).resolve().parents[2] +SHA40 = re.compile(r"^[0-9a-f]{40}$") +SHA64 = re.compile(r"^[0-9a-f]{64}$") +MAX_LOG_BYTES = 4 * 1024 * 1024 + + +class StampingError(ValueError): + pass + + +def need(ok, msg): + if not ok: + raise StampingError(msg) + + +def source_git(*args): + try: + return subprocess.check_output(["git", *args], cwd=ROOT, text=True, + stderr=subprocess.DEVNULL, timeout=25).strip() + except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc: + raise StampingError("genuine local Git identity unavailable") from exc + + +def attestation(folder: Path, *, head: str, env: dict) -> dict: + need(SHA40.fullmatch(head) is not None, "invalid expected source H") + need(source_git("rev-parse", "HEAD") == head, "CI did not execute frozen source H") + tree = source_git("rev-parse", "HEAD^{tree}") + need(SHA40.fullmatch(tree) is not None, "invalid source tree identity") + need(env.get("GITHUB_ACTIONS") == "true" and + env.get("GITHUB_REPOSITORY") == "HMarcusWH/PermanssonLean" and + env.get("GITHUB_EVENT_NAME") in {"workflow_dispatch", "push"} and + env.get("GITHUB_SHA") == head, + "untrusted runner/repository/event/source identity") + rid = env.get("GITHUB_RUN_ID", "") + attempt = env.get("GITHUB_RUN_ATTEMPT", "") + need(re.fullmatch(r"[1-9][0-9]*", rid) is not None and + re.fullmatch(r"[1-9][0-9]*", attempt) is not None and + env.get("GITHUB_JOB") == "compiler-candidate" and + env.get("GITHUB_WORKFLOW") == "PR113 Compiler Evidence Candidate", + "missing genuine CI run/attempt/job/workflow binding") + folder = Path(folder).resolve() + need(not folder.is_relative_to(ROOT.resolve()) and folder.is_dir(), + "candidate log directory must be detached from source") + data = json.loads((folder / "compiler_candidate.json").read_bytes()) + rows = data.get("compiler_axiom_receipts") + need(data.get("status") == "COMPILER_ONLY_UNREVIEWED" and + data.get("head_sha") == head and + data.get("claim_count") == 11 and + isinstance(rows, list) and bool(rows) and + len(rows) == data.get("proof_count"), + "compiled candidate claims are missing or fabricated") + ids = set() + evidence_files = {} + for item in rows: + need(isinstance(item, dict), "invalid theorem record") + rid_ = item.get("id") + name = item.get("axiom_log_member") + expected_hash = item.get("axiom_log_sha256") + need(isinstance(rid_, str) and rid_ and rid_ not in ids and + isinstance(name, str) and name and + SHA64.fullmatch(str(expected_hash)) is not None, + "duplicate/missing proof log identity") + ids.add(rid_) + rel = PurePosixPath(name) + need(not rel.is_absolute() and ".." not in rel.parts and + "\\" not in name and rel.as_posix() == name and + name.startswith("axiom_logs/"), + "unsafe proof artifact member path") + log_path = (folder / name).resolve() + need(log_path.is_relative_to(folder) and log_path.is_file() and + not (folder / name).is_symlink() and + log_path.stat().st_size <= MAX_LOG_BYTES, + "unretained/oversized proof artifact log") + raw = log_path.read_bytes() + sha = hashlib.sha256(raw).hexdigest() + need(sha == expected_hash, "compiled theorem log digest mismatch") + evidence_files[name] = sha + need(item.get("head_sha") == head and + item.get("check_run_id") is None and + item.get("compiler_result") == "PASS" and + item.get("CI_binding") == "PENDING_REAL_CHECK_RUN_ID_ON_FROZEN_H", + "candidate must never inherit unrelated or future CI ID") + require_owners = {"Q1", "Q2", "Q3", "M1", "M2", "M3", + "N1", "N2", "N3", "D8", "D9"} + owned = [c for item in rows for c in item.get("claim_ids", [])] + need(set(owned) == require_owners, "missing required eleven claim owners") + main = folder / "compiler_candidate.json" + result = { + "format": "Permansson.CIExecutedCompilerCandidate.v1", + "status": "EXECUTED_UNREVIEWED_NO_CHECKRUN_CLAIM", + "source_head": head, "source_tree": tree, + "github_repository": env["GITHUB_REPOSITORY"], + "github_workflow": env["GITHUB_WORKFLOW"], + "github_job": env["GITHUB_JOB"], + "github_run_id": int(rid), + "github_run_attempt": int(attempt), + "github_event_name": env["GITHUB_EVENT_NAME"], + "compiler_candidate_sha256": hashlib.sha256(main.read_bytes()).hexdigest(), + "compiler_evidence": dict(sorted(evidence_files.items())), + "proof_count": len(rows), "claim_count": len(require_owners), + "check_run_id": None, + "independent_review": False, + "merged_main_sha": None, + "full_mathematical_acceptance": False, + } + return result + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--head", required=True) + ap.add_argument("--evidence-dir", required=True, type=Path) + ap.add_argument("--out", required=True, type=Path) + a = ap.parse_args(argv) + try: + payload = attestation(a.evidence_dir, head=a.head, env=os.environ) + out = a.out.resolve() + need(out.parent == a.evidence_dir.resolve() and + not out.exists(), "stamp must be new and inside detached artifact") + out.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + except (StampingError, ValueError, OSError, KeyError) as exc: + print("CI EVIDENCE STAMP BLOCKED:", exc) + return 2 + print(json.dumps({"head": payload["source_head"], + "proofs": payload["proof_count"], + "status": payload["status"]})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_extra_producers_v31.py b/verification/research/final_closure_extra_producers_v31.py new file mode 100644 index 00000000..3db501a6 --- /dev/null +++ b/verification/research/final_closure_extra_producers_v31.py @@ -0,0 +1,422 @@ +"""Extra PR113 source-control CI producer authentication, versioned v3.1. + +The original 4+9+8 producing artifact set stays mandatory and unchanged. +New 45 positive case, 20 legacy negative and 11 claim true/false *pairs* +must each be traceable to the exact real CI job, check, downloaded artifact +and retained ZIP member bytes. This module NEVER grants security approval, +independent mathematical review, or publication. It does not install any +new GitHub workflow; reviewed owner/workflow mappings must be supplied by +a distinct authorized security process before a candidate can be checked. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import sys +from pathlib import PurePosixPath +import re + +from final_closure_authenticate import live, paginate +from final_closure_producer_bytes import inspect_zip, ProducerBytesError +from pr113_reviewed_producer_branch_v3 import ( + authentic_branch, ProducerBranchError, +) +from pr113_boundary_io import strict_json_object + +FORMAT = "Permansson.PR113.ControlProducerExtension.v3.1" +INDEX_FORMAT = "Permansson.PR113.AdditionalControlProducerIndex.v1" +RESULT_FORMAT = "Permansson.PR113.ControlCIAuthenticationCandidate.v3.1" +ORIGINAL_FORMAT = "Permansson.PR113.ProducerBytesRevalidated.v1" +KINDS = ("original_positive", "legacy_negative", "claim_pair") +STATUS = { + "original_positive": "SOURCE_TEST_AND_LEAN_EXECUTED_UNREVIEWED", + "legacy_negative": "SOURCE_APPENDED_REPLAYED_UNREVIEWED_NOT_CI_ATTRIBUTED", + "claim_pair": "LEAN_POSITIVE_AND_FALSE_MUTANT_EXECUTED_UNREVIEWED", +} +SHA40 = re.compile(r"^[0-9a-f]{40}$") +SHA256 = re.compile(r"^[0-9a-f]{64}$") +BRANCH = "research/final-mathematical-closure" + + +class ControlProducerError(ValueError): + pass + + +def need(ok, why): + if not ok: + raise ControlProducerError(why) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def expected_controls(original_case_ids, legacy_case_ids, claim_ids): + """Original 45/20/11 owners are derived from original source inventories.""" + cases, legacy, claims = set(original_case_ids), set(legacy_case_ids), set(claim_ids) + need(len(cases) == 45 and len(legacy) == 20 and legacy <= cases and + len(claims) == 11 and len(claim_ids) == 11, + "source-control owner inventory is not the immutable 45/20/11 scope") + return { + ("original_positive", cid) for cid in cases + } | { + ("legacy_negative", cid) for cid in legacy + } | { + ("claim_pair", cid) for cid in claims + } + + +def candidate_registry(payload, head, expected): + """Validate v3.1 shape before contacting GitHub. No synthetic approvals.""" + need(isinstance(payload, dict) and + payload.get("format") == FORMAT and + payload.get("head_sha") == head and + isinstance(head, str) and SHA40.fullmatch(head) and + isinstance(payload.get("original_21_producer_bundle_sha256"), str) and + SHA256.fullmatch(payload["original_21_producer_bundle_sha256"]) and + payload.get("original_producing_artifact_count") == 21 and + payload.get("source_controls_accepted") is False and + payload.get("independent_security_review") is False and + payload.get("mathematical_approval") is False and + payload.get("merged_main_sha") is None, + "additional CI producer registry claims an unauthorized acceptance") + jobs = payload.get("producers") + need(isinstance(jobs, list) and jobs, + "no exact H producing jobs registered") + all_owners, artifacts = set(), set() + for item in jobs: + need(isinstance(item, dict) and set(item) == { + "run_id","run_attempt","job_id","check_run_id","artifact_id", + "artifact_name","artifact_sha256","workflow_path","job_name", + "owner_index_member","owner_index_sha256","owners"}, + "malformed v3.1 job/check/artifact owner definition") + for name in ("run_id","run_attempt","job_id","check_run_id","artifact_id"): + need(type(item[name]) is int and item[name] > 0, + "untyped or forged producing identity: "+name) + for name in ("artifact_sha256","owner_index_sha256"): + need(isinstance(item[name],str) and SHA256.fullmatch(item[name]), + "untyped original producer SHA256") + for name in ("artifact_name","workflow_path","job_name", + "owner_index_member"): + need(isinstance(item[name],str) and item[name], + "producer missing required canonical name") + need(item["workflow_path"].startswith(".github/workflows/") and + item["workflow_path"].endswith((".yml",".yaml")) and + ".." not in PurePosixPath(item["workflow_path"]).parts, + "new producer workflow path escaped reviewed inventory") + need(item["artifact_id"] not in artifacts, + "artifact/check ownership cannot be duplicated") + artifacts.add(item["artifact_id"]) + owned = item["owners"] + need(isinstance(owned, list) and owned and + all(isinstance(owner, dict) and + set(owner) == {"kind","owner_id","receipt_member","receipt_sha256"} + for owner in owned), + "owner entries missing original member digests") + for owner in owned: + key = (owner["kind"],owner["owner_id"]) + need(key in expected and key not in all_owners and + isinstance(owner["receipt_member"],str) and + owner["receipt_member"] and + not owner["receipt_member"].startswith("/") and + ".." not in PurePosixPath(owner["receipt_member"]).parts and + isinstance(owner["receipt_sha256"],str) and + SHA256.fullmatch(owner["receipt_sha256"]), + "missing/duplicated/foreign control or noncanonical receipt") + all_owners.add(key) + need(all_owners == expected, + "the complete 45/20/11 source-control owner partition is absent") + return jobs + + +def authenticated_extra_controls(head, payload, zip_bytes, *, + original_producers, original_bundle_sha256, + expected, reviewed_workflow_blobs): + """Reopen exact original GitHub producer ZIPs, no approval promotion. + + The reviewed workflow OIDs must be authenticated separately. A caller + supplying an arbitrary OID does NOT grant the caller review authority; + this method only reports source-attributed candidate bytes. + """ + need(isinstance(original_producers, dict) and + original_producers.get("format") == ORIGINAL_FORMAT and + original_producers.get("status") == + "REAL_PRODUCING_BYTES_UNREVIEWED" and + original_producers.get("head_sha") == head and + original_producers.get("producer_count") == 21 and + isinstance(original_bundle_sha256,str) and + SHA256.fullmatch(original_bundle_sha256) and + payload.get("original_21_producer_bundle_sha256") == + original_bundle_sha256, + "original 21 producing artifact requirements were not authenticated") + producers = candidate_registry(payload, head, expected) + need(isinstance(zip_bytes, dict) and + set(zip_bytes) == {x["artifact_id"] for x in producers}, + "missing/extra original new-control ZIP bytes") + need(isinstance(reviewed_workflow_blobs,dict) and + set(reviewed_workflow_blobs) == + {x["workflow_path"] for x in producers} and + all(isinstance(v,str) and SHA40.fullmatch(v) + for v in reviewed_workflow_blobs.values()), + "new producer workflows have not been independently source-profiled") + results, total = {}, 0 + verified_branch = None + for job in producers: + path = job["workflow_path"] + # Real H must contain the reviewed producing workflow object; a + # made-up filepath or current-only unpublished workflow will fail. + source = live("/contents/"+path+"?ref="+head) + need(isinstance(source,dict) and + source.get("type") == "file" and + source.get("sha") == reviewed_workflow_blobs[path], + "actual GitHub H workflow object does not equal reviewed source") + run = live("/actions/runs/"+str(job["run_id"])) + if verified_branch is None: + try: + verified_branch = authentic_branch( + head, run.get("head_branch"), live=live) + except ProducerBranchError as exc: + raise ControlProducerError( + "current original-control producing branch not independently bound" + ) from exc + need(run.get("id") == job["run_id"] and + run.get("head_sha") == head and + run.get("head_branch") == verified_branch and + run.get("run_attempt") == job["run_attempt"] and + run.get("status") == "completed" and + run.get("conclusion") == "success" and + run.get("event") in ("push","workflow_dispatch") and + (run.get("path") == path or + str(run.get("path","")).endswith("/"+path)), + "control producer run is not a genuine exact-H workflow") + jobs = paginate("/actions/runs/"+str(job["run_id"])+"/jobs",key="jobs") + match = [j for j in jobs if j.get("id") == job["job_id"] and + j.get("name") == job["job_name"]] + need(len(match) == 1 and + match[0].get("run_id") == job["run_id"] and + match[0].get("run_attempt") == job["run_attempt"] and + match[0].get("head_sha") == head and + match[0].get("status") == "completed" and + match[0].get("conclusion") == "success" and + str(match[0].get("check_run_url","")).endswith( + "/check-runs/"+str(job["check_run_id"])), + "case/claim control source borrowed unrelated producing CI job") + check = live("/check-runs/"+str(job["check_run_id"])) + need(check.get("id") == job["check_run_id"] and + check.get("head_sha") == head and + check.get("name") == job["job_name"] and + check.get("status") == "completed" and + check.get("conclusion") == "success" and + (check.get("app") or {}).get("slug") == "github-actions", + "actual source control producing check did not pass") + artifact = live("/actions/artifacts/"+str(job["artifact_id"])) + need(artifact.get("id") == job["artifact_id"] and + artifact.get("name") == job["artifact_name"] and + artifact.get("expired") is False and + (artifact.get("workflow_run") or {}).get("id") == job["run_id"] and + (artifact.get("workflow_run") or {}).get("head_sha") == head and + artifact.get("digest") == "sha256:"+job["artifact_sha256"], + "GitHub authoritative control ZIP SHA or producing run mismatch") + raw = zip_bytes[job["artifact_id"]] + need(type(raw) is bytes and sha(raw) == job["artifact_sha256"], + "downloaded control ZIP was not the original producing artifact") + total += len(raw) + need(total <= 256*1024*1024, + "aggregate new controls exceeded bounded ZIP budget") + try: + members = inspect_zip(raw) + except ProducerBytesError as exc: + raise ControlProducerError("unsafe producer ZIP member") from exc + index_raw = members.get(job["owner_index_member"]) + need(index_raw is not None and + sha(index_raw) == job["owner_index_sha256"], + "original new-control producer ownership index missing/changed") + try: + index = strict_json_object(index_raw) + except (ValueError,TypeError,UnicodeError) as exc: + raise ControlProducerError("bad original new-control CI owner index") from exc + need(index.get("format") == INDEX_FORMAT and + index.get("head_sha") == head and + index.get("github_run_id") == job["run_id"] and + index.get("github_run_attempt") == job["run_attempt"] and + index.get("accepted_cases") == 0 and + index.get("accepted_claims") == 0 and + index.get("independent_review") is False and + index.get("owners") == job["owners"], + "producing source falsely promoted acceptance or changed owner list") + for owner in job["owners"]: + record = members.get(owner["receipt_member"]) + need(record is not None and sha(record) == owner["receipt_sha256"], + "true owner record absent from retained producing CI ZIP") + try: + actual = strict_json_object(record) + except (ValueError,TypeError,UnicodeError) as exc: + raise ControlProducerError("malformed source control receipt") from exc + need(actual.get("status") == STATUS[owner["kind"]] and + actual.get("head_sha") == head and + actual.get("case_id" if owner["kind"] != "claim_pair" + else "claim_id") == owner["owner_id"] and + actual.get("accepted_claim") is not True and + actual.get("independent_case_review") is not True and + actual.get("independent_mathematical_review") is not True and + actual.get("scientific_release_approved") is False, + "producer did not retain genuine unreviewed owner receipt") + results[(owner["kind"],owner["owner_id"])] = { + "artifact_id":job["artifact_id"], + "check_run_id":job["check_run_id"], + "receipt_member":owner["receipt_member"], + "receipt_sha256":owner["receipt_sha256"], + "source_status":"GITHUB_PRODUCED_NOT_INDEPENDENTLY_REVIEWED", + } + need(set(results) == expected, + "one or more real source control CI owners disappeared") + return { + "format":RESULT_FORMAT, + "status":"ALL_ADDITIONAL_CONTROLS_SOURCE_ATTRIBUTED_UNREVIEWED", + "head_sha":head, + "original_21_producing_artifacts_still_required":21, + "additional_producing_artifacts":len(producers), + "original_case_positives":45, + "legacy_case_negatives":20, + "claim_positive_negative_pairs":11, + "source_owned_controls":[ + {"kind":kind,"owner_id":owner,**details} + for (kind,owner),details in sorted(results.items()) + ], + "schema_security_approved":False, + "accepted_cases":0,"accepted_claims":0, + "independent_mathematical_review":False, + "full_release_approved":False, + } + + +def assemble_review_pending(head, original_v3_receipts, original_v3_zip, + additional_contract_file, additional_zip_directory, + proposed_reviewed_workflow_blobs, output): + """No local JSON can independently approve a workflow or mathematical claim. + + GH check ownership is revalidated live on both original and additional + producers. The reviewed workflow OIDs argument supplies *candidate* source + objects only. Actual security signoff remains external to this process. + """ + from final_closure_authenticate import git + from final_closure_case_receipts import ( + ROOT, load_detached_bundle, authenticate_v3_sources, + ) + from final_closure_schema import validate_overlay + from final_closure_legacy_semantic_evidence import original_case_map + from final_closure_producer_v2_bridge import verified_producer_index + + need(git("rev-parse","HEAD")==head, + "control extension must execute in its exact original H checkout") + source_root=ROOT.resolve() + def external(path, *, directory=False): + given=Path(path) + need(not given.is_symlink() and not any( + p.is_symlink() for p in given.absolute().parents), + "source/control evidence cannot use symlinked inputs") + value=given.resolve(strict=True) + need(not value.is_relative_to(source_root) and + not source_root.is_relative_to(value) and + (value.is_dir() if directory else value.is_file()), + "original control evidence must be external to Git source") + return value + original_json=external(original_v3_receipts) + original_zip=external(original_v3_zip) + manifest_path=external(additional_contract_file) + zip_dir=external(additional_zip_directory,directory=True) + workflow_blobs_path=external(proposed_reviewed_workflow_blobs) + out=Path(output) + need(not out.is_symlink(),"detached output cannot be symlinked") + out=out.resolve() + need(not out.exists() and not out.is_relative_to(source_root) and + not out.is_relative_to(zip_dir), + "new control provenance output must be separate and fresh") + original=strict_json_object(original_json.read_bytes(), max_bytes=8*1024*1024) + validate_overlay(original) + need(original.get("head_sha")==head and + original.get("format")=="Permansson.FinalClosureDetachedReceipts.v3", + "missing original strict v3 21-producer receipt") + original_members=load_detached_bundle( + original_zip,original["evidence_bundle_sha256"]) + from_original=authenticate_v3_sources(original,head,original_members) + byid, legacy=original_case_map(head) + claims=strict_json_object((source_root/"research/paper_closures/post_merge_112/" + "claim_declaration_map.json").read_bytes()) + expected=expected_controls(byid.keys(),legacy, + [x["claim_id"] for x in claims["claims"]]) + contract=strict_json_object(manifest_path.read_bytes()) + rows=candidate_registry(contract,head,expected) + files={} + for row in rows: + candidate=zip_dir/(str(row["artifact_id"])+".zip") + need(candidate.is_file() and not candidate.is_symlink() and + candidate.resolve().parent==zip_dir, + "original extra control ZIP not found by immutable artifact ID") + files[row["artifact_id"]]=candidate.read_bytes() + reviewed=strict_json_object(workflow_blobs_path.read_bytes()) + result=authenticated_extra_controls( + head,contract,files,original_producers=from_original, + original_bundle_sha256=original["evidence_bundle_sha256"], + expected=expected,reviewed_workflow_blobs=reviewed) + need(result["accepted_cases"]==0 and result["accepted_claims"]==0 and + result["schema_security_approved"] is False and + result["independent_mathematical_review"] is False, + "additional source CI candidate cannot manufacture approval") + # A genuine GitHub check and original ZIP digest are necessary but not + # sufficient: the ZIP must actually contain source-positive test/Lean logs, + # 20 appended-source semantic negatives and 11 claim true/false pairs. + # This is still an execution-evidence candidate, NEVER review or approval. + from pr113_v31_execution_integrity import ( + exact_source, validate_executed_controls, + ) + real_controls = validate_executed_controls( + head, contract, files, source=exact_source(head, source_root), + original_producers=from_original) + need(real_controls["source_owner_count"] == 76 and + real_controls["claim_positive_negative_directions"] == 22 and + real_controls["canonical_routes_bound"] is True and + real_controls["legacy_original_captures_bound"] is True and + real_controls["independent_review"] is False and + real_controls["mathematical_acceptance"] is False, + "actual 76 original source-control execution bytes not authenticated") + result["actual_execution_member_validation"] = real_controls + + out.parent.mkdir(parents=True,exist_ok=True) + with out.open("x",encoding="utf-8") as writer: + writer.write(json.dumps(result,sort_keys=True,indent=2)+"\n") + return result + + +def main(argv=None): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head",required=True) + parser.add_argument("--original-v3-receipts",required=True,type=Path) + parser.add_argument("--original-v3-zip",required=True,type=Path) + parser.add_argument("--additional-contract",required=True,type=Path) + parser.add_argument("--additional-zips",required=True,type=Path) + parser.add_argument("--reviewed-workflow-blob-candidates", + required=True,type=Path) + parser.add_argument("--output",required=True,type=Path) + options=parser.parse_args(argv) + try: + output=assemble_review_pending( + options.head,options.original_v3_receipts, + options.original_v3_zip,options.additional_contract, + options.additional_zips, + options.reviewed_workflow_blob_candidates,options.output) + except (ControlProducerError,OSError,ValueError,TypeError,KeyError) as exc: + print("ADDITIONAL SOURCE CONTROL PRODUCER AUTHENTICATION BLOCKED:", + exc,file=sys.stderr) + return 2 + print(json.dumps({"format":output["format"], + "status":output["status"], + "additional_owners":len(output["source_owned_controls"]), + "original_required_producers":21},sort_keys=True)) + return 0 + + +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_full_contract.py b/verification/research/final_closure_full_contract.py new file mode 100644 index 00000000..1f4c307e --- /dev/null +++ b/verification/research/final_closure_full_contract.py @@ -0,0 +1,353 @@ +"""Exact 45-case AND 11-claim selected-source conjunction. + +This module checks EFFECTIVE decisions issued by the authenticated gate, +not historic candidate counts, hand-authored owner labels, or CI green flags. +It is required before an exact-H source can be called full-source ready. +""" +from __future__ import annotations + +from final_closure_case_receipts import EvidenceError + +ORIGINAL_CLAIMS = frozenset(( + "Q1", "Q2", "Q3", "M1", "M2", "M3", + "N1", "N2", "N3", "D8", "D9", +)) +VERIFIED = frozenset(("VERIFIED_SELECTED_SCOPE", "VERIFIED_NEGATIVE")) + + +def require_full_source(result: dict, case_ledger: dict, + claims: list[dict], case_map: dict) -> dict: + """Fail closed on missing, duplicated or spurious EFFECTIVE requirements.""" + if not isinstance(result, dict) or not isinstance(case_ledger, dict) or \ + not isinstance(claims, list): + raise EvidenceError("full source evidence has invalid types") + cases = case_ledger.get("cases") + if not isinstance(cases, list) or len(cases) != 45 or \ + any(not isinstance(row, dict) for row in cases): + raise EvidenceError("full source must preserve all 45 original rows") + originals = [row.get("case_id") for row in cases] + if len(set(originals)) != 45 or any(not isinstance(x, str) for x in originals): + raise EvidenceError("original case identifiers are not unique") + claim_ids = [row.get("id") for row in claims if isinstance(row, dict)] + if len(claim_ids) != 11 or len(set(claim_ids)) != 11 or \ + set(claim_ids) != ORIGINAL_CLAIMS: + raise EvidenceError("canonical eleven claims are missing or substituted") + + # An accepted count is meaningless while any original case still lacks an + # explicit current-source test selection. Source maps are tracked at H and + # reviewed with H; historical aliases alone are not execution receipts. + selected = case_map.get("cases") if isinstance(case_map, dict) else None + if not isinstance(selected, list) or len(selected) != 45 or \ + any(not isinstance(row, dict) for row in selected): + raise EvidenceError("frozen case-to-test mapping is missing") + selection = [row.get("case_id") for row in selected] + if len(set(selection)) != 45 or set(selection) != set(originals): + raise EvidenceError("case-to-test mapping is incomplete or altered") + for row in selected: + tests = row.get("test_source_candidates") + if (not isinstance(tests, list) or not tests or + len(set(tests)) != len(tests) or + not all(isinstance(t, str) and + t.startswith("verification/research/test_") and + t.endswith(".py") for t in tests)): + raise EvidenceError( + "original case lacks an explicit selected test: " + row["case_id"]) + if not row.get("historical_proof_declarations"): + raise EvidenceError("original case lacks proof route: " + row["case_id"]) + method_rows = row.get("test_method_candidates") + if not isinstance(method_rows, list) or not method_rows: + raise EvidenceError("original case lacks named test-method selection: " + row["case_id"]) + known = set() + for candidate in method_rows: + if (not isinstance(candidate, dict) or + candidate.get("path") not in tests or + not isinstance(candidate.get("method"), str) or + not candidate["method"].startswith("test_") or + candidate.get("role") not in { + "COMPILER_SOURCE", "EXACT_CHECK", "SEMANTIC_NEGATIVE", + "SCOPE_BOUNDARY" + } or + candidate.get("status") != + "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE"): + raise EvidenceError("unreviewed method route invalid: " + row["case_id"]) + key = (candidate["path"], candidate["method"]) + if key in known: + raise EvidenceError("duplicated case method route: " + row["case_id"]) + known.add(key) + + effective_cases = result.get("effective_cases") + effective_claims = result.get("effective_claims") + if not isinstance(effective_cases, dict) or \ + set(effective_cases) != set(originals) or \ + any(status not in VERIFIED for status in effective_cases.values()) or \ + result.get("accepted_case_count") != 45 or \ + result.get("pending_case_count") != 0: + raise EvidenceError("full selected source is missing accepted original cases") + if not isinstance(effective_claims, list) or \ + len(effective_claims) != 11 or len(set(effective_claims)) != 11 or \ + set(effective_claims) != ORIGINAL_CLAIMS or \ + result.get("accepted_claim_count") != 11: + raise EvidenceError("full selected source is missing an accepted claim") + return { + "status": "FULL_SELECTED_SOURCE_SCOPE_REVIEWED", + "effective_case_count": 45, + "effective_claim_count": 11, + "case_ids": sorted(originals), + "claim_ids": sorted(ORIGINAL_CLAIMS), + "not_yet_release": True, + } + + +def require_preseal_release(result: dict, case_ledger: dict, claims: list[dict], + case_map: dict, manifest: dict, + provenance: dict) -> dict: + """Require genuine merged M, 45+11 review, PDF and clean-build evidence. + + A final sealed ZIP does not exist at this phase. No scholarly or CI + approval may come from hand-written source JSON. This is NOT a release. + """ + import re + source = require_full_source(result, case_ledger, claims, case_map) + if not isinstance(manifest, dict) or not isinstance(provenance, dict): + raise EvidenceError("full release lacks reviewed manuscript/provenance") + merged = provenance.get("M") + if not isinstance(merged, str) or not re.fullmatch(r"[0-9a-f]{40}", merged): + raise EvidenceError("actual merged M is unavailable") + if manifest.get("status") != "APPROVED_SOURCE_BOUND" or \ + manifest.get("source_commit_sha") != merged: + raise EvidenceError("source-faithful approved M manuscript missing") + published = manifest.get("claims") + reviewed = manifest.get("approved_manuscript_claims") + if not isinstance(published, list) or \ + published != reviewed or len(published) != 11 or \ + len(set(published)) != 11 or set(published) != ORIGINAL_CLAIMS: + raise EvidenceError("paper is not approved for exactly all 11 claims") + accepted = manifest.get("accepted_claim_receipts") + if not isinstance(accepted, list) or len(accepted) != 11 or \ + any(not isinstance(r, dict) or + not isinstance(r.get("claim_id"), str) or + not re.fullmatch(r"[0-9a-f]{64}", + str(r.get("receipt_sha256", ""))) + for r in accepted) or \ + {r["claim_id"] for r in accepted} != ORIGINAL_CLAIMS: + raise EvidenceError("full paper has incomplete accepted-claim receipts") + pdf = manifest.get("pdf_sha256") + pages = manifest.get("pdf_pages") + if not isinstance(pdf, str) or not re.fullmatch(r"[0-9a-f]{64}", pdf) or \ + not isinstance(pages, dict) or not pages or \ + any(not re.fullmatch(r"page-[0-9]+[.]png", name) or + not isinstance(dig, str) or + not re.fullmatch(r"[0-9a-f]{64}", dig) + for name, dig in pages.items()): + raise EvidenceError("complete exact PDF page digests are missing") + numbered = sorted(int(name[5:-4]) for name in pages) + if numbered != list(range(1, len(pages)+1)): + raise EvidenceError("full PDF pages are missing or duplicated") + if manifest.get("pdf_page_count") != len(pages): + raise EvidenceError("PDF page count does not match full render") + if manifest.get("empirical_identification_status") != "NOT_ESTABLISHED" or \ + not manifest.get("scientific_limitations"): + raise EvidenceError("paper has unsupported empirical or scope claims") + reviews = provenance.get("scholarly_reviews", []) + if not isinstance(reviews, list) or not any( + isinstance(s, dict) and s.get("pdf_sha256") == pdf and + s.get("pages") == pages and + set(s.get("claim_ids", [])) == ORIGINAL_CLAIMS + for s in reviews + ): + raise EvidenceError("independent exact-PDF review is missing") + + # The final artifact receipts are produced by an independent authenticator, + # never by copying JSON values from the candidate release manifest. + for label, kind in (("reproduction", "TWO_CLEAN_SOURCE_BUILDS"),): + expected = manifest.get(label + "_evidence_sha256") + signed = provenance.get(label + "_evidence") + if (not isinstance(expected, str) or + not re.fullmatch(r"[0-9a-f]{64}", expected) or + not isinstance(signed, dict) or + signed.get("status") != kind or + signed.get("source_commit_sha") != merged or + signed.get("pdf_sha256") != pdf or + signed.get("artifact_sha256") != expected or + signed.get("authenticated_ci_artifact") is not True): + raise EvidenceError("authenticated " + label + " evidence missing") + return { + **source, + "status": "POSTMERGE_PRESEAL_READY", + "actual_merged_main_sha": merged, + "pdf_sha256": pdf, + "pdf_page_count": len(pages), + "not_yet_release": True, + } + + +def require_full_release(result: dict, case_ledger: dict, claims: list[dict], + case_map: dict, manifest: dict, + provenance: dict) -> dict: + """Full release only after independently authenticated postseal replay.""" + import re + candidate = require_preseal_release( + result, case_ledger, claims, case_map, manifest, provenance) + recorded = provenance.get("archive_evidence") + expected = manifest.get("archive_evidence_sha256") + if (not isinstance(expected, str) or + not re.fullmatch(r"[0-9a-f]{64}", expected) or + not isinstance(recorded, dict) or + recorded.get("status") != "COMPLETE_ARCHIVE_REPLAY" or + recorded.get("source_commit_sha") != provenance["M"] or + recorded.get("pdf_sha256") != manifest["pdf_sha256"] or + recorded.get("artifact_sha256") != expected or + recorded.get("authenticated_ci_artifact") is not True): + raise EvidenceError( + "independently authenticated postseal archive replay is missing") + return {**candidate, + "status": "FULL_MATHEMATICAL_CLOSURE_RELEASE_READY", + "not_yet_release": False} + + +def require_complete_obligation_receipts( + overlay: dict, case_map: dict, claim_map: dict +) -> dict: + """Full-release receipt-set coverage, not mere case/claim labels. + + Every historical declaration must be present with the *correct source + path and declaration name*. Every declared supporting theorem for each + new claim is required; one theorem tagged with eleven owner IDs does not + suffice. Case-specific positive and semantic-negative tests must be + drawn from the frozen named-method route list. All referenced individual + receipts must already have passed source/compiler/CI validation in + effective_acceptance() before calling this. + """ + if not all(isinstance(x, dict) for x in (overlay, case_map, claim_map)): + raise EvidenceError("complete obligation inputs must be dictionaries") + case_list = case_map.get("cases") + claim_list = claim_map.get("claims") + if (not isinstance(case_list, list) or len(case_list) != 45 or + not isinstance(claim_list, list) or len(claim_list) != 11): + raise EvidenceError("canonical 45/11 source map unavailable") + def by_unique_id(rows, key, name): + if not isinstance(rows, list) or any(not isinstance(x, dict) + for x in rows): + raise EvidenceError("malformed " + name) + result = {} + for row in rows: + rid = row.get(key) + if not isinstance(rid, str) or not rid or rid in result: + raise EvidenceError("duplicate/invalid " + name + " identifier") + result[rid] = row + return result + cases = by_unique_id(case_list, "case_id", "case-source") + claims = by_unique_id(claim_list, "claim_id", "claim-source") + decisions = by_unique_id(overlay.get("case_decisions"), "case_id", "case-decision") + claimed = by_unique_id(overlay.get("claim_decisions"), "claim_id", "claim-decision") + proof = by_unique_id(overlay.get("compiler_axiom_receipts"), "id", "proof") + positive = by_unique_id(overlay.get("positive_test_receipts"), "id", "positive") + negative = by_unique_id(overlay.get("semantic_negative_receipts"), "id", "negative") + if (set(cases) != set(decisions) or + set(claims) != set(claimed) or + set(claims) != ORIGINAL_CLAIMS): + raise EvidenceError("45 original/11 claim decisions incomplete or substituted") + + def ids(row, key, records, owner_key, owner): + values = row.get(key) + if not isinstance(values, list) or not values or \ + len(values) != len(set(values)): + raise EvidenceError("missing/duplicate " + key + " for " + owner) + for value in values: + if value not in records or owner not in records[value].get(owner_key, []): + raise EvidenceError("borrowed/unknown " + key + " for " + owner) + return {value: records[value] for value in values} + + def proof_identity(p): + path = p.get("path") + decl = p.get("declaration") + if not isinstance(path, str) or not isinstance(decl, str): + raise EvidenceError("untyped compiler proof identity") + return (path, decl) + + for cid, original in cases.items(): + chosen = decisions[cid] + prs = ids(chosen, "proof_ids", proof, "case_ids", cid) + ps = ids(chosen, "positive_ids", positive, "case_ids", cid) + ns = ids(chosen, "negative_ids", negative, "case_ids", cid) + expected = original.get("historical_proof_declarations") + if not isinstance(expected, list) or not expected: + raise EvidenceError("original theorem inventory absent for " + cid) + actual = {proof_identity(p) for p in prs.values()} + for row in expected: + if not isinstance(row, dict): + raise EvidenceError("malformed historical proof pin: " + cid) + path, name = row.get("path"), row.get("name") + if (not isinstance(path, str) or not isinstance(name, str) or + not any(p == path and + (decl == name or decl.endswith("." + name)) + for p, decl in actual)): + raise EvidenceError( + "missing original source declaration for " + cid + + ": " + str(path) + "/" + str(name)) + routes = original.get("test_method_candidates") + if not isinstance(routes, list) or not routes: + raise EvidenceError("missing required method routes: " + cid) + registry = { + (x.get("path"), x.get("method"), x.get("role")) + for x in routes if isinstance(x, dict) + } + if len(registry) != len(routes): + raise EvidenceError("ambiguous method route registry: " + cid) + def admissible(row, permitted): + return any( + (row.get("test_path"), row.get("test_name"), role) in registry + for role in permitted + ) + if not any(admissible(p, {"COMPILER_SOURCE", "EXACT_CHECK"}) + for p in ps.values()): + raise EvidenceError("original case lacks route-selected positive: " + cid) + if not any(admissible(n, {"SEMANTIC_NEGATIVE"}) + for n in ns.values()): + raise EvidenceError("original case lacks semantic negative route: " + cid) + # Scoped source links are mandatory; no arbitrary proof can + # silently replace a newly introduced case-linked theorem owner. + for link in original.get("new_claim_source_candidates", []): + if not isinstance(link, dict): + raise EvidenceError("malformed case-linked claim source") + path = link.get("path") + if not isinstance(path, str) or not any(p[0] == path for p in actual): + raise EvidenceError( + "missing case-linked claim source: " + cid + "/" + str(path)) + + for cid, required in claims.items(): + prs = ids(claimed[cid], "proof_ids", proof, "claim_ids", cid) + ps = ids(claimed[cid], "positive_ids", positive, "claim_ids", cid) + ns = ids(claimed[cid], "negative_ids", negative, "claim_ids", cid) + selected_paths = required.get("test_source_candidates") + if (not isinstance(selected_paths, list) or not selected_paths or + len(selected_paths) != len(set(selected_paths)) or + not all(isinstance(path, str) and + path.startswith("verification/research/test_") and + path.endswith(".py") for path in selected_paths)): + raise EvidenceError("claim lacks frozen selected test sources: " + cid) + if not any(row.get("test_path") in selected_paths for row in ps.values()): + raise EvidenceError("claim lacks source-selected positive: " + cid) + if not any(row.get("test_path") in selected_paths for row in ns.values()): + raise EvidenceError("claim lacks source-selected semantic negative: " + cid) + actual = {proof_identity(p) for p in prs.values()} + expected = required.get("declarations") + if not isinstance(expected, list) or not expected: + raise EvidenceError("claim has no explicit required support: " + cid) + expected_pairs = set() + for entry in expected: + if not isinstance(entry, dict): + raise EvidenceError("malformed support declaration: " + cid) + p, d = entry.get("path"), entry.get("declaration") + if not isinstance(p, str) or not isinstance(d, str): + raise EvidenceError("unqualified supporting theorem: " + cid) + expected_pairs.add((p, d)) + if not expected_pairs <= actual: + raise EvidenceError("claim omits supporting compiler theorem(s): " + cid) + + return { + "receipt_contract": "ALL_45_HISTORICAL_AND_ALL_11_COMPLETE", + "cases_covered": 45, + "claims_covered": 11, + "review_and_ci_still_required": True, + } diff --git a/verification/research/final_closure_gate.py b/verification/research/final_closure_gate.py new file mode 100644 index 00000000..d39f9767 --- /dev/null +++ b/verification/research/final_closure_gate.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Explicit premerge REVIEW gate and postmerge strict acceptance entrypoint. + +A premerge reviewed integration candidate is NOT mathematical full45 or +scientific release approval. This script never merges or tags Git objects. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + +from final_closure_authenticate import ( + AuthenticationError, verify_premerge, +) +from final_closure_case_receipts import ( + EvidenceError, FORMAT, FORMAT_V3, load_detached_bundle, effective_acceptance, +) +from final_closure_verify import ( + GateError, NEW, read_json, strict, +) +from final_closure_schema import validate_overlay +from final_closure_full_contract import require_full_source, require_complete_obligation_receipts +from pr113_mandatory_v31 import authenticate_all_76, MandatoryV31Error +from pr113_boundary_io import strict_json_object + + +def premerge(receipts: dict, bundle: Path, *, full: bool = False, + additional_contract=None, additional_zips=None, + workflow_blob_candidates=None): + validate_overlay(receipts) + if receipts.get("format") not in (FORMAT, FORMAT_V3) or \ + receipts.get("phase") != "premerge_review" or \ + receipts.get("merged_main_sha") is not None: + raise GateError("premerge requires detached v2 source, no future M") + provenance = verify_premerge(receipts, require_independent_review=True) + members = load_detached_bundle(bundle, receipts.get("evidence_bundle_sha256")) + decisions = read_json(NEW/"acceptance_decisions.json") + inventory = read_json(NEW/"claim_inventory.json") + verified = effective_acceptance( + receipts, head=provenance["H"], case_ledger=decisions, + claims=inventory["claims"], members=members, + accepted_checks=provenance["checks"], + approved_review_scopes=provenance["review_scopes"]) + if full: + if receipts.get("format") != FORMAT_V3: + raise GateError("complete premerge 45/11 requires authenticated v3 producers") + source_map = read_json(NEW/"acceptance_case_source_map.json") + full_result = require_full_source( + verified, decisions, inventory["claims"], source_map) + claim_map = read_json(NEW/"claim_declaration_map.json") + full_result.update(require_complete_obligation_receipts( + receipts, source_map, claim_map)) + # Reauthenticate all 76 actually executed source controls against + # current GitHub original artifacts. A v3-only/full45 label is NOT + # mathematical full-source readiness. + if any(x is None for x in ( + additional_contract, additional_zips, + workflow_blob_candidates)): + raise GateError("premerge-full requires all 76 real v3.1 GitHub source controls") + full_result["additional_execution"] = authenticate_all_76( + provenance["H"], receipts, members, + additional_contract=additional_contract, + additional_zips=additional_zips, + workflow_blob_candidates=workflow_blob_candidates) + else: + if verified["accepted_case_count"] + verified["accepted_claim_count"] == 0: + raise GateError("no exact source-scope case or claim independently reviewed") + full_result = {} + return {**verified, **full_result, + "gate": "PREMERGE_FULL_SOURCE_READY" if full else + "PREMERGE_REVIEWED_CANDIDATE", + "source_head": provenance["H"], + "synthetic_sha": provenance["S"], + "merged_main_sha": None, + "merge_authorized": False, + "scientific_release_approved": False} + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument("--phase", + choices=("premerge", "premerge-full", "full45-strict", + "scoped-release-strict", + "postmerge-preseal-strict", + "full-release-strict"), + required=True) + parser.add_argument("--receipts", type=Path, required=True) + parser.add_argument("--bundle", type=Path, required=True) + parser.add_argument("--approved-manifest", type=Path) + parser.add_argument("--reproduction-zip", type=Path) + parser.add_argument("--reproduction-ids", type=Path) + parser.add_argument("--postseal-zip", type=Path) + parser.add_argument("--postseal-ids", type=Path) + parser.add_argument("--additional-contract", type=Path) + parser.add_argument("--additional-zips", type=Path) + parser.add_argument("--workflow-blob-candidates", type=Path) + opts = parser.parse_args(argv) + try: + receipts = strict_json_object(opts.receipts.read_bytes(), max_bytes=8*1024*1024) + if opts.phase in ("premerge", "premerge-full"): + result = premerge( + receipts, opts.bundle, full=opts.phase == "premerge-full", + additional_contract=opts.additional_contract, + additional_zips=opts.additional_zips, + workflow_blob_candidates=opts.workflow_blob_candidates) + else: + manifest = ( + strict_json_object(opts.approved_manifest.read_bytes()) + if opts.approved_manifest else None) + if bool(opts.reproduction_zip) != bool(opts.reproduction_ids): + raise GateError("merged-M reproduction artifact and IDs must be paired") + reproduction = ({ + "zip_file": opts.reproduction_zip, + "identities": strict_json_object(opts.reproduction_ids.read_bytes())} + if opts.reproduction_zip else None) + if bool(opts.postseal_zip) != bool(opts.postseal_ids): + raise GateError("postseal original artifact and IDs must be paired") + postseal = ({ + "zip_file":opts.postseal_zip, + "identities":strict_json_object(opts.postseal_ids.read_bytes())} + if opts.postseal_zip else None) + additional = ({ + "additional_contract": opts.additional_contract, + "additional_zips": opts.additional_zips, + "workflow_blob_candidates": opts.workflow_blob_candidates} + if any(x is not None for x in ( + opts.additional_contract, opts.additional_zips, + opts.workflow_blob_candidates)) else None) + result = strict(receipts, opts.phase, manifest, + bundle_path=opts.bundle, + reproduction_attachment=reproduction, + postseal_attachment=postseal, + v31_attachment=additional) + except (GateError, EvidenceError, AuthenticationError, + MandatoryV31Error, OSError, KeyError, ValueError) as exc: + print("NOT ACCEPTED:", exc, file=sys.stderr) + return 2 + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_generate.py b/verification/research/final_closure_generate.py new file mode 100644 index 00000000..4ffe6e05 --- /dev/null +++ b/verification/research/final_closure_generate.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +"""Inventory exact theorem source headers and candidate test SHA digests. + +Source names and hashes are not Lean compilation, transitive-axiom checks, +semantic negative evidence, or third-party review. No auto-acceptance. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re +import subprocess + +ROOT = Path(__file__).resolve().parents[2] +SNAP = ROOT / "research/paper_closures/post_merge_112" +DECLARATION = re.compile( + r"(?m)^\s*(?:private\s+|protected\s+)?" + r"(?:(?:noncomputable|unsafe)\s+)?" + r"(?:theorem|lemma|def)\s+([A-Za-z0-9_']+)(?=\W)" +) + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + +def git_head() -> str | None: + try: + return subprocess.check_output( + ("git", "rev-parse", "HEAD"), cwd=ROOT, text=True, + stderr=subprocess.DEVNULL, timeout=15).strip() + except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired): + return None + +def exact_statements(path: Path) -> list[dict]: + """Report definition header hashes, never claim proof execution.""" + content = path.read_text(encoding="utf-8") + matches = list(DECLARATION.finditer(content)) + rows = [] + for i, match in enumerate(matches): + limit = matches[i+1].start() if i+1 < len(matches) else len(content) + end = content.find(":=", match.end(), limit) + if end < 0: + continue + statement = content[match.start():end].strip() + rows.append({ + "declaration": match.group(1), + "statement_sha256": hashlib.sha256(statement.encode("utf-8")).hexdigest(), + "compiler_result": "NOT_VERIFIED", + "transitive_axioms": None, + "semantic_negative_receipts": [], + }) + return rows + +def generate(output: Path) -> dict: + decisions = json.loads((SNAP/"acceptance_decisions.json").read_text()) + crosswalk = json.loads((SNAP/"theorem_crosswalk.json").read_text()) + root_imports = (ROOT/"PermanssonResearch.lean").read_text().splitlines() + source_rows = [] + for item in crosswalk["closure_additions"]: + relative = Path(item["path"]) + if relative.is_absolute() or ".." in relative.parts: + raise ValueError("unsafe acceptance source path") + path = ROOT/relative + imported = ("import " + str(relative.with_suffix("")).replace("/", ".")) in root_imports + exists = path.is_file() + source_rows.append({ + "id": item["id"], "path": item["path"], + "imported_by_research_root": imported, + "source_sha256": sha256(path) if exists else None, + "declarations": exact_statements(path) if exists else [], + "status": "SOURCE_PRESENT_UNREVIEWED" if exists and imported else "MISSING_SOURCE_OR_IMPORT", + "compiled_head_sha": None, "axiom_receipts": [], + "independent_review_receipts": [], + }) + tests = [] + for path in sorted((ROOT/"verification/research").glob("test_final*.py")): + tests.append({ + "path": str(path.relative_to(ROOT)).replace("\\", "/"), + "source_sha256": sha256(path), + "status": "SOURCE_PRESENT_EXECUTION_NOT_VERIFIED", + }) + report = { + "format": "Permansson.FinalClosureCandidateObservation.v2", + "source_head_sha": git_head(), "source_rows": source_rows, + "candidate_test_sources": tests, + "original_case_count": len(decisions["cases"]), + "unresolved_original_case_ids": [ + c["case_id"] for c in decisions["cases"] if c["decision"] + not in ("VERIFIED_SELECTED_SCOPE", "VERIFIED_NEGATIVE") + ], + "source_bound_ci": [], "independent_reviews": [], + "status": "CANDIDATE_UNREVIEWED", + "trust_boundary": "Hashes show literal source text only, not Lean proof or review.", + } + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n") + return report + +def main(argv=None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args(argv) + report = generate(args.output) + print(json.dumps({ + "source_sha": report["source_head_sha"], + "modules": len(report["source_rows"]), + "source_files_found": sum(bool(c["source_sha256"]) for c in report["source_rows"]), + "root_imported": sum(c["imported_by_research_root"] for c in report["source_rows"]), + "theorem_headers": sum(len(c["declarations"]) for c in report["source_rows"]), + "tests_indexed": len(report["candidate_test_sources"]), + "original_cases_pending": len(report["unresolved_original_case_ids"]), + "output": str(args.output), + }, indent=2)) + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_lean_evidence.py b/verification/research/final_closure_lean_evidence.py new file mode 100644 index 00000000..92bcb35e --- /dev/null +++ b/verification/research/final_closure_lean_evidence.py @@ -0,0 +1,214 @@ +"""Actual pinned-Lean theorem evidence exporter; NEVER auto-accepts claims. + +Run from a clean checkout of ONE chosen frozen branch head H. The output is +external to the repository and contains compiler proofs and literal source +hashes, but no independent review IDs or fabricated final main SHA. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import shutil +import subprocess +import tempfile + +from final_closure_case_receipts import statement_header, parse_target_axioms, EvidenceError +from closure_elaborated_type import ( + lean_probe, compiled_type_record, elaborated_type_sha256, TypeEvidenceError, +) + +ROOT = Path(__file__).resolve().parents[2] +MAP = ROOT / "research/paper_closures/post_merge_112/claim_declaration_map.json" +DECL_RE = re.compile(r"^[A-Za-z0-9_'.]+$") +SHA40 = re.compile(r"^[0-9a-f]{40}$") +CLAIM_IDS = ("Q1", "Q2", "Q3", "M1", "M2", "M3", + "N1", "N2", "N3", "D8", "D9") +BAD = ("error:", "sorryax", "unknown identifier", "unknown module", + "maximum number of heartbeats", "timeout", "failed to import") +AXIOM_LIST = re.compile(r"depends on axioms:\s*\[([^\]]*)\]") + + +class CompilerEvidenceError(RuntimeError): + pass + + +def sha256(payload: bytes) -> str: + return hashlib.sha256(payload).hexdigest() + + +def git_head() -> str: + result = subprocess.run(["git", "rev-parse", "HEAD"], cwd=ROOT, + text=True, capture_output=True, timeout=30) + if result.returncode: + raise CompilerEvidenceError("genuine local Git head unavailable") + return result.stdout.strip() + + +def run_probe(path: Path): + if not shutil.which("lake"): + raise CompilerEvidenceError("pinned Lean/Lake unavailable") + try: + return subprocess.run(["lake", "env", "lean", str(path)], cwd=ROOT, + text=True, capture_output=True, timeout=900) + except subprocess.TimeoutExpired as exc: + raise CompilerEvidenceError("compiler heartbeat/process timeout") from exc + + +def parse_axioms(log: str, declaration: str) -> list[str]: + """Validate an isolated one-declaration Lean probe, never a mixed log. + + The detached receipt parser may locate an exact target among unrelated + diagnostics, but the *producing* compiler probe must contain precisely one + axiom statement overall. This also prevents a valid target record hiding + a second, off-target axiom record. + """ + if any(word in log.lower() for word in BAD): + raise CompilerEvidenceError("failed or untrusted compiler output: " + declaration) + if declaration not in log: + raise CompilerEvidenceError("wrong compiled Lean declaration: " + declaration) + records = re.findall( + r"\bdepends on axioms:\s*\[|\bdoes not depend on any axioms\b", + log) + if len(records) != 1: + raise CompilerEvidenceError( + "isolated compiler probe must emit exactly one axiom record: " + + declaration) + try: + return parse_target_axioms(log, declaration) + except EvidenceError as exc: + raise CompilerEvidenceError(str(exc)) from exc + + +def candidate_records(head: str, out: Path) -> dict: + if not SHA40.fullmatch(head) or head != git_head(): + raise CompilerEvidenceError("checkout HEAD differs from frozen source head") + if out.resolve().is_relative_to(ROOT.resolve()): + raise CompilerEvidenceError("evidence output must be detached from Git checkout") + if not MAP.is_file(): + raise CompilerEvidenceError("qualified source map missing") + source_map = json.loads(MAP.read_text()) + claim_rows = source_map.get("claims") + if (source_map.get("count") != len(CLAIM_IDS) or + not isinstance(claim_rows, list) or + len(claim_rows) != len(CLAIM_IDS) or + [c.get("claim_id") for c in claim_rows if isinstance(c, dict)] + != list(CLAIM_IDS)): + raise CompilerEvidenceError("exact eleven claim identities/rows missing") + declaration_owners = set() + for claim in claim_rows: + rows = claim.get("declarations") + if (not isinstance(rows, list) or not rows or + not isinstance(claim.get("original_case_links"), list) or + not claim["original_case_links"] or + not isinstance(claim.get("test_source_candidates"), list) or + not claim["test_source_candidates"]): + raise CompilerEvidenceError("claim lacks declarations/case/test obligations") + for row in rows: + if not isinstance(row, dict): + raise CompilerEvidenceError("invalid declaration owner entry") + pair = (row.get("path"), row.get("declaration")) + if pair in declaration_owners: + raise CompilerEvidenceError("duplicated primary claim declaration owner") + declaration_owners.add(pair) + for test in claim["test_source_candidates"]: + if (not isinstance(test, str) or + not test.startswith("verification/research/test_") or + not test.endswith(".py") or + not (ROOT / test).is_file()): + raise CompilerEvidenceError("claim test candidate is not current source") + log_dir = out / "axiom_logs" + log_dir.mkdir(parents=True, exist_ok=True) + outputs = [] + for claim in source_map["claims"]: + for idx, row in enumerate(claim["declarations"]): + src = ROOT / row["path"] + if not src.is_file() or not src.resolve().is_relative_to(ROOT.resolve()): + raise CompilerEvidenceError("missing actual theorem source") + declaration = row["declaration"] + if not DECL_RE.fullmatch(declaration) or "." not in declaration: + raise CompilerEvidenceError("unqualified theorem") + source = src.read_text(encoding="utf-8") + header = statement_header(source, declaration) + module = row["path"].removesuffix(".lean").replace("/", ".") + code = lean_probe(module, declaration) + # Compile from a temporary source under the configured Lean path. + with tempfile.TemporaryDirectory( + prefix="closure-proof-", dir=ROOT / ".lake") as temp: + probe = Path(temp) / "Proof.lean" + probe.write_text(code, encoding="utf-8") + result = run_probe(probe) + log = (result.stdout + "\n" + result.stderr).encode("utf-8") + if result.returncode != 0: + raise CompilerEvidenceError( + "Lean #check/#print axioms failed: " + declaration + + "\n" + log.decode("utf-8", "replace")[-700:]) + axioms = parse_axioms(log.decode("utf-8"), declaration) + try: + compiled = compiled_type_record(log.decode("utf-8"), declaration) + except TypeEvidenceError as exc: + raise CompilerEvidenceError(str(exc)) from exc + member = f"axiom_logs/{claim['claim_id']}_{idx}.log" + (out / member).write_bytes(log) + outputs.append({ + "id": f"{claim['claim_id']}:{idx}", "claim_ids": [claim["claim_id"]], + "case_ids": claim["original_case_links"], + "head_sha": head, "path": row["path"], + "declaration": declaration, + "source_sha256": sha256(src.read_bytes()), + # Display-only lexical excerpt; NEVER authoritative theorem type. + "statement_sha256": sha256(header.encode("utf-8")), + "elaborated_type_sha256": elaborated_type_sha256(compiled), + "declaration_kind": compiled["declaration_kind"], + "universe_params_repr": compiled["universe_params_repr"], + "transitive_axioms": axioms, + "axiom_log_member": member, + "axiom_log_sha256": sha256(log), + "compiler_check": "research", + "compiler_result": "PASS", + "check_run_id": None, + "CI_binding": "PENDING_REAL_CHECK_RUN_ID_ON_FROZEN_H", + "independent_review": "NOT_RECEIVED", + }) + report = { + "format": "Permansson.LeanCompilerCandidateEvidence.v1", + "head_sha": head, + "claim_count": 11, + "proof_count": len(outputs), + "status": "COMPILER_ONLY_UNREVIEWED", + "compiler_axiom_receipts": outputs, + "semantic_negative_receipts": [], + "reviews": [], + "limitations": [ + "Compiler success does not imply criterion or hypothesis sufficiency", + "Check-run ID, signed negative mutant and reviewer scope not yet bound", + "No empirical GR/PR, unrestricted randomized optimum, or final M inferred", + ], + } + out.mkdir(parents=True, exist_ok=True) + (out / "compiler_candidate.json").write_text( + json.dumps(report, indent=2, sort_keys=True) + "\n") + return report + + +def main(argv=None) -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--head", required=True) + ap.add_argument("--output", type=Path, required=True) + opt = ap.parse_args(argv) + try: + result = candidate_records(opt.head, opt.output) + except (CompilerEvidenceError, OSError, ValueError) as exc: + print("COMPILER EVIDENCE BLOCKED:", exc) + return 2 + print(json.dumps({"head_sha": result["head_sha"], + "proofs": result["proof_count"], + "status": result["status"]})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_legacy_replay.py b/verification/research/final_closure_legacy_replay.py new file mode 100644 index 00000000..2af1ec4a --- /dev/null +++ b/verification/research/final_closure_legacy_replay.py @@ -0,0 +1,284 @@ +"""Replay a selected legacy false Lean probe against exact original source. + +Captured old unittest inputs are not append-only original-source mutations and +MUST NOT be converted by changing their JSON status. This opt-in adapter +reconstructs a *new* append-only Lean negative candidate from one captured +compiler input, verifies the selected original case, and genuinely recompiles +both positive original source and combined false source with pinned Lean. + +All outputs are detached, explicitly UNREVIEWED and CI-unattributed. Even +a successful replay has no independently accepted case/claim or scholarly +release status. Missing theorem relevance remains subject to external review. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re +import shutil +import sys +import tempfile + +from final_closure_mutation_evidence import ( + MutationEvidenceError, committed_file_bytes, git_head, produce, seal_logs, +) +from final_closure_legacy_semantic_evidence import ( + ROOT, original_case_map, selected_methods, test_observations, +) + +MAX_FALSE_SOURCE = 16 * 1024 +MAX_COMBINED_SOURCE = 4 * 1024 * 1024 +SHA40 = re.compile(r"^[a-f0-9]{40}$") +EVENT_ID = re.compile(r"^event-[0-9]+-[0-9]{6}$") +TEST_NAME = re.compile(r"^test_[A-Za-z0-9_]+$") + + +def need(condition, message): + if not condition: + raise MutationEvidenceError(message) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def appended_mutant(original: bytes, captured: bytes, provider_module: str) -> bytes: + """Strip only leading imports from a standalone Lean falsification probe.""" + need(isinstance(original, bytes) and original + and isinstance(captured, bytes) and captured, "empty Lean sources") + need(len(original) <= MAX_COMBINED_SOURCE and + len(captured) <= MAX_COMBINED_SOURCE + MAX_FALSE_SOURCE, + "unbounded Lean replay input") + if captured.startswith(original): + # Some legacy tests actually compile the complete frozen original + # provider with a short source-appended false declaration. When that + # exact immutable prefix is proven byte-for-byte, retain the already + # source-preserving capture instead of converting it to an import-only + # synthetic probe. + suffix = captured[len(original):] + need(0 < len(suffix) <= MAX_FALSE_SOURCE, + "missing or oversized appended mathematical false statement") + try: + remaining = suffix.decode("utf-8") + except UnicodeDecodeError as exc: + raise MutationEvidenceError("non-UTF8 appended Lean source") from exc + need(re.search(r"(?m)^\s*(?:example|theorem|lemma)\b", remaining), + "appended original source lacks an explicit mathematical assertion") + need(not re.search( + r"(?m)^\s*(?:import|axiom|opaque|constant|set_option)\b", + remaining) and + not re.search(r"(?i)\b(?:sorry|sorryAx|admit)\b", remaining), + "appended original source changes imports, axioms or admissions") + return captured + need(len(captured) <= MAX_FALSE_SOURCE, + "unbounded standalone Lean replay input") + try: + text = captured.decode("utf-8") + except UnicodeDecodeError as exc: + raise MutationEvidenceError("non-UTF8 captured Lean input") from exc + lines = text.splitlines(keepends=True) + imports = [] + body = [] + seen_statement = False + for line in lines: + trimmed = line.strip() + if trimmed.startswith("import "): + need(not seen_statement, "legacy false source has a late import") + imports.append(trimmed) + else: + body.append(line) + if trimmed and not trimmed.startswith("--"): + seen_statement = True + need("import " + provider_module in imports, + "legacy false source did not import the selected original provider") + remaining = "".join(body).strip() + need(remaining and re.search(r"(?m)^\s*(?:example|theorem|lemma)\b", + remaining), + "captured Lean probe has no explicit false mathematical statement") + need(not re.search(r"(?m)^\s*(?:import|axiom|opaque|constant|set_option)\b", + remaining) and + not re.search(r"(?i)\b(?:sorry|sorryAx|admit)\b", remaining), + "captured false source introduces imports, admissions or axioms") + suffix = ("\n/- PR113 ORIGINAL LEGACY REPLAY: CANDIDATE ONLY -/\n" + + remaining + "\n").encode("utf-8") + need(len(original) + len(suffix) <= MAX_COMBINED_SOURCE, + "replayed Lean source exceeds source limit") + return original + suffix + + +def choose_original(head: str, row: dict, provider: str): + rel = Path(provider) + need(not rel.is_absolute() and ".." not in rel.parts and + rel.suffix == ".lean" and + rel.as_posix().startswith("PermanssonResearch/"), + "invalid provider path") + required = { + item.get("path") for item in + row.get("historical_proof_declarations", []) + if isinstance(item, dict) + } | { + item.get("path") for item in + row.get("new_claim_source_candidates", []) + if isinstance(item, dict) + } + need(provider in required, + "selected original provider is not in the frozen case theorem map") + source = ROOT / rel + raw = committed_file_bytes(head, source, root=ROOT) + return source, raw, ".".join(rel.with_suffix("").parts) + + +def replay(head: str, *, case_id: str, method: str, provider: str, + test_output: Path, observation_id: str, output: Path): + need(SHA40.fullmatch(head) is not None and git_head() == head, + "source head not frozen") + need(TEST_NAME.fullmatch(method) is not None and + EVENT_ID.fullmatch(observation_id) is not None, + "malformed named case or compiler event identity") + byid, legacy_ids = original_case_map(head) + need(case_id in legacy_ids, "case is not in twenty original legacy candidates") + row = byid[case_id] + if test_output.is_symlink() or output.is_symlink(): + raise MutationEvidenceError("symlinked evidence roots are not allowed") + test_output = test_output.resolve() + output = output.resolve() + root = ROOT.resolve() + need(test_output.is_dir() and not test_output.is_symlink() and + not test_output.is_relative_to(root) and + not output.exists() and + not output.is_relative_to(root) and + not root.is_relative_to(output) and + not test_output.is_relative_to(output) and + not output.is_relative_to(test_output), + "detached legacy input/output roots invalid or overlapping") + test_report = test_output / "unreviewed_test_manifest.json" + need(test_report.is_file() and not test_report.is_symlink(), + "selected legacy test report missing") + manifest = json.loads(test_report.read_bytes()) + need(manifest.get("case_id") == case_id and + manifest.get("format") == "Permansson.LegacyCaseTestExecutionCandidate.v1" + and manifest.get("independent_review") is False + and manifest.get("ci_check_run_id") is None + and manifest.get("release_approved") is False and + manifest.get("test_exit_code") == 0 and + manifest.get("status") == "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED", + "legacy test did not pass as a non-approved candidate") + relative_test = manifest.get("test_file") + names = manifest.get("selected_methods") + need(isinstance(relative_test, str) and + isinstance(names, list) and method in names and + names == sorted(set(names)), "selected test method manifest invalid") + registered = selected_methods(head, row) + need(any(rel == relative_test and method in method_names + for rel, _cls, method_names in registered), + "legacy method not registered against original case") + test_file = ROOT / relative_test + original_test = committed_file_bytes(head, test_file, root=ROOT) + need(manifest.get("source_test_sha256") == sha(original_test), + "legacy selected test source does not match frozen Git") + for name, digest_field in (("test_stdout.log", "stdout_sha256"), + ("test_stderr.log", "stderr_sha256")): + file = test_output / name + need(file.is_file() and not file.is_symlink() and + sha(file.read_bytes()) == manifest.get(digest_field), + "legacy Python execution log is not retained") + raw_root = test_output / "raw_compiler" + need(raw_root.is_dir() and not raw_root.is_symlink(), + "raw original compiler observation root unavailable") + observations = test_observations(raw_root, expected_case=case_id, + expected_methods=",".join(names)) + observed = [obs for obs in observations + if obs["observation_id"] == observation_id and + obs.get("method_observed_unverified") == method] + need(len(observed) == 1 and + observed[0]["possible_semantic_diagnostic"] is True and + observed[0]["method_attribution_ambiguous"] is False, + "selected named method has no retained semantic false diagnostic") + event = raw_root / observation_id + need(event.is_dir() and not event.is_symlink() and + (event / "source.lean").is_file() and + not (event / "source.lean").is_symlink(), + "original false Lean input not retained") + false_raw = (event / "source.lean").read_bytes() + need(sha(false_raw) == observed[0]["source_sha256"], + "captured false source differs from compiler observation") + true_source, original, module = choose_original(head, row, provider) + mutated = appended_mutant(original, false_raw, module) + output.mkdir(parents=True, exist_ok=False) + # Bind the captured candidate inputs both before and after Lean work. + frozen_test_report = test_report.read_bytes() + frozen_observation = (event / "observation.json").read_bytes() + with tempfile.TemporaryDirectory(prefix="pr113-replay-", dir=output) as td: + replay_file = Path(td) / "OriginalAppendedFalse.lean" + replay_file.write_bytes(mutated) + logs = output / "source_logs" + receipt = produce( + head, true_source=true_source, mutant=replay_file, + test_path=test_file, test_name=method, + case_id=case_id, claim_ids=[], out=logs) + if (test_report.read_bytes() != frozen_test_report or + (event / "observation.json").read_bytes() != frozen_observation or + (event / "source.lean").read_bytes() != false_raw): + raise MutationEvidenceError("legacy captured input changed during replay") + zip_path = output / "semantic_replay_candidate.zip" + bundle_sha = seal_logs(logs, zip_path) + raw_capture = output / "original_legacy_capture" + raw_capture.mkdir() + for name in ("observation.json", "source.lean", + "stdout.log", "stderr.log"): + inp = event / name + if inp.is_file() and not inp.is_symlink(): + shutil.copyfile(inp, raw_capture / name) + replay_record = { + "format": "Permansson.LegacyAppendOnlyReplayCandidate.v1", + "status": "REPLAYED_APPEND_ONLY_UNREVIEWED_UNATTRIBUTED", + "head_sha": head, "case_id": case_id, + "selected_test_path": relative_test, + "selected_method": method, + "original_provider_path": provider, + "original_observation_id": observation_id, + "legacy_test_manifest_sha256": sha(test_report.read_bytes()), + "raw_observation_sha256": observed[0]["raw_metadata_sha256"], + "captured_false_source_sha256": sha(false_raw), + "replayed_false_source_sha256": sha(mutated), + "detached_bundle_sha256": bundle_sha, + "mutation_record": receipt, + "ci_check_run_id": None, + "independent_review": False, + "mathematical_acceptance": False, + "scientific_release_approved": False, + } + (output / "unreviewed_replay.json").write_text( + json.dumps(replay_record, sort_keys=True, indent=2) + "\n", + encoding="utf-8") + return replay_record + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument("--head", required=True) + parser.add_argument("--case-id", required=True) + parser.add_argument("--method", required=True) + parser.add_argument("--original-provider", required=True) + parser.add_argument("--test-output", required=True, type=Path) + parser.add_argument("--observation-id", required=True) + parser.add_argument("--out", required=True, type=Path) + args = parser.parse_args(argv) + try: + record = replay(args.head, case_id=args.case_id, + method=args.method, provider=args.original_provider, + test_output=args.test_output, + observation_id=args.observation_id, + output=args.out) + except (MutationEvidenceError, OSError, ValueError, KeyError) as exc: + print("LEGACY APPEND-ONLY REPLAY BLOCKED:", exc, file=sys.stderr) + return 2 + print(json.dumps({k: record[k] for k in ( + "status", "head_sha", "case_id", "detached_bundle_sha256")})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_legacy_semantic_evidence.py b/verification/research/final_closure_legacy_semantic_evidence.py new file mode 100644 index 00000000..005f76c2 --- /dev/null +++ b/verification/research/final_closure_legacy_semantic_evidence.py @@ -0,0 +1,438 @@ +"""Bounded detached execution and retention for the other 20 original cases. + +The canonical map contains 45 source-routed semantic negatives. Twenty routes +are older unittest methods rather than direct source append-mutant records. +This runner executes their exact selected methods, retaining the actual Lean +input bytes and compiler results via an opt-in passive capture hook, including +nested Python audit drivers. Output remains UNREVIEWED and is NOT an accepted +semantic-negative receipt or a GitHub-authenticated execution attestation. +""" +from __future__ import annotations + +import argparse +import ast +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys +import tempfile + +from final_closure_case_receipts import INVALID_DIAGNOSTICS, SEMANTIC_DIAGNOSTICS, ROOT +from final_closure_mutation_evidence import ( + MutationEvidenceError, committed_file_bytes, git_head +) +from final_closure_original_semantic_batch import SPECS as DETACHED_SPECS + +CASE_MAP = ROOT / "research/paper_closures/post_merge_112/acceptance_case_source_map.json" +HOOK = ROOT / "verification/research/pr113_compiler_trace_hook.py" +RUNNER = ROOT / "verification/research/final_closure_legacy_semantic_evidence.py" +CHECKOUT_PATHS = ("PermanssonResearch", "PermanssonLean", + "verification/research", "research/paper_closures/post_merge_112") +MAX_CASES = 45 +MAX_EVENT_COUNT = 120 +MAX_GROUP_BYTES = 64 * 1024 * 1024 +MAX_RUN_LOG = 4 * 1024 * 1024 +SOURCE_PREFIX = "verification/research/test_" +SHA40 = re.compile(r"^[0-9a-f]{40}$") + + +def canonical(data): + return (json.dumps(data, indent=2, sort_keys=True, ensure_ascii=False) + "\n").encode("utf-8") + + +def digest(raw): + return hashlib.sha256(raw).hexdigest() + + +def original_case_map(head): + committed_file_bytes(head, CASE_MAP, root=ROOT) + data = json.loads(CASE_MAP.read_bytes()) + cases = data.get("cases") + if data.get("original_cases") != MAX_CASES or not isinstance(cases, list): + raise MutationEvidenceError("original 45-case source map unavailable") + ids = [row.get("case_id") for row in cases if isinstance(row, dict)] + if len(ids) != MAX_CASES or len(set(ids)) != MAX_CASES: + raise MutationEvidenceError("original 45 IDs malformed") + if not isinstance(DETACHED_SPECS, dict) or len(DETACHED_SPECS) != 25: + raise MutationEvidenceError("detached producer inventory changed") + missing = sorted(set(ids) - set(DETACHED_SPECS)) + if len(missing) != 20 or set(ids) != set(missing) | set(DETACHED_SPECS): + raise MutationEvidenceError("25+20 original-case partition violated") + return {row["case_id"]: row for row in cases}, tuple(missing) + + +def shard_cases(head, index, count): + byid, missing = original_case_map(head) + if not 1 <= count <= len(missing) or not 0 <= index < count: + raise MutationEvidenceError("invalid original case shard") + selected = tuple(cid for i, cid in enumerate(missing) if i % count == index) + if not selected: + raise MutationEvidenceError("empty original case shard") + return byid, selected + + +def selected_methods(head, row): + if row.get("effective_accepted") is not False: + raise MutationEvidenceError("legacy candidate was approved without review") + choices = row.get("test_method_candidates") + if not isinstance(choices, list): + raise MutationEvidenceError("legacy case missing method routes") + seen = set() + groups = {} + for route in choices: + if route.get("role") != "SEMANTIC_NEGATIVE": + continue + rel = route.get("path") + method = route.get("method") + if (not isinstance(rel, str) or not rel.startswith(SOURCE_PREFIX) + or not rel.endswith(".py") or ".." in Path(rel).parts + or not isinstance(method, str) or not re.fullmatch(r"test_[A-Za-z0-9_]+", method)): + raise MutationEvidenceError("invalid selected semantic method") + if (rel, method) in seen: + raise MutationEvidenceError("duplicate semantic method") + seen.add((rel, method)) + source = ROOT / rel + raw = committed_file_bytes(head, source, root=ROOT) + tree = ast.parse(raw.decode("utf-8"), filename=rel) + owners = [ + node.name for node in tree.body if isinstance(node, ast.ClassDef) + and any(isinstance(inner, (ast.FunctionDef, ast.AsyncFunctionDef)) + and inner.name == method for inner in node.body) + ] + if len(owners) != 1: + raise MutationEvidenceError("named original unittest method not uniquely owned") + group = (rel, owners[0]) + groups.setdefault(group, []).append(method) + if not seen: + raise MutationEvidenceError("original case missing semantic negative method") + return tuple((path, cls, tuple(sorted(methods))) + for (path, cls), methods in sorted(groups.items())) + + +def assert_clean_checkout(): + proc = subprocess.run( + ["git", "status", "--porcelain=v1", "--untracked-files=all", + "--", *CHECKOUT_PATHS], cwd=ROOT, text=True, capture_output=True, + timeout=30) + if proc.returncode or proc.stdout.strip(): + raise MutationEvidenceError("Git source or selected test tree is dirty") + + +def test_observations(directory, *, expected_case=None, expected_methods=None): + events = sorted(directory.glob("event-*/observation.json")) + if len(events) > MAX_EVENT_COUNT: + raise MutationEvidenceError("unbounded Lean capture event count") + total = 0 + results = [] + for item in events: + root = item.parent + if item.is_symlink() or root.is_symlink(): + raise MutationEvidenceError("symlink in compiler observation") + raw = item.read_bytes() + meta = json.loads(raw) + if meta.get("format") != "Permansson.LegacyLeanCompilerObservation.v1": + raise MutationEvidenceError("wrong compiler observation schema") + if (expected_case is not None and + meta.get("case_id_unverified") != expected_case): + raise MutationEvidenceError("cross-case compiler observation substituted") + observed_method = meta.get("selected_method_unverified") + ambiguous_method = False + if expected_methods is not None: + permitted = expected_methods.split(",") + if observed_method not in permitted: + # A class-level setup runs *before* TestCase.run can mark the + # individual test. Retain the diagnostic for inspection but + # never count it toward any one semantic-negative method. + if len(permitted) > 1 and observed_method == expected_methods: + ambiguous_method = True + else: + raise MutationEvidenceError( + "cross-method compiler observation substituted") + allowed_members = {"observation.json", "source.lean", "stdout.log", "stderr.log"} + if (any(p.is_symlink() for p in root.iterdir()) or + any(p.name not in allowed_members for p in root.iterdir())): + raise MutationEvidenceError("unsafe or unexpected compiler observation member") + total += len(raw) + for label, field in (("source.lean", "source_sha256"), + ("stdout.log", "stdout_sha256"), + ("stderr.log", "stderr_sha256")): + target = root / label + reported = meta.get(field) + if target.exists(): + if target.is_symlink() or not target.is_file(): + raise MutationEvidenceError("unsafe compiler observation") + blob = target.read_bytes() + total += len(blob) + if not isinstance(reported, str) or digest(blob) != reported: + raise MutationEvidenceError("compiler observation hash mismatch") + elif reported is not None: + # A too-large or missing observation cannot be silently certified. + meta["truncated_or_missing"] = True + output = b"" + for label in ("stdout.log", "stderr.log"): + target = root / label + if target.is_file(): + output += target.read_bytes().lower() + b"\n" + semantic = ( + isinstance(meta.get("returncode"), int) and meta["returncode"] != 0 + and b"error:" in output + and any(token.encode() in output for token in SEMANTIC_DIAGNOSTICS) + and not any(token.encode() in output for token in INVALID_DIAGNOSTICS) + and meta.get("source_retained") is True + and not meta.get("truncated_or_missing") + and not ambiguous_method + ) + results.append({ + "observation_id": root.name, + "source_sha256": meta.get("source_sha256"), + "returncode": meta.get("returncode"), + "method_observed_unverified": observed_method, + "method_attribution_ambiguous": ambiguous_method, + "possible_semantic_diagnostic": semantic, + "source_retained": meta.get("source_retained"), + "raw_metadata_sha256": digest(raw), + }) + if total > MAX_GROUP_BYTES: + raise MutationEvidenceError("total compiler evidence exceeds bound") + return results + + +def legacy_provider_candidates(row, false_source: bytes) -> list[str]: + """Unreviewed source suggestions, never a mathematical attribution. + + A legacy standalone false probe may explicitly import its original source + provider; a source-appended probe instead CONTAINS the exact immutable + provider prefix and need not import itself. Both forms are supported, but + the prefix form is offered only after comparison to the committed Git blob. + No suggestion discharges theorem relevance, a producing job, or review. + """ + if not isinstance(false_source, bytes): + return [] + try: + lines = false_source.decode("utf-8").splitlines() + except UnicodeDecodeError: + return [] + imported = set() + for line in lines: + words = line.strip().split() + if words and words[0] == "import": + imported.update(words[1:]) + paths = { + entry.get("path") for entry in + (row.get("historical_proof_declarations", []) + + row.get("new_claim_source_candidates", [])) + if isinstance(entry, dict) + } + paths = sorted( + path for path in paths + if isinstance(path, str) and path.startswith("PermanssonResearch/") + and path.endswith(".lean") and ".." not in Path(path).parts + and chr(92) not in path + ) + matches = { + path for path in paths + if ".".join(Path(path).with_suffix("").parts) in imported + } + # A merely similar file, an imported dependency, or a dirty worktree + # cannot serve as a committed original-source prefix. + try: + head = git_head() + except (MutationEvidenceError, OSError): + head = None + if head: + for path in paths: + try: + original = committed_file_bytes(head, ROOT / path, root=ROOT) + except (MutationEvidenceError, OSError): + continue + if not false_source.startswith(original): + continue + suffix = false_source[len(original):] + if not 0 < len(suffix) <= 16 * 1024: + continue + if (not re.search(r"(?m)^\s*(?:example|lemma|theorem)\b", + suffix.decode("utf-8", errors="replace")) + or re.search( + rb"(?m)^\s*(?:import|axiom|opaque|constant|set_option)\b", + suffix) + or re.search(rb"(?i)\b(?:sorry|sorryAx|admit)\b", suffix)): + continue + matches.add(path) + return sorted(matches) + + +def covered_semantic_methods(methods, events): + """Require one source-retained semantic negative for EACH selected method. + + A successful unittest class can run several methods; one failed assertion + is not evidence that the other selected methods were actually falsified. + Class setup/unattributed diagnostics were already marked ineligible. + """ + planned = set(methods) + if not planned or len(planned) != len(methods): + raise MutationEvidenceError("empty or duplicate selected semantic methods") + found = {event.get("method_observed_unverified") + for event in events if event.get("possible_semantic_diagnostic") + and not event.get("method_attribution_ambiguous")} + return sorted(planned & found), sorted(planned - found) + + +def run_case(head, case_id, row, output, timeout): + grouped = selected_methods(head, row) + case_dir = output / case_id + case_dir.mkdir(parents=True, exist_ok=False) + records = [] + with tempfile.TemporaryDirectory(prefix="pr113-tracer-") as td: + hook_dir = Path(td) + (hook_dir / "sitecustomize.py").write_text( + "from pr113_compiler_trace_hook import install\ninstall()\n", + encoding="utf-8") + for number, (relative, cls, methods) in enumerate(grouped): + stem = Path(relative).stem + target = case_dir / ("test-%02d" % number) + target.mkdir() + capture = target / "raw_compiler" + capture.mkdir() + env = os.environ.copy() + env["PR113_CAPTURE_DIR"] = str(capture) + env["PR113_CASE_ID"] = case_id + env["PR113_SELECTED_METHOD"] = ",".join(methods) + env["PYTHONPATH"] = os.pathsep.join( + [str(hook_dir), str(ROOT / "verification/research"), + env.get("PYTHONPATH", "")]) + argv = [sys.executable, "-m", "unittest", "-v"] + [ + stem + "." + cls + "." + method for method in methods + ] + try: + done = subprocess.run(argv, cwd=ROOT, env=env, + capture_output=True, timeout=timeout) + rc, stdout, stderr = done.returncode, done.stdout, done.stderr + exception = None + except subprocess.TimeoutExpired as exc: + rc = None + stdout = exc.stdout or b"" + stderr = exc.stderr or b"" + exception = "test timeout; not mathematical evidence" + if len(stdout) > MAX_RUN_LOG or len(stderr) > MAX_RUN_LOG: + raise MutationEvidenceError("original unittest stdout/stderr over limit") + (target / "test_stdout.log").write_bytes(stdout) + (target / "test_stderr.log").write_bytes(stderr) + events = test_observations( + capture, expected_case=case_id, + expected_methods=",".join(methods)) + for event in events: + raw_source = capture / event["observation_id"] / "source.lean" + event["original_provider_candidates_unreviewed"] = ( + legacy_provider_candidates(row, raw_source.read_bytes()) + if event.get("possible_semantic_diagnostic") and + raw_source.is_file() and not raw_source.is_symlink() + else []) + covered, missing = covered_semantic_methods(methods, events) + record = { + "format": "Permansson.LegacyCaseTestExecutionCandidate.v1", + "case_id": case_id, + "test_file": relative, + "test_class": cls, + "selected_methods": list(methods), + "source_test_sha256": digest(committed_file_bytes( + head, ROOT / relative, root=ROOT)), + "command": argv, + "test_exit_code": rc, + "exception": exception, + "stdout_sha256": digest(stdout), + "stderr_sha256": digest(stderr), + "compiler_events": events, + "compiler_observations_count": len(events), + "possible_semantic_observations": sum( + bool(e["possible_semantic_diagnostic"]) for e in events), + "semantic_methods_with_candidate": covered, + "semantic_methods_missing_candidate": missing, + "status": "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED" + if rc == 0 and not missing + else "INCOMPLETE_OR_UNVERIFIABLE_UNREVIEWED", + "independent_review": False, + "ci_check_run_id": None, + "release_approved": False, + } + (target / "unreviewed_test_manifest.json").write_bytes(canonical(record)) + records.append(record) + return records + + +def run_shard(head, index, count, output, timeout): + if not SHA40.fullmatch(head) or git_head() != head: + raise MutationEvidenceError("frozen source head mismatch") + for source in (HOOK, RUNNER, ROOT / "lean-toolchain", + ROOT / "lake-manifest.json"): + committed_file_bytes(head, source, root=ROOT) + assert_clean_checkout() + byid, selected = shard_cases(head, index, count) + output = output.resolve() + root = ROOT.resolve() + if (output.exists() or output.is_relative_to(root) + or root.is_relative_to(output)): + raise MutationEvidenceError("candidate output cannot overlap checkout") + tree = subprocess.check_output(["git", "rev-parse", "HEAD^{tree}"], + cwd=ROOT, text=True).strip() + if not SHA40.fullmatch(tree): + raise MutationEvidenceError("invalid source tree") + output.mkdir(parents=True, exist_ok=False) + rows = [] + for case_id in selected: + rows.append({"case_id": case_id, + "tests": run_case(head, case_id, byid[case_id], output, timeout)}) + assert_clean_checkout() + failed = [row["case_id"] for row in rows if any( + entry["status"] != "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED" + for entry in row["tests"])] + manifest = { + "format": "Permansson.OriginalLegacyCompilerShard.v1", + "status": "INCOMPLETE_UNREVIEWED" if failed + else "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED", + "source_head": head, + "source_tree": tree, + "case_ids": list(selected), + "case_reports": rows, + "remaining_source_append_producer_cases": len(DETACHED_SPECS), + "independent_review": False, + "ci_check_run_id": None, + "merged_main_sha": None, + "scientific_release_approved": False, + "github_run_environment_unverified": { + k: os.environ.get(k) for k in ( + "GITHUB_REPOSITORY", "GITHUB_SHA", "GITHUB_RUN_ID", + "GITHUB_RUN_ATTEMPT", "GITHUB_JOB", "GITHUB_WORKFLOW") + }, + } + (output / "unreviewed_shard_manifest.json").write_bytes(canonical(manifest)) + return manifest + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument("--head", required=True) + parser.add_argument("--shard-index", required=True, type=int) + parser.add_argument("--shard-count", required=True, type=int) + parser.add_argument("--out", required=True, type=Path) + parser.add_argument("--timeout", type=int, default=2100) + args = parser.parse_args(argv) + try: + if not 60 <= args.timeout <= 3600: + raise MutationEvidenceError("unbounded original test timeout") + output = run_shard(args.head, args.shard_index, args.shard_count, + args.out, args.timeout) + print(json.dumps({"status": output["status"], + "head": output["source_head"], + "cases": output["case_ids"]}, sort_keys=True)) + return 0 if output["status"] == "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED" else 2 + except (MutationEvidenceError, OSError, KeyError, ValueError, + subprocess.CalledProcessError) as exc: + print("ORIGINAL LEGACY EVIDENCE BLOCKED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_merge_compiler_shards.py b/verification/research/final_closure_merge_compiler_shards.py new file mode 100644 index 00000000..e7e289e8 --- /dev/null +++ b/verification/research/final_closure_merge_compiler_shards.py @@ -0,0 +1,218 @@ +"""Fail-closed recombination of original 45-case / 11-claim compiler shards. + +Accepted inputs: DETACHED per-shard raw logs and manifests which match +the exact reviewed source-owner inventory. The output is only an unreviewed +compiler candidate; its evidence still requires an authenticated GitHub +artifact, immutable exact-head CI check-run mapping, semantic false mutants, +and independent review. No check-run ID is asserted or synthesized. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re +import shutil +import sys + +from closure_elaborated_type import ( + compiled_type_record, elaborated_type_sha256, TypeEvidenceError +) +from final_closure_case_receipts import ( + TRUSTED_AXIOMS, parse_target_axioms, EvidenceError, +) +from final_closure_complete_lean_evidence import ( + ROOT, required_inventory, inventory_digest, shard_indices, git_head +) + +SHA40 = re.compile(r"^[0-9a-f]{40}$") +SHA256 = re.compile(r"^[0-9a-f]{64}$") +MAX_LOG_BYTES = 4 * 1024 * 1024 +MAX_TOTAL_BYTES = 64 * 1024 * 1024 + + +class ShardMergeError(ValueError): + pass + + +def need(ok, reason): + if not ok: + raise ShardMergeError(reason) + + +def sha(content): + return hashlib.sha256(content).hexdigest() + + +def merge(shard_root: Path, output: Path, *, head: str, + inventory: list[dict] | None = None) -> dict: + need(isinstance(head, str) and SHA40.fullmatch(head), + "invalid exact source head") + need(git_head() == head, "source checkout does not match merged compiler H") + shard_root = Path(shard_root).resolve() + output = Path(output).resolve() + need(shard_root.is_dir() and not shard_root.is_symlink() and + not shard_root.is_relative_to(ROOT.resolve()) and + not output.is_relative_to(ROOT.resolve()) and + not output.is_relative_to(shard_root) and + not shard_root.is_relative_to(output) and + not output.exists(), + "shard and merged output must be detached, disjoint and new") + planned = required_inventory() if inventory is None else inventory + need(isinstance(planned, list) and len(planned) >= 11, + "canonical compiler source-owner inventory unavailable") + fingerprint = inventory_digest(planned) + # The shard tree must contain only numbered shard folders; never + # silently skip a missing/cancelled shard or accept an extra directory. + child_names = sorted(p.name for p in shard_root.iterdir()) + need(child_names and all(re.fullmatch(r"shard-[0-9]{2}", n) + for n in child_names), + "unexpected file or directory in compiler shard root") + count = len(child_names) + need(1 <= count <= 16 and + child_names == [f"shard-{i:02}" for i in range(count)], + "missing, duplicated or out-of-order compiler shard") + imported = {} + raw_logs = {} + total_bytes = 0 + for i in range(count): + folder = shard_root / f"shard-{i:02}" + need(folder.is_dir() and not folder.is_symlink(), + "compiler shard folder missing/unsafe") + path = folder / "complete_compiler_candidate.json" + need(path.is_file() and not path.is_symlink(), + "compiler shard candidate manifest missing") + data = json.loads(path.read_bytes()) + need(isinstance(data, dict) and + data.get("format") == "Permansson.AllCaseCompilerCandidate.v1" and + data.get("status") == "COMPILER_ONLY_UNREVIEWED" and + data.get("head_sha") == head and + data.get("case_count") == 45 and + data.get("claim_count") == 11 and + data.get("inventory_count") == len(planned) and + data.get("inventory_sha256") == fingerprint and + data.get("shard_index") == i and + data.get("shard_count") == count and + data.get("independent_review") is False and + data.get("authentic_ci_job_bound") is False and + data.get("merged_main_sha") is None, + "compiler shards disagree on inventory or provenance") + expected_indices = shard_indices(len(planned), i, count) + rows = data.get("compiler_axiom_receipts") + need(isinstance(rows, list) and + len(rows) == data.get("proof_count") == len(expected_indices), + "compiler shard omitted required source obligations") + index_to_row = {} + for row in rows: + need(isinstance(row, dict), "malformed compiler shard receipt") + j = row.get("inventory_index") + need(type(j) is int and j in expected_indices and + j not in index_to_row and j not in imported, + "extra/duplicate/wrong-shard compiler proof") + required = planned[j] + need(row.get("id") == f"all:{j:04}" and + row.get("path") == required["path"] and + row.get("declaration") == required["declaration"] and + row.get("case_ids") == required["case_ids"] and + row.get("claim_ids") == required["claim_ids"] and + row.get("head_sha") == head and + row.get("compiler_result") == "PASS" and + row.get("compiler_check") == "research" and + row.get("check_run_id") is None and + row.get("declaration_kind") in + {"theorem", "definition", "opaque", "axiom", "inductive", + "constructor", "recursor", "quotient"}, + "compiler receipt does not match pinned original theorem") + src = (ROOT / row["path"]).resolve() + need(src.is_relative_to(ROOT.resolve()) and src.is_file() and + sha(src.read_bytes()) == row.get("source_sha256"), + "source bytes differ from compiler proof input") + name = f"axiom_logs/all_{j:04}.log" + need(row.get("axiom_log_member") == name and + SHA256.fullmatch(str(row.get("axiom_log_sha256"))) is not None, + "invented proof log member/digest") + log_path = folder / name + need(log_path.is_file() and not log_path.is_symlink() and + log_path.resolve().is_relative_to(folder.resolve()), + "required true proof log absent") + blob = log_path.read_bytes() + total_bytes += len(blob) + need(0 < len(blob) <= MAX_LOG_BYTES and + total_bytes <= MAX_TOTAL_BYTES and + sha(blob) == row["axiom_log_sha256"], + "log bytes exceed bound or mismatch original attestation") + log = blob.decode("utf-8", errors="replace") + need("error:" not in log.lower() and "sorryAx" not in log, + "invalid compiler-success diagnostic") + try: + actual = compiled_type_record(log, row["declaration"]) + expected_axioms = parse_target_axioms(log, row["declaration"]) + except (TypeEvidenceError, EvidenceError) as exc: + raise ShardMergeError( + "unverifiable compiler theorem type or axioms") from exc + need(row.get("elaborated_type_sha256") == + elaborated_type_sha256(actual) and + row.get("declaration_kind") == + actual["declaration_kind"] and + row.get("universe_params_repr") == + actual["universe_params_repr"] and + row.get("transitive_axioms") == expected_axioms and + set(expected_axioms) <= TRUSTED_AXIOMS, + "source theorem elaboration/axioms do not match log") + index_to_row[j] = row + imported[j] = row + raw_logs[name] = blob + need(set(index_to_row) == set(expected_indices), + "shard has incomplete original theorem coverage") + need(set(p.relative_to(folder).as_posix() for p in folder.rglob("*") + if p.is_file()) == + {"complete_compiler_candidate.json"} | + {f"axiom_logs/all_{j:04}.log" for j in expected_indices}, + "unexpected/hidden files in source compiler shard") + need(set(imported) == set(range(len(planned))), + "union of compiler shards does not cover all exact original owners") + output.mkdir(parents=True, exist_ok=False) + (output / "axiom_logs").mkdir() + for name, raw in sorted(raw_logs.items()): + (output / name).write_bytes(raw) + merged = { + "format": "Permansson.AllCaseCompilerCandidate.v1", + "status": "COMPILER_ONLY_UNREVIEWED", + "head_sha": head, + "proof_count": len(imported), + "case_count": 45, "claim_count": 11, + "inventory_sha256": fingerprint, "inventory_count": len(planned), + "compiler_axiom_receipts": [imported[i] for i in sorted(imported)], + "merged_shards": count, + "independent_review": False, + "authentic_ci_job_bound": False, + "check_run_id": None, + "merged_main_sha": None, + } + (output / "complete_compiler_candidate.json").write_text( + json.dumps(merged, sort_keys=True, indent=2)+"\n", encoding="utf-8") + return merged + + +def main(argv=None): + p = argparse.ArgumentParser() + p.add_argument("--head", required=True) + p.add_argument("--shards", required=True, type=Path) + p.add_argument("--output", required=True, type=Path) + args = p.parse_args(argv) + try: + summary = merge(args.shards, args.output, head=args.head) + except (ShardMergeError, OSError, ValueError, KeyError) as exc: + print("COMPLETE COMPILER SHARD MERGE BLOCKED:", exc, file=sys.stderr) + return 2 + print(json.dumps({"status": summary["status"], + "source_head": summary["head_sha"], + "proof_count": summary["proof_count"], + "case_count": summary["case_count"], + "claim_count": summary["claim_count"]})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_mutation_evidence.py b/verification/research/final_closure_mutation_evidence.py new file mode 100644 index 00000000..291c2434 --- /dev/null +++ b/verification/research/final_closure_mutation_evidence.py @@ -0,0 +1,243 @@ +"""Source-preserving real Lean false-mutant evidence, and deterministic ZIP. + +Emits candidate diagnostic bytes with explicit head/source/test bindings. +No result here is a verified original case until independently reviewed and +bound to genuine GitHub check-run IDs on the frozen source head. +""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path +import re +import shutil +import subprocess +import sys +import tempfile +import zipfile + +from final_closure_case_receipts import ( + INVALID_DIAGNOSTICS, SEMANTIC_DIAGNOSTICS, ROOT as SOURCE_ROOT, +) + +ROOT = Path(__file__).resolve().parents[2] +SHA40 = re.compile(r"^[0-9a-f]{40}$") +BAD_LOG = INVALID_DIAGNOSTICS + ("sorryax", "failed to synthesize") +MAX_LOG_BYTES = 4 * 1024 * 1024 + + +class MutationEvidenceError(RuntimeError): + pass + + +def hash_bytes(raw: bytes) -> str: + return hashlib.sha256(raw).hexdigest() + + +def git_head() -> str: + p = subprocess.run(["git", "rev-parse", "HEAD"], cwd=ROOT, + capture_output=True, text=True, timeout=20) + if p.returncode: + raise MutationEvidenceError("missing frozen Git checkout") + return p.stdout.strip() + + +def committed_file_bytes(head: str, file: Path, *, root: Path = ROOT) -> bytes: + """Require the actual committed Git blob and reject local substitution. + + Matching rev-parse HEAD alone does not establish the source file bytes. + Clean compiler products and independent execution remain separate gates. + """ + root = root.resolve() + if not SHA40.fullmatch(head): + raise MutationEvidenceError("invalid frozen source commit identity") + if file.is_symlink() or not file.is_file(): + raise MutationEvidenceError("source symlink or missing file") + target = file.resolve() + if not target.is_relative_to(root): + raise MutationEvidenceError("source escapes frozen checkout") + relative = target.relative_to(root).as_posix() + if not relative or ":" in relative: + raise MutationEvidenceError("unsupported tracked source path") + try: + record = subprocess.run( + ["git", "cat-file", "blob", f"{head}:{relative}"], + cwd=root, capture_output=True, timeout=30) + except (OSError, subprocess.TimeoutExpired) as exc: + raise MutationEvidenceError("committed source unavailable") from exc + if record.returncode: + raise MutationEvidenceError("source not tracked at frozen Git head") + actual = target.read_bytes() + if actual != record.stdout: + raise MutationEvidenceError("source bytes differ from frozen Git blob") + return actual + +def pinned_lean_version(head: str) -> str: + """Authenticate the actually invoked pinned Lean binary, not a label.""" + toolchain = committed_file_bytes(head, ROOT / "lean-toolchain", root=ROOT) + if toolchain != b"leanprover/lean4:v4.34.0\n": + raise MutationEvidenceError("frozen Lean toolchain pin changed") + try: + result = subprocess.run( + ["lake", "env", "lean", "--version"], cwd=ROOT, + capture_output=True, text=True, timeout=30) + except (OSError, subprocess.TimeoutExpired) as exc: + raise MutationEvidenceError("pinned Lean version check unavailable") from exc + report = (result.stdout or "") + "\n" + (result.stderr or "") + actual = re.search(r"\bversion\s+([0-9]+\.[0-9]+\.[0-9]+)\b", report) + if result.returncode != 0 or not actual or actual.group(1) != "4.34.0": + raise MutationEvidenceError("running Lean binary disagrees with frozen 4.34.0") + return actual.group(1) + + +def lean_result(source: Path): + try: + return subprocess.run(["lake", "env", "lean", str(source)], + cwd=ROOT, capture_output=True, + text=True, timeout=900) + except (OSError, subprocess.TimeoutExpired) as exc: + raise MutationEvidenceError("Lean/Lake unavailable or timed out") from exc + + +def verify_negative(positive, negative, original_source: bytes, + mutant_source: bytes): + positive_log = (positive.stdout + "\n" + positive.stderr).lower() + log = (negative.stdout + "\n" + negative.stderr).lower() + if (positive.returncode != 0 or + any(x in positive_log for x in BAD_LOG)): + raise MutationEvidenceError("positive control did not compile cleanly") + if negative.returncode == 0: + raise MutationEvidenceError("false mutant accidentally compiled") + if (not any(x in log for x in SEMANTIC_DIAGNOSTICS) or + "error:" not in log or + any(x in log for x in BAD_LOG)): + raise MutationEvidenceError("invalid negative: parser/import/resource failure") + if original_source == mutant_source: + raise MutationEvidenceError("mutant did not change any source bytes") + def imports(raw): + return [s.strip() for s in raw.decode("utf-8").splitlines() + if s.strip().startswith("import ")] + if imports(original_source) != imports(mutant_source): + raise MutationEvidenceError("mutation changed imports, not only semantics") + if len(positive_log.encode()) > MAX_LOG_BYTES or len(log.encode()) > MAX_LOG_BYTES: + raise MutationEvidenceError("compiler diagnostic too large") + return True + + +def produce(head: str, *, true_source: Path, mutant: Path, test_path: Path, + test_name: str, case_id: str, claim_ids: list[str], + out: Path) -> dict: + if head != git_head() or not SHA40.fullmatch(head): + raise MutationEvidenceError("stale or synthetic Git HEAD") + if out.resolve().is_relative_to(ROOT.resolve()): + raise MutationEvidenceError("detached evidence must be out of the source tree") + if true_source.is_symlink() or test_path.is_symlink(): + raise MutationEvidenceError("symlink source/test cannot certify") + source = true_source.resolve() + test = test_path.resolve() + if (not source.is_relative_to(ROOT.resolve()) or + not test.is_relative_to(ROOT.resolve()) or + not source.is_file() or source.suffix != ".lean" or + not test.is_file() or test.suffix != ".py"): + raise MutationEvidenceError("source/test must be genuine repo files") + test_bytes = committed_file_bytes(head, test, root=ROOT) + if not re.search(r"(?m)^\s*def\s+" + re.escape(test_name) + r"\(", + test_bytes.decode("utf-8")): + raise MutationEvidenceError("test name not defined by source") + if not case_id or not isinstance(claim_ids, list): + raise MutationEvidenceError("case/review ownership missing") + original_bytes = committed_file_bytes(head, source, root=ROOT) + mutant_bytes = mutant.read_bytes() + if not shutil.which("lake"): + raise MutationEvidenceError("pinned Lean required, cannot skip") + actual_toolchain = pinned_lean_version(head) + positive = lean_result(source) + negative = lean_result(mutant) + if committed_file_bytes(head, source, root=ROOT) != original_bytes or \ + committed_file_bytes(head, test, root=ROOT) != test_bytes: + raise MutationEvidenceError("committed source/test mutated during compiler run") + if mutant.read_bytes() != mutant_bytes: + raise MutationEvidenceError("false mutant changed during compiler run") + verify_negative(positive, negative, original_bytes, mutant_bytes) + out.mkdir(parents=True, exist_ok=True) + prefix = re.sub(r"[^A-Za-z0-9_-]", "_", case_id + "_" + test_name) + positive_name = prefix + ".positive.log" + negative_name = prefix + ".negative.log" + positive_log = (positive.stdout + "\n" + positive.stderr).encode("utf-8") + negative_log = (negative.stdout + "\n" + negative.stderr).encode("utf-8") + (out / positive_name).write_bytes(positive_log) + (out / negative_name).write_bytes(negative_log) + mutant_name = prefix + ".mutant.lean" + (out / mutant_name).write_bytes(mutant_bytes) + # Preserve the ORIGINAL provider bytes as well as the false assertion. + # A detached reviewer must not need an accessible mutable checkout. + original_name = prefix + ".original.lean" + (out / original_name).write_bytes(original_bytes) + return { + "id": prefix, "head_sha": head, + "test_path": str(test.relative_to(ROOT)).replace("\\", "/"), + "test_name": test_name, + "source_sha256": hash_bytes(test_bytes), + "case_ids": [case_id], "claim_ids": claim_ids, + "mutant_source_sha256": hash_bytes(mutant_bytes), + "mutant_source_member": mutant_name, + "original_lean_source_sha256": hash_bytes(original_bytes), + "original_lean_source_member": original_name, + "original_lean_source_path": str(source.relative_to(ROOT)).replace("\\", "/"), + "positive_control": { + "compiled": True, "exit_code": 0, "lean_toolchain": actual_toolchain, + "log_member": positive_name, "log_sha256": hash_bytes(positive_log), + }, + "outcome": "REJECTED_SEMANTIC", "compiler_exit_code": negative.returncode, + "log_member": negative_name, "log_sha256": hash_bytes(negative_log), + "check_run_id": None, + "CI_binding": "AWAITING_REAL_HEAD_CHECK_RUN_AND_REVIEW", + "review_status": "NOT_RECEIVED", + } + + +def seal_logs(source: Path, archive: Path) -> str: + """Stable member ordering/timestamps, excluding mutable archive itself.""" + files = [p for p in source.rglob("*") if p.is_file()] + if not files: + raise MutationEvidenceError("no diagnostic logs to archive") + if archive.resolve().is_relative_to(source.resolve()): + raise MutationEvidenceError("archive must be detached from member directory") + archive.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(archive, "w") as out: + for item in sorted(files, key=lambda p: p.relative_to(source).as_posix()): + rel = item.relative_to(source).as_posix() + if item.is_symlink() or ".." in Path(rel).parts: + raise MutationEvidenceError("unsafe evidence member") + info = zipfile.ZipInfo(rel, (1980, 1, 1, 0, 0, 0)) + info.external_attr = 0o100644 << 16 + info.compress_type = zipfile.ZIP_DEFLATED + out.writestr(info, item.read_bytes(), compress_type=zipfile.ZIP_DEFLATED) + return hash_bytes(archive.read_bytes()) + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--head", required=True) + ap.add_argument("--true-source", type=Path, required=True) + ap.add_argument("--mutant", type=Path, required=True) + ap.add_argument("--test-path", type=Path, required=True) + ap.add_argument("--test-name", required=True) + ap.add_argument("--case-id", required=True) + ap.add_argument("--claim-id", action="append", default=[]) + ap.add_argument("--output", type=Path, required=True) + opts = ap.parse_args(argv) + try: + record = produce(opts.head, true_source=opts.true_source, + mutant=opts.mutant, test_path=opts.test_path, + test_name=opts.test_name, case_id=opts.case_id, + claim_ids=opts.claim_id, out=opts.output) + except (MutationEvidenceError, OSError, ValueError) as exc: + print("SEMANTIC MUTATION EVIDENCE BLOCKED:", exc, file=sys.stderr) + return 2 + print(json.dumps(record, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_obligations_report.py b/verification/research/final_closure_obligations_report.py new file mode 100644 index 00000000..87de9dc6 --- /dev/null +++ b/verification/research/final_closure_obligations_report.py @@ -0,0 +1,155 @@ +"""Readable 45-case/11-claim acceptance backlog from frozen candidate maps. + +The report is not an acceptance decision. Never rewrite original criteria, +promote reviewer statuses, or infer proof completeness from imported modules. +""" +import argparse +import json +from pathlib import Path +import sys + +ROOT = Path(__file__).resolve().parents[2] +SNAP = ROOT/"research/paper_closures/post_merge_112" +OUTPUT = SNAP/"acceptance_obligations_report.md" + + +def safe(value): + return str(value).replace("|", "\\|").replace("\n", " ") + + +def render(): + cases = json.loads((SNAP/"acceptance_case_source_map.json").read_text()) + claims = json.loads((SNAP/"claim_declaration_map.json").read_text()) + if cases["original_cases"] != 45 or claims["count"] != 11: + raise ValueError("candidate case or claim map incomplete") + source_cases = cases["cases"] + pending = [c for c in source_cases + if c["current_candidate_decision"] == "PENDING"] + partial = [c for c in source_cases + if c["current_candidate_decision"] == "PARTIAL_SCOPE"] + if len(pending) != 15 or len(partial) != 30: + raise ValueError("unexpected candidate case statuses") + lines = [ + "# Permansson PR #113 — original acceptance obligation work queue", + "", + "**CANDIDATE / NOT ACCEPTED / NO INDEPENDENT REVIEW.**", + "", + "This report is derived from unchanged original criteria and source mappings." + " It is not an exact-head CI receipt or independent scholarly approval.", + "", + f"- Original cases: {len(source_cases)}; pending: {len(pending)};" + f" partial scope: {len(partial)}; independently accepted: 0.", + f"- New claim owners: {len(claims['claims'])}; accepted: 0.", + "- Candidate test paths below are NOT verified as sufficient" + " for their original criteria.", + "", + "## Original acceptance requirements (all 45)", + "", + "| Case | Candidate state | Original required result |" + " Historical theorem pins | Candidate tests |", + "|---|---|---|---:|---|", + ] + for row in source_cases: + lines.append( + "| " + safe(row["case_id"]) + + " | " + safe(row["current_candidate_decision"]) + + " | " + safe(row["required_expected_result"]) + + " | " + str(len(row["historical_proof_declarations"])) + + " | " + safe(", ".join(row["test_source_candidates"]) or + "TEST SELECTION STILL UNASSIGNED") + " |") + lines.extend([ + "", + "## Fifteen case-specific historical objections", + "", + "| Case | Still-to-be-reconciled obligation(s) |", + "|---|---|", + ]) + for row in pending: + residuals = [ + x for x in row["remaining_obligations"] + if not x.startswith("Exact-head compiler and transitive-axiom receipts") + and not x.startswith("Strengthened original acceptance fixture")] + lines.append("| " + row["case_id"] + " | " + + safe(" / ".join(residuals) or "NO REVIEWED DISPOSITION") + " |") + lines.extend([ + "", + "## Eleven candidate claim owners (not yet accepted)", + "", + "| Claim | Owner source | Fully qualified declaration count | Status |", + "|---|---|---:|---|", + ]) + for row in claims["claims"]: + lines.append("| " + row["claim_id"] + " | " + row["primary_path"] + + " | " + str(len(row["declarations"])) + + " | SOURCE_PRESENT_REVIEW_PENDING |") + # These are merely route-role selections. The negative role is + # SEMANTIC_NEGATIVE only: a SCOPE_BOUNDARY observation must never replace + # an actually compiled, semantically false proposition. + missing_pos = [c for c in source_cases if not any( + x.get("role") in {"COMPILER_SOURCE", "EXACT_CHECK"} + for x in c.get("test_method_candidates", []))] + missing_neg = [c for c in source_cases if not any( + x.get("role") == "SEMANTIC_NEGATIVE" + for x in c.get("test_method_candidates", []))] + lines.extend([ + "", + "## Executable-route gaps (unreviewed; not acceptance)", + "", + f"- Missing selected positive controls: {len(missing_pos)} of 45.", + f"- Missing selected semantic false-mutant routes: {len(missing_neg)} of 45.", + "- Passing scope-boundary checks do **not** discharge the mandatory" + " compiler-negative evidence obligation.", + "- A selected method still requires source relevance, actual exact-head" + " execution, intact logs, authenticated check-run attribution and" + " independent approval.", + "", + "| Case | Positive method routes | Semantic-negative routes |" + " Scope-boundary observations |", + "|---|---:|---:|---:|", + ]) + for c in source_cases: + roles = [x.get("role") for x in c.get("test_method_candidates", [])] + p = sum(x in {"COMPILER_SOURCE", "EXACT_CHECK"} for x in roles) + n = sum(x == "SEMANTIC_NEGATIVE" for x in roles) + b = sum(x == "SCOPE_BOUNDARY" for x in roles) + lines.append(f"| {c['case_id']} | {p} | {n} | {b} |") + + lines.extend([ + "", + "## Closure protocol", + "", + "Each original case requires a correct Lean source/premise binding," + " a positive control and semantically false mutant, genuine check-run" + " IDs and a separately authenticated non-author exact-H case-specific" + " review. Source-only PENDING/PARTIAL decisions cannot self-promote.", + "", + "After source H freezes, signed reviewer and effective acceptance overlays" + " remain DETACHED. Any approved mathematical paper additionally requires" + " genuine merged M, page-by-page PDF inspection and independent" + " postmerge scholarly signoff. A compiler-green source does not certify" + " empirical GR/PR or unrestricted randomized policy optimality.", + "", + ]) + return "\n".join(lines) + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, default=OUTPUT) + parser.add_argument("--check", action="store_true") + a = parser.parse_args(argv) + result = render() + if a.check: + if not a.output.is_file() or a.output.read_text() != result: + print("candidate obligations report stale", file=sys.stderr) + return 2 + else: + a.output.parent.mkdir(parents=True, exist_ok=True) + a.output.write_text(result, encoding="utf-8") + print(json.dumps({"status": "UNREVIEWED_CASE_WORK_QUEUE", + "original_cases": 45, "new_claims": 11})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_original_semantic_batch.py b/verification/research/final_closure_original_semantic_batch.py new file mode 100644 index 00000000..7390cef2 --- /dev/null +++ b/verification/research/final_closure_original_semantic_batch.py @@ -0,0 +1,504 @@ +"""Execute nine frozen original-case semantic mutants as DETACHED candidates. + +This file bridges disposable unittest mutants to the complete byte-preserving +mutation verifier. Every mutant is the genuine on-repository Lean provider +plus exactly one explicit mathematically false assertion. Original imports +and declarations are not replaced. The positive control compiles the +unmodified provider; the false source must be rejected semantically. + +No artifact emitted here is independently reviewed or CI-attributed. +Only a real external artifact collected from a known GitHub producing job +can be considered for the separate authenticated receipt contract. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re +import sys +import tempfile +import textwrap + +from final_closure_mutation_evidence import ( + MutationEvidenceError, produce, seal_logs, git_head +) +from final_closure_case_receipts import ROOT + +MAP = ROOT / "research/paper_closures/post_merge_112/acceptance_case_source_map.json" + +# Each source statement is about the ACTUAL typed original provider, not a +# free-standing surrogate number or an unrelated source. +SPECS = { + "F-03": { + "source": "PermanssonResearch/MultiAgent/F03ActualInfeasibleAnchor.lean", + "test_path": "verification/research/test_f03_actual_infeasible_strict_anchor_lean.py", + "method": "test_false_feasible_anchor_with_conflicting_own_fibre_rejected", + "body": """ +open PermanssonResearch.MultiAgent +open PermanssonResearch.MultiAgent.F03ActualInfeasibleAnchor +example : ∃ a : PlayerTwoMechanism Unit Bool Unit Unit Unit Bool, + IsStrictlyNonReactive a ∧ + ∀ y : Y, a.action y (legal y) = 1 := by + exact no_feasible_original_strict_anchor +""", + }, + "D-02": { + "source": "PermanssonResearch/ReverseSolver/CompactResidenceMonitor.lean", + "test_path": "verification/research/test_final_original_chronological_false_lean.py", + "method": "test_D_02_false_retryable_residence_word", + "body": """ +open PermanssonResearch.ReverseSolver.FiniteMonitor +example : + (residenceScan (fun z : Bool => z = true) 1 + [true, false, true, true]).2 = false := by + have hcorrect : + (residenceScan (fun z : Bool => z = true) 1 + [true, false, true, true]).2 = true := by decide + exact hcorrect +""", + }, + "D-03": { + "source": "PermanssonResearch/ReverseSolver/AvoidResidenceMonitor.lean", + "test_path": "verification/research/test_final_original_chronological_false_lean.py", + "method": "test_D_03_false_danger_after_success_revokes_it", + "body": """ +open PermanssonResearch.ReverseSolver.FiniteMonitor +example : + (avoidRun (fun b : Bool => b = true) + (fun b : Bool => b = false) 0 ((0,false),false) + [true, false]).1.2 = false := by + exact danger_after_success_does_not_undo +""", + }, + "D-04": { + "source": "PermanssonResearch/ReverseSolver/SolverBoundaryWitnesses.lean", + "test_path": "verification/research/test_final_original_risk_belief_false_lean.py", + "method": "test_D_04_false_hard_risk_budget_accepts_p_one", + "body": """ +open PermanssonResearch.ReverseSolver.SolverBoundaryWitnesses +example : ((1 : ℚ) / 10 ≤ 1/20) := by + exact hard_risk_half_mixture_attains.2.2 +""", + }, + "D-05": { + "source": "PermanssonResearch/ReverseSolver/CommittedVsRectangularPathFixture.lean", + "test_path": "verification/research/test_final_original_chronological_false_lean.py", + "method": "test_D_05_false_committed_rectangular_path_law_equality", + "body": """ +open PermanssonResearch.ReverseSolver.CommittedVsRectangular +example : + (pathLaw false true).expectation survivesBoth = + min ((pathLaw false false).expectation survivesBoth) + ((pathLaw true true).expectation survivesBoth) := by + rw [rectangular_actual_success, + committed_false_actual_success, committed_true_actual_success] + have hneq : (1/100 : ℝ) ≠ 9/100 := by norm_num + exact hneq +""", + }, + "D-06": { + "source": "PermanssonResearch/ReverseSolver/ReachableCompletionBeliefFixture.lean", + "test_path": "verification/research/test_final_original_risk_belief_false_lean.py", + "method": "test_D_06_false_same_readout_identifies_completion", + "body": """ +open PermanssonResearch.ReverseSolver.ReachableCompletionBelief +example : (model.filter 0 initial [(false, ())]).weight true = + (model.filter 0 initial [(true, ())]).weight true := by + have hneq : + (model.filter 0 initial [(false, ())]).weight true ≠ + (model.filter 0 initial [(true, ())]).weight true := by + rw [reachable_posteriors_disagree.1, + reachable_posteriors_disagree.2] + norm_num + exact hneq +""", + }, + "Q-03": { + "source": "PermanssonResearch/Probability/DependentThreeStageSaturation.lean", + "test_path": "verification/research/test_final_actual_product_reporting_false_lean.py", + "method": "test_Q_03_false_independent_error_mass", + "body": """ +open PermanssonResearch.Probability.DependentThreeStage +example : dependentLaw.atom (true, (true,true)) = (9/20 : ℝ) := by + exact true_history_mass +""", + }, + "E-05": { + "source": "PermanssonResearch/Specialized/E05FullGramMaskingCountermodel.lean", + "test_path": "verification/research/test_e05_full_gram_masking_lean.py", + "method": "test_false_unqualified_full_gram_negativity_rejected", + "body": """ +open PermanssonResearch.Specialized.E05FullGramMaskingCountermodel +open PermanssonResearch.Specialized +open scoped InnerProductSpace +example {E : Type*} [NormedAddCommGroup E] + [InnerProductSpace ℝ E] {c : ℝ} (hc : 0 < c) + (x y : E) (hgap : 0 < gramGap x y) : + c * (⟪x, gramWitness x y⟫_ℝ ^ 2 - + ⟪y, gramWitness x y⟫_ℝ ^ 2) + + 2 * c * gramGap x y ^ 2 < 0 := by + exact (original_signed_negative_but_total_positive hc x y hgap).2 +""", + }, + "Q-04": { + "source": "PermanssonResearch/ConstitutiveQuasi/LocalizedPrefixExamples.lean", + "test_path": "verification/research/test_final_actual_product_reporting_false_lean.py", + "method": "test_Q_04_false_zero_intervention_report_error", + "body": """ +open PermanssonResearch.ConstitutiveQuasi.LocalizedExamples +example : dist (0 : ℝ) (1/10) ≤ 0 := by + exact cannot_omit_treatment_report_error +""", + }, + "A-03": { + "source": "PermanssonResearch/Probability/KantorovichTV.lean", + "test_path": "verification/research/test_pr113_semantic_boundary_candidates.py", + "method": "test_A_03_unbounded_TV_does_not_bound_W1", + "body": """ +namespace PermanssonResearch.Probability +open PermanssonLean.ProbabilitySupport +example {ε : ℝ} (hε : 0 < ε) (hε1 : ε ≤ 1) (C : ℝ) : + ¬ ∃ n : ℕ, (haveI := tailMix_isProbability hε.le hε1 n + eventTotalVariation (Measure.dirac (0 : ℝ)) (tailMix ε n) ≤ ε) ∧ + ¬ W1DualLE (Measure.dirac (0 : ℝ)) (tailMix ε n) (C * ε) := by + exact tv_does_not_control_w1_on_real hε hε1 C +end PermanssonResearch.Probability +""", + }, + "C-01": { + "source": "PermanssonResearch/MultipleLimit/GrowingBlockCounterexample.lean", + "test_path": "verification/research/test_pr113_semantic_boundary_candidates.py", + "method": "test_C_01_bounded_descriptor_occupation_nonconvergence", + "body": """ +namespace PermanssonResearch.MultipleLimit.GrowingBlock +open PermanssonResearch.MultipleLimit.FiniteStateOccupation +open Filter MeasureTheory +open scoped Topology +example : + ∃ π : ProbabilityMeasure (Fin 2), + Tendsto (fun n => empiricalOccupation descriptor (n+1) (Nat.succ_pos n)) + atTop (𝓝 π) := by + exact descriptor_occupations_do_not_converge +end PermanssonResearch.MultipleLimit.GrowingBlock +""", + }, + "D-07": { + "source": "PermanssonResearch/ReverseSolver/InfiniteSafetyLeakage.lean", + "test_path": "verification/research/test_pr113_semantic_boundary_candidates.py", + "method": "test_D_07_finite_survival_does_not_imply_infinite_safety", + "body": """ +namespace PermanssonResearch.ReverseSolver.InfiniteSafety +open ControlledKernel FiniteMonitor +example : limitValue leakageSystem leakageTarget 0 = (99 : ℝ) / 100 := by + exact leakage_limitValue +end PermanssonResearch.ReverseSolver.InfiniteSafety +""", + }, + "E-01": { + "source": "PermanssonResearch/Specialized/SeamReduction.lean", + "test_path": "verification/research/test_pr113_semantic_boundary_candidates.py", + "method": "test_E_01_seam_invisibility_does_not_force_rank_one", + "body": """ +namespace PermanssonResearch.Specialized +example (H : Matrix (Fin 2) (Fin 2) ℝ) (p : Fin 2) + (hsym : ∀ i j, H i j = H j i) + (hseam : ∀ i j, seam H p i j = 0) : + H.rank ≤ 1 := by + exact seam_rank_le_two H p hsym hseam +end PermanssonResearch.Specialized +""", + }, + "S-01": { + "source": "PermanssonResearch/Games/DiscountedVerification.lean", + "test_path": "verification/research/test_pr113_semantic_second_candidates.py", + "method": "test_S_01_no_terminal_correction_is_false", + "body": """ +namespace PermanssonResearch.DiscountedVerification +open ActionTrace +example {Y A : Type} [Fintype Y] [Fintype A] + (G : Game Y A) (σ : BehavioralPolicy Y A) + (μ : Row Y) (V : Y → ℝ) (n : ℕ) : + finitePayoff G σ μ n + finiteLoss G σ μ V n = μ.expect V := by + exact finite_performance_difference G σ μ V n +end PermanssonResearch.DiscountedVerification +""", + }, + "M-01": { + "source": "PermanssonResearch/MultiAgent/AutonomousAEContinuumFixture.lean", + "test_path": "verification/research/test_pr113_semantic_second_candidates.py", + "method": "test_M_01_null_key_is_not_free_initial_support", + "body": """ +namespace PermanssonResearch.MultiAgent.Autonomous.ContinuumFixture +open MeasureTheory ProbabilityTheory PermanssonLean +example : + ((game.openLoop (player true) (anchor false)).toModel world).pathLaw + (Measure.dirac (initialState 0)) = + ((game.openLoop (player true) (anchor true)).toModel world).pathLaw + (Measure.dirac (initialState 0)) := by + exact point_start_treated_pathLaw_ne +end PermanssonResearch.MultiAgent.Autonomous.ContinuumFixture +""", + }, + "A-02": { + "source": "PermanssonResearch/Perturbation/DirectDualContraction.lean", + "test_path": "verification/research/test_pr113_semantic_second_candidates.py", + "method": "test_A_02_identity_kernel_is_not_strictly_contractive", + "body": """ +namespace PermanssonResearch.Perturbation +open MeasureTheory ProbabilityTheory +open PermanssonResearch.Probability +-- The original all-law contraction predicate on a concrete nontrivial +-- measurable metric space. The identity kernel only has factor one; +-- supplying Y = ℝ rules out elaboration/typeclass failures being mistaken +-- for semantic rejection of the intentionally false strict factor 1/2. +example : IsW1Contraction (Kernel.id : Kernel ℝ ℝ) (1 / 2 : ℝ) := by + exact (identityKernel_isW1Contraction (Y := ℝ)) +end PermanssonResearch.Perturbation +""", + }, + "D-01": { + "source": "PermanssonResearch/ReverseSolver/ResidenceEvent.lean", + "test_path": "verification/research/test_pr113_semantic_second_candidates.py", + "method": "test_D_01_hitting_is_not_residence", + "body": """ +namespace PermanssonResearch.ReverseSolver.Residence +example : + hasRun (fun z : Bool => z = true) 2 [false, true, false] = true := by + exact hit_does_not_imply_stay.2 +end PermanssonResearch.ReverseSolver.Residence +""", + }, + "C11-01": { + "source": "PermanssonResearch/ReverseSolver/C11ReferenceExamples.lean", + "test_path": "verification/research/test_pr113_semantic_second_candidates.py", + "method": "test_C11_01_zero_dimensional_family_is_rejected", + "body": """ +namespace PermanssonResearch.ReverseSolver.C11.ReferenceExamples +open PermanssonResearch.ReverseSolver.C11 +example : checkStationaryFamily empty + (referenceStationaryFamily empty) = true := by + exact zero_dimension_reference_rejected +end PermanssonResearch.ReverseSolver.C11.ReferenceExamples +""", + }, + "C11-02": { + "source": "PermanssonResearch/ReverseSolver/C11ReferenceExamples.lean", + "test_path": "verification/research/test_pr113_semantic_second_candidates.py", + "method": "test_C11_02_corrupted_stationary_report_is_rejected", + "body": """ +namespace PermanssonResearch.ReverseSolver.C11.ReferenceExamples +open PermanssonResearch.ReverseSolver.C11 +example : checkClassStationary periodic corruptedPeriodic = true := by + exact corrupted_stationary_receipt_rejected +end PermanssonResearch.ReverseSolver.C11.ReferenceExamples +""", + }, + "XPLUS-01": { + "source": "PermanssonResearch/EconomicWitness/ConstitutionCore.lean", + "test_path": "verification/research/test_pr113_semantic_second_candidates.py", + "method": "test_XPLUS_01_constructed_original_margin_is_not_zero", + "body": """ +namespace PermanssonResearch.EconomicWitness +open PermanssonLean.RegimeSpecification +example : + constitutiveMargin model specification family survivalProperty + comparisonSet extractionIntervention = (0 : ℝ) := by + exact original_constitutive_margin_one +end PermanssonResearch.EconomicWitness +""", + }, + "S-02": { + "source": "PermanssonResearch/Games/S02InformationBoundaryCountermodel.lean", + "test_path": "verification/research/test_pr113_semantic_structural_candidates.py", + "method": "test_S_02_clairvoyant_action_is_not_admissible_history_row", + "body": """ +namespace PermanssonResearch.ActionTrace.S02InformationBoundary +open PermanssonResearch.ActionTrace +example (p : Row Bool) : + p.expect independentFairMatch = (1 : ℝ) := by + exact every_nonanticipating_row_half p +end PermanssonResearch.ActionTrace.S02InformationBoundary +""", + }, + "Q-01": { + "source": "PermanssonResearch/Quotient/Q01NonLumpableProjectionCountermodel.lean", + "test_path": "verification/research/test_pr113_semantic_structural_candidates.py", + "method": "test_Q_01_fine_projection_does_not_always_lump", + "body": """ +namespace PermanssonResearch.Quotient.Q01NonLumpable +example : + ∃ g : Bool → Bool, ∀ x : Fin 3, + observed (fineStep x) = g (observed x) := by + exact no_autonomous_coarse_transition +end PermanssonResearch.Quotient.Q01NonLumpable +""", + }, + "A-01": { + "source": "PermanssonResearch/Probability/A01SupnormClassCountermodel.lean", + "test_path": "verification/research/test_pr113_semantic_structural_candidates.py", + "method": "test_A_01_unit_supnorm_is_not_correct_KR_test_class", + "body": """ +namespace PermanssonResearch.Probability.A01SupnormClass +example : + ∃ f : ℝ → ℝ, |f 0| ≤ 1 ∧ |f 4| ≤ 1 ∧ + (4 : ℝ) ≤ |f 4 - f 0| := by + exact no_unit_supnorm_witness_for_gap_four +end PermanssonResearch.Probability.A01SupnormClass +""", + }, + "A-04": { + "source": "PermanssonResearch/Perturbation/A04FourOutputNecessityCountermodel.lean", + "test_path": "verification/research/test_pr113_semantic_factorization_candidates.py", + "method": "test_A_04_missing_fourth_output_changes_margin", + "body": """ +namespace PermanssonResearch.Perturbation.A04FourOutputNecessity +example : + ∃ f : ℝ → ℝ → ℝ → ℝ, + ∀ base treated perturbedBase perturbedTreated : ℝ, + f base treated perturbedBase = + marginDifference base treated perturbedBase perturbedTreated := by + exact no_general_three_output_margin_formula +end PermanssonResearch.Perturbation.A04FourOutputNecessity +""", + }, + "A-05": { + "source": "PermanssonResearch/Perturbation/A05SurvivalOccupationCountermodel.lean", + "test_path": "verification/research/test_pr113_semantic_factorization_candidates.py", + "method": "test_A_05_stationary_mass_is_not_forever_survival", + "body": """ +namespace PermanssonResearch.ReverseSolver.InfiniteSafety.A05OccupationBoundary +example : + limitValue constantSystem safe 0 = + ((stationaryWeight (0 : Fin 2) : ℚ) : ℝ) := by + exact cannot_replace_full_path_survival_by_stationary_mass +end PermanssonResearch.ReverseSolver.InfiniteSafety.A05OccupationBoundary +""", + }, +} + + +def frozen_spec(case_id: str, *, root: Path = ROOT) -> tuple[dict, bytes]: + if case_id not in SPECS: + raise MutationEvidenceError("unregistered original semantic mutation case") + entry = SPECS[case_id] + if not isinstance(entry, dict) or set(entry) != { + "source", "test_path", "method", "body", + }: + raise MutationEvidenceError("semantic case contract is malformed") + rows = json.loads((root / "research/paper_closures/post_merge_112/" + "acceptance_case_source_map.json").read_bytes()) + matches = [row for row in rows.get("cases", []) + if row.get("case_id") == case_id] + if len(matches) != 1 or rows.get("original_cases") != 45: + raise MutationEvidenceError("case ID not in exact original acceptance map") + original = matches[0] + if original.get("effective_accepted") is not False: + raise MutationEvidenceError("unreviewed mutation cannot inherit approval") + requested = ("verification/research/" + + Path(entry["test_path"]).name, entry["method"], + "SEMANTIC_NEGATIVE") + selected = {(row.get("path"), row.get("method"), row.get("role")) + for row in original.get("test_method_candidates", []) + if isinstance(row, dict)} + if requested not in selected: + raise MutationEvidenceError("semantic mutant is not a selected original method") + relative = Path(entry["source"]) + if (relative.is_absolute() or ".." in relative.parts or + not relative.as_posix().startswith("PermanssonResearch/") or + relative.suffix != ".lean"): + raise MutationEvidenceError("unsafe/non-original Lean provider path") + candidate_source = root / relative + original_path = candidate_source.resolve() + if (candidate_source.is_symlink() or + not original_path.is_relative_to(root.resolve()) or + not original_path.is_file() or original_path.is_symlink()): + raise MutationEvidenceError("actual original Lean source unavailable") + candidate_test = root / entry["test_path"] + test_path = candidate_test.resolve() + if (candidate_test.is_symlink() or + not test_path.is_relative_to(root.resolve()) or + not test_path.is_file() or test_path.is_symlink()): + raise MutationEvidenceError("original named test unavailable") + body = textwrap.dedent(entry["body"]).strip() + "\n" + if any(re.match(r"(?i)^\s*(?:import|axiom|sorry|admit)\b", line) + for line in body.splitlines()): + raise MutationEvidenceError("false fixture may not alter imports or introduce axioms") + if len(body.encode("utf-8")) > 4096: + raise MutationEvidenceError("unbounded original semantic mutation body") + original_bytes = original_path.read_bytes() + if not original_bytes or len(original_bytes) > 4*1024*1024: + raise MutationEvidenceError("unbounded original source") + mutated = (original_bytes + + ("\n/- ORIGINAL CASE " + case_id + + ": FALSE SEMANTIC TEST ONLY -/\n" + body).encode("utf-8")) + expected_imports = [line for line in original_bytes.decode("utf-8").splitlines() + if line.startswith("import ")] + changed_imports = [line for line in mutated.decode("utf-8").splitlines() + if line.startswith("import ")] + if expected_imports != changed_imports: + raise MutationEvidenceError("source import identity changed") + return entry, mutated + + +def run_one(head: str, case_id: str, out: Path) -> dict: + if (not re.fullmatch(r"[a-f0-9]{40}", head) or head != git_head()): + raise MutationEvidenceError("source checkout is not exact frozen H") + out = out.resolve() + if (out.exists() or out.is_relative_to(ROOT.resolve()) or + ROOT.resolve().is_relative_to(out)): + raise MutationEvidenceError("must write to a new detached output root") + entry, mutated = frozen_spec(case_id) + out.mkdir(parents=True, exist_ok=False) + with tempfile.TemporaryDirectory(prefix="mutant-", dir=out) as td: + input_path = Path(td) / "OriginalFalse.lean" + input_path.write_bytes(mutated) + directory = out / "source_logs" + result = produce( + head, true_source=ROOT / entry["source"], + mutant=input_path, + test_path=ROOT / entry["test_path"], + test_name=entry["method"], + case_id=case_id, claim_ids=[], out=directory) + archive = out / "semantic_candidate_logs.zip" + archive_digest = seal_logs(out / "source_logs", archive) + return { + "format": "Permansson.OriginalSemanticMutantCandidate.v1", + "status": "DETACHED_UNREVIEWED_UNATTRIBUTED", + "source_head": head, + "case_id": case_id, + "test_path": entry["test_path"], + "test_name": entry["method"], + "source_path": entry["source"], + "mutant_sha256": hashlib.sha256(mutated).hexdigest(), + "detached_bundle_sha256": archive_digest, + "mutation_record": result, + "ci_check_run_id": None, + "independent_review": False, + "full_mathematical_acceptance": False, + } + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument("--head", required=True) + parser.add_argument("--case-id", choices=sorted(SPECS), required=True) + parser.add_argument("--out", required=True, type=Path) + args = parser.parse_args(argv) + try: + result = run_one(args.head, args.case_id, args.out) + (args.out / "unreviewed_candidate.json").write_text( + json.dumps(result, indent=2, sort_keys=True) + "\n", + encoding="utf-8") + except (MutationEvidenceError, OSError, ValueError, KeyError) as exc: + print("ORIGINAL SEMANTIC EVIDENCE BLOCKED:", exc, file=sys.stderr) + return 2 + print(json.dumps({k: result[k] for k in ( + "status", "source_head", "case_id", "detached_bundle_sha256", + )})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_positive_route_candidate.py b/verification/research/final_closure_positive_route_candidate.py new file mode 100644 index 00000000..235ff7eb --- /dev/null +++ b/verification/research/final_closure_positive_route_candidate.py @@ -0,0 +1,236 @@ +"""Execute original positive-test method routes for all 45 cases. + +This is a genuine, unreviewed *execution candidate*, NOT a v3 producing-CI +receipt, an original-case decision or an independent mathematical review. +A selected positive method MUST be frozen as COMPILER_SOURCE/EXACT_CHECK; +the positive control of a SEMANTIC_NEGATIVE method cannot stand in for it. +""" +from __future__ import annotations + +import argparse +import ast +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys + +from final_closure_mutation_evidence import ( + ROOT, committed_file_bytes, git_head, lean_result, + pinned_lean_version, MutationEvidenceError, +) +from final_closure_case_receipts import INVALID_DIAGNOSTICS +from final_closure_producer_v2_bridge import SHA40 + +FORMAT = "Permansson.PR113.Original45PositiveRoutesCandidate.v1" +SELECTION = "Permansson.PR113.OriginalPositiveSelection.v1" +TEST_METHOD = re.compile(r"^test_[A-Za-z0-9_]+$") +CLASS = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") +MAX_REPORT = 4 * 1024 * 1024 + + +class PositiveRouteError(ValueError): + pass + + +def require(ok, reason): + if not ok: + raise PositiveRouteError(reason) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def source_method(head, path, cls, method): + require(isinstance(path, str) and + path.startswith("verification/research/test_") and + path.endswith(".py") and + ".." not in Path(path).parts and + isinstance(cls, str) and CLASS.fullmatch(cls) and + isinstance(method, str) and TEST_METHOD.fullmatch(method), + "not a named original unittest source method") + raw = committed_file_bytes(head, ROOT / path, root=ROOT) + try: + parsed = ast.parse(raw.decode("utf-8"), filename=path) + except (ValueError, UnicodeError, SyntaxError) as exc: + raise PositiveRouteError("named original test source is not parseable") from exc + found = [node for node in parsed.body + if isinstance(node, ast.ClassDef) and node.name == cls and + any(isinstance(inner, (ast.FunctionDef, ast.AsyncFunctionDef)) + and inner.name == method for inner in node.body)] + require(len(found) == 1, "original named positive test was not uniquely defined") + return raw + + +def parse_selection(payload, case_map): + cases = case_map.get("cases") if isinstance(case_map, dict) else None + require(isinstance(cases, list) and len(cases) == 45 and + isinstance(payload, dict) and + payload.get("format") == SELECTION and + isinstance(payload.get("cases"), list), + "45 original positive source-method selections required") + canon = {row["case_id"]:row for row in cases} + require(len(canon) == 45, "original 45 case IDs are not unique") + chosen = {} + required = {"case_id", "test_path", "test_class", + "test_method", "proof_source_path"} + for row in payload["cases"]: + require(isinstance(row, dict) and set(row) == required, + "malformed or unauthorized positive route selection") + cid = row["case_id"] + require(cid in canon and cid not in chosen, + "duplicated/unknown original source case") + original = canon[cid] + require(isinstance(row["test_class"], str) and + CLASS.fullmatch(row["test_class"]) is not None and + isinstance(row["test_method"], str) and + TEST_METHOD.fullmatch(row["test_method"]) is not None and + isinstance(row["test_path"], str) and + row["test_path"].startswith("verification/research/test_") and + row["test_path"].endswith(".py"), + "selected original positive class/method/path is malformed") + matches = [route for route in original.get("test_method_candidates", []) + if route.get("path") == row["test_path"] and + route.get("method") == row["test_method"] and + route.get("role") in ("COMPILER_SOURCE","EXACT_CHECK")] + require(len(matches) == 1, + "positive route is not frozen as COMPILER_SOURCE/EXACT_CHECK") + proof_path = row["proof_source_path"] + require(any(x.get("path") == proof_path for x in + original.get("historical_proof_declarations", ())), + "selected successful Lean source does not belong to original case") + chosen[cid] = row + require(set(chosen) == set(canon), + "at least one original case lacks a selected positive role") + return chosen + + +def execute(head, selection_path, out): + require(isinstance(head, str) and SHA40.fullmatch(head) and + git_head() == head, "stale/not actual exact-H source checkout") + selected_file = Path(selection_path) + require(selected_file.is_file() and not selected_file.is_symlink() and + not selected_file.resolve().is_relative_to(ROOT.resolve()), + "selection must be an external reviewed input file") + case_map_file = ROOT/"research/paper_closures/post_merge_112/acceptance_case_source_map.json" + case_map = json.loads(committed_file_bytes( + head, case_map_file, root=ROOT)) + chosen = parse_selection(json.loads(selected_file.read_bytes()), case_map) + target = Path(out) + require(not target.is_symlink(), "candidate output symlink prohibited") + target = target.resolve() + require(not target.exists() and + not target.is_relative_to(ROOT.resolve()) and + not target.is_relative_to(selected_file.parent.resolve()), + "source-method output must be new and disjoint from trusted inputs") + lean_version = pinned_lean_version(head) + target.mkdir(parents=True, exist_ok=False) + results = [] + for cid, row in sorted(chosen.items()): + test_path = row["test_path"] + method = row["test_method"] + cls = row["test_class"] + test_bytes = source_method(head, test_path, cls, method) + proof_path = row["proof_source_path"] + proof_bytes = committed_file_bytes( + head, ROOT/proof_path, root=ROOT) + sandbox = target/cid + sandbox.mkdir() + # unittest imports by basename. Keep both original research helper + # directory and repository root on sys.path without touching the Git + # checkout or inserting source-owned fake test outcomes. + env = os.environ.copy() + env["PYTHONDONTWRITEBYTECODE"] = "1" + env["PYTHONPATH"] = (str(ROOT/"verification/research") + + os.pathsep + str(ROOT) + + os.pathsep + env.get("PYTHONPATH", "")) + qualified = Path(test_path).stem + "." + cls + "." + method + try: + proc = subprocess.run( + [sys.executable, "-B", "-m", "unittest", qualified, "-v"], + cwd=ROOT, env=env, capture_output=True, timeout=900) + except (OSError, subprocess.TimeoutExpired) as exc: + raise PositiveRouteError("registered original positive test unavailable") from exc + require(proc.returncode == 0 and + b"Ran 1 test" in proc.stderr and b"OK" in proc.stderr and + b"skipped=" not in proc.stderr and + b"expected failures=" not in proc.stderr and + len(proc.stdout)+len(proc.stderr) <= MAX_REPORT, + "named positive unittest failed, skipped or ran wrong scope: "+cid) + original_lean = lean_result(ROOT/proof_path) + real_log = (original_lean.stdout+"\n"+original_lean.stderr).encode() + low = real_log.decode("utf-8", errors="replace").lower() + require(original_lean.returncode == 0 and + len(real_log) <= MAX_REPORT and + not any(fragment in low for fragment in INVALID_DIAGNOSTICS) and + "error:" not in low, + "original case positive Lean provider does not compile: "+cid) + # Re-open all source bytes after real method + compiler execution. + require(committed_file_bytes(head,ROOT/test_path,root=ROOT) == test_bytes and + committed_file_bytes(head,ROOT/proof_path,root=ROOT) == proof_bytes, + "source/test changed while running positive controls") + (sandbox/"unittest_stdout.log").write_bytes(proc.stdout) + (sandbox/"unittest_stderr.log").write_bytes(proc.stderr) + (sandbox/"source_positive.log").write_bytes(real_log) + record = { + "format":"Permansson.PR113.OriginalCasePositiveCandidate.v1", + "status":"SOURCE_TEST_AND_LEAN_EXECUTED_UNREVIEWED", + "head_sha":head, "case_id":cid, "test_path":test_path, + "test_class":cls, "test_name":method, + "positive_role":next(x["role"] for x in + next(y for y in case_map["cases"] if y["case_id"]==cid) + ["test_method_candidates"] + if x["path"]==test_path and x["method"]==method), + "source_sha256":sha(test_bytes), + "proof_source_path":proof_path, + "proof_source_sha256":sha(proof_bytes), + "lean_toolchain":lean_version, + "unittest_stdout_sha256":sha(proc.stdout), + "unittest_stderr_sha256":sha(proc.stderr), + "source_positive_log_sha256":sha(real_log), + "source_positive_exit_code":original_lean.returncode, + "registered_method_exit_code":proc.returncode, + "actual_producing_check_run_id":None, + "authenticated_ci_artifact":False, + "independent_case_review":False, + "scientific_release_approved":False, + } + (sandbox/"unreviewed_positive.json").write_text( + json.dumps(record,sort_keys=True,indent=2)+"\n") + results.append(record) + require(len(results)==45,"not all original positives actually executed") + manifest={ + "format":FORMAT,"status":"EXECUTED_NO_PRODUCING_CI_OR_MATH_REVIEW", + "head_sha":head,"actual_positive_cases":45, + "positive_route_records":results, + "independent_case_reviews":0, + "authenticated_ci_artifact":False, + "scientific_release_approved":False, + } + (target/"unreviewed_45_positive_routes.json").write_text( + json.dumps(manifest,sort_keys=True,indent=2)+"\n") + return manifest + + +def main(argv=None): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head",required=True) + parser.add_argument("--selection",required=True,type=Path) + parser.add_argument("--out",required=True,type=Path) + args=parser.parse_args(argv) + try: + result=execute(args.head,args.selection,args.out) + except (PositiveRouteError,MutationEvidenceError,ValueError, + KeyError,TypeError,OSError) as exc: + print("ORIGINAL 45 POSITIVE ROUTES BLOCKED:",exc,file=sys.stderr) + return 2 + print(json.dumps({"status":result["status"], + "actual_positive_cases":result["actual_positive_cases"]})) + return 0 + + +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_postseal_auth.py b/verification/research/final_closure_postseal_auth.py new file mode 100644 index 00000000..3197f967 --- /dev/null +++ b/verification/research/final_closure_postseal_auth.py @@ -0,0 +1,222 @@ +"""Independent exact-M postseal GH artifact authentication (review-pending). + +The producer workflow path here is an EXPLICIT *future* security-review target. +Do not introduce it into the accepted 25-workflow inventory until authorized. +A successful local postseal_replay.json is never a valid release certificate. +This verifier demands a real completed GitHub run/job/check/artifact SHA256, +actual candidate ZIP and sidecar bytes, and a second full archive replay. + +The work order remains BLOCKED until that workflow exists, an independent +security reviewer approves it, and authentic postmerge evidence is available. + +The upstream preseal producer is authenticated in its own right: an artifact +digest and run ID are not enough, because an `if: always()` upload from a +failed, cancelled or superseded producer attempt carries an authentic digest. +Its workflow/job/artifact names below are the proposed P1-12 contract, also +pending independent security review. +""" +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +import re +import sys +import tempfile + +from final_closure_authenticate import live, paginate +from final_closure_producer_bytes import inspect_zip, ProducerBytesError + +WORKFLOW=".github/workflows/pr113-postseal-replay.yml" +JOB="postseal-archive-replay" +ARTIFACT="pr113-complete-postseal-candidate" +UPSTREAM_WORKFLOW=".github/workflows/pr113-preseal-fullscope.yml" +UPSTREAM_JOB="preseal-fullscope-candidate" +UPSTREAM_ARTIFACT="pr113-preseal-fullscope-candidate" +UPSTREAM_EVENTS=("push","workflow_dispatch") +UPSTREAM_METADATA="upstream_source_artifact.json" +IDENTITY_KEYS=("run_id","run_attempt","job_id","check_run_id","artifact_id") +FORMAT="Permansson.PR113.GenuinePostsealCI.v1" +SHA40=re.compile(r"^[0-9a-f]{40}$") +SHA256=re.compile(r"^[0-9a-f]{64}$") + +class PostsealAuthenticationError(ValueError): + pass + +def demand(ok, reason): + if not ok:raise PostsealAuthenticationError(reason) + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + +def validate_postseal_payload(members, *, merged, expected_pdf): + """Replay complete candidate archive, not a source-owned Boolean label.""" + base="candidate/" + for name in ("Permansson_Mathematical_Closure_CANDIDATE.zip", + "SHA256SUMS.txt"): + demand(base+name in members, + "postseal producer omitted original released candidate bytes") + receipt_name="postseal_replay_receipt.json" + demand(receipt_name in members,"actual postseal execution receipt missing") + try: + executed=json.loads(members[receipt_name]) + except (TypeError,UnicodeError,ValueError) as exc: + raise PostsealAuthenticationError("invalid postseal execution JSON") from exc + demand(executed.get("format") == + "Permansson.MathClosure.PostsealReplayCandidate.v1" and + executed.get("status") == + "CANDIDATE_ARCHIVE_REPLAYED_UNREVIEWED" and + executed.get("head_sha") and SHA40.fullmatch(executed["head_sha"]) and + executed.get("source_commit_sha")==merged and + executed.get("pdf_sha256")==expected_pdf and + executed.get("complete_original_members_verified") is True and + executed.get("authenticated_ci_artifact") is False and + executed.get("independent_review") is False and + executed.get("full_release_approved") is False, + "replay claimed approval or omitted required M/PDF/ZIP provenance") + candidate=members[base+"Permansson_Mathematical_Closure_CANDIDATE.zip"] + sidecar=members[base+"SHA256SUMS.txt"] + demand(sha(candidate)==executed.get("archive_sha256"), + "released candidate ZIP changed after postseal job") + root=Path(__file__).resolve().parents[2] + release=root/"release/mathematical-closure-1" + if str(release) not in sys.path: + sys.path.insert(0,str(release)) + from postseal_replay import replay + with tempfile.TemporaryDirectory(prefix="pr113-verified-postseal-") as tmp: + original=Path(tmp)/"original" + original.mkdir() + zip_path=original/"Permansson_Mathematical_Closure_CANDIDATE.zip" + sums=original/"SHA256SUMS.txt" + zip_path.write_bytes(candidate) + sums.write_bytes(sidecar) + actual=replay(zip_path,sums,Path(tmp)/"detached"/"verified.json") + demand(all(actual.get(key)==executed.get(key) for key in ( + "format","status","source_commit_sha","head_sha","pdf_sha256", + "archive_sha256","manifest_sha256", + "complete_original_members_verified","authenticated_ci_artifact", + "independent_review","full_release_approved")), + "independent replay of original candidate bytes differs from job") + return actual + +def authentic_producer(ids, merged, *, workflow, job, artifact_name, events): + """Authenticate one completed, successful exact-M producer and artifact.""" + run=live("/actions/runs/"+str(ids["run_id"])) + demand(run.get("id")==ids["run_id"] and + run.get("head_sha")==merged and + run.get("head_branch")=="main" and + run.get("event") in events and + run.get("run_attempt")==ids["run_attempt"] and + run.get("status")=="completed" and + run.get("conclusion")=="success" and + (run.get("path")==workflow or + str(run.get("path","")).endswith("/"+workflow)), + "producing workflow not a genuine successful exact-M run: "+job) + rows=paginate("/actions/runs/"+str(ids["run_id"])+"/jobs",key="jobs") + jobs=[row for row in rows if row.get("id")==ids["job_id"] and + row.get("name")==job] + demand(len(jobs)==1 and jobs[0].get("run_id")==ids["run_id"] and + jobs[0].get("head_sha")==merged and + jobs[0].get("run_attempt")==ids["run_attempt"] and + jobs[0].get("status")=="completed" and + jobs[0].get("conclusion")=="success" and + str(jobs[0].get("check_run_url","")).endswith( + "/check-runs/"+str(ids["check_run_id"])), + "producer job/check is not authentic: "+job) + check=live("/check-runs/"+str(ids["check_run_id"])) + demand(check.get("id")==ids["check_run_id"] and + check.get("head_sha")==merged and + check.get("name")==job and + check.get("status")=="completed" and + check.get("conclusion")=="success" and + (check.get("app") or {}).get("slug")=="github-actions", + "producing check-run is missing or failed: "+job) + artifact=live("/actions/artifacts/"+str(ids["artifact_id"])) + digest=artifact.get("digest") + demand(artifact.get("id")==ids["artifact_id"] and + artifact.get("name")==artifact_name and + artifact.get("expired") is False and + (artifact.get("workflow_run") or {}).get("id")==ids["run_id"] and + (artifact.get("workflow_run") or {}).get("head_sha")==merged and + isinstance(digest,str) and digest.startswith("sha256:") and + SHA256.fullmatch(digest[7:]), + "GitHub authoritative artifact identity missing: "+artifact_name) + # The artifacts API has no attempt field: bind the upload to the window + # of the authenticated successful job attempt, so an artifact retained + # from a failed earlier attempt of the same run is not accepted. + stamps=(jobs[0].get("started_at"),artifact.get("created_at"), + jobs[0].get("completed_at")) + demand(all(isinstance(x,str) and + re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ",x) + for x in stamps) and stamps[0]<=stamps[1]<=stamps[2], + "artifact was not uploaded by the successful producer attempt: "+ + artifact_name) + return run, digest[7:] + +def bind_upstream_metadata(entries, merged, upstream_ids, upstream_digest): + """The postseal job must have replayed exactly the authenticated input.""" + demand(UPSTREAM_METADATA in entries, + "postseal job did not retain its upstream artifact identity") + try: + meta=json.loads(entries[UPSTREAM_METADATA]) + except (TypeError,UnicodeError,ValueError) as exc: + raise PostsealAuthenticationError("invalid upstream metadata JSON") from exc + demand(isinstance(meta,dict) and + meta.get("id")==upstream_ids["artifact_id"] and + meta.get("name")==UPSTREAM_ARTIFACT and + meta.get("digest")=="sha256:"+upstream_digest and + (meta.get("workflow_run") or {}).get("id")==upstream_ids["run_id"] and + (meta.get("workflow_run") or {}).get("head_sha")==merged, + "postseal replayed a different upstream preseal artifact") + +def authenticate_postseal(merged, expected_pdf, identities, zipped_artifact): + """Bind archived M/PDF bytes to approved producing-GH job, not a fake flag.""" + demand(isinstance(merged,str) and SHA40.fullmatch(merged) and + isinstance(expected_pdf,str) and SHA256.fullmatch(expected_pdf) and + isinstance(identities,dict), + "postseal needs real merged-M source and PDF identity") + upstream={key:identities.get("upstream_"+key) for key in IDENTITY_KEYS} + for key in IDENTITY_KEYS: + demand(type(identities.get(key)) is int and identities[key]>0, + "missing source-bound postseal GitHub identity: "+key) + demand(type(upstream[key]) is int and upstream[key]>0, + "missing upstream preseal producer identity: "+key) + demand(upstream["run_id"]!=identities["run_id"] and + upstream["artifact_id"]!=identities["artifact_id"], + "postseal cannot be its own upstream preseal producer") + upstream_run,upstream_digest=authentic_producer( + upstream,merged,workflow=UPSTREAM_WORKFLOW,job=UPSTREAM_JOB, + artifact_name=UPSTREAM_ARTIFACT,events=UPSTREAM_EVENTS) + run,digest=authentic_producer( + identities,merged,workflow=WORKFLOW,job=JOB, + artifact_name=ARTIFACT,events=("workflow_dispatch",)) + stamps=(upstream_run.get("updated_at"),run.get("run_started_at")) + demand(all(isinstance(x,str) for x in stamps) and stamps[0]<=stamps[1], + "upstream preseal producer had not finished before postseal began") + raw_path=Path(zipped_artifact) + demand(raw_path.is_file() and not raw_path.is_symlink() and + not any(x.is_symlink() for x in raw_path.absolute().parents), + "postseal evidence artifact is not a regular unsymlinked input") + raw=raw_path.read_bytes() + demand(sha(raw)==digest, + "postseal original uploaded ZIP differs from real GitHub digest") + try: + entries=inspect_zip(raw) + except ProducerBytesError as exc: + raise PostsealAuthenticationError("unsafe postseal ZIP") from exc + bind_upstream_metadata(entries,merged,upstream,upstream_digest) + actual=validate_postseal_payload( + entries,merged=merged,expected_pdf=expected_pdf) + return { + "format":FORMAT,"status":"COMPLETE_ARCHIVE_REPLAY", + "source_commit_sha":merged,"pdf_sha256":expected_pdf, + "artifact_sha256":digest, + "authenticated_ci_artifact":True, + "archive_sha256":actual["archive_sha256"], + "run_id":identities["run_id"],"run_attempt":identities["run_attempt"], + "job_id":identities["job_id"],"check_run_id":identities["check_run_id"], + "artifact_id":identities["artifact_id"], + "upstream_preseal":{**upstream,"artifact_sha256":upstream_digest}, + "independent_scholarly_approval":False, + "mathematical_release_approved":False, + } diff --git a/verification/research/final_closure_producer_bytes.py b/verification/research/final_closure_producer_bytes.py new file mode 100644 index 00000000..c3de0d3c --- /dev/null +++ b/verification/research/final_closure_producer_bytes.py @@ -0,0 +1,160 @@ +"""Exact source-bound 21-artifact verification; never mathematical approval.""" +from __future__ import annotations +import hashlib +import io +import re +import stat +import zipfile +from pathlib import PurePosixPath + +from final_closure_authenticate import live, paginate +from final_closure_candidate_artifact_auth import KINDS +from final_closure_producer_v2_bridge import verified_producer_index +from pr113_reviewed_producer_branch_v3 import ( + authentic_branch, ProducerBranchError, +) + +FORMAT = "Permansson.PR113.ProducerBytesRevalidated.v1" + +class ProducerBytesError(ValueError): + pass + +def demand(ok, reason): + if not ok: + raise ProducerBytesError(reason) + +def sha(data): + return hashlib.sha256(data).hexdigest() + +def inspect_zip(raw): + demand(type(raw) is bytes and 0 < len(raw) <= 64*1024*1024, + "missing or oversized producer ZIP") + result, seen, total = {}, set(), 0 + try: + with zipfile.ZipFile(io.BytesIO(raw)) as z: + rows = z.infolist() + demand(len(rows) <= 512, "too many producer members") + for row in rows: + name = row.filename + path = PurePosixPath(name) + mode = (row.external_attr >> 16) & 0o170000 + demand(name and not row.is_dir() and + not path.is_absolute() and ".." not in path.parts and + "\\" not in name and path.as_posix() == name and + not any(x in ("", ".") for x in name.split("/")) and + mode in (0, stat.S_IFREG) and + name.casefold() not in seen, + "unsafe, duplicated or nonregular producer member") + seen.add(name.casefold()) + total += row.file_size + demand(row.file_size <= 4*1024*1024 and total <= 64*1024*1024, + "unbounded producer archive expansion") + for name in seen: + segments = name.split("/") + demand(not any("/".join(segments[:i]) in seen + for i in range(1, len(segments))), + "producer ZIP member shadows path ancestor") + for row in rows: + content = z.read(row) + demand(len(content) == row.file_size, + "ZIP member decompression length mismatch") + result[row.filename] = content + except (OSError, zipfile.BadZipFile, RuntimeError) as exc: + raise ProducerBytesError("invalid producer archive") from exc + return result + +def authenticate(head, attestations, zip_bytes): + """All 4+9+8 genuine job, check and SHA-256 bindings must match.""" + try: + indexed = verified_producer_index(head, attestations) + except (ValueError, TypeError, KeyError) as exc: + raise ProducerBytesError("incomplete/untrusted 21-shard index") from exc + demand(type(zip_bytes) is dict and set(zip_bytes) == + {x["artifact_id"] for x in indexed.values()}, + "missing or borrowed producer ZIP") + verified = {} + total = 0 + verified_branch = None + for (kind, shard), row in indexed.items(): + spec = KINDS[kind] + run = live("/actions/runs/" + str(row["run_id"])) + if verified_branch is None: + try: + verified_branch = authentic_branch( + head, run.get("head_branch"), live=live) + except ProducerBranchError as exc: + raise ProducerBytesError( + "original 21 branch is unrelated to reviewed PR source") from exc + demand(run.get("id") == row["run_id"] and + run.get("head_sha") == head and + run.get("run_attempt") == row["run_attempt"] and + run.get("status") == "completed" and + run.get("conclusion") == "success" and + run.get("event") in ("push", "workflow_dispatch") and + run.get("head_branch") == verified_branch and + (run.get("path") == spec["workflow"] or + str(run.get("path", "")).endswith("/" + spec["workflow"])), + "GitHub producer workflow identity not authenticated") + jobs = paginate("/actions/runs/"+str(row["run_id"])+"/jobs", key="jobs") + expected_name = spec["job"] + f" ({shard})" + matched = [j for j in jobs if + j.get("id") == row["job_id"] and + j.get("name") == expected_name] + demand(len(matched) == 1 and + matched[0].get("head_sha") == head and + matched[0].get("run_attempt") == row["run_attempt"] and + matched[0].get("run_id") == row["run_id"] and + matched[0].get("status") == "completed" and + matched[0].get("conclusion") == "success" and + str(matched[0].get("check_run_url","")).endswith( + "/check-runs/" + str(row["actual_check_run_id"])), + "producer job/check identity mismatch") + check = live("/check-runs/" + str(row["actual_check_run_id"])) + demand(check.get("id") == row["actual_check_run_id"] and + check.get("head_sha") == head and + check.get("name") == expected_name and + check.get("status") == "completed" and + check.get("conclusion") == "success" and + (check.get("app") or {}).get("slug") == "github-actions", + "producer check must be actual successful shard, not research") + artifact = live("/actions/artifacts/" + str(row["artifact_id"])) + demand(artifact.get("id") == row["artifact_id"] and + artifact.get("name") == spec["artifact"]+str(shard) and + artifact.get("expired") is False and + (artifact.get("workflow_run") or {}).get("id") == row["run_id"] and + (artifact.get("workflow_run") or {}).get("head_sha") == head and + artifact.get("digest") == "sha256:" + row["artifact_sha256"], + "GitHub authoritative producer ZIP metadata mismatch") + data = zip_bytes[row["artifact_id"]] + demand(type(data) is bytes and sha(data) == row["artifact_sha256"], + "source ZIP bytes do not equal GitHub sha256") + total += len(data) + demand(total <= 256*1024*1024, "aggregate producer ZIP budget exceeded") + members = inspect_zip(data) + manifest = spec["manifest"] + demand(manifest in members and sha(members[manifest]) == + row["manifest_sha256"], + "original producer manifest not retained") + verified[row["artifact_id"]] = { + "check_run_id":row["actual_check_run_id"], + "head_sha":head, "kind":kind, "shard":shard, "members":members} + return {"format":FORMAT,"status":"REAL_PRODUCING_BYTES_UNREVIEWED", + "head_sha":head,"producers":verified, + "producer_count":21,"accepted_cases":0,"accepted_claims":0, + "independent_review":False,"release_approved":False} + +def original_member(record, artifact_id, check_run_id, member, digest): + """Bind an individual receipt byte string to original producing ZIP.""" + demand(record.get("format") == FORMAT and + type(artifact_id) is int and type(check_run_id) is int and + isinstance(member,str) and isinstance(digest,str) and + re.fullmatch("[0-9a-f]{64}",digest), + "malformed receipt producing identity") + entry = record.get("producers",{}).get(artifact_id) + demand(entry is not None and entry.get("check_run_id") == check_run_id and + entry.get("head_sha") == record.get("head_sha"), + "wrong producing job for receipt") + data = entry["members"].get(member) + demand(data is not None and sha(data) == digest, + "receipt member not present in true producing archive") + return data diff --git a/verification/research/final_closure_producer_catalog.py b/verification/research/final_closure_producer_catalog.py new file mode 100644 index 00000000..155e4302 --- /dev/null +++ b/verification/research/final_closure_producer_catalog.py @@ -0,0 +1,275 @@ +"""Exact-head collection of ALL actual PR113 CI producing artifacts. + +This is a detached, read-only *candidate evidence* assembler, NOT an +independent case review or mathematical release. It authenticates the real +run/job/check/artifact SHA256 through GitHub for 4 compiler, 9 append-source, +and 8 legacy-original shards. Then it replays the existing full 45/11 theorem +shard merger on the retained compiler outputs. + +No ordinary 'research' green check is attributed to a separate candidate +compiler or mutation. No unchecked status flags become approvals. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re +import sys +import tempfile + +from final_closure_producer_v2_bridge import (ProducerBindingError, bind as bind_v2_producer) +from final_closure_producer_bytes import ( + authenticate as authenticate_producing_bytes, ProducerBytesError, +) +from final_closure_review_dossier import (ReviewDossierError, extracted_case_refs, + make_dossier) +from final_closure_mutation_evidence import committed_file_bytes +from final_closure_authenticate import git + +from final_closure_authenticate import AuthenticationError +from final_closure_candidate_artifact_auth import KINDS, verify_artifact +from final_closure_case_receipts import EvidenceError, load_detached_bundle +from final_closure_complete_lean_evidence import ROOT, git_head +from final_closure_merge_compiler_shards import merge, ShardMergeError +from final_closure_all_original_semantics import select_cases +from final_closure_legacy_semantic_evidence import original_case_map + +FORMAT = "Permansson.PR113.ExternalProducerCatalog.v1" +OUT_FORMAT = "Permansson.PR113.ExactHeadProducingEvidence.v1" +SHA40 = re.compile(r"^[a-f0-9]{40}$") +MAX_INPUT_BYTES = 64 * 1024 * 1024 +MAX_CATALOG_BYTES = 1024 * 1024 +MAX_TOTAL_BYTES = 256 * 1024 * 1024 + + +class ProducerCatalogError(ValueError): + pass + + +def need(ok, reason): + if not ok: + raise ProducerCatalogError(reason) + + +def digest(raw): + return hashlib.sha256(raw).hexdigest() + + +def entries(catalog, head): + """Check shape and full disjoint 4+9+8 coverage without network access.""" + need(isinstance(head, str) and SHA40.fullmatch(head), + "invalid source head") + need(isinstance(catalog, dict) and + catalog.get("format") == FORMAT and + catalog.get("head_sha") == head, + "catalog belongs to wrong source head or schema") + data = catalog.get("artifacts") + expected = {(kind, i) for kind, spec in KINDS.items() + for i in range(spec["count"])} + need(isinstance(data, list) and len(data) == len(expected), + "missing or extra required CI producer shards") + seen, ids, names, result = set(), set(), set(), [] + for row in data: + need(isinstance(row, dict) and + set(row) == {"kind", "shard", "run_id", "artifact_id", "zip_file"}, + "untyped producing artifact entry") + kind, shard = row["kind"], row["shard"] + need(type(kind) is str and type(shard) is int and + (kind, shard) in expected and (kind, shard) not in seen, + "duplicate/unexpected/missing producer shard") + seen.add((kind, shard)) + need(type(row["run_id"]) is int and row["run_id"] > 0 and + type(row["artifact_id"]) is int and row["artifact_id"] > 0 and + row["artifact_id"] not in ids, + "duplicate or malformed producing artifact ID") + ids.add(row["artifact_id"]) + name = row["zip_file"] + need(isinstance(name, str) and + re.fullmatch(r"[a-zA-Z0-9][a-zA-Z0-9_.-]{0,180}[.]zip", name) and + name not in (".zip", "..zip") and + "/" not in name and chr(92) not in name and + name not in names, + "unsafe, duplicated or nonflat detached artifact ZIP path") + names.add(name) + result.append(row) + need(seen == expected, "incomplete source-producer coverage") + return sorted(result, key=lambda r: (r["kind"], r["shard"])) + + +def collect(*, head, catalog_path: Path, output: Path): + catalog_path = Path(catalog_path) + output = Path(output) + root = ROOT.resolve() + need(catalog_path.is_file() and not catalog_path.is_symlink() and + not catalog_path.resolve().is_relative_to(root) and + 0 < catalog_path.stat().st_size <= MAX_CATALOG_BYTES, + "external, bounded and nonsymlinked catalog required") + input_dir = catalog_path.resolve().parent + output = output.resolve() + need(not output.exists() and + not output.is_relative_to(root) and + not output.is_relative_to(input_dir) and + not input_dir.is_relative_to(output), + "output must be a new, separate external evidence directory") + need(git_head() == head, "checked-out source is not requested H") + rows = entries(json.loads(catalog_path.read_bytes()), head) + total = 0 + trusted = [] + authentic_zip_bytes = {} + case_candidates = {} + with tempfile.TemporaryDirectory(prefix="pr113-producer-check-") as td: + shard_root = Path(td) / "compiler-shards" + shard_root.mkdir() + for row in rows: + artifact = input_dir / row["zip_file"] + need(artifact.is_file() and not artifact.is_symlink() and + artifact.resolve().parent == input_dir and + 0 < artifact.stat().st_size <= MAX_INPUT_BYTES, + "unsafe or missing actual producing artifact") + total += artifact.stat().st_size + need(total <= MAX_TOTAL_BYTES, "aggregate artifact size exceeded") + verified = verify_artifact( + kind=row["kind"], shard=row["shard"], head=head, + run_id=row["run_id"], artifact_id=row["artifact_id"], + artifact_zip=artifact) + need(verified["status"] == + "CI_ARTIFACT_DIGEST_VERIFIED_UNREVIEWED" and + verified["head_sha"] == head and + verified["artifact_id"] == row["artifact_id"], + "producing CI attestation is unverified or mismatched") + trusted.append(verified) + authentic_zip_bytes[row["artifact_id"]] = artifact.read_bytes() + need(digest(authentic_zip_bytes[row["artifact_id"]]) == + verified["artifact_sha256"], + "producer ZIP changed after live authentication") + members = load_detached_bundle( + artifact, verified["artifact_sha256"]) + if row["kind"] != "compiler": + candidates = extracted_case_refs(row["kind"], members, + head, verified) + need(not set(case_candidates) & set(candidates), + "same original case appeared in multiple candidate shards") + case_candidates.update(candidates) + if row["kind"] == "compiler": + folder = shard_root / ("shard-%02d" % row["shard"]) + need(not folder.exists(), "duplicate compiler shard directory") + folder.mkdir() + for member, content in members.items(): + target = folder.joinpath(*member.split("/")) + need(target.resolve().is_relative_to(folder.resolve()), + "unsafe compiler evidence member") + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(content) + output.mkdir(parents=True, exist_ok=False) + full = merge(shard_root, output / "compiler", head=head) + need(full["case_count"] == 45 and full["claim_count"] == 11 and + full["proof_count"] == full["inventory_count"], + "complete 45/11 compiler theorem coverage not proven") + # Second independent byte-level check against the actual GitHub + # producing run/job/check/artifact identity. The result is a source + # provenance candidate, NEVER reviewer or mathematical acceptance. + byte_auth = authenticate_producing_bytes( + head, trusted, authentic_zip_bytes) + need(byte_auth["producer_count"] == 21 and + byte_auth["accepted_cases"] == 0 and + byte_auth["accepted_claims"] == 0 and + byte_auth["independent_review"] is False, + "producer ZIP verifier fabricated acceptance") + member_index = { + "format": "Permansson.PR113.ProducerMemberIndexCandidate.v1", + "status": "AUTHENTICATED_UNREVIEWED", + "head_sha": head, + "members": [ + {"artifact_id": artifact_id, + "check_run_id": row["check_run_id"], + "entries": [ + {"name": name, "sha256": digest(payload)} + for name, payload in sorted(row["members"].items())]} + for artifact_id, row in sorted(byte_auth["producers"].items()) + ], + "independent_review": False, + "mathematical_acceptance": False, + } + member_index_bytes = ( + json.dumps(member_index, sort_keys=True, indent=2) + "\n").encode("utf-8") + (output / "unreviewed_original_producer_members.json").write_bytes( + member_index_bytes) + from final_closure_all_original_semantics import EXPECTED + original, legacy_cases = original_case_map(head) + need(len(original) == 45 and len(EXPECTED) == 25 and + len(legacy_cases) == 20 and + not (EXPECTED & set(legacy_cases)) and + set(original) == EXPECTED | set(legacy_cases), + "original 25+20 original-case partition changed") + claim_map_path = ROOT / ( + "research/paper_closures/post_merge_112/claim_declaration_map.json") + claim_map = json.loads(committed_file_bytes( + head, claim_map_path, root=ROOT)) + actual_tree = git("rev-parse", head + "^{tree}") + reviewed_dossier = make_dossier( + head=head, tree=actual_tree, original={"cases": [ + original[x] for x in sorted(original)]}, + claim_map=claim_map, compiler=full, + candidate_cases=case_candidates, producers=trusted) + dossier_bytes = (json.dumps(reviewed_dossier, sort_keys=True, + indent=2) + "\n").encode("utf-8") + (output / "unreviewed_source_review_dossier.json").write_bytes( + dossier_bytes) + typed_binding = bind_v2_producer( + head, reviewed_dossier, full, trusted) + typed_bytes = (json.dumps(typed_binding, sort_keys=True, + indent=2) + "\n").encode("utf-8") + (output / "unreviewed_v2_producer_binding.json").write_bytes( + typed_bytes) + report = { + "v2_producer_binding_sha256": digest(typed_bytes), + "source_producer_member_index_sha256": digest(member_index_bytes), + "source_review_dossier_sha256": digest(dossier_bytes), + "format": OUT_FORMAT, + "status": "CI_PRODUCER_ARTIFACTS_AUTHENTICATED_UNREVIEWED", + "head_sha": head, + "producing_artifact_count": len(trusted), + "compiler_targets_executed": full["proof_count"], + "original_cases_in_canonical_map": 45, + "claim_ids_in_canonical_map": 11, + "append_candidate_routes": 25, + "legacy_observation_routes": 20, + "producer_artifact_checks": trusted, + "merged_compiler_sha256": digest( + (output / "compiler" / "complete_compiler_candidate.json").read_bytes()), + "independent_source_review": False, + "semantic_criterion_acceptance": False, + "mathematical_release_approved": False, + "merged_main_sha": None, + } + (output / "unreviewed_producer_catalog.json").write_text( + json.dumps(report, sort_keys=True, indent=2) + "\n", + encoding="utf-8") + return report + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--head", required=True) + ap.add_argument("--catalog", type=Path, required=True) + ap.add_argument("--output", type=Path, required=True) + opts = ap.parse_args(argv) + try: + result = collect(head=opts.head, catalog_path=opts.catalog, + output=opts.output) + except (ProducerCatalogError, AuthenticationError, EvidenceError, + ShardMergeError, ReviewDossierError, ProducerBindingError, + ProducerBytesError, + OSError, KeyError, ValueError) as exc: + print("PR113 PRODUCER CATALOG BLOCKED:", exc, file=sys.stderr) + return 2 + print(json.dumps({k: result[k] for k in ( + "status", "head_sha", "producing_artifact_count", + "compiler_targets_executed")}, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_producer_v2_bridge.py b/verification/research/final_closure_producer_v2_bridge.py new file mode 100644 index 00000000..779dee70 --- /dev/null +++ b/verification/research/final_closure_producer_v2_bridge.py @@ -0,0 +1,308 @@ +"""Source-bound bridge between 21 authenticated CI producers and strict v2 review. + +The bridge binds each of 259 compiler receipts, 45 original semantic case +observations and 11 claims to the actual successful CI job that produced it. +It deliberately DOES NOT create v2 approval receipts: no historical verifier +is weakened, no global 'research' check is borrowed, and no independent +mathematical review is inferred from a green GitHub Actions job. +""" +from __future__ import annotations + +import hashlib +import json +import re + +SHA40 = re.compile(r"^[a-f0-9]{40}$") +SHA256 = re.compile(r"^[a-f0-9]{64}$") +AXIOMS = frozenset(("propext", "Classical.choice", "Quot.sound")) +WORKFLOWS = { + "compiler": (".github/workflows/pr113-all-case-compiler.yml", + "pr113-compiler-shard-", 4), + "append": (".github/workflows/pr113-all-original-semantic-candidates.yml", + "pr113-all-original-semantic-candidates-", 9), + "legacy": (".github/workflows/pr113-legacy-semantic-evidence.yml", + "pr113-legacy-semantic-candidates-", 8), +} +FORMAT = "Permansson.PR113.ProducerBoundV2Preparation.v1" + + +class ProducerBindingError(ValueError): + pass + + +def require(ok, reason): + if not ok: + raise ProducerBindingError(reason) + + +def canonical_sha(obj): + return hashlib.sha256(json.dumps( + obj, ensure_ascii=False, sort_keys=True, + separators=(",", ":")).encode("utf-8")).hexdigest() + + +def verified_producer_index(head, producers): + """Require the exact successful source-bound 4+9+8 producer partition.""" + require(isinstance(head, str) and SHA40.fullmatch(head), + "invalid exact source H") + require(isinstance(producers, list) and len(producers) == 21, + "all 21 authenticated producing shards are required") + by_shard = {} + ids, checks, artifacts = set(), set(), set() + for row in producers: + require(isinstance(row, dict) and row.get("head_sha") == head and + row.get("format") == + "Permansson.PR113.AuthenticatedProducerArtifact.v1" and + row.get("status") == + "CI_ARTIFACT_DIGEST_VERIFIED_UNREVIEWED" and + row.get("mathematical_acceptance") is False and + row.get("independent_review") is False and + row.get("scientific_release_approved") is False and + row.get("merged_main_sha") is None, + "stale/untrusted/approved producer attestation") + for key in ("artifact_id", "actual_check_run_id", + "job_id", "run_id", "run_attempt"): + require(type(row.get(key)) is int and row[key] > 0, + "missing actual producing identity: " + key) + for key in ("artifact_sha256", "manifest_sha256"): + require(isinstance(row.get(key), str) and + SHA256.fullmatch(row[key]), + "missing authoritative producing SHA256") + name = row.get("artifact_name") + path = row.get("workflow_path") + matched = [ + (kind, int(name[len(prefix):])) + for kind, (workflow, prefix, count) in WORKFLOWS.items() + if type(name) is str and name.startswith(prefix) + and name[len(prefix):].isdigit() + and type(path) is str and path.endswith(workflow) + and 0 <= int(name[len(prefix):]) < count + ] + require(len(matched) == 1, "unrecognized producing shard source") + key = matched[0] + require(key not in by_shard and row["artifact_id"] not in artifacts + and row["job_id"] not in ids + and row["actual_check_run_id"] not in checks, + "duplicate/shadowed check or artifact identity") + by_shard[key] = row + artifacts.add(row["artifact_id"]) + ids.add(row["job_id"]) + checks.add(row["actual_check_run_id"]) + expected = {(kind, idx) + for kind, (_workflow, _prefix, count) in WORKFLOWS.items() + for idx in range(count)} + require(set(by_shard) == expected, "missing or substituted producing shard") + return by_shard + + +def producer_identity(row): + return { + "workflow_path": row["workflow_path"], + "run_id": row["run_id"], + "run_attempt": row["run_attempt"], + "job_id": row["job_id"], + "actual_producing_check_run_id": row["actual_check_run_id"], + "artifact_id": row["artifact_id"], + "artifact_name": row["artifact_name"], + "artifact_sha256": row["artifact_sha256"], + "manifest_sha256": row["manifest_sha256"], + } + + +def bind(head, dossier, compiler, producers): + """Build a reviewable, NOT-APPROVED receipt provenance crosswalk.""" + source = verified_producer_index(head, producers) + require(isinstance(dossier, dict) and + dossier.get("format") == + "Permansson.PR113.SourceReviewDossierCandidate.v1" and + dossier.get("status") == + "UNREVIEWED_AUTHENTICATED_PRODUCERS_SCOPE_PENDING" and + dossier.get("head_sha") == head and + dossier.get("case_count") == 45 and + dossier.get("claim_count") == 11 and + dossier.get("compiler_target_count") == 259 and + dossier.get("producer_count") == 21 and + dossier.get("accepted_case_count") == 0 and + dossier.get("accepted_claim_count") == 0 and + dossier.get("independent_review") is False and + dossier.get("release_approved") is False, + "source review dossier is stale, incomplete or already promoted") + tree = dossier.get("tree_sha") + require(isinstance(tree, str) and SHA40.fullmatch(tree), + "missing real frozen source tree") + require(isinstance(compiler, dict) and + compiler.get("head_sha") == head and + compiler.get("status") == "COMPILER_ONLY_UNREVIEWED" and + compiler.get("proof_count") == 259 and + compiler.get("inventory_count") == 259 and + compiler.get("case_count") == 45 and + compiler.get("claim_count") == 11 and + compiler.get("authentic_ci_job_bound") is False and + compiler.get("independent_review") is False, + "full source-owner compiler inventory unavailable or promoted") + records = compiler.get("compiler_axiom_receipts") + require(isinstance(records, list) and len(records) == 259, + "missing exact 259 theorem records") + all_cases = set() + all_claims = set() + accepted_proofs = {} + bound_proofs = [] + for r in records: + require(isinstance(r, dict) and + type(r.get("inventory_index")) is int and + 0 <= r["inventory_index"] < 259 and + r.get("id") == f'all:{r["inventory_index"]:04d}' and + r.get("head_sha") == head and + r.get("compiler_check") == "research" and + r.get("compiler_result") == "PASS" and + r.get("check_run_id") is None and + r.get("axiom_log_member") == + f'axiom_logs/all_{r["inventory_index"]:04d}.log' and + isinstance(r.get("source_sha256"), str) and + SHA256.fullmatch(r["source_sha256"]) and + isinstance(r.get("axiom_log_sha256"), str) and + SHA256.fullmatch(r["axiom_log_sha256"]) and + isinstance(r.get("elaborated_type_sha256"), str) and + SHA256.fullmatch(r["elaborated_type_sha256"]) and + isinstance(r.get("universe_params_repr"), str) and + bool(r["universe_params_repr"]) and + isinstance(r.get("transitive_axioms"), list) and + len(r["transitive_axioms"]) == + len(set(r["transitive_axioms"])) and + set(r["transitive_axioms"]) <= AXIOMS and + isinstance(r.get("case_ids"), list) and + isinstance(r.get("claim_ids"), list) and + (r["case_ids"] or r["claim_ids"]), + "missing, borrowed or untrusted exact-H Lean source proof") + idx = r["inventory_index"] + require(idx not in accepted_proofs, "duplicate source theorem index") + accepted_proofs[idx] = r + all_cases.update(r["case_ids"]) + all_claims.update(r["claim_ids"]) + producer = source[("compiler", idx % 4)] + bound_proofs.append({ + "proof_id": r["id"], "inventory_index": idx, + "source_path": r["path"], + "qualified_declaration": r["declaration"], + "source_sha256": r["source_sha256"], + "elaborated_type_sha256": r["elaborated_type_sha256"], + "transitive_axioms": list(r["transitive_axioms"]), + "axiom_log_sha256": r["axiom_log_sha256"], + "case_ids": list(r["case_ids"]), + "claim_ids": list(r["claim_ids"]), + "authenticated_producer": producer_identity(producer), + "original_candidate_check_run_id": None, + "strict_v2_proof_receipt": "PENDING_REAL_PRODUCER_TYPED_CONTRACT", + "statement_header_sha256": None, + "reviewed_mathematical_scope": False, + }) + require(set(accepted_proofs) == set(range(259)), + "incomplete/displaced all-case theorem indices") + docket_cases = dossier.get("case_dockets") + docket_claims = dossier.get("claim_dockets") + require(isinstance(docket_cases, list) and len(docket_cases) == 45 and + isinstance(docket_claims, list) and len(docket_claims) == 11, + "case/claim review dockets incomplete") + ids = set() + bound_cases = [] + for row in docket_cases: + cid = row.get("case_id") if isinstance(row, dict) else None + require(isinstance(cid, str) and cid not in ids and + row.get("accepted_case_decision") is None and + row.get("semantic_scope_review_required") is True, + "missing or preapproved original case") + ids.add(cid) + candidate = row.get("producing_negative_candidate") + kind = candidate.get("route") if isinstance(candidate, dict) else None + require(kind in ("append", "legacy") and + isinstance(candidate.get("selected_semantic_methods"), list) + and bool(candidate["selected_semantic_methods"]), + "original case has no attributable semantic method") + artifact_id = candidate.get("producing_artifact_id") + producer = next((x for (k, _), x in source.items() + if k == kind and x["artifact_id"] == artifact_id), None) + require(producer is not None and + candidate.get( + "actual_producing_check_run_id_provenance_only") == + producer["actual_check_run_id"] and + candidate.get("accepted_semantic_negative") is False, + "semantic case borrowed unrelated producing check/ZIP") + proof_ids = [p["id"] for p in row.get("source_theorem_receipts", [])] + require(bool(proof_ids) and + all(any(cid in rec["case_ids"] and rec["id"] == proof_id + for rec in records) for proof_id in proof_ids), + "case lacks source-owned theorem evidence: " + cid) + bound_cases.append({ + "case_id": cid, "original_required_expected_result": + row.get("original_required_expected_result"), + "compiler_proof_ids": proof_ids, + "negative_selected_methods": + list(candidate["selected_semantic_methods"]), + "negative_kind": kind, + "authenticated_negative_producer": producer_identity(producer), + "legacy_typed_source_replay_required": kind == "legacy", + "strict_v2_positive_test_receipts": [], + "strict_v2_negative_test_receipts": [], + "independent_case_scope_review": None, + "accepted_case_decision": None, + }) + require(all_cases == ids and len(ids) == 45 and + sum(c["negative_kind"] == "append" for c in bound_cases) == 25 and + sum(c["negative_kind"] == "legacy" for c in bound_cases) == 20, + "case scope or original 25/20 partition changed") + known_claims = set() + bound_claims = [] + for row in docket_claims: + cid = row.get("claim_id") if isinstance(row, dict) else None + require(isinstance(cid, str) and cid not in known_claims and + row.get("accepted_claim_decision") is None and + row.get("positive_negative_claim_receipts") == "NOT_ASSEMBLED" and + row.get("independent_review") == "NOT_RECEIVED", + "claim scope not pending or duplicate") + known_claims.add(cid) + proof_ids = row.get("compiler_receipt_ids") + require(isinstance(proof_ids, list) and proof_ids and + all(any(cid in rec["claim_ids"] and rec["id"] == proof_id + for rec in records) for proof_id in proof_ids), + "claim missing one or more supporting compiler proofs") + bound_claims.append({ + "claim_id": cid, "compiler_proof_ids": proof_ids, + "selected_claim_tests": row.get("test_source_candidates", []), + "strict_v2_positive_test_receipts": [], + "strict_v2_negative_test_receipts": [], + "independent_claim_review": None, + "accepted_claim_decision": None, + }) + require(all_claims == known_claims and len(known_claims) == 11, + "original eleven claim owners incomplete") + return { + "format": FORMAT, + "status": "AUTHENTIC_PRODUCER_CROSSWALK_UNREVIEWED_NOT_V2_ACCEPTANCE", + "head_sha": head, "source_tree_sha": tree, + "source_review_dossier_sha256": canonical_sha(dossier), + "producer_artifact_count": 21, + "proof_record_count": len(bound_proofs), + "case_record_count": len(bound_cases), + "claim_record_count": len(bound_claims), + "proof_producers": bound_proofs, + "case_producers": bound_cases, + "claim_producers": bound_claims, + "accepted_cases": 0, "accepted_claims": 0, + "pending_explicit_review_scopes": 56, + "global_required_ci_green": False, + "strict_v2_ready": False, + "actual_merged_main_sha": None, + "mathematical_release_approved": False, + "independent_review": False, + "missing_v2_contract": [ + "real_producer_specific_check_authentication_in_strict_v2", + "source_statement_header_hashes_for_259_declarations", + "selected_positive_source_tests_for_45_original_cases", + "valid_source_append_replays_for_20_legacy_cases", + "individual_positive_and_negative_tests_for_11_new_claims", + "real_independent_exact_H_45_case_and_11_claim_review", + "all_required_source_checks_green", + "historical_tooling_T_C_authorized_resolution", + "actual_merge_M_postmerge_source_and_scholarly_authorization", + ], + } diff --git a/verification/research/final_closure_receipt_migrate.py b/verification/research/final_closure_receipt_migrate.py new file mode 100644 index 00000000..06515d5e --- /dev/null +++ b/verification/research/final_closure_receipt_migrate.py @@ -0,0 +1,96 @@ +"""Fail-closed legacy receipt dialect migration into UNVERIFIED candidates. + +Never turn old schema fields, free-form axiom lists or source_ci_receipts +into v2 authenticated evidence. A real compiler run, live CI check ID and +external non-author exact-H review are required after candidate migration. +""" +from __future__ import annotations +import argparse +import json +from pathlib import Path +import re +import sys + +from final_closure_case_receipts import ROOT + +SHA256 = re.compile(r"^[0-9a-f]{64}$") + + +class MigrationError(ValueError): + pass + + +def statement_candidate(old: dict): + if not isinstance(old, dict): + raise MigrationError("old statement must be an object") + source = old.get("source_sha256") + declared = old.get("statement_sha256") + if not (isinstance(source, str) and SHA256.fullmatch(source) and + isinstance(declared, str) and SHA256.fullmatch(declared)): + raise MigrationError("missing authentic historical bytes") + if not isinstance(old.get("declaration"), str) or not old["declaration"]: + raise MigrationError("missing named historical declaration") + axioms = old.get("axiom_allowlist") + if axioms is not None and not isinstance(axioms, list): + raise MigrationError("malformed legacy allowed axiom list") + return { + "format": "Permansson.LegacyStatementReviewCandidate.v2", + "declaration": old["declaration"], + "path": old.get("path"), + "head_sha_candidate": old.get("reviewed_head_sha"), + "source_sha256": source, + "statement_sha256": declared, + "untrusted_legacy_axiom_allowlist": axioms, + "untrusted_legacy_compiler_run_url": old.get("compiler_run_url"), + "transitive_axioms": None, + "compiler_result": "NOT_VERIFIED", + "compiler_check": None, + "check_run_id": None, + "semantic_negative_receipts": [], + "independent_review_receipts": [], + "status": "LEGACY_SOURCE_CANDIDATE_ONLY_NOT_ACCEPTED", + } + + +def source_receipt_candidate(old: dict): + if not isinstance(old, dict) or not old.get("base_sha"): + raise MigrationError("legacy source template lacks baseline") + return { + "format": "Permansson.LegacySourceReceiptCandidate.v2", + "base_sha_candidate": old["base_sha"], + "head_sha_candidate": old.get("head_sha"), + "synthetic_sha_candidate": old.get("synthetic_sha"), + "merged_sha_candidate": old.get("merged_main_sha"), + "unverified_legacy_ci_receipts": old.get("source_ci_receipts", []), + "source_bound_ci": [], + "compiler_axiom_receipts": [], + "semantic_negative_receipts": [], + "independent_source_reviews": [], + "status": "UNVERIFIED_LEGACY_SOURCE_TEMPLATE", + } + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument("--input", type=Path, required=True) + parser.add_argument("--type", choices=["statement", "source"], required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args(argv) + if args.output.resolve().is_relative_to(ROOT.resolve()): + print("MIGRATION BLOCKED: output must be detached from source HEAD") + return 2 + try: + old = json.loads(args.input.read_text()) + result = (statement_candidate(old) if args.type == "statement" + else source_receipt_candidate(old)) + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(result, indent=2, sort_keys=True)+"\n") + except (OSError, ValueError) as exc: + print("MIGRATION BLOCKED:", exc) + return 2 + print(json.dumps({"status": result["status"]})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/final_closure_reproduction_auth.py b/verification/research/final_closure_reproduction_auth.py new file mode 100644 index 00000000..04672d07 --- /dev/null +++ b/verification/research/final_closure_reproduction_auth.py @@ -0,0 +1,198 @@ +"""Authenticate REAL merged-M, two-source-root reproducibility GitHub artifact. + +This is not a scholarly signoff. It binds the actual successful Actions run, +job/check, authoritative artifact SHA256, retained receipt and TWO independently +built byte-identical PDFs, archives, manifests and rendered pages. The target +workflow may be dispatched on main only after genuine authorized M exists. +No Boolean claimed inside producer JSON grants authentication. +""" +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +import re + +from final_closure_authenticate import live, paginate +from final_closure_producer_bytes import inspect_zip, ProducerBytesError + +FORMAT = "Permansson.PR113.GenuineCleanReproductionCI.v1" +WORKFLOW = ".github/workflows/pr113-clean-paper-rebuild.yml" +JOB = "clean-paper-rebuild" +ARTIFACT = "pr113-clean-paper-rebuild-draft" +SHA40 = re.compile(r"^[0-9a-f]{40}$") +SHA256 = re.compile(r"^[0-9a-f]{64}$") + + +class ReproductionAuthenticationError(ValueError): + pass + + +def demand(ok, message): + if not ok: + raise ReproductionAuthenticationError(message) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def exact(payload, name): + demand(name in payload, "missing original clean root artifact: " + name) + return payload[name] + + +def validate_clean_root_bytes(members, *, merged, expected_pdf): + """Check canonical execution metadata AND both actual byte-identical roots.""" + try: + report = json.loads(exact(members, "reproduction_receipt.json")) + identity = json.loads(exact(members, "ci_execution_identity.json")) + except (TypeError, UnicodeDecodeError, ValueError) as exc: + raise ReproductionAuthenticationError("malformed real reproduction receipt") from exc + demand(isinstance(report, dict) and + report.get("format") == "Permansson.TwoCleanSourceRebuilds.v1" and + report.get("status") == "REPRODUCIBLE_DRAFT_ONLY" and + report.get("source_commit_sha") == merged and + report.get("independent_source_roots") == 2 and + report.get("git_clone_mode") == "local_no_hardlinks_no_checkout" and + report.get("rendered_pages") is True and + report.get("two_complete_archive_audits") is True and + report.get("pdf_sha256") == expected_pdf and + report.get("authenticated_ci_artifact") is False and + report.get("scientific_release_approved") is False, + "actual two-source-root receipt is incomplete or self-approved") + comparison = report.get("byte_comparison") + demand(isinstance(comparison,dict) and + comparison.get("status") == "REPRODUCIBLE_DRAFT_ONLY" and + comparison.get("source_commit_sha") == merged and + comparison.get("pdf_sha256") == expected_pdf, + "clean source-root comparison not genuinely complete") + demand(identity.get("format") == "Permansson.CICleanRebuildCandidate.v1" + and identity.get("status") == "EXECUTED_DRAFT_ONLY_NO_SCHOLARLY_REVIEW" + and identity.get("source_head") == merged + and identity.get("github_repository") == "HMarcusWH/PermanssonLean" + and identity.get("github_job") == JOB + and identity.get("pdf_sha256") == expected_pdf + and identity.get("authenticated_ci_artifact") is False, + "uploaded exact-run identity is incorrect or promoted") + files = { + "Permansson_Mathematical_Closure.pdf":expected_pdf, + "Permansson_Mathematical_Closure_E2E.zip":report["archive_sha256"], + "manifest.lock.json":report["manifest_sha256"], + } + for relative,digest in files.items(): + demand(isinstance(digest,str) and SHA256.fullmatch(digest), + "source-root digest invalid") + a=exact(members,"first/"+relative) + b=exact(members,"second/"+relative) + demand(a == b and sha(a) == digest, + "two source roots do not contain identical real artifacts") + import json as _json + manifest=_json.loads(members["first/manifest.lock.json"]) + demand(manifest.get("source_commit_sha") == merged and + manifest.get("status") == "DRAFT_NOT_ACCEPTED" and + manifest.get("pdf_sha256") == expected_pdf and + isinstance(manifest.get("pdf_page_count"), int) and + manifest.get("pdf_page_count") > 0, + "reproduced source manifest malformed or incorrectly approved") + for item in ("Permansson_Mathematical_Closure.tex", + "latex_build.log", "SHA256SUMS.txt", "source_ci_receipts.lock.json"): + demand(exact(members,"first/"+item) == + exact(members,"second/"+item), + "canonical draft artifact differs between source roots: "+item) + pagesA={path[len("first/pages/"):]:data + for path,data in members.items() if path.startswith("first/pages/") + and path.endswith(".png")} + pagesB={path[len("second/pages/"):]:data + for path,data in members.items() if path.startswith("second/pages/") + and path.endswith(".png")} + demand(len(pagesA) == manifest["pdf_page_count"] and + pagesA and pagesA == pagesB and + all(re.fullmatch(r"page-[0-9]+[.]png", name) for name in pagesA), + "missing/duplicated/non-identical actual rendered PDF pages") + return report, identity, pagesA + + +def authenticate_merged_reproduction(merged, expected_pdf, identities, + zip_path: Path): + """Verify authentic GitHub run+check+artifact, then retained original bytes.""" + demand(isinstance(merged,str) and SHA40.fullmatch(merged) and + isinstance(expected_pdf,str) and SHA256.fullmatch(expected_pdf) + and isinstance(identities,dict), + "reproduction requires real M/PDF and typed producer identities") + for key in ("run_id","run_attempt","job_id","check_run_id","artifact_id"): + demand(type(identities.get(key)) is int and identities[key] > 0, + "missing real producing GitHub identity: "+key) + run=live("/actions/runs/"+str(identities["run_id"])) + demand(run.get("id") == identities["run_id"] and + run.get("head_sha") == merged and + run.get("head_branch") == "main" and + run.get("run_attempt") == identities["run_attempt"] and + run.get("status") == "completed" and + run.get("conclusion") == "success" and + run.get("event") in ("push","workflow_dispatch") and + (run.get("path") == WORKFLOW or + str(run.get("path","")).endswith("/"+WORKFLOW)), + "clean-paper producer is not genuinely successful on merged main M") + jobs=paginate("/actions/runs/"+str(identities["run_id"])+"/jobs",key="jobs") + matches=[j for j in jobs if j.get("id") == identities["job_id"] + and j.get("name") == JOB] + demand(len(matches)==1 and + matches[0].get("run_id") == identities["run_id"] and + matches[0].get("run_attempt") == identities["run_attempt"] and + matches[0].get("head_sha") == merged and + matches[0].get("status") == "completed" and + matches[0].get("conclusion") == "success" and + str(matches[0].get("check_run_url","")).endswith( + "/check-runs/"+str(identities["check_run_id"])), + "producer job/check does not match original clean rebuild") + check=live("/check-runs/"+str(identities["check_run_id"])) + demand(check.get("id") == identities["check_run_id"] and + check.get("head_sha") == merged and + check.get("name") == JOB and + check.get("status") == "completed" and + check.get("conclusion") == "success" and + (check.get("app") or {}).get("slug") == "github-actions", + "actual clean rebuild GitHub check not successful") + artifact=live("/actions/artifacts/"+str(identities["artifact_id"])) + gh_digest=artifact.get("digest") + demand(artifact.get("id") == identities["artifact_id"] and + artifact.get("name") == ARTIFACT and + artifact.get("expired") is False and + (artifact.get("workflow_run") or {}).get("id") == + identities["run_id"] and + (artifact.get("workflow_run") or {}).get("head_sha") == merged and + isinstance(gh_digest,str) and gh_digest.startswith("sha256:") and + SHA256.fullmatch(gh_digest[7:]), + "missing genuine GitHub artifact SHA256 at exact merged M") + input_path=Path(zip_path) + demand(input_path.is_file() and not input_path.is_symlink() and + not any(parent.is_symlink() for parent in input_path.absolute().parents), + "detached clean source-root ZIP must be a regular unsymlinked file") + raw=input_path.read_bytes() + demand(sha(raw) == gh_digest[7:], + "downloaded two-root ZIP bytes differ from GitHub digest") + try: + members=inspect_zip(raw) + except ProducerBytesError as exc: + raise ReproductionAuthenticationError("unsafe original two-root ZIP") from exc + report,identity,pages=validate_clean_root_bytes( + members,merged=merged,expected_pdf=expected_pdf) + demand(identity.get("github_run_id") == identities["run_id"] and + identity.get("github_run_attempt") == identities["run_attempt"] and + identity.get("archive_sha256") == report["archive_sha256"], + "retained CI execution file belongs to another job/attempt") + return { + "format": FORMAT, + "status":"TWO_CLEAN_SOURCE_BUILDS", + "source_commit_sha":merged, "pdf_sha256":expected_pdf, + "artifact_sha256":gh_digest[7:], + "authenticated_ci_artifact":True, + "source_tree_sha256":None, + "rendered_page_count":len(pages), + "run_id":identities["run_id"],"run_attempt":identities["run_attempt"], + "job_id":identities["job_id"],"check_run_id":identities["check_run_id"], + "artifact_id":identities["artifact_id"], + "scholarly_review_approved":False, + "mathematical_release_approved":False, + } diff --git a/verification/research/final_closure_review_dossier.py b/verification/research/final_closure_review_dossier.py new file mode 100644 index 00000000..06a6ad90 --- /dev/null +++ b/verification/research/final_closure_review_dossier.py @@ -0,0 +1,293 @@ +"""Fail-closed review dossier for complete, authenticated PR113 producer artifacts. + +Every output is an UNREVIEWED candidate, not a v2 receipt or scope decision. +The live producing-job and artifact SHA-256 verification MUST happen in the +caller, final_closure_producer_catalog.collect, before using these helpers. +No author-written JSON or synthetic check ID becomes mathematical acceptance. +""" +from __future__ import annotations + +import hashlib +import json +import re + +SHA40 = re.compile(r"^[0-9a-f]{40}$") +CLAIMS = {"Q1", "Q2", "Q3", "M1", "M2", "M3", "N1", "N2", "N3", "D8", "D9"} +AXIOMS = {"propext", "Classical.choice", "Quot.sound"} + + +class ReviewDossierError(ValueError): + pass + + +def need(value, message): + if not value: + raise ReviewDossierError(message) + + +def digest(raw): + return hashlib.sha256(raw).hexdigest() + + +def extracted_case_refs(kind, members, head, producer): + """Validate preserved candidate inputs without certifying their semantics.""" + def get(name): + need(isinstance(name, str) and name in members, + "raw source/diagnostic member missing: " + str(name)) + return members[name] + + def doc(name): + try: + parsed = json.loads(get(name)) + except (ValueError, TypeError) as exc: + raise ReviewDossierError("invalid candidate JSON: " + name) from exc + need(isinstance(parsed, dict), "candidate JSON is not an object") + return parsed + + need(kind in ("append", "legacy") and producer.get("head_sha") == head and + producer.get("status") == "CI_ARTIFACT_DIGEST_VERIFIED_UNREVIEWED" and + producer.get("independent_review") is False and + type(producer.get("actual_check_run_id")) is int, + "no real unreviewed producer attestation") + manifest = doc("unreviewed_shard_manifest.json") + result = {} + if kind == "append": + need(manifest.get("head") == head and manifest.get("status") == + "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED_RECEIPT" and + manifest.get("ci_check_run_id") is None and + manifest.get("independent_review") is False, + "append candidate identity/status invalid") + for cid in manifest.get("case_ids", []): + need(isinstance(cid, str) and cid not in result, + "duplicate or invalid append case") + record = doc(cid + "/unreviewed_candidate.json") + proof = record.get("mutation_record") + need(isinstance(proof, dict) and record.get("case_id") == cid and + record.get("source_head") == head and + record.get("ci_check_run_id") is None and + record.get("independent_review") is False and + proof.get("head_sha") == head and + proof.get("case_ids") == [cid] and + proof.get("outcome") == "REJECTED_SEMANTIC" and + proof.get("check_run_id") is None, + "append candidate forged, stale or not rejected") + prefix = cid + "/source_logs/" + original = get(prefix + proof["original_lean_source_member"]) + mutated = get(prefix + proof["mutant_source_member"]) + negative = get(prefix + proof["log_member"]) + positive_record = proof["positive_control"] + positive = get(prefix + positive_record["log_member"]) + need(mutated.startswith(original) and len(mutated) > len(original) and + digest(original) == proof["original_lean_source_sha256"] and + digest(mutated) == proof["mutant_source_sha256"] and + digest(negative) == proof["log_sha256"] and + digest(positive) == positive_record["log_sha256"] and + positive_record.get("compiled") is True and + positive_record.get("exit_code") == 0 and + positive_record.get("lean_toolchain") == "4.34.0", + "true/false source bytes or compiler logs not retained") + appended = mutated[len(original):].decode("utf-8") + need(not re.search( + r"(?mi)^\s*(?:import|axiom|constant|opaque|set_option)\b", + appended) and not re.search( + r"(?i)\b(?:sorry|sorryAx|admit)\b", appended), + "false source changes imports, axioms or admissions") + need(digest(get(cid + "/semantic_candidate_logs.zip")) == + record.get("detached_bundle_sha256"), + "retained nested mutation ZIP digest mismatch") + result[cid] = { + "route": "append", + "selected_semantic_methods": [proof["test_name"]], + "test_path": proof["test_path"], + "original_source_path": proof["original_lean_source_path"], + "original_source_sha256": digest(original), + "positive_log_sha256": digest(positive), + "negative_log_sha256": digest(negative), + "producing_artifact_id": producer["artifact_id"], + "actual_producing_check_run_id_provenance_only": + producer["actual_check_run_id"], + "independent_criterion_review": "NOT_RECEIVED", + "accepted_semantic_negative": False, + } + else: + need(manifest.get("source_head") == head and manifest.get("status") == + "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED" and + manifest.get("ci_check_run_id") is None and + manifest.get("independent_review") is False, + "legacy candidate identity/status invalid") + case_rows = manifest.get("case_reports") + need(isinstance(case_rows, list) and + [x.get("case_id") for x in case_rows if isinstance(x, dict)] == + manifest.get("case_ids"), "legacy case coverage differs from manifest") + for case in case_rows: + cid = case["case_id"] + need(cid not in result, "duplicate legacy case") + methods = [] + events = [] + for i, item in enumerate(case.get("tests", [])): + prefix = f"{cid}/test-{i:02d}/" + row = doc(prefix + "unreviewed_test_manifest.json") + need(row == item and row.get("case_id") == cid and + row.get("status") == + "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED" and + row.get("test_exit_code") == 0 and + row.get("ci_check_run_id") is None and + row.get("independent_review") is False and + not row.get("semantic_methods_missing_candidate"), + "legacy named method not executed or missing semantic diagnostic") + need(digest(get(prefix + "test_stdout.log")) == + row["stdout_sha256"] and + digest(get(prefix + "test_stderr.log")) == + row["stderr_sha256"], "legacy unittest log hash mismatch") + methods += row["selected_methods"] + actually_observed = set() + for event in row["compiler_events"]: + loc = prefix + "raw_compiler/" + event["observation_id"] + "/" + need(digest(get(loc + "observation.json")) == + event["raw_metadata_sha256"], + "legacy compiler observation metadata changed") + if event.get("source_retained"): + need(digest(get(loc + "source.lean")) == + event["source_sha256"], + "legacy original Lean source observation changed") + if event.get("possible_semantic_diagnostic"): + need(event.get("method_attribution_ambiguous") is False + and event.get("method_observed_unverified") + in row["selected_methods"], + "class-level legacy diagnostic borrowed as a method") + method = event["method_observed_unverified"] + actually_observed.add(method) + events.append({ + "method": method, + "observation_id": event["observation_id"], + "source_sha256": event["source_sha256"], + "original_provider_candidates_unreviewed": + event.get("original_provider_candidates_unreviewed", []), + }) + need(actually_observed == set(row["selected_methods"]), + "no individually attributable legacy diagnostic: " + cid) + need(methods and len(methods) == len(set(methods)), + "missing/duplicate selected legacy methods") + result[cid] = { + "route": "legacy", + "selected_semantic_methods": sorted(methods), + "retained_semantic_events": events, + "source_appended_replay_required": True, + "producing_artifact_id": producer["artifact_id"], + "actual_producing_check_run_id_provenance_only": + producer["actual_check_run_id"], + "independent_criterion_review": "NOT_RECEIVED", + "accepted_semantic_negative": False, + } + return result + + +def make_dossier(head, tree, original, claim_map, compiler, candidate_cases, + producers): + """Bind every canonical criterion to review references, never approvals.""" + need(isinstance(head, str) and SHA40.fullmatch(head) and + isinstance(tree, str) and SHA40.fullmatch(tree), + "bad reviewed source/tree identity") + origins = original.get("cases") if isinstance(original, dict) else None + claims = claim_map.get("claims") if isinstance(claim_map, dict) else None + proofs = compiler.get("compiler_axiom_receipts") if isinstance(compiler, dict) else None + need(isinstance(origins, list) and len(origins) == 45 and + isinstance(claims, list) and len(claims) == 11 and + isinstance(proofs, list) and len(proofs) == 259, + "45/11/259 theorem acceptance graph is incomplete") + case_ids = {x.get("case_id") for x in origins} + claim_ids = {x.get("claim_id") for x in claims} + need(len(case_ids) == 45 and claim_ids == CLAIMS and + isinstance(candidate_cases, dict) and set(candidate_cases) == case_ids, + "missing or substituted canonical cases/claims") + need(sum(x.get("route") == "append" for x in candidate_cases.values()) == 25 + and sum(x.get("route") == "legacy" for x in candidate_cases.values()) == 20, + "original 25+20 case partition changed") + need(isinstance(producers, list) and len(producers) == 21 and + len({p.get("artifact_id") for p in producers}) == 21 and + all(p.get("status") == "CI_ARTIFACT_DIGEST_VERIFIED_UNREVIEWED" + and p.get("head_sha") == head + and type(p.get("actual_check_run_id")) is int + and p.get("independent_review") is False + and p.get("mathematical_acceptance") is False + for p in producers), "actual exact-H producing artifacts unavailable") + seen = set() + for row in proofs: + key = (row.get("path"), row.get("declaration")) + need(key not in seen and row.get("head_sha") == head + and row.get("compiler_result") == "PASS" + and row.get("check_run_id") is None + and set(row.get("transitive_axioms", [])) <= AXIOMS, + "invalid borrowed theorem or axiom list") + seen.add(key) + need({c for r in proofs for c in r.get("case_ids", [])} == case_ids and + {c for r in proofs for c in r.get("claim_ids", [])} == claim_ids, + "uncovered case or claim owners") + docket = [] + for item in sorted(origins, key=lambda x: x["case_id"]): + cid = item["case_id"] + applicable = [p for p in proofs if cid in p.get("case_ids", [])] + requirements = item.get("historical_proof_declarations") + need(isinstance(requirements, list) and requirements + and all(any(p["path"] == req.get("path") and + (p["declaration"] == req.get("name") or + p["declaration"].endswith("." + req.get("name", ""))) + for p in applicable) for req in requirements), + "original selected theorem not compiled for: " + cid) + routes = item.get("test_method_candidates", []) + candidate = candidate_cases[cid] + methods = candidate.get("selected_semantic_methods") + need(isinstance(methods, list) and bool(methods) + and len(set(methods)) == len(methods) + and all(any(r.get("role") == "SEMANTIC_NEGATIVE" + and r.get("method") == method for r in routes) + for method in methods), + "case semantic negative not registered to original wording: " + cid) + docket.append({ + "case_id": cid, + "original_required_expected_result": + item.get("required_expected_result"), + "current_candidate_decision": item.get("current_candidate_decision"), + "historical_remaining_obligations": item.get("remaining_obligations", []), + "source_theorem_receipts": [ + {k: p[k] for k in ("id", "path", "declaration", + "elaborated_type_sha256", "axiom_log_sha256")} + for p in applicable], + "registered_positive_methods": [ + x for x in routes if x.get("role") in ("COMPILER_SOURCE", "EXACT_CHECK")], + "registered_negative_methods": [ + x for x in routes if x.get("role") == "SEMANTIC_NEGATIVE"], + "producing_negative_candidate": candidate, + "semantic_scope_review_required": True, + "accepted_case_decision": None, + }) + claim_docket = [] + for item in sorted(claims, key=lambda x: x["claim_id"]): + cid = item["claim_id"] + applicable = [p for p in proofs if cid in p.get("claim_ids", [])] + required = item.get("declarations") + need(isinstance(required, list) and bool(required) + and all(any(p["path"] == x.get("path") + and p["declaration"] == x.get("declaration") + for p in applicable) for x in required), + "original claim support missing: " + cid) + claim_docket.append({ + "claim_id": cid, "registered_declarations": required, + "test_source_candidates": item.get("test_source_candidates", []), + "compiler_receipt_ids": [p["id"] for p in applicable], + "original_case_links": item.get("original_case_links", []), + "positive_negative_claim_receipts": "NOT_ASSEMBLED", + "independent_review": "NOT_RECEIVED", + "accepted_claim_decision": None, + }) + return { + "format": "Permansson.PR113.SourceReviewDossierCandidate.v1", + "status": "UNREVIEWED_AUTHENTICATED_PRODUCERS_SCOPE_PENDING", + "head_sha": head, "tree_sha": tree, + "case_count": 45, "claim_count": 11, "compiler_target_count": 259, + "producer_count": 21, + "case_dockets": docket, "claim_dockets": claim_docket, + "accepted_case_count": 0, "accepted_claim_count": 0, + "independent_review": False, "required_ci_accepted": False, + "postmerge_source": None, "release_approved": False, + } diff --git a/verification/research/final_closure_schema.py b/verification/research/final_closure_schema.py new file mode 100644 index 00000000..d86c2277 --- /dev/null +++ b/verification/research/final_closure_schema.py @@ -0,0 +1,130 @@ +"""Validate the exact bounded detached-receipt v2 schema without optional packages. + +Only the schema keywords in this repository's v2 dialect are supported. +Unknown keywords fail closed. Passing this validator does not authenticate CI, +Lean proofs, Git objects, or independent reviews. +""" +from __future__ import annotations + +import json +from pathlib import Path +import re + +from final_closure_case_receipts import EvidenceError, ROOT + +SCHEMA = ROOT / "research/paper_closures/post_merge_112/detached_receipts_v2.schema.json" +SCHEMA_V3 = ROOT / "research/paper_closures/post_merge_112/detached_receipts_v3.schema.json" +FORMAT_V3 = "Permansson.FinalClosureDetachedReceipts.v3" +KEYWORDS = frozenset({ + "$schema", "$id", "title", "description", "type", "properties", + "additionalProperties", "required", "items", "prefixItems", "const", + "enum", "pattern", "minimum", "minLength", "minItems", "maxItems", + "uniqueItems", "oneOf", "allOf", "if", "then", +}) + + +def _check_spec(schema, where="$"): + if not isinstance(schema, dict): + raise EvidenceError(f"malformed receipt schema at {where}") + unknown = set(schema) - KEYWORDS + if unknown: + raise EvidenceError(f"unsupported receipt schema keyword at {where}: {sorted(unknown)}") + for key, value in schema.get("properties", {}).items(): + _check_spec(value, where + "." + key) + for key in ("items", "if", "then"): + if key in schema: + _check_spec(schema[key], where + "." + key) + for key in ("prefixItems", "oneOf", "allOf"): + for idx, item in enumerate(schema.get(key, [])): + _check_spec(item, f"{where}.{key}[{idx}]") + + +def _type_matches(value, kind): + checks = { + "object": type(value) is dict, + "array": type(value) is list, + "string": type(value) is str, + "integer": type(value) is int, + "number": type(value) in (int, float), + "boolean": type(value) is bool, + "null": value is None, + } + if kind not in checks: + raise EvidenceError("unsupported receipt schema type: " + str(kind)) + return checks[kind] + + +def _matches(value, schema): + try: + _validate(value, schema) + return True + except EvidenceError: + return False + + +def _validate(value, schema, where="$"): + if "type" in schema and not _type_matches(value, schema["type"]): + raise EvidenceError(f"{where}: invalid JSON type") + if "const" in schema and ( + type(value) is not type(schema["const"]) or value != schema["const"]): + raise EvidenceError(f"{where}: wrong required value") + if "enum" in schema and not any( + type(value) is type(x) and value == x for x in schema["enum"]): + raise EvidenceError(f"{where}: unexpected enum value") + if "oneOf" in schema and sum(_matches(value, s) for s in schema["oneOf"]) != 1: + raise EvidenceError(f"{where}: ambiguous or missing variant") + for sub in schema.get("allOf", []): + _validate(value, sub, where) + if "if" in schema and _matches(value, schema["if"]) and "then" in schema: + _validate(value, schema["then"], where) + if type(value) is str: + if len(value) < schema.get("minLength", 0): + raise EvidenceError(f"{where}: short string") + if "pattern" in schema and re.search(schema["pattern"], value) is None: + raise EvidenceError(f"{where}: invalid string") + if type(value) in (int, float) and "minimum" in schema: + if value < schema["minimum"]: + raise EvidenceError(f"{where}: below minimum") + if type(value) is dict: + if any(x not in value for x in schema.get("required", [])): + raise EvidenceError(f"{where}: required property missing") + allowed = schema.get("properties", {}) + if schema.get("additionalProperties") is False and set(value) - set(allowed): + raise EvidenceError(f"{where}: unknown property") + for key, sub in allowed.items(): + if key in value: + _validate(value[key], sub, where + "." + key) + if type(value) is list: + if len(value) < schema.get("minItems", 0) or ( + "maxItems" in schema and len(value) > schema["maxItems"]): + raise EvidenceError(f"{where}: wrong array length") + if schema.get("uniqueItems"): + encoded = [json.dumps(x, sort_keys=True, ensure_ascii=False) + for x in value] + if len(encoded) != len(set(encoded)): + raise EvidenceError(f"{where}: duplicate item") + for i, sub in enumerate(schema.get("prefixItems", [])): + if i < len(value): + _validate(value[i], sub, f"{where}[{i}]") + if "items" in schema: + for i, item in enumerate(value): + _validate(item, schema["items"], f"{where}[{i}]") + + +def validate_overlay(payload): + """Schema gate for input/output; never converts candidates into approvals.""" + selected = SCHEMA_V3 if isinstance(payload, dict) and ( + payload.get("format") == FORMAT_V3) else SCHEMA + try: + schema = json.loads(selected.read_text(encoding="utf-8")) + except (OSError, ValueError) as exc: + raise EvidenceError("canonical detached receipt schema unavailable") from exc + _check_spec(schema) + _validate(payload, schema) + for family in ("compiler_axiom_receipts", "positive_test_receipts", + "semantic_negative_receipts"): + for receipt in payload[family]: + if not receipt["case_ids"] and not receipt["claim_ids"]: + raise EvidenceError( + "unowned proof/test receipt in " + family) + return payload diff --git a/verification/research/final_closure_v3_source_readiness.py b/verification/research/final_closure_v3_source_readiness.py new file mode 100644 index 00000000..f668f23e --- /dev/null +++ b/verification/research/final_closure_v3_source_readiness.py @@ -0,0 +1,198 @@ +"""Full PR113 v3 candidate type, source, route and producer validation. + +This checker returns only source-evidence readiness, NEVER case/claim approval. +It is invoked *after* all 21 real GitHub producer ZIPs are reauthenticated. +A mathematically unrelated, successfully compiled test is not a valid original +COMPILER_SOURCE/EXACT_CHECK or SEMANTIC_NEGATIVE route. +""" +from __future__ import annotations + +from final_closure_case_receipts import ( + EvidenceError, validate_compiler_receipt, validate_test_receipt, +) + +CLAIMS = frozenset(("Q1", "Q2", "Q3", "M1", "M2", "M3", + "N1", "N2", "N3", "D8", "D9")) +POSITIVE_ROLES = frozenset(("COMPILER_SOURCE", "EXACT_CHECK")) + + +def need(condition, message): + if not condition: + raise EvidenceError(message) + + +def indexed(rows, kind): + need(isinstance(rows, list) and all(isinstance(x, dict) for x in rows), + "invalid " + kind + " rows") + result = {} + for row in rows: + key = row.get("id") + need(isinstance(key, str) and key and key not in result, + "duplicate or missing " + kind + " id") + result[key] = row + return result + + +def supports_identity(row, required): + """Full source path + qualified Lean declaration, not an owner label.""" + path, name = required.get("path"), required.get("declaration") + if name is None: + name = required.get("name") + return (isinstance(name, str) and row.get("path") == path and + (row.get("declaration") == name or + str(row.get("declaration", "")).endswith("." + name))) + return (isinstance(name, str) and + row.get("path") == path and row.get("declaration") == name) + + +def _original_roles(case): + routes = case.get("test_method_candidates") + need(isinstance(routes, list) and routes, + "original method registry unavailable: " + str(case.get("case_id"))) + lookup = {} + for row in routes: + need(isinstance(row, dict) and + row.get("role") in (POSITIVE_ROLES | {"SEMANTIC_NEGATIVE"}) and + isinstance(row.get("path"), str) and + isinstance(row.get("method"), str), + "invalid original case method/role registry") + key = (row["path"], row["method"]) + need(key not in lookup or lookup[key] == row["role"], + "ambiguous original method role: " + str(case.get("case_id"))) + lookup[key] = row["role"] + return lookup + + +def route_coverage(overlay, original_map, claim_map): + """Require declared route roles/required theorem pairs, not ID intersections. + + Call only AFTER all proof/test source bytes and true GitHub producer jobs + have passed validate_compiler_receipt / validate_test_receipt. + """ + cases = original_map.get("cases") if isinstance(original_map, dict) else None + claims = claim_map.get("claims") if isinstance(claim_map, dict) else None + need(isinstance(cases, list) and len(cases) == 45 and + isinstance(claims, list) and len(claims) == 11, + "original 45 case/11 claim contract unavailable") + bycase = {case["case_id"]: case for case in cases} + byclaim = {claim["claim_id"]: claim for claim in claims} + need(len(bycase) == 45 and set(byclaim) == CLAIMS, + "duplicate or changed acceptance owners") + proofs = indexed(overlay.get("compiler_axiom_receipts"), "proof") + positives = indexed(overlay.get("positive_test_receipts"), "positive") + negatives = indexed(overlay.get("semantic_negative_receipts"), "negative") + need(len(proofs) == 259, "not all 259 source-owned Lean declarations supplied") + cp, cn, qp, qn, wrong_roles, wrong_claim_sources, missing_support = ( + set(), set(), set(), set(), [], [], []) + for cid, original in sorted(bycase.items()): + applicable = [row for row in proofs.values() + if cid in row.get("case_ids", ())] + required = original.get("historical_proof_declarations") + need(isinstance(required, list) and required, + "original case lacks genuine theorem requirements: " + cid) + if (not all(any(supports_identity(row, req) for row in applicable) + for req in required) or + not all(any(row.get("path") == link.get("path") for row in applicable) + for link in original.get("new_claim_source_candidates", ()))): + missing_support.append(cid) + roles = _original_roles(original) + attached_plus = [row for row in positives.values() + if cid in row.get("case_ids", ())] + attached_minus = [row for row in negatives.values() + if cid in row.get("case_ids", ())] + correct_plus = [ + row for row in attached_plus + if roles.get((row.get("test_path"), row.get("test_name"))) + in POSITIVE_ROLES] + correct_minus = [ + row for row in attached_minus + if roles.get((row.get("test_path"), row.get("test_name"))) == + "SEMANTIC_NEGATIVE"] + if correct_plus: cp.add(cid) + if correct_minus: cn.add(cid) + # Do not ignore a role-ineligible extra record merely because a + # different record correctly satisfies this case. + if (any(roles.get((row.get("test_path"),row.get("test_name"))) + not in POSITIVE_ROLES for row in attached_plus) or + any(roles.get((row.get("test_path"),row.get("test_name"))) != + "SEMANTIC_NEGATIVE" for row in attached_minus)): + wrong_roles.append(cid) + for cid, original in sorted(byclaim.items()): + attached = [row for row in proofs.values() + if cid in row.get("claim_ids", ())] + required = original.get("declarations") + need(isinstance(required, list) and required, + "new claim missing full declaration list: " + cid) + if not all(any(supports_identity(row, req) for row in attached) + for req in required): + missing_support.append(cid) + sources = original.get("test_source_candidates") + need(isinstance(sources, list) and sources and + len(sources) == len(set(sources)), + "new claim missing registered test source paths: " + cid) + plus = [row for row in positives.values() + if cid in row.get("claim_ids", ())] + minus = [row for row in negatives.values() + if cid in row.get("claim_ids", ())] + if any(row.get("test_path") in sources for row in plus): qp.add(cid) + if any(row.get("test_path") in sources for row in minus): qn.add(cid) + # A seemingly legitimate positive must not hide an unrelated + # claim test in the same owner set. + if any(row.get("test_path") not in sources + for row in plus + minus): + wrong_claim_sources.append(cid) + required_cases = set(bycase) + result = { + "format":"Permansson.PR113.V3RoleCoverage.v1", + "cases_with_admissible_positive":len(cp), + "cases_with_admissible_negative":len(cn), + "claims_with_source_selected_positive":len(qp), + "claims_with_source_selected_negative":len(qn), + "missing_original_positive":sorted(required_cases - cp), + "missing_original_semantic_negative":sorted(required_cases - cn), + "missing_claim_positive":sorted(CLAIMS - qp), + "missing_claim_semantic_negative":sorted(CLAIMS - qn), + "missing_required_declaration_or_source":sorted(set(missing_support)), + "inadmissible_original_method_roles":sorted(set(wrong_roles)), + "inadmissible_claim_test_sources":sorted(set(wrong_claim_sources)), + "independent_review":False, + "mathematical_release_approved":False, + } + result["all_original_roles_and_claim_sources_complete"] = ( + len(cp) == len(cn) == 45 and len(qp) == len(qn) == 11 and + not missing_support and not wrong_roles and not wrong_claim_sources) + return result + + +def revalidate_source_receipts(overlay, head, members, accepted_checks, + producer_record, original_map, claim_map): + """Real positive/negative compiler results, source hashes and job ZIP bytes.""" + need(producer_record.get("producer_count") == 21 and + producer_record.get("status") == "REAL_PRODUCING_BYTES_UNREVIEWED" and + producer_record.get("head_sha") == head, + "actual 21 verified producer jobs are not available") + proofs = indexed(overlay.get("compiler_axiom_receipts"), "proof") + positives = indexed(overlay.get("positive_test_receipts"), "positive") + negatives = indexed(overlay.get("semantic_negative_receipts"), "negative") + need(len(proofs) == 259 and + {row.get("id") for row in proofs.values()} == + {f"all:{n:04}" for n in range(259)}, + "canonical complete 259-case compiler indices are not retained") + case_ids = {x.get("case_id") for x in original_map.get("cases", ())} + need(len(case_ids) == 45, "original criterion identity changed") + owners = {x for row in proofs.values() for x in row.get("case_ids", ())} + claims = {x for row in proofs.values() for x in row.get("claim_ids", ())} + need(owners == case_ids and claims == CLAIMS, + "all 45/11 source proof owner coverage not authentic") + for row in proofs.values(): + validate_compiler_receipt( + row, head, members, accepted_checks, producer_record=producer_record) + for row in positives.values(): + validate_test_receipt( + row, head, members, accepted_checks, + negative=False, producer_record=producer_record) + for row in negatives.values(): + validate_test_receipt( + row, head, members, accepted_checks, + negative=True, producer_record=producer_record) + return route_coverage(overlay, original_map, claim_map) diff --git a/verification/research/final_closure_verify.py b/verification/research/final_closure_verify.py new file mode 100644 index 00000000..4188bb23 --- /dev/null +++ b/verification/research/final_closure_verify.py @@ -0,0 +1,393 @@ +#!/usr/bin/env python3 +"""Fail-closed final mathematical-closure acceptance verifier. + +An audit PASS means a complete and truthful candidate snapshot, not theorem +acceptance. Strict modes require a *separately authenticated* final-head, +synthetic-merge, merged-main and independent reviewer record. Offline audit needs no network; strict modes authenticate live GitHub CI/review and local Git objects. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +from final_closure_authenticate import verify_source, verify_merged_v2, AuthenticationError +from final_closure_case_receipts import ( + FORMAT as DETACHED_FORMAT, FORMAT_V3 as DETACHED_V3, EvidenceError, + load_detached_bundle, effective_acceptance, +) +from final_closure_schema import validate_overlay +from pr113_boundary_io import strict_json_object +from final_closure_full_contract import ( + require_preseal_release, require_full_release, + require_complete_obligation_receipts, +) +import post_merge_102_verify as historical_audit +import re +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[2] +NEW = ROOT / "research/paper_closures/post_merge_112" +OLD = ROOT / "research/paper_closures/post_merge_79" +PREV = ROOT / "research/paper_closures/post_merge_102" +VERIFIED = {"VERIFIED_SELECTED_SCOPE", "VERIFIED_NEGATIVE"} +STATUSES = VERIFIED | {"PENDING", "PARTIAL_SCOPE", "BLOCKED"} +SHA40 = re.compile(r"^[a-f0-9]{40}$") +SHA256 = re.compile(r"^[a-f0-9]{64}$") +BASE = "17c315f7df7abf457eb94d393bd526d46da6f6f4" + + +class GateError(ValueError): + pass + + +def check(test: bool, message: str) -> None: + if not test: + raise GateError(message) + + +def read_json(path: Path) -> dict: + # Historical and current case/claim documents must not have + # ambiguous JSON keys before the research audit uses their values. + return strict_json_object(path.read_bytes(), max_bytes=8*1024*1024) + + +def secure_path(path: str) -> Path: + p = Path(path) + check(not p.is_absolute() and ".." not in p.parts and "\\" not in path, + f"unsafe path: {path}") + candidate = (ROOT / p).resolve() + check(candidate.is_relative_to(ROOT.resolve()), f"escaped repo: {path}") + return candidate + + +def digest(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def current_git_head() -> str | None: + try: + return subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=ROOT, + stderr=subprocess.DEVNULL, text=True, timeout=15).strip() + except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired): + return None + + +def audit() -> dict: + # Keep the original 124-provider historical SHA and case-source checks + # as an independent precondition; never replace a preserved audit. + historical_audit.validate_audit() + decisions = read_json(NEW / "acceptance_decisions.json") + cross = read_json(NEW / "theorem_crosswalk.json") + inv = read_json(NEW / "claim_inventory.json") + index = read_json(NEW / "source_ci_index.template.json") + reviews = read_json(NEW / "review_receipts.json") + historical = read_json(OLD / "acceptance_decisions.json") + prev = read_json(PREV / "acceptance_decisions.json") + original = {row["id"]: row for row in historical["cases"]} + prevmap = {row["case_id"]: row for row in prev["cases"]} + cases = decisions.get("cases", []) + check(len(cases) == len(original) == 45, "45-case count mismatch") + ids = [r["case_id"] for r in cases] + check(len(set(ids)) == 45 and set(ids) == set(original), + "lost, extra or duplicate original acceptance case") + check(all(v.get("source_baseline_sha") == BASE for v in + (decisions, cross, index, inv)), "wrong source baseline") + check(index["source_baseline_tree"] == + "1bd1c31a67d28c97b82d6234d6c013b31336a0fd", + "incorrect original tree") + check(cross.get("historical_contract_count") == 42 and + len(cross.get("contracts", [])) == 42, + "the 42 original atlas contracts are missing") + check(len(cross.get("closure_additions", [])) == 11, + "missing one of the planned closure obligations") + check(len(inv.get("claims", [])) == 11, "claim inventory mismatch") + check(reviews.get("independent_review") is False or + bool(reviews.get("academic_reviews")), + "fabricated independent review") + check(index.get("historical_receipt_limits", {}).get( + "original_17_679_census") == "UNVERIFIED_DO_NOT_CITE", + "unverified historical census promoted") + check(index.get("historical_receipt_limits", {}).get( + "constitution_core") == "RETIRED_UNRECONCILED_OWNER_RECEIPT_BINDING", + "unreconciled historical source binding promoted") + for row in cases: + cid = row["case_id"] + check(row["decision"] in STATUSES, f"unknown decision for {cid}") + check(row["required_expected_result"] == + original[cid]["required_expected_result"], + f"weakened original expected result: {cid}") + check(row["historical_contract_ids"] == original[cid]["atlas_ids"], + f"changed original atlas binding: {cid}") + check(row["historical_contract_ids"] == + prevmap[cid]["historical_contract_ids"], + f"changed historical case definition: {cid}") + check(row["proof_declarations"] == prevmap[cid]["proof_declarations"], + f"historical theorem statement/source pin mutated: {cid}") + # A tracked source candidate cannot contain future exact-H reviewer + # approvals. Approved decisions are DETACHED and authenticated live. + # Nonempty strings or locally forged receipt arrays never suffice. + check(row["decision"] not in VERIFIED, + f"source-embedded acceptance bypasses detached review: {cid}") + check(bool(row.get("remaining_obligations")), + f"unresolved case with no requirements: {cid}") + for c in cross["closure_additions"]: + path = secure_path(c["path"]) + check(c["id"] in {e["id"] for e in inv["claims"]}, + f"undeclared closure {c['id']}") + if c["status"] not in {"PLANNED_NOT_VERIFIED", "NOT_LEAN_VERIFIED", "VERIFIED_SELECTED_SCOPE", "BLOCKED"}: + raise GateError(f"unknown closure status: {c['id']}") + if c["status"] == "VERIFIED_SELECTED_SCOPE": + check(path.exists() and SHA256.fullmatch(c.get("source_sha256") or "") and + digest(path) == c["source_sha256"] and + bool(c["actual_declarations"] and c["axiom_receipts"]), + f"unsubstantiated theorem status: {c['id']}") + return {"cases": 45, "contracts": 42, "closures": 11, + "verified": sum(r["decision"] in VERIFIED for r in cases), + "pending": sum(r["decision"] not in VERIFIED for r in cases), + "audit": "PASS_WITH_DECLARED_PENDING"} + + + +def verify_claim_source_receipts(receipts: dict, source_head: str) -> set[str]: + """Legacy v1 proof-receipt API intentionally DISABLED for acceptance. + + The old source-name/digest and diagnostic text checks were insufficient + for case-specific acceptance and external bundle provenance. Always use + the read-only authenticated v2 strict() gate with exact head, CI, tested + mutant bytes and independent scope review. + """ + raise GateError( + "legacy free-form proof receipt verification retired; " + "use detached authenticated v2 receipt bundle and strict()") + + +def strict(receipts: dict, scope: str, manifest: dict | None, + *, bundle_path: Path | str | None = None, + reproduction_attachment: dict | None = None, + postseal_attachment: dict | None = None, + v31_attachment: dict | None = None) -> dict: + """Calculate EFFECTIVE reviewed cases/claims; never trust source JSON flags. + + The detached bundle is an external post-freeze artifact. Real signed + GitHub check runs and independently authored exact-H review scopes are + authenticated anew against live GitHub and local genuine Git objects. + """ + try: + validate_overlay(receipts) + except EvidenceError as exc: + raise GateError(str(exc)) from exc + summary = audit() + check(scope in {"full45-strict", "scoped-release-strict", + "postmerge-preseal-strict", "full-release-strict"}, + "unknown strict acceptance scope") + check(isinstance(receipts, dict) and + receipts.get("format") in (DETACHED_FORMAT, DETACHED_V3), + "canonical detached v2/v3 receipts required; old flags cannot promote") + check(receipts.get("phase") == "postmerge_acceptance", + "postmerge strict acceptance requires actual M") + if scope in ("full45-strict", "postmerge-preseal-strict", + "full-release-strict"): + check(receipts.get("format") == DETACHED_V3, + "full 45/11 scope requires genuine producer-bound v3 receipts") + try: + provenance = verify_merged_v2(receipts) + external = bundle_path or receipts.get("evidence_bundle_path") + check(bool(external), "missing external immutable diagnostic bundle") + members = load_detached_bundle(external, + receipts.get("evidence_bundle_sha256")) + decisions = read_json(NEW / "acceptance_decisions.json") + inventory = read_json(NEW / "claim_inventory.json") + result = effective_acceptance( + receipts, head=provenance["H"], case_ledger=decisions, + claims=inventory["claims"], members=members, + accepted_checks=provenance["checks"], + approved_review_scopes=provenance["review_scopes"]) + except (AuthenticationError, EvidenceError, OSError) as exc: + raise GateError(str(exc)) from exc + + # All 76 real source-owned positive/negative controls are mandatory for + # either complete scholarly release mode. A legacy original-v3 only + # source receipt cannot be promoted by successful review or green CI. + additional_source = {} + if scope in ("postmerge-preseal-strict", "full-release-strict"): + from pr113_mandatory_v31 import ( + authenticate_all_76, MandatoryV31Error) + check(isinstance(v31_attachment, dict) and + set(v31_attachment) == { + "additional_contract", "additional_zips", + "workflow_blob_candidates"} and + all(v31_attachment[name] is not None for name + in v31_attachment), + "full scholarly release requires 76 original v3.1 source controls") + try: + additional_source = authenticate_all_76( + provenance["H"], receipts, members, **v31_attachment) + except (MandatoryV31Error, OSError, TypeError, ValueError) as exc: + raise GateError( + "real full-scope v3.1 producer evidence is missing: " + + str(exc)) from exc + full_result = {} + if scope == "full45-strict": + check(result["accepted_case_count"] == 45 and + result["pending_case_count"] == 0, + "full45 still has unaccepted original case(s)") + else: + check(isinstance(manifest, dict) and + manifest.get("status") == "APPROVED_SOURCE_BOUND", + "scoped paper is not genuinely accepted") + check(manifest.get("source_commit_sha") == provenance["M"], + "paper manifest is not bound to the real merged M") + claim_ids = manifest.get("claims", []) + approved = manifest.get("approved_manuscript_claims", []) + check(isinstance(claim_ids, list) and bool(claim_ids) and + len(claim_ids) == len(set(claim_ids)) and + claim_ids == approved and + set(claim_ids) <= set(result["effective_claims"]), + "manuscript uses unreviewed or missing claim") + cross = read_json(NEW / "theorem_crosswalk.json") + owners = {r["id"]: r for r in cross["closure_additions"]} + proof_rows = receipts.get("compiler_axiom_receipts", []) + for cid in claim_ids: + check(cid in owners and + any(cid in row.get("claim_ids", []) and + row.get("path") == owners[cid]["path"] + for row in proof_rows), + "claim source owner not bound: " + cid) + # Scientific manuscript review happens AFTER real M and signed PDF + # inspection. A postmerge non-author GitHub attestation must bind the + # literal PDF, all rendered page hashes and the approved claim set. + pdf_hash = manifest.get("pdf_sha256") + pages = manifest.get("pdf_pages") + check(isinstance(pdf_hash, str) and SHA256.fullmatch(pdf_hash) and + isinstance(pages, dict) and bool(pages), + "approved PDF checksum and complete page hashes absent") + check(any(s.get("pdf_sha256") == pdf_hash and + s.get("pages") == pages and + set(s.get("claim_ids", [])) == set(claim_ids) + for s in provenance["scholarly_reviews"]), + "postmerge independent manuscript/page review absent") + check(bool(manifest.get("accepted_claim_receipts")) and + bool(manifest.get("scientific_limitations")) and + manifest.get("empirical_identification_status") == "NOT_ESTABLISHED", + "claim/limitation boundaries missing or empirical proof invented") + if scope in ("postmerge-preseal-strict", "full-release-strict"): + # A claimed PDF/CI digest is NOT evidence. Authenticate the actual + # successful merged-main producing job and its original ZIP bytes. + if reproduction_attachment is not None: + from final_closure_reproduction_auth import ( + authenticate_merged_reproduction, + ReproductionAuthenticationError, + ) + check(isinstance(reproduction_attachment, dict) and + set(reproduction_attachment) == {"identities", "zip_file"} and + isinstance(manifest, dict) and + isinstance(manifest.get("pdf_sha256"), str), + "untyped detached genuine reproduction input") + try: + reproduced = authenticate_merged_reproduction( + provenance["M"], manifest["pdf_sha256"], + reproduction_attachment["identities"], + Path(reproduction_attachment["zip_file"])) + except (ReproductionAuthenticationError, OSError, KeyError) as exc: + raise GateError("real merged source/PDF reproduction failed: " + + str(exc)) from exc + provenance = {**provenance, "reproduction_evidence": reproduced} + if scope == "full-release-strict" and postseal_attachment is not None: + from final_closure_postseal_auth import ( + authenticate_postseal, PostsealAuthenticationError) + check(isinstance(postseal_attachment, dict) and + set(postseal_attachment) == {"identities", "zip_file"} and + isinstance(manifest, dict) and + isinstance(manifest.get("pdf_sha256"), str), + "untyped detached genuine postseal evidence") + try: + archive = authenticate_postseal( + provenance["M"], manifest["pdf_sha256"], + postseal_attachment["identities"], + Path(postseal_attachment["zip_file"])) + except (PostsealAuthenticationError, OSError, KeyError) as exc: + raise GateError("genuine merged-M postseal authentication failed: "+ + str(exc)) from exc + provenance = {**provenance, "archive_evidence": archive} + try: + original_map = read_json(NEW / "acceptance_case_source_map.json") + validator = (require_preseal_release + if scope == "postmerge-preseal-strict" + else require_full_release) + full_result = validator( + result, decisions, inventory["claims"], + original_map, manifest, provenance) + full_result.update(require_complete_obligation_receipts( + receipts, original_map, + read_json(NEW / "claim_declaration_map.json"))) + except EvidenceError as exc: + raise GateError(str(exc)) from exc + return {**summary, **result, **full_result, + "additional_source_execution": additional_source, + "strict": scope, + "source": provenance["M"], "frozen_head": provenance["H"]} + + +def main(argv=None) -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--mode", choices=("audit", "full45-strict", + "scoped-release-strict", + "postmerge-preseal-strict", + "full-release-strict"), default="audit") + ap.add_argument("--receipts", type=Path) + ap.add_argument("--manifest", type=Path) + ap.add_argument("--bundle", type=Path, + help="externally retained hash-bound exact-H diagnostic archive") + ap.add_argument("--reproduction-zip", type=Path, + help="original real merged-M clean-build Actions artifact ZIP") + ap.add_argument("--reproduction-ids", type=Path, + help="detached source-bound GitHub run/job/check/artifact IDs") + ap.add_argument("--postseal-zip", type=Path, + help="future independently authorized GH postseal artifact ZIP") + ap.add_argument("--postseal-ids", type=Path, + help="real source-bound producing postseal job/check/artifact IDs") + ap.add_argument("--additional-contract", type=Path) + ap.add_argument("--additional-zips", type=Path) + ap.add_argument("--workflow-blob-candidates", type=Path) + args = ap.parse_args(argv) + try: + if args.mode == "audit": + out = audit() + else: + check(args.receipts is not None, "strict mode requires authenticated receipts") + receipts = read_json(args.receipts) + manifest = read_json(args.manifest) if args.manifest else None + check(bool(args.reproduction_zip) == bool(args.reproduction_ids), + "reproduction ZIP and exact GitHub identities must be paired") + source = ({"zip_file": args.reproduction_zip, + "identities": read_json(args.reproduction_ids)} + if args.reproduction_zip else None) + check(bool(args.postseal_zip) == bool(args.postseal_ids), + "postseal ZIP and exact GitHub producing IDs must be paired") + replay = ({"zip_file":args.postseal_zip, + "identities":read_json(args.postseal_ids)} + if args.postseal_zip else None) + v31 = ({ + "additional_contract": args.additional_contract, + "additional_zips": args.additional_zips, + "workflow_blob_candidates": args.workflow_blob_candidates} + if any(x is not None for x in ( + args.additional_contract, args.additional_zips, + args.workflow_blob_candidates)) else None) + out = strict(receipts, args.mode, manifest, + bundle_path=args.bundle, + reproduction_attachment=source, + postseal_attachment=replay, + v31_attachment=v31) + except (GateError, EvidenceError, KeyError, OSError, ValueError) as exc: + print(f"NOT ACCEPTED: {exc}", file=sys.stderr) + return 2 + print(json.dumps(out, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/infrastructure_acceptance.py b/verification/research/infrastructure_acceptance.py new file mode 100644 index 00000000..d766bb71 --- /dev/null +++ b/verification/research/infrastructure_acceptance.py @@ -0,0 +1,590 @@ +"""PR113 infrastructure-only acceptance, never mathematical or release acceptance. + +Use only after a security-reviewed source-controlled T/T2 + A2 workflow is +actually ACTIVE. Detached historical artifact ZIPs must be from successful, +source-bound GitHub Actions jobs. Security review must be real, authorized and +bound to the exact head/artifact bytes; an approval label in JSON is useless. +""" +from __future__ import annotations + +import argparse +import hashlib +import io +import json +from pathlib import Path, PurePosixPath +import re +import subprocess +import stat +import sys +import zipfile + +from final_closure_authenticate import ( + AuthenticationError, REQUIRED, authenticated_checks, current_reviews_by_user, + git as real_git, live as real_live, paginate as real_paginate, +) +import release_target +from pr113_dual_tooling_candidate import validate_dual_reports +from localized_prefix_gate import MUTATIONS as HISTORICAL_MUTATIONS +from pr113_a2_successor_compiler import MUTATIONS as SUCCESSOR_MUTATIONS + +ROOT = Path(__file__).resolve().parents[2] +FORMAT = "Permansson.PR113.InfrastructureEvidence.v1" +AUTHORITY_PATH = "research/certification/infrastructure_reviewer_authority.json" +ACTIVE_WORKFLOW = ".github/workflows/pr113-historical-migration-candidates.yml" +REVIEW_PREFIX = "PERMANSSON-INFRA-SECURITY-V1:" +A2_HISTORICAL = "f71a76032b169aac7bfe0694aa715339a94d4e05" +EXPECTED_ARTIFACTS = { + "T": ("historical-T-and-H-unreviewed", "pr113-T-H-historical-candidate"), + "A2": ("historical-A2-and-H-unreviewed", "pr113-A2-historical-candidate"), +} +SOURCE_FIELDS = frozenset(( + "run_id", "run_attempt", "job_id", "check_run_id", "artifact_id", +)) +EVIDENCE_FIELDS = frozenset(( + "format", "target", "source_bound_ci", "historical_T", + "historical_A2", "security_review_id", +)) +HEX256 = re.compile(r"^[a-f0-9]{64}$") + + +class InfrastructureError(ValueError): + pass + + +def need(test, why): + if not test: + raise InfrastructureError(why) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def json_member(files, name): + need(name in files and 0 < len(files[name]) <= 4 * 1024 * 1024, + "missing/big original evidence: " + name) + from pr113_boundary_io import strict_json_object + try: + item = strict_json_object(files[name]) + except (ValueError, TypeError) as exc: + raise InfrastructureError("original source report not valid JSON: " + name) from exc + need(isinstance(item, dict), "report is not a JSON object: " + name) + return item + + +def inspect_zip(raw): + """Bounded and exact-byte historical artifact member intake; no extraction.""" + need(type(raw) is bytes and 0 < len(raw) <= 256 * 1024 * 1024, + "missing/oversize original GitHub historical artifact bytes") + files = {} + folded = set() + total = 0 + try: + with zipfile.ZipFile(io.BytesIO(raw)) as z: + infos = z.infolist() + need(1 <= len(infos) <= 500, "unbounded historical evidence members") + for item in infos: + name = item.filename + path = PurePosixPath(name) + mode = (item.external_attr >> 16) & 0o170000 + need(isinstance(name, str) and name == path.as_posix() + and not path.is_absolute() and not item.is_dir() + and "\\" not in name and ".." not in path.parts + and len(name) <= 300 and len(path.parts) <= 12 + and all(x not in ("", ".") for x in name.split("/")) + and name.casefold() not in folded + and mode in (0, stat.S_IFREG), + "unsafe/duplicate historical evidence ZIP member") + folded.add(name.casefold()) + need(0 < item.file_size <= 64 * 1024 * 1024 + and item.compress_size > 0 + and item.file_size <= item.compress_size * 300, + "unbounded historical evidence expanded member") + total += item.file_size + need(total <= 320 * 1024 * 1024, + "historical evidence ZIP exceeds total expanded budget") + data = z.read(item) + need(len(data) == item.file_size, + "incomplete original historical evidence ZIP member") + files[name] = data + except (OSError, RuntimeError, ValueError, zipfile.BadZipFile, + zipfile.LargeZipFile) as exc: + raise InfrastructureError("invalid original GH historical ZIP") from exc + return files + + +def inspect_T(files, head): + old_raw = files.get("original_T_archive.json") + current_raw = files.get("current_H_workflow.json") + need(type(old_raw) is bytes and type(current_raw) is bytes, + "historical T and H real underlying reports missing") + old = json_member(files, "original_T_archive.json") + current = json_member(files, "current_H_workflow.json") + try: + verified = validate_dual_reports( + old, current, T_source=release_target.T, H_source=head) + except (ValueError, KeyError, TypeError) as exc: + raise InfrastructureError("historical T/current H report validation failed") from exc + receipt = json_member(files, "dual_tooling_unreviewed.json") + dossier = json_member(files, "T2_security_review_candidate.json") + need(verified["status"] == "DUAL_T_AND_H_EXECUTED_UNREVIEWED" + and receipt.get("status") == verified["status"] + and receipt.get("original_T_archive_unchanged") is True + and receipt.get("historical_T") == release_target.T + and receipt.get("current_H") == head + and receipt.get("historical_T_report_sha256") == sha(old_raw) + and receipt.get("current_H_report_sha256") == sha(current_raw) + and receipt.get("independent_security_review") is False + and receipt.get("versioned_successor_T2_approved") is False + and dossier.get("format") + == "Permansson.PR113.ToolingSecurityReviewDossier.v1" + and dossier.get("status") + == "COMPLETE_DUAL_EXECUTION_CANDIDATE_SECURITY_REVIEW_PENDING" + and dossier.get("T") == release_target.T + and dossier.get("H") == head + and dossier.get("T_original_offline_restore_report_sha256") + == sha(old_raw) + and dossier.get("H_original_workflow_test_report_sha256") + == sha(current_raw) + and dossier.get("legacy_T_did_not_accept_current_C") is True + and dossier.get("owner_authorization_present") is False + and dossier.get("independent_security_review_present") is False + and dossier.get("historical_required_CI_replaced") is False + and dossier.get("required_workflow_inventory_count") == 25, + "T/T2 candidate does not bind original real reports or historical invariants") + return {"T_report_sha256": sha(old_raw), + "H_report_sha256": sha(current_raw), + "T2_dossier_sha256": sha(files["T2_security_review_candidate.json"])} + + +def verify_compiler_receipt(files, prefix, expected_status, expected_mutations): + path = prefix + "/receipt.json" + row = json_member(files, path) + need(row.get("status") == expected_status + and row.get("source_unchanged") is True + and isinstance(row.get("declarations"), dict) + and len(row["declarations"]) > 0 + and isinstance(row.get("mutations"), dict) + and set(row["mutations"]) == set(expected_mutations) + and all(isinstance(x, dict) and x.get("status") == "SEMANTICALLY_REJECTED" + for x in row["mutations"].values()), + "A2 historical/current compiler proof or semantic negative missing") + commands = row.get("commands") + need(isinstance(commands, list) and len(commands) >= 8, + "A2 compiler did not retain all real command logs") + labels = {x.get("label") for x in commands if isinstance(x, dict)} + need({"compiler_version", "research_build", "signatures_axioms", + "positive_fixture"} <= labels + and set(expected_mutations) <= labels, + "A2 compiler failed to run a mandatory proof/control") + for command in commands: + need(isinstance(command, dict) and + type(command.get("returncode")) is int and + command["returncode"] not in (-999, -998) and + isinstance(command.get("log"), str) and + isinstance(command.get("log_sha256"), str) and + HEX256.fullmatch(command["log_sha256"]), + "untrusted A2 log source/exit code") + name = prefix + "/" + command["log"] + need(name in files and sha(files[name]) == command["log_sha256"], + "A2 compiled command was not preserved byte-for-byte") + if command["label"] in expected_mutations: + need(command["returncode"] != 0 and + b"error:" in files[name].lower() and + b"parser error" not in files[name].lower() and + b"timeout" not in files[name].lower(), + "A2 false source did not produce a semantic rejection") + else: + need(command["returncode"] == 0, + "A2 original nonnegative compiler leg failed") + for name, entry in row.get("artifacts", {}).items(): + full = prefix + "/" + name + need(isinstance(entry, dict) and full in files and + entry.get("sha256") == sha(files[full]) and + entry.get("bytes") == len(files[full]), + "A2 reported artifact bytes do not match original ZIP") + return row, sha(files[path]) + + +def inspect_A2(files, head): + overall = json_member(files, "unreviewed_dual_A2_candidate.json") + historical, hhash = verify_compiler_receipt( + files, "historical_original_A2", + "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING", HISTORICAL_MUTATIONS) + successor, shash = verify_compiler_receipt( + files, "pr113_successor_A2", + "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING", SUCCESSOR_MUTATIONS) + prov = overall.get("historical_runtime_provenance") + need(overall.get("format") == "Permansson.PR113.DualA2CompilerCandidate.v2" + and overall.get("status") + == "BOTH_ACTUAL_A2_COMPILER_PATHS_EXECUTED_UNREVIEWED" + and overall.get("historical_source_commit") == A2_HISTORICAL + and overall.get("current_source_commit") == head + and overall.get("historical_test_executed") is True + and overall.get("successor_test_executed") is True + and overall.get("historical_semantic_mutants") == 4 + and overall.get("successor_semantic_mutants") == 4 + and overall.get("original_verifier_modified") is False + and overall.get("independent_security_approval") is False + and overall.get("independent_mathematical_approval") is False + and overall.get("merged_main_sha") is None + and overall.get("full_release_approved") is False + and isinstance(prov, dict) + and prov.get("imported_current_A2_gate") is False + and prov.get("historical_checkout_sha") == A2_HISTORICAL + and prov.get("output_receipt_sha256") == hhash + and historical.get("source_unchanged") is True + and successor.get("source_unchanged") is True, + "dual A2 report not genuine historical+successor execution scope") + return {"original_A2_sha256": hhash, "successor_A2_sha256": shash, + "dual_A2_sha256": sha(files["unreviewed_dual_A2_candidate.json"])} + + +def exact_committed_blob(revision, path, *, git=real_git, + max_bytes=1024*1024): + """Preserve exact Git source bytes; never strip trailing whitespace. + + Commit-identification helpers deliberately trim newline, but that + normalization invalidates hashes of any arbitrary committed file. + Workflows, dependency manifests and reviewer-visible source profiles + must be verified against exact original Git object bytes. + """ + need(isinstance(revision,str) and + re.fullmatch(r"[a-f0-9]{40}",revision) and + isinstance(path,str) and path and + not path.startswith("/") and "\x00" not in path and + ".." not in Path(path).parts, + "invalid committed source revision or path") + if git is real_git: + try: + raw=subprocess.check_output( + ["git","show",revision+":"+path],cwd=ROOT, + stderr=subprocess.DEVNULL,timeout=25) + except (OSError,subprocess.CalledProcessError, + subprocess.TimeoutExpired) as exc: + raise InfrastructureError("authentic Git source blob unavailable") from exc + else: + candidate=git("show",revision+":"+path) + raw=candidate.encode("utf-8") if isinstance(candidate,str) else candidate + need(type(raw) is bytes and 0 < len(raw) <= max_bytes, + "missing or unbounded raw committed source blob") + return raw + + +def exact_committed_lake_blob(head, *, git=real_git): + """Original Lake manifest SHA must include all committed raw bytes.""" + return exact_committed_blob(head,"lake-manifest.json",git=git) + + +def verify_A2_dependency_bridge(files, head, *, manifest_raw): + """Check the historical dependency bridge against actual committed H bytes. + + It authenticates *candidate evidence* only. It never makes a historical + verifier into the new mandatory gate and never supplies reviewer authority. + """ + bridge = json_member(files, "historical_dependency_bridge.json") + if isinstance(manifest_raw, str): + manifest_raw = manifest_raw.encode("utf-8") + need(type(manifest_raw) is bytes and 0 < len(manifest_raw) <= 1024 * 1024, + "missing true current-H committed Lake manifest") + try: + manifest = json.loads(manifest_raw) + except (ValueError, TypeError) as exc: + raise InfrastructureError("committed Lean dependency manifest invalid") from exc + packages = manifest.get("packages") if isinstance(manifest, dict) else None + need(isinstance(packages, list) and bool(packages) and + manifest.get("packagesDir") == ".lake/packages", + "committed dependency manifest invalid or missing package pins") + expected = {} + for item in packages: + need(isinstance(item, dict) and item.get("type") == "git" + and isinstance(item.get("name"), str) + and re.fullmatch(r"[A-Za-z_][A-Za-z0-9_-]*", item["name"]) + and item["name"] not in expected + and isinstance(item.get("rev"), str) + and re.fullmatch(r"[a-f0-9]{40}", item["rev"]), + "untrusted, duplicated or unpinned historical dependency") + expected[item["name"]] = item["rev"] + need(expected.get("mathlib") == + "5ed2965256430c3649e86755f9576b54eca72435", + "historical A2 dependency closure uses unauthorized mathlib") + need(set(bridge) == { + "format", "status", "historical_head", "current_head", + "manifest_sha256", "pinned_packages", "original_verifier_modified", + "historical_project_proofs_reused", "independent_security_review", + "mathematical_acceptance"} and + bridge.get("format") == "Permansson.PR113.HistoricalA2DependencyBridge.v1" + and bridge.get("status") == + "PINNED_DEPENDENCIES_REUSED_WITHOUT_PROOF_BUILD_IMPORT" + and bridge.get("historical_head") == A2_HISTORICAL + and bridge.get("current_head") == head + and bridge.get("manifest_sha256") == sha(manifest_raw) + and bridge.get("pinned_packages") == expected + and bridge.get("original_verifier_modified") is False + and bridge.get("historical_project_proofs_reused") is False + and bridge.get("independent_security_review") is False + and bridge.get("mathematical_acceptance") is False, + "historical A2 dependency reuse receipt not bound to real pins") + return sha(files["historical_dependency_bridge.json"]) + + +def source_meta(data): + need(isinstance(data, dict) and set(data) == SOURCE_FIELDS + and all(type(data.get(x)) is int and data[x] > 0 + for x in SOURCE_FIELDS), + "GH source evidence must contain exact positive run/attempt/job/check/artifact IDs") + return data + + +def authenticate_original_zip(kind, identity, original_bytes, head, *, live): + source_meta(identity) + run_id, attempt = identity["run_id"], identity["run_attempt"] + job_id, check_id, artifact_id = ( + identity["job_id"], identity["check_run_id"], identity["artifact_id"]) + run = live(f"/actions/runs/{run_id}") + jobs = live(f"/actions/runs/{run_id}/jobs?per_page=100") + check = live(f"/check-runs/{check_id}") + meta = live(f"/actions/artifacts/{artifact_id}") + name, artifact_name = EXPECTED_ARTIFACTS[kind] + need(isinstance(run, dict) and + run.get("head_sha") == head and + run.get("head_branch") == "research/final-mathematical-closure" and + run.get("event") in ("push", "workflow_dispatch") and + run.get("status") == "completed" and + run.get("conclusion") == "success" and + run.get("run_attempt") == attempt and + str(run.get("path", "")).endswith(ACTIVE_WORKFLOW), + "historical artifact run not authentic successful final-H workflow") + joblist = jobs.get("jobs") if isinstance(jobs, dict) else None + matches = [j for j in joblist if isinstance(j, dict) and + j.get("id") == job_id] if isinstance(joblist, list) else [] + need(len(matches) == 1, "historical producing job absent/ambiguous") + job = matches[0] + need(job.get("run_id") == run_id and + job.get("run_attempt") == attempt and + job.get("head_sha") == head and + job.get("name") == name and + job.get("status") == "completed" and + job.get("conclusion") == "success" and + str(job.get("check_run_url", "")).endswith("/check-runs/" + str(check_id)), + "historical evidence uploaded by failed/foreign producer job") + need(isinstance(check, dict) and + check.get("id") == check_id and + check.get("name") == name and + check.get("head_sha") == head and + check.get("status") == "completed" and + check.get("conclusion") == "success" and + (check.get("app") or {}).get("slug") == "github-actions", + "historical source check is not successful GitHub Actions") + need(isinstance(meta, dict) and + meta.get("id") == artifact_id and + meta.get("name") == artifact_name and + meta.get("expired") is False and + meta.get("digest") == "sha256:" + sha(original_bytes) and + (meta.get("workflow_run") or {}).get("id") == run_id and + (meta.get("workflow_run") or {}).get("head_sha") == head, + "source historical artifact ZIP digest/name/run invalid") + timestamps = [job.get("started_at"), meta.get("created_at"), + job.get("completed_at")] + need(all(isinstance(v, str) and + re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ", v) + for v in timestamps) + and timestamps[0] <= timestamps[1] <= timestamps[2], + "historical artifact uploaded outside producing job attempt") + return {"run_id": run_id, "attempt": attempt, + "job_id": job_id, "check_run_id": check_id, + "artifact_id": artifact_id, "sha256": sha(original_bytes)} + + +def authority_policy(head, *, git): + try: + policy = json.loads(git("show", head + ":" + AUTHORITY_PATH)) + except (ValueError, TypeError, OSError) as exc: + raise InfrastructureError("versioned security reviewer authority unavailable") from exc + need(isinstance(policy, dict) and + set(policy) == {"format", "status", "reviewers", "owner"} and + policy.get("format") == "Permansson.InfrastructureReviewerAuthority.v1" + and policy.get("owner") == "HMarcusWH" and + policy.get("status") == "OWNER_AUTHORIZED" and + isinstance(policy.get("reviewers"), list) and + bool(policy["reviewers"]), + "independent security authority has not been owner-configured") + return policy + + +def approved_security_review(review_id, author, head, attest, policy, *, paginate): + need(type(review_id) is int and review_id > 0, + "security approval must have an actual GitHub PR review ID") + approved = 0 + rows = paginate("/pulls/113/reviews") + for review in current_reviews_by_user(rows).values(): + user = (review.get("user") or {}).get("login") + if not user or user == author or review.get("id") != review_id or \ + review.get("commit_id") != head or \ + review.get("state") != "APPROVED" or \ + not review.get("submitted_at"): + continue + roster = [r for r in policy["reviewers"] + if isinstance(r, dict) and r.get("login") == user + and r.get("independence_disclosed") is True + and r.get("approved_by") == "HMarcusWH"] + if len(roster) != 1: + continue + lines = [line.removeprefix(REVIEW_PREFIX).strip() + for line in (review.get("body") or "").splitlines() + if line.strip().startswith(REVIEW_PREFIX)] + need(len(lines) == 1, "review missing/duplicated security attestation") + try: + rec = json.loads(lines[0]) + except (ValueError, TypeError) as exc: + raise InfrastructureError("GitHub security attestation malformed") from exc + need(isinstance(rec, dict) and rec == attest, + "security reviewer did not accept exact H/config/T/A2 bytes") + approved += 1 + need(approved == 1, "genuine current-H independent security review absent") + return True + + +def check_historical_only(payload, T_bytes, A2_bytes, *, + git=real_git, live=real_live, + paginate=real_paginate): + """Noncircular mandatory-check dependency: T/T2+A2 and real review only. + + Both source-bound producing jobs and the independent security review must + be genuine. No claim is made about the *caller* being a green check, so this + can be used inside an authorized replacement required-check workflow. + The complete 12-context aggregate is verified separately afterward. + """ + need(isinstance(payload, dict) and set(payload) == EVIDENCE_FIELDS + and payload.get("format") == FORMAT, + "wrong infrastructure-only evidence type") + source = payload.get("target") + release_target.candidate_bindings(source) + target = release_target.bind( + source, intent=release_target.exact_intent( + source["head_sha"], git=git), git=git, live=live) + need(target["phase"] == "infrastructure_premerge", + "certification target cannot borrow infrastructure provenance") + H = target["head_sha"] + workflow_raw=exact_committed_blob(H,ACTIVE_WORKFLOW,git=git) + workflow=workflow_raw.decode("utf-8") + need(workflow.startswith("# Review-pending source-controlled") + and "historical-T-and-H-unreviewed" in workflow + and "historical-A2-and-H-unreviewed" in workflow, + "required source-controlled T/T2+A2 producer workflow not installed") + workflow_digest = sha(workflow_raw) + t_auth = authenticate_original_zip( + "T", payload["historical_T"], T_bytes, H, live=live) + a_auth = authenticate_original_zip( + "A2", payload["historical_A2"], A2_bytes, H, live=live) + t = inspect_T(inspect_zip(T_bytes), H) + a = inspect_A2(inspect_zip(A2_bytes), H) + a["dependency_bridge_sha256"] = verify_A2_dependency_bridge( + inspect_zip(A2_bytes), H, + manifest_raw=exact_committed_lake_blob(H, git=git)) + policy = authority_policy(H, git=git) + pr = live("/pulls/113") + attestation = { + "head_sha": H, + "workflow_sha256": workflow_digest, + "T_artifact_sha256": sha(T_bytes), + "A2_artifact_sha256": sha(A2_bytes), + "original_base_sha": release_target.B113, + } + approved_security_review( + payload["security_review_id"], + (pr.get("user") or {}).get("login"), + H, attestation, policy, paginate=paginate) + return { + "format": "Permansson.PR113.HistoricalDualRequiredSourceReceipt.v1", + "status": "HISTORICAL_T_AND_A2_GENUINE_WITH_INDEPENDENT_SECURITY_REVIEW", + "source_head": H, + "base_sha": release_target.B113, + "synthetic_sha": target["synthetic_sha"], + "workflow_sha256": workflow_digest, + "T_execution": t, + "A2_execution": a, + "genuine_artifacts": {"T": t_auth, "A2": a_auth}, + "security_review_id": payload["security_review_id"], + "historical_source_and_security_review_authenticated": True, + "required_ci_successful": 0, + "independent_mathematical_review": False, + "accepted_original_cases": 0, + "accepted_new_claims": 0, + "merged_main_sha": None, + "merge_authorized": False, + "publication_authorized": False, + "full_release_approved": False, + } + + +def check_evidence(payload, T_bytes, A2_bytes, *, git=real_git, live=real_live, + checks=authenticated_checks, paginate=real_paginate): + """Infrastructure aggregation only AFTER all twelve genuine checks pass. + + Required historical checks should call check_historical_only, never this + aggregate, so they do not depend on their own green check-run statuses. + """ + historical = check_historical_only( + payload,T_bytes,A2_bytes,git=git,live=live,paginate=paginate) + H = historical["source_head"] + actual = checks(H) + requested = payload.get("source_bound_ci") + need(isinstance(requested, list) and + len(requested) == len(REQUIRED) and + {r.get("name") for r in requested if isinstance(r, dict)} == REQUIRED, + "infrastructure requires all twelve genuine source checks") + for row in requested: + need(isinstance(row, dict) and + set(row) == {"name", "id", "head_sha"} and + row.get("head_sha") == H and + type(row.get("id")) is int and + row.get("name") in actual and + row["id"] == actual[row["name"]]["id"], + "failed/stale/borrowed required source check") + result = dict(historical) + result.update({ + "format": "Permansson.PR113.InfrastructureAcceptanceReceipt.v1", + "status": "INFRASTRUCTURE_READY_FOR_SEPARATE_OWNER_MERGE_DECISION", + "required_ci_successful": len(REQUIRED), + "independent_security_review_authenticated": True, + }) + return result + + +def main(argv=None): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--evidence", type=Path, required=True) + p.add_argument("--historical-T-zip", type=Path, required=True) + p.add_argument("--historical-A2-zip", type=Path, required=True) + p.add_argument("--out", type=Path, required=True) + p.add_argument("--historical-only", action="store_true", + help="Verify both real original legs and independent review without querying the twelve required check statuses") + args = p.parse_args(argv) + try: + out = args.out.absolute() + need(not out.exists() and not out.is_symlink() + and not any(z.is_symlink() for z in out.parents) + and not out.is_relative_to(ROOT.resolve()), + "infrastructure result must be fresh and detached") + ev = args.evidence.read_bytes() + need(0 < len(ev) <= 64 * 1024, "unbounded detached infrastructure envelope") + checker = check_historical_only if args.historical_only else check_evidence + result = checker( + json.loads(ev), args.historical_T_zip.read_bytes(), + args.historical_A2_zip.read_bytes()) + out.parent.mkdir(parents=True, exist_ok=True) + with out.open("x", encoding="utf-8") as f: + json.dump(result, f, sort_keys=True, indent=2) + f.write("\n") + print(json.dumps({"status": result["status"], + "source_head": result["source_head"]}, sort_keys=True)) + return 0 + except (InfrastructureError, release_target.TargetError, + AuthenticationError, OSError, KeyError, TypeError, ValueError) as exc: + print("INFRASTRUCTURE NOT ACCEPTED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/infrastructure_acceptance_v2.py b/verification/research/infrastructure_acceptance_v2.py new file mode 100644 index 00000000..0d74e15f --- /dev/null +++ b/verification/research/infrastructure_acceptance_v2.py @@ -0,0 +1,226 @@ +"""PR113 proposed V2 T/T2+A2 required evidence gate (NOT INSTALLED). + +Versioned input source, exactly 25 historical / 30 current workflow sources. +Authentic GitHub run/job/attempt/check/artifact and current-H independent +security reviewer decisions are REQUIRED. Never interpret an infrastructure +receipt as mathematical certification or owner merge/publication authorization. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + +import infrastructure_acceptance as legacy +from final_closure_authenticate import ( + REQUIRED, authenticated_checks, git as real_git, live as real_live, + paginate as real_paginate, +) +import release_target +from pr113_historical_evidence_contract_v2 import ( + validate_envelope, parse_envelope, +) +from pr113_workflow_profile_v2 import audit as workflow_audit +from pr113_historical_intake_v2 import ( + inspect_T_v2, inspect_A2_v2, +) + +ROOT = Path(__file__).resolve().parents[2] +FORMAT = "Permansson.PR113.InfrastructureEvidence.v2" +ACTIVE_WORKFLOW = legacy.ACTIVE_WORKFLOW +AUTHORITY_PATH = legacy.AUTHORITY_PATH +EXPECTED_ARTIFACTS = legacy.EXPECTED_ARTIFACTS +EVIDENCE_FIELDS = legacy.EVIDENCE_FIELDS +authenticate_original_zip = legacy.authenticate_original_zip +inspect_zip = legacy.inspect_zip +from pr113_a2_dependency_bridge_v2 import verify_A2_dependency_bridge +exact_committed_lake_blob = legacy.exact_committed_lake_blob +exact_committed_blob = legacy.exact_committed_blob +from pr113_secure_security_review_v3 import ( + authority_policy, approved_security_review, +) +sha = legacy.sha +need = legacy.need +InfrastructureError = legacy.InfrastructureError + + +def checked_profile(head, *, git=real_git): + """Source identity from real PR merge S or approved main merge M. + + Runtime checkout must have the exact H tree. The current 30 source + profile is measured from Git objects; its digest is not a human review. + """ + checked = git("rev-parse", "HEAD") + need(isinstance(checked, str) and len(checked) == 40 and + git("cat-file","-t",checked) == "commit" and + git("cat-file","-t",head) == "commit", + "v2 checkout/H source must be genuine Git commits") + if checked != head: + need(git("show","-s","--format=%P",checked).split() == + [release_target.B113,head], + "v2 checkout S/M must have exact ordered original B113 and reviewed H") + need(git("rev-parse", checked + "^{tree}") == + git("rev-parse", head + "^{tree}"), + "v2 source is not the same exact Git tree as reviewed H") + result = workflow_audit(ROOT, checked, phase="active") + need(result.get("head_sha") == checked + and result.get("current_reviewed_workflow_count") == 30 + and result.get("historical_original_workflow_count") == 25 + and result.get("independent_security_review") is False, + "current T2 profile cannot be inferred from 25-file evidence") + return result + + +def inspect_T(files, head): + profile = checked_profile(head) + return inspect_T_v2( + files, head, profile_sha256=profile["current_source_profile_sha256"]) + + +def inspect_A2(files, head): + checked_profile(head) + return inspect_A2_v2(files, head) + + +def check_historical_only(payload, T_bytes, A2_bytes, *, + git=real_git, live=real_live, + paginate=real_paginate): + """Noncircular mandatory check: real dual producers and review, NOT 12/12.""" + validate_envelope(payload) + need(isinstance(payload, dict) and set(payload) == EVIDENCE_FIELDS + and payload.get("format") == FORMAT, + "unversioned/forged historical input envelope") + source = payload.get("target") + release_target.candidate_bindings(source) + target = release_target.bind( + source, intent=release_target.exact_intent( + source["head_sha"], git=git), git=git, live=live) + need(target["phase"] == "infrastructure_premerge", + "new certification target cannot reuse PR113 historical authority") + head = target["head_sha"] + profile = checked_profile(head, git=git) + workflow_raw = exact_committed_blob(head, ACTIVE_WORKFLOW, git=git) + workflow = workflow_raw.decode("utf-8") + need(workflow.startswith("# Review-pending source-controlled") + and "historical-T-and-H-unreviewed" in workflow + and "historical-A2-and-H-unreviewed" in workflow, + "genuine reviewed producing workflow missing from exact H") + authentic_T = authenticate_original_zip( + "T", payload["historical_T"], T_bytes, head, live=live) + authentic_A2 = authenticate_original_zip( + "A2", payload["historical_A2"], A2_bytes, head, live=live) + t = inspect_T_v2( + inspect_zip(T_bytes), head, + profile_sha256=profile["current_source_profile_sha256"]) + a_files = inspect_zip(A2_bytes) + a = inspect_A2_v2(a_files, head) + a["dependency_bridge_sha256"] = verify_A2_dependency_bridge( + a_files, head, + manifest_raw=exact_committed_lake_blob(head, git=git)) + policy = authority_policy(head, git=git) + pr = live("/pulls/113") + attestation = { + "head_sha": head, + "workflow_sha256": sha(workflow_raw), + "T_artifact_sha256": sha(T_bytes), + "A2_artifact_sha256": sha(A2_bytes), + "original_base_sha": release_target.B113, + } + approved_security_review( + payload["security_review_id"], + (pr.get("user") or {}).get("login"), + head, attestation, policy, paginate=paginate) + return { + "format": "Permansson.PR113.HistoricalDualRequiredSourceReceipt.v2", + "status": "HISTORICAL_T_AND_A2_GENUINE_WITH_INDEPENDENT_SECURITY_REVIEW", + "source_head": head, + "base_sha": release_target.B113, + "synthetic_sha": target["synthetic_sha"], + "workflow_sha256": sha(workflow_raw), + "workflow_profile_sha256": profile["current_source_profile_sha256"], + "historical_original_workflow_count": 25, + "current_reviewed_workflow_count": 30, + "T_execution": t, + "A2_execution": a, + "genuine_artifacts": {"T": authentic_T, "A2": authentic_A2}, + "security_review_id": payload["security_review_id"], + "historical_source_and_security_review_authenticated": True, + "required_ci_successful": 0, + "independent_mathematical_review": False, + "accepted_original_cases": 0, + "accepted_new_claims": 0, + "merged_main_sha": None, + "merge_authorized": False, + "publication_authorized": False, + "full_release_approved": False, + } + + +def check_evidence(payload, T_bytes, A2_bytes, *, + git=real_git, live=real_live, + paginate=real_paginate, checks=authenticated_checks): + """Later aggregate 12 genuine checks. It cannot self-certify its caller.""" + validate_envelope(payload, require_all_checks=True) + old = check_historical_only( + payload,T_bytes,A2_bytes,git=git,live=live,paginate=paginate) + head = old["source_head"] + observed = checks(head) + requested = payload.get("source_bound_ci") + need(isinstance(requested, list) and len(requested) == len(REQUIRED) + and {x.get("name") for x in requested if isinstance(x,dict)} == + REQUIRED, + "12 required source contexts not listed") + for item in requested: + need(isinstance(item,dict) and set(item) == {"name","id","head_sha"} + and item.get("head_sha") == head + and type(item.get("id")) is int + and item["name"] in observed + and item["id"] == observed[item["name"]]["id"], + "required 12-context check missing, wrong source or stale run") + result=dict(old) + result.update({ + "format": "Permansson.PR113.InfrastructureAcceptanceReceipt.v2", + "status": "INFRASTRUCTURE_READY_FOR_SEPARATE_OWNER_MERGE_DECISION", + "required_ci_successful": len(REQUIRED), + "independent_security_review_authenticated": True, + "mathematical_acceptance": False, + "owner_merge_authorized": False, + "publication_authorized": False, + }) + return result + + +def main(argv=None): + ap=argparse.ArgumentParser(description=__doc__) + ap.add_argument("--evidence",required=True,type=Path) + ap.add_argument("--historical-T-zip",required=True,type=Path) + ap.add_argument("--historical-A2-zip",required=True,type=Path) + ap.add_argument("--out",required=True,type=Path) + ap.add_argument("--historical-only",action="store_true") + args=ap.parse_args(argv) + try: + from pr113_boundary_io import detached_destination + dest=detached_destination(args.out, ROOT) + envelope=args.evidence.read_bytes() + need(0 < len(envelope) <= 64*1024, + "missing/unbounded authentic versioned evidence envelope") + check=check_historical_only if args.historical_only else check_evidence + receipt=check(parse_envelope( + envelope, require_all_checks=not args.historical_only), + args.historical_T_zip.read_bytes(), + args.historical_A2_zip.read_bytes()) + dest.parent.mkdir(parents=True,exist_ok=True) + with dest.open("x",encoding="utf-8") as f: + json.dump(receipt,f,indent=2,sort_keys=True) + f.write("\n") + print(json.dumps({"status":receipt["status"], + "source_head":receipt["source_head"]},sort_keys=True)) + return 0 + except (OSError, ValueError, TypeError, KeyError, RuntimeError) as exc: + print("PR113 V2 INFRASTRUCTURE NOT ACCEPTED:",exc,file=sys.stderr) + return 2 + + +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_a2_compiler_route_v3.py b/verification/research/pr113_a2_compiler_route_v3.py new file mode 100644 index 00000000..5a6b7e0d --- /dev/null +++ b/verification/research/pr113_a2_compiler_route_v3.py @@ -0,0 +1,92 @@ +"""Choose the real A2 compiler from an audited Git source profile. + +The staged-25 compiler and active-30 compiler have intentionally incompatible +source identities. Never fall back from one to the other after a failure. +This module does not replace historical A2, approve a migration, or accept CI. +""" +from __future__ import annotations + +from dataclasses import dataclass +import importlib +from pathlib import Path +import re +import subprocess +from types import ModuleType + +PROFILE_FORMAT = "Permansson.PR113.VersionedWorkflowEvidence.v2" +PHASES = { + "staged": ( + "EXACT_STAGED_25_TO_30_CANDIDATE_UNAPPROVED", + "pr113_a2_successor_compiler", + "Permansson.PR113.A2SuccessorCompilerCandidate.v1", + ), + "active": ( + "EXACT_ACTIVE_30_CANDIDATE_SECURITY_REVIEW_PENDING", + "pr113_a2_successor_compiler_v2", + "Permansson.PR113.A2SuccessorCompilerCandidate.v2", + ), +} +SHA = re.compile(r"^[0-9a-f]{40}$") + + +class CompilerRouteError(ValueError): + pass + + +@dataclass(frozen=True) +class CompilerRoute: + phase: str + checked_sha: str + module: ModuleType + receipt_format: str + + +def _head(repo: Path) -> str: + try: + raw = subprocess.check_output( + ["git", "-C", str(repo), "rev-parse", "--verify", "HEAD"], + stderr=subprocess.PIPE, timeout=20, + ) + value = raw.decode("ascii").strip() + except (OSError, UnicodeError, subprocess.SubprocessError) as exc: + raise CompilerRouteError("real A2 checkout HEAD unavailable") from exc + if SHA.fullmatch(value) is None: + raise CompilerRouteError("A2 checkout is not a complete Git commit ID") + return value + + +def _audited_profile(repo: Path, head: str): + # Audit exact Git objects, modes, manifests and installed guard sources; + # counting files or trusting a receipt's status is not sufficient. + from pr113_workflow_profile_v2 import audited_source_phase + return audited_source_phase(repo, head) + + +def select_compiler(repo: Path) -> CompilerRoute: + root = Path(repo).resolve(strict=True) + head = _head(root) + phase, profile = _audited_profile(root, head) + if type(phase) is not str or phase not in PHASES: + raise CompilerRouteError("unsupported A2 workflow source phase") + expected_status, module_name, receipt_format = PHASES[phase] + if (type(profile) is not dict + or profile.get("format") != PROFILE_FORMAT + or profile.get("status") != expected_status + or profile.get("head_sha") != head + or profile.get("versioned_source_phase") != phase + or type(profile.get("historical_original_workflow_count")) is not int + or profile["historical_original_workflow_count"] != 25 + or type(profile.get("current_reviewed_workflow_count")) is not int + or profile["current_reviewed_workflow_count"] != 30 + or profile.get("independent_security_review") is not False): + raise CompilerRouteError("A2 compiler route is not bound to audited source") + compiler = importlib.import_module(module_name) + historical = importlib.import_module("localized_prefix_gate") + if (compiler.DECLS != historical.DECLS + or compiler.MUTATIONS != historical.MUTATIONS + or len(compiler.DECLS) != 40 or len(compiler.MUTATIONS) != 4 + or not callable(compiler.verify)): + raise CompilerRouteError("A2 compiler changed the original proof/control scope") + if _head(root) != head: + raise CompilerRouteError("A2 checkout moved during compiler selection") + return CompilerRoute(phase, head, compiler, receipt_format) diff --git a/verification/research/pr113_a2_dependency_bridge_v2.py b/verification/research/pr113_a2_dependency_bridge_v2.py new file mode 100644 index 00000000..59a2ea9d --- /dev/null +++ b/verification/research/pr113_a2_dependency_bridge_v2.py @@ -0,0 +1,153 @@ +"""Corrected-baseline dependency bridge, distinct from immutable legacy v1. + +Only exact pinned dependency packages may be shared. Current project proof +builds, receipt relabeling, TLS exceptions and source edits are never allowed. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re + +import pr113_historical_a2_baseline_v1 as baseline + +FORMAT = "Permansson.PR113.HistoricalA2DependencyBridge.v2" +STATUS = "PINNED_DEPENDENCIES_REUSED_WITHOUT_PROOF_BUILD_IMPORT" +MATHLIB_REV = "5ed2965256430c3649e86755f9576b54eca72435" +FIELDS = frozenset(( + "format", "status", "historical_head", "historical_tree_sha", + "historical_introducer", "current_head", "manifest_sha256", "pinned_packages", + "original_verifier_modified", "historical_project_proofs_reused", + "independent_security_review", "mathematical_acceptance", +)) + + +class DependencyBridgeError(ValueError): + pass + + +def need(ok, message): + if not ok: + raise DependencyBridgeError(message) + + +def strict_object(raw): + need(type(raw) is bytes and 0 < len(raw) <= 1024 * 1024, + "missing or unbounded dependency JSON bytes") + def unique(pairs): + result = {} + for key, value in pairs: + need(key not in result, "duplicate dependency JSON key") + result[key] = value + return result + def invalid_number(value): + raise DependencyBridgeError("noninteger dependency JSON number") + try: + value = json.loads(raw, object_pairs_hook=unique, + parse_constant=invalid_number, parse_float=invalid_number) + except (UnicodeError, json.JSONDecodeError) as exc: + raise DependencyBridgeError("invalid dependency JSON") from exc + need(type(value) is dict, "dependency JSON must be an object") + return value + + +def manifest_packages(raw): + manifest = strict_object(raw) + need(manifest.get("packagesDir") == ".lake/packages" + and type(manifest.get("packages")) is list and manifest["packages"], + "invalid pinned dependency manifest") + expected = {} + for item in manifest["packages"]: + need(type(item) is dict and item.get("type") == "git" + and type(item.get("name")) is str + and re.fullmatch(r"[A-Za-z_][A-Za-z0-9_-]*", item["name"]) + and item["name"] not in expected and type(item.get("rev")) is str + and re.fullmatch(r"[a-f0-9]{40}", item["rev"]), + "duplicate, unsafe or unpinned dependency") + expected[item["name"]] = item["rev"] + need(expected.get("mathlib") == MATHLIB_REV, "unauthorized historical mathlib") + return expected + + +def verify_A2_dependency_bridge(files, head, *, manifest_raw): + need(type(head) is str and re.fullmatch(r"[a-f0-9]{40}", head), + "exact current source commit required") + if isinstance(manifest_raw, str): + manifest_raw = manifest_raw.encode("utf-8") + packages = manifest_packages(manifest_raw) + raw = files.get("historical_dependency_bridge.json") + bridge = strict_object(raw) + need(set(bridge) == FIELDS and bridge.get("format") == FORMAT + and bridge.get("status") == STATUS + and bridge.get("historical_head") == baseline.REPLAY_HEAD + and bridge.get("historical_tree_sha") == baseline.REPLAY_TREE + and bridge.get("historical_introducer") == baseline.INTRODUCER + and bridge.get("current_head") == head + and bridge.get("manifest_sha256") == hashlib.sha256(manifest_raw).hexdigest() + and bridge.get("pinned_packages") == packages + and all(bridge.get(flag) is False for flag in ( + "original_verifier_modified", "historical_project_proofs_reused", + "independent_security_review", "mathematical_acceptance")), + "corrected-baseline dependency bridge is stale, mixed-version or unauthenticated") + return hashlib.sha256(raw).hexdigest() + + +def prepare(historical, current, head, output): + from pr113_historical_a2_replay_v1 import dependency_bridge, unchanged, scalar + old, now = Path(historical).resolve(strict=True), Path(current).resolve(strict=True) + baseline.verify_positive_source(historical) + need(old != now, "historical and current source must differ") + need(type(head) is str and re.fullmatch(r"[a-f0-9]{40}", head), "bad current head") + need(Path(scalar(now, "rev-parse", "--show-toplevel")).resolve() == now, + "current source must be real checkout root") + unchanged(now, head) + baseline.git(now, "merge-base", "--is-ancestor", baseline.REPLAY_HEAD, head) + dest = Path(output).absolute() + need(not dest.exists() and not dest.is_symlink() + and not any(p.is_symlink() for p in dest.parents), "fresh detached bridge required") + dest = dest.resolve() + need(not dest.is_relative_to(old) and not dest.is_relative_to(now), + "bridge cannot be written inside source") + # This checks all package revisions/cleanliness both before and after linking. + packages = dependency_bridge(now, old) + raw = (old / "lake-manifest.json").read_bytes() + need(packages == manifest_packages(raw), "bridge package execution differs from manifest") + receipt = { + "format": FORMAT, "status": STATUS, "historical_head": baseline.REPLAY_HEAD, + "historical_tree_sha": baseline.REPLAY_TREE, + "historical_introducer": baseline.INTRODUCER, "current_head": head, + "manifest_sha256": hashlib.sha256(raw).hexdigest(), + "pinned_packages": dict(sorted(packages.items())), + "original_verifier_modified": False, "historical_project_proofs_reused": False, + "independent_security_review": False, "mathematical_acceptance": False, + } + encoded = (json.dumps(receipt, sort_keys=True, indent=2) + "\n").encode() + verify_A2_dependency_bridge({"historical_dependency_bridge.json": encoded}, head, + manifest_raw=raw) + unchanged(old, baseline.REPLAY_HEAD) + unchanged(now, head) + dest.parent.mkdir(parents=True, exist_ok=True) + with dest.open("xb") as stream: + stream.write(encoded) + return receipt + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--historical-source", required=True, type=Path) + parser.add_argument("--current-source", required=True, type=Path) + parser.add_argument("--current-head", required=True) + parser.add_argument("--out", required=True, type=Path) + args = parser.parse_args(argv) + try: + receipt = prepare(args.historical_source, args.current_source, args.current_head, args.out) + except (OSError, ValueError, RuntimeError) as exc: + parser.exit(2, "HISTORICAL_A2_DEPENDENCY_BRIDGE_BLOCKED: " + str(exc) + "\n") + print(json.dumps({"status": receipt["status"], "historical_head": receipt["historical_head"]})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_a2_successor_compiler.py b/verification/research/pr113_a2_successor_compiler.py new file mode 100644 index 00000000..88b0ca62 --- /dev/null +++ b/verification/research/pr113_a2_successor_compiler.py @@ -0,0 +1,267 @@ +"""Versioned PR113 A2-P02 successor compiler/signature/axiom/semantic gate. + +Imported by test_localized_prefix_compiler.py in unchanged Research CI discovery. +Also runnable directly. No test discovery is called here (avoids recursive CI). +All evidence is written OUTSIDE the repository into a fresh or empty directory. +No compiler, wrong source, timeout or wrong failure mode => FAIL_CLOSED. +""" +from __future__ import annotations +import argparse +from datetime import datetime, timezone +import json +import os +from pathlib import Path +import re +import shutil +import subprocess +import sys +import time +from typing import Any + +if __package__: + from . import localized_prefix_support as support +else: + import localized_prefix_support as support + +import pr113_a2_successor_identity as successor_identity + +ALLOWED_AXIOMS = frozenset({"propext", "Classical.choice", "Quot.sound"}) +DECLS = tuple("PermanssonResearch.Probability." + x for x in ( + "goodHistoryMajorant", "canonicalPrefixKernel", "canonicalPrefixKernel_isMarkov", + "mixedFinitePrefixLaw", "mixedFinitePrefixLaw_isProbability", + "goodHistoryMajorant_measurable", "goodHistoryMajorant_nonneg", "goodHistoryMajorant_le_one", + "goodHistory_lintegral_le", "historyEvolution_tv_le_goodVisits", + "finitePrefixLaw_tv_le_averaged", "finitePrefixLaw_tv_le_goodVisits", + "finitePathExpectation_abs_le_goodVisits", "canonicalPrefixKernel_apply", + "mixedFinitePrefixLaw_dirac", "mixedFinitePrefixLaw_tv_le_initial", + "finitePrefixLaw_tv_le_goodVisits_initial", "finitePathExpectation_abs_le_goodVisits_initial", +)) + ("PermanssonResearch.ConstitutiveQuasi.LocalizedProtocolErrors",) + tuple("PermanssonResearch.ConstitutiveQuasi.LocalizedProtocolErrors." + x for x in ( + "baselineBudget", "interventionBudget", "baselineBudget_nonneg", "interventionBudget_nonneg", "toConstitutiveOutputErrors", + "reportedMargin_abs_le", "trueMargin_pos_of_reported", "reportedMargin_pos_of_true", +)) + tuple("PermanssonResearch.ConstitutiveQuasi.LocalizedExamples." + x for x in ( + "reportingProfile", "matchedPair", "reportedEffect_four_fifths", "initialScore", + "exactLocalizedErrors", "reportedMargin_four_fifths", "localizedBudget_total_one_fifth", + "originalMargin_ge_three_fifths", "originalMargin_positive", + "cannot_omit_baseline_report_error", "cannot_omit_treatment_report_error", + "initialScore_point", "initialMismatch_gap_one", +)) +FIXTURE = support.LIBRARIES[-1] +MUTATIONS = { + "omit_baseline_reporting_error": (" reportBase := 1 / 10\n", " reportBase := 0\n"), + "omit_treated_reporting_error": (" reportTreated := 1 / 10\n", " reportTreated := 0\n"), + "wrong_reported_effect": ( + "perturbedConstitutiveEffect reportingProfile y = 4 / 5 := by", + "perturbedConstitutiveEffect reportingProfile y = 3 / 4 := by"), + "omit_zero_horizon_initial_error": ( + "(mixedFinitePrefixLaw (Kernel.id : Kernel Bool Bool) (Measure.dirac true) 0)| = 1 := by", + "(mixedFinitePrefixLaw (Kernel.id : Kernel Bool Bool) (Measure.dirac true) 0)| = 0 := by"), +} + + +def parse_version(text: str) -> str: + found = re.findall(r"\bversion\s+([^,\s)]+)", text) + if found != ["4.34.0"]: + raise support.GuardError(f"expected exact Lean release 4.34.0, got {text.strip()!r}") + return found[0] + + +def parse_axioms(text: str, names: tuple[str, ...] = DECLS) -> dict[str, list[str]]: + normalized = re.sub(r"\.\{[^{}\n]*\}", "", text) + pattern = re.compile( + r"['`]?([A-Za-z_][A-Za-z0-9_.]*)['`]?\s+" + r"(?:depends on axioms:\s*\[([^\]]*)\]|does not depend on any axioms)", re.S) + out: dict[str, list[str]] = {} + for match in pattern.finditer(normalized): + name, atoms = match.groups() + if name not in names: raise support.GuardError(f"unexpected axiom declaration: {name}") + if name in out: raise support.GuardError(f"duplicate axiom record: {name}") + axioms = [] if atoms is None else [a.strip() for a in atoms.split(",") if a.strip()] + if not set(axioms) <= ALLOWED_AXIOMS: + raise support.GuardError(f"prohibited transitive axiom for {name}: {axioms}") + out[name] = sorted(set(axioms)) + if set(out) != set(names): + raise support.GuardError(f"missing axiom records: {sorted(set(names) - set(out))}") + return out + + +def semantic_rejection(returncode: int, output: str) -> bool: + if returncode == 0: return False + if returncode < 0: raise support.GuardError("mutant terminated by signal, not a proof rejection") + bad = ("unknown identifier", "unknown constant", "unknown module", "unknown package", + "unexpected token", "file not found", "no such file", "object file", "out of memory", + "maximum recursion depth", "deterministic timeout", "heartbeat", "timed out", + "failed to synthesize", "failed to import", "invalid import") + low = output.lower() + if any(t in low for t in bad): + raise support.GuardError("mutant failed for parser/import/resource/interface reasons") + if not re.search(r"unsolved goals|type mismatch|tactic[^\n]*(?:failed|failure)|failed to prove", low): + raise support.GuardError("mutant lacked a recognized semantic proof diagnostic") + return True + + +def mutated_source(source: str, name: str) -> str: + before, after = MUTATIONS[name] + if source.count(before) != 1: + raise support.GuardError(f"semantic mutation target missing or duplicated: {name}") + return source.replace(before, after, 1) + + +def check_dependencies(repo: Path) -> dict[str, str]: + manifest = support.strict_json(support.read_member(repo, "lake-manifest.json").decode("utf-8")) + if manifest.get("packagesDir") != ".lake/packages": + raise support.GuardError("unexpected dependency directory") + packages = manifest.get("packages") + if not isinstance(packages, list) or not packages: + raise support.GuardError("dependency manifest is empty") + found: dict[str, str] = {} + for package in packages: + if package.get("type") != "git": raise support.GuardError("non-Git dependency not supported") + name, rev = package["name"], package["rev"] + support.safe_relative(name) + if "/" in name or name in found: raise support.GuardError("bad/duplicate package name") + if not re.fullmatch(r"[0-9a-f]{40}", rev): raise support.GuardError("dependency is not commit pinned") + path = repo / ".lake/packages" / name + if not path.is_dir(): raise support.GuardError(f"pinned dependency checkout missing: {name}") + actual = support.git_text(path, "rev-parse", "HEAD") + if actual != rev: raise support.GuardError(f"dependency revision mismatch: {name}") + if support.git_bytes(path, "status", "--porcelain=v1", "--untracked-files=all"): + raise support.GuardError(f"dependency source is dirty: {name}") + found[name] = actual + if found.get("mathlib") != support.MATHLIB_REV: + raise support.GuardError("mathlib is not the audited exact revision") + return found + + +def prepare_output(repo: Path, output: Path) -> Path: + output = output.resolve() + if output == repo or output.is_relative_to(repo): + raise support.GuardError("evidence output must be outside the repository") + if output.exists() and (not output.is_dir() or any(output.iterdir())): + raise support.GuardError("evidence output must be fresh or empty; historical logs are not overwritten") + output.mkdir(parents=True, exist_ok=True) + return output + + +def verify(repo: Path, output: Path, *, mode: str = "full", timeout: int = 3600) -> dict[str, Any]: + repo = repo.resolve(strict=True) + output = prepare_output(repo, output) + report: dict[str, Any] = {"format": "Permansson.PR113.A2SuccessorCompilerCandidate.v1", + "contract": "Permansson.A2P02.PR113.SOURCE_PROFILE_REVIEW_PENDING", "mode": mode, "status": "NOT_RUN", + "started_at": datetime.now(timezone.utc).isoformat(), "commands": [], + "mutations": {}, "declarations": {}, "event_binding": "LOCAL_NOT_H_B_S_M_CI", + "scope": "Source/compiler evidence only, not empirical or Exact-GR certification"} + before: dict[str, Any] | None = None + environment = os.environ.copy() + environment["PYTHONDONTWRITEBYTECODE"] = "1" + environment["LAKE_NO_CACHE"] = "1" # no remote project build-cache replacement + for key in ("LEAN_PATH", "LEAN_SRC_PATH", "LEAN", "LEAN_SYSROOT", "LAKE_OVERRIDE_LEAN"): + environment.pop(key, None) + + def run(label: str, args: list[str], *, negative: bool = False, seconds: int = timeout) -> tuple[int, str]: + started = time.monotonic() + entry: dict[str, Any] = {"label": label, "argv": args, "timeout_seconds": seconds} + report["commands"].append(entry) + try: + p = subprocess.run(args, cwd=repo, env=environment, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, timeout=seconds) + raw = p.stdout + code = p.returncode + except subprocess.TimeoutExpired as exc: + raw = (exc.stdout or b"") + b"\nGATE_TIMEOUT\n" + code = -999 + except OSError as exc: + raw, code = str(exc).encode(), -998 + log = f"{len(report['commands']):02d}_{label}.log" + (output / log).write_bytes(raw) + entry.update(returncode=code, log=log, log_sha256=support.sha256(raw), + elapsed_seconds=round(time.monotonic() - started, 3)) + text = raw.decode("utf-8", "replace") + if code in (-999, -998) or (code != 0 and not negative): + raise support.GuardError(f"{label} failed ({code}): {text[-6000:]}") + return code, text + + try: + if mode not in {"full", "focused"}: raise support.GuardError("unsupported gate mode") + if type(timeout) is not int or timeout <= 0: raise support.GuardError("invalid timeout") + before = successor_identity.inspect_successor_repo(repo) + report["source"] = {k: v for k, v in before.items() if k != "files"} + (output / "source_before.json").write_text(json.dumps(before, indent=2, sort_keys=True) + "\n") + if support.read_member(repo, "lean-toolchain").decode().strip() != support.TOOLCHAIN: + raise support.GuardError("compiler pin changed") + if shutil.which("lake") is None: + raise support.GuardError("Lean/Lake unavailable; no compiler certification performed") + _, version = run("compiler_version", ["lake", "env", "lean", "--version"], seconds=180) + report["compiler"] = {"version": parse_version(version), "raw": version.strip()} + # Do not permit a build to silently substitute already-present dirty dependencies. + report["dependencies"] = check_dependencies(repo) + run("research_build", ["lake", "build", "PermanssonResearch"]) + if mode == "full": run("original_default_build", ["lake", "build"]) + # Explicitly elaborate all four final source files, even after an incremental build. + for i, source in enumerate(support.LIBRARIES, 1): + run(f"source_{i}", ["lake", "env", "lean", source]) + audit = "import PermanssonResearch\nset_option pp.universes true\n" + "\n".join( + f"#check @{name}\n#print axioms {name}" for name in DECLS) + "\n" + audit_file = output / "AuditSignatures.lean" + audit_file.write_text(audit, encoding="utf-8") + _, signature_log = run("signatures_axioms", ["lake", "env", "lean", str(audit_file)]) + report["declarations"] = parse_axioms(signature_log) + # This is a recompiled positive control using the same imports as each mutant. + source = support.read_member(repo, FIXTURE).decode("utf-8") + positive = output / "PositiveControl.lean" + positive.write_text(source, encoding="utf-8") + run("positive_fixture", ["lake", "env", "lean", str(positive)]) + for name in MUTATIONS: + mutation = mutated_source(source, name) + file = output / (name + ".lean") + file.write_text(mutation, encoding="utf-8") + code, text = run(name, ["lake", "env", "lean", str(file)], negative=True, seconds=300) + if not semantic_rejection(code, text): + raise support.GuardError(f"false mathematical mutation accepted: {name}") + report["mutations"][name] = {"status": "SEMANTICALLY_REJECTED", + "positive_source_sha256": support.sha256(source.encode()), + "mutant_source_sha256": support.sha256(mutation.encode())} + report["dependencies_after"] = check_dependencies(repo) + after = successor_identity.inspect_successor_repo(repo) + if any(before[key] != after[key] for key in ("source_digest", "git_head", "git_head_tree", "git_status")): + raise support.GuardError("source, HEAD or index changed during verification") + report["source_unchanged"] = True + report["status"] = "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING" + except Exception as exc: + report["status"] = "FAIL_CLOSED" + report["reason"] = str(exc) + if before is not None: + try: + after = successor_identity.inspect_successor_repo(repo) + report["source_unchanged"] = (before["source_digest"] == after["source_digest"]) + except Exception as post: + report["source_unchanged"] = False + report["postcheck_error"] = str(post) + report["historical_a2_verifier_replaced"] = False + report["independent_security_review"] = False + report["mathematical_acceptance"] = False + report["scientific_release_approved"] = False + report["finished_at"] = datetime.now(timezone.utc).isoformat() + # Receipts name and hash all generated files except themselves (no circular hash). + report["artifacts"] = {p.name: {"bytes": p.stat().st_size, "sha256": support.sha256(p.read_bytes())} + for p in sorted(output.iterdir()) if p.is_file()} + (output / "receipt.json").write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8") + return report + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--mode", choices=("focused", "full"), default="full") + args = parser.parse_args() + try: + report = verify(args.repo, args.output, mode=args.mode) + print(json.dumps(report, indent=2, sort_keys=True)) + except Exception as exc: + print(f"FAIL_CLOSED: {exc}", file=sys.stderr) + raise SystemExit(2) + if report["status"] == "FAIL_CLOSED": raise SystemExit(2) + + +if __name__ == "__main__": + main() diff --git a/verification/research/pr113_a2_successor_compiler_v2.py b/verification/research/pr113_a2_successor_compiler_v2.py new file mode 100644 index 00000000..e31e4101 --- /dev/null +++ b/verification/research/pr113_a2_successor_compiler_v2.py @@ -0,0 +1,267 @@ +"""Versioned active30 A2 compiler candidate; original verifier unchanged. + +Imported by test_localized_prefix_compiler.py in unchanged Research CI discovery. +Also runnable directly. No test discovery is called here (avoids recursive CI). +All evidence is written OUTSIDE the repository into a fresh or empty directory. +No compiler, wrong source, timeout or wrong failure mode => FAIL_CLOSED. +""" +from __future__ import annotations +import argparse +from datetime import datetime, timezone +import json +import os +from pathlib import Path +import re +import shutil +import subprocess +import sys +import time +from typing import Any + +if __package__: + from . import localized_prefix_support as support +else: + import localized_prefix_support as support + +import pr113_a2_successor_identity_v2 as successor_identity + +ALLOWED_AXIOMS = frozenset({"propext", "Classical.choice", "Quot.sound"}) +DECLS = tuple("PermanssonResearch.Probability." + x for x in ( + "goodHistoryMajorant", "canonicalPrefixKernel", "canonicalPrefixKernel_isMarkov", + "mixedFinitePrefixLaw", "mixedFinitePrefixLaw_isProbability", + "goodHistoryMajorant_measurable", "goodHistoryMajorant_nonneg", "goodHistoryMajorant_le_one", + "goodHistory_lintegral_le", "historyEvolution_tv_le_goodVisits", + "finitePrefixLaw_tv_le_averaged", "finitePrefixLaw_tv_le_goodVisits", + "finitePathExpectation_abs_le_goodVisits", "canonicalPrefixKernel_apply", + "mixedFinitePrefixLaw_dirac", "mixedFinitePrefixLaw_tv_le_initial", + "finitePrefixLaw_tv_le_goodVisits_initial", "finitePathExpectation_abs_le_goodVisits_initial", +)) + ("PermanssonResearch.ConstitutiveQuasi.LocalizedProtocolErrors",) + tuple("PermanssonResearch.ConstitutiveQuasi.LocalizedProtocolErrors." + x for x in ( + "baselineBudget", "interventionBudget", "baselineBudget_nonneg", "interventionBudget_nonneg", "toConstitutiveOutputErrors", + "reportedMargin_abs_le", "trueMargin_pos_of_reported", "reportedMargin_pos_of_true", +)) + tuple("PermanssonResearch.ConstitutiveQuasi.LocalizedExamples." + x for x in ( + "reportingProfile", "matchedPair", "reportedEffect_four_fifths", "initialScore", + "exactLocalizedErrors", "reportedMargin_four_fifths", "localizedBudget_total_one_fifth", + "originalMargin_ge_three_fifths", "originalMargin_positive", + "cannot_omit_baseline_report_error", "cannot_omit_treatment_report_error", + "initialScore_point", "initialMismatch_gap_one", +)) +FIXTURE = support.LIBRARIES[-1] +MUTATIONS = { + "omit_baseline_reporting_error": (" reportBase := 1 / 10\n", " reportBase := 0\n"), + "omit_treated_reporting_error": (" reportTreated := 1 / 10\n", " reportTreated := 0\n"), + "wrong_reported_effect": ( + "perturbedConstitutiveEffect reportingProfile y = 4 / 5 := by", + "perturbedConstitutiveEffect reportingProfile y = 3 / 4 := by"), + "omit_zero_horizon_initial_error": ( + "(mixedFinitePrefixLaw (Kernel.id : Kernel Bool Bool) (Measure.dirac true) 0)| = 1 := by", + "(mixedFinitePrefixLaw (Kernel.id : Kernel Bool Bool) (Measure.dirac true) 0)| = 0 := by"), +} + + +def parse_version(text: str) -> str: + found = re.findall(r"\bversion\s+([^,\s)]+)", text) + if found != ["4.34.0"]: + raise support.GuardError(f"expected exact Lean release 4.34.0, got {text.strip()!r}") + return found[0] + + +def parse_axioms(text: str, names: tuple[str, ...] = DECLS) -> dict[str, list[str]]: + normalized = re.sub(r"\.\{[^{}\n]*\}", "", text) + pattern = re.compile( + r"['`]?([A-Za-z_][A-Za-z0-9_.]*)['`]?\s+" + r"(?:depends on axioms:\s*\[([^\]]*)\]|does not depend on any axioms)", re.S) + out: dict[str, list[str]] = {} + for match in pattern.finditer(normalized): + name, atoms = match.groups() + if name not in names: raise support.GuardError(f"unexpected axiom declaration: {name}") + if name in out: raise support.GuardError(f"duplicate axiom record: {name}") + axioms = [] if atoms is None else [a.strip() for a in atoms.split(",") if a.strip()] + if not set(axioms) <= ALLOWED_AXIOMS: + raise support.GuardError(f"prohibited transitive axiom for {name}: {axioms}") + out[name] = sorted(set(axioms)) + if set(out) != set(names): + raise support.GuardError(f"missing axiom records: {sorted(set(names) - set(out))}") + return out + + +def semantic_rejection(returncode: int, output: str) -> bool: + if returncode == 0: return False + if returncode < 0: raise support.GuardError("mutant terminated by signal, not a proof rejection") + bad = ("unknown identifier", "unknown constant", "unknown module", "unknown package", + "unexpected token", "file not found", "no such file", "object file", "out of memory", + "maximum recursion depth", "deterministic timeout", "heartbeat", "timed out", + "failed to synthesize", "failed to import", "invalid import") + low = output.lower() + if any(t in low for t in bad): + raise support.GuardError("mutant failed for parser/import/resource/interface reasons") + if not re.search(r"unsolved goals|type mismatch|tactic[^\n]*(?:failed|failure)|failed to prove", low): + raise support.GuardError("mutant lacked a recognized semantic proof diagnostic") + return True + + +def mutated_source(source: str, name: str) -> str: + before, after = MUTATIONS[name] + if source.count(before) != 1: + raise support.GuardError(f"semantic mutation target missing or duplicated: {name}") + return source.replace(before, after, 1) + + +def check_dependencies(repo: Path) -> dict[str, str]: + manifest = support.strict_json(support.read_member(repo, "lake-manifest.json").decode("utf-8")) + if manifest.get("packagesDir") != ".lake/packages": + raise support.GuardError("unexpected dependency directory") + packages = manifest.get("packages") + if not isinstance(packages, list) or not packages: + raise support.GuardError("dependency manifest is empty") + found: dict[str, str] = {} + for package in packages: + if package.get("type") != "git": raise support.GuardError("non-Git dependency not supported") + name, rev = package["name"], package["rev"] + support.safe_relative(name) + if "/" in name or name in found: raise support.GuardError("bad/duplicate package name") + if not re.fullmatch(r"[0-9a-f]{40}", rev): raise support.GuardError("dependency is not commit pinned") + path = repo / ".lake/packages" / name + if not path.is_dir(): raise support.GuardError(f"pinned dependency checkout missing: {name}") + actual = support.git_text(path, "rev-parse", "HEAD") + if actual != rev: raise support.GuardError(f"dependency revision mismatch: {name}") + if support.git_bytes(path, "status", "--porcelain=v1", "--untracked-files=all"): + raise support.GuardError(f"dependency source is dirty: {name}") + found[name] = actual + if found.get("mathlib") != support.MATHLIB_REV: + raise support.GuardError("mathlib is not the audited exact revision") + return found + + +def prepare_output(repo: Path, output: Path) -> Path: + output = output.resolve() + if output == repo or output.is_relative_to(repo): + raise support.GuardError("evidence output must be outside the repository") + if output.exists() and (not output.is_dir() or any(output.iterdir())): + raise support.GuardError("evidence output must be fresh or empty; historical logs are not overwritten") + output.mkdir(parents=True, exist_ok=True) + return output + + +def verify(repo: Path, output: Path, *, mode: str = "full", timeout: int = 3600) -> dict[str, Any]: + repo = repo.resolve(strict=True) + output = prepare_output(repo, output) + report: dict[str, Any] = {"format": "Permansson.PR113.A2SuccessorCompilerCandidate.v2", + "contract": "Permansson.A2P02.PR113.SOURCE_PROFILE_REVIEW_PENDING", "mode": mode, "status": "NOT_RUN", + "started_at": datetime.now(timezone.utc).isoformat(), "commands": [], + "mutations": {}, "declarations": {}, "event_binding": "LOCAL_NOT_H_B_S_M_CI", + "scope": "Source/compiler evidence only, not empirical or Exact-GR certification"} + before: dict[str, Any] | None = None + environment = os.environ.copy() + environment["PYTHONDONTWRITEBYTECODE"] = "1" + environment["LAKE_NO_CACHE"] = "1" # no remote project build-cache replacement + for key in ("LEAN_PATH", "LEAN_SRC_PATH", "LEAN", "LEAN_SYSROOT", "LAKE_OVERRIDE_LEAN"): + environment.pop(key, None) + + def run(label: str, args: list[str], *, negative: bool = False, seconds: int = timeout) -> tuple[int, str]: + started = time.monotonic() + entry: dict[str, Any] = {"label": label, "argv": args, "timeout_seconds": seconds} + report["commands"].append(entry) + try: + p = subprocess.run(args, cwd=repo, env=environment, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, timeout=seconds) + raw = p.stdout + code = p.returncode + except subprocess.TimeoutExpired as exc: + raw = (exc.stdout or b"") + b"\nGATE_TIMEOUT\n" + code = -999 + except OSError as exc: + raw, code = str(exc).encode(), -998 + log = f"{len(report['commands']):02d}_{label}.log" + (output / log).write_bytes(raw) + entry.update(returncode=code, log=log, log_sha256=support.sha256(raw), + elapsed_seconds=round(time.monotonic() - started, 3)) + text = raw.decode("utf-8", "replace") + if code in (-999, -998) or (code != 0 and not negative): + raise support.GuardError(f"{label} failed ({code}): {text[-6000:]}") + return code, text + + try: + if mode not in {"full", "focused"}: raise support.GuardError("unsupported gate mode") + if type(timeout) is not int or timeout <= 0: raise support.GuardError("invalid timeout") + before = successor_identity.inspect_successor_repo(repo) + report["source"] = {k: v for k, v in before.items() if k != "files"} + (output / "source_before.json").write_text(json.dumps(before, indent=2, sort_keys=True) + "\n") + if support.read_member(repo, "lean-toolchain").decode().strip() != support.TOOLCHAIN: + raise support.GuardError("compiler pin changed") + if shutil.which("lake") is None: + raise support.GuardError("Lean/Lake unavailable; no compiler certification performed") + _, version = run("compiler_version", ["lake", "env", "lean", "--version"], seconds=180) + report["compiler"] = {"version": parse_version(version), "raw": version.strip()} + # Do not permit a build to silently substitute already-present dirty dependencies. + report["dependencies"] = check_dependencies(repo) + run("research_build", ["lake", "build", "PermanssonResearch"]) + if mode == "full": run("original_default_build", ["lake", "build"]) + # Explicitly elaborate all four final source files, even after an incremental build. + for i, source in enumerate(support.LIBRARIES, 1): + run(f"source_{i}", ["lake", "env", "lean", source]) + audit = "import PermanssonResearch\nset_option pp.universes true\n" + "\n".join( + f"#check @{name}\n#print axioms {name}" for name in DECLS) + "\n" + audit_file = output / "AuditSignatures.lean" + audit_file.write_text(audit, encoding="utf-8") + _, signature_log = run("signatures_axioms", ["lake", "env", "lean", str(audit_file)]) + report["declarations"] = parse_axioms(signature_log) + # This is a recompiled positive control using the same imports as each mutant. + source = support.read_member(repo, FIXTURE).decode("utf-8") + positive = output / "PositiveControl.lean" + positive.write_text(source, encoding="utf-8") + run("positive_fixture", ["lake", "env", "lean", str(positive)]) + for name in MUTATIONS: + mutation = mutated_source(source, name) + file = output / (name + ".lean") + file.write_text(mutation, encoding="utf-8") + code, text = run(name, ["lake", "env", "lean", str(file)], negative=True, seconds=300) + if not semantic_rejection(code, text): + raise support.GuardError(f"false mathematical mutation accepted: {name}") + report["mutations"][name] = {"status": "SEMANTICALLY_REJECTED", + "positive_source_sha256": support.sha256(source.encode()), + "mutant_source_sha256": support.sha256(mutation.encode())} + report["dependencies_after"] = check_dependencies(repo) + after = successor_identity.inspect_successor_repo(repo) + if any(before[key] != after[key] for key in ("source_digest", "git_head", "git_head_tree", "git_status")): + raise support.GuardError("source, HEAD or index changed during verification") + report["source_unchanged"] = True + report["status"] = "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING" + except Exception as exc: + report["status"] = "FAIL_CLOSED" + report["reason"] = str(exc) + if before is not None: + try: + after = successor_identity.inspect_successor_repo(repo) + report["source_unchanged"] = (before["source_digest"] == after["source_digest"]) + except Exception as post: + report["source_unchanged"] = False + report["postcheck_error"] = str(post) + report["historical_a2_verifier_replaced"] = False + report["independent_security_review"] = False + report["mathematical_acceptance"] = False + report["scientific_release_approved"] = False + report["finished_at"] = datetime.now(timezone.utc).isoformat() + # Receipts name and hash all generated files except themselves (no circular hash). + report["artifacts"] = {p.name: {"bytes": p.stat().st_size, "sha256": support.sha256(p.read_bytes())} + for p in sorted(output.iterdir()) if p.is_file()} + (output / "receipt.json").write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8") + return report + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--mode", choices=("focused", "full"), default="full") + args = parser.parse_args() + try: + report = verify(args.repo, args.output, mode=args.mode) + print(json.dumps(report, indent=2, sort_keys=True)) + except Exception as exc: + print(f"FAIL_CLOSED: {exc}", file=sys.stderr) + raise SystemExit(2) + if report["status"] == "FAIL_CLOSED": raise SystemExit(2) + + +if __name__ == "__main__": + main() diff --git a/verification/research/pr113_a2_successor_identity.py b/verification/research/pr113_a2_successor_identity.py new file mode 100644 index 00000000..820c024e --- /dev/null +++ b/verification/research/pr113_a2_successor_identity.py @@ -0,0 +1,237 @@ +"""PR113 successor A2-P02 source-identity proposal: a NEW read-only profile. + +The original PR79/PR80 compiler/support remain unchanged. The current +integration adapter replays them at their original commit; current source +is checked separately. Original integration bytes remain pinned in history. +This separately versioned candidate pins the *entire* known T/B-to-PR113 +modified-provider delta by actual Git blob identity, preserves all original +A2 mathematical provider objects, and checks the complete clean current tree. + +It is NOT an authorized replacement for the historical verifier and cannot +issue mathematical acceptance, security approval, or a release receipt. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import re +import sys + +import localized_prefix_support as original + +FORMAT = "Permansson.PR113.A2SuccessorIdentityCandidate.v1" +SHA = re.compile(r"^[0-9a-f]{40}$") +# The exact twelve already-changed former baseline files. No wildcard grants. +PINNED_MODIFIED_PROVIDERS = { + ".github/workflows/application.yml": "bdd02d48265e08f8cb94b7402da56da9c8e3ec9d", + ".github/workflows/e1-approx-quotient-smoke.yml": "11bba31567059d968133a513c78b9103e43418fc", + ".github/workflows/f1-multiagent-protocols-smoke.yml": "b87a8c082f51f24dd7d533147e676813d1a78b85", + ".github/workflows/lean.yml": "65aa095a9564cbef0bdca035c95b6d8f568d2cf4", + ".github/workflows/research-integrity-portability.yml": "380a592039dd9e50b87bb2891771e76b071a2aaa", + ".github/workflows/research-integrity.yml": "19bab92285ab2c981b0a1200c03024f7d022e31c", + ".github/workflows/research-lean.yml": "06842a2da34d6ff06434f202a7630edd57e8cb20", + ".github/workflows/research-release-verification.yml": "46ea7ebeae142f794394179154385cf1762b78d3", + ".github/workflows/verification.yml": "c016aa6efcc42e19d81cb148f5db6bc6a439d7d3", + "verification/integrity/tests/test_workflow.py": "1fa90e0dce90518b8052e42199867f11c77e1010", + "verification/integrity/workflow_contract.py": "f8fa8196a7a0e33d2f218faa0ba570c09aac9a3a", + "verification/research/test_paper_x1_lean_closure.py": "2ee078c86c58b5e577597acfc3ae950e4028ebac", +} +# Exact current callable surface: two unchanged historical executors and +# the separately versioned engineering replay integration adapter. The +# original integration blob c0f49038 remains pinned at its historical commit. +INHERITED_A2_VERIFIERS = { + "verification/research/localized_prefix_support.py": "93462e9546ba494e8384d75ce49ff9020f831216", + "verification/research/localized_prefix_gate.py": "6f15dca74895e59a4d4bfcb54d38b471a1798be3", + "verification/research/test_localized_prefix_compiler.py": "cb3384bce991e1fb7900cfae6633d61c2735561a", +} + + +def demand(ok: bool, message: str) -> None: + if not ok: + raise original.GuardError(message) + + +def reconcile(old: dict, current: dict) -> dict: + """Reviewable pure Git-tree delta gate, never a blob-prefix wildcard.""" + demand(set(old) <= set(current), "historical provider deleted") + modified = {p for p in old if old[p] != current[p]} + exceptions = original.REVIEWABLE_DOCUMENTATION | {"PermanssonResearch.lean"} + demand(modified - exceptions == set(PINNED_MODIFIED_PROVIDERS), + "PR113 successor source delta differs from exact frozen inventory") + for path, oid in PINNED_MODIFIED_PROVIDERS.items(): + demand(current[path] == ("100644", oid), + "PR113 pinned provider changed without new security review: " + path) + for path in modified & original.REVIEWABLE_DOCUMENTATION: + demand(old[path][0] == current[path][0] == "100644", + "reviewable document mode changed: " + path) + demand("PermanssonResearch.lean" in modified, + "A2 research-root extension unexpectedly absent") + for path, oid in original.ACCEPTED_A2_PROOF_BLOBS.items(): + demand(current.get(path) == ("100644", oid), + "accepted A2 mathematical proof blob changed: " + path) + for path, oid in INHERITED_A2_VERIFIERS.items(): + demand(current.get(path) == ("100644", oid), + "pinned A2 executor/integration source changed: " + path) + for path in ("lean-toolchain", "lake-manifest.json"): + demand(old.get(path) == current.get(path), + "frozen Lean/dependency pin changed: " + path) + return { + "modified_old_provider_paths": sorted(modified), + "pinned_successor_provider_paths": sorted(PINNED_MODIFIED_PROVIDERS), + "reviewable_documentation_paths": sorted( + modified & original.REVIEWABLE_DOCUMENTATION), + "accepted_a2_proof_blobs": len(original.ACCEPTED_A2_PROOF_BLOBS), + "historical_a2_verifier_blobs": len(INHERITED_A2_VERIFIERS), + } + + +def normalize_additive_root_separator(raw: bytes) -> bytes: + """Permit only the two blank lines introduced at the original A2 tail. + + This is an exact-position formatting equivalence, never global + whitespace canonicalization of mathematical source. + """ + anchor = (b"import PermanssonResearch.ConstitutiveQuasi." + b"LocalizedPrefixExamples\n") + original = anchor + b"\n/-!\n" + current = anchor + b"\n\n\n/-!\n" + if raw.count(current) == 1 and raw.count(original) == 0: + return raw.replace(current, original, 1) + return raw + + +def verify_root(repo: Path, old: dict, current: dict) -> None: + """Original A2 imports plus direct imports of truly new modules only.""" + source = original.git_bytes(repo, "show", + original.BASE + ":PermanssonResearch.lean") + demand(original.blob_sha(source) == original.ROOT_BLOB, + "original A2 root anchor changed") + expected = original.rewrite_root(source) + actual = original.read_member(repo, "PermanssonResearch.lean") + demand(original.blob_sha(actual) == current["PermanssonResearch.lean"][1], + "current root differs from committed Git blob") + for path in sorted(set(current) - set(old)): + if path.startswith("PermanssonResearch/") and path.endswith(".lean"): + module = path.removesuffix(".lean").replace("/", ".") + line = ("import " + module + "\n").encode("utf-8") + if line in expected: # One of the original A2 new imports. + continue + demand(actual.count(line) == 1, + "missing/duplicated direct import: " + module) + actual = actual.replace(line, b"", 1) + demand(normalize_additive_root_separator(actual) == expected, + "Research root differs from exact historical A2 import bridge") + + + +PR113_BASE = "17c315f7df7abf457eb94d393bd526d46da6f6f4" + + +def check_checkout_identity(repo: Path, head: str) -> tuple[str, str]: + """Accept exact H or a real ordered [B,H] synthetic merge S, not a label.""" + checked = original.git_text(repo, "rev-parse", "HEAD") + tree_h = original.git_text(repo, "rev-parse", head + "^{tree}") + tree_checked = original.git_text(repo, "rev-parse", "HEAD^{tree}") + if checked != head: + parents = original.git_text( + repo, "show", "-s", "--format=%P", checked).split() + demand(parents == [PR113_BASE, head], + "checked-out S is not a real ordered [B,H] commit") + demand(tree_checked == tree_h, + "checked-out source tree differs from actual H tree") + return checked, tree_h + + +def inspect_successor_repo(repo: Path) -> dict: + """Unapproved successor inspection for the actual pinned Lean runtime.""" + checked = original.git_text(repo, "rev-parse", "HEAD") + parents = original.git_text(repo, "show", "-s", "--format=%P", checked).split() + head = parents[1] if len(parents) == 2 and parents[0] == PR113_BASE else checked + record = audit(repo, head) + return { + **record, + "source_digest": record["head_tree_sha"], + "git_head": record["checked_sha"], + "git_head_tree": record["head_tree_sha"], + "git_status": [], + "files": {}, + } + + +def audit(repo: Path, head: str) -> dict: + repo = repo.resolve(strict=True) + demand(SHA.fullmatch(head) is not None, "invalid reviewed head") + demand(Path(original.git_text(repo, "rev-parse", "--show-toplevel")).resolve() + == repo, "not a genuine repository root") + checked, current_tree = check_checkout_identity(repo, head) + demand(original.git_text(repo, "rev-parse", + original.BASE + "^{tree}") == original.BASE_TREE, + "historical PR79 source/tree identity changed") + original.git_bytes(repo, "merge-base", "--is-ancestor", original.BASE, head) + demand(not original.git_bytes(repo, "status", "--porcelain=v1", + "--untracked-files=all"), + "successor source has tracked or untracked modifications") + old = original.parse_tree(original.git_bytes( + repo, "ls-tree", "-r", "-z", original.BASE)) + current = original.parse_tree(original.git_bytes( + repo, "ls-tree", "-r", "-z", "HEAD")) + reconciled = reconcile(old, current) + verify_root(repo, old, current) + # No ignored/untracked/assume-unchanged source or byte substitution. + for path, (mode, blob) in sorted(current.items()): + actual = original.read_member(repo, path) + demand(original.blob_sha(actual) == blob, + "source file differs from exact Git blob: " + path) + if path in original.REVIEWABLE_DOCUMENTATION: + demand(b"\r" not in actual and actual.endswith(b"\n"), + "noncanonical reviewed documentation: " + path) + if path in original.ACCEPTED_A2_PROOF_BLOBS: + original.source_firewall(actual.decode("utf-8", "strict")) + if path.endswith(".lean") and path.startswith("PermanssonResearch/"): + demand(mode == "100644", "unexpected research proof mode: " + path) + return { + "format": FORMAT, + "status": "SOURCE_IDENTITY_CANDIDATE_NEEDS_INDEPENDENT_SECURITY_REVIEW", + "head_sha": head, + "checked_sha": checked, + "head_tree_sha": current_tree, + "inherited_baseline": original.BASE, + "inherited_baseline_tree": original.BASE_TREE, + "successor_delta": reconciled, + "current_file_count": len(current), + "source_byte_identity_checked": True, + "original_a2_compiler_run": False, + "successor_a2_compiler_run": False, + "historical_verifier_replaced": False, + "independent_security_review": False, + "mathematical_acceptance": False, + "full_release_approved": False, + } + + +def main(argv=None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--head", required=True) + parser.add_argument("--repo", type=Path, default=Path(__file__).resolve().parents[2]) + parser.add_argument("--out", type=Path, required=True) + options = parser.parse_args(argv) + try: + target = options.out.resolve() + repo = options.repo.resolve(strict=True) + demand(not target.exists() and not target.is_relative_to(repo), + "report must be a fresh detached path") + report = audit(repo, options.head) + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", + encoding="utf-8") + print(json.dumps({"status": report["status"], + "head_sha": report["head_sha"]})) + return 0 + except (original.GuardError, OSError, UnicodeError, ValueError) as exc: + print("PR113 A2 SUCCESSOR IDENTITY BLOCKED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_a2_successor_identity_v2.py b/verification/research/pr113_a2_successor_identity_v2.py new file mode 100644 index 00000000..31b5b95f --- /dev/null +++ b/verification/research/pr113_a2_successor_identity_v2.py @@ -0,0 +1,273 @@ +"""UNAPPROVED active30 A2 successor source-identity profile v2. + +The inherited PR79/PR80 localized_prefix_support.inspect_repo and its test +remain untouched, and intentionally reject changed historical providers. +This separately versioned candidate pins the *entire* known T/B-to-PR113 +modified-provider delta by actual Git blob identity, preserves all original +A2 mathematical provider objects, and checks the complete clean current tree. + +It is NOT an authorized replacement for the historical verifier and cannot +issue mathematical acceptance, security approval, or a release receipt. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import re +import sys + +import localized_prefix_support as original + +FORMAT = "Permansson.PR113.A2SuccessorIdentityCandidate.v2" +SHA = re.compile(r"^[0-9a-f]{40}$") +# Exactly twelve providers present in PR79 BASE and modified at current source. +# The later-introduced A2 test is protected under a different historical anchor. +PINNED_MODIFIED_PROVIDERS = { + ".github/workflows/application.yml": "bdd02d48265e08f8cb94b7402da56da9c8e3ec9d", + ".github/workflows/e1-approx-quotient-smoke.yml": "11bba31567059d968133a513c78b9103e43418fc", + ".github/workflows/f1-multiagent-protocols-smoke.yml": "b87a8c082f51f24dd7d533147e676813d1a78b85", + ".github/workflows/lean.yml": "65aa095a9564cbef0bdca035c95b6d8f568d2cf4", + ".github/workflows/research-integrity-portability.yml": "380a592039dd9e50b87bb2891771e76b071a2aaa", + ".github/workflows/research-integrity.yml": "76ab437ef4781e534b0bf7de79c646890dae1be7", + ".github/workflows/research-lean.yml": "8e993121fdd0e278a2b9585f90dd73795058d938", + ".github/workflows/research-release-verification.yml": "46ea7ebeae142f794394179154385cf1762b78d3", + ".github/workflows/verification.yml": "c016aa6efcc42e19d81cb148f5db6bc6a439d7d3", + "verification/integrity/tests/test_workflow.py": "d49819f064a4da330d4ba8fcc1456232fdc27fb6", + "verification/integrity/workflow_contract.py": "cf8b89114adc280dbb2bb68ff6037a0822e8c1d2", + "verification/research/test_paper_x1_lean_closure.py": "2ee078c86c58b5e577597acfc3ae950e4028ebac", +} +# This historical test did NOT exist at PR79 BASE. The real A2 introducer +# committed its original source; an approved 30-workflow cutover changes it. +LATE_A2_TEST = "verification/research/test_localized_prefix_compiler.py" +LATE_A2_TEST_PROPOSED_BLOB = "89d1772038e0d6e07d095e44ddf4a2bb93c86ee4" +LATE_A2_TEST_ORIGINAL_BLOB = "c0f49038ea9b590720c78be85968eb63f5ac820f" +HISTORICAL_A2_INTRODUCER = "f71a76032b169aac7bfe0694aa715339a94d4e05" + +# Preserve original A2 executor and support source byte-for-byte. +INHERITED_A2_VERIFIERS = { + "verification/research/localized_prefix_support.py": "93462e9546ba494e8384d75ce49ff9020f831216", + "verification/research/localized_prefix_gate.py": "6f15dca74895e59a4d4bfcb54d38b471a1798be3", +} + + +def demand(ok: bool, message: str) -> None: + if not ok: + raise original.GuardError(message) + + +def reconcile(old: dict, current: dict) -> dict: + """Reviewable pure Git-tree delta gate, never a blob-prefix wildcard.""" + demand(set(old) <= set(current), "historical provider deleted") + demand(LATE_A2_TEST not in old, + "forged PR79 baseline membership for later-introduced A2 test") + demand(current.get(LATE_A2_TEST) == ("100644", LATE_A2_TEST_PROPOSED_BLOB), + "late-introduced A2 test was not replaced by exact reviewed blob") + modified = {p for p in old if old[p] != current[p]} + exceptions = original.REVIEWABLE_DOCUMENTATION | {"PermanssonResearch.lean"} + demand(modified - exceptions == set(PINNED_MODIFIED_PROVIDERS), + "PR113 successor source delta differs from exact frozen inventory") + for path, oid in PINNED_MODIFIED_PROVIDERS.items(): + demand(current[path] == ("100644", oid), + "PR113 pinned provider changed without new security review: " + path) + for path in modified & original.REVIEWABLE_DOCUMENTATION: + demand(old[path][0] == current[path][0] == "100644", + "reviewable document mode changed: " + path) + demand("PermanssonResearch.lean" in modified, + "A2 research-root extension unexpectedly absent") + for path, oid in original.ACCEPTED_A2_PROOF_BLOBS.items(): + demand(current.get(path) == ("100644", oid), + "accepted A2 mathematical proof blob changed: " + path) + for path, oid in INHERITED_A2_VERIFIERS.items(): + demand(current.get(path) == ("100644", oid), + "inherited A2 historical verifier changed: " + path) + for path in ("lean-toolchain", "lake-manifest.json"): + demand(old.get(path) == current.get(path), + "frozen Lean/dependency pin changed: " + path) + return { + "modified_old_provider_paths": sorted(modified), + "pinned_successor_provider_paths": sorted(PINNED_MODIFIED_PROVIDERS), + "late_introduced_A2_test": LATE_A2_TEST, + "late_A2_original_introducer": HISTORICAL_A2_INTRODUCER, + "late_A2_original_blob": LATE_A2_TEST_ORIGINAL_BLOB, + "late_A2_proposed_blob": LATE_A2_TEST_PROPOSED_BLOB, + "reviewable_documentation_paths": sorted( + modified & original.REVIEWABLE_DOCUMENTATION), + "accepted_a2_proof_blobs": len(original.ACCEPTED_A2_PROOF_BLOBS), + "historical_a2_verifier_blobs": len(INHERITED_A2_VERIFIERS), + } + + +def normalize_additive_root_separator(raw: bytes) -> bytes: + """Permit only the two blank lines introduced at the original A2 tail. + + This is an exact-position formatting equivalence, never global + whitespace canonicalization of mathematical source. + """ + anchor = (b"import PermanssonResearch.ConstitutiveQuasi." + b"LocalizedPrefixExamples\n") + original = anchor + b"\n/-!\n" + current = anchor + b"\n\n\n/-!\n" + if raw.count(current) == 1 and raw.count(original) == 0: + return raw.replace(current, original, 1) + return raw + + +def verify_root(repo: Path, old: dict, current: dict) -> None: + """Original A2 imports plus direct imports of truly new modules only.""" + source = original.git_bytes(repo, "show", + original.BASE + ":PermanssonResearch.lean") + demand(original.blob_sha(source) == original.ROOT_BLOB, + "original A2 root anchor changed") + expected = original.rewrite_root(source) + actual = original.read_member(repo, "PermanssonResearch.lean") + demand(original.blob_sha(actual) == current["PermanssonResearch.lean"][1], + "current root differs from committed Git blob") + for path in sorted(set(current) - set(old)): + if path.startswith("PermanssonResearch/") and path.endswith(".lean"): + module = path.removesuffix(".lean").replace("/", ".") + line = ("import " + module + "\n").encode("utf-8") + if line in expected: # One of the original A2 new imports. + continue + demand(actual.count(line) == 1, + "missing/duplicated direct import: " + module) + actual = actual.replace(line, b"", 1) + demand(normalize_additive_root_separator(actual) == expected, + "Research root differs from exact historical A2 import bridge") + + + +PR113_BASE = "17c315f7df7abf457eb94d393bd526d46da6f6f4" + + +def check_checkout_identity(repo: Path, head: str) -> tuple[str, str]: + """Accept exact H or a real ordered [B,H] synthetic merge S, not a label.""" + checked = original.git_text(repo, "rev-parse", "HEAD") + tree_h = original.git_text(repo, "rev-parse", head + "^{tree}") + tree_checked = original.git_text(repo, "rev-parse", "HEAD^{tree}") + if checked != head: + parents = original.git_text( + repo, "show", "-s", "--format=%P", checked).split() + demand(parents == [PR113_BASE, head], + "checked-out S is not a real ordered [B,H] commit") + demand(tree_checked == tree_h, + "checked-out source tree differs from actual H tree") + return checked, tree_h + + +def inspect_successor_repo(repo: Path) -> dict: + """Unapproved successor inspection for the actual pinned Lean runtime.""" + checked = original.git_text(repo, "rev-parse", "HEAD") + parents = original.git_text(repo, "show", "-s", "--format=%P", checked).split() + head = parents[1] if len(parents) == 2 and parents[0] == PR113_BASE else checked + record = audit(repo, head) + return { + **record, + "source_digest": record["head_tree_sha"], + "git_head": record["checked_sha"], + "git_head_tree": record["head_tree_sha"], + "git_status": [], + "files": {}, + } + + +def audit(repo: Path, head: str) -> dict: + repo = repo.resolve(strict=True) + demand(SHA.fullmatch(head) is not None, "invalid reviewed head") + demand(Path(original.git_text(repo, "rev-parse", "--show-toplevel")).resolve() + == repo, "not a genuine repository root") + checked, current_tree = check_checkout_identity(repo, head) + demand(original.git_text(repo, "rev-parse", + original.BASE + "^{tree}") == original.BASE_TREE, + "historical PR79 source/tree identity changed") + original.git_bytes(repo, "merge-base", "--is-ancestor", original.BASE, head) + original.git_bytes( + repo, "merge-base", "--is-ancestor", HISTORICAL_A2_INTRODUCER, head) + demand(original.git_text( + repo,"rev-parse",HISTORICAL_A2_INTRODUCER+":"+LATE_A2_TEST) + == LATE_A2_TEST_ORIGINAL_BLOB, + "actual historical A2 introducer test Git blob changed") + demand(not original.git_bytes(repo, "status", "--porcelain=v1", + "--untracked-files=all"), + "successor source has tracked or untracked modifications") + old = original.parse_tree(original.git_bytes( + repo, "ls-tree", "-r", "-z", original.BASE)) + current = original.parse_tree(original.git_bytes( + repo, "ls-tree", "-r", "-z", "HEAD")) + reconciled = reconcile(old, current) + # Exact active 30-workflow trust surface, including the staged A2 test, + # must be present. A 25-file source is never a successor A2 certificate. + from pr113_workflow_profile_v2 import audit as versioned_profile + # H is the reviewed source; checked is the actual checkout. In required + # PR CI checked may be the genuine ordered [B113,H] synthetic merge S. + # check_checkout_identity already proves identical trees and parents. + source_profile = versioned_profile(repo, checked, phase="active") + demand(source_profile["head_sha"] == checked and + original.git_text(repo, "rev-parse", checked + "^{tree}") == current_tree and + original.git_text(repo, "rev-parse", head + "^{tree}") == current_tree and + source_profile["current_reviewed_workflow_count"] == 30 and + source_profile["independent_security_review"] is False, + "A2 successor did not authenticate exact active 30-workflow bytes") + verify_root(repo, old, current) + # No ignored/untracked/assume-unchanged source or byte substitution. + for path, (mode, blob) in sorted(current.items()): + actual = original.read_member(repo, path) + demand(original.blob_sha(actual) == blob, + "source file differs from exact Git blob: " + path) + if path in original.REVIEWABLE_DOCUMENTATION: + demand(b"\r" not in actual and actual.endswith(b"\n"), + "noncanonical reviewed documentation: " + path) + if path in original.ACCEPTED_A2_PROOF_BLOBS: + original.source_firewall(actual.decode("utf-8", "strict")) + if path.endswith(".lean") and path.startswith("PermanssonResearch/"): + demand(mode == "100644", "unexpected research proof mode: " + path) + return { + "format": FORMAT, + "status": "SOURCE_IDENTITY_CANDIDATE_NEEDS_INDEPENDENT_SECURITY_REVIEW", + "head_sha": head, + "checked_sha": checked, + "head_tree_sha": current_tree, + "inherited_baseline": original.BASE, + "inherited_baseline_tree": original.BASE_TREE, + "successor_delta": reconciled, + "current_file_count": len(current), + "source_byte_identity_checked": True, + "current_workflow_profile_sha256": + source_profile["current_source_profile_sha256"], + "current_reviewed_workflow_count":30, + "historical_original_workflow_count":25, + "owner_activation_authorized":False, + "original_a2_compiler_run": False, + "successor_a2_compiler_run": False, + "historical_verifier_replaced": False, + "independent_security_review": False, + "mathematical_acceptance": False, + "full_release_approved": False, + } + + +def main(argv=None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--head", required=True) + parser.add_argument("--repo", type=Path, default=Path(__file__).resolve().parents[2]) + parser.add_argument("--out", type=Path, required=True) + options = parser.parse_args(argv) + try: + target = options.out.resolve() + repo = options.repo.resolve(strict=True) + demand(not target.exists() and not target.is_relative_to(repo), + "report must be a fresh detached path") + report = audit(repo, options.head) + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", + encoding="utf-8") + print(json.dumps({"status": report["status"], + "head_sha": report["head_sha"]})) + return 0 + except (original.GuardError, OSError, UnicodeError, ValueError) as exc: + print("PR113 A2 SUCCESSOR IDENTITY BLOCKED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_archive_pre_activation_rehearsal_v1.py b/verification/research/pr113_archive_pre_activation_rehearsal_v1.py new file mode 100644 index 00000000..fa572693 --- /dev/null +++ b/verification/research/pr113_archive_pre_activation_rehearsal_v1.py @@ -0,0 +1,167 @@ +"""Preactivation T/H rehearsal. NOT authentic active-30 T2/A2 source evidence. + +Historical T runs the unchanged offline verifier; H runs the current 25-file +workflow tests; an independent exact-Git staged audit checks the virtual 30. +No original archive, protected workflow, reviewer or check-run is rewritten. +""" +from __future__ import annotations +import argparse +import json +from pathlib import Path +import sys + +import pr113_dual_tooling_candidate_v2 as dual +from pr113_boundary_io import detached_destination, strict_json_object +from pr113_tooling_migration_v2 import audit as tooling_audit +from pr113_tooling_migration import ROOT, T + +FORMAT = "Permansson.PR113.ArchivePreactivationRehearsal.v1" +STATUS = "STAGED_25_T_H_VIRTUAL_30_REHEARSED_UNAPPROVED" +MAX_LOG_BYTES = 4 * 1024 * 1024 + + +class RehearsalError(ValueError): + pass + + +def need(ok, message): + if not ok: + raise RehearsalError(message) + + +def validate_reports(old, new, profile, head): + need(isinstance(profile, dict) + and profile.get("format") == "Permansson.PR113.ToolingMigrationCandidate.v2" + and profile.get("status") == "T2_EXACT_SOURCE_REVIEW_PENDING" + and profile.get("source_commit_sha") == head + and profile.get("reviewed_workflow_source_mode") == "staged" + and profile.get("historical_original_workflow_count") == 25 + and profile.get("current_reviewed_workflow_count") == 30 + and profile.get("historical_packaged_tooling_files_verified") == 35 + and profile.get("historical_archive_unchanged") is True + and profile.get("new_successor_T_approved") is False + and profile.get("owner_activation_authorized") is False + and profile.get("independent_security_review") is False, + "virtual 30 is not a complete original/25-current staged source audit") + deltas = profile.get("changed_canonical_tooling") + expected = {"verification/integrity/workflow_contract.py", + "verification/integrity/tests/test_workflow.py"} + need(type(deltas) is list and len(deltas) == 2 + and all(type(row) is dict and + set(row) == {"path", "T_blob", "H_blob"} + and type(row["path"]) is str + and type(row["T_blob"]) is str + and type(row["H_blob"]) is str + and len(row["T_blob"]) == len(row["H_blob"]) == 40 + and row["T_blob"] != row["H_blob"] + for row in deltas) + and {row["path"] for row in deltas} == expected, + "rehearsal profile did not bind exactly the two known T/H differences") + try: + checked = dual.validate_dual_reports( + old, new, T_source=T, H_source=head) + except (dual.DualToolingError, KeyError, TypeError) as exc: + raise RehearsalError("real separate T and current 25 workflow executions failed") from exc + need(checked["original_T_archive_offline_restoration_passed"] is True + and checked["current_H_workflow_test_suite_passed"] is True, + "one of the independent source legs was not successful") + return { + "format": FORMAT, "status": STATUS, + "historical_T": T, "current_H": head, + "historical_active_workflows": 25, "actual_active_workflows": 25, + "virtual_staged_workflows": 30, "original_T_offline_replay": True, + "current_25_workflow_tests": True, "virtual_30_profile_source_audit": True, + "historical_supplement_tree": profile["historical_supplement_tree"], + "historical_manifest_sha256": profile["historical_manifest_sha256"], + "workflow_profile_sha256": profile["workflow_profile_sha256"], + "known_two_source_deltas": profile["changed_canonical_tooling"], + "active_30_reverified": False, "original_T2_producer_zip": False, + "genuine_required_ci": False, "independent_security_review": False, + "mathematical_acceptance": False, "owner_activation_authorized": False, + "merge_authorized": False, "publication_authorized": False, + } + + +def _trace(process, argv): + out = getattr(process, "stdout", None) + err = getattr(process, "stderr", None) + need(type(process.returncode) is int and process.returncode == 0 + and type(out) is str and type(err) is str + and len(out.encode()) <= MAX_LOG_BYTES + and len(err.encode()) <= MAX_LOG_BYTES, + "source child exited abnormally or produced unbounded logs") + from hashlib import sha256 + return {"exit_code": 0, "command": argv, + "stdout_sha256": sha256(out.encode()).hexdigest(), + "stderr_sha256": sha256(err.encode()).hexdigest(), + "non_authorizing": True}, out, err + + +def execute(original_checkout, current_checkout, head, output): + old = dual.checkout(original_checkout, T) + current = dual.checkout(current_checkout, head) + need(old != current and head != T, "T/H checkouts not independent") + out = detached_destination(output, ROOT) + need(not out.is_relative_to(old) and not out.is_relative_to(current) + and not old.is_relative_to(out) and not current.is_relative_to(out), + "rehearsal output overlaps either source checkout") + profile = tooling_audit(current, head, phase="staged") + need(profile.get("reviewed_workflow_source_mode") == "staged", + "the archive rehearsal must not claim active 30") + supplement = current/"archival/v0.2.0/supplement-1" + need(supplement.is_dir() and not supplement.is_symlink(), + "unchanged original T supplement unavailable") + out.mkdir(parents=True, exist_ok=False) + t_file, h_file = out/"historical_T_archive.json", out/"current_H_workflow.json" + t_args = [sys.executable, "-B", "-m", "verification.archive.offline_smoke", + "--supplement", str(supplement), + "--scratch", str(out/"original_T_restoration"), + "--report", str(t_file)] + h_args = [sys.executable, "-B", "-m", "verification.integrity.run_tests", + "--suite", "workflow", "--report", str(h_file)] + t_process = dual.command(t_args, old, historical_artifact=True) + t_trace, t_stdout, t_stderr = _trace(t_process, t_args) + h_process = dual.command(h_args, current, detached_current_suite=True) + h_trace, h_stdout, h_stderr = _trace(h_process, h_args) + t_json = strict_json_object(t_file.read_bytes(), max_bytes=MAX_LOG_BYTES) + h_json = strict_json_object(h_file.read_bytes(), max_bytes=MAX_LOG_BYTES) + result = validate_reports(t_json, h_json, profile, head) + result["historical_report_sha256"] = dual.sha_file(t_file) + result["current_report_sha256"] = dual.sha_file(h_file) + for name, data in ( + ("T_stdout.log", t_stdout), ("T_stderr.log", t_stderr), + ("H_stdout.log", h_stdout), ("H_stderr.log", h_stderr), + ): + with (out/name).open("x", encoding="utf-8") as stream: + stream.write(data) + with (out/"STAGED_REHEARSAL_NOT_T2_AUTHORITY.json").open("x") as stream: + json.dump(result, stream, sort_keys=True, indent=2) + stream.write("\n") + with (out/"unreviewed_process_trace.json").open("x") as stream: + json.dump({"T": t_trace, "H": h_trace, + "independent_security_review": False}, stream, + sort_keys=True, indent=2) + stream.write("\n") + return result + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--historical-T-source", type=Path, required=True) + ap.add_argument("--current-source", type=Path, required=True) + ap.add_argument("--head", required=True) + ap.add_argument("--out", type=Path, required=True) + a = ap.parse_args(argv) + try: + value = execute(a.historical_T_source, a.current_source, a.head, a.out) + print(json.dumps({"status": value["status"], "required_ci_accepted": False}, + sort_keys=True)) + return 0 + except (RehearsalError, dual.DualToolingError, OSError, ValueError, + KeyError, TypeError) as exc: + print("STAGED_T_H_REHEARSAL_BLOCKED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_archive_source_review_packet_v1.py b/verification/research/pr113_archive_source_review_packet_v1.py new file mode 100644 index 00000000..8ea2f190 --- /dev/null +++ b/verification/research/pr113_archive_source_review_packet_v1.py @@ -0,0 +1,150 @@ +"""Deterministic ARC-01/04 exact Git-source review dossier, without authority. + +Reads the real immutable historical T and current H Git objects plus the +source-staged 30-workflow contract; does NOT execute archive/Lean, download +producer artifacts, approve source or replace the required check. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import re +import sys + +from pr113_boundary_io import detached_destination +from pr113_tooling_migration import ( + ROOT, T, B, SUPPLEMENT, TOOLING, EXPECTED_CHANGES, + git, sha, exact_tree, ToolingMigrationError, +) +from pr113_tooling_migration_v2 import audit as boundary_audit +from pr113_workflow_profile_v2 import audit as workflow_audit, FORMAT as WF_FORMAT + +FORMAT = "Permansson.PR113.ArchiveTCSourceReviewPacket.v1" +OID = re.compile(r"^[a-f0-9]{40}$") + + +class PacketError(ValueError): + pass + + +def need(ok, why): + if not ok: + raise PacketError(why) + + +def produce(head, *, root=ROOT): + root = Path(root).resolve(strict=True) + boundary = boundary_audit(root, head, phase="staged") + profile = workflow_audit(root, head, phase="staged") + need(boundary.get("status") == "T2_EXACT_SOURCE_REVIEW_PENDING" + and boundary.get("historical_archive_unchanged") is True + and boundary.get("historical_packaged_tooling_files_verified") == 35 + and boundary.get("original_archive_T") == T + and boundary.get("original_release_base_B") == B + and boundary.get("reviewed_workflow_source_mode") == "staged", + "full frozen T/H source audit absent") + need(profile.get("format") == WF_FORMAT + and profile.get("versioned_source_phase") == "staged" + and profile.get("head_sha") == head + and profile.get("historical_original_workflow_count") == 25 + and profile.get("current_reviewed_workflow_count") == 30 + and profile.get("retained_historical_count") == 23 + and profile.get("replacement_count") == 2 + and profile.get("addition_count") == 5 + and profile.get("independent_security_review") is False + and profile.get("owner_activation_authorized") is False, + "staged 25-to-30 Git-object contract absent") + T_files, H_files = ( + exact_tree(root, rev, *TOOLING) for rev in (T, head)) + need(set(T_files) == set(H_files) and len(T_files) == 35, + "complete 35-file canonical T/H membership not authenticated") + records = [] + for path in sorted(T_files): + old_mode, old_oid = T_files[path] + cur_mode, cur_oid = H_files[path] + need(old_mode == cur_mode + and old_mode in ("100644", "100755") + and OID.fullmatch(old_oid) and OID.fullmatch(cur_oid), + "source path is not the immutable canonical regular Git file") + old_bytes = git(root, "show", T + ":" + path) + current_bytes = git(root, "show", head + ":" + path) + records.append({ + "path": path, "mode": old_mode, + "T_git_blob": old_oid, "H_git_blob": cur_oid, + "T_sha256": sha(old_bytes), "H_sha256": sha(current_bytes), + "changed": old_oid != cur_oid, + }) + change_paths = {x["path"] for x in records if x["changed"]} + need(change_paths == EXPECTED_CHANGES + and len(boundary["changed_canonical_tooling"]) == 2 + and {row["path"] for row in boundary["changed_canonical_tooling"]} + == change_paths, + "T-to-H source contained an unreviewed change") + virtual = profile.get("expected_source_blobs") + need(type(virtual) is dict and len(virtual) == 30 + and all(type(k) is str and type(v) is str + and OID.fullmatch(v) for k,v in virtual.items()), + "review-pending 30-source blob inventory is incomplete") + raw_manifest = git(root, "show", + head + ":" + SUPPLEMENT + "/archive-manifest.json") + need(sha(raw_manifest) == boundary["historical_manifest_sha256"], + "original T archived manifest bytes changed since source audit") + return { + "format": FORMAT, + "status": "REAL_GIT_SOURCE_ONLY_NO_HUMAN_AUTHORIZATION", + "head_sha": head, + "historical_T": T, "original_base_B": B, + "historical_supplement_tree": boundary["historical_supplement_tree"], + "historical_archive_manifest_sha256": sha(raw_manifest), + "historical_tooling": records, + "tooling_file_count": 35, + "permitted_changed_paths": sorted(change_paths), + "actual_active_workflows": 25, + "proposed_active_workflows": 30, + "retained_workflows": 23, + "proposed_replacements": 2, "proposed_additions": 5, + "virtual_workflow_git_blobs": dict(sorted(virtual.items())), + "virtual_30_profile_sha256": profile["current_source_profile_sha256"], + "mutable_action_refs_pending_independent_review": + profile["inherited_mutable_action_refs"], + "original_T_replay_runtime": "NOT_PRODUCED_BY_THIS_SOURCE_PACKET", + "historical_A2_runtime": "NOT_PRODUCED_BY_THIS_SOURCE_PACKET", + "original_GitHub_producer_zips": + "NOT_AUTHENTICATED_BY_THIS_SOURCE_PACKET", + "independent_security_review": False, + "owner_activation_authorized": False, + "current_required_archive_check_passed": False, + "current_mathematical_acceptance": False, + "PR113_merge_authorized": False, + "publication_authorized": False, + } + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head", required=True) + parser.add_argument("--out", type=Path, required=True) + options = parser.parse_args(argv) + try: + result = produce(options.head) + out = detached_destination(options.out, ROOT) + need(not out.exists() and not ROOT.resolve().is_relative_to(out), + "review packet output must be new and detached from source") + out.parent.mkdir(parents=True, exist_ok=True) + with out.open("x", encoding="utf-8") as writer: + json.dump(result, writer, sort_keys=True, indent=2) + writer.write("\n") + print(json.dumps({"status": result["status"], "H": options.head, + "canonical_files": result["tooling_file_count"], + "virtual_workflows": result["proposed_active_workflows"]}, + sort_keys=True)) + return 0 + except (PacketError, ToolingMigrationError, OSError, + ValueError, KeyError, TypeError) as exc: + print("ARCHIVE_SOURCE_REVIEW_PACKET_BLOCKED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_atomic_migration_preview.py b/verification/research/pr113_atomic_migration_preview.py new file mode 100644 index 00000000..65eeabad --- /dev/null +++ b/verification/research/pr113_atomic_migration_preview.py @@ -0,0 +1,341 @@ +"""Read-only virtual 30-workflow activation profile for independent review. + +No branch, workflow, required check, Git object or authority record is edited. +The preactivation checkout MUST be the original active 25-workflow source. +The program replaces two of those only in an in-memory preview and adds five +explicit staged source blobs. Approval of these bytes must be obtained from an +actual authorized independent security reviewer; this artifact grants none. +""" +from __future__ import annotations +import argparse +import ast +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys + +# Historical 25-SHA256 workflow authority is resolved lazily from the actual +# pinned Git source. Neither current v2 installed contract nor its namespace +# can supply (or impersonate) the immutable original 25-workflow allowlist. + +ROOT = Path(__file__).resolve().parents[2] +OLD = "docs/operations/pr113_unapproved_workflows/manifest.json" +FULL = "docs/operations/pr113_required_gate_replacement_unapproved/full_workflow_manifest.json" +R3 = "docs/operations/pr113_required_gate_replacement_unapproved/manifest.json" +V2_CONTRACT = "docs/operations/pr113_required_gate_replacement_unapproved/workflow_contract_v2_manifest.json" +V2_SUITE = "docs/operations/pr113_required_gate_replacement_unapproved/test_workflow_v2_manifest.json" +FORMAT = "Permansson.PR113.AtomicWorkflowTransitionReview.v1" +SHA = re.compile(r"^[a-f0-9]{40}$") + + +class TransitionError(ValueError): + pass + + +def need(ok, why): + if not ok: + raise TransitionError(why) + + +def git(*args): + try: + data=subprocess.check_output(["git",*args],cwd=ROOT, + stderr=subprocess.DEVNULL,timeout=30).decode("utf-8") + # Git blob bytes must be preserved exactly, including final newlines. + # Only identity-command scalar output is stripped. + return data if args and args[0]=="show" else data.strip() + except (OSError,subprocess.CalledProcessError, + subprocess.TimeoutExpired) as exc: + raise TransitionError("real Git source unavailable") from exc + + +def raw(head, path, *, git=git): + need(isinstance(path,str) and path + and ".." not in Path(path).parts + and not Path(path).is_absolute(), + "invalid Git source path") + return git("show",head+":"+path).encode("utf-8") + + +def digest(data): + return hashlib.sha256(data).hexdigest() + + +def git_blob_oid(blob): + payload=b"blob "+str(len(blob)).encode()+b"\0"+blob + return hashlib.sha1(payload).hexdigest() + + +def preview(head, *, git=git, old_audit=None): + need(isinstance(head,str) and SHA.fullmatch(head) and + git("rev-parse","HEAD")==head, + "virtual activation must bind one real, original preactivation H") + # Fail closed if someone reuses this PREACTIVATION reviewer on the actual + # installed-30 tree, even if an injected old audit would claim otherwise. + active=git("ls-tree","-r","--name-only",head,"--",".github/workflows") + original_paths=active.splitlines() + need(len(original_paths)==25 + and len(set(original_paths))==25 + and all(re.fullmatch(r"\.github/workflows/[A-Za-z0-9_.-]+\.ya?ml",p) + for p in original_paths), + "virtual original 25-source preview cannot evaluate installed30") + from pr113_successor_workflow_profile import ( + audit as authentic_old_audit, authenticated_historical_25, + ) + if old_audit is None: + old_audit=authentic_old_audit + original=old_audit(head) + need(original.get("status") == + "EXACT_25_PLUS_5_SOURCE_PROFILE_CANDIDATE_NOT_ACTIVATED" and + original.get("original_workflow_count")==25 and + original.get("successor_proposal_count")==5, + "original 25+5 source review profile not valid") + original_members={} + historical_allowlist=authenticated_historical_25(head) + need(set(original_paths) == + {".github/workflows/"+name for name in historical_allowlist}, + "original 25-workflow membership diverged from frozen Git history") + for name,hash_value in sorted(historical_allowlist.items()): + path=".github/workflows/"+name + data=raw(head,path,git=git) + need(digest(data)==hash_value and + git("rev-parse",head+":"+path)==git_blob_oid(data), + "original workflow changed before review: "+path) + original_members[path]={ + "path":path,"sha256":digest(data),"git_blob_oid":git_blob_oid(data), + "origin":"UNCHANGED_ORIGINAL_25", + } + full=json.loads(raw(head,FULL,git=git)) + need(full.get("format")=="Permansson.PR113.FullRequiredWorkflowPreactivation.v1" + and full.get("status") == + "SOURCE_STAGED_NONEXECUTABLE_AWAITING_COMPLETE_SECURITY_REVIEW" + and full.get("original_workflow_count")==25 and + full.get("proposed_final_workflow_count")==30 and + full.get("independent_security_review") is False + and full.get("owner_activation_authorized") is False + and full.get("requires_main_PR_and_ordered_merge") is True, + "full mandatory workflow source has unauthorized status") + replacements=full.get("full_replacements") + need(isinstance(replacements,list) and len(replacements)==2, + "exact two replacement mandatory workflows required") + old=full.get("old_workflows") + need(isinstance(old,list) and len(old)==2 + and {x.get("path") for x in old} == + {".github/workflows/research-lean.yml", + ".github/workflows/research-integrity.yml"}, + "original exact two workflow sources not bound") + for x in old: + need(x.get("git_blob_oid")==original_members[x["path"]]["git_blob_oid"], + "old protected original source hash changed") + # A workflow's self-declared manifest SHA cannot stand in for the real + # versioned artifact-delivery module's independently committed identity. + delivery_path="verification/research/pr113_historical_artifact_delivery_v2.py" + delivery=raw(head,delivery_path,git=git) + need(git("rev-parse",head+":"+delivery_path)==git_blob_oid(delivery) + and b'FORMAT = "Permansson.PR113.ExactHistoricalArtifactDelivery.v2"' in delivery + and b"from pr113_required_dual_gate_v2 import actual_ci_event" in delivery, + "v2 artifact delivery source absent, substituted or wired to v1 gate") + # Direct reviewed job entry: v3; original T/A2 delivery+verification is + # authenticated transitively from the genuine committed runtime source. + runtime_path="verification/research/pr113_historical_runtime_v3.py" + runtime=raw(head,runtime_path,git=git) + need(git("rev-parse",head+":"+runtime_path)==git_blob_oid(runtime) + and b"from pr113_historical_artifact_delivery_v2 import" in runtime + and b"from pr113_required_dual_gate_v2 import verify" in runtime + and b"independent_security_review_authenticated" in runtime, + "v3 required gate's original reviewed T/A2 dependencies absent") + plan=dict(original_members) + for x in replacements: + need(isinstance(x,dict) and + set(x) == {"target","staged_source","git_blob_oid", + "installed","independent_reviewed"} + and x.get("target") in {y["path"] for y in old} + and x.get("installed") is False + and x.get("independent_reviewed") is False, + "unauthorized or installed mandatory replacement") + data=raw(head,x["staged_source"],git=git) + need(git_blob_oid(data)==x["git_blob_oid"] and + data.startswith(b"name: ") and + b"pull_request:\n" in data and + b"push:\n" in data and + data.count(b"pr113_historical_runtime_v3.py")==( + 1 if x["target"].endswith("/research-lean.yml") else 2) and + b"--producer-run-id" in data and + b"--security-review-id" in data and + (b"pr113_regression_diagnostics.py" in data + if x["target"].endswith("/research-lean.yml") + else b"--check-existing" in data) and + b"pr113_historical_artifact_delivery_v2.py" not in data and + b"pr113_historical_artifact_delivery.py" not in data, + "reviewed mandatory candidate source byte drift") + plan[x["target"]]={ + "path":x["target"],"sha256":digest(data), + "git_blob_oid":git_blob_oid(data), + "origin":"REVIEW_PENDING_MANDATORY_REPLACEMENT", + "staged_source":x["staged_source"], + } + future=json.loads(raw(head,OLD,git=git)) + proposals=future.get("proposals") + need(isinstance(proposals,list) and len(proposals)==5 + and future.get("status") == + "SOURCE_STAGED_NONEXECUTABLE_PENDING_INDEPENDENT_SECURITY_REVIEW" + and future.get("owner_authorized_activation") is False + and future.get("independent_security_review") is False, + "original staged five workflow approvals not pending") + for row in proposals: + dest=row["workflow_path"] + need(dest not in plan and + dest.startswith(".github/workflows/pr113-") and + row.get("approved") is False and + row.get("installed") is False, + "unexpected original workflow collision or premature installation") + source_bytes=raw(head,row["staged_source_path"],git=git) + need(git_blob_oid(source_bytes)==row["git_blob_oid"], + "future stage blob differs from original committed source") + plan[dest]={ + "path":dest,"sha256":digest(source_bytes), + "git_blob_oid":git_blob_oid(source_bytes), + "origin":"REVIEW_PENDING_NEW_WORKFLOW", + "staged_source":row["staged_source_path"], + } + need(len(plan)==30 and + len([p for p in plan.values() if p["origin"]== + "UNCHANGED_ORIGINAL_25"])==23, + "virtual workflow source does not include 23+2+5 exact members") + ordered=[plan[k] for k in sorted(plan)] + material=json.dumps(ordered,sort_keys=True,separators=(",",":")).encode() + r3=json.loads(raw(head,R3,git=git)) + need(r3.get("format")=="Permansson.PR113.UnapprovedRequiredGateTransition.v1" + and len(r3.get("staged_replacements",[]))==2 + and r3.get("owner_activation_authorized") is False + and r3.get("requires_independent_security_review") is True + and r3.get("original_T_A2_unmodified") is True, + "historical original A2 replacement source not reviewed") + for replacement in r3["staged_replacements"]: + stage=replacement.get("source") + materialized=raw(head,stage,git=git) + need(git_blob_oid(materialized)==replacement.get("git_blob_oid") + and replacement.get("installed") is False + and replacement.get("independent_reviewed") is False, + "original A2/R3 exact source binding absent") + # The authorization surface also changes two canonical integrity Python + # files. Count and hash those exact source replacements; a reviewed YAML + # set without the matching CI-06/07 test suite cannot be activated. + v2_contract=json.loads(raw(head,V2_CONTRACT,git=git)) + v2_tests=json.loads(raw(head,V2_SUITE,git=git)) + need(v2_contract.get("format") == + "Permansson.PR113.ReviewPendingVersionedWorkflowContract.v2" + and v2_contract.get("status") == + "EXACT_30_WORKFLOW_GIT_BLOBS_SOURCE_STAGED_NOT_ACTIVE" + and v2_contract.get("current_original_count")==25 + and v2_contract.get("proposed_active_count")==30 + and v2_contract.get("installed") is False + and v2_contract.get("independent_security_review") is False + and v2_contract.get("owner_activation_authorized") is False + and v2_tests.get("format") == + "Permansson.PR113.VersionedWorkflowSuiteCandidate.v2" + and v2_tests.get("installed") is False + and v2_tests.get("independent_security_review") is False + and v2_tests.get("owner_activation_authorized") is False + and v2_tests.get("original_CI06_CI07_case_mapping_preserved") is True, + "v2 source contract and mapped workflow tests missing/unreviewed") + new_integrity = [] + for descriptor, destination, stage, oid in ( + (v2_contract,"verification/integrity/workflow_contract.py", + "staged_source","staged_git_blob_oid"), + (v2_tests,"verification/integrity/tests/test_workflow.py", + "staged_test_source","staged_test_blob_oid"), + ): + need(descriptor.get("activation_target", + descriptor.get("target_test_path"))==destination, + "reviewed v2 integrity tooling destination mismatch") + original_oid=git("rev-parse",head+":"+destination) + source_path=descriptor.get(stage) + need(isinstance(source_path,str) and source_path.startswith( + "docs/operations/pr113_required_gate_replacement_unapproved/"), + "reviewed v2 integrity source must remain staged") + raw_source=raw(head,source_path,git=git) + need(git_blob_oid(raw_source)==descriptor.get(oid) and + git_blob_oid(raw_source)!=original_oid, + "source-pinned v2 integrity verifier source not actually changed") + new_integrity.append({ + "target":destination, + "original_git_blob_oid":original_oid, + "review_pending_git_blob_oid":git_blob_oid(raw_source), + "staged_source":source_path, + }) + need(len(new_integrity)==2 and + len({x["target"] for x in new_integrity})==2, + "precise two-file historical integrity contract transition missing") + contract_bytes=raw(head,v2_contract["staged_source"],git=git) + try: + syntax=ast.parse(contract_bytes.decode("utf-8")) + profile_node=next( + n.value for n in syntax.body if isinstance(n,ast.Assign) + and any(isinstance(x,ast.Name) and + x.id=="APPROVED_WORKFLOW_BLOBS" for x in n.targets)) + profile=ast.literal_eval(profile_node) + except (SyntaxError,StopIteration,ValueError,UnicodeError) as exc: + raise TransitionError("versioned workflow allowlist unparseable") from exc + expected={Path(row["path"]).name:row["git_blob_oid"] for row in ordered} + need(profile==expected and + v2_contract.get("expected_workflow_git_blobs")==expected, + "30-workflow v2 contract pins an unexpected source set") + return { + "format":FORMAT, + "status":"EXACT_30_SOURCE_ACTIVATION_PREVIEW_UNAPPROVED", + "source_head":head, + "historical_original_workflows":25, + "historical_preserved_workflows":23, + "review_pending_mandatory_replacements":2, + "review_pending_additional_workflows":5, + "proposed_total_workflows":30, + "virtual_profile_sha256":digest(material), + "review_pending_integrity_tooling_replacements":new_integrity, + "versioned_workflow_allowlist_count":len(profile), + "original_25_plus_5_profile_sha256":original["profile_sha256"], + "virtual_source_members":ordered, + "additional_original_test_replacements":[ + {"path":row["target"], + "source":row["source"],"git_blob_oid":row["git_blob_oid"]} + for row in r3["staged_replacements"] + ], + "historical_T_not_modified":True, + "historical_A2_not_modified":True, + "active_workflow_set_modified":False, + "requires_independent_security_review":True, + "independent_security_review":False, + "owner_activation_authorized":False, + "merge_authorized":False, + "mathematical_acceptance":False, + "publication_authorized":False, + } + + +def cli(argv=None): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head",required=True) + parser.add_argument("--output",required=True,type=Path) + args=parser.parse_args(argv) + try: + out=args.output.absolute() + need(not out.exists() and not out.is_relative_to(ROOT.resolve()) + and not any(p.is_symlink() for p in out.parents), + "security review material must be detached and new") + report=preview(args.head) + out.parent.mkdir(parents=True,exist_ok=True) + with out.open("x",encoding="utf-8") as f: + json.dump(report,f,sort_keys=True,indent=2) + f.write("\n") + print(json.dumps({"status":report["status"], + "sha256":report["virtual_profile_sha256"], + "source_head":report["source_head"]},sort_keys=True)) + return 0 + except (TransitionError,OSError,TypeError,ValueError,KeyError) as exc: + print("VIRTUAL HISTORICAL MIGRATION NOT REVIEWED:",exc,file=sys.stderr) + return 2 + +if __name__=="__main__": + raise SystemExit(cli()) diff --git a/verification/research/pr113_boundary_io.py b/verification/research/pr113_boundary_io.py new file mode 100644 index 00000000..e345ae64 --- /dev/null +++ b/verification/research/pr113_boundary_io.py @@ -0,0 +1,135 @@ +"""Strict, nonauthorizing intake helpers for post-PR113 evidence consumers. + +These helpers validate local bytes and paths. They NEVER authenticate GitHub +identities or independently authorize review, merge, claims or publication. +""" +from __future__ import annotations + +import json +from pathlib import Path +import urllib.parse +import urllib.request + +MAX_REDIRECTS = 5 + + +class BoundaryError(ValueError): + pass + + +def strict_json_object(raw: bytes, *, max_bytes: int = 4 * 1024 * 1024) -> dict: + if (type(raw) is not bytes or type(max_bytes) is not int + or not 0 < max_bytes <= 8 * 1024 * 1024 + or not 0 < len(raw) <= max_bytes): + raise BoundaryError("JSON source missing or unbounded") + + def unique(pairs): + out = {} + for key, value in pairs: + if key in out: + raise BoundaryError("duplicate JSON key: " + key) + out[key] = value + return out + + def invalid_constant(value): + raise BoundaryError("nonfinite JSON number: " + value) + + try: + result = json.loads(raw, object_pairs_hook=unique, + parse_constant=invalid_constant) + except BoundaryError: + raise + except (UnicodeError, ValueError, TypeError) as exc: + raise BoundaryError("JSON source malformed") from exc + if type(result) is not dict: + raise BoundaryError("JSON source must be an object") + return result + + +def detached_destination(path, source_root, *, forbidden=(), must_exist=False): + """Resolve a safe, unsymlinked, fresh external path before writing. + + Existing output paths are rejected unless explicitly opened for read. + Opaque symlinks and parent traversal are rejected *before* canonicalization. + """ + candidate = Path(path).expanduser() + if ".." in candidate.parts: + raise BoundaryError("parent-traversing detached evidence path") + absolute = candidate.absolute() + if any(part.is_symlink() for part in (absolute, *absolute.parents)): + raise BoundaryError("symlinked detached evidence path") + resolved = absolute.resolve(strict=False) + if resolved.exists() != bool(must_exist): + raise BoundaryError("detached evidence path existence mismatch") + if must_exist and not resolved.is_file(): + raise BoundaryError("detached evidence must be a regular file") + roots = (source_root, *forbidden) + for root in roots: + original = Path(root).resolve() + if resolved == original or resolved.is_relative_to(original): + raise BoundaryError("detached evidence path resides within protected root") + return resolved + + +def approved_artifact_url(value): + try: + part = urllib.parse.urlsplit(value) + host = (part.hostname or "").lower().rstrip(".") + return (part.scheme == "https" and part.port in (None, 443) + and part.username is None and part.password is None + and not part.fragment + and (host == "api.github.com" + or host.endswith(".githubusercontent.com") + or host.endswith(".blob.core.windows.net") + or host.endswith(".amazonaws.com"))) + except (TypeError, ValueError): + return False + + +class BoundedArtifactRedirect(urllib.request.HTTPRedirectHandler): + def __init__(self): + super().__init__() + self.hops = 0 + + def redirect_request(self, req, fp, code, msg, headers, newurl): + self.hops += 1 + if self.hops > MAX_REDIRECTS or not approved_artifact_url(newurl): + raise BoundaryError("artifact redirect escaped approved HTTPS hosts") + child = super().redirect_request(req, fp, code, msg, headers, newurl) + if child is None: + raise BoundaryError("artifact redirect rejected") + # The bearer token may only accompany the initial literal GitHub API. + # Never allow a redirected request to carry authorization, even if + # redirect target or source is another API endpoint. + for mapping in (child.headers, child.unredirected_hdrs): + for key in list(mapping): + if key.lower() == "authorization": + del mapping[key] + return child + + +def github_artifact_zip(repository, artifact_id, token, *, max_bytes): + if (type(artifact_id) is not int or artifact_id <= 0 + or type(max_bytes) is not int or not 0 < max_bytes <= 256*1024*1024 + or not isinstance(token, str) or not token): + raise BoundaryError("artifact request lacks bounded original source") + if repository != "HMarcusWH/PermanssonLean": + raise BoundaryError("foreign artifact repository") + url = ("https://api.github.com/repos/" + repository + + "/actions/artifacts/" + str(artifact_id) + "/zip") + if not approved_artifact_url(url): + raise BoundaryError("untrusted artifact API origin") + request = urllib.request.Request(url, headers={ + "User-Agent": "Permansson-ReadOnlyOriginalArtifact/2", + "Accept": "application/vnd.github+json", + "Authorization": "Bearer " + token, + }) + opener = urllib.request.build_opener(BoundedArtifactRedirect()) + with opener.open(request, timeout=55) as response: + size = response.headers.get("Content-Length") + if size is not None and int(size) > max_bytes: + raise BoundaryError("original ZIP exceeds compressed-byte budget") + data = response.read(max_bytes + 1) + if not 0 < len(data) <= max_bytes or not data.startswith(b"PK"): + raise BoundaryError("original ZIP unavailable, truncated, or oversized") + return data diff --git a/verification/research/pr113_cert_release_source_v3.py b/verification/research/pr113_cert_release_source_v3.py new file mode 100644 index 00000000..281da9af --- /dev/null +++ b/verification/research/pr113_cert_release_source_v3.py @@ -0,0 +1,106 @@ +"""Resolve actual PR113 or genuinely later CERT PR for review/release gates. + +Candidate base/head/merge SHAs are untrusted until GitHub PR identity and +Git ancestry are independently checked. This module never accepts a source +receipt, review, scientific claim, or publication request by itself. +""" +from __future__ import annotations + +import re + +from release_target import B113, REPOSITORY + +SHA = re.compile(r"^[a-f0-9]{40}$") +BRANCH = re.compile(r"^research/[A-Za-z0-9._/-]{1,160}$") + + +class ReleaseSourceError(ValueError): + pass + + +def need(ok, why): + if not ok: + raise ReleaseSourceError(why) + + +def source_pr(base, head, merged=None, *, git, live): + """Return verified (PR number, live PR), never an approval decision.""" + need(all(isinstance(v, str) and SHA.fullmatch(v) + for v in (base, head)) + and (merged is None or ( + isinstance(merged, str) and SHA.fullmatch(merged))), + "unbound B/H/M source identity") + old = live("/pulls/113") + need(isinstance(old, dict) and old.get("number") == 113 + and (old.get("head") or {}).get("sha"), + "original infrastructure PR source not available") + if (old.get("head") or {}).get("sha") == head: + need(base == B113, + "original PR113 base must remain its frozen historical B") + if merged is None: + need(old.get("merged") is False and old.get("state") == "open", + "original PR113 is not a current unmerged source") + else: + need(old.get("merged") is True + and old.get("merge_commit_sha") == merged + and old.get("state") == "closed", + "original PR113 merged source not authenticated") + return 113, old + + M113 = old.get("merge_commit_sha") + H113 = (old.get("head") or {}).get("sha") + need(old.get("merged") is True and old.get("state") == "closed" + and isinstance(M113, str) and SHA.fullmatch(M113) + and isinstance(H113, str) and SHA.fullmatch(H113) + and git("cat-file", "-t", M113) == "commit" + and git("cat-file", "-t", H113) == "commit" + and git("show", "-s", "--format=%P", M113).split() == [B113, H113] + and git("rev-parse", M113+"^{tree}") == + git("rev-parse", H113+"^{tree}") + and git("merge-base", "--is-ancestor", M113, base) == "", + "CERT base is not descended from a genuine owner-merged M113") + discovered = live("/commits/"+head+"/pulls?per_page=100") + need(isinstance(discovered, list) and len(discovered) < 100, + "GitHub candidate PR inventory missing or truncated") + found = [] + for row in discovered: + if not isinstance(row, dict): + continue + number = row.get("number") + if type(number) is not int or number <= 114: + continue + pr = live("/pulls/"+str(number)) + if not isinstance(pr, dict) or pr.get("number") != number: + continue + b = pr.get("base") or {} + h = pr.get("head") or {} + branch = h.get("ref") + if not (h.get("sha") == head and b.get("ref") == "main" + and (b.get("repo") or {}).get("full_name") == REPOSITORY + and (h.get("repo") or {}).get("full_name") == REPOSITORY + and isinstance(branch, str) and BRANCH.fullmatch(branch) + and branch != "research/final-mathematical-closure" + and "//" not in branch and ".." not in branch): + continue + if merged is None: + # The unmerged PR base must still be the literal current base. + if (pr.get("state") != "open" or pr.get("merged") is not False + or b.get("sha") != base + or (live("/branches/main").get("commit") or {}).get("sha") != base): + continue + else: + # GitHub may move base.sha after a merge. The actual immutable + # ordered merge parents and source tree, not the mutable PR-base + # display, define the historical source B. + if not (pr.get("state") == "closed" + and pr.get("merged") is True + and pr.get("merge_commit_sha") == merged + and git("cat-file", "-t", merged) == "commit" + and git("show", "-s", "--format=%P", merged).split() == [base, head] + and git("rev-parse", merged+"^{tree}") == + git("rev-parse", head+"^{tree}")): + continue + found.append(pr) + need(len(found) == 1, + "source not uniquely bound to genuine successor CERT PR") + return found[0]["number"], found[0] diff --git a/verification/research/pr113_ci_producer_source_v3.py b/verification/research/pr113_ci_producer_source_v3.py new file mode 100644 index 00000000..a8274f79 --- /dev/null +++ b/verification/research/pr113_ci_producer_source_v3.py @@ -0,0 +1,110 @@ +"""Authenticate real PR113/CERT source-control workflow execution. + +The dispatch branch, PR and source head must be live GitHub/Git identities; +inputs are hints only. This module never approves cases, claims or a release. +""" +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import re +import sys + +from pr113_boundary_io import strict_json_object +from pr113_producer_target_v3 import authenticated_producer_branch +from release_target import REPOSITORY + +SHA = re.compile(r"^[0-9a-f]{40}$") +MODES = frozenset(("source_controls", "reviewed_legacy20")) +FORMAT = "Permansson.PR113.ActualOwnerProducerSource.v3" + + +class SourceDispatchError(ValueError): + pass + + +def require(value, message): + if not value: + raise SourceDispatchError(message) + + +def authenticate(head, mode, *, env=None, event_data=None, + branch_check=authenticated_producer_branch): + """Return a source-only receipt; current event, NOT static source labels.""" + env = os.environ if env is None else env + require(isinstance(head, str) and bool(SHA.fullmatch(head)) + and mode in MODES + and env.get("GITHUB_ACTIONS") == "true" + and env.get("GITHUB_REPOSITORY") == REPOSITORY + and env.get("GITHUB_SHA") == head, + "genuine checked-out producer source event required") + kind = env.get("GITHUB_EVENT_NAME") + require(kind in ("push", "workflow_dispatch"), + "owner controls require a branch push or explicit dispatch") + if event_data is None: + fp = Path(env.get("GITHUB_EVENT_PATH", "")) + require(fp.is_file() and not fp.is_symlink() + and 0 < fp.stat().st_size <= 8*1024*1024, + "real GitHub producer event unavailable") + event_data = fp.read_bytes() + event = strict_json_object(event_data, max_bytes=8*1024*1024) + require((event.get("repository") or {}).get("full_name") == REPOSITORY, + "producer event targets foreign repository") + hint = env.get("PERMANSSON_CERT_PR_NUMBER", "") + if kind == "push": + require(mode == "source_controls" + and event.get("ref") == env.get("GITHUB_REF") + and event.get("after") == head + and env.get("GITHUB_REF") == + "refs/heads/research/final-mathematical-closure" + and not hint, + "push is not original exact-H unreviewed producer source") + else: + inputs = event.get("inputs") + require(isinstance(inputs, dict) + and inputs.get("source_head") == head + and inputs.get("run_mode") == mode + and inputs.get("cert_pr_number", "") == hint + and env.get("PR113_REQUESTED_SOURCE_HEAD") == head + and env.get("PR113_REQUESTED_RUN_MODE") == mode, + "workflow dispatch is not bound to original source/mode/input") + try: + branch = branch_check(head, env=env) + except (ValueError, TypeError, KeyError) as exc: + raise SourceDispatchError("producer branch/PR failed live verification") from exc + require(env.get("GITHUB_REF") == "refs/heads/" + branch, + "authenticated producer branch differs from actual GitHub ref") + return { + "format": FORMAT, + "status": "AUTHENTIC_GITHUB_PRODUCER_SOURCE_NOT_REVIEWED", + "head_sha": head, + "branch": branch, + "event": kind, + "mode": mode, + "pr_number": 113 if branch == "research/final-mathematical-closure" + else int(hint), + "case_acceptance": False, + "claim_acceptance": False, + "reviewer_selected": False, + "publication_authorized": False, + } + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head", required=True) + parser.add_argument("--mode", required=True, choices=sorted(MODES)) + args = parser.parse_args(argv) + try: + receipt = authenticate(args.head, args.mode) + print(json.dumps(receipt, sort_keys=True)) + return 0 + except (SourceDispatchError, KeyError, TypeError, ValueError, OSError) as exc: + print("OWNER_PRODUCER_SOURCE_BLOCKED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_compiler_trace_hook.py b/verification/research/pr113_compiler_trace_hook.py new file mode 100644 index 00000000..06cbe90c --- /dev/null +++ b/verification/research/pr113_compiler_trace_hook.py @@ -0,0 +1,142 @@ +"""Passive, opt-in recorder for Lean subprocess calls in legacy unittest routes. + +Installed only via an explicitly generated sitecustomize.py in a detached +test subprocess, and only when PR113_CAPTURE_DIR is supplied. The true +subprocess.run implementation receives the original arguments unchanged. +Recordings are UNREVIEWED observations, never independent approval. +""" +from __future__ import annotations + +import hashlib +import itertools +import json +import os +from pathlib import Path +import subprocess +import unittest + +MAX_SOURCE = 4 * 1024 * 1024 +MAX_OUTPUT = 4 * 1024 * 1024 +_COUNTER = itertools.count() + + +def _bytes(value): + if value is None: + return None + if isinstance(value, bytes): + return value + if isinstance(value, str): + return value.encode("utf-8", errors="replace") + return None + + +def _source(argv, cwd): + # Callers may use /usr/bin/lake, lake env lean, or a pinned lean binary. + # Never execute or rewrite a compiler command in the recorder. + parts = [str(x) for x in argv] if isinstance(argv, (list, tuple)) else [] + if not parts: + return None + exe = Path(parts[0]).name + if not ((exe == "lake" and len(parts) >= 3 and parts[1:3] == ["env", "lean"]) + or exe in ("lean", "lean.exe")): + return None + sources = [x for x in parts[1:] if x.endswith(".lean")] + if len(sources) != 1: + return (parts, None, None) + original = Path(sources[0]) + root = Path(cwd or os.getcwd()) + path = original if original.is_absolute() else root / original + try: + if not path.is_file() or path.is_symlink(): + return (parts, str(path), None) + if path.stat().st_size > MAX_SOURCE: + return (parts, str(path), None) + return (parts, str(path), path.read_bytes()) + except OSError: + return (parts, str(path), None) + + +def _record(directory, cmd, source_path, source, outcome=None, error=None): + event = Path(directory) / ("event-%d-%06d" % (os.getpid(), next(_COUNTER))) + event.mkdir(parents=True, exist_ok=False) + stdout = _bytes(getattr(outcome, "stdout", None)) + stderr = _bytes(getattr(outcome, "stderr", None)) + for label, raw in (("source.lean", source), ("stdout.log", stdout), + ("stderr.log", stderr)): + if raw is not None and len(raw) <= (MAX_SOURCE if label == "source.lean" + else MAX_OUTPUT): + (event / label).write_bytes(raw) + meta = { + "format": "Permansson.LegacyLeanCompilerObservation.v1", + "status": "RAW_UNREVIEWED", + "case_id_unverified": os.environ.get("PR113_CASE_ID"), + "selected_method_unverified": os.environ.get("PR113_SELECTED_METHOD"), + "command": cmd, + "original_input_path_untrusted": source_path, + "returncode": getattr(outcome, "returncode", None), + "exception": str(error) if error is not None else None, + "source_sha256": hashlib.sha256(source).hexdigest() if source is not None else None, + "stdout_sha256": hashlib.sha256(stdout).hexdigest() if stdout is not None else None, + "stderr_sha256": hashlib.sha256(stderr).hexdigest() if stderr is not None else None, + "source_retained": source is not None and len(source) <= MAX_SOURCE, + "stdout_retained": stdout is not None and len(stdout) <= MAX_OUTPUT, + "stderr_retained": stderr is not None and len(stderr) <= MAX_OUTPUT, + "independent_review": False, + "ci_check_run_id": None, + } + (event / "observation.json").write_text( + json.dumps(meta, sort_keys=True, indent=2) + "\n", encoding="utf-8") + + +def install(): + """Patch only the current opted-in Python interpreter, not project files.""" + directory = os.environ.get("PR113_CAPTURE_DIR") + if not directory: + return + if not Path(directory).is_dir(): + raise RuntimeError("requested detached compiler observation directory unavailable") + original_run = subprocess.run + if getattr(original_run, "_pr113_capture_installed", False): + return + + def observed_run(*args, **kwargs): + argv = args[0] if args else kwargs.get("args") + observation = _source(argv, kwargs.get("cwd")) + if observation is None: + return original_run(*args, **kwargs) + command, path, source = observation + try: + result = original_run(*args, **kwargs) + except BaseException as exc: + # CalledProcessError/TimeoutExpired may carry the compiler's raw + # stdout/stderr even though no CompletedProcess was returned. + # Preserve those bytes; the collector still rejects invalid + # exception/non-mathematical outcomes. + _record(directory, command, path, source, outcome=exc, error=exc) + raise + _record(directory, command, path, source, outcome=result) + return result + + observed_run._pr113_capture_installed = True + subprocess.run = observed_run + + # Set one exact unittest method only while its own body executes. Python + # subprocesses inherit this environment, so nested Lean audit drivers can + # be attributed to the actual original named test. Class-level setUpClass + # executions still carry a grouped/unattributed marker and cannot satisfy + # an individual semantic-negative method. + actual_test_run = unittest.TestCase.run + if not getattr(actual_test_run, "_pr113_method_context_installed", False): + def run_with_method(self, *args, **kwargs): + old_method = os.environ.get("PR113_SELECTED_METHOD") + os.environ["PR113_SELECTED_METHOD"] = self._testMethodName + try: + return actual_test_run(self, *args, **kwargs) + finally: + if old_method is None: + os.environ.pop("PR113_SELECTED_METHOD", None) + else: + os.environ["PR113_SELECTED_METHOD"] = old_method + + run_with_method._pr113_method_context_installed = True + unittest.TestCase.run = run_with_method diff --git a/verification/research/pr113_disposable_transition_rehearsal.py b/verification/research/pr113_disposable_transition_rehearsal.py new file mode 100644 index 00000000..e560491f --- /dev/null +++ b/verification/research/pr113_disposable_transition_rehearsal.py @@ -0,0 +1,665 @@ +"""PR113 fully detached 25->30 workflow candidate rehearsal. NOT activation. + +Construct one disposable exact-source Git commit from a real current H. Install +ONLY the two reviewed workflow replacements, five reviewed additions, and +three exact versioned verification source replacements. Exercise both actual +H-like checkout and a true ordered [B113,C] synthetic commit with equal tree. +All worktrees are removed. No branch, ruleset, reviewer or required CI changed. + +This is an engineering SOURCE rehearsal, not old-T archive restoration, full +A2 compiler execution, external reviewer acceptance, merge or publication. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import shutil +import subprocess +import sys +import tempfile + +from pr113_workflow_profile_v2 import ( + audit as workflow_audit, audited_source_phase, PREACTIVATION_HEAD, STAGE, PROPOSALS, + FULL_MANIFEST, PROPOSAL_MANIFEST, CONTRACT, +) +from pr113_tooling_migration_v2 import audit as tooling_audit +from pr113_a2_successor_identity_v2 import audit as a2_audit, PR113_BASE + +# Executable checkouts must remain free of untracked import bytecode. +sys.dont_write_bytecode = True + +ROOT=Path(__file__).resolve().parents[2] +FORMAT="Permansson.PR113.DisposableSourceTransitionRehearsal.v1" +SHA=re.compile(r"^[0-9a-f]{40}$") +SOURCE_REPLACEMENTS={ + "verification/integrity/workflow_contract.py":CONTRACT, + "verification/integrity/tests/test_workflow.py": + STAGE+"test_workflow_v2.py", + "verification/research/test_localized_prefix_compiler.py": + STAGE+"test_localized_prefix_compiler.py", +} + + +class RehearsalError(ValueError): + pass + + +def need(ok, reason): + if not ok: + raise RehearsalError(reason) + + +def run(repo, *args, input_bytes=None, seconds=180, author=False): + cmd=["git","-C",str(repo)] + if author: + cmd+=["-c","user.name=PR113 Ephemeral Source Rehearsal", + "-c","user.email=pr113-rehearsal@invalid.example", + "-c","commit.gpgsign=false"] + env=os.environ.copy() + env["GIT_TERMINAL_PROMPT"]="0" + env["GIT_AUTHOR_NAME"]="PR113 Ephemeral Source Rehearsal" + env["GIT_AUTHOR_EMAIL"]="pr113-rehearsal@invalid.example" + env["GIT_COMMITTER_NAME"]="PR113 Ephemeral Source Rehearsal" + env["GIT_COMMITTER_EMAIL"]="pr113-rehearsal@invalid.example" + try: + cp=subprocess.run(cmd+list(args),input=input_bytes, + capture_output=True,env=env,timeout=seconds) + except (OSError,subprocess.TimeoutExpired) as exc: + raise RehearsalError("disposable Git operation unavailable") from exc + if cp.returncode!=0: + raise RehearsalError( + "disposable Git operation failed "+ + repr(args[:4])+": "+cp.stderr.decode("utf-8","replace")[-1600:]) + return cp.stdout + + +def scalar(root,*args): + return run(root,*args).decode("utf-8").strip() + + +def object_bytes(root,commit,path): + need(isinstance(path,str) and path and + not path.startswith("/") and "\\" not in path and + ".." not in path.split("/"), + "unsafe candidate Git path") + raw=run(root,"show",commit+":"+path) + oid=scalar(root,"rev-parse",commit+":"+path) + computed=hashlib.sha1( + b"blob "+str(len(raw)).encode("ascii")+b"\0"+raw).hexdigest() + need(oid==computed, + "source content and exact Git blob object disagree: "+path) + return oid,raw + + +def safe_json(root,head,path): + _,raw=object_bytes(root,head,path) + try: + data=json.loads(raw) + except (UnicodeError,ValueError) as exc: + raise RehearsalError("invalid exact committed staged manifest") from exc + need(isinstance(data,dict),"candidate manifest must be JSON object") + return data + + +def original_source(root,head): + need(isinstance(head,str) and SHA.fullmatch(head), + "actual target H must be an exact Git object ID") + checked=scalar(root,"rev-parse","HEAD") + need(scalar(root,"cat-file","-t",head)=="commit", + "candidate H is not an actual Git commit") + if checked!=head: + need(scalar(root,"show","-s","--format=%P",checked).split()== + [PR113_BASE,head], + "source checkout must be exact H or real ordered [B113,H]") + need(scalar(root,"rev-parse",checked+"^{tree}")== + scalar(root,"rev-parse",head+"^{tree}"), + "incoming synthetic source checkout differs from H tree") + need(not run(root,"status","--porcelain=v1","--untracked-files=all"), + "source repository is dirty before detached rehearsal") + run(root,"merge-base","--is-ancestor",PREACTIVATION_HEAD,head) + return checked + + +def planned_sources(root,head): + full=safe_json(root,head,FULL_MANIFEST) + other=safe_json(root,head,PROPOSAL_MANIFEST) + required=full.get("full_replacements") + added=other.get("proposals") + need(isinstance(required,list) and len(required)==2 + and isinstance(added,list) and len(added)==5 + and full.get("independent_security_review") is False + and full.get("owner_activation_authorized") is False + and other.get("independent_security_review") is False + and other.get("owner_authorized_activation") is False, + "candidate source asks for premature installation or approval") + mapping={} + for row in required: + need(row.get("installed") is False and + row.get("independent_reviewed") is False, + "replaced workflow has fabricated installation/review state") + target=row.get("target") + stage=row.get("staged_source") + need(isinstance(target,str) and + target.startswith(".github/workflows/") and + isinstance(stage,str) and stage.startswith(STAGE) + and target not in mapping, + "invalid replacement workflow source") + mapping[target]=(stage,row.get("git_blob_oid")) + for row in added: + need(row.get("installed") is False and + row.get("approved") is False, + "new workflow has fabricated installation/approval state") + target=row.get("workflow_path") + stage=row.get("staged_source_path") + need(isinstance(target,str) and + target.startswith(".github/workflows/pr113-") and + isinstance(stage,str) and stage.startswith(PROPOSALS) + and target not in mapping, + "invalid addition workflow source") + mapping[target]=(stage,row.get("git_blob_oid")) + for destination,source in SOURCE_REPLACEMENTS.items(): + need(destination not in mapping,"candidate source destination collision") + oid,_=object_bytes(root,head,source) + mapping[destination]=(source,oid) + need(len(mapping)==10, + "disposable source requires exact seven workflows and three guards") + for destination,(stage,oid) in mapping.items(): + actual,_=object_bytes(root,head,stage) + need(oid==actual and SHA.fullmatch(oid) is not None, + "candidate staged source has incorrect committed object ID") + return mapping + + +def verify_target_worktree(worktree,reviewed_head): + checked=scalar(worktree,"rev-parse","HEAD") + need(not run(worktree,"status","--porcelain=v1","--untracked-files=all"), + "candidate worktree unreviewed modification or untracked content") + profile=workflow_audit(worktree,checked,phase="active") + tooling=tooling_audit(worktree,checked,phase="active") + identity=a2_audit(worktree,reviewed_head) + need(profile["head_sha"]==checked and + profile["current_reviewed_workflow_count"]==30 and + profile["retained_historical_count"]==23 and + profile["replacement_count"]==2 and + profile["addition_count"]==5 and + profile["independent_security_review"] is False and + tooling["workflow_profile_sha256"]== + profile["current_source_profile_sha256"] and + identity["checked_sha"]==checked and + identity["head_sha"]==reviewed_head and + identity["head_tree_sha"]==scalar(worktree,"rev-parse", + checked+"^{tree}") and + identity["current_workflow_profile_sha256"]== + profile["current_source_profile_sha256"] and + identity["independent_security_review"] is False, + "candidate historical and current-source profiles disagree") + return profile,tooling,identity + + + +def _nested_workflow_failure(finished, evidence): + """Preserve bounded real nested failure evidence in the outer CI failure. + + The detached report can be removed with the disposable worktree, so + failure IDs, error codes and output must also reach the outer job log. + This diagnostic is never an approval or a successful test receipt. + """ + output=(finished.stdout or "")+"\n"+(finished.stderr or "") + details={ + "format":"Permansson.PR113.WorkflowSuiteFailureDiagnostic.v1", + "status":"FAIL_CLOSED", + "nested_returncode":finished.returncode, + "nested_output_sha256":hashlib.sha256(output.encode("utf-8")).hexdigest(), + "report_state":"ABSENT", + "failed_test_methods":[], + "required_github_ci_authenticated":False, + "independent_security_review":False, + "owner_activation_authorized":False, + "mathematical_acceptance":False, + } + if evidence.is_file() and not evidence.is_symlink(): + try: + raw=evidence.read_bytes() + if not 024000: + output=output[:8000]+"\n... bounded nested output omitted ...\n"+output[-16000:] + envelope=json.dumps(details,sort_keys=True) + print("PR113_NESTED_WORKFLOW_FAILURE_BEGIN",file=sys.stderr) + print(envelope,file=sys.stderr) + print("PR113_NESTED_WORKFLOW_FAILURE_END",file=sys.stderr) + return "actual installed CI-06/CI-07 test suite failed:\n"+envelope+"\n"+output + + +def run_installed_workflow_suite(checkout, evidence): + """Run actual 30-source CI-06/CI-07 unittest+registry, not a source mock. + + A sanitized local execution is independent of PR and cannot be reused as + genuine required GitHub CI or as an approval of its own source. + """ + checkout=Path(checkout).resolve(strict=True) + evidence=Path(evidence).absolute() + need(not evidence.exists() and not evidence.is_symlink() + and not evidence.is_relative_to(checkout) + and not any(p.is_symlink() for p in evidence.parents), + "local workflow suite evidence must be new and outside Git checkout") + env={name:value for name,value in os.environ.items() + if not name.startswith(("GITHUB_","GH_","GIT_")) + and name not in ("CI","PYTHONPATH","PYTHONHOME") + and not any(key in name.upper() for key in + ("TOKEN","PASSWORD","COOKIE","SECRET","AUTHORIZATION", + "CREDENTIAL","PROXY"))} + env["PYTHONDONTWRITEBYTECODE"]="1" + source=scalar(checkout,"rev-parse","HEAD") + command=[sys.executable,"-B","-m","verification.integrity.run_tests", + "--suite","workflow","--report",str(evidence)] + try: + finished=subprocess.run(command,cwd=checkout,env=env, + capture_output=True,text=True,timeout=180) + except (OSError,subprocess.TimeoutExpired) as exc: + raise RehearsalError("actual versioned workflow suite unavailable") from exc + if finished.returncode!=0 or not evidence.is_file(): + raise RehearsalError(_nested_workflow_failure(finished,evidence)) + try: + report=json.loads(evidence.read_bytes()) + except (ValueError,UnicodeError,OSError) as exc: + raise RehearsalError("actual CI-06/CI-07 report malformed") from exc + if not isinstance(report,dict) or report.get("status")!="PASS": + raise RehearsalError(_nested_workflow_failure(finished,evidence)) + context=report.get("context") + need(report.get("format")=="Permansson.Hardening.Tests.v1" + and report.get("status")=="PASS" + and report.get("suite")=="workflow" + and report.get("execution_tracked_clean") is True + and type(report.get("methods_run")) is int and + report["methods_run"]>=8 + and report.get("failures")==0 and report.get("errors")==0 + and report.get("skips")==0 and + report.get("covered_requirements")==["CI-06","CI-07"] + and isinstance(context,dict) + and context.get("checked_sha")==source + and context.get("event")=="local", + "current30 workflow execution omitted source-bound CI-06/CI-07 requirements") + need(not run(checkout,"status","--porcelain=v1","--untracked-files=all"), + "executing installed workflow suite modified Git source") + return { + "status":"CURRENT_30_WORKFLOW_CI06_CI07_EXECUTED_LOCALLY", + "source_head":source, + "methods_run":report["methods_run"], + "covered_requirements":report["covered_requirements"], + "report_sha256":hashlib.sha256(evidence.read_bytes()).hexdigest(), + "real_execution":True, + "required_github_check_authenticated":False, + "independent_security_review":False, + "mathematical_acceptance":False, + } + + + +ACTIVE_30_SUITES=( + "test_pr113_tooling_migration_v2.py", + "test_pr113_a2_successor_identity_v2.py", + "test_pr113_successor_workflow_profile.py", + "test_pr113_atomic_migration_preview.py", + "test_pr113_v2_workflow_contract_preview.py", + "test_pr113_full_required_workflow_preview.py", + "test_pr113_inactive_required_gate_replacement.py", + "test_pr113_workflow_proposal_boundary.py", +) + + + +def _active30_suite_passed(process, output): + """Require executed unittest success, not caller-supplied status text.""" + return bool( + process.returncode == 0 + and re.search(r"Ran [1-9][0-9]* tests? in ", output) + and re.search(r"(?:^|\n)OK(?:\n|$)", output) + and "FAILED (" not in output + and "skipped=" not in output + and "expected failures=" not in output + ) + + +def _active30_bounded_output(output): + """Redact credential-bearing diagnostic lines before external logging.""" + sensitive=re.compile( + r"(?i)(?:\b(?:GITHUB_TOKEN|GH_TOKEN|ACCESS_TOKEN|API_KEY|" + r"SECRET|PASSWORD|CREDENTIAL|AUTHORIZATION|COOKIE)\b\s*[:=]|" + r"\bbearer\s+\S+|(?:github_pat_|gh[pousr]_)[A-Za-z0-9_]+)" + ) + clean="\n".join( + "[redacted credential-bearing diagnostic line]" + if sensitive.search(line) else line + for line in output.splitlines() + ) + if len(clean) <= 9000: + return clean, "" + return clean[:3500], clean[-5500:] + + +def _active30_failure_receipt(checkout, filename, process, output, prior, *, + timed_out=False): + """Bounded, nonauthorizing record of a genuine failed nested test run. + + Parse the complete captured test stream before bounding; preserve its + SHA256 and exact unittest FAIL/ERROR identifiers. Logs remain detached. + """ + head,tail=_active30_bounded_output(output) + methods=[] + seen=set() + for match in re.finditer(r"(?m)^(FAIL|ERROR):[ \t]*([^\r\n]+)$",output): + kind,header=match.groups() + safe_header,_=_active30_bounded_output(header) + name=re.search(r"\(([^()\r\n]+)\)\s*$",safe_header) + ident=(name.group(1) if name else safe_header).strip() + if ident not in seen: + methods.append({"method":ident[:400],"status":kind, + "header":safe_header[:550]}) + seen.add(ident) + if len(methods)>=32: + break + summaries=[] + for line in output.splitlines(): + stripped=line.strip() + if re.match(r"^(?:AssertionError|[A-Za-z_][A-Za-z0-9_.]*" + r"(?:Error|Exception|Failure)):",stripped): + safe,_=_active30_bounded_output(stripped) + if safe not in summaries: + summaries.append(safe[:650]) + if len(summaries)>=20: + break + ran=re.search(r"(?m)^Ran ([0-9]+) tests? in ",output) + return { + "format":"Permansson.PR113.Active30RegressionFailure.v2", + "status":"FAIL_CLOSED", + "checked_sha":scalar(checkout,"rev-parse","HEAD"), + "failed_suite":filename, + "failed_returncode":process.returncode, + "timed_out":bool(timed_out), + "tests_run":int(ran.group(1)) if ran else None, + "failed_methods":methods, + "error_summaries":summaries, + "failed_output_sha256":hashlib.sha256(output.encode("utf-8")).hexdigest(), + "diagnostic_head":head, + "diagnostic_tail":tail, + "earlier_executed_suites":prior, + "github_required_ci_authenticated":False, + "independent_security_review":False, + "owner_activation_authorized":False, + "mathematical_acceptance":False, + } + + +def run_active_30_regressions(checkout, evidence_dir): + """Run the REAL phase-aware suites against committed installed 30 blobs. + + No source-owned test result can authorize activation or security review. + """ + checkout=Path(checkout).resolve(strict=True) + evidence_dir=Path(evidence_dir).absolute() + need(not evidence_dir.exists() + and not evidence_dir.is_relative_to(checkout) + and not any(p.is_symlink() for p in evidence_dir.parents), + "active30 regression logs must be detached fresh evidence") + evidence_dir.mkdir(parents=True,exist_ok=False) + env={key:value for key,value in os.environ.items() + if not key.startswith(("GITHUB_","GH_","GIT_")) + and key not in ("CI","PYTHONPATH","PYTHONHOME") + and not any(token in key.upper() for token in ( + "TOKEN","PASSWORD","COOKIE","SECRET","AUTHORIZATION", + "CREDENTIAL","PROXY"))} + env["PYTHONDONTWRITEBYTECODE"]="1" + receipts=[] + for filename in ACTIVE_30_SUITES: + need((checkout/"verification/research"/filename).is_file(), + "active30 regression source absent: "+filename) + args=[sys.executable,"-B","-m","unittest","discover", + "-s","verification/research","-p",filename,"-v"] + timed_out=False + try: + process=subprocess.run( + args,cwd=checkout,env=env,timeout=150, + capture_output=True,text=True) + except subprocess.TimeoutExpired as exc: + # Preserve timeout as a failed execution; never manufacture OK. + timed_out=True + def decoded(value): + if isinstance(value,bytes): + return value.decode("utf-8","replace") + return value if isinstance(value,str) else "" + process=subprocess.CompletedProcess( + args,124,decoded(exc.stdout), + decoded(exc.stderr)+"\nACTIVE30_SUBPROCESS_TIMEOUT") + except OSError as exc: + raise RehearsalError("actual current30 regression unavailable: "+ + filename) from exc + output=(process.stdout+"\n"+process.stderr) + log=evidence_dir/filename.removesuffix(".py") + log.write_text(output,encoding="utf-8") + passed=_active30_suite_passed(process,output) and not timed_out + if not passed: + # This receipt is only a diagnostic of FAILED local execution. + # It cannot impersonate GitHub CI or confer review/activation. + failure=_active30_failure_receipt( + checkout,filename,process,output,receipts,timed_out=timed_out) + failure_path=evidence_dir/"FAILURE_ONLY.json" + with failure_path.open("x",encoding="utf-8") as f: + json.dump(failure,f,sort_keys=True,indent=2) + f.write("\n") + print("PR113_ACTIVE30_FAILURE_RECEIPT_BEGIN", + file=sys.stderr) + print(json.dumps(failure,sort_keys=True),file=sys.stderr) + print("PR113_ACTIVE30_FAILURE_RECEIPT_END", + file=sys.stderr) + raise RehearsalError( + "authentic active30 regression failed: "+filename+ + "\n"+json.dumps(failure,sort_keys=True)) + receipts.append({ + "source_test":filename, + "status":"PASS_EXECUTED", + "output_sha256":hashlib.sha256(output.encode()).hexdigest(), + }) + need(not run(checkout,"status","--porcelain=v1","--untracked-files=all"), + "active30 regressions changed committed source") + return { + "format":"Permansson.PR113.Active30RegressionRehearsal.v1", + "status":"EIGHT_REAL_ACTIVE30_SUITES_PASSED_UNREVIEWED", + "checked_sha":scalar(checkout,"rev-parse","HEAD"), + "executed_suites":receipts, + "independent_security_review":False, + "required_github_ci_authenticated":False, + "mathematical_acceptance":False, + } + + +def execute(head,output): + """Run actual source-code verification; no mocked compiler/reviewer claims.""" + output=Path(output).absolute() + need(not output.exists() and not output.is_symlink() and + not output.is_relative_to(ROOT.resolve()) and + not any(p.is_symlink() for p in output.parents), + "rehearsal report must be a new external regular-file path") + source_at_entry=original_source(ROOT,head) + phase,pre=audited_source_phase(ROOT,source_at_entry) + need(pre.get("historical_original_workflow_count")==25 and + pre.get("current_reviewed_workflow_count")==30 and + pre.get("versioned_source_phase")==phase, + "original source not fully verified as exact staged25 or active30") + sources=planned_sources(ROOT,head) + report={} + with tempfile.TemporaryDirectory(prefix="pr113-disposable-source-") as temp: + worktree=Path(temp)/"checkout" + need(not worktree.exists(),"temporary checkout destination occupied") + run(ROOT,"worktree","add","--detach",str(worktree),head,seconds=180) + try: + if phase=="staged": + need(len(list((worktree/".github/workflows").glob("*.yml")))==25, + "historical staged checkout must contain exactly 25 workflows") + for path,(stage,oid) in sorted(sources.items()): + _,raw=object_bytes(ROOT,head,stage) + dest=worktree/path + need(not dest.is_symlink(),"symlinked target in disposable tree") + dest.parent.mkdir(parents=True,exist_ok=True) + dest.write_bytes(raw) + need(hashlib.sha1( + b"blob "+str(len(raw)).encode("ascii")+b"\0"+dest.read_bytes() + ).hexdigest()==oid, + "disposable source write not exactly staged Git blob") + run(worktree,"add","--",*sorted(sources)) + run(worktree,"commit","--no-gpg-sign", + "-m","PR113 DISPOSABLE 30-source review rehearsal only", + author=True,seconds=180) + C=scalar(worktree,"rev-parse","HEAD") + need(C!=head,"25-to-30 rehearsal did not produce a new Git tree") + else: + need(len(list((worktree/".github/workflows").glob("*.yml")))==30, + "activated disposable worktree must contain 30 workflows") + C=scalar(worktree,"rev-parse","HEAD") + need(C==head,"active30 direct source checkout must be exact H") + for path,(stage,oid) in sorted(sources.items()): + actual=scalar(worktree,"rev-parse","HEAD:"+path) + need(actual==oid, + "installed source not identical to reviewed staged blob: "+path) + profile,tooling,identity=verify_target_worktree(worktree,C) + workflow_suite=run_installed_workflow_suite( + worktree,Path(temp)/"actual_current30_workflow_suite.json") + need(workflow_suite["source_head"]==C, + "current30 CI-06/07 suite ran against unrelated source") + active_regressions=run_active_30_regressions( + worktree,Path(temp)/"active30_regression_evidence") + need(active_regressions["checked_sha"]==C, + "active30 unit suites ran against unrelated Git source") + tree=scalar(worktree,"rev-parse",C+"^{tree}") + S=run(worktree,"commit-tree",tree,"-p",PR113_BASE,"-p",C, + input_bytes=b"PR113 DISPOSABLE ordered [B113,C] dry-run\n", + author=True).decode().strip() + need(SHA.fullmatch(S) is not None and + scalar(worktree,"show","-s","--format=%P",S).split()== + [PR113_BASE,C] and + scalar(worktree,"rev-parse",S+"^{tree}")==tree, + "failed to form authentic detached ordered synthetic checkout") + run(worktree,"checkout","--detach",S) + synthetic_profile,synthetic_tooling,synthetic_identity=\ + verify_target_worktree(worktree,C) + need(synthetic_profile["current_source_profile_sha256"]== + profile["current_source_profile_sha256"] and + synthetic_tooling["workflow_profile_sha256"]== + tooling["workflow_profile_sha256"] and + synthetic_identity["checked_sha"]==S and + synthetic_identity["head_sha"]==C, + "direct C and ordered S disagree on exact active30 source") + # Even in the synthetic candidate checkout, changing one exact + # source byte must not satisfy the reviewed profile contract. + target=worktree/".github/workflows/research-lean.yml" + original=target.read_bytes() + try: + target.write_bytes(original+b"\n# malicious unreviewed drift\n") + rejected=False + try: + workflow_audit(worktree,S,phase="active") + except (OSError,ValueError): + rejected=True + need(rejected,"corrupted synthetic workflow source was accepted") + finally: + target.write_bytes(original) + need(not run(worktree,"status","--porcelain=v1","--untracked-files=all"), + "disposable workflow source was not restored after mutation") + report={ + "format":FORMAT, + "status":("DISPOSABLE_SOURCE_AND_ORDERED_SYNTHETIC_REHEARSED_UNREVIEWED" + if phase=="staged" else + "DISPOSABLE_ACTIVE_30_AND_ORDERED_SYNTHETIC_REHEARSED_UNREVIEWED"), + "source_phase_at_invocation":phase, + "current30_CI06_CI07_workflow_suite":workflow_suite, + "current30_phase_aware_regressions":active_regressions, + "current30_local_suite_is_required_CI":False, + "actual_requested_head":head, + "source_at_invocation":scalar(ROOT,"rev-parse","HEAD"), + "original_25_workflow_anchor":PREACTIVATION_HEAD, + "disposable_current_source_commit":C, + "disposable_synthetic_source_commit":S, + "disposable_git_tree":tree, + "historical_original_workflow_count":25, + "current_reviewed_workflow_count":30, + "retained_workflow_count":23, + "replacement_workflow_count":2, + "addition_workflow_count":5, + "guard_source_replacements":sorted(SOURCE_REPLACEMENTS), + "profile_sha256":profile["current_source_profile_sha256"], + "synthetic_profile_sha256": + synthetic_profile["current_source_profile_sha256"], + "disposable_source_mutation_rejected":True, + "original_T_archive_replay":"NOT_EXECUTED", + "original_A2_compiler_replay":"NOT_EXECUTED", + "current_A2_successor_compiler_replay":"NOT_EXECUTED", + "source_bound_GitHub_producer_authenticated":False, + "independent_security_review":False, + "owner_activation_authorized":False, + "all_required_CI_green":False, + "mathematical_acceptance":False, + "merged_main_sha":None, + "owner_merge_authorized":False, + "publication_authorized":False, + } + finally: + run(ROOT,"worktree","remove","--force",str(worktree),seconds=180) + need(not run(ROOT,"status","--porcelain=v1","--untracked-files=all"), + "original checkout changed during detached source rehearsal") + output.parent.mkdir(parents=True,exist_ok=True) + with output.open("x",encoding="utf-8") as f: + json.dump(report,f,sort_keys=True,indent=2) + f.write("\n") + return report + + +def main(argv=None): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head",required=True) + parser.add_argument("--out",required=True,type=Path) + args=parser.parse_args(argv) + try: + result=execute(args.head,args.out) + print(json.dumps({"status":result["status"], + "candidate":result["disposable_current_source_commit"], + "original_source_unmodified":True},sort_keys=True)) + return 0 + except (OSError,ValueError,KeyError) as exc: + print("PR113 DISPOSABLE WORKTREE REHEARSAL BLOCKED:",exc,file=sys.stderr) + return 2 + + +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_draft_rehearsal_auth.py b/verification/research/pr113_draft_rehearsal_auth.py new file mode 100644 index 00000000..f3131222 --- /dev/null +++ b/verification/research/pr113_draft_rehearsal_auth.py @@ -0,0 +1,274 @@ +"""Bounded source-bound verification of an existing two-root draft CI ZIP. + +Read-only offline audit of actual draft bytes; not mathematical acceptance, +scholarly signoff, authentic live CI, or release authorization. This is +intentionally separate from the strict post-merge seal and from postseal replay. +""" +from __future__ import annotations +import argparse +import hashlib +import io +import json +from pathlib import Path +import re +import stat +import sys +import zipfile +from pathlib import PurePosixPath + +from infrastructure_acceptance import inspect_zip + +SOURCE_PATHS = frozenset([ + "README.md", "main.tex", "preamble.tex", "references.bib", + "sections/01_scope_and_semantics.tex", + "sections/02_existing_foundations.tex", + "sections/03_discounted_original_margin.tex", + "sections/04_measurable_multiagent_recovery.tex", + "sections/05_nonlinear_occupation.tex", + "sections/06_counterexamples_and_limits.tex", + "sections/07_formal_verification.tex", + "appendices/A_theorem_crosswalk.tex", + "appendices/B_reproducibility.tex", +]) +PREFIX = "research_papers/mathematical_closure/" +FORMAT = "Permansson.PR113.DraftByteRehearsal.v1" +SHA = re.compile(r"^[0-9a-f]{40}$") +HEX = re.compile(r"^[0-9a-f]{64}$") +ROOT = Path(__file__).resolve().parents[2] + + +class DraftRehearsalError(ValueError): + pass + + +def need(ok, msg): + if not ok: + raise DraftRehearsalError(msg) + + +def digest(raw): + return hashlib.sha256(raw).hexdigest() + + +def jsonfile(files, name): + need(name in files and 0 < len(files[name]) <= 1024*1024, + "missing or excessive original draft receipt: "+name) + try: + data = json.loads(files[name]) + except (ValueError, TypeError) as exc: + raise DraftRehearsalError("invalid original draft JSON: "+name) from exc + need(isinstance(data, dict), "draft receipt must be JSON object") + return data + + +def bounded_nested_archive(raw): + """Reject inner archive bombs, symlinks, traversal and duplicate members.""" + need(type(raw) is bytes and 0 < len(raw) <= 64*1024*1024, + "draft nested archive byte budget exceeded") + try: + with zipfile.ZipFile(io.BytesIO(raw)) as z: + infos=z.infolist() + need(1 <= len(infos) <= 100, + "nested draft ZIP member count unsafe") + total=0 + seen=set() + output={} + for item in infos: + name=item.filename + path=PurePosixPath(name) + mode=(item.external_attr >> 16) & 0o170000 + need(name==path.as_posix() and + not path.is_absolute() and + all(part not in ("", ".", "..") for part in name.split("/")) + and "\\" not in name and len(name) <= 300 + and not item.is_dir() and mode in (0,stat.S_IFREG) + and name.casefold() not in seen and + 0 < item.file_size <= 16*1024*1024 + and item.compress_size > 0 and + item.file_size <= item.compress_size * 300 and + not (item.flag_bits & 1), + "nested draft ZIP member unsafe, duplicate or compressed bomb") + seen.add(name.casefold()) + total+=item.file_size + need(total <= 64*1024*1024, + "nested original manuscript expands beyond source limit") + output[name]=z.read(item) + need(z.testzip() is None,"nested original draft archive CRC invalid") + return output + except (OSError,RuntimeError,ValueError,zipfile.BadZipFile, + zipfile.LargeZipFile) as exc: + raise DraftRehearsalError("invalid actual nested draft ZIP") from exc + + +def audit(artifact_zip, head): + need(isinstance(head, str) and SHA.fullmatch(head), + "draft rehearsal needs exact Git H") + files = inspect_zip(artifact_zip) + ci = jsonfile(files,"ci_execution_identity.json") + report = jsonfile(files,"reproduction_receipt.json") + need(ci.get("status") == "EXECUTED_DRAFT_ONLY_NO_SCHOLARLY_REVIEW" and + ci.get("format") == "Permansson.CICleanRebuildCandidate.v1" and + ci.get("source_head") == head and + ci.get("github_repository") == "HMarcusWH/PermanssonLean" and + ci.get("github_job") == "clean-paper-rebuild" and + type(ci.get("github_run_id")) is int and ci["github_run_id"] > 0 and + type(ci.get("github_run_attempt")) is int and + ci["github_run_attempt"] > 0 and + ci.get("authenticated_ci_artifact") is False and + ci.get("scientific_release_approved") is False, + "draft source execution identity missing or claiming reviewer approval") + comparison=report.get("byte_comparison") + need(report.get("format") == "Permansson.TwoCleanSourceRebuilds.v1" and + report.get("status") == "REPRODUCIBLE_DRAFT_ONLY" and + report.get("source_commit_sha") == head and + report.get("independent_source_roots") == 2 and + report.get("two_complete_archive_audits") is True and + report.get("rendered_pages") is True and + report.get("authenticated_ci_artifact") is False and + report.get("scientific_release_approved") is False and + isinstance(report.get("source_tree_sha"),str) and + SHA.fullmatch(report["source_tree_sha"]) and + isinstance(comparison,dict) and + comparison.get("status") == "REPRODUCIBLE_DRAFT_ONLY" and + comparison.get("source_commit_sha") == head, + "two-root actual draft execution or comparison was incomplete") + names=("Permansson_Mathematical_Closure.pdf", + "Permansson_Mathematical_Closure.tex", + "Permansson_Mathematical_Closure_E2E.zip", + "manifest.lock.json", "source_ci_receipts.lock.json", + "SHA256SUMS.txt", "latex_build.log") + page_hashes={} + for side in ("first","second"): + prefix=side+"/" + original={name:files.get(prefix+name) for name in names} + need(all(type(x) is bytes and bool(x) for x in original.values()), + "two root outputs missing draft PDF, ZIP or checksum metadata") + manifest=jsonfile(files,prefix+"manifest.lock.json") + reviews=jsonfile(files,prefix+"source_ci_receipts.lock.json") + need(manifest.get("format") == "Permansson.MathClosure.BuildReceipt.v1" + and manifest.get("status") == "DRAFT_NOT_ACCEPTED" + and manifest.get("source_commit_sha") == head + and manifest.get("independent_review") == "NOT_RECEIVED" + and manifest.get("visual_inspection") == "NOT_PERFORMED" + and manifest.get("source_ci_validation") == + "NOT_BOUND_TO_MERGED_MAIN" + and reviews == { + "status":"MISSING_POST_MERGE_SOURCE_AND_REVIEWS", + "ci_receipts":[], "independent_reviews":[]}, + "draft output falsely reports source review, merged CI or acceptance") + index=manifest.get("source_tree_files") + need(isinstance(index,dict) and + set(index)=={PREFIX+p for p in SOURCE_PATHS} and + all(isinstance(x,str) and HEX.fullmatch(x) for x in index.values()), + "draft no longer records exact original thirteen manuscript inputs") + for name,expected_hash in ( + ("pdf_sha256",original[names[0]]), + ("tex_sha256",original[names[1]]), + ("latex_log_sha256",original["latex_build.log"]), + ): + need(manifest.get(name)==digest(expected_hash), + "draft original artifact hash mismatch: "+name) + need(original[names[0]].startswith(b"%PDF-"), + "original two-root draft does not contain a real PDF") + sums={} + try: + for line in original["SHA256SUMS.txt"].decode("utf-8").splitlines(): + hashval,filename=line.split(" ",1) + need(HEX.fullmatch(hashval) and filename not in sums, + "invalid/duplicate source sidecar entry") + sums[filename]=hashval + except (UnicodeError, ValueError) as exc: + raise DraftRehearsalError("invalid actual source SHA256 sidecar") from exc + required={names[0],names[1],names[2],names[3],names[4]} + need(set(sums)==required and + all(digest(original[name])==sums[name] for name in required), + "incomplete or modified original draft SHA256SUMS.txt") + page_count=manifest.get("pdf_page_count") + need(type(page_count) is int and 1<=page_count<=128, + "missing or invalid rendered PDF page count") + pages={} + for number in range(1,page_count+1): + name=prefix+f"pages/page-{number}.png" + need(name in files and len(files[name])>0, + "missing rendered page from exact PDF build") + pages[name.removeprefix(prefix)]=digest(files[name]) + extras=[name for name in files + if name.startswith(prefix+"pages/") and + name.removeprefix(prefix) not in pages] + need(not extras,"unexpected or duplicated PDF render page") + inner=bounded_nested_archive(original[names[2]]) + need(all("manuscript/"+path in inner for path in SOURCE_PATHS), + "draft nested archive lost manuscript source") + need(inner.get("manifest.lock.json")==original[names[3]] and + inner.get("Permansson_Mathematical_Closure.pdf")==original[names[0]] and + inner.get("source_ci_receipts.lock.json")==original[names[4]], + "two-root draft nested archive replaced its source outputs") + for path in SOURCE_PATHS: + need(digest(inner["manuscript/"+path])==index[PREFIX+path], + "actual original draft manuscript source byte mismatch") + if side=="first": + first_manifest=original[names[3]] + first_files={key:digest(val) for key,val in original.items()} + page_hashes=pages + else: + need(original[names[3]]==first_manifest and + {key:digest(val) for key,val in original.items()}==first_files + and pages==page_hashes, + "independent draft outputs/renders differ in actual bytes") + pdf=digest(files["first/"+names[0]]) + zip_digest=digest(files["first/"+names[2]]) + need(report.get("pdf_sha256")==pdf and + report.get("archive_sha256")==zip_digest and + report.get("manifest_sha256")==digest(first_manifest) and + comparison.get("pdf_sha256")==pdf and + comparison.get("zip_sha256")==zip_digest and + comparison.get("page_count")==len(page_hashes) and + ci.get("pdf_sha256")==pdf and + ci.get("archive_sha256")==zip_digest, + "outside reproduction/CI summary does not match actual draft bytes") + return { + "format":FORMAT, + "status":"REPRODUCIBLE_DRAFT_BYTES_AUDITED_NOT_A_RELEASE", + "source_head":head, + "pdf_sha256":pdf, + "archive_sha256":zip_digest, + "source_member_count":len(SOURCE_PATHS), + "independent_source_roots":2, + "rendered_pages_verified":len(page_hashes), + "original_ci_run_id_reported_unverified":ci["github_run_id"], + "actual_GitHub_artifact_authentication":False, + "independent_scholarly_review":False, + "mathematical_acceptance":False, + "publication_authorized":False, + } + + +def cli(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--head",required=True) + p.add_argument("--artifact-zip",type=Path,required=True) + p.add_argument("--out",type=Path,required=True) + a=p.parse_args(argv) + try: + out=a.out.absolute() + need(not out.exists() and not any(x.is_symlink() for x in (out,*out.parents)) + and not out.is_relative_to(ROOT.resolve()), + "draft evidence report must be a new detached output") + raw=a.artifact_zip.read_bytes() + need(00 and + current.get("failures")==0 and current.get("errors")==0 and + current.get("skips")==0 and + current.get("covered_requirements")==["CI-06","CI-07"] and + isinstance(context,dict) and + context.get("repository")=="HMarcusWH/PermanssonLean" and + context.get("checked_sha")==H_source and + context.get("event")=="local" and + isinstance(context.get("checked_tree"),str) and + SHA40.fullmatch(context["checked_tree"]) is not None, + "current H original workflow verifier did not run all local source-bound tests") + return { + "format":FORMAT, + "status":"DUAL_T_AND_H_EXECUTED_UNREVIEWED", + "historical_T":T_source,"current_H":H_source, + "original_T_archive_offline_restoration_passed":True, + "current_H_workflow_test_suite_passed":True, + "source_review_approval":False, + "independent_security_review":False, + "required_current_archive_CI_green":False, + "mathematical_release_approved":False, + } + +def candidate_ci_context(head): + """Permit only the exact-branch push from the reviewed CANDIDATE job. + + This is neither a source-security authorization nor a mandatory CI gate. + Historical T itself must run with GitHub context removed (see command()). + """ + if os.environ.get("GITHUB_ACTIONS") != "true": + return "LOCAL_UNREVIEWED" + demand(os.environ.get("GITHUB_REPOSITORY") == + "HMarcusWH/PermanssonLean" and + os.environ.get("GITHUB_SHA") == head and + os.environ.get("GITHUB_REF") == + "refs/heads/research/final-mathematical-closure" and + os.environ.get("GITHUB_EVENT_NAME") == + "push" and + os.environ.get("GITHUB_JOB") == + "original-T-and-current-H" and + os.environ.get("GITHUB_WORKFLOW") == + "PR113 Historical T and A2 candidates", + "historical verification must execute only in genuine staged candidate job") + return "REVIEWED_SOURCE_PUSH_CANDIDATE_UNAPPROVED" + + +def command(argv,cwd,limit=1800, *, historical_artifact=False, + detached_current_suite=False): + environment=os.environ.copy() + for key in ("PYTHONPATH","PYTHONHOME"): + environment.pop(key,None) + if historical_artifact or detached_current_suite: + # Both historical T and the original current-H test runner must run + # WITHOUT event/credential inputs. The current-H source test runner + # rejects a feature-branch push as a genuine main/PR required check. + # The outer candidate job separately binds the real GitHub push. + # Neither child may claim original required CI acceptance. + environment = { + name: value for name,value in environment.items() + if not name.startswith(("GITHUB_","GH_","GIT_")) and + name != "CI" and + not any(token in name.upper() for token in + ("TOKEN","PASSWORD","COOKIE","SECRET","AUTHORIZATION", + "CREDENTIAL","PROXY")) + } + environment["PYTHONDONTWRITEBYTECODE"]="1" + try: + result=subprocess.run(argv,cwd=cwd,env=environment,timeout=limit, + capture_output=True,text=True) + except (OSError,subprocess.TimeoutExpired) as exc: + raise DualToolingError("isolated original verification unavailable") from exc + if result.returncode!=0: + raise DualToolingError("actual historical/current verifier exited "+ + str(result.returncode)+": "+ + (result.stdout+result.stderr)[-1800:]) + return result + +def execute(historical_source,current_source,head,out): + historical=checkout(historical_source,T) + current=checkout(current_source,head) + demand(historical!=current and head!=T, + "separate genuine T and H checkouts mandatory") + output=Path(out) + demand(not output.is_symlink(),"output symlink forbidden") + output=output.resolve() + demand(not output.exists() and + not output.is_relative_to(historical) and + not output.is_relative_to(current), + "archive evidence output must be new, detached and disjoint") + execution_context=candidate_ci_context(head) + proposal=tooling_audit(current,head) + demand(proposal["status"]=="DRAFT_NEEDS_OWNER_AND_INDEPENDENT_SECURITY_REVIEW" + and proposal["historical_archive_unchanged"] is True and + len(proposal["changed_canonical_tooling"])==2, + "T/H tooling changes are not the exactly reviewed two-file proposal") + supplement=current/"archival/v0.2.0/supplement-1" + demand(supplement.is_dir() and not supplement.is_symlink(), + "actual immutable historical supplement missing") + output.mkdir(parents=True,exist_ok=False) + original_report=output/"original_T_archive.json" + current_report=output/"current_H_workflow.json" + # Separate cwd and sanitized Python import path ensure the original + # historical T archive implementation is actually executed, rather than + # importing the newer H module via PYTHONPATH. + command([sys.executable,"-B","-m","verification.archive.offline_smoke", + "--supplement",str(supplement), + "--scratch",str(output/"historical_restore"), + "--report",str(original_report)],historical, + historical_artifact=True) + command([sys.executable,"-B","-m","verification.integrity.run_tests", + "--suite","workflow","--report",str(current_report)],current, + detached_current_suite=True) + old=json.loads(original_report.read_bytes()) + new=json.loads(current_report.read_bytes()) + receipt=validate_dual_reports(old,new,T_source=T,H_source=head) + receipt["candidate_execution_context"]=execution_context + receipt["historical_leg_environment"]="NO_GITHUB_EVENT_CONTEXT_STANDALONE_T" + receipt["current_leg_environment"]="CURRENT_ACTUAL_H_WORKFLOW_TEST_SUITE" + receipt["historical_manifest_sha256"]=proposal["historical_manifest_sha256"] + receipt["reviewed_25_workflow_inventory"]=proposal["current_workflow_file_count"] + receipt["historical_tooling_diff"]=proposal["changed_canonical_tooling"] + receipt["historical_T_report_sha256"]=sha_file(original_report) + receipt["current_H_report_sha256"]=sha_file(current_report) + receipt["original_T_archive_source_commit"]=T + receipt["genuine_current_source_commit"]=head + receipt["original_T_archive_unchanged"]=proposal["historical_archive_unchanged"] + receipt["independent_security_review"]=False + receipt["versioned_successor_T2_approved"]=False + proposal_dossier={ + "format":T2_FORMAT, + "status":"COMPLETE_DUAL_EXECUTION_CANDIDATE_SECURITY_REVIEW_PENDING", + "base_sha":proposal["original_release_base_B"], + "T":T,"H":head, + "historical_supplement_tree":proposal["historical_supplement_tree"], + "historical_manifest_sha256":proposal["historical_manifest_sha256"], + "canonical_tooling_changes":proposal["changed_canonical_tooling"], + "required_workflow_inventory_count":proposal["current_workflow_file_count"], + "T_original_offline_restore_report_sha256":sha_file(original_report), + "H_original_workflow_test_report_sha256":sha_file(current_report), + "legacy_T_did_not_accept_current_C":True, + "owner_authorization_present":False, + "independent_security_review_present":False, + "historical_required_CI_replaced":False, + "mathematical_release_approved":False, + } + (output/"dual_tooling_unreviewed.json").write_text( + json.dumps(receipt,sort_keys=True,indent=2)+"\n") + (output/"T2_security_review_candidate.json").write_text( + json.dumps(proposal_dossier,sort_keys=True,indent=2)+"\n") + return receipt + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--historical-T-source",required=True,type=Path) + p.add_argument("--current-source",required=True,type=Path) + p.add_argument("--head",required=True) + p.add_argument("--output",required=True,type=Path) + a=p.parse_args(argv) + try: + print(json.dumps(execute(a.historical_T_source, + a.current_source,a.head,a.output), + sort_keys=True)) + return 0 + except (DualToolingError,OSError,ValueError,KeyError) as exc: + print("PR113 T/H DUAL TOOLING BLOCKED:",exc,file=sys.stderr) + return 2 +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_dual_tooling_candidate_v2.py b/verification/research/pr113_dual_tooling_candidate_v2.py new file mode 100644 index 00000000..f89ddee7 --- /dev/null +++ b/verification/research/pr113_dual_tooling_candidate_v2.py @@ -0,0 +1,277 @@ +"""Versioned 30-workflow T/T2 dual-runner CANDIDATE; no security approval. + +Run the ORIGINAL historical offline archive verifier from an unmodified real T +checkout, independently of the current H's workflow integrity suite. The +historical supplement is supplied from H but independently checked against +the immutable T package via pr113_tooling_migration.audit. + +This is deliberately NOT wired into the required archive workflow before +owner authorization and an independent security review. Neither the original +T/C closure error nor original verifier is rewritten, skipped or relabelled. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys + +from pr113_tooling_migration_v2 import T, audit as tooling_audit +import localized_prefix_support as git_source + +FORMAT="Permansson.PR113.DualToolingCandidate.v3" +T2_FORMAT="Permansson.PR113.ToolingSecurityReviewDossier.v2" + + +def checksum(raw): + return hashlib.sha256(raw).hexdigest() + + +def sha_file(path): + return checksum(Path(path).read_bytes()) +SHA40=re.compile(r"^[0-9a-f]{40}$") + +class DualToolingError(ValueError): + pass + +def demand(value,message): + if not value:raise DualToolingError(message) + +def checkout(root,commit): + raw=Path(root) + demand(not raw.is_symlink() and + not any(parent.is_symlink() for parent in raw.absolute().parents), + "historical/current source may not have symlink ancestors") + src=raw.resolve(strict=True) + demand(src.is_dir() and SHA40.fullmatch(commit) is not None and + git_source.git_text(src,"rev-parse","HEAD")==commit and + Path(git_source.git_text(src,"rev-parse","--show-toplevel")).resolve()==src, + "not a genuine exact-commit source checkout") + demand(not git_source.git_bytes( + src,"status","--porcelain=v1","--untracked-files=all"), + "archival tooling checkout has unreviewed source changes") + return src + +def validate_dual_reports(historical, current, *, T_source, H_source): + demand(isinstance(historical,dict) and + historical.get("format")=="Permansson.Hardening.Check.v1" and + isinstance(historical.get("context"), dict) and + historical.get("status")=="PASS" and + historical.get("check")=="archive-reproducibility", + "original T offline archive verifier did not PASS") + details=historical.get("details") + demand(isinstance(details,dict) and + details.get("acceptance_scope")=="accepted_artifact_replay_only" and + isinstance(details.get("isolation"),dict) and + details["isolation"].get("network_isolation")=="ENFORCED" and + details["isolation"].get("capabilities")=="all_zero", + "original T offline restore omitted true network isolation") + context=current.get("context") if isinstance(current,dict) else None + demand(isinstance(current,dict) and + current.get("format")=="Permansson.Hardening.Tests.v1" and + current.get("schema_version")==1 and + current.get("status")=="PASS" and + current.get("suite")=="workflow" and + current.get("execution_tracked_clean") is True and + type(current.get("methods_run")) is int and + current["methods_run"]>0 and + current.get("failures")==0 and current.get("errors")==0 and + current.get("skips")==0 and + current.get("covered_requirements")==["CI-06","CI-07"] and + isinstance(context,dict) and + context.get("repository")=="HMarcusWH/PermanssonLean" and + context.get("checked_sha")==H_source and + context.get("event")=="local" and + isinstance(context.get("checked_tree"),str) and + SHA40.fullmatch(context["checked_tree"]) is not None, + "current H original workflow verifier did not run all local source-bound tests") + return { + "format":FORMAT, + "status":"DUAL_T_AND_H_EXECUTED_UNREVIEWED", + "historical_T":T_source,"current_H":H_source, + "original_T_archive_offline_restoration_passed":True, + "current_H_workflow_test_suite_passed":True, + "source_review_approval":False, + "independent_security_review":False, + "required_current_archive_CI_green":False, + "mathematical_release_approved":False, + } + + +def validate_dual_reports_v2(historical, current, *, T_source, H_source, profile): + """Do not turn a 25-file v1 report into a current 30-workflow receipt.""" + demand(isinstance(profile, dict) and + profile.get("format")=="Permansson.PR113.ToolingMigrationCandidate.v2" and + profile.get("reviewed_workflow_source_mode")=="active" and + profile.get("historical_original_workflow_count")==25 and + profile.get("current_reviewed_workflow_count")==30 and + profile.get("independent_security_review") is False, + "current source was not verified against exact active 30-workflow profile") + source=validate_dual_reports(historical,current,T_source=T_source,H_source=H_source) + return {**source, "format":FORMAT, + "status":"DUAL_T_AND_30_H_EXECUTED_UNREVIEWED", + "historical_original_workflow_count":25, + "current_reviewed_workflow_count":30, + "workflow_profile_sha256":profile["workflow_profile_sha256"], + "independent_security_review":False, + "owner_activation_authorized":False, + "mathematical_release_approved":False} + +def candidate_ci_context(head): + """Permit only the exact-branch push from the reviewed CANDIDATE job. + + This is neither a source-security authorization nor a mandatory CI gate. + Historical T itself must run with GitHub context removed (see command()). + """ + if os.environ.get("GITHUB_ACTIONS") != "true": + return "LOCAL_UNREVIEWED" + demand(os.environ.get("GITHUB_REPOSITORY") == + "HMarcusWH/PermanssonLean" and + os.environ.get("GITHUB_SHA") == head and + os.environ.get("GITHUB_REF") == + "refs/heads/research/final-mathematical-closure" and + os.environ.get("GITHUB_EVENT_NAME") == + "push" and + os.environ.get("GITHUB_JOB") == + "original-T-and-current-H" and + os.environ.get("GITHUB_WORKFLOW") == + "PR113 Historical T and A2 candidates", + "historical verification must execute only in genuine staged candidate job") + return "REVIEWED_SOURCE_PUSH_CANDIDATE_UNAPPROVED" + + +def command(argv,cwd,limit=1800, *, historical_artifact=False, + detached_current_suite=False): + environment=os.environ.copy() + for key in ("PYTHONPATH","PYTHONHOME"): + environment.pop(key,None) + if historical_artifact or detached_current_suite: + # Both historical T and the original current-H test runner must run + # WITHOUT event/credential inputs. The current-H source test runner + # rejects a feature-branch push as a genuine main/PR required check. + # The outer candidate job separately binds the real GitHub push. + # Neither child may claim original required CI acceptance. + environment = { + name: value for name,value in environment.items() + if not name.startswith(("GITHUB_","GH_","GIT_")) and + name != "CI" and + not any(token in name.upper() for token in + ("TOKEN","PASSWORD","COOKIE","SECRET","AUTHORIZATION", + "CREDENTIAL","PROXY")) + } + environment["PYTHONDONTWRITEBYTECODE"]="1" + try: + result=subprocess.run(argv,cwd=cwd,env=environment,timeout=limit, + capture_output=True,text=True) + except (OSError,subprocess.TimeoutExpired) as exc: + raise DualToolingError("isolated original verification unavailable") from exc + if result.returncode!=0: + raise DualToolingError("actual historical/current verifier exited "+ + str(result.returncode)+": "+ + (result.stdout+result.stderr)[-1800:]) + return result + +def execute(historical_source,current_source,head,out): + historical=checkout(historical_source,T) + current=checkout(current_source,head) + demand(historical!=current and head!=T, + "separate genuine T and H checkouts mandatory") + output=Path(out) + demand(not output.is_symlink(),"output symlink forbidden") + output=output.resolve() + demand(not output.exists() and + not output.is_relative_to(historical) and + not output.is_relative_to(current), + "archive evidence output must be new, detached and disjoint") + execution_context=candidate_ci_context(head) + proposal=tooling_audit(current,head,phase="active") + demand(proposal["status"]=="T2_EXACT_SOURCE_REVIEW_PENDING" + and proposal["historical_archive_unchanged"] is True and + len(proposal["changed_canonical_tooling"])==2 and + proposal["current_reviewed_workflow_count"]==30 and + proposal["historical_original_workflow_count"]==25 and + proposal["reviewed_workflow_source_mode"]=="active", + "T/H tooling changes are not the exactly reviewed two-file proposal") + supplement=current/"archival/v0.2.0/supplement-1" + demand(supplement.is_dir() and not supplement.is_symlink(), + "actual immutable historical supplement missing") + output.mkdir(parents=True,exist_ok=False) + original_report=output/"original_T_archive.json" + current_report=output/"current_H_workflow.json" + # Separate cwd and sanitized Python import path ensure the original + # historical T archive implementation is actually executed, rather than + # importing the newer H module via PYTHONPATH. + command([sys.executable,"-B","-m","verification.archive.offline_smoke", + "--supplement",str(supplement), + "--scratch",str(output/"historical_restore"), + "--report",str(original_report)],historical, + historical_artifact=True) + command([sys.executable,"-B","-m","verification.integrity.run_tests", + "--suite","workflow","--report",str(current_report)],current, + detached_current_suite=True) + old=json.loads(original_report.read_bytes()) + new=json.loads(current_report.read_bytes()) + receipt=validate_dual_reports_v2(old,new,T_source=T,H_source=head, + profile=proposal) + receipt["candidate_execution_context"]=execution_context + receipt["historical_leg_environment"]="NO_GITHUB_EVENT_CONTEXT_STANDALONE_T" + receipt["current_leg_environment"]="CURRENT_ACTUAL_H_WORKFLOW_TEST_SUITE" + receipt["historical_manifest_sha256"]=proposal["historical_manifest_sha256"] + receipt["historical_original_workflow_count"]=25 + receipt["current_reviewed_workflow_count"]=30 + receipt["workflow_profile_sha256"]=proposal["workflow_profile_sha256"] + receipt["historical_tooling_diff"]=proposal["changed_canonical_tooling"] + receipt["historical_T_report_sha256"]=sha_file(original_report) + receipt["current_H_report_sha256"]=sha_file(current_report) + receipt["original_T_archive_source_commit"]=T + receipt["genuine_current_source_commit"]=head + receipt["original_T_archive_unchanged"]=proposal["historical_archive_unchanged"] + receipt["independent_security_review"]=False + receipt["versioned_successor_T2_approved"]=False + proposal_dossier={ + "format":T2_FORMAT, + "status":"COMPLETE_DUAL_EXECUTION_CANDIDATE_SECURITY_REVIEW_PENDING", + "base_sha":proposal["original_release_base_B"], + "T":T,"H":head, + "historical_supplement_tree":proposal["historical_supplement_tree"], + "historical_manifest_sha256":proposal["historical_manifest_sha256"], + "canonical_tooling_changes":proposal["changed_canonical_tooling"], + "historical_original_workflow_count":25, + "required_workflow_inventory_count":30, + "current_workflow_profile_sha256":proposal["workflow_profile_sha256"], + "T_original_offline_restore_report_sha256":sha_file(original_report), + "H_original_workflow_test_report_sha256":sha_file(current_report), + "legacy_T_did_not_accept_current_C":True, + "owner_authorization_present":False, + "independent_security_review_present":False, + "historical_required_CI_replaced":False, + "independent_security_review":False, + "mathematical_release_approved":False, + } + (output/"dual_tooling_unreviewed.json").write_text( + json.dumps(receipt,sort_keys=True,indent=2)+"\n") + (output/"T2_security_review_candidate.json").write_text( + json.dumps(proposal_dossier,sort_keys=True,indent=2)+"\n") + return receipt + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--historical-T-source",required=True,type=Path) + p.add_argument("--current-source",required=True,type=Path) + p.add_argument("--head",required=True) + p.add_argument("--output",required=True,type=Path) + a=p.parse_args(argv) + try: + print(json.dumps(execute(a.historical_T_source, + a.current_source,a.head,a.output), + sort_keys=True)) + return 0 + except (DualToolingError,OSError,ValueError,KeyError) as exc: + print("PR113 T/H DUAL TOOLING BLOCKED:",exc,file=sys.stderr) + return 2 +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_fast_engineering_preflight.py b/verification/research/pr113_fast_engineering_preflight.py new file mode 100644 index 00000000..fd116f27 --- /dev/null +++ b/verification/research/pr113_fast_engineering_preflight.py @@ -0,0 +1,213 @@ +"""Fast read-only PR113 engineering QA, never a substitute for required CI. + +Run real Python syntax checks and selected mock-only adversarial unit suites, +without executing >1,500-test Research Lean discovery or changing any +historically pinned .github/workflows context. Full original Lean, archive +and scholarly gates remain mandatory. +""" +from __future__ import annotations +import argparse +import ast +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys +import time + +ROOT = Path(__file__).resolve().parents[2] +TEST_ROOT = ROOT/"verification/research" +SOURCE_ROOTS = (TEST_ROOT, ROOT/"release/mathematical-closure-1") +SUITES = ( + # The versioned migration and actual detached 30-source CI-06/CI-07 + # executions must be present before the whole producer may pass. + "test_pr113_tooling_migration_v2.py", + "test_pr113_a2_successor_identity_v2.py", + "test_pr113_a2_compiler_route_v3.py", + "test_pr113_historical_a2_replay_v1.py", + "test_pr113_historical_a2_baseline_v1.py", + "test_pr113_a2_dependency_bridge_v2.py", + "test_pr113_corrected_a2_intake.py", + "test_pr113_disposable_transition_rehearsal.py", + "test_pr113_v2_runtime.py", + "test_pr113_t2_intake_v2.py", + # The actual pinned Lean A2 positive/negative compiler runs in its + # separately configured historical-A2 producer job with lean-action. + "test_pr113_a2_successor_identity.py", + "test_pr113_dual_a2_compiler.py", + "test_pr113_dual_tooling_candidate.py", + "test_pr113_dual_review_packet.py", + "test_pr113_required_dual_gate.py", + "test_pr113_historical_postmerge.py", + "test_pr113_historical_artifact_delivery.py", + "test_pr113_inactive_required_gate_replacement.py", + "test_pr113_full_required_workflow_preview.py", + "test_pr113_atomic_migration_preview.py", + # Real phase-aware A2 input contract and exact historical Git source pin. + # These are Python-only source/receipt tests; genuine Lean and the old + # isolated historical executor remain separate mandatory CI. + "test_localized_prefix_integrity.py", + "test_pr113_phase_aware_a2_consumer.py", + "test_pr113_boundary_hardening_20261027.py", + "test_pr113_semantic_receipt_v2.py", + "test_pr113_historical_evidence_contract_v2.py", + "test_pr113_inheritance_optimized.py", + "test_pr113_v2_workflow_contract_preview.py", + "test_pr113_draft_rehearsal_auth.py", + "test_pr113_tooling_migration.py", + "test_pr113_final_source_readiness.py", + "test_final_closure_v3_source_readiness.py", + "test_final_closure_positive_route_candidate.py", + "test_final_closure_claim_controls_candidate.py", + "test_final_closure_assemble_v3.py", + "test_final_closure_batch_legacy_replay.py", + "test_final_closure_producer_bytes.py", + "test_final_closure_extra_producers_v31.py", + "test_final_closure_reproduction_auth.py", + "test_final_closure_postseal_auth.py", + "test_final_postseal_replay.py", + "test_final_closure_schema.py", + "test_clean_source_reproduction.py", + # Focused source guards must fail fast before 1,500+ research regressions. + "test_final_closure_compiler_diagnostic_retention.py", + "test_pr113_full_inventory_gate.py", + "test_pr113_mandatory_v31.py", + "test_pr113_v31_execution_integrity.py", + "test_pr113_v31_seal_entry.py", + "test_pr113_preseal_input_carrier.py", + "test_pr113_original_producer_trigger_candidate.py", + "test_pr113_original21_live_catalog.py", + "test_pr113_legacy20_runtime_resolution.py", + "test_pr113_workflow_proposal_boundary.py", + "test_pr113_release_target.py", + "test_pr113_infrastructure_acceptance.py", + "test_pr113_legacy20_choice_delivery.py", + "test_pr113_implementation_matrix.py", + "test_pr113_successor_workflow_profile.py", +) +FORMAT = "Permansson.PR113.FastEngineeringPreflight.v1" + + +class PreflightError(ValueError): + pass + + +def require(ok, msg): + if not ok: + raise PreflightError(msg) + + +def run_cli(args, timeout): + try: + return subprocess.run(args,cwd=ROOT,capture_output=True, + text=True,timeout=timeout) + except (OSError,subprocess.TimeoutExpired) as exc: + raise PreflightError("fast-source tool failed or timed out") from exc + + +def inspect_source_tree(): + rows=[] + for src in SOURCE_ROOTS: + require(src.is_dir() and not src.is_symlink(), + "expected original Python source root is unavailable") + for p in sorted(src.rglob("*.py")): + require(p.is_file() and not p.is_symlink(), + "Python source symlink or missing source") + raw=p.read_bytes() + require(len(raw)<=4*1024*1024, + "unexpectedly oversized Python source") + try: + ast.parse(raw.decode("utf-8"),filename=str(p)) + except (SyntaxError,UnicodeError) as exc: + raise PreflightError( + "unparseable original Python module: "+ + p.relative_to(ROOT).as_posix()) from exc + rows.append((p.relative_to(ROOT).as_posix(), + hashlib.sha256(raw).hexdigest())) + return rows + + +def execute(head, output): + require(isinstance(head,str) and re.fullmatch("[0-9a-f]{40}",head), + "actual source H required") + branch=run_cli(["git","rev-parse","HEAD"],30) + require(branch.returncode==0 and branch.stdout.strip()==head, + "preflight source checkout differs from requested exact H") + base=run_cli(["git","status","--porcelain=v1","--untracked-files=all", + "--","verification/research", + "release/mathematical-closure-1"],30) + require(base.returncode==0 and not base.stdout.strip(), + "original engineering test/tooling source is dirty or untracked") + output=Path(output) + require(not output.is_symlink(),"fast report output symlink prohibited") + output=output.resolve() + require(not output.exists() and + not output.is_relative_to(ROOT.resolve()) and + not ROOT.resolve().is_relative_to(output), + "fast output must be a new independent external directory") + output.mkdir(parents=True) + modules=inspect_source_tree() + tests=[] + total_ok=True + for pattern in SUITES: + require((TEST_ROOT/pattern).is_file(), + "expected source-controlled QA suite missing") + result=run_cli( + [sys.executable,"-B","-m","unittest","discover", + "-s",str(TEST_ROOT),"-p",pattern,"-v"],120) + name=pattern.removesuffix(".py") + combined=(result.stdout+"\n"+result.stderr) + (output/(name+".log")).write_text(combined) + ok=result.returncode==0 and "Ran " in combined and "OK" in combined and \ + "FAILED (" not in combined and "skipped=" not in combined + total_ok=total_ok and ok + tests.append({ + "module":pattern, + "passed":ok, + "returncode":result.returncode, + "log_sha256":hashlib.sha256(combined.encode()).hexdigest(), + }) + changed=run_cli( + ["git","status","--porcelain=v1","--untracked-files=all", + "--","verification/research","release/mathematical-closure-1"],30) + require(changed.returncode==0 and not changed.stdout.strip(), + "test execution modified tracked/untracked source") + report={ + "format":FORMAT, + "status":"FAST_ENGINEERING_QA_PASS" if total_ok else "FAST_ENGINEERING_QA_FAIL", + "source_head":head, + "python_modules_ast_parsed":len(modules), + "source_blob_digest":hashlib.sha256(json.dumps( + modules,separators=(",",":"),sort_keys=True).encode()).hexdigest(), + "test_modules_executed":len(tests), + "test_results":tests, + "original_A2_security_gate_passed":False, + "historical_T_archive_gate_passed":False, + "independent_security_review":False, + "independent_mathematical_review":False, + "scholarly_release_approved":False, + } + (output/"fast_preflight.json").write_text( + json.dumps(report,sort_keys=True,indent=2)+"\n") + require(total_ok,"one or more nonhistorical source regression tests failed") + return report + + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--head",required=True) + p.add_argument("--out",required=True,type=Path) + a=p.parse_args(argv) + try: + report=execute(a.head,a.out) + except (PreflightError,OSError,ValueError) as exc: + print("PR113 FAST QA FAILED:",exc,file=sys.stderr) + return 2 + print(json.dumps({"status":report["status"], + "source_head":report["source_head"], + "tests":report["test_modules_executed"]},sort_keys=True)) + return 0 + +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_final_source_readiness.py b/verification/research/pr113_final_source_readiness.py new file mode 100644 index 00000000..867f8d07 --- /dev/null +++ b/verification/research/pr113_final_source_readiness.py @@ -0,0 +1,157 @@ +"""Read-only PR113 exact-H candidate readiness; never authorizes merge/release. + +Requires all 12 genuine required CI contexts, every real 4+9+8 producing ZIP, +259 source types, individually owned positive/negative controls for all +45 original criteria AND 11 claims, and authentic B/H/S topology. Test +registration or legacy Python observations cannot replace source-positive/ +negative execution. Reviewer approvals remain a distinct later gate. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + +from final_closure_authenticate import BASE, REQUIRED, authenticated_checks, git, live +from final_closure_case_receipts import ( + FORMAT_V3, load_detached_bundle, authenticate_v3_sources, EvidenceError) +from final_closure_schema import validate_overlay +from final_closure_complete_lean_evidence import ROOT +from final_closure_v3_source_readiness import revalidate_source_receipts + +CLAIMS=frozenset(("Q1","Q2","Q3","M1","M2","M3","N1","N2","N3","D8","D9")) + +class ReadinessError(ValueError): + pass + +def demand(ok,message): + if not ok:raise ReadinessError(message) + +def candidate_coverage(overlay,original_ids,*,verified_roles=None): + demand(isinstance(overlay,dict) and overlay.get("format")==FORMAT_V3 and + isinstance(original_ids,set) and len(original_ids)==45, + "original strict-v3/45-case source inventory unavailable") + proofs=overlay.get("compiler_axiom_receipts",[]) + plus=overlay.get("positive_test_receipts",[]) + minus=overlay.get("semantic_negative_receipts",[]) + producers=overlay.get("producer_artifacts",[]) + demand(len(proofs)==259 and + len({r.get("id") for r in proofs})==259 and + len(producers)==21 and + len({r.get("artifact_id") for r in producers})==21, + "missing 259 exact theorem owners or 21 authentic producer entries") + proof_owners={k for row in proofs for k in row.get("case_ids",[])} + claim_owners={k for row in proofs for k in row.get("claim_ids",[])} + demand(proof_owners==original_ids and claim_owners==CLAIMS, + "canonical 45 original/11 claim theorem source coverage incomplete") + pp={k for row in plus for k in row.get("case_ids",[])} + pm={k for row in minus for k in row.get("case_ids",[])} + cp={k for row in plus for k in row.get("claim_ids",[])} + cm={k for row in minus for k in row.get("claim_ids",[])} + missing_cases=sorted(original_ids - (pp & pm)) + missing_claims=sorted(CLAIMS - (cp & cm)) + demand(not (pp|pm)-original_ids and not (cp|cm)-CLAIMS, + "foreign case/claim controls were inserted") + source_ready = ( + isinstance(verified_roles, dict) and + verified_roles.get("all_original_roles_and_claim_sources_complete") is True + and verified_roles.get("independent_review") is False + and verified_roles.get("mathematical_release_approved") is False) + return { + "format":"Permansson.PR113.ReadinessGapAnalysis.v1", + "source_proofs":259,"producer_shards":21, + "cases_with_positive_negative_controls":len(original_ids)-len(missing_cases), + "claims_with_positive_negative_controls":len(CLAIMS)-len(missing_claims), + "missing_original_case_controls":missing_cases, + "missing_new_claim_controls":missing_claims, + # Owner labels alone NEVER establish a validated source/test receipt. + "label_coverage_only":not missing_cases and not missing_claims, + "candidate_controls_complete":bool(source_ready and + not missing_cases and not missing_claims), + "validated_original_case_control_count":45 if source_ready else 0, + "validated_new_claim_control_count":11 if source_ready else 0, + "producer_source_and_role_validation":"PASSED" if source_ready else "NOT_VALIDATED", + "validated_role_summary":verified_roles, + "independent_case_reviews":0, + "independent_claim_reviews":0, + "merge_authorized":False,"mathematical_release_approved":False, + } + +def inspect(head,synthetic,source_receipts,source_zip): + demand(git("rev-parse","HEAD")==head and + git("cat-file","-t",BASE)=="commit" and + (live("/branches/main").get("commit") or {}).get("sha")==BASE and + git("show","-s","--format=%P",synthetic).split()==[BASE,head] and + git("rev-parse",synthetic+"^{tree}")== + git("rev-parse",head+"^{tree}"), + "B/H/S identities do not bind exact frozen source tree") + receipts=json.loads(Path(source_receipts).read_bytes()) + validate_overlay(receipts) + demand(receipts.get("head_sha")==head and + receipts.get("phase")=="premerge_review" and + receipts.get("merged_main_sha") is None and + not receipts.get("case_decisions") and + not receipts.get("claim_decisions"), + "premerge readiness cannot contain fake M or self-awarded reviews") + checks=authenticated_checks(head) + demand(set(checks)>=set(REQUIRED) and + all(any(row.get("name")==name and + row.get("head_sha")==head and + row.get("id")==checks[name]["id"] + for row in receipts["source_bound_ci"]) + for name in REQUIRED), + "required exact-H CI incomplete, stale or forged") + members=load_detached_bundle( + source_zip,receipts["evidence_bundle_sha256"]) + producer=authenticate_v3_sources(receipts,head,members) + demand(producer.get("producer_count")==21, + "complete original ZIP provenance unavailable") + original=json.loads((ROOT/"research/paper_closures/post_merge_112/" + "acceptance_decisions.json").read_bytes()) + ids={row["case_id"] for row in original["cases"]} + original_map=json.loads( + (ROOT/"research/paper_closures/post_merge_112/" + "acceptance_case_source_map.json").read_bytes()) + claim_map=json.loads( + (ROOT/"research/paper_closures/post_merge_112/" + "claim_declaration_map.json").read_bytes()) + validated_roles=revalidate_source_receipts( + receipts,head,members,checks,producer,original_map,claim_map) + report=candidate_coverage(receipts,ids,verified_roles=validated_roles) + demand(report["candidate_controls_complete"], + "unreviewed source candidate lacks genuinely compiled role-correct "+ + "case/claim controls: "+json.dumps({ + "cases":report["missing_original_case_controls"], + "claims":report["missing_new_claim_controls"]},sort_keys=True)) + return { + **report,"source_head":head,"synthetic_sha":synthetic, + "status":"COMPLETE_SOURCE_EVIDENCE_UNREVIEWED_NOT_ACCEPTED", + "exact_head_required_ci":12, + "actual_github_producer_shards":21, + "merge_authorized":False,"scholarly_review_approved":False, + } + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--head",required=True) + p.add_argument("--synthetic",required=True) + p.add_argument("--receipts",type=Path,required=True) + p.add_argument("--bundle",type=Path,required=True) + p.add_argument("--out",type=Path,required=True) + a=p.parse_args(argv) + try: + out=Path(a.out) + demand(not out.exists() and not out.is_symlink() and + not out.resolve().is_relative_to(ROOT.resolve()), + "readiness output must be new and detached") + verdict=inspect(a.head,a.synthetic,a.receipts,a.bundle) + out.parent.mkdir(parents=True,exist_ok=True) + out.write_text(json.dumps(verdict,sort_keys=True,indent=2)+"\n") + print(json.dumps(verdict,sort_keys=True)) + return 0 + except (ReadinessError,EvidenceError,OSError,KeyError,ValueError) as exc: + print("PR113 EXACT-H READINESS BLOCKED:",exc,file=sys.stderr) + return 2 +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_full_v31_source_join.py b/verification/research/pr113_full_v31_source_join.py new file mode 100644 index 00000000..6bca65d6 --- /dev/null +++ b/verification/research/pr113_full_v31_source_join.py @@ -0,0 +1,169 @@ +"""Full PR113 v3+v3.1 exact-H source execution join (NOT mathematical approval). + +The only input to this join is the result of two independent *live* +authentication passes: the mandatory original 21 producer ZIPs/259 proof +types/45+11 semantic owner routes and all 76 additional original GitHub +producer owners with actual positive/negative source and compiler log bytes. +Neither an unverified catalog JSON nor 12 green checks alone satisfies it. + +Real security/mathematical review, actual merged main M and publication are +separate downstream gates. This program never creates approvals or a merge. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + +from pr113_final_source_readiness import ( + inspect as original_inspect, ReadinessError, ROOT, +) +from final_closure_extra_producers_v31 import ( + assemble_review_pending, ControlProducerError, +) + + +class CombinedEvidenceError(ValueError): + pass + + +def require(ok, reason): + if not ok: + raise CombinedEvidenceError(reason) + + +def join_authenticated_evidence(original, additional): + """Join verified *execution* records, not author-asserted acceptance flags.""" + require(isinstance(original, dict) and + original.get("status") == + "COMPLETE_SOURCE_EVIDENCE_UNREVIEWED_NOT_ACCEPTED" and + original.get("candidate_controls_complete") is True and + original.get("source_proofs") == 259 and + original.get("producer_shards") == 21 and + original.get("exact_head_required_ci") == 12 and + original.get("actual_github_producer_shards") == 21 and + original.get("cases_with_positive_negative_controls") == 45 and + original.get("claims_with_positive_negative_controls") == 11 and + original.get("independent_case_reviews") == 0 and + original.get("independent_claim_reviews") == 0, + "original v3 mandatory real source evidence is incomplete") + head = original["source_head"] + require(isinstance(additional, dict) and + additional.get("format") == + "Permansson.PR113.ControlCIAuthenticationCandidate.v3.1" and + additional.get("status") == + "ALL_ADDITIONAL_CONTROLS_SOURCE_ATTRIBUTED_UNREVIEWED" and + additional.get("head_sha") == head and + additional.get("original_21_producing_artifacts_still_required") == 21 and + additional.get("original_case_positives") == 45 and + additional.get("legacy_case_negatives") == 20 and + additional.get("claim_positive_negative_pairs") == 11 and + additional.get("accepted_cases") == 0 and + additional.get("accepted_claims") == 0 and + additional.get("schema_security_approved") is False and + additional.get("independent_mathematical_review") is False and + additional.get("full_release_approved") is False, + "new controls are not authenticated unreviewed exact-H source jobs") + rows = additional.get("source_owned_controls") + checks = additional.get("actual_execution_member_validation") + require(isinstance(rows, list) and len(rows) == 76 and + all(isinstance(x, dict) for x in rows) and + len({(x.get("kind"), x.get("owner_id")) for x in rows}) == 76 and + isinstance(checks, dict) and + checks.get("format") == + "Permansson.PR113.GenuineSourceExecutionBytes.v1" and + checks.get("status") == + "ACTUAL_CONTROL_BYTES_REVALIDATED_UNREVIEWED" and + checks.get("source_head") == head and + checks.get("source_owner_count") == 76 and + checks.get("canonical_routes_bound") is True and + checks.get("legacy_original_captures_bound") is True and + checks.get("claim_positive_negative_directions") == 22 and + checks.get("execution_log_members_checked", 0) >= 76 and + checks.get("independent_review") is False and + checks.get("mathematical_acceptance") is False and + checks.get("publication_approved") is False, + "true and false source-control execution members not revalidated") + from collections import Counter + kinds = Counter(row["kind"] for row in rows) + require(kinds == {"original_positive": 45, "legacy_negative": 20, + "claim_pair": 11} and + {r["owner_id"] for r in rows if r["kind"] == "claim_pair"} == + {"Q1", "Q2", "Q3", "M1", "M2", "M3", "N1", "N2", "N3", + "D8", "D9"}, + "source owner classes cannot be substituted") + return { + "format": "Permansson.PR113.FullExecutionEvidenceJoin.v3.1", + "status": "ALL_SOURCE_EXECUTION_AUTHENTICATED_UNREVIEWED", + "head_sha": head, + "synthetic_sha": original["synthetic_sha"], + "original_ci_checks": 12, + "original_genuine_producer_zips": 21, + "kernel_theorem_receipts": 259, + "case_positive_negative_controls": 45, + "claim_positive_negative_pairs": 11, + "additional_gh_source_owner_records": 76, + "additional_log_members_verified": + checks["execution_log_members_checked"], + "independent_review": False, + "security_approval": False, + "merge_authorized": False, + "mathematical_release_approved": False, + "publication_authorized": False, + } + + +def execute(head, synthetic, original_json, original_zip, extra_contract, + extra_dir, workflow_blobs, additional_report, joined_report): + out = Path(joined_report).absolute() + v31 = Path(additional_report).absolute() + for p in (out, v31): + require(not p.exists() and not p.is_symlink() and + not any(x.is_symlink() for x in p.parents) and + not p.is_relative_to(ROOT.resolve()), + "joined source provenance must be fresh and outside Git source") + require(out != v31, "source run and join outputs cannot overwrite each other") + original = original_inspect(head, synthetic, original_json, original_zip) + additional = assemble_review_pending( + head, original_json, original_zip, extra_contract, extra_dir, + workflow_blobs, v31) + result = join_authenticated_evidence(original, additional) + out.parent.mkdir(parents=True, exist_ok=True) + with out.open("x", encoding="utf-8") as stream: + stream.write(json.dumps(result, sort_keys=True, indent=2) + "\n") + return result + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head", required=True) + parser.add_argument("--synthetic", required=True) + parser.add_argument("--original-receipts", type=Path, required=True) + parser.add_argument("--original-zip", type=Path, required=True) + parser.add_argument("--additional-contract", type=Path, required=True) + parser.add_argument("--additional-zips", type=Path, required=True) + parser.add_argument("--workflow-blob-candidates", type=Path, required=True) + parser.add_argument("--additional-report", type=Path, required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args(argv) + try: + result = execute( + args.head, args.synthetic, args.original_receipts, + args.original_zip, args.additional_contract, + args.additional_zips, args.workflow_blob_candidates, + args.additional_report, args.out) + print(json.dumps({"status": result["status"], + "head": result["head_sha"], + "owner_count": 76}, sort_keys=True)) + return 0 + except (CombinedEvidenceError, ReadinessError, + ControlProducerError, OSError, ValueError, + TypeError, KeyError) as exc: + print("PR113 COMPLETE SOURCE EXECUTION JOIN BLOCKED:", exc, + file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_historical_a2_baseline_v1.py b/verification/research/pr113_historical_a2_baseline_v1.py new file mode 100644 index 00000000..e06c6d73 --- /dev/null +++ b/verification/research/pr113_historical_a2_baseline_v1.py @@ -0,0 +1,145 @@ +"""Exact positive PR80 replay baseline; introduction is provenance, not success. + +The initial A2 commit is preserved as failed/pre-compilation history. Positive +replay uses PR80's corrected final head, with its own original executor bytes. +A past green run identifies the baseline; it never replaces a fresh execution, +independent review, or permission to merge/publish. +""" +from __future__ import annotations + +import hashlib +from pathlib import Path +import subprocess + +INTRODUCER = "f71a76032b169aac7bfe0694aa715339a94d4e05" +REPLAY_HEAD = "55f872abddfbbd8eaa996f3c95f91e15a5ea421a" +REPLAY_TREE = "00f199d7c3e275f76b39d85c9fb5cacd67f5282a" +MERGED_PR80 = "8f807660bcbea3f9d29129d1bb85645ce50c3249" +HISTORICAL_GREEN_RUN = 39166505301 +FORMAT = "Permansson.PR113.PositiveHistoricalA2Baseline.v1" +DUAL_FORMAT = "Permansson.PR113.DualA2CompilerCandidate.v4" +EXECUTORS = { + "verification/research/localized_prefix_gate.py": + "6f15dca74895e59a4d4bfcb54d38b471a1798be3", + "verification/research/localized_prefix_support.py": + "b07bc7d3b9ebe55b131466aebc28149fa2f03228", +} +ORIGINAL_TEST = "verification/research/test_localized_prefix_compiler.py" +ORIGINAL_TEST_BLOB = "c0f49038ea9b590720c78be85968eb63f5ac820f" +PROOF_PATHS = ( + "PermanssonResearch/Probability/GoodHistoryVisit.lean", + "PermanssonResearch/Probability/LocalizedFinitePrefix.lean", + "PermanssonResearch/ConstitutiveQuasi/LocalizedPrefixError.lean", + "PermanssonResearch/ConstitutiveQuasi/LocalizedPrefixExamples.lean", +) +PIN_PATHS = ("lean-toolchain", "lake-manifest.json", "lakefile.toml") + + +class HistoricalBaselineError(ValueError): + pass + + +def need(ok, message): + if not ok: + raise HistoricalBaselineError(message) + + +def git(root, *args): + try: + result = subprocess.run( + ["git", "-C", str(root), "-c", "core.hooksPath=/dev/null", *args], + capture_output=True, timeout=90, check=False) + except (OSError, subprocess.SubprocessError) as exc: + raise HistoricalBaselineError("historical baseline Git unavailable") from exc + need(result.returncode == 0, + "historical baseline Git failed: " + repr(args[:3]) + ": " + + result.stderr.decode("utf-8", "replace")[-1200:]) + return result.stdout + + +def text(root, *args): + return git(root, *args).decode("ascii").strip() + + +def blob(raw): + return hashlib.sha1(b"blob " + str(len(raw)).encode("ascii") + b"\0" + raw).hexdigest() + + +def verify_positive_source(root): + """Authenticate the complete pinned commit/tree and its execution surface.""" + original = Path(root) + need(not original.is_symlink() and + not any(p.is_symlink() for p in original.absolute().parents), + "historical baseline cannot traverse symlinks") + root = original.resolve(strict=True) + need(Path(text(root, "rev-parse", "--show-toplevel")).resolve() == root, + "historical baseline must be the real checkout root") + need(text(root, "rev-parse", "HEAD") == REPLAY_HEAD, + "positive A2 replay requires corrected PR80 head, not introduction") + need(text(root, "rev-parse", "HEAD^{tree}") == REPLAY_TREE, + "positive historical A2 tree differs from the pinned PR80 tree") + git(root, "merge-base", "--is-ancestor", INTRODUCER, REPLAY_HEAD) + need(not git(root, "status", "--porcelain=v1", "--untracked-files=all"), + "positive historical A2 source is dirty") + pins = {**EXECUTORS, ORIGINAL_TEST: ORIGINAL_TEST_BLOB} + paths = (*pins, *PIN_PATHS, *PROOF_PATHS, "PermanssonResearch.lean") + measured = {} + for path in paths: + entry = git(root, "ls-tree", "HEAD", "--", path).rstrip(b"\n") + need(b"\t" in entry, "historical source missing: " + path) + meta, member = entry.split(b"\t", 1) + fields = meta.decode("ascii").split() + need(len(fields) == 3 and fields[:2] == ["100644", "blob"] + and member.decode("utf-8") == path, + "historical source has unexpected type/mode: " + path) + raw = git(root, "show", "HEAD:" + path) + file = root / path + need(file.is_file() and not file.is_symlink() + and not any(p.is_symlink() for p in file.parents) + and file.read_bytes() == raw and blob(raw) == fields[2], + "historical source bytes differ from pinned Git: " + path) + need(path not in pins or fields[2] == pins[path], + "original historical executor/test changed: " + path) + measured[path] = fields[2] + return { + "format": FORMAT, "introduction_sha": INTRODUCER, + "positive_replay_sha": REPLAY_HEAD, "positive_replay_tree": REPLAY_TREE, + "source_blobs": measured, "fresh_compiler_execution": False, + "independent_security_review": False, "mathematical_acceptance": False, + } + + +def require_runtime_success(returncode, receipt): + """Expose compiler failures separately from later source-authentication checks.""" + need(type(receipt) is dict, "historical runtime receipt is not an object") + if type(returncode) is not int or returncode != 0 or receipt.get("status") != "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING": + commands = receipt.get("commands", []) + commands = commands if isinstance(commands, list) else [] + expected_negatives = { + "omit_baseline_reporting_error", "omit_treated_reporting_error", + "omit_zero_horizon_initial_error", "wrong_reported_effect", + } + failed = [c for c in commands if isinstance(c, dict) + and type(c.get("returncode")) is int and c["returncode"] != 0 + and c.get("label") not in expected_negatives] + last = failed[-1] if failed else {} + # Preserve the original raw receipt/logs. This is a bounded diagnostic, + # not a synthetic replacement receipt or an accepted semantic failure. + reason = str(receipt.get("reason", "missing successful historical compiler receipt"))[:6000] + raise HistoricalBaselineError( + "HISTORICAL_A2_COMPILER_FAILED: process_exit=" + str(returncode) + + "; command=" + str(last.get("label", "unknown")) + + "; command_exit=" + str(last.get("returncode", "unknown")) + + "; log=" + str(last.get("log", "unknown")) + "; " + reason) + + +def verify_receipt_source(receipt): + need(type(receipt) is dict and type(receipt.get("source")) is dict, + "historical compiler receipt source missing") + source = receipt["source"] + need(receipt.get("format") == "Permansson.A2P02.CompilerReceipt.v2" + and receipt.get("status") == "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING" + and source.get("git_head") == REPLAY_HEAD + and source.get("git_head_tree") == REPLAY_TREE + and receipt.get("source_unchanged") is True, + "HISTORICAL_A2_SOURCE_MISMATCH: corrected PR80 head/tree not authenticated") diff --git a/verification/research/pr113_historical_a2_replay_v1.py b/verification/research/pr113_historical_a2_replay_v1.py new file mode 100644 index 00000000..39ec486f --- /dev/null +++ b/verification/research/pr113_historical_a2_replay_v1.py @@ -0,0 +1,228 @@ +"""Replay original A2 in its own immutable checkout, not against current H. + +This is mandatory engineering evidence, not migration/security approval. +The same acceptance method also compiles current source with its native +successor, preserving P-02 coverage. The standalone current-source test is +retained. No workflow, historical executor, proof or review is replaced. +""" +from __future__ import annotations + +from pathlib import Path +import subprocess +import tempfile + +import pr113_historical_a2_baseline_v1 as baseline + +# The introducer is an immutable provenance anchor, not the positive baseline. +HISTORICAL_A2 = baseline.REPLAY_HEAD +ORIGINAL_TEST = "verification/research/test_localized_prefix_compiler.py" +ORIGINAL_TEST_BLOB = "c0f49038ea9b590720c78be85968eb63f5ac820f" +# Explicit, revision-specific immutable Git blobs. The original gate script +# is unchanged, but the historical support source predates the later-reviewed +# current source. Never demand that two distinct Git revisions share its blob. +# EXECUTORS retains its existing name for current-source callers/tests. +EXECUTORS = { + "verification/research/localized_prefix_gate.py": + "6f15dca74895e59a4d4bfcb54d38b471a1798be3", + "verification/research/localized_prefix_support.py": + "93462e9546ba494e8384d75ce49ff9020f831216", +} +HISTORICAL_EXECUTORS = { + "verification/research/localized_prefix_gate.py": + "6f15dca74895e59a4d4bfcb54d38b471a1798be3", + "verification/research/localized_prefix_support.py": + "b07bc7d3b9ebe55b131466aebc28149fa2f03228", +} +PINS = ("lean-toolchain", "lake-manifest.json", "lakefile.toml") +FORMAT = "Permansson.PR113.HistoricalA2EngineeringReplay.v2" +STATUS = "HISTORICAL_AND_CURRENT_A2_ENGINEERING_PASSED" + + +class HistoricalReplayError(ValueError): + pass + + +def need(value, message): + if not value: + raise HistoricalReplayError(message) + + +def git(root, *args): + try: + process = subprocess.run( + ["git", "-C", str(root), "-c", "core.hooksPath=/dev/null", *args], + capture_output=True, timeout=90, check=False) + except (OSError, subprocess.SubprocessError) as exc: + raise HistoricalReplayError("historical Git operation unavailable") from exc + need(process.returncode == 0, + "historical Git operation failed: " + repr(args[:3]) + ": " + + process.stderr.decode("utf-8", "replace")[-1200:]) + return process.stdout + + +def scalar(root, *args): + return git(root, *args).decode("ascii").strip() + + +def unchanged(root, expected): + need(scalar(root, "rev-parse", "HEAD") == expected, + "source HEAD changed during historical replay") + need(not git(root, "status", "--porcelain=v1", "--untracked-files=all"), + "historical replay source contains tracked or untracked changes") + + +def verify_executors(current, old): + # This is an exact two-revision source contract, not an exception list. + # Both original and current sources must match their own reviewed Git blobs + # and their own filesystem bytes; an edited file on either leg fails closed. + paths = {"verification/research/localized_prefix_gate.py", + "verification/research/localized_prefix_support.py"} + need(set(EXECUTORS) == set(HISTORICAL_EXECUTORS) == paths, + "A2 historical/current executor inventory changed") + for scope, root, pins in (("historical", old, HISTORICAL_EXECUTORS), + ("current", current, EXECUTORS)): + for path, expected in pins.items(): + raw = git(root, "show", "HEAD:" + path) + need(scalar(root, "rev-parse", "HEAD:" + path) == expected, + scope + " A2 executor Git identity changed: " + path) + file = root / path + need(file.is_file() and not file.is_symlink() + and file.read_bytes() == raw, + scope + " A2 executable bytes differ from Git: " + path) + need(scalar(old, "rev-parse", "HEAD:" + ORIGINAL_TEST) == ORIGINAL_TEST_BLOB, + "original historical A2 integration source was not preserved") + + +def dependency_bridge(current, old): + """Reuse clean pinned dependency packages, NEVER current project oleans.""" + for name in PINS: + original = git(old, "show", "HEAD:" + name) + need(original == git(current, "show", "HEAD:" + name) + and (old / name).read_bytes() == original + and (current / name).read_bytes() == original, + "historical/current dependency source differs: " + name) + from localized_prefix_gate import check_dependencies + before = check_dependencies(current) + packages = current / ".lake/packages" + need(packages.is_dir() and not packages.is_symlink(), + "current pinned package directory missing or substituted") + need(not (old / ".lake/build").exists() + and not (old / ".lake/build").is_symlink(), + "historical project build objects must start absent") + link = old / ".lake/packages" + need(not link.exists() and not link.is_symlink(), + "historical dependency directory must start absent") + link.parent.mkdir(parents=True, exist_ok=True) + link.symlink_to(packages.resolve(), target_is_directory=True) + need(check_dependencies(old) == before, + "historical dependency bridge does not match original pins") + return before + + +def validate_output(output, source_root, head, prefix, receipt_format, status): + """Check real original receipts, artifact hashes and semantic diagnostics.""" + from localized_prefix_gate import DECLS, MUTATIONS + from pr113_dual_a2_compiler_v2 import validate_run + from pr113_semantic_receipt_v2 import validate_compiler_receipt_v2 + files = {} + for path in output.iterdir(): + need(path.is_file() and not path.is_symlink() + and path.stat().st_size <= 16 * 1024 * 1024, + "unbounded or nonregular historical compiler artifact") + files[prefix + "/" + path.name] = path.read_bytes() + try: + receipt, digest = validate_compiler_receipt_v2( + files, prefix, status, MUTATIONS) + validate_run(receipt, status, MUTATIONS, DECLS) + except (ValueError, KeyError, TypeError) as exc: + raise HistoricalReplayError(prefix + ": " + str(exc)) from exc + source = receipt.get("source", {}) + need(source.get("git_head") == head + and source.get("git_head_tree") == scalar(source_root, "rev-parse", "HEAD^{tree}") + and receipt.get("mode") == "focused" + and receipt.get("format") == receipt_format, + "historical receipt belongs to a different source or compiler mode") + return receipt, digest + + +def verify(repo, output, *, mode="focused"): + """Fresh original AND current compilation; any missing leg fails.""" + need(mode == "focused", "unsupported original A2 engineering replay mode") + current = Path(repo).resolve(strict=True) + destination = Path(output).absolute() + need(not destination.exists() and not destination.is_symlink() + and not any(p.is_symlink() for p in destination.parents), + "historical evidence output must be fresh and unsymlinked") + destination = destination.resolve() + need(not destination.is_relative_to(current) + and not current.is_relative_to(destination), + "historical evidence output overlaps current source") + need(Path(scalar(current, "rev-parse", "--show-toplevel")).resolve() == current, + "current replay source must be a real Git checkout root") + checked = scalar(current, "rev-parse", "HEAD") + unchanged(current, checked) + git(current, "merge-base", "--is-ancestor", HISTORICAL_A2, checked) + tree = scalar(current, "rev-parse", "HEAD^{tree}") + destination.mkdir(parents=True, exist_ok=False) + with tempfile.TemporaryDirectory(prefix="pr113-original-A2-") as temporary: + old = Path(temporary) / "checkout" + added = False + try: + git(current, "worktree", "add", "--detach", str(old), HISTORICAL_A2) + added = True + unchanged(old, HISTORICAL_A2) + baseline.verify_positive_source(old) + verify_executors(current, old) + packages = dependency_bridge(current, old) + from pr113_dual_a2_compiler_v2 import run_historical_verified + # Executes old/localized_prefix_gate.py in a NEW process. It + # cannot import the current compiler or reuse current .lake/build. + original_output = destination / "historical_original_A2" + run_historical_verified(old, HISTORICAL_A2, original_output, mode) + original, receipt_sha = validate_output( + original_output, old, HISTORICAL_A2, "historical_original_A2", + "Permansson.A2P02.CompilerReceipt.v2", + "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING") + unchanged(current, checked) + # P-02's registered method must still establish CURRENT-source + # compiler evidence; historical success alone cannot satisfy it. + from pr113_a2_compiler_route_v3 import select_compiler + selected = select_compiler(current) + need(selected.checked_sha == checked, + "current A2 compiler selected for a different checkout") + current_output = destination / "pr113_successor_A2" + selected.module.verify(current, current_output, mode=mode) + current_receipt, current_digest = validate_output( + current_output, current, checked, "pr113_successor_A2", + selected.receipt_format, "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING") + from localized_prefix_gate import check_dependencies + need(check_dependencies(current) == packages + and check_dependencies(old) == packages, + "dependency source changed during original A2 compilation") + unchanged(old, HISTORICAL_A2) + unchanged(current, checked) + need(scalar(current, "rev-parse", "HEAD^{tree}") == tree, + "current source tree changed during historical replay") + return { + "format": FORMAT, "status": STATUS, + "execution_scope": "HISTORICAL_A2_AND_CURRENT_SOURCE", + "historical_source_sha": HISTORICAL_A2, + "historical_introducer_sha": baseline.INTRODUCER, + "historical_tree_sha": baseline.REPLAY_TREE, + "current_checked_sha": checked, "current_tree_sha": tree, + "original_receipt_sha256": receipt_sha, + "original_compiler_receipt": original, + "current_compiler_receipt": current_receipt, + "current_receipt_sha256": current_digest, + "current_source_compiler_executed": True, + "pinned_dependency_packages": packages, + "historical_project_build_imported": False, + "historical_executor_modified": False, + "current_compiler_required_separately": True, + "independent_security_review": False, + "mathematical_acceptance": False, + "merge_authorized": False, + } + finally: + if added: + git(current, "worktree", "remove", "--force", str(old)) diff --git a/verification/research/pr113_historical_artifact_delivery.py b/verification/research/pr113_historical_artifact_delivery.py new file mode 100644 index 00000000..147290ed --- /dev/null +++ b/verification/research/pr113_historical_artifact_delivery.py @@ -0,0 +1,380 @@ +"""Read-only, exact-H delivery of authentic T/T2 and A2 GitHub ZIPs. + +This is a transportation component, NOT a reviewer decision. The actual +required-check job supplies the true PR event identity. A separately +authorized security review ID is a runtime input, never committed source. + +The independent consumer *again* runs infrastructure_acceptance's exact +run/job/check/artifact and source/semantic verifiers. This module cannot mark +a failed original producer, changed historical source or unreviewed execution +as approved. It never runs a remote workflow or modifies GitHub. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import sys +import urllib.parse +import urllib.request + +from final_closure_authenticate import live as real_live +from infrastructure_acceptance import ( + FORMAT as EVIDENCE_FORMAT, EXPECTED_ARTIFACTS, ACTIVE_WORKFLOW, + authenticate_original_zip, inspect_zip, inspect_T, inspect_A2, + verify_A2_dependency_bridge, +) +from release_target import ( + B113, REPOSITORY, BINDING_FORMAT, SHA as SHA40, +) +from pr113_required_dual_gate import actual_ci_event + +ROOT = Path(__file__).resolve().parents[2] +FORMAT = "Permansson.PR113.ExactHistoricalArtifactDelivery.v1" +MAX_ARTIFACT = 256 * 1024 * 1024 +MAX_PAGE = 100 +MAX_DOWNLOAD_REDIRECTS = 5 + + +class DeliveryError(ValueError): + pass + + +def need(ok, msg): + if not ok: + raise DeliveryError(msg) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def approved_https_url(url): + """Permit only HTTPS GitHub API and signed GitHub artifact CDN endpoints.""" + try: + parts = urllib.parse.urlsplit(url) + host = (parts.hostname or "").lower() + return (parts.scheme == "https" and parts.port in (None, 443) + and parts.username is None and parts.password is None + and (host == "api.github.com" + or host.endswith(".githubusercontent.com") + or host.endswith(".blob.core.windows.net"))) + except ValueError: + return False + + +class BoundedGithubRedirect(urllib.request.HTTPRedirectHandler): + def __init__(self): + self.hops = 0 + + def redirect_request(self, req, fp, code, msg, headers, newurl): + self.hops += 1 + if self.hops > MAX_DOWNLOAD_REDIRECTS or not approved_https_url(newurl): + raise DeliveryError("historical artifact redirect left the approved HTTPS source") + child = super().redirect_request(req, fp, code, msg, headers, newurl) + need(child is not None, "historical artifact redirect unavailable") + # Never forward the GitHub Actions bearer token to the signed CDN. + if urllib.parse.urlsplit(newurl).hostname != "api.github.com": + for k in list(child.headers): + if k.lower() == "authorization": + del child.headers[k] + for k in list(child.unredirected_hdrs): + if k.lower() == "authorization": + del child.unredirected_hdrs[k] + return child + + +def github_artifact_bytes(artifact_id, *, token=None): + """Download bounded original GitHub-created ZIP bytes without extraction.""" + need(type(artifact_id) is int and artifact_id > 0, + "unsigned GitHub artifact ID") + if token is None: + token = os.environ.get("GITHUB_TOKEN") + need(isinstance(token, str) and bool(token), + "read-only GitHub Actions token required for original artifact") + url = ("https://api.github.com/repos/" + REPOSITORY + + "/actions/artifacts/" + str(artifact_id) + "/zip") + need(approved_https_url(url), "GitHub artifact source not trusted") + request = urllib.request.Request(url, headers={ + "Accept": "application/vnd.github+json", + "User-Agent": "Permansson-PR113-Historical-ReadOnly/1", + "Authorization": "Bearer " + token, + }) + try: + client = urllib.request.build_opener(BoundedGithubRedirect()) + with client.open(request, timeout=55) as resp: + size = resp.headers.get("Content-Length") + if size is not None: + need(int(size) <= MAX_ARTIFACT, + "GitHub artifact exceeds original ZIP budget") + data = resp.read(MAX_ARTIFACT + 1) + except (OSError, ValueError, urllib.error.URLError) as exc: + raise DeliveryError("authentic GitHub artifact download unavailable") from exc + need(0 < len(data) <= MAX_ARTIFACT and data.startswith(b"PK"), + "original GitHub ZIP empty, unbounded or replaced") + return data + + +def unique_job_and_artifact(run, jobs_data, artifacts_data, *, kind, head): + need(kind in EXPECTED_ARTIFACTS, "unknown historical producing leg") + job_name, artifact_name = EXPECTED_ARTIFACTS[kind] + jobs = jobs_data.get("jobs") if isinstance(jobs_data, dict) else None + artifacts = (artifacts_data.get("artifacts") + if isinstance(artifacts_data, dict) else None) + need(isinstance(jobs, list) and len(jobs) < MAX_PAGE and + isinstance(artifacts, list) and len(artifacts) < MAX_PAGE, + "historical producer job/artifact inventory truncated or malformed") + need(isinstance(run, dict) and run.get("head_sha") == head and + run.get("head_branch") == "research/final-mathematical-closure" and + run.get("event") == "push" and + run.get("status") == "completed" and + run.get("conclusion") == "success" and + str(run.get("path", "")).endswith(ACTIVE_WORKFLOW) and + type(run.get("run_attempt")) is int and run["run_attempt"] > 0, + "historical producing run not successful exact-H source") + j = [x for x in jobs if isinstance(x, dict) and x.get("name") == job_name] + a = [x for x in artifacts if isinstance(x, dict) + and x.get("name") == artifact_name] + need(len(j) == 1 and len(a) == 1, + "missing or ambiguous historical original job/artifact") + job, artifact = j[0], a[0] + need(job.get("run_id") == run.get("id") and + job.get("run_attempt") == run["run_attempt"] and + job.get("head_sha") == head and + job.get("status") == "completed" and + job.get("conclusion") == "success" and + type(job.get("id")) is int and job["id"] > 0 and + type(artifact.get("id")) is int and artifact["id"] > 0 and + artifact.get("expired") is False and + (artifact.get("workflow_run") or {}).get("id") == run.get("id") and + (artifact.get("workflow_run") or {}).get("head_sha") == head, + "historical original artifact did not originate from successful job") + url = job.get("check_run_url") + need(isinstance(url, str) and + re.fullmatch(r"https://api.github.com/repos/" + + re.escape(REPOSITORY) + r"/check-runs/[1-9][0-9]*", url), + "historical original check URL is not an authentic GitHub API check") + cid = int(url.rsplit("/", 1)[-1]) + return { + "run_id": run["id"], "run_attempt": run["run_attempt"], + "job_id": job["id"], "check_run_id": cid, + "artifact_id": artifact["id"], + } + + +def collect(head, run_id, *, live=real_live, download=github_artifact_bytes, + committed_manifest=None): + """Return two immutable archive bytes and a non-approved provenance record.""" + need(isinstance(head, str) and SHA40.fullmatch(head), + "actual PR source H required") + need(type(run_id) is int and run_id > 0, "original producer run ID required") + run = live("/actions/runs/" + str(run_id)) + jobs = live("/actions/runs/" + str(run_id) + "/jobs?per_page=100") + artifacts = live("/actions/runs/" + str(run_id) + "/artifacts?per_page=100") + selected, data, details = {}, {}, {} + for kind in ("T", "A2"): + source = unique_job_and_artifact( + run, jobs, artifacts, kind=kind, head=head) + raw = download(source["artifact_id"]) + # Re-execute the independent original provenance validator, which + # authenticates live check-run, timestamps, ZIP SHA and source H. + source_receipt = authenticate_original_zip( + kind, source, raw, head, live=live) + members = inspect_zip(raw) + if kind == "T": + scope = inspect_T(members, head) + else: + scope = inspect_A2(members, head) + need(type(committed_manifest) is bytes + and 0 < len(committed_manifest) <= 1024*1024, + "current-H committed Lake manifest required") + scope["dependency_bridge_sha256"] = verify_A2_dependency_bridge( + members, head, manifest_raw=committed_manifest) + selected[kind], data[kind], details[kind] = source, raw, { + "source": source_receipt, + "scope": scope, "archive_sha256": sha(raw), + } + need(selected["T"]["run_id"] == selected["A2"]["run_id"] + and selected["T"]["job_id"] != selected["A2"]["job_id"] + and selected["T"]["artifact_id"] != selected["A2"]["artifact_id"], + "two genuine historical legs must be distinct within one exact-H run") + return data, selected, details + + +def detached_target(context): + from release_target import candidate_bindings + need(isinstance(context, dict), "real PR context missing") + record = { + "format": BINDING_FORMAT, "phase": "infrastructure_premerge", + "repository": REPOSITORY, "pr_number": 113, + "branch": "research/final-mathematical-closure", + "base_sha": context["original_base_sha"], + "head_sha": context["head_sha"], + "synthetic_sha": context["synthetic_sha"], + "infrastructure_merge_sha": None, + } + candidate_bindings(record) + need(record["base_sha"] == B113, + "original immutable #113 base changed") + return record + + +def deliver(context, run_id, reviewer_id, *, git, + live=real_live, download=github_artifact_bytes): + need(type(reviewer_id) is int and reviewer_id > 0, + "real independent GitHub security review ID must be supplied") + need(git("rev-parse", "HEAD") == context["synthetic_sha"] and + git("cat-file", "-t", context["head_sha"]) == "commit" and + git("show", "-s", "--format=%P", context["synthetic_sha"]).split() == + [context["original_base_sha"], context["head_sha"]], + "GitHub synthetic merge does not use ordered exact B/H parents") + # Use the true H Git blob; never the potentially modified local worktree. + from infrastructure_acceptance import exact_committed_lake_blob + manifest=exact_committed_lake_blob(context["head_sha"],git=git) + raw, ids, verified = collect( + context["head_sha"],run_id,live=live,download=download, + committed_manifest=manifest) + envelope = { + "format": EVIDENCE_FORMAT, + "target": detached_target(context), + "source_bound_ci": [], # Strict 12-context aggregate is a LATER gate. + "historical_T": ids["T"], "historical_A2": ids["A2"], + "security_review_id": reviewer_id, + } + return { + "envelope": envelope, + "zips": raw, + "transport": { + "format": FORMAT, + "status": "REAL_ORIGINAL_ZIPS_AUTHENTICATED_REVIEW_PENDING", + "head": context["head_sha"], + "synthetic_sha": context["synthetic_sha"], + "source_run_id": run_id, + "originals": verified, + "independent_security_review": False, + "required_checks_successful": 0, + "mathematical_acceptance": False, + "owner_merge_authorized": False, + "publication_authorized": False, + }, + } + + +def deliver_merged(context, run_id, reviewer_id, *, git, + live=real_live, download=github_artifact_bytes): + """Source-bound postmerge transportation; cannot infer merge authorization.""" + from pr113_historical_postmerge import FORMAT as MERGED_FORMAT, source_target + need(type(reviewer_id) is int and reviewer_id > 0, + "genuine historical independent review ID is mandatory") + need(isinstance(context,dict) and + context.get("original_base_sha") == B113 and + context.get("merged_main_sha") == git("rev-parse","HEAD") and + git("show","-s","--format=%P", + context["merged_main_sha"]).split() == + [B113,context["reviewed_head_sha"]], + "postmerge historical delivery requires authentic B/H/M") + H=context["reviewed_head_sha"] + from infrastructure_acceptance import exact_committed_lake_blob + manifest=exact_committed_lake_blob(H,git=git) + raw,ids,verified=collect( + H,run_id,live=live,download=download, + committed_manifest=manifest) + envelope={ + "format":MERGED_FORMAT, + "target":source_target(context), + "source_bound_ci":[], + "historical_T":ids["T"],"historical_A2":ids["A2"], + "security_review_id":reviewer_id, + } + return { + "envelope":envelope, + "zips":raw, + "transport":{ + "format":FORMAT, + "status":"REAL_MERGED_SOURCE_ORIGINAL_ZIPS_RETRIEVED_REVIEW_PENDING", + "head":H, + "merged_main_sha":context["merged_main_sha"], + "source_run_id":run_id, + "originals":verified, + "independent_security_review":False, + "required_checks_successful":0, + "mathematical_acceptance":False, + "owner_merge_authorized":False, + "publication_authorized":False, + }, + } + + +def fresh_detached_path(path): + original = Path(path).absolute() + need(not original.exists() and not original.is_symlink() + and not original.is_relative_to(ROOT.resolve()) + and not any(x.is_symlink() for x in original.parents), + "historical delivery output must be new, outside source and unsymlinked") + return original + + +def source_git(*commands): + """Return scalar Git identities as text and committed file bytes verbatim. + + Never pass bytes as a SHA comparison, and never use .strip() on a + Git show REV:PATH payload. Both mistakes break authentic required checks. + """ + import subprocess + try: + raw=subprocess.check_output(["git",*commands],cwd=ROOT, + stderr=subprocess.DEVNULL,timeout=20) + except (OSError,subprocess.CalledProcessError, + subprocess.TimeoutExpired) as exc: + raise DeliveryError("genuine Git object unavailable") from exc + if (len(commands)>=2 and commands[0]=="show" + and ":" in commands[1]): + return raw + return raw.decode("utf-8").strip() + + +def cli(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--producer-run-id", required=True, type=int) + parser.add_argument("--security-review-id", required=True, type=int) + parser.add_argument("--out", required=True, type=Path) + args = parser.parse_args(argv) + try: + if os.environ.get("GITHUB_EVENT_NAME") == "pull_request": + context = actual_ci_event(git=source_git) + result = deliver(context,args.producer_run_id, + args.security_review_id,git=source_git) + elif os.environ.get("GITHUB_EVENT_NAME") == "push": + from pr113_historical_postmerge import exact_main_event + context = exact_main_event(git=source_git) + result = deliver_merged(context,args.producer_run_id, + args.security_review_id,git=source_git) + else: + raise DeliveryError("historical delivery requires a real main PR or push") + out = fresh_detached_path(args.out) + out.mkdir(parents=True,exist_ok=False) + for k, name in (("T","historical_T.zip"),("A2","historical_A2.zip")): + with (out/name).open("xb") as f: + f.write(result["zips"][k]) + for key, name in (("envelope","historical_envelope.json"), + ("transport","historical_transport_unreviewed.json")): + with (out/name).open("x",encoding="utf-8") as f: + json.dump(result[key],f,sort_keys=True,indent=2) + f.write("\n") + print(json.dumps({ + "status":result["transport"]["status"], + "head":result["transport"]["head"], + "original_zip_count":2, + "independent_review_authenticated":False, + },sort_keys=True)) + return 0 + except (DeliveryError,ValueError,OSError,KeyError,TypeError) as exc: + print("AUTHENTIC HISTORICAL DELIVERY BLOCKED:",exc,file=sys.stderr) + return 2 + + +if __name__=="__main__": + raise SystemExit(cli()) diff --git a/verification/research/pr113_historical_artifact_delivery_v2.py b/verification/research/pr113_historical_artifact_delivery_v2.py new file mode 100644 index 00000000..a34fab45 --- /dev/null +++ b/verification/research/pr113_historical_artifact_delivery_v2.py @@ -0,0 +1,380 @@ +"""Read-only, exact-H delivery of authentic T/T2 and A2 GitHub ZIPs. + +This is a transportation component, NOT a reviewer decision. The actual +required-check job supplies the true PR event identity. A separately +authorized security review ID is a runtime input, never committed source. + +The independent consumer *again* runs infrastructure_acceptance's exact +run/job/check/artifact and source/semantic verifiers. This module cannot mark +a failed original producer, changed historical source or unreviewed execution +as approved. It never runs a remote workflow or modifies GitHub. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import sys +import urllib.parse +import urllib.request + +from final_closure_authenticate import live as real_live +from infrastructure_acceptance_v2 import ( + FORMAT as EVIDENCE_FORMAT, EXPECTED_ARTIFACTS, ACTIVE_WORKFLOW, + authenticate_original_zip, inspect_zip, inspect_T, inspect_A2, + verify_A2_dependency_bridge, +) +from release_target import ( + B113, REPOSITORY, BINDING_FORMAT, SHA as SHA40, +) +from pr113_required_dual_gate_v2 import actual_ci_event +from pr113_historical_evidence_contract_v2 import validate_envelope + +ROOT = Path(__file__).resolve().parents[2] +FORMAT = "Permansson.PR113.ExactHistoricalArtifactDelivery.v2" +MAX_ARTIFACT = 256 * 1024 * 1024 +MAX_PAGE = 100 +MAX_DOWNLOAD_REDIRECTS = 5 + + +class DeliveryError(ValueError): + pass + + +def need(ok, msg): + if not ok: + raise DeliveryError(msg) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def approved_https_url(url): + """Permit only HTTPS GitHub API and signed GitHub artifact CDN endpoints.""" + try: + parts = urllib.parse.urlsplit(url) + host = (parts.hostname or "").lower() + return (parts.scheme == "https" and parts.port in (None, 443) + and parts.username is None and parts.password is None + and (host == "api.github.com" + or host.endswith(".githubusercontent.com") + or host.endswith(".blob.core.windows.net"))) + except ValueError: + return False + + +class BoundedGithubRedirect(urllib.request.HTTPRedirectHandler): + def __init__(self): + self.hops = 0 + + def redirect_request(self, req, fp, code, msg, headers, newurl): + self.hops += 1 + if self.hops > MAX_DOWNLOAD_REDIRECTS or not approved_https_url(newurl): + raise DeliveryError("historical artifact redirect left the approved HTTPS source") + child = super().redirect_request(req, fp, code, msg, headers, newurl) + need(child is not None, "historical artifact redirect unavailable") + # Never forward the GitHub Actions bearer token to the signed CDN. + if urllib.parse.urlsplit(newurl).hostname != "api.github.com": + for k in list(child.headers): + if k.lower() == "authorization": + del child.headers[k] + for k in list(child.unredirected_hdrs): + if k.lower() == "authorization": + del child.unredirected_hdrs[k] + return child + + +def github_artifact_bytes(artifact_id, *, token=None): + """Download bounded original GitHub-created ZIP bytes without extraction.""" + need(type(artifact_id) is int and artifact_id > 0, + "unsigned GitHub artifact ID") + if token is None: + token = os.environ.get("GITHUB_TOKEN") + need(isinstance(token, str) and bool(token), + "read-only GitHub Actions token required for original artifact") + url = ("https://api.github.com/repos/" + REPOSITORY + + "/actions/artifacts/" + str(artifact_id) + "/zip") + need(approved_https_url(url), "GitHub artifact source not trusted") + request = urllib.request.Request(url, headers={ + "Accept": "application/vnd.github+json", + "User-Agent": "Permansson-PR113-Historical-ReadOnly/1", + "Authorization": "Bearer " + token, + }) + try: + client = urllib.request.build_opener(BoundedGithubRedirect()) + with client.open(request, timeout=55) as resp: + size = resp.headers.get("Content-Length") + if size is not None: + need(int(size) <= MAX_ARTIFACT, + "GitHub artifact exceeds original ZIP budget") + data = resp.read(MAX_ARTIFACT + 1) + except (OSError, ValueError, urllib.error.URLError) as exc: + raise DeliveryError("authentic GitHub artifact download unavailable") from exc + need(0 < len(data) <= MAX_ARTIFACT and data.startswith(b"PK"), + "original GitHub ZIP empty, unbounded or replaced") + return data + + +def unique_job_and_artifact(run, jobs_data, artifacts_data, *, kind, head): + need(kind in EXPECTED_ARTIFACTS, "unknown historical producing leg") + job_name, artifact_name = EXPECTED_ARTIFACTS[kind] + jobs = jobs_data.get("jobs") if isinstance(jobs_data, dict) else None + artifacts = (artifacts_data.get("artifacts") + if isinstance(artifacts_data, dict) else None) + need(isinstance(jobs, list) and len(jobs) < MAX_PAGE and + isinstance(artifacts, list) and len(artifacts) < MAX_PAGE, + "historical producer job/artifact inventory truncated or malformed") + need(isinstance(run, dict) and run.get("head_sha") == head and + run.get("head_branch") == "research/final-mathematical-closure" and + run.get("event") == "push" and + run.get("status") == "completed" and + run.get("conclusion") == "success" and + str(run.get("path", "")).endswith(ACTIVE_WORKFLOW) and + type(run.get("run_attempt")) is int and run["run_attempt"] > 0, + "historical producing run not successful exact-H source") + j = [x for x in jobs if isinstance(x, dict) and x.get("name") == job_name] + a = [x for x in artifacts if isinstance(x, dict) + and x.get("name") == artifact_name] + need(len(j) == 1 and len(a) == 1, + "missing or ambiguous historical original job/artifact") + job, artifact = j[0], a[0] + need(job.get("run_id") == run.get("id") and + job.get("run_attempt") == run["run_attempt"] and + job.get("head_sha") == head and + job.get("status") == "completed" and + job.get("conclusion") == "success" and + type(job.get("id")) is int and job["id"] > 0 and + type(artifact.get("id")) is int and artifact["id"] > 0 and + artifact.get("expired") is False and + (artifact.get("workflow_run") or {}).get("id") == run.get("id") and + (artifact.get("workflow_run") or {}).get("head_sha") == head, + "historical original artifact did not originate from successful job") + url = job.get("check_run_url") + need(isinstance(url, str) and + re.fullmatch(r"https://api.github.com/repos/" + + re.escape(REPOSITORY) + r"/check-runs/[1-9][0-9]*", url), + "historical original check URL is not an authentic GitHub API check") + cid = int(url.rsplit("/", 1)[-1]) + return { + "run_id": run["id"], "run_attempt": run["run_attempt"], + "job_id": job["id"], "check_run_id": cid, + "artifact_id": artifact["id"], + } + + +def collect(head, run_id, *, live=real_live, download=github_artifact_bytes, + committed_manifest=None): + """Return two immutable archive bytes and a non-approved provenance record.""" + need(isinstance(head, str) and SHA40.fullmatch(head), + "actual PR source H required") + need(type(run_id) is int and run_id > 0, "original producer run ID required") + run = live("/actions/runs/" + str(run_id)) + jobs = live("/actions/runs/" + str(run_id) + "/jobs?per_page=100") + artifacts = live("/actions/runs/" + str(run_id) + "/artifacts?per_page=100") + selected, data, details = {}, {}, {} + for kind in ("T", "A2"): + source = unique_job_and_artifact( + run, jobs, artifacts, kind=kind, head=head) + raw = download(source["artifact_id"]) + # Re-execute the independent original provenance validator, which + # authenticates live check-run, timestamps, ZIP SHA and source H. + source_receipt = authenticate_original_zip( + kind, source, raw, head, live=live) + members = inspect_zip(raw) + if kind == "T": + scope = inspect_T(members, head) + else: + scope = inspect_A2(members, head) + need(type(committed_manifest) is bytes + and 0 < len(committed_manifest) <= 1024*1024, + "current-H committed Lake manifest required") + scope["dependency_bridge_sha256"] = verify_A2_dependency_bridge( + members, head, manifest_raw=committed_manifest) + selected[kind], data[kind], details[kind] = source, raw, { + "source": source_receipt, + "scope": scope, "archive_sha256": sha(raw), + } + need(selected["T"]["run_id"] == selected["A2"]["run_id"] + and selected["T"]["job_id"] != selected["A2"]["job_id"] + and selected["T"]["artifact_id"] != selected["A2"]["artifact_id"], + "two genuine historical legs must be distinct within one exact-H run") + return data, selected, details + + +def detached_target(context): + from release_target import candidate_bindings + need(isinstance(context, dict), "real PR context missing") + record = { + "format": BINDING_FORMAT, "phase": "infrastructure_premerge", + "repository": REPOSITORY, "pr_number": 113, + "branch": "research/final-mathematical-closure", + "base_sha": context["original_base_sha"], + "head_sha": context["head_sha"], + "synthetic_sha": context["synthetic_sha"], + "infrastructure_merge_sha": None, + } + candidate_bindings(record) + need(record["base_sha"] == B113, + "original immutable #113 base changed") + return record + + +def deliver(context, run_id, reviewer_id, *, git, + live=real_live, download=github_artifact_bytes): + need(type(reviewer_id) is int and reviewer_id > 0, + "real independent GitHub security review ID must be supplied") + need(git("rev-parse", "HEAD") == context["synthetic_sha"] and + git("cat-file", "-t", context["head_sha"]) == "commit" and + git("show", "-s", "--format=%P", context["synthetic_sha"]).split() == + [context["original_base_sha"], context["head_sha"]], + "GitHub synthetic merge does not use ordered exact B/H parents") + # Use the true H Git blob; never the potentially modified local worktree. + from infrastructure_acceptance_v2 import exact_committed_lake_blob + manifest=exact_committed_lake_blob(context["head_sha"],git=git) + raw, ids, verified = collect( + context["head_sha"],run_id,live=live,download=download, + committed_manifest=manifest) + envelope = { + "format": EVIDENCE_FORMAT, + "target": detached_target(context), + "source_bound_ci": [], # Strict 12-context aggregate is a LATER gate. + "historical_T": ids["T"], "historical_A2": ids["A2"], + "security_review_id": reviewer_id, + } + # The noncircular v2 source-only envelope legitimately has zero CI + # contexts. The later twelve-context aggregate uses the SAME schema. + validate_envelope(envelope) + return { + "envelope": envelope, + "zips": raw, + "transport": { + "format": FORMAT, + "status": "REAL_ORIGINAL_ZIPS_AUTHENTICATED_REVIEW_PENDING", + "head": context["head_sha"], + "synthetic_sha": context["synthetic_sha"], + "source_run_id": run_id, + "originals": verified, + "independent_security_review": False, + "required_checks_successful": 0, + "mathematical_acceptance": False, + "owner_merge_authorized": False, + "publication_authorized": False, + }, + } + + +def deliver_merged(context, run_id, reviewer_id, *, git, + live=real_live, download=github_artifact_bytes): + """Source-bound postmerge transportation; cannot infer merge authorization.""" + from pr113_historical_postmerge_v2 import FORMAT as MERGED_FORMAT, source_target + need(type(reviewer_id) is int and reviewer_id > 0, + "genuine historical independent review ID is mandatory") + need(isinstance(context,dict) and + context.get("original_base_sha") == B113 and + context.get("merged_main_sha") == git("rev-parse","HEAD") and + git("show","-s","--format=%P", + context["merged_main_sha"]).split() == + [B113,context["reviewed_head_sha"]], + "postmerge historical delivery requires authentic B/H/M") + H=context["reviewed_head_sha"] + from infrastructure_acceptance_v2 import exact_committed_lake_blob + manifest=exact_committed_lake_blob(H,git=git) + raw,ids,verified=collect( + H,run_id,live=live,download=download, + committed_manifest=manifest) + envelope={ + "format":MERGED_FORMAT, + "target":source_target(context), + "source_bound_ci":[], + "historical_T":ids["T"],"historical_A2":ids["A2"], + "security_review_id":reviewer_id, + } + return { + "envelope":envelope, + "zips":raw, + "transport":{ + "format":FORMAT, + "status":"REAL_MERGED_SOURCE_ORIGINAL_ZIPS_RETRIEVED_REVIEW_PENDING", + "head":H, + "merged_main_sha":context["merged_main_sha"], + "source_run_id":run_id, + "originals":verified, + "independent_security_review":False, + "required_checks_successful":0, + "mathematical_acceptance":False, + "owner_merge_authorized":False, + "publication_authorized":False, + }, + } + + +def fresh_detached_path(path): + from pr113_boundary_io import detached_destination + return detached_destination(path, ROOT) + + +def source_git(*commands): + """Return scalar Git identities as text and committed file bytes verbatim. + + Never pass bytes as a SHA comparison, and never use .strip() on a + Git show REV:PATH payload. Both mistakes break authentic required checks. + """ + import subprocess + try: + raw=subprocess.check_output(["git",*commands],cwd=ROOT, + stderr=subprocess.DEVNULL,timeout=20) + except (OSError,subprocess.CalledProcessError, + subprocess.TimeoutExpired) as exc: + raise DeliveryError("genuine Git object unavailable") from exc + if (len(commands)>=2 and commands[0]=="show" + and ":" in commands[1]): + return raw + return raw.decode("utf-8").strip() + + +def cli(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--producer-run-id", required=True, type=int) + parser.add_argument("--security-review-id", required=True, type=int) + parser.add_argument("--out", required=True, type=Path) + args = parser.parse_args(argv) + try: + if os.environ.get("GITHUB_EVENT_NAME") == "pull_request": + context = actual_ci_event(git=source_git) + result = deliver(context,args.producer_run_id, + args.security_review_id,git=source_git) + elif os.environ.get("GITHUB_EVENT_NAME") == "push": + from pr113_historical_postmerge_v2 import exact_main_event + context = exact_main_event(git=source_git) + result = deliver_merged(context,args.producer_run_id, + args.security_review_id,git=source_git) + else: + raise DeliveryError("historical delivery requires a real main PR or push") + out = fresh_detached_path(args.out) + out.mkdir(parents=True,exist_ok=False) + for k, name in (("T","historical_T.zip"),("A2","historical_A2.zip")): + with (out/name).open("xb") as f: + f.write(result["zips"][k]) + for key, name in (("envelope","historical_envelope.json"), + ("transport","historical_transport_unreviewed.json")): + with (out/name).open("x",encoding="utf-8") as f: + json.dump(result[key],f,sort_keys=True,indent=2) + f.write("\n") + print(json.dumps({ + "status":result["transport"]["status"], + "head":result["transport"]["head"], + "original_zip_count":2, + "independent_review_authenticated":False, + },sort_keys=True)) + return 0 + except (DeliveryError,ValueError,OSError,KeyError,TypeError) as exc: + print("AUTHENTIC HISTORICAL DELIVERY BLOCKED:",exc,file=sys.stderr) + return 2 + + +if __name__=="__main__": + raise SystemExit(cli()) diff --git a/verification/research/pr113_historical_evidence_contract_v2.py b/verification/research/pr113_historical_evidence_contract_v2.py new file mode 100644 index 00000000..1011b138 --- /dev/null +++ b/verification/research/pr113_historical_evidence_contract_v2.py @@ -0,0 +1,152 @@ +"""Strict nonauthorizing PR113 v2 historical T/T2 and A2 envelope contract. + +V1 required a completed 12-check set; a v2 historical-only producer must +instead bind its TWO real source artifacts without circularly requiring +the very Research/Archive jobs that consume them. The later aggregate +requires the exact 12 source-bound CI contexts. + +Structure and source intent here are not authentication: consumers must +still verify live GitHub jobs/artifacts, true Git objects, and independent +review through the existing gates. +""" +from __future__ import annotations + +import json + +from final_closure_authenticate import REQUIRED +from infrastructure_acceptance import InfrastructureError +from release_target import candidate_bindings + +FORMAT = "Permansson.PR113.InfrastructureEvidence.v2" +FIELDS = frozenset({ + "format", "target", "source_bound_ci", "historical_T", + "historical_A2", "security_review_id", +}) +ARTIFACT_FIELDS = frozenset({ + "run_id", "run_attempt", "job_id", "check_run_id", "artifact_id", +}) +CHECK_FIELDS = frozenset({"name", "id", "head_sha"}) +MAX_BYTES = 64 * 1024 + + +class InfrastructureEnvelopeError(InfrastructureError): + pass + + +def need(value, message): + if not value: + raise InfrastructureEnvelopeError(message) + + +def strictly_positive(value): + """bool is a subclass of int in Python, but never an API ID.""" + return type(value) is int and value > 0 + + +def validate_envelope(payload, *, require_all_checks=False): + """Validate external v2 structure; leave every approval decision false. + + A historical-only check may read an empty CI list or a complete + aggregate without treating the list as authenticated. Only the real + aggregate gate authenticates all twelve live checks, and it invokes + this validator with require_all_checks=True. + """ + need(isinstance(payload, dict) and set(payload) == FIELDS + and payload.get("format") == FORMAT, + "unknown/malformed versioned historical evidence envelope") + target = payload.get("target") + try: + candidate_bindings(target) + except (TypeError, ValueError) as exc: + raise InfrastructureEnvelopeError( + "wrong or unbound historical evidence source target") from exc + need(target["phase"] == "infrastructure_premerge", + "certification successor cannot borrow PR113 historical evidence") + need(strictly_positive(payload.get("security_review_id")), + "historical evidence requires genuine review ID placeholder, not approval") + records = {} + for name in ("historical_T", "historical_A2"): + rec = payload.get(name) + need(isinstance(rec, dict) and set(rec) == ARTIFACT_FIELDS + and all(strictly_positive(rec.get(key)) + for key in ARTIFACT_FIELDS), + "invalid v2 historical run/job/check/artifact identity: " + name) + records[name] = rec + t, a = records["historical_T"], records["historical_A2"] + need(t["run_id"] == a["run_id"] + and t["run_attempt"] == a["run_attempt"] + and t["job_id"] != a["job_id"] + and t["check_run_id"] != a["check_run_id"] + and t["artifact_id"] != a["artifact_id"], + "historical T and A2 must be distinct jobs/artifacts in the same run attempt") + + checks = payload.get("source_bound_ci") + need(isinstance(checks, list) + and len(checks) in (0, len(REQUIRED)), + "historical input must have zero or all twelve declared CI contexts") + if require_all_checks: + need(len(checks) == len(REQUIRED), + "aggregate infrastructure report needs twelve CI contexts") + if checks: + names, ids = [], [] + for row in checks: + need(isinstance(row, dict) and set(row) == CHECK_FIELDS + and isinstance(row.get("name"), str) + and row["name"] in REQUIRED + and strictly_positive(row.get("id")) + and row.get("head_sha") == target["head_sha"], + "foreign, malformed or stale historical CI identity") + names.append(row["name"]) + ids.append(row["id"]) + need(len(names) == len(set(names)) + and set(names) == REQUIRED + and len(ids) == len(set(ids)), + "missing/repeated source CI context or borrowed check-run ID") + return payload + + +def decode_unique_json(raw, *, max_bytes=MAX_BYTES): + """Bound all original GitHub event / evidence JSON without last-key-wins.""" + need(type(max_bytes) is int and 0 < max_bytes <= 8 * 1024 * 1024 + and type(raw) is bytes and 0 < len(raw) <= max_bytes, + "historical JSON bytes missing or unbounded") + + def no_duplicate_keys(pairs): + result = {} + for key, value in pairs: + if key in result: + raise InfrastructureEnvelopeError( + "duplicate historical evidence JSON key: " + key) + result[key] = value + return result + + def reject_nonfinite_constant(value): + raise InfrastructureEnvelopeError( + "nonfinite historical evidence JSON number: " + value) + + def finite_float(value): + import math + number = float(value) + if not math.isfinite(number): + raise InfrastructureEnvelopeError( + "nonfinite historical evidence JSON float") + return number + + try: + value = json.loads(raw, object_pairs_hook=no_duplicate_keys, + parse_constant=reject_nonfinite_constant, + parse_float=finite_float) + except InfrastructureEnvelopeError: + raise + except (UnicodeError, ValueError, TypeError) as exc: + raise InfrastructureEnvelopeError( + "malformed/ambiguous historical evidence JSON") from exc + need(isinstance(value, dict), + "historical JSON must be an object, not a scalar or array") + return value + + +def parse_envelope(raw, *, require_all_checks=False): + """Decode and validate the v2 PR premerge historical evidence contract.""" + return validate_envelope( + decode_unique_json(raw), require_all_checks=require_all_checks) diff --git a/verification/research/pr113_historical_inheritance_v3.py b/verification/research/pr113_historical_inheritance_v3.py new file mode 100644 index 00000000..1024af6b --- /dev/null +++ b/verification/research/pr113_historical_inheritance_v3.py @@ -0,0 +1,211 @@ +"""Independently reverify the immutable approved PR113 historical gate on descendants. + +Run original historical T/T2/A2 *consumer source* from the authentic M113 +worktree, never by replaying a forged PR113 event in a successor required job. +This does not certify any new source, mathematics, merge or publication. +""" +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile + +from pr113_boundary_io import detached_destination, strict_json_object +from pr113_lifecycle_source_v3 import actual_source + +ROOT = Path(__file__).resolve().parents[2] +FORMAT = "Permansson.PR113.InheritedHistoricalInfrastructure.v3" + +# This child is executed inside the independently authenticated immutable M113 +# tree. Its imports resolve to M113 source, not to the successor PR source. +# No GITHUB_EVENT_NAME, GITHUB_SHA or event JSON is forged or rewritten. +CHILD = r""" +import json,sys +from pr113_historical_artifact_delivery_v2 import collect +from pr113_historical_postmerge_v2 import ( + check_historical_merged, source_target, FORMAT as MERGED_FORMAT, +) +from infrastructure_acceptance_v2 import exact_committed_lake_blob +from release_target import B113 +from final_closure_authenticate import REQUIRED, authenticated_checks +H,M,run_id,reviewer_id=sys.argv[1:] +run_id=int(run_id) +reviewer_id=int(reviewer_id) +if run_id <= 0 or reviewer_id <= 0: + raise ValueError("historical source run/review IDs must be positive") +context={"original_base_sha":B113,"reviewed_head_sha":H, + "merged_main_sha":M} +bytes_by_kind,ids,verified=collect( + H,run_id,committed_manifest=exact_committed_lake_blob(H)) +envelope={ + "format":MERGED_FORMAT,"target":source_target(context), + "source_bound_ci":[],"historical_T":ids["T"], + "historical_A2":ids["A2"],"security_review_id":reviewer_id, +} +receipt=check_historical_merged( + envelope,bytes_by_kind["T"],bytes_by_kind["A2"], + event_context=context) +required=authenticated_checks(H) +# Assertions can disappear with python -O or PYTHONOPTIMIZE. Use explicit +# checks in every inherited historical source/review authentication path. +if (len(REQUIRED) != 12 or not isinstance(required,dict) + or not set(REQUIRED) <= set(required)): + raise ValueError("historical source lacks all twelve live required checks") +for name in REQUIRED: + check=required[name] + if (not isinstance(check,dict) + or check.get("head_sha") != H + or check.get("status") != "completed" + or check.get("conclusion") != "success" + or type(check.get("id")) is not int + or check["id"] <= 0): + raise ValueError("unbound or unsuccessful historical check: " + name) +if receipt.get("status") != ( + "MERGED_HISTORICAL_T_AND_A2_REVERIFIED_UNDER_REVIEWED_SOURCE"): + raise ValueError("original M113 historical replay has not succeeded") +if receipt.get("independent_security_review_authenticated") is not True: + raise ValueError("original M113 security review is not authenticated") +if type(receipt.get("required_ci_successful")) is not int or ( + receipt["required_ci_successful"] != 0): + raise ValueError("historical-only result cannot assert current CI approval") +print(json.dumps({ + "source_head_sha":H,"merged_main_sha":M, + "historical_original_producer_run_id":run_id, + "security_review_id":reviewer_id, + "historical_status":receipt["status"], + "historical_original_workflow_count":25, + "current_reviewed_workflow_count":30, + "original_required_context_count":len(REQUIRED), + "historical_security_review_authenticated":True, + "historical_T_and_A2_genuine":True, + "new_mathematical_acceptance":False, + "publication_authorized":False, +},sort_keys=True)) +""" + + +class InheritanceError(ValueError): + pass + + +def demand(ok, why): + if not ok: + raise InheritanceError(why) + + +def verify_child_report(result, lifecycle, *, run_id, reviewer_id): + demand(isinstance(result, dict) + and result.get("source_head_sha") == lifecycle["infrastructure_head_sha"] + and result.get("merged_main_sha") == lifecycle["infrastructure_merge_sha"] + and type(result.get("historical_original_producer_run_id")) is int + and result["historical_original_producer_run_id"] == run_id + and type(result.get("security_review_id")) is int + and result["security_review_id"] == reviewer_id + and result.get("historical_status") == + "MERGED_HISTORICAL_T_AND_A2_REVERIFIED_UNDER_REVIEWED_SOURCE" + and result.get("original_required_context_count") == 12 + and result.get("historical_original_workflow_count") == 25 + and result.get("current_reviewed_workflow_count") == 30 + and result.get("historical_security_review_authenticated") is True + and result.get("historical_T_and_A2_genuine") is True + and result.get("new_mathematical_acceptance") is False + and result.get("publication_authorized") is False, + "inherited historical source validation is incomplete") + return { + "format": FORMAT, + "status": "PR113_HISTORICAL_SOURCE_REVERIFIED_FOR_AUTHENTIC_DESCENDANT", + "current_source_sha": lifecycle["source_commit_sha"], + "current_phase": lifecycle["phase"], + "current_github_run_id": lifecycle["github_run_id"], + "current_github_run_attempt": lifecycle["github_run_attempt"], + "historical_source_head_sha": result["source_head_sha"], + "historical_merged_main_sha": result["merged_main_sha"], + "historical_original_producer_run_id": run_id, + "historical_security_review_id": reviewer_id, + "original_required_context_count": 12, + "current_required_ci_accepted": False, + "new_mathematical_acceptance": False, + "owner_merge_authorized": False, + "publication_authorized": False, + } + + +def verify(producer_run_id, security_review_id, *, lifecycle=None): + demand(type(producer_run_id) is int and producer_run_id > 0 + and type(security_review_id) is int and security_review_id > 0, + "genuine original run/review IDs required") + lifecycle = actual_source() if lifecycle is None else lifecycle + demand(isinstance(lifecycle, dict) and + lifecycle.get("phase") in ("main_descendant_pr", + "main_descendant_push") + and lifecycle.get("infrastructure_merge_sha") + and lifecycle.get("infrastructure_head_sha"), + "historical inheritance cannot replace original PR113 T/A2 required gate") + token = os.environ.get("GITHUB_TOKEN") + demand(isinstance(token, str) and bool(token), + "GitHub token required for actual historical evidence revalidation") + M = lifecycle["infrastructure_merge_sha"] + H = lifecycle["infrastructure_head_sha"] + with tempfile.TemporaryDirectory(prefix="pr113-historical-inheritance-") as temp: + old = Path(temp) / "reviewed-M113" + try: + subprocess.run(["git", "-C", str(ROOT), "worktree", "add", "--detach", + str(old), M], stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, check=True, timeout=60) + found = subprocess.check_output( + ["git", "-C", str(old), "rev-parse", "HEAD"], + timeout=20).decode().strip() + demand(found == M, "historical executor checkout not exact M113") + executed = subprocess.run( + [sys.executable, "-B", "-c", + "import sys;sys.path.insert(0, 'verification/research');" + CHILD, + H, M, str(producer_run_id), str(security_review_id)], + cwd=old, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + check=False, timeout=900) + demand(executed.returncode == 0 + and len(executed.stdout) <= 32768 + and len(executed.stdout) > 0, + "actual M113 historical source/review verification failed") + report = strict_json_object(executed.stdout, max_bytes=32768) + return verify_child_report(report, lifecycle, + run_id=producer_run_id, + reviewer_id=security_review_id) + except (OSError, subprocess.SubprocessError) as exc: + raise InheritanceError("authentic original M113 execution unavailable") from exc + finally: + if old.exists(): + subprocess.run(["git", "-C", str(ROOT), "worktree", "remove", + "--force", str(old)], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, timeout=60, + check=False) + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--producer-run-id", type=int, required=True) + ap.add_argument("--security-review-id", type=int, required=True) + ap.add_argument("--out", type=Path, required=True) + args = ap.parse_args(argv) + try: + output = detached_destination(args.out, ROOT) + receipt = verify(args.producer_run_id, args.security_review_id) + output.parent.mkdir(parents=True, exist_ok=True) + with output.open("x", encoding="utf-8") as file: + json.dump(receipt, file, sort_keys=True, indent=2) + file.write("\n") + print(json.dumps({"status": receipt["status"], + "current_source_sha": receipt["current_source_sha"]})) + return 0 + except (InheritanceError, OSError, KeyError, ValueError) as exc: + print("INHERITED_PR113_SOURCE_REVALIDATION_BLOCKED", exc, + file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_historical_intake_v2.py b/verification/research/pr113_historical_intake_v2.py new file mode 100644 index 00000000..62fccfd9 --- /dev/null +++ b/verification/research/pr113_historical_intake_v2.py @@ -0,0 +1,153 @@ +"""V2 historical ZIP *candidate* consumers: 25 historical / exact 30 current. + +These pure parsers do not certify GitHub artifact provenance or human review. +Production must additionally check run/job/attempt/check/artifact identities +and source-bound reviewer authority. V1 receipts cannot upgrade to V2. +""" +from __future__ import annotations + +from infrastructure_acceptance import ( + json_member, need, sha, verify_compiler_receipt, + HISTORICAL_MUTATIONS, SUCCESSOR_MUTATIONS, +) +from pr113_semantic_receipt_v2 import validate_compiler_receipt_v2 +from pr113_dual_tooling_candidate_v2 import validate_dual_reports_v2 +from pr113_tooling_migration import T +import pr113_historical_a2_baseline_v1 as baseline +from localized_prefix_gate import DECLS + +T2_FORMAT = "Permansson.PR113.DualToolingCandidate.v3" +DOSSIER_FORMAT = "Permansson.PR113.ToolingSecurityReviewDossier.v2" +A2_FORMAT = baseline.DUAL_FORMAT + + +def inspect_T_v2(files, head, *, profile_sha256): + need(isinstance(head, str) and len(head) == 40 and + isinstance(profile_sha256, str) and len(profile_sha256) == 64, + "v2 T/T2 source or profile not supplied") + old_raw = files.get("original_T_archive.json") + current_raw = files.get("current_H_workflow.json") + need(type(old_raw) is bytes and type(current_raw) is bytes, + "versioned T2 requires both real underlying test reports") + historical = json_member(files, "original_T_archive.json") + current = json_member(files, "current_H_workflow.json") + profile = { + "format": "Permansson.PR113.ToolingMigrationCandidate.v2", + "reviewed_workflow_source_mode": "active", + "historical_original_workflow_count": 25, + "current_reviewed_workflow_count": 30, + "workflow_profile_sha256": profile_sha256, + "independent_security_review": False, + } + checked = validate_dual_reports_v2( + historical, current, T_source=T, H_source=head, profile=profile) + receipt = json_member(files, "dual_tooling_unreviewed.json") + dossier = json_member(files, "T2_security_review_candidate.json") + need(checked["status"] == "DUAL_T_AND_30_H_EXECUTED_UNREVIEWED" + and receipt.get("format") == T2_FORMAT + and receipt.get("status") == checked["status"] + and receipt.get("historical_T") == T + and receipt.get("current_H") == head + and receipt.get("genuine_current_source_commit") == head + and receipt.get("original_T_archive_source_commit") == T + and receipt.get("historical_original_workflow_count") == 25 + and receipt.get("current_reviewed_workflow_count") == 30 + and receipt.get("workflow_profile_sha256") == profile_sha256 + and receipt.get("historical_T_report_sha256") == sha(old_raw) + and receipt.get("current_H_report_sha256") == sha(current_raw) + and receipt.get("original_T_archive_unchanged") is True + and receipt.get("independent_security_review") is False + and receipt.get("versioned_successor_T2_approved") is False + and dossier.get("format") == DOSSIER_FORMAT + and dossier.get("status") == + "COMPLETE_DUAL_EXECUTION_CANDIDATE_SECURITY_REVIEW_PENDING" + and dossier.get("T") == T + and dossier.get("H") == head + and dossier.get("historical_original_workflow_count") == 25 + and dossier.get("required_workflow_inventory_count") == 30 + and dossier.get("current_workflow_profile_sha256") == profile_sha256 + and dossier.get("T_original_offline_restore_report_sha256") == + sha(old_raw) + and dossier.get("H_original_workflow_test_report_sha256") == + sha(current_raw) + and dossier.get("legacy_T_did_not_accept_current_C") is True + and dossier.get("owner_authorization_present") is False + and dossier.get("independent_security_review_present") is False + and dossier.get("historical_required_CI_replaced") is False + and dossier.get("mathematical_release_approved") is False, + "versioned original T/current 30 evidence incomplete, stale, or self-approved") + return { + "format": "Permansson.PR113.T2DualSourceCandidate.v2", + "historical_T_report_sha256": sha(old_raw), + "current_H_workflow_report_sha256": sha(current_raw), + "T2_dossier_sha256": sha(files["T2_security_review_candidate.json"]), + "historical_original_workflow_count": 25, + "current_reviewed_workflow_count": 30, + "current_workflow_profile_sha256": profile_sha256, + "historical_T_unchanged": True, + "independent_security_review": False, + "mathematical_acceptance": False, + } + + +def inspect_A2_v2(files, head): + overall = json_member(files, "unreviewed_dual_A2_candidate.json") + historical, hhash = validate_compiler_receipt_v2( + files, "historical_original_A2", + "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING", HISTORICAL_MUTATIONS) + successor, shash = validate_compiler_receipt_v2( + files, "pr113_successor_A2", + "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING", SUCCESSOR_MUTATIONS) + baseline.verify_receipt_source(historical) + need(set(historical.get("declarations", {})) == set(DECLS) + and set(successor.get("declarations", {})) == set(DECLS), + "both A2 legs must retain all forty original declarations") + provenance = overall.get("historical_runtime_provenance") + need(overall.get("format") == A2_FORMAT + and overall.get("status") == + "BOTH_ACTUAL_A2_COMPILER_PATHS_EXECUTED_UNREVIEWED" + and overall.get("historical_source_commit") == baseline.REPLAY_HEAD + and overall.get("historical_introducer_sha") == baseline.INTRODUCER + and overall.get("historical_tree_sha") == baseline.REPLAY_TREE + and overall.get("current_source_commit") == head + and overall.get("historical_original_workflow_count") == 25 + and overall.get("current_reviewed_workflow_count") == 30 + and overall.get("historical_test_executed") is True + and overall.get("successor_test_executed") is True + and overall.get("historical_signatures") == 40 + and overall.get("successor_signatures") == 40 + and overall.get("historical_semantic_mutants") == 4 + and overall.get("successor_semantic_mutants") == 4 + and overall.get("original_verifier_modified") is False + and overall.get("independent_security_approval") is False + and overall.get("independent_mathematical_approval") is False + and overall.get("merged_main_sha") is None + and overall.get("full_release_approved") is False + and isinstance(provenance, dict) + and provenance.get("imported_current_A2_gate") is False + and provenance.get("historical_checkout_sha") == baseline.REPLAY_HEAD + and provenance.get("historical_tree_sha") == baseline.REPLAY_TREE + and provenance.get("introduced_executable_sha") == baseline.INTRODUCER + and provenance.get("baseline_contract") == baseline.FORMAT + and provenance.get("original_runtime_git_blobs") == baseline.EXECUTORS + and provenance.get("output_receipt_sha256") == hhash + and historical.get("source_unchanged") is True + and successor.get("source_unchanged") is True + and isinstance(successor.get("source"), dict) + and successor["source"].get("git_head") == head + and successor.get("format") == + "Permansson.PR113.A2SuccessorCompilerCandidate.v2", + "A2 historical and active30 successor proof evidence not authenticated") + return { + "format": "Permansson.PR113.A2DualSourceCandidate.v3", + "historical_introducer_sha": baseline.INTRODUCER, + "historical_replay_sha": baseline.REPLAY_HEAD, + "historical_replay_tree": baseline.REPLAY_TREE, + "historical_A2_report_sha256": hhash, + "successor_A2_report_sha256": shash, + "dual_A2_sha256": sha(files["unreviewed_dual_A2_candidate.json"]), + "historical_original_workflow_count": 25, + "current_reviewed_workflow_count": 30, + "independent_security_review": False, + "mathematical_acceptance": False, + } diff --git a/verification/research/pr113_historical_postmerge.py b/verification/research/pr113_historical_postmerge.py new file mode 100644 index 00000000..2866f985 --- /dev/null +++ b/verification/research/pr113_historical_postmerge.py @@ -0,0 +1,197 @@ +"""Fail-closed historical verification for a genuine ordered #113 main merge. + +The original required checks run on both main PRs *and* main pushes. The PR +binding intentionally rejects merged PRs; using it on a main push would +permanently deadlock the release. This distinct v1 postmerge contract accepts +ONLY the genuine owner-authorized GitHub merge M with exact ordered B/H +parents, H's source tree, old real T/A2 producing ZIPs and authentic +independent approval of the actual H/artifact bytes. + +This is code for a reviewed replacement gate, NOT current main authorization. +""" +from __future__ import annotations + +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess + +from final_closure_authenticate import ( + live as real_live, git as real_git, paginate as real_paginate, +) +from infrastructure_acceptance import ( + FORMAT as HISTORICAL_INPUT, ACTIVE_WORKFLOW, AUTHORITY_PATH, + authenticate_original_zip, inspect_zip, inspect_T, inspect_A2, + verify_A2_dependency_bridge, exact_committed_lake_blob, + exact_committed_blob, + authority_policy, approved_security_review, +) +from release_target import B113, REPOSITORY + +ROOT = Path(__file__).resolve().parents[2] +SHA = re.compile(r"^[0-9a-f]{40}$") +FORMAT = "Permansson.PR113.PostmergeHistoricalEvidence.v1" +TARGET = "Permansson.PR113.OrderedMainMergeTarget.v1" +RESULT = "Permansson.PR113.PostmergeRequiredHistoricalGate.v1" +JOBS = { + "research": "Research Lean", + "archive-reproducibility": "Research integrity", +} + + +class PostmergeError(ValueError): + pass + + +def need(value, why): + if not value: + raise PostmergeError(why) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def exact_main_event(*, env=None, event_bytes=None, git=real_git, live=real_live): + env = os.environ if env is None else env + need(env.get("GITHUB_ACTIONS") == "true" + and env.get("GITHUB_EVENT_NAME") == "push" + and env.get("GITHUB_REF") == "refs/heads/main" + and env.get("GITHUB_REPOSITORY") == REPOSITORY + and env.get("GITHUB_JOB") in JOBS + and env.get("GITHUB_WORKFLOW") == JOBS[env["GITHUB_JOB"]], + "historical merged-source adapter must run in genuine required main job") + M = env.get("GITHUB_SHA") + need(isinstance(M, str) and SHA.fullmatch(M), + "real main pushed merge commit missing") + if event_bytes is None: + filepath = Path(env.get("GITHUB_EVENT_PATH", "")) + need(filepath.is_file() and not filepath.is_symlink() + and 0 < filepath.stat().st_size <= 8*1024*1024, + "main GitHub event file missing/unsafe") + event_bytes = filepath.read_bytes() + need(type(event_bytes) is bytes and + 0 < len(event_bytes) <= 8*1024*1024, + "main event must be the original bounded GitHub JSON") + try: + event = json.loads(event_bytes) + except (ValueError, TypeError) as exc: + raise PostmergeError("main GitHub event JSON invalid") from exc + need(isinstance(event,dict) and + event.get("ref") == "refs/heads/main" and + event.get("after") == M and + (event.get("repository") or {}).get("full_name") == REPOSITORY, + "main push event does not bind original source") + try: + run_id = int(env.get("GITHUB_RUN_ID", "")) + attempt = int(env.get("GITHUB_RUN_ATTEMPT", "")) + except (ValueError, TypeError) as exc: + raise PostmergeError("main source run identity missing") from exc + need(run_id > 0 and attempt > 0, + "main source run IDs invalid") + need(git("rev-parse","HEAD") == M and + git("cat-file","-t",M) == "commit", + "source checkout is not exact genuine pushed main") + parents = git("show","-s","--format=%P",M).split() + need(len(parents) == 2 and parents[0] == B113 and + SHA.fullmatch(parents[1]) is not None and + git("cat-file","-t",parents[1]) == "commit" and + git("rev-parse",M+"^{tree}") == + git("rev-parse",parents[1]+"^{tree}"), + "merged main must be real ordered original B/H with reviewed H tree") + H = parents[1] + pr = live("/pulls/113") + main = live("/branches/main") + need(isinstance(pr,dict) and pr.get("number") == 113 + and pr.get("state") == "closed" and pr.get("merged") is True + and pr.get("merge_commit_sha") == M + and (pr.get("head") or {}).get("sha") == H + and isinstance(main,dict) + and (main.get("commit") or {}).get("sha") == M, + "merged infrastructure PR and actual remote main disagree") + return { + "original_base_sha": B113, "reviewed_head_sha": H, + "merged_main_sha": M, "github_run_id": run_id, + "github_run_attempt": attempt, "github_job": env["GITHUB_JOB"], + "github_event_sha256": sha(event_bytes), + } + + +def source_target(context): + return { + "format": TARGET, "repository": REPOSITORY, + "pr_number": 113, + "original_base_sha": context["original_base_sha"], + "reviewed_head_sha": context["reviewed_head_sha"], + "merged_main_sha": context["merged_main_sha"], + } + + +def check_historical_merged(envelope, t_zip, a2_zip, *, + event_context, git=real_git, live=real_live, + paginate=real_paginate): + need(isinstance(envelope,dict) and + set(envelope) == { + "format","target","historical_T","historical_A2", + "security_review_id","source_bound_ci" + } and envelope.get("format") == FORMAT + and envelope.get("target") == source_target(event_context) + and envelope.get("source_bound_ci") == [], + "historical postmerge target not bound to real M/B/H") + H = event_context["reviewed_head_sha"] + M = event_context["merged_main_sha"] + workflow_raw=exact_committed_blob(H,ACTIVE_WORKFLOW,git=git) + workflow=workflow_raw.decode("utf-8") + need(workflow.startswith("# Review-pending source-controlled") and + "historical-T-and-H-unreviewed" in workflow and + "historical-A2-and-H-unreviewed" in workflow and + exact_committed_blob(M,ACTIVE_WORKFLOW,git=git)==workflow_raw, + "reviewed historical T/A2 source not identical in real M") + t = authenticate_original_zip( + "T",envelope["historical_T"],t_zip,H,live=live) + a = authenticate_original_zip( + "A2",envelope["historical_A2"],a2_zip,H,live=live) + archived=inspect_T(inspect_zip(t_zip),H) + current=inspect_A2(inspect_zip(a2_zip),H) + current["dependency_bridge_sha256"] = verify_A2_dependency_bridge( + inspect_zip(a2_zip),H, + manifest_raw=exact_committed_lake_blob(H,git=git)) + policy=authority_policy(H,git=git) + reviewer_attestation={ + "head_sha":H, + "workflow_sha256":sha(workflow_raw), + "T_artifact_sha256":sha(t_zip), + "A2_artifact_sha256":sha(a2_zip), + "original_base_sha":B113, + } + pr=live("/pulls/113") + approved_security_review( + envelope["security_review_id"],(pr.get("user") or {}).get("login"), + H,reviewer_attestation,policy,paginate=paginate) + return { + "format":RESULT, + "status":"MERGED_HISTORICAL_T_AND_A2_REVERIFIED_UNDER_REVIEWED_SOURCE", + "original_base_sha":B113, + "head_sha":H, + "merged_main_sha":M, + "original_T":t,"original_A2":a, + "original_T_checks":archived,"original_A2_checks":current, + "security_review_id":envelope["security_review_id"], + "independent_security_review_authenticated":True, + "required_ci_successful":0, + "independent_mathematical_review":False, + "accepted_original_cases":0,"accepted_new_claims":0, + "owner_merge_authorized":False, + "publication_authorized":False, + "full_release_approved":False, + } + + +def verify(envelope,t_zip,a2_zip,*,env=None,event_bytes=None,git=real_git, + live=real_live,paginate=real_paginate): + context=exact_main_event(env=env,event_bytes=event_bytes,git=git,live=live) + return check_historical_merged( + envelope,t_zip,a2_zip,event_context=context, + git=git,live=live,paginate=paginate) diff --git a/verification/research/pr113_historical_postmerge_v2.py b/verification/research/pr113_historical_postmerge_v2.py new file mode 100644 index 00000000..a74c3af3 --- /dev/null +++ b/verification/research/pr113_historical_postmerge_v2.py @@ -0,0 +1,200 @@ +"""Fail-closed historical verification for a genuine ordered #113 main merge. + +The original required checks run on both main PRs *and* main pushes. The PR +binding intentionally rejects merged PRs; using it on a main push would +permanently deadlock the release. This distinct v1 postmerge contract accepts +ONLY the genuine owner-authorized GitHub merge M with exact ordered B/H +parents, H's source tree, old real T/A2 producing ZIPs and authentic +independent approval of the actual H/artifact bytes. + +This is code for a reviewed replacement gate, NOT current main authorization. +""" +from __future__ import annotations + +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess + +from final_closure_authenticate import ( + live as real_live, git as real_git, paginate as real_paginate, +) +from infrastructure_acceptance_v2 import ( + FORMAT as HISTORICAL_INPUT, ACTIVE_WORKFLOW, AUTHORITY_PATH, + authenticate_original_zip, inspect_zip, inspect_T, inspect_A2, + verify_A2_dependency_bridge, exact_committed_lake_blob, + exact_committed_blob, + authority_policy, approved_security_review, +) +from release_target import B113, REPOSITORY +from pr113_historical_evidence_contract_v2 import ( + InfrastructureEnvelopeError, decode_unique_json, +) + +ROOT = Path(__file__).resolve().parents[2] +SHA = re.compile(r"^[0-9a-f]{40}$") +FORMAT = "Permansson.PR113.PostmergeHistoricalEvidence.v2" +TARGET = "Permansson.PR113.OrderedMainMergeTarget.v1" +RESULT = "Permansson.PR113.PostmergeRequiredHistoricalGate.v2" +JOBS = { + "research": "Research Lean", + "archive-reproducibility": "Research integrity", +} + + +class PostmergeError(ValueError): + pass + + +def need(value, why): + if not value: + raise PostmergeError(why) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def exact_main_event(*, env=None, event_bytes=None, git=real_git, live=real_live): + env = os.environ if env is None else env + need(env.get("GITHUB_ACTIONS") == "true" + and env.get("GITHUB_EVENT_NAME") == "push" + and env.get("GITHUB_REF") == "refs/heads/main" + and env.get("GITHUB_REPOSITORY") == REPOSITORY + and env.get("GITHUB_JOB") in JOBS + and env.get("GITHUB_WORKFLOW") == JOBS[env["GITHUB_JOB"]], + "historical merged-source adapter must run in genuine required main job") + M = env.get("GITHUB_SHA") + need(isinstance(M, str) and SHA.fullmatch(M), + "real main pushed merge commit missing") + if event_bytes is None: + filepath = Path(env.get("GITHUB_EVENT_PATH", "")) + need(filepath.is_file() and not filepath.is_symlink() + and 0 < filepath.stat().st_size <= 8*1024*1024, + "main GitHub event file missing/unsafe") + event_bytes = filepath.read_bytes() + need(type(event_bytes) is bytes and + 0 < len(event_bytes) <= 8*1024*1024, + "main event must be the original bounded GitHub JSON") + try: + event = decode_unique_json(event_bytes, max_bytes=8*1024*1024) + except InfrastructureEnvelopeError as exc: + raise PostmergeError("main GitHub event JSON invalid or ambiguous") from exc + need(isinstance(event,dict) and + event.get("ref") == "refs/heads/main" and + event.get("after") == M and + (event.get("repository") or {}).get("full_name") == REPOSITORY, + "main push event does not bind original source") + try: + run_id = int(env.get("GITHUB_RUN_ID", "")) + attempt = int(env.get("GITHUB_RUN_ATTEMPT", "")) + except (ValueError, TypeError) as exc: + raise PostmergeError("main source run identity missing") from exc + need(run_id > 0 and attempt > 0, + "main source run IDs invalid") + need(git("rev-parse","HEAD") == M and + git("cat-file","-t",M) == "commit", + "source checkout is not exact genuine pushed main") + parents = git("show","-s","--format=%P",M).split() + need(len(parents) == 2 and parents[0] == B113 and + SHA.fullmatch(parents[1]) is not None and + git("cat-file","-t",parents[1]) == "commit" and + git("rev-parse",M+"^{tree}") == + git("rev-parse",parents[1]+"^{tree}"), + "merged main must be real ordered original B/H with reviewed H tree") + H = parents[1] + pr = live("/pulls/113") + main = live("/branches/main") + need(isinstance(pr,dict) and pr.get("number") == 113 + and pr.get("state") == "closed" and pr.get("merged") is True + and pr.get("merge_commit_sha") == M + and (pr.get("head") or {}).get("sha") == H + and isinstance(main,dict) + and (main.get("commit") or {}).get("sha") == M, + "merged infrastructure PR and actual remote main disagree") + return { + "original_base_sha": B113, "reviewed_head_sha": H, + "merged_main_sha": M, "github_run_id": run_id, + "github_run_attempt": attempt, "github_job": env["GITHUB_JOB"], + "github_event_sha256": sha(event_bytes), + } + + +def source_target(context): + return { + "format": TARGET, "repository": REPOSITORY, + "pr_number": 113, + "original_base_sha": context["original_base_sha"], + "reviewed_head_sha": context["reviewed_head_sha"], + "merged_main_sha": context["merged_main_sha"], + } + + +def check_historical_merged(envelope, t_zip, a2_zip, *, + event_context, git=real_git, live=real_live, + paginate=real_paginate): + need(isinstance(envelope,dict) and + set(envelope) == { + "format","target","historical_T","historical_A2", + "security_review_id","source_bound_ci" + } and envelope.get("format") == FORMAT + and envelope.get("target") == source_target(event_context) + and envelope.get("source_bound_ci") == [], + "historical postmerge target not bound to real M/B/H") + H = event_context["reviewed_head_sha"] + M = event_context["merged_main_sha"] + workflow_raw=exact_committed_blob(H,ACTIVE_WORKFLOW,git=git) + workflow=workflow_raw.decode("utf-8") + need(workflow.startswith("# Review-pending source-controlled") and + "historical-T-and-H-unreviewed" in workflow and + "historical-A2-and-H-unreviewed" in workflow and + exact_committed_blob(M,ACTIVE_WORKFLOW,git=git)==workflow_raw, + "reviewed historical T/A2 source not identical in real M") + t = authenticate_original_zip( + "T",envelope["historical_T"],t_zip,H,live=live) + a = authenticate_original_zip( + "A2",envelope["historical_A2"],a2_zip,H,live=live) + archived=inspect_T(inspect_zip(t_zip),H) + current=inspect_A2(inspect_zip(a2_zip),H) + current["dependency_bridge_sha256"] = verify_A2_dependency_bridge( + inspect_zip(a2_zip),H, + manifest_raw=exact_committed_lake_blob(H,git=git)) + policy=authority_policy(H,git=git) + reviewer_attestation={ + "head_sha":H, + "workflow_sha256":sha(workflow_raw), + "T_artifact_sha256":sha(t_zip), + "A2_artifact_sha256":sha(a2_zip), + "original_base_sha":B113, + } + pr=live("/pulls/113") + approved_security_review( + envelope["security_review_id"],(pr.get("user") or {}).get("login"), + H,reviewer_attestation,policy,paginate=paginate) + return { + "format":RESULT, + "status":"MERGED_HISTORICAL_T_AND_A2_REVERIFIED_UNDER_REVIEWED_SOURCE", + "original_base_sha":B113, + "head_sha":H, + "merged_main_sha":M, + "original_T":t,"original_A2":a, + "original_T_checks":archived,"original_A2_checks":current, + "security_review_id":envelope["security_review_id"], + "independent_security_review_authenticated":True, + "required_ci_successful":0, + "independent_mathematical_review":False, + "accepted_original_cases":0,"accepted_new_claims":0, + "owner_merge_authorized":False, + "publication_authorized":False, + "full_release_approved":False, + } + + +def verify(envelope,t_zip,a2_zip,*,env=None,event_bytes=None,git=real_git, + live=real_live,paginate=real_paginate): + context=exact_main_event(env=env,event_bytes=event_bytes,git=git,live=live) + return check_historical_merged( + envelope,t_zip,a2_zip,event_context=context, + git=git,live=live,paginate=paginate) diff --git a/verification/research/pr113_historical_runtime_v3.py b/verification/research/pr113_historical_runtime_v3.py new file mode 100644 index 00000000..75274580 --- /dev/null +++ b/verification/research/pr113_historical_runtime_v3.py @@ -0,0 +1,226 @@ +"""Stage-gated original historical T/A2 or inherited M113 evidence adapter. + +The original PR113 uses its exact v2 required gate. Descendants rerun that +same reviewed M113 source in an isolated checkout with actual GitHub inputs. +Never convert an inherited infrastructure receipt into current mathematics. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + +from pr113_boundary_io import detached_destination, strict_json_object +from pr113_lifecycle_source_v3 import actual_source +from pr113_historical_inheritance_v3 import verify as inherited_verify + +ROOT = Path(__file__).resolve().parents[2] +FORMAT = "Permansson.PR113.RequiredHistoricalLifecycleTransport.v3" + + +class RuntimeError113(ValueError): + pass + + +def need(ok, why): + if not ok: + raise RuntimeError113(why) + + +def produce(producer_run_id, security_review_id, *, event=None): + event = actual_source() if event is None else event + phase = event["phase"] + need(phase in ("initial_113_pr", "initial_113_main", + "main_descendant_pr", "main_descendant_push"), + "unknown required historical lifecycle phase") + if phase.startswith("main_descendant"): + receipt = inherited_verify(producer_run_id, security_review_id, + lifecycle=event) + return {"phase": phase, "event": event, "required": receipt, + "envelope": None, "zips": {}, "transport": None} + from pr113_historical_artifact_delivery_v2 import ( + deliver, deliver_merged, source_git, + ) + source_context = { + "original_base_sha": event["base_sha"] if phase == "initial_113_pr" + else event["base_sha"], + "head_sha": event["head_sha"] if phase == "initial_113_pr" + else event["infrastructure_head_sha"], + "synthetic_sha": event["synthetic_sha"], + } + if phase == "initial_113_pr": + original = deliver(source_context, producer_run_id, security_review_id, + git=source_git) + from pr113_required_dual_gate_v2 import verify + checked = verify(original["envelope"], + original["zips"]["T"], original["zips"]["A2"], + git=source_git) + else: + from pr113_historical_postmerge_v2 import ( + check_historical_merged, + ) + merged_context = { + "original_base_sha": event["base_sha"], + "reviewed_head_sha": event["infrastructure_head_sha"], + "merged_main_sha": event["infrastructure_merge_sha"], + } + original = deliver_merged(merged_context, producer_run_id, + security_review_id, git=source_git) + checked = check_historical_merged( + original["envelope"], original["zips"]["T"], + original["zips"]["A2"], event_context=merged_context, + git=source_git) + need(checked.get("independent_security_review_authenticated") is True, + "original historical security evidence not independently reviewed") + return {"phase": phase, "event": event, "required": checked, + "envelope": original["envelope"], + "zips": original["zips"], "transport": original["transport"]} + + +def store(result, output, producer_run_id, security_review_id): + out = detached_destination(output, ROOT) + out.mkdir(parents=True, exist_ok=False) + current = result["event"] + receipt = { + "format": FORMAT, + "phase": result["phase"], + "source_commit_sha": current["source_commit_sha"], + "original_github_run_id": current["github_run_id"], + "original_github_run_attempt": current["github_run_attempt"], + "historical_producer_run_id": producer_run_id, + "historical_security_review_id": security_review_id, + "historical_required_result": result["required"], + "accepted_current_mathematics": False, + "owner_merge_authorized": False, + "publication_authorized": False, + } + for item, path in ( + (receipt, "historical_required.json"), + (result["envelope"], "historical_envelope.json"), + (result["transport"], "historical_transport_unreviewed.json"), + ): + if item is None: + continue + with (out / path).open("x", encoding="utf-8") as writer: + json.dump(item, writer, sort_keys=True, indent=2) + writer.write("\n") + for kind, file in (("T", "historical_T.zip"), ("A2", "historical_A2.zip")): + if kind in result["zips"]: + with (out / file).open("xb") as writer: + writer.write(result["zips"][kind]) + return out, receipt + + +def check_stored(path, producer_run_id, security_review_id, *, event=None, + recompute=None): + """Fail closed on a saved receipt unless authentic evidence rechecks. + + The production CLI never injects the recomputation function. Only + source-bound unit tests may provide a synthetic recomputation. + """ + event = actual_source() if event is None else event + output = detached_destination(path, ROOT, must_exist=True) + original = strict_json_object(output.read_bytes(), max_bytes=4*1024*1024) + need(original.get("format") == FORMAT + and original.get("phase") == event["phase"] + and original.get("source_commit_sha") == event["source_commit_sha"] + and original.get("original_github_run_id") == event["github_run_id"] + and original.get("original_github_run_attempt") == event["github_run_attempt"] + and original.get("historical_producer_run_id") == producer_run_id + and original.get("historical_security_review_id") == security_review_id + and original.get("accepted_current_mathematics") is False + and original.get("owner_merge_authorized") is False + and original.get("publication_authorized") is False, + "retained historical result is not bound to current genuine runner") + source = original.get("historical_required_result") + need(isinstance(source, dict), "original required gate result missing") + if event["phase"].startswith("main_descendant"): + need(source.get("status") == + "PR113_HISTORICAL_SOURCE_REVERIFIED_FOR_AUTHENTIC_DESCENDANT" + and source.get("historical_merged_main_sha") == + event["infrastructure_merge_sha"] + and source.get("current_required_ci_accepted") is False, + "no actual inherited original historical evidence") + else: + need(source.get("independent_security_review_authenticated") is True + and source.get("head_sha") == event["infrastructure_head_sha"] + if event["phase"] == "initial_113_main" + else source.get("independent_security_review_authenticated") is True + and source.get("head_sha") == event["head_sha"], + "original PR113 historical source is not bound") + if recompute is None: + recompute = produce + fresh = recompute(producer_run_id, security_review_id, event=event) + need(isinstance(fresh, dict) + and fresh.get("phase") == event["phase"] + and fresh.get("event") == event + and fresh.get("required") == source, + "saved historical receipt differs from live reauthenticated evidence") + parent = output.parent + if event["phase"].startswith("initial_113"): + for key, name in (("envelope", "historical_envelope.json"), + ("transport", "historical_transport_unreviewed.json")): + saved = detached_destination(parent / name, ROOT, must_exist=True) + need(isinstance(fresh.get(key), dict) + and strict_json_object(saved.read_bytes()) == fresh[key], + "saved historical envelope or transport differs from original") + for kind, name in (("T", "historical_T.zip"), ("A2", "historical_A2.zip")): + saved = detached_destination(parent / name, ROOT, must_exist=True) + need(fresh.get("zips", {}).get(kind) == saved.read_bytes(), + "saved genuine historical GitHub ZIP differs from revalidated bytes") + else: + need(fresh.get("envelope") is None and fresh.get("transport") is None + and fresh.get("zips") == {} + and not any((parent / name).exists() for name in ( + "historical_envelope.json", "historical_transport_unreviewed.json", + "historical_T.zip", "historical_A2.zip")), + "descendant evidence cannot borrow original PR113 ZIP files") + return original + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--producer-run-id", type=int, required=True) + ap.add_argument("--security-review-id", type=int, required=True) + ap.add_argument("--out", required=True, type=Path) + ap.add_argument("--check-existing", action="store_true") + args = ap.parse_args(argv) + try: + if args.check_existing: + receipt = check_stored(args.out, args.producer_run_id, + args.security_review_id) + else: + out, receipt = store( + produce(args.producer_run_id, args.security_review_id), + args.out, args.producer_run_id, args.security_review_id) + # The next Research regression step always receives the real + # phase-neutral, source-bound receipt, including descendants. + # The original T/A2 ZIP variables remain initial-only for + # consumers that explicitly verify the original PR113 phase. + env_path = __import__("os").environ.get("GITHUB_ENV") + if env_path: + with Path(env_path).open("a", encoding="utf-8") as f: + f.write( + "PERMANSSON_HISTORICAL_REQUIRED_RECEIPT=" + + str(out / "historical_required.json") + "\n") + if receipt["phase"].startswith("initial_113"): + for key, path in { + "PERMANSSON_DUAL_HISTORICAL_EVIDENCE": + "historical_envelope.json", + "PERMANSSON_DUAL_ORIGINAL_T_ZIP": "historical_T.zip", + "PERMANSSON_DUAL_ORIGINAL_A2_ZIP": "historical_A2.zip", + }.items(): + f.write(key + "=" + str(out / path) + "\n") + print(json.dumps({"status": receipt["phase"], + "source_commit_sha": receipt["source_commit_sha"], + "historical_required": True, + "new_mathematical_acceptance": False})) + return 0 + except (RuntimeError113, OSError, KeyError, ValueError, TypeError) as exc: + print("REQUIRED_HISTORICAL_LIFECYCLE_BLOCKED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_implementation_matrix.py b/verification/research/pr113_implementation_matrix.py new file mode 100644 index 00000000..4718f83e --- /dev/null +++ b/verification/research/pr113_implementation_matrix.py @@ -0,0 +1,277 @@ +"""Reconcile 45 original criteria + 11 claims against exact Git-committed source. + +This is a *discovery and implementation-gap ledger*, not a Lean proof or +independent case/claim review. Header/name presence and AST test discovery +can never turn PARTIAL into COMPLETE. A separate true Lean type/axiom + +semantic test receipt and independent reviewer decision are required. +""" +from __future__ import annotations +import argparse +import ast +import hashlib +import json +from pathlib import Path +from pr113_boundary_io import strict_json_object +import re +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[2] +MAP_ROOT = "research/paper_closures/post_merge_112/" +CASE_MAP = MAP_ROOT + "acceptance_case_source_map.json" +CLAIM_MAP = MAP_ROOT + "claim_declaration_map.json" +ORIGINAL = MAP_ROOT + "acceptance_decisions.json" +INVENTORY = MAP_ROOT + "claim_inventory.json" +SHA40 = re.compile(r"^[a-f0-9]{40}$") +CLAIM_IDS = frozenset(( + "Q1","Q2","Q3","M1","M2","M3","N1","N2","N3","D8","D9" +)) + + +class MatrixError(ValueError): + pass + + +def check(ok, why): + if not ok: + raise MatrixError(why) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def committed(head, path): + check(isinstance(path, str) and not path.startswith("/") + and ".." not in Path(path).parts + and (path.startswith("research/paper_closures/post_merge_112/") + or path.startswith("verification/research/") + or path.startswith("PermanssonResearch/")), + "unrecognized source path for implementation reconciliation") + try: + result = subprocess.run(["git", "show", head + ":" + path], + cwd=ROOT, capture_output=True, + timeout=30, check=False) + except (OSError, subprocess.TimeoutExpired) as exc: + raise MatrixError("exact Git object fetch unavailable") from exc + check(result.returncode == 0 and len(result.stdout) <= 4*1024*1024, + "no actual source blob at H: " + path) + return result.stdout + + +def strict_json(raw): + check(type(raw) is bytes and len(raw) > 0, "empty JSON source") + try: + obj = strict_json_object(raw) + except (ValueError, UnicodeError) as exc: + raise MatrixError("malformed JSON source") from exc + check(isinstance(obj, dict), "object required") + return obj + + +def index_exact(rows, key, expected): + check(isinstance(rows, list) and len(rows) == len(expected), + "historical original set/cardinality changed") + out = {} + for r in rows: + check(isinstance(r, dict) and + isinstance(r.get(key), str) and + r[key] not in out, + "duplicate or malformed original criterion") + out[r[key]] = r + check(set(out) == set(expected), "old criteria/claims silently changed") + return out + + +def method_candidates(routes, read): + rows = [] + for route in routes: + check(isinstance(route, dict), "invalid original test route record") + path, name = route.get("path"), route.get("method") + check(isinstance(path, str) and + path.startswith("verification/research/") and + path.endswith(".py") and + isinstance(name, str) and name.startswith("test_"), + "test method does not have an original explicit source route") + status = "MISSING_SOURCE_OR_METHOD" + digest = None + try: + raw = read(path) + digest = sha(raw) + tree = ast.parse(raw.decode("utf-8")) + # ast.walk() also finds nested, conditionally hidden functions + # that unittest discovery will never execute. Route discovery + # may inspect only module-level tests or direct methods of + # module-level classes; execution still requires real CI logs. + functions = (ast.FunctionDef, ast.AsyncFunctionDef) + funcs = [n for n in tree.body + if isinstance(n, functions) and n.name == name] + funcs += [method for cls in tree.body + if isinstance(cls, ast.ClassDef) + for method in cls.body + if isinstance(method, functions) + and method.name == name] + if len(funcs) == 1: + status = "DISCOVERED_UNREVIEWED" + elif len(funcs) > 1: + status = "AMBIGUOUS_UNREVIEWED" + except (OSError, MatrixError, KeyError, SyntaxError, UnicodeError): + pass + rows.append({ + "path": path, "method": name, "role": route.get("role"), + "source_sha256": digest, "source_status": status, + "semantic_relevance_review": "NOT_YET_ACCEPTED", + "actual_execution_provenance": None, + }) + return rows + + +def proof_candidates(proofs, read, *, claim=False): + result = [] + check(isinstance(proofs, list), "source proof list is malformed") + for item in proofs: + check(isinstance(item, dict), "invalid theorem provider") + path = item.get("path") + name = item.get("declaration") if claim else item.get("name") + check(isinstance(path, str) and + path.startswith("PermanssonResearch/") and + path.endswith(".lean") and + isinstance(name, str) and len(name) > 0, + "invalid original theorem provider") + digest = None + status = "MISSING_SOURCE" + try: + digest = sha(read(path)) + status = "SOURCE_PRESENT__COMPILED_TYPE_UNCHECKED" + except (OSError, MatrixError, KeyError): + pass + result.append({ + "path": path, "declaration": name, "source_sha256": digest, + "candidate_status": status, + "full_elaborated_type_sha256": None, + "transitive_axiom_receipt": None, + "independent_statement_review": False, + }) + return result + + +def produce(head, read=None): + check(isinstance(head, str) and SHA40.fullmatch(head), + "source H not exact 40-character Git identifier") + if read is None: + read = lambda p: committed(head, p) + case_map = strict_json(read(CASE_MAP)) + claim_map = strict_json(read(CLAIM_MAP)) + original = strict_json(read(ORIGINAL)) + inventory = strict_json(read(INVENTORY)) + cids = {c.get("case_id") for c in original.get("cases", []) + if isinstance(c, dict)} + check(len(cids) == 45 and None not in cids and + case_map.get("original_cases") == 45 and + case_map.get("new_closure_owners") == 11 and + claim_map.get("count") == 11 and + original.get("full45_accepted") is False, + "frozen acceptance requirements or historical decision changed") + by_case = index_exact(case_map.get("cases"), "case_id", cids) + historical = index_exact(original.get("cases"), "case_id", cids) + by_claim = index_exact(claim_map.get("claims"), "claim_id", CLAIM_IDS) + frozen_claims = index_exact(inventory.get("claims"), "id", CLAIM_IDS) + cases = [] + for cid, case in sorted(by_case.items()): + old = historical[cid] + check(case.get("required_expected_result") == + old.get("required_expected_result"), + "original criterion wording drift: " + cid) + proofs = proof_candidates(case.get("historical_proof_declarations"), read) + tests = method_candidates(case.get("test_method_candidates", []), read) + missing = (not proofs or not tests or + any(r["candidate_status"] == "MISSING_SOURCE" for r in proofs) or + any(r["source_status"] != "DISCOVERED_UNREVIEWED" for r in tests)) + cases.append({ + "case_id": cid, + "requirement": case["required_expected_result"], + "historical_contract_ids": case.get("historical_contract_ids", []), + "historical_decision": old["decision"], + "source_theorem_candidates": proofs, + "candidate_tests": tests, + "stated_remaining_obligations": case.get("remaining_obligations", []), + "implementation_state": "MISSING" if missing else "PARTIAL", + "independent_case_acceptance": "PENDING", + }) + claims = [] + for cid, claim in sorted(by_claim.items()): + check(frozen_claims[cid].get("accepted") is False and + claim.get("accepted") is False, + "claim inventory/history pretends acceptance") + proofs = proof_candidates(claim.get("declarations"), read, claim=True) + paths = claim.get("test_source_candidates", []) + tests = [] + for path in paths: + check(isinstance(path, str) and + path.startswith("verification/research/") and + path.endswith(".py"), "foreign claim test source") + try: + raw = read(path) + tests.append({"path": path, "sha256": sha(raw), + "status": "SOURCE_PRESENT_UNREVIEWED"}) + except (OSError, MatrixError): + tests.append({"path": path, "sha256": None, + "status": "MISSING_SOURCE"}) + missing = (not proofs or not tests or + any(p["candidate_status"] == "MISSING_SOURCE" for p in proofs) + or any(t["status"] == "MISSING_SOURCE" for t in tests)) + claims.append({ + "claim_id": cid, "draft_title": frozen_claims[cid].get("title"), + "original_case_links": claim.get("original_case_links", []), + "source_theorem_candidates": proofs, + "candidate_test_files": tests, + "scope_exclusions": claim.get("scope_exclusions", []), + "implementation_state": "MISSING" if missing else "PARTIAL", + "independent_claim_acceptance": "PENDING", + }) + return { + "format": "Permansson.PR113.ImplementationCandidateMatrix.v1", + "status": "SOURCE_DISCOVERY_ONLY_NOT_LEAN_OR_INDEPENDENT_ACCEPTANCE", + "audited_head": head, + "original_case_count": 45, "new_claim_count": 11, + "cases": cases, "claims": claims, + "case_source_missing": [ + x["case_id"] for x in cases if x["implementation_state"] == "MISSING"], + "claim_source_missing": [ + x["claim_id"] for x in claims if x["implementation_state"] == "MISSING"], + "unconditionally_implemented": 0, + "independently_accepted_cases": 0, + "independently_accepted_claims": 0, + "mathematical_certification": False, + "publication_authorized": False, + } + + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--head", required=True) + p.add_argument("--out", type=Path, required=True) + args=p.parse_args(argv) + try: + from pr113_boundary_io import detached_destination + target=detached_destination(args.out, ROOT) + report=produce(args.head) + target.parent.mkdir(parents=True, exist_ok=True) + with target.open("x",encoding="utf-8") as file: + json.dump(report,file,sort_keys=True,indent=2) + file.write("\n") + print(json.dumps({ + "status":report["status"], + "cases":report["original_case_count"], + "claims":report["new_claim_count"], + "missing_case_sources":len(report["case_source_missing"]), + "missing_claim_sources":len(report["claim_source_missing"]), + },sort_keys=True)) + return 0 + except (MatrixError, OSError, TypeError, ValueError) as exc: + print("IMPLEMENTATION MATRIX BLOCKED:",exc,file=sys.stderr) + return 2 + + +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_legacy20_choice_delivery.py b/verification/research/pr113_legacy20_choice_delivery.py new file mode 100644 index 00000000..8a71f0fa --- /dev/null +++ b/verification/research/pr113_legacy20_choice_delivery.py @@ -0,0 +1,352 @@ +"""Authenticated detached 20-case legacy-event selection delivery. + +The survey must be a successful original exact-H GitHub producing job, and the +human event choices must come from an owner-authorized independent reviewer's +real GitHub PR comment *after* that survey was produced. Static intent cannot +invent runtime event or artifact IDs. No case is mathematically accepted here. +""" +from __future__ import annotations +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import sys + +from final_closure_authenticate import git as real_git, live as real_live +from infrastructure_acceptance import inspect_zip +from pr113_boundary_io import strict_json_object, detached_destination +from pr113_legacy20_runtime_resolution import ( + FORMAT_DECISION, FORMAT_REPORT, decision_to_selection, +) +from release_target import B113, REPOSITORY, T +from pr113_producer_target_v3 import authenticated_producer_branch, BRANCH113 + +ROOT = Path(__file__).resolve().parents[2] +POLICY = "research/certification/legacy_event_reviewer_authority.json" +ACTIVE_WORKFLOW = ".github/workflows/pr113-source-controls-v3_1.yml" +PREFIX = "PERMANSSON-LEGACY-EVENT-CHOICES-V1:" +REVIEW_FORMAT = "Permansson.PR113.Legacy20SelectionAttestation.v1" +SHA40 = re.compile(r"^[a-f0-9]{40}$") +SHA256 = re.compile(r"^[a-f0-9]{64}$") +SURVEY_MEMBER = "unreviewed_legacy20_candidates.json" +SURVEY_JOB = "original-legacy20" +SURVEY_ARTIFACT = "pr113-v31-legacy20-observation-candidates" +SOURCE_FIELDS = frozenset(( + "run_id", "run_attempt", "job_id", "check_run_id", "artifact_id", +)) +SELECTION_FIELDS = frozenset(( + "case_id", "test_index", "observation_id", "producing_artifact_id", +)) +ATTEST_FIELDS = frozenset(( + "format", "head_sha", "survey_zip_sha256", "survey_member_sha256", + "observation_artifact_id", "status", "cases", +)) + + +class ChoiceDeliveryError(ValueError): + pass + + +def require(ok, message): + if not ok: + raise ChoiceDeliveryError(message) + + +def digest(raw): + return hashlib.sha256(raw).hexdigest() + + +def reviewer_policy(head, *, git): + try: + raw = git("show", head + ":" + POLICY) + policy = strict_json_object(raw if type(raw) is bytes + else raw.encode("utf-8")) + except (OSError, ValueError, TypeError) as exc: + raise ChoiceDeliveryError("legacy reviewer authority not source-bound") from exc + require(isinstance(policy, dict) + and set(policy) == {"format", "status", "owner", "reviewers"} + and policy.get("format") + == "Permansson.PR113.LegacyEventReviewerAuthority.v1" + and policy.get("owner") == "HMarcusWH" + and policy.get("status") == "OWNER_AUTHORIZED" + and isinstance(policy.get("reviewers"), list) + and bool(policy["reviewers"]), + "independent legacy-event reviewer not owner-authorized") + return policy + + +def authenticated_reviewer_target(head, *, git=real_git, live=real_live, env=None): + """Resolve the *real* source PR/branch, never a caller-approved reviewer. + + The future PR ID is a lookup hint. The common original-producer target + authentication proves current GitHub event, PR head/base and M113 ancestry. + This does not grant reviewer independence or mathematical acceptance. + """ + env = os.environ if env is None else env + try: + branch = authenticated_producer_branch(head, env=env, git=git, live=live) + except (ValueError, KeyError, TypeError) as exc: + raise ChoiceDeliveryError("legacy survey target is not a genuine current PR") from exc + if branch == BRANCH113: + number = 113 + else: + hint = env.get("PERMANSSON_CERT_PR_NUMBER", "") + require(type(hint) is str and hint.isdecimal() + and not hint.startswith("0") and int(hint) > 114, + "future legacy reviewer PR number absent or invalid") + number = int(hint) + return {"branch": branch, "pr_number": number, "head_sha": head} + + +def authenticate_survey(head, ids, survey_bytes, *, live, branch=BRANCH113): + require(isinstance(ids, dict) and set(ids) == SOURCE_FIELDS + and all(type(ids.get(x)) is int and ids[x] > 0 + for x in SOURCE_FIELDS), + "original survey runtime IDs incomplete") + run_id, attempt, job_id, check_id, artifact_id = ( + ids[k] for k in ("run_id", "run_attempt", "job_id", + "check_run_id", "artifact_id")) + run = live(f"/actions/runs/{run_id}") + jobs = live(f"/actions/runs/{run_id}/jobs?per_page=100") + check = live(f"/check-runs/{check_id}") + artifact = live(f"/actions/artifacts/{artifact_id}") + require(isinstance(run, dict) and + run.get("id") == run_id + and run.get("head_sha") == head + and run.get("head_branch") == branch + and run.get("event") in ("push", "workflow_dispatch") + and run.get("run_attempt") == attempt + and run.get("status") == "completed" + and run.get("conclusion") == "success" + and str(run.get("path", "")).endswith(ACTIVE_WORKFLOW), + "legacy survey must be a completed exact-H producer, not replay") + rows = jobs.get("jobs") if isinstance(jobs, dict) else None + matching = [x for x in rows if isinstance(x, dict) + and x.get("id") == job_id] if isinstance(rows, list) else [] + require(len(matching) == 1, "legacy producing job not unique") + job = matching[0] + require(job.get("run_id") == run_id + and job.get("run_attempt") == attempt + and job.get("head_sha") == head + and job.get("name") == SURVEY_JOB + and job.get("status") == "completed" + and job.get("conclusion") == "success" + and str(job.get("check_run_url", "")).endswith( + "/check-runs/" + str(check_id)), + "legacy survey source job failed or is unrelated") + require(isinstance(check, dict) + and check.get("id") == check_id + and check.get("head_sha") == head + and check.get("name") == SURVEY_JOB + and check.get("status") == "completed" + and check.get("conclusion") == "success" + and (check.get("app") or {}).get("slug") == "github-actions", + "original survey check-run unauthenticated") + require(isinstance(artifact, dict) + and artifact.get("id") == artifact_id + and artifact.get("name") == SURVEY_ARTIFACT + and artifact.get("expired") is False + and artifact.get("digest") == "sha256:" + digest(survey_bytes) + and (artifact.get("workflow_run") or {}).get("id") == run_id + and (artifact.get("workflow_run") or {}).get("head_sha") == head, + "survey ZIP bytes/identity do not match GitHub") + timestamps = [job.get("started_at"), artifact.get("created_at"), + job.get("completed_at")] + require(all(isinstance(v, str) and + re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ", v) + for v in timestamps) and + timestamps[0] <= timestamps[1] <= timestamps[2], + "original survey artifact not uploaded inside successful job") + files = inspect_zip(survey_bytes) + require(SURVEY_MEMBER in files and + 0 < len(files[SURVEY_MEMBER]) <= 4*1024*1024, + "survey artifact lost the real enumerated event choices") + survey_raw = files[SURVEY_MEMBER] + try: + survey = strict_json_object(survey_raw) + except (TypeError, ValueError) as exc: + raise ChoiceDeliveryError("survey source is invalid JSON") from exc + require(isinstance(survey, dict) and + survey.get("format") == FORMAT_REPORT and + survey.get("status") == "ACTUAL_CAPTURE_OPTIONS_UNREVIEWED" and + survey.get("source_head") == head and + survey.get("original_legacy_case_count") == 20 and + survey.get("all_cases_have_candidates") is True and + survey.get("independent_case_review") is False and + survey.get("mathematical_acceptance") is False and + isinstance(survey.get("cases"), list) and + len(survey["cases"]) == 20, + "incomplete or self-approved legacy observed-events survey") + bycase = {} + for entry in survey["cases"]: + require(isinstance(entry, dict) + and set(entry) == {"case_id", "options"} + and isinstance(entry["case_id"], str) + and entry["case_id"] not in bycase + and isinstance(entry["options"], list) + and bool(entry["options"]), + "real survey has duplicate/missing legacy observations") + bycase[entry["case_id"]] = entry["options"] + return bycase, digest(survey_raw), artifact["created_at"] + + +def comment_choices(head, comment_id, policy, survey_zip_sha256, + survey_member_sha256, survey_artifact_id, survey_time, + candidates, *, live, pr_number=113): + require(type(comment_id) is int and comment_id > 0, + "reviewer comment must be a real GitHub comment ID") + comment = live("/issues/comments/" + str(comment_id)) + pr = live("/pulls/" + str(pr_number)) + require(isinstance(comment, dict) and + comment.get("id") == comment_id and + comment.get("issue_url") == + "https://api.github.com/repos/" + REPOSITORY + "/issues/" + str(pr_number) + and isinstance(pr, dict) and + pr.get("number") == pr_number and + pr.get("state") == "open" and + pr.get("merged") is False and + (pr.get("head") or {}).get("sha") == head, + "reviewer input belongs to wrong PR or stale head") + author = (comment.get("user") or {}).get("login") + # Issue comments are mutable. An edited old comment is NOT the same + # immutable reviewer selection originally submitted after the producer + # artifact existed. Refuse edits rather than accept a historical ID that + # now points at different choices. + require(isinstance(author, str) and author != + (pr.get("user") or {}).get("login") and + isinstance(comment.get("created_at"), str) and + comment["created_at"] > survey_time and + isinstance(comment.get("updated_at"), str) and + comment["updated_at"] == comment["created_at"], + "review comment is early, post-edited, or not independent") + eligible = [entry for entry in policy["reviewers"] + if isinstance(entry, dict) and + entry.get("login") == author + and entry.get("approved_by") == "HMarcusWH" + and entry.get("independence_disclosed") is True] + require(len(eligible) == 1, "commenter is not an authorized independent reviewer") + lines = [line.strip().removeprefix(PREFIX).strip() + for line in (comment.get("body") or "").splitlines() + if line.strip().startswith(PREFIX)] + require(len(lines) == 1, + "exactly one JSON reviewed choice line required from GitHub") + try: + att = strict_json_object(lines[0].encode("utf-8")) + except (TypeError, ValueError) as exc: + raise ChoiceDeliveryError("reviewer selection is invalid JSON") from exc + require(isinstance(att, dict) and set(att) == ATTEST_FIELDS + and att.get("format") == REVIEW_FORMAT + and att.get("status") == "RELEVANCE_CHOICES_ONLY_NOT_CASE_ACCEPTANCE" + and att.get("head_sha") == head + and att.get("survey_zip_sha256") == survey_zip_sha256 + and att.get("survey_member_sha256") == survey_member_sha256 + and att.get("observation_artifact_id") == survey_artifact_id + and isinstance(att.get("cases"), list) + and len(att["cases"]) == 20, + "review choices are not bound to real source/head/survey ZIP") + decisions = {} + for row in att["cases"]: + require(isinstance(row, dict) and set(row) == SELECTION_FIELDS + and isinstance(row.get("case_id"), str) + and row["case_id"] in candidates + and row["case_id"] not in decisions + and type(row.get("test_index")) is int + and type(row.get("producing_artifact_id")) is int, + "reviewer supplied malformed or repeated original-case choice") + matched = [option for option in candidates[row["case_id"]] + if all(option.get(k) == val for k, val in row.items())] + require(len(matched) == 1, "reviewer choice not in observed original capture") + decisions[row["case_id"]] = row + require(set(decisions) == set(candidates), + "not all twenty original legacy cases selected") + detached = { + "format": FORMAT_DECISION, + "head_sha": head, + "status": "DETACHED_EVENT_CHOICES_UNREVIEWED", + "independent_case_review": False, + "mathematical_acceptance": False, + "cases": [decisions[cid] for cid in sorted(decisions)], + } + # Apply the existing original case/method/actual-event binder as-is. + decision_to_selection(head, candidates, detached) + return detached, {"review_comment_id": comment_id, + "reviewer": author, "survey_zip_sha256": survey_zip_sha256, + "survey_member_sha256": survey_member_sha256, + "review_comment_created_at": comment["created_at"], + "review_comment_body_sha256": + digest(comment["body"].encode("utf-8"))} + + +def deliver(head, ids, original_zip, comment_id, *, git=real_git, + live=real_live, env=None): + require(isinstance(head, str) and SHA40.fullmatch(head) + and git("rev-parse", "HEAD") == head, + "runtime choice delivery must use frozen actual H") + target = authenticated_reviewer_target(head, env=env, git=git, live=live) + reviewer = reviewer_policy(head, git=git) + candidates, observed_hash, observed_at = authenticate_survey( + head, ids, original_zip, live=live, branch=target["branch"]) + decisions, reviewer_info = comment_choices( + head, comment_id, reviewer, digest(original_zip), observed_hash, + ids["artifact_id"], observed_at, candidates, live=live, + pr_number=target["pr_number"]) + return { + "selection": decisions, + "diagnostic": { + "format": "Permansson.PR113.LegacyChoiceDelivery.v1", + "status": "AUTHENTIC_SURVEY_AND_REVIEWER_CHOICE_DELIVERED_UNCERTIFIED", + "head_sha": head, + "reviewed_pr_number": target["pr_number"], + "reviewed_branch": target["branch"], + "original_survey_run_id": ids["run_id"], + "original_survey_job_id": ids["job_id"], + "original_survey_artifact_id": ids["artifact_id"], + **reviewer_info, + "reviewer_selection_count": 20, + "mathematical_acceptance": False, + "publication_authorized": False, + "source_mutated": False, + } + } + + +def main(argv=None): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--head", required=True) + p.add_argument("--survey-zip", type=Path, required=True) + for label in ("run-id", "run-attempt", "job-id", "check-run-id", + "artifact-id", "review-comment-id"): + p.add_argument("--" + label, required=True, type=int) + p.add_argument("--out", type=Path, required=True) + a = p.parse_args(argv) + try: + dest = detached_destination(a.out, ROOT) + require(not ROOT.resolve().is_relative_to(dest), + "detached reviewer choice folder must be new and external") + survey_file = detached_destination(a.survey_zip, ROOT, must_exist=True) + require(0 < survey_file.stat().st_size <= 256 * 1024 * 1024, + "real survey original ZIP missing or oversized") + raw = survey_file.read_bytes() + report = deliver( + a.head, + {"run_id": a.run_id, "run_attempt": a.run_attempt, + "job_id": a.job_id, "check_run_id": a.check_run_id, + "artifact_id": a.artifact_id}, + raw, a.review_comment_id) + dest.mkdir(parents=True, exist_ok=False) + (dest / "reviewed_legacy20_runtime_event_choices.json").write_text( + json.dumps(report["selection"], sort_keys=True, indent=2) + "\n") + (dest / "choice_transport.json").write_text( + json.dumps(report["diagnostic"], sort_keys=True, indent=2) + "\n") + print(json.dumps(report["diagnostic"], sort_keys=True)) + return 0 + except (ChoiceDeliveryError, OSError, TypeError, ValueError, + KeyError) as exc: + print("LEGACY EVENT DELIVERY BLOCKED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_legacy20_runtime_resolution.py b/verification/research/pr113_legacy20_runtime_resolution.py new file mode 100644 index 00000000..5235864b --- /dev/null +++ b/verification/research/pr113_legacy20_runtime_resolution.py @@ -0,0 +1,318 @@ +"""Resolve legacy runtime compiler events *after* original exact-H CI. + +The frozen source can contain a case -> named negative-test -> theorem-provider +intent, but CANNOT contain run/artifact/event IDs that only materialize after +frozen-H GitHub jobs. This adapter authenticates all original 21 GitHub ZIPs, +enumerates actual intended semantic-negative compiler events, and accepts only +an explicit detached 20-case event decision. No semantic reviewer is forged. + +Relevance of a false statement to the case remains an independent mathematical +review obligation. A rejected parser/unknown identifier/timeout is NOT an +acceptable semantic negative. +""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path, PurePosixPath +import re +import sys + +from final_closure_complete_lean_evidence import ROOT, git_head +from final_closure_legacy_semantic_evidence import original_case_map +from final_closure_producer_bytes import authenticate +from final_closure_producer_v2_bridge import verified_producer_index +from pr113_v31_execution_integrity import exact_source, source_clean_negative +from pr113_boundary_io import strict_json_object, detached_destination + +FORMAT_INTENT="Permansson.PR113.Legacy20StaticSourceIntent.v1" +FORMAT_DECISION="Permansson.PR113.Legacy20DetachedEventDecision.v1" +FORMAT_SELECTION="Permansson.PR113.LegacyReplaySelection.v1" +FORMAT_REPORT="Permansson.PR113.Legacy20ObservedEventsCandidate.v1" +EVENT=re.compile(r"^event-[0-9]+-[0-9]{6}$") +TEST=re.compile(r"^test_[A-Za-z0-9_]+$") +SHA40=re.compile(r"^[a-f0-9]{40}$") + + +class RuntimeLegacyError(ValueError): + pass + +def require(ok,reason): + if not ok:raise RuntimeLegacyError(reason) + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + +def committed_selection(head, path): + """Only static, criterion-selected source intent can precede source H.""" + requested = Path(path) + require(".." not in requested.parts, + "pre-freeze legacy source cannot use parent traversal") + absolute = requested.absolute() + require(not any(p.is_symlink() for p in (absolute, *absolute.parents)), + "pre-freeze legacy source cannot use symlinked ancestors") + path = absolute.resolve(strict=True) + require(path.is_relative_to(ROOT.resolve()) and + path.as_posix().endswith( + "/research/paper_closures/post_merge_112/legacy20_source_intent_v31.json"), + "pre-freeze legacy route intent must be source-controlled") + source=exact_source(head,ROOT) + rel=path.relative_to(ROOT.resolve()).as_posix() + raw=source.blob(rel) + require(path.is_file() and path.read_bytes()==raw, + "working source intent differs from exact-H Git blob") + return strict_json_object(raw), source + +def parse_intent(payload, head, source): + byid,legacy=original_case_map(head) + required=set(legacy) + require(isinstance(payload,dict) and + payload.get("format")==FORMAT_INTENT and + payload.get("source_head_binding")=="ACTUAL_CI_HEAD_AT_RUN" and + payload.get("review_status")=="REQUIRES_INDEPENDENT_MATHEMATICAL_REVIEW" and + isinstance(payload.get("cases"),list) and + len(payload["cases"])==20 and len(required)==20, + "static 20-case legacy intent malformed or claims acceptance") + mapped={} + for record in payload["cases"]: + require(isinstance(record,dict) and + set(record)=={"case_id","test_path","test_method","original_provider"}, + "invalid pre-freeze legacy selection keys") + cid=record["case_id"] + require(cid in required and cid not in mapped and + isinstance(record["test_path"],str) and + isinstance(record["test_method"],str) and + TEST.fullmatch(record["test_method"]) and + isinstance(record["original_provider"],str), + "legacy static intent missing/duplicates original case") + criteria=byid[cid] + require(any(isinstance(m,dict) and + m.get("role")=="SEMANTIC_NEGATIVE" and + m.get("path")==record["test_path"] and + m.get("method")==record["test_method"] + for m in criteria.get("test_method_candidates",())), + "original legacy negative test does not belong to case") + provider=record["original_provider"] + require(provider.startswith("PermanssonResearch/") and + provider.endswith(".lean") and + ".." not in PurePosixPath(provider).parts and + provider in {x.get("path") for x in + (criteria.get("historical_proof_declarations",())+ + criteria.get("new_claim_source_candidates",())) + if isinstance(x,dict)}, + "legacy provider not owned by original criterion") + source.blob(provider); source.blob(record["test_path"]) + mapped[cid]=record + require(set(mapped)==required, "original 20-case legacy intent incomplete") + return mapped + +def original_detached_directory(path): + """Reject lexical traversal and every symlink ancestor before resolution.""" + requested=Path(path) + require(".." not in requested.parts, + "original producer evidence directory traverses parent") + absolute=requested.absolute() + require(not any(p.is_symlink() for p in (absolute,*absolute.parents)), + "original producer directory has symlink ancestor") + canonical=absolute.resolve(strict=True) + require(canonical.is_dir() and + not canonical.is_relative_to(ROOT.resolve()) and + not ROOT.resolve().is_relative_to(canonical), + "original producer evidence directory is not detached") + return canonical + +def original21(catalog_dir, zips_dir, head): + catalog=original_detached_directory(catalog_dir) + directory=original_detached_directory(zips_dir) + raw=(catalog/"unreviewed_producer_catalog.json").read_bytes() + require(00, "missing original GH artifact ID") + p=directory/(str(aid)+".zip") + require(p.is_file() and not p.is_symlink() and + p.stat().st_size<=64*1024*1024, + "original GH source ZIP not available") + raw=p.read_bytes() + require(sha(raw)==row.get("artifact_sha256"), + "downloaded original producer ZIP digest changed") + zips[aid]=raw + auth=authenticate(head,rows,zips) + require(auth.get("producer_count")==21 and + auth.get("head_sha")==head, + "original 21 source producers not authenticated") + legacy_artifacts={ + x["artifact_id"] for (kind,shard),x in indexed.items() + if kind=="legacy" + } + require(len(legacy_artifacts)==8, + "missing full 8-shard original historical legacy observations") + return auth,legacy_artifacts + +def enumerate_events(head,intents,source,originals,legacy_artifacts): + result={} + for cid in sorted(intents): + target=intents[cid] + method=target["test_method"] + test_source=source.blob(target["test_path"]) + expected_hash=sha(test_source) + options=[] + for aid in sorted(legacy_artifacts): + members=originals["producers"][aid]["members"] + prefix=cid+"/" + manifests=[ + (name,data) for name,data in members.items() + if name.startswith(prefix+"test-") and + name.endswith("/unreviewed_test_manifest.json") + ] + for name,raw in manifests: + record=json.loads(raw) + if (record.get("case_id")!=cid or + record.get("test_file")!=target["test_path"] or + record.get("source_test_sha256")!=expected_hash or + method not in record.get("selected_methods",()) or + record.get("test_exit_code")!=0 or + record.get("status")!="EXECUTED_UNREVIEWED_NOT_AUTHENTICATED"): + continue + base=name[:-len("unreviewed_test_manifest.json")] + m=re.fullmatch(re.escape(cid)+r"/test-([0-9]{2})/",base) + require(m is not None, "legacy captured test index malformed") + test_index=int(m.group(1)) + for obs_path,meta_raw in members.items(): + if (not obs_path.startswith(base+"raw_compiler/") or + not obs_path.endswith("/observation.json")): + continue + evt=obs_path[len(base+"raw_compiler/"):-len("/observation.json")] + if not EVENT.fullmatch(evt): + continue + o=json.loads(meta_raw) + if (o.get("format")!="Permansson.LegacyLeanCompilerObservation.v1" or + o.get("case_id_unverified")!=cid or + o.get("selected_method_unverified")!=method or + o.get("status")!="RAW_UNREVIEWED" or + type(o.get("returncode")) is not int or + o["returncode"]==0): + continue + false=members.get(base+"raw_compiler/"+evt+"/source.lean") + stderr=members.get(base+"raw_compiler/"+evt+"/stderr.log") + if not isinstance(false,bytes) or not isinstance(stderr,bytes): + continue + if sha(false)!=o.get("source_sha256") or sha(stderr)!=o.get("stderr_sha256"): + continue + try: + source_clean_negative(stderr) + except ValueError: + continue + options.append({ + "case_id":cid,"test_index":test_index, + "test_method":method, + "original_provider":target["original_provider"], + "observation_id":evt, + "producing_artifact_id":aid, + "observation_sha256":sha(meta_raw), + "false_input_sha256":sha(false), + "unreviewed":True, + }) + unique={(r["test_index"],r["observation_id"], + r["producing_artifact_id"]) for r in options} + require(len(unique)==len(options), + "duplicate historical capture identity across source artifacts") + result[cid]=sorted(options,key=lambda x:( + x["producing_artifact_id"],x["test_index"],x["observation_id"])) + return result + +def decision_to_selection(head, candidates, raw): + require(isinstance(raw,dict) and + raw.get("format")==FORMAT_DECISION and + raw.get("head_sha")==head and + raw.get("status")=="DETACHED_EVENT_CHOICES_UNREVIEWED" and + raw.get("independent_case_review") is False and + raw.get("mathematical_acceptance") is False and + isinstance(raw.get("cases"),list) and + len(raw["cases"])==20, + "legacy detached reviewer event choices incomplete or self-approved") + chosen={} + for row in raw["cases"]: + require(isinstance(row,dict) and + set(row)=={"case_id","test_index","observation_id", + "producing_artifact_id"}, + "legacy runtime event selection contains unsupported keys") + cid=row["case_id"] + require(cid in candidates and cid not in chosen, + "duplicate or foreign legacy runtime choice") + matched=[x for x in candidates[cid] + if all(x.get(k)==v for k,v in row.items())] + require(len(matched)==1, + "legacy runtime choice was not a genuine observed semantic source") + selected=matched[0] + chosen[cid]={key:selected[key] for key in ( + "case_id","test_index","test_method","original_provider", + "observation_id","producing_artifact_id")} + require(set(chosen)==set(candidates), + "legacy source runtime selection does not cover 20 cases") + return { + "format":FORMAT_SELECTION,"cases":[chosen[cid] for cid in sorted(chosen)], + } + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--head",required=True) + p.add_argument("--intent",required=True,type=Path) + p.add_argument("--catalog-dir",required=True,type=Path) + p.add_argument("--producer-zips",required=True,type=Path) + p.add_argument("--detached-event-choice",type=Path) + p.add_argument("--out",required=True,type=Path) + a=p.parse_args(argv) + try: + require(isinstance(a.head,str) and SHA40.fullmatch(a.head) and + git_head()==a.head, + "must use genuine current exact H Git source") + dest=detached_destination(a.out, ROOT) + require(not ROOT.resolve().is_relative_to(dest), + "legacy event choices output must not contain source") + source_intent,source=committed_selection(a.head,a.intent) + intents=parse_intent(source_intent,a.head,source) + original,legacies=original21(a.catalog_dir,a.producer_zips,a.head) + options=enumerate_events(a.head,intents,source,original,legacies) + report={ + "format":FORMAT_REPORT, + "status":"ACTUAL_CAPTURE_OPTIONS_UNREVIEWED" if + all(options.values()) else "INCOMPLETE_ORIGINAL_CAPTURE_OPTIONS", + "source_head":a.head, + "original_legacy_case_count":20, + "cases":[{"case_id":cid,"options":options[cid]} + for cid in sorted(options)], + "all_cases_have_candidates":all(bool(options[cid]) for cid in options), + "independent_case_review":False, + "mathematical_acceptance":False, + "source_commit_changed":False, + } + # Every report is nonauthoritative; even one synthetic choice is banned. + dest.mkdir(parents=True,exist_ok=False) + (dest/"unreviewed_legacy20_candidates.json").write_text( + json.dumps(report,sort_keys=True,indent=2)+"\n") + if a.detached_event_choice is not None: + external=detached_destination( + a.detached_event_choice, ROOT, must_exist=True) + require(0 len(original), + "legacy false mutant is not appended to its original committed provider") + demand(original_receipt.get("replayed_false_source_sha256") + == digest(appended_false) + and mutation.get("head_sha") == head + and mutation.get("test_path") == test_path + and mutation.get("test_name") == method + and mutation.get("source_sha256") == digest(test_bytes) + and mutation.get("original_lean_source_path") == provider + and mutation.get("original_lean_source_sha256") == digest(original), + "legacy compile receipt does not bind named test/provider source") + demand(original_receipt.get("head_sha") == head + and original_receipt.get("case_id") == owner_id + and mutation.get("case_ids") == [owner_id] + and mutation.get("claim_ids") == [], + "legacy source receipt uses a borrowed case or claim owner") + + expected_sha = original_receipt.get("legacy_test_manifest_sha256") + metadata_sha = original_receipt.get("raw_observation_sha256") + captured_sha = original_receipt.get("captured_false_source_sha256") + demand(all(isinstance(x, str) and SHA256.fullmatch(x) for x in ( + expected_sha, metadata_sha, captured_sha)), + "legacy capture manifest/observation/source SHA-256 missing") + artifact_id = wrapper.get("historical_producing_artifact_id") + demand(type(artifact_id) is int and artifact_id > 0 + and isinstance(authenticated_original, dict) + and authenticated_original.get("status") == + "REAL_PRODUCING_BYTES_UNREVIEWED" + and authenticated_original.get("head_sha") == head, + "legacy capture not bound to authenticated exact-H original producers") + produced = authenticated_original.get("producers", {}).get(artifact_id) + demand(isinstance(produced, dict) + and produced.get("kind") == "legacy" + and produced.get("head_sha") == head + and type(produced.get("check_run_id")) is int, + "legacy original observation borrowed a different producer shard") + members = produced.get("members") + demand(isinstance(members, dict), "legacy original producing ZIP members missing") + # Precisely identify one real case/test source manifest by SHA, not by + # a caller-supplied member index. Ambiguity fails closed. + options = [ + (name, raw) + for name, raw in members.items() + if isinstance(name, str) + and name.startswith(owner_id + "/test-") + and name.endswith("/unreviewed_test_manifest.json") + and type(raw) is bytes + and digest(raw) == expected_sha + ] + demand(len(options) == 1, "selected original historical test capture missing or duplicated") + name, raw = options[0] + captured = json_record(raw) + demand(captured.get("format") == + "Permansson.LegacyCaseTestExecutionCandidate.v1" + and captured.get("case_id") == owner_id + and captured.get("status") == "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED" + and captured.get("test_file") == test_path + and captured.get("source_test_sha256") == digest(test_bytes) + and captured.get("test_exit_code") == 0 + and captured.get("independent_review") is False + and captured.get("release_approved") is False + and method in captured.get("selected_methods", ()), + "original GitHub producing test manifest does not match replayed route") + prefix = name[:-len("unreviewed_test_manifest.json")] + observation_member = prefix + "raw_compiler/" + event + "/observation.json" + source_member = prefix + "raw_compiler/" + event + "/source.lean" + metadata = members.get(observation_member) + false_original = members.get(source_member) + demand(type(metadata) is bytes and digest(metadata) == metadata_sha + and type(false_original) is bytes and digest(false_original) == captured_sha, + "original compiler observation/false-source bytes do not match GitHub ZIP") + observed = json_record(metadata) + demand(observed.get("format") == + "Permansson.LegacyLeanCompilerObservation.v1" + and observed.get("case_id_unverified") == owner_id + and observed.get("selected_method_unverified") == method + and observed.get("source_sha256") == captured_sha, + "captured event belongs to another case/method/source") + # A syntactically successful original-source mutation does not establish + # scientific relevance; independent review is a separate requirement. + return { + "case_id": owner_id, "selected_method": method, + "selected_test_class": cls, "source_provider": provider, + "original_producer_artifact_id": artifact_id, + "original_producer_check_run_id": produced["check_run_id"], + "observation_id": event, + "exact_h_source_and_original_capture_bound": True, + "independent_mathematical_review": False, + } diff --git a/verification/research/pr113_lifecycle_source_v3.py b/verification/research/pr113_lifecycle_source_v3.py new file mode 100644 index 00000000..bc5c8b42 --- /dev/null +++ b/verification/research/pr113_lifecycle_source_v3.py @@ -0,0 +1,183 @@ +"""Genuine required-job source routing for PR113, CERT, and later main. + +Only classifies independently authenticated Git/GitHub source identity. +It never approves infrastructure, security review, 45/11 mathematics, merge, +postseal, or publication. Historical migrations need a separate verifier. +""" +from __future__ import annotations + +import hashlib +import json +import os +from pathlib import Path +import re +import sys + +from final_closure_authenticate import git as real_git, live as real_live +from pr113_boundary_io import strict_json_object, detached_destination +from release_target import B113, REPOSITORY + +SHA = re.compile(r"^[0-9a-f]{40}$") +JOBS = {"research": "Research Lean", + "archive-reproducibility": "Research integrity"} +FORMAT = "Permansson.PR113.CurrentRequiredLifecycleSource.v3" + + +class LifecycleError(ValueError): + pass + + +def require(ok, why): + if not ok: + raise LifecycleError(why) + + +def signed(value): + return isinstance(value, str) and SHA.fullmatch(value) is not None + + +def merged_113(*, git, live): + """Verify the live original PR plus its ordered immutable B113/H113/M113.""" + pr = live("/pulls/113") + require(isinstance(pr, dict) and pr.get("number") == 113 + and pr.get("merged") is True and pr.get("state") == "closed", + "initial historical infrastructure PR has not been merged") + H = (pr.get("head") or {}).get("sha") + M = pr.get("merge_commit_sha") + require(signed(H) and signed(M) + and git("cat-file", "-t", H) == "commit" + and git("cat-file", "-t", M) == "commit" + and git("show", "-s", "--format=%P", M).split() == [B113, H] + and git("rev-parse", M + "^{tree}") == + git("rev-parse", H + "^{tree}"), + "reviewed exact B113/H113/M113 Git parent/tree topology invalid") + return H, M + + +def actual_source(*, env=None, event_bytes=None, git=real_git, live=real_live): + env = os.environ if env is None else env + require(env.get("GITHUB_ACTIONS") == "true" + and env.get("GITHUB_REPOSITORY") == REPOSITORY + and env.get("GITHUB_JOB") in JOBS + and env.get("GITHUB_WORKFLOW") == JOBS[env["GITHUB_JOB"]], + "genuine original Research/Archive required job absent") + event_kind = env.get("GITHUB_EVENT_NAME") + current = env.get("GITHUB_SHA") + require(event_kind in ("pull_request", "push") + and signed(current) + and git("rev-parse", "HEAD") == current + and git("cat-file", "-t", current) == "commit", + "actual required job checkout or GitHub head not authentic") + try: + run_id = int(env.get("GITHUB_RUN_ID", "")) + attempt = int(env.get("GITHUB_RUN_ATTEMPT", "")) + except (ValueError, TypeError) as exc: + raise LifecycleError("original run identity absent") from exc + require(run_id > 0 and attempt > 0, "original GitHub attempt invalid") + if event_bytes is None: + path = Path(env.get("GITHUB_EVENT_PATH", "")) + require(path.is_file() and not path.is_symlink() + and 0 < path.stat().st_size <= 8*1024*1024, + "authentic required job event file missing") + event_bytes = path.read_bytes() + event = strict_json_object(event_bytes, max_bytes=8*1024*1024) + require((event.get("repository") or {}).get("full_name") == REPOSITORY, + "event repository does not match required job") + output = { + "format": FORMAT, + "source_commit_sha": current, + "github_job": env["GITHUB_JOB"], + "github_run_id": run_id, + "github_run_attempt": attempt, + "event_sha256": hashlib.sha256(event_bytes).hexdigest(), + "historical_migration_accepted": False, + "required_ci_accepted": False, + "mathematical_acceptance": False, + "owner_merge_authorized": False, + "publication_authorized": False, + } + if event_kind == "pull_request": + try: + number = event.get("number") + pr = event.get("pull_request") + require(type(number) is int and number >= 1 and isinstance(pr, dict), + "no original PR number/event") + base, head = (pr.get("base") or {}), (pr.get("head") or {}) + B, H = base.get("sha"), head.get("sha") + require(signed(B) and signed(H) + and base.get("ref") == "main" + and (base.get("repo") or {}).get("full_name") == REPOSITORY + and (head.get("repo") or {}).get("full_name") == REPOSITORY + and B != H + and env.get("GITHUB_REF") == "refs/pull/" + str(number) + "/merge" + and git("show", "-s", "--format=%P", current).split() == [B, H] + and git("rev-parse", current + "^{tree}") == + git("rev-parse", H + "^{tree}"), + "not a genuine current same-repo B/H test-merge source") + remote = live("/branches/main") + require((remote.get("commit") or {}).get("sha") == B, + "original base differs from current remote main") + current_pr = live("/pulls/" + str(number)) + require(current_pr.get("number") == number + and current_pr.get("merged") is False + and (current_pr.get("head") or {}).get("sha") == H + and (current_pr.get("base") or {}).get("sha") == B, + "remote PR has stale or unrelated B/H identity") + if number == 113: + require(B == B113 + and head.get("ref") == "research/final-mathematical-closure", + "PR113 old immutable source/branch changed") + output["phase"] = "initial_113_pr" + output["infrastructure_head_sha"] = H + output["infrastructure_merge_sha"] = None + else: + old_H, old_M = merged_113(git=git, live=live) + require(git("merge-base", "--is-ancestor", old_M, B) == "", + "new development/CERT base does not inherit actual M113") + output["phase"] = "main_descendant_pr" + output["infrastructure_head_sha"] = old_H + output["infrastructure_merge_sha"] = old_M + output.update({"pr_number": number, "base_sha": B, + "head_sha": H, "synthetic_sha": current}) + except (KeyError, TypeError, AttributeError) as exc: + raise LifecycleError("original PR source lacks required structure") from exc + else: + require(env.get("GITHUB_REF") == "refs/heads/main" + and event.get("ref") == "refs/heads/main" + and event.get("after") == current + and (live("/branches/main").get("commit") or {}).get("sha") == current, + "current main push is not source-bound") + old_H, old_M = merged_113(git=git, live=live) + require(git("merge-base", "--is-ancestor", old_M, current) == "", + "main push is not a descendant of the authorized initial migration") + output["phase"] = ("initial_113_main" if current == old_M + else "main_descendant_push") + output.update({"pr_number": None, "base_sha": B113, + "head_sha": current, "synthetic_sha": None, + "infrastructure_head_sha": old_H, + "infrastructure_merge_sha": old_M}) + return output + + +def main(argv=None): + import argparse + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--out", required=True, type=Path) + args = ap.parse_args(argv) + try: + report = actual_source() + out = detached_destination(args.out, Path(__file__).resolve().parents[2]) + out.parent.mkdir(parents=True, exist_ok=True) + with out.open("x", encoding="utf-8") as f: + json.dump(report, f, sort_keys=True, indent=2) + f.write("\n") + print(json.dumps({"status": report["phase"], + "head": report["source_commit_sha"]})) + return 0 + except (LifecycleError, OSError, KeyError, TypeError, ValueError) as exc: + print("VERSIONED_REQUIRED_SOURCE_BLOCKED", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_mandatory_v31.py b/verification/research/pr113_mandatory_v31.py new file mode 100644 index 00000000..e7f8bb29 --- /dev/null +++ b/verification/research/pr113_mandatory_v31.py @@ -0,0 +1,164 @@ +"""Mandatory original-v3 + v3.1 executed-source gate (read-only). + +No JSON label here grants review or mathematical certification. Before a +strict premerge/postmerge acceptance result, this routine independently +reattributes every new owner to authentic exact-H GitHub runs, jobs, checks, +workflow Git blobs and original downloaded artifact ZIPs, then re-examines +the raw positive and intended false-source compiler/test bytes. The original +21 producer ZIPs remain mandatory and independently authenticated. +""" +from __future__ import annotations + +from collections import Counter +import json +from pathlib import Path + +from final_closure_authenticate import git +from final_closure_case_receipts import ( + ROOT, authenticate_v3_sources, EvidenceError, +) +from final_closure_legacy_semantic_evidence import original_case_map +from final_closure_extra_producers_v31 import ( + candidate_registry, expected_controls, authenticated_extra_controls, + ControlProducerError, +) +from final_closure_mutation_evidence import committed_file_bytes +from pr113_boundary_io import strict_json_object, detached_destination +from pr113_v31_execution_integrity import ( + exact_source, validate_executed_controls, RealControlError, +) + + +class MandatoryV31Error(ValueError): + pass + + +def need(condition, message): + if not condition: + raise MandatoryV31Error(message) + + +def external(path, *, directory=False): + src = Path(path) + need(".." not in src.parts, + "parent-traversing owner evidence path") + absolute = src.absolute() + need(not any(part.is_symlink() for part in (absolute, *absolute.parents)), + "symlinked owner-control evidence or ancestor") + src = absolute.resolve(strict=True) + need(not src.is_relative_to(ROOT.resolve()) and + not ROOT.resolve().is_relative_to(src) and + (src.is_dir() if directory else src.is_file()), + "control source files must be detached from the Git checkout") + return src + + +def read_object(path): + raw = external(path).read_bytes() + need(0 < len(raw) <= 4 * 1024 * 1024, "oversized or missing control JSON") + obj = strict_json_object(raw) + need(isinstance(obj, dict), "malformed control producer JSON") + return obj + + +def authenticate_all_76(head, original_receipts, original_members, *, + additional_contract, additional_zips, + workflow_blob_candidates): + """Return only real authenticated *execution* provenance, never approval. + + Call after verify_premerge() or verify_merged_v2() authenticates exact H, + required source-head and/or M checks and independent review scopes. + This routine works both from a genuine H checkout and later genuine M + whose verified source tree equals the signed H tree. + """ + need(isinstance(head, str) and len(head) == 40 and + isinstance(original_receipts, dict) and + original_receipts.get("head_sha") == head and + original_receipts.get("format") == + "Permansson.FinalClosureDetachedReceipts.v3" and + isinstance(original_members, dict), + "full-source gate requires real original exact-H v3 members") + need(git("cat-file", "-t", head) == "commit", + "original reviewed H Git object unavailable") + try: + authentic_original = authenticate_v3_sources( + original_receipts, head, original_members) + need(authentic_original.get("status") == + "REAL_PRODUCING_BYTES_UNREVIEWED" and + authentic_original.get("head_sha") == head and + authentic_original.get("producer_count") == 21, + "the 21 original real source-control artifact ZIPs are incomplete") + by_case, legacy = original_case_map(head) + claim_path = ROOT / ("research/paper_closures/post_merge_112/" + "claim_declaration_map.json") + claims = strict_json_object(committed_file_bytes( + head, claim_path, root=ROOT)) + claim_ids = [r.get("claim_id") for r in claims.get("claims", [])] + expected = expected_controls( + list(by_case), legacy, claim_ids) + need(len(expected) == 76, + "original 45 case/20 legacy/11 claim source ownership corrupted") + proposal = read_object(additional_contract) + jobs = candidate_registry(proposal, head, expected) + root = external(additional_zips, directory=True) + names = {str(j["artifact_id"]) + ".zip" for j in jobs} + need({p.name for p in root.iterdir()} == names, + "extra producer ZIP directory has a missing or foreign artifact") + bytes_by_id, total = {}, 0 + for job in jobs: + path = external(root / (str(job["artifact_id"]) + ".zip")) + raw = path.read_bytes() + need(0 < len(raw) <= 64 * 1024 * 1024, + "missing or oversized original source-control ZIP") + total += len(raw) + need(total <= 256 * 1024 * 1024, + "original source-control ZIPs exceed bounded input") + bytes_by_id[job["artifact_id"]] = raw + workflow_blobs = read_object(workflow_blob_candidates) + attributed = authenticated_extra_controls( + head, proposal, bytes_by_id, + original_producers=authentic_original, + original_bundle_sha256=original_receipts.get( + "evidence_bundle_sha256"), + expected=expected, + reviewed_workflow_blobs=workflow_blobs) + execution = validate_executed_controls( + head, proposal, bytes_by_id, source=exact_source(head, ROOT), + original_producers=authentic_original) + need(attributed.get("head_sha") == head and + attributed.get("status") == + "ALL_ADDITIONAL_CONTROLS_SOURCE_ATTRIBUTED_UNREVIEWED" and + len(attributed.get("source_owned_controls", [])) == 76 and + execution.get("status") == + "ACTUAL_CONTROL_BYTES_REVALIDATED_UNREVIEWED" and + execution.get("source_owner_count") == 76 and + execution.get("claim_positive_negative_directions") == 22 and + execution.get("canonical_routes_bound") is True and + execution.get("legacy_original_captures_bound") is True and + attributed.get("schema_security_approved") is False and + attributed.get("independent_mathematical_review") is False, + "additional true/false source evidence is incomplete or self-approved") + seen = {(row["kind"], row["owner_id"]) + for row in attributed["source_owned_controls"]} + need(seen == expected, "authenticated producer owner partition differs") + except (EvidenceError, ControlProducerError, RealControlError, + KeyError, TypeError, OSError, ValueError) as exc: + raise MandatoryV31Error( + "genuine source-control 76-owner authentication failed: " + + str(exc)) from exc + return { + "format": "Permansson.PR113.StrictAdditionalSourceGate.v3.1", + "status": "SOURCE_EXECUTION_AUTHENTICATED_REVIEW_SEPARATE", + "head_sha": head, + "original_artifacts": 21, + "additional_owners": 76, + "original_case_positives": 45, + "legacy_case_negatives": 20, + "claim_pairs": 11, + "claim_directions": 22, + "execution_log_members_verified": + execution["execution_log_members_checked"], + "independent_security_approval": False, + "independent_math_approval": False, + "publication_authorized": False, + } diff --git a/verification/research/pr113_original21_live_catalog.py b/verification/research/pr113_original21_live_catalog.py new file mode 100644 index 00000000..e46fa367 --- /dev/null +++ b/verification/research/pr113_original21_live_catalog.py @@ -0,0 +1,244 @@ +"""Discover exact-H original 4+9+8 producer ZIPs AFTER they have run. + +Original GitHub job/run/check/artifact IDs cannot be committed before source +freeze H: those IDs only exist after CI executes at H. This stage constructs a +DETACHED runtime catalog, authenticates actual successful GitHub producer +checks, downloads original ZIP bytes and records their immutable digests. + +This never changes H, accepts mathematical claims or grants a security review. +A failed/pending/old-head latest producer blocks the whole collection. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import sys +import urllib.error +import urllib.request +from urllib.parse import urlsplit + +from final_closure_authenticate import live, paginate, AuthenticationError +from final_closure_candidate_artifact_auth import KINDS +from final_closure_complete_lean_evidence import ROOT, git_head + +FORMAT="Permansson.PR113.ExternalProducerCatalog.v1" +REPO="HMarcusWH/PermanssonLean" +HEAD=re.compile(r"^[a-f0-9]{40}$") +RUN_URL=re.compile(r"^https://github.com/HMarcusWH/PermanssonLean/actions/runs/([0-9]+)(?:/job/([0-9]+))?$") +MAX_ZIP=64*1024*1024 +MAX_TOTAL=256*1024*1024 + + +class DiscoveryError(ValueError): + pass + + +def require(ok,reason): + if not ok:raise DiscoveryError(reason) + + +def sha(raw):return hashlib.sha256(raw).hexdigest() + + +def classify_latest(checks, head): + """Choose latest check ID per original job; never borrow stale green.""" + require(isinstance(head,str) and HEAD.fullmatch(head) and + isinstance(checks,list), "exact real H/check list required") + required={(kind,i) for kind,spec in KINDS.items() + for i in range(spec["count"])} + latest={} + for check in checks: + if not isinstance(check,dict) or check.get("head_sha")!=head: + continue + if (check.get("app") or {}).get("slug")!="github-actions": + continue + name=check.get("name") + for kind,shard in required: + if name!=KINDS[kind]["job"]+f" ({shard})":continue + id_=check.get("id") + require(type(id_) is int and id_>0, "missing authentic check-run ID") + key=kind,shard + if key not in latest or id_>latest[key]["id"]: + latest[key]=check + require(set(latest)==required, "missing full 4+9+8 exact-H producing checks") + for key,check in latest.items(): + require(check.get("status")=="completed" and + check.get("conclusion")=="success", + "latest original producing check not successful: "+ + str(key)) + return latest + + +def validate_run_job_artifact(*, head, kind, shard, check, + run, jobs, artifacts, + branch="research/final-mathematical-closure"): + """Authentic completed run, latest attempt/job, and correct artifact.""" + spec=KINDS[kind] + require(isinstance(run,dict) and isinstance(jobs,list) and + isinstance(artifacts,list) and isinstance(check,dict), + "source GitHub producer API metadata unavailable") + link=check.get("details_url") + match=RUN_URL.fullmatch(link) if isinstance(link,str) else None + require(match is not None, "GitHub check is missing original run URL") + run_id=int(match.group(1)) + # The immutable original 4+9+8 workflows have narrow push path filters, + # but all three also expose manual workflow_dispatch. After exact source + # freeze they must be reproducible at the SAME H without editing a + # workflow or manufacturing a triggering source commit. Both event types + # still require original head/branch/workflow/job/attempt/artifact binding. + require(run.get("id")==run_id and + run.get("head_sha")==head and + run.get("head_branch")==branch and + run.get("event") in ("push","workflow_dispatch") and + run.get("status")=="completed" and + run.get("conclusion")=="success" and + type(run.get("run_attempt")) is int and run["run_attempt"]>0 and + (run.get("path")==spec["workflow"] or + str(run.get("path","")).endswith("/"+spec["workflow"])), + "original producer run not successful at this exact H") + same=[job for job in jobs if isinstance(job,dict) and + job.get("name")==spec["job"]+f" ({shard})" and + job.get("head_sha")==head and + job.get("run_id")==run_id and + job.get("run_attempt")==run["run_attempt"] and + job.get("status")=="completed" and + job.get("conclusion")=="success" and + str(job.get("check_run_url","")).endswith( + "/check-runs/"+str(check["id"]))] + require(len(same)==1, "source attempt/job/check aliasing or missing") + producer=same[0] + if match.group(2) is not None: + require(int(match.group(2))==producer.get("id"), + "check details links to a different original job") + named=[a for a in artifacts if isinstance(a,dict) and + a.get("name")==spec["artifact"]+str(shard) and + a.get("expired") is False and + (a.get("workflow_run") or {}).get("id")==run_id and + (a.get("workflow_run") or {}).get("head_sha")==head] + require(len(named)==1, "original artifact missing/duplicated or expired") + artifact=named[0] + stamps=[producer.get("started_at"),artifact.get("created_at"), + producer.get("completed_at")] + require(all(isinstance(x,str) and + re.fullmatch(r"\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ",x) + for x in stamps) and stamps[0]<=stamps[1]<=stamps[2], + "original artifact not produced by current successful job attempt") + require(type(artifact.get("id")) is int and artifact["id"]>0 and + isinstance(artifact.get("digest"),str) and + re.fullmatch(r"sha256:[0-9a-f]{64}",artifact["digest"]), + "GitHub original artifact digest unavailable") + return { + "kind":kind,"shard":shard,"run_id":run_id, + "run_attempt":run["run_attempt"], + "job_id":producer["id"], + "check_run_id":check["id"], + "artifact_id":artifact["id"], + "artifact_name":artifact["name"], + "artifact_sha256":artifact["digest"][7:], + } + + +def original_artifact_zip(artifact_id): + """Download exact GitHub ZIP through the bounded all-hop source policy.""" + from pr113_boundary_io import github_artifact_zip + token = os.environ.get("GITHUB_TOKEN") + try: + return github_artifact_zip(REPO, artifact_id, token, max_bytes=MAX_ZIP) + except (OSError, ValueError) as exc: + raise DiscoveryError("authentic original ZIP delivery unavailable") from exc + + +def collect(head, output, *, checks=None, lookup=live, + paginate_source=paginate, download=original_artifact_zip): + require(isinstance(head,str) and HEAD.fullmatch(head) + and git_head()==head, + "original 21 producer collection requires exact committed source") + from pr113_boundary_io import detached_destination + out=detached_destination(output, ROOT) + require(not ROOT.resolve().is_relative_to(out), + "original 21 producer input must not encompass source") + from pr113_producer_target_v3 import authenticated_producer_branch + # Genuine workflow execution is mandatory outside explicit test injection. + branch=(authenticated_producer_branch(head) + if checks is None else "research/final-mathematical-closure") + candidates=classify_latest( + paginate_source("/commits/"+head+"/check-runs",key="check_runs") + if checks is None else checks,head) + run_cache={} + records=[] + provenance=[] + inputs={} + total=0 + for kind,shard in sorted(candidates): + check=candidates[(kind,shard)] + url=check.get("details_url","") + m=RUN_URL.fullmatch(url) + require(m is not None, "original check-run has no source run ID") + rid=int(m.group(1)) + if rid not in run_cache: + run_cache[rid]=( + lookup("/actions/runs/"+str(rid)), + paginate_source("/actions/runs/"+str(rid)+"/jobs",key="jobs"), + paginate_source("/actions/runs/"+str(rid)+"/artifacts",key="artifacts"), + ) + run,jobs,artifacts=run_cache[rid] + info=validate_run_job_artifact( + head=head,kind=kind,shard=shard, + check=check,run=run,jobs=jobs,artifacts=artifacts, + branch=branch) + raw=download(info["artifact_id"]) + total+=len(raw) + require(total<=MAX_TOTAL and sha(raw)==info["artifact_sha256"], + "original GH producer ZIP differed from authoritative digest") + filename=str(info["artifact_id"])+".zip" + require(filename not in inputs, "duplicate original artifact") + inputs[filename]=raw + records.append({ + "kind":kind,"shard":shard,"run_id":info["run_id"], + "artifact_id":info["artifact_id"],"zip_file":filename, + }) + provenance.append(info) + require(len(records)==21 and len(inputs)==21, + "full original 4+9+8 source artifact set not completed") + # No output until ALL required original jobs and ZIPs authenticate. + out.mkdir(parents=True,exist_ok=False) + for name,raw in sorted(inputs.items()): + (out/name).write_bytes(raw) + manifest={"format":FORMAT,"head_sha":head, + "artifacts":records} + (out/"original21_catalog.json").write_text( + json.dumps(manifest,sort_keys=True,indent=2)+"\n") + (out/"source_execution_provenance.json").write_text( + json.dumps({ + "format":"Permansson.PR113.RuntimeOriginal21Provenance.v1", + "status":"ORIGINAL_21_GITHUB_PRODUCERS_PRESENT_UNREVIEWED", + "source_head":head,"original_ci_sources":21, + "actual_run_job_check_artifact_identities":provenance, + "independent_case_review":False, + "independent_claim_review":False, + "publication_authorized":False, + },sort_keys=True,indent=2)+"\n") + return manifest + + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--head",required=True) + p.add_argument("--out",type=Path,required=True) + a=p.parse_args(argv) + try: + result=collect(a.head,a.out) + print(json.dumps({"status":"ORIGINAL_21_RUNTIME_CATALOG_UNREVIEWED", + "artifacts":len(result["artifacts"])})) + return 0 + except (DiscoveryError, AuthenticationError,ValueError, + OSError,KeyError,TypeError) as exc: + print("PR113 ORIGINAL21 LIVE CATALOG BLOCKED:",exc,file=sys.stderr) + return 2 + +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_original_producer_trigger_candidate.py b/verification/research/pr113_original_producer_trigger_candidate.py new file mode 100644 index 00000000..3baff54e --- /dev/null +++ b/verification/research/pr113_original_producer_trigger_candidate.py @@ -0,0 +1,168 @@ +"""Review-only P1-05 exact-H trigger migration; never change active CI. + +The original 4+9+8 producer jobs exist, but path-filtered push events are not +guaranteed at frozen H, and manual dispatch of a workflow absent on the +default branch is not a reliable substitute. Retain all original jobs and +change only the branch-scoped push trigger after independent security review. + +This program cannot attest independent approval or run any required check. +""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys + +ROOT=Path(__file__).resolve().parents[2] +MANIFEST="docs/operations/pr113_unapproved_workflows/trigger_candidates/manifest.json" +SHA40=re.compile(r"^[a-f0-9]{40}$") +TRIGGER="on:\n push:\n branches: [research/final-mathematical-closure]\n workflow_dispatch:" +PRODUCER_IDS={ + "pr113-all-case-compiler.yml":"compiler-shard", + "pr113-all-original-semantic-candidates.yml":"semantic-candidate-shard", + "pr113-legacy-semantic-evidence.yml":"legacy-original-shard", +} +SHARD_COUNT={ + "pr113-all-case-compiler.yml":4, + "pr113-all-original-semantic-candidates.yml":9, + "pr113-legacy-semantic-evidence.yml":8, +} +class TriggerReviewError(ValueError):pass + +def demand(ok,reason): + if not ok:raise TriggerReviewError(reason) + +def git(*args): + try: + proc=subprocess.run(["git", *args],cwd=ROOT, + timeout=35,capture_output=True,check=False) + except (OSError,subprocess.TimeoutExpired) as exc: + raise TriggerReviewError("Git source unavailable") from exc + demand(proc.returncode==0 and len(proc.stdout)<4*1024*1024, + "real Git source object missing") + return proc.stdout + +def committed(head,path): + demand(isinstance(path,str) and + (path.startswith(".github/workflows/") or + path.startswith("docs/operations/pr113_unapproved_workflows/")) and + ".." not in Path(path).parts and "\\" not in path, + "untrusted original or proposed CI path") + return git("show",head+":"+path) + +def digest(value):return hashlib.sha256(value).hexdigest() +def blob_oid(value): + return hashlib.sha1(b"blob "+str(len(value)).encode()+b"\0"+value).hexdigest() + +def revised(original): + """Strict original bytes transformation, never a YAML reformatter.""" + demand(type(original) is bytes and len(original)<1_000_000, + "invalid original source-controlled workflow") + try:src=original.decode("utf-8") + except UnicodeError as exc:raise TriggerReviewError("invalid UTF-8") from exc + begin=src.find("on:\n") + end=src.find(" workflow_dispatch:",begin) + demand(begin>=0 and end>begin and src.count("on:\n")==1 + and src.count(" workflow_dispatch:")==1 + and " paths:" in src[begin:end] + and "research/final-mathematical-closure" in src[begin:end] + and src[:begin].startswith("name: PR113 "), + "unknown original workflow trigger format") + return (src[:begin]+TRIGGER+src[end+len(" workflow_dispatch:"):]).encode() + +def validate_proposals(head,data,read=committed): + demand(isinstance(head,str) and SHA40.fullmatch(head) and + isinstance(data,dict) and + data.get("format")=="Permansson.PR113.StagedFullProducerTriggerReview.v1" and + data.get("status")=="REVIEW_PENDING__ACTIVE_WORKFLOWS_UNCHANGED" and + data.get("original_requested_producer_jobs")==21 and + data.get("independent_security_review") is False and + data.get("owner_approved_installation") is False and + data.get("mathematical_acceptance") is False and + data.get("publication_authorized") is False and + isinstance(data.get("proposals"),list) and len(data["proposals"])==3, + "candidate source contract claimed approval or lost original shards") + seen=set();rows=[] + for row in data["proposals"]: + demand(isinstance(row,dict) and + set(row)=={"active","proposal","original_blob","proposed_blob", + "producer_shards","expected_job"}, + "invalid protected workflow proposal member") + old,new=row["active"],row["proposal"] + name=Path(old).name + demand(name in PRODUCER_IDS and + old==".github/workflows/"+name and + new=="docs/operations/pr113_unapproved_workflows/trigger_candidates/"+name and + name not in seen and + row["expected_job"]==PRODUCER_IDS[name] and + type(row["producer_shards"]) is int and + row["producer_shards"]==SHARD_COUNT[name] and + all(isinstance(row[key],str) and SHA40.fullmatch(row[key]) + for key in ("original_blob","proposed_blob")), + "foreign original job, replay shard or byte identity") + seen.add(name) + original=read(head,old) + candidate=read(head,new) + demand(blob_oid(original)==row["original_blob"] and + blob_oid(candidate)==row["proposed_blob"], + "staged or active producer blob differs from pinned review object") + demand(candidate==revised(original), + "candidate CI differs from active source beyond approved trigger patch") + demand((" "+PRODUCER_IDS[name]+":").encode() in candidate, + "original producing job removed") + shard=(" shard: ["+ + ", ".join(str(i) for i in range(SHARD_COUNT[name]))+ + "]").encode() + demand(shard in candidate and + ("--shard-count "+str(SHARD_COUNT[name])).encode() in candidate, + "original shard matrix/count changed") + rows.append({ + "workflow":old,"staged_candidate":new, + "old_source_sha256":digest(original), + "new_source_sha256":digest(candidate), + "producer_job":PRODUCER_IDS[name], + "original_shard_count":SHARD_COUNT[name], + "reviewed":False,"installed":False, + }) + demand(seen==set(PRODUCER_IDS),"original producer workflow family missing") + return { + "format":"Permansson.PR113.TriggerCandidateAudit.v1", + "status":"THREE_BYTE_EXACT_CANDIDATES_SECURITY_REVIEW_PENDING", + "source_head":head, + "original_shard_count":sum(SHARD_COUNT.values()), + "original_producing_workflow_count":3, + "active_files_unchanged":True, + "proposed":rows, + "independent_security_review":False, + "installed":False, + "accepted_controls":0, + "publication_authorized":False, + } + +def main(argv=None): + ap=argparse.ArgumentParser(description=__doc__) + ap.add_argument("--head",required=True) + ap.add_argument("--out",type=Path,required=True) + a=ap.parse_args(argv) + try: + demand(git("rev-parse","HEAD").decode().strip()==a.head, + "checkout differs from requested exact H") + target=a.out.absolute() + demand(not target.exists() and + not any(p.is_symlink() for p in (target,*target.parents)) and + not target.is_relative_to(ROOT.resolve()), + "review output must be a fresh detached nonsymlinked source path") + result=validate_proposals(a.head,json.loads(committed(a.head,MANIFEST))) + target.parent.mkdir(parents=True,exist_ok=True) + target.write_text(json.dumps(result,indent=2,sort_keys=True)+"\n") + print(json.dumps({"status":result["status"], + "original_shards":result["original_shard_count"]})) + return 0 + except (TriggerReviewError,OSError,ValueError,KeyError,TypeError) as exc: + print("PR113 ALL-SHARD CI TRIGGER REVIEW BLOCKED:",exc,file=sys.stderr) + return 2 + +if __name__=="__main__":raise SystemExit(main()) diff --git a/verification/research/pr113_owner_partition_v3.py b/verification/research/pr113_owner_partition_v3.py new file mode 100644 index 00000000..db39c906 --- /dev/null +++ b/verification/research/pr113_owner_partition_v3.py @@ -0,0 +1,59 @@ +"""Typed structural inventory for the 45+20+11 original source owners. + +One GitHub artifact ZIP may contain many owners. This validates only source +contract SHAPE and the exact claimed ownership partition. GitHub run, digest, +Lean execution and independent review remain separately mandatory. +""" +from __future__ import annotations + +FORMAT = "Permansson.PR113.ControlProducerExtension.v3.1" +CLAIMS = frozenset(("Q1", "Q2", "Q3", "M1", "M2", "M3", + "N1", "N2", "N3", "D8", "D9")) +COUNTS = {"original_positive": 45, "legacy_negative": 20, "claim_pair": 11} + + +class OwnerPartitionError(ValueError): + pass + + +def require(ok, message): + if not ok: + raise OwnerPartitionError(message) + + +def original_owner_artifacts(payload): + """Return actual producer ARTIFACT IDs, never a fixed number of ZIPs.""" + require(type(payload) is dict and payload.get("format") == FORMAT + and payload.get("source_controls_accepted") is False + and payload.get("independent_security_review") is False + and payload.get("mathematical_approval") is False, + "full owner contract is malformed or self-approved") + jobs = payload.get("producers") + require(type(jobs) is list and 0 < len(jobs) <= 76, + "missing or unbounded original owner-producing jobs") + artifacts = set() + by_kind = {key: set() for key in COUNTS} + for job in jobs: + require(type(job) is dict and + type(job.get("artifact_id")) is int + and job["artifact_id"] > 0 + and job["artifact_id"] not in artifacts, + "duplicate or nonnumeric original CI artifact identity") + artifacts.add(job["artifact_id"]) + owners = job.get("owners") + require(type(owners) is list and bool(owners), + "owner-producing artifact has no actual source owners") + for owner in owners: + require(type(owner) is dict + and type(owner.get("kind")) is str + and owner["kind"] in COUNTS + and type(owner.get("owner_id")) is str + and bool(owner["owner_id"]) + and owner["owner_id"] not in by_kind[owner["kind"]], + "unknown, missing or repeated original owner identity") + by_kind[owner["kind"]].add(owner["owner_id"]) + require(all(len(by_kind[k]) == n for k, n in COUNTS.items()) + and by_kind["legacy_negative"] <= by_kind["original_positive"] + and by_kind["claim_pair"] == CLAIMS, + "original source owners do not cover exactly 45+20+11") + return sorted(artifacts) diff --git a/verification/research/pr113_pack_ci_positive_owners.py b/verification/research/pr113_pack_ci_positive_owners.py new file mode 100644 index 00000000..4d44ac60 --- /dev/null +++ b/verification/research/pr113_pack_ci_positive_owners.py @@ -0,0 +1,179 @@ +"""Package actual 45-case source-positive execution as a v3.1 CI owner ZIP root. + +This module never makes GitHub, independent-review or mathematical acceptance +claims. Artifact IDs and trusted check-run identities are supplied ONLY by +the later independent GitHub artifact authenticator. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re +import shutil +import sys + +SHA40 = re.compile(r"^[0-9a-f]{40}$") +SHA256 = re.compile(r"^[0-9a-f]{64}$") +INDEX_FORMAT = "Permansson.PR113.AdditionalControlProducerIndex.v1" +SUMMARY_FORMAT = "Permansson.PR113.Original45PositiveRoutesCandidate.v1" +RECEIPT_FORMAT = "Permansson.PR113.OriginalCasePositiveCandidate.v1" +STATUS = "SOURCE_TEST_AND_LEAN_EXECUTED_UNREVIEWED" +LOGS = ("unittest_stdout.log", "unittest_stderr.log", "source_positive.log") +KEYS = ("unittest_stdout_sha256", "unittest_stderr_sha256", + "source_positive_log_sha256") + + +class ProducerError(ValueError): + pass + + +def require(ok, reason): + if not ok: + raise ProducerError(reason) + + +def digest(raw): + return hashlib.sha256(raw).hexdigest() + + +def regular(path): + path = Path(path).absolute() + require(not any(part.is_symlink() for part in (path, *path.parents)), + "symlinked producer path forbidden") + require(path.is_file(), "required producer member missing") + return path + + +def validate_one(head, case_id, directory): + """Inspect REAL source-run outputs and return original, audited bytes.""" + require(isinstance(case_id, str) and + re.fullmatch(r"[A-Za-z][A-Za-z0-9_-]{1,30}", case_id), + "invalid original source case ID") + receipt = regular(directory / "unreviewed_positive.json").read_bytes() + data = json.loads(receipt) + require(data.get("format") == RECEIPT_FORMAT and + data.get("status") == STATUS and + data.get("case_id") == case_id and + data.get("head_sha") == head and + data.get("positive_role") in ("COMPILER_SOURCE", "EXACT_CHECK") and + data.get("source_positive_exit_code") == 0 and + data.get("registered_method_exit_code") == 0 and + data.get("actual_producing_check_run_id") is None and + data.get("authenticated_ci_artifact") is False and + data.get("independent_case_review") is False and + data.get("scientific_release_approved") is False, + "positive owner does not represent successful, unreviewed execution") + checked = {} + for filename, key in zip(LOGS, KEYS): + raw = regular(directory / filename).read_bytes() + require(len(raw) <= 4*1024*1024 and + isinstance(data.get(key), str) and + SHA256.fullmatch(data[key]) and + digest(raw) == data[key], + "original producer log digest/size mismatch: " + case_id) + checked[filename] = raw + stderr = checked["unittest_stderr.log"].decode("utf-8", errors="replace") + leanlog = checked["source_positive.log"].decode("utf-8", + errors="replace").lower() + require("Ran 1 test" in stderr and "OK" in stderr and + "skipped=" not in stderr and "FAILED (" not in stderr and + "error:" not in leanlog and "sorryax" not in leanlog, + "actual source control was not a successful exact-scope execution") + return receipt, checked + + +def owner_manifest(head, run_id, run_attempt, source, destination): + require(isinstance(head, str) and SHA40.fullmatch(head), + "exact real source head required") + require(type(run_id) is int and run_id > 0 and + type(run_attempt) is int and run_attempt > 0, + "real GitHub run/attempt identity required") + source = Path(source).resolve(strict=True) + destination = Path(destination).absolute() + require(not destination.exists() and not destination.is_symlink() and + not destination.is_relative_to(source) and + not destination.is_relative_to( + Path(__file__).resolve().parents[2]), + "fresh detached artifact output required") + summary = json.loads(regular( + source / "unreviewed_45_positive_routes.json").read_bytes()) + records = summary.get("positive_route_records") + require(summary.get("format") == SUMMARY_FORMAT and + summary.get("status") == + "EXECUTED_NO_PRODUCING_CI_OR_MATH_REVIEW" and + summary.get("head_sha") == head and + summary.get("actual_positive_cases") == 45 and + summary.get("authenticated_ci_artifact") is False and + summary.get("scientific_release_approved") is False and + isinstance(records, list) and len(records) == 45, + "complete original 45-case real positive-run manifest missing") + names = [row.get("case_id") for row in records + if isinstance(row, dict)] + require(len(names) == 45 and len(set(names)) == 45, + "duplicate or malformed case-level execution report") + require(set(names) == set(p.name for p in source.iterdir() if p.is_dir()), + "source-positive report omitted or invented a case directory") + # Verify ALL original bytes before writing an artifact at all. + verified = [] + for cid in sorted(names): + report = next(row for row in records if row["case_id"] == cid) + actual, logs = validate_one(head, cid, source / cid) + require(json.loads(actual) == report, + "source original per-case receipt changed after summary") + verified.append((cid, actual, logs)) + destination.mkdir(parents=True) + (destination / "positive").mkdir() + owners = [] + for cid, receipt, logs in verified: + path = destination / "positive" / cid + path.mkdir() + (path / "unreviewed_positive.json").write_bytes(receipt) + for name, raw in logs.items(): + (path / name).write_bytes(raw) + member = "positive/" + cid + "/unreviewed_positive.json" + owners.append({ + "kind": "original_positive", + "owner_id": cid, + "receipt_member": member, + "receipt_sha256": digest(receipt), + }) + index = { + "format": INDEX_FORMAT, + "head_sha": head, + "github_run_id": run_id, + "github_run_attempt": run_attempt, + "accepted_cases": 0, + "accepted_claims": 0, + "independent_review": False, + "owners": owners, + } + (destination / "owner_index.json").write_text( + json.dumps(index, sort_keys=True, indent=2) + "\n") + return index + + +def main(argv=None): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--head", required=True) + p.add_argument("--run-id", required=True, type=int) + p.add_argument("--run-attempt", required=True, type=int) + p.add_argument("--source", required=True, type=Path) + p.add_argument("--output", required=True, type=Path) + a = p.parse_args(argv) + try: + result = owner_manifest(a.head, a.run_id, a.run_attempt, + a.source, a.output) + except (ProducerError, OSError, ValueError, KeyError, TypeError) as exc: + print("PR113 authentic source-positive CI artifact BLOCKED:", exc, + file=sys.stderr) + return 2 + print(json.dumps({"status": "45_UNREVIEWED_OWNERS_PACKED", + "owner_count": len(result["owners"])}, + sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_pack_ci_remaining_owners.py b/verification/research/pr113_pack_ci_remaining_owners.py new file mode 100644 index 00000000..45db3129 --- /dev/null +++ b/verification/research/pr113_pack_ci_remaining_owners.py @@ -0,0 +1,245 @@ +"""Package actual twenty legacy and eleven claim-control executions for v3.1. + +Inputs must be the original detached outputs of the real Lean/unittest +executors, never hand-authored acceptance rows. This adds *unreviewed* CI +ownership only. The final v3.1 verifier separately authenticates the real +GitHub workflow, job, check, artifact digest and entire member set. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re +import sys + +from pr113_v31_execution_integrity import ( + verify_owner, RealControlError, REQUIRED_CLAIM_MEMBERS, +) + +ROOT = Path(__file__).resolve().parents[2] +INDEX_FORMAT = "Permansson.PR113.AdditionalControlProducerIndex.v1" +ID = re.compile(r"^[A-Za-z][A-Za-z0-9_-]{1,40}$") +CLAIMS = frozenset(("Q1", "Q2", "Q3", "M1", "M2", "M3", + "N1", "N2", "N3", "D8", "D9")) + + +class PackError(ValueError): + pass + + +def need(ok, msg): + if not ok: + raise PackError(msg) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def secure_file(path): + raw = Path(path).absolute() + need(not any(p.is_symlink() for p in (raw, *raw.parents)) and + raw.is_file(), "untrusted producer source file or symlink: " + + str(path)) + return raw.read_bytes() + + +def secure_source(source): + given = Path(source).absolute() + need(not any(x.is_symlink() for x in (given, *given.parents)), + "source artifact path contains a symlink") + root = given.resolve(strict=True) + need(root.is_dir() and not root.is_relative_to(ROOT.resolve()) and + not ROOT.resolve().is_relative_to(root), + "original executed evidence must live outside checkout") + return root + + +def safe_output(output, source): + given = Path(output).absolute() + need(not any(p.is_symlink() for p in (given, *given.parents)) and + not given.exists() and + not given.is_relative_to(source) and + not given.is_relative_to(ROOT.resolve()) and + not ROOT.resolve().is_relative_to(given), + "candidate producer output must be fresh and outside source") + return given + + +def manifest(head, run_id, run_attempt, kind, source, output): + need(isinstance(head, str) and re.fullmatch("[0-9a-f]{40}", head), + "original exact head SHA required") + need(type(run_id) is int and run_id > 0 and + type(run_attempt) is int and run_attempt > 0, + "real positive GitHub run and attempt numbers required") + need(kind in ("legacy_negative", "claim_pair"), + "unsupported CI producer ownership kind") + source = secure_source(source) + target = safe_output(output, source) + if kind == "legacy_negative": + data = json.loads(secure_file( + source / "unreviewed_batch_replays.json")) + rows = data.get("replays") + need(data.get("format") == + "Permansson.PR113.LegacyBatchAppendOnlyCandidate.v1" and + data.get("status") == + "TWENTY_REAL_LEAN_REPLAYS_UNREVIEWED_NOT_CI_BOUND" and + data.get("source_head") == head and + data.get("replayed_case_count") == 20 and + data.get("new_source_bound_CI_acceptance") is False and + data.get("independent_case_reviews") == 0 and + data.get("mathematical_release_approved") is False and + isinstance(rows, list) and len(rows) == 20, + "actual 20 original source-appended executions missing") + else: + data = json.loads(secure_file( + source / "unreviewed_11_claim_control_summary.json")) + rows = data.get("claim_controls") + need(data.get("format") == + "Permansson.PR113.ClaimControlsCandidate.v1" and + data.get("status") == + "ALL_ELEVEN_EXECUTED_UNREVIEWED_NO_CI_ATTRIBUTION" and + data.get("source_head") == head and + data.get("actual_new_claims_executed") == 11 and + data.get("actual_producing_ci_check_run_id") is None and + data.get("independent_claim_reviews") == 0 and + data.get("mathematical_release_approved") is False and + isinstance(rows, list) and len(rows) == 11, + "all eleven genuine claim controls are not executed") + expected_ids = ([row.get("case_id") for row in rows] + if kind == "legacy_negative" else + [row.get("claim_id") for row in rows]) + need(all(isinstance(cid, str) and ID.fullmatch(cid) + for cid in expected_ids) and + len(set(expected_ids)) == len(rows) and + (kind != "claim_pair" or set(expected_ids) == CLAIMS), + "actual original source owner IDs are not unique or complete") + + # Authenticate every original member before creating ANY release artifact. + payloads = [] + total_bytes = 0 + for row in sorted(rows, key=lambda item: item[ + "case_id" if kind == "legacy_negative" else "claim_id"]): + cid = row["case_id" if kind == "legacy_negative" else "claim_id"] + if kind == "legacy_negative": + original = secure_file(source / cid / "unreviewed_replay.json") + replay = json.loads(original) + binary = secure_file(source / cid / "semantic_replay_candidate.zip") + need(row.get("replay_bundle_sha256") == sha(binary) and + replay.get("detached_bundle_sha256") == sha(binary) and + row.get("status") == + "SOURCE_APPENDED_REPLAYED_UNREVIEWED_NOT_CI_ATTRIBUTED" and + row.get("new_replay_producer_check_run_id") is None and + type(row.get("original_legacy_producing_artifact_id")) is int and + row["original_legacy_producing_artifact_id"] > 0, + "historical nested semantic replay cannot be traced to source") + base = "legacy/" + cid + "/" + path = base + "unreviewed_legacy_ci.json" + wrapped = { + "status": row["status"], + "head_sha": head, + "case_id": cid, + "historical_producing_artifact_id": + row["original_legacy_producing_artifact_id"], + "actual_ci_check_run_id": None, + "execution_receipt_member": + base + "original_unreviewed_replay.json", + "execution_receipt_sha256": sha(original), + "semantic_zip_member": base + "semantic_replay_candidate.zip", + "semantic_zip_sha256": sha(binary), + "independent_case_review": False, + "mathematical_acceptance": False, + "scientific_release_approved": False, + } + meta = (json.dumps(wrapped, sort_keys=True, indent=2) + + "\n").encode("utf-8") + entries = { + path: meta, + base + "original_unreviewed_replay.json": original, + base + "semantic_replay_candidate.zip": binary, + } + else: + original = secure_file( + source / cid / "unreviewed_claim_control.json") + receipt = json.loads(original) + need(receipt == row and + receipt.get("status") == + "LEAN_POSITIVE_AND_FALSE_MUTANT_EXECUTED_UNREVIEWED" and + receipt.get("head_sha") == head and + receipt.get("actual_ci_check_run_id") is None, + "claim source controller differs from original summary") + base = "claim/" + cid + "/" + path = base + "unreviewed_claim_control.json" + entries = {path: original} + for name in sorted(REQUIRED_CLAIM_MEMBERS): + entries[base + name] = secure_file(source / cid / name) + meta = original + # Match the exact strict limits of inspect_zip() in the downstream + # authenticating verifier, not a weaker preliminary packer budget. + need(all(len(x) <= 4 * 1024 * 1024 for x in entries.values()), + "source owner member exceeds canonical 4 MiB ZIP limit") + total_bytes += sum(len(x) for x in entries.values()) + need(total_bytes <= 64 * 1024 * 1024, + "source owner archive exceeds canonical 64 MiB ZIP budget") + # The same exact verifier will re-run on GitHub's original uploaded ZIP. + need(verify_owner(kind, cid, path, entries, head)["owner_id"] == cid, + "original executed Lean/source evidence is invalid") + payloads.append((cid, path, meta, entries)) + + target.mkdir(parents=True) + all_entries = {} + owners = [] + for cid, path, raw, entries in payloads: + owners.append({ + "kind": kind, "owner_id": cid, + "receipt_member": path, "receipt_sha256": sha(raw), + }) + for name, bytes_ in entries.items(): + need(name not in all_entries, "duplicate output source member") + all_entries[name] = bytes_ + for name, bytes_ in sorted(all_entries.items()): + file = target / name + file.parent.mkdir(parents=True, exist_ok=True) + file.write_bytes(bytes_) + index = { + "format": INDEX_FORMAT, + "head_sha": head, + "github_run_id": run_id, + "github_run_attempt": run_attempt, + "accepted_cases": 0, + "accepted_claims": 0, + "independent_review": False, + "owners": owners, + } + (target / "owner_index.json").write_text( + json.dumps(index, sort_keys=True, indent=2) + "\n") + return index + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head", required=True) + parser.add_argument("--kind", choices=("legacy_negative", "claim_pair"), + required=True) + parser.add_argument("--run-id", required=True, type=int) + parser.add_argument("--run-attempt", required=True, type=int) + parser.add_argument("--source", required=True, type=Path) + parser.add_argument("--output", required=True, type=Path) + args = parser.parse_args(argv) + try: + result = manifest(args.head, args.run_id, args.run_attempt, + args.kind, args.source, args.output) + except (PackError, RealControlError, OSError, ValueError, + KeyError, TypeError) as exc: + print("PR113 SOURCE-OWNED CONTROL PACKAGING BLOCKED:", exc, + file=sys.stderr) + return 2 + print(json.dumps({"status": "EXECUTED_CI_OWNERS_UNREVIEWED", + "control_owners": len(result["owners"])})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_prepare_positive_selection.py b/verification/research/pr113_prepare_positive_selection.py new file mode 100644 index 00000000..3d14465a --- /dev/null +++ b/verification/research/pr113_prepare_positive_selection.py @@ -0,0 +1,191 @@ +"""Produce a review-pending, exact-source selection for ALL original 45 case positives. + +Never turn a discovered method into an accepted mathematical case. This +prepares the explicit external selection expected by +final_closure_positive_route_candidate.py; that executor still has to run. +The original 45 criteria and historical candidate ledger stay unchanged. +""" +from __future__ import annotations + +import argparse +import ast +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[2] +MAP = "research/paper_closures/post_merge_112/acceptance_case_source_map.json" +FORMAT = "Permansson.PR113.OriginalPositiveSelection.v1" +SHA40 = re.compile(r"^[0-9a-f]{40}$") +PREFERRED_ROLES = ("COMPILER_SOURCE", "EXACT_CHECK") + + +class SelectionError(ValueError): + pass + + +def require(condition, reason): + if not condition: + raise SelectionError(reason) + + +def git_bytes(*args): + try: + return subprocess.check_output( + ["git", *args], cwd=ROOT, stderr=subprocess.DEVNULL, + timeout=45, + ) + except (OSError, subprocess.CalledProcessError, + subprocess.TimeoutExpired) as exc: + raise SelectionError("source Git object unavailable") from exc + + +def committed(head, path): + require(isinstance(path, str) and not path.startswith("/") and + ".." not in Path(path).parts and "\\" not in path, + "unsafe source path") + return git_bytes("show", head + ":" + path) + + +def method_class(source, name): + """Recover the actual unittest class, never guess from a filename.""" + try: + parsed = ast.parse(source.decode("utf-8")) + except (SyntaxError, UnicodeError) as exc: + raise SelectionError("original test Python cannot be parsed") from exc + matches = [] + for node in ast.walk(parsed): + if isinstance(node, ast.ClassDef): + if any(isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)) + and child.name == name for child in node.body): + matches.append(node.name) + require(len(matches) == 1, + "selected positive method must have a unique unittest class: " + + name) + return matches[0] + + +def choose_role(case): + routes = case.get("test_method_candidates") + require(isinstance(routes, list), "missing original method registry") + available = [r for r in routes if isinstance(r, dict) and + r.get("role") in PREFERRED_ROLES] + require(bool(available), "no source-positive route: " + case["case_id"]) + available.sort(key=lambda r: ( + PREFERRED_ROLES.index(r["role"]), r.get("path", ""), + r.get("method", ""), + )) + chosen = available[0] + require(isinstance(chosen.get("path"), str) and + chosen["path"].startswith("verification/research/test_") and + chosen["path"].endswith(".py") and + isinstance(chosen.get("method"), str) and + re.fullmatch(r"test_[a-zA-Z0-9_]+", chosen["method"]), + "malformed original positive route") + return chosen + + +def derive(head, source_map, source_reader=committed): + """Deterministic candidate route coverage; NEVER semantic acceptance.""" + cases = source_map.get("cases") + require(isinstance(cases, list) and len(cases) == 45, + "exactly 45 original case criteria are required") + ids = [row.get("case_id") for row in cases if isinstance(row, dict)] + require(len(ids) == 45 and all(isinstance(i, str) and i for i in ids) + and len(set(ids)) == 45, "duplicate or missing original case ID") + selections, source_hashes = [], {} + for case in sorted(cases, key=lambda c: c["case_id"]): + cid = case["case_id"] + method = choose_role(case) + test = method["path"] + test_raw = source_reader(head, test) + cls = method_class(test_raw, method["method"]) + providers = case.get("historical_proof_declarations") + require(isinstance(providers, list) and providers, + "original theorem provider missing: " + cid) + paths = sorted({p.get("path") for p in providers + if isinstance(p, dict) and isinstance( + p.get("path"), str)}) + require(bool(paths), "malformed provider inventory: " + cid) + # A single route needs a source path for execution, while every + # supporting declaration stays obligatory in the full acceptance gate. + proof_path = paths[0] + for source_path in (test, *paths): + raw = source_reader(head, source_path) + source_hashes[source_path] = hashlib.sha256(raw).hexdigest() + selections.append({ + "case_id": cid, + "test_path": test, + "test_class": cls, + "test_method": method["method"], + "proof_source_path": proof_path, + }) + return { + "format": FORMAT, + "cases": selections, + }, { + "format": "Permansson.PR113.PositiveSelectionReviewCandidate.v1", + "source_head": head, + "status": "ROUTES_DERIVED_REQUIRE_INDEPENDENT_CRITERION_RELEVANCE_REVIEW", + "selected_case_count": 45, + "reviewed": False, + "executed": False, + "accepted_cases": 0, + "source_sha256": dict(sorted(source_hashes.items())), + "warnings": [ + "One deterministically selected original registered route per case is NOT a proof of scope relevance.", + "The full acceptance graph still requires all supporting theorem types, positives and semantic negatives.", + "Execute the separate real Lean/unittest route runner from the same committed source head.", + ], + } + + +def execute(head, out): + require(isinstance(head, str) and SHA40.fullmatch(head), + "full exact source SHA required") + require(git_bytes("rev-parse", "HEAD").decode().strip() == head, + "source checkout is not requested head") + require(not git_bytes("status", "--porcelain=v1", + "--untracked-files=all", + "--", "verification/research", + "research/paper_closures/post_merge_112"), + "original selection sources are dirty or untracked") + target = Path(out) + require(not target.is_symlink(), "symlink output forbidden") + target = target.resolve() + require(not target.exists() and + not target.is_relative_to(ROOT.resolve()) and + not ROOT.resolve().is_relative_to(target), + "output must be a new detached external directory") + mapping = json.loads(committed(head, MAP)) + selection, review = derive(head, mapping) + target.mkdir(parents=True) + (target / "selection.json").write_text( + json.dumps(selection, indent=2, sort_keys=True) + "\n") + (target / "selection_review_pending.json").write_text( + json.dumps(review, indent=2, sort_keys=True) + "\n") + return review + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head", required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args(argv) + try: + result = execute(args.head, args.out) + except (SelectionError, OSError, ValueError, TypeError, KeyError) as exc: + print("PR113 original positive selection BLOCKED:", exc, + file=sys.stderr) + return 2 + print(json.dumps({"status": result["status"], + "selected_cases": result["selected_case_count"]}, + sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_preseal_input_carrier.py b/verification/research/pr113_preseal_input_carrier.py new file mode 100644 index 00000000..3cddf0c5 --- /dev/null +++ b/verification/research/pr113_preseal_input_carrier.py @@ -0,0 +1,287 @@ +"""Authenticate and unpack an untrusted exact-M preseal input carrier. + +The input carrier is a transport, never mathematical or security approval: +the subsequent seal_approved --full path independently authenticates the +real 12/12 H/M CI, original 21 producer ZIPs, all 76 v3.1 controls, +nonauthor source/scholarly reviews, and exact merged-M reproduction. + +A genuine GitHub artifact digest does NOT prove an accepted release. +""" +from __future__ import annotations + +import argparse +import hashlib +import io +import json +from pathlib import Path, PurePosixPath +import re +import stat +import sys +import zipfile + +from pr113_boundary_io import strict_json_object +from pr113_owner_partition_v3 import original_owner_artifacts, OwnerPartitionError + +SOURCE = Path(__file__).resolve().parents[2] +NAME = "pr113-preseal-review-inputs" +FORMAT = "Permansson.PR113.PresealInputCarrier.v1" +SHA40 = re.compile(r"^[0-9a-f]{40}$") +SHA256 = re.compile(r"^[0-9a-f]{64}$") +ID = re.compile(r"^[1-9][0-9]*$") +EXPECTED_FILES = frozenset(( + "reviewed/approved_claim_manifest.json", + "reviewed/source_receipts.json", + "reviewed/evidence_bundle.zip", + "reviewed/reproduction.zip", + "reviewed/reproduction_ids.json", + "reviewed/additional_contract.json", + "reviewed/workflow_blob_candidates.json", +)) +MAX_ZIP_BYTES = 256 * 1024 * 1024 +MAX_MEMBER_BYTES = 64 * 1024 * 1024 +MAX_TOTAL_UNCOMPRESSED = 320 * 1024 * 1024 +MAX_MEMBERS = 100 +CI_APP = "github-actions" + + +class PresealInputError(ValueError): + pass + + +def demand(condition, reason): + if not condition: + raise PresealInputError(reason) + + +def digest(raw): + return hashlib.sha256(raw).hexdigest() + + +def read_json_file(path): + p = Path(path) + demand(p.is_file() and not p.is_symlink(), + "mandatory original GitHub API source JSON missing") + raw = p.read_bytes() + demand(0 < len(raw) <= 4*1024*1024, "invalid GitHub API response size") + try: + obj = strict_json_object(raw) + except (UnicodeError, TypeError, ValueError) as exc: + raise PresealInputError("GitHub API JSON invalid") from exc + demand(isinstance(obj, dict), "GitHub API source is not an object") + return obj + + +def verified_transport(*, merged, run_id, attempt, job_id, check_id, + artifact_id, meta, run, jobs, check, raw): + """Authenticate real completed success + correct attempt + artifact bytes.""" + demand(isinstance(merged, str) and SHA40.fullmatch(merged), + "exact real merged main M SHA missing") + for label, number in (("run",run_id), ("attempt",attempt), + ("job",job_id), ("check",check_id), + ("artifact",artifact_id)): + demand(type(number) is int and number > 0, "invalid numeric " + label) + demand(isinstance(meta, dict) and isinstance(run, dict) + and isinstance(jobs, dict) and isinstance(check, dict) + and type(raw) is bytes and 0 < len(raw) <= MAX_ZIP_BYTES, + "missing original preseal carrier and GH identity responses") + demand(run.get("id") == run_id and + run.get("head_sha") == merged and + run.get("head_branch") == "main" and + run.get("status") == "completed" and + run.get("conclusion") == "success" and + run.get("event") in ("push", "workflow_dispatch") and + run.get("run_attempt") == attempt and + isinstance(run.get("path"), str) and + run["path"].endswith(".yml") and + (run["path"].startswith(".github/workflows/") or + "/.github/workflows/" in run["path"]), + "review-input run is not successful exact-M GitHub Actions source") + rows = jobs.get("jobs") + demand(isinstance(rows, list), "successful original GitHub job inventory absent") + selected = [j for j in rows if isinstance(j, dict) + and j.get("id") == job_id] + demand(len(selected) == 1, "review-input producing job ID missing/ambiguous") + job = selected[0] + demand(job.get("run_id") == run_id and + job.get("run_attempt") == attempt and + job.get("head_sha") == merged and + job.get("status") == "completed" and + job.get("conclusion") == "success" and + str(job.get("check_run_url", "")).endswith( + "/check-runs/" + str(check_id)), + "review-input job not successful in the claimed run attempt") + demand(check.get("id") == check_id and + check.get("head_sha") == merged and + check.get("name") == job.get("name") and + check.get("status") == "completed" and + check.get("conclusion") == "success" and + (check.get("app") or {}).get("slug") == CI_APP, + "review-input original check-run not authentic/successful") + expected = "sha256:" + digest(raw) + demand(meta.get("id") == artifact_id and + meta.get("name") == NAME and + meta.get("expired") is False and + meta.get("digest") == expected and + (meta.get("workflow_run") or {}).get("id") == run_id and + (meta.get("workflow_run") or {}).get("head_sha") == merged, + "review-input original GitHub artifact SHA/name/source mismatch") + stamps = [job.get("started_at"), meta.get("created_at"), + job.get("completed_at")] + demand(all(isinstance(x, str) and + re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ", x) + for x in stamps) and stamps[0] <= stamps[1] <= stamps[2], + "input artifact not uploaded during successful exact attempt") + return { + "transport_run_id": run_id, + "transport_run_attempt": attempt, + "transport_job_id": job_id, + "transport_check_run_id": check_id, + "transport_artifact_id": artifact_id, + "transport_artifact_sha256": digest(raw), + "source_commit_sha": merged, + "status": "TRANSPORT_BYTES_BOUND_NOT_APPROVED", + } + + +def inspect_original_carrier(raw): + """Reject paths, ZIP bombs, aliasing, unexpected files, unbounded entries.""" + demand(type(raw) is bytes and 0 < len(raw) <= MAX_ZIP_BYTES, + "original preseal input ZIP missing/oversized") + entries = {} + seen = set() + total = 0 + try: + with zipfile.ZipFile(io.BytesIO(raw)) as z: + infos = z.infolist() + demand(len(infos) >= len(EXPECTED_FILES) + 1 and + len(infos) <= MAX_MEMBERS, + "preseal carrier lacks required evidence or has too many members") + for info in infos: + name = info.filename + rel = PurePosixPath(name) + mode = (info.external_attr >> 16) & 0o170000 + demand(isinstance(name, str) and + name == rel.as_posix() and + not rel.is_absolute() and + not info.is_dir() and + "\\" not in name and + ".." not in rel.parts and + not any(x in (".", "") for x in name.split("/")) and + mode in (0, stat.S_IFREG) and + name.casefold() not in seen, + "unsafe or duplicate original carrier member") + allowed = name in EXPECTED_FILES + if name.startswith("reviewed/additional_zips/") and name.endswith(".zip"): + stem = name[len("reviewed/additional_zips/"):-4] + allowed = bool(ID.fullmatch(stem)) + demand(allowed, "foreign/unapproved preseal input member") + seen.add(name.casefold()) + demand(0 < info.file_size <= MAX_MEMBER_BYTES and + info.compress_size > 0 and + info.file_size <= 200 * info.compress_size, + "unbounded evidence input/ZIP compression ratio") + total += info.file_size + demand(total <= MAX_TOTAL_UNCOMPRESSED, + "aggregate evidence input exceeds bounded size") + # 76 ownership obligations may be grouped in fewer genuine + # producer ZIP artifacts. Derive the *exact* ZIP inventory from + # the original claimed 45+20+11 partition, then independently + # authenticate that contract through the mandatory live gate. + all_names = {x.filename for x in infos} + demand(EXPECTED_FILES <= all_names, + "full v3.1 review carrier lacks required source inputs") + contract_info = z.getinfo("reviewed/additional_contract.json") + demand(0 < contract_info.file_size <= 4*1024*1024, + "full owner producer contract exceeds bounded intake") + with z.open(contract_info) as reader: + contract_raw = reader.read(4*1024*1024 + 1) + demand(len(contract_raw) == contract_info.file_size, + "original owner contract was not decompressed completely") + try: + artifact_ids = original_owner_artifacts( + strict_json_object(contract_raw)) + except (OwnerPartitionError, ValueError) as exc: + raise PresealInputError( + "carrier owner registry lacks genuine-shaped 45+20+11") from exc + expected = set(EXPECTED_FILES) | { + "reviewed/additional_zips/" + str(i) + ".zip" + for i in artifact_ids + } + demand(all_names == expected and len(infos) == len(expected), + "extra/missing producer ZIP identity for exact owner partition") + for info in infos: + with z.open(info) as handle: + content = handle.read(info.file_size + 1) + demand(len(content) == info.file_size, + "original GH ZIP decompression length mismatch") + entries[info.filename] = content + except (OSError, RuntimeError, zipfile.BadZipFile, zipfile.LargeZipFile) as exc: + raise PresealInputError("unreadable original GitHub review carrier ZIP") from exc + return entries + + +def extract(artifact_zip, output, *, merged, run_id, attempt, job_id, + check_id, artifact_id, meta, run, jobs, check): + from pr113_boundary_io import detached_destination + # Reject lexical traversal and symlink ancestors before normalization. + # GitHub original input and all outputs must stay outside source. + src = detached_destination(artifact_zip, SOURCE, must_exist=True) + target = detached_destination(output, SOURCE) + demand(not SOURCE.resolve().is_relative_to(target), + "original GH artifact/read-only target path invalid") + demand(0 < src.stat().st_size <= MAX_ZIP_BYTES, + "original GH carrier ZIP size exceeds bounded intake") + raw = src.read_bytes() + transport = verified_transport( + merged=merged, run_id=run_id, attempt=attempt, job_id=job_id, + check_id=check_id, artifact_id=artifact_id, meta=meta, + run=run, jobs=jobs, check=check, raw=raw) + contents = inspect_original_carrier(raw) + target.mkdir(parents=True, exist_ok=False) + members = {} + for name, data in sorted(contents.items()): + rel = name.removeprefix("reviewed/") + path = target / rel + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + members[rel] = digest(data) + manifest = { + "format": FORMAT, + **transport, + # The transport status must not replace the outer result contract. + "status": "GH_SOURCE_TRANSPORT_VERIFIED__RELEASE_REVIEW_PENDING", + "members_sha256": members, + "independent_review": False, + "mathematical_acceptance": False, + "publication_authorized": False, + } + (target / "transport_diagnostics.json").write_text( + json.dumps(manifest, sort_keys=True, indent=2) + "\n") + return manifest + + +def main(argv=None): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--merged", required=True) + for arg in ("run-id", "attempt", "job-id", "check-id", "artifact-id"): + p.add_argument("--" + arg, type=int, required=True) + for arg in ("zip", "metadata", "run", "jobs", "check", "out"): + p.add_argument("--" + arg, type=Path, required=True) + a = p.parse_args(argv) + try: + report = extract( + a.zip, a.out, merged=a.merged, run_id=a.run_id, + attempt=a.attempt, job_id=a.job_id, check_id=a.check_id, + artifact_id=a.artifact_id, meta=read_json_file(a.metadata), + run=read_json_file(a.run), jobs=read_json_file(a.jobs), + check=read_json_file(a.check)) + except (PresealInputError, ValueError, KeyError, OSError, TypeError) as exc: + print("PRESEAL REVIEW INPUT BLOCKED:", exc, file=sys.stderr) + return 2 + print(json.dumps({"status": report["status"], + "source": report["source_commit_sha"]}, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_producer_target_v3.py b/verification/research/pr113_producer_target_v3.py new file mode 100644 index 00000000..04f5c27f --- /dev/null +++ b/verification/research/pr113_producer_target_v3.py @@ -0,0 +1,94 @@ +"""Authenticate original21 producer branch at real PR113 or successor H. + +An operator-supplied future PR ID/branch is an untrusted lookup hint, +never approval. Every ID is checked against current GitHub event, live PR, +real main, immutable reviewed M113, and true Git ancestry. +""" +from __future__ import annotations +import os +import re + +from final_closure_authenticate import live as real_live, git as real_git +from release_target import B113, REPOSITORY + +BRANCH113 = "research/final-mathematical-closure" +SHA40 = re.compile(r"^[a-f0-9]{40}$") +BRANCH = re.compile(r"^research/[a-zA-Z0-9._/-]{1,160}$") + + +class ProducerTargetError(ValueError): + pass + + +def require(ok, why): + if not ok: + raise ProducerTargetError(why) + + +def authenticated_producer_branch(head, *, env=None, + live=real_live, git=real_git): + """Verify a genuine source-controlled producer H before trusting its branch.""" + env = os.environ if env is None else env + require(isinstance(head, str) and SHA40.fullmatch(head), + "genuine original producer head required") + require(env.get("GITHUB_ACTIONS") == "true" + and env.get("GITHUB_REPOSITORY") == REPOSITORY + and env.get("GITHUB_SHA") == head + and env.get("GITHUB_EVENT_NAME") in ("push", "workflow_dispatch"), + "original producer must be running at real GitHub branch head H") + ref = env.get("GITHUB_REF") + require(isinstance(ref, str) and ref.startswith("refs/heads/"), + "branch-pinned GitHub producer event required") + branch = ref.removeprefix("refs/heads/") + require(BRANCH.fullmatch(branch) is not None + and "//" not in branch and ".." not in branch + and git("rev-parse", "HEAD") == head + and git("cat-file", "-t", head) == "commit", + "producer checkout, branch or commit identity invalid") + if branch == BRANCH113: + pr = live("/pulls/113") + require(isinstance(pr, dict) and pr.get("number") == 113 + and pr.get("merged") is False + and (pr.get("head") or {}).get("sha") == head + and (pr.get("head") or {}).get("ref") == branch + and (pr.get("base") or {}).get("sha") == B113, + "initial original21 source not authentic unmerged PR113") + return branch + + hint = env.get("PERMANSSON_CERT_PR_NUMBER", "") + require(isinstance(hint, str) and hint.isdigit() + and not hint.startswith("0") + and int(hint) > 114, + "successor producer needs real future CERT PR number") + number = int(hint) + successor = live("/pulls/" + str(number)) + require(isinstance(successor, dict) + and successor.get("number") == number + and successor.get("state") == "open" + and successor.get("merged") is False + and (successor.get("base") or {}).get("ref") == "main" + and (successor.get("head") or {}).get("sha") == head + and (successor.get("head") or {}).get("ref") == branch + and (successor.get("head") or {}).get("repo", {}).get("full_name") + == REPOSITORY, + "future producer H is not current same-repo CERT PR head") + B = (successor.get("base") or {}).get("sha") + require(isinstance(B, str) and SHA40.fullmatch(B) + and (live("/branches/main").get("commit") or {}).get("sha") == B, + "future CERT PR base differs from real remote main") + original = live("/pulls/113") + M = original.get("merge_commit_sha") if isinstance(original, dict) else None + historical_head = (original.get("head") or {}).get("sha") if isinstance(original, dict) else None + require(original.get("merged") is True and original.get("state") == "closed" + and isinstance(M, str) and SHA40.fullmatch(M) + and isinstance(historical_head, str) and SHA40.fullmatch(historical_head) + and git("cat-file", "-t", M) == "commit" + and git("cat-file", "-t", historical_head) == "commit" + and git("show", "-s", "--format=%P", M).split() == + [B113, historical_head] + and git("rev-parse", M + "^{tree}") == + git("rev-parse", historical_head + "^{tree}") + and git("merge-base", "--is-ancestor", M, B) == "" + and git("merge-base", "--is-ancestor", B, head) == "", + "CERT branch not descended from authentic initial approved M113") + return branch diff --git a/verification/research/pr113_regression_diagnostics.py b/verification/research/pr113_regression_diagnostics.py new file mode 100644 index 00000000..9b814a75 --- /dev/null +++ b/verification/research/pr113_regression_diagnostics.py @@ -0,0 +1,78 @@ +"""Bounded actual Research failure diagnostics; preserve failing exit status.""" +import argparse +import json +import os +from pathlib import Path +import sys +import unittest + +FORMAT = "Permansson.PR113.ResearchRegressionDiagnostics.v1" + + +class Result(unittest.TextTestResult): + def __init__(self, *args): + super().__init__(*args) + self.subtests = [] + + def addSubTest(self, test, subtest, err): + super().addSubTest(test, subtest, err) + if err is not None: + self.subtests.append({ + "id": subtest.id(), "parent_id": test.id(), + "status": "failure" if issubclass(err[0], test.failureException) else "error", + "traceback": self._exc_info_to_string(err, test)[-12000:], + }) + + +def summary(result): + return { + "format": FORMAT, + "status": "TESTS_PASSED" if result.wasSuccessful() else "TESTS_FAILED", + "tests_run": result.testsRun, + "failure_count": len(result.failures), + "direct_failure_count": sum( + test.id() not in {row["id"] for row in result.subtests} + for test, _detail in result.failures), + "subtest_failure_count": sum(row["status"] == "failure" + for row in result.subtests), + "error_count": len(result.errors), + "skips": len(result.skipped), + "subtest_failures": result.subtests, + "failures": [{"id": test.id(), "traceback": detail[-12000:]} + for test, detail in result.failures], + "errors": [{"id": test.id(), "traceback": detail[-12000:]} + for test, detail in result.errors], + "github_sha": os.environ.get("GITHUB_SHA"), + "github_run_id": os.environ.get("GITHUB_RUN_ID"), + "historical_source_approved": False, + "mathematical_acceptance": False, + } + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--out", required=True, type=Path) + args = ap.parse_args(argv) + from pr113_boundary_io import detached_destination + out = detached_destination(args.out, Path(__file__).resolve().parents[2]) + try: + suite = unittest.defaultTestLoader.discover( + "verification/research", pattern="test_*.py") + result = unittest.TextTestRunner( + verbosity=2, resultclass=Result).run(suite) + report = summary(result) + out.parent.mkdir(parents=True, exist_ok=True) + with out.open("x", encoding="utf-8") as handle: + json.dump(report, handle, sort_keys=True, indent=2) + handle.write("\n") + print("PR113_RESEARCH_REGRESSION_DIAGNOSTICS", + json.dumps({k: report[k] for k in ( + "status", "tests_run", "failure_count", "error_count", "skips")})) + return 0 if result.wasSuccessful() else 1 + except Exception as exc: + print("RESEARCH_DIAGNOSTICS_FAILED_CLOSED", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_release_preflight.py b/verification/research/pr113_release_preflight.py new file mode 100644 index 00000000..640dcd70 --- /dev/null +++ b/verification/research/pr113_release_preflight.py @@ -0,0 +1,124 @@ +"""PR113 detached B/H/T baseline snapshot, deliberately NOT a release gate. + +A stable engineering preflight for the original 45 obligations, 11 claim IDs, +259 complete source-owner compiler targets, preserved T tooling and original +A2 proof blobs. Both underlying archived verifiers must still run and obtain +genuine independent security authorization before their successor integration. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + +from final_closure_complete_lean_evidence import ROOT, required_inventory +from final_closure_authenticate import BASE, git +from final_closure_case_receipts import digest +from pr113_tooling_migration import audit as tooling_audit +from pr113_a2_successor_identity import audit as a2_audit + +CLAIMS = frozenset(("Q1","Q2","Q3","M1","M2","M3","N1","N2","N3","D8","D9")) + +class BaselineError(ValueError): + pass + +def need(ok, message): + if not ok: + raise BaselineError(message) + +def frozen_scope(case_ledger, claim_map, records): + cases=case_ledger.get("cases") if isinstance(case_ledger,dict) else None + claims=claim_map.get("claims") if isinstance(claim_map,dict) else None + need(isinstance(cases,list) and len(cases)==45 and + len({c.get("case_id") for c in cases if isinstance(c,dict)})==45, + "original acceptance cases were removed, duplicated or altered") + need(isinstance(claims,list) and len(claims)==11 and + {c.get("claim_id") for c in claims if isinstance(c,dict)}==CLAIMS, + "eleven mathematical claim owners were replaced") + need(isinstance(records,list) and len(records)==259 and + len({(r.get("path"),r.get("declaration")) for r in records + if isinstance(r,dict)})==259, + "canonical 259 theorem owner inventory incomplete") + owners={c for r in records for c in r.get("case_ids",[])} + cids={c for r in records for c in r.get("claim_ids",[])} + need(owners=={c["case_id"] for c in cases} and cids==CLAIMS, + "proof inventory lost original case/claim ownership") + return { + "original_case_count":45, + "original_case_ids":sorted(c["case_id"] for c in cases), + "claim_count":11,"claim_ids":sorted(CLAIMS), + "compiler_target_count":259, + "distinct_proof_declarations":259, + } + +def baseline(head, outfile): + output=Path(outfile) + need(not output.is_symlink(), "baseline output cannot be symlinked") + output=output.resolve() + root=ROOT.resolve() + need(not output.exists() and not output.is_relative_to(root) and + not root.is_relative_to(output), + "baseline must be a new source-external output") + need(git("rev-parse","HEAD")==head and + git("cat-file","-t",head)=="commit" and + git("cat-file","-t",BASE)=="commit", + "no authentic exact-H and B Git commits") + original=json.loads((root/"research/paper_closures/post_merge_112/" + "acceptance_decisions.json").read_bytes()) + claim=json.loads((root/"research/paper_closures/post_merge_112/" + "claim_declaration_map.json").read_bytes()) + inventory=required_inventory() + scoped=frozen_scope(original,claim,inventory) + historic=tooling_audit(root,head) + a2=a2_audit(root,head) + need(historic["status"]=="DRAFT_NEEDS_OWNER_AND_INDEPENDENT_SECURITY_REVIEW" + and historic["historical_archive_unchanged"] is True + and a2["status"]=="SOURCE_IDENTITY_CANDIDATE_NEEDS_INDEPENDENT_SECURITY_REVIEW" + and a2["successor_delta"]["accepted_a2_proof_blobs"]==4, + "original T or A2 source preservation was weakened") + policy=json.loads((root/"research/paper_closures/post_merge_112/" + "reviewer_authority.json").read_bytes()) + report={ + "format":"Permansson.PR113.ExactSourceBaseline.v1", + "status":"SOURCE_IDENTITY_ONLY_ALL_APPROVALS_PENDING", + "B":BASE,"H":head, + "B_tree":git("rev-parse",BASE+"^{tree}"), + "H_tree":git("rev-parse",head+"^{tree}"), + "original_acceptance_sha256":digest(json.dumps( + original,sort_keys=True,separators=(",",":")).encode()), + "claim_owner_sha256":digest(json.dumps( + claim,sort_keys=True,separators=(",",":")).encode()), + **scoped, + "historical_tooling_diff":historic["changed_canonical_tooling"], + "historical_archive_source_unchanged":True, + "A2_historical_proof_blobs":4, + "A2_original_verifier_untouched":True, + "original_security_review":False, + "T2_independent_security_review":False, + "A2_independent_security_review":False, + "reviewer_authority_status":policy.get("status"), + "independent_case_reviews_authenticated":0, + "independent_claim_reviews_authenticated":0, + "real_M":None,"paper_release_approved":False, + } + output.parent.mkdir(parents=True,exist_ok=True) + with output.open("x",encoding="utf-8") as writer: + writer.write(json.dumps(report,sort_keys=True,indent=2)+"\n") + return report + +def main(argv=None): + p=argparse.ArgumentParser(description=__doc__) + p.add_argument("--head",required=True) + p.add_argument("--output",type=Path,required=True) + a=p.parse_args(argv) + try: + r=baseline(a.head,a.output) + print(json.dumps({"status":r["status"],"H":r["H"], + "proof_count":r["compiler_target_count"]})) + return 0 + except (BaselineError,ValueError,KeyError,OSError) as exc: + print("PR113 BASELINE BLOCKED:",exc,file=sys.stderr) + return 2 +if __name__=="__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_required_dual_gate.py b/verification/research/pr113_required_dual_gate.py new file mode 100644 index 00000000..2b211fc3 --- /dev/null +++ b/verification/research/pr113_required_dual_gate.py @@ -0,0 +1,206 @@ +"""Proposed mandatory historical verification adapter; INACTIVE until review. + +Runs only from a genuine PR113 synthetic-merge required-check event. It +validates both original historical producer ZIPs and one real independently +approved, exact-H security review using the noncircular historical gate. +No local mode, no GitHub status overrides, no release or merge authority. + +The two original required workflow definitions and the original A2 test are +not changed by this module. Installation requires separate security review +of the complete 25+5 workflow set and explicit owner activation. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys + +import infrastructure_acceptance as historical + +ROOT = Path(__file__).resolve().parents[2] +FORMAT = "Permansson.PR113.VersionedRequiredHistoricalGate.v1" +REPOSITORY = "HMarcusWH/PermanssonLean" +PR = 113 +SHA = re.compile(r"^[0-9a-f]{40}$") +JOBS = { + "research": "Research Lean", + "archive-reproducibility": "Research integrity", +} + + +class RequiredGateError(ValueError): + pass + + +def need(ok, message): + if not ok: + raise RequiredGateError(message) + + +def actual_ci_event(*, env=None, git=None, event_data=None): + """Bind the actual runner event and checkout, not text-supplied H/B/S.""" + env = os.environ if env is None else env + need(env.get("GITHUB_ACTIONS") == "true" + and env.get("GITHUB_REPOSITORY") == REPOSITORY + and env.get("GITHUB_EVENT_NAME") == "pull_request" + and env.get("GITHUB_REF") == "refs/pull/113/merge" + and env.get("GITHUB_JOB") in JOBS + and env.get("GITHUB_WORKFLOW") == JOBS[env["GITHUB_JOB"]], + "must be an authentic PR113 original required-check runner event") + synthetic = env.get("GITHUB_SHA") + need(isinstance(synthetic, str) and SHA.fullmatch(synthetic), + "real GitHub PR test-merge source SHA absent") + try: + run_id,attempt=int(env.get("GITHUB_RUN_ID", "")), int( + env.get("GITHUB_RUN_ATTEMPT", "")) + except (ValueError,TypeError) as exc: + raise RequiredGateError("original GitHub run/attempt identity missing") from exc + need(run_id > 0 and attempt > 0, + "original required-check run identity not positive") + if event_data is None: + path=Path(env.get("GITHUB_EVENT_PATH", "")) + need(path.is_file() and not path.is_symlink() and + 0 < path.stat().st_size <= 8*1024*1024, + "original runner event JSON not available") + event_data=path.read_bytes() + need(type(event_data) is bytes and + 0 < len(event_data) <= 8*1024*1024, + "original event bytes absent or oversized") + try: + event=json.loads(event_data) + except (ValueError,TypeError) as exc: + raise RequiredGateError("invalid GitHub required-check event JSON") from exc + pr=event.get("pull_request") if isinstance(event,dict) else None + need(isinstance(pr,dict) and + event.get("number")==PR and + (event.get("repository") or {}).get("full_name")==REPOSITORY and + (pr.get("base") or {}).get("ref")=="main" and + (pr.get("base") or {}).get("repo",{}).get("full_name")==REPOSITORY and + (pr.get("head") or {}).get("repo",{}).get("full_name")==REPOSITORY, + "PR event is not source-bound to this repository's main") + base=(pr.get("base") or {}).get("sha") + head=(pr.get("head") or {}).get("sha") + need(isinstance(base,str) and SHA.fullmatch(base) and + isinstance(head,str) and SHA.fullmatch(head) and head!=base, + "missing authentic PR original head/base identity") + if git is None: + def git(*args): + try: + return subprocess.check_output(["git",*args],cwd=ROOT, + stderr=subprocess.DEVNULL,text=True,timeout=20).strip() + except (OSError,subprocess.CalledProcessError, + subprocess.TimeoutExpired) as exc: + raise RequiredGateError("original Git checkout unavailable") from exc + need(git("rev-parse","HEAD")==synthetic, + "runner's actual checkout is not the genuine GitHub test merge") + return { + "original_base_sha":base, "head_sha":head, + "synthetic_sha":synthetic,"github_run_id":run_id, + "github_run_attempt":attempt,"github_job":env["GITHUB_JOB"], + "event_sha256":hashlib.sha256(event_data).hexdigest(), + } + + +def verify(input_envelope, t_zip, a2_zip, *, env=None, git=None, + event_data=None, historical_gate=None): + # Authentic merged-main pushes require an independent ordered B/H/M check, + # not a forged replay of the draft-only PR test-merge source binding. + env = os.environ if env is None else env + if env.get("GITHUB_EVENT_NAME") == "push": + from pr113_historical_postmerge import verify as verify_main + if historical_gate is not None: + raise RequiredGateError("fake premerge acceptance cannot run on main") + # An omitted Git adapter means the real authenticated Git reader, + # not an explicit None that would disable postmerge source checking. + if git is None: + return verify_main(input_envelope,t_zip,a2_zip,env=env, + event_bytes=event_data) + return verify_main(input_envelope,t_zip,a2_zip,env=env, + git=git,event_bytes=event_data) + context=actual_ci_event(env=env,git=git,event_data=event_data) + need(isinstance(input_envelope,dict) + and isinstance(input_envelope.get("target"),dict) + and input_envelope["target"].get("base_sha")== + context["original_base_sha"] + and input_envelope["target"].get("head_sha")==context["head_sha"] + and input_envelope["target"].get("synthetic_sha")== + context["synthetic_sha"], + "runtime target does not match actual GitHub PR H/B/S") + if historical_gate is None: + historical_gate=historical.check_historical_only + source=historical_gate(input_envelope,t_zip,a2_zip) + need(isinstance(source,dict) and + source.get("status") == + "HISTORICAL_T_AND_A2_GENUINE_WITH_INDEPENDENT_SECURITY_REVIEW" + and source.get("source_head")==context["head_sha"] + and source.get("historical_source_and_security_review_authenticated") + is True + and source.get("required_ci_successful")==0 + and source.get("merge_authorized") is False + and source.get("publication_authorized") is False + and source.get("independent_mathematical_review") is False, + "historical dual-check acceptance is incomplete or asserts release") + return { + "format":FORMAT, + "status":"REQUIRED_HISTORICAL_SOURCE_AND_SECURITY_REVIEW_VERIFIED", + "head_sha":context["head_sha"], + "original_base_sha":context["original_base_sha"], + "synthetic_sha":context["synthetic_sha"], + "github_run_id":context["github_run_id"], + "github_run_attempt":context["github_run_attempt"], + "github_job":context["github_job"], + "event_sha256":context["event_sha256"], + "original_T_provenance":source["genuine_artifacts"]["T"], + "original_A2_provenance":source["genuine_artifacts"]["A2"], + "original_T_checks":source["T_execution"], + "original_A2_checks":source["A2_execution"], + "independent_security_review_authenticated":True, + "all_required_CI_accepted":False, + "mathematical_acceptance":False, + "owner_merge_authorized":False, + "publication_authorized":False, + } + + +def cli(argv=None): + ap=argparse.ArgumentParser(description=__doc__) + ap.add_argument("--evidence",type=Path,required=True) + ap.add_argument("--historical-T-zip",type=Path,required=True) + ap.add_argument("--historical-A2-zip",type=Path,required=True) + ap.add_argument("--out",type=Path,required=True) + args=ap.parse_args(argv) + try: + output=args.out.absolute() + need(not output.exists() and + not any(x.is_symlink() for x in (output,*output.parents)) and + not output.is_relative_to(ROOT.resolve()), + "required historical receipt must be new and detached") + envelope=args.evidence.read_bytes() + need(0 < len(envelope) <= 64*1024, + "original detached historical evidence too large") + zips=[] + for path in (args.historical_T_zip,args.historical_A2_zip): + need(path.is_file() and not path.is_symlink() and + 0 < path.stat().st_size <= 256*1024*1024, + "historical original ZIP absent or unbounded") + zips.append(path.read_bytes()) + result=verify(json.loads(envelope),*zips) + output.parent.mkdir(parents=True,exist_ok=True) + with output.open("x",encoding="utf-8") as writer: + json.dump(result,writer,sort_keys=True,indent=2) + writer.write("\n") + print(json.dumps({"status":result["status"],"head_sha":result["head_sha"]})) + return 0 + except (RequiredGateError,historical.InfrastructureError,OSError,TypeError, + ValueError,KeyError) as exc: + print("HISTORICAL REQUIRED GATE NOT ACCEPTED:",exc,file=sys.stderr) + return 2 + + +if __name__=="__main__": + raise SystemExit(cli()) diff --git a/verification/research/pr113_required_dual_gate_v2.py b/verification/research/pr113_required_dual_gate_v2.py new file mode 100644 index 00000000..aa555f5d --- /dev/null +++ b/verification/research/pr113_required_dual_gate_v2.py @@ -0,0 +1,212 @@ +"""Proposed mandatory historical verification adapter; INACTIVE until review. + +Runs only from a genuine PR113 synthetic-merge required-check event. It +validates both original historical producer ZIPs and one real independently +approved, exact-H security review using the noncircular historical gate. +No local mode, no GitHub status overrides, no release or merge authority. + +The two original required workflow definitions and the original A2 test are +not changed by this module. Installation requires separate security review +of the complete 25+5 workflow set and explicit owner activation. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys + +import infrastructure_acceptance_v2 as historical +from pr113_historical_evidence_contract_v2 import ( + InfrastructureEnvelopeError, decode_unique_json, +) + +ROOT = Path(__file__).resolve().parents[2] +FORMAT = "Permansson.PR113.VersionedRequiredHistoricalGate.v2" +REPOSITORY = "HMarcusWH/PermanssonLean" +PR = 113 +SHA = re.compile(r"^[0-9a-f]{40}$") +JOBS = { + "research": "Research Lean", + "archive-reproducibility": "Research integrity", +} + + +class RequiredGateError(ValueError): + pass + + +def need(ok, message): + if not ok: + raise RequiredGateError(message) + + +def actual_ci_event(*, env=None, git=None, event_data=None): + """Bind the actual runner event and checkout, not text-supplied H/B/S.""" + env = os.environ if env is None else env + need(env.get("GITHUB_ACTIONS") == "true" + and env.get("GITHUB_REPOSITORY") == REPOSITORY + and env.get("GITHUB_EVENT_NAME") == "pull_request" + and env.get("GITHUB_REF") == "refs/pull/113/merge" + and env.get("GITHUB_JOB") in JOBS + and env.get("GITHUB_WORKFLOW") == JOBS[env["GITHUB_JOB"]], + "must be an authentic PR113 original required-check runner event") + synthetic = env.get("GITHUB_SHA") + need(isinstance(synthetic, str) and SHA.fullmatch(synthetic), + "real GitHub PR test-merge source SHA absent") + try: + run_id,attempt=int(env.get("GITHUB_RUN_ID", "")), int( + env.get("GITHUB_RUN_ATTEMPT", "")) + except (ValueError,TypeError) as exc: + raise RequiredGateError("original GitHub run/attempt identity missing") from exc + need(run_id > 0 and attempt > 0, + "original required-check run identity not positive") + if event_data is None: + path=Path(env.get("GITHUB_EVENT_PATH", "")) + need(path.is_file() and not path.is_symlink() and + 0 < path.stat().st_size <= 8*1024*1024, + "original runner event JSON not available") + event_data=path.read_bytes() + need(type(event_data) is bytes and + 0 < len(event_data) <= 8*1024*1024, + "original event bytes absent or oversized") + try: + event=decode_unique_json(event_data, max_bytes=8*1024*1024) + except InfrastructureEnvelopeError as exc: + raise RequiredGateError("invalid or duplicated GitHub required-check event JSON") from exc + pr=event.get("pull_request") if isinstance(event,dict) else None + need(isinstance(pr,dict) and + event.get("number")==PR and + (event.get("repository") or {}).get("full_name")==REPOSITORY and + (pr.get("base") or {}).get("ref")=="main" and + (pr.get("base") or {}).get("repo",{}).get("full_name")==REPOSITORY and + (pr.get("head") or {}).get("repo",{}).get("full_name")==REPOSITORY, + "PR event is not source-bound to this repository's main") + base=(pr.get("base") or {}).get("sha") + head=(pr.get("head") or {}).get("sha") + need(isinstance(base,str) and SHA.fullmatch(base) and + isinstance(head,str) and SHA.fullmatch(head) and head!=base, + "missing authentic PR original head/base identity") + if git is None: + def git(*args): + try: + return subprocess.check_output(["git",*args],cwd=ROOT, + stderr=subprocess.DEVNULL,text=True,timeout=20).strip() + except (OSError,subprocess.CalledProcessError, + subprocess.TimeoutExpired) as exc: + raise RequiredGateError("original Git checkout unavailable") from exc + need(git("rev-parse","HEAD")==synthetic, + "runner's actual checkout is not the genuine GitHub test merge") + return { + "original_base_sha":base, "head_sha":head, + "synthetic_sha":synthetic,"github_run_id":run_id, + "github_run_attempt":attempt,"github_job":env["GITHUB_JOB"], + "event_sha256":hashlib.sha256(event_data).hexdigest(), + } + + +def verify(input_envelope, t_zip, a2_zip, *, env=None, git=None, + event_data=None, historical_gate=None): + # Authentic merged-main pushes require an independent ordered B/H/M check, + # not a forged replay of the draft-only PR test-merge source binding. + env = os.environ if env is None else env + if env.get("GITHUB_EVENT_NAME") == "push": + from pr113_historical_postmerge_v2 import verify as verify_main + if historical_gate is not None: + raise RequiredGateError("fake premerge acceptance cannot run on main") + # An omitted Git adapter means the real authenticated Git reader, + # not an explicit None that would disable postmerge source checking. + if git is None: + return verify_main(input_envelope,t_zip,a2_zip,env=env, + event_bytes=event_data) + return verify_main(input_envelope,t_zip,a2_zip,env=env, + git=git,event_bytes=event_data) + context=actual_ci_event(env=env,git=git,event_data=event_data) + need(isinstance(input_envelope,dict) + and isinstance(input_envelope.get("target"),dict) + and input_envelope["target"].get("base_sha")== + context["original_base_sha"] + and input_envelope["target"].get("head_sha")==context["head_sha"] + and input_envelope["target"].get("synthetic_sha")== + context["synthetic_sha"], + "runtime target does not match actual GitHub PR H/B/S") + if historical_gate is None: + historical_gate=historical.check_historical_only + source=historical_gate(input_envelope,t_zip,a2_zip) + need(isinstance(source,dict) and + source.get("format") == + "Permansson.PR113.HistoricalDualRequiredSourceReceipt.v2" + and source.get("historical_original_workflow_count")==25 + and source.get("current_reviewed_workflow_count")==30 + and isinstance(source.get("workflow_profile_sha256"),str) + and len(source["workflow_profile_sha256"])==64 + and source.get("status") == + "HISTORICAL_T_AND_A2_GENUINE_WITH_INDEPENDENT_SECURITY_REVIEW" + and source.get("source_head")==context["head_sha"] + and source.get("historical_source_and_security_review_authenticated") + is True + and source.get("required_ci_successful")==0 + and source.get("merge_authorized") is False + and source.get("publication_authorized") is False + and source.get("independent_mathematical_review") is False, + "historical dual-check acceptance is incomplete or asserts release") + return { + "format":FORMAT, + "status":"REQUIRED_HISTORICAL_SOURCE_AND_SECURITY_REVIEW_VERIFIED", + "head_sha":context["head_sha"], + "original_base_sha":context["original_base_sha"], + "synthetic_sha":context["synthetic_sha"], + "github_run_id":context["github_run_id"], + "github_run_attempt":context["github_run_attempt"], + "github_job":context["github_job"], + "event_sha256":context["event_sha256"], + "original_T_provenance":source["genuine_artifacts"]["T"], + "original_A2_provenance":source["genuine_artifacts"]["A2"], + "original_T_checks":source["T_execution"], + "original_A2_checks":source["A2_execution"], + "independent_security_review_authenticated":True, + "all_required_CI_accepted":False, + "mathematical_acceptance":False, + "owner_merge_authorized":False, + "publication_authorized":False, + } + + +def cli(argv=None): + ap=argparse.ArgumentParser(description=__doc__) + ap.add_argument("--evidence",type=Path,required=True) + ap.add_argument("--historical-T-zip",type=Path,required=True) + ap.add_argument("--historical-A2-zip",type=Path,required=True) + ap.add_argument("--out",type=Path,required=True) + args=ap.parse_args(argv) + try: + from pr113_boundary_io import detached_destination + output=detached_destination(args.out, ROOT) + envelope=args.evidence.read_bytes() + need(0 < len(envelope) <= 64*1024, + "original detached historical evidence too large") + zips=[] + for path in (args.historical_T_zip,args.historical_A2_zip): + need(path.is_file() and not path.is_symlink() and + 0 < path.stat().st_size <= 256*1024*1024, + "historical original ZIP absent or unbounded") + zips.append(path.read_bytes()) + result=verify(decode_unique_json(envelope),*zips) + output.parent.mkdir(parents=True,exist_ok=True) + with output.open("x",encoding="utf-8") as writer: + json.dump(result,writer,sort_keys=True,indent=2) + writer.write("\n") + print(json.dumps({"status":result["status"],"head_sha":result["head_sha"]})) + return 0 + except (RequiredGateError,historical.InfrastructureError,OSError,TypeError, + ValueError,KeyError) as exc: + print("HISTORICAL REQUIRED GATE NOT ACCEPTED:",exc,file=sys.stderr) + return 2 + + +if __name__=="__main__": + raise SystemExit(cli()) diff --git a/verification/research/pr113_reviewed_producer_branch_v3.py b/verification/research/pr113_reviewed_producer_branch_v3.py new file mode 100644 index 00000000..cd63f780 --- /dev/null +++ b/verification/research/pr113_reviewed_producer_branch_v3.py @@ -0,0 +1,111 @@ +"""Authenticate post-freeze producer branch against actual successor PR/Git. + +Original PR113 producer verification retains its existing v1 branch semantics. +A DIFFERENT branch requires real M113, an actual PR >114, ordered Git merge +topology when merged, exact source H and immutable M113 ancestry. +No operator hint, source flag, or reviewer text is accepted as authorization. +""" +from __future__ import annotations + +import re + +from final_closure_authenticate import live as real_live, git as real_git +from release_target import B113, REPOSITORY + +ORIGINAL_BRANCH = "research/final-mathematical-closure" +SHA = re.compile(r"^[0-9a-f]{40}$") +BRANCH = re.compile(r"^research/[A-Za-z0-9._/-]{1,160}$") + + +class ProducerBranchError(ValueError): + pass + + +def need(ok, why): + if not ok: + raise ProducerBranchError(why) + + +def authentic_branch(head, claimed_branch, *, live=real_live, git=real_git): + need(isinstance(head, str) and SHA.fullmatch(head) + and isinstance(claimed_branch, str), + "original producer head or branch missing") + if claimed_branch == ORIGINAL_BRANCH: + # Keep the immutable original 21-producer v1 behavior unchanged. + return claimed_branch + need(BRANCH.fullmatch(claimed_branch) is not None + and "//" not in claimed_branch and ".." not in claimed_branch + and claimed_branch != ORIGINAL_BRANCH, + "CERT producer branch malformed or aliases original PR113") + old = live("/pulls/113") + need(isinstance(old, dict) and old.get("number") == 113 + and old.get("merged") is True and old.get("state") == "closed", + "CERT cannot borrow unmerged PR113 infrastructure") + M113 = old.get("merge_commit_sha") + H113 = (old.get("head") or {}).get("sha") + need(isinstance(M113, str) and SHA.fullmatch(M113) + and isinstance(H113, str) and SHA.fullmatch(H113) + and git("cat-file", "-t", M113) == "commit" + and git("cat-file", "-t", H113) == "commit" + and git("show", "-s", "--format=%P", M113).split() == [B113, H113] + and git("rev-parse", M113+"^{tree}") == git("rev-parse", H113+"^{tree}") + and git("cat-file", "-t", head) == "commit" + and git("merge-base", "--is-ancestor", M113, head) == "", + "CERT producer head not descended from authentic ordered M113") + candidate = live("/commits/"+head+"/pulls?per_page=100") + need(isinstance(candidate, list) and len(candidate) < 100, + "source commit's PR discovery missing/truncated") + matching = [] + for pr in candidate: + if not isinstance(pr, dict): + continue + number = pr.get("number") + candidate_head, base = pr.get("head") or {}, pr.get("base") or {} + if not (type(number) is int and number > 114 + and candidate_head.get("sha") == head + and candidate_head.get("ref") == claimed_branch + and (candidate_head.get("repo") or {}).get("full_name") == REPOSITORY + and base.get("ref") == "main" + and (base.get("repo") or {}).get("full_name") == REPOSITORY): + continue + # The commit-associated PR listing is only discovery, not authority: + # GET the exact live PR, whose merged flag/parents can be authenticated. + actual = live("/pulls/" + str(number)) + if (not isinstance(actual, dict) or actual.get("number") != number + or (actual.get("head") or {}).get("sha") != head + or (actual.get("head") or {}).get("ref") != claimed_branch + or (actual.get("head") or {}).get("repo", {}).get("full_name") != REPOSITORY + or (actual.get("base") or {}).get("ref") != "main" + or (actual.get("base") or {}).get("repo", {}).get("full_name") != REPOSITORY): + continue + if actual.get("merged") is True and actual.get("state") == "closed": + M = actual.get("merge_commit_sha") + if not (isinstance(M, str) and SHA.fullmatch(M) + and git("cat-file", "-t", M) == "commit"): + continue + parents = git("show", "-s", "--format=%P", M).split() + if len(parents) != 2 or parents[1] != head: + continue + B = parents[0] + # Immutable Git parents, not a subsequently advanced base.sha. + if git("rev-parse", M+"^{tree}") != git("rev-parse", head+"^{tree}"): + continue + elif actual.get("state") == "open" and actual.get("merged") is False: + B = (actual.get("base") or {}).get("sha") + main = live("/branches/main") + if (B != base.get("sha") + or (main.get("commit") or {}).get("sha") != B): + continue + else: + continue + if not (isinstance(B, str) and SHA.fullmatch(B) + and git("cat-file", "-t", B) == "commit" + and git("merge-base", "--is-ancestor", M113, B) == "" + and git("merge-base", "--is-ancestor", B, head) == ""): + continue + matching.append(actual) + need(len(matching) == 1, + "CERT source head/branch not uniquely bound to genuine GitHub PR") + # The author, approval and evidence consumers still authenticate their + # own scope; matching a PR is only a source-location fact. + return claimed_branch diff --git a/verification/research/pr113_secure_security_review_v3.py b/verification/research/pr113_secure_security_review_v3.py new file mode 100644 index 00000000..84b25438 --- /dev/null +++ b/verification/research/pr113_secure_security_review_v3.py @@ -0,0 +1,112 @@ +"""Strict, source-bound independent security review for the *versioned* T2/A2. + +This replaces only the proposed v2/v3 review intake; the frozen v1 original +and historical T interpreter are never mutated. No synthetic review or +source-embedded status becomes reviewer authority. +""" +from __future__ import annotations + +import re +from final_closure_authenticate import current_reviews_by_user +from infrastructure_acceptance import ( + AUTHORITY_PATH, REVIEW_PREFIX, InfrastructureError, + exact_committed_blob, +) +from pr113_boundary_io import strict_json_object +from release_target import B113 + +SHA = re.compile(r"^[0-9a-f]{40}$") +SHA256 = re.compile(r"^[0-9a-f]{64}$") +CLAIMED = frozenset(("head_sha", "workflow_sha256", "T_artifact_sha256", + "A2_artifact_sha256", "original_base_sha")) + + +def need(ok, reason): + if not ok: + raise InfrastructureError(reason) + + +def authority_policy(head, *, git): + """Read the exact committed policy; reject duplicated JSON authority.""" + need(isinstance(head, str) and SHA.fullmatch(head), + "security reviewer policy requires genuine exact H") + try: + raw = exact_committed_blob(head, AUTHORITY_PATH, git=git) + policy = strict_json_object(raw, max_bytes=64*1024) + except (TypeError, OSError, ValueError) as exc: + raise InfrastructureError( + "independent reviewer roster absent or contains ambiguous JSON") from exc + need(isinstance(policy, dict) and + set(policy) == {"format", "status", "owner", "reviewers"} + and policy.get("format") == "Permansson.InfrastructureReviewerAuthority.v1" + and policy.get("status") == "OWNER_AUTHORIZED" + and policy.get("owner") == "HMarcusWH" + and type(policy.get("reviewers")) is list + and bool(policy["reviewers"]), + "independent reviewer not separately owner-authorized") + return policy + + +def approved_security_review(review_id, author, head, attest, policy, *, + paginate): + """Require a live, latest, nonauthor GitHub PR review on exact H/T2/A2.""" + need(type(review_id) is int and review_id > 0 + and type(author) is str and bool(author) + and type(head) is str and SHA.fullmatch(head), + "original security review identity or head is missing") + need(type(attest) is dict and set(attest) == CLAIMED + and attest["head_sha"] == head + and attest["original_base_sha"] == B113 + and all(type(attest[k]) is str and SHA256.fullmatch(attest[k]) + for k in ("workflow_sha256", "T_artifact_sha256", + "A2_artifact_sha256")), + "security review contract is not exact H/workflow/T/A2 source") + need(type(policy) is dict and + policy.get("format") == "Permansson.InfrastructureReviewerAuthority.v1" + and policy.get("status") == "OWNER_AUTHORIZED" + and policy.get("owner") == "HMarcusWH" + and type(policy.get("reviewers")) is list + and bool(policy["reviewers"]), + "security reviewer roster has not been owner-authorized") + rows = paginate("/pulls/113/reviews") + need(type(rows) is list, "genuine GitHub review source absent") + latest = current_reviews_by_user(rows) + matching = [] + for review in latest.values(): + if not isinstance(review, dict) or review.get("id") != review_id: + continue + login = (review.get("user") or {}).get("login") + need(type(login) is str and login != author + and review.get("commit_id") == head + and review.get("state") == "APPROVED" + and type(review.get("submitted_at")) is str + and bool(review["submitted_at"]), + "current exact-H security review is absent, dismissed or self-authored") + roster = [entry for entry in policy["reviewers"] + if type(entry) is dict + and entry.get("login") == login + and entry.get("approved_by") == "HMarcusWH" + and entry.get("independence_disclosed") is True] + need(len(roster) == 1, + "review author lacks a unique approved independent reviewer role") + body = review.get("body") + need(type(body) is str and 0 < len(body.encode("utf-8")) <= 64*1024, + "missing or unbounded original reviewer attestation") + declarations = [line.strip()[len(REVIEW_PREFIX):].strip() + for line in body.splitlines() + if line.strip().startswith(REVIEW_PREFIX)] + need(len(declarations) == 1, + "security reviewer attestation absent or ambiguous") + try: + record = strict_json_object(declarations[0].encode("utf-8"), + max_bytes=64*1024) + except (ValueError, TypeError) as exc: + raise InfrastructureError( + "duplicate, noncanonical or invalid security review JSON") from exc + need(type(record) is dict and set(record) == CLAIMED + and record == attest, + "independent reviewer did not approve exact T/H/A2 artifact SHA") + matching.append(review["id"]) + need(matching == [review_id], + "genuine latest independent exact-H security approval missing") + return True diff --git a/verification/research/pr113_selection_candidate_survey.py b/verification/research/pr113_selection_candidate_survey.py new file mode 100644 index 00000000..3c5ef12e --- /dev/null +++ b/verification/research/pr113_selection_candidate_survey.py @@ -0,0 +1,433 @@ +"""Survey exact-H positive-route and claim-test candidates (P1-06/P1-08). + +Reads ONLY the committed exact-H case/claim maps and the committed test and +Lean source blobs they name. It enumerates every original-case positive +route and every claim's test/Lean candidates, checks by AST that a named +test method is defined in exactly one class, and flags each choice that a +reviewer must still make. It writes a detached UNREVIEWED JSON/Markdown +survey, never a selection: method discovery is NOT mathematical relevance, +and no negative Lean statement is ever invented here. +""" +from __future__ import annotations + +import argparse +import ast +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys + +from pr113_v31_execution_integrity import ( + CASE_MAP, CLAIM_MAP, CLAIMS, ExactSource, LEAN_PATH, POSITIVE_ROLES, + RealControlError, TEST_METHOD, TEST_PATH, git_blob_reader, json_bytes, +) + +ROOT = Path(__file__).resolve().parents[2] +FORMAT = "Permansson.PR113.SelectionCandidateSurvey.v1" +JSON_NAME = "selection_candidate_survey.json" +MARKDOWN_NAME = "selection_candidate_survey.md" +CASE_COUNT = 45 +CASE_ID = re.compile(r"^[A-Za-z][A-Za-z0-9_-]{1,30}$") +ROLES = frozenset(POSITIVE_ROLES) | {"SEMANTIC_NEGATIVE", "SCOPE_BOUNDARY"} +SHA40 = re.compile(r"^[0-9a-f]{40}$") + + +class SurveyError(ValueError): + pass + + +def need(ok, message): + if not ok: + raise SurveyError(message) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def committed(source, path): + """Committed exact-H bytes, or None when the path is absent at H.""" + try: + return source.blob(path) + except RealControlError: + return None + + +def parsed(raw, path): + try: + return ast.parse(raw.decode("utf-8"), filename=path) + except (ValueError, UnicodeError, SyntaxError) as exc: + raise SurveyError("exact-H test source is not parseable: " + + path) from exc + + +def defining_classes(tree, method): + return sorted( + node.name for node in tree.body if isinstance(node, ast.ClassDef) and + any(isinstance(inner, (ast.FunctionDef, ast.AsyncFunctionDef)) and + inner.name == method for inner in node.body)) + + +def test_methods(tree): + return { + node.name: sorted( + inner.name for inner in node.body + if isinstance(inner, (ast.FunctionDef, ast.AsyncFunctionDef)) and + TEST_METHOD.fullmatch(inner.name)) + for node in tree.body if isinstance(node, ast.ClassDef) + } + + +def route_record(source, route): + path, method = route["path"], route["method"] + raw = committed(source, path) + record = {"path": path, "method": method, "role": route["role"], + "test_source_sha256": None, "defining_classes": []} + if raw is None: + record["status"] = "ABSENT_TEST_SOURCE" + return record + classes = defining_classes(parsed(raw, path), method) + record["test_source_sha256"] = sha(raw) + record["defining_classes"] = classes + record["status"] = ("ELIGIBLE_UNREVIEWED" if len(classes) == 1 else + "ABSENT_METHOD" if not classes else "AMBIGUOUS_CLASS") + return record + + +def provider_records(source, rows, *, name_key): + need(isinstance(rows, list) and rows, + "owner declares no Lean provider") + by_path = {} + for row in rows: + need(isinstance(row, dict) and isinstance(row.get("path"), str) and + LEAN_PATH.fullmatch(row["path"]) is not None and + ".." not in row["path"].split("/") and + isinstance(row.get(name_key), str) and row[name_key], + "foreign or malformed Lean provider declaration") + by_path.setdefault(row["path"], set()).add(row[name_key]) + records = [] + for path in sorted(by_path): + raw = committed(source, path) + records.append({ + "path": path, + "declarations": sorted(by_path[path]), + "present_at_head": raw is not None, + "source_sha256": None if raw is None else sha(raw), + }) + return records + + +def survey_case(source, case): + cid = case.get("case_id") + routes = case.get("test_method_candidates") + need(isinstance(cid, str) and CASE_ID.fullmatch(cid) and + isinstance(routes, list), "malformed original case owner") + seen = set() + for route in routes: + need(isinstance(route, dict) and + isinstance(route.get("path"), str) and + TEST_PATH.fullmatch(route["path"]) is not None and + isinstance(route.get("method"), str) and + TEST_METHOD.fullmatch(route["method"]) is not None and + route.get("role") in ROLES, + "malformed or foreign test route for case " + cid) + key = (route["path"], route["method"], route["role"]) + need(key not in seen, "duplicate test route for case " + cid) + seen.add(key) + ordered = sorted(routes, key=lambda r: (r["path"], r["method"], r["role"])) + positive = [route_record(source, r) for r in ordered + if r["role"] in POSITIVE_ROLES] + negative = [route_record(source, r) for r in ordered + if r["role"] == "SEMANTIC_NEGATIVE"] + providers = provider_records( + source, case.get("historical_proof_declarations"), name_key="name") + eligible = [r for r in positive if r["status"] == "ELIGIBLE_UNREVIEWED"] + present = [p for p in providers if p["present_at_head"]] + flags = set() + if len(eligible) > 1: + flags.add("MULTIPLE_ELIGIBLE_POSITIVE_ROUTES") + if not eligible: + flags.add("NO_ELIGIBLE_POSITIVE_ROUTE") + if any(r["status"] != "ELIGIBLE_UNREVIEWED" for r in positive): + flags.add("INELIGIBLE_POSITIVE_ROUTE_PRESENT") + if len(present) > 1: + flags.add("MULTIPLE_LEAN_PROVIDERS") + if not present: + flags.add("NO_LEAN_PROVIDER_AT_HEAD") + if len({(r["path"], r["method"]) for r in negative}) > 1: + flags.add("MULTIPLE_SEMANTIC_NEGATIVE_ROUTES") + choices = ["confirm the chosen positive route tests this case's original " + "criterion (discovery is not relevance)"] + if len(eligible) > 1: + choices.append("choose one of %d eligible positive routes" % + len(eligible)) + if len(present) > 1: + choices.append("choose the Lean provider among %d present at H" % + len(present)) + if not eligible or not present: + choices.append("supply a missing positive route or Lean provider; " + "this case cannot be selected yet") + return { + "case_id": cid, + "eligible_positive_route_count": len(eligible), + "positive_routes": positive, + "semantic_negative_routes": negative, + "scope_boundary_route_count": sum( + r["role"] == "SCOPE_BOUNDARY" for r in routes), + "lean_providers": providers, + "flags": sorted(flags), + "coverage_complete": bool(eligible and present), + "reviewer_choices": choices, + } + + +def survey_claim(source, claim, case_ids): + cid = claim.get("claim_id") + tests = claim.get("test_source_candidates") + links = claim.get("original_case_links") + need(cid in CLAIMS and isinstance(tests, list) and tests and + len(set(tests)) == len(tests) and isinstance(links, list) and + all(isinstance(x, str) and x in case_ids for x in links), + "malformed claim owner or foreign original case link") + providers = provider_records( + source, claim.get("declarations"), name_key="declaration") + need(claim.get("primary_path") in {p["path"] for p in providers}, + "claim primary provider is foreign to its declarations: " + cid) + sources = [] + for path in sorted(tests): + need(isinstance(path, str) and TEST_PATH.fullmatch(path) is not None, + "foreign or malformed claim test source: " + cid) + raw = committed(source, path) + methods = {} if raw is None else test_methods(parsed(raw, path)) + sources.append({ + "path": path, "present_at_head": raw is not None, + "test_source_sha256": None if raw is None else sha(raw), + "test_methods_by_class": methods, + "test_method_count": sum(len(m) for m in methods.values()), + }) + present = [p for p in providers if p["present_at_head"]] + available = sum(s["test_method_count"] for s in sources) + flags = {"POSITIVE_AND_NEGATIVE_TEST_METHODS_REQUIRE_REVIEW", + "FALSE_LEAN_STATEMENT_NOT_PROVIDED"} + if len(present) > 1: + flags.add("MULTIPLE_LEAN_PROVIDERS") + if not present: + flags.add("NO_LEAN_PROVIDER_AT_HEAD") + if len(sources) == 1: + flags.add("SINGLE_TEST_SOURCE") + if not all(s["present_at_head"] for s in sources): + flags.add("TEST_SOURCE_ABSENT_AT_HEAD") + if available < 2: + flags.add("FEWER_THAN_TWO_TEST_METHODS") + return { + "claim_id": cid, + "original_case_links": sorted(links), + "lean_providers": providers, + "test_sources": sources, + "negative_lean_statement": None, + "negative_lean_statement_status": + "NOT_PROVIDED_REVIEWER_MUST_AUTHOR_CRITERION_RELEVANT_FALSE_STATEMENT", + "flags": sorted(flags), + "coverage_complete": bool(present and available >= 2), + "reviewer_choices": [ + "choose the Lean provider supporting this claim" + if len(present) > 1 else + "confirm the single Lean provider supports this claim", + "choose distinct positive and negative test class/method from " + "%d discovered test methods" % available, + "author a claim-specific false Lean statement appended to the " + "original provider (no import/axiom/sorry change)", + ], + } + + +def build_survey(head, case_raw, claim_raw, read): + """Return the deterministic UNREVIEWED survey or fail closed.""" + need(isinstance(head, str) and SHA40.fullmatch(head) is not None, + "exact 40-hex source head required") + case_map, claim_map = json_bytes(case_raw), json_bytes(claim_raw) + try: + source = ExactSource(head, case_map, claim_map, read) + except RealControlError as exc: + raise SurveyError(str(exc)) from exc + cases, claims = case_map["cases"], claim_map["claims"] + need(len(source.cases) == CASE_COUNT and + all(isinstance(cid, str) for cid in source.cases), + "original case owners are missing, duplicated or malformed") + need(set(source.claims) == CLAIMS and len(claims) == len(CLAIMS) and + claim_map.get("count") == len(CLAIMS), + "claim owners are missing, duplicated or foreign") + surveyed_cases = [survey_case(source, case) + for case in sorted(cases, key=lambda c: c["case_id"])] + surveyed_claims = [survey_claim(source, claim, set(source.cases)) + for claim in sorted(claims, key=lambda c: c["claim_id"])] + # A test file shared between claims risks one claim borrowing another's + # positive/negative method; the reviewer must keep them claim-specific. + owners = {} + for claim in surveyed_claims: + for test in claim["test_sources"]: + owners.setdefault(test["path"], []).append(claim["claim_id"]) + for claim in surveyed_claims: + for test in claim["test_sources"]: + test["shared_with_claims"] = [ + cid for cid in owners[test["path"]] if cid != claim["claim_id"]] + if any(test["shared_with_claims"] for test in claim["test_sources"]): + claim["flags"] = sorted( + set(claim["flags"]) | {"TEST_SOURCE_SHARED_WITH_OTHER_CLAIMS"}) + + def flagged(rows, key, flag): + return [row[key] for row in rows if flag in row["flags"]] + + complete = (all(c["coverage_complete"] for c in surveyed_cases) and + all(c["coverage_complete"] for c in surveyed_claims)) + return { + "format": FORMAT, + "status": ("UNREVIEWED_SURVEY_COVERAGE_COMPLETE" if complete else + "UNREVIEWED_SURVEY_COVERAGE_INCOMPLETE"), + "source_head": head, + "case_map": {"path": CASE_MAP, "sha256": sha(case_raw), + "format": case_map.get("format"), + "status": case_map.get("status")}, + "claim_map": {"path": CLAIM_MAP, "sha256": sha(claim_raw), + "format": claim_map.get("format"), + "status": claim_map.get("status")}, + "summary": { + "original_cases": len(surveyed_cases), + "claims": len(surveyed_claims), + "coverage_complete": complete, + "cases_with_multiple_eligible_positive_routes": flagged( + surveyed_cases, "case_id", "MULTIPLE_ELIGIBLE_POSITIVE_ROUTES"), + "cases_with_multiple_lean_providers": flagged( + surveyed_cases, "case_id", "MULTIPLE_LEAN_PROVIDERS"), + "cases_with_multiple_semantic_negative_routes": flagged( + surveyed_cases, "case_id", + "MULTIPLE_SEMANTIC_NEGATIVE_ROUTES"), + "cases_with_ineligible_positive_routes": flagged( + surveyed_cases, "case_id", "INELIGIBLE_POSITIVE_ROUTE_PRESENT"), + "cases_without_complete_coverage": [ + c["case_id"] for c in surveyed_cases + if not c["coverage_complete"]], + "claims_requiring_positive_negative_test_choice": flagged( + surveyed_claims, "claim_id", + "POSITIVE_AND_NEGATIVE_TEST_METHODS_REQUIRE_REVIEW"), + "claims_with_multiple_lean_providers": flagged( + surveyed_claims, "claim_id", "MULTIPLE_LEAN_PROVIDERS"), + "claims_sharing_test_sources": flagged( + surveyed_claims, "claim_id", + "TEST_SOURCE_SHARED_WITH_OTHER_CLAIMS"), + "claims_without_complete_coverage": [ + c["claim_id"] for c in surveyed_claims + if not c["coverage_complete"]], + }, + "cases": surveyed_cases, + "claims": surveyed_claims, + "selection_written": False, + "independent_review": False, + "mathematical_relevance_established": False, + "accepted_cases": 0, + "accepted_claims": 0, + } + + +def markdown(report): + summary = report["summary"] + + def ids(values): + return ", ".join(values) if values else "none" + + lines = [ + "# PR113 selection candidate survey (UNREVIEWED)", + "", + "Generated from committed exact-H maps; not a selection, review or " + "acceptance. Method discovery does not establish mathematical " + "relevance, and no negative Lean statement is provided.", + "", + "- Source head: `%s`" % report["source_head"], + "- Case map SHA-256: `%s`" % report["case_map"]["sha256"], + "- Claim map SHA-256: `%s`" % report["claim_map"]["sha256"], + "- Status: `%s`" % report["status"], + "", + "## Summary", + "", + "| Item | Owners |", + "|---|---|", + ] + for key in sorted(k for k in summary if isinstance(summary[k], list)): + lines.append("| %s (%d) | %s |" % ( + key.replace("_", " "), len(summary[key]), ids(summary[key]))) + lines += ["", "## Original cases", "", + "| Case | Eligible positive routes | Lean providers | Flags |", + "|---|---|---|---|"] + for case in report["cases"]: + lines.append("| %s | %s | %s | %s |" % ( + case["case_id"], + "
".join("`%s::%s.%s` (%s, %s)" % ( + r["path"], ",".join(r["defining_classes"]) or "-", + r["method"], r["role"], r["status"]) + for r in case["positive_routes"]), + "
".join("`%s`%s" % (p["path"], "" if p["present_at_head"] + else " (absent)") + for p in case["lean_providers"]), + ", ".join(case["flags"]) or "-")) + lines += ["", "## Claims", "", + "| Claim | Lean providers | Test sources (methods) | Flags |", + "|---|---|---|---|"] + for claim in report["claims"]: + lines.append("| %s | %s | %s | %s |" % ( + claim["claim_id"], + "
".join("`%s`" % p["path"] for p in claim["lean_providers"]), + "
".join("`%s` (%d)" % (s["path"], s["test_method_count"]) + for s in claim["test_sources"]), + ", ".join(claim["flags"]))) + return "\n".join(lines) + "\n" + + +def write(head, output): + need(isinstance(head, str) and SHA40.fullmatch(head) is not None, + "exact 40-hex source head required") + actual = subprocess.run(["git", "rev-parse", "HEAD"], cwd=ROOT, + capture_output=True, text=True, timeout=30) + need(actual.returncode == 0 and actual.stdout.strip() == head, + "stale/not actual exact-H source checkout") + target = Path(output) + need(not target.is_symlink() and + not target.name.startswith("reviewed_"), + "survey output must not be symlinked or named as a reviewed input") + target = target.resolve() + need(not target.exists() and not target.is_relative_to(ROOT.resolve()), + "survey output must be a new directory outside the Git checkout") + read = git_blob_reader(head, ROOT) + try: + case_raw, claim_raw = read(CASE_MAP), read(CLAIM_MAP) + except RealControlError as exc: + raise SurveyError(str(exc)) from exc + report = build_survey(head, case_raw, claim_raw, read) + target.mkdir(parents=True) + (target / JSON_NAME).write_text( + json.dumps(report, sort_keys=True, indent=2) + "\n", encoding="utf-8") + (target / MARKDOWN_NAME).write_text(markdown(report), encoding="utf-8") + return report + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head", required=True) + parser.add_argument("--out", required=True, type=Path) + args = parser.parse_args(argv) + try: + report = write(args.head, args.out) + except (SurveyError, RealControlError, OSError, KeyError, + TypeError) as exc: + print("PR113 SELECTION SURVEY BLOCKED:", exc, file=sys.stderr) + return 2 + print(json.dumps({"status": report["status"], + "original_cases": report["summary"]["original_cases"], + "claims": report["summary"]["claims"]}, + sort_keys=True)) + return 0 if report["summary"]["coverage_complete"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_semantic_receipt_v2.py b/verification/research/pr113_semantic_receipt_v2.py new file mode 100644 index 00000000..76220898 --- /dev/null +++ b/verification/research/pr113_semantic_receipt_v2.py @@ -0,0 +1,94 @@ +"""Stricter current v2 A2 producer receipt validator; not historical source. + +Validate the *actual* compiler diagnostics, not merely returncode!=0 and the +literal word "error". The original frozen historical executor is unchanged. +""" +from __future__ import annotations + +import hashlib +from pathlib import PurePosixPath + +import infrastructure_acceptance as legacy +from localized_prefix_gate import ( + semantic_rejection as historical_rejection, + mutated_source as historical_mutated_source, +) +from pr113_a2_successor_compiler import ( + semantic_rejection as successor_rejection, + mutated_source as successor_mutated_source, +) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def validate_compiler_receipt_v2(files, prefix, expected_status, expected_mutations): + row, receipt_sha = legacy.verify_compiler_receipt( + files, prefix, expected_status, expected_mutations) + # A genuine source compiler always executes four real Lean file probes + # before the signature/positive/mutant controls. An earlier candidate + # omitted source_1..source_4 and rejected every authentic real receipt. + mandatory = set(expected_mutations) | { + "compiler_version", "research_build", "signatures_axioms", + "positive_fixture", "source_1", "source_2", "source_3", "source_4", + } + mode = row.get("mode") + legacy.need(mode in {"focused", "full"}, "A2 compiler mode absent or foreign") + optional = {"original_default_build"} if mode == "full" else set() + commands = row["commands"] + names = [entry.get("label") for entry in commands] + expected_commands = mandatory | optional + legacy.need(len(names) == len(set(names)) + and set(names) == expected_commands, + "A2 command identities are missing, repeated or unrecognized") + positive_path = prefix + "/PositiveControl.lean" + legacy.need(positive_path in files + and len(files[positive_path]) > 0, + "A2 positive fixture original bytes missing") + positive_sha = sha(files[positive_path]) + mutate = (historical_mutated_source if prefix == "historical_original_A2" + else successor_mutated_source) + legacy.need(prefix in ("historical_original_A2", "pr113_successor_A2"), + "foreign A2 proof runner prefix") + try: + positive_text = files[positive_path].decode("utf-8", "strict") + except UnicodeError as exc: + raise legacy.InfrastructureError("A2 original positive Lean source not UTF-8") from exc + for mutant in expected_mutations: + entry = row["mutations"][mutant] + source_path = prefix + "/" + mutant + ".lean" + legacy.need(source_path in files + and entry.get("positive_source_sha256") == positive_sha + and entry.get("mutant_source_sha256") == sha(files[source_path]) + and files[source_path] != files[positive_path], + "A2 mutant bytes not bound to the positive source") + try: + expected_source = mutate(positive_text, mutant).encode("utf-8") + except (KeyError, ValueError, RuntimeError) as exc: + raise legacy.InfrastructureError( + "A2 intended semantic mutation does not match the real positive fixture") from exc + legacy.need(files[source_path] == expected_source, + "A2 named mutant altered more or other source than the intended false theorem") + rejection = (historical_rejection if prefix == "historical_original_A2" + else successor_rejection) + for command in commands: + label = command["label"] + if label not in expected_mutations: + legacy.need(command["returncode"] == 0, + "A2 positive/setup control not successful") + continue + log_path = prefix + "/" + command["log"] + log = files[log_path].decode("utf-8", errors="replace") + # Reject unrelated errors from appended or imported source. Lean's + # diagnostics identify the source filename, which must be this mutant. + marker = label + ".lean:" + legacy.need(marker in log, + "A2 negative error is not from the named mutant fixture") + try: + accepted = rejection(command["returncode"], log) + except Exception as exc: + raise legacy.InfrastructureError( + "A2 negative failed for setup/parser/resource reason") from exc + legacy.need(accepted, "A2 negative lacked a semantic proof failure") + return row, receipt_sha diff --git a/verification/research/pr113_successor_workflow_profile.py b/verification/research/pr113_successor_workflow_profile.py new file mode 100644 index 00000000..1ffcf3ba --- /dev/null +++ b/verification/research/pr113_successor_workflow_profile.py @@ -0,0 +1,354 @@ +"""Exact proposed 25+5 GitHub workflow profile: read-only and NOT activation. + +A candidate source manifest is not independent security approval. This +preflight explicitly retains all 25 approved historical workflows byte-for- +byte and binds five proposed successor workflow source blobs to a versioned +review dossier. It never edits .github/workflows or relaxes historical T/A2. +""" +from __future__ import annotations + +import argparse +import ast +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys + +# Direct invocation (python verification/research/pr113_successor_workflow_profile.py) +# otherwise includes only verification/research in sys.path, not the repository +# package root. Resolve the exact local checkout before import, then audit all +# 25 protected Git blobs against the original hashes as before. +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from release_target import B113, T +# Do NOT import the current workflow contract: the independently staged v2 +# replacement intentionally removes the historical APPROVED_WORKFLOWS symbol. +# The 25-source contract is instead parsed from its immutable actual Git blob. +HISTORICAL_25_HEAD = "5189f0c4a0db7535d57a9ff2a7aef6a3e76191b6" +HISTORICAL_CONTRACT = "verification/integrity/workflow_contract.py" +HISTORICAL_CONTRACT_BLOB = "f8fa8196a7a0e33d2f218faa0ba570c09aac9a3a" +MANIFEST = "docs/operations/pr113_unapproved_workflows/manifest.json" +FORMAT = "Permansson.PR113.SuccessorWorkflowProfile.v1" +PATH = re.compile(r"^\.github/workflows/[a-zA-Z0-9_.-]+\.ya?ml$") +STAGED_PREFIX = "docs/operations/pr113_unapproved_workflows/" +USES = re.compile(r"(?m)^\s+- uses: [A-Za-z0-9_.@/-]+@([a-f0-9]{40})\s*$") +SHA = re.compile(r"^[a-f0-9]{40}$") +PROPOSED = frozenset(( + "pr113-historical-migration-candidates.yml", + "pr113-original-positive-controls-candidate.yml", + "pr113-postseal-replay.yml", + "pr113-source-controls-v3_1.yml", + "pr113-preseal-fullscope.yml", +)) + + +class ProfileError(ValueError): + pass + + +def need(ok, reason): + if not ok: + raise ProfileError(reason) + + +def digest(raw): + return hashlib.sha256(raw).hexdigest() + + +def git_bytes(head, path=None, *, op=None): + if op is not None: + args=list(op) + else: + # One immutable historical Python source is the ONLY non-workflow + # exception. Never borrow the same path from current H or another + # commit, and never admit prefix lookalikes or Git revspec injection. + need(type(head) is str and SHA.fullmatch(head) is not None and + type(path) is str, + "untrusted workflow source path") + workflow_path=PATH.fullmatch(path) is not None + staged_name=(path[len(STAGED_PREFIX):] + if path.startswith(STAGED_PREFIX) else "") + staged_path=(bool(staged_name) and + re.fullmatch(r"[A-Za-z0-9_.-]+",staged_name) is not None) + historical_contract_only=( + head==HISTORICAL_25_HEAD and path==HISTORICAL_CONTRACT) + need(workflow_path or staged_path or historical_contract_only, + "untrusted workflow source path") + args=["show",head+":"+path] + try: + proc=subprocess.run(["git",*args],cwd=ROOT,capture_output=True, + timeout=30,check=False) + except (OSError,subprocess.TimeoutExpired) as exc: + raise ProfileError("original Git source unavailable") from exc + need(proc.returncode==0 and len(proc.stdout)<=4*1024*1024, + "Git workflow source unavailable") + return proc.stdout + + + +def parse_historical_25_contract(source): + """Read exactly the historical 25 SHA-256 pins; never execute old Python. + + The original source uses one named SHA-256 literal for the Research + integrity workflow and 24 additional string literals. Every other AST + expression and all duplicate names are rejected, rather than evaluated. + """ + need(type(source) is bytes and 0 None: + if not condition: + raise ToolingMigrationError(message) + + +def git(root: Path, *args: str) -> bytes: + try: + result = subprocess.run( + ["git", *args], cwd=root, capture_output=True, timeout=45) + except (OSError, subprocess.TimeoutExpired) as exc: + raise ToolingMigrationError("Git source object unavailable") from exc + if result.returncode != 0 or len(result.stdout) > MAX_OBJECT: + raise ToolingMigrationError("Git rejected or oversized source object: " + + " ".join(args[:3])) + return result.stdout + + +def sha(raw: bytes) -> str: + return hashlib.sha256(raw).hexdigest() + + +def exact_tree(root: Path, rev: str, *prefixes: str) -> dict[str, tuple[str, str]]: + raw = git(root, "ls-tree", "-r", "-z", "--full-tree", rev, "--", *prefixes) + result = {} + for item in raw.split(b"\0"): + if not item: + continue + try: + header, name = item.split(b"\t", 1) + mode, kind, oid = header.decode("ascii").split() + path = name.decode("utf-8") + except (ValueError, UnicodeError) as exc: + raise ToolingMigrationError("Malformed Git tree entry") from exc + require(kind == "blob" and mode in ("100644", "100755") + and path not in result, "Unsafe duplicate/nonfile Git tooling") + result[path] = (mode, oid) + return result + + +def audit(root: Path, head: str, *, + historical_t: str = T, release_base: str = B) -> dict: + root = Path(root).resolve() + require(all(isinstance(k, str) and SHA40.fullmatch(k) + for k in (head, historical_t, release_base)), + "Invalid immutable Git object ID") + require(git(root, "rev-parse", "HEAD").decode().strip() == head, + "Checkout does not match requested source head") + for old in (historical_t, release_base): + try: + git(root, "merge-base", "--is-ancestor", old, head) + except ToolingMigrationError as exc: + raise ToolingMigrationError( + "Historical source/base is not an ancestor of candidate") from exc + old_snapshot = git(root, "rev-parse", f"{release_base}:{SUPPLEMENT}").strip() + new_snapshot = git(root, "rev-parse", f"{head}:{SUPPLEMENT}").strip() + require(old_snapshot == new_snapshot, + "Historical supplement bytes changed since accepted base B") + manifest_raw = git(root, "show", f"{head}:{SUPPLEMENT}/archive-manifest.json") + manifest = json.loads(manifest_raw) + data = manifest.get("tooling") + require(isinstance(data, dict) and + data.get("source_commit") == historical_t, + "Historical archive no longer binds the genuine T commit") + rows = data.get("files") + require(isinstance(rows, list) and len(rows) >= 3, + "Historical packaged tooling closure is incomplete") + for row in rows: + require(isinstance(row, dict), "Malformed frozen tooling declaration") + path, blob, expected = row.get("path"), row.get("blob"), row.get("sha256") + require(isinstance(path, str) and + (path.startswith("verification/integrity/") or + path.startswith("verification/archive/") or + path == "archival/v0.2.0/README.md") and + isinstance(blob, str) and SHA40.fullmatch(blob) and + isinstance(expected, str) and re.fullmatch("[0-9a-f]{64}", expected), + "Untrusted tooling closure member") + original = git(root, "show", f"{historical_t}:{path}") + packaged = git(root, "show", f"{head}:{SUPPLEMENT}/tooling/{path}") + actual_oid = git(root, "rev-parse", f"{historical_t}:{path}").decode().strip() + require(original == packaged and sha(original) == expected + and actual_oid == blob, + "Historical packaged T object failed independent byte comparison: " + + path) + + original = exact_tree(root, historical_t, *TOOLING) + current = exact_tree(root, head, *TOOLING) + require(set(original) == set(current), + "Canonical T-to-H tooling file membership was changed") + changed = sorted(path for path in original + if current[path] != original[path]) + require(set(changed) == EXPECTED_CHANGES, + "Unexpected canonical T-to-H tooling divergence: " + repr(changed)) + workflows = exact_tree(root, head, ".github/workflows") + require(len(workflows) == 25 and + all(path.endswith((".yml", ".yaml")) + and path.startswith(".github/workflows/") + for path in workflows), + "Successor candidate no longer has the exact 25-workflow inventory") + return { + "format": "Permansson.PR113.ToolingMigrationProposal.v1", + "status": "DRAFT_NEEDS_OWNER_AND_INDEPENDENT_SECURITY_REVIEW", + "source_commit_sha": head, + "original_archive_T": historical_t, + "original_release_base_B": release_base, + "historical_supplement_tree": new_snapshot.decode(), + "historical_manifest_sha256": sha(manifest_raw), + "historical_packaged_tooling_files_verified": len(rows), + "current_workflow_file_count": len(workflows), + "changed_canonical_tooling": [ + {"path": path, + "T_blob": original[path][1], "H_blob": current[path][1]} + for path in changed + ], + "historical_archive_unchanged": True, + "new_successor_T_approved": False, + "independent_tooling_review": False, + "mathematical_acceptance": False, + "full_release_approved": False, + "request": "Authorize a new versioned T/R release closure without " + "rewriting the immutable v0.2.0 archive.", + } + + +def main(argv=None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--head", required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args(argv) + try: + target = args.output.resolve() + require(not target.exists() and + not target.is_relative_to(ROOT.resolve()) and + not ROOT.resolve().is_relative_to(target), + "Tooling proposal must be detached from the Git checkout") + result = audit(ROOT, args.head) + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(json.dumps(result, sort_keys=True, indent=2) + "\n", + encoding="utf-8") + print(json.dumps({"status": result["status"], + "changed_paths": [r["path"] for r in + result["changed_canonical_tooling"]]})) + return 0 + except (ToolingMigrationError, OSError, ValueError, KeyError) as exc: + print("PR113 TOOLING MIGRATION BLOCKED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_tooling_migration_v2.py b/verification/research/pr113_tooling_migration_v2.py new file mode 100644 index 00000000..b8d478df --- /dev/null +++ b/verification/research/pr113_tooling_migration_v2.py @@ -0,0 +1,137 @@ +"""T2 30-workflow review candidate. The v1 25-file audit remains unchanged. + +The historical accepted T archive is read at its actual Git revision and +compared byte-for-byte with the fixed supplemental bundle. Both known tooling +edits are enumerated, never turned into a global wildcard exception. +""" +from __future__ import annotations +import argparse +import json +from pathlib import Path +import re +import sys + +from pr113_tooling_migration import ( + ROOT, T, B, SUPPLEMENT, TOOLING, EXPECTED_CHANGES, + ToolingMigrationError, exact_tree, git, sha, require, SHA40, +) +from pr113_workflow_profile_v2 import audit as audit_workflows +from pr113_boundary_io import strict_json_object + +FORMAT = "Permansson.PR113.ToolingMigrationCandidate.v2" + + +def audit(root, head, *, phase="staged"): + root = Path(root).resolve(strict=True) + require(isinstance(head, str) and SHA40.fullmatch(head), + "invalid versioned current head identity") + require(git(root, "rev-parse", "HEAD").decode().strip() == head, + "candidate checkout no longer matches its actual commit") + for ancestor in (T, B): + git(root, "merge-base", "--is-ancestor", ancestor, head) + original_tree = git(root, "rev-parse", B + ":" + SUPPLEMENT).strip() + actual_tree = git(root, "rev-parse", head + ":" + SUPPLEMENT).strip() + require(original_tree == actual_tree, + "historical accepted supplement changed from original base") + manifest_raw = git(root, "show", head + ":" + SUPPLEMENT + + "/archive-manifest.json") + try: + manifest = strict_json_object(manifest_raw) + except (ValueError, UnicodeError) as exc: + raise ToolingMigrationError("original T archive manifest malformed") from exc + tooling = manifest.get("tooling") if isinstance(manifest, dict) else None + require(isinstance(tooling, dict) and tooling.get("source_commit") == T, + "original historical T source not pinned by archived manifest") + # The original manifest is a COMPLETE 35-file historical source closure. + # A self-consistent partial repackage is NOT the approved T artifact. + old = exact_tree(root, T, *TOOLING) + current = exact_tree(root, head, *TOOLING) + files = tooling.get("files") + require(isinstance(files, list) and len(files) == len(old) == 35, + "historical packaged T closure is not the complete 35-file set") + declared = set() + for row in files: + require(isinstance(row, dict) + and set(row) == {"path", "blob", "sha256"}, + "historical T manifest row invalid or has unknown keys") + path, oid, digest = row["path"], row["blob"], row["sha256"] + require(isinstance(path, str) and path in old + and path not in declared and + isinstance(oid, str) and SHA40.fullmatch(oid) + and isinstance(digest, str) and + re.fullmatch(r"[0-9a-f]{64}", digest), + "historical T manifest has missing, foreign or duplicate source") + declared.add(path) + at_t = git(root, "show", T + ":" + path) + packaged = git(root, "show", head + ":" + SUPPLEMENT + + "/tooling/" + path) + original_oid = git(root, "rev-parse", T + ":" + path).decode().strip() + require(at_t == packaged and sha(at_t) == digest and + oid == original_oid == old[path][1], + "historical T file is not the byte-exact frozen member: " + path) + require(declared == set(old), "historical packaged T omits an original file") + require(set(old) == set(current), "T2 changed historical tooling membership") + require(all(old[path][0] == current[path][0] for path in old), + "historical tooling file mode changed (not an approved migration)") + changed = sorted(path for path in old if old[path][1] != current[path][1]) + require(set(changed) == EXPECTED_CHANGES, + "changed integrity verifier surface not limited to named two files") + profile = audit_workflows(root, head, phase=phase) + require(profile.get("current_reviewed_workflow_count") == 30 + and profile.get("historical_original_workflow_count") == 25 + and profile.get("independent_security_review") is False, + "current 30-workflow source profile not authenticated") + return { + "format": FORMAT, + "status": "T2_EXACT_SOURCE_REVIEW_PENDING", + "source_commit_sha": head, + "original_archive_T": T, + "original_release_base_B": B, + "historical_supplement_tree": actual_tree.decode(), + "historical_manifest_sha256": sha(manifest_raw), + "historical_packaged_tooling_files_verified": len(files), + "changed_canonical_tooling": [ + {"path": path, "T_blob": old[path][1], + "H_blob": current[path][1]} for path in changed], + "historical_original_workflow_count": 25, + "current_reviewed_workflow_count": 30, + "reviewed_workflow_source_mode": phase, + "workflow_profile_sha256": profile["current_source_profile_sha256"], + "inherited_mutable_action_refs": profile["inherited_mutable_action_refs"], + "historical_archive_unchanged": True, + "new_successor_T_approved": False, + "independent_security_review": False, + "owner_activation_authorized": False, + "mathematical_acceptance": False, + "full_release_approved": False, + } + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--head", required=True) + parser.add_argument("--phase", choices=("staged", "active"), default="staged") + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args(argv) + try: + result = audit(ROOT, args.head, phase=args.phase) + dest = args.out.absolute() + require(not dest.exists() and not dest.is_symlink() + and not dest.is_relative_to(ROOT.resolve()) + and not any(p.is_symlink() for p in dest.parents), + "v2 candidate report output must be fresh, detached, unsymlinked") + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", + encoding="utf-8") + print(json.dumps({"status": result["status"], + "head_sha": result["source_commit_sha"], + "current_workflows": result["current_reviewed_workflow_count"]}, + sort_keys=True)) + return 0 + except (OSError, ValueError, KeyError) as exc: + print("T2 UNAPPROVED SOURCE PROFILE REJECTED:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/pr113_v31_execution_integrity.py b/verification/research/pr113_v31_execution_integrity.py new file mode 100644 index 00000000..17d11805 --- /dev/null +++ b/verification/research/pr113_v31_execution_integrity.py @@ -0,0 +1,516 @@ +"""Revalidate actual v3.1 control execution bytes inside authenticated GH ZIPs. + +This validates retained bytes and diagnostic kinds, NOT mathematical +relevance, historical security approval, reviewer authority, or release. +Call only after final_closure_extra_producers_v31 authenticates the actual +run, job, check, workflow Git blob and original GitHub artifact digest. + +The full validator also binds every positive and claim owner to its exact-H +canonical route (case/claim map, committed test and Lean source bytes, +named class/method and its own `unittest -v` line). An optional reviewed +selection is compared exactly; route binding is NOT relevance review. +""" +from __future__ import annotations + +import ast +import hashlib +import json +from pathlib import Path, PurePosixPath +import re +import subprocess + +from final_closure_producer_bytes import inspect_zip, ProducerBytesError + +SHA256 = re.compile(r"^[0-9a-f]{64}$") +CLAIMS = frozenset(("Q1", "Q2", "Q3", "M1", "M2", "M3", + "N1", "N2", "N3", "D8", "D9")) +STATUSES = { + "original_positive": "SOURCE_TEST_AND_LEAN_EXECUTED_UNREVIEWED", + "legacy_negative": "SOURCE_APPENDED_REPLAYED_UNREVIEWED_NOT_CI_ATTRIBUTED", + "claim_pair": "LEAN_POSITIVE_AND_FALSE_MUTANT_EXECUTED_UNREVIEWED", +} +REQUIRED_CLAIM_MEMBERS = frozenset(( + "original.lean", "appended_false.lean", + "positive_lean.log", "negative_lean.log", + "positive_test.stdout", "positive_test.stderr", + "negative_test.stdout", "negative_test.stderr", +)) +POSITIVE_FORMAT = "Permansson.PR113.OriginalCasePositiveCandidate.v1" +POSITIVE_ROLES = ("COMPILER_SOURCE", "EXACT_CHECK") +TEST_PATH = re.compile(r"^verification/research/test_[A-Za-z0-9_]+\.py$") +LEAN_PATH = re.compile(r"^Permansson(?:Lean|Research)/[A-Za-z0-9_/]+\.lean$") +IDENT = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") +TEST_METHOD = re.compile(r"^test_[A-Za-z0-9_]+$") +CASE_MAP = "research/paper_closures/post_merge_112/acceptance_case_source_map.json" +CLAIM_MAP = "research/paper_closures/post_merge_112/claim_declaration_map.json" +POSITIVE_SELECTION = "Permansson.PR113.OriginalPositiveSelection.v1" +CLAIM_SELECTION_KEYS = frozenset(( + "claim_id", "proof_source_path", "positive_test_path", + "positive_test_class", "positive_test_method", + "negative_test_path", "negative_test_class", "negative_test_method", +)) + + +class RealControlError(ValueError): + pass + + +class ExactSource: + """Canonical exact-H routes plus a committed-blob reader (read-only). + + `blob(path)` must return the committed Git blob of `path` at `head`. + Selections, when given, are reviewed external inputs keyed by owner ID; + their absence is reported, never treated as relevance review. + """ + + def __init__(self, head, case_map, claim_map, blob, *, + positive_selection=None, claim_selection=None): + cases = case_map.get("cases") if isinstance(case_map, dict) else None + claims = claim_map.get("claims") if isinstance(claim_map, dict) else None + need(isinstance(head, str) and re.fullmatch(r"[0-9a-f]{40}", head) and + isinstance(cases, list) and isinstance(claims, list) and + callable(blob), "exact-H canonical route source unavailable") + self.head = head + self.cases = {row.get("case_id"): row for row in cases + if isinstance(row, dict)} + self.claims = {row.get("claim_id"): row for row in claims + if isinstance(row, dict)} + need(len(self.cases) == len(cases) and + len(self.claims) == len(claims), + "canonical case/claim route identities are not unique") + self._blob = blob + self._cache = {} + self.positive_selection = self._positive(positive_selection) + self.claim_selection = self._claims(claim_selection) + + def _positive(self, payload): + if payload is None: + return None + need(isinstance(payload, dict) and + payload.get("format") == POSITIVE_SELECTION and + isinstance(payload.get("cases"), list), + "reviewed positive selection is malformed") + rows = {} + for row in payload["cases"]: + need(isinstance(row, dict) and row.get("case_id") in self.cases and + row["case_id"] not in rows, + "reviewed positive selection names a foreign/duplicate case") + rows[row["case_id"]] = row + return rows + + def _claims(self, payload): + if payload is None: + return None + need(isinstance(payload, dict) and + isinstance(payload.get("claims"), list), + "reviewed claim selection is malformed") + rows = {} + for row in payload["claims"]: + need(isinstance(row, dict) and set(row) == CLAIM_SELECTION_KEYS and + row["claim_id"] in self.claims and row["claim_id"] not in rows, + "reviewed claim selection names a foreign/duplicate claim") + rows[row["claim_id"]] = row + return rows + + def blob(self, path): + need(isinstance(path, str) and path and + not PurePosixPath(path).is_absolute() and + ".." not in PurePosixPath(path).parts and ":" not in path, + "unsafe canonical source path") + if path not in self._cache: + raw = self._blob(path) + need(type(raw) is bytes, "exact-H committed source unavailable: " + + path) + self._cache[path] = raw + return self._cache[path] + + +def git_blob_reader(head, root): + """Read committed blobs of exact H without trusting the working tree.""" + def read(path): + try: + result = subprocess.run( + ["git", "cat-file", "blob", head + ":" + path], + cwd=root, capture_output=True, timeout=30) + except (OSError, subprocess.TimeoutExpired) as exc: + raise RealControlError("committed exact-H source unavailable") from exc + need(result.returncode == 0, + "source not tracked at exact H: " + path) + return result.stdout + return read + + +def exact_source(head, root=None, *, positive_selection=None, + claim_selection=None): + root = Path(root) if root is not None else Path(__file__).resolve().parents[2] + read = git_blob_reader(head, root) + return ExactSource(head, json_bytes(read(CASE_MAP)), + json_bytes(read(CLAIM_MAP)), read, + positive_selection=positive_selection, + claim_selection=claim_selection) + + +def need(ok, message): + if not ok: + raise RealControlError(message) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def json_bytes(raw): + need(type(raw) is bytes and len(raw) <= 4 * 1024 * 1024, + "unbounded or missing source control JSON") + try: + return json.loads(raw) + except (ValueError, UnicodeError, TypeError) as exc: + raise RealControlError("original control receipt is invalid JSON") from exc + + +def member(members, name, expected): + need(isinstance(name, str) and name and + name in members and + type(members[name]) is bytes and + len(members[name]) <= 4 * 1024 * 1024 and + isinstance(expected, str) and SHA256.fullmatch(expected) and + sha(members[name]) == expected, + "original producer member not present at authenticated digest: " + + str(name)) + return members[name] + + +def source_clean_negative(log): + text = log.decode("utf-8", errors="replace").lower() + need("error:" in text and + any(kind in text for kind in + ("type mismatch", "unsolved goals", "application type mismatch")), + "Lean did not reject the intended mathematical proof goal") + need(not any(kind in text for kind in ( + "unknown module", "unknown identifier", "failed to import", + "parser error", "syntax error", "out of memory", "timed out", + "timeout", "maximum number of heartbeats", + "failed to synthesize", "sorryax", "unknown tactic", + )), "Lean failed for tooling/parser/resource rather than mathematics") + + +def defines_method(raw, cls, method): + try: + tree = ast.parse(raw.decode("utf-8")) + except (ValueError, UnicodeError, SyntaxError) as exc: + raise RealControlError("exact-H test source is not parseable") from exc + found = [node for node in tree.body + if isinstance(node, ast.ClassDef) and node.name == cls and + sum(isinstance(inner, (ast.FunctionDef, ast.AsyncFunctionDef)) + and inner.name == method for inner in node.body) == 1] + return len(found) == 1 + + +def ran_exactly(stderr, test_path, method, cls=None): + """Return the class of the one named `unittest -v` method that passed.""" + text = stderr.decode("utf-8", errors="replace") + module = re.escape(PurePosixPath(test_path).stem) + name = re.escape(method) + klass = re.escape(cls) if cls is not None else r"[A-Za-z_][A-Za-z0-9_]*" + found = re.findall( + r"(?m)^" + name + r" \(" + module + r"\.(" + klass + r")(?:\." + + name + r")?\)(?:\n[^\n]*)? \.\.\. ok$", text) + need(len(found) == 1 and + len(re.findall(r"(?m)^Ran 1 test in ", text)) == 1 and + re.search(r"(?m)^OK$", text) and "FAILED (" not in text, + "registered test log is not the exact named passing method") + return found[0] + + +def bind_positive(source, owner_id, wrapper): + need(source is not None, "exact-H canonical route source required") + case = source.cases.get(owner_id) + need(isinstance(case, dict), "positive owner is not an original case") + path, cls = wrapper.get("test_path"), wrapper.get("test_class") + method, proof = wrapper.get("test_name"), wrapper.get("proof_source_path") + need(wrapper.get("format") == POSITIVE_FORMAT and + isinstance(path, str) and TEST_PATH.fullmatch(path) and + isinstance(cls, str) and IDENT.fullmatch(cls) and + isinstance(method, str) and TEST_METHOD.fullmatch(method) and + isinstance(proof, str) and LEAN_PATH.fullmatch(proof), + "positive owner route identity is missing or malformed") + routes = [r for r in case.get("test_method_candidates", ()) + if isinstance(r, dict) and r.get("path") == path and + r.get("method") == method and r.get("role") in POSITIVE_ROLES] + need(len(routes) == 1 and routes[0]["role"] == wrapper.get("positive_role"), + "positive test is not this case's frozen positive route") + need(any(isinstance(d, dict) and d.get("path") == proof + for d in case.get("historical_proof_declarations", ())), + "positive Lean provider is foreign to the original case") + test_bytes, proof_bytes = source.blob(path), source.blob(proof) + need(wrapper.get("source_sha256") == sha(test_bytes) and + wrapper.get("proof_source_sha256") == sha(proof_bytes), + "positive test/Lean source digest differs from exact H") + need(defines_method(test_bytes, cls, method), + "selected positive class/method is not defined at exact H") + if source.positive_selection is not None: + need(source.positive_selection.get(owner_id) == { + "case_id": owner_id, "test_path": path, "test_class": cls, + "test_method": method, "proof_source_path": proof}, + "positive owner differs from the reviewed route selection") + + +def bind_claim(source, owner_id, wrapper, content): + need(source is not None, "exact-H canonical route source required") + claim = source.claims.get(owner_id) + need(isinstance(claim, dict), "claim owner is not a mapped new claim") + original = wrapper.get("original_lean_source_path") + tests = { + side: (wrapper.get(side + "_test_path"), + wrapper.get(side + "_test_name")) + for side in ("positive", "negative") + } + digests = wrapper.get("source_test_hashes") + need(isinstance(original, str) and LEAN_PATH.fullmatch(original) and + any(isinstance(d, dict) and d.get("path") == original + for d in claim.get("declarations", ())), + "claim Lean provider is foreign to the mapped claim") + need(content["original.lean"] == source.blob(original), + "claim original Lean bytes differ from exact H") + need(isinstance(digests, dict) and set(digests) == set(tests) and + tests["positive"][1] != tests["negative"][1], + "claim test identities are incomplete or reused") + classes = {} + for side, (path, method) in tests.items(): + need(isinstance(path, str) and TEST_PATH.fullmatch(path) and + path in claim.get("test_source_candidates", ()) and + isinstance(method, str) and TEST_METHOD.fullmatch(method), + "claim test is not a mapped source candidate") + raw = source.blob(path) + need(digests[side] == sha(raw), + "claim test source digest differs from exact H") + cls = ran_exactly(content[side + "_test.stderr"], path, method) + need(defines_method(raw, cls, method), + "claim test class/method is not defined at exact H") + classes[side] = cls + if source.claim_selection is not None: + need(source.claim_selection.get(owner_id) == { + "claim_id": owner_id, "proof_source_path": original, + "positive_test_path": tests["positive"][0], + "positive_test_class": classes["positive"], + "positive_test_method": tests["positive"][1], + "negative_test_path": tests["negative"][0], + "negative_test_class": classes["negative"], + "negative_test_method": tests["negative"][1]}, + "claim owner differs from the reviewed claim selection") + + +def verify_owner(kind, owner_id, receipt_name, members, head, source=None, original_producers=None): + """Return an unreviewed byte-evidence diagnostic or fail closed. + + Without `source` only retained bytes are checked (producer-side packing); + the final authenticator always passes the exact-H ExactSource. + """ + need(kind in STATUSES and isinstance(owner_id, str) and owner_id and + isinstance(receipt_name, str) and receipt_name.endswith(".json") and + receipt_name in members, + "missing typed original control owner") + wrapper = json_bytes(members[receipt_name]) + owner_key = "claim_id" if kind == "claim_pair" else "case_id" + need(wrapper.get("status") == STATUSES[kind] and + wrapper.get("head_sha") == head and + wrapper.get(owner_key) == owner_id and + wrapper.get("scientific_release_approved") is False, + "source execution owner falsely promoted or mismatched") + base = receipt_name.rsplit("/", 1)[0] + "/" + if kind == "original_positive": + need(wrapper.get("positive_role") in ("COMPILER_SOURCE", "EXACT_CHECK") and + type(wrapper.get("source_positive_exit_code")) is int and + wrapper["source_positive_exit_code"] == 0 and + type(wrapper.get("registered_method_exit_code")) is int and + wrapper["registered_method_exit_code"] == 0 and + wrapper.get("actual_producing_check_run_id") is None and + wrapper.get("authenticated_ci_artifact") is False and + wrapper.get("independent_case_review") is False, + "invalid original positive control execution") + logs = ( + ("unittest_stdout.log", "unittest_stdout_sha256"), + ("unittest_stderr.log", "unittest_stderr_sha256"), + ("source_positive.log", "source_positive_log_sha256"), + ) + raw = {} + for name, digest_field in logs: + raw[name] = member(members, base + name, wrapper.get(digest_field)) + test = raw["unittest_stderr.log"].decode("utf-8", errors="replace") + compiled = raw["source_positive.log"].decode("utf-8", errors="replace").lower() + need("Ran 1 test" in test and "OK" in test and + "skipped=" not in test and "FAILED (" not in test and + "error:" not in compiled and "sorryax" not in compiled, + "registered true control or Lean source did not pass") + if source is not None: + bind_positive(source, owner_id, wrapper) + ran_exactly(raw["unittest_stderr.log"], wrapper["test_path"], + wrapper["test_name"], wrapper["test_class"]) + return {"kind": kind, "owner_id": owner_id, + "actual_log_members_checked": 3} + + if kind == "claim_pair": + need(owner_id in CLAIMS and + wrapper.get("format") == "Permansson.PR113.ClaimOnlyControlCandidate.v1" and + wrapper.get("claim_ids") == [owner_id] and + wrapper.get("case_ids") == [] and + type(wrapper.get("lean_positive_exit_code")) is int and + wrapper.get("lean_positive_exit_code") == 0 and + type(wrapper.get("lean_negative_exit_code")) is int and + wrapper["lean_negative_exit_code"] != 0 and + wrapper.get("actual_ci_check_run_id") is None and + wrapper.get("independent_mathematical_review") is False and + wrapper.get("accepted_claim") is False, + "claim positive/negative paths are missing or self-approved") + hashes = wrapper.get("retained_member_sha256") + need(isinstance(hashes, dict) and set(hashes) == REQUIRED_CLAIM_MEMBERS, + "claim proof source/log set is incomplete") + content = { + name: member(members, base + name, hashes[name]) + for name in REQUIRED_CLAIM_MEMBERS + } + original = content["original.lean"] + false = content["appended_false.lean"] + need(len(original) > 0 and false.startswith(original) and + len(false) > len(original), "claim false proof was not append-only") + append = false[len(original):].decode("utf-8", errors="strict") + need(not re.search( + r"(?mi)^\s*(?:import|axiom|constant|opaque|set_option)\b", + append) and + not re.search(r"(?i)\b(?:sorry|sorryAx|admit)\b", append), + "claim mutant silently changed its proof assumptions") + source_clean_negative(content["negative_lean.log"]) + pl = content["positive_lean.log"].decode("utf-8", errors="replace").lower() + need("error:" not in pl and "sorryax" not in pl and + "ok" in content["positive_test.stderr"].decode( + "utf-8", errors="replace").lower() and + "ok" in content["negative_test.stderr"].decode( + "utf-8", errors="replace").lower(), + "claim source positive / registered test failed") + if source is not None: + bind_claim(source, owner_id, wrapper, content) + return {"kind": kind, "owner_id": owner_id, + "actual_log_members_checked": len(content)} + + need(wrapper.get("independent_case_review") is False and + wrapper.get("actual_ci_check_run_id") is None and + wrapper.get("mathematical_acceptance") is False and + type(wrapper.get("historical_producing_artifact_id")) is int and + wrapper["historical_producing_artifact_id"] > 0, + "historical source-negative owner fabricated current CI acceptance") + replay_raw = member( + members, wrapper.get("execution_receipt_member"), + wrapper.get("execution_receipt_sha256")) + replay = json_bytes(replay_raw) + raw_zip = members.get(wrapper.get("semantic_zip_member")) + need(type(raw_zip) is bytes and + len(raw_zip) <= 64 * 1024 * 1024 and + sha(raw_zip) == wrapper.get("semantic_zip_sha256"), + "historical original source-appended replay ZIP missing or modified") + try: + nested = inspect_zip(raw_zip) + except ProducerBytesError as exc: + raise RealControlError("unsafe historical nested compiler ZIP") from exc + mut = replay.get("mutation_record") + need(replay.get("format") == + "Permansson.LegacyAppendOnlyReplayCandidate.v1" and + replay.get("status") == + "REPLAYED_APPEND_ONLY_UNREVIEWED_UNATTRIBUTED" and + replay.get("case_id") == owner_id and + replay.get("head_sha") == head and + replay.get("independent_review") is False and + replay.get("mathematical_acceptance") is False and + replay.get("ci_check_run_id") is None and + isinstance(mut, dict) and + mut.get("head_sha") == head and + mut.get("case_ids") == [owner_id] and + mut.get("outcome") == "REJECTED_SEMANTIC" and + type(mut.get("compiler_exit_code")) is int and + mut["compiler_exit_code"] != 0 and + isinstance(mut.get("positive_control"), dict) and + mut["positive_control"].get("compiled") is True and + mut["positive_control"].get("exit_code") == 0, + "historical replay did not actually execute source positive and false") + pos = mut["positive_control"] + poslog = member(nested, pos.get("log_member"), pos.get("log_sha256")) + neglog = member(nested, mut.get("log_member"), mut.get("log_sha256")) + original = member(nested, mut.get("original_lean_source_member"), + mut.get("original_lean_source_sha256")) + false = member(nested, mut.get("mutant_source_member"), + mut.get("mutant_source_sha256")) + need(false.startswith(original) and len(false) > len(original), + "legacy false assertion did not retain full original Lean provider") + need("error:" not in poslog.decode("utf-8", errors="replace").lower(), + "historical original positive control did not compile") + source_clean_negative(neglog) + if source is not None: + from pr113_legacy_route_binding import bind_legacy, LegacyRouteError + try: + bind_legacy( + head, owner_id, replay, mut, wrapper, + original, false, source=source, + authenticated_original=original_producers) + except LegacyRouteError as exc: + raise RealControlError( + "original legacy case/provider/observation route mismatch: " + + str(exc)) from exc + return {"kind": kind, "owner_id": owner_id, + "actual_log_members_checked": 4} + + +def validate_executed_controls(head, contract, zipped, *, source, original_producers=None): + """Validate all 76 original GH ZIP owner payloads, without review claims.""" + need(isinstance(contract, dict) and contract.get("head_sha") == head and + isinstance(zipped, dict), "original v3.1 CI payload unavailable") + need(isinstance(source, ExactSource) and source.head == head, + "exact-H canonical route source required for 76-owner binding") + need(isinstance(original_producers, dict) and + original_producers.get("status") == + "REAL_PRODUCING_BYTES_UNREVIEWED" and + original_producers.get("head_sha") == head and + original_producers.get("producer_count") == 21, + "all 21 authenticated original producers required for legacy capture linkage") + owners = {} + jobs = contract.get("producers") + need(isinstance(jobs, list) and jobs and + {j.get("artifact_id") for j in jobs} == set(zipped), + "producer artifacts not in exact declared set") + for job in jobs: + raw = zipped[job["artifact_id"]] + need(type(raw) is bytes, "missing genuine original ZIP bytes") + try: + members = inspect_zip(raw) + except ProducerBytesError as exc: + raise RealControlError("unsafe original source producer ZIP") from exc + for row in job["owners"]: + key = (row["kind"], row["owner_id"]) + need(key not in owners, "duplicate source control owner") + name = row["receipt_member"] + member(members, name, row["receipt_sha256"]) + owners[key] = verify_owner( + row["kind"], row["owner_id"], name, members, head, + source, original_producers) + from collections import Counter + n = Counter(kind for kind, _ in owners) + need(len(owners) == 76 and n == { + "original_positive": 45, "legacy_negative": 20, "claim_pair": 11 + }, "original complete 45/20/11 source-control ownership incomplete") + need({k[1] for k in owners if k[0] == "claim_pair"} == CLAIMS, + "claim owner identity differs from original eleven") + return { + "format": "Permansson.PR113.GenuineSourceExecutionBytes.v1", + "status": "ACTUAL_CONTROL_BYTES_REVALIDATED_UNREVIEWED", + "source_head": head, + "source_owner_count": 76, + "claim_positive_negative_directions": 22, + "execution_log_members_checked": sum( + row["actual_log_members_checked"] for row in owners.values()), + "canonical_routes_bound": True, + "legacy_original_captures_bound": True, + "reviewed_positive_selection_bound": + source.positive_selection is not None, + "reviewed_claim_selection_bound": source.claim_selection is not None, + "independent_review": False, + "mathematical_acceptance": False, + "publication_approved": False, + } diff --git a/verification/research/pr113_workflow_profile_v2.py b/verification/research/pr113_workflow_profile_v2.py new file mode 100644 index 00000000..7df63dd8 --- /dev/null +++ b/verification/research/pr113_workflow_profile_v2.py @@ -0,0 +1,283 @@ +"""Exact 25-history / 30-successor workflow candidate, without activation authority. + +Read all workflow bytes from Git objects. The historical 25-workflow preactivation +snapshot is a real, immutable ancestor, NOT the 19-workflow #113 original base. +This checker does not install files, authorize an action tag, or accept CI. +""" +from __future__ import annotations + +import hashlib +import importlib.util +import json +from pathlib import Path +import re + +from pr113_boundary_io import strict_json_object +from pr113_tooling_migration import ( + ROOT, git, exact_tree, require, ToolingMigrationError, +) + +FORMAT = "Permansson.PR113.VersionedWorkflowEvidence.v2" +PREACTIVATION_HEAD = "5189f0c4a0db7535d57a9ff2a7aef6a3e76191b6" +STAGE = "docs/operations/pr113_required_gate_replacement_unapproved/" +PROPOSALS = "docs/operations/pr113_unapproved_workflows/" +CONTRACT = STAGE + "workflow_contract_v2.py" +CONTRACT_MANIFEST = STAGE + "workflow_contract_v2_manifest.json" +FULL_MANIFEST = STAGE + "full_workflow_manifest.json" +PROPOSAL_MANIFEST = PROPOSALS + "manifest.json" +SHA = re.compile(r"^[a-f0-9]{40}$") + + +def _json(root, head, path): + raw = git(root, "show", head + ":" + path) + require(0 < len(raw) <= 2_000_000, "unbounded staged manifest") + try: + item = strict_json_object(raw) + except (UnicodeError, ValueError) as exc: + raise ToolingMigrationError("noncanonical workflow manifest: " + path) from exc + require(isinstance(item, dict), "workflow manifest is not an object") + return item + + +def _blob(root, head, path): + raw = git(root, "show", head + ":" + path) + require(0 < len(raw) <= 1_000_000, "missing/oversized reviewed Git blob") + oid = git(root, "rev-parse", head + ":" + path).decode().strip() + require(SHA.fullmatch(oid) is not None, "invalid Git blob object identity") + computed=hashlib.sha1( + b"blob "+str(len(raw)).encode("ascii")+b"\0"+raw).hexdigest() + require(computed==oid, "Git object contents differ from recorded blob OID") + try: + text = raw.decode("utf-8") + except UnicodeError as exc: + raise ToolingMigrationError("workflow is not UTF-8") from exc + return oid, text + + +def _contract(root, head): + manifest = _json(root, head, CONTRACT_MANIFEST) + require(manifest.get("format") == + "Permansson.PR113.ReviewPendingVersionedWorkflowContract.v2" + and manifest.get("installed") is False + and manifest.get("independent_security_review") is False + and manifest.get("owner_activation_authorized") is False + and manifest.get("mathematical_acceptance") is False + and manifest.get("publication_authorized") is False, + "versioned proposal incorrectly asserts outside approval") + oid, text = _blob(root, head, CONTRACT) + require(oid == manifest.get("staged_git_blob_oid"), + "versioned contract source differs from committed Git blob pin") + source = root / CONTRACT + require(source.is_file() and not source.is_symlink() + and source.read_bytes() == text.encode("utf-8"), + "versioned contract must load exact clean committed source") + spec = importlib.util.spec_from_file_location( + "pr113_review_pending_v2_exact_profile", source) + require(spec is not None and spec.loader is not None, + "versioned contract module unavailable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + expected = manifest.get("expected_workflow_git_blobs") + require(isinstance(expected, dict) and len(expected) == 30 + and expected == module.APPROVED_WORKFLOW_BLOBS, + "30-source manifest and actual versioned contract disagree") + return module, manifest + + +def _historical(root, head): + git(root, "merge-base", "--is-ancestor", PREACTIVATION_HEAD, head) + old = exact_tree(root, PREACTIVATION_HEAD, ".github/workflows") + require(len(old) == 25 + and all(mode == "100644" for mode, oid in old.values()), + "preactivation historical 25-workflow reference is not intact") + return {p.removeprefix(".github/workflows/"): oid + for p, (mode, oid) in old.items()} + + + +def source_phase(root, head): + """Return the only two permissible inventory phases from real Git tree. + + This is a classification only, not approval. Always call audit() with the + result before claiming even a source-bound engineering preflight. + """ + root = Path(root).resolve(strict=True) + require(isinstance(head, str) and SHA.fullmatch(head) is not None + and git(root, "rev-parse", "HEAD").decode().strip() == head, + "phase classification requires exact checked Git HEAD") + tree = exact_tree(root, head, ".github/workflows") + require(len(tree) in (25, 30) + and all(mode == "100644" and + path.startswith(".github/workflows/") and + path.endswith((".yml", ".yaml")) + for path, (mode, oid) in tree.items()), + "workflow source phase must be exactly 25 staged or 30 active Git blobs") + return "staged" if len(tree) == 25 else "active" + + +def audited_source_phase(root, head): + """Verify the complete exact Git phase, not merely its filename count.""" + phase = source_phase(root, head) + return phase, audit(root, head, phase=phase) + + +def audit(root, head, *, phase="staged"): + """Return an exact-content preview, never an activation certificate. + + staged: 25 active objects plus two source-staged replacements and five + proposed additions make an exact virtual 30-object candidate. + active: require 30 actual installed objects + matching installed guard/test; + this remains review-pending until separate real external authorization. + """ + root = Path(root).resolve(strict=True) + require(phase in ("staged", "active") + and isinstance(head, str) and SHA.fullmatch(head), + "invalid successor workflow phase or source head") + require(git(root, "rev-parse", "HEAD").decode().strip() == head, + "current checkout is not the declared actual head") + require(not git(root, "status", "--porcelain=v1", "--untracked-files=all"), + "workflow review source is dirty/untracked") + originals = _historical(root, head) + module, manifest = _contract(root, head) + full = _json(root, head, FULL_MANIFEST) + proposals = _json(root, head, PROPOSAL_MANIFEST) + replacements = full.get("full_replacements") + additions = proposals.get("proposals") + require(full.get("format") == + "Permansson.PR113.FullRequiredWorkflowPreactivation.v1" + and full.get("independent_security_review") is False + and full.get("owner_activation_authorized") is False + and full.get("mathematical_acceptance") is False + and full.get("publication_authorized") is False + and isinstance(replacements, list) and len(replacements) == 2 + and proposals.get("format") == + "Permansson.PR113.UnreviewedWorkflowProposals.v1" + and proposals.get("independent_security_review") is False + and proposals.get("owner_authorized_activation") is False + and isinstance(additions, list) and len(additions) == 5, + "incomplete/incorrectly approved 2+5 workflow staging") + replaced = {} + for row in replacements: + require(isinstance(row, dict) + and row.get("installed") is False + and row.get("independent_reviewed") is False, + "unreviewed replacement says installed/approved") + target = row.get("target") + path = row.get("staged_source") + require(isinstance(target, str) + and target.startswith(".github/workflows/") + and isinstance(path, str) and path.startswith(STAGE), + "invalid exact replacement path") + oid, text = _blob(root, head, path) + name = target.removeprefix(".github/workflows/") + require(name in originals and oid == row.get("git_blob_oid") + and name not in replaced, + "replacement SHA/name does not match exact 25-version history") + replaced[name] = (oid, text) + proposed = {} + for row in additions: + require(isinstance(row, dict) and + row.get("installed") is False and row.get("approved") is False, + "unreviewed new workflow says installed/approved") + target = row.get("workflow_path") + path = row.get("staged_source_path") + require(isinstance(target, str) + and target.startswith(".github/workflows/") + and isinstance(path, str) and path.startswith(PROPOSALS), + "invalid source of reviewed additional workflow") + name = target.removeprefix(".github/workflows/") + oid, text = _blob(root, head, path) + require(name not in originals and name not in proposed + and oid == row.get("git_blob_oid"), + "new workflow duplicates original or mismatches staged Git blob") + proposed[name] = (oid, text) + require(set(replaced) == set(module.REPLACEMENT_WORKFLOWS) + and set(proposed) == set(module.ADDITION_WORKFLOWS) + and set(originals) - set(replaced) == + set(module.RETAINED_HISTORICAL_WORKFLOWS), + "original/replacement/addition partition is not exactly 23+2+5") + tree = exact_tree(root, head, ".github/workflows") + if phase == "staged": + require(len(tree) == 25 and + {p.removeprefix(".github/workflows/"): v[1] + for p, v in tree.items()} == originals, + "preactivation source cannot silently alter original 25 files") + source = {} + for name in originals: + source[name] = _blob(root, head, ".github/workflows/" + name)[1] + source.update({n: text for n, (oid, text) in replaced.items()}) + source.update({n: text for n, (oid, text) in proposed.items()}) + else: + require(len(tree) == 30 and + all(mode == "100644" for mode, oid in tree.values()) + and {p.removeprefix(".github/workflows/") for p in tree} == + set(module.APPROVED_WORKFLOW_BLOBS), + "activated source is not exactly the 30 reviewed workflow objects") + source = {} + for name in module.APPROVED_WORKFLOW_BLOBS: + path = ".github/workflows/" + name + oid, text = _blob(root, head, path) + require(tree[path] == ("100644", oid) + and module.git_blob_oid(text.encode("utf-8")) == oid, + "activated workflow differs from committed exact blob") + source[name] = text + for name in module.RETAINED_HISTORICAL_WORKFLOWS: + require(tree[".github/workflows/" + name][1] == originals[name], + "a retained historical workflow was changed") + installations = { + "verification/integrity/workflow_contract.py": CONTRACT, + "verification/integrity/tests/test_workflow.py": + STAGE + "test_workflow_v2.py", + "verification/research/test_localized_prefix_compiler.py": + STAGE + "test_localized_prefix_compiler.py", + } + for installed, staged in installations.items(): + require(git(root, "rev-parse", head + ":" + installed).strip() == + git(root, "rev-parse", head + ":" + staged).strip(), + "source migration incomplete or test/contract unreviewed: " + + installed) + # Authenticate the versioned historical artifact transport separately + # from workflow YAMLs; the installed job must not borrow a v1 event gate. + transport_path="verification/research/pr113_historical_artifact_delivery_v2.py" + transport_oid,transport_text=_blob(root,head,transport_path) + require(module.git_blob_oid(transport_text.encode("utf-8"))==transport_oid + and 'FORMAT = "Permansson.PR113.ExactHistoricalArtifactDelivery.v2"' + in transport_text + and "from pr113_required_dual_gate_v2 import actual_ci_event" + in transport_text + and "from pr113_historical_postmerge_v2 import exact_main_event" + in transport_text, + "versioned transport source differs from exact Git blob or v2 event gate") + approved = module.validate_workflow_set(source) + actual = {name: module.git_blob_oid(text.encode("utf-8")) + for name, text in sorted(source.items())} + require(actual == module.APPROVED_WORKFLOW_BLOBS, + "30 workflow full blob map has unexpected values") + profile_digest = hashlib.sha256(json.dumps( + actual, sort_keys=True, separators=(",", ":")).encode()).hexdigest() + return { + "format": FORMAT, + "status": ("EXACT_STAGED_25_TO_30_CANDIDATE_UNAPPROVED" + if phase == "staged" else + "EXACT_ACTIVE_30_CANDIDATE_SECURITY_REVIEW_PENDING"), + "head_sha": head, + "historical_original_head": PREACTIVATION_HEAD, + "historical_original_workflow_count": 25, + "current_reviewed_workflow_count": 30, + "retained_historical_count": 23, + "replacement_count": 2, + "addition_count": 5, + "versioned_source_phase": phase, + "current_source_profile_sha256": profile_digest, + "expected_source_blobs": actual, + "inherited_mutable_action_refs": + approved["retained_historical_mutable_action_refs"], + "all_actions_commit_pinned": + approved["all_workflow_actions_commit_pinned"], + "historical_T_A2_verification": "SEPARATE_DUAL_EXECUTION_REQUIRED", + "independent_security_review": False, + "owner_activation_authorized": False, + "mathematical_acceptance": False, + "merge_authorized": False, + "publication_authorized": False, + } diff --git a/verification/research/release_target.py b/verification/research/release_target.py new file mode 100644 index 00000000..5ea0f674 --- /dev/null +++ b/verification/research/release_target.py @@ -0,0 +1,240 @@ +"""Versioned, fail-closed PR113 -> CERT Git/source target authentication. + +This module binds source-controlled *intent* to genuine Git/GitHub objects at +execution time. It cannot grant review, CI, merge, or publication authority. +No future certification PR number or commit is hardcoded in checked-in source. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import re +import sys + +from final_closure_authenticate import git as real_git, live as real_live +from pr113_boundary_io import strict_json_object, detached_destination + +ROOT = Path(__file__).resolve().parents[2] +INTENT_PATH = "research/certification/target_intent.json" +REPOSITORY = "HMarcusWH/PermanssonLean" +B113 = "17c315f7df7abf457eb94d393bd526d46da6f6f4" +T = "71cc34fa0bc4b639c05d2ec5f39f0b88afd56388" +FORMAT = "Permansson.CertificationTargetIntent.v1" +BINDING_FORMAT = "Permansson.DetachedExecutionTarget.v1" +SHA = re.compile(r"^[0-9a-f]{40}$") +INTENT_KEYS = frozenset(( + "format", "repository", "original_base_sha", "historical_T_sha", + "infrastructure_pr", "certification_pr", "status", + "independent_security_review", "independent_mathematical_review", + "publication_authorized", +)) +BINDING_KEYS = frozenset(( + "format", "phase", "repository", "pr_number", "branch", "base_sha", + "head_sha", "synthetic_sha", "infrastructure_merge_sha", +)) + + +class TargetError(ValueError): + pass + + +def require(ok, reason): + if not ok: + raise TargetError(reason) + + +def is_sha(value): + return isinstance(value, str) and bool(SHA.fullmatch(value)) + + +def intent_contract(obj): + """Literal source intent cannot supply dynamic SHA IDs or approvals.""" + require(isinstance(obj, dict) and set(obj) == INTENT_KEYS, + "target intent has missing or unauthorized fields") + require( + obj.get("format") == FORMAT and obj.get("repository") == REPOSITORY + and obj.get("original_base_sha") == B113 + and obj.get("historical_T_sha") == T + and type(obj.get("infrastructure_pr")) is int + and obj["infrastructure_pr"] == 113 + and obj.get("certification_pr") is None + and obj.get("status") == "CERTIFICATION_NOT_YET_BOUND" + and obj.get("independent_security_review") is False + and obj.get("independent_mathematical_review") is False + and obj.get("publication_authorized") is False, + "source intent attempted historical retargeting or approval", + ) + return obj + + +def exact_intent(head, *, git=real_git): + """Read source-controlled bytes from Git, never a shadow worktree file.""" + require(is_sha(head), "target source head not a commit SHA") + require(git("cat-file", "-t", head) == "commit", + "target source head not an accessible Git commit") + raw = git("show", head + ":" + INTENT_PATH) + try: + parsed = strict_json_object(raw.encode('utf-8') if isinstance(raw, str) else raw) + except (ValueError, TypeError) as exc: + raise TargetError("invalid Git-committed target intent") from exc + return intent_contract(parsed) + + +def candidate_bindings(record): + require(isinstance(record, dict) and set(record) == BINDING_KEYS, + "detached target lacks exact required keys") + require(record.get("format") == BINDING_FORMAT + and record.get("repository") == REPOSITORY + and record.get("phase") in ( + "infrastructure_premerge", "certification_premerge"), + "unknown target record version/repository/phase") + require(type(record.get("pr_number")) is int + and record["pr_number"] >= 1 + and isinstance(record.get("branch"), str) + and record["branch"].startswith("research/") + and len(record["branch"]) <= 180 + and all(is_sha(record.get(k)) for k in ( + "base_sha", "head_sha", "synthetic_sha")), + "target has unbound or invalid PR/source identity") + require(record.get("infrastructure_merge_sha") is None + if record["phase"] == "infrastructure_premerge" + else is_sha(record.get("infrastructure_merge_sha")), + "certification target has missing/early infrastructure merge") + require(record["base_sha"] != record["head_sha"], + "target cannot be its own source/base") + if record["phase"] == "infrastructure_premerge": + require(record["pr_number"] == 113 and + record["branch"] == "research/final-mathematical-closure" and + record["base_sha"] == B113, + "infrastructure branch or original base was changed") + else: + require(record["pr_number"] not in (113, 114) and + record["pr_number"] > 114 and + record["branch"] != "research/final-mathematical-closure", + "CERT must be a newly assigned successor PR, never old #114") + return record + + +def git_topology(base, head, synthetic, *, git=real_git): + for name, sha in (("B", base), ("H", head), ("S", synthetic)): + require(git("cat-file", "-t", sha) == "commit", + "missing real " + name + " commit") + require(git("show", "-s", "--format=%P", synthetic).split() == [base, head], + "synthetic merge does not have actual ordered base/head parents") + require(git("rev-parse", synthetic + "^{tree}") == + git("rev-parse", head + "^{tree}"), + "synthetic source tree differs from reviewed head") + + +def bind(record, *, intent, git=real_git, live=real_live): + """Authenticate the PR identity; return a *nonaccepted* bound target. + + This is intentionally independent of mathematical acceptance. A source + binding proves no reviewer independence, historical execution, or CI. + """ + intent_contract(intent) + candidate_bindings(record) + base, head, syn = (record[k] for k in ( + "base_sha", "head_sha", "synthetic_sha")) + git_topology(base, head, syn, git=git) + current = live("/branches/main") + require(isinstance(current, dict) and + (current.get("commit") or {}).get("sha") == base, + "remote main does not equal frozen target base") + pr = live("/pulls/" + str(record["pr_number"])) + require(isinstance(pr, dict) + and pr.get("number") == record["pr_number"] + and pr.get("state") == "open" + and pr.get("merged") is False + and pr.get("draft") is True + and (pr.get("base") or {}).get("ref") == "main" + and (pr.get("base") or {}).get("sha") == base + and (pr.get("head") or {}).get("sha") == head + and (pr.get("head") or {}).get("ref") == record["branch"] + and (pr.get("head") or {}).get("repo", {}).get("full_name") + == REPOSITORY, + "live PR is unrelated, modified, merged or no longer draft") + # GitHub creates an independent ephemeral test merge with its own + # committer metadata. Its SHA need not equal a separately constructed + # synthetic merge S, even when both have the exact ordered [B,H] parents + # and H's source tree. Authenticate both objects instead of equating + # their incidental commit-object hashes. + github_merge_sha = pr.get("merge_commit_sha") + require(is_sha(github_merge_sha), + "live PR did not supply a genuine test-merge commit identifier") + try: + github_merge = live("/git/commits/" + github_merge_sha) + except Exception as exc: + raise TargetError("GitHub test merge unavailable for authentication") from exc + require(isinstance(github_merge, dict) + and github_merge.get("sha") == github_merge_sha + and isinstance(github_merge.get("parents"), list) + and [row.get("sha") for row in github_merge["parents"] + if isinstance(row, dict)] == [base, head] + and (github_merge.get("tree") or {}).get("sha") == + git("rev-parse", head + "^{tree}"), + "live GitHub test-merge topology or tree differs from exact B/H/S") + if record["phase"] == "certification_premerge": + p113 = live("/pulls/113") + infra_merge = record["infrastructure_merge_sha"] + require(isinstance(p113, dict) and + p113.get("merged") is True and + p113.get("merge_commit_sha") == infra_merge, + "M113 not independently established by GitHub") + require(git("cat-file", "-t", infra_merge) == "commit", + "genuine infrastructure merge not in Git object store") + require(git("merge-base", "--is-ancestor", infra_merge, base) == "", + "CERT base is not descendant of real M113") + require(git("rev-parse", infra_merge + "^{tree}") == + git("rev-parse", p113["head"]["sha"] + "^{tree}"), + "infrastructure merged tree differs from reviewed source") + else: + require(record["infrastructure_merge_sha"] is None, + "unmerged infrastructure cannot cite future M") + return { + "format": "Permansson.AuthenticatedTargetBinding.v1", + "status": "GENUINE_PR_AND_GIT_IDENTITY_BOUND_NOT_REVIEWED", + "phase": record["phase"], + "repository": REPOSITORY, + "pr_number": record["pr_number"], + "branch": record["branch"], + "base_sha": base, + "head_sha": head, + "synthetic_sha": syn, + "infrastructure_merge_sha": record["infrastructure_merge_sha"], + "independent_security_review": False, + "independent_mathematical_review": False, + "merge_authorized": False, + "publication_authorized": False, + } + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--head", required=True) + ap.add_argument("--target", required=True, type=Path) + ap.add_argument("--out", required=True, type=Path) + args = ap.parse_args(argv) + try: + out = detached_destination(args.out, ROOT) + intent = exact_intent(args.head) + raw = args.target.read_bytes() + require(0 < len(raw) <= 16 * 1024, "invalid detached target size") + report = bind(strict_json_object(raw, max_bytes=16*1024), intent=intent) + require(report["head_sha"] == args.head, + "frozen source does not match target") + out.parent.mkdir(parents=True, exist_ok=True) + with out.open("x", encoding="utf-8") as f: + json.dump(report, f, indent=2, sort_keys=True) + f.write("\n") + print(json.dumps({"status": report["status"], + "head_sha": report["head_sha"]}, sort_keys=True)) + return 0 + except (TargetError, OSError, KeyError, TypeError, ValueError) as exc: + print("TARGET NOT BOUND:", exc, file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verification/research/test_b02_actual_original_margin_lean.py b/verification/research/test_b02_actual_original_margin_lean.py new file mode 100644 index 00000000..216b84a6 --- /dev/null +++ b/verification/research/test_b02_actual_original_margin_lean.py @@ -0,0 +1,59 @@ +"""B-02 actual original finite blockMargin proof and false generalization.""" +from pathlib import Path +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +MOD = "PermanssonResearch.GrammarRobust.B02ActualOriginalMarginFixture" +DECLS = ( + "two_actual_minimal_original_blocks", + "nonidentity_relabeling_preserves_actual_margin", + "actual_local_minimality_transport_not_global_identity", + "actual_coarsening_does_not_preserve_minimality", + "and_joint_is_positive_but_not_minimal", +) + + +def compile_text(s): + with tempfile.TemporaryDirectory(prefix="b02-original-", dir=ROOT / ".lake") as folder: + p = Path(folder)/"Test.lean" + p.write_text(s, encoding="utf-8") + return subprocess.run( + ["lake", "env", "lean", str(p)], cwd=ROOT, + text=True, capture_output=True, timeout=300) + + +class B02OriginalMarginCompiler(unittest.TestCase): + def test_actual_original_margin_minimality_and_axioms(self): + log = "import " + MOD + "\n" + for theorem in DECLS: + log += "#check " + MOD + "." + theorem + "\n" + log += "#print axioms " + MOD + "." + theorem + "\n" + p = compile_text(log) + diagnostic = p.stdout + p.stderr + self.assertEqual(p.returncode, 0, diagnostic[-2500:]) + for theorem in DECLS: + self.assertIn(MOD + "." + theorem, diagnostic) + self.assertNotIn("error:", diagnostic.lower()) + self.assertNotIn("sorryAx", diagnostic) + + def test_false_joint_minimality_is_rejected_semantically(self): + p = compile_text( + "import " + MOD + "\n" + "open PermanssonResearch.GrammarRobust\n" + "open PermanssonResearch.GrammarRobust.BooleanMinimalityCases\n" + "open PermanssonResearch.GrammarRobust.BooleanModel\n" + "example : IsMinimalAllowed fine andPos ({0,1} : Finset (Fin 2)) := by\n" + " exact PermanssonResearch.GrammarRobust.B02ActualOriginalMarginFixture.and_joint_is_positive_but_not_minimal.2\n" + ) + diagnostic = (p.stdout + p.stderr).lower() + self.assertNotEqual(p.returncode, 0, diagnostic[-2500:]) + self.assertTrue("type mismatch" in diagnostic, diagnostic[-2500:]) + for invalid in ("unknown identifier", "unknown module", + "sorryax", "maximum number of heartbeats", "parser error"): + self.assertNotIn(invalid, diagnostic) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_b02_local_nonisomorphic_margin_lean.py b/verification/research/test_b02_local_nonisomorphic_margin_lean.py new file mode 100644 index 00000000..b72d34ea --- /dev/null +++ b/verification/research/test_b02_local_nonisomorphic_margin_lean.py @@ -0,0 +1,78 @@ +"""B-02 original uniformly constitutive margin under local lower coverage. + +Proves this using the actual Boolean physical atom bank and the original +blockMargin/constitutive comparison law, with a noninjective component +coarsening and a predeclared admission grammar. Positive source and a +mathematically false singleton-admission mutation are separate. +""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +MOD = "PermanssonResearch.GrammarRobust.B02LocalOriginalMarginClosure" +DECL = ( + "actualCoarsening_not_global_injective", + "actualCoarsening_lift_full", + "original_admitted_lower_coverage", + "coarse_original_OR_positive", + "coarse_original_OR_minimal", + "original_local_minimality_without_global_isomorphism", + "false_extra_admission_rejected", +) +ALLOW = {"propext", "Classical.choice", "Quot.sound"} + + +def compile_source(source): + if shutil.which("lake") is None: + raise AssertionError("pinned Lean required") + with tempfile.TemporaryDirectory(prefix="B02-real-", dir=ROOT/".lake") as td: + path = Path(td)/"Check.lean" + path.write_text(source, encoding="utf-8") + return subprocess.run(["lake", "env", "lean", str(path)], + cwd=ROOT, text=True, capture_output=True, + timeout=900) + + +class B02LocalOriginalProofs(unittest.TestCase): + def test_original_nonisomorphic_local_margin_compiles(self): + s = "import " + MOD + "\n" + "".join( + "#check " + MOD + "." + name + "\n#print axioms " + + MOD + "." + name + "\n" for name in DECL + ) + p = compile_source(s) + log = p.stdout + "\n" + p.stderr + self.assertEqual(p.returncode, 0, log[-3500:]) + self.assertNotIn("error:", log.lower()) + self.assertNotIn("sorryAx", log) + for name in DECL: + self.assertIn(MOD+"."+name, log) + rows = re.findall(r"depends on axioms:\s*\[([^\]]*)\]", log) + self.assertTrue(rows, log) + for row in rows: + self.assertLessEqual( + {a.strip() for a in row.split(",") if a.strip()}, ALLOW) + + def test_inadmissible_original_singleton_false_claim_rejected(self): + s = ( + "import " + MOD + "\n" + "open " + MOD + "\n" + "example : fullOrEmptyFine.allowed ({0} : Finset (Fin 2)) := by\n" + " exact false_extra_admission_rejected\n" + ) + p = compile_source(s) + log = (p.stdout + "\n" + p.stderr).lower() + self.assertNotEqual(p.returncode, 0, "forbidden singleton admitted") + self.assertIn("error:", log) + for bad in ("unknown identifier", "unknown module", "failed to import", + "unexpected token", "maximum number of heartbeats", + "sorryax", "out of memory"): + self.assertNotIn(bad, log) + self.assertRegex(log, r"type mismatch|unsolved goals|application type mismatch") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_clean_source_reproduction.py b/verification/research/test_clean_source_reproduction.py new file mode 100644 index 00000000..1ea997fa --- /dev/null +++ b/verification/research/test_clean_source_reproduction.py @@ -0,0 +1,237 @@ +"""Synthetic command stubs test two-source-root control flow, not PDF evidence.""" +import json +import os +from pathlib import Path +import sys +import tempfile +import unittest +from unittest import mock + +RELEASE = Path(__file__).resolve().parents[2] / "release/mathematical-closure-1" +sys.path.insert(0, str(RELEASE)) +import reproduce_clean_sources as clean +import build as draft_builder + +H = "a"*40 +T = "b"*40 + + +class CleanSourceRootRebuildTests(unittest.TestCase): + def test_invalid_sha_and_source_checkout_output_rejected(self): + with tempfile.TemporaryDirectory(prefix="clean-root-unit-") as td: + out = Path(td)/"new" + with self.assertRaises(clean.CleanReproductionError): + clean.reproduce_from_clean_source_roots(out, head="x") + with self.assertRaises(clean.CleanReproductionError): + clean.reproduce_from_clean_source_roots( + clean.ROOT / "release" / "illegal", head=H) + out.mkdir() + with self.assertRaises(clean.CleanReproductionError): + clean.reproduce_from_clean_source_roots(out, head=H) + + def test_diagnostic_mismatches_never_pass_as_reproducible(self): + with tempfile.TemporaryDirectory(prefix="clean-root-negative-") as td: + target = Path(td) / "output" + completed = [] + def git(args, cwd): + if args == ["rev-parse", "HEAD"]: return H + if args == ["rev-parse", "HEAD^{tree}"]: return T + if args == ["status", "--porcelain", "--untracked-files=no"]: return "" + if args[:2] == ["checkout", "--detach"]: return "" + raise AssertionError(args) + def run(arguments, *, cwd=None, timeout=600): + if len(arguments) > 1 and "build.py" in str(arguments[1]): + dest = Path(arguments[arguments.index("--out")+1]) + dest.mkdir(parents=True) + serial = "d"*64 if len(completed) == 0 else "e"*64 + completed.append(serial) + (dest / "manifest.lock.json").write_text(json.dumps({ + "status": "DRAFT_NOT_ACCEPTED", + "source_commit_sha": H, + "pdf_sha256": serial, + "pdf_page_count": 1, + })) + (dest / "Permansson_Mathematical_Closure_E2E.zip").write_bytes(b"Z") + (dest / "pages").mkdir() + return "" + with mock.patch.object(clean, "git_object", side_effect=git), \ + mock.patch.object(clean, "cmd", side_effect=run): + with self.assertRaises(clean.CleanReproductionError) as ctx: + clean.reproduce_from_clean_source_roots(target, head=H) + self.assertIn("pdf_sha256", str(ctx.exception)) + report = json.loads((target/"reproduction_failure.json").read_text()) + self.assertEqual(report["status"], "TWO_CLEAN_SOURCE_BUILDS_BLOCKED") + self.assertIn("pdf_sha256", report["differences"]) + self.assertFalse(report["scientific_release_approved"]) + + def test_mismatch_diagnostic_names_real_manifest_log_and_zip_fields(self): + with tempfile.TemporaryDirectory(prefix="perm-r02-diff-") as td: + one, two = Path(td)/"first", Path(td)/"second" + one.mkdir(); two.mkdir() + (one/"manifest.lock.json").write_text( + json.dumps({"pdf_sha256":"a"*64,"latex_log_sha256":"b"*64})) + (two/"manifest.lock.json").write_text( + json.dumps({"pdf_sha256":"a"*64,"latex_log_sha256":"c"*64})) + (one/"latex_build.log").write_text("ok\n/one\nwarning: A\n") + (two/"latex_build.log").write_text("ok\n/two\nwarning: A\n") + import zipfile + for path, body in ((one,b"alpha"),(two,b"bravo")): + with zipfile.ZipFile(path/"Permansson_Mathematical_Closure_E2E.zip","w") as z: + z.writestr("manifest.lock.json",body) + z.writestr("stable.txt",b"same") + left=clean.diagnose_root_differences(one,two) + self.assertEqual(list(left["manifest_changed_fields"]),["latex_log_sha256"]) + self.assertEqual(left["differing_logs"]["latex_build.log"] + ["first_differing_line_index"],1) + self.assertEqual(left["draft_archive"]["all_changed_member_count"],1) + self.assertIn("manifest.lock.json", + left["draft_archive"]["first_different_members"]) + self.assertFalse(left["byte_identity_waived"]) + self.assertFalse(left["scientific_release_approved"]) + + def test_different_zip_member_order_is_detected(self): + with tempfile.TemporaryDirectory(prefix="perm-r02-order-") as td: + import zipfile + one,two=Path(td)/"first.zip",Path(td)/"second.zip" + with zipfile.ZipFile(one,"w") as z: + z.writestr("a.txt",b"A");z.writestr("b.txt",b"B") + with zipfile.ZipFile(two,"w") as z: + z.writestr("b.txt",b"B");z.writestr("a.txt",b"A") + detail=clean._zip_delta(one,two) + self.assertFalse(detail["member_order_matches"]) + self.assertEqual(detail["all_changed_member_count"],0) + + def test_missing_or_altered_page_is_reported(self): + with tempfile.TemporaryDirectory(prefix="perm-r02-page-") as td: + one,two=Path(td)/"first",Path(td)/"second" + for root in (one,two): + (root/"pages").mkdir(parents=True) + (one/"pages/page-1.png").write_bytes(b"page-A") + (two/"pages/page-1.png").write_bytes(b"page-B") + detail=clean.diagnose_root_differences(one,two) + self.assertEqual(detail["rendered_pages"]["changed"],["page-1.png"]) + (two/"pages/page-1.png").unlink() + detail=clean.diagnose_root_differences(one,two) + self.assertEqual(detail["rendered_pages"]["changed"],["page-1.png"]) + + def test_tex_input_search_is_relative_and_preserves_existing_system_search(self): + environment = draft_builder.scoped_tex_environment( + {"TEXINPUTS":"system-trees", "BIBINPUTS":"bib-trees", "ROOT":"unchanged"}) + self.assertEqual(environment["TEXINPUTS"], + "." + os.pathsep + "system-trees") + self.assertEqual(environment["BIBINPUTS"], + "." + os.pathsep + "bib-trees") + self.assertEqual(environment["ROOT"],"unchanged") + unset = draft_builder.scoped_tex_environment({}) + self.assertEqual(unset["TEXINPUTS"], "." + os.pathsep) + self.assertEqual(unset["BIBINPUTS"], "." + os.pathsep) + + def test_second_pass_detects_shared_stage_cache_contamination(self): + with tempfile.TemporaryDirectory(prefix="perm-r02-cache-") as td: + target = Path(td) / "outside" + builds = [] + def git(args, cwd): + if args == ["rev-parse", "HEAD"]: return H + if args == ["rev-parse", "HEAD^{tree}"]: return T + if args == ["status", "--porcelain", "--untracked-files=no"]: return "" + if args[:2] == ["checkout", "--detach"]: return "" + raise AssertionError(args) + def run(arguments, *, cwd=None, timeout=600): + if arguments[:3] == ["git", "clone", "--local"] and builds: + dirty = target/"_canonical_tex_stage" + dirty.mkdir() + (dirty/"stale-cache").write_bytes(b"untrusted leftover") + if len(arguments) > 1 and "build.py" in str(arguments[1]): + output = Path(arguments[arguments.index("--out")+1]) + output.mkdir(parents=True, exist_ok=False) + (output/"manifest.lock.json").write_text(json.dumps({ + "status":"DRAFT_NOT_ACCEPTED", "source_commit_sha":H, + "pdf_sha256":"d"*64, "pdf_page_count":1})) + (output/"Permansson_Mathematical_Closure_E2E.zip").write_bytes(b"ZIP") + (output/"pages").mkdir() + builds.append(output) + return "" + with mock.patch.object(clean, "git_object", side_effect=git), \ + mock.patch.object(clean, "cmd", side_effect=run): + with self.assertRaises(clean.CleanReproductionError) as error: + clean.reproduce_from_clean_source_roots(target, head=H) + self.assertIn("shared TeX build stage", str(error.exception)) + self.assertEqual(len(builds), 1) + self.assertFalse((target/"reproduction_receipt.json").exists()) + + def test_fixed_pdftex_trailer_identity_is_explicit_and_source_bound(self): + """Different output-root paths must not randomize the PDF backend ID.""" + preamble = (clean.ROOT / "research_papers/mathematical_closure" / + "preamble.tex").read_text(encoding="utf-8") + self.assertIn(r"\ifdefined\pdftrailerid", preamble) + self.assertIn( + r"\pdftrailerid{<5045524D414E53534F4E2D4D41544821> " + r"<5045524D414E53534F4E2D4D41544821>}", + preamble) + self.assertIn(r"\fi", preamble) + + def test_mock_two_distinct_clones_and_no_approval(self): + with tempfile.TemporaryDirectory(prefix="clean-root-unit-") as td: + target = Path(td)/"output" + calls = [] + pdf_sha = "d"*64 + zip_sha = "e"*64 + manifest_sha = None + + def git(args, cwd): + if args == ["rev-parse", "HEAD"]: return H + if args == ["rev-parse", "HEAD^{tree}"]: return T + if args == ["status", "--porcelain", "--untracked-files=no"]: return "" + if args[:2] == ["checkout", "--detach"]: return "" + raise AssertionError(args) + + def runner(arguments, *, cwd=None, timeout=600): + calls.append(tuple(str(x) for x in arguments)) + if "build.py" in str(arguments[1]) if len(arguments) > 1 else False: + pos = arguments.index("--out") + 1 + out = Path(arguments[pos]) + out.mkdir(parents=True) + (out/"manifest.lock.json").write_text(json.dumps({ + "status": "DRAFT_NOT_ACCEPTED", + "source_commit_sha": H, + "pdf_sha256": pdf_sha, + "pdf_page_count": 1, + })) + (out/"Permansson_Mathematical_Closure_E2E.zip").write_bytes(b"ZIP") + (out/"pages").mkdir() + return "" + + from hashlib import sha256 + real_zip_sha = sha256(b"ZIP").hexdigest() + with mock.patch.object(clean, "git_object", side_effect=git), \ + mock.patch.object(clean, "cmd", side_effect=runner), \ + mock.patch.object(clean.reproduce, "compare_artifacts", + return_value={"status": "REPRODUCIBLE_DRAFT_ONLY", + "pdf_sha256": pdf_sha, + "zip_sha256": real_zip_sha}): + result = clean.reproduce_from_clean_source_roots(target, head=H) + clones = [c for c in calls if c[:3] == ("git", "clone", "--local")] + build_commands = [c for c in calls if len(c) > 1 and + "build.py" in c[1]] + self.assertEqual(len(build_commands), 2) + self.assertEqual(build_commands[0][build_commands[0].index("--out")+1], + build_commands[1][build_commands[1].index("--out")+1]) + self.assertIn("_canonical_tex_stage", + build_commands[0][build_commands[0].index("--out")+1]) + self.assertFalse((target/"_canonical_tex_stage").exists()) + self.assertTrue((target/"first"/"manifest.lock.json").is_file()) + self.assertTrue((target/"second"/"manifest.lock.json").is_file()) + self.assertEqual(len(clones), 2) + self.assertNotEqual(clones[0][-1], clones[1][-1]) + self.assertIn("--no-hardlinks", clones[0]) + self.assertIn("--no-hardlinks", clones[1]) + self.assertEqual(result["independent_source_roots"], 2) + self.assertEqual(result["source_commit_sha"], H) + self.assertTrue(result["two_complete_archive_audits"]) + self.assertFalse(result["authenticated_ci_artifact"]) + self.assertFalse(result["scientific_release_approved"]) + self.assertTrue((target/"reproduction_receipt.json").is_file()) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_closure_elaborated_type.py b/verification/research/test_closure_elaborated_type.py new file mode 100644 index 00000000..ba866d5a --- /dev/null +++ b/verification/research/test_closure_elaborated_type.py @@ -0,0 +1,58 @@ +"""Test exact elaborated-type export; mock probes are never proof receipts.""" +import shutil +import subprocess +from pathlib import Path +import tempfile +import unittest + +import closure_elaborated_type as et + +ROOT = Path(__file__).resolve().parents[2] + + +class ElaboratedCompilerTypeTests(unittest.TestCase): + def test_type_parser_preserves_target_and_entire_ast(self): + log = (et.BEGIN + "\n" + "NAME=Demo.foo\nKIND=theorem\nUNIVERSES=[u]\n" + "TYPE=Expr.forallE (x : Type u) (more := x)\n" + + et.END + "\n") + record = et.compiled_type_record(log, "Demo.foo") + self.assertIn("more := x", record["type_expr_repr"]) + first_hash = et.elaborated_type_sha256(record) + later_hypothesis = et.compiled_type_record( + log.replace("more := x", "more := (x, x)"), "Demo.foo") + self.assertNotEqual(first_hash, et.elaborated_type_sha256(later_hypothesis)) + for invalid in ( + log + log, + log.replace("Demo.foo", "Other.foo"), + log.replace("KIND=theorem", "KIND=invented"), + log.replace("TYPE=Expr.forallE (x : Type u) (more := x)", "TYPE= "), + ): + with self.subTest(invalid=invalid[-50:]), self.assertRaises(et.TypeEvidenceError): + et.compiled_type_record(invalid, "Demo.foo") + + def test_full_compiler_probe_of_builtin_lean_theorem(self): + # This is a real Lean invocation, not simulated stdout. It is required + # by Research CI after installing the project's pinned toolchain. + self.assertIsNotNone(shutil.which("lake"), + "pinned Lake is required for elaborated type evidence") + root_lake = ROOT / ".lake" + self.assertTrue(root_lake.is_dir(), "project Lean environment is missing") + with tempfile.TemporaryDirectory(prefix="closure-type-", dir=root_lake) as tmp: + source = Path(tmp) / "Audit.lean" + source.write_text(et.lean_probe("Lean", "Nat.add_comm"), encoding="utf-8") + result = subprocess.run( + ["lake", "env", "lean", str(source)], + cwd=ROOT, text=True, capture_output=True, timeout=120) + self.assertEqual( + result.returncode, 0, + (result.stdout + result.stderr)[-4000:]) + record = et.compiled_type_record( + result.stdout + "\n" + result.stderr, "Nat.add_comm") + self.assertEqual(record["declaration_kind"], "theorem") + self.assertIn("forallE", record["type_expr_repr"]) + self.assertEqual(len(et.elaborated_type_sha256(record)), 64) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_e05_full_gram_masking_lean.py b/verification/research/test_e05_full_gram_masking_lean.py new file mode 100644 index 00000000..0b29c6fe --- /dev/null +++ b/verification/research/test_e05_full_gram_masking_lean.py @@ -0,0 +1,84 @@ +"""E-05 actual positive-gap Gram witness and false full-form negativity. + +Imports the original signedGram_witness_value / witness-negative theorem and +a source-only additional positive summand. A false universal full-form sign +claim must be rejected for a mathematical type reason. +""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +MOD = "PermanssonResearch.Specialized.E05FullGramMaskingCountermodel" +DECLS = ( + "added_component_masks_actual_negative_gram", + "original_signed_negative_but_total_positive", + "extra_component_exceeds_negative_budget", +) +ALLOW = {"propext", "Classical.choice", "Quot.sound"} + + +def compile_lean(source): + if shutil.which("lake") is None: + raise AssertionError("pinned Lean compiler required") + with tempfile.TemporaryDirectory(dir=ROOT / ".lake", + prefix="E05-gram-mask-") as td: + path = Path(td) / "Probe.lean" + path.write_text(source, encoding="utf-8") + return subprocess.run(["lake", "env", "lean", str(path)], + cwd=ROOT, capture_output=True, text=True, + timeout=900) + + +class E05OriginalGramFullForm(unittest.TestCase): + def test_original_signed_negative_and_masked_total_compiler(self): + source = "import " + MOD + "\n" + source += "".join( + "#check " + MOD + "." + n + "\n#print axioms " + + MOD + "." + n + "\n" for n in DECLS + ) + run = compile_lean(source) + output = run.stdout + "\n" + run.stderr + self.assertEqual(run.returncode, 0, output[-4000:]) + for name in DECLS: + self.assertIn(MOD + "." + name, output) + self.assertNotIn("error:", output.lower()) + self.assertNotIn("sorryAx", output) + groups = re.findall(r"depends on axioms:\s*\[([^\]]*)\]", output) + self.assertTrue(groups, output) + for group in groups: + self.assertLessEqual( + {x.strip() for x in group.split(",") if x.strip()}, + ALLOW, output + ) + + def test_false_unqualified_full_gram_negativity_rejected(self): + source = ( + "import " + MOD + "\n" + "open " + MOD + "\n" + "open PermanssonResearch.Specialized\n" + "open scoped InnerProductSpace\n" + "example {E : Type*} [NormedAddCommGroup E]\n" + " [InnerProductSpace ℝ E] {c : ℝ} (hc : 0 < c)\n" + " (x y : E) (hgap : 0 < gramGap x y) :\n" + " c * (⟪x, gramWitness x y⟫_ℝ ^ 2 -\n" + " ⟪y, gramWitness x y⟫_ℝ ^ 2) +\n" + " 2 * c * gramGap x y ^ 2 < 0 := by\n" + " exact (original_signed_negative_but_total_positive hc x y hgap).2\n" + ) + run = compile_lean(source) + output = (run.stdout + "\n" + run.stderr).lower() + self.assertNotEqual(run.returncode, 0, + "source-disproved unqualified negativity accepted") + for invalid in ("unknown module", "unknown identifier", "failed to import", + "parser error", "unexpected token", "out of memory", + "maximum number of heartbeats", "sorryax"): + self.assertNotIn(invalid, output[-4000:]) + self.assertRegex(output, r"type mismatch|unsolved goals|application type mismatch") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_f03_actual_infeasible_strict_anchor_lean.py b/verification/research/test_f03_actual_infeasible_strict_anchor_lean.py new file mode 100644 index 00000000..aa93fb5f --- /dev/null +++ b/verification/research/test_f03_actual_infeasible_strict_anchor_lean.py @@ -0,0 +1,79 @@ +"""F-03 original typed infeasible strict opponent anchor. + +The source constructs genuine nonempty pointwise legal sets and proves +the empty own-key fibre using the same original all-state semantics. +A false positive strict-anchor existential must be rejected by Lean, +not by a parser, missing import, or time/resource failure. +""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +MOD = "PermanssonResearch.MultiAgent.F03ActualInfeasibleAnchor" +DECLS = ( + "each_original_menu_nonempty", + "both_starts_have_same_original_strict_key", + "legal_fibre_empty_at_genuine_key", + "no_feasible_original_strict_anchor", + "all_pointwise_menus_nonempty_but_global_anchor_impossible", +) +ALLOW = {"propext", "Classical.choice", "Quot.sound"} + + +def compile_lean(source): + if shutil.which("lake") is None: + raise AssertionError("pinned Lean compiler required") + with tempfile.TemporaryDirectory(dir=ROOT / ".lake", + prefix="F03-actual-") as td: + file = Path(td) / "F03.lean" + file.write_text(source, encoding="utf-8") + return subprocess.run(["lake", "env", "lean", str(file)], + cwd=ROOT, capture_output=True, text=True, + timeout=600) + + +class OriginalStrictAnchorFibreTests(unittest.TestCase): + def test_actual_all_state_legal_fibre_and_original_existence_theorems(self): + source = "import " + MOD + "\n" + "".join( + "#check " + MOD + "." + name + "\n#print axioms " + + MOD + "." + name + "\n" for name in DECLS + ) + result = compile_lean(source) + out = result.stdout + "\n" + result.stderr + self.assertEqual(result.returncode, 0, out[-4000:]) + self.assertNotIn("error:", out.lower()) + self.assertNotIn("sorryAx", out) + for name in DECLS: + self.assertIn(MOD + "." + name, out) + found = re.findall(r"depends on axioms:\s*\[([^\]]*)\]", out) + self.assertTrue(found, out) + for row in found: + self.assertLessEqual({x.strip() for x in row.split(",") if x.strip()}, + ALLOW, out) + + def test_false_feasible_anchor_with_conflicting_own_fibre_rejected(self): + source = ( + "import " + MOD + "\n" + "open PermanssonResearch.MultiAgent\n" + "open " + MOD + "\n" + "example : ∃ a : PlayerTwoMechanism Unit Bool Unit Unit Unit Bool,\n" + " IsStrictlyNonReactive a ∧\n" + " ∀ y : Y, a.action y (legal y) = 1 := by\n" + " exact no_feasible_original_strict_anchor\n" + ) + run = compile_lean(source) + log = (run.stdout + "\n" + run.stderr).lower() + self.assertNotEqual(run.returncode, 0, "mathematically false anchor accepted") + for invalid in ("unknown module", "unknown identifier", + "failed to import", "unexpected token", + "maximum number of heartbeats", "out of memory"): + self.assertNotIn(invalid, log[-4000:]) + self.assertRegex(log, r"type mismatch|unsolved goals|application type mismatch") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final04_canonical_output.py b/verification/research/test_final04_canonical_output.py new file mode 100644 index 00000000..e2d80afc --- /dev/null +++ b/verification/research/test_final04_canonical_output.py @@ -0,0 +1,34 @@ +"""Independent exact Fraction tests for discounted common-mass envelopes.""" +from fractions import Fraction as F +from math import prod +import unittest + +def retained(eta,n): + return prod((1-F(e) for e in eta[:n]),start=F(1)) + +def finite_error(beta,eta,epsilon,horizon): + return sum(((1-beta)*beta**n*(1-(1-epsilon)*retained(eta,n)) + for n in range(horizon)),start=F(0)) + +class CanonicalEnvelope(unittest.TestCase): + def test_frozen_geometric_exact_thirteen_fortyfive(self): + b,e,a=F(1,3),F(1,5),F(1,4) + analytic=1-(1-e)*(1-b)/(1-b*(1-a)) + self.assertEqual(analytic,F(13,45)) + def test_zero_row_error_keeps_initial_mismatch(self): + self.assertEqual(1-(1-F(2,7))*(1-F(1,3))/ + (1-F(1,3)),F(2,7)) + def test_chronological_defects_not_last_row(self): + e=F(1,7) + ds=[F(1,10),F(1,5),F(1,4)] + self.assertEqual(retained(ds,3),F(27,50)) + self.assertEqual(finite_error(F(1,3),ds,e,4), + sum((F(2,3)*F(1,3)**n* + (1-F(6,7)*retained(ds,n)) + for n in range(4)),F(0))) + def test_intervention_only_reachable_row_charged(self): + self.assertGreater(finite_error(F(1,2),[F(0),F(1,2)],F(0),3),0) + def test_positive_leakage_cannot_certify_exact_retention(self): + survival=(1-F(1,100))**500 + self.assertLess(survival,F(1,10)) +if __name__=="__main__":unittest.main() diff --git a/verification/research/test_final04_source_lean.py b/verification/research/test_final04_source_lean.py new file mode 100644 index 00000000..ebe8eb48 --- /dev/null +++ b/verification/research/test_final04_source_lean.py @@ -0,0 +1,41 @@ +"""Actual pinned Lean/compiler/axiom acceptance for final P-04/P-05 source.""" +import pathlib,re,shutil,subprocess,tempfile,unittest +ROOT=pathlib.Path(__file__).resolve().parents[2] +ALLOW={"propext","Classical.choice","Quot.sound"} + +def check_module(testcase,module,namespace,declarations): + testcase.assertIsNotNone(shutil.which("lake"),"required pinned Lean compiler unavailable") + with tempfile.TemporaryDirectory(dir=ROOT/".lake") as temp: + path=pathlib.Path(temp)/"ProofReceipt.lean" + path.write_text("import "+module+"\nopen "+namespace+"\n"+ + "\n".join("#check "+name+"\n#print axioms "+name for name in declarations)+"\n") + p=subprocess.run(["lake","env","lean",str(path)],cwd=ROOT, + capture_output=True,text=True,timeout=900) + out=p.stdout+p.stderr + testcase.assertEqual(p.returncode,0,out) + for name in declarations: testcase.assertIn(name,out) + sets=re.findall(r"depends on axioms:\s*\[([^\]]*)\]",out) + testcase.assertGreater(len(sets),0,"missing compiler axiom records\n"+out) + for s in sets: + testcase.assertLessEqual({a.strip() for a in s.split(",") if a.strip()},ALLOW,out) + testcase.assertNotIn("sorryAx",out) + return True + +class Final04CompilerTests(unittest.TestCase): + def test_canonical_source_and_full_margin(self): + check_module(self, + "PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure", + "PermanssonResearch.Quotient.DiscountedCanonicalSourceWitness\n"+ + "open PermanssonResearch.Quotient.DiscountedCanonicalMarginClosure", + ("canonical_discounted_output_error","point_witness_output", + "actual_canonical_discounted_original_margin")) + def test_actual_kernel_witness_examples_and_axioms(self): + check_module(self, + "PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples", + "PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples\n" + "open PermanssonResearch.Quotient.DiscountedCanonicalWitnessExamples.SourceBoundExactWitness", + ("correct_frozen_discounted_budget", "wrong_frozen_discounted_budget", + "exactKernelPointWitness", "exactKernelPointWitness_retained_one", + "identity_canonical_full_path")) + +if __name__=="__main__":unittest.main() diff --git a/verification/research/test_final05_matched_protocols.py b/verification/research/test_final05_matched_protocols.py new file mode 100644 index 00000000..6cc4d8fa --- /dev/null +++ b/verification/research/test_final05_matched_protocols.py @@ -0,0 +1,32 @@ +"""Independent asymmetric/conditional product finite counterchecks. + +These are numerical implementation adversaries. The all-event Lean kernel +intertwining is separately compiler-certified, not deduced from this test. +""" +from fractions import Fraction as F +from itertools import product +import unittest + +class MatchedProtocols(unittest.TestCase): + def test_asymmetric_opponent_anchor_not_changed_by_treated_component(self): + g=[F(1,5),F(3,4)] + replacement=F(2,3) + closed=[replacement,g[1]] + frozen_anchor=[F(1,3),F(4,5)] + open_loop=[replacement,frozen_anchor[1]] + self.assertEqual(closed[1],g[1]) + self.assertEqual(open_loop[1],frozen_anchor[1]) + self.assertNotEqual(closed,open_loop) + def test_joint_action_order_changes_physical_law_when_asymmetric(self): + def p(a,b):return F(1,10)+a*F(1,5)+b*F(1,2) + self.assertNotEqual(p(1,0),p(0,1)) + def test_correlated_actions_are_outside_product_subclass(self): + joint={(0,0):F(1,2),(1,1):F(1,2),(0,1):F(0),(1,0):F(0)} + independent={(a,b):F(1,4) for a,b in product((0,1),repeat=2)} + self.assertNotEqual(joint,independent) + def test_mixed_initial_law_requires_both_atoms(self): + mu={0:F(2,7),1:F(5,7)} + mapped={0:mu[0],1:mu[1]} + self.assertEqual(sum(mapped.values()),1) + self.assertNotEqual(mapped,{0:F(1),1:F(0)}) +if __name__=="__main__":unittest.main() diff --git a/verification/research/test_final05_measurable_lean.py b/verification/research/test_final05_measurable_lean.py new file mode 100644 index 00000000..1acee08e --- /dev/null +++ b/verification/research/test_final05_measurable_lean.py @@ -0,0 +1,48 @@ +"""Source-bound matched two-player closed/open-loop Lean theorem verification.""" +import pathlib,re,shutil,subprocess,tempfile,unittest +ROOT=pathlib.Path(__file__).resolve().parents[2] +NAMES=("matchedBinaryClosed","matchedBinaryOpen","matched_closed_kernel_two", + "matched_open_kernel_two","matched_closed_loop_original_margin", + "matched_open_loop_original_margin","matched_closed_loop_all_initial_paths", + "matched_open_loop_all_initial_paths") + +class MatchedCompiler(unittest.TestCase): + def test_full_original_margin_compiler_axioms(self): + self.assertTrue(shutil.which("lake"),"Lean required") + with tempfile.TemporaryDirectory(dir=ROOT/".lake") as temp: + src=pathlib.Path(temp)/"Matched.lean" + src.write_text("import PermanssonResearch.MultiAgent.MeasurableProductTwoOriginalMarginClosure\n" + "open PermanssonResearch.MultiAgent.MeasurableProduct.Two\n"+ + "\n".join("#check "+n+"\n#print axioms "+n for n in NAMES)+"\n") + p=subprocess.run(["lake","env","lean",str(src)],cwd=ROOT, + capture_output=True,text=True,timeout=900) + output=p.stdout+p.stderr + self.assertEqual(p.returncode,0,output) + for n in NAMES:self.assertIn(n,output) + sets=re.findall(r"depends on axioms:\s*\[([^\]]*)\]",output) + self.assertTrue(sets,output) + for a in sets: + self.assertLessEqual({s.strip() for s in a.split(",") if s.strip()}, + {"propext","Quot.sound","Classical.choice"}) + self.assertNotIn("sorryAx",output) + def test_matched_asymmetric_source_witnesses(self): + self.assertIsNotNone(shutil.which("lake"),"Lean required; no skip") + names = ("PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_closed_worlds_both_frozen", + "PermanssonResearch.MultiAgent.MeasurableProduct.Two.matched_open_worlds_both_frozen", + "PermanssonResearch.MultiAgent.FinalMatchedAsymmetry.wrong_asymmetric_input_change_is_detectable") + with tempfile.TemporaryDirectory(dir=ROOT/".lake") as temp: + src=pathlib.Path(temp)/"MatchedAsymmetric.lean" + src.write_text("import PermanssonResearch.MultiAgent.MeasurableProductTwoMatchedExamples\n"+ + "\n".join("#check "+n+"\n#print axioms "+n for n in names)+"\n") + proc=subprocess.run(["lake","env","lean",str(src)],cwd=ROOT, + capture_output=True,text=True,timeout=900) + output=proc.stdout+proc.stderr + self.assertEqual(proc.returncode,0,output) + self.assertTrue(all(n in output for n in names),output) + sets=re.findall(r"depends on axioms:\s*\[([^\]]*)\]",output) + self.assertTrue(sets,output) + for item in sets: + self.assertLessEqual({s.strip() for s in item.split(",") if s.strip()}, + {"propext","Quot.sound","Classical.choice"},output) + +if __name__=="__main__":unittest.main() diff --git a/verification/research/test_final06_actual_score_lean.py b/verification/research/test_final06_actual_score_lean.py new file mode 100644 index 00000000..b920b7a0 --- /dev/null +++ b/verification/research/test_final06_actual_score_lean.py @@ -0,0 +1,62 @@ +"""Full explicit Lean kernel/canonical-path/conditional-score compiler signatures.""" +import pathlib +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[2] +MODULE = "PermanssonResearch.ReverseSolver.NonlinearPeriodThreeExpectedScore" +NAMES = ( + "native_inducedKernel_eq_deterministic", + "native_pathLaw_dirac", + "native_actual_occupation_ae_limit", + "native_phase_orbit_is_cycle", + "native_three_phase_true_share", + "actual_phase_aware_score_one_of_canonical_true_mass", + "native_phase_aware_actual_score_one", + "actual_phase_blind_score_le_of_class_identification", + "actual_restricted_gap_of_identified_classes", +) +ALLOW = {"propext", "Classical.choice", "Quot.sound"} + + +def run_lean(testcase, body): + testcase.assertIsNotNone(shutil.which("lake"), "required compiler unavailable; no skip") + with tempfile.TemporaryDirectory(dir=ROOT/".lake") as directory: + path = pathlib.Path(directory)/"ActualScore.lean" + path.write_text("import " + MODULE + "\n" + "open PermanssonResearch.ReverseSolver.NonlinearPeriodThree\n" + + body + "\n") + result = subprocess.run(("lake", "env", "lean", str(path)), cwd=ROOT, + capture_output=True, text=True, timeout=900) + return result.returncode, result.stdout + result.stderr + + +class NativeScoreCompiler(unittest.TestCase): + def test_source_signatures_and_axioms(self): + rc, out = run_lean(self, "\n".join( + "#check " + name + "\n#print axioms " + name for name in NAMES)) + self.assertEqual(rc, 0, out) + for name in NAMES: + self.assertIn(name, out) + sets = re.findall(r"depends on axioms:\s*\[([^\]]*)\]", out) + self.assertGreater(len(sets), 0, "no transitive axiom records: " + out) + for item in sets: + self.assertLessEqual( + {x.strip() for x in item.split(",") if x.strip()}, ALLOW, out) + self.assertNotIn("sorryAx", out) + + def test_false_native_cycle_score_rejected_semantically(self): + rc, out = run_lean(self, + "example : nativePeriodThreeShare = (1/2 : ℚ) := by\n" + " exact native_period_three_share\n") + self.assertNotEqual(rc, 0, "incorrect true share accepted") + self.assertNotRegex(out.lower(), + r"unknown (module|identifier)|failed to import|parser error|out of memory|timed out") + self.assertRegex(out.lower(), r"type mismatch|unsolved goals|application type mismatch") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final06_c11_evaluator.py b/verification/research/test_final06_c11_evaluator.py new file mode 100644 index 00000000..1a45ccd7 --- /dev/null +++ b/verification/research/test_final06_c11_evaluator.py @@ -0,0 +1,56 @@ +"""Lean C11 evaluator source signatures and corrupted-stationarity adversary.""" +import pathlib +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[2] +HEAD = "import PermanssonResearch.ReverseSolver.NonlinearBooleanClassEvaluator\n" \ + "import PermanssonResearch.ReverseSolver.C11ReferenceExamples\n" + + +def compile_lean(body): + if not shutil.which("lake"): + raise AssertionError("pinned Lean is required; never skip") + with tempfile.TemporaryDirectory(dir=ROOT/".lake") as folder: + source = pathlib.Path(folder)/"C11Boolean.lean" + source.write_text(HEAD + body + "\n") + proc = subprocess.run(("lake", "env", "lean", str(source)), cwd=ROOT, + capture_output=True, text=True, timeout=900) + return proc.returncode, proc.stdout + proc.stderr + + +class C11BooleanEvaluator(unittest.TestCase): + def test_named_compiler_and_transitive_axioms(self): + names = ( + "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.booleanClassFunctional", + "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.boolean_rational_objective_eq_actual", + "PermanssonResearch.ReverseSolver.NonlinearPeriodThree.certified_boolean_score_exact", + "PermanssonResearch.ReverseSolver.C11.ReferenceExamples.corrupted_stationary_receipt_rejected", + ) + rc, out = compile_lean("\n".join( + "#check " + name + "\n#print axioms " + name for name in names)) + self.assertEqual(rc, 0, out) + for name in names: + self.assertIn(name, out) + axioms = re.findall(r"depends on axioms:\s*\[([^\]]*)\]", out) + self.assertTrue(axioms, out) + for a in axioms: + self.assertLessEqual({x.strip() for x in a.split(",") if x.strip()}, + {"propext", "Classical.choice", "Quot.sound"}, out) + + def test_false_stationarity_accepted_mutant_rejected(self): + rc, out = compile_lean( + "open PermanssonResearch.ReverseSolver.C11\n" + "open PermanssonResearch.ReverseSolver.C11.ReferenceExamples\n" + "example : checkClassStationary periodic corruptedPeriodic = true := by\n" + " exact corrupted_stationary_receipt_rejected\n") + self.assertNotEqual(rc, 0) + self.assertNotRegex(out.lower(), r"unknown (module|identifier)|failed to import|parser error|out of memory|timed out") + self.assertRegex(out.lower(), r"type mismatch|unsolved goals") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final06_native_class_lean.py b/verification/research/test_final06_native_class_lean.py new file mode 100644 index 00000000..3bb65857 --- /dev/null +++ b/verification/research/test_final06_native_class_lean.py @@ -0,0 +1,60 @@ +"""Proof-level native recurrent-class and original occupation source receipts.""" +import pathlib +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[2] +MOD = "PermanssonResearch.ReverseSolver.NonlinearPeriodThreeClassGeometry" +NS = "PermanssonResearch.ReverseSolver.NonlinearPeriodThree" +CLAIMS = ( + "reachedCycle_mem", + "native_cycle_positive_edge", + "reachedCycle_closed", + "native_cycle_two_way_one", + "native_cycle_two_way_two", + "reachedCycle_eq_communicatingClass", + "reachedCycle_recurrent", + "native_phase_orbit_in_reachedCycle", + "native_selected_communicatingClass", + "native_actual_occupation_equals_reached_stationary", + "phase_aware_invariant_weights", + "phase_aware_invariant_physical_true_mass", + "native_phase_aware_actual_true_mass", +) + +def compile_lean(code): + if not shutil.which("lake"): + raise AssertionError("pinned Lean compiler required, never skip") + with tempfile.TemporaryDirectory(dir=ROOT/".lake") as folder: + file=pathlib.Path(folder)/"NativeClass.lean" + file.write_text(f"import {MOD}\nopen {NS}\n"+code+"\n") + p=subprocess.run(["lake","env","lean",str(file)],cwd=ROOT, + capture_output=True,text=True,timeout=900) + return p.returncode,p.stdout+p.stderr + +class NativeThreeCycleClassProof(unittest.TestCase): + def test_real_native_class_source_signatures_and_axioms(self): + rc,out=compile_lean("\n".join("#check "+n+"\n#print axioms "+n for n in CLAIMS)) + self.assertEqual(rc,0,out) + for name in CLAIMS:self.assertIn(name,out) + found=re.findall(r"depends on axioms:\s*\[([^\]]*)\]",out) + self.assertTrue(found,out) + for a in found: + self.assertLessEqual({x.strip() for x in a.split(",") if x.strip()}, + {"propext","Classical.choice","Quot.sound"},out) + + def test_false_transition_arc_rejected_semantically(self): + rc,out=compile_lean( + "example : nextState phaseAwarePolicy (canonicalCycleState 0) = " + "canonicalCycleState 2 := by\n" + " exact native_cycle_successor (0 : Phase)\n") + self.assertNotEqual(rc,0,"false native phase transition accepted") + self.assertNotRegex(out.lower(), + r"unknown module|unknown identifier|parser error|failed to import|out of memory|timed out") + self.assertRegex(out.lower(),r"type mismatch|unsolved goals|application type mismatch") + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_final06_phaseblind_original.py b/verification/research/test_final06_phaseblind_original.py new file mode 100644 index 00000000..05373e7c --- /dev/null +++ b/verification/research/test_final06_phaseblind_original.py @@ -0,0 +1,90 @@ +"""N2 original native policy-class certification; no Unit/Fin6 model substitution. + +All four Bool-only feedback maps remain deterministic stationary on the +physical observation, with identical native P/U. Compiler signatures, +axiom dependencies and semantic false-mutant behavior are checked directly. +""" +from fractions import Fraction +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT=Path(__file__).resolve().parents[2] +MODULE="PermanssonResearch.ReverseSolver.NonlinearPhaseBlindClassGeometry" +NAMES=( + "native_false_preserving_true_mass_zero", + "native_false_preserving_score", + "native_true_after_false_score", + "native_phase_blind_actual_le_five_sixths", + "native_phase_blind_actual_attained", + "native_actual_phase_aware_strictly_beats_phase_blind", +) + + +def lean_check(contents: str): + if shutil.which("lake") is None: + raise AssertionError("Pinned Lean toolchain required; no skipped mathematical tests") + with tempfile.TemporaryDirectory(dir=ROOT/".lake") as dir: + src=Path(dir)/"PhaseBlindOriginal.lean" + src.write_text("import "+MODULE+"\n" + "open PermanssonResearch.ReverseSolver.NonlinearPeriodThree\n" + +contents+"\n") + proc=subprocess.run(["lake","env","lean",str(src)],cwd=ROOT, + capture_output=True,text=True,timeout=900) + return proc.returncode,proc.stdout+proc.stderr + + +def score(true_probability: Fraction): + return Fraction(1)-abs(true_probability-Fraction(2,3)) + + +class NativePhaseBlindOriginalTests(unittest.TestCase): + def test_exact_policy_scores_not_just_orbit_table(self): + scores={(False,False):score(Fraction(0)), + (False,True):score(Fraction(0)), + (True,False):score(Fraction(1,2)), + (True,True):score(Fraction(1))} + self.assertEqual(scores[(True,False)],Fraction(5,6)) + self.assertEqual(max(scores.values()),Fraction(5,6)) + self.assertEqual(score(Fraction(2,3)),1) + self.assertTrue(all(value<1 for value in scores.values())) + + def test_lean_original_canonical_expected_scores_and_axioms(self): + rc,out=lean_check("\n".join( + "#check "+name+"\n#print axioms "+name for name in NAMES)) + self.assertEqual(rc,0,out) + for name in NAMES: + self.assertIn(name,out) + lists=re.findall(r"depends on axioms:\s*\[([^\]]*)\]",out) + self.assertTrue(lists,"no transitive axiom records found: "+out) + for row in lists: + self.assertLessEqual({x.strip() for x in row.split(",") if x.strip()}, + {"propext","Classical.choice","Quot.sound"},out) + self.assertNotIn("sorryAx",out) + + def test_false_unrestricted_optimum_is_rejected_mathematically(self): + # Keep the false claim in the actual original score semantics, but + # rewrite by the proved attainable value before the negative step. + # A direct failed `exact` can exhaust whnf heartbeats while trying to + # compare the unfolded infinite-path objective, which is NOT a + # mathematical rejection. Here Lean reaches a literal false equality. + rc,out=lean_check( + "example : nativeExpectedNonlinearScore\n" + " (phaseBlindPolicy\n" + " PermanssonResearch.ReverseSolver.NonlinearOccupationBoundary.alternatingFeedback)\n" + " = (1 : ℝ) := by\n" + " rw [native_phase_blind_actual_attained]\n" + " have hneq : (5 / 6 : ℝ) ≠ 1 := by norm_num\n" + " exact hneq\n") + self.assertNotEqual(rc,0,"false deterministic optimality was accepted") + self.assertNotRegex(out.lower(), + r"unknown (module|identifier)|failed to import|parser error|" + r"out of memory|timed out|deterministic.*timeout|" + r"maximum number of heartbeats|maximum recursion depth") + self.assertRegex(out.lower(),r"type mismatch|unsolved goals|application type mismatch") + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_final06_stationary_arithmetic.py b/verification/research/test_final06_stationary_arithmetic.py new file mode 100644 index 00000000..80805052 --- /dev/null +++ b/verification/research/test_final06_stationary_arithmetic.py @@ -0,0 +1,60 @@ +"""Independent Fraction evaluator for the *native* deterministic Fin 3/Bool policies. + +This verifies finite rational dynamics, not the Lean canonical class-law proof. +""" +from fractions import Fraction as Q +from itertools import product +import unittest + + +def periodic_tail(policy, *, phase_aware=False): + """Canonical recurrent orbit of the phase/physical state from (0, False).""" + state = (0, False) + visited = {} + orbit = [] + while state not in visited: + visited[state] = len(orbit) + orbit.append(state) + phase, physical = state + action = phase != 2 if phase_aware else policy[physical] + state = ((phase + 1) % 3, action) + if len(orbit) > 6: + raise AssertionError("finite six-state deterministic orbit did not close") + cycle = orbit[visited[state]:] + return tuple(orbit), tuple(cycle) + + +def score(cycle): + physical_true_mass = Q(sum(int(x) for _, x in cycle), len(cycle)) + return Q(1) - abs(physical_true_mass - Q(2, 3)) + + +class NativeExactPeriodicPolicyArithmetic(unittest.TestCase): + def test_phase_aware_real_native_three_cycle(self): + orbit, cycle = periodic_tail(None, phase_aware=True) + self.assertEqual(cycle, ((0, False), (1, True), (2, True))) + self.assertEqual(score(cycle), Q(1)) + + def test_all_four_deterministic_physical_only_feedbacks(self): + results = {p: score(periodic_tail(p)[1]) for p in product((False, True), repeat=2)} + self.assertEqual(results[(False, False)], Q(1, 3)) + self.assertEqual(results[(True, True)], Q(2, 3)) + self.assertEqual(results[(False, True)], Q(1, 3)) + self.assertEqual(results[(True, False)], Q(5, 6)) + self.assertEqual(max(results.values()), Q(5, 6)) + self.assertLess(max(results.values()), Q(1)) + + def test_randomized_class_tie_not_unrestricted_dominance(self): + self.assertEqual(Q(1) - abs(Q(2, 3) - Q(2, 3)), Q(1)) + + def test_cannot_replace_classwise_expectation_with_nonlinear_barycenter(self): + class_expectation = (Q(1) - abs(Q(0)-Q(2,3)) + + Q(1) - abs(Q(1)-Q(2,3)))/2 + barycenter_score = Q(1) - abs(Q(1,2) - Q(2,3)) + self.assertEqual(class_expectation, Q(1,2)) + self.assertEqual(barycenter_score, Q(5,6)) + self.assertNotEqual(class_expectation,barycenter_score) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_actual_product_reporting_false_lean.py b/verification/research/test_final_actual_product_reporting_false_lean.py new file mode 100644 index 00000000..830f8540 --- /dev/null +++ b/verification/research/test_final_actual_product_reporting_false_lean.py @@ -0,0 +1,66 @@ +"""Q-03 dependent chronological mass / Q-04 intervention-side error mutants. + +Original typed Lean constructions are imported. Q-03 rejects an additive +independent-error shortcut for the actual conditional three-stage law. +Q-04 rejects omitting the nonzero treated reporting error of the original +finite-prefix constitutive-margin certificate. +""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +INVALID = re.compile( + r"unknown (?:module|identifier|constant|tactic)|failed to import|" + r"unexpected token|parser error|out of memory|" + r"maximum number of heartbeats|object file|timed out|" + r"failed to synthesize", re.I) +SEMANTIC = re.compile( + r"type mismatch|application type mismatch|unsolved goals|" + r"tactic .?norm_num.? failed|proved.*false", re.I) + + +def reject(module, body): + if shutil.which("lake") is None: + raise AssertionError("pinned Lean compiler required") + with tempfile.TemporaryDirectory(dir=ROOT / ".lake", + prefix="Q03Q04-source-") as td: + path = Path(td) / "Mutant.lean" + path.write_text("import " + module + "\n" + body + "\n", + encoding="utf-8") + result = subprocess.run(["lake", "env", "lean", str(path)], + cwd=ROOT, capture_output=True, text=True, + timeout=900) + log = result.stdout + "\n" + result.stderr + if result.returncode == 0: + raise AssertionError("false original-source theorem was accepted") + if "error:" not in log.lower() or INVALID.search(log) or not SEMANTIC.search(log): + raise AssertionError("incorrect failure kind:\n" + log[-4500:]) + return log + + +class OriginalConditionalMassAndErrorNegatives(unittest.TestCase): + def test_Q_03_false_independent_error_mass(self): + out = reject( + "PermanssonResearch.Probability.DependentThreeStageSaturation", + "open PermanssonResearch.Probability.DependentThreeStage\n" + "example : dependentLaw.atom (true, (true,true)) = (9/20 : ℝ) := by\n" + " exact true_history_mass\n" + ) + self.assertIn("error:", out.lower()) + + def test_Q_04_false_zero_intervention_report_error(self): + out = reject( + "PermanssonResearch.ConstitutiveQuasi.LocalizedPrefixExamples", + "open PermanssonResearch.ConstitutiveQuasi.LocalizedExamples\n" + "example : dist (0 : ℝ) (1/10) ≤ 0 := by\n" + " exact cannot_omit_treatment_report_error\n" + ) + self.assertIn("error:", out.lower()) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_acceptance.py b/verification/research/test_final_closure_acceptance.py new file mode 100644 index 00000000..40f58b17 --- /dev/null +++ b/verification/research/test_final_closure_acceptance.py @@ -0,0 +1,39 @@ +"""Single-PR candidate acceptance must never masquerade as final review.""" +import copy +from pathlib import Path +import sys +import unittest + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "verification/research")) +import final_closure_verify as gate + + +class FinalClosureAcceptanceTests(unittest.TestCase): + def test_candidate_inventory_is_full_but_pending(self): + result = gate.audit() + self.assertEqual(result["cases"], 45) + self.assertEqual(result["contracts"], 42) + self.assertEqual(result["closures"], 11) + self.assertGreater(result["pending"], 0) + + def test_full45_without_real_source_receipts_rejects(self): + with self.assertRaises(gate.GateError): + gate.strict({}, "full45-strict", None) + + def test_scoped_release_needs_authenticated_compiler_and_review(self): + with self.assertRaises(gate.GateError): + gate.strict({"authenticated": True}, "scoped-release-strict", + {"status": "APPROVED_SOURCE_BOUND"}) + + def test_historical_expectations_remain_complete(self): + hist = gate.read_json(gate.OLD / "acceptance_decisions.json") + snap = gate.read_json(gate.NEW / "acceptance_decisions.json") + byid = {c["id"]: c for c in hist["cases"]} + for case in snap["cases"]: + self.assertEqual(case["required_expected_result"], + byid[case["case_id"]]["required_expected_result"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_all_original_semantics.py b/verification/research/test_final_closure_all_original_semantics.py new file mode 100644 index 00000000..a9c8b21e --- /dev/null +++ b/verification/research/test_final_closure_all_original_semantics.py @@ -0,0 +1,197 @@ +"""Python-only tests for the 25-case detached shard dispatcher. + +Mocks exercise allocation and fail-closed metadata handling only; they are +never counted as successful Lean, real CI execution, or mathematical review. +""" +from pathlib import Path +import copy +import tempfile +import unittest +from unittest import mock +import json + +import final_closure_all_original_semantics as all_cases +from final_closure_mutation_evidence import MutationEvidenceError + +H = "a" * 40 +T = "b" * 40 + + +class SemanticShardingTests(unittest.TestCase): + def test_exact_25_cases_once_across_nine_shards(self): + shards = [all_cases.select_cases(i, 9) for i in range(9)] + flat = [c for shard in shards for c in shard] + self.assertEqual(len(flat), 25) + self.assertEqual(set(flat), all_cases.EXPECTED) + self.assertEqual(len(flat), len(set(flat))) + self.assertTrue(all(len(x) in (2, 3) for x in shards)) + + def test_invalid_shards_and_modified_inventory_fail_closed(self): + for i, n in ((-1, 9), (9, 9), (0, 0), (0, 26)): + with self.subTest(index=i, total=n): + with self.assertRaises(MutationEvidenceError): + all_cases.select_cases(i, n) + changed = dict(all_cases.batch.SPECS) + changed.pop("A-05") + with mock.patch.dict(all_cases.batch.SPECS, changed, clear=True): + with self.assertRaises(MutationEvidenceError): + all_cases.select_cases(0, 9) + + def test_fake_execution_cannot_self_assign_review_or_check_run(self): + with tempfile.TemporaryDirectory(prefix="perm-original-shard-") as root: + out = Path(root) / "detached" + selected = all_cases.select_cases(0, 25) + self.assertEqual(len(selected), 1) + def fake_run(_head, cid, directory): + self.assertEqual(cid, selected[0]) + directory.mkdir(parents=True, exist_ok=False) + spec = all_cases.batch.SPECS[cid] + return { + "format": "Permansson.OriginalSemanticMutantCandidate.v1", + "status": "DETACHED_UNREVIEWED_UNATTRIBUTED", + "source_head": H, "case_id": cid, + "test_path": spec["test_path"], "test_name": spec["method"], + "source_path": spec["source"], + "mutant_sha256": "c" * 64, + "detached_bundle_sha256": "d" * 64, + "mutation_record": { + "head_sha": H, "case_ids": [cid], "claim_ids": [], + "test_path": spec["test_path"], + "test_name": spec["method"], + "original_lean_source_path": spec["source"], + "mutant_source_sha256": "c" * 64, + "outcome": "REJECTED_SEMANTIC", + "compiler_exit_code": 1, + "check_run_id": None, "review_status": "NOT_RECEIVED", + "positive_control": { + "compiled": True, "exit_code": 0, + "lean_toolchain": "leanprover/lean4:v4.34.0", + }, + "original_lean_source_sha256": "e" * 64, + "source_sha256": "f" * 64, + }, + "ci_check_run_id": None, "independent_review": False, + "full_mathematical_acceptance": False, + } + with ( + mock.patch.object(all_cases, "git_head", return_value=H), + mock.patch.object(all_cases.subprocess, "check_output", + return_value=T), + mock.patch.object(all_cases.batch, "run_one", + side_effect=fake_run), + ): + out_record = all_cases.run_exact_shard(H, 0, 25, out) + manifest = json.loads( + (out / "unreviewed_shard_manifest.json").read_text()) + self.assertEqual(out_record["cases"], list(selected)) + self.assertEqual(manifest["case_ids"], list(selected)) + self.assertFalse(manifest["independent_review"]) + self.assertFalse(manifest["release_approved"]) + self.assertFalse(manifest["mathematical_acceptance"]) + self.assertIsNone(manifest["ci_check_run_id"]) + self.assertIn("UNREVIEWED", manifest["status"]) + with self.assertRaises(MutationEvidenceError): + all_cases.run_exact_shard("bad", 0, 25, out) + def test_attested_semantic_shard_rejects_fake_positive_or_approval(self): + cid = all_cases.select_cases(0, 25)[0] + spec = all_cases.batch.SPECS[cid] + base = { + "format": "Permansson.OriginalSemanticMutantCandidate.v1", + "status": "DETACHED_UNREVIEWED_UNATTRIBUTED", + "source_head": H, "case_id": cid, + "test_path": spec["test_path"], "test_name": spec["method"], + "source_path": spec["source"], + "mutant_sha256": "c" * 64, + "detached_bundle_sha256": "d" * 64, + "mutation_record": { + "head_sha": H, "case_ids": [cid], "claim_ids": [], + "test_path": spec["test_path"], + "test_name": spec["method"], + "original_lean_source_path": spec["source"], + "mutant_source_sha256": "c" * 64, + "outcome": "REJECTED_SEMANTIC", + "compiler_exit_code": 1, + "check_run_id": None, "review_status": "NOT_RECEIVED", + "positive_control": { + "compiled": True, "exit_code": 0, + "lean_toolchain": "leanprover/lean4:v4.34.0", + }, + "original_lean_source_sha256": "e" * 64, + "source_sha256": "f" * 64, + }, + "ci_check_run_id": None, "independent_review": False, + "full_mathematical_acceptance": False, + } + all_cases.validate_candidate_case(H, cid, base) + changes = ( + lambda x: x.update(case_id="foreign"), + lambda x: x.update(independent_review=True), + lambda x: x.update(ci_check_run_id=999), + lambda x: x["mutation_record"].update(head_sha="0" * 40), + lambda x: x["mutation_record"].update(outcome="UNVERIFIED"), + lambda x: x["mutation_record"].update(compiler_exit_code=0), + lambda x: x["mutation_record"]["positive_control"].update( + compiled=False), + lambda x: x["mutation_record"].update(check_run_id=999), + lambda x: x["mutation_record"].update(source_sha256="not-a-sha"), + lambda x: x["mutation_record"].update( + mutant_source_sha256="0" * 64), + ) + for mutate in changes: + row = copy.deepcopy(base) + mutate(row) + with self.subTest(mutate=mutate), self.assertRaises( + MutationEvidenceError): + all_cases.validate_candidate_case(H, cid, row) + + def test_semantic_shard_detects_source_head_or_tree_transition(self): + with tempfile.TemporaryDirectory(prefix="perm-original-shard-") as root: + selected = all_cases.select_cases(0, 25)[0] + entry = all_cases.batch.SPECS[selected] + def run_one(head, cid, output): + output.mkdir(parents=True) + return { + "format": "Permansson.OriginalSemanticMutantCandidate.v1", + "status": "DETACHED_UNREVIEWED_UNATTRIBUTED", + "source_head": head, "case_id": cid, + "source_path": entry["source"], + "test_path": entry["test_path"], "test_name": entry["method"], + "mutant_sha256": "c"*64, + "detached_bundle_sha256": "d"*64, + "mutation_record": { + "head_sha": head, "case_ids":[cid], "claim_ids":[], + "test_path":entry["test_path"], + "test_name":entry["method"], + "original_lean_source_path":entry["source"], + "mutant_source_sha256":"c"*64, + "outcome":"REJECTED_SEMANTIC", + "compiler_exit_code":1, "check_run_id":None, + "review_status":"NOT_RECEIVED", + "positive_control":{ + "compiled":True,"exit_code":0, + "lean_toolchain":"leanprover/lean4:v4.34.0"}, + "original_lean_source_sha256":"e"*64, + "source_sha256":"f"*64, + }, + "ci_check_run_id":None, + "independent_review":False, + "full_mathematical_acceptance":False, + } + for mutate in ("head", "tree"): + with self.subTest(mutation=mutate): + start = Path(root)/mutate + gh = [H, "b"*40] if mutate == "head" else [H, H] + tree = [T, "c"*40] if mutate == "tree" else [T, T] + with mock.patch.object(all_cases, "git_head", side_effect=gh), \ + mock.patch.object(all_cases.subprocess, "check_output", + side_effect=tree), \ + mock.patch.object(all_cases.batch, "run_one", + side_effect=run_one): + with self.assertRaises(MutationEvidenceError): + all_cases.run_exact_shard(H, 0, 25, start) + self.assertFalse((start/"unreviewed_shard_manifest.json").exists()) + + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_assemble.py b/verification/research/test_final_closure_assemble.py new file mode 100644 index 00000000..96be9118 --- /dev/null +++ b/verification/research/test_final_closure_assemble.py @@ -0,0 +1,96 @@ +"""Exact-source candidate assembly cannot invent CI IDs or review approvals.""" +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +import final_closure_assemble as a +import final_closure_authenticate as auth + + +class AssemblyTests(unittest.TestCase): + def test_mocked_green_head_still_yields_zero_accepted_claims(self): + with tempfile.TemporaryDirectory() as folder: + root = Path(folder) + comp = root / "compiler.json" + mutations = root / "mutations.json" + bundle = root / "evidence.zip" + comp.write_text(json.dumps({ + "head_sha": "a"*40, + "status": "COMPILER_ONLY_UNREVIEWED", + "compiler_axiom_receipts": [{ + "id": "p1", "head_sha": "a"*40, + "compiler_result": "PASS", + "path": "PermanssonResearch/Simple.lean", + "declaration": "Example.foo", + "source_sha256": "0"*64, + "statement_sha256": "1"*64, + "elaborated_type_sha256": "3"*64, + "declaration_kind": "theorem", + "universe_params_repr": "[]", + "transitive_axioms": [], + "compiler_check": "research", + "axiom_log_sha256": "2"*64, + "case_ids": ["Q-05"], "claim_ids": ["Q1"], + "axiom_log_member": "axiom_logs/p1.log", + "check_run_id": None, + "CI_binding": "PENDING_REAL_CHECK_RUN_ID_ON_FROZEN_H", + "independent_review": "NOT_RECEIVED", + }], + })) + mutations.write_text("[]") + bundle.write_bytes(b"mock source bundle") + def git(*args): + if args == ("rev-parse", "HEAD"): + return "a"*40 + if args[:2] == ("cat-file", "-t"): + return "commit" + if args[:3] == ("show", "-s", "--format=%P"): + return auth.BASE + " " + "a"*40 + if args[:1] == ("rev-parse",): + return "f"*40 + raise AssertionError("unhandled git read") + checks = { + key: {"id": idx+1, "head_sha": "a"*40} + for idx, key in enumerate(sorted(auth.REQUIRED))} + with mock.patch.object(a, "git", side_effect=git), \ + mock.patch.object(a, "authenticated_checks", return_value=checks), \ + mock.patch.object(a, "load_detached_bundle", + return_value={"axiom_logs/p1.log": b"proof"}): + value = a.assemble("a"*40, "b"*40, + compiler_candidate=comp, + mutation_candidates=mutations, + bundle=bundle) + self.assertEqual(value["format"], + "Permansson.FinalClosureDetachedReceipts.v2") + self.assertEqual(value["case_decisions"], []) + self.assertEqual(value["claim_decisions"], []) + self.assertEqual(value["independent_source_reviews"], []) + self.assertIsNone(value["compiler_axiom_receipts"][0]["check_run_id"]) + self.assertTrue(value["source_bound_ci"]) + + def test_wrong_synthetic_parents_fail_before_evidence_promotion(self): + with tempfile.TemporaryDirectory() as folder: + root = Path(folder) + for name in ("compiler.json", "mutation.json"): + (root/name).write_text("{}") + (root/"bundle.zip").write_bytes(b"garbage") + def bogus(*args): + if args == ("rev-parse", "HEAD"): + return "a"*40 + if args[:2] == ("cat-file", "-t"): + return "commit" + if args[:3] == ("show", "-s", "--format=%P"): + return "wrong" + " " + "a"*40 + return "0"*40 + with mock.patch.object(a, "git", side_effect=bogus): + with self.assertRaises(auth.AuthenticationError): + a.assemble("a"*40, "b"*40, + compiler_candidate=root/"compiler.json", + mutation_candidates=root/"mutation.json", + bundle=root/"bundle.zip") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_assemble_v3.py b/verification/research/test_final_closure_assemble_v3.py new file mode 100644 index 00000000..4218d352 --- /dev/null +++ b/verification/research/test_final_closure_assemble_v3.py @@ -0,0 +1,111 @@ +"""Mock-only candidate preparation regressions; never approves 45/11.""" +import json +from unittest import TestCase, mock + +import final_closure_assemble_v3 as mod + +H = "a"*40 +SHA = mod.digest + +class AssemblyV3Tests(TestCase): + def test_valid_compiler_member_is_bound_to_actual_shard(self): + compiler_log = b"kernel elaborated type and axioms" + source = b"theorem genuine : True := by trivial\n" + record = { + "inventory_index": 1, "id":"all:0001", + "head_sha":H, "path":"Proof.lean", + "declaration":"Test.genuine", "source_sha256":SHA(source), + "case_ids":["C-01"], "claim_ids":[], + "compiler_result":"PASS", "compiler_check":"research", + "check_run_id":None,"axiom_log_member":"axiom_logs/all_0001.log", + "axiom_log_sha256":SHA(compiler_log), + "elaborated_type_sha256":"b"*64,"declaration_kind":"theorem", + "universe_params_repr":"[]","transitive_axioms":[], + } + producer = {"actual_check_run_id":83,"artifact_id":99} + compiled = {"declaration_kind":"theorem","universe_params_repr":"[]"} + with mock.patch.object(mod, "exact_source_bytes", return_value=source), \ + mock.patch.object(mod, "compiled_type_record",return_value=compiled), \ + mock.patch.object(mod, "elaborated_type_sha256",return_value="b"*64), \ + mock.patch.object(mod, "parse_target_axioms",return_value=[]): + observed=mod.proof_row( + H, record, producer, compiler_log, + "compiler/axiom_logs/all_0001.log") + self.assertEqual(observed["check_run_id"],83) + self.assertEqual(observed["producer_artifact_id"],99) + self.assertEqual(observed["statement_sha256"],"b"*64) + self.assertEqual(observed["compiler_check"],"pr113-compiler-shard") + self.assertEqual(observed["producer_member_bindings"],[ + {"outer_member":"compiler/axiom_logs/all_0001.log", + "source_member":"axiom_logs/all_0001.log"}]) + corrupted=dict(record, elaborated_type_sha256="c"*64) + with self.assertRaises(mod.AssemblyError): + mod.proof_row(H, corrupted, producer, compiler_log, + "compiler/axiom_logs/all_0001.log") + + def test_exact_append_case_preserves_positive_and_negative_source_bytes(self): + original=b"theorem actual : True := by trivial\n" + mutant=original+b"\nexample : False := by exact actual\n" + positive_log=b"positive compiled\n" + negative_log=b"error: unsolved goals\n" + test_bytes=b"def test_original():\n pass\n" + record={ + "case_id":"C-01","source_head":H, + "independent_review":False, + "mutation_record":{ + "id":"C-01_test_original","head_sha":H, + "test_path":"verification/research/test_original.py", + "test_name":"test_original","source_sha256":SHA(test_bytes), + "case_ids":["C-01"],"claim_ids":[], + "original_lean_source_path":"Proof.lean", + "original_lean_source_member":"orig.lean", + "original_lean_source_sha256":SHA(original), + "mutant_source_member":"mut.lean", + "mutant_source_sha256":SHA(mutant), + "positive_control":{"compiled":True,"exit_code":0, + "lean_toolchain":"4.34.0", + "log_member":"pos.log","log_sha256":SHA(positive_log)}, + "outcome":"REJECTED_SEMANTIC","compiler_exit_code":1, + "log_member":"neg.log","log_sha256":SHA(negative_log), + "check_run_id":None, + } + } + content={ + "C-01/unreviewed_candidate.json":json.dumps(record).encode(), + "C-01/source_logs/orig.lean":original, + "C-01/source_logs/mut.lean":mutant, + "C-01/source_logs/pos.log":positive_log, + "C-01/source_logs/neg.log":negative_log, + } + def frozen(_head,path): + return test_bytes if path.endswith(".py") else original + sealed={} + with mock.patch.object(mod, "exact_source_bytes",side_effect=frozen): + p,n=mod.append_case_rows( + H,"C-01",{"actual_check_run_id":88,"artifact_id":177}, + content,sealed) + self.assertEqual(len(sealed),4) + self.assertEqual(p["check_run_id"],88) + self.assertEqual(n["producer_artifact_id"],177) + self.assertEqual(p["positive_control"]["log_member"],p["log_member"]) + self.assertEqual(n["positive_control"]["log_member"],p["log_member"]) + self.assertEqual(len(n["producer_member_bindings"]),4) + self.assertEqual(n["case_ids"],["C-01"]) + content["C-01/source_logs/mut.lean"]=b"altered" + with mock.patch.object(mod, "exact_source_bytes",side_effect=frozen): + with self.assertRaises(mod.AssemblyError): + mod.append_case_rows( + H,"C-01",{"actual_check_run_id":88,"artifact_id":177}, + content,{}) + + def test_rejects_unsafe_members_and_duplicate_evidence(self): + items={} + mod.pack_member(items,"proof/proof.log",b"genuine") + for path in ("proof/proof.log","../escape","/absolute", + "a\\b"): + with self.subTest(path=path),self.assertRaises(mod.AssemblyError): + mod.pack_member(items,path,b"danger") + + def test_gap_report_never_claims_mathematical_acceptance(self): + self.assertEqual(mod.FORMAT_V3, + "Permansson.FinalClosureDetachedReceipts.v3") diff --git a/verification/research/test_final_closure_authenticate.py b/verification/research/test_final_closure_authenticate.py new file mode 100644 index 00000000..cebfbd39 --- /dev/null +++ b/verification/research/test_final_closure_authenticate.py @@ -0,0 +1,121 @@ +"""No self-claimed CI or reviewer receipt can pass final B/H/S/M authentication. + +These tests use forged stand-ins only to challenge the gate's logic; they +do not create or claim real approvals, CI checks, or merged commits. +""" +from __future__ import annotations + +from pathlib import Path +import sys +import unittest +from unittest import mock + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "verification/research")) +import final_closure_authenticate as source + +B = source.BASE +H = "a" * 40 +S = "b" * 40 +M = "c" * 40 +T = "d" * 40 + + +def genuine_shape(): + checked = [ + {"name": name, "id": i + 1, "head_sha": H, + "status": "completed", "conclusion": "success"} + for i, name in enumerate(sorted(source.REQUIRED)) + ] + pr = { + "number": 113, "merged": True, + "base": {"sha": B}, "head": {"sha": H}, + "merge_commit_sha": M, "user": {"login": "owner"}, + } + reviews = [{ + "id": 42, "state": "APPROVED", "commit_id": H, + "submitted_at": "2026-10-21T00:00:00Z", + "user": {"login": "independent_reviewer"}, + }] + receipts = { + "base_sha": B, "head_sha": H, "synthetic_sha": S, + "merged_main_sha": M, "source_bound_ci": [ + {"name": x["name"], "id": x["id"], "head_sha": x["head_sha"]} + for x in checked + ], + "independent_source_reviews": [{"id": 42}], + } + return receipts, pr, checked, reviews + + +def git_stub(*args): + if args[:2] == ("cat-file", "-t"): + return "commit" + if args[:3] == ("show", "-s", "--format=%P"): + return B + " " + H + if args[:1] == ("rev-parse",): + return T + if args[:2] == ("merge-base", "--is-ancestor"): + return "" + raise AssertionError("unexpected Git query: " + repr(args)) + + +class FinalSourceAuthenticationTests(unittest.TestCase): + def _invoke(self, receipts, pr, checks, reviews): + def github_stub(path): + if path == "/pulls/113": + return pr + if path == "/pulls/113/reviews?per_page=100": + return reviews + if path == "/commits/" + H + "/check-runs?per_page=100": + return {"check_runs": checks} + raise AssertionError("unexpected endpoint " + path) + with mock.patch.object(source, "git", side_effect=git_stub), \ + mock.patch.object(source, "live", side_effect=github_stub): + return source.verify_source(receipts) + + def test_boolean_authenticated_flag_never_suffices(self): + with self.assertRaises(source.AuthenticationError): + source.verify_source({"authenticated": True}) + + def test_forged_non_git_synthetic_parents_rejected(self): + receipts, pr, checks, reviews = genuine_shape() + with mock.patch.object(source, "git", side_effect=lambda *args: + "0" * 40 + " " + H if args[:3] == ("show", "-s", "--format=%P") + else git_stub(*args)), \ + mock.patch.object(source, "live", return_value=pr): + with self.assertRaises(source.AuthenticationError): + source.verify_source(receipts) + + def test_source_head_ci_must_be_complete(self): + receipts, pr, checks, reviews = genuine_shape() + with self.assertRaises(source.AuthenticationError): + self._invoke(receipts, pr, checks[1:], reviews) + + def test_author_approval_is_not_independent_review(self): + receipts, pr, checks, reviews = genuine_shape() + reviews[0]["user"]["login"] = "owner" + with self.assertRaises(source.AuthenticationError): + self._invoke(receipts, pr, checks, reviews) + + def test_stale_review_commit_is_rejected(self): + receipts, pr, checks, reviews = genuine_shape() + reviews[0]["commit_id"] = "9" * 40 + with self.assertRaises(source.AuthenticationError): + self._invoke(receipts, pr, checks, reviews) + + def test_claimed_ci_id_must_match_live_check(self): + receipts, pr, checks, reviews = genuine_shape() + receipts["source_bound_ci"][0]["id"] = 999999 + with self.assertRaises(source.AuthenticationError): + self._invoke(receipts, pr, checks, reviews) + + def test_live_merge_and_read_only_checks_are_all_bound(self): + receipts, pr, checks, reviews = genuine_shape() + got = self._invoke(receipts, pr, checks, reviews) + self.assertEqual((got["B"], got["H"], got["S"], got["M"]), (B, H, S, M)) + self.assertEqual(got["reviewer_ids"], {42}) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_authenticate_v2.py b/verification/research/test_final_closure_authenticate_v2.py new file mode 100644 index 00000000..2f4af5ad --- /dev/null +++ b/verification/research/test_final_closure_authenticate_v2.py @@ -0,0 +1,308 @@ +"""Negative/positive v2 auth logic tests with mocked GitHub, NOT real approvals.""" +import copy +import json +import unittest +from unittest import mock + +import final_closure_authenticate as auth + +B = auth.BASE +H = "a" * 40 +S = "b" * 40 +M = "c" * 40 +T = "d" * 40 +BUNDLE = "e" * 64 + + +def fixture(merged=False): + checks = [ + {"name": name, "head_sha": H, "id": i + 10, + "status": "completed", "conclusion": "success", + "app": {"slug": "github-actions"}} + for i, name in enumerate(sorted(auth.REQUIRED))] + integrated = [dict(c, head_sha=M, id=c["id"]+100) for c in checks] + pr = { + "number": 113, "state": "closed" if merged else "open", + "merged": merged, + "user": {"login": "author"}, + "head": {"sha": H}, "base": {"sha": "f"*40 if merged else B}, + "merge_commit_sha": M if merged else S, + "merged_at": "2026-10-21T18:00:00Z" if merged else None, + } + reviews = [{ + "id": 88, "state": "APPROVED", "submitted_at": "2026-10-21T17:00:00Z", + "commit_id": H, "user": {"login": "external"}, + "body": auth.REVIEW_PREFIX + " " + + json.dumps({"head_sha": H, "evidence_bundle_sha256": BUNDLE, + "cases": {"C-01": "VERIFIED_SELECTED_SCOPE"}, + "claims": ["Q1"]}, sort_keys=True), + }] + receipts = { + "base_sha": B, "head_sha": H, "synthetic_sha": S, + "merged_main_sha": M if merged else None, + "evidence_bundle_sha256": BUNDLE, + "source_bound_ci": [ + {"name": c["name"], "id": c["id"], "head_sha": H} + for c in checks], + "merged_bound_ci": [ + {"name": c["name"], "id": c["id"], "head_sha": M} + for c in integrated], + "independent_source_reviews": [{"id": 88}], + } + attestation = json.loads(reviews[0]["body"].split(auth.REVIEW_PREFIX, 1)[1]) + attestation["receipt_payload_sha256"] = auth.receipt_payload_digest(receipts) + reviews[0]["body"] = auth.REVIEW_PREFIX + " " + json.dumps(attestation, sort_keys=True) + return receipts, pr, checks, integrated, reviews + + +class AuthenticationV2Tests(unittest.TestCase): + def run_gate(self, *, merged=False, mutate=None): + receipts, pr, checks, integrated, reviews = fixture(merged) + parent_s = [B, H] + parent_m = [B, H] + trees = {"S": T, "M": T, "H": T} + main_ref = B + if mutate: + mutate(receipts, pr, checks, integrated, reviews, + parent_s, parent_m, trees) + + def git(*parts): + if parts[:2] == ("cat-file", "-t"): + return "commit" + if parts[:3] == ("show", "-s", "--format=%P"): + return " ".join(parent_m if parts[3] == M else parent_s) + if parts == ("rev-parse", "main"): + return main_ref + if parts[:1] == ("rev-parse",): + value = parts[1].split("^")[0] + return trees["M" if value == M else "S" if value == S else "H"] + if parts[:2] == ("merge-base", "--is-ancestor"): + return "" + raise AssertionError("unexpected Git call " + repr(parts)) + + def live(path): + if path == "/pulls/113": + return pr + if path == "/pulls/113/reviews?per_page=100": + return reviews + if path == "/issues/113/comments?per_page=100": + return [] + if path.startswith("/commits/") and path.endswith("/check-runs?per_page=100"): + sha = path.split("/")[2] + return {"check_runs": integrated if sha == M else checks} + raise AssertionError("unexpected GitHub endpoint " + path) + + with mock.patch.object(auth, "git", side_effect=git), \ + mock.patch.object(auth, "live", side_effect=live), \ + mock.patch.object(auth, "reviewer_authorized", + side_effect=lambda name, role: name in + ("external", "external_scholar")): + if merged: + return auth.verify_merged_v2(receipts) + return auth.verify_premerge(receipts) + + def test_paginated_checks_latest_failure_overrides_green(self): + rows = [ + {"name": name, "head_sha": H, "id": i + 1, + "status": "completed", "conclusion": "success", + "app": {"slug": "github-actions"}} + for i, name in enumerate(sorted(auth.REQUIRED)) + ] + # Fill page 1 with unrelated valid jobs so the later trusted check + # can only be found on page two. + page1 = rows + [ + {"name": f"other_{i}", "head_sha": H, "id": i + 300, + "status": "completed", "conclusion": "success", + "app": {"slug": "github-actions"}} + for i in range(100 - len(rows)) + ] + failure = dict(rows[0], id=99999, conclusion="failure") + def live(path): + if path.endswith("&page=2"): + return {"check_runs": [failure]} + if path.endswith("?per_page=100"): + return {"check_runs": page1} + raise AssertionError(path) + with mock.patch.object(auth, "live", side_effect=live): + with self.assertRaises(auth.AuthenticationError): + auth.authenticated_checks(H) + with mock.patch.object(auth, "live", return_value={"check_runs": rows}): + self.assertEqual(len(auth.authenticated_checks(H)), len(auth.REQUIRED)) + + def test_authority_policy_unconfigured_denies_non_author(self): + with mock.patch.object(auth, "REVIEWER_POLICY") as target: + policy = { + "format": "Permansson.ReviewerAuthority.v1", + "status": "UNCONFIGURED", + "source_reviewers": [{"login": "external", + "approved_by": "HMarcusWH", + "independence_disclosed": True}], + "scholarly_reviewers": [], + } + target.read_bytes.return_value = json.dumps(policy).encode("utf-8") + self.assertFalse(auth.reviewer_authorized("external", "source")) + policy["status"] = "OWNER_AUTHORIZED" + target.read_bytes.return_value = json.dumps(policy).encode("utf-8") + self.assertTrue(auth.reviewer_authorized("external", "source")) + self.assertFalse(auth.reviewer_authorized("external", "scholarly")) + target.read_bytes.return_value = ( + b'{"format":"Permansson.ReviewerAuthority.v1",' + b'"status":"UNCONFIGURED","status":"OWNER_AUTHORIZED",' + b'"source_reviewers":[],"scholarly_reviewers":[]}') + with self.assertRaises(auth.AuthenticationError): + auth.reviewer_authorized("external", "source") + + def test_revoked_review_does_not_survive_later_changes_requested(self): + def bad(r, p, c, i, reviews, ps, pm, ts): + new = dict(reviews[0], id=89, state="CHANGES_REQUESTED", + submitted_at="2026-10-21T17:01:00Z") + reviews.append(new) + with self.assertRaises(auth.AuthenticationError): + self.run_gate(mutate=bad) + + def test_review_on_page_two_is_authenticated(self): + receipts, pr, checks, integrated, reviews = fixture() + page1 = [ + {"id": i + 1000, "state": "COMMENTED", + "submitted_at": "2026-10-21T16:00:00Z", + "commit_id": H, "user": {"login": f"other_{i}"}, + "body": ""} + for i in range(100) + ] + def live(path): + if path == "/pulls/113/reviews?per_page=100": + return page1 + if path == "/pulls/113/reviews?per_page=100&page=2": + return reviews + raise AssertionError(path) + with mock.patch.object(auth, "live", side_effect=live), \ + mock.patch.object(auth, "reviewer_authorized", return_value=True): + scopes = auth.authenticate_scoped_reviews( + H, "author", BUNDLE, + receipts["independent_source_reviews"], + auth.receipt_payload_digest(receipts)) + self.assertIn(88, scopes) + + def test_valid_mocked_premerge_review_scopes(self): + outcome = self.run_gate() + self.assertEqual(outcome["H"], H) + self.assertEqual(outcome["review_scopes"][88]["cases"]["C-01"], + "VERIFIED_SELECTED_SCOPE") + self.assertIsNone(outcome["M"]) + + def test_valid_mocked_merged_exact_BH_and_integrated_checks(self): + outcome = self.run_gate(merged=True) + self.assertEqual(outcome["M"], M) + self.assertEqual(len(outcome["integrated_checks"]), len(auth.REQUIRED)) + + def test_new_required_jobs_cannot_be_borrowed_or_ignored(self): + required = { + "executable-verification", "research-release", + "integrity-portability-windows", + "Application contract (ubuntu-latest)", + "Application contract (windows-latest)", + } + self.assertTrue(required <= auth.REQUIRED) + for name in sorted(required): + def invalidate(receipts, pr, checks, integrated, reviews, + parent_s, parent_m, trees): + for check in checks: + if check["name"] == name: + check["conclusion"] = "failure" + return + self.fail("missing required test fixture: " + name) + with self.subTest(name=name), self.assertRaises(auth.AuthenticationError): + self.run_gate(mutate=invalidate) + def missing_receipt(receipts, pr, checks, integrated, reviews, + parent_s, parent_m, trees): + receipts["source_bound_ci"] = [ + item for item in receipts["source_bound_ci"] + if item["name"] != name] + with self.subTest(name=name + " receipt"), self.assertRaises( + auth.AuthenticationError): + self.run_gate(mutate=missing_receipt) + + def test_fake_check_run_id_rejected(self): + def mutate(r, p, c, i, reviews, ps, pm, ts): + r["source_bound_ci"][0]["id"] = 9999 + with self.assertRaises(auth.AuthenticationError): + self.run_gate(mutate=mutate) + + def test_stale_review_or_owner_approval_rejected(self): + for mode in ("stale", "author", "wrong-digest"): + def mutate(r, p, c, i, reviews, ps, pm, ts): + if mode == "stale": + reviews[0]["commit_id"] = "f" * 40 + elif mode == "author": + reviews[0]["user"]["login"] = "author" + else: + reviews[0]["body"] = reviews[0]["body"].replace(BUNDLE, "0"*64) + with self.subTest(mode=mode): + with self.assertRaises(auth.AuthenticationError): + self.run_gate(mutate=mutate) + + def test_synthetic_wrong_parent_rejected(self): + def mutate(r, p, c, i, reviews, ps, pm, ts): + ps[0] = "f"*40 + with self.assertRaises(auth.AuthenticationError): + self.run_gate(mutate=mutate) + + def test_merged_wrong_parent_and_tree_rejected(self): + for mode in ("parent", "tree"): + def mutate(r, p, c, i, reviews, ps, pm, ts): + if mode == "parent": + pm[0] = "f"*40 + else: + ts["M"] = "0"*40 + with self.subTest(mode=mode): + with self.assertRaises(auth.AuthenticationError): + self.run_gate(merged=True, mutate=mutate) + + def test_changed_main_base_premerge_rejected(self): + def mutate(r, p, c, i, reviews, ps, pm, ts): + p["base"]["sha"] = "f"*40 + with self.assertRaises(auth.AuthenticationError): + self.run_gate(mutate=mutate) + + def test_old_author_comment_cannot_authenticate(self): + def mutate(r, p, c, i, reviews, ps, pm, ts): + reviews[0]["state"] = "COMMENTED" + with self.assertRaises(auth.AuthenticationError): + self.run_gate(mutate=mutate) + + + def test_scholarly_review_has_real_merged_pdf_and_all_pages(self): + receipts, pr, checks, integrated, reviews = fixture(True) + record = { + "head_sha": H, "merged_main_sha": M, + "pdf_sha256": "f"*64, + "pages": {"page-1.png": "a"*64, "page-2.png": "b"*64}, + "approved_claim_ids": ["Q1"], + "all_pages_visually_inspected": True, + "scholarly_review": "APPROVED", + } + comments = [{ + "id": 910, "user": {"login": "external_scholar"}, + "created_at": "2026-10-21T19:00:00Z", + "updated_at": "2026-10-21T19:00:00Z", + "body": auth.SCHOLARLY_PREFIX + " " + json.dumps(record), + }] + with mock.patch.object(auth, "live", return_value=comments), \ + mock.patch.object(auth, "reviewer_authorized", + side_effect=lambda name, role: name == "external_scholar"): + got = auth.authentic_scholarly_attestations(pr, H, M) + self.assertEqual(got[0]["pages"], record["pages"]) + comments[0]["updated_at"] = "2026-10-21T20:00:00Z" + with self.assertRaises(auth.AuthenticationError): + auth.authentic_scholarly_attestations(pr, H, M) + comments[0]["updated_at"] = comments[0]["created_at"] + comments[0]["user"]["login"] = "author" + self.assertEqual(auth.authentic_scholarly_attestations(pr, H, M), []) + comments[0]["user"]["login"] = "external_scholar" + comments[0]["created_at"] = "2026-10-21T17:00:00Z" + self.assertEqual(auth.authentic_scholarly_attestations(pr, H, M), []) + + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_batch_legacy_replay.py b/verification/research/test_final_closure_batch_legacy_replay.py new file mode 100644 index 00000000..fac9ede3 --- /dev/null +++ b/verification/research/test_final_closure_batch_legacy_replay.py @@ -0,0 +1,42 @@ +"""Twenty-case original-provider selection tests, not mathematical approvals.""" +import copy +import unittest +import final_closure_batch_legacy_replay as replay + +IDS={f"L-{i:02d}" for i in range(20)} + +def fixture(): + return { + "format":"Permansson.PR113.LegacyReplaySelection.v1", + "cases":[{ + "case_id":cid,"test_index":0, + "test_method":"test_false_lean", + "original_provider":"PermanssonResearch/Probability/Original.lean", + "observation_id":"obs_"+cid.replace("-","_"), + "producing_artifact_id":100+i, + } for i,cid in enumerate(sorted(IDS))] + } + +class OriginalLegacySelectionTests(unittest.TestCase): + def test_all_twenty_typed_choices_are_required(self): + rows=replay.parse_mapping(fixture(),IDS) + self.assertEqual(set(rows),IDS) + self.assertEqual(len(rows),20) + + def test_missing_duplicate_foreign_and_unreviewed_fields_rejected(self): + mutants=[] + x=fixture();x["cases"].pop();mutants.append(x) + x=fixture();x["cases"][1]["case_id"]=x["cases"][0]["case_id"];mutants.append(x) + x=fixture();x["cases"][1]["case_id"]="UNLISTED";mutants.append(x) + x=fixture();x["cases"][1]["test_method"]="other";mutants.append(x) + x=fixture();x["cases"][1]["test_index"]=True;mutants.append(x) + x=fixture();x["cases"][1]["producing_artifact_id"]=None;mutants.append(x) + x=fixture();x["cases"][1]["original_provider"]="../escape.lean";mutants.append(x) + x=fixture();x["cases"][1]["unauthorized_review"]="APPROVED";mutants.append(x) + for idx,item in enumerate(mutants): + with self.subTest(idx=idx),self.assertRaises(replay.LegacyBatchError): + replay.parse_mapping(item,IDS) + + def test_selection_scope_cannot_be_shrunk_to_eight_shards(self): + with self.assertRaises(replay.LegacyBatchError): + replay.parse_mapping(fixture(),set(sorted(IDS)[:8])) diff --git a/verification/research/test_final_closure_candidate_artifact_auth.py b/verification/research/test_final_closure_candidate_artifact_auth.py new file mode 100644 index 00000000..149fb918 --- /dev/null +++ b/verification/research/test_final_closure_candidate_artifact_auth.py @@ -0,0 +1,208 @@ +"""Synthetic GitHub API responses test candidate artifact authenticity gates. + +No mocked GitHub response is an actual check run, true source compiler output +or independent scientific review. Real mode must query the live GitHub API. +""" +import copy +import hashlib +import json +import os +from pathlib import Path +import tempfile +import unittest +from unittest import mock +import zipfile + +import final_closure_candidate_artifact_auth as auth +import pr113_producer_target_v3 as producer +from release_target import B113 +from final_closure_authenticate import AuthenticationError + +H = "a" * 40 +RUN, CHECK, ARTIFACT = 111, 222, 333 + + +class CandidateArtifactIntegrity(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory(prefix="pr113-artifact-auth-") + self.addCleanup(self.tmp.cleanup) + root = Path(self.tmp.name) + self.artifact = root / "artifact.zip" + cases = ["A-01", "B-01"] + self.manifest = { + "format": "Permansson.AllOriginalSemanticCandidateShard.v1", + "status": "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED_RECEIPT", + "head": H, "shard_index": 0, "shard_count": 9, + "case_ids": cases, + "completed": [{"case_id": case} for case in cases], + "independent_review": False, + "ci_check_run_id": None, "mathematical_acceptance": False, + } + with zipfile.ZipFile(self.artifact, "w") as archive: + archive.writestr("unreviewed_shard_manifest.json", + json.dumps(self.manifest)) + digest = hashlib.sha256(self.artifact.read_bytes()).hexdigest() + self.responses = { + f"/actions/runs/{RUN}": { + "id": RUN, "head_sha": H, + "head_branch": "research/final-mathematical-closure", + "event": "push", "status": "completed", + "conclusion": "success", "run_attempt": 1, + "path": ".github/workflows/pr113-all-original-semantic-candidates.yml", + }, + f"/check-runs/{CHECK}": { + "id": CHECK, "head_sha": H, + "status": "completed", "conclusion": "success", + "app": {"slug": "github-actions"}, + }, + f"/actions/artifacts/{ARTIFACT}": { + "id": ARTIFACT, + "name": "pr113-all-original-semantic-candidates-0", + "expired": False, + "digest": "sha256:" + digest, + "workflow_run": {"id": RUN, "head_sha": H}, + }, + } + self.synthetic_source_event = { + "GITHUB_ACTIONS": "true", "GITHUB_REPOSITORY": "HMarcusWH/PermanssonLean", + "GITHUB_SHA": H, "GITHUB_EVENT_NAME": "push", + "GITHUB_REF": "refs/heads/research/final-mathematical-closure", + } + self.synthetic_live_pr = { + "number": 113, "merged": False, + "head": {"sha": H, "ref": "research/final-mathematical-closure"}, + "base": {"sha": B113}, + } + self.jobs = [{ + "name": "semantic-candidate-shard (0)", + "run_id": RUN, "head_sha": H, + "run_attempt": 1, "status": "completed", + "conclusion": "success", + "check_run_url": + f"https://api.github.com/repos/HMarcusWH/PermanssonLean/check-runs/{CHECK}", + }] + + def evaluate(self): + # Explicit synthetic event/PR/Git fixture, retaining the actual + # production source guard and all of its fail-closed conditions. + real_guard = producer.authenticated_producer_branch + def git(*args): + if args == ("rev-parse", "HEAD"): + return H + if args == ("cat-file", "-t", H): + return "commit" + raise AssertionError("unmocked producer Git command: " + repr(args)) + def synthetic_source_guard(head): + def live(url): + if url == "/pulls/113": + return self.synthetic_live_pr + raise AssertionError("unmocked producer GitHub path: " + url) + return real_guard(head, env=self.synthetic_source_event, live=live, git=git) + with ( + mock.patch.dict(os.environ, {"GITHUB_TOKEN": "fake-test-token"}), + mock.patch.object(producer, "authenticated_producer_branch", + side_effect=synthetic_source_guard), + mock.patch.object(auth, "git_head", return_value=H), + mock.patch.object(auth, "live", + side_effect=lambda path: self.responses[path]), + mock.patch.object(auth, "paginate", return_value=self.jobs), + mock.patch.object(auth, "select_cases", + return_value=("A-01", "B-01")), + ): + return auth.verify_artifact( + kind="append", shard=0, head=H, + run_id=RUN, artifact_id=ARTIFACT, + artifact_zip=self.artifact) + + def test_verified_git_origin_still_remains_unreviewed(self): + report = self.evaluate() + self.assertEqual(report["actual_check_run_id"], CHECK) + self.assertEqual(report["run_id"], RUN) + self.assertEqual(report["artifact_id"], ARTIFACT) + self.assertIn("UNREVIEWED", report["status"]) + self.assertFalse(report["mathematical_acceptance"]) + self.assertFalse(report["independent_review"]) + self.assertFalse(report["scientific_release_approved"]) + self.assertIsNone(report["merged_main_sha"]) + + def test_wrong_job_status_run_head_and_artifact_digest_rejected(self): + wrongs = ( + ("run", lambda: self.responses[f"/actions/runs/{RUN}"].update( + head_sha="b"*40)), + ("job", lambda: self.jobs[0].update(conclusion="failure")), + ("artifact", lambda: self.responses[ + f"/actions/artifacts/{ARTIFACT}"].update(digest="sha256:"+"0"*64)), + ("expired", lambda: self.responses[ + f"/actions/artifacts/{ARTIFACT}"].update(expired=True)), + ("app", lambda: self.responses[ + f"/check-runs/{CHECK}"]["app"].update(slug="untrusted")), + ) + baseline_resp = copy.deepcopy(self.responses) + baseline_jobs = copy.deepcopy(self.jobs) + for label, mutate in wrongs: + self.responses = copy.deepcopy(baseline_resp) + self.jobs = copy.deepcopy(baseline_jobs) + mutate() + with self.subTest(label=label), self.assertRaises( + AuthenticationError): + self.evaluate() + self.responses = baseline_resp + self.jobs = baseline_jobs + + + def test_synthetic_wrong_source_event_or_pr_fails_before_artifact(self): + original_event = copy.deepcopy(self.synthetic_source_event) + for key, value in ( + ("GITHUB_ACTIONS", "false"), + ("GITHUB_SHA", "f"*40), + ("GITHUB_REF", "refs/heads/invalid"), + ("GITHUB_REPOSITORY", "foreign/repository"), + ): + self.synthetic_source_event = dict(original_event, **{key: value}) + with self.subTest(event=key), self.assertRaises( + producer.ProducerTargetError): + self.evaluate() + self.synthetic_source_event = original_event + original_pr = copy.deepcopy(self.synthetic_live_pr) + for edit in ( + lambda pr: pr["head"].update(sha="f"*40), + lambda pr: pr["base"].update(sha="f"*40), + lambda pr: pr.update(merged=True), + ): + self.synthetic_live_pr = copy.deepcopy(original_pr) + edit(self.synthetic_live_pr) + with self.subTest(pr=self.synthetic_live_pr), self.assertRaises( + producer.ProducerTargetError): + self.evaluate() + self.synthetic_live_pr = original_pr + + def test_attestation_output_traversal_rejects_before_live_source(self): + with tempfile.TemporaryDirectory() as tmp: + base = Path(tmp) + destination = base / "outside" + destination.mkdir() + args = [ + "--head", H, "--kind", "append", "--shard", "0", + "--run-id", str(RUN), "--artifact-id", str(ARTIFACT), + "--artifact-zip", str(self.artifact), + "--output", str(destination / ".." / "forged.json"), + ] + with mock.patch.object(auth, "verify_artifact") as external: + self.assertEqual(auth.main(args), 2) + external.assert_not_called() + self.assertFalse((base / "forged.json").exists()) + + def test_false_approval_in_candidate_manifest_rejected(self): + modified = dict(self.manifest, independent_review=True) + with zipfile.ZipFile(self.artifact, "w") as archive: + archive.writestr("unreviewed_shard_manifest.json", + json.dumps(modified)) + new_digest = hashlib.sha256(self.artifact.read_bytes()).hexdigest() + self.responses[f"/actions/artifacts/{ARTIFACT}"]["digest"] = ( + "sha256:" + new_digest) + with self.assertRaises(AuthenticationError): + self.evaluate() + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_case_method_routes.py b/verification/research/test_final_closure_case_method_routes.py new file mode 100644 index 00000000..5a43508d --- /dev/null +++ b/verification/research/test_final_closure_case_method_routes.py @@ -0,0 +1,74 @@ +"""Scope-route smoke/adversarial checks: 45 selected IDs are NOT acceptance.""" +from __future__ import annotations + +import copy +import unittest + +import final_closure_case_method_routes as routes +import final_closure_case_map as cases + + +class OriginalMethodRoutes(unittest.TestCase): + def test_all_45_current_test_methods_resolve(self): + source = cases.generate() + rows = source["cases"] + self.assertEqual(len(rows), 45) + self.assertEqual(len({r["case_id"] for r in rows}), 45) + self.assertTrue(all(r["test_source_candidates"] and + r["test_method_candidates"] and + r["effective_accepted"] is False + for r in rows)) + self.assertEqual(sum(len(r["test_method_candidates"]) for r in rows), 154) + self.assertEqual(source["status"], "UNREVIEWED_CANDIDATE_ONLY") + selected = routes.validated_routes([r["case_id"] for r in rows]) + for row in rows: + self.assertEqual(selected[row["case_id"]], + row["test_method_candidates"]) + self.assertEqual(row["case_specific_test_status"], + "CANDIDATES_REQUIRE_CRITERION_RELEVANCE_REVIEW") + self.assertFalse(any("review_id" in candidate for candidate + in row["test_method_candidates"])) + self.assertTrue(all(c["status"] == + "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + for c in row["test_method_candidates"])) + + def test_missing_or_extra_case_rejected(self): + ids = list(routes.METHOD_ROUTES) + for changed in (ids[:-1], ids + ["X-999"], ids[:-1] + [ids[0]]): + with self.subTest(changed=len(changed)), self.assertRaises(ValueError): + routes.validated_routes(changed) + + def test_wrong_method_and_wrong_role_rejected(self): + ids = list(routes.METHOD_ROUTES) + for fault in ("missing_method", "wrong_role", "duplicate"): + bad = copy.deepcopy(routes.METHOD_ROUTES) + original = bad["X-02"][0] + if fault == "missing_method": + original[1] = "test_aliased_nonexistent_proof" + elif fault == "wrong_role": + original[2] = "APPROVED_BY_CI" + else: + bad["X-02"].append(original[:]) + with self.subTest(fault=fault), self.assertRaises(ValueError): + routes.validated_routes(ids, routes=bad) + + def test_preserved_original_case_wording(self): + source = cases.generate() + old = cases.json.loads((cases.SNAP / "acceptance_decisions.json").read_text()) + for historical, selected in zip(old["cases"], source["cases"]): + self.assertEqual(historical["required_expected_result"], + selected["required_expected_result"]) + self.assertEqual(historical["historical_contract_ids"], + selected["historical_contract_ids"]) + self.assertEqual(historical["proof_declarations"], + selected["historical_proof_declarations"]) + + def test_b02_uses_specific_minimality_negative_not_only_d08(self): + row = next(c for c in cases.generate()["cases"] if c["case_id"] == "B-02") + self.assertTrue(any(c["method"] == "test_10_XOR_destroys_upward_monotonicity" + for c in row["test_method_candidates"])) + self.assertFalse(row["effective_accepted"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_case_receipts.py b/verification/research/test_final_closure_case_receipts.py new file mode 100644 index 00000000..b4436db3 --- /dev/null +++ b/verification/research/test_final_closure_case_receipts.py @@ -0,0 +1,412 @@ +"""Adversarial logic tests; synthetic fixtures are NOT genuine GitHub reviews.""" +import copy +import hashlib +from pathlib import Path +import stat +import tempfile +import unittest +from unittest import mock +import zipfile + +import final_closure_case_receipts as gate +from closure_elaborated_type import elaborated_type_sha256 + +H = "a" * 40 +sha = lambda raw: hashlib.sha256(raw).hexdigest() + + +class DetachedCaseReceiptTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory(prefix="perm-case-") + self.addCleanup(self.tmp.cleanup) + root = Path(self.tmp.name) + self.repo = root / "repo" + self.repo.mkdir() + (self.repo / "Proof.lean").write_text( + "namespace Test.Scope\n" + "theorem genuine : True := by trivial\n" + "end Test.Scope\n") + (self.repo / "test_actual.py").write_text( + "def test_meaningful_mutant():\n assert True\n") + self.logs = { + "axiom.log": ( + b"PERMANSSON_ELABORATED_TYPE_V1_BEGIN\n" + b"NAME=Test.Scope.genuine\nKIND=theorem\nUNIVERSES=[]\n" + b"TYPE=Expr.const `True []\n" + b"PERMANSSON_ELABORATED_TYPE_V1_END\n" + b"Test.Scope.genuine depends on axioms: []\n"), + "positive.log": b"test_meaningful_mutant ... ok\n", + "negative.log": b"Test.Scope.genuine: error: unsolved goals\n", + "mutant.lean": ( + (self.repo / "Proof.lean").read_bytes() + + b"\nnamespace Test.Scope\nexample : False := by exact genuine\nend Test.Scope\n"), + "original.lean": (self.repo / "Proof.lean").read_bytes(), + } + self.bundle = root / "evidence.zip" + self.save() + self.checks = {"research": {"id": 42, "head_sha": H}} + self.proof = { + "id": "p", "head_sha": H, "path": "Proof.lean", + "declaration": "Test.Scope.genuine", + "source_sha256": sha((self.repo / "Proof.lean").read_bytes()), + "statement_sha256": sha(b"theorem genuine : True"), + "elaborated_type_sha256": elaborated_type_sha256({ + "declaration": "Test.Scope.genuine", + "declaration_kind": "theorem", + "universe_params_repr": "[]", + "type_expr_repr": "Expr.const `True []", + }), + "declaration_kind": "theorem", + "universe_params_repr": "[]", + "transitive_axioms": [], "compiler_check": "research", + "compiler_result": "PASS", "check_run_id": 42, + "axiom_log_member": "axiom.log", + "axiom_log_sha256": sha(self.logs["axiom.log"]), + "case_ids": ["C-01"], "claim_ids": ["Q1"], + } + self.test = { + "id": "t", "head_sha": H, "test_path": "test_actual.py", + "test_name": "test_meaningful_mutant", + "source_sha256": sha((self.repo / "test_actual.py").read_bytes()), + "case_ids": ["C-01"], "claim_ids": ["Q1"], "check_run_id": 42, + "positive_control": {"compiled": True, "exit_code": 0, + "lean_toolchain": "4.34.0", "log_member": "positive.log", + "log_sha256": sha(self.logs["positive.log"])}, + "log_member": "positive.log", + "log_sha256": sha(self.logs["positive.log"]), + "outcome": "PASS", "compiler_exit_code": 0, + } + self.negative = dict(self.test, + id="n", log_member="negative.log", + log_sha256=sha(self.logs["negative.log"]), + mutant_source_sha256=sha(self.logs["mutant.lean"]), + mutant_source_member="mutant.lean", + original_lean_source_path="Proof.lean", + original_lean_source_sha256=sha((self.repo/"Proof.lean").read_bytes()), + original_lean_source_member="original.lean", + outcome="REJECTED_SEMANTIC", compiler_exit_code=1) + self.ledger = {"cases": [ + {"case_id": "C-%02d" % i, + "required_expected_result": "Requirement %d" % i} + for i in range(1, 46)]} + self.claims = [{"id": "Q1"}] + [{"id": "Other%d" % i} for i in range(10)] + self.overlay = { + "format": gate.FORMAT, "head_sha": H, + "compiler_axiom_receipts": [self.proof], + "positive_test_receipts": [self.test], + "semantic_negative_receipts": [self.negative], + "case_decisions": [{ + "id": "case", "case_id": "C-01", + "decision": "VERIFIED_SELECTED_SCOPE", + "required_expected_result_sha256": sha(b"Requirement 1"), + "proof_ids": ["p"], "positive_ids": ["t"], + "negative_ids": ["n"], "review_id": 100}], + "claim_decisions": [{ + "id": "claim", "claim_id": "Q1", + "proof_ids": ["p"], "positive_ids": ["t"], + "negative_ids": ["n"], "review_id": 100}], + } + self.scopes = {100: {"cases": {"C-01": "VERIFIED_SELECTED_SCOPE"}, + "claims": ["Q1"]}} + patcher = mock.patch.object(gate, "ROOT", self.repo) + patcher.start() + self.addCleanup(patcher.stop) + + def save(self): + with zipfile.ZipFile(self.bundle, "w") as z: + for name, data in self.logs.items(): + z.writestr(name, data) + self.bundle_sha = sha(self.bundle.read_bytes()) + + def check(self, overlay=None, scopes=None): + members = gate.load_detached_bundle(self.bundle, self.bundle_sha) + return gate.effective_acceptance( + self.overlay if overlay is None else overlay, + head=H, case_ledger=self.ledger, claims=self.claims, + members=members, accepted_checks=self.checks, + approved_review_scopes=self.scopes if scopes is None else scopes) + + def test_claim_only_positive_and_negative_receipts_are_scoped(self): + """A new claim may have no original case, but must retain claim ownership.""" + clean = copy.deepcopy(self.overlay) + clean["case_decisions"] = [] + for name in ("compiler_axiom_receipts", "positive_test_receipts", + "semantic_negative_receipts"): + for row in clean[name]: + row["case_ids"] = [] + scopes = {100: {"cases": {}, "claims": ["Q1"]}} + observed = self.check(overlay=clean, scopes=scopes) + self.assertEqual(observed["accepted_case_count"], 0) + self.assertEqual(observed["accepted_claim_count"], 1) + for section in ("positive_test_receipts", "semantic_negative_receipts"): + invalid = copy.deepcopy(clean) + invalid[section][0]["claim_ids"] = [] + with self.subTest(section=section), self.assertRaises(gate.EvidenceError): + self.check(overlay=invalid, scopes=scopes) + + def test_one_mocked_case_never_promotes_other_forty_four(self): + r = self.check() + self.assertEqual((r["accepted_case_count"], r["pending_case_count"], + r["accepted_claim_count"]), (1, 44, 1)) + + def test_real_lean_quoted_multiline_axiom_target(self): + log = ("info: Provider.lean:11:0: 'Other.noise' depends on axioms: [sorryAx]\n" + "info: Proof.lean:5:0: 'Test.Scope.genuine' depends on axioms: " + "[propext,\n Classical.choice.{u},\n Quot.sound]\n") + self.assertEqual(set(gate.parse_target_axioms( + log, "Test.Scope.genuine")), gate.TRUSTED_AXIOMS) + + def test_empty_target_axioms_and_wrong_target(self): + log = ("'Other.noise' depends on axioms: [sorryAx]\n" + "'Test.Scope.genuine' does not depend on any axioms\n") + self.assertEqual(gate.parse_target_axioms(log, "Test.Scope.genuine"), []) + with self.assertRaises(gate.EvidenceError): + gate.parse_target_axioms(log, "Test.Scope.missing") + + def test_duplicate_or_forbidden_target_axioms_rejected(self): + line = "'Test.Scope.genuine' depends on axioms: [Quot.sound]" + with self.assertRaises(gate.EvidenceError): + gate.parse_target_axioms(line + "\n" + line, "Test.Scope.genuine") + for bad in ("[sorryAx.{u_1}]", "[propext, propext]", + "[Classical.choiceUnsafe.{u}]"): + with self.subTest(bad=bad), self.assertRaises(gate.EvidenceError): + gate.parse_target_axioms( + "'Test.Scope.genuine' depends on axioms: " + bad, + "Test.Scope.genuine") + + def test_v3_proof_and_positive_negative_test_logs_are_true_producer_bytes(self): + """Mocked authenticated source graph; no GitHub or review approval.""" + source = self.logs + record = { + "format": "Permansson.PR113.ProducerBytesRevalidated.v1", + "head_sha": H, + "producers": { + 911: { + "kind": "compiler", "check_run_id": 811, + "head_sha": H, + "members": {"actual/axiom.log": source["axiom.log"]}, + }, + 912: { + "kind": "append", "check_run_id": 812, + "head_sha": H, + "members": { + "actual/positive.log": source["positive.log"], + "actual/negative.log": source["negative.log"], + "actual/mutant.lean": source["mutant.lean"], + "actual/original.lean": source["original.lean"], + }, + }, + }, + } + + def bind(row, artifact_id, check_id, names): + row = copy.deepcopy(row) + row["producer_artifact_id"] = artifact_id + row["check_run_id"] = check_id + row["producer_member_bindings"] = [ + {"outer_member": name, "source_member": "actual/" + name} + for name in names] + return row + + proof = bind(self.proof, 911, 811, ["axiom.log"]) + proof["compiler_check"] = "pr113-compiler-shard" + proof["statement_sha256"] = proof["elaborated_type_sha256"] + positive = bind(self.test, 912, 812, ["positive.log"]) + negative = bind( + self.negative, 912, 812, + ["positive.log", "negative.log", "mutant.lean", "original.lean"]) + members = gate.load_detached_bundle(self.bundle, self.bundle_sha) + self.assertEqual( + gate.validate_compiler_receipt( + proof, H, members, self.checks, producer_record=record), "p") + self.assertEqual( + gate.validate_test_receipt( + positive, H, members, self.checks, + negative=False, producer_record=record), "t") + self.assertEqual( + gate.validate_test_receipt( + negative, H, members, self.checks, + negative=True, producer_record=record), "n") + forged = copy.deepcopy(proof) + forged["statement_sha256"] = "f"*64 + with self.assertRaises(gate.EvidenceError): + gate.validate_compiler_receipt( + forged, H, members, self.checks, producer_record=record) + swapped = copy.deepcopy(negative) + swapped["producer_artifact_id"] = 911 + with self.assertRaises(gate.EvidenceError): + gate.validate_test_receipt( + swapped, H, members, self.checks, + negative=True, producer_record=record) + stolen = copy.deepcopy(negative) + stolen["producer_member_bindings"][0]["source_member"] = "actual/axiom.log" + with self.assertRaises(gate.EvidenceError): + gate.validate_test_receipt( + stolen, H, members, self.checks, + negative=True, producer_record=record) + incomplete = copy.deepcopy(negative) + incomplete["producer_member_bindings"].pop() + with self.assertRaises(gate.EvidenceError): + gate.validate_test_receipt( + incomplete, H, members, self.checks, + negative=True, producer_record=record) + + def test_wrong_detached_digest_rejected(self): + with self.assertRaises(gate.EvidenceError): + gate.load_detached_bundle(self.bundle, "0"*64) + + def test_traversal_duplicate_and_symlink_members_rejected(self): + for unsafe in ("../escape", "/absolute", "a\\wrong"): + with self.subTest(unsafe=unsafe): + with zipfile.ZipFile(self.bundle, "w") as z: + z.writestr(unsafe, b"payload") + with self.assertRaises(gate.EvidenceError): + gate.load_detached_bundle(self.bundle, sha(self.bundle.read_bytes())) + with zipfile.ZipFile(self.bundle, "w") as z: + info = zipfile.ZipInfo("symlink") + info.external_attr = (stat.S_IFLNK | 0o777) << 16 + z.writestr(info, b"target") + with self.assertRaises(gate.EvidenceError): + gate.load_detached_bundle(self.bundle, sha(self.bundle.read_bytes())) + + def test_archive_metadata_bombs_fail_before_extraction(self): + with zipfile.ZipFile(self.bundle, "w") as z: + for i in range(gate.MAX_MEMBERS + 1): + z.writestr(f"items/{i}.log", b"x") + with self.assertRaises(gate.EvidenceError): + gate.load_detached_bundle(self.bundle, sha(self.bundle.read_bytes())) + with zipfile.ZipFile(self.bundle, "w", compression=zipfile.ZIP_DEFLATED) as z: + for i in range(gate.MAX_EXPANDED_BYTES // gate.MAX_MEMBER_BYTES + 1): + z.writestr(f"big/{i}.log", b"z" * gate.MAX_MEMBER_BYTES) + with self.assertRaises(gate.EvidenceError): + gate.load_detached_bundle(self.bundle, sha(self.bundle.read_bytes())) + + def test_archive_ancestor_collision_fails(self): + with zipfile.ZipFile(self.bundle, "w") as z: + z.writestr("a", b"file") + z.writestr("a/b", b"not a directory") + with self.assertRaises(gate.EvidenceError): + gate.load_detached_bundle(self.bundle, sha(self.bundle.read_bytes())) + + def test_wrong_head_source_header_and_namespace_rejected(self): + for key, value in [("head_sha", "b"*40), + ("source_sha256", "0"*64), + ("elaborated_type_sha256", "0"*64), + ("declaration", "Other.Scope.genuine")]: + with self.subTest(key=key): + o = copy.deepcopy(self.overlay) + o["compiler_axiom_receipts"][0][key] = value + with self.assertRaises(gate.EvidenceError): + self.check(o) + + def test_wrong_target_axiom_list_rejected(self): + self.logs["axiom.log"] = ( + b"Test.Scope.genuine : True\n" + b"Other.Scope.genuine depends on axioms: []\n") + self.save() + o = copy.deepcopy(self.overlay) + o["compiler_axiom_receipts"][0]["axiom_log_sha256"] = sha(self.logs["axiom.log"]) + with self.assertRaises(gate.EvidenceError): + self.check(o) + + def test_bad_compiler_diagnostics_not_semantic(self): + warnings = [ + b"error: unknown identifier false\nunsolved goals", + b"error: parser error\nunsolved goals", + b"error: maximum number of heartbeats\nunsolved goals", + b"error: declaration uses " + bytes([96]) + + b"sorry" + bytes([96]) + b"\nunsolved goals", + ] + for log in warnings: + with self.subTest(log=log[:25]): + self.logs["negative.log"] = log + self.save() + o = copy.deepcopy(self.overlay) + o["semantic_negative_receipts"][0]["log_sha256"] = sha(log) + with self.assertRaises(gate.EvidenceError): + self.check(o) + + def test_borrowed_case_and_forged_review_rejected(self): + o = copy.deepcopy(self.overlay) + o["case_decisions"][0]["case_id"] = "C-02" + o["case_decisions"][0]["required_expected_result_sha256"] = sha(b"Requirement 2") + with self.assertRaises(gate.EvidenceError): + self.check(o) + with self.assertRaises(gate.EvidenceError): + self.check(scopes={}) + + def test_mismatched_check_and_failed_positive_control_rejected(self): + for key, value in [("check_run_id", 404), + ("positive_control", {"compiled": False})]: + with self.subTest(key=key): + o = copy.deepcopy(self.overlay) + o["semantic_negative_receipts"][0][key] = value + with self.assertRaises(gate.EvidenceError): + self.check(o) + + def test_claim_cannot_reuse_proof_without_test_controls(self): + for key in ("positive_ids", "negative_ids"): + fake = copy.deepcopy(self.overlay) + fake["claim_decisions"][0][key] = [] + with self.subTest(key=key), self.assertRaises(gate.EvidenceError): + self.check(fake) + forged = copy.deepcopy(self.overlay) + forged["semantic_negative_receipts"][0]["claim_ids"] = ["Q2"] + with self.assertRaises(gate.EvidenceError): + self.check(forged) + + def test_duplicate_evidence_cannot_satisfy_case(self): + o = copy.deepcopy(self.overlay) + o["compiler_axiom_receipts"].append(copy.deepcopy(self.proof)) + with self.assertRaises(gate.EvidenceError): + self.check(o) + + + def test_original_provider_archive_missing_or_tampered_is_rejected(self): + original_log = dict(self.logs) + for attack in ("missing", "mutated", "wrong_member"): + with self.subTest(attack=attack): + self.logs = dict(original_log) + overlay = copy.deepcopy(self.overlay) + if attack == "missing": + self.logs.pop("original.lean") + elif attack == "mutated": + self.logs["original.lean"] = b"altered source bytes" + else: + overlay["semantic_negative_receipts"][0][ + "original_lean_source_member"] = "mutant.lean" + self.save() + with self.assertRaises(gate.EvidenceError): + self.check(overlay) + self.logs = original_log + self.save() + + def test_replacing_original_or_appending_axiom_not_accepted(self): + original_log = dict(self.logs) + for attack in ("replacement", "new_axiom", "new_sorry"): + with self.subTest(attack=attack): + self.logs = dict(original_log) + overlay = copy.deepcopy(self.overlay) + if attack == "replacement": + fake = (b"namespace Test.Scope\n" + b"theorem genuine : False := by trivial\n" + b"end Test.Scope\n") + elif attack == "new_axiom": + fake = self.logs["original.lean"] + ( + b"\naxiom extra : False\n" + b"example : False := extra\n") + else: + fake = self.logs["original.lean"] + ( + b"\nexample : False := by sorry\n") + self.logs["mutant.lean"] = fake + overlay["semantic_negative_receipts"][0][ + "mutant_source_sha256"] = sha(fake) + self.save() + with self.assertRaises(gate.EvidenceError): + self.check(overlay) + self.logs = original_log + self.save() + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_claim_controls_candidate.py b/verification/research/test_final_closure_claim_controls_candidate.py new file mode 100644 index 00000000..3205cbaa --- /dev/null +++ b/verification/research/test_final_closure_claim_controls_candidate.py @@ -0,0 +1,60 @@ +"""Eleven original claim-control selections are not self-awarding proofs.""" +import unittest + +import final_closure_claim_controls_candidate as claim + + +def fixture(): + claims = {} + selected = [] + for index, cid in enumerate(sorted(claim.CLAIMS)): + original = f"PermanssonResearch/Claim_{index}.lean" + test = f"verification/research/test_claim_{index}.py" + claims[cid] = { + "claim_id":cid, + "declarations":[{"path":original, + "declaration":f"Claim_{index}.statement"}], + "test_source_candidates":[test], + } + selected.append({ + "claim_id":cid, "proof_source_path":original, + "positive_test_path":test,"negative_test_path":test, + "positive_test_class":"Controls", + "negative_test_class":"Controls", + "positive_test_method":f"test_positive_{index}", + "negative_test_method":f"test_negative_{index}", + }) + return {"format":claim.SELECTION,"claims":selected},claims + + +class ClaimOnlySelectionTests(unittest.TestCase): + def test_all_eleven_source_owned_claims_are_required(self): + data,scope=fixture() + result=claim.selection_map(data,scope) + self.assertEqual(set(result),set(claim.CLAIMS)) + self.assertEqual(len(result),11) + + def test_missing_or_borrowed_claim_and_source_rejected(self): + changes=[ + lambda r:r["claims"].pop(), + lambda r:r["claims"][0].update(claim_id="FOREIGN"), + lambda r:r["claims"][0].update( + proof_source_path="PermanssonResearch/Unrelated.lean"), + lambda r:r["claims"][0].update( + positive_test_path="verification/research/test_wrong.py"), + lambda r:r["claims"][0].update( + negative_test_method=r["claims"][0]["positive_test_method"]), + lambda r:r["claims"][0].update(approved=True), + ] + for i,mutation in enumerate(changes): + with self.subTest(i=i): + data,scope=fixture() + mutation(data) + with self.assertRaises(claim.ClaimControlError): + claim.selection_map(data,scope) + + def test_one_forged_claim_does_not_replace_all_other_claims(self): + data,scope=fixture() + data["claims"]=[data["claims"][0]]*11 + with self.assertRaises(claim.ClaimControlError): + claim.selection_map(data,scope) diff --git a/verification/research/test_final_closure_compiler_diagnostic_retention.py b/verification/research/test_final_closure_compiler_diagnostic_retention.py new file mode 100644 index 00000000..27070d37 --- /dev/null +++ b/verification/research/test_final_closure_compiler_diagnostic_retention.py @@ -0,0 +1,107 @@ +"""No mocked output below is compiler proof or independent acceptance. + +These tests verify only that real diagnostics cannot disappear merely because +a theorem's elaborated type, target axiom output, or Lean process failed. +The required CI job and complete 45/11 proof graph must still pass independently. +""" +from pathlib import Path +from types import SimpleNamespace +import tempfile +import unittest +from unittest import mock + +import final_closure_complete_lean_evidence as compiler + +H = "a" * 40 + + +class CompilerDiagnosticRetention(unittest.TestCase): + def setUp(self): + self.dir = tempfile.TemporaryDirectory(prefix="pr113-retained-log-") + self.addCleanup(self.dir.cleanup) + self.root = Path(self.dir.name) / "repo" + (self.root / ".lake").mkdir(parents=True) + self.source = self.root / "PermanssonResearch/Demo.lean" + self.source.parent.mkdir() + self.source.write_text( + "namespace Demo\n" + "theorem genuine : True := by trivial\n" + "end Demo\n", encoding="utf-8") + self.out = Path(self.dir.name) / "detached" + # Private diagnostics runner accepts a focused fixture; production does not. + self.inventory = [ + {"path": "PermanssonResearch/Demo.lean", + "declaration": "Demo.genuine", + "case_ids": ["X-01"], "claim_ids": []} + for _ in range(12) + ] + + def run_with(self, result): + with mock.patch.object(compiler, "ROOT", self.root), \ + mock.patch.object(compiler, "git_head", return_value=H), \ + mock.patch.object(compiler, "required_inventory", + return_value=self.inventory), \ + mock.patch.object(compiler, "committed_file_bytes", + side_effect=lambda *_args, **_kw: + self.source.read_bytes()), \ + mock.patch.object(compiler, "run_probe", return_value=result): + return compiler._export_checked_shard( + H, self.out, self.inventory, shard_index=0, + shard_count=1) + + def test_unparseable_type_keeps_verbatim_diagnostic_without_pass_receipt(self): + result = SimpleNamespace(returncode=0, + stdout="info: actual malformed type record", + stderr="Lean emitted unrelated diagnostic") + with self.assertRaisesRegex(compiler.CompleteCompilerError, + "unverifiable compiler declaration"): + self.run_with(result) + raw = (self.out / "axiom_logs/all_0000.log").read_bytes() + self.assertEqual(raw, ( + "info: actual malformed type record\n" + "Lean emitted unrelated diagnostic").encode("utf-8")) + self.assertFalse((self.out / "complete_compiler_candidate.json").exists()) + + def test_nonzero_compiler_rejection_keeps_log_but_never_passes(self): + result = SimpleNamespace( + returncode=1, stdout="", + stderr="error: type mismatch in original source") + with self.assertRaisesRegex(compiler.CompleteCompilerError, + "compiler rejected mandatory source theorem"): + self.run_with(result) + self.assertEqual( + (self.out / "axiom_logs/all_0000.log").read_bytes(), + b"\nerror: type mismatch in original source") + self.assertFalse((self.out / "complete_compiler_candidate.json").exists()) + + def test_no_accepted_output_for_oversized_compiler_diagnostic(self): + result = SimpleNamespace(returncode=1, + stdout="x" * (4 * 1024 * 1024), + stderr="error: oversized") + with self.assertRaisesRegex(compiler.CompleteCompilerError, + "compiler theorem log missing or too large"): + self.run_with(result) + self.assertFalse((self.out / "axiom_logs/all_0000.log").exists()) + self.assertFalse((self.out / "complete_compiler_candidate.json").exists()) + + + def test_production_export_does_not_accept_focused_diagnostic_subset(self): + # The public exporter now authenticates H and source-ledger Git + # objects before checking the 259-target size. Supply an explicit + # synthetic clean-Git boundary so this unit test exercises its + # intended insufficient-inventory rejection, not the unrelated + # foreign-HEAD guard. + with mock.patch.object(compiler, "git_head", return_value=H), \ + mock.patch.object(compiler, "committed_file_bytes", + return_value=b"synthetic exact-H blob"), \ + mock.patch.object(compiler, "required_inventory", + return_value=self.inventory): + with self.assertRaisesRegex(compiler.CompleteCompilerError, + "259 original proof targets"): + compiler.export_all(H, self.out) + self.assertFalse(self.out.exists()) + + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_compiler_shards.py b/verification/research/test_final_closure_compiler_shards.py new file mode 100644 index 00000000..632a58a0 --- /dev/null +++ b/verification/research/test_final_closure_compiler_shards.py @@ -0,0 +1,197 @@ +"""Adversarial all45/all11 compiler-shard merging; fixtures are UNREVIEWED. + +The mock compiler output is never represented as a real Lean execution or +a real authenticated Github check. This suite validates source binding, +coverage, decomposition, and refusal of missing/duplicate/altered shards. +""" +from __future__ import annotations + +import copy +import hashlib +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +import final_closure_complete_lean_evidence as exporter +import final_closure_merge_compiler_shards as merger +from closure_elaborated_type import ( + BEGIN, END, elaborated_type_sha256 +) + +H = "a" * 40 +SHA = lambda b: hashlib.sha256(b).hexdigest() + + +class CompleteCompilerShardTests(unittest.TestCase): + def setUp(self): + td = tempfile.TemporaryDirectory(prefix="perm-shard-unit-") + self.addCleanup(td.cleanup) + self.root = Path(td.name) + self.repo = self.root / "checkout" + self.repo.mkdir() + self.shards = self.root / "shards" + self.shards.mkdir() + self.output = self.root / "combined" + self.source = self.repo / "PermanssonResearch" / "Proof.lean" + self.source.parent.mkdir(parents=True) + self.source.write_text( + "namespace Test\n" + + "\n".join(f"theorem owner_{i} : True := by trivial" + for i in range(13)) + + "\nend Test\n") + self.owners = [ + {"path": "PermanssonResearch/Proof.lean", + "declaration": f"Test.owner_{i}", + "case_ids": [f"C-{i:02}"], + "claim_ids": ["Q1"] if i == 0 else []} + for i in range(13) + ] + self.source_hash = SHA(self.source.read_bytes()) + self.digest = exporter.inventory_digest(self.owners) + patcher1 = mock.patch.object(merger, "ROOT", self.repo) + patcher2 = mock.patch.object(merger, "git_head", return_value=H) + patcher1.start(); patcher2.start() + self.addCleanup(patcher1.stop) + self.addCleanup(patcher2.stop) + self.write_shards(4) + + def row(self, idx): + declaration = self.owners[idx]["declaration"] + log = ( + f"{BEGIN}\nNAME={declaration}\nKIND=theorem\n" + f"UNIVERSES=[]\nTYPE=Expr.const \u0060True []\n{END}\n" + f"{declaration} depends on axioms: []\n" + ).encode() + record = { + "declaration": declaration, + "declaration_kind": "theorem", + "universe_params_repr": "[]", + "type_expr_repr": "Expr.const \u0060True []", + } + candidate = { + "id": f"all:{idx:04}", "inventory_index": idx, + "head_sha": H, "path": self.owners[idx]["path"], + "declaration": declaration, + "case_ids": self.owners[idx]["case_ids"], + "claim_ids": self.owners[idx]["claim_ids"], + "source_sha256": self.source_hash, + "elaborated_type_sha256": elaborated_type_sha256(record), + "declaration_kind": "theorem", + "universe_params_repr": "[]", + "transitive_axioms": [], + "compiler_result": "PASS", + "compiler_check": "research", + "axiom_log_member": f"axiom_logs/all_{idx:04}.log", + "axiom_log_sha256": SHA(log), + "check_run_id": None, + } + return candidate, log + + def write_shards(self, count): + for folder in self.shards.iterdir(): + if folder.is_dir(): + import shutil + shutil.rmtree(folder) + for i in range(count): + dest = self.shards / f"shard-{i:02}" + (dest/"axiom_logs").mkdir(parents=True) + candidates = [] + for j in exporter.shard_indices(len(self.owners), i, count): + row, log = self.row(j) + candidates.append(row) + (dest / row["axiom_log_member"]).write_bytes(log) + manifest = { + "format": "Permansson.AllCaseCompilerCandidate.v1", + "status": "COMPILER_ONLY_UNREVIEWED", + "head_sha": H, + "proof_count": len(candidates), + "inventory_count": len(self.owners), + "inventory_sha256": self.digest, + "shard_index": i, + "shard_count": count, + "case_count": 45, "claim_count": 11, + "compiler_axiom_receipts": candidates, + "independent_review": False, + "authentic_ci_job_bound": False, + "merged_main_sha": None, + } + (dest/"complete_compiler_candidate.json").write_text( + json.dumps(manifest)) + + def run_merge(self): + return merger.merge(self.shards, self.output, head=H, + inventory=self.owners) + + def test_shards_partition_all_original_proofs_without_overlap(self): + expected = set(range(len(self.owners))) + for count in (1, 2, 3, 4, 7, 13): + parts = [set(exporter.shard_indices(len(self.owners), i, count)) + for i in range(count)] + self.assertEqual(set.union(*parts), expected) + self.assertEqual(sum(len(p) for p in parts), len(expected)) + for idx, count in ((-1, 4), (4, 4), (0, 0), (0, 17), (0, 14)): + with self.subTest(idx=idx, count=count), self.assertRaises( + exporter.CompleteCompilerError): + exporter.shard_indices(len(self.owners), idx, count) + + def test_complete_mock_merge_keeps_unreviewed_zero_approval(self): + outcome = self.run_merge() + self.assertEqual(outcome["proof_count"], len(self.owners)) + self.assertEqual(outcome["merged_shards"], 4) + self.assertIsNone(outcome["check_run_id"]) + self.assertFalse(outcome["independent_review"]) + self.assertFalse(outcome["authentic_ci_job_bound"]) + self.assertEqual(outcome["status"], "COMPILER_ONLY_UNREVIEWED") + self.assertTrue((self.output/"axiom_logs/all_0012.log").is_file()) + + def test_missing_shard_never_looks_like_a_complete_release(self): + import shutil + shutil.rmtree(self.shards/"shard-02") + with self.assertRaises(merger.ShardMergeError): + self.run_merge() + + def test_swapped_or_duplicate_owner_never_fills_a_gap(self): + folder = self.shards/"shard-01" + manifest = folder/"complete_compiler_candidate.json" + obj = json.loads(manifest.read_text()) + obj["compiler_axiom_receipts"][0]["inventory_index"] = 0 + manifest.write_text(json.dumps(obj)) + with self.assertRaises(merger.ShardMergeError): + self.run_merge() + + def test_one_modified_theorem_body_invalidates_compiler_log_source(self): + self.source.write_text(self.source.read_text() + "\n-- changed source\n") + with self.assertRaises(merger.ShardMergeError): + self.run_merge() + + def test_fake_axiom_type_hash_or_log_is_rejected(self): + folder = self.shards/"shard-00" + manifest = folder/"complete_compiler_candidate.json" + obj = json.loads(manifest.read_text()) + obj["compiler_axiom_receipts"][0]["elaborated_type_sha256"] = "0"*64 + manifest.write_text(json.dumps(obj)) + with self.assertRaises(merger.ShardMergeError): + self.run_merge() + self.write_shards(4) + (folder/"axiom_logs/all_0000.log").write_text( + "Test.owner_0 depends on axioms: []") + with self.assertRaises(merger.ShardMergeError): + self.run_merge() + + def test_unexpected_member_or_wrong_full_inventory_hash_rejected(self): + (self.shards/"shard-02"/"unrelated.txt").write_text("injected") + with self.assertRaises(merger.ShardMergeError): + self.run_merge() + (self.shards/"shard-02"/"unrelated.txt").unlink() + objpath = self.shards/"shard-02"/"complete_compiler_candidate.json" + obj = json.loads(objpath.read_text()) + obj["inventory_sha256"] = "0"*64 + objpath.write_text(json.dumps(obj)) + with self.assertRaises(merger.ShardMergeError): + self.run_merge() + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_complete_dag.py b/verification/research/test_final_closure_complete_dag.py new file mode 100644 index 00000000..cae55c45 --- /dev/null +++ b/verification/research/test_final_closure_complete_dag.py @@ -0,0 +1,177 @@ +"""Adversarial exact 45/11 receipt DAG tests. Mock graph is NOT acceptance.""" +import copy +import unittest + +import final_closure_full_contract as full +from final_closure_case_receipts import EvidenceError + + +def mocked_graph(): + ids = [f"C-{n:02d}" for n in range(1, 46)] + cases = [] + compiler = [] + positives, negatives = [], [] + decisions = [] + path = "verification/research/test_required.py" + for idx, cid in enumerate(ids): + p = f"proof-{cid}" + d = f"Node.valid{idx}" + pp = f"Proofs/{idx}.lean" + cases.append({ + "case_id": cid, + "historical_proof_declarations": [{"path": pp, "name": f"valid{idx}"}], + "test_method_candidates": [ + {"path": path, "method": "test_positive", "role": "EXACT_CHECK"}, + {"path": path, "method": "test_false", "role": "SEMANTIC_NEGATIVE"}, + ], + "new_claim_source_candidates": [], + }) + compiler.append({"id": p, "path": pp, "declaration": d, + "case_ids": [cid], "claim_ids": []}) + positives.append({"id": f"positive-{cid}", "test_path": path, + "test_name": "test_positive", "case_ids": [cid]}) + negatives.append({"id": f"negative-{cid}", "test_path": path, + "test_name": "test_false", "case_ids": [cid]}) + decisions.append({"case_id": cid, "proof_ids": [p], + "positive_ids": [f"positive-{cid}"], + "negative_ids": [f"negative-{cid}"]}) + claims, claim_decisions = [], [] + for cid in sorted(full.ORIGINAL_CLAIMS): + decls, refs = [], [] + for k in range(2 if cid == "Q1" else 1): + proofid = f"{cid}:{k}" + decl = f"ClaimScope.{cid}.support{k}" + source = f"Claims/{cid}-{k}.lean" + decls.append({"path": source, "declaration": decl}) + compiler.append({"id": proofid, "path": source, + "declaration": decl, "claim_ids": [cid], + "case_ids": []}) + refs.append(proofid) + claims.append({"claim_id": cid, "declarations": decls, + "test_source_candidates": [path]}) + positives.append({"id": "claim-positive-" + cid, "test_path": path, + "test_name": "test_positive", + "case_ids": ["C-01"], "claim_ids": [cid]}) + negatives.append({"id": "claim-negative-" + cid, "test_path": path, + "test_name": "test_false", + "case_ids": ["C-01"], "claim_ids": [cid]}) + claim_decisions.append({"claim_id": cid, "proof_ids": refs, + "positive_ids": ["claim-positive-" + cid], + "negative_ids": ["claim-negative-" + cid]}) + overlay = { + "compiler_axiom_receipts": compiler, + "positive_test_receipts": positives, + "semantic_negative_receipts": negatives, + "case_decisions": decisions, + "claim_decisions": claim_decisions, + } + return overlay, {"cases": cases}, {"claims": claims} + + +class CompleteObligationReceiptTests(unittest.TestCase): + def test_mock_complete_dag_is_only_structurally_covered(self): + a, b, c = mocked_graph() + result = full.require_complete_obligation_receipts(a, b, c) + self.assertEqual(result["cases_covered"], 45) + self.assertEqual(result["claims_covered"], 11) + self.assertTrue(result["review_and_ci_still_required"]) + + def test_one_of_two_required_claim_proofs_cannot_suffice(self): + a, b, c = mocked_graph() + row = next(r for r in a["claim_decisions"] if r["claim_id"] == "Q1") + row["proof_ids"].pop() + with self.assertRaisesRegex(EvidenceError, "omits supporting"): + full.require_complete_obligation_receipts(a, b, c) + + def test_unrelated_proof_with_valid_case_label_does_not_cover_contract(self): + a, b, c = mocked_graph() + proof = next(p for p in a["compiler_axiom_receipts"] + if p["id"] == "proof-C-12") + proof["path"] = "Other/Unrelated.lean" + with self.assertRaisesRegex(EvidenceError, "missing original"): + full.require_complete_obligation_receipts(a, b, c) + + def test_source_right_but_declaration_wrong_rejected(self): + a, b, c = mocked_graph() + proof = next(p for p in a["compiler_axiom_receipts"] + if p["id"] == "proof-C-07") + proof["declaration"] = "Node.validDifferent" + with self.assertRaises(EvidenceError): + full.require_complete_obligation_receipts(a, b, c) + + def test_missing_wrong_role_and_forged_nonselected_method_rejected(self): + for mode in ("wrongrole", "unselected", "borrowed"): + a, b, c = mocked_graph() + negative = next(n for n in a["semantic_negative_receipts"] + if n["id"] == "negative-C-05") + if mode == "wrongrole": + b["cases"][4]["test_method_candidates"][1]["role"] = "EXACT_CHECK" + elif mode == "unselected": + negative["test_name"] = "test_unrelated" + else: + negative["case_ids"] = ["C-06"] + with self.subTest(mode=mode), self.assertRaises(EvidenceError): + full.require_complete_obligation_receipts(a, b, c) + + def test_scope_boundary_does_not_substitute_for_compiler_rejected_mutant(self): + a, b, c = mocked_graph() + # A passing boundary observation is not evidence that a semantically + # false Lean proposition failed to elaborate under the original types. + b["cases"][3]["test_method_candidates"][1]["role"] = "SCOPE_BOUNDARY" + with self.assertRaisesRegex(EvidenceError, "semantic negative route"): + full.require_complete_obligation_receipts(a, b, c) + + def test_claim_controls_and_ownership_are_mandatory(self): + for mode in ("missing_positive", "missing_negative", + "wrong_owner", "unselected", "borrowed"): + overlay, cases, claims = mocked_graph() + row = next(x for x in overlay["claim_decisions"] if x["claim_id"] == "D9") + mutant = next(x for x in overlay["semantic_negative_receipts"] + if x["id"] == "claim-negative-D9") + if mode == "missing_positive": + row["positive_ids"].clear() + elif mode == "missing_negative": + row["negative_ids"].clear() + elif mode == "wrong_owner": + mutant["claim_ids"] = ["Q1"] + elif mode == "unselected": + mutant["test_path"] = "verification/research/test_unrelated.py" + else: + row["negative_ids"] = ["claim-negative-Q1"] + with self.subTest(mode=mode), self.assertRaises(EvidenceError): + full.require_complete_obligation_receipts(overlay, cases, claims) + + def test_claim_cannot_borrow_one_proof_from_another_owner(self): + a, b, c = mocked_graph() + row = next(r for r in a["claim_decisions"] if r["claim_id"] == "M1") + other = next(p for p in a["compiler_axiom_receipts"] + if p["id"] == "Q1:0") + other["claim_ids"].append("M1") + row["proof_ids"] = ["Q1:0"] + with self.assertRaisesRegex(EvidenceError, "omits supporting"): + full.require_complete_obligation_receipts(a, b, c) + + def test_unlisted_or_duplicate_case_or_claim_never_conjoins(self): + for what in ("case", "claim", "duplicate"): + a, b, c = mocked_graph() + if what == "case": + a["case_decisions"].pop() + elif what == "claim": + a["claim_decisions"].pop() + else: + a["compiler_axiom_receipts"].append( + copy.deepcopy(a["compiler_axiom_receipts"][0])) + with self.subTest(what=what), self.assertRaises(EvidenceError): + full.require_complete_obligation_receipts(a, b, c) + + def test_case_link_to_new_claim_source_is_required(self): + a, b, c = mocked_graph() + b["cases"][0]["new_claim_source_candidates"] = [ + {"id": "Q1", "path": "Claims/Q1-0.lean"} + ] + with self.assertRaises(EvidenceError): + full.require_complete_obligation_receipts(a, b, c) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_complete_lean_evidence.py b/verification/research/test_final_closure_complete_lean_evidence.py new file mode 100644 index 00000000..697d9550 --- /dev/null +++ b/verification/research/test_final_closure_complete_lean_evidence.py @@ -0,0 +1,287 @@ +"""All-case Lean theorem plan tests; no mock is a genuine compiler record.""" +import copy +from pathlib import Path +import tempfile +import unittest + +import final_closure_complete_lean_evidence as combined + +CLAIMS = ("Q1", "Q2", "Q3", "M1", "M2", "M3", "N1", "N2", + "N3", "D8", "D9") +P = "PermanssonResearch/Demo.lean" + + +def synthetic_source(root): + repo = Path(root) + (repo / "PermanssonResearch").mkdir() + lines = ["namespace Demo"] + lines += [f"theorem historical_{n} : True := by trivial" for n in range(45)] + lines += [f"theorem claim_{k} : True := by trivial" for k in CLAIMS] + lines += ["end Demo"] + (repo / P).write_text("\n".join(lines), encoding="utf-8") + cases = {"cases": [ + {"case_id": f"C-{i:02d}", + "historical_proof_declarations": [ + {"path": P, "name": f"historical_{i-1}"}]} + for i in range(1, 46) + ]} + claims = {"claims": [ + {"claim_id": cid, "declarations": [ + {"path": P, "declaration": f"Demo.claim_{cid}"}]} + for cid in CLAIMS + ]} + return cases, claims + + +class AllCaseCompilerManifestTests(unittest.TestCase): + def test_all_45_and_all_11_exact_qualified_names(self): + with tempfile.TemporaryDirectory() as td: + cases, claims = synthetic_source(td) + got = combined.required_inventory( + root=Path(td), case_map=cases, claim_map=claims) + self.assertEqual(len(got), 56) + self.assertEqual(len({c for row in got for c in row["case_ids"]}), 45) + self.assertEqual({c for row in got for c in row["claim_ids"]}, + set(CLAIMS)) + self.assertIn(("Demo.historical_0", "C-01"), + {(r["declaration"], c) + for r in got for c in r["case_ids"]}) + self.assertTrue(all(r["path"] == P and + r["declaration"].startswith("Demo.") + for r in got)) + + def test_renamed_or_missing_historical_theorem_blocks_manifest(self): + with tempfile.TemporaryDirectory() as td: + cases, claims = synthetic_source(td) + cases["cases"][8]["historical_proof_declarations"][0]["name"] = "invented" + with self.assertRaises(combined.CompleteCompilerError): + combined.required_inventory( + root=Path(td), case_map=cases, claim_map=claims) + + def test_duplicated_historical_ownership_case_is_blocked(self): + with tempfile.TemporaryDirectory() as td: + cases, claims = synthetic_source(td) + cases["cases"][1]["case_id"] = "C-01" + with self.assertRaises(combined.CompleteCompilerError): + combined.required_inventory( + root=Path(td), case_map=cases, claim_map=claims) + + def test_claim_missing_one_of_exact_eleven_is_blocked(self): + with tempfile.TemporaryDirectory() as td: + cases, claims = synthetic_source(td) + claims["claims"].pop() + with self.assertRaises(combined.CompleteCompilerError): + combined.required_inventory( + root=Path(td), case_map=cases, claim_map=claims) + + def test_deduplication_retains_all_case_and_claim_owners(self): + with tempfile.TemporaryDirectory() as td: + cases, claims = synthetic_source(td) + cases["cases"][1]["historical_proof_declarations"][0]["name"] = "historical_0" + claims["claims"][0]["declarations"][0]["declaration"] = "Demo.historical_0" + got = combined.required_inventory( + root=Path(td), case_map=cases, claim_map=claims) + own = next(x for x in got if x["declaration"] == "Demo.historical_0") + self.assertEqual(own["case_ids"], ["C-01", "C-02"]) + self.assertEqual(own["claim_ids"], ["Q1"]) + self.assertEqual(len(got), 54) + + def test_unrelated_private_lemmas_and_anonymous_instances_are_ignored(self): + with tempfile.TemporaryDirectory() as td: + cases, claims = synthetic_source(td) + original = (Path(td)/P).read_text(encoding="utf-8") + (Path(td)/P).write_text( + "namespace NonPublic\n" + "private theorem hidden : True := by trivial\n" + "instance : Inhabited Unit := ⟨()⟩\n" + "/- namespace Spoof\n" + "theorem historical_0 : False := by contradiction\n" + "-/\n" + "-- theorem historical_0 : False := by contradiction\n" + "end NonPublic\n" + original) + result = combined.required_inventory( + root=Path(td), case_map=cases, claim_map=claims) + self.assertEqual(len(result), 56) + self.assertEqual({r["declaration"] for r in result + if "C-01" in r["case_ids"]}, + {"Demo.historical_0"}) + + def test_dotted_public_declarations_keep_distinct_qualified_owners(self): + source = ( + "namespace Demo\n" + "structure Bounds where\n" + " coefficient : Nat\n" + "def Bounds.factor (x : Bounds) : Nat := x.coefficient\n" + "theorem Bounds.factor_nonneg (x : Bounds) : 0 ≤ x.factor := by\n" + " exact Nat.zero_le _\n" + "end Demo\n" + ) + names = combined.addressable_declarations(source) + self.assertEqual(names, [ + "Demo.Bounds", "Demo.Bounds.factor", + "Demo.Bounds.factor_nonneg", + ]) + with tempfile.TemporaryDirectory() as td: + root = Path(td) + (root / "PermanssonResearch").mkdir() + (root / P).write_text(source) + resolved = combined.resolved_name(root, P, "Bounds.factor_nonneg", {}) + self.assertEqual(resolved, "Demo.Bounds.factor_nonneg") + # A genuine repeated fully qualified name must still be blocked. + repeated = source.replace( + "end Demo\n", + "theorem Bounds.factor_nonneg : True := by trivial\nend Demo\n", + ) + with self.assertRaises(combined.CompleteCompilerError): + combined.addressable_declarations(repeated) + + def test_attribute_prefixed_public_theorems_remain_addressable(self): + original = ( + "namespace Demo\n" + "@[simp] theorem typedRow_measure : True := by trivial\n" + "@[simp] protected theorem factor_positive : True := by trivial\n" + "-- @[simp] theorem spoof : False := by contradiction\n" + "end Demo\n" + ) + found = combined.addressable_declarations(original) + self.assertEqual(found, [ + "Demo.typedRow_measure", "Demo.factor_positive", + ]) + + def test_targeting_private_lemma_cannot_resolve(self): + with tempfile.TemporaryDirectory() as td: + cases, claims = synthetic_source(td) + source = Path(td)/P + source.write_text( + "namespace Demo\n" + "private theorem secret : True := by trivial\n" + "end Demo\n" + source.read_text(encoding="utf-8")) + cases["cases"][0]["historical_proof_declarations"][0]["name"] = "secret" + with self.assertRaisesRegex(combined.CompleteCompilerError, + "unresolved"): + combined.required_inventory( + root=Path(td), case_map=cases, claim_map=claims) + + def test_nested_comments_cannot_supply_fake_declaration(self): + masked = ( + "namespace Outer\n" + "/- test\n" + "/- namespace Fake\n" + "theorem fabricated : True := by trivial -/\n" + "still hidden -/\n" + "theorem real : True := by trivial\n" + "end Outer\n" + ) + self.assertEqual(combined.addressable_declarations(masked), ["Outer.real"]) + with self.assertRaisesRegex(combined.CompleteCompilerError, "unterminated"): + combined.addressable_declarations("namespace Foo\n/- never ends") + + def test_namespace_ambiguity_never_assumes_theorem(self): + with tempfile.TemporaryDirectory() as td: + cases, claims = synthetic_source(td) + with (Path(td)/P).open("a") as f: + f.write("\nnamespace Other\n" + "theorem historical_0 : True := by trivial\n" + "end Other\n") + with self.assertRaises(combined.CompleteCompilerError): + combined.required_inventory( + root=Path(td), case_map=cases, claim_map=claims) + + def test_path_traversal_never_reads_untracked_external_source(self): + with tempfile.TemporaryDirectory() as td: + cases, claims = synthetic_source(td) + cases["cases"][0]["historical_proof_declarations"][0]["path"] = "../Fake.lean" + with self.assertRaises(combined.CompleteCompilerError): + combined.required_inventory( + root=Path(td), case_map=cases, claim_map=claims) + + def test_inventory_only_fails_closed_on_stale_head_or_dirty_ledger(self): + import unittest.mock as mock + H = "a" * 40 + claims = tuple(CLAIMS) + fabricated = [{ + "path": "PermanssonResearch/Demo.lean", + "declaration": "Demo.public_" + str(i), + "case_ids": [sorted(combined.FROZEN_ORIGINAL_CASE_IDS)[i % 45]], + "claim_ids": [claims[i % len(claims)]], + } for i in range(259)] + import hashlib + digest = hashlib.sha256(b"original Git object").digest() + expected_file_count = len({ + p for p in ["PermanssonResearch/Demo.lean"] + }) + with mock.patch.object(combined, "git_head", return_value="b" * 40), \ + mock.patch.object(combined, "committed_file_bytes") as check_blob: + with self.assertRaises(combined.CompleteCompilerError): + combined.source_bound_full_inventory(H) + check_blob.assert_not_called() + source_ledger = { + str(combined.SNAP / "acceptance_case_source_map.json"): b"case map", + str(combined.SNAP / "claim_declaration_map.json"): b"claim map", + } + calls = [] + def genuine_blob(_head, path, *, root): + self.assertEqual(root, combined.ROOT) + calls.append(str(path)) + if str(path) == str(combined.ROOT / "PermanssonResearch/Demo.lean"): + return b"true Lean bytes" + return source_ledger[str(path)] + with mock.patch.object(combined, "git_head", return_value=H), \ + mock.patch.object(combined, "required_inventory", + return_value=fabricated), \ + mock.patch.object(combined, "committed_file_bytes", + side_effect=genuine_blob): + actual = combined.source_bound_full_inventory(H) + self.assertEqual(len(actual), 259) + self.assertEqual(len(calls), 5) + self.assertEqual(calls.count(str(combined.ROOT / + "PermanssonResearch/Demo.lean")), 1) + source_ledger[str(combined.SNAP / + "claim_declaration_map.json")] = b"mutated" + with mock.patch.object(combined, "git_head", return_value=H), \ + mock.patch.object(combined, "required_inventory", + return_value=fabricated), \ + mock.patch.object(combined, "committed_file_bytes", + side_effect=genuine_blob) as checker: + # Mutate the claim ledger after its initial Git-object read. + initial = source_ledger[str(combined.SNAP / + "claim_declaration_map.json")] + def changed(_head, path, *, root): + if (str(path).endswith("claim_declaration_map.json") + and changed.calls >= 1): + return b"changed while generating" + if str(path).endswith("claim_declaration_map.json"): + changed.calls += 1 + return genuine_blob(_head, path, root=root) + changed.calls = 0 + checker.side_effect = changed + with self.assertRaises(combined.CompleteCompilerError): + combined.source_bound_full_inventory(H) + + + def test_original_45_case_names_cannot_be_substituted_at_equal_count(self): + from unittest import mock + claim_ids = sorted(combined.FROZEN_CLAIM_IDS) + cases = sorted(combined.FROZEN_ORIGINAL_CASE_IDS) + records = [ + {"path": "PermanssonResearch/Proof.lean", + "declaration": "SomeModule.thm_" + str(i), + "case_ids": [cases[i % len(cases)]], + "claim_ids": [claim_ids[i % len(claim_ids)]]} + for i in range(259)] + combined.validate_full_release_inventory(records) + changed = [dict(row) for row in records] + original = cases[0] + changed = [ + {**row, "case_ids": ["FAKE-CASE-00"] + if original in row["case_ids"] else row["case_ids"]} + for row in changed + ] + self.assertEqual(len({c for r in changed for c in r["case_ids"]}), 45) + with self.assertRaises(combined.CompleteCompilerError): + combined.validate_full_release_inventory(changed) + + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_execution_stamp.py b/verification/research/test_final_closure_execution_stamp.py new file mode 100644 index 00000000..c001fb69 --- /dev/null +++ b/verification/research/test_final_closure_execution_stamp.py @@ -0,0 +1,131 @@ +"""Mocked GitHub runner context is never an authentic CI attestation.""" +import copy +import hashlib +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +import final_closure_execution_stamp as stamp + +H = "a" * 40 +T = "b" * 40 +CLAIMS = ("Q1", "Q2", "Q3", "M1", "M2", "M3", "N1", "N2", + "N3", "D8", "D9") +sha = lambda b: hashlib.sha256(b).hexdigest() + + +class ExecutedCandidateStampTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory(prefix="perm-run-stamp-") + self.addCleanup(self.tmp.cleanup) + self.out = Path(self.tmp.name) + self.env = { + "GITHUB_ACTIONS": "true", + "GITHUB_REPOSITORY": "HMarcusWH/PermanssonLean", + "GITHUB_EVENT_NAME": "push", + "GITHUB_SHA": H, + "GITHUB_RUN_ID": "123456", + "GITHUB_RUN_ATTEMPT": "2", + "GITHUB_JOB": "compiler-candidate", + "GITHUB_WORKFLOW": "PR113 Compiler Evidence Candidate", + } + rows = [] + (self.out/"axiom_logs").mkdir() + for cid in CLAIMS: + data = ("actual genuine output expected for " + cid).encode() + name = "axiom_logs/" + cid + ".log" + (self.out/name).write_bytes(data) + rows.append({ + "id": cid, "claim_ids": [cid], + "head_sha": H, + "compiler_result": "PASS", + "check_run_id": None, + "CI_binding": "PENDING_REAL_CHECK_RUN_ID_ON_FROZEN_H", + "axiom_log_member": name, + "axiom_log_sha256": sha(data), + }) + self.json = { + "status": "COMPILER_ONLY_UNREVIEWED", "head_sha": H, + "claim_count": 11, "proof_count": 11, + "compiler_axiom_receipts": rows, + } + self.save() + patcher = mock.patch.object(stamp, "source_git", side_effect=self.git) + patcher.start() + self.addCleanup(patcher.stop) + + def git(self, *cmd): + if cmd == ("rev-parse", "HEAD"): return H + if cmd == ("rev-parse", "HEAD^{tree}"): return T + raise AssertionError(cmd) + + def save(self): + (self.out/"compiler_candidate.json").write_text(json.dumps(self.json)) + + def check(self, env=None): + return stamp.attestation(self.out, head=H, env=self.env if env is None else env) + + def test_mock_execution_is_unreviewed_even_with_all_claims(self): + record = self.check() + self.assertEqual(record["proof_count"], 11) + self.assertEqual(record["source_tree"], T) + self.assertEqual(record["github_run_attempt"], 2) + self.assertIsNone(record["check_run_id"]) + self.assertFalse(record["independent_review"]) + self.assertFalse(record["full_mathematical_acceptance"]) + + def test_forged_repository_synthetic_sha_and_job_rejected(self): + for key, val in ( + ("GITHUB_REPOSITORY", "evil/repo"), + ("GITHUB_SHA", "c"*40), + ("GITHUB_JOB", "research"), + ("GITHUB_RUN_ID", "0"), + ("GITHUB_RUN_ATTEMPT", "no"), + ("GITHUB_EVENT_NAME", "pull_request"), + ("GITHUB_WORKFLOW", "Confusing Similar Name"), + ): + env = dict(self.env, **{key: val}) + with self.subTest(key=key), self.assertRaises(stamp.StampingError): + self.check(env) + + def test_proof_log_corruption_or_absence_blocks_stamp(self): + name = "axiom_logs/Q1.log" + (self.out/name).write_bytes(b"mismatched") + with self.assertRaises(stamp.StampingError): + self.check() + (self.out/name).unlink() + with self.assertRaises(stamp.StampingError): + self.check() + + def test_claim_subset_or_inherited_checkrun_fails(self): + self.json["compiler_axiom_receipts"].pop() + self.json["proof_count"] = 10 + self.save() + with self.assertRaises(stamp.StampingError): + self.check() + self.json["compiler_axiom_receipts"].append( + dict(self.json["compiler_axiom_receipts"][0], + id="Extra", claim_ids=["D9"])) + self.json["proof_count"] = 11 + self.json["compiler_axiom_receipts"][0]["check_run_id"] = 42 + self.save() + with self.assertRaises(stamp.StampingError): + self.check() + + def test_source_path_traversal_and_duplicate_id_rejected(self): + first = self.json["compiler_axiom_receipts"][0] + first["axiom_log_member"] = "../outside.log" + self.save() + with self.assertRaises(stamp.StampingError): + self.check() + first["axiom_log_member"] = "axiom_logs/Q1.log" + self.json["compiler_axiom_receipts"][1]["id"] = first["id"] + self.save() + with self.assertRaises(stamp.StampingError): + self.check() + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_existing_false_semantics_lean.py b/verification/research/test_final_closure_existing_false_semantics_lean.py new file mode 100644 index 00000000..a60c4b4a --- /dev/null +++ b/verification/research/test_final_closure_existing_false_semantics_lean.py @@ -0,0 +1,93 @@ +"""Original-source semantic rejection tests: X-06 and Appendix E. + +These are actual false propositions of already-proved Lean non-implication +theorems. They must fail with theorem/type-goal diagnostics; import/parse/ +resource failure is never accepted as mathematical evidence. + +Separate independent reviewer approval remains mandatory for each case. +""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +INVALID = re.compile( + r"unknown (?:module|identifier|constant|tactic)|failed to import|" + r"unexpected token|parser error|out of memory|" + r"maximum number of heartbeats|object file|timeout|" + r"failed to synthesize|invalid field notation", re.I) +SEMANTIC = re.compile( + r"type mismatch|application type mismatch|unsolved goals|" + r"tactic .?exact.? failed|type is not definitionally equal", re.I) + + +def challenge(module: str, body: str): + if shutil.which("lake") is None: + raise AssertionError("pinned Lean compiler required") + with tempfile.TemporaryDirectory(prefix="original-semantic-", + dir=ROOT / ".lake") as td: + path = Path(td) / "FalseButWellTyped.lean" + path.write_text("import " + module + "\n" + body + "\n", + encoding="utf-8") + result = subprocess.run(["lake", "env", "lean", str(path)], + cwd=ROOT, capture_output=True, text=True, + timeout=900) + out = result.stdout + "\n" + result.stderr + return result.returncode, out + + +class OriginalNegativeSemanticBranches(unittest.TestCase): + def assert_false_mathematical_result(self, module, body): + rc, output = challenge(module, body) + self.assertNotEqual(rc, 0, "mathematically forbidden result compiled") + self.assertIn("error:", output.lower()) + self.assertIsNone(INVALID.search(output), output[-4000:]) + self.assertIsNotNone(SEMANTIC.search(output), output[-4000:]) + + def test_X_06_false_exact_gr_under_real_leakage(self): + self.assert_false_mathematical_result( + "PermanssonResearch.X1.Comparison", + "open MeasureTheory PermanssonResearch.X1\n" + "example : PermanssonLean.RegimeSpecification.IsExactGeneratedRegime\n" + " (model Mode.equilibrium) (specification Mode.equilibrium)\n" + " (Measure.dirac y0) := by\n" + " exact replete_not_exactGR Mode.equilibrium\n" + " (specification Mode.equilibrium) rfl (Measure.dirac y0)\n" + ) + + def test_E_02_false_legal_negative_witness_from_illegal_approximation(self): + self.assert_false_mathematical_result( + "PermanssonResearch.Specialized.BoundaryWitnesses", + "open PermanssonResearch.Specialized.BoundaryWitnesses\n" + "example : ∃ p ∈ ({0} : Set ℝ), negativeForm p < 0 := by\n" + " exact exact_but_illegal_approximant.2.2.2\n" + ) + + def test_E_03_false_two_dimensional_shell_collapse(self): + self.assert_false_mathematical_result( + "PermanssonResearch.Specialized.BoundaryWitnesses", + "open Matrix PermanssonResearch.Specialized\n" + "open PermanssonResearch.Specialized.BoundaryWitnesses\n" + "example :\n" + " let S := shellComplement (0 : Matrix (Fin 1) (Fin 1) ℝ)\n" + " (0 : Matrix (Fin 1) (Fin 2) ℝ) shell (-1)\n" + " S *ᵥ ![0, 1] = 0 := by\n" + " exact dimension_two_retains_second_equation.2.2.2\n" + ) + + def test_E_04_false_zero_shift_inverse(self): + self.assert_false_mathematical_result( + "PermanssonResearch.Specialized.BoundaryWitnesses", + "open Matrix PermanssonResearch.Specialized\n" + "open PermanssonResearch.Specialized.BoundaryWitnesses\n" + "example : IsUnit\n" + " (negativeShift (0 : Matrix (Fin 1) (Fin 1) ℝ) 0) := by\n" + " exact zero_shift_does_not_supply_inverse\n" + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_extra_producers_v31.py b/verification/research/test_final_closure_extra_producers_v31.py new file mode 100644 index 00000000..a94007ea --- /dev/null +++ b/verification/research/test_final_closure_extra_producers_v31.py @@ -0,0 +1,258 @@ +"""Additional v3.1 producer controls: only real source/CI, no auto-approval.""" +import io +import json +import unittest +from unittest import mock +import zipfile + +import final_closure_extra_producers_v31 as auth + +H="a"*40 +ORIGINAL_HASH="d"*64 +CASES=[f"C-{n:02}" for n in range(1,46)] +LEGACY=CASES[:20] +CLAIMS=["Q1","Q2","Q3","M1","M2","M3","N1","N2","N3","D8","D9"] + + +def zip_bytes(members): + f=io.BytesIO() + with zipfile.ZipFile(f,"w") as z: + for name,data in sorted(members.items()): + z.writestr(name,data) + return f.getvalue() + + +def fixture(): + expected=auth.expected_controls(CASES,LEGACY,CLAIMS) + mapping={ + "original_positive":CASES, + "legacy_negative":LEGACY, + "claim_pair":CLAIMS, + } + producers=[] + zipped={} + endpoints={} + jobs={} + approved={} + for i,(kind,owners) in enumerate(mapping.items(),1): + run_id,job_id,check_id,artifact_id=i+100,i+200,i+300,i+400 + workflow=f".github/workflows/proposed-control-{kind}.yml" + job_name=f"real-control-{kind}" + workflow_sha=(str(i)*40) + approved[workflow]=workflow_sha + endpoints["/contents/"+workflow+"?ref="+H]={ + "type":"file","sha":workflow_sha} + source={} + owners_index=[] + for owner in owners: + receipt={ + "status":auth.STATUS[kind], + "head_sha":H, + "case_id" if kind!="claim_pair" else "claim_id":owner, + "accepted_claim":False, + "independent_case_review":False, + "independent_mathematical_review":False, + "scientific_release_approved":False, + } + member=f"receipts/{owner}.json" + raw=(json.dumps(receipt,sort_keys=True)+"\n").encode() + source[member]=raw + owners_index.append({ + "kind":kind,"owner_id":owner, + "receipt_member":member,"receipt_sha256":auth.sha(raw)}) + index={ + "format":auth.INDEX_FORMAT, + "head_sha":H,"github_run_id":run_id, + "github_run_attempt":1,"owners":owners_index, + "accepted_cases":0,"accepted_claims":0, + "independent_review":False, + } + index_bytes=(json.dumps(index,sort_keys=True)+"\n").encode() + source["producer_owner_index.json"]=index_bytes + ZIP=zip_bytes(source) + zipped[artifact_id]=ZIP + producers.append({ + "run_id":run_id,"run_attempt":1,"job_id":job_id, + "check_run_id":check_id,"artifact_id":artifact_id, + "artifact_name":f"pr113-control-{kind}-source", + "artifact_sha256":auth.sha(ZIP), + "workflow_path":workflow,"job_name":job_name, + "owner_index_member":"producer_owner_index.json", + "owner_index_sha256":auth.sha(index_bytes),"owners":owners_index}) + endpoints[f"/actions/runs/{run_id}"]={ + "id":run_id,"head_sha":H,"head_branch":auth.BRANCH, + "run_attempt":1,"status":"completed","conclusion":"success", + "event":"workflow_dispatch","path":workflow} + jobs[run_id]=[{ + "id":job_id,"name":job_name,"run_id":run_id, + "run_attempt":1,"head_sha":H,"status":"completed", + "conclusion":"success", + "check_run_url":f"https://api.github.com/repo/check-runs/{check_id}"}] + endpoints[f"/check-runs/{check_id}"]={ + "id":check_id,"head_sha":H,"name":job_name, + "status":"completed","conclusion":"success", + "app":{"slug":"github-actions"}} + endpoints[f"/actions/artifacts/{artifact_id}"]={ + "id":artifact_id,"name":producers[-1]["artifact_name"], + "expired":False,"digest":"sha256:"+auth.sha(ZIP), + "workflow_run":{"id":run_id,"head_sha":H}} + payload={ + "format":auth.FORMAT,"head_sha":H, + "original_21_producer_bundle_sha256":ORIGINAL_HASH, + "original_producing_artifact_count":21, + "source_controls_accepted":False, + "independent_security_review":False, + "mathematical_approval":False, + "merged_main_sha":None, + "producers":producers, + } + original={ + "format":auth.ORIGINAL_FORMAT, + "status":"REAL_PRODUCING_BYTES_UNREVIEWED", + "producer_count":21,"head_sha":H, + } + return expected,payload,zipped,endpoints,jobs,original,approved + + +class ExtraControlProvenanceTests(unittest.TestCase): + def execute(self,mutate=None): + expected,contract,binaries,api,jobs,original,approved=fixture() + if mutate: + mutate(expected,contract,binaries,api,jobs,original,approved) + with mock.patch.object(auth,"live",side_effect=lambda path:api[path]), \ + mock.patch.object(auth,"paginate", + side_effect=lambda path,key:jobs[int(path.split("/")[3])]): + return auth.authenticated_extra_controls( + H,contract,binaries,original_producers=original, + original_bundle_sha256=ORIGINAL_HASH, + expected=expected,reviewed_workflow_blobs=approved) + + def test_all_45_20_11_real_job_shapes_source_attributed_not_approved(self): + report=self.execute() + self.assertEqual(len(report["source_owned_controls"]),76) + self.assertEqual(report["original_21_producing_artifacts_still_required"],21) + self.assertEqual(report["original_case_positives"],45) + self.assertEqual(report["legacy_case_negatives"],20) + self.assertEqual(report["claim_positive_negative_pairs"],11) + self.assertFalse(report["schema_security_approved"]) + self.assertEqual(report["accepted_cases"],0) + self.assertEqual(report["accepted_claims"],0) + json.dumps(report) # source receipt must serialize canonically + + def test_missing_or_borrowed_owner_and_false_source_index_fail(self): + changes=[ + lambda e,c,b,a,j,o,p:c["producers"][0]["owners"].pop(), + lambda e,c,b,a,j,o,p:c["producers"][1]["owners"][0].update( + owner_id="FOREIGN"), + lambda e,c,b,a,j,o,p:c["producers"][2]["owners"][0].update( + kind="original_positive"), + lambda e,c,b,a,j,o,p:c.update(mathematical_approval=True), + lambda e,c,b,a,j,o,p:c["producers"][0].update( + check_run_id=c["producers"][1]["check_run_id"]), + lambda e,c,b,a,j,o,p:b.update({401:b"borrowed bytes"}), + ] + for idx,mutation in enumerate(changes): + with self.subTest(idx=idx),self.assertRaises( + (auth.ControlProducerError,ValueError)): + self.execute(mutation) + + def test_forged_live_workflow_head_run_check_and_zip_digest_fail(self): + changes=[ + lambda e,c,b,a,j,o,p:a["/actions/runs/101"].update(head_sha="b"*40), + lambda e,c,b,a,j,o,p:a["/actions/runs/101"].update( + head_branch="malicious"), + lambda e,c,b,a,j,o,p:j[101][0].update(conclusion="failure"), + lambda e,c,b,a,j,o,p:a["/check-runs/301"].update( + conclusion="failure"), + lambda e,c,b,a,j,o,p:a["/actions/artifacts/401"].update( + digest="sha256:"+"e"*64), + lambda e,c,b,a,j,o,p:p.clear(), + ] + for idx,mutation in enumerate(changes): + with self.subTest(idx=idx),self.assertRaises( + (auth.ControlProducerError,ValueError)): + self.execute(mutation) + + def test_original_21_bound_CI_cannot_be_substituted(self): + def no_original(e,c,b,a,j,o,p): + o["producer_count"]=20 + with self.assertRaises(auth.ControlProducerError): + self.execute(no_original) + def no_real_source(e,c,b,a,j,o,p): + a["/contents/.github/workflows/proposed-control-original_positive.yml?ref="+H]["sha"]="f"*40 + with self.assertRaises(auth.ControlProducerError): + self.execute(no_real_source) + + + def test_assembler_calls_real_76_owner_validator_with_original_producers(self): + """Exercise public assembler, authenticating synthetic owner ZIPs locally.""" + from pathlib import Path + import tempfile + import final_closure_authenticate as gh + import final_closure_case_receipts as original_auth + import final_closure_schema as schema + import final_closure_legacy_semantic_evidence as legacy + import pr113_v31_execution_integrity as integrity + from test_pr113_v31_execution_integrity import ( + CASE_IDS, complete, source, authentic_original_legacy, + ) + contract, binaries = complete() + original = authentic_original_legacy() + with tempfile.TemporaryDirectory(prefix="pr113-real-callee-") as td: + root = Path(td) + zip_dir = root / "additional-zips" + zip_dir.mkdir() + for artifact_id, raw in binaries.items(): + (zip_dir / f"{artifact_id}.zip").write_bytes(raw) + original_file = root / "original.json" + original_file.write_text(json.dumps({ + "format": "Permansson.FinalClosureDetachedReceipts.v3", + "head_sha": H, "evidence_bundle_sha256": ORIGINAL_HASH, + })) + original_zip = root / "original.zip" + original_zip.write_bytes(b"test-only original GH ZIP") + contract_file = root / "contract.json" + contract_file.write_text(json.dumps(contract)) + blobs_file = root / "workflow-blobs.json" + blobs_file.write_text("{}") + case_map = {cid: {} for cid in CASE_IDS} + candidate = { + "accepted_cases": 0, "accepted_claims": 0, + "schema_security_approved": False, + "independent_mathematical_review": False, + } + with mock.patch.object(gh, "git", return_value=H), \ + mock.patch.object(schema, "validate_overlay"), \ + mock.patch.object(original_auth, "load_detached_bundle", + return_value={}), \ + mock.patch.object(original_auth, "authenticate_v3_sources", + return_value=original), \ + mock.patch.object(legacy, "original_case_map", + return_value=(case_map, set(CASE_IDS[:20]))), \ + mock.patch.object(auth, "candidate_registry", + return_value=contract["producers"]), \ + mock.patch.object(auth, "authenticated_extra_controls", + return_value=candidate), \ + mock.patch.object(integrity, "exact_source", + return_value=source()), \ + mock.patch.object(integrity, "validate_executed_controls", + wraps=integrity.validate_executed_controls) as validator: + report = auth.assemble_review_pending( + H, original_file, original_zip, contract_file, + zip_dir, blobs_file, root / "validated.json") + self.assertEqual( + report["actual_execution_member_validation"]["source_owner_count"], + 76) + self.assertEqual(validator.call_count, 1) + self.assertIs( + validator.call_args.kwargs["original_producers"], original) + self.assertFalse( + report["actual_execution_member_validation"]["mathematical_acceptance"]) + original["producers"][555]["members"][ + "C-01/test-00/raw_compiler/event-1-000001/source.lean" + ] = b"forged original capture" + with self.assertRaises(integrity.RealControlError): + auth.assemble_review_pending( + H, original_file, original_zip, contract_file, + zip_dir, blobs_file, root / "rejected.json") + diff --git a/verification/research/test_final_closure_full_contract.py b/verification/research/test_final_closure_full_contract.py new file mode 100644 index 00000000..323b7684 --- /dev/null +++ b/verification/research/test_final_closure_full_contract.py @@ -0,0 +1,198 @@ +"""Full selected-source gates use mocked effective decisions, not live approvals.""" +import copy +import unittest +from unittest import mock + +import final_closure_full_contract as full +import final_closure_gate as gate +from final_closure_case_receipts import EvidenceError + + +def valid_inputs(): + cases = [{"case_id": f"C-{i:02d}"} for i in range(1, 46)] + claims = [{"id": cid} for cid in sorted(full.ORIGINAL_CLAIMS)] + result = { + "effective_cases": {c["case_id"]: "VERIFIED_SELECTED_SCOPE" + for c in cases}, + "effective_claims": sorted(full.ORIGINAL_CLAIMS), + "accepted_case_count": 45, "pending_case_count": 0, + "accepted_claim_count": 11, + } + selection = { + "cases": [ + {"case_id": row["case_id"], + "historical_proof_declarations": [{"name": "real_proof"}], + "test_source_candidates": ["verification/research/test_selected.py"], + "test_method_candidates": [{ + "path": "verification/research/test_selected.py", + "method": "test_selected_original", + "role": "COMPILER_SOURCE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE" + }]} + for row in cases + ] + } + return result, {"cases": cases}, claims, selection + + +class FullScopeContractTests(unittest.TestCase): + def test_fully_mocked_graph_passes_only_as_source_not_release(self): + evidence, cases, claims, selection = valid_inputs() + proof = full.require_full_source(evidence, cases, claims, selection) + self.assertEqual(proof["effective_case_count"], 45) + self.assertEqual(proof["effective_claim_count"], 11) + self.assertTrue(proof["not_yet_release"]) + + def test_44_cases_and_10_claims_each_reject(self): + for what in ("cases", "claims", "counterfeit"): + evidence, cases, claims, selection = valid_inputs() + if what == "cases": + evidence["effective_cases"].pop("C-45") + evidence["accepted_case_count"] = 44 + evidence["pending_case_count"] = 1 + elif what == "claims": + evidence["effective_claims"].remove("Q3") + evidence["accepted_claim_count"] = 10 + else: + evidence["effective_claims"][-1] = "FAKE" + with self.subTest(what=what), self.assertRaises(EvidenceError): + full.require_full_source(evidence, cases, claims, selection) + + def test_missing_case_specific_selection_never_full(self): + evidence, cases, claims, selection = valid_inputs() + selection["cases"][7]["test_source_candidates"] = [] + with self.assertRaises(EvidenceError): + full.require_full_source(evidence, cases, claims, selection) + + def test_method_only_filename_or_forged_role_cannot_count(self): + for fault in ("drop", "forged", "duplicate"): + evidence, cases, claims, selection = valid_inputs() + routes = selection["cases"][4]["test_method_candidates"] + if fault == "drop": + routes.clear() + elif fault == "forged": + routes[0]["role"] = "APPROVED" + else: + import copy + routes.append(copy.deepcopy(routes[0])) + with self.subTest(fault=fault), self.assertRaises(EvidenceError): + full.require_full_source(evidence, cases, claims, selection) + + def test_labels_cannot_replace_full_exact_sets(self): + evidence, cases, claims, selection = valid_inputs() + evidence["accepted_case_count"] = 45 + evidence["effective_cases"] = {"C-01": "VERIFIED_SELECTED_SCOPE"} + with self.assertRaises(EvidenceError): + full.require_full_source(evidence, cases, claims, selection) + evidence, cases, claims, selection = valid_inputs() + claims[-1]["id"] = "EXTRA" + with self.assertRaises(EvidenceError): + full.require_full_source(evidence, cases, claims, selection) + + def test_full_release_conjunction_uses_authenticated_external_artifacts(self): + evidence, cases, claims, selection = valid_inputs() + pdf = "d"*64 + pages = {"page-1.png": "1"*64, "page-2.png": "2"*64} + ids = sorted(full.ORIGINAL_CLAIMS) + manifest = { + "status": "APPROVED_SOURCE_BOUND", + "source_commit_sha": "f"*40, + "claims": ids, "approved_manuscript_claims": ids, + "accepted_claim_receipts": [ + {"claim_id": cid, "receipt_sha256": "a"*64} for cid in ids], + "pdf_sha256": pdf, "pdf_pages": pages, "pdf_page_count": 2, + "empirical_identification_status": "NOT_ESTABLISHED", + "scientific_limitations": ["No empirical GR/PR certificate"], + "reproduction_evidence_sha256": "b"*64, + "archive_evidence_sha256": "c"*64, + } + provenance = { + "M": "f"*40, + "scholarly_reviews": [ + {"pdf_sha256": pdf, "pages": pages, "claim_ids": ids}], + "reproduction_evidence": { + "status": "TWO_CLEAN_SOURCE_BUILDS", + "source_commit_sha": "f"*40, + "pdf_sha256": pdf, "artifact_sha256": "b"*64, + "authenticated_ci_artifact": True}, + "archive_evidence": { + "status": "COMPLETE_ARCHIVE_REPLAY", + "source_commit_sha": "f"*40, + "pdf_sha256": pdf, "artifact_sha256": "c"*64, + "authenticated_ci_artifact": True}, + } + # This is explicitly a synthetic contract graph. The real provenance + # authenticator cannot yet issue these artifacts; no release is claimed. + pre = full.require_preseal_release( + evidence, cases, claims, selection, manifest, provenance) + self.assertEqual(pre["status"], "POSTMERGE_PRESEAL_READY") + self.assertTrue(pre["not_yet_release"]) + without_archive = dict(provenance, archive_evidence=None) + self.assertEqual(full.require_preseal_release( + evidence, cases, claims, selection, manifest, + without_archive)["status"], "POSTMERGE_PRESEAL_READY") + success = full.require_full_release( + evidence, cases, claims, selection, manifest, provenance) + self.assertEqual(success["status"], + "FULL_MATHEMATICAL_CLOSURE_RELEASE_READY") + for defect in ("10claims", "44cases", "no_scholarly", "no_repro", + "no_archive", "wrong_pdf_count", "wrong_merged_m"): + import copy + e, m, p = copy.deepcopy(evidence), copy.deepcopy(manifest), copy.deepcopy(provenance) + if defect == "10claims": + e["effective_claims"].pop() + e["accepted_claim_count"] = 10 + elif defect == "44cases": + e["effective_cases"].pop("C-45") + e["accepted_case_count"] = 44 + e["pending_case_count"] = 1 + elif defect == "no_scholarly": + p["scholarly_reviews"] = [] + elif defect == "no_repro": + p["reproduction_evidence"] = None + elif defect == "no_archive": + p["archive_evidence"] = None + elif defect == "wrong_pdf_count": + m["pdf_page_count"] = 1 + else: + m["source_commit_sha"] = "e"*40 + with self.subTest(defect=defect), self.assertRaises(EvidenceError): + full.require_full_release(e, cases, claims, selection, m, p) + + def test_premerge_full_gates_full_review_and_requires_no_future_M(self): + evidence, cases, claims, selection = valid_inputs() + receipt = { + "format": gate.FORMAT_V3, "phase": "premerge_review", + "merged_main_sha": None, "head_sha": "a"*40, + "evidence_bundle_sha256": "b"*64, + } + provenance = {"H": "a"*40, "S": "c"*40, + "checks": {}, "review_scopes": {}} + with mock.patch.object(gate, "require_complete_obligation_receipts", + return_value={"receipt_contract": "MOCK_ONLY"}), \ + mock.patch.object(gate, "validate_overlay"), \ + mock.patch.object(gate, "verify_premerge", + return_value=provenance), \ + mock.patch.object(gate, "load_detached_bundle", + return_value={}), \ + mock.patch.object(gate, "effective_acceptance", + return_value=evidence), \ + mock.patch.object(gate, "authenticate_all_76", + return_value={"status": "MOCK_ONLY_V31"}), \ + mock.patch.object(gate, "read_json", + side_effect=[cases, {"claims": claims}, selection, {"claims": []}]): + status = gate.premerge( + receipt, None, full=True, + additional_contract="/mock/contract.json", + additional_zips="/mock/producer-zips", + workflow_blob_candidates="/mock/oid-candidates.json") + self.assertEqual(status["gate"], "PREMERGE_FULL_SOURCE_READY") + self.assertFalse(status["merge_authorized"]) + self.assertFalse(status["scientific_release_approved"]) + self.assertIsNone(status["merged_main_sha"]) + self.assertEqual(status["additional_execution"]["status"], + "MOCK_ONLY_V31") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_generator.py b/verification/research/test_final_closure_generator.py new file mode 100644 index 00000000..eac8cd0d --- /dev/null +++ b/verification/research/test_final_closure_generator.py @@ -0,0 +1,37 @@ +"""The source inventory must include every added theorem and never accept it.""" +from pathlib import Path +import json +import sys +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "verification/research")) +import final_closure_generate as generator + +class ClosureGeneratorTests(unittest.TestCase): + def test_all_eleven_closure_modules_present_and_directly_imported(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory)/"current_source.json" + report = generator.generate(path) + self.assertEqual(report["original_case_count"],45) + self.assertEqual(len(report["source_rows"]),11) + self.assertTrue(all(r["source_sha256"] for r in report["source_rows"])) + self.assertTrue(all(r["imported_by_research_root"] for r in report["source_rows"])) + self.assertGreater(sum(len(r["declarations"]) for r in report["source_rows"]),11) + self.assertEqual(report["status"],"CANDIDATE_UNREVIEWED") + self.assertEqual(report["source_bound_ci"],[]) + self.assertEqual(report["independent_reviews"],[]) + self.assertTrue(all(r["compiled_head_sha"] is None for r in report["source_rows"])) + self.assertEqual(json.loads(path.read_text())["format"], + "Permansson.FinalClosureCandidateObservation.v2") + + def test_statement_hashes_have_strict_byte_identity(self): + source = ROOT/"PermanssonResearch/MultiAgent/MeasurableProductTwoMatchedProtocols.lean" + entries = generator.exact_statements(source) + self.assertIn("matched_closed_kernel_two",{e["declaration"] for e in entries}) + self.assertTrue(all(len(e["statement_sha256"])==64 for e in entries)) + self.assertTrue(all(e["compiler_result"]=="NOT_VERIFIED" for e in entries)) + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_lean_evidence.py b/verification/research/test_final_closure_lean_evidence.py new file mode 100644 index 00000000..780cd8d2 --- /dev/null +++ b/verification/research/test_final_closure_lean_evidence.py @@ -0,0 +1,135 @@ +"""Source/axiom evidence exporter mocks are logic tests, not Lean certificates.""" +from pathlib import Path +import json +import re +import tempfile +import types +import unittest +from unittest import mock + +import final_closure_lean_evidence as e + + +class LeanEvidenceTests(unittest.TestCase): + def test_axiom_parser_is_literal_and_fail_closed(self): + valid = "Demo.foo : True\nDemo.foo depends on axioms: [propext, Quot.sound]\n" + self.assertEqual(e.parse_axioms(valid, "Demo.foo"), + ["propext", "Quot.sound"]) + for output in ( + valid.replace("Quot.sound", "sorryAx"), + valid.replace("propext", "Foo.unsafeAxiom"), + "Demo.foo : True\nerror: unknown identifier\n", + "Demo.foo : True\nerror: maximum number of heartbeats\n", + "Other.foo depends on axioms: []\n", + "Demo.foo : True\n", + ): + with self.subTest(output=output[:50]): + with self.assertRaises(e.CompilerEvidenceError): + e.parse_axioms(output, "Demo.foo") + + def test_unrelated_last_axiom_record_never_replaces_target(self): + for output in ( + "Demo.foo : True\nOther.foo depends on axioms: []\n", + "Demo.foo depends on axioms: []\nOther.foo depends on axioms: []\n", + "Demo.foo depends on axioms: []\nDemo.foo does not depend on any axioms\n", + ): + with self.subTest(output=output), self.assertRaises(e.CompilerEvidenceError): + e.parse_axioms(output, "Demo.foo") + self.assertEqual(e.parse_axioms( + "Demo.foo : True\nDemo.foo does not depend on any axioms\n", + "Demo.foo"), []) + + def test_quoted_single_target_keeps_real_lean_multiline_output(self): + log = ("PERMANSSON_ELABORATED_TYPE_V1_BEGIN\n" + "NAME=Demo.foo\nKIND=theorem\nUNIVERSES=[]\n" + "TYPE=Expr.const `True []\n" + "PERMANSSON_ELABORATED_TYPE_V1_END\n" + "info: Demo.lean:3:0: 'Demo.foo' depends on axioms: " + "[propext,\n Classical.choice.{u},\n Quot.sound]\n") + self.assertEqual(set(e.parse_axioms(log, "Demo.foo")), + {"propext", "Classical.choice", "Quot.sound"}) + with self.assertRaises(e.CompilerEvidenceError): + e.parse_axioms(log + "'Other.foo' depends on axioms: []\n", + "Demo.foo") + + def test_missing_or_wrong_head_never_emits_certificate(self): + with tempfile.TemporaryDirectory() as root: + with mock.patch.object(e, "ROOT", Path(root)), \ + mock.patch.object(e, "git_head", return_value="a"*40): + with self.assertRaises(e.CompilerEvidenceError): + e.candidate_records("b"*40, Path(root)/"outside") + with self.assertRaises(e.CompilerEvidenceError): + e.candidate_records("a"*40, Path(root)/"within") + + def test_forged_count_with_one_claim_is_rejected(self): + with tempfile.TemporaryDirectory() as root: + base = Path(root) + fake = base / "map.json" + fake.write_text(json.dumps({ + "count": 11, "claims": [{ + "claim_id": "Q1", "original_case_links": ["X-01"], + "declarations": [{"path": "Simple.lean", + "declaration": "Demo.foo"}], + "test_source_candidates": ["verification/research/test_fake.py"], + }] + })) + outside = base.parent / (base.name + "-evidence") + with mock.patch.object(e, "ROOT", base), \ + mock.patch.object(e, "MAP", fake), \ + mock.patch.object(e, "git_head", return_value="a"*40): + with self.assertRaises(e.CompilerEvidenceError): + e.candidate_records("a"*40, outside) + + def test_complete_mocked_claim_map_only_emits_unreviewed_candidates(self): + with tempfile.TemporaryDirectory() as root: + base = Path(root) + (base / ".lake").mkdir() + (base / "PermanssonResearch").mkdir() + (base / "verification/research").mkdir(parents=True) + (base / "verification/research/test_fake.py").write_text( + "def test_fake(): pass\n") + declarations = [f"Demo.foo_{i}" for i in range(11)] + (base / "PermanssonResearch/Simple.lean").write_text( + "namespace Demo\n" + + "".join(f"theorem foo_{i} : True := by trivial\n" + for i in range(11)) + "end Demo\n") + ids = e.CLAIM_IDS + claim_file = base / "claim_map.json" + claim_file.write_text(json.dumps({ + "count": 11, + "claims": [{ + "claim_id": cid, "original_case_links": ["X-01"], + "declarations": [{"path": "PermanssonResearch/Simple.lean", + "declaration": decl}], + "test_source_candidates": ["verification/research/test_fake.py"], + } for cid, decl in zip(ids, declarations)] + })) + external = base.parent / (base.name + "-outside") + external.mkdir() + self.addCleanup(lambda: __import__("shutil").rmtree( + external, ignore_errors=True)) + def fake_compiler(probe): + decl = re.search(r"let nm : Name := `([^\n]+)", probe.read_text()).group(1) + output = ( + "PERMANSSON_ELABORATED_TYPE_V1_BEGIN\n" + f"NAME={decl}\nKIND=theorem\nUNIVERSES=[]\n" + "TYPE=Expr.const `True []\n" + "PERMANSSON_ELABORATED_TYPE_V1_END\n" + f"{decl} depends on axioms: []\n" + ) + return types.SimpleNamespace( + returncode=0, stdout=output, stderr="") + with mock.patch.object(e, "ROOT", base), \ + mock.patch.object(e, "MAP", claim_file), \ + mock.patch.object(e, "git_head", return_value="a"*40), \ + mock.patch.object(e, "run_probe", side_effect=fake_compiler): + record = e.candidate_records("a"*40, external) + self.assertEqual(record["status"], "COMPILER_ONLY_UNREVIEWED") + self.assertEqual(record["proof_count"], 11) + self.assertTrue(all(row["check_run_id"] is None + for row in record["compiler_axiom_receipts"])) + self.assertEqual(record["reviews"], []) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_legacy_replay.py b/verification/research/test_final_closure_legacy_replay.py new file mode 100644 index 00000000..817f028a --- /dev/null +++ b/verification/research/test_final_closure_legacy_replay.py @@ -0,0 +1,242 @@ +"""Original source-append replay adapters: Python-only adversarial tests. + +No mocked test outcome here is a Lean or independent scientific certificate. +""" +from pathlib import Path +import hashlib +import json +import subprocess +import tempfile +import unittest +from unittest import mock + +import final_closure_legacy_replay as replay +from final_closure_mutation_evidence import MutationEvidenceError + +H = "a" * 40 + + +class LegacyAppendOnlyReplayBoundaries(unittest.TestCase): + def setUp(self): + self.original = (b"import Mathlib.Tactic\n" + b"namespace Original\n" + b"theorem established : True := by trivial\n" + b"end Original\n") + self.probe = ( + b"import PermanssonResearch.X1.Exact\n" + b"open Original\n" + b"example : False := by exact established\n") + + def test_replay_preserves_exact_original_prefix_and_imports(self): + combined = replay.appended_mutant( + self.original, self.probe, "PermanssonResearch.X1.Exact") + self.assertTrue(combined.startswith(self.original)) + self.assertGreater(len(combined), len(self.original)) + self.assertEqual( + [x for x in combined.splitlines() if x.startswith(b"import ")], + [x for x in self.original.splitlines() if x.startswith(b"import ")]) + self.assertIn(b"example : False", combined) + self.assertIn(b"CANDIDATE ONLY", combined) + + def test_exact_original_prefix_capture_is_eligible_only_if_append_only(self): + # Synthetic bytes exercise the parser, not real Lean execution. + appended = (b"\nnamespace Original\n" + b"theorem wrong_corner : False := by\n" + b" exact established\n" + b"end Original\n") + captured = self.original + appended + self.assertEqual(replay.appended_mutant( + self.original, captured, "PermanssonResearch.X1.Exact"), captured) + for bad_suffix in (b"", b"\naxiom forged : False\n", + b"\nimport Other.Lean\nexample : False := by trivial\n", + b"\nexample : False := by sorry\n", + b"\nset_option autoImplicit true\nexample : False := by trivial\n"): + with self.subTest(suffix=bad_suffix): + with self.assertRaises(MutationEvidenceError): + replay.appended_mutant( + self.original, self.original + bad_suffix, + "PermanssonResearch.X1.Exact") + tampered = self.original.replace(b"established", b"impostor") + appended + with self.assertRaises(MutationEvidenceError): + replay.appended_mutant( + self.original, tampered, "PermanssonResearch.X1.Exact") + + def test_missing_provider_and_missing_statement_fail_closed(self): + invalid = ( + (b"import Unrelated.Module\nexample : False := by trivial\n", + "PermanssonResearch.X1.Exact"), + (b"import PermanssonResearch.X1.Exact\n#check True\n", + "PermanssonResearch.X1.Exact"), + (b"", "PermanssonResearch.X1.Exact"), + ) + for raw, source in invalid: + with self.subTest(raw=raw), self.assertRaises(MutationEvidenceError): + replay.appended_mutant(self.original, raw, source) + + def test_forbidden_axioms_admissions_and_late_imports_fail(self): + forbidden = ( + b"axiom fake : False\nexample : False := fake\n", + b"example : False := by sorry\n", + b"example : False := by admit\n", + b"example : False := by trivial\nimport Unexpected.Module\n", + b"set_option autoImplicit true\nexample : False := by trivial\n", + ) + for body in forbidden: + with self.subTest(body=body), self.assertRaises( + MutationEvidenceError): + replay.appended_mutant( + self.original, + b"import PermanssonResearch.X1.Exact\n" + body, + "PermanssonResearch.X1.Exact") + + def test_original_provider_cannot_be_replaced_with_unrelated_module(self): + with tempfile.TemporaryDirectory(prefix="pr113-replay-provider-") as td: + root = Path(td) + src = root / "PermanssonResearch" / "X1" / "Exact.lean" + src.parent.mkdir(parents=True) + src.write_bytes(self.original) + row = { + "historical_proof_declarations": [ + {"path": "PermanssonResearch/X1/Exact.lean"} + ], + "new_claim_source_candidates": [], + } + with mock.patch.object(replay, "ROOT", root), mock.patch.object( + replay, "committed_file_bytes", + return_value=self.original + ): + path, raw, mod = replay.choose_original( + H, row, "PermanssonResearch/X1/Exact.lean") + self.assertEqual(path, src) + self.assertEqual(raw, self.original) + self.assertEqual(mod, "PermanssonResearch.X1.Exact") + for bad in ( + "PermanssonResearch/X1/Other.lean", + "PermanssonResearch/../untracked.lean", + "/tmp/Original.lean", + ): + with self.subTest(provider=bad), self.assertRaises( + MutationEvidenceError): + replay.choose_original(H, row, bad) + + + def test_replay_keeps_identity_but_never_creates_review_or_ci(self): + """Synthetic local Git with mocked Lean compiler only; not a proof.""" + with tempfile.TemporaryDirectory(prefix="pr113-replay-contract-") as td: + base = Path(td) + root = base / "repo" + root.mkdir() + subprocess.run(["git", "init", "-q"], cwd=root, check=True) + relative = "PermanssonResearch/X1/Exact.lean" + source = root / relative + source.parent.mkdir(parents=True) + source.write_bytes(self.original) + test_relative = "verification/research/test_original.py" + test_file = root / test_relative + test_file.parent.mkdir(parents=True) + test_file.write_text("def test_false_original():\n pass\n") + subprocess.run(["git", "add", "."], cwd=root, check=True) + subprocess.run( + ["git", "-c", "user.name=ClosureAudit", + "-c", "user.email=audit@example.invalid", + "commit", "-qm", "source-bound fixture"], + cwd=root, check=True) + h = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=root, text=True).strip() + test_dir = base / "legacy-test" + capture = test_dir / "raw_compiler" / "event-1-000000" + capture.mkdir(parents=True) + false_input = self.probe + (capture / "source.lean").write_bytes(false_input) + (capture / "observation.json").write_text( + json.dumps({"status": "RAW_UNREVIEWED"})) + (capture / "stdout.log").write_text("error: type mismatch") + (capture / "stderr.log").write_text("") + test_stdout = b"legacy test ok\n" + (test_dir / "test_stdout.log").write_bytes(test_stdout) + (test_dir / "test_stderr.log").write_bytes(b"") + hash256 = lambda raw: hashlib.sha256(raw).hexdigest() + test_manifest = { + "format": "Permansson.LegacyCaseTestExecutionCandidate.v1", + "case_id": "X-01", "test_file": test_relative, + "selected_methods": ["test_false_original"], + "test_exit_code": 0, + "status": "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED", + "independent_review": False, "release_approved": False, + "ci_check_run_id": None, + "source_test_sha256": hash256(test_file.read_bytes()), + "stdout_sha256": hash256(test_stdout), + "stderr_sha256": hash256(b""), + } + (test_dir / "unreviewed_test_manifest.json").write_text( + json.dumps(test_manifest)) + row = { + "historical_proof_declarations": [{"path": relative}], + "new_claim_source_candidates": [], + } + observations = [{ + "observation_id": "event-1-000000", + "possible_semantic_diagnostic": True, + "method_observed_unverified": "test_false_original", + "method_attribution_ambiguous": False, + "source_sha256": hash256(false_input), + "raw_metadata_sha256": hash256( + (capture / "observation.json").read_bytes()), + }] + + def mocked_lean_producer(_head, *, true_source, mutant, + test_path, test_name, case_id, + claim_ids, out): + self.assertEqual(_head, h) + self.assertEqual(true_source, source) + self.assertEqual(test_path, test_file) + self.assertEqual(test_name, "test_false_original") + self.assertEqual(case_id, "X-01") + self.assertEqual(claim_ids, []) + self.assertTrue(mutant.read_bytes().startswith(self.original)) + self.assertIn(b"example : False", mutant.read_bytes()) + out.mkdir() + (out / "original.lean").write_bytes(self.original) + (out / "mutated.lean").write_bytes(mutant.read_bytes()) + (out / "positive.log").write_bytes(b"mock positive\n") + (out / "negative.log").write_bytes( + b"mock negative: type mismatch\n") + return { + "original_lean_source_member": "original.lean", + "original_lean_source_sha256": hash256(self.original), + "mutant_source_member": "mutated.lean", + "mutant_source_sha256": hash256(mutant.read_bytes()), + "check_run_id": None, + } + + detached = base / "replay" + with ( + mock.patch.object(replay, "ROOT", root), + mock.patch.object(replay, "git_head", return_value=h), + mock.patch.object(replay, "original_case_map", + return_value=({"X-01": row}, ("X-01",))), + mock.patch.object(replay, "selected_methods", + return_value=((test_relative, + "Class", ("test_false_original",)),)), + mock.patch.object(replay, "test_observations", + return_value=observations), + mock.patch.object(replay, "produce", + side_effect=mocked_lean_producer), + ): + produced = replay.replay( + h, case_id="X-01", method="test_false_original", + provider=relative, test_output=test_dir, + observation_id="event-1-000000", output=detached) + self.assertIn("UNREVIEWED", produced["status"]) + self.assertFalse(produced["independent_review"]) + self.assertFalse(produced["mathematical_acceptance"]) + self.assertFalse(produced["scientific_release_approved"]) + self.assertIsNone(produced["ci_check_run_id"]) + self.assertEqual( + (detached / "original_legacy_capture" / "source.lean").read_bytes(), + false_input) + self.assertTrue((detached / "semantic_replay_candidate.zip").is_file()) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_legacy_semantic_evidence.py b/verification/research/test_final_closure_legacy_semantic_evidence.py new file mode 100644 index 00000000..0c8f95d3 --- /dev/null +++ b/verification/research/test_final_closure_legacy_semantic_evidence.py @@ -0,0 +1,256 @@ +"""No-approval and source-identity regressions for legacy Lean evidence capture. + +These deliberately test the plumbing with fixture bytes, NOT Lean proofs, +semantic truth, genuine CI attribution, or independent scholarly review. +""" +from pathlib import Path +import json +import os +import subprocess +import tempfile +import types +import unittest +from unittest import mock + +import final_closure_legacy_semantic_evidence as legacy +import pr113_compiler_trace_hook as hook +from final_closure_mutation_evidence import MutationEvidenceError + +H = "a" * 40 + + +class LegacyEvidenceContract(unittest.TestCase): + def test_exact_25_plus_20_original_partition_and_named_methods(self): + with mock.patch.object( + legacy, "committed_file_bytes", + side_effect=lambda _head, path, **kwargs: path.read_bytes()): + all_rows, ids = legacy.shard_cases(H, 0, 1) + self.assertEqual(len(all_rows), 45) + self.assertEqual(len(ids), 20) + self.assertFalse(set(ids) & set(legacy.DETACHED_SPECS)) + self.assertEqual(set(ids) | set(legacy.DETACHED_SPECS), + set(all_rows)) + for cid in ids: + groups = legacy.selected_methods(H, all_rows[cid]) + self.assertTrue(groups, cid) + self.assertTrue(all(method.startswith("test_") + for _path, _cls, methods in groups + for method in methods)) + self.assertFalse(all_rows[cid]["effective_accepted"]) + + def test_shards_disjoint_and_invalid_counts_rejected(self): + with mock.patch.object( + legacy, "committed_file_bytes", + side_effect=lambda _head, path, **kwargs: path.read_bytes()): + shards = [legacy.shard_cases(H, i, 8)[1] for i in range(8)] + flat = [cid for group in shards for cid in group] + self.assertEqual(len(flat), 20) + self.assertEqual(len(set(flat)), 20) + for i, n in ((-1, 8), (8, 8), (0, 0), (0, 21)): + with self.subTest(index=i, count=n), self.assertRaises( + MutationEvidenceError): + legacy.shard_cases(H, i, n) + + def test_observer_records_exact_temporary_source_before_cleanup(self): + with tempfile.TemporaryDirectory(prefix="pr113-legacy-event-") as td: + root = Path(td) + source = root / "false.lean" + original = b"import Valid.Module\nexample : False := by trivial\n" + source.write_bytes(original) + observation = hook._source( + ["lake", "env", "lean", str(source)], str(root)) + self.assertEqual(observation[2], original) + self.assertIsNone(hook._source(["python", "-V"], str(root))) + captured = root / "events" + captured.mkdir() + hook._record(str(captured), observation[0], observation[1], + observation[2], + outcome=types.SimpleNamespace( + returncode=1, stdout="error: type mismatch", + stderr="")) + events = legacy.test_observations(captured) + self.assertEqual(len(events), 1) + self.assertTrue(events[0]["possible_semantic_diagnostic"]) + self.assertTrue(events[0]["source_retained"]) + source.unlink() + self.assertEqual( + (next(captured.glob("event-*")) / "source.lean").read_bytes(), + original) + + def test_mutated_compiler_log_and_unrelated_error_fail_closed(self): + with tempfile.TemporaryDirectory(prefix="pr113-legacy-hash-") as td: + root = Path(td) + source = b"import Valid.Module\nexample : False := by trivial\n" + hook._record(str(root), ["lake", "env", "lean", "false.lean"], + "false.lean", source, + outcome=types.SimpleNamespace( + returncode=1, stdout="error: parser error", + stderr="")) + observations = legacy.test_observations(root) + self.assertEqual(len(observations), 1) + self.assertFalse(observations[0]["possible_semantic_diagnostic"]) + event = next(root.glob("event-*")) + (event / "stdout.log").write_bytes(b"error: type mismatch") + with self.assertRaises(MutationEvidenceError): + legacy.test_observations(root) + + def test_cross_case_method_and_extra_compiler_member_rejected(self): + with tempfile.TemporaryDirectory(prefix="pr113-cross-case-") as td: + root = Path(td) + hook._record(str(root), ["lake", "env", "lean", "probe.lean"], + "probe.lean", b"example : False := by trivial", + outcome=types.SimpleNamespace( + returncode=1, stdout="error: unsolved goals", + stderr="")) + with self.assertRaises(MutationEvidenceError): + legacy.test_observations(root, expected_case="X-01") + with self.assertRaises(MutationEvidenceError): + legacy.test_observations(root, expected_methods="test_original") + event = next(root.glob("event-*")) + (event / "unexpected.txt").write_text("smuggled evidence") + with self.assertRaises(MutationEvidenceError): + legacy.test_observations(root) + + def test_group_only_compiler_event_cannot_credit_specific_method(self): + with tempfile.TemporaryDirectory(prefix="pr113-setup-") as td: + root = Path(td) + with mock.patch.dict(os.environ, { + "PR113_CASE_ID": "X-02", + "PR113_SELECTED_METHOD": "test_first,test_second"}): + hook._record( + str(root), ["lake", "env", "lean", "source.lean"], + "source.lean", b"example : False := by trivial", + outcome=types.SimpleNamespace( + returncode=1, stdout="error: type mismatch", + stderr="")) + observed = legacy.test_observations( + root, expected_case="X-02", + expected_methods="test_first,test_second") + self.assertEqual(len(observed), 1) + self.assertTrue(observed[0]["method_attribution_ambiguous"]) + self.assertFalse(observed[0]["possible_semantic_diagnostic"]) + + def test_active_unittest_method_replaces_group_only_context(self): + with tempfile.TemporaryDirectory(prefix="pr113-method-") as td: + raw_subprocess_run = subprocess.run + raw_unittest_run = unittest.TestCase.run + try: + with mock.patch.dict(os.environ, { + "PR113_CAPTURE_DIR": td, + "PR113_SELECTED_METHOD": "test_first,test_second" + }): + hook.install() + class MethodFixture(unittest.TestCase): + def test_first(self): + self.assertEqual( + os.environ.get("PR113_SELECTED_METHOD"), + "test_first") + outcome = unittest.TestResult() + MethodFixture("test_first").run(outcome) + self.assertTrue(outcome.wasSuccessful()) + self.assertEqual( + os.environ.get("PR113_SELECTED_METHOD"), + "test_first,test_second") + finally: + subprocess.run = raw_subprocess_run + unittest.TestCase.run = raw_unittest_run + + def test_each_selected_legacy_method_requires_own_false_diagnostic(self): + selected = ("test_one", "test_two") + observed = [{ + "method_observed_unverified": "test_one", + "possible_semantic_diagnostic": True, + "method_attribution_ambiguous": False, + }] + covered, missing = legacy.covered_semantic_methods(selected, observed) + self.assertEqual(covered, ["test_one"]) + self.assertEqual(missing, ["test_two"]) + observed.append({ + "method_observed_unverified": "test_one,test_two", + "possible_semantic_diagnostic": False, + "method_attribution_ambiguous": True, + }) + self.assertEqual(legacy.covered_semantic_methods( + selected, observed)[1], ["test_two"]) + observed.append({ + "method_observed_unverified": "test_two", + "possible_semantic_diagnostic": True, + "method_attribution_ambiguous": False, + }) + self.assertEqual(legacy.covered_semantic_methods( + selected, observed), (["test_one", "test_two"], [])) + with self.assertRaises(MutationEvidenceError): + legacy.covered_semantic_methods(("test_one", "test_one"), observed) + + def test_replay_source_suggestions_require_original_declared_imports(self): + row = { + "historical_proof_declarations": [ + {"path": "PermanssonResearch/X1/Actual.lean"} + ], + "new_claim_source_candidates": [ + {"path": "PermanssonResearch/X1/Related.lean"} + ], + } + captured = ( + b"import PermanssonResearch.X1.Actual Mathlib.Tactic\n" + b"example : False := by trivial\n") + self.assertEqual( + legacy.legacy_provider_candidates(row, captured), + ["PermanssonResearch/X1/Actual.lean"]) + self.assertEqual(legacy.legacy_provider_candidates( + row, b"import PermanssonResearch.X1.Unrelated\n" + b"example : False := by trivial\n"), []) + self.assertEqual(legacy.legacy_provider_candidates( + row, b"\xff\xfe"), []) + + def test_exact_committed_original_prefix_is_suggested_but_not_approved(self): + source = (b"import Mathlib.Tactic\nnamespace Original\n" + b"theorem established : True := by trivial\n" + b"end Original\n") + suffix = (b"\nnamespace Original\n" + b"example : False := by exact established\n" + b"end Original\n") + provider = "PermanssonResearch/X1/Actual.lean" + row = {"historical_proof_declarations": [{"path": provider}], + "new_claim_source_candidates": []} + with tempfile.TemporaryDirectory() as td: + root = Path(td) + source_path = root / provider + source_path.parent.mkdir(parents=True) + source_path.write_bytes(source) + with mock.patch.object(legacy, "ROOT", root), \ + mock.patch.object(legacy, "git_head", return_value=H), \ + mock.patch.object(legacy, "committed_file_bytes", + side_effect=lambda _head, p, **kwargs: source): + self.assertEqual( + legacy.legacy_provider_candidates(row, source + suffix), + [provider]) + for false_source in ( + source, + source.replace(b"established", b"impostor") + suffix, + source + b"\naxiom fake : False\n" + suffix, + source + b"\nexample : False := by sorry\n", + ): + with self.subTest(source=false_source[-36:]): + self.assertEqual( + legacy.legacy_provider_candidates(row, false_source), + []) + + def test_unexecuted_and_unreviewed_cannot_become_case_acceptance(self): + with tempfile.TemporaryDirectory(prefix="pr113-legacy-payload-") as td: + target = Path(td) / "sample.json" + data = { + "format": "Permansson.LegacyCaseTestExecutionCandidate.v1", + "status": "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED", + "compiler_events": [], + "independent_review": False, + "ci_check_run_id": None, + "release_approved": False, + } + target.write_bytes(legacy.canonical(data)) + self.assertFalse(json.loads(target.read_bytes())["independent_review"]) + self.assertIsNone(json.loads(target.read_bytes())["ci_check_run_id"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_missing_original_positives.py b/verification/research/test_final_closure_missing_original_positives.py new file mode 100644 index 00000000..f91c86b1 --- /dev/null +++ b/verification/research/test_final_closure_missing_original_positives.py @@ -0,0 +1,125 @@ +"""Exact original 45-case positive proof probes for missing ENG-04 routes. + +Each method imports the ACTUAL original source owners for one frozen case, +asks pinned Lean for every required declaration, checks that its elaborated +type and transitive axioms are present, and permits only trusted axioms. +This is a positive compiler control, never a semantic negative, independent +case approval, or proof that the original acceptance wording is satisfied. + +The case ID and requirement are obtained from the unchanged 45-case registry, +not from an assistant-authored list of substitute theorem statements. +""" +from __future__ import annotations + +import json +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +from closure_elaborated_type import ( + lean_probe, compiled_type_record, TypeEvidenceError +) +from final_closure_case_receipts import parse_target_axioms, TRUSTED_AXIOMS +from final_closure_complete_lean_evidence import ( + CompleteCompilerError, resolved_name +) + +ROOT = Path(__file__).resolve().parents[2] +MAP = ROOT / "research/paper_closures/post_merge_112/acceptance_case_source_map.json" + +# Only exact original cases that have no positive method selection yet. +ORIGINAL_MISSING_POSITIVE = ( + "X-04", "X-05", "X-06", "F-03", "A-05", + "C11-02", "B-01", "E-02", "E-03", "E-04", +) + + +class HistoricalPositiveCompilerControls(unittest.TestCase): + def check_original_case(self, case_id: str): + self.assertIn(case_id, ORIGINAL_MISSING_POSITIVE) + self.assertTrue(shutil.which("lake"), "pinned Lean compiler required") + data = json.loads(MAP.read_text(encoding="utf-8")) + self.assertEqual(data["original_cases"], 45) + rows = [x for x in data["cases"] if x["case_id"] == case_id] + self.assertEqual(len(rows), 1) + row = rows[0] + self.assertFalse(row["effective_accepted"]) + decls = row["historical_proof_declarations"] + self.assertTrue(decls, "historical theorem inventory missing") + seen = set() + records = [] + cache = {} + for item in decls: + path, short = item["path"], item["name"] + fqn = resolved_name(ROOT, path, short, cache) + key = (path, fqn) + self.assertNotIn(key, seen, "duplicate historical theorem route") + seen.add(key) + module = path.removesuffix(".lean").replace("/", ".") + records.append((module, fqn)) + # The dedicated 45-case compiler sharded job verifies the COMPLETE + # historical declaration graph. This independent per-case positive + # control chooses one original theorem, but never represents the + # remaining theorem dependencies as successfully executed. + module, declaration = records[0] + source = lean_probe(module, declaration) + with tempfile.TemporaryDirectory( + prefix="original-" + case_id.replace("-", "_") + "-", + dir=ROOT / ".lake" + ) as tmp: + file = Path(tmp) / "OriginalCase.lean" + file.write_text(source, encoding="utf-8") + result = subprocess.run( + ["lake", "env", "lean", str(file)], cwd=ROOT, + text=True, capture_output=True, timeout=900 + ) + out = result.stdout + "\n" + result.stderr + self.assertEqual(result.returncode, 0, out[-5000:]) + self.assertNotIn("error:", out.lower()) + self.assertNotIn("sorryAx", out) + try: + type_record = compiled_type_record(out, declaration) + axioms = parse_target_axioms(out, declaration) + except (TypeEvidenceError, ValueError) as exc: + self.fail("missing exact compiler type/axioms for " + + declaration + ": " + str(exc)) + self.assertEqual(type_record["declaration"], declaration) + self.assertTrue(type_record["type_expr_repr"]) + self.assertLessEqual(set(axioms), TRUSTED_AXIOMS) + + def test_X_04_original_source_positive(self): + self.check_original_case("X-04") + + def test_X_05_original_source_positive(self): + self.check_original_case("X-05") + + def test_X_06_original_source_positive(self): + self.check_original_case("X-06") + + def test_F_03_original_source_positive(self): + self.check_original_case("F-03") + + def test_A_05_original_source_positive(self): + self.check_original_case("A-05") + + def test_C11_02_original_source_positive(self): + self.check_original_case("C11-02") + + def test_B_01_original_source_positive(self): + self.check_original_case("B-01") + + def test_E_02_original_source_positive(self): + self.check_original_case("E-02") + + def test_E_03_original_source_positive(self): + self.check_original_case("E-03") + + def test_E_04_original_source_positive(self): + self.check_original_case("E-04") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_more_false_semantics_lean.py b/verification/research/test_final_closure_more_false_semantics_lean.py new file mode 100644 index 00000000..280bdc30 --- /dev/null +++ b/verification/research/test_final_closure_more_false_semantics_lean.py @@ -0,0 +1,97 @@ +"""Further source-grounded semantic false claims: F-02/F-04/F-05/Q-02. + +Mathematical falsehoods: a frozen nonstationary real clock cannot stall; +a genuinely correlated Bernoulli joint action cannot be a product measure; +two observationally identical SCMs need not have equal joint interventions; +and zero transition mismatch does not erase a nonzero initial-law budget. +The probes require real pinned Lean and reject invalid diagnostic shortcuts. +""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +INVALID = re.compile( + r"unknown (?:module|identifier|constant|tactic)|failed to import|" + r"unexpected token|parse error|parser error|out of memory|" + r"maximum number of heartbeats|failed to synthesize|" + r"object file|timed out", re.I) +SEMANTIC = re.compile( + r"type mismatch|unsolved goals|tactic .?(?:simp|norm_num|exact).? failed|" + r"application type mismatch|tactic .?decide.? proved", re.I) + + +def check_false_source(module, body): + if shutil.which("lake") is None: + raise AssertionError("pinned Lean required") + with tempfile.TemporaryDirectory(prefix="strict-negative-", dir=ROOT/".lake") as td: + path = Path(td)/"False.lean" + path.write_text("import " + module + "\n" + body, encoding="utf-8") + r = subprocess.run(["lake", "env", "lean", str(path)], + cwd=ROOT, text=True, capture_output=True, timeout=900) + return r.returncode, r.stdout + "\n" + r.stderr + + +class MoreActualSourceNegatives(unittest.TestCase): + def fail_mathematically(self, module, body): + status, out = check_false_source(module, body) + self.assertNotEqual(status, 0, "original false claim was accepted") + self.assertIn("error:", out.lower()) + self.assertIsNone(INVALID.search(out), out[-4000:]) + self.assertIsNotNone(SEMANTIC.search(out), out[-4000:]) + + def test_F_02_false_stalled_original_natural_clock(self): + self.fail_mathematically( + "PermanssonResearch.MultiAgent.MeasurableNatClockFixture", + "open MeasureTheory Set\n" + "open PermanssonResearch.MultiAgent.MeasurableNatPrivateClock\n" + "open PermanssonResearch.MultiAgent.MeasurableNatPrivateClock.RealFixture\n" + "example (y : OldState (S0 := ℝ) (S1 := ℝ) (S2 := ℝ) (X := ℝ)) :\n" + " (anchor schedule Nat.succ).update\n" + " ((lift (0 : ℕ) y,(0,0)),0) (Set.univ ×ˢ {(0 : ℕ)}) = 1 := by\n" + " exact stalled_clock_rejected 0 y\n" + ) + + def test_F_04_false_product_representation_of_correlated_law(self): + self.fail_mathematically( + "PermanssonResearch.MultiAgent.ProtocolWitnesses", + "import PermanssonResearch.X1.TypedModels\n" + "open MeasureTheory ProbabilityTheory\n" + "open PermanssonResearch.MultiAgent\n" + "open PermanssonResearch.MultiAgent.Witness\n" + "open PermanssonResearch.X1\n" + "example : correlatedLaw = (coin (1/2)).prod (coin (1/2)) := by\n" + " haveI : IsProbabilityMeasure (coin (1/2)) :=\n" + " coin_probability _ (by norm_num)\n" + " exact correlatedLaw_ne_prod (coin (1/2)) (coin (1/2))\n" + ) + + def test_F_05_false_identification_of_original_joint_do(self): + self.fail_mathematically( + "PermanssonResearch.MultiAgent.FiniteMediation", + "open PermanssonResearch.MultiAgent.FiniteMediation\n" + "example : (nonidentificationSCM false).response () true false =\n" + " (nonidentificationSCM true).response () true false := by\n" + " obtain ⟨hfalse,htrue⟩ := two_SCMs_different_joint_do\n" + " rw [hfalse,htrue]\n" + " norm_num\n" + ) + + def test_Q_02_false_zero_initial_error_under_zero_row_defect(self): + self.fail_mathematically( + "PermanssonResearch.Probability.DiscountedInitialBudget", + "open PermanssonResearch.Probability\n" + "example : (∑' n : ℕ, (1-(1/3 : ℝ))*(1/3 : ℝ)^n *\n" + " (1-(1-(1/5 : ℝ))*(1-(0 : ℝ))^n)) = 0 := by\n" + " rw [zero_row_defect_retains_initial_budget\n" + " (show 0 ≤ (1/3 : ℝ) by norm_num)\n" + " (show (1/3 : ℝ) < 1 by norm_num)]\n" + " norm_num\n" + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_mutation_evidence.py b/verification/research/test_final_closure_mutation_evidence.py new file mode 100644 index 00000000..651b983c --- /dev/null +++ b/verification/research/test_final_closure_mutation_evidence.py @@ -0,0 +1,222 @@ +"""Mutation-evidence logic tests (compiler results mocked; not real proof receipts).""" +from pathlib import Path +import tempfile +import subprocess +import types +import unittest +from unittest import mock + +import final_closure_mutation_evidence as m + + +def result(rc, log): + return types.SimpleNamespace(returncode=rc, stdout=log, stderr="") + + +class MutantEvidenceTests(unittest.TestCase): + def test_true_control_and_semantically_false_mutant(self): + source = b"import Valid.Module\ntheorem t : True := by trivial\n" + wrong = b"import Valid.Module\ntheorem t : False := by trivial\n" + self.assertTrue(m.verify_negative( + result(0, "no errors"), + result(1, "error: unsolved goals\n \u22a2 False"), source, wrong)) + + def test_bad_positive_and_parser_heartbeat_are_rejected(self): + source = b"import Valid.Module\ntheorem t : True := by trivial\n" + wrong = b"import Valid.Module\ntheorem t : False := by trivial\n" + failures = ( + (result(1, "error: unsolved goals"), result(1, "error: unsolved goals")), + (result(0, "ok"), result(1, "error: parser error\nunsolved goals")), + (result(0, "ok"), result(1, "error: unknown identifier\nunsolved goals")), + (result(0, "ok"), result(1, "error: maximum number of heartbeats\nunsolved goals")), + ) + for positive, negative in failures: + with self.assertRaises(m.MutationEvidenceError): + m.verify_negative(positive, negative, source, wrong) + + def test_same_source_or_changed_import_not_accepted_as_semantic_mutant(self): + src = b"import Valid.Module\ntheorem t : True := by trivial\n" + for invalid in (src, b"import Other.Module\ntheorem t : False := by trivial\n"): + with self.assertRaises(m.MutationEvidenceError): + m.verify_negative(result(0, "ok"), result(1, "error: unsolved goals"), + src, invalid) + + def test_detached_bundle_bytes_reproducible_across_output_paths(self): + with tempfile.TemporaryDirectory() as root: + base = Path(root) + logs = base/"logs" + logs.mkdir() + (logs/"b.log").write_text("negative diagnostic") + (logs/"a.log").write_text("positive control") + first = base/"one.zip" + second = base/"two.zip" + self.assertEqual(m.seal_logs(logs, first), m.seal_logs(logs, second)) + self.assertEqual(first.read_bytes(), second.read_bytes()) + + + def test_git_blob_binding_refuses_dirty_and_untracked_source(self): + with tempfile.TemporaryDirectory(prefix="pr113-git-bound-") as td: + root = Path(td) + subprocess.run(["git", "init", "-q"], cwd=root, check=True) + source = root / "proof.lean" + original = b"import Mathlib.Tactic\\ntheorem fact : True := by trivial\\n" + source.write_bytes(original) + subprocess.run(["git", "add", "proof.lean"], cwd=root, check=True) + subprocess.run([ + "git", "-c", "user.name=ClosureAudit", + "-c", "user.email=audit@example.invalid", + "commit", "-qm", "source" + ], cwd=root, check=True) + head = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=root, text=True).strip() + self.assertEqual( + m.committed_file_bytes(head, source, root=root), original) + untracked = root / "untracked.lean" + untracked.write_bytes(original) + with self.assertRaises(m.MutationEvidenceError): + m.committed_file_bytes(head, untracked, root=root) + source.write_bytes(original + b"-- false local alteration\\n") + with self.assertRaises(m.MutationEvidenceError): + m.committed_file_bytes(head, source, root=root) + source.unlink() + source.symlink_to(untracked) + with self.assertRaises(m.MutationEvidenceError): + m.committed_file_bytes(head, source, root=root) + + + def test_producer_rechecks_source_after_compiler_run(self): + # Deliberate dirty-worktree race, with mocked Lean results only. + # Success here is a validator regression, not mathematical evidence. + with tempfile.TemporaryDirectory(prefix="pr113-source-race-") as tmp: + base = Path(tmp) + repo = base / "repo" + repo.mkdir() + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + source = repo / "PermanssonResearch" / "Proof.lean" + source.parent.mkdir(parents=True) + original = b"import Mathlib.Tactic\ntheorem positive : True := by trivial\n" + source.write_bytes(original) + test = repo / "verification" / "research" / "test_case.py" + test.parent.mkdir(parents=True) + test.write_text("def test_boundary():\n pass\n") + subprocess.run(["git", "add", "."], cwd=repo, check=True) + subprocess.run([ + "git", "-c", "user.name=ClosureAudit", + "-c", "user.email=audit@example.invalid", + "commit", "-qm", "frozen candidate" + ], cwd=repo, check=True) + head = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip() + mutant = base / "mutant.lean" + mutant.write_bytes(original + b"example : False := by trivial\n") + out = base / "detached-evidence" + + def fake_compile(path): + if path == mutant: + source.write_bytes(original + b"-- changed during compilation\n") + return result(1, "error: type mismatch") + return result(0, "positive compilation simulated") + + with ( + mock.patch.object(m, "ROOT", repo), + mock.patch.object(m, "git_head", return_value=head), + mock.patch.object(m.shutil, "which", return_value="/fake/lake"), + mock.patch.object(m, "pinned_lean_version", return_value="4.34.0"), + mock.patch.object(m, "lean_result", side_effect=fake_compile), + ): + with self.assertRaises(m.MutationEvidenceError): + m.produce( + head, true_source=source, mutant=mutant, test_path=test, + test_name="test_boundary", case_id="T-01", claim_ids=[], + out=out) + self.assertFalse(out.exists()) + + + def test_true_original_provider_bytes_are_retained_in_detached_logs(self): + # Compiler results are mocked; Git tracked bytes are NOT mocked. + with tempfile.TemporaryDirectory(prefix="pr113-both-bytes-") as td: + base = Path(td) + repo = base / "repo" + repo.mkdir() + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + source = repo / "PermanssonResearch" / "Actual.lean" + source.parent.mkdir() + original = (b"import Mathlib.Tactic\n" + b"theorem original : True := by trivial\n") + source.write_bytes(original) + test = repo / "verification" / "research" / "test_actual.py" + test.parent.mkdir(parents=True) + test.write_text("def test_original_mutation():\n pass\n") + subprocess.run(["git", "add", "."], cwd=repo, check=True) + subprocess.run([ + "git", "-c", "user.name=ClosureAudit", + "-c", "user.email=audit@example.invalid", + "commit", "-qm", "frozen provider" + ], cwd=repo, check=True) + head = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=repo, + text=True).strip() + mutant = base / "false.lean" + false_bytes = original + b"\nexample : False := by trivial\n" + mutant.write_bytes(false_bytes) + out = base / "detached" + def compile_fixture(path): + return (result(1, "error: type mismatch") + if path == mutant else result(0, "clean Lean source")) + with ( + mock.patch.object(m, "ROOT", repo), + mock.patch.object(m, "git_head", return_value=head), + mock.patch.object(m.shutil, "which", return_value="/fake/lake"), + mock.patch.object(m, "pinned_lean_version", return_value="4.34.0"), + mock.patch.object(m, "lean_result", side_effect=compile_fixture), + ): + record = m.produce( + head, true_source=source, mutant=mutant, + test_path=test, test_name="test_original_mutation", + case_id="X-01", claim_ids=[], out=out) + preserved = out / record["original_lean_source_member"] + self.assertEqual(preserved.read_bytes(), original) + self.assertEqual( + record["original_lean_source_sha256"], m.hash_bytes(original)) + self.assertEqual( + (out / record["mutant_source_member"]).read_bytes(), false_bytes) + self.assertIsNone(record["check_run_id"]) + + + def test_verified_running_compiler_must_match_frozen_pin(self): + head = "a" * 40 + with ( + mock.patch.object(m, "committed_file_bytes", + return_value=b"leanprover/lean4:v4.34.0\n"), + mock.patch.object(m.subprocess, "run", + return_value=result(0, "Lean (version 4.34.0, test)")), + ): + self.assertEqual(m.pinned_lean_version(head), "4.34.0") + with ( + mock.patch.object(m, "committed_file_bytes", + return_value=b"leanprover/lean4:v4.34.0\n"), + mock.patch.object(m.subprocess, "run", + return_value=result(0, "Lean (version 4.33.0, test)")), + ): + with self.assertRaises(m.MutationEvidenceError): + m.pinned_lean_version(head) + with ( + mock.patch.object(m, "committed_file_bytes", + return_value=b"leanprover/lean4:v4.35.0\n"), + mock.patch.object(m.subprocess, "run", + return_value=result(0, "Lean (version 4.34.0, test)")), + ): + with self.assertRaises(m.MutationEvidenceError): + m.pinned_lean_version(head) + with ( + mock.patch.object(m, "committed_file_bytes", + return_value=b"leanprover/lean4:v4.34.0\n"), + mock.patch.object(m.subprocess, "run", + return_value=result(1, "compiler failed")), + ): + with self.assertRaises(m.MutationEvidenceError): + m.pinned_lean_version(head) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_original_semantic_batch.py b/verification/research/test_final_closure_original_semantic_batch.py new file mode 100644 index 00000000..c16015c5 --- /dev/null +++ b/verification/research/test_final_closure_original_semantic_batch.py @@ -0,0 +1,157 @@ +"""Adversarial byte/source-contract tests for the unreviewed case runner. + +No test uses a mocked compiler result as a mathematical proof. The mock only +checks I/O, allowlists and anti-forgery contract behavior. +""" +import copy +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +import final_closure_original_semantic_batch as batch + +H = "a" * 40 + + +class FrozenOriginalCaseMutations(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory(prefix="perm-case-source-") + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) / "synthetic-repository" + self.root.mkdir() + src = batch.SPECS["F-03"] + self.source_path = self.root / src["source"] + self.source_path.parent.mkdir(parents=True) + self.source_path.write_bytes( + b"import Mathlib.Tactic\n\nnamespace Fixture\n" + b"theorem genuine : True := by trivial\nend Fixture\n" + ) + test = self.root / src["test_path"] + test.parent.mkdir(parents=True) + test.write_text( + "def test_false_feasible_anchor_with_conflicting_own_fibre_rejected():\n" + " pass\n" + ) + mapping = self.root / "research/paper_closures/post_merge_112" + mapping.mkdir(parents=True) + row = { + "case_id": "F-03", "effective_accepted": False, + "test_method_candidates": [{ + "path": src["test_path"], "method": src["method"], + "role": "SEMANTIC_NEGATIVE", + "status": "DISCOVERED_UNREVIEWED_NOT_EXECUTION_EVIDENCE", + }], + } + rows = [row] + [ + {"case_id": f"T-{i:02}", "effective_accepted": False, + "test_method_candidates": []} + for i in range(44) + ] + self.map = {"original_cases": 45, "cases": rows} + self.map_path = mapping / "acceptance_case_source_map.json" + self.save() + + def save(self): + self.map_path.write_text(json.dumps(self.map)) + + def test_original_source_bytes_are_preserved_and_no_import_was_added(self): + entry, mutant = batch.frozen_spec("F-03", root=self.root) + original = self.source_path.read_bytes() + self.assertEqual(entry["source"], batch.SPECS["F-03"]["source"]) + self.assertTrue(mutant.startswith(original)) + self.assertNotEqual(mutant, original) + self.assertEqual(mutant.count(b"import "), original.count(b"import ")) + self.assertIn(b"no_feasible_original_strict_anchor", mutant) + self.assertNotIn(b"\nsorry", mutant) + self.assertNotIn(b"\naxiom", mutant) + + def test_wrong_method_role_or_original_id_fails_closed(self): + original = copy.deepcopy(self.map) + for mutation in ("wrong_role", "wrong_name", "no_case", + "pretend_approved", "wrong_total"): + self.map = copy.deepcopy(original) + if mutation == "wrong_role": + self.map["cases"][0]["test_method_candidates"][0]["role"] = "SCOPE_BOUNDARY" + elif mutation == "wrong_name": + self.map["cases"][0]["test_method_candidates"][0]["method"] = "test_unrelated" + elif mutation == "no_case": + self.map["cases"][0]["case_id"] = "C-OTHER" + elif mutation == "pretend_approved": + self.map["cases"][0]["effective_accepted"] = True + else: + self.map["original_cases"] = 44 + self.save() + with self.subTest(mutation=mutation), self.assertRaises( + batch.MutationEvidenceError + ): + batch.frozen_spec("F-03", root=self.root) + + def test_source_symlink_and_unregistered_case_rejected(self): + with self.assertRaises(batch.MutationEvidenceError): + batch.frozen_spec("D-999", root=self.root) + self.source_path.unlink() + outside = Path(self.tmp.name) / "outside" + outside.write_bytes(b"theorem unrelated : True := by trivial") + self.source_path.symlink_to(outside) + with self.assertRaises(batch.MutationEvidenceError): + batch.frozen_spec("F-03", root=self.root) + + def test_missing_test_or_new_import_rejected(self): + original = copy.deepcopy(batch.SPECS["F-03"]) + with mock.patch.dict( + batch.SPECS["F-03"], + {"body": "import Mathlib.Tactic\nexample : False := by trivial"}, + ): + with self.assertRaises(batch.MutationEvidenceError): + batch.frozen_spec("F-03", root=self.root) + test = self.root / original["test_path"] + test.unlink() + with self.assertRaises(batch.MutationEvidenceError): + batch.frozen_spec("F-03", root=self.root) + + def test_mock_runner_cannot_claim_ci_id_or_independent_review(self): + output = Path(self.tmp.name) / "detached-output" + entry = copy.deepcopy(batch.SPECS["F-03"]) + with mock.patch.object(batch, "git_head", return_value=H), \ + mock.patch.object(batch, "frozen_spec", + return_value=(entry, b"import A\nexample : False := by trivial")), \ + mock.patch.object(batch, "produce", + return_value={"check_run_id": None, + "outcome": "REJECTED_SEMANTIC"}) as producer, \ + mock.patch.object(batch, "seal_logs", return_value="e" * 64) as seal: + result = batch.run_one(H, "F-03", output) + self.assertTrue(producer.called) + self.assertTrue(seal.called) + self.assertEqual(result["status"], "DETACHED_UNREVIEWED_UNATTRIBUTED") + self.assertIsNone(result["ci_check_run_id"]) + self.assertFalse(result["independent_review"]) + self.assertFalse(result["full_mathematical_acceptance"]) + self.assertTrue(output.is_dir()) + with self.assertRaises(batch.MutationEvidenceError): + batch.run_one("not-a-commit", "F-03", output) + + def test_all_twenty_five_spec_ids_and_method_owners_are_distinct(self): + self.assertEqual( + set(batch.SPECS), + {"F-03", "D-02", "D-03", "D-04", "D-05", + "D-06", "Q-03", "Q-04", "E-05", + "A-03", "C-01", "D-07", "E-01", + "S-01", "M-01", "A-02", "D-01", + "C11-01", "C11-02", "XPLUS-01", + "S-02", "Q-01", "A-01", + "A-04", "A-05", + } + ) + seen = set() + for cid, entry in batch.SPECS.items(): + marker = (entry["test_path"], entry["method"]) + self.assertNotIn(marker, seen) + seen.add(marker) + self.assertTrue(entry["source"].startswith("PermanssonResearch/")) + self.assertEqual(Path(entry["source"]).suffix, ".lean") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_positive_route_candidate.py b/verification/research/test_final_closure_positive_route_candidate.py new file mode 100644 index 00000000..907ab9c2 --- /dev/null +++ b/verification/research/test_final_closure_positive_route_candidate.py @@ -0,0 +1,60 @@ +"""Positive-method route selection is never interchangeable with negative.""" +import copy +import unittest + +import final_closure_positive_route_candidate as positive + +CASEIDS=[f"C-{i:02d}" for i in range(1,46)] + +def fixture(): + cases=[] + rows=[] + for n,cid in enumerate(CASEIDS): + test_path=f"verification/research/test_case_{n}.py" + neg_path=f"verification/research/test_false_{n}.py" + method=f"test_positive_{n}" + proof_path=f"PermanssonResearch/Case_{n}.lean" + cases.append({ + "case_id":cid, + "historical_proof_declarations":[ + {"path":proof_path,"name":f"Case_{n}.trueStatement"}], + "test_method_candidates":[ + {"path":test_path,"method":method,"role":"EXACT_CHECK"}, + {"path":neg_path,"method":f"test_false_{n}", + "role":"SEMANTIC_NEGATIVE"}], + }) + rows.append({"case_id":cid,"test_path":test_path, + "test_class":"ScopeTests","test_method":method, + "proof_source_path":proof_path}) + return {"format":positive.SELECTION,"cases":rows},{"cases":cases} + +class OriginalPositiveRoutesTests(unittest.TestCase): + def test_all_forty_five_correct_positive_roles(self): + mapped,scope=fixture() + result=positive.parse_selection(mapped,scope) + self.assertEqual(set(result),set(CASEIDS)) + self.assertEqual(len(result),45) + + def test_cannot_promote_semantic_negative_to_positive_role(self): + mapped,scope=fixture() + mapped["cases"][0]["test_path"]=scope["cases"][0]["test_method_candidates"][1]["path"] + mapped["cases"][0]["test_method"]=scope["cases"][0]["test_method_candidates"][1]["method"] + with self.assertRaises(positive.PositiveRouteError): + positive.parse_selection(mapped,scope) + + def test_missing_or_alien_selected_lean_owner_never_valid(self): + changes=[ + lambda m,s:m["cases"].pop(), + lambda m,s:m["cases"][0].update( + proof_source_path="PermanssonResearch/Other.lean"), + lambda m,s:m["cases"][0].update(case_id="MISSING-CASE"), + lambda m,s:m["cases"][0].update(test_method="test_unregistered"), + lambda m,s:m["cases"][0].update(test_class=""), + lambda m,s:m["cases"][0].update(approved=True), + ] + for idx,change in enumerate(changes): + with self.subTest(index=idx): + mapped,scope=fixture() + change(mapped,scope) + with self.assertRaises(positive.PositiveRouteError): + positive.parse_selection(mapped,scope) diff --git a/verification/research/test_final_closure_postseal_auth.py b/verification/research/test_final_closure_postseal_auth.py new file mode 100644 index 00000000..beddbf3a --- /dev/null +++ b/verification/research/test_final_closure_postseal_auth.py @@ -0,0 +1,207 @@ +"""Postseal GitHub job/ZIP authentication adversaries; mock-only not approval.""" +import io +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock +import zipfile + +import final_closure_postseal_auth as auth + +M="a"*40 +PDF="b"*64 +IDS={"run_id":101,"run_attempt":1,"job_id":102, + "check_run_id":103,"artifact_id":104, + "upstream_run_id":201,"upstream_run_attempt":2,"upstream_job_id":202, + "upstream_check_run_id":203,"upstream_artifact_id":204} +UPSTREAM_DIGEST="e"*64 + +def upstream_metadata(): + return {"id":204,"name":auth.UPSTREAM_ARTIFACT,"expired":False, + "digest":"sha256:"+UPSTREAM_DIGEST, + "created_at":"2026-10-24T08:05:00Z", + "workflow_run":{"id":201,"head_sha":M}} + +def artifact_bytes(metadata=None): + out=io.BytesIO() + with zipfile.ZipFile(out,"w") as z: + z.writestr(auth.UPSTREAM_METADATA,json.dumps( + upstream_metadata() if metadata is None else metadata)) + z.writestr("candidate/Permansson_Mathematical_Closure_CANDIDATE.zip", + b"mock candidate") + z.writestr("candidate/SHA256SUMS.txt",b"mock sidecar") + z.writestr("postseal_replay_receipt.json",json.dumps({ + "format":"Permansson.MathClosure.PostsealReplayCandidate.v1", + "status":"CANDIDATE_ARCHIVE_REPLAYED_UNREVIEWED", + "source_commit_sha":M,"head_sha":"c"*40, + "pdf_sha256":PDF, + "archive_sha256":auth.sha(b"mock candidate"), + "complete_original_members_verified":True, + "authenticated_ci_artifact":False, + "independent_review":False,"full_release_approved":False, + })) + return out.getvalue() + +class PostsealAuthTests(unittest.TestCase): + def setUp(self): + tmp=tempfile.TemporaryDirectory() + self.addCleanup(tmp.cleanup) + self.file=Path(tmp.name)/"original.zip" + self.file.write_bytes(artifact_bytes()) + self.run={"id":101,"head_sha":M,"head_branch":"main", + "event":"workflow_dispatch","run_attempt":1, + "status":"completed","conclusion":"success", + "path":auth.WORKFLOW, + "run_started_at":"2026-10-24T09:00:00Z"} + self.job={"id":102,"run_id":101,"head_sha":M,"run_attempt":1, + "name":auth.JOB,"status":"completed","conclusion":"success", + "started_at":"2026-10-24T09:00:10Z", + "completed_at":"2026-10-24T09:20:00Z", + "check_run_url":"https://api.github.com/repos/HMarcusWH/PermanssonLean/check-runs/103"} + self.check={"id":103,"head_sha":M,"name":auth.JOB, + "status":"completed","conclusion":"success", + "app":{"slug":"github-actions"}} + self.artifact={"id":104,"name":auth.ARTIFACT,"expired":False, + "digest":"sha256:"+auth.sha(self.file.read_bytes()), + "created_at":"2026-10-24T09:19:00Z", + "workflow_run":{"id":101,"head_sha":M}} + self.up_run={"id":201,"head_sha":M,"head_branch":"main", + "event":"push","run_attempt":2, + "status":"completed","conclusion":"success", + "path":auth.UPSTREAM_WORKFLOW, + "updated_at":"2026-10-24T08:10:00Z"} + self.up_job={"id":202,"run_id":201,"head_sha":M,"run_attempt":2, + "name":auth.UPSTREAM_JOB,"status":"completed", + "conclusion":"success", + "started_at":"2026-10-24T08:00:00Z", + "completed_at":"2026-10-24T08:06:00Z", + "check_run_url":"https://api.github.com/repos/HMarcusWH/PermanssonLean/check-runs/203"} + self.up_check={"id":203,"head_sha":M,"name":auth.UPSTREAM_JOB, + "status":"completed","conclusion":"success", + "app":{"slug":"github-actions"}} + self.up_artifact=upstream_metadata() + + def invoke(self): + api={"/actions/runs/101":self.run,"/check-runs/103":self.check, + "/actions/artifacts/104":self.artifact, + "/actions/runs/201":self.up_run,"/check-runs/203":self.up_check, + "/actions/artifacts/204":self.up_artifact} + jobs={"/actions/runs/101/jobs":[self.job], + "/actions/runs/201/jobs":[self.up_job]} + with mock.patch.object(auth,"live",side_effect=lambda path:api[path]), \ + mock.patch.object(auth,"paginate", + side_effect=lambda path,key:jobs[path]), \ + mock.patch.object(auth,"validate_postseal_payload", + return_value={"archive_sha256":auth.sha(b"mock candidate")}): + return auth.authenticate_postseal(M,PDF,IDS,self.file) + + def test_reproduced_archive_receipt_still_has_no_scholarly_authority(self): + observed=self.invoke() + self.assertEqual(observed["status"],"COMPLETE_ARCHIVE_REPLAY") + self.assertTrue(observed["authenticated_ci_artifact"]) + self.assertFalse(observed["independent_scholarly_approval"]) + self.assertFalse(observed["mathematical_release_approved"]) + self.assertEqual(observed["upstream_preseal"]["run_id"],201) + self.assertEqual(observed["upstream_preseal"]["artifact_sha256"], + UPSTREAM_DIGEST) + + def test_failed_or_stale_upstream_producer_cannot_be_certified(self): + # Each upstream artifact keeps an authentic digest; only the + # producer run/job/check/attempt identity distinguishes it. + for name,edit in ( + ("run-failed",lambda: self.up_run.update(conclusion="failure")), + ("run-cancelled",lambda: self.up_run.update(conclusion="cancelled")), + ("run-in-progress",lambda: self.up_run.update(status="in_progress")), + ("run-wrong-workflow",lambda: self.up_run.update( + path=".github/workflows/research-lean.yml")), + ("run-wrong-head",lambda: self.up_run.update(head_sha="f"*40)), + ("run-pull-request",lambda: self.up_run.update(event="pull_request")), + ("run-rerun-attempt",lambda: self.up_run.update(run_attempt=3)), + ("job-failed",lambda: self.up_job.update(conclusion="failure")), + ("job-other-attempt",lambda: self.up_job.update(run_attempt=1)), + ("job-renamed",lambda: self.up_job.update(name="other")), + ("check-failed",lambda: self.up_check.update(conclusion="failure")), + ("artifact-other-run",lambda: self.up_artifact.update( + workflow_run={"id":999,"head_sha":M})), + ("artifact-expired",lambda: self.up_artifact.update(expired=True)), + ("artifact-earlier-attempt",lambda: self.up_artifact.update( + created_at="2026-10-24T07:00:00Z")), + ("artifact-after-job",lambda: self.up_artifact.update( + created_at="2026-10-24T08:07:00Z")), + ("finished-after-postseal",lambda: self.up_run.update( + updated_at="2026-10-24T09:30:00Z")), + ): + with self.subTest(fault=name): + self.setUp() + edit() + with self.assertRaises(auth.PostsealAuthenticationError): + self.invoke() + + def test_upstream_identity_is_mandatory_and_distinct(self): + global IDS + original=IDS + try: + for key in ("upstream_run_id","upstream_check_run_id", + "upstream_artifact_id"): + with self.subTest(missing=key): + IDS={k:v for k,v in original.items() if k!=key} + with self.assertRaises(auth.PostsealAuthenticationError): + self.invoke() + IDS={**original,"upstream_run_id":101} + with self.assertRaises(auth.PostsealAuthenticationError): + self.invoke() + finally: + IDS=original + + def test_postseal_must_have_replayed_the_authenticated_upstream(self): + for name,meta in ( + ("digest",{**upstream_metadata(),"digest":"sha256:"+"0"*64}), + ("artifact",{**upstream_metadata(),"id":205}), + ("run",{**upstream_metadata(),"workflow_run":{"id":999,"head_sha":M}}), + ): + with self.subTest(fault=name): + self.setUp() + self.file.write_bytes(artifact_bytes(meta)) + self.artifact["digest"]="sha256:"+auth.sha(self.file.read_bytes()) + with self.assertRaises(auth.PostsealAuthenticationError): + self.invoke() + + def test_borrowed_or_unreviewed_workflow_does_not_promote(self): + for name,field,value in ( + ("branch","run","wrong"), + ("head","run","wrong"), + ("event","run","push"), + ("attempt","run",2), + ("job","job","failure"), + ("check","check","failure"), + ("digest","artifact","sha256:"+"f"*64), + ): + with self.subTest(fault=name): + self.setUp() + if name=="branch":self.run["head_branch"]=value + elif name=="head":self.run["head_sha"]=value + elif name=="event":self.run["event"]=value + elif name=="attempt":self.run["run_attempt"]=value + elif name=="job":self.job["conclusion"]=value + elif name=="check":self.check["conclusion"]=value + elif name=="digest":self.artifact["digest"]=value + with self.assertRaises(auth.PostsealAuthenticationError): + self.invoke() + + def test_local_replay_flags_cannot_substitute_for_nested_bytes(self): + entries={ + "postseal_replay_receipt.json":json.dumps({ + "format":"Permansson.MathClosure.PostsealReplayCandidate.v1", + "status":"FULL_MATHEMATICAL_CLOSURE_RELEASE_READY", + "source_commit_sha":M,"pdf_sha256":PDF, + "authenticated_ci_artifact":True}).encode(), + "candidate/Permansson_Mathematical_Closure_CANDIDATE.zip": + b"fake archive", + "candidate/SHA256SUMS.txt":b"fake", + } + with self.assertRaises(auth.PostsealAuthenticationError): + auth.validate_postseal_payload(entries,merged=M,expected_pdf=PDF) + entries.pop("candidate/SHA256SUMS.txt") + with self.assertRaises(auth.PostsealAuthenticationError): + auth.validate_postseal_payload(entries,merged=M,expected_pdf=PDF) diff --git a/verification/research/test_final_closure_producer_bytes.py b/verification/research/test_final_closure_producer_bytes.py new file mode 100644 index 00000000..c3a4de3e --- /dev/null +++ b/verification/research/test_final_closure_producer_bytes.py @@ -0,0 +1,125 @@ +"""Mock adversaries for real producer-byte authentication; not real review.""" +import io +import json +import unittest +from unittest import mock +import zipfile +import final_closure_producer_bytes as unit +from final_closure_candidate_artifact_auth import KINDS + +H = "a"*40 + +def zip_payload(members): + f = io.BytesIO() + with zipfile.ZipFile(f, "w") as z: + for name, body in members.items(): + z.writestr(name, body) + return f.getvalue() + +def fixture(): + index, binary, urls, jobs = [], {}, {}, {} + for kind in ("compiler", "append", "legacy"): + spec = KINDS[kind] + for shard in range(spec["count"]): + seq = len(index)+1 + run, job, check, artifact = ( + 10000+seq, 20000+seq, 30000+seq, 40000+seq) + manifest = json.dumps({"kind":kind,"shard":shard}).encode() + raw = zip_payload({spec["manifest"]:manifest, + "source.log":b"actual source evidence"}) + rec = { + "format":"Permansson.PR113.AuthenticatedProducerArtifact.v1", + "status":"CI_ARTIFACT_DIGEST_VERIFIED_UNREVIEWED", + "head_sha":H, "workflow_path":spec["workflow"], + "run_id":run, "run_attempt":1, "job_id":job, + "actual_check_run_id":check, "artifact_id":artifact, + "artifact_name":spec["artifact"]+str(shard), + "artifact_sha256":unit.sha(raw), + "manifest_sha256":unit.sha(manifest), + "mathematical_acceptance":False, + "independent_review":False, "scientific_release_approved":False, + "merged_main_sha":None, + } + index.append(rec) + binary[artifact]=raw + urls[f"/actions/runs/{run}"]={ + "id":run,"head_sha":H,"run_attempt":1, + "status":"completed","conclusion":"success", + "event":"push","path":spec["workflow"], + "head_branch":"research/final-mathematical-closure"} + jobs[run]=[{ + "id":job, "name":spec["job"]+f" ({shard})", + "head_sha":H,"run_attempt":1,"run_id":run, + "status":"completed","conclusion":"success", + "check_run_url":f"https://api.github.com/repos/HMarcusWH/PermanssonLean/check-runs/{check}"}] + urls[f"/check-runs/{check}"]={ + "id":check,"head_sha":H,"name":spec["job"]+f" ({shard})", + "status":"completed","conclusion":"success", + "app":{"slug":"github-actions"}} + urls[f"/actions/artifacts/{artifact}"]={ + "id":artifact,"name":spec["artifact"]+str(shard), + "expired":False,"digest":"sha256:"+unit.sha(raw), + "workflow_run":{"id":run,"head_sha":H}} + return index, binary, urls, jobs + +class ProducerByteTests(unittest.TestCase): + def execute(self, mutate=None): + index, binary, urls, jobs = fixture() + if mutate:mutate(index,binary,urls,jobs) + with mock.patch.object(unit, "live", side_effect=lambda p:urls[p]), \ + mock.patch.object(unit, "paginate", + side_effect=lambda p, key: jobs[int(p.split("/")[3])]): + return unit.authenticate(H,index,binary) + + def test_exact_21_producers_are_not_review_or_release(self): + record=self.execute() + self.assertEqual(record["producer_count"],21) + self.assertEqual(record["accepted_cases"],0) + self.assertFalse(record["release_approved"]) + for artifact,p in record["producers"].items(): + got=unit.original_member( + record,artifact,p["check_run_id"], + "source.log",unit.sha(b"actual source evidence")) + self.assertEqual(got,b"actual source evidence") + + def test_stale_job_borrowed_check_and_zip_tamper_reject(self): + changes=[ + lambda a,b,c,d:b.pop(40001), + lambda a,b,c,d:a[0].update( + actual_check_run_id=a[1]["actual_check_run_id"]), + lambda a,b,c,d:c["/actions/runs/10001"].update(head_sha="b"*40), + lambda a,b,c,d:b.update({40001:b"tampered"}), + ] + for n,change in enumerate(changes): + with self.subTest(n=n),self.assertRaises( + (unit.ProducerBytesError,ValueError)): + self.execute(change) + + def test_wrong_workflow_branch_or_incomplete_job_rejected(self): + faults=[ + lambda a,b,c,d:c["/actions/runs/10001"].update( + head_branch="untrusted-branch"), + lambda a,b,c,d:d[10001][0].update(status="queued"), + lambda a,b,c,d:d[10001][0].update(run_id=10002), + ] + for idx,change in enumerate(faults): + with self.subTest(idx=idx),self.assertRaises( + (unit.ProducerBytesError,ValueError)): + self.execute(change) + + def test_altered_receipt_member_and_borrowed_ids_reject(self): + record=self.execute() + for check,hash_ in [(30002,unit.sha(b"actual source evidence")), + (30001,"f"*64)]: + with self.subTest(check=check),self.assertRaises(unit.ProducerBytesError): + unit.original_member(record,40001,check,"source.log",hash_) + + def test_zip_duplicate_and_unsafe_names_reject(self): + for contents in [ + {"../bad":b"x"},{"../bad2":b"x"}, + {"dir/FILE":b"x","dir/file":b"x"}, + {"same":b"file","same/child":b"invalid ancestor"}, + ]: + with self.subTest(contents=list(contents)),self.assertRaises( + unit.ProducerBytesError): + unit.inspect_zip(zip_payload(contents)) diff --git a/verification/research/test_final_closure_producer_catalog.py b/verification/research/test_final_closure_producer_catalog.py new file mode 100644 index 00000000..c994a084 --- /dev/null +++ b/verification/research/test_final_closure_producer_catalog.py @@ -0,0 +1,104 @@ +"""Pure adversarial producer-catalog tests; none is real GitHub attestation.""" +import ast +import copy +from pathlib import Path +import unittest + +from final_closure_producer_catalog import entries, ProducerCatalogError, FORMAT +from final_closure_candidate_artifact_auth import KINDS + +H = "a" * 40 + + +def fixture(): + entries_ = [] + count = 0 + for kind, spec in KINDS.items(): + for shard in range(spec["count"]): + count += 1 + entries_.append({ + "kind": kind, + "shard": shard, + "run_id": 9000 + count, + "artifact_id": 10000 + count, + "zip_file": "artifact-%03d.zip" % count, + }) + return {"format": FORMAT, "head_sha": H, "artifacts": entries_} + + +class ProducerCatalogTests(unittest.TestCase): + def test_all_research_python_parses_before_long_ci(self): + """Cheap syntax gate; runs before Lean-dependent cases in this module.""" + research = Path(__file__).resolve().parent + sources = sorted(research.rglob("*.py")) + self.assertGreater(len(sources), 10) + for path in sources: + with self.subTest(path=path.name): + ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + + def test_literal_backslash_n_import_is_a_syntax_regression(self): + catalog = Path(__file__).with_name("final_closure_producer_catalog.py") + source = catalog.read_text(encoding="utf-8") + self.assertIn("from final_closure_review_dossier import", source) + self.assertNotIn("bind as bind_v2_producer)" + chr(92) + "nfrom", source) + ast.parse(source, filename=str(catalog)) + + def test_exact_full_inventory_remains_unapproved_candidate(self): + items = entries(fixture(), H) + self.assertEqual(len(items), 21) + self.assertEqual({x["kind"] for x in items}, + {"append", "legacy", "compiler"}) + self.assertEqual(len([x for x in items if x["kind"] == "compiler"]), 4) + self.assertEqual(len([x for x in items if x["kind"] == "append"]), 9) + self.assertEqual(len([x for x in items if x["kind"] == "legacy"]), 8) + + def test_missing_extra_and_duplicate_shards_fail_closed(self): + cases = [] + no_one = fixture() + no_one["artifacts"].pop() + cases.append(no_one) + duplicate = fixture() + duplicate["artifacts"][0] = dict(duplicate["artifacts"][1]) + cases.append(duplicate) + changed_index = fixture() + changed_index["artifacts"][0]["shard"] = 100 + cases.append(changed_index) + for candidate in cases: + with self.subTest(case=candidate["artifacts"][:2]): + with self.assertRaises(ProducerCatalogError): + entries(candidate, H) + + def test_invalid_head_duplicate_ids_and_unsafe_paths_fail_closed(self): + base = fixture() + for value in ("../evil.zip", "/tmp/shadow.zip", "dir/file.zip", + "x\\evil.zip", "payload.json", ".zip"): + payload = copy.deepcopy(base) + payload["artifacts"][0]["zip_file"] = value + with self.subTest(name=value), self.assertRaises(ProducerCatalogError): + entries(payload, H) + duplicate_file = copy.deepcopy(base) + duplicate_file["artifacts"][0]["zip_file"] = ( + duplicate_file["artifacts"][1]["zip_file"]) + with self.assertRaises(ProducerCatalogError): + entries(duplicate_file, H) + bad = copy.deepcopy(base) + bad["artifacts"][0]["artifact_id"] = bad["artifacts"][1]["artifact_id"] + with self.assertRaises(ProducerCatalogError): + entries(bad, H) + for wrong in (True, 9, "", "a"*39, "b"*40): + with self.subTest(head=wrong), self.assertRaises(ProducerCatalogError): + entries(base, wrong) + + def test_status_or_approval_fields_cannot_be_smuggled(self): + payload = fixture() + payload["artifacts"][0]["approved"] = True + with self.assertRaises(ProducerCatalogError): + entries(payload, H) + payload = fixture() + payload["format"] = "Permansson.FakeApprovedRelease.v1" + with self.assertRaises(ProducerCatalogError): + entries(payload, H) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_producer_v2_bridge.py b/verification/research/test_final_closure_producer_v2_bridge.py new file mode 100644 index 00000000..0753cf1b --- /dev/null +++ b/verification/research/test_final_closure_producer_v2_bridge.py @@ -0,0 +1,140 @@ +"""Synthetic, non-certificate tests for real-producer-v2 preparation bridge.""" +import copy +import unittest + +from final_closure_producer_v2_bridge import ( + bind, verified_producer_index, ProducerBindingError +) +from final_closure_review_dossier import make_dossier +from test_final_closure_review_dossier import fixture + +H = "a" * 40 + + +def test_input(): + f = fixture() + workflows = [ + ("compiler", ".github/workflows/pr113-all-case-compiler.yml", + "pr113-compiler-shard-", 4), + ("append", ".github/workflows/pr113-all-original-semantic-candidates.yml", + "pr113-all-original-semantic-candidates-", 9), + ("legacy", ".github/workflows/pr113-legacy-semantic-evidence.yml", + "pr113-legacy-semantic-candidates-", 8), + ] + producers = [] + ident = 0 + for kind, workflow, prefix, count in workflows: + for shard in range(count): + ident += 1 + producers.append({ + "format": "Permansson.PR113.AuthenticatedProducerArtifact.v1", + "status": "CI_ARTIFACT_DIGEST_VERIFIED_UNREVIEWED", + "workflow_path": workflow, + "artifact_name": prefix + str(shard), + "run_id": 1200 + ["compiler","append","legacy"].index(kind), + "run_attempt": 1, + "job_id": 3000 + ident, + "actual_check_run_id": 4000 + ident, + "artifact_id": 5000 + ident, + "artifact_sha256": "%064x" % ident, + "manifest_sha256": "%064x" % (ident + 21), + "head_sha": H, + "independent_review": False, + "mathematical_acceptance": False, + "scientific_release_approved": False, + "merged_main_sha": None, + }) + lookup = verified_producer_index(H, producers) + for i, (cid, cand) in enumerate(f["candidate_cases"].items()): + kind = cand["route"] + shard = i % 9 if kind == "append" else (i - 25) % 8 + p = lookup[(kind, shard)] + cand["producing_artifact_id"] = p["artifact_id"] + cand["actual_producing_check_run_id_provenance_only"] = ( + p["actual_check_run_id"]) + cand["accepted_semantic_negative"] = False + full = f["compiler"]["compiler_axiom_receipts"] + for i, proof in enumerate(full): + proof.update({ + "inventory_index": i, "source_sha256": "f"*64, + "compiler_check": "research", "axiom_log_member": + f"axiom_logs/all_{i:04d}.log", + "universe_params_repr": "[]", + }) + dossier = make_dossier(**dict(f, producers=producers)) + manifest = { + "head_sha": H, + "status": "COMPILER_ONLY_UNREVIEWED", + "proof_count": 259, "inventory_count": 259, + "case_count": 45, "claim_count": 11, + "authentic_ci_job_bound": False, "independent_review": False, + "compiler_axiom_receipts": full, + } + return dossier, manifest, producers + + +class ExactProducerBoundBridge(unittest.TestCase): + def test_all_259_45_11_have_correct_shard_provenance_never_accepted(self): + d, compiler, producers = test_input() + record = bind(H, d, compiler, producers) + self.assertEqual( + (record["proof_record_count"], record["case_record_count"], + record["claim_record_count"]), (259, 45, 11)) + for proof in record["proof_producers"]: + idx = proof["inventory_index"] + self.assertEqual(proof["authenticated_producer"]["artifact_name"], + "pr113-compiler-shard-" + str(idx % 4)) + self.assertIsNone(proof["original_candidate_check_run_id"]) + self.assertFalse(record["strict_v2_ready"]) + self.assertFalse(record["mathematical_release_approved"]) + self.assertEqual(record["pending_explicit_review_scopes"], 56) + self.assertEqual(record["accepted_cases"], 0) + + def test_duplicate_or_missing_producer_check_is_blocked(self): + _, _, producers = test_input() + broken = copy.deepcopy(producers) + broken.pop() + with self.assertRaises(ProducerBindingError): + verified_producer_index(H, broken) + broken = copy.deepcopy(producers) + broken[2]["actual_check_run_id"] = broken[1]["actual_check_run_id"] + with self.assertRaises(ProducerBindingError): + verified_producer_index(H, broken) + + def test_stale_job_head_or_fake_approved_artifact_is_blocked(self): + d, compiler, producers = test_input() + other = copy.deepcopy(producers) + other[0]["head_sha"] = "b"*40 + with self.assertRaises(ProducerBindingError): + bind(H, d, compiler, other) + other = copy.deepcopy(producers) + other[0]["independent_review"] = True + with self.assertRaises(ProducerBindingError): + bind(H, d, compiler, other) + + def test_wrong_shard_and_borrowed_case_negative_are_blocked(self): + d, compiler, producers = test_input() + c = copy.deepcopy(compiler) + c["compiler_axiom_receipts"][0]["inventory_index"] = 1 + with self.assertRaises(ProducerBindingError): + bind(H, d, c, producers) + d2 = copy.deepcopy(d) + d2["case_dockets"][0]["producing_negative_candidate"][ + "actual_producing_check_run_id_provenance_only"] = 12345 + with self.assertRaises(ProducerBindingError): + bind(H, d2, compiler, producers) + + def test_missing_original_claim_support_fails(self): + d, compiler, producers = test_input() + d2 = copy.deepcopy(d) + d2["claim_dockets"][0]["compiler_receipt_ids"] = ["nonexistent"] + with self.assertRaises(ProducerBindingError): + bind(H, d2, compiler, producers) + c = copy.deepcopy(compiler) + c["compiler_axiom_receipts"].pop() + with self.assertRaises(ProducerBindingError): + bind(H, d, c, producers) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_receipt_migrate.py b/verification/research/test_final_closure_receipt_migrate.py new file mode 100644 index 00000000..673bfe62 --- /dev/null +++ b/verification/research/test_final_closure_receipt_migrate.py @@ -0,0 +1,44 @@ +"""Legacy labels never become independent acceptance by migration.""" +import unittest +import final_closure_receipt_migrate as m + + +class LegacyReceiptTests(unittest.TestCase): + def test_old_axiom_allowlist_never_becomes_observed_transitive_axioms(self): + old = { + "declaration": "Example.theorem", + "path": "PermanssonResearch/Example.lean", + "reviewed_head_sha": "a"*40, + "source_sha256": "b"*64, + "statement_sha256": "c"*64, + "axiom_allowlist": ["propext", "Classical.choice"], + "compiler_run_url": "https://example.com/fake", + "compiler_result": "PASS", "reviewer_signoff": True, + } + r = m.statement_candidate(old) + self.assertIsNone(r["transitive_axioms"]) + self.assertEqual(r["compiler_result"], "NOT_VERIFIED") + self.assertIsNone(r["check_run_id"]) + self.assertEqual(r["independent_review_receipts"], []) + + def test_legacy_source_ci_ids_must_be_reauthenticated(self): + old = { + "base_sha": "a"*40, + "head_sha": "b"*40, + "merged_main_sha": "c"*40, + "source_ci_receipts": [{"name": "research", "id": 777}], + "authenticated": True, + } + r = m.source_receipt_candidate(old) + self.assertEqual(r["source_bound_ci"], []) + self.assertEqual(r["independent_source_reviews"], []) + self.assertNotEqual(r["format"], "Permansson.FinalClosureDetachedReceipts.v2") + + def test_missing_bytes_cannot_migrate_to_verified_receipts(self): + with self.assertRaises(m.MigrationError): + m.statement_candidate({"declaration":"Fake.theorem", + "compiler_result":"PASS"}) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_reproduction_auth.py b/verification/research/test_final_closure_reproduction_auth.py new file mode 100644 index 00000000..e41eb346 --- /dev/null +++ b/verification/research/test_final_closure_reproduction_auth.py @@ -0,0 +1,172 @@ +"""Mock GitHub API + original ZIP adversaries; never real release evidence.""" +import io +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock +import zipfile + +import final_closure_reproduction_auth as auth + +M="a"*40 +PDF=b"%PDF-1.7\nSYNTHETIC TEST CONTENT\n" +ARCHIVE=b"synthetic-archive-not-real-research" +PAGE=b"png-mocked-page-bytes" +IDS={"run_id":101,"run_attempt":1,"job_id":102,"check_run_id":103, + "artifact_id":104} + + +def artifact_fixture(): + report={ + "format":"Permansson.TwoCleanSourceRebuilds.v1", + "status":"REPRODUCIBLE_DRAFT_ONLY", + "source_commit_sha":M, + "source_tree_sha":"b"*40, + "independent_source_roots":2, + "git_clone_mode":"local_no_hardlinks_no_checkout", + "engine":"pdflatex","rendered_pages":True, + "pdf_sha256":auth.sha(PDF), + "archive_sha256":auth.sha(ARCHIVE), + "manifest_sha256":None, + "two_complete_archive_audits":True, + "byte_comparison":{ + "status":"REPRODUCIBLE_DRAFT_ONLY", + "source_commit_sha":M, + "pdf_sha256":auth.sha(PDF), + "zip_sha256":auth.sha(ARCHIVE), + "page_count":1}, + "authenticated_ci_artifact":False, + "scientific_release_approved":False, + } + manifest={ + "source_commit_sha":M,"status":"DRAFT_NOT_ACCEPTED", + "pdf_sha256":auth.sha(PDF),"pdf_page_count":1, + } + mb=json.dumps(manifest).encode() + report["manifest_sha256"]=auth.sha(mb) + identity={ + "format":"Permansson.CICleanRebuildCandidate.v1", + "status":"EXECUTED_DRAFT_ONLY_NO_SCHOLARLY_REVIEW", + "source_head":M,"github_repository":"HMarcusWH/PermanssonLean", + "github_job":"clean-paper-rebuild", + "github_run_id":101,"github_run_attempt":1, + "pdf_sha256":auth.sha(PDF), + "archive_sha256":auth.sha(ARCHIVE), + "authenticated_ci_artifact":False, + } + items={ + "reproduction_receipt.json":json.dumps(report).encode(), + "ci_execution_identity.json":json.dumps(identity).encode(), + } + same={ + "Permansson_Mathematical_Closure.pdf":PDF, + "Permansson_Mathematical_Closure_E2E.zip":ARCHIVE, + "manifest.lock.json":mb, + "Permansson_Mathematical_Closure.tex":b"fake tex", + "latex_build.log":b"fake build log", + "SHA256SUMS.txt":b"fake sidecar", + "source_ci_receipts.lock.json":b"false approval flags absent", + "pages/page-1.png":PAGE, + } + for side in ("first","second"): + for name,body in same.items(): + items[side+"/"+name]=body + return items + + +def make_zip(members): + data=io.BytesIO() + with zipfile.ZipFile(data,"w") as z: + for name,body in sorted(members.items()): + z.writestr(name,body) + return data.getvalue() + + +class MergedCleanEvidenceTests(unittest.TestCase): + def setUp(self): + self.tmp=tempfile.TemporaryDirectory(prefix="perm-clean-gh-") + self.addCleanup(self.tmp.cleanup) + self.path=Path(self.tmp.name)/"draft.zip" + self.contents=artifact_fixture() + self.build() + self.run={ + "id":101,"head_sha":M,"head_branch":"main", + "run_attempt":1,"status":"completed","conclusion":"success", + "event":"workflow_dispatch","path":auth.WORKFLOW, + } + self.job={ + "id":102,"name":"clean-paper-rebuild","run_id":101, + "run_attempt":1,"head_sha":M,"status":"completed", + "conclusion":"success", + "check_run_url": + "https://api.github.com/repos/HMarcusWH/PermanssonLean/check-runs/103", + } + self.check={ + "id":103,"head_sha":M,"name":"clean-paper-rebuild", + "status":"completed","conclusion":"success", + "app":{"slug":"github-actions"}, + } + self.artifact={ + "id":104,"name":auth.ARTIFACT,"expired":False, + "digest":"sha256:"+auth.sha(self.path.read_bytes()), + "workflow_run":{"id":101,"head_sha":M}, + } + + def build(self): + self.path.write_bytes(make_zip(self.contents)) + + def attest(self): + api={ + "/actions/runs/101":self.run, + "/check-runs/103":self.check, + "/actions/artifacts/104":self.artifact, + } + with mock.patch.object(auth, "live", + side_effect=lambda path:api[path]), \ + mock.patch.object(auth, "paginate", + return_value=[self.job]): + return auth.authenticate_merged_reproduction( + M,auth.sha(PDF),IDS,self.path) + + def test_exact_success_is_authenticated_only_as_clean_build(self): + result=self.attest() + self.assertEqual(result["status"],"TWO_CLEAN_SOURCE_BUILDS") + self.assertTrue(result["authenticated_ci_artifact"]) + self.assertFalse(result["scholarly_review_approved"]) + self.assertFalse(result["mathematical_release_approved"]) + self.assertEqual(result["rendered_page_count"],1) + + def test_wrong_head_run_attempt_job_and_source_rejected(self): + mutations=[ + lambda:self.run.update(head_sha="f"*40), + lambda:self.run.update(run_attempt=2), + lambda:self.job.update(conclusion="failure"), + lambda:self.check.update(conclusion="failure"), + lambda:self.artifact.update(digest="sha256:"+"f"*64), + lambda:self.run.update(head_branch="research/final-mathematical-closure"), + ] + for index,mutation in enumerate(mutations): + with self.subTest(index=index): + self.setUp() + mutation() + with self.assertRaises(auth.ReproductionAuthenticationError): + self.attest() + + def test_changed_pdf_missing_page_or_forged_receipt_rejected(self): + attacks=[ + lambda c:c.update({"second/Permansson_Mathematical_Closure.pdf":b"altered"}), + lambda c:c.pop("second/pages/page-1.png"), + lambda c:c.update({"first/pages/page-1.png":b"altered"}), + lambda c:c.update({"reproduction_receipt.json": + json.dumps({"status":"TWO_CLEAN_SOURCE_BUILDS", + "authenticated_ci_artifact":True}).encode()}), + ] + for idx,attack in enumerate(attacks): + with self.subTest(idx=idx): + contents=artifact_fixture() + attack(contents) + payload=auth.inspect_zip(make_zip(contents)) + with self.assertRaises(auth.ReproductionAuthenticationError): + auth.validate_clean_root_bytes( + payload,merged=M,expected_pdf=auth.sha(PDF)) diff --git a/verification/research/test_final_closure_review_dossier.py b/verification/research/test_final_closure_review_dossier.py new file mode 100644 index 00000000..50adc1be --- /dev/null +++ b/verification/research/test_final_closure_review_dossier.py @@ -0,0 +1,105 @@ +"""Pure adversarial candidate-dossier tests. No synthetic Lean/review acceptance.""" +import unittest +from final_closure_review_dossier import make_dossier, ReviewDossierError + +H = "a" * 40 +T = "b" * 40 +CLAIMS = ("Q1","Q2","Q3","M1","M2","M3","N1","N2","N3","D8","D9") + + +def fixture(): + ids = ["C-%02d" % i for i in range(1, 46)] + proof = [{ + "id": "all:%04d" % i, "head_sha": H, + "path": "PermanssonResearch/Proof%d.lean" % i, + "declaration": "Demo.proof%d" % i, + "case_ids": [ids[i % 45]], "claim_ids": [CLAIMS[i % 11]], + "compiler_result": "PASS", "check_run_id": None, + "transitive_axioms": ["propext"], + "elaborated_type_sha256": "c"*64, "axiom_log_sha256": "d"*64, + } for i in range(259)] + originals = [] + candidates = {} + for i, cid in enumerate(ids): + theorem = proof[i] + name = "test_false_%02d" % i + originals.append({ + "case_id": cid, "required_expected_result": "synthetic criterion", + "current_candidate_decision": "PENDING", + "historical_proof_declarations": [ + {"path": theorem["path"], "name": theorem["declaration"]}], + "test_method_candidates": [ + {"method": name, "role": "SEMANTIC_NEGATIVE", + "path": "verification/research/test_false.py"}], + }) + candidates[cid] = {"route": "append" if i < 25 else "legacy", + "selected_semantic_methods": [name]} + claims = [{ + "claim_id": claim, + "declarations": [{"path": proof[i]["path"], + "declaration": proof[i]["declaration"]}], + } for i, claim in enumerate(CLAIMS)] + producers = [{ + "status": "CI_ARTIFACT_DIGEST_VERIFIED_UNREVIEWED", + "head_sha": H, "artifact_id": 1000+i, + "actual_check_run_id": 100+i, + "independent_review": False, "mathematical_acceptance": False, + } for i in range(21)] + return dict(head=H, tree=T, original={"cases": originals}, + claim_map={"claims": claims}, + compiler={"compiler_axiom_receipts": proof}, + candidate_cases=candidates, producers=producers) + + +class ReviewDossierBoundaries(unittest.TestCase): + def test_all_45_11_259_unreviewed(self): + got = make_dossier(**fixture()) + self.assertEqual((got["case_count"], got["claim_count"], + got["compiler_target_count"]), (45, 11, 259)) + self.assertEqual((got["accepted_case_count"], + got["accepted_claim_count"]), (0, 0)) + self.assertTrue(all(x["accepted_case_decision"] is None + for x in got["case_dockets"])) + self.assertFalse(got["release_approved"]) + + def test_unchanged_original_theorem_is_mandatory(self): + f = fixture() + f["original"]["cases"][0]["historical_proof_declarations"][0][ + "name"] = "fake_original" + with self.assertRaises(ReviewDossierError): + make_dossier(**f) + + def test_missing_or_cross_case_false_method_blocks(self): + f = fixture() + f["candidate_cases"].pop("C-01") + with self.assertRaises(ReviewDossierError): + make_dossier(**f) + f = fixture() + f["candidate_cases"]["C-01"]["selected_semantic_methods"] = [ + "test_borrowed"] + with self.assertRaises(ReviewDossierError): + make_dossier(**f) + + def test_forged_producer_head_or_approval_blocks(self): + f = fixture() + f["producers"][0]["head_sha"] = "b"*40 + with self.assertRaises(ReviewDossierError): + make_dossier(**f) + f = fixture() + f["producers"][0]["independent_review"] = True + with self.assertRaises(ReviewDossierError): + make_dossier(**f) + + def test_missing_compiler_claim_support_blocks(self): + f = fixture() + f["compiler"]["compiler_axiom_receipts"].pop() + with self.assertRaises(ReviewDossierError): + make_dossier(**f) + f = fixture() + f["claim_map"]["claims"].pop() + with self.assertRaises(ReviewDossierError): + make_dossier(**f) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_schema.py b/verification/research/test_final_closure_schema.py new file mode 100644 index 00000000..0940019c --- /dev/null +++ b/verification/research/test_final_closure_schema.py @@ -0,0 +1,190 @@ +"""Detached receipt schema adversaries. All successful inputs are mock candidates.""" +import copy +import unittest + +import final_closure_schema as schema +from final_closure_case_receipts import EvidenceError + +H = "a" * 40 + + +def candidate(): + return { + "format": "Permansson.FinalClosureDetachedReceipts.v2", + "phase": "premerge_review", "base_sha": "b"*40, + "head_sha": H, "synthetic_sha": "c"*40, + "merged_main_sha": None, "evidence_bundle_sha256": "d"*64, + "source_bound_ci": [ + {"name": f"check{i}", "id": i + 1, "head_sha": H} + for i in range(7) + ], + "compiler_axiom_receipts": [], "positive_test_receipts": [], + "semantic_negative_receipts": [], "case_decisions": [], + "claim_decisions": [], "independent_source_reviews": [], + "trusted_axioms": ["Classical.choice", "Quot.sound", "propext"], + } + + +class SchemaBoundaryTests(unittest.TestCase): + def test_premerge_candidate_is_valid_but_not_approved(self): + x = candidate() + self.assertIs(schema.validate_overlay(x), x) + self.assertEqual(x["claim_decisions"], []) + self.assertEqual(x["case_decisions"], []) + + def test_unknown_property_and_boolean_ci_id_rejected(self): + x = candidate() + x["surprise"] = "yes" + with self.assertRaises(EvidenceError): + schema.validate_overlay(x) + x = candidate() + x["source_bound_ci"][0]["id"] = True + with self.assertRaises(EvidenceError): + schema.validate_overlay(x) + + def test_phase_requires_matching_merged_SHA_and_bound_receipts(self): + x = candidate() + x["merged_main_sha"] = "f"*40 + with self.assertRaises(EvidenceError): + schema.validate_overlay(x) + x["phase"] = "postmerge_acceptance" + schema.validate_overlay(x) + x["merged_main_sha"] = None + with self.assertRaises(EvidenceError): + schema.validate_overlay(x) + + def test_test_receipts_allow_claim_ids_but_not_unknown_data(self): + x = candidate() + test = { + "id": "negative:1", "head_sha": H, "check_run_id": None, + "test_path": "verification/research/test_demo.py", + "test_name": "test_demo", "source_sha256": "1"*64, + "case_ids": ["Q-05"], "claim_ids": ["Q1"], + "positive_control": { + "compiled": True, "exit_code": 0, + "lean_toolchain": "4.34.0", + "log_member": "pos.log", "log_sha256": "2"*64, + }, + "outcome": "PASS", "compiler_exit_code": 0, + "log_member": "pos.log", "log_sha256": "2"*64, + } + x["positive_test_receipts"] = [test] + schema.validate_overlay(x) + x["semantic_negative_receipts"] = [{ + **test, "id": "negative:2", "outcome": "REJECTED_SEMANTIC", + "compiler_exit_code": 1, + "log_member": "neg.log", + "mutant_source_member": "mut.lean", + "mutant_source_sha256": "3"*64, + "original_lean_source_path": "Demo.lean", + "original_lean_source_sha256": "4"*64, + "original_lean_source_member": "original.lean", + }] + schema.validate_overlay(x) + bad = copy.deepcopy(x) + bad["semantic_negative_receipts"][0]["unexpected"] = True + with self.assertRaises(EvidenceError): + schema.validate_overlay(bad) + x["phase"] = "postmerge_acceptance" + x["merged_main_sha"] = "e"*40 + with self.assertRaises(EvidenceError): + schema.validate_overlay(x) # no attributable execution check + + def test_claim_only_receipts_validate_but_orphans_fail(self): + x = candidate() + base = { + "id": "claim-only", "head_sha": H, "check_run_id": None, + "test_path": "verification/research/test_demo.py", + "test_name": "test_demo", "source_sha256": "1"*64, + "case_ids": [], "claim_ids": ["N2"], + "positive_control": { + "compiled": True, "exit_code": 0, "lean_toolchain": "4.34.0", + "log_member": "positive.log", "log_sha256": "2"*64, + }, + "outcome": "PASS", "compiler_exit_code": 0, + "log_member": "positive.log", "log_sha256": "2"*64, + } + x["positive_test_receipts"] = [base] + schema.validate_overlay(x) + orphan = copy.deepcopy(x) + orphan["positive_test_receipts"][0]["claim_ids"] = [] + with self.assertRaises(EvidenceError): + schema.validate_overlay(orphan) + x["semantic_negative_receipts"] = [{ + **base, "id": "claim-only-negative", "outcome": "REJECTED_SEMANTIC", + "compiler_exit_code": 1, "log_member": "negative.log", + "mutant_source_member": "mutated.lean", "mutant_source_sha256": "3"*64, + "original_lean_source_path": "Proof.lean", + "original_lean_source_member": "original.lean", + "original_lean_source_sha256": "4"*64, + }] + schema.validate_overlay(x) + x["semantic_negative_receipts"][0]["claim_ids"] = [] + with self.assertRaises(EvidenceError): + schema.validate_overlay(x) + + def test_claim_decision_requires_positive_and_negative_receipts(self): + decision = {"id": "claim-1", "claim_id": "Q1", + "proof_ids": ["proof"], "positive_ids": ["pos"], + "negative_ids": ["neg"], "review_id": 11} + good = candidate() + good["claim_decisions"] = [decision] + schema.validate_overlay(good) # Shape only, not real approval. + for key in ("positive_ids", "negative_ids"): + missing = copy.deepcopy(good) + del missing["claim_decisions"][0][key] + with self.subTest(key=key), self.assertRaises(EvidenceError): + schema.validate_overlay(missing) + empty = copy.deepcopy(good) + empty["claim_decisions"][0][key] = [] + with self.subTest(key=key + " empty"), self.assertRaises(EvidenceError): + schema.validate_overlay(empty) + + def test_v3_requires_true_producing_job_refs_and_all_21_entries(self): + x = candidate() + x["format"] = schema.FORMAT_V3 + # v3 independently requires all 12 global source checks too. + x["source_bound_ci"] = [ + {"name": f"required-{i}", "id": i + 1, "head_sha": H} + for i in range(12) + ] + x["producer_artifacts"] = [] + with self.assertRaises(EvidenceError): + schema.validate_overlay(x) + from final_closure_candidate_artifact_auth import KINDS + records = [] + for kind, spec in KINDS.items(): + for shard in range(spec["count"]): + k = len(records)+1 + records.append({ + "format":"Permansson.PR113.AuthenticatedProducerArtifact.v1", + "status":"CI_ARTIFACT_DIGEST_VERIFIED_UNREVIEWED", + "head_sha": H, + "workflow_path":spec["workflow"], + "run_id":1000+k,"run_attempt":1, + "job_id":2000+k,"actual_check_run_id":3000+k, + "artifact_id":4000+k, + "artifact_name":spec["artifact"]+str(shard), + "artifact_sha256":"a"*64,"manifest_sha256":"b"*64, + "mathematical_acceptance":False,"independent_review":False, + "scientific_release_approved":False, + "merged_main_sha":None, + "evidence_zip_member":f"producer/{4000+k}.zip", + }) + x["producer_artifacts"] = records + schema.validate_overlay(x) # STRUCTURAL ONLY; not real GH review + x["producer_artifacts"][0]["actual_check_run_id"] = True + with self.assertRaises(EvidenceError): + schema.validate_overlay(x) + x["producer_artifacts"][0]["actual_check_run_id"] = 3001 + x["producer_artifacts"][0]["mathematical_acceptance"] = True + with self.assertRaises(EvidenceError): + schema.validate_overlay(x) + + def test_unknown_schema_rule_fails_closed(self): + with self.assertRaises(EvidenceError): + schema._check_spec({"magicallyAccept": True}) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_source_maps.py b/verification/research/test_final_closure_source_maps.py new file mode 100644 index 00000000..a21d4db5 --- /dev/null +++ b/verification/research/test_final_closure_source_maps.py @@ -0,0 +1,99 @@ +"""Candidate maps must exactly replay immutable original statements and sources.""" +import json +from pathlib import Path +import unittest + +import final_closure_case_map as cases +import final_closure_claim_map as claims +import final_closure_obligations_report as report +from final_closure_case_receipts import EvidenceError, statement_header + + +class OriginalSourceMapTests(unittest.TestCase): + def test_original_45_map_has_literal_regenerability(self): + expected = cases.OUTPUT.read_text(encoding="utf-8") + self.assertEqual(expected, cases.render()) + data = cases.generate() + self.assertEqual(data["original_cases"], 45) + self.assertEqual(len(data["cases"]), 45) + self.assertEqual(len({c["case_id"] for c in data["cases"]}), 45) + self.assertTrue(all(c["effective_accepted"] is False + for c in data["cases"])) + historical = json.loads((cases.SNAP/"acceptance_decisions.json").read_text()) + for old, new in zip(historical["cases"], data["cases"]): + self.assertEqual(old["proof_declarations"], + new["historical_proof_declarations"]) + self.assertEqual(old["required_expected_result"], + new["required_expected_result"]) + self.assertEqual(old["historical_contract_ids"], + new["historical_contract_ids"]) + + def test_eleven_claims_have_real_full_names_and_supports(self): + self.assertEqual(claims.OUTPUT.read_text(encoding="utf-8"), + claims.render()) + data = claims.generate() + self.assertEqual(data["count"], 11) + self.assertEqual(len(data["claims"]), 11) + self.assertFalse(any(c["accepted"] for c in data["claims"])) + for c in data["claims"]: + self.assertTrue(c["declarations"]) + for source in c["declarations"]: + p = claims.ROOT / source["path"] + self.assertTrue(p.is_file()) + self.assertTrue(statement_header( + p.read_text(encoding="utf-8"), + source["declaration"])) + byid = {c["claim_id"]: c for c in data["claims"]} + self.assertTrue(any("NonlinearPhaseBlindClassGeometry.lean" in d["path"] + for d in byid["N2"]["declarations"])) + self.assertTrue(any("actual_mixed_nonlinear_half_not_barycenter" in d["declaration"] + for d in byid["D9"]["declarations"])) + self.assertTrue(any(".ConcreteRestrictedBoolean." in d["declaration"] + for d in byid["D8"]["declarations"])) + self.assertTrue(any(".FinalMatchedAsymmetry." in d["declaration"] + for d in byid["M3"]["declarations"])) + self.assertTrue(any(".SourceBoundExactWitness." in d["declaration"] + for d in byid["Q3"]["declarations"])) + + def test_mandatory_claim_support_coverage(self): + byid = {c["claim_id"]: c for c in claims.generate()["claims"]} + required = { + "Q2": {"point_witness_output"}, + "Q3": {"correct_frozen_discounted_budget", + "wrong_frozen_discounted_budget"}, + "M2": {"matched_closed_loop_all_initial_paths", + "matched_open_loop_all_initial_paths"}, + "M3": {"matched_open_worlds_both_frozen"}, + "N2": {"actual_restricted_gap_of_identified_classes"}, + "N3": {"certified_boolean_score_exact", "boolean_score_bounded"}, + "D9": {"physically_distinct_classes_with_shared_descriptor", + "actual_mixed_initial_nonlinear_expectation", + "exact_two_absorbing_rational_scores"}, + } + for cid, names in required.items(): + with self.subTest(cid=cid): + actual = {d["declaration"].rsplit(".", 1)[-1] + for d in byid[cid]["declarations"]} + self.assertTrue(names <= actual) + self.assertFalse(byid[cid]["accepted"]) + + def test_acceptance_obligations_report_is_deterministic_and_not_approved(self): + text = report.render() + self.assertEqual(report.OUTPUT.read_text(encoding="utf-8"), text) + self.assertIn("pending: 15; partial scope: 30", text) + self.assertIn("NOT ACCEPTED", text) + + def test_declared_local_name_outside_namespace_is_not_enough(self): + source = ("namespace A.B\n" + "theorem token : True := by trivial\n" + "end A.B\n") + self.assertEqual(statement_header(source, "A.B.token"), + "theorem token : True") + for incorrect in ("Other.B.token", "A.B.unknown", "token"): + with self.subTest(incorrect=incorrect): + with self.assertRaises(EvidenceError): + statement_header(source, incorrect) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_closure_v3_source_readiness.py b/verification/research/test_final_closure_v3_source_readiness.py new file mode 100644 index 00000000..169846d3 --- /dev/null +++ b/verification/research/test_final_closure_v3_source_readiness.py @@ -0,0 +1,130 @@ +"""Role/owner correctness tests. Mock rows are NOT compiler/reviewer evidence.""" +import copy +import unittest + +import final_closure_v3_source_readiness as gate + +CASEIDS=[f"C-{i:02d}" for i in range(1,46)] +CLAIMS=sorted(gate.CLAIMS) + +def fixture(): + cases,proofs,plus,minus=[],[],[],[] + for n,cid in enumerate(CASEIDS): + qualified=f"Test.Valid.theorem_{n}" + path=f"Proofs/Case_{n}.lean" + pos=f"test_positive_{n}" + neg=f"test_semantic_negative_{n}" + tpos=f"verification/research/test_source_{n}.py" + tneg=f"verification/research/test_negative_{n}.py" + cases.append({ + "case_id":cid, + "historical_proof_declarations":[{"path":path,"name":qualified}], + "new_claim_source_candidates":[], + "test_method_candidates":[ + {"path":tpos,"method":pos,"role":"EXACT_CHECK"}, + {"path":tneg,"method":neg,"role":"SEMANTIC_NEGATIVE"}, + ], + }) + proofs.append({"id":f"case:{n}","path":path,"declaration":qualified, + "case_ids":[cid],"claim_ids":[]}) + plus.append({"id":f"pos:{n}","test_path":tpos,"test_name":pos, + "case_ids":[cid],"claim_ids":[]}) + minus.append({"id":f"neg:{n}","test_path":tneg,"test_name":neg, + "case_ids":[cid],"claim_ids":[]}) + claim_rows=[] + for n,cid in enumerate(CLAIMS): + path=f"Proofs/Claim_{n}.lean";qualified=f"Claim.Valid.statement_{n}" + t=f"verification/research/test_claim_{n}.py" + claim_rows.append({"claim_id":cid, + "declarations":[{"path":path,"declaration":qualified}], + "test_source_candidates":[t]}) + proofs.append({"id":f"claim:{n}","path":path, + "declaration":qualified,"case_ids":[], + "claim_ids":[cid]}) + plus.append({"id":f"claim-positive:{n}","test_path":t, + "test_name":f"test_claim_positive_{n}", + "case_ids":[],"claim_ids":[cid]}) + minus.append({"id":f"claim-negative:{n}","test_path":t, + "test_name":f"test_claim_false_{n}", + "case_ids":[],"claim_ids":[cid]}) + for n in range(259-len(proofs)): + proofs.append({"id":f"extra:{n}","path":"Proofs/Other.lean", + "declaration":f"Other.theorem_{n}", + "case_ids":[], "claim_ids":[]}) + return ( + {"compiler_axiom_receipts":proofs, + "positive_test_receipts":plus, + "semantic_negative_receipts":minus}, + {"cases":cases},{"claims":claim_rows}, + ) + +class ExactOriginalRoleTests(unittest.TestCase): + def test_complete_synthetic_scope_still_is_unreviewed(self): + over,cases,claims=fixture() + report=gate.route_coverage(over,cases,claims) + self.assertEqual(report["cases_with_admissible_positive"],45) + self.assertEqual(report["cases_with_admissible_negative"],45) + self.assertEqual(report["claims_with_source_selected_positive"],11) + self.assertEqual(report["claims_with_source_selected_negative"],11) + self.assertTrue(report["all_original_roles_and_claim_sources_complete"]) + self.assertFalse(report["independent_review"]) + self.assertFalse(report["mathematical_release_approved"]) + + def test_negative_role_positive_log_is_not_admissible_positive(self): + over,cases,claims=fixture() + over["positive_test_receipts"][0]["test_path"] = ( + over["semantic_negative_receipts"][0]["test_path"]) + over["positive_test_receipts"][0]["test_name"] = ( + over["semantic_negative_receipts"][0]["test_name"]) + report=gate.route_coverage(over,cases,claims) + self.assertIn(CASEIDS[0], report["missing_original_positive"]) + self.assertIn(CASEIDS[0], report["inadmissible_original_method_roles"]) + self.assertFalse(report["all_original_roles_and_claim_sources_complete"]) + + def test_one_valid_route_cannot_hide_an_ineligible_extra(self): + over,cases,claims=fixture() + alien=dict(over["positive_test_receipts"][0]) + alien["id"]="alien-positive" + alien["test_path"]=over["semantic_negative_receipts"][0]["test_path"] + alien["test_name"]=over["semantic_negative_receipts"][0]["test_name"] + over["positive_test_receipts"].append(alien) + result=gate.route_coverage(over,cases,claims) + self.assertIn(CASEIDS[0],result["inadmissible_original_method_roles"]) + self.assertFalse(result["all_original_roles_and_claim_sources_complete"]) + over,cases,claims=fixture() + alien=dict(over["positive_test_receipts"][-1]) + alien["id"]="alien-claim-positive" + alien["test_path"]="verification/research/test_unrelated_claim.py" + over["positive_test_receipts"].append(alien) + result=gate.route_coverage(over,cases,claims) + self.assertIn(CLAIMS[-1],result["inadmissible_claim_test_sources"]) + self.assertFalse(result["all_original_roles_and_claim_sources_complete"]) + + def test_claim_wrong_source_and_omitted_support_are_rejected(self): + over,cases,claims=fixture() + over["positive_test_receipts"][-1]["test_path"] = ( + "verification/research/unregistered_claim.py") + report=gate.route_coverage(over,cases,claims) + self.assertIn(CLAIMS[-1],report["missing_claim_positive"]) + self.assertFalse(report["all_original_roles_and_claim_sources_complete"]) + over,cases,claims=fixture() + over["compiler_axiom_receipts"][45]["declaration"]="Claim.False" + report=gate.route_coverage(over,cases,claims) + self.assertFalse(report["all_original_roles_and_claim_sources_complete"]) + self.assertTrue(report["missing_required_declaration_or_source"]) + + def test_duplicate_evidence_id_and_forged_method_registry_rejected(self): + over,cases,claims=fixture() + over["positive_test_receipts"][1]["id"] = ( + over["positive_test_receipts"][0]["id"]) + with self.assertRaises(gate.EvidenceError): + gate.route_coverage(over,cases,claims) + over,cases,claims=fixture() + case=cases["cases"][0] + case["test_method_candidates"].append({ + "path":case["test_method_candidates"][0]["path"], + "method":case["test_method_candidates"][0]["method"], + "role":"SEMANTIC_NEGATIVE", + }) + with self.assertRaises(gate.EvidenceError): + gate.route_coverage(over,cases,claims) diff --git a/verification/research/test_final_closure_x1_trace_contract.py b/verification/research/test_final_closure_x1_trace_contract.py new file mode 100644 index 00000000..8a54c5a2 --- /dev/null +++ b/verification/research/test_final_closure_x1_trace_contract.py @@ -0,0 +1,64 @@ +"""Pure contract tests for individual X1 source-appended Lean false probes. + +These tests do not pretend that a source-string test is a Lean compiler +certificate. The compiler and independent CI producer remain mandatory. +""" +import json +from pathlib import Path +import unittest + +import test_paper_x1_lean_closure as trace + +ROOT = Path(__file__).resolve().parents[2] +CASE_MAP = ROOT / "research/paper_closures/post_merge_112/acceptance_case_source_map.json" +CASES = {"X-02": {"test_corrupt_terminal_literal", + "test_wrong_terminal_horizon"}, + "X-05": {"test_reset_before_action", + "test_new_world_learning", + "test_swapped_update_protocol"}} + + +class OriginalX1MethodTraceContract(unittest.TestCase): + def test_exact_case_method_selection_matches_original_registry(self): + registered = json.loads(CASE_MAP.read_text(encoding="utf-8"))["cases"] + by_id = {row["case_id"]: row for row in registered} + for case_id, expected in CASES.items(): + methods = {row["method"] for row in + by_id[case_id]["test_method_candidates"] + if row["role"] == "SEMANTIC_NEGATIVE"} + self.assertEqual(methods, expected) + expected_methods = set().union(*CASES.values()) + mapped = trace.X1LeanClosure._TRACE_FALSE_SOURCE + self.assertEqual(set(mapped), { + "corrupt_terminal_literal", "wrong_terminal_horizon", + "reset_before_action", "new_world_learning", + "swapped_update_protocol"}) + self.assertEqual({"test_" + name for name in mapped}, expected_methods) + + def test_each_probe_preserves_declared_original_provider_and_false_goal(self): + mapped = trace.X1LeanClosure._TRACE_FALSE_SOURCE + self.assertEqual(len(mapped), 5) + for name, (module, statement) in mapped.items(): + with self.subTest(name=name): + self.assertIn(module, trace.driver.MODULES) + src = ROOT / f"PermanssonResearch/X1/{module}.lean" + self.assertTrue(src.is_file()) + original = src.read_bytes() + suffix = ("\nnamespace PermanssonResearch.X1\n" + + "open PermanssonLean PermanssonLean.RegimeSpecification\n" + + statement + "\nend PermanssonResearch.X1\n").encode("utf-8") + self.assertTrue((original + suffix).startswith(original)) + self.assertIn(" := by\n exact ", statement) + self.assertFalse(any(x in suffix for x in ( + b"\nimport ", b"sorry", b"admit", b"axiom "))) + self.assertLess(len(suffix), 16 * 1024) + + def test_unexecuted_string_inspection_has_no_acceptance_status(self): + # Preserve the separation of source candidates from real producing + # compiler log, reviewer decision and original-case acceptance. + self.assertTrue(CASES) + self.assertFalse(hasattr(trace.X1LeanClosure, "independent_case_approval")) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_d08_admission.py b/verification/research/test_final_d08_admission.py new file mode 100644 index 00000000..7e8ad13b --- /dev/null +++ b/verification/research/test_final_d08_admission.py @@ -0,0 +1,61 @@ +"""D-08 restricted grammar: proof certificate must reject a forbidden typed block.""" +import pathlib +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[2] +PREFIX = ( + "import PermanssonResearch.ReverseSolver.AdaptiveGrammarAdmissionFixture\n" + "open PermanssonResearch.ReverseSolver.AdaptiveGrammarAdmissionFixture\n" + "open PermanssonResearch.GrammarRobust.BooleanDynamics\n" + "open PermanssonResearch.GrammarRobust.AdversarialAdmission\n" +) + + +def lean(body): + if not shutil.which("lake"): + raise AssertionError("Lean compiler unavailable") + with tempfile.TemporaryDirectory(dir=ROOT/".lake") as temp: + f = pathlib.Path(temp)/"D08.lean" + f.write_text(PREFIX + body + "\n") + p = subprocess.run(("lake", "env", "lean", str(f)), cwd=ROOT, + capture_output=True, text=True, timeout=900) + return p.returncode, p.stdout + p.stderr + + +class D08Admission(unittest.TestCase): + def test_positive_signatures_and_axioms(self): + names = ("forbidden_joint_not_in_restricted_menu", + "admitted_block_row_normalized", + "ConcreteRestrictedBoolean.restrictedCertifiedMenu", + "ConcreteRestrictedBoolean.actual_restricted_menu_admits_both_components", + "ConcreteRestrictedBoolean.actual_restricted_menu_rejects_joint", + "ConcreteRestrictedBoolean.actual_restricted_menu_row_normalized", + "restricted_joint_rejected") + rc, output = lean("\n".join( + "#check " + name + "\n#print axioms " + name for name in names)) + self.assertEqual(rc, 0, output) + for name in names: + self.assertIn(name, output) + groups = re.findall(r"depends on axioms:\s*\[([^\]]*)\]", output) + self.assertGreater(len(groups), 0, output) + for item in groups: + self.assertLessEqual( + {x.strip() for x in item.split(",") if x.strip()}, + {"propext", "Classical.choice", "Quot.sound"}) + + def test_false_frozen_joint_admission_is_semantic_mutant(self): + rc, output = lean( + "example : restricted.allowed joint := by\n" + " exact restricted_action_allowed\n") + self.assertNotEqual(rc, 0, "forbidden joint was unexpectedly certified") + self.assertNotRegex(output.lower(), + r"unknown (module|identifier)|failed to import|parser error|out of memory|timed out") + self.assertRegex(output.lower(), r"type mismatch|unsolved goals") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_d09_multichain.py b/verification/research/test_final_d09_multichain.py new file mode 100644 index 00000000..1fc15df7 --- /dev/null +++ b/verification/research/test_final_d09_multichain.py @@ -0,0 +1,85 @@ +"""D-09 native classwise-versus-barycenter semantics, and exact Lean mutant.""" +from fractions import Fraction as Q +import pathlib +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[2] +NAMESPACE = "PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture" + + +def compile_lean(body): + if not shutil.which("lake"): + raise AssertionError("Lean is required; no soft skip") + with tempfile.TemporaryDirectory(dir=ROOT/".lake") as work: + file = pathlib.Path(work)/"D09.lean" + file.write_text( + "import PermanssonResearch.ReverseSolver.NonlinearMultichainAggregationFixture\n" + "open " + NAMESPACE + "\n" + body + "\n") + p = subprocess.run(("lake", "env", "lean", str(file)), cwd=ROOT, + capture_output=True, text=True, timeout=900) + return p.returncode, p.stdout + p.stderr + + +class D09Classwise(unittest.TestCase): + def test_exact_expected_score_not_score_of_expected_law(self): + F = lambda p: Q(1) - abs(p-Q(2,3)) + mixture = (F(Q(0))+F(Q(1)))/2 + averaged = F(Q(1,2)) + self.assertEqual(mixture,Q(1,2)) + self.assertEqual(averaged,Q(5,6)) + self.assertNotEqual(mixture,averaged) + + def test_compiled_distinct_physical_classes_and_nonlinear_lemma(self): + names = ("physically_distinct_classes_with_shared_descriptor", + "nonlinear_classwise_aggregation_not_barycenter", + "actual_two_absorbing_full_path_mixture", + "exact_two_absorbing_rational_scores", + "actual_equal_weight_original_scores", + "actual_multichain_classwise_differs_from_barycenter", + "actual_mixed_initial_nonlinear_expectation", + "actual_equal_mixture_original_nonlinear_score", + "actual_mixed_nonlinear_half_not_barycenter") + rc, out = compile_lean("\n".join( + "#check "+name+"\n#print axioms "+name for name in names)) + self.assertEqual(rc, 0, out) + for name in names: + self.assertIn(name, out) + groups = re.findall(r"depends on axioms:\s*\[([^\]]*)\]", out) + self.assertTrue(groups, out) + for g in groups: + self.assertLessEqual( + {a.strip() for a in g.split(",") if a.strip()}, + {"propext", "Classical.choice", "Quot.sound"}, out) + + def test_wrong_barycenter_literal_fails_for_mathematical_reason(self): + rc, out = compile_lean( + "example : (1/2 : ℚ) = 5/6 := by\n" + " exact nonlinear_classwise_aggregation_not_barycenter.1\n") + self.assertNotEqual(rc, 0, "arithmetic counterexample was laundered") + self.assertNotRegex(out.lower(), + r"unknown (module|identifier)|failed to import|parser error|out of memory|timed out") + self.assertRegex(out.lower(), r"type mismatch|unsolved goals") + + + def test_wrong_single_mixed_canonical_score_cannot_be_five_sixths(self): + rc, out = compile_lean( + "open MeasureTheory\n" + "example :\n" + " (∫ w, actualNonlinearPathScore w\n" + " ∂(PermanssonResearch.ReverseSolver.FiniteStrategicRealization.realizedModel\n" + " PermanssonResearch.ReverseSolver.C11.ReferenceExamples.twoAbsorbing).pathLaw\n" + " equalWeightInitialLaw.toMeasure) = (5/6 : ℝ) := by\n" + " exact actual_mixed_nonlinear_half_not_barycenter.1\n") + self.assertNotEqual(rc, 0, "incorrect mixed-law score was proved") + self.assertNotRegex(out.lower(), + r"unknown (module|identifier)|failed to import|parser error|out of memory|timed out") + self.assertRegex(out.lower(), r"type mismatch|unsolved goals") + + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_draft_archive_completeness.py b/verification/research/test_final_draft_archive_completeness.py new file mode 100644 index 00000000..48cc7046 --- /dev/null +++ b/verification/research/test_final_draft_archive_completeness.py @@ -0,0 +1,147 @@ +"""Adversarial release-source archive verification. Fake bytes are DRAFT ONLY.""" +import copy +import hashlib +import json +from pathlib import Path +import stat +import tempfile +import unittest +import zipfile +import sys + +RELEASE = Path(__file__).resolve().parents[2] / "release/mathematical-closure-1" +if str(RELEASE) not in sys.path: + sys.path.insert(0, str(RELEASE)) + +import archive_integrity as a +import verify_release as verifier + + +def sha(b): + return hashlib.sha256(b).hexdigest() + + +class CompleteDraftArchiveTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory(prefix="perm-draft-check-") + self.addCleanup(self.temp.cleanup) + self.out = Path(self.temp.name) + self.source = { + "manuscript/" + name: ("synthetic test source: " + name).encode() + for name in a.SOURCE_FILES + } + self.generated = { + "Permansson_Mathematical_Closure.pdf": b"%PDF-1.4\nSYNTHETIC TEST BYTES\n", + "Permansson_Mathematical_Closure.tex": b"\\documentclass{article}", + "latex_build.log": b"fake normalized LaTeX log", + "source_ci_receipts.lock.json": json.dumps({ + "status": "MISSING_POST_MERGE_SOURCE_AND_REVIEWS", + "ci_receipts": [], "independent_reviews": [], + }).encode(), + } + pdf = self.generated["Permansson_Mathematical_Closure.pdf"] + self.manifest = { + "format": "Permansson.MathClosure.BuildReceipt.v1", + "mode": "DRAFT", "status": "DRAFT_NOT_ACCEPTED", + "source_commit_sha": "a"*40, "pdf_sha256": sha(pdf), + "tex_sha256": sha(self.generated["Permansson_Mathematical_Closure.tex"]), + "latex_log_sha256": sha(self.generated["latex_build.log"]), + "pdf_page_count": 2, + "empirical_identification_status": "NOT_ESTABLISHED", + "source_tree_files": { + "research_papers/mathematical_closure/" + name: + sha(self.source["manuscript/" + name]) + for name in a.SOURCE_FILES + }, + } + self.generated["manifest.lock.json"] = json.dumps(self.manifest).encode() + self.save() + + def save(self, *, extra=None, override=None, mode_override=None): + members = dict(self.source, **self.generated) + if override: + members.update(override) + if extra: + members.update(extra) + path = self.out / "Permansson_Mathematical_Closure_E2E.zip" + with zipfile.ZipFile(path, "w") as z: + for name, raw in sorted(members.items()): + info = zipfile.ZipInfo(name, (1980, 1, 1, 0, 0, 0)) + info.external_attr = (mode_override.get(name, 0o100644) + if mode_override else 0o100644) << 16 + z.writestr(info, raw) + for name, raw in self.generated.items(): + (self.out / name).write_bytes(raw) + ledger = "" + for name in a.SUMMED: + ledger += sha((self.out/name).read_bytes()) + " " + name + "\n" + (self.out / "SHA256SUMS.txt").write_text(ledger) + return path + + def test_complete_13_sources_and_draft_ledger_pass(self): + got = a.verify_draft_archive(self.out, self.manifest) + self.assertEqual(got["canonical_sources"], 13) + self.assertEqual(got["archived_outputs"], 5) + self.assertEqual(verifier.verify(self.out/"manifest.lock.json")["integrity"], + "PASS") + + def test_extra_or_missing_archive_members_never_pass(self): + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive( + self.out, dict(self.manifest, source_tree_files={})) + self.save(extra={"manuscript/unexpected.tex": b"hello"}) + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive(self.out, self.manifest) + self.save(extra={"../escape.txt": b"x"}) + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive(self.out, self.manifest) + + def test_modified_bibliography_or_generated_pdf_rejected(self): + self.save(override={"manuscript/references.bib": b"silently changed"}) + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive(self.out, self.manifest) + self.save() + (self.out/"Permansson_Mathematical_Closure.pdf").write_bytes(b"%PDF-BAD") + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive(self.out, self.manifest) + + def test_symlink_member_is_not_a_source_file(self): + self.save(mode_override={"manuscript/main.tex": stat.S_IFLNK | 0o777}) + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive(self.out, self.manifest) + + def test_unbound_source_ci_receipt_is_rejected(self): + replacement = json.dumps({ + "status": "ACCEPTED", "ci_receipts": [17], + "independent_reviews": [{"review": "faked"}], + }).encode() + self.save(override={"source_ci_receipts.lock.json": replacement}) + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive(self.out, self.manifest) + + def test_altered_checksum_ledger_and_duplicate_line_rejected(self): + p = self.out/"SHA256SUMS.txt" + p.write_text(p.read_text() + "0"*64 + " " + a.SUMMED[0] + "\n") + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive(self.out, self.manifest) + self.save() + p.write_text(p.read_text().replace("a", "b", 1)) + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive(self.out, self.manifest) + + def test_page_images_must_match_entire_1_to_n_set(self): + pages = self.out/"pages" + pages.mkdir() + (pages/"page-1.png").write_bytes(b"fake") + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive(self.out, self.manifest) + (pages/"page-02.png").write_bytes(b"fake") + self.assertEqual(a.verify_draft_archive(self.out, self.manifest) + ["status"], "COMPLETE_UNAPPROVED_DRAFT") + (pages/"irrelevant.png").write_bytes(b"fake") + with self.assertRaises(a.DraftArchiveError): + a.verify_draft_archive(self.out, self.manifest) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_f05_positive_scm_lean.py b/verification/research/test_final_f05_positive_scm_lean.py new file mode 100644 index 00000000..01e7e0dc --- /dev/null +++ b/verification/research/test_final_f05_positive_scm_lean.py @@ -0,0 +1,71 @@ +"""F-05 positive-support original SCM instance and true-semantic Lean mutant.""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +MODULE = "PermanssonResearch.MultiAgent.PositiveMediationWitness" +SOURCE = ROOT / "PermanssonResearch/MultiAgent/PositiveMediationWitness.lean" +PREFIX = ("import " + MODULE + "\n" + + "open " + MODULE + "\n") +INVALID = re.compile( + r"unknown (?:module|identifier|constant)|failed to import|parser error|" + r"syntax error|maximum number of heartbeats|timed out|timeout|sorryAx", + re.I) + + +def lean_probe(body): + if not shutil.which("lake"): + raise AssertionError("pinned Lean required; cannot skip formal F-05") + with tempfile.TemporaryDirectory(dir=ROOT/".lake", prefix="f05-scm-") as temp: + filename = Path(temp) / "Witness.lean" + filename.write_text(PREFIX + body, encoding="utf-8") + p = subprocess.run(["lake", "env", "lean", str(filename)], cwd=ROOT, + text=True, capture_output=True, timeout=600) + return p.returncode, p.stdout + p.stderr + + +class PositiveOriginalSCMLean(unittest.TestCase): + def test_compiled_positive_nonvacuous_identification_and_axioms(self): + self.assertTrue(SOURCE.is_file()) + rc, output = lean_probe( + "#check observationalMass_pos\n" + "#check identified_for_every_treatment\n" + "#check four_intervention_values\n" + "#check mediator_shift_nonzero\n" + "#print axioms identified_for_every_treatment\n" + "#print axioms four_intervention_values\n" + "example : exampleSCM.nestedExpectation true false = " + "(10 / 3 : \u211d) := by\n" + " exact four_intervention_values.2.2.1\n") + self.assertEqual(rc, 0, output[-4000:]) + self.assertNotRegex(output, INVALID) + for name in ("observationalMass_pos", "identified_for_every_treatment", + "four_intervention_values", "mediator_shift_nonzero"): + self.assertIn(name, output) + reports = re.findall(r"depends on axioms:\s*\[([^\]]*)\]", output) + self.assertTrue(reports, output) + for values in reports: + self.assertLessEqual( + set(x.strip() for x in values.split(",") if x.strip()), + {"propext", "Classical.choice", "Quot.sound"}, output) + + def test_wrong_positive_SCM_nested_equality_fails_semantically(self): + rc, output = lean_probe( + "example : exampleSCM.nestedExpectation true false = " + "exampleSCM.nestedExpectation true true := by\n" + " rw [four_intervention_values.2.2.1,\n" + " four_intervention_values.2.2.2]\n" + " norm_num\n") + self.assertNotEqual(rc, 0, "false SCM equality was accepted") + self.assertNotRegex(output, INVALID) + self.assertRegex(output.lower(), + r"unsolved goals|failed to prove|tactic .*failed|" + r"no goals to be solved") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_original_chronological_false_lean.py b/verification/research/test_final_original_chronological_false_lean.py new file mode 100644 index 00000000..4928818f --- /dev/null +++ b/verification/research/test_final_original_chronological_false_lean.py @@ -0,0 +1,93 @@ +"""D-02/D-03/D-05 original-source semantic false propositions in Lean. + +These are NOT source-independent numerical tests. Each false proposition +uses the actual original residence monitor or the original two-stage +committed/rectangular probability laws. The imports, types and positive +controls are pinned; only mathematical contradiction can count. +""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +INVALID = re.compile( + r"unknown (?:module|identifier|constant|tactic)|failed to import|" + r"unexpected token|parser error|out of memory|" + r"maximum number of heartbeats|object file|timed out|" + r"failed to synthesize", re.I +) +SEMANTIC = re.compile( + r"type mismatch|application type mismatch|unsolved goals|" + r"tactic .?decide.? proved that the proposition|" + r"tactic .?norm_num.? failed", re.I +) + + +def lean(module, body): + if shutil.which("lake") is None: + raise AssertionError("pinned Lean compiler is required") + with tempfile.TemporaryDirectory(dir=ROOT / ".lake", + prefix="D02D03D05-actual-") as td: + p = Path(td) / "Negative.lean" + p.write_text("import " + module + "\n" + body + "\n", + encoding="utf-8") + return subprocess.run(["lake", "env", "lean", str(p)], + cwd=ROOT, capture_output=True, text=True, + timeout=900) + + +class OriginalChronologicalBoundaryNegatives(unittest.TestCase): + def assert_genuine_false(self, module, code): + result = lean(module, code) + log = result.stdout + "\n" + result.stderr + self.assertNotEqual(result.returncode, 0, + "a false original mathematical claim compiled") + self.assertIn("error:", log.lower()) + self.assertIsNone(INVALID.search(log), log[-4500:]) + self.assertIsNotNone(SEMANTIC.search(log), log[-4500:]) + + def test_D_02_false_retryable_residence_word(self): + self.assert_genuine_false( + "PermanssonResearch.ReverseSolver.CompactResidenceMonitor", + "open PermanssonResearch.ReverseSolver.FiniteMonitor\n" + "example :\n" + " (residenceScan (fun z : Bool => z = true) 1\n" + " [true, false, true, true]).2 = false := by\n" + " have hcorrect :\n" + " (residenceScan (fun z : Bool => z = true) 1\n" + " [true, false, true, true]).2 = true := by decide\n" + " exact hcorrect\n" + ) + + def test_D_03_false_danger_after_success_revokes_it(self): + self.assert_genuine_false( + "PermanssonResearch.ReverseSolver.AvoidResidenceMonitor", + "open PermanssonResearch.ReverseSolver.FiniteMonitor\n" + "example :\n" + " (avoidRun (fun b : Bool => b = true)\n" + " (fun b : Bool => b = false) 0 ((0,false),false)\n" + " [true, false]).1.2 = false := by\n" + " exact danger_after_success_does_not_undo\n" + ) + + def test_D_05_false_committed_rectangular_path_law_equality(self): + self.assert_genuine_false( + "PermanssonResearch.ReverseSolver.CommittedVsRectangularPathFixture", + "open PermanssonResearch.ReverseSolver.CommittedVsRectangular\n" + "example :\n" + " (pathLaw false true).expectation survivesBoth =\n" + " min ((pathLaw false false).expectation survivesBoth)\n" + " ((pathLaw true true).expectation survivesBoth) := by\n" + " rw [rectangular_actual_success,\n" + " committed_false_actual_success,\n" + " committed_true_actual_success]\n" + " have hneq : (1/100 : ℝ) ≠ 9/100 := by norm_num\n" + " exact hneq\n" + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_original_risk_belief_false_lean.py b/verification/research/test_final_original_risk_belief_false_lean.py new file mode 100644 index 00000000..c6d4a6c0 --- /dev/null +++ b/verification/research/test_final_original_risk_belief_false_lean.py @@ -0,0 +1,76 @@ +"""D-04 / D-06 original mathematical false-claim tests. + +D-04 falsifies an illegal p=1 hard-risk choice using the real 17/20 +finite budget result. D-06 falsifies equality of two actually reachable +posterior completion beliefs generated from ONE original prior with both +histories of positive probability. +""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +INVALID = re.compile( + r"unknown (?:module|identifier|constant|tactic)|failed to import|" + r"unexpected token|parse error|out of memory|" + r"maximum number of heartbeats|object file|timed out|" + r"failed to synthesize", re.I +) +SEMANTIC = re.compile( + r"type mismatch|application type mismatch|unsolved goals|" + r"tactic .?norm_num.? failed|proved.*false", re.I +) + + +def compile_lean(module, source): + if shutil.which("lake") is None: + raise AssertionError("pinned Lean toolchain required") + with tempfile.TemporaryDirectory(dir=ROOT / ".lake", + prefix="D04D06-actual-") as td: + path = Path(td)/"False.lean" + path.write_text("import "+module+"\n"+source+"\n", + encoding="utf-8") + return subprocess.run(["lake", "env", "lean", str(path)], + cwd=ROOT, text=True, capture_output=True, + timeout=900) + + +class OriginalRiskAndBeliefNegatives(unittest.TestCase): + def reject_false(self, module, source): + run = compile_lean(module, source) + out = run.stdout + "\n" + run.stderr + self.assertNotEqual(run.returncode, 0, + "mathematically false original-source conclusion accepted") + self.assertIn("error:", out.lower()) + self.assertIsNone(INVALID.search(out), out[-3500:]) + self.assertIsNotNone(SEMANTIC.search(out), out[-3500:]) + + def test_D_04_false_hard_risk_budget_accepts_p_one(self): + self.reject_false( + "PermanssonResearch.ReverseSolver.SolverBoundaryWitnesses", + "open PermanssonResearch.ReverseSolver.SolverBoundaryWitnesses\n" + "example : ((1 : ℚ) / 10 ≤ 1/20) := by\n" + " exact hard_risk_half_mixture_attains.2.2\n" + ) + + def test_D_06_false_same_readout_identifies_completion(self): + self.reject_false( + "PermanssonResearch.ReverseSolver.ReachableCompletionBeliefFixture", + "open PermanssonResearch.ReverseSolver.ReachableCompletionBelief\n" + "example : (model.filter 0 initial [(false, ())]).weight true =\n" + " (model.filter 0 initial [(true, ())]).weight true := by\n" + " have hneq :\n" + " (model.filter 0 initial [(false, ())]).weight true ≠\n" + " (model.filter 0 initial [(true, ())]).weight true := by\n" + " rw [reachable_posteriors_disagree.1,\n" + " reachable_posteriors_disagree.2]\n" + " norm_num\n" + " exact hneq\n" + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_final_postseal_replay.py b/verification/research/test_final_postseal_replay.py new file mode 100644 index 00000000..ff950222 --- /dev/null +++ b/verification/research/test_final_postseal_replay.py @@ -0,0 +1,122 @@ +"""Postseal candidate negative tests; never claim real authentication.""" +import json +from pathlib import Path +import sys +import tempfile +import unittest +from unittest import mock +import zipfile + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "release/mathematical-closure-1")) +import postseal_replay as replay +from seal_approved import SealError + +class PostsealTests(unittest.TestCase): + def setUp(self): + temp = tempfile.TemporaryDirectory() + self.addCleanup(temp.cleanup) + root = Path(temp.name) + self.folder = root / "candidate" + self.folder.mkdir() + self.archive = self.folder / "Permansson_Mathematical_Closure_CANDIDATE.zip" + self.sidecar = self.folder / "SHA256SUMS.txt" + self.out = root / "detached" / "replay.json" + self.manifest = { + "format": "Permansson.MathClosure.CandidateSeal.v3", + "status": "CANDIDATE_SEAL_BUILT", "full45_claimed": True, + "accepted_case_count": 45, "accepted_claim_count": 11, + "source_commit_sha": "a"*40, "head_sha": "b"*40, + "pdf_sha256": "c"*64, + } + self.make_zip() + + def make_zip(self): + with zipfile.ZipFile(self.archive, "w") as z: + z.writestr("approved_seal.json", json.dumps(self.manifest)) + with zipfile.ZipFile(self.archive) as z: + mb = z.read("approved_seal.json") + self.sidecar.write_text( + replay.sha(self.archive.read_bytes())+" "+self.archive.name+"\n"+ + replay.sha(mb)+" approved_seal.json\n") + + def test_replay_always_stays_unapproved(self): + with mock.patch.object(replay, "verify_approved_archive", return_value=True): + result = replay.replay(self.archive, self.sidecar, self.out) + self.assertFalse(result["authenticated_ci_artifact"]) + self.assertFalse(result["full_release_approved"]) + self.assertEqual(result["status"], "CANDIDATE_ARCHIVE_REPLAYED_UNREVIEWED") + + def test_original_input_symlinks_and_ancestor_links_fail_closed(self): + other = self.folder / "alias.zip" + other.symlink_to(self.archive) + with mock.patch.object(replay, "verify_approved_archive", return_value=True): + with self.assertRaises(SealError): + replay.replay(other, self.sidecar, self.out) + other.unlink() + sidecar_alias = self.folder / "alias.sha256" + sidecar_alias.symlink_to(self.sidecar) + with mock.patch.object(replay, "verify_approved_archive", return_value=True): + with self.assertRaises(SealError): + replay.replay(self.archive, sidecar_alias, self.out) + sidecar_alias.unlink() + parent_alias = self.folder.parent / "alias-dir" + parent_alias.symlink_to(self.folder, target_is_directory=True) + with mock.patch.object(replay, "verify_approved_archive", return_value=True): + with self.assertRaises(SealError): + replay.replay(parent_alias / self.archive.name, + self.sidecar, self.out) + with self.assertRaises(SealError): + replay.replay(self.archive, self.sidecar, + parent_alias / "replay.json") + self.assertFalse(self.out.exists()) + + def test_obsolete_candidate_v1_is_not_accepted_as_a_full_seal(self): + self.manifest["format"] = "Permansson.MathClosure.CandidateSeal.v1" + self.make_zip() + with mock.patch.object(replay, "verify_approved_archive") as verifier: + with self.assertRaises(SealError): + replay.replay(self.archive, self.sidecar, self.out) + verifier.assert_not_called() + + def test_wrong_sidecar_fails(self): + self.sidecar.write_text("bad checksum\n") + with mock.patch.object(replay, "verify_approved_archive", return_value=True): + with self.assertRaises(SealError): + replay.replay(self.archive, self.sidecar, self.out) + self.assertFalse(self.out.exists()) + + def test_replay_bounds_seal_before_parsing_or_full_archive_verification(self): + with mock.patch.object(replay, "MAX_MANIFEST_BYTES", 16), \ + mock.patch.object(replay, "verify_approved_archive") as verifier: + with self.assertRaises(SealError): + replay.replay(self.archive, self.sidecar, self.out) + verifier.assert_not_called() + self.assertFalse(self.out.exists()) + + def test_duplicate_or_case_colliding_seal_members_never_replay(self): + for duplicate_name in ("approved_seal.json", "APPROVED_SEAL.JSON"): + with self.subTest(duplicate_name=duplicate_name): + with zipfile.ZipFile(self.archive, "w") as z: + z.writestr("approved_seal.json", json.dumps(self.manifest)) + z.writestr(duplicate_name, json.dumps(self.manifest)) + with mock.patch.object(replay, "verify_approved_archive") as verifier: + with self.assertRaises(SealError): + replay.replay(self.archive, self.sidecar, self.out) + verifier.assert_not_called() + self.assertFalse(self.out.exists()) + + def test_incomplete_claims_and_existing_output_fail(self): + self.manifest["accepted_claim_count"] = 10 + self.make_zip() + with mock.patch.object(replay, "verify_approved_archive", return_value=True): + with self.assertRaises(SealError): + replay.replay(self.archive, self.sidecar, self.out) + self.manifest["accepted_claim_count"] = 11 + self.make_zip() + self.out.parent.mkdir(parents=True) + self.out.write_text("KEEP") + with mock.patch.object(replay, "verify_approved_archive", return_value=True): + with self.assertRaises(SealError): + replay.replay(self.archive, self.sidecar, self.out) + self.assertEqual(self.out.read_text(), "KEEP") diff --git a/verification/research/test_final_release_manifest.py b/verification/research/test_final_release_manifest.py new file mode 100644 index 00000000..4cae61a6 --- /dev/null +++ b/verification/research/test_final_release_manifest.py @@ -0,0 +1,29 @@ +"""No incomplete draft may masquerade as a mathematical release.""" +import json,sys,tempfile,unittest +from pathlib import Path +ROOT=Path(__file__).resolve().parents[2] +sys.path.insert(0,str(ROOT/"release/mathematical-closure-1")) +import verify_release as vr + +class CandidateReleaseTests(unittest.TestCase): + def test_unreviewed_manifest_is_still_draft(self): + obj=json.loads((ROOT/"release/mathematical-closure-1/manifest.json").read_text()) + self.assertEqual(obj["status"],"DRAFT_NOT_ACCEPTED") + self.assertIsNone(obj["source_commit_sha"]) + self.assertFalse(obj["approved_manuscript_claims"]) + def test_fake_receipt_with_no_pdf_is_rejected(self): + with tempfile.TemporaryDirectory() as t: + p=Path(t)/"manifest.lock.json" + p.write_text(json.dumps({"format":"Permansson.MathClosure.BuildReceipt.v1", + "mode":"DRAFT","empirical_identification_status":"NOT_ESTABLISHED", + "pdf_sha256":"0"*64,"tex_sha256":"1"*64, + "latex_log_sha256":"2"*64})) + with self.assertRaises(ValueError): vr.verify(p) + def test_strict_rejects_draft(self): + with tempfile.TemporaryDirectory() as t: + p=Path(t)/"manifest.lock.json" + p.write_text(json.dumps({"format":"Permansson.MathClosure.BuildReceipt.v1", + "mode":"DRAFT","status":"DRAFT_NOT_ACCEPTED", + "empirical_identification_status":"NOT_ESTABLISHED"})) + with self.assertRaises((KeyError,ValueError)): vr.verify(p,strict=True) +if __name__=="__main__":unittest.main() diff --git a/verification/research/test_final_release_seal.py b/verification/research/test_final_release_seal.py new file mode 100644 index 00000000..3be03a35 --- /dev/null +++ b/verification/research/test_final_release_seal.py @@ -0,0 +1,386 @@ +"""Byte integrity tests; mocked approvals are NEVER actual release evidence.""" +import hashlib +import importlib.util +import json +from pathlib import Path +import sys +import tempfile +import unittest +from unittest import mock +import zipfile + +ROOT = Path(__file__).resolve().parents[2] +RELEASE = ROOT / "release/mathematical-closure-1" +if str(RELEASE) not in sys.path: + sys.path.insert(0, str(RELEASE)) +import reproduce +import seal_approved as seal +import archive_integrity as integrity + +H = "a" * 40 +M = "b" * 40 +digest = lambda x: hashlib.sha256(x).hexdigest() + + +class ApprovedSealByteTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory(prefix="perm-seal-test-") + self.addCleanup(self.tmp.cleanup) + base = Path(self.tmp.name) + self.draft = base / "draft" + self.draft.mkdir() + self.output = base / "approved" + self.pdf = b"%PDF-1.4\nFAKE TEST BYTES ONLY\n" + (self.draft / "Permansson_Mathematical_Closure.pdf").write_bytes(self.pdf) + (self.draft / "Permansson_Mathematical_Closure.tex").write_text( + "% Mock TeX without real paper/reviewer\n") + (self.draft / "pages").mkdir() + (self.draft / "pages" / "page-1.png").write_bytes(b"MOCK PNG") + self.pdfsha = digest(self.pdf) + self.pages = {"page-1.png": digest(b"MOCK PNG")} + self.manifest = { + "format": "Permansson.MathClosure.BuildReceipt.v1", + "status": "DRAFT_NOT_ACCEPTED", "mode": "DRAFT", + "source_commit_sha": M, "pdf_sha256": self.pdfsha, + "tex_sha256": digest((self.draft / "Permansson_Mathematical_Closure.tex").read_bytes()), + "pdf_page_count": 1, + "source_tree_files": { + "research_papers/mathematical_closure/" + path: + digest(("FAKE_SOURCE_" + path).encode()) + for path in integrity.SOURCE_FILES + }, + } + (self.draft / "manifest.lock.json").write_text( + json.dumps(self.manifest)) + (self.draft / "latex_build.log").write_bytes(b"FAKE_TEX_LOG") + self.manifest["latex_log_sha256"] = digest(b"FAKE_TEX_LOG") + (self.draft / "manifest.lock.json").write_text(json.dumps(self.manifest)) + source_ci = {"status": "MISSING_POST_MERGE_SOURCE_AND_REVIEWS", + "ci_receipts": [], "independent_reviews": []} + (self.draft / "source_ci_receipts.lock.json").write_text(json.dumps(source_ci)) + archive_path = self.draft / "Permansson_Mathematical_Closure_E2E.zip" + with zipfile.ZipFile(archive_path, "w") as z: + for path in integrity.SOURCE_FILES: + z.writestr("manuscript/" + path, + ("FAKE_SOURCE_" + path).encode()) + for name in integrity.GENERATED: + z.write(self.draft / name, name) + (self.draft / "SHA256SUMS.txt").write_text("".join( + digest((self.draft / name).read_bytes()) + " " + name + "\n" + for name in integrity.SUMMED)) + self.external_bundle = self.draft / "unavailable.zip" + self.external_bundle.write_bytes(b"MOCK_EXTERNAL_UNREVIEWED_PROOF_BUNDLE") + self.receipts = { + "evidence_bundle_sha256": digest(self.external_bundle.read_bytes()) + } + self.approved = { + "status": "APPROVED_SOURCE_BOUND", "source_commit_sha": M, + "pdf_sha256": self.pdfsha, "pdf_pages": self.pages, + "claims": ["Q1"], "scientific_limitations": ["no empirical GR/PR"], + "empirical_identification_status": "NOT_ESTABLISHED", + } + + def test_standard_zip_permission_only_mode_not_mistaken_for_symlink(self): + import io + import stat + raw = io.BytesIO() + with zipfile.ZipFile(raw, "w", zipfile.ZIP_DEFLATED) as archive: + archive.writestr("valid.txt", b"synthetic regular artifact") + with zipfile.ZipFile(io.BytesIO(raw.getvalue())) as archive: + self.assertFalse( + stat.S_ISREG(archive.infolist()[0].external_attr >> 16)) + seal.bounded_zip_members( + archive, {"valid.txt"}, total_limit=1024, member_limit=1024) + + malicious = io.BytesIO() + with zipfile.ZipFile(malicious, "w", zipfile.ZIP_DEFLATED) as archive: + entry = zipfile.ZipInfo("valid.txt") + entry.external_attr = (stat.S_IFLNK | 0o777) << 16 + archive.writestr(entry, b"unauthorized symlink") + with zipfile.ZipFile(io.BytesIO(malicious.getvalue())) as archive: + with self.assertRaises(seal.SealError): + seal.bounded_zip_members( + archive, {"valid.txt"}, total_limit=1024, member_limit=1024) + + def test_changed_pdf_or_unrendered_page_never_seals(self): + approval = dict(self.approved, pdf_sha256="0"*64) + with self.assertRaises(seal.SealError): + seal.prepare_approved_manifest(self.draft, approval) + (self.draft/"pages"/"page-1.png").unlink() + with self.assertRaises(seal.SealError): + seal.prepare_approved_manifest(self.draft, self.approved) + + def test_external_ids_require_exact_original_bytes(self): + from pr113_owner_partition_v3 import FORMAT, CLAIMS + folder = Path(self.tmp.name) / "source" + folder.mkdir() + (folder/"101.zip").write_bytes(b"PK_fake") + (folder/"102.zip").write_bytes(b"PK_fake") + (folder/"103.zip").write_bytes(b"PK_fake") + cases = [f"C-{i:02d}" for i in range(1, 46)] + contract = {"format": FORMAT, "source_controls_accepted": False, + "independent_security_review": False, + "mathematical_approval": False, + "producers": [ + {"artifact_id":101, "owners":[ + {"kind":"original_positive","owner_id":i} for i in cases]}, + {"artifact_id":102, "owners":[ + {"kind":"legacy_negative","owner_id":i} for i in cases[:20]]}, + {"artifact_id":103, "owners":[ + {"kind":"claim_pair","owner_id":i} for i in sorted(CLAIMS)]}, + ]} + extra = Path(self.tmp.name)/"contract.json" + extra.write_text(json.dumps(contract)) + workflow = Path(self.tmp.name)/"workflow.json" + workflow.write_text("{}") + rep = Path(self.tmp.name)/"reproduction.zip" + rep.write_bytes(b"PK_mock") + ids = Path(self.tmp.name)/"ids.json" + ids.write_bytes(b'{"source":"original", "run_id":101}') + inputs = {"zip_file":rep,"identities":{"source":"original","run_id":101}} + extras = {"additional_contract":extra,"workflow_blob_candidates":workflow, + "additional_zips":folder} + children = seal.external_evidence_children( + inputs, extras, reproduction_ids_source=ids) + self.assertEqual(children["external/reproduction_ids.json"], + ids.read_bytes()) + ids.write_bytes(b'{"source":"changed","run_id":101}') + with self.assertRaises(seal.SealError): + seal.external_evidence_children( + inputs, extras, reproduction_ids_source=ids) + + def test_mocked_seal_preserves_original_draft_and_exact_bytes(self): + before = (self.draft/"manifest.lock.json").read_bytes() + with mock.patch.object(seal.draft_verifier, "verify", return_value={ + "integrity": "PASS", "accepted": False}), \ + mock.patch.object(seal, "strict_acceptance", return_value={ + "source": M, "frozen_head": H, + "accepted_case_count": 1, "accepted_claim_count": 1}): + result = seal.seal(self.draft, self.approved, self.receipts, + self.external_bundle, self.output) + self.assertEqual(result["status"], "APPROVED_SOURCE_BOUND") + self.assertEqual((self.draft/"manifest.lock.json").read_bytes(), before) + with zipfile.ZipFile(self.output/"Permansson_Mathematical_Closure_APPROVED.zip") as z: + self.assertEqual(z.read("Permansson_Mathematical_Closure.pdf"), self.pdf) + self.assertEqual(json.loads(z.read("original_draft_manifest.lock.json")), + self.manifest) + self.assertEqual(z.read("detached_proof_mutation_bundle.zip"), + self.external_bundle.read_bytes()) + self.assertEqual(json.loads(z.read("source_acceptance_receipts.json")), + self.receipts) + self.assertEqual(len([n for n in z.namelist() + if n.startswith("manuscript/")]), 13) + + def test_full_seal_cannot_promote_partial_scope(self): + from final_closure_full_contract import ORIGINAL_CLAIMS + approved = dict(self.approved, + claims=sorted(ORIGINAL_CLAIMS), + approved_manuscript_claims=sorted(ORIGINAL_CLAIMS)) + with mock.patch.object(seal.draft_verifier, "verify", + return_value={"integrity": "PASS"}), \ + mock.patch.object(seal, "strict_acceptance", + return_value={ + "source": M, "frozen_head": H, + "status": "FULL_MATHEMATICAL_CLOSURE_RELEASE_READY", + "accepted_case_count": 44, + "accepted_claim_count": 11}): + with self.assertRaises(seal.SealError): + seal.seal(self.draft, approved, {}, + self.draft/"fake_bundle.zip", self.output, full=True) + + def test_full_seal_mocked_success_still_requires_exact_claim_set(self): + with mock.patch.object(seal.draft_verifier, "verify", + return_value={"integrity": "PASS"}), \ + mock.patch.object(seal, "strict_acceptance", + return_value={ + "source": M, "frozen_head": H, + "status": "FULL_MATHEMATICAL_CLOSURE_RELEASE_READY", + "accepted_case_count": 45, + "accepted_claim_count": 11}): + with self.assertRaises(seal.SealError): + seal.seal(self.draft, self.approved, {}, + self.draft/"fake_bundle.zip", self.output, full=True) + + def test_connected_mock_preseal_postseal_replay_does_not_authorize(self): + """Run actual ZIP/manifest/checksum/replay code end to end. + + The only substituted boundary is the external acceptance gate. + These fixture bytes and their output are not GitHub or scholarly + evidence, even when the candidate archive replays successfully. + """ + import postseal_replay as replay + from final_closure_full_contract import ORIGINAL_CLAIMS + + claims = sorted(ORIGINAL_CLAIMS) + approved = dict(self.approved, claims=claims, + approved_manuscript_claims=claims) + mock_external_review = { + "source": M, "frozen_head": H, + "status": "POSTMERGE_PRESEAL_READY", + "accepted_case_count": 45, "accepted_claim_count": 11, + "receipt_contract": "ALL_45_HISTORICAL_AND_ALL_11_COMPLETE", + "effective_cases": { + f"C-{index:02d}": "VERIFIED_SELECTED_SCOPE" + for index in range(1, 46)}, + "effective_claims": claims, + } + # Explicit synthetic owner inputs: 76 owners across THREE artifacts, + # not 76 ZIPs. The external acceptance is mocked and never approved. + from pr113_owner_partition_v3 import FORMAT as OWNER_FORMAT, CLAIMS + owner_dir = Path(self.tmp.name) / "mock-original-owners" + owner_dir.mkdir() + for i in (101, 102, 103): + (owner_dir / (str(i) + ".zip")).write_bytes(b"PK_fake_unreviewed") + reproduction_zip = Path(self.tmp.name) / "mock-reproduction.zip" + reproduction_zip.write_bytes(b"PK_fake_reproduction") + cases = [f"C-{i:02d}" for i in range(1, 46)] + owner_registry = { + "format": OWNER_FORMAT, + "source_controls_accepted": False, + "independent_security_review": False, + "mathematical_approval": False, + "producers": [ + {"artifact_id":101, "owners":[ + {"kind":"original_positive","owner_id":cid} for cid in cases]}, + {"artifact_id":102, "owners":[ + {"kind":"legacy_negative","owner_id":cid} for cid in cases[:20]]}, + {"artifact_id":103, "owners":[ + {"kind":"claim_pair","owner_id":cid} for cid in sorted(CLAIMS)]}, + ], + } + extra_contract = Path(self.tmp.name) / "extra_contract.json" + extra_contract.write_text(json.dumps(owner_registry)) + workflow_blobs = Path(self.tmp.name) / "workflow_blobs.json" + workflow_blobs.write_text("{}") + reproduction_ids = Path(self.tmp.name) / "mock-reproduction-ids.json" + reproduction_ids.write_text(json.dumps({"synthetic": True})) + reproduction = {"zip_file": reproduction_zip, "identities": {"synthetic": True}} + v31 = {"additional_contract": extra_contract, + "workflow_blob_candidates": workflow_blobs, + "additional_zips": owner_dir} + with mock.patch.object( + seal.draft_verifier, "verify", + return_value={"integrity": "TEST_FIXTURE_ONLY"}): + with mock.patch.object( + seal, "strict_acceptance", + return_value=mock_external_review): + result = seal.seal( + self.draft, approved, self.receipts, + self.external_bundle, self.output, full=True, + reproduction_attachment=reproduction, v31_attachment=v31, + reproduction_ids_source=reproduction_ids) + + self.assertEqual(result["status"], "CANDIDATE_SEAL_BUILT") + archive = self.output / "Permansson_Mathematical_Closure_CANDIDATE.zip" + sidecar = self.output / "SHA256SUMS.txt" + target = Path(self.tmp.name) / "detached" / "replay.json" + receipt = replay.replay(archive, sidecar, target) + self.assertEqual( + receipt["status"], "CANDIDATE_ARCHIVE_REPLAYED_UNREVIEWED") + self.assertEqual(receipt["source_commit_sha"], M) + self.assertTrue(receipt["complete_original_members_verified"]) + self.assertEqual(json.loads(target.read_bytes()), receipt) + for key in ("authenticated_ci_artifact", "independent_review", + "full_release_approved"): + self.assertFalse(receipt[key], key) + + # The exact sidecar is mandatory: altered bytes must fail + # independently of the simulated upstream review predicate. + sidecar.write_text("0" * 64 + " forged.zip\n") + with self.assertRaises(seal.SealError): + replay.replay( + archive, sidecar, + Path(self.tmp.name) / "tampered" / "replay.json") + + def test_partial_additional_evidence_inputs_fail_before_seal(self): + approved = Path(self.tmp.name) / "approved.json" + receipts = Path(self.tmp.name) / "receipts.json" + approved.write_text(json.dumps(self.approved)) + receipts.write_text(json.dumps(self.receipts)) + args = [ + "--draft-dir", str(self.draft), + "--approved-claim-manifest", str(approved), + "--source-receipts", str(receipts), + "--bundle", str(self.external_bundle), + "--out", str(self.output), + ] + fields = ("--additional-contract", "--additional-zips", + "--workflow-blob-candidates") + for flags in ((fields[0],), fields[:2], (fields[2],)): + with self.subTest(flags=flags), mock.patch.object( + seal, "seal") as actual_seal: + test_args = list(args) + for flag in flags: + test_args.extend((flag, str(self.draft))) + self.assertEqual(seal.main(test_args), 2) + actual_seal.assert_not_called() + self.assertFalse(self.output.exists()) + + def test_full_seal_uses_preseal_not_the_circular_full_gate(self): + from final_closure_full_contract import ORIGINAL_CLAIMS + approved = dict(self.approved, claims=sorted(ORIGINAL_CLAIMS), + approved_manuscript_claims=sorted(ORIGINAL_CLAIMS)) + scopes = [] + def blocked(receipts, scope, manifest, bundle_path=None): + scopes.append(scope) + raise seal.SealError("no real independent preseal authorization") + with mock.patch.object(seal.draft_verifier, "verify", + return_value={"integrity": "PASS"}), \ + mock.patch.object(seal, "strict_acceptance", side_effect=blocked): + with self.assertRaises(seal.SealError): + seal.seal(self.draft, approved, self.receipts, + self.external_bundle, self.output, full=True) + self.assertEqual(scopes, ["postmerge-preseal-strict"]) + self.assertFalse(self.output.exists()) + + def test_reproducibility_reports_different_bytes_instead_of_relabeling(self): + first, second = self.draft, Path(self.tmp.name)/"second" + import shutil + shutil.copytree(first, second) + for name in reproduce.FILES: + payload = (json.dumps(self.manifest).encode() + if name == "manifest.lock.json" else b"identical") + (first/name).write_bytes(payload) + (second/name).write_bytes(payload) + data = reproduce.compare_artifacts(first, second) + self.assertEqual(data["status"], "REPRODUCIBLE_DRAFT_ONLY") + (second/"Permansson_Mathematical_Closure.pdf").write_bytes(b"different") + with self.assertRaises(reproduce.ReproducibilityError): + reproduce.compare_artifacts(first, second) + + + def test_modified_approved_claim_member_rejects_even_with_new_zip_crc(self): + """Reproduce the formerly accepted claim-member mutation.""" + with mock.patch.object(seal.draft_verifier, "verify", return_value={ + "integrity": "TEST_ONLY"}), \ + mock.patch.object(seal, "strict_acceptance", return_value={ + "source": M, "frozen_head": H, + "accepted_case_count": 1, "accepted_claim_count": 1}): + seal.seal(self.draft, self.approved, self.receipts, + self.external_bundle, self.output) + archive = self.output / "Permansson_Mathematical_Closure_APPROVED.zip" + with zipfile.ZipFile(archive) as source: + seal_manifest = json.loads(source.read("approved_seal.json")) + members = {name: source.read(name) for name in source.namelist()} + wrong_claims = json.loads(members["approved_claims_manifest.json"]) + wrong_claims["claims"] = ["MUTATED_CLAIM"] + members["approved_claims_manifest.json"] = json.dumps(wrong_claims).encode() + replacement = self.output / "rewritten.zip" + with zipfile.ZipFile(replacement, "w", compression=zipfile.ZIP_DEFLATED) as zipped: + for name, raw in sorted(members.items()): + zipped.writestr(name, raw) + with self.assertRaises(seal.SealError): + seal.verify_approved_archive(replacement, seal_manifest) + + def test_approved_archive_cannot_contain_extra_members(self): + archive = Path(self.tmp.name)/"bad.zip" + manifest = {"pdf_sha256": self.pdfsha, "pdf_pages": self.pages} + with zipfile.ZipFile(archive, "w") as z: + z.writestr("approved_seal.json", json.dumps(manifest)) + z.writestr("unexpected", b"malicious") + with self.assertRaises(seal.SealError): + seal.verify_approved_archive(archive, manifest) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_localized_prefix_compiler.py b/verification/research/test_localized_prefix_compiler.py index c0f49038..cb3384bc 100644 --- a/verification/research/test_localized_prefix_compiler.py +++ b/verification/research/test_localized_prefix_compiler.py @@ -1,13 +1,13 @@ -"""Real, mandatory compiler gate in the existing Research discovery step. +"""Mandatory ORIGINAL A2 replay at its frozen source, not at current HEAD. -No skip path. Unit-only kit QA intentionally runs separately and does NOT -claim this integration test passed. No recursive test discovery is invoked. +This registered P-02 method also compiles current source through the native +successor. Neither result grants migration review or merge authority. """ import json from pathlib import Path import tempfile import unittest -from localized_prefix_gate import verify +from pr113_historical_a2_replay_v1 import verify, HISTORICAL_A2, STATUS ROOT = Path(__file__).resolve().parents[2] @@ -15,16 +15,35 @@ class LocalizedCompilerIntegration(unittest.TestCase): def test_actual_final_source_and_semantic_mutations(self): with tempfile.TemporaryDirectory(prefix="a2p02-proof-") as temp: - out = Path(temp) - report = verify(ROOT, out, mode="focused") - # Existing required workflow stores stdout in its source-bound job log. - # Emit raw logs as well as the receipt; do not leave the only evidence - # in a temporary directory that disappears at the end of this test. - print("\nA2P02_RECEIPT_BEGIN\n" + json.dumps(report, sort_keys=True) + "\nA2P02_RECEIPT_END") - for entry in report["commands"]: - print("\nA2P02_LOG_BEGIN " + entry["log"]) - print((out / entry["log"]).read_text(encoding="utf-8", errors="replace")) - print("A2P02_LOG_END " + entry["log"]) - self.assertEqual(report["status"], "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING", - report.get("reason", "compiler gate failed")) - self.assertTrue(report["source_unchanged"]) + out = Path(temp) / "original-evidence" + try: + report = verify(ROOT, out, mode="focused") + finally: + # Preserve original raw evidence even if replay fails. A + # failing compiler must never disappear with its worktree. + for scope in ("historical_original_A2", "pr113_successor_A2"): + folder = out / scope + receipt = folder / "receipt.json" + if receipt.is_file(): + print("A2P02_SCOPED_RECEIPT_BEGIN", scope) + print(receipt.read_text(encoding="utf-8")) + print("A2P02_SCOPED_RECEIPT_END", scope) + for log in sorted(folder.glob("*.log")): + print("A2P02_SCOPED_LOG_BEGIN", scope, log.name) + print(log.read_text(encoding="utf-8", errors="replace")) + print("A2P02_SCOPED_LOG_END", scope, log.name) + self.assertEqual(report["status"], STATUS) + self.assertEqual(report["historical_source_sha"], HISTORICAL_A2) + self.assertEqual(report["execution_scope"], "HISTORICAL_A2_AND_CURRENT_SOURCE") + self.assertTrue(report["original_compiler_receipt"]["source_unchanged"]) + self.assertTrue(report["current_compiler_required_separately"]) + self.assertTrue(report["current_source_compiler_executed"]) + self.assertTrue(report["current_compiler_receipt"]["source_unchanged"]) + self.assertFalse(report["historical_project_build_imported"]) + self.assertFalse(report["historical_executor_modified"]) + self.assertFalse(report["independent_security_review"]) + self.assertFalse(report["mathematical_acceptance"]) + self.assertFalse(report["merge_authorized"]) + print("PR113_HISTORICAL_ENGINEERING_REPLAY_BEGIN") + print(json.dumps(report, sort_keys=True)) + print("PR113_HISTORICAL_ENGINEERING_REPLAY_END") diff --git a/verification/research/test_localized_prefix_integrity.py b/verification/research/test_localized_prefix_integrity.py index a73684be..c636b08d 100644 --- a/verification/research/test_localized_prefix_integrity.py +++ b/verification/research/test_localized_prefix_integrity.py @@ -94,13 +94,89 @@ def test_no_automatic_GR_or_empirical_promotion(self): self.assertNotIn(forbidden, text) def test_compiler_gate_is_installed_and_discovered(self): - integration = (ROOT / "verification/research/test_localized_prefix_compiler.py").read_text() - self.assertIn("report = verify(ROOT, out, mode=\"focused\")", integration) - self.assertNotIn("skipTest", integration) - self.assertNotIn("@unittest.skip", integration) + """Historical compiler remains exact; active30 uses authenticated v3. + + A changed installed source may NOT borrow the original PR80 proof: + the historical test is checked at its real introducer Git object. + The current installed test must match the exact staged successor. + Neither path may be skipped or relabelled as mathematical acceptance. + """ + from pr113_a2_successor_identity_v2 import ( + HISTORICAL_A2_INTRODUCER, LATE_A2_TEST_ORIGINAL_BLOB, + LATE_A2_TEST_PROPOSED_BLOB, + ) + from pr113_workflow_profile_v2 import audited_source_phase + + rel = "verification/research/test_localized_prefix_compiler.py" + original_oid = subprocess.check_output( + ["git", "rev-parse", HISTORICAL_A2_INTRODUCER + ":" + rel], + cwd=ROOT, stderr=subprocess.DEVNULL, timeout=20, + text=True, + ).strip() + self.assertEqual(original_oid, LATE_A2_TEST_ORIGINAL_BLOB) + original = subprocess.check_output( + ["git", "show", HISTORICAL_A2_INTRODUCER + ":" + rel], + cwd=ROOT, stderr=subprocess.DEVNULL, timeout=20, + ) + self.assertIn( + b'report = verify(ROOT, out, mode="focused")', original) + self.assertNotIn(b"skipTest", original) + + checked = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=ROOT, + stderr=subprocess.DEVNULL, timeout=20, text=True, + ).strip() + phase, profile = audited_source_phase(ROOT, checked) + self.assertIn(phase, {"staged", "active"}) + self.assertEqual(profile["historical_original_workflow_count"], 25) + self.assertEqual(profile["current_reviewed_workflow_count"], 30) + + installed = (ROOT / rel).read_bytes() + if phase == "staged": + # Current engineering discovery freshly replays the unchanged + # executor at original A2; it does not compare new H to old H. + import hashlib + from pr113_a2_successor_identity import INHERITED_A2_VERIFIERS + actual = hashlib.sha1( + b"blob " + str(len(installed)).encode() + b"\0" + installed + ).hexdigest() + self.assertEqual(actual, INHERITED_A2_VERIFIERS[rel]) + self.assertEqual(actual, subprocess.check_output( + ["git", "rev-parse", "HEAD:" + rel], cwd=ROOT, + text=True, timeout=20).strip()) + self.assertIn(b"from pr113_historical_a2_replay_v1 import verify", installed) + self.assertIn(b'HISTORICAL_A2_AND_CURRENT_SOURCE', installed) + current = (ROOT / "verification/research/test_pr113_a2_successor_compiler.py").read_text() + self.assertIn("selected = select_compiler(ROOT)", current) + self.assertIn('record = compiler.verify(ROOT, folder, mode="focused")', current) + self.assertNotIn("skipTest", current) + self.assertNotIn("@unittest.skip", current) + else: + staged = ( + "docs/operations/pr113_required_gate_replacement_unapproved/" + "test_localized_prefix_compiler.py" + ) + self.assertEqual(subprocess.check_output( + ["git", "rev-parse", "HEAD:" + staged], + cwd=ROOT, stderr=subprocess.DEVNULL, timeout=20, + text=True, + ).strip(), LATE_A2_TEST_PROPOSED_BLOB) + self.assertEqual(installed, (ROOT / staged).read_bytes()) + self.assertIn(b"check_stored(candidate, producer_id, reviewer_id)", + installed) + self.assertIn(b"strict_json_object(", installed) + self.assertIn(b"PERMANSSON_HISTORICAL_REQUIRED_RECEIPT", installed) + self.assertIn( + b"PR113_HISTORICAL_SOURCE_REVERIFIED_FOR_AUTHENTIC_DESCENDANT", + installed) + self.assertNotIn( + b'report = verify(ROOT, out, mode="focused")', installed) + self.assertNotIn(b"skipTest", installed) + self.assertNotIn(b"@unittest.skip", installed) gate = (ROOT / "verification/research/localized_prefix_gate.py").read_text() self.assertNotIn("unittest discover", gate) - for rel in LIBRARIES: self.assertTrue((ROOT / rel).is_file()) + for rel in LIBRARIES: + self.assertTrue((ROOT / rel).is_file()) def test_audit_declarations_are_unique_and_located(self): self.assertEqual(len(DECLS), len(set(DECLS))) diff --git a/verification/research/test_m02_actual_history_kernel_lean.py b/verification/research/test_m02_actual_history_kernel_lean.py new file mode 100644 index 00000000..ef77d5d2 --- /dev/null +++ b/verification/research/test_m02_actual_history_kernel_lean.py @@ -0,0 +1,76 @@ +"""M-02: original sampled-action/next-opponent-key marked source proofs. + +Requires exact Lean elaboration, actual oneStepHistoryKernel.map opponentMark, +and a semantically rejected false diagonal assertion. A passing unmarked +state law alone is deliberately insufficient. +""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +MOD = "PermanssonResearch.MultiAgent.M02ActualMarkedHistoryFixture" +DECL = ( + "strictAnchor_isStrict", + "markedLaw_actual_source_atom", + "actual_unmarked_next_key_fair", + "actual_unmarked_next_key_laws_equal", + "actual_marked_diagonal_separation", + "update_mechanisms_differ", +) +ALLOW = {"propext", "Classical.choice", "Quot.sound"} + + +def compile_lean(source): + if shutil.which("lake") is None: + raise AssertionError("pinned Lean required") + with tempfile.TemporaryDirectory(prefix="M02-full-", dir=ROOT/".lake") as d: + p = Path(d)/"Probe.lean" + p.write_text(source, encoding="utf-8") + return subprocess.run(["lake", "env", "lean", str(p)], + cwd=ROOT, capture_output=True, + text=True, timeout=900) + + +class M02RealMarkedHistory(unittest.TestCase): + def test_real_marked_history_full_source_and_axioms(self): + source = "import " + MOD + "\n" + source += "".join("#check " + MOD + "." + d + "\n" + "#print axioms " + MOD + "." + d + "\n" for d in DECL) + r = compile_lean(source) + log = r.stdout + "\n" + r.stderr + self.assertEqual(r.returncode, 0, log[-3800:]) + self.assertNotIn("error:", log.lower()) + self.assertNotIn("sorryAx", log) + for d in DECL: + self.assertIn(MOD+"."+d, log) + rows = re.findall(r"depends on axioms:\s*\[([^\]]*)\]", log) + self.assertTrue(rows, log) + for row in rows: + self.assertLessEqual( + {v.strip() for v in row.split(",") if v.strip()}, ALLOW) + + def test_falsified_actual_marked_diagonal_is_rejected(self): + source = ( + "import " + MOD + "\n" + "open " + MOD + "\n" + "example (y : PermanssonResearch.MultiAgent.MarkedStrategicPathCountermodel.Y) :\n" + " (∑ b : Bool, markedLaw true y {(b,((),b))}) = 1 := by\n" + " exact (actual_marked_diagonal_separation y).2\n" + ) + r = compile_lean(source) + log = (r.stdout+"\n"+r.stderr).lower() + self.assertNotEqual(r.returncode, 0, "forged marked diagonal accepted") + self.assertIn("error:", log) + for invalid in ("unknown module", "unknown identifier", + "failed to import", "parser error", + "maximum number of heartbeats", "sorryax"): + self.assertNotIn(invalid, log) + self.assertRegex(log, r"type mismatch|unsolved goals|application type mismatch") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_m02_marked_actual_source_lean.py b/verification/research/test_m02_marked_actual_source_lean.py new file mode 100644 index 00000000..1d17479b --- /dev/null +++ b/verification/research/test_m02_marked_actual_source_lean.py @@ -0,0 +1,73 @@ +"""M-02 original generator update key and exact marked-law counterexample. + +These are compiler/semantic checks, NOT an independently reviewed case receipt. +""" +from pathlib import Path +import re +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +MOD = "PermanssonResearch.MultiAgent.MarkedStrategicPathCountermodel" +THEOREMS = ( + "same_physical_P_for_both_actual_protocols", + "actual_opponent_private_update", + "same_allowed_anchor_update_key", + "unequal_actual_update_rows", + "original_strict_anchor_premise_rejected", + "actual_opponent_action_fair", + "source_aligned_atom", + "source_crossed_atom", + "unmarked_key_laws_identical", + "marked_diagonal_separates", + "unmarked_not_strict_certificate", +) + + +def compile_source(text): + with tempfile.TemporaryDirectory(dir=ROOT / ".lake", + prefix="m02-source-") as folder: + source = Path(folder) / "Check.lean" + source.write_text(text, encoding="utf-8") + return subprocess.run( + ["lake", "env", "lean", str(source)], + cwd=ROOT, text=True, capture_output=True, timeout=180) + + +class MarkedOriginalSourceTests(unittest.TestCase): + def test_original_typed_generator_source_and_axioms(self): + source = ("import " + MOD + "\n" + + "".join("#check " + MOD + "." + d + "\n" + + "#print axioms " + MOD + "." + d + "\n" + for d in THEOREMS)) + run = compile_source(source) + log = run.stdout + "\n" + run.stderr + self.assertEqual(run.returncode, 0, log[-3500:]) + for name in THEOREMS: + self.assertIn(MOD + "." + name, log) + self.assertNotIn("sorryAx", log) + self.assertNotIn("error:", log.lower()) + + def test_invalid_strict_autonomy_promotion_fails_semantically(self): + source = ( + "import " + MOD + "\n" + + "open " + MOD + "\n" + + "example : PermanssonResearch.MultiAgent.IsStrictlyNonReactive " + "opponent := by\n" + " exact original_strict_anchor_premise_rejected\n" + ) + run = compile_source(source) + log = run.stdout + "\n" + run.stderr + self.assertNotEqual(run.returncode, 0, log[-2500:]) + self.assertIn("error:", log.lower()) + for fragment in ("unknown identifier", "unknown module", "unexpected token", + "sorryax", "maximum number of heartbeats"): + self.assertNotIn(fragment, log.lower()) + self.assertTrue("type mismatch" in log.lower() or + "has type" in log.lower(), + log[-2500:]) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_next03_asymmetric_common_swap_lean.py b/verification/research/test_next03_asymmetric_common_swap_lean.py index 01585ab8..28b123ad 100644 --- a/verification/research/test_next03_asymmetric_common_swap_lean.py +++ b/verification/research/test_next03_asymmetric_common_swap_lean.py @@ -19,6 +19,33 @@ class AsymmetricPlayerSwap(unittest.TestCase): + def test_source_bound_non_dirac_asymmetric_whole_path(self): + module = "PermanssonResearch.MultiAgent.AsymmetricCommonSwapFixture" + declarations = ( + "mixed_starts_distinct", + "mixed_initial_positive_each_state", + "mixed_initial_not_point_mass", + "asymmetric_mixed_original_path_law_transport", + ) + code = "import " + module + "\n" + "".join( + "#check " + module + "." + item + "\n" + + "#print axioms " + module + "." + item + "\n" + for item in declarations + ) + with tempfile.TemporaryDirectory(prefix="F01_mixed_", dir=ROOT / ".lake") as tmp: + source = Path(tmp) / "F01Mixed.lean" + source.write_text(code, encoding="utf-8") + run = subprocess.run( + ["lake", "env", "lean", str(source)], cwd=ROOT, + capture_output=True, text=True, timeout=180, + ) + output = run.stdout + run.stderr + self.assertEqual(run.returncode, 0, output[-3500:]) + self.assertNotIn("sorryAx", output) + self.assertNotIn("error:", output.lower()) + for item in declarations: + self.assertIn(module + "." + item, output) + def test_actual_common_update_false_same_input_claim(self): text = SOURCE.read_text(encoding="utf-8") self.assertEqual(text.count(OLD), 1) diff --git a/verification/research/test_next03_stochastic_lean.py b/verification/research/test_next03_stochastic_lean.py index 6c3b19f1..971bbae0 100644 --- a/verification/research/test_next03_stochastic_lean.py +++ b/verification/research/test_next03_stochastic_lean.py @@ -18,7 +18,7 @@ EXPECTED_OLD='![0, 0, 3/4, 1/4, 0, 0,' EXPECTED_MUTANT='![0, 0, 2/4, 2/4, 0, 0,' INVALID = re.compile(r'unknown (?:module|identifier|constant|tactic)|file not found|failed to import|\bsyntax error\b|unexpected token|failed to synthesize|heartbeat|out of memory|timed out|object file',re.I) -SEMANTIC = re.compile(r"tactic .?decide.? proved that the proposition|unsolved goals|Tactic .?decide.? failed|failed to prove",re.I) +SEMANTIC = re.compile(r"type mismatch|application type mismatch|unsolved goals",re.I) class StochasticWrongX1Literal(unittest.TestCase): def test_actual_kernel_equality_rejects_normalized_wrong_row(self): @@ -28,10 +28,27 @@ def test_actual_kernel_equality_rejects_normalized_wrong_row(self): source=SOURCE.read_text(encoding='utf-8') self.assertEqual(source, generator.render(), 'source must be byte-identical to canonical certificate renderer') self.assertGreater(source.count(EXPECTED_OLD),0) - mutant=source.replace(EXPECTED_OLD,EXPECTED_MUTANT,1) - self.assertNotEqual(mutant,source) - self.assertEqual(mutant.count('import '),source.count('import ')) - self.assertEqual(mutant.count('theorem matrix_eq_entry'),1) + # Keep the original compiled X1 kernel theorem byte-identical. + # A normalized but incorrect 1/2 corner is refuted by the existing + # actual-source baseline_corner_literal theorem (3/4 at this state). + # This is a semantic false theorem, not a global decide timeout. + false_corner = """ +theorem normalized_wrong_row_matches_original : + matrix false false false ((0,0),false) ((0,0),false) = (1/2 : ℚ) := by + exact baseline_corner_literal + +""" + namespace_end = "end PermanssonResearch.X1.InterventionNumerics" + self.assertEqual(source.count(namespace_end), 1) + # Append a fresh namespace rather than modifying any proven source + # byte. The legacy replay adapter can verify this exact Git prefix. + mutant = (source + "\nnamespace PermanssonResearch.X1.InterventionNumerics\n" + + false_corner + namespace_end + "\n") + self.assertNotEqual(mutant, source) + self.assertTrue(mutant.startswith(source)) + self.assertEqual(mutant.count('import '), source.count('import ')) + self.assertEqual(mutant.count('theorem matrix_eq_entry'), 1) + self.assertEqual(mutant.count('theorem normalized_wrong_row_matches_original'), 1) # First changed E-baseline row is 0+0+1/2+1/2=1, nonnegative. row=[Fraction('0'),Fraction('0'),Fraction('2/4'),Fraction('2/4')]+[Fraction('0')]*14 self.assertEqual(sum(row),Fraction(1)) @@ -49,11 +66,12 @@ def test_actual_kernel_equality_rejects_normalized_wrong_row(self): negative=subprocess.run(['lake','env','lean',str(file)],cwd=ROOT,capture_output=True,text=True, encoding='utf-8',timeout=600) output=negative.stdout+'\n'+negative.stderr - self.assertNotEqual(negative.returncode,0,'stochasticly wrong X1 matrix compiled as theorem') + self.assertNotEqual(negative.returncode,0,'normalized wrong X1 corner compiled as equality') self.assertIsNone(INVALID.search(output),'invalid syntax/import/resource rejection is not semantic evidence') self.assertIsNotNone(SEMANTIC.search(output),'not the intended kernel-equality proof rejection') - # The unchanged baseline-corner theorem is true; matrix_eq_entry - # must do the work, not a malformed literal or row-sum guard. + # The original compiler-checked row remains the positive witness. + # The false entry is rejected by the existing certified 3/4 atom, + # not by a parser/import/resource or huge reduction failure. self.assertIn('matrix_eq_entry',source) self.assertIn('matrix_eq_entry',mutant) diff --git a/verification/research/test_next03_tied_start_lean.py b/verification/research/test_next03_tied_start_lean.py index 1281c761..167b1e97 100644 --- a/verification/research/test_next03_tied_start_lean.py +++ b/verification/research/test_next03_tied_start_lean.py @@ -17,10 +17,10 @@ ) FALSE_CLAIM = """theorem incorrectly_identical_B1 : omittedTiedStart = comparisonStarts := by - rfl + exact omitted_domain_not_original """ -SEMANTIC = re.compile(r"Tactic .?rfl.? failed|unsolved goals", re.I) +SEMANTIC = re.compile(r"type mismatch|application type mismatch|unsolved goals", re.I) class SameNumericMinNotSameDomain(unittest.TestCase): diff --git a/verification/research/test_paper_x1_lean_closure.py b/verification/research/test_paper_x1_lean_closure.py index af61c1fc..2ee078c8 100644 --- a/verification/research/test_paper_x1_lean_closure.py +++ b/verification/research/test_paper_x1_lean_closure.py @@ -89,11 +89,104 @@ def test_exact_positive_controls(self): with self.subTest(module=name): self.assertEqual(result["status"], "PASS", str(self.receipt_path)) + # The historic X1 driver runs all its false probes in setUpClass. A + # class-wide compiler observation cannot be attributed to an individual + # original-case method. Under the PR113 trace hook, replay one additional + # per-method SOURCE-APPENDED false equality with the actual original + # provider as a byte-identical prefix. No ordinary driver evidence is + # promoted or reclassified. + _TRACE_FALSE_SOURCE = { + "corrupt_terminal_literal": ( + "ExactTerminalTables", + """example : + InterventionNumerics.table false false false 0 ((0,0),false) = (1 : ℚ) := by + exact InterventionNumerics.table_zero false false false ((0,0),false) +""" + ), + "wrong_terminal_horizon": ( + "ExactTerminalTables", + """example : + baselinePropertyValue property + (protocolModel .equilibrium false false) ((0,0),true) = + (InterventionNumerics.table false false false 2 ((0,0),true) : ℝ) := by + exact original_property_eq_table .equilibrium false false ((0,0),true) +""" + ), + "new_world_learning": ( + "InterventionNumericalBridge", + """example : + (protocolModel .learner false false).inducedKernel + ((0,0),true) {((1,1),true)} = + ENNReal.ofReal + (InterventionNumerics.entry false false false + ((0,0),true) ((1,1),true) : ℝ) := by + exact protocol_atom_eq .learner false false ((0,0),true) ((1,1),true) +""" + ), + "swapped_update_protocol": ( + "InterventionNumericalBridge", + """example : + (protocolModel .learner false true).inducedKernel + ((0,0),true) {((1,1),true)} = + ENNReal.ofReal + (InterventionNumerics.entry true true false + ((0,0),true) ((1,1),true) : ℝ) := by + exact protocol_atom_eq .learner false true ((0,0),true) ((1,1),true) +""" + ), + "reset_before_action": ( + "InterventionNumericalBridge", + """example : + (protocolModel .learner false true).inducedKernel + ((0,0),true) {((1,1),true)} = + ENNReal.ofReal + (InterventionNumerics.entry true false true + ((1,1),true) ((1,1),true) : ℝ) := by + exact protocol_atom_eq .learner false true ((0,0),true) ((1,1),true) +""" + ), + } + + def _trace_actual_false_source(self, name): + if not os.environ.get("PR113_CAPTURE_DIR"): + return + if name not in self._TRACE_FALSE_SOURCE: + return + import tempfile + module, assertion = self._TRACE_FALSE_SOURCE[name] + source_file = driver.ROOT / f"PermanssonResearch/X1/{module}.lean" + original = source_file.read_bytes() + suffix = ("\nnamespace PermanssonResearch.X1\n" + + "open PermanssonLean PermanssonLean.RegimeSpecification\n" + + assertion + "\nend PermanssonResearch.X1\n").encode("utf-8") + self.assertNotIn(b"\nimport ", suffix) + self.assertNotIn(b"sorry", suffix.lower()) + self.assertNotIn(b"admit", suffix.lower()) + self.assertTrue(original) + with tempfile.TemporaryDirectory( + prefix="x1-method-specific-", dir=driver.ROOT / ".lake") as td: + mutant_file = Path(td) / "FalseX1Source.lean" + mutant_file.write_bytes(original + suffix) + self.assertTrue(mutant_file.read_bytes().startswith(original)) + result = subprocess.run( + ["lake", "env", "lean", str(mutant_file)], + cwd=driver.ROOT, capture_output=True, text=True, timeout=600) + diagnostic = (result.stdout + "\n" + result.stderr).lower() + self.assertNotEqual(result.returncode, 0, + "mathematically false original-source assertion compiled") + self.assertIn("type mismatch", diagnostic, diagnostic[-2700:]) + for bad in ("unknown identifier", "unknown module", + "parser error", "maximum number of heartbeats", + "failed to synthesize", "sorryax", "timed out"): + self.assertNotIn(bad, diagnostic, diagnostic[-2700:]) + def _mutation(self, name): result = self.receipt["mutations"].get(name, {}) - self.assertEqual(result.get("status"), "REJECTED", f"{name}: {result}; {self.receipt_path}") + self.assertEqual(result.get("status"), "REJECTED", + f"{name}: {result}; {self.receipt_path}") self.assertTrue(result.get("expected_proof_failure")) self.assertEqual(result.get("invalid_diagnostics"), []) + self._trace_actual_false_source(name) def test_corrupt_terminal_literal(self): self._mutation("corrupt_terminal_literal") diff --git a/verification/research/test_pr113_a2_compiler_route_v3.py b/verification/research/test_pr113_a2_compiler_route_v3.py new file mode 100644 index 00000000..ee007835 --- /dev/null +++ b/verification/research/test_pr113_a2_compiler_route_v3.py @@ -0,0 +1,186 @@ +"""A2 source-phase dispatch contracts; synthetic routing is not Lean evidence.""" +from __future__ import annotations + +import ast +import copy +from pathlib import Path +import subprocess +import tempfile +from types import ModuleType +import unittest +from unittest import mock + +import pr113_a2_compiler_route_v3 as routing + +H = "a" * 40 +DECLARATIONS = tuple("Synthetic.declaration_%02d" % i for i in range(40)) +MUTATIONS = {"synthetic_mutation_%d" % i: ("before%d" % i, "after%d" % i) + for i in range(4)} + + +def profile(phase): + return { + "format": routing.PROFILE_FORMAT, + "status": routing.PHASES[phase][0], + "head_sha": H, + "versioned_source_phase": phase, + "historical_original_workflow_count": 25, + "current_reviewed_workflow_count": 30, + "independent_security_review": False, + } + + +def module(name): + result = ModuleType(name) + result.DECLS = DECLARATIONS + result.MUTATIONS = copy.deepcopy(MUTATIONS) + result.verify = mock.Mock(name="SYNTHETIC_COMPILER_NOT_LEAN") + return result + + +class A2CompilerRouteTests(unittest.TestCase): + def select(self, phase, record=None, compiler=None, head=None): + compiler = compiler or module(routing.PHASES.get(phase, (None, "bad"))[1]) + historical = module("localized_prefix_gate") + loaded = [] + + def load(name): + loaded.append(name) + if name == "localized_prefix_gate": + return historical + return compiler + + with tempfile.TemporaryDirectory() as temp: + with mock.patch.object(routing, "_head", side_effect=head or [H, H]), \ + mock.patch.object(routing, "_audited_profile", + return_value=(phase, record if record is not None + else profile(phase))) as audit, \ + mock.patch.object(routing.importlib, "import_module", side_effect=load): + result = routing.select_compiler(Path(temp)) + audit.assert_called_once_with(Path(temp).resolve(), H) + return result, loaded, compiler + + def test_staged_25_uses_v1_not_the_active30_compiler(self): + selected, loaded, compiler = self.select("staged") + self.assertEqual(selected.phase, "staged") + self.assertEqual(selected.checked_sha, H) + self.assertEqual(selected.receipt_format, + "Permansson.PR113.A2SuccessorCompilerCandidate.v1") + self.assertEqual(loaded, ["pr113_a2_successor_compiler", "localized_prefix_gate"]) + self.assertIs(selected.module, compiler) + compiler.verify.assert_not_called() + + def test_active_30_uses_v2_not_the_historical25_compiler(self): + selected, loaded, compiler = self.select("active") + self.assertEqual(selected.receipt_format, + "Permansson.PR113.A2SuccessorCompilerCandidate.v2") + self.assertEqual(loaded, ["pr113_a2_successor_compiler_v2", "localized_prefix_gate"]) + self.assertIs(selected.module, compiler) + compiler.verify.assert_not_called() + + def test_count_only_receipt_is_not_an_authenticated_profile(self): + with self.assertRaises(routing.CompilerRouteError): + self.select("active", {"historical_original_workflow_count": 25, + "current_reviewed_workflow_count": 30}) + + def test_unknown_phase_has_no_fallback(self): + for phase in ("unknown", "staged25", "approved", "", None): + with self.subTest(phase=phase), self.assertRaises(routing.CompilerRouteError): + self.select(phase, {}) + + def test_wrong_source_phase_format_count_or_authority_fails_closed(self): + changes = ( + ("head_sha", "b" * 40), ("versioned_source_phase", "staged"), + ("format", "Permansson.PR113.VersionedWorkflowEvidence.v1"), + ("status", routing.PHASES["staged"][0]), + ("historical_original_workflow_count", 30), + ("current_reviewed_workflow_count", 25), + ("historical_original_workflow_count", 25.0), + ("current_reviewed_workflow_count", True), + ("independent_security_review", True), + ) + for key, value in changes: + wrong = profile("active") + wrong[key] = value + with self.subTest(field=key), self.assertRaises(routing.CompilerRouteError): + self.select("active", wrong) + + def test_profile_failure_never_attempts_a_less_strict_compiler(self): + with tempfile.TemporaryDirectory() as temp: + with mock.patch.object(routing, "_head", return_value=H), \ + mock.patch.object(routing, "_audited_profile", + side_effect=ValueError("exact blob mismatch")), \ + mock.patch.object(routing.importlib, "import_module") as load: + with self.assertRaisesRegex(ValueError, "exact blob mismatch"): + routing.select_compiler(Path(temp)) + load.assert_not_called() + + def test_native_compiler_failure_is_not_retried_or_promoted(self): + compiler = module("pr113_a2_successor_compiler_v2") + compiler.verify.side_effect = ValueError("source mismatch") + selected, loaded, compiler = self.select("active", compiler=compiler) + with self.assertRaisesRegex(ValueError, "source mismatch"): + selected.module.verify(Path("repo"), Path("out"), mode="focused") + compiler.verify.assert_called_once() + self.assertNotIn("pr113_a2_successor_compiler", loaded) + + def test_changed_proof_or_negative_control_inventory_is_rejected(self): + for field in ("DECLS", "MUTATIONS", "verify"): + compiler = module("pr113_a2_successor_compiler_v2") + if field == "DECLS": + compiler.DECLS = compiler.DECLS[:-1] + elif field == "MUTATIONS": + compiler.MUTATIONS.pop(next(iter(compiler.MUTATIONS))) + else: + compiler.verify = None + with self.subTest(field=field), self.assertRaises(routing.CompilerRouteError): + self.select("active", compiler=compiler) + + def test_head_moving_during_selection_is_rejected(self): + with self.assertRaisesRegex(routing.CompilerRouteError, "checkout moved"): + self.select("active", head=[H, "b" * 40]) + + def test_real_profile_adapter_calls_the_exact_source_auditor(self): + source = ModuleType("pr113_workflow_profile_v2") + source.audited_source_phase = mock.Mock(return_value=("active", profile("active"))) + with mock.patch.dict("sys.modules", {"pr113_workflow_profile_v2": source}): + actual = routing._audited_profile(Path("source"), H) + self.assertEqual(actual, ("active", profile("active"))) + source.audited_source_phase.assert_called_once_with(Path("source"), H) + + def test_discovered_mandatory_lean_test_uses_the_phase_selected_compiler(self): + source = Path(__file__).with_name("test_pr113_a2_successor_compiler.py").read_text() + syntax = ast.parse(source) + imports = [node for node in ast.walk(syntax) if isinstance(node, ast.ImportFrom)] + self.assertTrue(any(node.module == "pr113_a2_compiler_route_v3" and + any(alias.name == "select_compiler" for alias in node.names) + for node in imports)) + self.assertFalse(any(node.module in ("pr113_a2_successor_compiler", + "pr113_a2_successor_compiler_v2") + for node in imports)) + self.assertIn('record = compiler.verify(ROOT, folder, mode="focused")', source) + self.assertIn('self.assertEqual(record["format"], selected.receipt_format)', source) + self.assertNotIn("skipTest", source) + self.assertNotIn("@unittest.skip", source) + + def test_git_head_is_observed_from_real_repository_not_environment(self): + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + subprocess.run(["git", "init", "--quiet", str(root)], check=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.run(["git", "-C", str(root), "-c", "user.name=Synthetic Test", + "-c", "user.email=test@example.invalid", "commit", + "--allow-empty", "--quiet", "-m", "synthetic source fixture"], + check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + observed = subprocess.check_output(["git", "-C", str(root), "rev-parse", "HEAD"]) + with mock.patch.dict("os.environ", {"GITHUB_SHA": "f" * 40}): + self.assertEqual(routing._head(root), observed.decode().strip()) + + def test_absent_git_repository_is_not_accepted(self): + with tempfile.TemporaryDirectory() as temp: + with self.assertRaises(routing.CompilerRouteError): + routing._head(Path(temp)) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_a2_dependency_bridge_v2.py b/verification/research/test_pr113_a2_dependency_bridge_v2.py new file mode 100644 index 00000000..de041c46 --- /dev/null +++ b/verification/research/test_pr113_a2_dependency_bridge_v2.py @@ -0,0 +1,110 @@ +"""Corrected-head dependency bridge rejection tests; inputs are synthetic.""" +from __future__ import annotations +import copy +import hashlib +import json +import unittest +import pr113_a2_dependency_bridge_v2 as bridge +import pr113_historical_a2_baseline_v1 as baseline + +H = "a" * 40 + + +def encoded(value): + return (json.dumps(value, sort_keys=True) + "\n").encode() + + +def fixture(): + manifest = encoded({"packagesDir": ".lake/packages", "packages": [ + {"name": "mathlib", "type": "git", "rev": bridge.MATHLIB_REV}]}) + row = {"format": bridge.FORMAT, "status": bridge.STATUS, + "historical_head": baseline.REPLAY_HEAD, + "historical_tree_sha": baseline.REPLAY_TREE, + "historical_introducer": baseline.INTRODUCER, + "current_head": H, "manifest_sha256": hashlib.sha256(manifest).hexdigest(), + "pinned_packages": {"mathlib": bridge.MATHLIB_REV}, + "original_verifier_modified": False, "historical_project_proofs_reused": False, + "independent_security_review": False, "mathematical_acceptance": False} + return manifest, row + + +class CorrectedDependencyBridgeTests(unittest.TestCase): + def verify(self, manifest, row): + raw = encoded(row) + return bridge.verify_A2_dependency_bridge( + {"historical_dependency_bridge.json": raw}, H, manifest_raw=manifest) + + def test_exact_corrected_baseline_roundtrip(self): + manifest, row = fixture() + self.assertEqual(self.verify(manifest, row), hashlib.sha256(encoded(row)).hexdigest()) + + def test_old_or_mixed_baseline_and_contract_versions_rejected(self): + manifest, row = fixture() + for key, value in (("format", "Permansson.PR113.HistoricalA2DependencyBridge.v1"), + ("historical_head", baseline.INTRODUCER), + ("historical_tree_sha", "f" * 40), + ("historical_introducer", baseline.REPLAY_HEAD), + ("current_head", "b" * 40), ("manifest_sha256", "0" * 64)): + with self.subTest(field=key), self.assertRaises(bridge.DependencyBridgeError): + self.verify(manifest, dict(row, **{key: value})) + + def test_approval_and_project_build_reuse_are_not_inferred(self): + manifest, row = fixture() + for key in ("original_verifier_modified", "historical_project_proofs_reused", + "independent_security_review", "mathematical_acceptance"): + for false_like in (True, 0, "false", None): + with self.subTest(field=key, value=false_like), self.assertRaises(bridge.DependencyBridgeError): + self.verify(manifest, dict(row, **{key: false_like})) + + def test_missing_or_extra_bridge_fields_rejected(self): + manifest, row = fixture() + for key in row: + bad = dict(row); bad.pop(key) + with self.subTest(key=key), self.assertRaises(bridge.DependencyBridgeError): + self.verify(manifest, bad) + with self.assertRaises(bridge.DependencyBridgeError): + self.verify(manifest, dict(row, accepted=True)) + + def test_wrong_dependency_set_rejected_even_with_correct_metadata(self): + manifest, row = fixture() + with self.assertRaises(bridge.DependencyBridgeError): + self.verify(manifest, dict(row, pinned_packages={"mathlib": "0" * 40})) + + def test_manifest_corruption_not_solved_by_rehashing(self): + manifest, row = fixture() + original = json.loads(manifest) + cases = [] + bad = copy.deepcopy(original); bad["packagesDir"] = "../packages"; cases.append(bad) + bad = copy.deepcopy(original); bad["packages"][0]["name"] = "../mathlib"; cases.append(bad) + bad = copy.deepcopy(original); bad["packages"][0]["rev"] = "f" * 40; cases.append(bad) + bad = copy.deepcopy(original); bad["packages"].append(dict(bad["packages"][0])); cases.append(bad) + bad = copy.deepcopy(original); bad["packages"][0]["type"] = "path"; cases.append(bad) + for value in cases: + raw = encoded(value) + forged = dict(row, manifest_sha256=hashlib.sha256(raw).hexdigest()) + with self.subTest(value=value), self.assertRaises(bridge.DependencyBridgeError): + self.verify(raw, forged) + + def test_duplicate_nonfinite_and_nonobject_json_rejected(self): + manifest, row = fixture() + for raw in (b'{"x":1,"x":2}', b'{"x":NaN}', b'{"x":Infinity}', + b'{"x":1e999}', b'[]', b'null', b'{}'): + with self.subTest(raw=raw), self.assertRaises(bridge.DependencyBridgeError): + bridge.verify_A2_dependency_bridge( + {"historical_dependency_bridge.json": raw}, H, manifest_raw=manifest) + + def test_exact_manifest_bytes_not_stripped(self): + manifest, row = fixture() + with self.assertRaises(bridge.DependencyBridgeError): + self.verify(manifest.rstrip(), row) + + def test_missing_or_oversized_bridge_bytes_rejected(self): + manifest, row = fixture() + for raw in (None, b"", b" " * (1024 * 1024 + 1)): + with self.subTest(size=None if raw is None else len(raw)), self.assertRaises(bridge.DependencyBridgeError): + bridge.verify_A2_dependency_bridge( + {"historical_dependency_bridge.json": raw}, H, manifest_raw=manifest) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_a2_successor_compiler.py b/verification/research/test_pr113_a2_successor_compiler.py new file mode 100644 index 00000000..6a6d6dee --- /dev/null +++ b/verification/research/test_pr113_a2_successor_compiler.py @@ -0,0 +1,47 @@ +"""Actual PR113 A2 successor Lean evidence, NOT original security approval. + +Run the exact source-phase compiler, pinned Lean 4.34.0 library/axiom checks, +a positive control and four genuine failing semantic mutants. Historical A2 +continues to execute separately under its unchanged historical contract. +""" +from __future__ import annotations +import json +from pathlib import Path +import tempfile +import unittest +from pr113_a2_compiler_route_v3 import select_compiler + +ROOT = Path(__file__).resolve().parents[2] + + +class SuccessorA2CompilerExecution(unittest.TestCase): + def test_real_current_source_positive_and_negative_lean(self): + selected = select_compiler(ROOT) + compiler = selected.module + with tempfile.TemporaryDirectory(prefix="pr113-a2-") as tmp: + folder = Path(tmp) / "evidence" + record = compiler.verify(ROOT, folder, mode="focused") + print("PR113_A2_SUCCESSOR_BEGIN") + print(json.dumps(record, sort_keys=True)) + print("PR113_A2_SUCCESSOR_END") + self.assertEqual(record["format"], selected.receipt_format) + self.assertEqual(record["status"], + "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING", + record.get("reason", "real successor compiler failed")) + self.assertEqual(set(record["declarations"]), set(compiler.DECLS)) + self.assertEqual(set(record["mutations"]), set(compiler.MUTATIONS)) + self.assertTrue(record["source_unchanged"]) + self.assertFalse(record["historical_a2_verifier_replaced"]) + self.assertFalse(record["independent_security_review"]) + self.assertFalse(record["mathematical_acceptance"]) + labels = [x["label"] for x in record["commands"]] + self.assertIn("positive_fixture", labels) + self.assertIn("signatures_axioms", labels) + for name in compiler.MUTATIONS: + self.assertIn(name, labels) + for entry in record["commands"]: + log = folder / entry["log"] + self.assertTrue(log.is_file(), entry["label"]) + print("PR113_A2_LOG_BEGIN", entry["label"]) + print(log.read_text(encoding="utf-8", errors="replace")) + print("PR113_A2_LOG_END", entry["label"]) diff --git a/verification/research/test_pr113_a2_successor_identity.py b/verification/research/test_pr113_a2_successor_identity.py new file mode 100644 index 00000000..09c1e63c --- /dev/null +++ b/verification/research/test_pr113_a2_successor_identity.py @@ -0,0 +1,141 @@ +"""Source-identity successor contract tests; NO simulated compiler approval.""" +import copy +import unittest + +import localized_prefix_support as inherited +import pr113_a2_successor_identity as successor + + +def fixture(): + old = {p: ("100644", "a"*40) + for p in successor.PINNED_MODIFIED_PROVIDERS} + old.update({ + "PermanssonResearch.lean": ("100644", inherited.ROOT_BLOB), + "lean-toolchain": ("100644", "b"*40), + "lake-manifest.json": ("100644", "c"*40), + "README.md": ("100644", "d"*40), + }) + new = dict(old) + for p, sha in successor.PINNED_MODIFIED_PROVIDERS.items(): + new[p] = ("100644", sha) + new["PermanssonResearch.lean"] = ("100644", "e"*40) + new["README.md"] = ("100644", "f"*40) + for p, sha in inherited.ACCEPTED_A2_PROOF_BLOBS.items(): + new[p] = ("100644", sha) + for p, sha in successor.INHERITED_A2_VERIFIERS.items(): + new[p] = ("100644", sha) + return old, new + + +class SuccessorIdentityPolicyTests(unittest.TestCase): + def test_only_exact_source_root_separator_is_normalized(self): + anchor = (b"import PermanssonResearch.ConstitutiveQuasi." + b"LocalizedPrefixExamples\n") + original = anchor + b"\n/-!\n" + newer = anchor + b"\n\n\n/-!\n" + self.assertEqual(successor.normalize_additive_root_separator(newer), original) + for bad in (anchor + b"\n\n/-!\n", + anchor + b"\n\n\n\n/-!\n", + newer + b"\n"): + with self.subTest(bad=bad): + self.assertNotEqual(successor.normalize_additive_root_separator(bad), + original) + + def test_real_synthetic_requires_B_H_parent_order_and_exact_H_tree(self): + from pathlib import Path + from unittest import mock + root = Path(".") + H = "a" * 40 + B = successor.PR113_BASE + S = "b" * 40 + def good(actual_repo, *git_args): + self.assertEqual(actual_repo, root) + return { + ("rev-parse", "HEAD"): S, + ("rev-parse", H + "^{tree}"): "c" * 40, + ("rev-parse", "HEAD^{tree}"): "c" * 40, + ("show", "-s", "--format=%P", S): B + " " + H, + }[git_args] + with mock.patch.object(inherited, "git_text", side_effect=good): + self.assertEqual(successor.check_checkout_identity(root, H), + (S, "c" * 40)) + + # A direct exact-H checkout is valid and does not need a synthetic + # parent assertion; its tree must still equal the immutable H tree. + def direct_h(actual_repo, *git_args): + self.assertEqual(actual_repo, root) + if git_args == ("rev-parse", "HEAD"): + return H + return good(actual_repo, *git_args) + with mock.patch.object(inherited, "git_text", side_effect=direct_h): + self.assertEqual(successor.check_checkout_identity(root, H), + (H, "c" * 40)) + + def reversed_parent(actual_repo, *git_args): + return H + " " + B if git_args[:1] == ("show",) else good(actual_repo, *git_args) + with mock.patch.object(inherited, "git_text", side_effect=reversed_parent): + with self.assertRaises(inherited.GuardError): + successor.check_checkout_identity(root, H) + + def drifted_tree(actual_repo, *git_args): + return "d" * 40 if git_args == ("rev-parse", "HEAD^{tree}") else good(actual_repo, *git_args) + with mock.patch.object(inherited, "git_text", side_effect=drifted_tree): + with self.assertRaises(inherited.GuardError): + successor.check_checkout_identity(root, H) + + def test_exact_delta_passes_only_as_candidate(self): + old, current = fixture() + result = successor.reconcile(old, current) + self.assertEqual(len(result["pinned_successor_provider_paths"]), 12) + self.assertEqual(result["accepted_a2_proof_blobs"], 4) + + def test_any_changed_trusted_provider_fails(self): + old, current = fixture() + for p in successor.PINNED_MODIFIED_PROVIDERS: + bad = dict(current) + bad[p] = ("100644", "f"*40) + with self.subTest(path=p), self.assertRaises(inherited.GuardError): + successor.reconcile(old, bad) + + def test_new_unreviewed_baseline_change_fails(self): + old, current = fixture() + old["verification/archive/offline_smoke.py"] = ("100644", "a"*40) + current["verification/archive/offline_smoke.py"] = ("100644", "b"*40) + with self.assertRaises(inherited.GuardError): + successor.reconcile(old, current) + + def test_history_and_mathematics_cannot_be_deleted_or_mutated(self): + old, current = fixture() + deleted = dict(current) + del deleted["lean-toolchain"] + with self.assertRaises(inherited.GuardError): + successor.reconcile(old, deleted) + for p in inherited.ACCEPTED_A2_PROOF_BLOBS: + changed = dict(current) + changed[p] = ("100644", "1"*40) + with self.subTest(proof=p), self.assertRaises(inherited.GuardError): + successor.reconcile(old, changed) + + def test_historical_verifier_remains_byte_pinned(self): + old, current = fixture() + for p in successor.INHERITED_A2_VERIFIERS: + bad = dict(current) + bad[p] = ("100644", "2"*40) + with self.subTest(path=p), self.assertRaises(inherited.GuardError): + successor.reconcile(old, bad) + + def test_pin_and_reviewable_doc_modes_cannot_change(self): + old, current = fixture() + for p in ("lake-manifest.json", "lean-toolchain"): + changed = dict(current) + changed[p] = ("100644", "1"*40) + with self.subTest(path=p), self.assertRaises(inherited.GuardError): + successor.reconcile(old, changed) + changed = dict(current) + changed["README.md"] = ("100755", "f"*40) + with self.assertRaises(inherited.GuardError): + successor.reconcile(old, changed) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_a2_successor_identity_v2.py b/verification/research/test_pr113_a2_successor_identity_v2.py new file mode 100644 index 00000000..00091dda --- /dev/null +++ b/verification/research/test_pr113_a2_successor_identity_v2.py @@ -0,0 +1,395 @@ +"""Versioned A2 current-30 source invariants; historical verifiers are untouched.""" +import unittest +import hashlib +import json +import subprocess +import copy +import pr113_workflow_profile_v2 as workflow_profile +from unittest import mock +from pathlib import Path +import localized_prefix_support as frozen +import pr113_a2_successor_identity as legacy +import pr113_a2_successor_identity_v2 as successor + + +def fixture(): + old={path:("100644","a"*40) + for path in successor.PINNED_MODIFIED_PROVIDERS} + old.update({ + "PermanssonResearch.lean":("100644",frozen.ROOT_BLOB), + "lean-toolchain":("100644","b"*40), + "lake-manifest.json":("100644","c"*40), + "README.md":("100644","d"*40), + }) + newer=dict(old) + # This test did not exist at original PR79 BASE. + assert successor.LATE_A2_TEST not in old + newer[successor.LATE_A2_TEST]=( + "100644",successor.LATE_A2_TEST_PROPOSED_BLOB) + for path,oid in successor.PINNED_MODIFIED_PROVIDERS.items(): + newer[path]=("100644",oid) + newer["PermanssonResearch.lean"]=("100644","e"*40) + newer["README.md"]=("100644","f"*40) + for path,oid in frozen.ACCEPTED_A2_PROOF_BLOBS.items(): + newer[path]=("100644",oid) + for path,oid in successor.INHERITED_A2_VERIFIERS.items(): + newer[path]=("100644",oid) + return old,newer + + +class SuccessorA2Active30Tests(unittest.TestCase): + def test_exact_twelve_original_modified_and_one_historically_late_test(self): + old,current=fixture() + report=successor.reconcile(old,current) + self.assertEqual(len(report["pinned_successor_provider_paths"]),12) + self.assertNotIn(successor.LATE_A2_TEST,old) + self.assertEqual(report["late_introduced_A2_test"],successor.LATE_A2_TEST) + self.assertEqual(report["late_A2_original_introducer"], + successor.HISTORICAL_A2_INTRODUCER) + self.assertEqual(report["late_A2_original_blob"], + successor.LATE_A2_TEST_ORIGINAL_BLOB) + self.assertEqual(len(successor.INHERITED_A2_VERIFIERS),2) + self.assertEqual(len(legacy.INHERITED_A2_VERIFIERS),3) + self.assertEqual(len(frozen.ACCEPTED_A2_PROOF_BLOBS),4) + # Three independent source identities replace the obsolete frozen + # *successor* literal. The historical original stays immutable. + root = Path(__file__).resolve().parents[2] + staged = ("docs/operations/pr113_required_gate_replacement_unapproved/" + "test_localized_prefix_compiler.py") + manifest_path = ("docs/operations/pr113_required_gate_replacement_unapproved/" + "manifest.json") + manifest = json.loads((root / manifest_path).read_bytes()) + self.assertEqual(manifest["format"], + "Permansson.PR113.UnapprovedRequiredGateTransition.v1") + matching = [record for record in manifest["staged_replacements"] + if record.get("target") == successor.LATE_A2_TEST] + self.assertEqual(len(matching), 1) + record = matching[0] + self.assertEqual(record["source"], staged) + self.assertIs(record["installed"], False) + self.assertIs(record["independent_reviewed"], False) + payload = (root / staged).read_bytes() + computed = hashlib.sha1( + b"blob " + str(len(payload)).encode("ascii") + b"\0" + payload + ).hexdigest() + committed = subprocess.check_output( + ["git", "rev-parse", "HEAD:" + staged], cwd=root, + text=True, stderr=subprocess.DEVNULL, timeout=20, + ).strip() + self.assertEqual(computed, committed) + self.assertEqual(record["git_blob_oid"], committed) + self.assertEqual(successor.LATE_A2_TEST_PROPOSED_BLOB, committed) + self.assertEqual(report["late_A2_proposed_blob"], committed) + self.assertNotEqual(committed, successor.LATE_A2_TEST_ORIGINAL_BLOB) + self.assertNotEqual( + successor.PINNED_MODIFIED_PROVIDERS[ + "verification/integrity/workflow_contract.py"], + legacy.PINNED_MODIFIED_PROVIDERS[ + "verification/integrity/workflow_contract.py"]) + + def test_a2_source_pin_delta_is_exact_and_no_wildcard(self): + old,current=fixture() + for changed in successor.PINNED_MODIFIED_PROVIDERS: + altered=dict(current) + altered[changed]=("100644","0"*40) + with self.subTest(changed=changed),self.assertRaises(frozen.GuardError): + successor.reconcile(old,altered) + for candidate in ("0000000000000000000000000000000000000000", + successor.LATE_A2_TEST_ORIGINAL_BLOB): + changed=dict(current) + changed[successor.LATE_A2_TEST]=("100644",candidate) + with self.assertRaises(frozen.GuardError): + successor.reconcile(old,changed) + absent=dict(current) + absent.pop(successor.LATE_A2_TEST) + with self.assertRaises(frozen.GuardError): + successor.reconcile(old,absent) + spoofed=dict(old) + spoofed[successor.LATE_A2_TEST]=( + "100644",successor.LATE_A2_TEST_ORIGINAL_BLOB) + with self.assertRaises(frozen.GuardError): + successor.reconcile(spoofed,current) + for protected in successor.INHERITED_A2_VERIFIERS: + altered=dict(current) + altered[protected]=("100644","1"*40) + with self.subTest(protected=protected),self.assertRaises(frozen.GuardError): + successor.reconcile(old,altered) + + def test_v1_preactivation_receipt_cannot_satisfy_v2(self): + old,current=fixture() + previous=dict(current) + old_pin=legacy.PINNED_MODIFIED_PROVIDERS[ + "verification/integrity/workflow_contract.py"] + previous["verification/integrity/workflow_contract.py"]=("100644",old_pin) + with self.assertRaises(frozen.GuardError): + successor.reconcile(old,previous) + self.assertNotEqual(successor.FORMAT,legacy.FORMAT) + + def test_new_unapproved_mathematical_provider_not_allowed(self): + old,current=fixture() + old["PermanssonResearch/InjectedTheorem.lean"]=("100644","1"*40) + current["PermanssonResearch/InjectedTheorem.lean"]=("100644","2"*40) + with self.assertRaises(frozen.GuardError): + successor.reconcile(old,current) + + def test_ordered_direct_H_and_synthetic_S_identity(self): + B=successor.PR113_BASE + H="a"*40 + S="b"*40 + TREE="c"*40 + def authenticated(root,*args): + mapping={ + ("rev-parse","HEAD"):S, + ("rev-parse",H+"^{tree}"):TREE, + ("rev-parse","HEAD^{tree}"):TREE, + ("show","-s","--format=%P",S):B+" "+H, + } + return mapping[args] + with mock.patch.object(frozen,"git_text",side_effect=authenticated): + self.assertEqual( + successor.check_checkout_identity(Path("."),H),(S,TREE)) + def direct(root,*args): + if args==("rev-parse","HEAD"): + return H + return authenticated(root,*args) + with mock.patch.object(frozen,"git_text",side_effect=direct): + self.assertEqual( + successor.check_checkout_identity(Path("."),H),(H,TREE)) + for parents in (H+" "+B,B+" "+"d"*40,B+" "+H+" "+"e"*40): + def fake(root,*args): + if args==("show","-s","--format=%P",S): + return parents + return authenticated(root,*args) + with mock.patch.object(frozen,"git_text",side_effect=fake): + with self.assertRaises(frozen.GuardError): + successor.check_checkout_identity(Path("."),H) + def false_tree(root,*args): + if args==("rev-parse","HEAD^{tree}"): + return "f"*40 + return authenticated(root,*args) + with mock.patch.object(frozen,"git_text",side_effect=false_tree): + with self.assertRaises(frozen.GuardError): + successor.check_checkout_identity(Path("."),H) + + def test_original_historical_a2_test_git_identity_is_not_assumed(self): + self.assertEqual(successor.HISTORICAL_A2_INTRODUCER, + "f71a76032b169aac7bfe0694aa715339a94d4e05") + self.assertEqual(successor.LATE_A2_TEST_ORIGINAL_BLOB, + "c0f49038ea9b590720c78be85968eb63f5ac820f") + self.assertNotEqual(successor.LATE_A2_TEST_ORIGINAL_BLOB, + successor.LATE_A2_TEST_PROPOSED_BLOB) + + def test_history_cannot_change_toolchain_or_proofs(self): + old,current=fixture() + for key in ("lean-toolchain","lake-manifest.json", + *frozen.ACCEPTED_A2_PROOF_BLOBS): + altered=dict(current) + altered[key]=("100644","0"*40) + with self.subTest(key=key),self.assertRaises(frozen.GuardError): + successor.reconcile(old,altered) + + + @staticmethod + def _git_raw(root, *args): + return subprocess.check_output( + ["git", *args], cwd=root, stderr=subprocess.DEVNULL, timeout=35) + + @staticmethod + def _blob_oid(raw): + return hashlib.sha1( + b"blob " + str(len(raw)).encode("ascii") + b"\0" + raw + ).hexdigest() + + def _historically_anchored_virtual_tree(self): + """Build proposed 30-source truth from ACTUAL Git objects, not mocks.""" + root=Path(__file__).resolve().parents[2] + git=self._git_raw + checked=git(root,"rev-parse","HEAD").decode().strip() + self.assertRegex(checked,r"^[a-f0-9]{40}$") + for anchor in (frozen.BASE,successor.HISTORICAL_A2_INTRODUCER, + workflow_profile.PREACTIVATION_HEAD): + self.assertEqual(subprocess.run( + ["git","merge-base","--is-ancestor",anchor,checked], + cwd=root,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL, + timeout=35).returncode,0, + "actual historical baseline missing from exact Git history") + baseline=frozen.parse_tree( + git(root,"ls-tree","-r","-z",frozen.BASE)) + current=frozen.parse_tree( + git(root,"ls-tree","-r","-z","HEAD")) + old25=frozen.parse_tree( + git(root,"ls-tree","-r","-z",workflow_profile.PREACTIVATION_HEAD)) + self.assertEqual(git(root,"rev-parse",frozen.BASE+"^{tree}").decode().strip(), + frozen.BASE_TREE) + self.assertNotIn(successor.LATE_A2_TEST,baseline) + self.assertEqual(git(root,"rev-parse", + successor.HISTORICAL_A2_INTRODUCER+":"+ + successor.LATE_A2_TEST).decode().strip(), + successor.LATE_A2_TEST_ORIGINAL_BLOB) + phase,report=workflow_profile.audited_source_phase(root,checked) + self.assertEqual(current[successor.LATE_A2_TEST], + ("100644",legacy.INHERITED_A2_VERIFIERS[successor.LATE_A2_TEST] + if phase=="staged" else + successor.LATE_A2_TEST_PROPOSED_BLOB)) + def committed_json(path): + return json.loads(git(root,"show",checked+":"+path)) + full=committed_json(workflow_profile.FULL_MANIFEST) + contract=committed_json(workflow_profile.CONTRACT_MANIFEST) + suite=committed_json(workflow_profile.STAGE+"test_workflow_v2_manifest.json") + historical=committed_json(workflow_profile.STAGE+"manifest.json") + additions=committed_json(workflow_profile.PROPOSAL_MANIFEST) + self.assertEqual(report["historical_original_workflow_count"],25) + self.assertEqual(report["current_reviewed_workflow_count"],30) + self.assertEqual(report["retained_historical_count"],23) + self.assertFalse(report["independent_security_review"]) + self.assertFalse(report["owner_activation_authorized"]) + for manifest in (full,contract,suite,historical,additions): + for claim in ("independent_security_review","owner_activation_authorized", + "mathematical_acceptance","publication_authorized"): + if claim in manifest: + self.assertIs(manifest[claim],False, + "candidate manifest cannot grant "+claim) + replacements={r["target"]:r for r in full["full_replacements"]} + self.assertEqual(set(replacements),{ + ".github/workflows/research-lean.yml", + ".github/workflows/research-integrity.yml", + }) + self.assertEqual(len(contract["expected_workflow_git_blobs"]),30) + self.assertEqual(len(successor.PINNED_MODIFIED_PROVIDERS),12) + intended={} + future=dict(current) + def stage_oid(path,recorded): + raw=git(root,"show",checked+":"+path) + git_oid=git(root,"rev-parse",checked+":"+path).decode().strip() + self.assertEqual(git_oid,self._blob_oid(raw), + "actual Git object bytes do not match object ID") + self.assertEqual(git_oid,recorded, + "staged manifest is not an authority over Git") + return git_oid + for path,stored_pin in successor.PINNED_MODIFIED_PROVIDERS.items(): + self.assertIn(path,baseline,"only existing PR79 providers can be modified") + self.assertIn(path,old25,"original 25-history snapshot lost a provider") + if path in replacements: + stage=replacements[path] + name=path.removeprefix(".github/workflows/") + expected=stage_oid(stage["staged_source"],stage["git_blob_oid"]) + self.assertEqual(expected,contract["expected_workflow_git_blobs"][name]) + if phase=="staged": + self.assertEqual(current[path],old25[path], + "original required workflow changed before activation") + else: + self.assertEqual(current[path],("100644",expected), + "installed required workflow differs from reviewed staged blob") + future[path]=("100644",expected) + elif path=="verification/integrity/workflow_contract.py": + expected=stage_oid(contract["staged_source"], + contract["staged_git_blob_oid"]) + if phase=="active": + self.assertEqual(current[path],("100644",expected)) + future[path]=("100644",expected) + elif path=="verification/integrity/tests/test_workflow.py": + expected=stage_oid(suite["staged_test_source"], + suite["staged_test_blob_oid"]) + if phase=="active": + self.assertEqual(current[path],("100644",expected)) + future[path]=("100644",expected) + else: + expected=current[path][1] + self.assertEqual(old25[path],current[path], + "retained source changed since 25-workflow anchor") + if path.startswith(".github/workflows/"): + name=path.removeprefix(".github/workflows/") + self.assertEqual(expected, + contract["expected_workflow_git_blobs"][name]) + raw=git(root,"show",checked+":"+path) + self.assertEqual(self._blob_oid(raw),expected) + self.assertEqual(stored_pin,expected, + "A2 successor has stale provider pin: "+path) + intended[path]=expected + self.assertEqual(set(intended),set(successor.PINNED_MODIFIED_PROVIDERS)) + late=next(r for r in historical["staged_replacements"] + if r["target"]==successor.LATE_A2_TEST) + self.assertEqual(stage_oid(late["source"],late["git_blob_oid"]), + successor.LATE_A2_TEST_PROPOSED_BLOB) + future[successor.LATE_A2_TEST]=( + "100644",successor.LATE_A2_TEST_PROPOSED_BLOB) + if phase=="active": + self.assertEqual(current[successor.LATE_A2_TEST], + future[successor.LATE_A2_TEST]) + for added in additions["proposals"]: + path=added["workflow_path"] + oid=stage_oid(added["staged_source_path"],added["git_blob_oid"]) + if phase=="staged": + self.assertNotIn(path,current) + else: + self.assertEqual(current.get(path),("100644",oid), + "installed proposed workflow blob differs from stage") + self.assertNotIn(path,baseline) + future[path]=("100644",oid) + for path,oid in frozen.ACCEPTED_A2_PROOF_BLOBS.items(): + self.assertEqual(current[path],("100644",oid), + "accepted mathematical source blob was changed") + for path in ("lean-toolchain","lake-manifest.json"): + self.assertEqual(current[path],baseline[path], + "historical Lean/toolchain dependency identity changed") + self.assertEqual(len([p for p in future + if p.startswith(".github/workflows/") and p.endswith((".yml",".yaml"))]), + 30) + if phase=="active": + self.assertEqual(current,future, + "actual active 30-tree differs from reviewed virtual 30-tree") + audited=successor.reconcile(baseline,future) + self.assertEqual(len(audited["pinned_successor_provider_paths"]),12) + return root,checked,baseline,future,full,contract + + def test_all_twelve_actual_git_provider_pins_match_staged_manifests(self): + self._historically_anchored_virtual_tree() + + def test_each_stale_a2_pin_and_staged_source_substitution_fail_closed(self): + root,checked,baseline,future,full,contract=( + self._historically_anchored_virtual_tree()) + phase=workflow_profile.source_phase(root,checked) + for path in (".github/workflows/research-integrity.yml", + ".github/workflows/research-lean.yml", + "verification/integrity/workflow_contract.py"): + with self.subTest(forged_pin=path),mock.patch.dict( + successor.PINNED_MODIFIED_PROVIDERS,{path:"0"*40}): + with self.assertRaises(frozen.GuardError): + successor.reconcile(baseline,future) + for path in (".github/workflows/research-integrity.yml", + ".github/workflows/research-lean.yml"): + altered=dict(future) + altered[path]=("100644","0"*40) + with self.subTest(forged_candidate=path),self.assertRaises( + frozen.GuardError): + successor.reconcile(baseline,altered) + stage_paths=[contract["staged_source"]] + stage_paths += [r["staged_source"] for r in full["full_replacements"]] + true_git=workflow_profile.git + def committed_json(r,head,path): + return json.loads(true_git(r,"show",head+":"+path)) + for staged in stage_paths: + raw=true_git(root,"show",checked+":"+staged) + forged=raw+b"\n# changed and misleadingly rehashed\n" + spoofed_oid=self._blob_oid(forged) + def forged_git(r,*args): + if args==("show",checked+":"+staged): + return forged + return true_git(r,*args) + def forged_manifest(r,head,path): + data=committed_json(r,head,path) + if staged==workflow_profile.CONTRACT and path==( + workflow_profile.CONTRACT_MANIFEST): + data["staged_git_blob_oid"]=spoofed_oid + if path==workflow_profile.FULL_MANIFEST: + for row in data["full_replacements"]: + if row["staged_source"]==staged: + row["git_blob_oid"]=spoofed_oid + return data + with self.subTest(forged_stage=staged),\ + mock.patch.object(workflow_profile,"git", + side_effect=forged_git),\ + mock.patch.object(workflow_profile,"_json", + side_effect=forged_manifest),\ + self.assertRaises(workflow_profile.ToolingMigrationError): + workflow_profile.audit(root,checked,phase=phase) diff --git a/verification/research/test_pr113_archive_pre_activation_rehearsal_v1.py b/verification/research/test_pr113_archive_pre_activation_rehearsal_v1.py new file mode 100644 index 00000000..56923121 --- /dev/null +++ b/verification/research/test_pr113_archive_pre_activation_rehearsal_v1.py @@ -0,0 +1,120 @@ +"""Staged T/H engineering evidence can never satisfy the active-30 gate.""" +import json +from pathlib import Path +from types import SimpleNamespace +import tempfile +import unittest +from unittest import mock + +import pr113_archive_pre_activation_rehearsal_v1 as rehearsal +import pr113_dual_tooling_candidate_v2 as actual + +H = "a"*40 + + +def fixture(): + old = { + "format":"Permansson.Hardening.Check.v1","status":"PASS", + "check":"archive-reproducibility","context":{"scope":"accepted_artifact_replay"}, + "details":{"acceptance_scope":"accepted_artifact_replay_only", + "isolation":{"network_isolation":"ENFORCED", + "capabilities":"all_zero"}}, + } + now = { + "format":"Permansson.Hardening.Tests.v1","schema_version":1, + "status":"PASS","suite":"workflow","methods_run":17, + "execution_tracked_clean":True,"failures":0,"errors":0,"skips":0, + "covered_requirements":["CI-06","CI-07"], + "context":{"repository":"HMarcusWH/PermanssonLean", + "checked_sha":H,"checked_tree":"e"*40,"event":"local"}, + } + profile = { + "format":"Permansson.PR113.ToolingMigrationCandidate.v2", + "status":"T2_EXACT_SOURCE_REVIEW_PENDING", + "source_commit_sha":H,"reviewed_workflow_source_mode":"staged", + "historical_original_workflow_count":25,"current_reviewed_workflow_count":30, + "historical_packaged_tooling_files_verified":35, + "historical_archive_unchanged":True, + "new_successor_T_approved":False,"owner_activation_authorized":False, + "independent_security_review":False,"workflow_profile_sha256":"e"*64, + "historical_supplement_tree":"f"*40, + "historical_manifest_sha256":"d"*64, + "changed_canonical_tooling":[ + {"path":"verification/integrity/workflow_contract.py", + "T_blob":"a"*40,"H_blob":"b"*40}, + {"path":"verification/integrity/tests/test_workflow.py", + "T_blob":"c"*40,"H_blob":"d"*40}, + ], + } + return old,now,profile + + +class StagedRehearsalTests(unittest.TestCase): + def test_complete_staged_rehearsal_is_never_the_real_active_30(self): + old,now,profile=fixture() + result = rehearsal.validate_reports(old,now,profile,H) + self.assertEqual(result["status"],rehearsal.STATUS) + self.assertFalse(result["active_30_reverified"]) + self.assertFalse(result["genuine_required_ci"]) + self.assertFalse(result["independent_security_review"]) + with self.assertRaises(actual.DualToolingError): + actual.validate_dual_reports_v2( + old,now,T_source=actual.T,H_source=H,profile=profile) + + def test_faulted_isolation_source_test_or_review_field_fails(self): + edits = ( + lambda a,b,p: a.update(status="FAIL"), + lambda a,b,p: a["details"]["isolation"].update( + network_isolation="UNPROVEN"), + lambda a,b,p: b.update(errors=1), + lambda a,b,p: b.update(methods_run=0), + lambda a,b,p: p.update(reviewed_workflow_source_mode="active"), + lambda a,b,p: p.update(historical_packaged_tooling_files_verified=34), + lambda a,b,p: p.update(independent_security_review=True), + lambda a,b,p: p.update(owner_activation_authorized=True), + lambda a,b,p: p.update(changed_canonical_tooling=[]), + lambda a,b,p: p["changed_canonical_tooling"][0].update( + path="verification/archive/verify_bundle.py"), + ) + for update in edits: + old,now,profile=fixture() + update(old,now,profile) + with self.subTest(update=str(update)), self.assertRaises( + rehearsal.RehearsalError): + rehearsal.validate_reports(old,now,profile,H) + + def test_two_real_child_command_shapes_and_unapproved_artifact(self): + old,now,profile=fixture() + with tempfile.TemporaryDirectory() as tmp: + parent=Path(tmp) + historical,current,out=parent/"T",parent/"H",parent/"out" + historical.mkdir() + (current/"archival/v0.2.0/supplement-1").mkdir(parents=True) + ran=[] + def exec_command(argv,cwd,**kwargs): + ran.append((argv,cwd,kwargs)) + report=Path(argv[argv.index("--report")+1]) + report.write_text(json.dumps(old if kwargs.get( + "historical_artifact") else now)) + return SimpleNamespace(returncode=0,stdout="PASS",stderr="") + with mock.patch.object(rehearsal.dual,"checkout", + side_effect=[historical,current]), \ + mock.patch.object(rehearsal,"tooling_audit",return_value=profile), \ + mock.patch.object(rehearsal.dual,"command", + side_effect=exec_command): + result=rehearsal.execute(historical,current,H,out) + self.assertEqual(len(ran),2) + self.assertEqual((ran[0][1],ran[1][1]),(historical,current)) + self.assertTrue(ran[0][2]["historical_artifact"]) + self.assertTrue(ran[1][2]["detached_current_suite"]) + self.assertNotIn("--repo",ran[0][0]) + self.assertNotIn("--candidate",ran[0][0]) + self.assertEqual(result["status"],rehearsal.STATUS) + data=json.loads((out/"STAGED_REHEARSAL_NOT_T2_AUTHORITY.json").read_bytes()) + self.assertFalse(data["original_T2_producer_zip"]) + self.assertTrue((out/"T_stdout.log").is_file()) + self.assertTrue((out/"current_H_workflow.json").is_file()) + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_archive_source_review_packet_v1.py b/verification/research/test_pr113_archive_source_review_packet_v1.py new file mode 100644 index 00000000..0b74dcf2 --- /dev/null +++ b/verification/research/test_pr113_archive_source_review_packet_v1.py @@ -0,0 +1,67 @@ +"""Review packet is read-only real Git inventory, not external approval.""" +import unittest +from unittest import mock + +import pr113_archive_source_review_packet_v1 as packet +from pr113_tooling_migration import ROOT, git, exact_tree, ToolingMigrationError +from pr113_workflow_profile_v2 import source_phase + + +class SourcePacketTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.head=git(ROOT,"rev-parse","HEAD").decode().strip() + cls.phase=source_phase(ROOT,cls.head) + + def require_staged_or_refuse_active_rehearsal(self): + if self.phase == "staged": + return True + self.assertEqual(self.phase, "active") + self.assertEqual(len(exact_tree(ROOT,self.head,".github/workflows")),30) + with self.assertRaises(ToolingMigrationError): + packet.produce(self.head) + return False + + def test_exact_real_T_B_H_35_and_virtual_30_unapproved(self): + if not self.require_staged_or_refuse_active_rehearsal(): + return + actual=packet.produce(self.head) + self.assertEqual(actual["tooling_file_count"],35) + self.assertEqual(len(actual["historical_tooling"]),35) + self.assertEqual(sum(x["changed"] for x in actual["historical_tooling"]),2) + self.assertEqual(len(actual["virtual_workflow_git_blobs"]),30) + self.assertEqual(actual["actual_active_workflows"],25) + self.assertFalse(actual["independent_security_review"]) + self.assertFalse(actual["current_required_archive_check_passed"]) + self.assertFalse(actual["owner_activation_authorized"]) + self.assertFalse(actual["publication_authorized"]) + + def test_packet_refuses_forged_successor_workflow_count(self): + if not self.require_staged_or_refuse_active_rehearsal(): + return + actual=packet.workflow_audit + def forged(*args,**kwargs): + result=actual(*args,**kwargs) + return {**result,"current_reviewed_workflow_count":31} + with mock.patch.object(packet,"workflow_audit",side_effect=forged): + with self.assertRaises(packet.PacketError): + packet.produce(self.head) + + def test_packet_refuses_extra_canonical_blob_even_if_two_known_differ(self): + if not self.require_staged_or_refuse_active_rehearsal(): + return + original=packet.exact_tree + def altered(root,rev,*prefixes): + rows=original(root,rev,*prefixes) + if rev==self.head and "verification/archive/verify_bundle.py" in rows: + rows=dict(rows) + rows["verification/archive/verify_bundle.py"]=( + rows["verification/archive/verify_bundle.py"][0], "f"*40) + return rows + with mock.patch.object(packet,"exact_tree",side_effect=altered): + with self.assertRaises(packet.PacketError): + packet.produce(self.head) + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_atomic_migration_preview.py b/verification/research/test_pr113_atomic_migration_preview.py new file mode 100644 index 00000000..df67dcea --- /dev/null +++ b/verification/research/test_pr113_atomic_migration_preview.py @@ -0,0 +1,205 @@ +"""Review 23 unchanged originals, exact two replacements, five additions.""" +import json +from pathlib import Path +import unittest +from unittest import mock + +import pr113_atomic_migration_preview as preview +import pr113_workflow_profile_v2 as v2 + +def active30(root): + count=len(list((root/".github/workflows").glob("*.yml"))) + if count not in (25,30): + raise AssertionError("only exact 25 or exact 30 profile is valid") + return count==30 + + +def fake_source(): + ROOT=Path(__file__).resolve().parents[2] + head=preview.git("rev-parse","HEAD") + return head,ROOT + +class AtomicMigrationPreviewTests(unittest.TestCase): + def test_real_source_preview_is_honestly_unapproved(self): + H,root=fake_source() + if active30(root): + actual=v2.audit(root,H,phase="active") + self.assertEqual(actual["current_reviewed_workflow_count"],30) + self.assertEqual(actual["retained_historical_count"],23) + self.assertFalse(actual["independent_security_review"]) + # The old preview must never reclassify a genuinely installed + # current-30 checkout as the historical 25-source predecessor. + with self.assertRaisesRegex(preview.TransitionError, + "virtual original 25-source preview"): + preview.preview(H) + return + actual=preview.preview(H) + self.assertEqual(actual["proposed_total_workflows"],30) + self.assertEqual(actual["historical_preserved_workflows"],23) + self.assertEqual(actual["review_pending_mandatory_replacements"],2) + self.assertEqual(actual["review_pending_additional_workflows"],5) + self.assertEqual(len(actual["virtual_source_members"]),30) + self.assertEqual(len(actual["additional_original_test_replacements"]),2) + self.assertEqual(len(actual["review_pending_integrity_tooling_replacements"]),2) + self.assertEqual(actual["versioned_workflow_allowlist_count"],30) + self.assertRegex(actual["virtual_profile_sha256"],"^[a-f0-9]{64}$") + for field in ( + "independent_security_review","owner_activation_authorized", + "merge_authorized","mathematical_acceptance", + "publication_authorized","active_workflow_set_modified"): + self.assertIs(actual[field],False) + + def test_forged_head_or_staged_yaml_modification_is_rejected(self): + H,_=fake_source() + root=Path(__file__).resolve().parents[2] + if active30(root): + with self.assertRaises(v2.ToolingMigrationError): + v2.audit(root,"f"*40,phase="active") + original_blob=v2._blob + def altered(repo,head,path): + oid,source=original_blob(repo,head,path) + if path==".github/workflows/research-lean.yml": + return oid,source+"\n# unreviewed exact-active mutation\n" + return oid,source + with mock.patch.object(v2,"_blob",side_effect=altered): + with self.assertRaises(v2.ToolingMigrationError): + v2.audit(root,H,phase="active") + return + with self.assertRaises(preview.TransitionError): + preview.preview("f"*40) + original_git=preview.git + target="docs/operations/pr113_required_gate_replacement_unapproved/research_lean_full.yml" + def mutated(*args): + if args==("show",H+":"+target): + return original_git(*args)+"\n# unauthorized modification\n" + return original_git(*args) + with self.assertRaises(preview.TransitionError): + preview.preview(H,git=mutated) + + def test_modified_v2_integrity_contract_or_suite_is_rejected(self): + H,_=fake_source() + original_git=preview.git + root=Path(__file__).resolve().parents[2] + if active30(root): + from pr113_tooling_migration import git as genuine_git + def changed(repo,*args): + if args==("show",H+":"+v2.CONTRACT): + return genuine_git(repo,*args)+b"\n# unauthorized contract\n" + return genuine_git(repo,*args) + with mock.patch.object(v2,"git",side_effect=changed): + with self.assertRaises(v2.ToolingMigrationError): + v2.audit(root,H,phase="active") + return + for name in ("workflow_contract_v2.py","test_workflow_v2.py"): + target=("docs/operations/pr113_required_gate_replacement_unapproved/" + +name) + def changed(*args): + if args==("show",H+":"+target): + return original_git(*args)+"\n# unauthorized change\n" + return original_git(*args) + with self.subTest(name=name),self.assertRaises( + preview.TransitionError): + preview.preview(H,git=changed) + + def test_missing_source_or_invalid_old_profile_rejected(self): + H,_=fake_source() + root=Path(__file__).resolve().parents[2] + if active30(root): + old=v2._historical(root,H) + with mock.patch.object(v2,"_historical", + return_value=dict(list(old.items())[:-1])): + with self.assertRaises(v2.ToolingMigrationError): + v2.audit(root,H,phase="active") + return + def damaged(h): + return {"status":"CANDIDATE","original_workflow_count":25} + with self.assertRaises(preview.TransitionError): + preview.preview(H,old_audit=damaged) + + def test_staged_executables_must_be_exact_v2_even_when_manifest_is_rehashed(self): + import hashlib + H,root=fake_source() + phase=v2.source_phase(root,H) + if phase=="active": + original_blob=v2._blob + target=".github/workflows/research-lean.yml" + origin_oid,origin_source=original_blob(root,H,target) + token="pr113_historical_runtime_v3.py" + self.assertEqual(origin_source.count(token),1) + for variant in ("v1","missing","duplicated"): + with self.subTest(variant=variant): + if variant=="v1": + forged=origin_source.replace(token, + "pr113_historical_artifact_delivery.py") + elif variant=="missing": + forged=origin_source.replace(token, + "pr113_invalid_transport.py") + else: + forged=origin_source.replace( + token,token+" # pr113_historical_artifact_delivery_v2.py") + def wrong_source(repo,head,path): + if path==target: + return origin_oid,forged + return original_blob(repo,head,path) + with mock.patch.object(v2,"_blob",side_effect=wrong_source): + with self.assertRaisesRegex(v2.ToolingMigrationError, + "activated workflow differs"): + v2.audit(root,H,phase="active") + return + real=preview.git + full_path=preview.FULL + full=json.loads(preview.raw(H,full_path)) + for variant in ("v1", "missing", "duplicated"): + with self.subTest(variant=variant): + candidate=full["full_replacements"][0] + staged=candidate["staged_source"] + original=preview.raw(H,staged) + token=b"pr113_historical_runtime_v3.py" + self.assertEqual(original.count(token),1) + if variant=="v1": + source=original.replace(token,b"pr113_historical_artifact_delivery.py") + elif variant=="missing": + source=original.replace(token,b"pr113_invalid_transport.py") + else: + source=original.replace(token,token+b" # pr113_historical_artifact_delivery_v2.py") + altered=json.loads(json.dumps(full)) + altered["full_replacements"][0]["git_blob_oid"]=preview.git_blob_oid(source) + def fake(*args): + if args==("show",H+":"+staged): + return source.decode() + if args==("show",H+":"+full_path): + return json.dumps(altered)+"\n" + return real(*args) + with self.assertRaisesRegex(preview.TransitionError, + "mandatory candidate source byte drift"): + preview.preview(H,git=fake) + + def test_artifact_delivery_module_source_is_checked_independently(self): + H,root=fake_source() + phase=v2.source_phase(root,H) + if phase=="active": + original_blob=v2._blob + path="verification/research/pr113_historical_artifact_delivery_v2.py" + oid,source=original_blob(root,H,path) + def changed(repo,head,source_path): + if source_path==path: + return oid,source+"\n# tampered artifact deliverer\n" + return original_blob(repo,head,source_path) + with mock.patch.object(v2,"_blob",side_effect=changed): + with self.assertRaisesRegex(v2.ToolingMigrationError, + "versioned transport source"): + v2.audit(root,H,phase="active") + return + real=preview.git + path="verification/research/pr113_historical_artifact_delivery_v2.py" + with self.assertRaisesRegex(preview.TransitionError, + "v2 artifact delivery source"): + def fake(*args): + if args==("show",H+":"+path): + return real(*args)+"\n# unsigned transport change\n" + return real(*args) + preview.preview(H,git=fake) + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_boundary_hardening_20261027.py b/verification/research/test_pr113_boundary_hardening_20261027.py new file mode 100644 index 00000000..f680c400 --- /dev/null +++ b/verification/research/test_pr113_boundary_hardening_20261027.py @@ -0,0 +1,185 @@ +"""Non-authorizing counterexamples for PR113 versioned intake hardening.""" +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +import tempfile +import unittest + +from pr113_boundary_io import ( + BoundaryError, strict_json_object, detached_destination, + approved_artifact_url, BoundedArtifactRedirect, +) +from pr113_semantic_receipt_v2 import validate_compiler_receipt_v2 +import infrastructure_acceptance as original + + +class JSONBoundaryTests(unittest.TestCase): + def test_duplicate_keys_reject_at_every_depth(self): + for value in ( + b'{"status":"fail","status":"success"}', + b'{"scope":{"approved":false,"approved":true}}', + b'{"sources":[{"sha":"a","sha":"b"}]}', + ): + with self.subTest(raw=value), self.assertRaises(BoundaryError): + strict_json_object(value) + + def test_nonfinite_invalid_scalars_and_bounds(self): + for value in (b'[]', b'null', b'{"v":NaN}', b'{"v":Infinity}', b''): + with self.subTest(raw=value), self.assertRaises(BoundaryError): + strict_json_object(value) + self.assertEqual(strict_json_object(b'{"ok":true}'), {"ok": True}) + with self.assertRaises(BoundaryError): + strict_json_object(b'{"ok":true}', max_bytes=3) + + +class DetachedPathTests(unittest.TestCase): + def test_parent_traversal_never_writes_to_source(self): + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + source = root / "source" + source.mkdir() + outside = root / "outside" + outside.mkdir() + malicious = outside / ".." / "source" / "unexpected.json" + with self.assertRaises(BoundaryError): + detached_destination(malicious, source) + self.assertFalse((source / "unexpected.json").exists()) + + def test_symlink_and_existing_output_rejected(self): + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + source = root / "source" + source.mkdir() + target = root / "target" + target.mkdir() + link = root / "alias" + link.symlink_to(target) + with self.assertRaises(BoundaryError): + detached_destination(link / "receipt.json", source) + output = target / "receipt.json" + self.assertEqual(detached_destination(output, source), output.resolve()) + output.write_text("{}") + with self.assertRaises(BoundaryError): + detached_destination(output, source) + + +class RedirectTests(unittest.TestCase): + def test_every_hop_is_validated(self): + allowed = "https://something.blob.core.windows.net/path?sig=a" + self.assertTrue(approved_artifact_url(allowed)) + for url in ( + "http://something.blob.core.windows.net/path", + "https://evil.example/path", + "https://api.github.com:444/path", + "https://name:password@api.github.com/path", + ): + self.assertFalse(approved_artifact_url(url), url) + handler = BoundedArtifactRedirect() + from urllib.request import Request + request = Request("https://api.github.com/repos/HMarcusWH/PermanssonLean/a", + headers={"Authorization": "Bearer SECRET"}) + child = handler.redirect_request(request, None, 302, "Found", {}, + allowed) + self.assertFalse(any(key.lower() == "authorization" + for key in (*child.headers, *child.unredirected_hdrs))) + with self.assertRaises(BoundaryError): + handler.redirect_request(child, None, 302, "Found", {}, + "http://evil.example/path") + handler = BoundedArtifactRedirect() + for count in range(5): + handler.redirect_request(request, None, 302, "Found", {}, allowed) + with self.assertRaises(BoundaryError): + handler.redirect_request(request, None, 302, "Found", {}, allowed) + + +class A2DiagnosticTests(unittest.TestCase): + """Exercise the actual complete v2 A2 validator, without stubbing it. + + Synthetic Lean logs are only diagnostic fixtures: these tests never + represent an original historical Lean execution or security acceptance. + """ + PREFIX = "historical_original_A2" + MUTANT = "wrong_reported_effect" + EXPECTED_STATUS = "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING" + + def fixture(self, log): + # Reuse the complete, source-mutated four-negative fixture so all + # four real source probes, the exact positive, artifacts and command + # byte digests remain part of the versioned evidence contract. + from localized_prefix_gate import MUTATIONS, mutated_source + from test_pr113_semantic_receipt_v2 import fixture as full_fixture + files = full_fixture(self.PREFIX, MUTATIONS, mutated_source, + mode="focused") + receipt_path = self.PREFIX + "/receipt.json" + row = json.loads(files[receipt_path]) + matched = [entry for entry in row["commands"] + if entry["label"] == self.MUTANT] + self.assertEqual(len(matched), 1) + command = matched[0] + log_path = self.PREFIX + "/" + command["log"] + original_bytes = log.encode("utf-8") + files[log_path] = original_bytes + command["log_sha256"] = hashlib.sha256(original_bytes).hexdigest() + row["artifacts"][command["log"]] = { + "sha256": command["log_sha256"], "bytes": len(original_bytes)} + files[receipt_path] = ( + json.dumps(row, sort_keys=True) + "\n").encode("utf-8") + return self.PREFIX, files, row + + def _verify(self, files, prefix): + from localized_prefix_gate import MUTATIONS + return validate_compiler_receipt_v2( + files, prefix, self.EXPECTED_STATUS, MUTATIONS) + + def test_import_syntax_resource_and_wrong_file_fail_closed(self): + for log in ( + "wrong_reported_effect.lean:1:0: error: unknown module prefix 'Missing'", + "wrong_reported_effect.lean:1:0: error: unexpected token ')'; expected term", + "Other.lean:1:0: error: unsolved goals", + "wrong_reported_effect.lean:1:0: error: deterministic timeout", + "wrong_reported_effect.lean:1:0: error: failed to synthesize", + ): + with self.subTest(log=log): + prefix, files, row = self.fixture(log) + with self.assertRaises(original.InfrastructureError): + self._verify(files, prefix) + + def test_intended_goal_failure_with_correct_filename_is_distinguished(self): + prefix, files, row = self.fixture( + "wrong_reported_effect.lean:1:0: error: unsolved goals\n" + "case : False") + verified, identity = self._verify(files, prefix) + self.assertEqual(verified, row) + self.assertEqual(identity, hashlib.sha256( + files[prefix + "/receipt.json"]).hexdigest()) + self.assertEqual(verified["mode"], "focused") + self.assertEqual(len(verified["mutations"]), 4) + self.assertTrue(all( + source in {command["label"] for command in verified["commands"]} + for source in ("source_1", "source_2", "source_3", "source_4"))) + + def test_missing_or_duplicated_source_probe_never_passes(self): + prefix, files, row = self.fixture( + "wrong_reported_effect.lean:1:0: error: unsolved goals\n" + "case : False") + for variant in ("missing", "duplicate"): + altered = dict(files) + receipt = json.loads(files[prefix + "/receipt.json"]) + if variant == "missing": + receipt["commands"] = [ + command for command in receipt["commands"] + if command["label"] != "source_4"] + else: + copied = next(command for command in receipt["commands"] + if command["label"] == "source_4") + receipt["commands"].append(dict(copied)) + altered[prefix + "/receipt.json"] = json.dumps(receipt).encode() + with self.subTest(variant=variant), self.assertRaises( + original.InfrastructureError): + self._verify(altered, prefix) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_cert_release_source_v3.py b/verification/research/test_pr113_cert_release_source_v3.py new file mode 100644 index 00000000..4ded947d --- /dev/null +++ b/verification/research/test_pr113_cert_release_source_v3.py @@ -0,0 +1,94 @@ +"""Authenticated CERT PR identity requires actual B/H/M and M113 ancestry.""" +import unittest +import pr113_cert_release_source_v3 as v3 +from release_target import B113 + +H113 = "a"*40 +M113 = "b"*40 +B = "c"*40 +H = "d"*40 +M = "e"*40 + + +class ReleaseTargetTests(unittest.TestCase): + def git(self, *a): + if a[:2] == ("cat-file", "-t"): + return "commit" + if a == ("show", "-s", "--format=%P", M113): + return B113+" "+H113 + if a == ("show", "-s", "--format=%P", M): + return B+" "+H + if a[0] == "rev-parse" and a[1].endswith("^{tree}"): + return "source_tree" + if a == ("merge-base", "--is-ancestor", M113, B): + return "" + return "not expected" + + def original(self, merged=True): + return {"number":113,"state":"closed" if merged else "open", + "merged":merged,"merge_commit_sha":M113 if merged else None, + "head":{"sha":H113}} + + def cert(self, merged=True): + return {"number":115,"state":"closed" if merged else "open", + "merged":merged,"merge_commit_sha":M if merged else None, + "head":{"sha":H,"ref":"research/cert-original", + "repo":{"full_name":"HMarcusWH/PermanssonLean"}}, + "base":{"sha":B,"ref":"main", + "repo":{"full_name":"HMarcusWH/PermanssonLean"}}} + + def live(self, url): + if url == "/pulls/113": + return self.original() + if url == "/pulls/115": + return self.cert() + if url == "/commits/"+H+"/pulls?per_page=100": + return [self.cert()] + raise AssertionError(url) + + def test_original_and_cert_merge_targets_distinct(self): + self.assertEqual(v3.source_pr(B113,H113,M113,git=self.git,live=self.live)[0],113) + self.assertEqual(v3.source_pr(B,H,M,git=self.git,live=self.live)[0],115) + + def test_merged_CERT_base_display_can_move_without_changing_git_parents(self): + def advanced(url): + if url == "/pulls/115": + updated = self.cert() + updated["base"]["sha"] = "f"*40 + return updated + return self.live(url) + self.assertEqual(v3.source_pr(B,H,M,git=self.git,live=advanced)[0],115) + + def test_wrong_merge_or_ancestry_rejected(self): + with self.assertRaises(v3.ReleaseSourceError): + v3.source_pr(B,H,"f"*40,git=self.git,live=self.live) + def wrong_git(*a): + if a[:2] == ("merge-base","--is-ancestor"): + return "not descendant" + return self.git(*a) + with self.assertRaises(v3.ReleaseSourceError): + v3.source_pr(B,H,M,git=wrong_git,live=self.live) + + def test_original_current_pr_not_secretly_merged(self): + def live(url): + if url == "/pulls/113": + return self.original(False) + return self.live(url) + self.assertEqual(v3.source_pr(B113,H113,git=self.git,live=live)[0],113) + with self.assertRaises(v3.ReleaseSourceError): + v3.source_pr(B113,H113,M113,git=self.git,live=live) + + def test_other_pr_number_or_wrong_repo_rejected(self): + for edit in ({"number":114}, + {"head":{"sha":H,"ref":"research/cert-original", + "repo":{"full_name":"untrusted/repo"}}}): + def tampered(url): + if url == "/pulls/115": + return {**self.cert(),**edit} + return self.live(url) + with self.assertRaises(v3.ReleaseSourceError): + v3.source_pr(B,H,M,git=self.git,live=tampered) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_cert_review_auth_v3.py b/verification/research/test_pr113_cert_review_auth_v3.py new file mode 100644 index 00000000..cb8cddc6 --- /dev/null +++ b/verification/research/test_pr113_cert_review_auth_v3.py @@ -0,0 +1,126 @@ +"""Mock integration: real PR113 and later CERT may not borrow review scopes.""" +import unittest +from unittest import mock + +import final_closure_authenticate as source +from release_target import B113 + +H113="a"*40 +M113="b"*40 +B="c"*40 +H="d"*40 +S="e"*40 +M="f"*40 + + +def git(*args): + if args[:2] == ("cat-file", "-t"): + return "commit" + if args == ("show", "-s", "--format=%P", M113): + return B113+" "+H113 + if args == ("show", "-s", "--format=%P", S): + return B+" "+H + if args == ("show", "-s", "--format=%P", M): + return B+" "+H + if args[0] == "rev-parse" and args[1].endswith("^{tree}"): + return "same_tree" + if args[:2] == ("merge-base", "--is-ancestor"): + return "" + return "unexpected" + + +def pr(num): + if num==113: + return {"number":113,"state":"closed","merged":True, + "head":{"sha":H113},"merge_commit_sha":M113} + return {"number":115,"state":"closed","merged":True, + "merge_commit_sha":M,"user":{"login":"HMarcusWH"}, + "head":{"sha":H,"ref":"research/cert-real", + "repo":{"full_name":"HMarcusWH/PermanssonLean"}}, + "base":{"sha":B,"ref":"main", + "repo":{"full_name":"HMarcusWH/PermanssonLean"}}} + + +def live(endpoint): + if endpoint == "/pulls/113": + return pr(113) + if endpoint == "/pulls/115": + return pr(115) + if endpoint == "/commits/"+H+"/pulls?per_page=100": + return [pr(115)] + raise AssertionError(endpoint) + + +class AuthenticatedCertMergeTests(unittest.TestCase): + + def test_duplicate_GitHub_review_field_rejected_before_acceptance(self): + import json + sha = "e"*64 + payload = ('{"head_sha":"' + H + '","head_sha":"' + H + + '","evidence_bundle_sha256":"' + sha + + '","receipt_payload_sha256":"' + sha + + '","cases":{},"claims":[]}') + review = {"id":991,"state":"APPROVED","commit_id":H, + "submitted_at":"2026-10-27T01:00:00Z", + "user":{"login":"independent-reviewer"}, + "body":source.REVIEW_PREFIX+" "+payload} + with mock.patch.object(source,"paginate",return_value=[review]), \ + mock.patch.object(source,"reviewer_authorized",return_value=True): + with self.assertRaises(source.AuthenticationError): + source.authenticate_scoped_reviews( + H,"HMarcusWH",sha,[{"id":991}],sha,pr_number=115) + + def test_edited_scholarly_comment_does_not_become_approval(self): + import json + pr = {"merged_at": "2026-10-27T01:00:00Z", + "user": {"login": "HMarcusWH"}} + claim = { + "head_sha": H, "merged_main_sha": M, + "pdf_sha256": "e"*64, + "pages": {"page-1.png": "f"*64}, + "approved_claim_ids": ["Q1"], + "scholarly_review": "APPROVED", + "all_pages_visually_inspected": True, + } + comment = { + "id": 777, "user": {"login": "independent-scholarly"}, + "created_at": "2026-10-27T01:05:00Z", + "updated_at": "2026-10-27T02:05:00Z", + "body": source.SCHOLARLY_PREFIX + json.dumps(claim), + } + with mock.patch.object(source, "paginate", return_value=[comment]), \ + mock.patch.object(source, "reviewer_authorized", return_value=True): + with self.assertRaises(source.AuthenticationError): + source.authentic_scholarly_attestations( + pr, H, M, pr_number=115) + + def test_v2_source_gate_uses_real_cert_not_original_pr_reviewer_records(self): + def checks(commit): + return {name:{"id":i+1,"head_sha":commit, + "status":"completed","conclusion":"success"} + for i,name in enumerate(sorted(source.REQUIRED))} + receipt={"base_sha":B,"head_sha":H,"synthetic_sha":S, + "merged_main_sha":M, + "source_bound_ci":[{"name":k,"id":v["id"],"head_sha":H} + for k,v in checks(H).items()], + "merged_bound_ci":[{"name":k,"id":v["id"],"head_sha":M} + for k,v in checks(M).items()], + "evidence_bundle_sha256":"0"*64, + "independent_source_reviews":[]} + with mock.patch.object(source,"git",side_effect=git), \ + mock.patch.object(source,"live",side_effect=live), \ + mock.patch.object(source,"authenticated_checks",side_effect=checks), \ + mock.patch.object(source,"authenticate_scoped_reviews", + return_value={123:{"claims":["Q1"]}}) as reviewed, \ + mock.patch.object(source,"authentic_scholarly_attestations", + return_value=[]) as scholarly: + result=source.verify_merged_v2(receipt) + self.assertEqual(result["M"],M) + self.assertEqual(result["H"],H) + self.assertEqual(reviewed.call_args.kwargs["pr_number"],115) + self.assertEqual(scholarly.call_args.kwargs["pr_number"],115) + self.assertEqual(len(result["checks"]),12) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_ci_producer_source_v3.py b/verification/research/test_pr113_ci_producer_source_v3.py new file mode 100644 index 00000000..cf92f2c7 --- /dev/null +++ b/verification/research/test_pr113_ci_producer_source_v3.py @@ -0,0 +1,78 @@ +"""Mock-only source-input checks. No GitHub or mathematical approval implied.""" +import json +import unittest + +import pr113_ci_producer_source_v3 as source + +H = "a" * 40 +REPO = "HMarcusWH/PermanssonLean" + + +def event(kind, mode, branch, *, cert=""): + env = { + "GITHUB_ACTIONS": "true", "GITHUB_REPOSITORY": REPO, + "GITHUB_SHA": H, "GITHUB_REF": "refs/heads/" + branch, + "GITHUB_EVENT_NAME": kind, + "PERMANSSON_CERT_PR_NUMBER": cert, + "PR113_REQUESTED_SOURCE_HEAD": H if kind == "workflow_dispatch" else "", + "PR113_REQUESTED_RUN_MODE": mode if kind == "workflow_dispatch" else "", + } + payload = {"repository": {"full_name": REPO}} + if kind == "workflow_dispatch": + payload["inputs"] = { + "run_mode": mode, "source_head": H, "cert_pr_number": cert} + else: + payload["ref"] = env["GITHUB_REF"] + payload["after"] = H + return env, payload + + +class ProducerSourceTests(unittest.TestCase): + def check(self, env, payload, mode): + return source.authenticate(H, mode, env=env, + event_data=json.dumps(payload).encode(), + branch_check=lambda head, *, env: env["GITHUB_REF"].split( + "refs/heads/", 1)[1]) + + def test_original_push_and_future_cert_dispatch_are_unapproved(self): + env, payload = event("push", "source_controls", + "research/final-mathematical-closure") + receipt = self.check(env, payload, "source_controls") + self.assertEqual(receipt["pr_number"], 113) + self.assertFalse(receipt["case_acceptance"]) + env, payload = event("workflow_dispatch", "source_controls", + "research/cert-real", cert="115") + receipt = self.check(env, payload, "source_controls") + self.assertEqual(receipt["pr_number"], 115) + self.assertFalse(receipt["claim_acceptance"]) + env, payload = event("workflow_dispatch", "reviewed_legacy20", + "research/cert-real", cert="115") + receipt = self.check(env, payload, "reviewed_legacy20") + self.assertFalse(receipt["reviewer_selected"]) + + def test_borrowed_pr_head_mode_and_repo_fail(self): + env, payload = event("workflow_dispatch", "reviewed_legacy20", + "research/cert-real", cert="115") + for change in ( + lambda e, p: p["inputs"].update(source_head="b"*40), + lambda e, p: p["inputs"].update(run_mode="source_controls"), + lambda e, p: p["inputs"].update(cert_pr_number="116"), + lambda e, p: p["repository"].update(full_name="attacker/foreign"), + lambda e, p: e.update(GITHUB_SHA="c"*40), + ): + e = dict(env) + p = json.loads(json.dumps(payload)) + change(e, p) + with self.subTest(change=str(change)), self.assertRaises( + source.SourceDispatchError): + self.check(e, p, "reviewed_legacy20") + + def test_legacy_replay_cannot_run_on_push(self): + env, payload = event("push", "reviewed_legacy20", + "research/final-mathematical-closure") + with self.assertRaises(source.SourceDispatchError): + self.check(env, payload, "reviewed_legacy20") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_connected_source_carrier.py b/verification/research/test_pr113_connected_source_carrier.py new file mode 100644 index 00000000..2997d53a --- /dev/null +++ b/verification/research/test_pr113_connected_source_carrier.py @@ -0,0 +1,166 @@ +"""R05/R07: real source-control validation across a synthetic GH input carrier. + +The producer ZIPs and reviewer/GitHub responses are synthetic. This +rehearsal authenticates *no real CI, mathematical review or publication*. +""" +from __future__ import annotations + +import io +import json +from pathlib import Path +import tempfile +import unittest +import zipfile + +from pr113_preseal_input_carrier import ( + EXPECTED_FILES, PresealInputError, digest, extract, +) +from pr113_v31_execution_integrity import ( + RealControlError, validate_executed_controls, +) +from test_pr113_preseal_input_carrier import originals +from test_pr113_v31_execution_integrity import ( + H, authentic_original_legacy, complete, source, +) + + +def encoded(value): + return (json.dumps(value, sort_keys=True) + "\n").encode("utf-8") + + +def zipped(members): + buffer = io.BytesIO() + with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as handle: + for name, raw in sorted(members.items()): + handle.writestr(name, raw) + return buffer.getvalue() + + +def synthetic_carrier(contract, archives): + """Keep all three genuine-shaped 45+20+11 owner archives byte-exact.""" + members = { + "reviewed/approved_claim_manifest.json": encoded({ + "status": "SYNTHETIC_UNREVIEWED", "claims": [], + }), + "reviewed/source_receipts.json": encoded({ + "format": "SYNTHETIC_NOT_A_GITHUB_RECEIPT", + "head_sha": H, "independent_review": False, + }), + "reviewed/evidence_bundle.zip": zipped({ + "mock.txt": b"unreviewed source evidence placeholder", + }), + "reviewed/reproduction.zip": zipped({ + "mock.txt": b"unreviewed reproduction placeholder", + }), + "reviewed/reproduction_ids.json": encoded({ + "status": "SYNTHETIC_NOT_REPRODUCTION", + }), + "reviewed/additional_contract.json": encoded(contract), + "reviewed/workflow_blob_candidates.json": encoded({ + "status": "SYNTHETIC_UNREVIEWED", + }), + } + # This synthetic connected fixture represents a full 76-owner registry + # over only three producer ZIPs; the real validator still authenticates + # every run/check/artifact byte in subsequent separate stages. + contract = { + **contract, + "format": "Permansson.PR113.ControlProducerExtension.v3.1", + "source_controls_accepted": False, + "independent_security_review": False, + "mathematical_approval": False, + } + members["reviewed/additional_contract.json"] = encoded(contract) + for artifact_id, raw in archives.items(): + members[f"reviewed/additional_zips/{artifact_id}.zip"] = raw + assert EXPECTED_FILES <= set(members) + return zipped(members) + + +class ConnectedSourceCarrierTests(unittest.TestCase): + def test_76_owner_source_validation_survives_carrier_exactly(self): + contract, owner_archives = complete() + original_21 = authentic_original_legacy() + first = validate_executed_controls( + H, contract, owner_archives, + source=source(), original_producers=original_21) + self.assertEqual(first["source_owner_count"], 76) + self.assertEqual(first["claim_positive_negative_directions"], 22) + self.assertFalse(first["mathematical_acceptance"]) + raw = synthetic_carrier(contract, owner_archives) + + with tempfile.TemporaryDirectory(prefix="pr113-r05-r07-") as td: + base = Path(td) + artifact = base / "original-carrier.zip" + artifact.write_bytes(raw) + gh = originals(raw) + gh.pop("raw") + output = base / "unpacked" + transport = extract(artifact, output, **gh) + + self.assertEqual( + transport["status"], + "GH_SOURCE_TRANSPORT_VERIFIED__RELEASE_REVIEW_PENDING") + self.assertEqual(transport["transport_artifact_sha256"], digest(raw)) + self.assertFalse(transport["independent_review"]) + self.assertFalse(transport["mathematical_acceptance"]) + self.assertFalse(transport["publication_authorized"]) + self.assertEqual( + json.loads((output / "transport_diagnostics.json").read_bytes()), + transport) + + recovered_contract = json.loads( + (output / "additional_contract.json").read_bytes()) + recovered_archives = { + number: (output / "additional_zips" / + f"{number}.zip").read_bytes() + for number in owner_archives + } + self.assertEqual(recovered_archives, owner_archives) + second = validate_executed_controls( + H, recovered_contract, recovered_archives, + source=source(), original_producers=original_21) + self.assertEqual(first, second) + self.assertFalse(json.loads( + (output / "source_receipts.json").read_bytes() + )["independent_review"]) + + # A different valid ZIP at the claimed artifact ID must not + # inherit another owner's original execution provenance. + recovered_archives[1], recovered_archives[2] = ( + recovered_archives[2], recovered_archives[1]) + with self.assertRaises(RealControlError): + validate_executed_controls( + H, recovered_contract, recovered_archives, + source=source(), original_producers=original_21) + + def test_missing_original_producers_and_foreign_head_fail(self): + contract, archives = complete() + with self.assertRaises(RealControlError): + validate_executed_controls( + H, contract, archives, source=source(), + original_producers=None) + foreign = dict(contract, head_sha="b" * 40) + with self.assertRaises(RealControlError): + validate_executed_controls( + H, foreign, archives, source=source(), + original_producers=authentic_original_legacy()) + + def test_forged_github_artifact_digest_fails_before_unpack(self): + contract, archives = complete() + raw = synthetic_carrier(contract, archives) + with tempfile.TemporaryDirectory(prefix="pr113-bad-gh-") as td: + base = Path(td) + archive = base / "original.zip" + archive.write_bytes(raw) + gh = originals(raw) + gh.pop("raw") + gh["meta"]["digest"] = "sha256:" + ("0" * 64) + output = base / "rejected" + with self.assertRaises(PresealInputError): + extract(archive, output, **gh) + self.assertFalse(output.exists()) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_corrected_a2_intake.py b/verification/research/test_pr113_corrected_a2_intake.py new file mode 100644 index 00000000..7b4e0450 --- /dev/null +++ b/verification/research/test_pr113_corrected_a2_intake.py @@ -0,0 +1,195 @@ +"""Corrected A2 source-binding tests. Compiler receipts below are synthetic. + +The real intake/source checks run. The compiler receipt adapter is mocked only +here; actual source compilation and semantic-receipt validators remain separate +mandatory suites. Nothing here authenticates a GitHub artifact or human review. +""" +from __future__ import annotations +import ast +import copy +import hashlib +import json +from pathlib import Path +import unittest +from unittest import mock + +import pr113_historical_a2_baseline_v1 as baseline +import pr113_historical_intake_v2 as intake + +H = "a" * 40 +ROOT = Path(__file__).resolve().parents[2] + + +def encoded(value): + return (json.dumps(value, sort_keys=True) + "\n").encode() + + +def git_blob(raw): + return hashlib.sha1(b"blob " + str(len(raw)).encode() + b"\0" + raw).hexdigest() + + +def fixture(): + historical = { + "format": "Permansson.A2P02.CompilerReceipt.v2", + "status": "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING", + "source_unchanged": True, + "source": {"git_head": baseline.REPLAY_HEAD, "git_head_tree": baseline.REPLAY_TREE}, + "declarations": dict.fromkeys(intake.DECLS, []), + } + successor = { + "format": "Permansson.PR113.A2SuccessorCompilerCandidate.v2", + "status": "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING", "source_unchanged": True, + "source": {"git_head": H}, "declarations": dict.fromkeys(intake.DECLS, []), + } + provenance = { + "historical_checkout_sha": baseline.REPLAY_HEAD, + "historical_tree_sha": baseline.REPLAY_TREE, + "introduced_executable_sha": baseline.INTRODUCER, + "baseline_contract": baseline.FORMAT, + "original_runtime_git_blobs": dict(baseline.EXECUTORS), + "output_receipt_sha256": hashlib.sha256(encoded(historical)).hexdigest(), + "imported_current_A2_gate": False, + } + overall = { + "format": baseline.DUAL_FORMAT, + "status": "BOTH_ACTUAL_A2_COMPILER_PATHS_EXECUTED_UNREVIEWED", + "historical_source_commit": baseline.REPLAY_HEAD, + "historical_introducer_sha": baseline.INTRODUCER, + "historical_tree_sha": baseline.REPLAY_TREE, + "historical_runtime_provenance": provenance, "current_source_commit": H, + "historical_original_workflow_count": 25, "current_reviewed_workflow_count": 30, + "historical_test_executed": True, "successor_test_executed": True, + "historical_signatures": 40, "successor_signatures": 40, + "historical_semantic_mutants": 4, "successor_semantic_mutants": 4, + "original_verifier_modified": False, "independent_security_approval": False, + "independent_mathematical_approval": False, "merged_main_sha": None, + "full_release_approved": False, + } + return historical, successor, overall + + +def inspect(rows): + original, successor, overall = rows + files = { + "historical_original_A2/receipt.json": encoded(original), + "pr113_successor_A2/receipt.json": encoded(successor), + "unreviewed_dual_A2_candidate.json": encoded(overall), + } + def synthetic_compiler_adapter(files, prefix, status, mutations): + raw = files[prefix + "/receipt.json"] + return json.loads(raw), hashlib.sha256(raw).hexdigest() + with mock.patch.object(intake, "validate_compiler_receipt_v2", + side_effect=synthetic_compiler_adapter) as verify: + result = intake.inspect_A2_v2(files, H) + if verify.call_count != 2: + raise AssertionError("both independent receipt legs must be consumed") + return result + + +class CorrectedA2IntakeTests(unittest.TestCase): + def test_corrected_source_is_explicit_without_granting_review(self): + result = inspect(fixture()) + self.assertEqual(result["historical_replay_sha"], baseline.REPLAY_HEAD) + self.assertEqual(result["historical_replay_tree"], baseline.REPLAY_TREE) + self.assertEqual(result["historical_introducer_sha"], baseline.INTRODUCER) + self.assertFalse(result["independent_security_review"]) + self.assertFalse(result["mathematical_acceptance"]) + + def test_legacy_dual_format_cannot_be_upgraded(self): + for version in ("v2", "v3"): + rows = fixture(); rows[2]["format"] = "Permansson.PR113.DualA2CompilerCandidate." + version + with self.subTest(version=version), self.assertRaises(ValueError): + inspect(rows) + + def test_introduction_cannot_substitute_for_successful_replay(self): + for scope in ("receipt", "overall", "provenance"): + rows = fixture() + if scope == "receipt": rows[0]["source"]["git_head"] = baseline.INTRODUCER + elif scope == "overall": rows[2]["historical_source_commit"] = baseline.INTRODUCER + else: rows[2]["historical_runtime_provenance"]["historical_checkout_sha"] = baseline.INTRODUCER + with self.subTest(scope=scope), self.assertRaises(ValueError): inspect(rows) + + def test_wrong_tree_at_any_binding_is_rejected(self): + for scope in ("receipt", "overall", "provenance"): + rows = fixture() + if scope == "receipt": rows[0]["source"]["git_head_tree"] = "f" * 40 + elif scope == "overall": rows[2]["historical_tree_sha"] = "f" * 40 + else: rows[2]["historical_runtime_provenance"]["historical_tree_sha"] = "f" * 40 + with self.subTest(scope=scope), self.assertRaises(ValueError): inspect(rows) + + def test_provenance_requires_original_executors_and_introducer(self): + for field, value in (("original_runtime_git_blobs", {}), + ("introduced_executable_sha", baseline.REPLAY_HEAD), + ("baseline_contract", "v0"), ("imported_current_A2_gate", True), + ("output_receipt_sha256", "0" * 64)): + rows = fixture(); rows[2]["historical_runtime_provenance"][field] = value + with self.subTest(field=field), self.assertRaises(ValueError): inspect(rows) + + def test_historical_success_cannot_cover_foreign_current_source(self): + for field in ("current_source_commit", "receipt"): + rows = fixture() + if field == "receipt": rows[1]["source"]["git_head"] = "b" * 40 + else: rows[2][field] = "b" * 40 + with self.subTest(field=field), self.assertRaises(ValueError): inspect(rows) + + def test_both_legs_need_all_forty_original_declarations(self): + for which in (0, 1): + rows = fixture(); rows[which]["declarations"].pop(next(iter(intake.DECLS))) + with self.subTest(which=which), self.assertRaises(ValueError): inspect(rows) + for field in ("historical_signatures", "successor_signatures"): + rows = fixture(); rows[2][field] = 39 + with self.subTest(field=field), self.assertRaises(ValueError): inspect(rows) + + def test_review_or_math_acceptance_cannot_be_self_asserted(self): + for field in ("original_verifier_modified", "independent_security_approval", + "independent_mathematical_approval", "full_release_approved"): + rows = fixture(); rows[2][field] = True + with self.subTest(field=field), self.assertRaises(ValueError): inspect(rows) + + +class CorrectedA2SourceWiringTests(unittest.TestCase): + def test_all_changed_staged_fingerprints_agree_with_actual_bytes(self): + stage = ROOT / "docs/operations/pr113_required_gate_replacement_unapproved" + proposal = ROOT / "docs/operations/pr113_unapproved_workflows" + producer = proposal / "pr113-historical-migration-candidates.yml" + source = producer.read_bytes() + producer_oid = git_blob(source) + manifest = json.loads((proposal / "manifest.json").read_bytes()) + row = next(r for r in manifest["proposals"] + if r["workflow_path"].endswith("pr113-historical-migration-candidates.yml")) + self.assertEqual(row["git_blob_oid"], producer_oid) + self.assertFalse(row["approved"]) + self.assertFalse(row["installed"]) + contract = (stage / "workflow_contract_v2.py").read_bytes() + declared = json.loads((stage / "workflow_contract_v2_manifest.json").read_bytes()) + self.assertEqual(declared["staged_git_blob_oid"], git_blob(contract)) + def assigned(raw, name): + for node in ast.parse(raw).body: + if isinstance(node, ast.Assign) and any( + isinstance(t, ast.Name) and t.id == name for t in node.targets): + return ast.literal_eval(node.value) + raise AssertionError("missing source declaration: " + name) + expected = assigned(contract, "APPROVED_WORKFLOW_BLOBS") + self.assertEqual(expected, declared["expected_workflow_git_blobs"]) + self.assertEqual(len(expected), 30) + self.assertEqual(expected[producer.name], producer_oid) + pins = assigned((ROOT / "verification/research/pr113_a2_successor_identity_v2.py").read_bytes(), + "PINNED_MODIFIED_PROVIDERS") + self.assertEqual(pins["verification/integrity/workflow_contract.py"], git_blob(contract)) + self.assertIn(("--historical-head " + baseline.REPLAY_HEAD).encode(), source) + self.assertNotIn(("--historical-head " + baseline.INTRODUCER).encode(), source) + self.assertIn(b"pr113_a2_dependency_bridge_v2.py", source) + self.assertNotIn(b"HistoricalA2DependencyBridge.v1", source) + + def test_v2_runtime_imports_versioned_bridge_not_legacy_alias(self): + raw = (ROOT / "verification/research/infrastructure_acceptance_v2.py").read_bytes() + nodes = ast.parse(raw).body + self.assertTrue(any(isinstance(n, ast.ImportFrom) + and n.module == "pr113_a2_dependency_bridge_v2" + and any(a.name == "verify_A2_dependency_bridge" for a in n.names) + for n in nodes)) + self.assertNotIn(b"verify_A2_dependency_bridge = legacy.verify_A2_dependency_bridge", raw) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_disposable_transition_rehearsal.py b/verification/research/test_pr113_disposable_transition_rehearsal.py new file mode 100644 index 00000000..93cacba4 --- /dev/null +++ b/verification/research/test_pr113_disposable_transition_rehearsal.py @@ -0,0 +1,260 @@ +"""Actual disposable Git worktree/source rehearsal; no protected activation. + +One test runs the complete staged 25->30 installation using disposable Git +objects and proves genuine ordered synthetic S source identity. The original +PR branch, workflow files, owner/reviewer state and release claims stay inert. +""" +import io +import json +import subprocess +from contextlib import redirect_stderr +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +import pr113_disposable_transition_rehearsal as rehearsal + +ROOT=Path(__file__).resolve().parents[2] + + +class RealDisposableRehearsalTests(unittest.TestCase): + def test_exact_current_source_25_to_30_in_detached_git_worktree(self): + source=rehearsal.scalar(ROOT,"rev-parse","HEAD") + parents=rehearsal.scalar( + ROOT,"show","-s","--format=%P",source).split() + head=(parents[1] if len(parents)==2 + and parents[0]==rehearsal.PR113_BASE else source) + phase,pre=rehearsal.audited_source_phase(ROOT,source) + self.assertIn(phase,("staged","active")) + self.assertEqual(pre["historical_original_workflow_count"],25) + before=rehearsal.scalar(ROOT,"rev-parse","HEAD") + with tempfile.TemporaryDirectory(prefix="pr113-report-outside-") as temp: + report_path=Path(temp)/"source_rehearsal.json" + result=rehearsal.execute(head,report_path) + self.assertEqual(result["status"], + ("DISPOSABLE_SOURCE_AND_ORDERED_SYNTHETIC_REHEARSED_UNREVIEWED" + if phase=="staged" else + "DISPOSABLE_ACTIVE_30_AND_ORDERED_SYNTHETIC_REHEARSED_UNREVIEWED")) + self.assertEqual(result["source_phase_at_invocation"],phase) + workflow=result["current30_CI06_CI07_workflow_suite"] + self.assertEqual(workflow["status"], + "CURRENT_30_WORKFLOW_CI06_CI07_EXECUTED_LOCALLY") + self.assertEqual(workflow["covered_requirements"], + ["CI-06","CI-07"]) + self.assertGreaterEqual(workflow["methods_run"],8) + self.assertTrue(workflow["real_execution"]) + self.assertFalse(workflow["required_github_check_authenticated"]) + self.assertFalse(result["current30_local_suite_is_required_CI"]) + regression=result["current30_phase_aware_regressions"] + self.assertEqual(regression["status"], + "EIGHT_REAL_ACTIVE30_SUITES_PASSED_UNREVIEWED") + self.assertEqual(len(regression["executed_suites"]),8) + self.assertEqual(regression["checked_sha"], + result["disposable_current_source_commit"]) + self.assertTrue(all(x["status"]=="PASS_EXECUTED" + for x in regression["executed_suites"])) + self.assertEqual({x["source_test"] + for x in regression["executed_suites"]}, + set(rehearsal.ACTIVE_30_SUITES)) + self.assertFalse(regression["required_github_ci_authenticated"]) + self.assertFalse(regression["independent_security_review"]) + self.assertEqual(workflow["source_head"], + result["disposable_current_source_commit"]) + self.assertEqual(result["historical_original_workflow_count"],25) + self.assertEqual(result["current_reviewed_workflow_count"],30) + self.assertEqual(result["retained_workflow_count"],23) + self.assertEqual(result["replacement_workflow_count"],2) + self.assertEqual(result["addition_workflow_count"],5) + self.assertEqual(result["actual_requested_head"],head) + self.assertEqual(result["source_at_invocation"],source) + if phase=="staged": + self.assertNotEqual(result["disposable_current_source_commit"],head) + else: + self.assertEqual(result["disposable_current_source_commit"],head) + self.assertNotEqual(result["disposable_synthetic_source_commit"], + result["disposable_current_source_commit"]) + self.assertEqual(result["profile_sha256"], + result["synthetic_profile_sha256"]) + self.assertTrue(result["disposable_source_mutation_rejected"]) + for field in ( + "source_bound_GitHub_producer_authenticated", + "independent_security_review","owner_activation_authorized", + "all_required_CI_green","mathematical_acceptance", + "owner_merge_authorized","publication_authorized", + ): + self.assertIs(result[field],False,field) + self.assertEqual(result["original_T_archive_replay"],"NOT_EXECUTED") + self.assertEqual(result["original_A2_compiler_replay"],"NOT_EXECUTED") + self.assertEqual(json.loads(report_path.read_bytes()),result) + # Persist the exact detached C/S source receipt in the genuine + # GitHub research job log before the scratch directory is removed. + # This does not fabricate a CI/security/release approval. + print("\nPR113_R5_SOURCE_REHEARSAL_RECEIPT_BEGIN") + print(json.dumps(result,sort_keys=True)) + print("PR113_R5_SOURCE_REHEARSAL_RECEIPT_END") + self.assertEqual(before,rehearsal.scalar(ROOT,"rev-parse","HEAD")) + self.assertFalse(rehearsal.run( + ROOT,"status","--porcelain=v1","--untracked-files=all")) + + def test_nested_failure_retains_exact_method_and_exit_status(self): + """A failed nested test must stay red and identify the failing method.""" + name=("verification.integrity.tests.test_workflow.WorkflowTests." + "test_shell_background_and_redirection_rejected") + with tempfile.TemporaryDirectory(prefix="pr113-nested-fail-test-") as temp: + checkout=Path(temp)/"source" + checkout.mkdir() + evidence=Path(temp)/"workflow-tests.json" + def failed_child(command,**kwargs): + self.assertEqual(command[-1],str(evidence)) + evidence.write_text(json.dumps({ + "format":"Permansson.Hardening.Tests.v1", + "suite":"workflow", + "status":"FAIL", + "error_code":"E_REPORT", + "message":"Suite failed, skipped, or incomplete", + "methods_run":8, + "failures":1, + "errors":0, + "skips":0, + "results":[{"method":name,"status":"FAIL"}], + }),encoding="utf-8") + return subprocess.CompletedProcess( + command,1,"workflow: FAIL\n", + "FAIL: "+name+"\nAssertionError: expected E_WORKFLOW_CONTRACT\n") + # Keep synthetic failure markers out of successful real CI logs. + # Assert the actual failure renderer emits them when required. + captured=io.StringIO() + with redirect_stderr(captured): + with mock.patch.object(rehearsal,"scalar",return_value="a"*40), \ + mock.patch.object(rehearsal.subprocess,"run", + side_effect=failed_child): + with self.assertRaises(rehearsal.RehearsalError) as raised: + rehearsal.run_installed_workflow_suite(checkout,evidence) + self.assertIn("PR113_NESTED_WORKFLOW_FAILURE_BEGIN", + captured.getvalue()) + self.assertIn(name,captured.getvalue()) + message=str(raised.exception) + self.assertIn(name,message) + self.assertIn("E_REPORT",message) + self.assertIn('"nested_returncode": 1',message) + self.assertIn('"status": "FAIL_CLOSED"',message) + self.assertEqual(json.loads(evidence.read_text())["status"],"FAIL") + + def test_active30_regression_failure_retains_exact_method_and_error(self): + """Synthetic red subprocess exercises real receipt and fail-closed code.""" + first=rehearsal.ACTIVE_30_SUITES[0] + qualified=("test_pr113_tooling_migration_v2." + "ToolingMigrationTests.test_reject_changed_archive_blob") + stderr=( + "test_reject_changed_archive_blob ("+qualified+") ... FAIL\n" + + "harmless preceding diagnostics\n"*500 + + "======================================================================\n" + + "FAIL: test_reject_changed_archive_blob ("+qualified+")\n" + + "----------------------------------------------------------------------\n" + + "Traceback (most recent call last):\n" + + "AssertionError: wrong pinned source remains unverified\n" + + "----------------------------------------------------------------------\n" + + "Ran 3 tests in 0.012s\n\nFAILED (failures=1)\n" + ) + failed=subprocess.CompletedProcess(["python"],1,"",stderr) + with tempfile.TemporaryDirectory(prefix="pr113-active30-negative-") as tmp: + checkout=Path(tmp)/"repo" + source=checkout/"verification/research"/first + source.parent.mkdir(parents=True) + source.write_text("# isolated test fixture\n",encoding="utf-8") + outside=Path(tmp)/"detached-logs" + capture=io.StringIO() + with mock.patch.object(rehearsal.subprocess,"run",return_value=failed), \ + mock.patch.object(rehearsal,"scalar",return_value="a"*40), \ + redirect_stderr(capture): + with self.assertRaises(rehearsal.RehearsalError) as caught: + rehearsal.run_active_30_regressions(checkout,outside) + report=json.loads((outside/"FAILURE_ONLY.json").read_bytes()) + self.assertEqual(report["status"],"FAIL_CLOSED") + self.assertEqual(report["failed_suite"],first) + self.assertEqual(report["failed_returncode"],1) + self.assertEqual(report["tests_run"],3) + self.assertEqual(report["failed_methods"][0]["method"],qualified) + self.assertIn("wrong pinned source",report["error_summaries"][0]) + self.assertEqual(report["diagnostic_tail"][-40:], + stderr.rstrip()[-40:]) + self.assertIn("PR113_ACTIVE30_FAILURE_RECEIPT_BEGIN", + capture.getvalue()) + self.assertIn(qualified,str(caught.exception)) + self.assertFalse(report["independent_security_review"]) + self.assertFalse(report["github_required_ci_authenticated"]) + + def test_active30_zero_skipped_forged_ok_and_error_stay_red(self): + """Only genuine nonempty unittest OK is a passing subprocess.""" + make=subprocess.CompletedProcess + examples=( + make(["python"],0,"","Ran 0 tests in 0.001s\n\nOK\n"), + make(["python"],0,"","Ran 2 tests in 0.001s\n\nOK (skipped=1)\n"), + make(["python"],0,"","OK\n"), + make(["python"],1,"","ERROR: testcase (module.Case.testcase)\n" + "RuntimeError: fixture failed\n"), + make(["python"],0,"","Ran 2 tests in 0.001s\n\nFAILED (errors=1)\n"), + ) + for process in examples: + with self.subTest(output=process.stderr): + self.assertFalse(rehearsal._active30_suite_passed( + process,process.stdout+"\n"+process.stderr)) + positive=make(["python"],0,"","Ran 2 tests in 0.001s\n\nOK\n") + self.assertTrue(rehearsal._active30_suite_passed( + positive,positive.stdout+"\n"+positive.stderr)) + with tempfile.TemporaryDirectory(prefix="pr113-active30-redact-") as tmp: + checkout=Path(tmp) + with mock.patch.object(rehearsal,"scalar",return_value="b"*40): + report=rehearsal._active30_failure_receipt( + checkout,"synthetic.py",examples[3], + "ERROR: testcase (module.Case.testcase)\n" + "ValueError: incorrect assumption\n" + "GITHUB_TOKEN: example-synthetic-secret\n", + [],timed_out=False) + self.assertEqual(report["failed_methods"][0]["method"], + "module.Case.testcase") + self.assertIn("[redacted credential-bearing diagnostic line]", + report["diagnostic_head"]) + self.assertNotIn("example-synthetic-secret", + json.dumps(report)) + + def test_active30_timeout_is_red_and_source_receipt_not_approval(self): + first=rehearsal.ACTIVE_30_SUITES[0] + with tempfile.TemporaryDirectory(prefix="pr113-active30-timeout-") as tmp: + checkout=Path(tmp)/"repo" + test_source=checkout/"verification/research"/first + test_source.parent.mkdir(parents=True) + test_source.write_text("# synthetic runner fixture\n",encoding="utf-8") + outside=Path(tmp)/"detached" + timeout=subprocess.TimeoutExpired(["python"],150, + output=b"test_started\n") + capture=io.StringIO() + with mock.patch.object(rehearsal.subprocess,"run",side_effect=timeout), \ + mock.patch.object(rehearsal,"scalar",return_value="c"*40), \ + redirect_stderr(capture): + with self.assertRaises(rehearsal.RehearsalError): + rehearsal.run_active_30_regressions(checkout,outside) + report=json.loads((outside/"FAILURE_ONLY.json").read_bytes()) + self.assertTrue(report["timed_out"]) + self.assertEqual(report["failed_returncode"],124) + self.assertEqual(report["status"],"FAIL_CLOSED") + self.assertIn("ACTIVE30_SUBPROCESS_TIMEOUT",capture.getvalue()) + + def test_active_30_rehearsal_requires_both_legacy_import_consumers(self): + actual=rehearsal.ACTIVE_30_SUITES + self.assertEqual(len(actual),8) + self.assertEqual(len(set(actual)),8) + self.assertIn("test_pr113_successor_workflow_profile.py",actual) + self.assertIn("test_pr113_atomic_migration_preview.py",actual) + self.assertIn("test_pr113_tooling_migration_v2.py",actual) + self.assertIn("test_pr113_a2_successor_identity_v2.py",actual) + + def test_foreign_or_nonexistent_head_never_creates_temporary_checkout(self): + with tempfile.TemporaryDirectory(prefix="pr113-report-outside-") as temp: + report=Path(temp)/"never_created.json" + for requested in ("wrong", "0"*40): + with self.subTest(head=requested),self.assertRaises( + (ValueError,RuntimeError)): + rehearsal.execute(requested,report) + self.assertFalse(report.exists()) diff --git a/verification/research/test_pr113_draft_rehearsal_auth.py b/verification/research/test_pr113_draft_rehearsal_auth.py new file mode 100644 index 00000000..87f0e9f0 --- /dev/null +++ b/verification/research/test_pr113_draft_rehearsal_auth.py @@ -0,0 +1,164 @@ +"""Real-byte equality checks for the unapproved, two-root draft rehearsal.""" +import io +import json +import hashlib +import unittest +import zipfile + +import pr113_draft_rehearsal_auth as r + +H="a"*40 + +def digest(raw):return hashlib.sha256(raw).hexdigest() + +def zipped(raw): + out=io.BytesIO() + with zipfile.ZipFile(out,"w",zipfile.ZIP_DEFLATED) as z: + for key,val in sorted(raw.items()): + z.writestr(key,val) + return out.getvalue() + +def fixture(): + pdf=b"%PDF-1.4\nexample draft bytes" + tex=b"\\documentclass{article}\n" + render=b"PNG-RENDER" + sources={r.PREFIX+p:digest(p.encode()) for p in r.SOURCE_PATHS} + sourcebytes={p:p.encode() for p in r.SOURCE_PATHS} + manifest={ + "format":"Permansson.MathClosure.BuildReceipt.v1", + "status":"DRAFT_NOT_ACCEPTED", + "source_commit_sha":H, + "independent_review":"NOT_RECEIVED", + "visual_inspection":"NOT_PERFORMED", + "source_ci_validation":"NOT_BOUND_TO_MERGED_MAIN", + "source_tree_files":sources, + "pdf_page_count":1, + "pdf_sha256":digest(pdf), + "tex_sha256":digest(tex), + "latex_log_sha256":digest(b"compiler-log"), + } + manifest_raw=(json.dumps(manifest,sort_keys=True)+"\n").encode() + review={"status":"MISSING_POST_MERGE_SOURCE_AND_REVIEWS", + "ci_receipts":[],"independent_reviews":[]} + review_raw=(json.dumps(review,sort_keys=True)+"\n").encode() + nested=zipped({ + "manuscript/"+p:sourcebytes[p] for p in r.SOURCE_PATHS + }|{ + "manifest.lock.json":manifest_raw, + "Permansson_Mathematical_Closure.pdf":pdf, + "source_ci_receipts.lock.json":review_raw, + }) + body={ + "Permansson_Mathematical_Closure.pdf":pdf, + "Permansson_Mathematical_Closure.tex":tex, + "Permansson_Mathematical_Closure_E2E.zip":nested, + "manifest.lock.json":manifest_raw, + "source_ci_receipts.lock.json":review_raw, + "latex_build.log":b"compiler-log", + } + sums=( + "".join(digest(body[k])+" "+k+"\n" for k in ( + "Permansson_Mathematical_Closure.pdf", + "Permansson_Mathematical_Closure.tex", + "manifest.lock.json","source_ci_receipts.lock.json", + "Permansson_Mathematical_Closure_E2E.zip")) + ).encode() + body["SHA256SUMS.txt"]=sums + outer={} + for side in ("first","second"): + for key,value in body.items(): + outer[side+"/"+key]=value + outer[side+"/pages/page-1.png"]=render + report={ + "format":"Permansson.TwoCleanSourceRebuilds.v1", + "status":"REPRODUCIBLE_DRAFT_ONLY", + "source_commit_sha":H, + "source_tree_sha":"b"*40, + "independent_source_roots":2, + "two_complete_archive_audits":True, + "rendered_pages":True, + "authenticated_ci_artifact":False, + "scientific_release_approved":False, + "pdf_sha256":digest(pdf), + "archive_sha256":digest(nested), + "manifest_sha256":digest(manifest_raw), + "byte_comparison":{ + "status":"REPRODUCIBLE_DRAFT_ONLY", + "source_commit_sha":H, + "pdf_sha256":digest(pdf), + "zip_sha256":digest(nested), + "page_count":1, + }, + } + ci={ + "format":"Permansson.CICleanRebuildCandidate.v1", + "status":"EXECUTED_DRAFT_ONLY_NO_SCHOLARLY_REVIEW", + "source_head":H, + "github_repository":"HMarcusWH/PermanssonLean", + "github_job":"clean-paper-rebuild", + "github_run_id":100, + "github_run_attempt":1, + "pdf_sha256":digest(pdf), + "archive_sha256":digest(nested), + "authenticated_ci_artifact":False, + "scientific_release_approved":False, + } + outer["reproduction_receipt.json"]=json.dumps(report).encode() + outer["ci_execution_identity.json"]=json.dumps(ci).encode() + return outer + +class DraftByteRehearsalTests(unittest.TestCase): + def test_full_synthetic_two_root_rehearsal_remains_unapproved(self): + checked=r.audit(zipped(fixture()),H) + self.assertEqual(checked["source_member_count"],13) + self.assertEqual(checked["rendered_pages_verified"],1) + self.assertFalse(checked["actual_GitHub_artifact_authentication"]) + self.assertFalse(checked["mathematical_acceptance"]) + self.assertFalse(checked["publication_authorized"]) + + def test_wrong_head_or_changed_actual_pdf_fails(self): + with self.assertRaises(r.DraftRehearsalError): + r.audit(zipped(fixture()),"c"*40) + data=fixture() + data["second/Permansson_Mathematical_Closure.pdf"]=b"%PDF-1.4\nchanged" + with self.assertRaises(r.DraftRehearsalError): + r.audit(zipped(data),H) + + def test_missing_page_and_false_approval_are_rejected(self): + data=fixture() + del data["first/pages/page-1.png"] + with self.assertRaises(r.DraftRehearsalError): + r.audit(zipped(data),H) + data=fixture() + report=json.loads(data["reproduction_receipt.json"]) + report["scientific_release_approved"]=True + data["reproduction_receipt.json"]=json.dumps(report).encode() + with self.assertRaises(r.DraftRehearsalError): + r.audit(zipped(data),H) + + def test_nested_archive_rejects_traversal_and_duplicate_source(self): + for filename in ("../escape", "manuscript/../escape"): + with self.subTest(path=filename): + with self.assertRaises(r.DraftRehearsalError): + r.bounded_nested_archive(zipped({filename:b"hello"})) + for name in ("first.json", "FIRST.JSON"): + pass + output=io.BytesIO() + with zipfile.ZipFile(output,"w",zipfile.ZIP_DEFLATED) as z: + z.writestr("manuscript/main.tex",b"true") + z.writestr("MANUSCRIPT/MAIN.TEX",b"false") + with self.assertRaises(r.DraftRehearsalError): + r.bounded_nested_archive(output.getvalue()) + + def test_changed_nested_manuscript_source_is_rejected(self): + data=fixture() + with zipfile.ZipFile(io.BytesIO( + data["first/Permansson_Mathematical_Closure_E2E.zip"])) as z: + rows={name:z.read(name) for name in z.namelist()} + rows["manuscript/main.tex"]=b"unreviewed new paper source" + data["first/Permansson_Mathematical_Closure_E2E.zip"]=zipped(rows) + with self.assertRaises(r.DraftRehearsalError): + r.audit(zipped(data),H) + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_dual_a2_compiler.py b/verification/research/test_pr113_dual_a2_compiler.py new file mode 100644 index 00000000..92c90949 --- /dev/null +++ b/verification/research/test_pr113_dual_a2_compiler.py @@ -0,0 +1,198 @@ +"""The dual A2 harness must execute both checks, never autoapprove either.""" +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +import pr113_dual_a2_compiler as dual + + +def fake_result(status, gate): + command_names = ["compiler_version","research_build","signatures_axioms", + "positive_fixture","source_1","source_2","source_3","source_4", + *gate.MUTATIONS] + return { + "status":status, + "mode":"focused", + "source_unchanged":True, + "declarations":{name:[] for name in gate.DECLS}, + "mutations":{name:{"status":"SEMANTICALLY_REJECTED", + "positive_source_sha256":"b"*64, + "mutant_source_sha256":"c"*64} + for name in gate.MUTATIONS}, + "commands":[{"label":name, + "returncode":1 if name in gate.MUTATIONS else 0, + "log_sha256":"a"*64} for name in command_names], + "independent_security_review":False, + "historical_a2_verifier_replaced":False, + "mathematical_acceptance":False, + "historical_runtime_provenance":{ + "historical_checkout_sha":"a"*40, + "introduced_executable_sha":dual.HISTORICAL_INTRODUCER, + "imported_current_A2_gate":False, + "independent_security_review":False, + }, + } + + +class DualA2Tests(unittest.TestCase): + def test_both_real_runs_retained_unapproved(self): + with tempfile.TemporaryDirectory() as tmp: + root=Path(tmp) + old,now=root/"old",root/"current" + old.mkdir();now.mkdir() + dest=root/"output" + original=fake_result("LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING", + dual.historical_gate) + new=fake_result("SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING", + dual.successor_gate) + with mock.patch.object(dual,"real_source",side_effect=[old,now]), \ + mock.patch.object(dual,"run_historical_verified", + return_value=original) as h, \ + mock.patch.object(dual.successor_gate,"verify", + return_value=new) as s: + result=dual.execute(old,"a"*40,now,"b"*40,dest) + h.assert_called_once() + s.assert_called_once() + self.assertEqual(result["historical_semantic_mutants"],4) + self.assertEqual(result["successor_semantic_mutants"],4) + self.assertFalse(result["independent_security_approval"]) + self.assertFalse(result["required_research_CI_accepted"]) + self.assertFalse(result["full_release_approved"]) + self.assertTrue((dest/"unreviewed_dual_A2_candidate.json").exists()) + + def test_any_incomplete_original_leg_blocks_successor(self): + with tempfile.TemporaryDirectory() as tmp: + root=Path(tmp); old=root/"old";now=root/"current" + old.mkdir();now.mkdir() + h=fake_result("LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING", + dual.historical_gate) + h["mutations"].pop(next(iter(h["mutations"]))) + with mock.patch.object(dual,"real_source",side_effect=[old,now]), \ + mock.patch.object(dual,"run_historical_verified",return_value=h), \ + mock.patch.object(dual.successor_gate,"verify") as s: + with self.assertRaises(dual.DualA2Error): + dual.execute(old,"a"*40,now,"b"*40,root/"out") + s.assert_not_called() + + def test_historical_exec_invokes_its_own_checkout_not_current_module(self): + with tempfile.TemporaryDirectory() as td: + old=Path(td)/"historical" + (old/"verification/research").mkdir(parents=True) + gate=old/dual.HISTORICAL_GATE + gate.write_text("original") + path=Path(td)/"candidate" + invocation=dual.historical_command(old,path,"focused") + self.assertEqual(invocation[:2],[dual.sys.executable,"-B"]) + self.assertEqual(Path(invocation[2]),gate) + self.assertEqual(invocation[3:],[ + "--repo",str(old),"--output",str(path),"--mode","focused"]) + self.assertNotEqual(Path(dual.historical_gate.__file__).resolve(), + gate.resolve()) + gate.unlink() + with self.assertRaises(dual.DualA2Error): + dual.historical_command(old,path,"focused") + + def test_missing_historical_runtime_provenance_must_fail_closed(self): + with tempfile.TemporaryDirectory() as td: + old,now=Path(td)/"old",Path(td)/"now" + old.mkdir();now.mkdir() + receipt=fake_result("LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING", + dual.historical_gate) + receipt.pop("historical_runtime_provenance") + with mock.patch.object(dual,"real_source",side_effect=[old,now]), \ + mock.patch.object(dual,"run_historical_verified", + return_value=receipt), \ + mock.patch.object(dual.successor_gate,"verify", + return_value=fake_result( + "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING", + dual.successor_gate)): + with self.assertRaises(dual.DualA2Error): + dual.execute(old,"a"*40,now,"b"*40,Path(td)/"result") + + def test_staged_historical_dependency_bridge_uses_identical_pins_only(self): + """The historical T/A2 migration candidate does not borrow H proofs.""" + proposed = (Path(__file__).resolve().parents[2] / + "docs/operations/pr113_unapproved_workflows/" + "pr113-historical-migration-candidates.yml").read_text() + begin = proposed.index(" - name: Bind historical A2 to the exact pinned dependency source") + end = proposed.index(" - name: Execute both genuine compiler gates", begin) + stage = proposed[begin:end] + from pr113_a2_dependency_bridge_v2 import FORMAT + from pr113_historical_a2_baseline_v1 import REPLAY_HEAD, INTRODUCER + self.assertIn("pr113_a2_dependency_bridge_v2.py", stage) + for option in ("--historical-source", "--current-source", + "--current-head", "--out"): + self.assertIn(option, stage) + self.assertIn("--historical-head " + REPLAY_HEAD, proposed) + self.assertNotIn("--historical-head " + INTRODUCER, proposed) + self.assertEqual(FORMAT, "Permansson.PR113.HistoricalA2DependencyBridge.v2") + root = Path(__file__).resolve().parents[2] + bridge = (root / "verification/research/pr113_a2_dependency_bridge_v2.py").read_text() + replay = (root / "verification/research/pr113_historical_a2_replay_v1.py").read_text() + # Check the executable helper and the original package-only bridge, + # not obsolete inline shell text. Runtime tests separately reject + # wrong pins, source edits, injected project builds and fake receipts. + for guard in ("baseline.verify_positive_source(historical)", + "packages = dependency_bridge(now, old)", + "packages == manifest_packages(raw)", + "unchanged(old, baseline.REPLAY_HEAD)", + '"historical_project_proofs_reused": False', + '"independent_security_review": False', + '"mathematical_acceptance": False'): + self.assertIn(guard, bridge) + for guard in ('"lean-toolchain", "lake-manifest.json", "lakefile.toml"', + 'not (old / ".lake/build").exists()', + "link.symlink_to(packages.resolve()", + "check_dependencies(old) == before"): + self.assertIn(guard, replay) + actual_commands = "\n".join( + line for line in stage.splitlines() + if not line.lstrip().startswith("#")) + self.assertNotIn("lake update", actual_commands) + self.assertIn("historical_dependency_bridge.json", proposed) + self.assertIn("cp --", proposed) + self.assertIn("test -s", proposed) + self.assertTrue(proposed.startswith("# Review-pending")) + + def test_wrong_successor_semantic_result_rejects(self): + h=fake_result("SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING", + dual.successor_gate) + h["mutations"]["omit_baseline_reporting_error"]["status"]="SKIPPED" + with self.assertRaises(dual.DualA2Error): + dual.validate_run(h,"SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING", + dual.successor_gate.MUTATIONS, + dual.successor_gate.DECLS) + + + def test_real_a2_both_legs_refuse_fake_green_status(self): + """A mock receipt cannot hide failed positives or accepted mutants.""" + for gate,status in ((dual.historical_gate, + "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING"), + (dual.successor_gate, + "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING")): + original=fake_result(status,gate) + self.assertTrue(dual.validate_run(original,status,gate.MUTATIONS, + gate.DECLS)) + mutate = next(iter(gate.MUTATIONS)) + broken = [ + lambda x: x["commands"][0].update(returncode=1), + lambda x: next(q for q in x["commands"] if + q["label"]==mutate).update(returncode=0), + lambda x: x["commands"].append(dict(x["commands"][0])), + lambda x: x["commands"][0].update(log_sha256="x"*64), + lambda x: x["commands"].pop(4), + lambda x: x["mutations"][mutate].update( + mutant_source_sha256="b"*64), + lambda x: x["mutations"][mutate].update( + positive_source_sha256="not-an-authentic-digest"), + ] + for edit in broken: + with self.subTest(status=status,edit=edit): + candidate=json.loads(json.dumps(original)) + edit(candidate) + with self.assertRaises(dual.DualA2Error): + dual.validate_run(candidate,status,gate.MUTATIONS, + gate.DECLS) + diff --git a/verification/research/test_pr113_dual_review_packet.py b/verification/research/test_pr113_dual_review_packet.py new file mode 100644 index 00000000..74fe7623 --- /dev/null +++ b/verification/research/test_pr113_dual_review_packet.py @@ -0,0 +1,128 @@ +"""Synthetic adversarial review-packet QA; not a substitute for true T/A2 CI.""" +import json +import unittest +from unittest import mock + +import pr113_dual_review_packet as packet +import test_pr113_infrastructure_acceptance as fixtures + +H = "a"*40 + + +def fake_tooling(h): + return { + "source_commit_sha": h, + "original_archive_T": packet.T, + "original_release_base_B": packet.B113, + "status": "DRAFT_NEEDS_OWNER_AND_INDEPENDENT_SECURITY_REVIEW", + "historical_archive_unchanged": True, + "current_workflow_file_count": 25, + "changed_canonical_tooling": [ + {"path": "verification/integrity/workflow_contract.py", + "T_blob": "1"*40, "H_blob": "2"*40}, + {"path": "verification/integrity/tests/test_workflow.py", + "T_blob": "3"*40, "H_blob": "4"*40}, + ], + "new_successor_T_approved": False, + "independent_tooling_review": False, + } + + +def fake_workflow(h): + return { + "head_sha": h, + "historical_T": packet.T, + "original_base": packet.B113, + "status": "EXACT_25_PLUS_5_SOURCE_PROFILE_CANDIDATE_NOT_ACTIVATED", + "original_workflow_count": 25, + "successor_proposal_count": 5, + "active_profile_changed": False, + "independent_security_review": False, + "owner_activation_authorized": False, + "profile_sha256": "5"*64, + } + + +def git(*args): + if args == ("rev-parse", "HEAD"): + return H + if args == ("cat-file", "-t", H): + return "commit" + if args == ("show", H+":lake-manifest.json"): + return json.dumps(fixtures.synthetic_lake_manifest(),sort_keys=True) + raise AssertionError("untrusted Git op "+repr(args)) + + +class DualPacketTests(unittest.TestCase): + def blobs(self): + return (fixtures.zipped(fixtures.historical_files()), + fixtures.zipped(fixtures.a2_files())) + + def assemble(self, t=None, a=None, *, + head=H, tooling=fake_tooling, workflow=fake_workflow, + current_git=git): + tb,ab = self.blobs() + return packet.assemble(head,t if t is not None else tb, + a if a is not None else ab,git=current_git, + audit_tooling=tooling, + audit_workflows=workflow) + + def test_genuine_payload_shape_not_approval(self): + report=self.assemble() + self.assertEqual(report["status"], + "BOTH_HISTORICAL_SOURCE_LEGS_VERIFIED_FOR_REVIEW_ONLY") + self.assertEqual(report["current_head"],H) + self.assertIn("T_execution",report) + self.assertIn("A2_execution",report) + self.assertEqual(len(report["canonical_tooling_changes"]),2) + for k in ("source_bound_GitHub_job_authenticated", + "independent_security_review","owner_activation_authorized", + "mathematical_acceptance","merge_authorized", + "publication_authorized"): + self.assertIs(report[k], False) + + def test_refuse_forged_identity_and_missing_historical_reports(self): + with self.assertRaises(packet.ReviewPacketError): + self.assemble(head="b"*40) + t,_=self.blobs() + with self.assertRaises(packet.InfrastructureError): + self.assemble(t=fixtures.zipped({"invented.json":b"{}"})) + with self.assertRaises(packet.InfrastructureError): + self.assemble(a=fixtures.zipped({"invented.json":b"{}"})) + + def test_staged_profile_and_unreviewed_tooling_are_mandatory(self): + def activated(h): + result=fake_workflow(h) + result["active_profile_changed"]=True + return result + with self.assertRaises(packet.ReviewPacketError): + self.assemble(workflow=activated) + def forged(h): + result=fake_tooling(h) + result["independent_tooling_review"]=True + return result + with self.assertRaises(packet.ReviewPacketError): + self.assemble(tooling=forged) + + def test_dependency_bridge_foreign_manifest_is_rejected(self): + def wrong_git(*args): + if args==("show",H+":lake-manifest.json"): + return "{}" + return git(*args) + with self.assertRaises(packet.InfrastructureError): + self.assemble(current_git=wrong_git) + + def test_extra_or_rewritten_original_artifacts_are_not_accepted(self): + old,a2=self.blobs() + damage=fixtures.historical_files() + damage["original_T_archive.json"]=b'{"status":"PASS"}' + with self.assertRaises(packet.InfrastructureError): + self.assemble(t=fixtures.zipped(damage)) + missing=fixtures.a2_files() + missing.pop("historical_dependency_bridge.json") + with self.assertRaises(packet.InfrastructureError): + self.assemble(a=fixtures.zipped(missing)) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_dual_tooling_candidate.py b/verification/research/test_pr113_dual_tooling_candidate.py new file mode 100644 index 00000000..020d5a29 --- /dev/null +++ b/verification/research/test_pr113_dual_tooling_candidate.py @@ -0,0 +1,190 @@ +"""Dual T/H report regression tests; mocks NEVER simulate real archive approval.""" +import copy +import unittest +from unittest import mock +from types import SimpleNamespace +import pr113_dual_tooling_candidate as dual + +T=dual.T +H="a"*40 + +def fixture(): + historical={ + "format":"Permansson.Hardening.Check.v1", + "context":{"source":"immutable-T"}, + "check":"archive-reproducibility", + "status":"PASS", + "details":{ + "acceptance_scope":"accepted_artifact_replay_only", + "isolation":{"network_isolation":"ENFORCED", + "capabilities":"all_zero"}, + }, + } + current={ + "format":"Permansson.Hardening.Tests.v1","schema_version":1, + "status":"PASS","suite":"workflow", + "context":{"repository":"HMarcusWH/PermanssonLean", + "checked_sha":H,"checked_tree":"9"*40,"event":"local"}, + "execution_tracked_clean":True, + "methods_run":9, "failures":0, "errors":0, "skips":0, + "covered_requirements":["CI-06","CI-07"], + } + return historical,current + +class DualToolingTests(unittest.TestCase): + def test_historical_and_new_provenance_stays_unapproved(self): + old,now=fixture() + result=dual.validate_dual_reports(old,now,T_source=T,H_source=H) + self.assertEqual(result["status"],"DUAL_T_AND_H_EXECUTED_UNREVIEWED") + self.assertTrue(result["original_T_archive_offline_restoration_passed"]) + self.assertFalse(result["independent_security_review"]) + self.assertFalse(result["required_current_archive_CI_green"]) + self.assertFalse(result["mathematical_release_approved"]) + + def test_missing_isolation_or_bypassed_historical_T_fails(self): + for key,value in [("status","FAIL"),("details",{}), + ("check","wrong context")]: + old,now=fixture() + old[key]=value + with self.subTest(key=key), self.assertRaises(dual.DualToolingError): + dual.validate_dual_reports(old,now,T_source=T,H_source=H) + for fault in ("network_isolation","capabilities"): + old,now=fixture() + old["details"]["isolation"][fault]="NOT_ENFORCED" + with self.subTest(fault=fault), self.assertRaises(dual.DualToolingError): + dual.validate_dual_reports(old,now,T_source=T,H_source=H) + + def test_tampered_run_or_missing_coverage_is_rejected(self): + for field, changed in ( + ("format","Permansson.Hardening.Check.v0"), + ("context",None), + ): + old,now=fixture() + old[field]=changed + with self.subTest(field=field), self.assertRaises(dual.DualToolingError): + dual.validate_dual_reports(old,now,T_source=T,H_source=H) + for field,changed in ( + ("covered_requirements",[]),("errors",1),("skips",1), + ("failures",1),("schema_version",2), + ): + old,now=fixture() + now[field]=changed + with self.subTest(field=field), self.assertRaises(dual.DualToolingError): + dual.validate_dual_reports(old,now,T_source=T,H_source=H) + + def test_mismatched_H_or_incomplete_workflow_coverage_blocks(self): + for edit in ( + lambda x: x["context"].update(checked_sha="b"*40), + lambda x: x["context"].update(event="push"), + lambda x: x["context"].update(checked_tree="malformed"), + lambda x: x.update(covered_requirements=["CI-06"]), + lambda x: x.update(covered_requirements=["CI-06","CI-07","OPS-01"]), + lambda x: x.update(methods_run=True), + ): + old,now=fixture() + edit(now) + with self.subTest(edit=edit), self.assertRaises( + dual.DualToolingError): + dual.validate_dual_reports(old,now,T_source=T,H_source=H) + + def test_real_branch_push_candidate_job_may_run_without_authorizing_archive(self): + context = { + "GITHUB_ACTIONS": "true", + "GITHUB_REPOSITORY": "HMarcusWH/PermanssonLean", + "GITHUB_SHA": H, + "GITHUB_REF": "refs/heads/research/final-mathematical-closure", + "GITHUB_EVENT_NAME": "push", + "GITHUB_JOB": "original-T-and-current-H", + "GITHUB_WORKFLOW": "PR113 Historical T and A2 candidates", + } + with mock.patch.dict(dual.os.environ, context, clear=True): + self.assertEqual( + dual.candidate_ci_context(H), + "REVIEWED_SOURCE_PUSH_CANDIDATE_UNAPPROVED") + for key, value in ( + ("GITHUB_SHA", "b" * 40), + ("GITHUB_REF", "refs/heads/main"), + ("GITHUB_EVENT_NAME", "workflow_dispatch"), + ("GITHUB_JOB", "archive-reproducibility"), + ("GITHUB_WORKFLOW", "Research integrity"), + ("GITHUB_REPOSITORY", "someone/else"), + ): + with self.subTest(key=key): + with mock.patch.dict(dual.os.environ, {key: value}): + with self.assertRaises(dual.DualToolingError): + dual.candidate_ci_context(H) + + def test_local_invocation_remains_unapproved(self): + with mock.patch.dict(dual.os.environ, + {"GITHUB_ACTIONS": ""}, clear=True): + self.assertEqual(dual.candidate_ci_context(H), + "LOCAL_UNREVIEWED") + + def test_historical_original_process_has_no_ci_or_secret_inheritance(self): + context = { + "GITHUB_ACTIONS": "true", + "GITHUB_SHA": H, + "GITHUB_TOKEN": "not-for-historical-T", + "GH_TOKEN": "not-for-historical-T", + "GIT_CONFIG_GLOBAL": "test", + "CI": "true", + "CLOUD_SECRET": "test", + "HTTPS_PROXY": "http://proxy.invalid", + "PYTHONPATH": "forbidden_current_H_import", + "PATH": "/usr/bin", + } + fake = SimpleNamespace(returncode=0, stdout="PASS", stderr="") + with mock.patch.dict(dual.os.environ, context, clear=True): + with mock.patch.object(dual.subprocess, "run", return_value=fake) as run: + dual.command(["python", "-B", "-m", + "verification.archive.offline_smoke"], "/tmp/T", + historical_artifact=True) + environment = run.call_args.kwargs["env"] + self.assertNotIn("GITHUB_ACTIONS", environment) + self.assertNotIn("GITHUB_SHA", environment) + self.assertNotIn("GITHUB_TOKEN", environment) + self.assertNotIn("GH_TOKEN", environment) + self.assertNotIn("GIT_CONFIG_GLOBAL", environment) + self.assertNotIn("CI", environment) + self.assertNotIn("CLOUD_SECRET", environment) + self.assertNotIn("HTTPS_PROXY", environment) + self.assertNotIn("PYTHONPATH", environment) + self.assertEqual(environment["PYTHONDONTWRITEBYTECODE"], "1") + self.assertEqual(environment["PATH"], "/usr/bin") + + def test_current_H_verifier_runs_as_sanitized_local_source(self): + context = {"GITHUB_ACTIONS": "true", "GITHUB_SHA": H, + "GITHUB_EVENT_NAME": "push", + "GITHUB_TOKEN": "never-for-current-suite", + "PYTHONPATH": "untrusted-import"} + fake = SimpleNamespace(returncode=0, stdout="PASS", stderr="") + with mock.patch.dict(dual.os.environ, context, clear=True): + with mock.patch.object(dual.subprocess, "run", return_value=fake) as run: + dual.command(["python", "-B", "-m", + "verification.integrity.run_tests"], "/tmp/H", + detached_current_suite=True) + environment = run.call_args.kwargs["env"] + self.assertEqual(dual.os.environ["GITHUB_ACTIONS"], "true") + self.assertEqual(dual.os.environ["GITHUB_SHA"], H) + self.assertNotIn("GITHUB_ACTIONS", environment) + self.assertNotIn("GITHUB_SHA", environment) + self.assertNotIn("GITHUB_TOKEN", environment) + self.assertNotIn("PYTHONPATH", environment) + + def test_failed_historical_T_subprocess_stays_red_not_approved(self): + fake = SimpleNamespace(returncode=3, stdout="historical: BLOCKED", + stderr="E_OFFLINE_UNAVAILABLE") + with mock.patch.object(dual.subprocess, "run", return_value=fake): + with self.assertRaisesRegex(dual.DualToolingError, "exited 3"): + dual.command(["python", "-m", "verification.archive.offline_smoke"], + "/tmp/T", historical_artifact=True) + + def test_unexecuted_current_workflow_or_dirty_source_rejected(self): + for key,value in [("status","FAIL"), + ("suite","reference"), + ("execution_tracked_clean",False), + ("methods_run",0)]: + old,now=fixture() + now[key]=value + with self.subTest(key=key), self.assertRaises(dual.DualToolingError): + dual.validate_dual_reports(old,now,T_source=T,H_source=H) diff --git a/verification/research/test_pr113_final_source_readiness.py b/verification/research/test_pr113_final_source_readiness.py new file mode 100644 index 00000000..3d992f75 --- /dev/null +++ b/verification/research/test_pr113_final_source_readiness.py @@ -0,0 +1,75 @@ +"""Synthetic-only source coverage adversaries, not effective math acceptance.""" +import copy +import unittest + +import pr113_final_source_readiness as ready + +CASE_IDS={f"C-{i:02d}" for i in range(1,46)} +CLAIM_IDS=sorted(ready.CLAIMS) + +def fixture(): + proofs=[ + {"id":f"all:{i:04d}", + "case_ids":[sorted(CASE_IDS)[i%45]], + "claim_ids":[CLAIM_IDS[i%11]]} + for i in range(259)] + plus=[{"case_ids":[cid],"claim_ids":[]} + for cid in sorted(CASE_IDS)[:25]] + minus=[{"case_ids":[cid],"claim_ids":[]} + for cid in sorted(CASE_IDS)[:25]] + return { + "format": "Permansson.FinalClosureDetachedReceipts.v3", + "compiler_axiom_receipts":proofs, + "producer_artifacts":[{"artifact_id":i} for i in range(1,22)], + "positive_test_receipts":plus, + "semantic_negative_receipts":minus, + } + +class ReadinessCoverageTests(unittest.TestCase): + def test_missing_20_cases_and_all_claim_controls_block_acceptance(self): + rows=fixture() + result=ready.candidate_coverage(rows,CASE_IDS) + self.assertEqual(result["source_proofs"],259) + self.assertEqual(result["producer_shards"],21) + self.assertEqual(result["cases_with_positive_negative_controls"],25) + self.assertEqual(len(result["missing_original_case_controls"]),20) + self.assertEqual(len(result["missing_new_claim_controls"]),11) + self.assertFalse(result["candidate_controls_complete"]) + self.assertFalse(result["mathematical_release_approved"]) + + def test_pretend_full_case_claim_controls_are_still_unreviewed(self): + rows=fixture() + for cid in sorted(CASE_IDS)[25:]: + rows["positive_test_receipts"].append( + {"case_ids":[cid],"claim_ids":[]}) + rows["semantic_negative_receipts"].append( + {"case_ids":[cid],"claim_ids":[]}) + for claim in CLAIM_IDS: + rows["positive_test_receipts"].append( + {"case_ids":[],"claim_ids":[claim]}) + rows["semantic_negative_receipts"].append( + {"case_ids":[],"claim_ids":[claim]}) + result=ready.candidate_coverage(rows,CASE_IDS) + # A complete set of labels must NOT assert genuine test execution. + self.assertTrue(result["label_coverage_only"]) + self.assertFalse(result["candidate_controls_complete"]) + self.assertEqual(result["producer_source_and_role_validation"], + "NOT_VALIDATED") + self.assertEqual(result["independent_case_reviews"],0) + self.assertEqual(result["independent_claim_reviews"],0) + self.assertFalse(result["merge_authorized"]) + + def test_source_declaration_or_producer_count_must_be_complete(self): + rows=fixture() + rows["compiler_axiom_receipts"].pop() + with self.assertRaises(ready.ReadinessError): + ready.candidate_coverage(rows,CASE_IDS) + rows=fixture() + rows["producer_artifacts"].pop() + with self.assertRaises(ready.ReadinessError): + ready.candidate_coverage(rows,CASE_IDS) + rows=fixture() + rows["positive_test_receipts"].append( + {"case_ids":["INVENTED-CASE"],"claim_ids":[]}) + with self.assertRaises(ready.ReadinessError): + ready.candidate_coverage(rows,CASE_IDS) diff --git a/verification/research/test_pr113_full_inventory_gate.py b/verification/research/test_pr113_full_inventory_gate.py new file mode 100644 index 00000000..3072ac10 --- /dev/null +++ b/verification/research/test_pr113_full_inventory_gate.py @@ -0,0 +1,76 @@ +"""Exact selected-scope inventory obligations; synthetic rows are NEVER proofs.""" +import copy +import unittest +from final_closure_complete_lean_evidence import ( + FULL_TARGET_COUNT, FROZEN_CLAIM_IDS, FROZEN_ORIGINAL_CASE_IDS, + CompleteCompilerError, validate_full_release_inventory, +) + + +def synthetic_inventory(): + claims = sorted(FROZEN_CLAIM_IDS) + original_cases = sorted(FROZEN_ORIGINAL_CASE_IDS) + return [ + { + "path": f"PermanssonResearch/Fake/F{n:03}.lean", + "declaration": f"PermanssonResearch.Fake.t{n:03}", + "case_ids": [original_cases[n % len(original_cases)]], + "claim_ids": [claims[n % len(claims)]], + } + for n in range(FULL_TARGET_COUNT) + ] + + +class FullReleaseInventoryScopeTests(unittest.TestCase): + def test_exact_259_originals_are_only_candidate_inventory(self): + rows = synthetic_inventory() + self.assertEqual(len(validate_full_release_inventory(rows)), 259) + + def test_258_successful_theorems_are_incomplete(self): + rows = synthetic_inventory() + rows.pop() + with self.assertRaisesRegex(CompleteCompilerError, "259"): + validate_full_release_inventory(rows) + + def test_duplicate_theorem_is_not_two_proof_receipts(self): + rows = synthetic_inventory() + rows[22] = copy.deepcopy(rows[21]) + with self.assertRaisesRegex(CompleteCompilerError, "duplicate"): + validate_full_release_inventory(rows) + + def test_duplicate_fqn_in_other_file_is_rejected(self): + rows = synthetic_inventory() + rows[22]["declaration"] = rows[21]["declaration"] + with self.assertRaisesRegex(CompleteCompilerError, "duplicate"): + validate_full_release_inventory(rows) + + def test_claim_or_case_omission_is_not_full_completion(self): + rows = synthetic_inventory() + for row in rows: + row["claim_ids"] = ["Q1"] + with self.assertRaisesRegex(CompleteCompilerError, "45/11"): + validate_full_release_inventory(rows) + rows = synthetic_inventory() + for row in rows: + row["case_ids"] = ["A-01"] + with self.assertRaisesRegex(CompleteCompilerError, "45/11"): + validate_full_release_inventory(rows) + + def test_foreign_claim_and_missing_source_owner_rejected(self): + rows = synthetic_inventory() + rows[0]["claim_ids"].append("UNAPPROVED") + with self.assertRaises(CompleteCompilerError): + validate_full_release_inventory(rows) + rows = synthetic_inventory() + rows[0]["path"] = "../shadow/NotProof.lean" + with self.assertRaises(CompleteCompilerError): + validate_full_release_inventory(rows) + rows = synthetic_inventory() + rows[0]["case_ids"] = [] + rows[0]["claim_ids"] = [] + with self.assertRaises(CompleteCompilerError): + validate_full_release_inventory(rows) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_full_required_workflow_preview.py b/verification/research/test_pr113_full_required_workflow_preview.py new file mode 100644 index 00000000..afcbbc38 --- /dev/null +++ b/verification/research/test_pr113_full_required_workflow_preview.py @@ -0,0 +1,110 @@ +"""Bound exact full staged required workflows; do not activate implicitly.""" +import json +from pathlib import Path +import subprocess +import unittest +from pr113_workflow_profile_v2 import PREACTIVATION_HEAD, audit as audit_v2 + +ROOT=Path(__file__).resolve().parents[2] +STAGE=ROOT/"docs/operations/pr113_required_gate_replacement_unapproved" + +def git(*args): + return subprocess.check_output(["git",*args],cwd=ROOT, + stderr=subprocess.DEVNULL,text=True,timeout=20).strip() + +class FullRequiredPreview(unittest.TestCase): + def setUp(self): + self.m=json.loads((STAGE/"full_workflow_manifest.json").read_bytes()) + self.lean=(STAGE/"research_lean_full.yml").read_text() + self.arch=(STAGE/"research_integrity_full.yml").read_text() + + def test_protected_originals_and_full_candidates_are_hash_pinned(self): + self.assertEqual(self.m["original_workflow_count"],25) + self.assertEqual(self.m["proposed_final_workflow_count"],30) + self.assertTrue(self.m["requires_main_PR_and_ordered_merge"]) + for k in ("independent_security_review","owner_activation_authorized", + "mathematical_acceptance","merge_authorized", + "publication_authorized"): + self.assertFalse(self.m[k]) + active={p.name for p in (ROOT/".github/workflows").iterdir() + if p.suffix in (".yml",".yaml")} + if len(active)==25: + from verification.integrity.workflow_contract import APPROVED_WORKFLOWS + self.assertEqual(active,set(APPROVED_WORKFLOWS)) + else: + self.assertEqual(len(active),30) + source=audit_v2(ROOT,git("rev-parse","HEAD"),phase="active") + self.assertEqual(source["current_reviewed_workflow_count"],30) + self.assertFalse(source["independent_security_review"]) + for entry in self.m["old_workflows"]: + origin=("HEAD" if len(active)==25 else PREACTIVATION_HEAD) + self.assertEqual(git("rev-parse",origin+":"+entry["path"]), + entry["git_blob_oid"]) + for entry in self.m["full_replacements"]: + self.assertFalse(entry["installed"]) + self.assertFalse(entry["independent_reviewed"]) + self.assertEqual(git("rev-parse","HEAD:"+entry["staged_source"]), + entry["git_blob_oid"]) + if len(active)==30: + self.assertEqual(git("rev-parse","HEAD:"+entry["target"]), + entry["git_blob_oid"]) + + def test_archive_original_other_jobs_unchanged_and_research_stages_preserved(self): + count=len([p for p in (ROOT/".github/workflows").iterdir() + if p.suffix in (".yml",".yaml")]) + old=( + (ROOT/".github/workflows/research-integrity.yml").read_text() + if count==25 else subprocess.check_output( + ["git","show",PREACTIVATION_HEAD+ + ":.github/workflows/research-integrity.yml"], + cwd=ROOT,text=True,timeout=20)) + self.assertEqual( + old.split(" archive-reproducibility:\n",1)[0], + self.arch.split(" archive-reproducibility:\n",1)[0]) + self.assertIn("Execute archive adversaries",self.arch) + self.assertIn("verification.integrity.run_tests --suite archive",self.arch) + for stage in ("Build research library and audit its transitive axioms", + "Run research regressions and retain test-level diagnostics", + "Generate and independently verify C10", + "Generate and independently recheck D0", + "Verify original default target still builds", + "Ensure every research module is imported", + "Reject proof placeholders"): + self.assertIn(stage,self.lean) + + def test_readonly_artifact_transport_and_source_checks_cannot_be_bypassed(self): + runtime=(ROOT/"verification/research/pr113_historical_runtime_v3.py").read_text() + diagnostics=(ROOT/"verification/research/pr113_regression_diagnostics.py").read_text() + self.assertIn("from pr113_historical_artifact_delivery_v2 import",runtime) + self.assertIn("from pr113_required_dual_gate_v2 import verify",runtime) + self.assertIn("PERMANSSON_DUAL_HISTORICAL_EVIDENCE",runtime) + self.assertIn("GITHUB_ENV",runtime) + self.assertIn("unittest.defaultTestLoader.discover(",diagnostics) + self.assertIn('pattern="test_*.py"',diagnostics) + self.assertIn("return 0 if result.wasSuccessful() else 1",diagnostics) + self.assertIn("Preserve diagnostics even when regressions fail",self.lean) + self.assertIn("--check-existing",self.arch) + self.assertIn("Verify genuine reviewed original T/T2 and A2 source",self.arch) + for source in (self.lean,self.arch): + for needle in ( + "GITHUB_TOKEN: ${{ github.token }}", + "PR113_HISTORICAL_PRODUCER_RUN_ID", + "PR113_SECURITY_REVIEW_ID", + "pr113_historical_runtime_v3.py", + "actions: read","checks: read","contents: read", + "pull-requests: read","pull_request:","push:", + '[[ "$ORIGINAL_RUN_ID" =~ ^[1-9][0-9]*$ ]]', + '[[ "$INDEPENDENT_REVIEW_ID" =~ ^[1-9][0-9]*$ ]]'): + self.assertIn(needle,source) + self.assertNotIn("actions: write",source) + self.assertNotIn("contents: write",source) + self.assertNotIn("pull-requests: write",source) + self.assertNotIn("pull_request_target",source) + self.assertNotIn("continue-on-error",source) + self.assertNotIn("|| true",source) + self.assertIn("--producer-run-id",self.lean) + self.assertIn("--security-review-id",self.arch) + self.assertIn("pr113_regression_diagnostics.py",self.lean) + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_full_v31_source_join.py b/verification/research/test_pr113_full_v31_source_join.py new file mode 100644 index 00000000..6b7340e7 --- /dev/null +++ b/verification/research/test_pr113_full_v31_source_join.py @@ -0,0 +1,125 @@ +"""Synthetic-only R08 full source join adversaries; no external approvals.""" +import copy +import unittest + +from pr113_full_v31_source_join import ( + CombinedEvidenceError, join_authenticated_evidence, +) + + +H = "a" * 40 +S = "b" * 40 +CLAIMS = ("Q1", "Q2", "Q3", "M1", "M2", "M3", "N1", "N2", "N3", "D8", "D9") + + +def complete(): + v3 = { + "status": "COMPLETE_SOURCE_EVIDENCE_UNREVIEWED_NOT_ACCEPTED", + "candidate_controls_complete": True, "source_proofs": 259, + "producer_shards": 21, "exact_head_required_ci": 12, + "actual_github_producer_shards": 21, + "cases_with_positive_negative_controls": 45, + "claims_with_positive_negative_controls": 11, + "independent_case_reviews": 0, "independent_claim_reviews": 0, + "source_head": H, "synthetic_sha": S, + } + owners = ([{"kind": "original_positive", "owner_id": f"C-{i:02}"} + for i in range(1, 46)] + + [{"kind": "legacy_negative", "owner_id": f"C-{i:02}"} + for i in range(1, 21)] + + [{"kind": "claim_pair", "owner_id": x} for x in CLAIMS]) + v31 = { + "format": "Permansson.PR113.ControlCIAuthenticationCandidate.v3.1", + "status": "ALL_ADDITIONAL_CONTROLS_SOURCE_ATTRIBUTED_UNREVIEWED", + "head_sha": H, + "original_21_producing_artifacts_still_required": 21, + "original_case_positives": 45, + "legacy_case_negatives": 20, + "claim_positive_negative_pairs": 11, + "accepted_cases": 0, "accepted_claims": 0, + "schema_security_approved": False, + "independent_mathematical_review": False, + "full_release_approved": False, + "source_owned_controls": owners, + "actual_execution_member_validation": { + "format": "Permansson.PR113.GenuineSourceExecutionBytes.v1", + "status": "ACTUAL_CONTROL_BYTES_REVALIDATED_UNREVIEWED", + "source_head": H, "source_owner_count": 76, + "canonical_routes_bound": True, + "legacy_original_captures_bound": True, + "claim_positive_negative_directions": 22, + "execution_log_members_checked": 208, + "independent_review": False, + "mathematical_acceptance": False, + "publication_approved": False, + }, + } + return v3, v31 + + +class FullSourceJoinTests(unittest.TestCase): + def test_complete_mock_graph_never_claims_release(self): + a, b = complete() + r = join_authenticated_evidence(a, b) + self.assertEqual(r["kernel_theorem_receipts"], 259) + self.assertEqual(r["additional_gh_source_owner_records"], 76) + self.assertFalse(r["independent_review"]) + self.assertFalse(r["security_approval"]) + self.assertFalse(r["publication_authorized"]) + self.assertFalse(r["merge_authorized"]) + + def test_missing_original_required_ci_or_proof_is_blocked(self): + for key, n in (("original_ci_checks", 11), ("source_proofs", 258), + ("cases_with_positive_negative_controls", 44), + ("claims_with_positive_negative_controls", 10)): + a, b = complete() + if key == "original_ci_checks": + a["exact_head_required_ci"] = 11 + else: + a[key] = n + with self.subTest(key=key), self.assertRaises(CombinedEvidenceError): + join_authenticated_evidence(a, b) + + def test_v31_75_owners_or_stale_head_blocks(self): + a, b = complete() + b["source_owned_controls"].pop() + with self.assertRaises(CombinedEvidenceError): + join_authenticated_evidence(a, b) + a, b = complete() + b["head_sha"] = "c" * 40 + with self.assertRaises(CombinedEvidenceError): + join_authenticated_evidence(a, b) + + def test_fake_source_and_missing_negative_direction_block(self): + a, b = complete() + b["actual_execution_member_validation"]["execution_log_members_checked"] = 0 + with self.assertRaises(CombinedEvidenceError): + join_authenticated_evidence(a, b) + a, b = complete() + b["actual_execution_member_validation"]["claim_positive_negative_directions"] = 11 + with self.assertRaises(CombinedEvidenceError): + join_authenticated_evidence(a, b) + + def test_unreviewed_status_cannot_be_replaced_with_approval(self): + a, b = complete() + b["schema_security_approved"] = True + with self.assertRaises(CombinedEvidenceError): + join_authenticated_evidence(a, b) + a, b = complete() + b["actual_execution_member_validation"]["mathematical_acceptance"] = True + with self.assertRaises(CombinedEvidenceError): + join_authenticated_evidence(a, b) + + def test_duplicate_or_wrong_kind_fails(self): + a, b = complete() + b["source_owned_controls"][3] = b["source_owned_controls"][2].copy() + with self.assertRaises(CombinedEvidenceError): + join_authenticated_evidence(a, b) + a, b = complete() + b["source_owned_controls"][0]["kind"] = "claim_pair" + with self.assertRaises(CombinedEvidenceError): + join_authenticated_evidence(a, b) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_historical_a2_baseline_v1.py b/verification/research/test_pr113_historical_a2_baseline_v1.py new file mode 100644 index 00000000..3480fb4e --- /dev/null +++ b/verification/research/test_pr113_historical_a2_baseline_v1.py @@ -0,0 +1,158 @@ +"""Real temporary-Git baseline tests; synthetic sources are never Lean evidence.""" +from __future__ import annotations + +import copy +from pathlib import Path +import subprocess +import tempfile +import unittest +from unittest import mock + +import pr113_historical_a2_baseline_v1 as baseline + + +def git(root, *args): + return subprocess.check_output(["git", "-C", str(root), *args], + stderr=subprocess.DEVNULL, timeout=30).decode().strip() + + +def commit(root, message): + git(root, "add", ".") + git(root, "commit", "-qm", message) + return git(root, "rev-parse", "HEAD") + + +class PositiveBaselineSourceTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory(prefix="positive-a2-baseline-") + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) / "repo" + self.root.mkdir() + git(self.root, "init", "-q") + git(self.root, "config", "user.name", "Synthetic historical test") + git(self.root, "config", "user.email", "synthetic@invalid.example") + git(self.root, "config", "commit.gpgsign", "false") + paths = (*baseline.EXECUTORS, baseline.ORIGINAL_TEST, + *baseline.PROOF_PATHS, *baseline.PIN_PATHS, "PermanssonResearch.lean") + for path in paths: + file = self.root / path + file.parent.mkdir(parents=True, exist_ok=True) + file.write_text("SYNTHETIC source, not a Lean proof: " + path + "\n") + self.intro = commit(self.root, "synthetic introduction, not a positive result") + proof = self.root / baseline.PROOF_PATHS[0] + proof.write_text("SYNTHETIC corrected source, not a compiler receipt\n") + self.positive = commit(self.root, "synthetic corrected baseline") + self.tree = git(self.root, "rev-parse", "HEAD^{tree}") + self.pins = {p: git(self.root, "rev-parse", "HEAD:" + p) + for p in baseline.EXECUTORS} + self.test_pin = git(self.root, "rev-parse", "HEAD:" + baseline.ORIGINAL_TEST) + patch = mock.patch.multiple(baseline, INTRODUCER=self.intro, + REPLAY_HEAD=self.positive, REPLAY_TREE=self.tree, + EXECUTORS=self.pins, ORIGINAL_TEST_BLOB=self.test_pin) + patch.start() + self.addCleanup(patch.stop) + + def test_corrected_source_is_distinct_and_does_not_claim_execution(self): + result = baseline.verify_positive_source(self.root) + self.assertNotEqual(result["introduction_sha"], result["positive_replay_sha"]) + self.assertEqual(result["positive_replay_tree"], self.tree) + self.assertEqual(set(result["source_blobs"]), { + *baseline.EXECUTORS, baseline.ORIGINAL_TEST, *baseline.PIN_PATHS, + *baseline.PROOF_PATHS, "PermanssonResearch.lean"}) + for flag in ("fresh_compiler_execution", "independent_security_review", "mathematical_acceptance"): + self.assertIs(result[flag], False) + + def test_introducer_cannot_be_used_as_positive_replay(self): + git(self.root, "checkout", "--detach", self.intro) + with self.assertRaisesRegex(baseline.HistoricalBaselineError, "corrected PR80"): + baseline.verify_positive_source(self.root) + + def test_forged_tree_fails_before_execution(self): + with mock.patch.object(baseline, "REPLAY_TREE", "0" * 40): + with self.assertRaisesRegex(baseline.HistoricalBaselineError, "tree differs"): + baseline.verify_positive_source(self.root) + + def test_wrong_executable_or_original_test_pin_is_rejected(self): + for member in self.pins: + with self.subTest(member=member), mock.patch.dict(baseline.EXECUTORS, {member: "0" * 40}): + with self.assertRaisesRegex(baseline.HistoricalBaselineError, "executor/test changed"): + baseline.verify_positive_source(self.root) + with mock.patch.object(baseline, "ORIGINAL_TEST_BLOB", "0" * 40): + with self.assertRaises(baseline.HistoricalBaselineError): + baseline.verify_positive_source(self.root) + + def test_dirty_source_is_rejected(self): + (self.root / baseline.PROOF_PATHS[0]).write_text("uncommitted replacement\n") + with self.assertRaisesRegex(baseline.HistoricalBaselineError, "dirty"): + baseline.verify_positive_source(self.root) + + def test_assume_unchanged_cannot_hide_replaced_proof_bytes(self): + member = baseline.PROOF_PATHS[0] + git(self.root, "update-index", "--assume-unchanged", member) + (self.root / member).write_text("untracked-by-index corruption\n") + self.assertEqual(git(self.root, "status", "--porcelain=v1"), "") + with self.assertRaisesRegex(baseline.HistoricalBaselineError, "bytes differ"): + baseline.verify_positive_source(self.root) + + def test_source_directory_alias_is_rejected(self): + alias = self.root.parent / "alias" + alias.symlink_to(self.root, target_is_directory=True) + with self.assertRaisesRegex(baseline.HistoricalBaselineError, "symlinks"): + baseline.verify_positive_source(alias) + + +class HistoricalDiagnosticTests(unittest.TestCase): + def successful(self): + return {"format": "Permansson.A2P02.CompilerReceipt.v2", + "status": "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING", + "source": {"git_head": baseline.REPLAY_HEAD, + "git_head_tree": baseline.REPLAY_TREE}, + "source_unchanged": True} + + def test_successful_status_still_requires_exact_source(self): + row = self.successful() + baseline.require_runtime_success(0, row) + baseline.verify_receipt_source(row) + for field, value in (("git_head", baseline.INTRODUCER), + ("git_head_tree", "f" * 40)): + bad = copy.deepcopy(row) + bad["source"][field] = value + with self.subTest(field=field), self.assertRaisesRegex( + baseline.HistoricalBaselineError, "SOURCE_MISMATCH"): + baseline.verify_receipt_source(bad) + + def test_compiler_failure_exposes_label_exit_log_and_original_reason(self): + row = {"status": "FAIL_CLOSED", "reason": "Unknown identifier mul_le_mul_left'", + "commands": [{"label": "research_build", "returncode": 1, + "log": "02_research_build.log"}]} + with self.assertRaises(baseline.HistoricalBaselineError) as caught: + baseline.require_runtime_success(2, row) + for text in ("COMPILER_FAILED", "process_exit=2", "command=research_build", + "command_exit=1", "02_research_build.log", "Unknown identifier"): + self.assertIn(text, str(caught.exception)) + self.assertNotIn("SOURCE_MISMATCH", str(caught.exception)) + + def test_zero_exit_cannot_upgrade_a_failed_receipt(self): + with self.assertRaisesRegex(baseline.HistoricalBaselineError, "COMPILER_FAILED"): + baseline.require_runtime_success(0, {"status": "FAIL_CLOSED", "commands": None}) + + def test_boolean_exit_is_not_a_real_success_code(self): + with self.assertRaises(baseline.HistoricalBaselineError): + baseline.require_runtime_success(False, self.successful()) + + def test_expected_semantic_negative_is_not_misreported_as_failed_positive(self): + row = {"status": "FAIL_CLOSED", "reason": "source changed after controls", + "commands": [{"label": "wrong_reported_effect", "returncode": 1}]} + with self.assertRaises(baseline.HistoricalBaselineError) as caught: + baseline.require_runtime_success(2, row) + self.assertIn("command=unknown", str(caught.exception)) + self.assertIn("source changed after controls", str(caught.exception)) + + def test_unbounded_diagnostic_is_truncated_not_accepted(self): + with self.assertRaises(baseline.HistoricalBaselineError) as caught: + baseline.require_runtime_success(2, {"status": "FAIL_CLOSED", "reason": "x" * 20000}) + self.assertLess(len(str(caught.exception)), 6500) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_historical_a2_replay_v1.py b/verification/research/test_pr113_historical_a2_replay_v1.py new file mode 100644 index 00000000..34b573ec --- /dev/null +++ b/verification/research/test_pr113_historical_a2_replay_v1.py @@ -0,0 +1,405 @@ +"""Non-authorizing historical replay protocol tests with real temporary Git. + +Git objects/worktrees, byte pins, dependency links, cleanup and scope checks +are exercised. The compiler and receipt-library adapters below are SYNTHETIC; +none of these tests claims a historical Lean run or independent approval. +""" +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import types +import unittest +from unittest.mock import patch + +import pr113_historical_a2_replay_v1 as replay + + +def command(root, *args): + return subprocess.check_output( + ["git", "-C", str(root), *args], stderr=subprocess.DEVNULL, + timeout=30).decode().strip() + + +def initialize(root): + root.mkdir(parents=True, exist_ok=True) + command(root, "init", "-q") + command(root, "config", "user.name", "Synthetic replay test") + command(root, "config", "user.email", "synthetic@invalid.example") + command(root, "config", "commit.gpgsign", "false") + + +def commit(root): + command(root, "add", ".") + command(root, "commit", "-qm", "Synthetic source, not original evidence") + return command(root, "rev-parse", "HEAD") + + +class HistoricalA2ReplayTests(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory(prefix="a2-replay-test-") + self.addCleanup(self.temporary.cleanup) + self.top = Path(self.temporary.name) + self.root = self.top / "current" + initialize(self.root) + (self.root / ".gitignore").write_text(".lake/\n") + self.package = self.root / ".lake/packages/mockdep" + initialize(self.package) + (self.package / "source").write_text("SYNTHETIC dependency\n") + self.pin = commit(self.package) + for name in replay.EXECUTORS: + path = self.root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("# Synthetic immutable executor: " + name + "\n") + (self.root / replay.ORIGINAL_TEST).write_text("# Synthetic original test\n") + (self.root / "lean-toolchain").write_text("SYNTHETIC toolchain\n") + (self.root / "lakefile.toml").write_text('name = "synthetic"\n') + (self.root / "lake-manifest.json").write_text(json.dumps({ + "packagesDir": ".lake/packages", "packages": [ + {"type": "git", "name": "mockdep", "rev": self.pin}]})) + # Populate the execution surface for the real source audit. These are + # explicit synthetic sources; no compiler success is inferred from them. + for name in (*replay.baseline.PROOF_PATHS, "PermanssonResearch.lean"): + file = self.root / name + file.parent.mkdir(parents=True, exist_ok=True) + file.write_text("-- SYNTHETIC baseline source, not a proof\n") + self.historical = commit(self.root) + self.historical_tree = command(self.root, "rev-parse", "HEAD^{tree}") + historical_blobs = { + name: command(self.root, "rev-parse", "HEAD:" + name) + for name in replay.EXECUTORS} + original_test = command(self.root, "rev-parse", "HEAD:" + replay.ORIGINAL_TEST) + # The real historical and current localized_prefix_support.py are NOT + # identical. Reproduce that difference: the old fixture incorrectly + # installed the same executor source in both historical/current trees. + self.support_path = "verification/research/localized_prefix_support.py" + (self.root / self.support_path).write_text( + "# SYNTHETIC separately reviewed successor support source\n") + (self.root / "README.md").write_text("SYNTHETIC future source\n") + self.head = commit(self.root) + current_blobs = { + name: command(self.root, "rev-parse", "HEAD:" + name) + for name in replay.EXECUTORS} + self.historical_blobs = historical_blobs + self.current_blobs = current_blobs + self.output = self.top / "evidence" + self.runs = [] + self.current_runs = [] + self.fault = None + self.declarations = tuple("synthetic.proof%02d" % i for i in range(40)) + self.mutations = {"synthetic_negative_%d" % i: None for i in range(4)} + historical_module = types.ModuleType("localized_prefix_gate") + historical_module.check_dependencies = self.dependencies + historical_module.DECLS = self.declarations + historical_module.MUTATIONS = self.mutations + dual = types.ModuleType("pr113_dual_a2_compiler_v2") + dual.run_historical_verified = self.synthetic_compile + dual.validate_run = self.validate_run + semantic = types.ModuleType("pr113_semantic_receipt_v2") + semantic.validate_compiler_receipt_v2 = self.synthetic_receipt + router = types.ModuleType("pr113_a2_compiler_route_v3") + router.select_compiler = lambda root: types.SimpleNamespace( + checked_sha=command(root, "rev-parse", "HEAD"), + receipt_format="Permansson.PR113.A2SuccessorCompilerCandidate.v1", + module=types.SimpleNamespace(verify=self.synthetic_current_compile)) + guards = patch.multiple(replay, HISTORICAL_A2=self.historical, + EXECUTORS=current_blobs, + HISTORICAL_EXECUTORS=historical_blobs, + ORIGINAL_TEST_BLOB=original_test) + guards.start() + self.addCleanup(guards.stop) + anchors = patch.multiple( + replay.baseline, INTRODUCER=self.historical, + REPLAY_HEAD=self.historical, REPLAY_TREE=self.historical_tree, + EXECUTORS=dict(historical_blobs), ORIGINAL_TEST_BLOB=original_test) + anchors.start() + self.addCleanup(anchors.stop) + modules = patch.dict(sys.modules, { + "localized_prefix_gate": historical_module, + "pr113_dual_a2_compiler_v2": dual, + "pr113_semantic_receipt_v2": semantic, + "pr113_a2_compiler_route_v3": router, + }) + modules.start() + self.addCleanup(modules.stop) + + def dependencies(self, root): + manifest = json.loads((root / "lake-manifest.json").read_bytes()) + found = {} + for package in manifest["packages"]: + path = root / ".lake/packages" / package["name"] + replay.need(path.is_dir(), "synthetic dependency missing") + actual = command(path, "rev-parse", "HEAD") + replay.need(actual == package["rev"], "synthetic dependency revision mismatch") + replay.need(not command(path, "status", "--porcelain=v1", + "--untracked-files=all"), "synthetic dependency dirty") + found[package["name"]] = actual + return found + + def synthetic_compile(self, old, historical, output, mode): + self.assertNotEqual(old, self.root) + self.assertEqual(command(old, "rev-parse", "HEAD"), self.historical) + self.assertEqual(historical, self.historical) + self.assertEqual(mode, "focused") + self.assertFalse((old / ".lake/build").exists()) + self.assertEqual((old / ".lake/packages").resolve(), + (self.root / ".lake/packages").resolve()) + self.runs.append(str(old)) + output.mkdir(parents=True) + if self.fault == "compiler_error": + (output / "compiler.log").write_text("SYNTHETIC compiler unavailable\n") + raise replay.HistoricalReplayError("SYNTHETIC compiler unavailable") + row = { + "format": "Permansson.A2P02.CompilerReceipt.v2", + "status": "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING", + "mode": mode, "source_unchanged": True, + "source": {"git_head": historical, + "git_head_tree": command(old, "rev-parse", "HEAD^{tree}")}, + "declarations": dict.fromkeys(self.declarations, []), + "mutations": dict.fromkeys(self.mutations, {"status": "SYNTHETIC"}), + } + if self.fault == "wrong_head": row["source"]["git_head"] = self.head + if self.fault == "wrong_tree": row["source"]["git_head_tree"] = "f" * 40 + if self.fault == "wrong_mode": row["mode"] = "full" + if self.fault == "bad_status": row["status"] = "FAIL_CLOSED" + if self.fault == "missing_declaration": row["declarations"].pop(self.declarations[0]) + if self.fault == "missing_mutant": row["mutations"].pop(next(iter(self.mutations))) + (output / "receipt.json").write_text(json.dumps(row)) + if self.fault == "changed_current": + (self.root / "README.md").write_text("SYNTHETIC source changed while running\n") + if self.fault == "symlink_artifact": + (output / "substituted.log").symlink_to(self.root / "README.md") + if self.fault == "subdirectory_artifact": (output / "unapproved").mkdir() + return row + + def synthetic_current_compile(self, root, output, *, mode): + self.assertEqual(root, self.root) + self.current_runs.append(str(root)) + output.mkdir(parents=True) + row = { + "format": "Permansson.PR113.A2SuccessorCompilerCandidate.v1", + "status": "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING", + "mode": mode, "source_unchanged": True, + "source": {"git_head": command(root, "rev-parse", "HEAD"), + "git_head_tree": command(root, "rev-parse", "HEAD^{tree}")}, + "declarations": dict.fromkeys(self.declarations, []), + "mutations": dict.fromkeys(self.mutations, {"status": "SYNTHETIC"}), + } + if self.fault == "current_failure": row["status"] = "FAIL_CLOSED" + if self.fault == "current_wrong_head": row["source"]["git_head"] = self.historical + if self.fault == "current_wrong_format": row["format"] = "FORGED_FORMAT" + (output / "receipt.json").write_text(json.dumps(row)) + return row + + def synthetic_receipt(self, files, prefix, status, mutations): + raw = files[prefix + "/receipt.json"] + row = json.loads(raw) + replay.need(row["status"] == status, "SYNTHETIC receipt rejected") + return row, hashlib.sha256(raw).hexdigest() + + def validate_run(self, row, status, mutations, declarations): + replay.need(row["status"] == status and row["source_unchanged"] is True + and set(row["mutations"]) == set(mutations) + and set(row["declarations"]) == set(declarations), + "SYNTHETIC original proof/control inventory incomplete") + return True + + def no_extra_worktrees(self): + rows = command(self.root, "worktree", "list", "--porcelain").splitlines() + self.assertEqual(sum(row.startswith("worktree ") for row in rows), 1) + + def test_fresh_original_source_and_no_current_build_reuse(self): + self.assertNotEqual(self.historical_blobs[self.support_path], + self.current_blobs[self.support_path]) + self.assertEqual( + self.historical_blobs["verification/research/localized_prefix_gate.py"], + self.current_blobs["verification/research/localized_prefix_gate.py"]) + (self.root / ".lake/build").mkdir() + (self.root / ".lake/build/current.olean").write_text("SYNTHETIC current-only cache") + result = replay.verify(self.root, self.output) + self.assertEqual(result["historical_source_sha"], self.historical) + self.assertEqual(result["current_checked_sha"], self.head) + self.assertEqual(result["execution_scope"], "HISTORICAL_A2_AND_CURRENT_SOURCE") + self.assertEqual(result["status"], replay.STATUS) + self.assertTrue(result["current_compiler_required_separately"]) + for flag in ("historical_project_build_imported", "historical_executor_modified", + "independent_security_review", "mathematical_acceptance", "merge_authorized"): + self.assertIs(result[flag], False) + self.assertEqual(len(self.runs), 1) + self.assertEqual(len(self.current_runs), 1) + self.assertTrue(result["current_source_compiler_executed"]) + self.no_extra_worktrees() + self.assertTrue((self.package / "source").is_file()) + self.assertFalse(command(self.root, "status", "--porcelain=v1", "--untracked-files=all")) + + def test_invalid_original_receipt_never_turns_green(self): + for fault in ("wrong_head", "wrong_tree", "wrong_mode", "bad_status", + "missing_declaration", "missing_mutant", "symlink_artifact", + "subdirectory_artifact", "current_failure", "current_wrong_head", + "current_wrong_format"): + self.fault = fault + with self.subTest(fault=fault), self.assertRaises(replay.HistoricalReplayError): + replay.verify(self.root, self.top / fault) + self.no_extra_worktrees() + + def test_failure_retains_logs_and_cleans_checkout(self): + self.fault = "compiler_error" + with self.assertRaisesRegex(replay.HistoricalReplayError, "compiler unavailable"): + replay.verify(self.root, self.output) + self.assertIn("unavailable", (self.output / "historical_original_A2/compiler.log").read_text()) + self.no_extra_worktrees() + + def test_current_source_cannot_change_during_replay(self): + self.fault = "changed_current" + with self.assertRaisesRegex(replay.HistoricalReplayError, "source contains"): + replay.verify(self.root, self.output) + self.no_extra_worktrees() + + def test_dirty_current_source_rejects_before_compilation(self): + (self.root / "untracked").write_text("SYNTHETIC dirt") + with self.assertRaises(replay.HistoricalReplayError): + replay.verify(self.root, self.output) + self.assertFalse(self.runs) + self.no_extra_worktrees() + + def test_dirty_dependency_rejects_before_compilation(self): + (self.package / "source").write_text("SYNTHETIC dirty dependency") + with self.assertRaisesRegex(replay.HistoricalReplayError, "dependency dirty"): + replay.verify(self.root, self.output) + self.assertFalse(self.runs) + self.no_extra_worktrees() + + def test_wrong_dependency_revision_rejects_before_compilation(self): + (self.package / "source").write_text("SYNTHETIC new dependency revision") + commit(self.package) + with self.assertRaisesRegex(replay.HistoricalReplayError, "revision mismatch"): + replay.verify(self.root, self.output) + self.assertFalse(self.runs) + self.no_extra_worktrees() + + def test_changed_dependency_manifest_never_bridges(self): + (self.root / "lake-manifest.json").write_text('{"packages": []}\n') + commit(self.root) + with self.assertRaisesRegex(replay.HistoricalReplayError, "dependency source differs"): + replay.verify(self.root, self.output) + self.assertFalse(self.runs) + self.no_extra_worktrees() + + def test_historical_support_cannot_borrow_current_blob_pin(self): + with patch.dict(replay.HISTORICAL_EXECUTORS, + {self.support_path: self.current_blobs[self.support_path]}): + with self.assertRaisesRegex(replay.HistoricalReplayError, + "historical A2 executor Git identity changed"): + replay.verify(self.root, self.output) + self.assertFalse(self.runs) + self.no_extra_worktrees() + + def test_current_support_cannot_borrow_historical_blob_pin(self): + with patch.dict(replay.EXECUTORS, + {self.support_path: self.historical_blobs[self.support_path]}): + with self.assertRaisesRegex(replay.HistoricalReplayError, + "current A2 executor Git identity changed"): + replay.verify(self.root, self.output) + self.assertFalse(self.runs) + self.no_extra_worktrees() + + def test_unreviewed_current_support_change_is_rejected(self): + (self.root / self.support_path).write_text( + "# SYNTHETIC unreviewed current support change\n") + commit(self.root) + with self.assertRaisesRegex(replay.HistoricalReplayError, + "current A2 executor Git identity changed"): + replay.verify(self.root, self.output) + self.assertFalse(self.runs) + self.no_extra_worktrees() + + def test_changed_original_executor_never_runs(self): + path = next(iter(replay.EXECUTORS)) + (self.root / path).write_text("# SYNTHETIC changed executor\n") + commit(self.root) + with self.assertRaisesRegex(replay.HistoricalReplayError, "executor Git identity"): + replay.verify(self.root, self.output) + self.assertFalse(self.runs) + self.no_extra_worktrees() + + def test_preexisting_historical_build_is_rejected(self): + genuine = replay.dependency_bridge + def injected(current, old): + (old / ".lake/build").mkdir(parents=True) + return genuine(current, old) + with patch.object(replay, "dependency_bridge", side_effect=injected): + with self.assertRaisesRegex(replay.HistoricalReplayError, "build objects"): + replay.verify(self.root, self.output) + self.assertFalse(self.runs) + self.no_extra_worktrees() + + def test_existing_output_is_not_overwritten(self): + self.output.mkdir() + marker = self.output / "old.log" + marker.write_text("SYNTHETIC prior evidence") + with self.assertRaises(replay.HistoricalReplayError): + replay.verify(self.root, self.output) + self.assertEqual(marker.read_text(), "SYNTHETIC prior evidence") + self.assertFalse(self.runs) + + def test_source_overlap_or_symlink_output_rejected(self): + alias = self.top / "alias" + alias.symlink_to(self.root, target_is_directory=True) + for target in (self.root / "new", alias / "new"): + with self.subTest(target=target), self.assertRaises(replay.HistoricalReplayError): + replay.verify(self.root, target) + self.assertFalse(self.runs) + self.assertFalse((self.root / "new").exists()) + + def test_unknown_compiler_mode_not_a_fallback(self): + with self.assertRaises(replay.HistoricalReplayError): + replay.verify(self.root, self.output, mode="pretend") + self.assertFalse(self.runs) + + +class RealHistoricalA2GitPins(unittest.TestCase): + def test_immutable_original_and_current_source_pins_are_revision_scoped(self): + root = Path(__file__).resolve().parents[2] + head = replay.scalar(root, "rev-parse", "HEAD") + self.assertNotEqual(replay.HISTORICAL_A2, replay.baseline.INTRODUCER) + self.assertEqual(replay.HISTORICAL_A2, replay.baseline.REPLAY_HEAD) + self.assertEqual(replay.scalar(root, "rev-parse", + replay.HISTORICAL_A2 + "^{tree}"), + replay.baseline.REPLAY_TREE) + replay.git(root, "merge-base", "--is-ancestor", + replay.baseline.INTRODUCER, replay.HISTORICAL_A2) + replay.git(root, "merge-base", "--is-ancestor", replay.HISTORICAL_A2, head) + self.assertEqual(set(replay.EXECUTORS), set(replay.HISTORICAL_EXECUTORS)) + path = "verification/research/localized_prefix_support.py" + self.assertNotEqual(replay.EXECUTORS[path], + replay.HISTORICAL_EXECUTORS[path]) + gate = "verification/research/localized_prefix_gate.py" + self.assertEqual(replay.EXECUTORS[gate], + replay.HISTORICAL_EXECUTORS[gate]) + for label, revision, pins in ( + ("historical", replay.HISTORICAL_A2, + replay.HISTORICAL_EXECUTORS), + ("current", head, replay.EXECUTORS)): + for member, oid in pins.items(): + with self.subTest(scope=label, member=member): + self.assertEqual( + replay.scalar(root, "rev-parse", revision + ":" + member), + oid) + raw = replay.git(root, "show", revision + ":" + member) + self.assertEqual( + hashlib.sha1( + b"blob " + str(len(raw)).encode("ascii") + b"\0" + raw + ).hexdigest(), oid) + if label == "current": + self.assertEqual((root / member).read_bytes(), raw) + self.assertEqual(replay.scalar( + root, "rev-parse", replay.HISTORICAL_A2 + ":" + replay.ORIGINAL_TEST), + replay.ORIGINAL_TEST_BLOB) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_historical_artifact_delivery.py b/verification/research/test_pr113_historical_artifact_delivery.py new file mode 100644 index 00000000..0620631d --- /dev/null +++ b/verification/research/test_pr113_historical_artifact_delivery.py @@ -0,0 +1,193 @@ +"""PR113 original ZIP downloader: fake API tests, never synthetic approvals.""" +import copy +import json +import unittest +from unittest import mock +import pr113_historical_artifact_delivery as d + +H="a"*40 +B=d.B113 +S="b"*40 +RUN=301 + + +def context(): + return {"original_base_sha":B,"head_sha":H,"synthetic_sha":S} + + +def inputs(): + from test_pr113_infrastructure_acceptance import ( + zipped,historical_files,a2_files,synthetic_lake_manifest) + original={"T":zipped(historical_files()),"A2":zipped(a2_files())} + run={ + "id":RUN, "head_sha":H, + "head_branch":"research/final-mathematical-closure", + "event":"push", "status":"completed", "conclusion":"success", + "run_attempt":1,"path":d.ACTIVE_WORKFLOW, + } + jobs=[] + artifacts=[] + checks={} + sources={} + for i,kind in enumerate(("T","A2"),1): + jn,an=d.EXPECTED_ARTIFACTS[kind] + jobid,aid=500+i,900+i + checks[kind]=400+i + jobs.append({ + "id":jobid, "run_id":RUN,"run_attempt":1,"head_sha":H, + "name":jn,"status":"completed","conclusion":"success", + "check_run_url":("https://api.github.com/repos/"+d.REPOSITORY+ + "/check-runs/"+str(checks[kind])), + "started_at":"2026-10-25T09:00:00Z", + "completed_at":"2026-10-25T09:40:00Z", + }) + artifacts.append({ + "id":aid, "name":an, "expired":False, + "digest":"sha256:"+d.sha(original[kind]), + "workflow_run":{"id":RUN,"head_sha":H}, + "created_at":"2026-10-25T09:20:00Z", + }) + sources[aid]=original[kind] + api={ + f"/actions/runs/{RUN}":run, + f"/actions/runs/{RUN}/jobs?per_page=100":{"jobs":jobs}, + f"/actions/runs/{RUN}/artifacts?per_page=100":{"artifacts":artifacts}, + } + return api,sources,json.dumps(synthetic_lake_manifest(), + sort_keys=True).encode() + + +def git(*args): + if args==("rev-parse","HEAD"): + return S + if args==("cat-file","-t",H): + return "commit" + if args==("show","-s","--format=%P",S): + return B+" "+H + if args==("show",H+":lake-manifest.json"): + return inputs()[2].decode() + raise AssertionError("unexpected Git op "+repr(args)) + + +class HistoricalDeliveryTests(unittest.TestCase): + def test_two_original_jobs_and_statuses_never_claim_approval(self): + api, zips, manifest=inputs() + with mock.patch.object(d,"authenticate_original_zip", + side_effect=lambda kind,ids,raw,head,live: + {"sha256":d.sha(raw)}): + payload=d.deliver(context(),RUN,77,git=git, + live=api.__getitem__,download=zips.__getitem__) + self.assertEqual(payload["envelope"]["source_bound_ci"],[]) + self.assertEqual(payload["envelope"]["security_review_id"],77) + self.assertEqual(payload["transport"]["head"],H) + self.assertEqual(set(payload["zips"]),{"T","A2"}) + self.assertEqual(len(payload["transport"]["originals"]),2) + self.assertFalse(payload["transport"]["independent_security_review"]) + self.assertFalse(payload["transport"]["owner_merge_authorized"]) + + def test_real_artifact_preflight_checks_run_and_metadata(self): + api,zips,raw=inputs() + for kind in ("T","A2"): + selected=d.unique_job_and_artifact( + api[f"/actions/runs/{RUN}"], + api[f"/actions/runs/{RUN}/jobs?per_page=100"], + api[f"/actions/runs/{RUN}/artifacts?per_page=100"], + kind=kind,head=H) + self.assertEqual(selected["run_id"],RUN) + self.assertIsInstance(selected["artifact_id"],int) + cases=[ + lambda a:a[f"/actions/runs/{RUN}"].update(head_sha="c"*40), + lambda a:a[f"/actions/runs/{RUN}"].update(conclusion="failure"), + lambda a:a[f"/actions/runs/{RUN}"].update(event="pull_request"), + lambda a:a[f"/actions/runs/{RUN}/jobs?per_page=100"]["jobs"][0].update( + conclusion="failure"), + lambda a:a[f"/actions/runs/{RUN}/artifacts?per_page=100"]["artifacts"][0].update( + expired=True), + lambda a:a[f"/actions/runs/{RUN}/jobs?per_page=100"]["jobs"].append( + copy.deepcopy(a[f"/actions/runs/{RUN}/jobs?per_page=100"]["jobs"][0])), + ] + for mutate in cases: + data=copy.deepcopy(api) + mutate(data) + with self.subTest(mutate=mutate),self.assertRaises(d.DeliveryError): + d.unique_job_and_artifact( + data[f"/actions/runs/{RUN}"], + data[f"/actions/runs/{RUN}/jobs?per_page=100"], + data[f"/actions/runs/{RUN}/artifacts?per_page=100"], + kind="T",head=H) + + def test_source_git_preserves_file_bytes_but_uses_text_git_identities(self): + import subprocess + raw=b'{"packagesDir":".lake/packages"}\n' + with mock.patch.object(subprocess,"check_output", + side_effect=[b"b"*40+b"\n",raw]) as stdout: + self.assertEqual(d.source_git("rev-parse","HEAD"),"b"*40) + self.assertEqual(d.source_git("show",H+":lake-manifest.json"),raw) + self.assertEqual(stdout.call_count,2) + + + def test_invalid_https_redirect_is_rejected(self): + for url in ("http://api.github.com/artifacts/1", + "https://127.0.0.1/latest", + "https://169.254.169.254/", + "https://notgithubusercontent.com/file", + "file:///etc/passwd", + "https://api.github.com:8080/foo", + "https://user:password@api.github.com/foo"): + with self.subTest(url=url): + self.assertFalse(d.approved_https_url(url)) + self.assertTrue(d.approved_https_url( + "https://pipelines.actions.githubusercontent.com/path")) + self.assertTrue(d.approved_https_url( + "https://githubactionsproduction.blob.core.windows.net/path")) + + def test_missing_reviewer_id_and_borrowed_original_sources_rejected(self): + api,zips,manifest=inputs() + with self.assertRaises(d.DeliveryError): + d.deliver(context(),RUN,0,git=git, + live=api.__getitem__,download=zips.__getitem__) + altered=context() + altered["original_base_sha"]="0"*40 + with self.assertRaises(d.DeliveryError): + d.deliver(altered,RUN,77,git=git, + live=api.__getitem__,download=zips.__getitem__) + + def test_wrong_or_partial_lake_manifest_blocks(self): + api,zips,m=inputs() + with self.assertRaises(Exception): + d.collect(H,RUN,live=api.__getitem__, + download=zips.__getitem__,committed_manifest=b"{}") + + def test_detached_main_transport_rejects_foreign_merge_and_bogus_review(self): + from pr113_historical_postmerge import source_target + ctx={"original_base_sha":B, + "reviewed_head_sha":H,"merged_main_sha":S} + api, zips, manifest=inputs() + def merged_git(*args): + if args==("rev-parse","HEAD"):return S + if args==("show","-s","--format=%P",S):return B+" "+H + if args==("show",H+":lake-manifest.json"): + return manifest.decode() + raise AssertionError(args) + with mock.patch.object(d,"authenticate_original_zip", + side_effect=lambda kind,ids,raw,head,live: + {"sha256":d.sha(raw)}): + result=d.deliver_merged(ctx,RUN,77,git=merged_git, + live=api.__getitem__,download=zips.__getitem__) + self.assertEqual(result["envelope"]["target"],source_target(ctx)) + self.assertEqual(result["envelope"]["security_review_id"],77) + self.assertFalse(result["transport"]["owner_merge_authorized"]) + broken=dict(ctx,merged_main_sha="0"*40) + with self.assertRaisesRegex(d.DeliveryError, + "authentic B/H/M"): + d.deliver_merged(broken,RUN,77,git=merged_git, + live=api.__getitem__,download=zips.__getitem__) + with self.assertRaisesRegex(d.DeliveryError, + "review ID is mandatory"): + d.deliver_merged(ctx,RUN,0,git=merged_git, + live=api.__getitem__,download=zips.__getitem__) + + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_historical_evidence_contract_v2.py b/verification/research/test_pr113_historical_evidence_contract_v2.py new file mode 100644 index 00000000..064e1fca --- /dev/null +++ b/verification/research/test_pr113_historical_evidence_contract_v2.py @@ -0,0 +1,182 @@ +"""Nonauthorizing historical evidence v2 structure and schema regression. + +All values are synthetic; only live GitHub and actual independent review +can turn a structural candidate into valid historical security evidence. +""" +from __future__ import annotations + +import copy +import json +from pathlib import Path +import unittest +from unittest import mock + +import infrastructure_acceptance_v2 as consumer +import pr113_historical_evidence_contract_v2 as contract +from final_closure_authenticate import REQUIRED +from test_pr113_release_target import fake_target + + +ROOT = Path(__file__).resolve().parents[2] +SCHEMA = ROOT / "research/certification/infrastructure_receipt_v2.schema.json" + + +def candidate(*, aggregate=False): + head = fake_target()["head_sha"] + rows = ([{"name":name, "id":200+index, "head_sha":head} + for index, name in enumerate(sorted(REQUIRED))] + if aggregate else []) + return { + "format": contract.FORMAT, + "target": fake_target(), + "source_bound_ci": rows, + "historical_T": { + "run_id":101, "run_attempt":2, "job_id":102, + "check_run_id":103, "artifact_id":104}, + "historical_A2": { + "run_id":101, "run_attempt":2, "job_id":105, + "check_run_id":106, "artifact_id":107}, + "security_review_id":108, + } + + +class HistoricalEnvelopeV2Tests(unittest.TestCase): + def test_two_phase_structural_contract_never_authenticates(self): + base = candidate() + self.assertIs(contract.validate_envelope(base), base) + self.assertEqual(contract.parse_envelope(json.dumps(base).encode()), base) + self.assertEqual(base["source_bound_ci"], []) + self.assertNotIn("independent_review", base) + self.assertNotIn("mathematical_acceptance", base) + aggregate = candidate(aggregate=True) + self.assertIs(contract.validate_envelope(aggregate, require_all_checks=True), + aggregate) + self.assertEqual(len(aggregate["source_bound_ci"]), 12) + with self.assertRaises(contract.InfrastructureEnvelopeError): + contract.validate_envelope(base, require_all_checks=True) + + def test_partial_borrowed_duplicate_stale_contexts_fail(self): + good = candidate(aggregate=True) + for edit in ( + lambda p:p["source_bound_ci"].pop(), + lambda p:p["source_bound_ci"].__setitem__(0,p["source_bound_ci"][1]), + lambda p:p["source_bound_ci"][0].update(id=True), + lambda p:p["source_bound_ci"][0].update(head_sha="0"*40), + lambda p:p["source_bound_ci"][0].update(name="foreign-workflow"), + lambda p:p["source_bound_ci"][0].update( + id=p["source_bound_ci"][1]["id"]), + ): + with self.subTest(edit=str(edit)): + payload = copy.deepcopy(good) + edit(payload) + with self.assertRaises(contract.InfrastructureEnvelopeError): + contract.validate_envelope(payload, require_all_checks=True) + + def test_distinct_historical_producers_and_exact_types_required(self): + for edit in ( + lambda p:p["historical_A2"].update(run_id=999), + lambda p:p["historical_A2"].update(run_attempt=1), + lambda p:p["historical_A2"].update(job_id=102), + lambda p:p["historical_A2"].update(check_run_id=103), + lambda p:p["historical_A2"].update(artifact_id=104), + lambda p:p["historical_T"].update(job_id=True), + lambda p:p.update(security_review_id=True), + lambda p:p.update(security_review_id=0), + lambda p:p.update(fake_approval=True), + lambda p:p["target"].update(phase="certification_premerge"), + ): + with self.subTest(edit=str(edit)): + payload = copy.deepcopy(candidate()) + edit(payload) + with self.assertRaises(contract.InfrastructureEnvelopeError): + contract.validate_envelope(payload) + + def test_duplicate_json_keys_and_unbounded_bytes_fail_before_git(self): + orig = candidate() + raw = json.dumps(orig).encode() + self.assertEqual(contract.parse_envelope(raw), orig) + double = raw[:-1] + b', "security_review_id":999}' + with self.assertRaises(contract.InfrastructureEnvelopeError): + contract.parse_envelope(double) + with self.assertRaises(contract.InfrastructureEnvelopeError): + contract.parse_envelope(b" " * (contract.MAX_BYTES + 1)) + with self.assertRaises(contract.InfrastructureEnvelopeError): + contract.parse_envelope(b"{bad json}") + + def test_generic_bounded_event_parser_rejects_duplicate_values(self): + message = b'{"after":"%s", "ref":"refs/heads/main"}' % (b"a"*40) + self.assertEqual(contract.decode_unique_json(message)["after"], + "a" * 40) + duplicate = message[:-1] + b', "after":"%s"}' % (b"a"*40) + with self.assertRaises(contract.InfrastructureEnvelopeError): + contract.decode_unique_json(duplicate, max_bytes=8*1024*1024) + for raw in (b"[]", b"false", b"null", b"{}"*(contract.MAX_BYTES+1)): + with self.subTest(raw=raw[:10]), self.assertRaises( + contract.InfrastructureEnvelopeError): + contract.decode_unique_json(raw) + + def test_json_nan_and_infinity_never_enter_historical_evidence(self): + for raw in (b'{"after": NaN}', b'{"after": Infinity}', + b'{"after": -Infinity}', b'{"after": 1e999}'): + # Parse constants or nonfinite numeric results must fail before + # a machine-readable verification status can be accepted. + with self.subTest(raw=raw), self.assertRaises( + contract.InfrastructureEnvelopeError): + contract.decode_unique_json(raw) + + def test_pr_and_main_event_decoders_reject_duplicate_object_keys(self): + from test_pr113_required_dual_gate import fake_env, fake_event + from test_pr113_historical_postmerge import ( + env as main_env, event as main_event, + ) + import pr113_required_dual_gate_v2 as pr_gate + import pr113_historical_postmerge_v2 as main_gate + + pr_raw = fake_event() + pr_dup = pr_raw[:-1] + b', "number":113}' + with self.assertRaises(pr_gate.RequiredGateError): + pr_gate.actual_ci_event( + env=fake_env(), event_data=pr_dup, + git=lambda *args: "irrelevant") + original = main_event() + main_dup = original[:-1] + b', "after":"%s"}' % (b"b"*40) + with self.assertRaises(main_gate.PostmergeError): + main_gate.exact_main_event( + env=main_env(), event_bytes=main_dup, + git=lambda *args: "irrelevant", + live=lambda *args: {}) + + def test_versioned_source_schema_preserves_v1_and_target_contract(self): + schema = json.loads(SCHEMA.read_bytes()) + self.assertEqual(schema["properties"]["format"]["const"], contract.FORMAT) + self.assertFalse(schema["additionalProperties"]) + self.assertEqual(set(schema["required"]), contract.FIELDS) + self.assertEqual(set(schema["$defs"]["artifact"]["required"]), + contract.ARTIFACT_FIELDS) + self.assertEqual( + set(schema["properties"]["source_bound_ci"]["items"] + ["properties"]["name"]["enum"]), REQUIRED) + self.assertEqual( + schema["properties"]["target"]["$ref"], + "target_contract.schema.json") + historical = json.loads((SCHEMA.parent/"infrastructure_receipt.schema.json") + .read_bytes()) + self.assertEqual(historical["properties"]["format"]["const"], + "Permansson.PR113.InfrastructureEvidence.v1") + + def test_v2_consumer_rejects_bad_phase_before_live_authority(self): + bad = candidate() + bad["historical_A2"]["run_id"] = 999 + with mock.patch.object(consumer.release_target, "bind") as live_auth: + with self.assertRaises(contract.InfrastructureEnvelopeError): + consumer.check_historical_only(bad, b"", b"") + live_auth.assert_not_called() + missing = candidate() + with mock.patch.object(consumer.release_target, "bind") as live_auth: + with self.assertRaises(contract.InfrastructureEnvelopeError): + consumer.check_evidence(missing, b"", b"") + live_auth.assert_not_called() + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_historical_postmerge.py b/verification/research/test_pr113_historical_postmerge.py new file mode 100644 index 00000000..9337e1e2 --- /dev/null +++ b/verification/research/test_pr113_historical_postmerge.py @@ -0,0 +1,146 @@ +"""Synthetic main-merge source identity tests; never owner approval.""" +import copy +import json +import unittest +from unittest import mock + +import pr113_historical_postmerge as gate +B=gate.B113 +H="a"*40 +M="b"*40 + + +def env(): + return { + "GITHUB_ACTIONS":"true", "GITHUB_EVENT_NAME":"push", + "GITHUB_REF":"refs/heads/main", + "GITHUB_REPOSITORY":gate.REPOSITORY, + "GITHUB_JOB":"archive-reproducibility", + "GITHUB_WORKFLOW":"Research integrity", + "GITHUB_SHA":M,"GITHUB_RUN_ID":"31337", + "GITHUB_RUN_ATTEMPT":"1", + } + + +def event(): + return json.dumps({ + "ref":"refs/heads/main","after":M, + "repository":{"full_name":gate.REPOSITORY}, + }).encode() + + +def git(*args): + if args==("rev-parse","HEAD"):return M + if args in (("cat-file","-t",M),("cat-file","-t",H)):return "commit" + if args==("show","-s","--format=%P",M):return B+" "+H + if args in (("rev-parse",M+"^{tree}"), + ("rev-parse",H+"^{tree}")):return "same_tree" + if args[0]=="show" and args[1] in ( + H+":"+gate.ACTIVE_WORKFLOW, + M+":"+gate.ACTIVE_WORKFLOW): + return ("# Review-pending source-controlled historical verification\n" + "historical-T-and-H-unreviewed\n" + "historical-A2-and-H-unreviewed\n") + if args==("show",H+":lake-manifest.json"):return "{}" + raise AssertionError("unexpected Git op "+repr(args)) + + +def live(path): + if path=="/pulls/113": + return {"number":113,"state":"closed","merged":True, + "merge_commit_sha":M, + "head":{"sha":H},"user":{"login":"HMarcusWH"}} + if path=="/branches/main": + return {"commit":{"sha":M}} + raise AssertionError(path) + + +class MainHistoricalGateTests(unittest.TestCase): + def test_original_main_binding_has_real_two_parents_and_unreviewed_state(self): + ctx=gate.exact_main_event(env=env(),event_bytes=event(), + git=git,live=live) + self.assertEqual(ctx["original_base_sha"],B) + self.assertEqual(ctx["reviewed_head_sha"],H) + self.assertEqual(ctx["merged_main_sha"],M) + self.assertEqual(ctx["github_job"],"archive-reproducibility") + + def test_foreign_main_or_squash_rebase_not_borrowed(self): + for mutate in ( + lambda e:e.update(GITHUB_SHA="0"*40), + lambda e:e.update(GITHUB_REF="refs/heads/research/final-mathematical-closure"), + lambda e:e.update(GITHUB_EVENT_NAME="pull_request"), + lambda e:e.update(GITHUB_WORKFLOW="Other"), + lambda e:e.update(GITHUB_RUN_ATTEMPT="0"), + ): + e=env() + mutate(e) + with self.subTest(mutate=mutate),self.assertRaises( + gate.PostmergeError): + gate.exact_main_event(env=e,event_bytes=event(), + git=git,live=live) + def squash(*args): + if args==("show","-s","--format=%P",M):return B + return git(*args) + with self.assertRaises(gate.PostmergeError): + gate.exact_main_event(env=env(),event_bytes=event(), + git=squash,live=live) + def wrong_tree(*args): + if args==("rev-parse",M+"^{tree}"):return "wrong" + return git(*args) + with self.assertRaises(gate.PostmergeError): + gate.exact_main_event(env=env(),event_bytes=event(), + git=wrong_tree,live=live) + + def test_false_merge_or_remote_source_mutation_rejected(self): + def unmerged(path): + obj=live(path) + if path=="/pulls/113":return dict(obj,merged=False) + return obj + with self.assertRaises(gate.PostmergeError): + gate.exact_main_event(env=env(),event_bytes=event(), + git=git,live=unmerged) + def wrongmain(path): + if path=="/branches/main":return {"commit":{"sha":"0"*40}} + return live(path) + with self.assertRaises(gate.PostmergeError): + gate.exact_main_event(env=env(),event_bytes=event(), + git=git,live=wrongmain) + + def test_checked_main_receipt_is_not_a_mathematical_certificate(self): + ctx=gate.exact_main_event(env=env(),event_bytes=event(), + git=git,live=live) + envelope={ + "format":gate.FORMAT,"target":gate.source_target(ctx), + "historical_T":{},"historical_A2":{}, + "security_review_id":321,"source_bound_ci":[], + } + with mock.patch.object(gate,"authenticate_original_zip", + side_effect=lambda kind,*args,**kwargs: + {"kind":kind}), \ + mock.patch.object(gate,"inspect_zip",return_value={}), \ + mock.patch.object(gate,"inspect_T",return_value={"T":"PASS"}), \ + mock.patch.object(gate,"inspect_A2",return_value={"A2":"PASS"}), \ + mock.patch.object(gate,"verify_A2_dependency_bridge", + return_value="1"*64), \ + mock.patch.object(gate,"authority_policy", + return_value={}), \ + mock.patch.object(gate,"approved_security_review", + return_value=True): + report=gate.check_historical_merged( + envelope,b"old T ZIP",b"old A2 ZIP", + event_context=ctx,git=git,live=live,paginate=lambda p:[]) + self.assertEqual(report["status"], + "MERGED_HISTORICAL_T_AND_A2_REVERIFIED_UNDER_REVIEWED_SOURCE") + self.assertEqual(report["required_ci_successful"],0) + self.assertFalse(report["independent_mathematical_review"]) + self.assertFalse(report["publication_authorized"]) + self.assertFalse(report["owner_merge_authorized"]) + bad=copy.deepcopy(envelope) + bad["target"]["merged_main_sha"]="f"*40 + with self.assertRaises(gate.PostmergeError): + gate.check_historical_merged(bad,b"old T",b"old A2", + event_context=ctx,git=git,live=live) + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_historical_runtime_v3.py b/verification/research/test_pr113_historical_runtime_v3.py new file mode 100644 index 00000000..b5c19a97 --- /dev/null +++ b/verification/research/test_pr113_historical_runtime_v3.py @@ -0,0 +1,179 @@ +"""The authenticated v3 historical adapter cannot borrow the original scope. + +All fixture decisions below are synthetically constructed *only* to exercise +the routing and output parser. No acceptance or GitHub approval is asserted. +""" +from __future__ import annotations + +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +import pr113_historical_runtime_v3 as adapter +import pr113_historical_inheritance_v3 as inheritance + +H = "b" * 40 +M = "c" * 40 +C = "d" * 40 + + +def event(phase): + return { + "phase": phase, "source_commit_sha": C, + "github_run_id": 222, "github_run_attempt": 1, + "base_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "head_sha": H, "synthetic_sha": C, + "infrastructure_head_sha": H, + "infrastructure_merge_sha": M if phase != "initial_113_pr" else None, + } + + +class HistoricalLifecycleUnitTests(unittest.TestCase): + def test_descendant_uses_original_M113_source_revalidation(self): + current = event("main_descendant_pr") + original = { + "format": inheritance.FORMAT, + "status": "PR113_HISTORICAL_SOURCE_REVERIFIED_FOR_AUTHENTIC_DESCENDANT", + "historical_merged_main_sha": M, + "current_required_ci_accepted": False, + } + with mock.patch.object(adapter, "inherited_verify", + return_value=original) as source: + result = adapter.produce(111, 333, event=current) + self.assertEqual(result["required"], original) + self.assertEqual(result["zips"], {}) + self.assertIsNone(result["envelope"]) + source.assert_called_once_with(111, 333, lifecycle=current) + + def test_original_PR_requires_authentic_v2_security_source(self): + current = event("initial_113_pr") + original = {"envelope": {"format": "mock_original"}, + "zips": {"T": b"PK_t", "A2": b"PK_a2"}, + "transport": {"status": "REVIEW_PENDING"}} + with mock.patch( + "pr113_historical_artifact_delivery_v2.deliver", + return_value=original), mock.patch( + "pr113_required_dual_gate_v2.verify", + return_value={"independent_security_review_authenticated": True}): + result = adapter.produce(111, 333, event=current) + self.assertEqual(result["phase"], "initial_113_pr") + self.assertFalse(result["required"].get("publication_authorized", False)) + + def test_reject_unrecognized_lifecycle(self): + with self.assertRaises(adapter.RuntimeError113): + adapter.produce(111, 333, event=event("stale_foreign_branch")) + + def test_unbound_receipt_and_borrowed_original_ids_reject(self): + current = event("main_descendant_pr") + with tempfile.TemporaryDirectory() as tmp: + file = Path(tmp) / "historical.json" + base = { + "format": adapter.FORMAT, + "phase": current["phase"], + "source_commit_sha": C, + "original_github_run_id": 222, + "original_github_run_attempt": 1, + "historical_producer_run_id": 111, + "historical_security_review_id": 333, + "accepted_current_mathematics": False, + "owner_merge_authorized": False, + "publication_authorized": False, + "historical_required_result": { + "status": "PR113_HISTORICAL_SOURCE_REVERIFIED_FOR_AUTHENTIC_DESCENDANT", + "historical_merged_main_sha": M, + "current_required_ci_accepted": False, + }, + } + file.write_text(json.dumps(base)) + # Only a synthetic test replay: production rechecks original GitHub. + valid_fresh = { + "phase": current["phase"], "event": current, + "required": base["historical_required_result"], + "envelope": None, "transport": None, "zips": {}, + } + def simulated(*args, **kwargs): + self.assertEqual(args, (111, 333)) + self.assertEqual(kwargs, {"event": current}) + return valid_fresh + adapter.check_stored(file, 111, 333, event=current, + recompute=simulated) + for change in ( + {"historical_security_review_id": 445}, + {"source_commit_sha": "f" * 40}, + {"accepted_current_mathematics": True}, + {"historical_required_result": { + "status": "FORGED_SUCCESS", + "historical_merged_main_sha": M, + "current_required_ci_accepted": False}}, + ): + candidate = {**base, **change} + file.write_text(json.dumps(candidate)) + with self.subTest(change=change), self.assertRaises( + adapter.RuntimeError113): + adapter.check_stored(file, 111, 333, event=current, + recompute=simulated) + + def test_structurally_valid_but_borrowed_receipt_is_rejected(self): + current = event("main_descendant_pr") + with tempfile.TemporaryDirectory() as tmp: + file = Path(tmp) / "historical.json" + source = { + "status": "PR113_HISTORICAL_SOURCE_REVERIFIED_FOR_AUTHENTIC_DESCENDANT", + "historical_merged_main_sha": M, "current_required_ci_accepted": False, + } + payload = { + "format": adapter.FORMAT, "phase": current["phase"], + "source_commit_sha": C, "original_github_run_id": 222, + "original_github_run_attempt": 1, "historical_producer_run_id": 111, + "historical_security_review_id": 333, + "accepted_current_mathematics": False, "owner_merge_authorized": False, + "publication_authorized": False, "historical_required_result": source, + } + file.write_text(json.dumps(payload)) + forged_recompute = { + "phase": current["phase"], "event": current, + "required": {**source, "historical_original_producer_run_id": 444}, + "envelope": None, "transport": None, "zips": {}, + } + with self.assertRaises(adapter.RuntimeError113): + adapter.check_stored(file, 111, 333, event=current, + recompute=lambda *a, **k: forged_recompute) + file.unlink() + file.symlink_to(Path(tmp) / "elsewhere.json") + with self.assertRaises(ValueError): + adapter.check_stored(file, 111, 333, event=current, + recompute=lambda *a, **k: forged_recompute) + + def test_inherited_source_result_requires_12_authenticated_contexts(self): + current = event("main_descendant_pr") + info = { + "source_head_sha": H, "merged_main_sha": M, + "historical_original_producer_run_id": 111, + "security_review_id": 333, + "historical_status": + "MERGED_HISTORICAL_T_AND_A2_REVERIFIED_UNDER_REVIEWED_SOURCE", + "historical_original_workflow_count": 25, + "current_reviewed_workflow_count": 30, + "original_required_context_count": 12, + "historical_security_review_authenticated": True, + "historical_T_and_A2_genuine": True, + "new_mathematical_acceptance": False, + "publication_authorized": False, + } + valid = inheritance.verify_child_report( + info, current, run_id=111, reviewer_id=333) + self.assertFalse(valid["new_mathematical_acceptance"]) + for mutation in ( + {"original_required_context_count": 11}, + {"historical_security_review_authenticated": False}, + {"historical_T_and_A2_genuine": False}, + {"merged_main_sha": "f" * 40}, + {"publication_authorized": True}, + ): + with self.subTest(mutation=mutation), self.assertRaises( + inheritance.InheritanceError): + inheritance.verify_child_report( + {**info, **mutation}, current, run_id=111, + reviewer_id=333) diff --git a/verification/research/test_pr113_implementation_matrix.py b/verification/research/test_pr113_implementation_matrix.py new file mode 100644 index 00000000..9d24b6c9 --- /dev/null +++ b/verification/research/test_pr113_implementation_matrix.py @@ -0,0 +1,169 @@ +"""A source candidate is not a Lean proof or a reviewer approval.""" +import json +import unittest +from unittest import mock + +import pr113_implementation_matrix as m + +H="a"*40 +C=[f"C-{i:02}" for i in range(1,46)] +Q=["Q1","Q2","Q3","M1","M2","M3","N1","N2","N3","D8","D9"] + + +def fake_tree(): + proof="PermanssonResearch/Actual.lean" + test="verification/research/test_actual.py" + original={"format":"old","full45_accepted":False,"cases":[{ + "case_id":cid,"decision":"PENDING", + "required_expected_result":"Original requirement "+cid + } for cid in C]} + cmap={"original_cases":45,"new_closure_owners":11, + "cases":[{ + "case_id":cid,"required_expected_result":"Original requirement "+cid, + "historical_contract_ids":["ORIGINAL"], + "historical_proof_declarations":[{"name":"name","path":proof}], + "test_method_candidates":[{"path":test,"method":"test_real", + "role":"COMPILER_SOURCE"}], + "remaining_obligations":["independent review"] + } for cid in C]} + claims={"count":11,"claims":[{ + "claim_id":cid,"accepted":False, + "declarations":[{"declaration":"Module.theorem","path":proof}], + "test_source_candidates":[test],"scope_exclusions":[] + } for cid in Q]} + inventory={"claims":[{"id":cid,"title":cid,"accepted":False} for cid in Q]} + values={ + m.ORIGINAL:original,m.CASE_MAP:cmap, + m.CLAIM_MAP:claims,m.INVENTORY:inventory, + } + contents={key:(json.dumps(val)+"\n").encode() for key,val in values.items()} + contents[proof]=b"theorem name : True := by trivial\n" + contents[test]=b"import unittest\nclass Test(unittest.TestCase):\n def test_real(self):\n self.assertTrue(True)\n" + return contents + + +class MatrixTests(unittest.TestCase): + def test_original_45_and_11_source_map_never_auto_certifies(self): + files=fake_tree() + result=m.produce(H,files.__getitem__) + self.assertEqual(len(result["cases"]),45) + self.assertEqual(len(result["claims"]),11) + self.assertFalse(result["mathematical_certification"]) + self.assertEqual(result["unconditionally_implemented"],0) + self.assertEqual(result["independently_accepted_cases"],0) + self.assertEqual({r["implementation_state"] for r in result["cases"]}, + {"PARTIAL"}) + self.assertEqual(result["cases"][0]["candidate_tests"][0]["source_status"], + "DISCOVERED_UNREVIEWED") + + def test_missing_theorem_or_test_is_not_source_complete(self): + files=fake_tree() + files.pop("PermanssonResearch/Actual.lean") + r=m.produce(H,files.__getitem__) + self.assertEqual(r["case_source_missing"],C) + self.assertEqual(set(r["claim_source_missing"]),set(Q)) + files=fake_tree() + files["verification/research/test_actual.py"]=b"def test_other(): pass\n" + r=m.produce(H,files.__getitem__) + self.assertEqual(len(r["case_source_missing"]),45) + + def test_nested_or_conditionally_hidden_method_cannot_cover_case(self): + files = fake_tree() + path = "verification/research/test_actual.py" + originals = ( + b"def helper():\n def test_real():\n pass\n", + b"if False:\n def test_real():\n pass\n", + b"class Test(unittest.TestCase):\n" + b" def helper(self):\n" + b" def test_real():\n" + b" pass\n", + ) + for raw in originals: + with self.subTest(source=raw[:35]): + files[path] = raw + report = m.produce(H, files.__getitem__) + self.assertEqual(len(report["case_source_missing"]), 45) + self.assertEqual( + report["cases"][0]["candidate_tests"][0]["source_status"], + "MISSING_SOURCE_OR_METHOD") + + def test_top_level_pytest_and_unittest_methods_remain_candidates(self): + for source_text in ( + "def test_real():\n assert True\n", + "import unittest\nclass Test(unittest.TestCase):\n" + " def test_real(self):\n self.assertTrue(True)\n", + ): + with self.subTest(source=source_text[:22]): + files = fake_tree() + files["verification/research/test_actual.py"] = ( + source_text.encode()) + report = m.produce(H, files.__getitem__) + self.assertFalse(report["case_source_missing"]) + self.assertEqual( + report["cases"][0]["candidate_tests"][0]["source_status"], + "DISCOVERED_UNREVIEWED") + self.assertEqual(report["independently_accepted_cases"], 0) + + def test_duplicate_top_level_methods_are_ambiguous(self): + files = fake_tree() + files["verification/research/test_actual.py"] = ( + b"class First:\n def test_real(self): pass\n" + b"class Second:\n def test_real(self): pass\n") + report = m.produce(H, files.__getitem__) + self.assertEqual(len(report["case_source_missing"]), 45) + self.assertEqual( + report["cases"][0]["candidate_tests"][0]["source_status"], + "AMBIGUOUS_UNREVIEWED") + + def test_live_tracked_source_case_claim_inventory_never_accepts(self): + """Inspect exact current Git blobs; not lexical proof certification.""" + import subprocess + current = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=m.ROOT, + text=True, timeout=20).strip() + report = m.produce(current) + self.assertEqual(report["original_case_count"], 45) + self.assertEqual(report["new_claim_count"], 11) + self.assertEqual(report["case_source_missing"], []) + self.assertEqual(report["claim_source_missing"], []) + self.assertEqual(report["independently_accepted_cases"], 0) + self.assertEqual(report["independently_accepted_claims"], 0) + self.assertEqual(report["unconditionally_implemented"], 0) + self.assertFalse(report["mathematical_certification"]) + self.assertEqual( + {row["implementation_state"] for row in report["cases"]}, + {"PARTIAL"}) + + def test_original_requirement_mutation_blocks_matrix(self): + files=fake_tree() + raw=json.loads(files[m.CASE_MAP]) + raw["cases"][0]["required_expected_result"]="Changed premise" + files[m.CASE_MAP]=json.dumps(raw).encode() + with self.assertRaises(m.MatrixError): + m.produce(H,files.__getitem__) + + def test_45_11_and_exact_claim_names_cannot_be_reduced(self): + files=fake_tree() + rows=json.loads(files[m.CASE_MAP]) + rows["cases"].pop() + files[m.CASE_MAP]=json.dumps(rows).encode() + with self.assertRaises(m.MatrixError): + m.produce(H,files.__getitem__) + files=fake_tree() + rows=json.loads(files[m.CLAIM_MAP]) + rows["claims"][0]["claim_id"]="UNKNOWN" + files[m.CLAIM_MAP]=json.dumps(rows).encode() + with self.assertRaises(m.MatrixError): + m.produce(H,files.__getitem__) + + def test_claim_historical_flag_cannot_be_relabelled(self): + files=fake_tree() + rows=json.loads(files[m.INVENTORY]) + rows["claims"][0]["accepted"]=True + files[m.INVENTORY]=json.dumps(rows).encode() + with self.assertRaises(m.MatrixError): + m.produce(H,files.__getitem__) + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_inactive_required_gate_replacement.py b/verification/research/test_pr113_inactive_required_gate_replacement.py new file mode 100644 index 00000000..767dde30 --- /dev/null +++ b/verification/research/test_pr113_inactive_required_gate_replacement.py @@ -0,0 +1,163 @@ +"""An inactive R3 transition must not silently relax the original checks.""" +import ast +import json +from pathlib import Path +import subprocess +import unittest + +ROOT=Path(__file__).resolve().parents[2] +STAGED=ROOT/"docs/operations/pr113_required_gate_replacement_unapproved" +from pr113_workflow_profile_v2 import PREACTIVATION_HEAD, audit as audit_v2 + + +def active30(): + count=len(list((ROOT/".github/workflows").glob("*.yml"))) + if count not in (25,30): + raise AssertionError("unexpected workflow count") + return count==30 + +def git(*args): + return subprocess.check_output(["git",*args],cwd=ROOT, + stderr=subprocess.DEVNULL,text=True,timeout=20).strip() + +class InactiveHistoricalTransition(unittest.TestCase): + def test_staged_files_and_original_git_blobs_remain_exact_and_unapproved(self): + obj=json.loads((STAGED/"manifest.json").read_bytes()) + self.assertEqual(obj["format"], + "Permansson.PR113.UnapprovedRequiredGateTransition.v1") + self.assertEqual(obj["baseline_original_workflow_count"],25) + if active30(): + result=audit_v2(ROOT,git("rev-parse","HEAD"),phase="active") + self.assertEqual(result["historical_original_workflow_count"],25) + self.assertEqual(result["current_reviewed_workflow_count"],30) + self.assertFalse(result["independent_security_review"]) + self.assertTrue(obj["original_T_A2_unmodified"]) + self.assertTrue(obj["requires_independent_security_review"]) + for k in ("owner_activation_authorized","mathematical_acceptance", + "merge_authorized","publication_authorized"): + self.assertFalse(obj[k]) + self.assertEqual(len(obj["original_unchanged_sources"]),3) + for rec in obj["original_unchanged_sources"]: + original_source=PREACTIVATION_HEAD + self.assertEqual(git("rev-parse",original_source+":"+rec["path"]), + rec["git_blob_oid"]) + self.assertEqual(len(obj["staged_replacements"]),2) + for rec in obj["staged_replacements"]: + self.assertFalse(rec["installed"]) + self.assertFalse(rec["independent_reviewed"]) + self.assertEqual(git("rev-parse","HEAD:"+rec["source"]), + rec["git_blob_oid"]) + # The original historical file remains in the preactivation + # commit. The reviewed successor is checked by the full v2 + # workflow and test source contract rather than a fragment hash. + + def test_original_A2_is_mandatory_and_staged_successor_is_real(self): + original=subprocess.check_output( + ["git","show",PREACTIVATION_HEAD+ + ":verification/research/test_localized_prefix_compiler.py"], + cwd=ROOT,text=True,timeout=20) + self.assertIn('report = verify(ROOT, out, mode="focused")',original) + replacement=(STAGED/"test_localized_prefix_compiler.py").read_text() + if active30(): + installed=(ROOT/"verification/research/test_localized_prefix_compiler.py").read_text() + self.assertEqual(installed,replacement) + else: + from pr113_a2_successor_identity import INHERITED_A2_VERIFIERS + path="verification/research/test_localized_prefix_compiler.py" + self.assertEqual(git("rev-parse","HEAD:"+path), + INHERITED_A2_VERIFIERS[path]) + installed=(ROOT/path).read_text() + self.assertIn("from pr113_historical_a2_replay_v1 import verify",installed) + self.assertIn("HISTORICAL_A2_AND_CURRENT_SOURCE",installed) + self.assertNotIn("skipTest",installed) + self.assertNotIn("@unittest.skip",installed) + from pr113_a2_successor_identity_v2 import ( + LATE_A2_TEST_ORIGINAL_BLOB, LATE_A2_TEST_PROPOSED_BLOB, + ) + path="verification/research/test_localized_prefix_compiler.py" + self.assertEqual( + git("rev-parse", PREACTIVATION_HEAD+":"+path), + LATE_A2_TEST_ORIGINAL_BLOB) + self.assertEqual(git( + "rev-parse","HEAD:docs/operations/" + "pr113_required_gate_replacement_unapproved/" + "test_localized_prefix_compiler.py"), + LATE_A2_TEST_PROPOSED_BLOB) + ast.parse(replacement) + self.assertIn( + "from pr113_historical_runtime_v3 import FORMAT, check_stored", + replacement) + self.assertIn("check_stored(candidate, producer_id, reviewer_id)", + replacement) + self.assertIn("strict_json_object(",replacement) + self.assertIn("PERMANSSON_HISTORICAL_REQUIRED_RECEIPT",replacement) + self.assertIn("test_actual_final_source_and_semantic_mutations", + replacement) + self.assertIn("PHASE_STATUSES",replacement) + for status in ( + "REQUIRED_HISTORICAL_SOURCE_AND_SECURITY_REVIEW_VERIFIED", + "MERGED_HISTORICAL_T_AND_A2_REVERIFIED_UNDER_REVIEWED_SOURCE", + "PR113_HISTORICAL_SOURCE_REVERIFIED_FOR_AUTHENTIC_DESCENDANT", + ): + self.assertIn(status,replacement) + self.assertNotIn("from pr113_required_dual_gate_v2 import verify", + replacement) + self.assertNotIn("@unittest.skip",replacement) + self.assertNotIn("skipTest(",replacement) + self.assertNotIn("mock.patch",replacement) + # Initial PR/main may use original T/A2 sidecars, but the v3 + # consumer must not require those inputs for later CERT/main phases. + for original_only in ("PERMANSSON_DUAL_HISTORICAL_EVIDENCE", + "PERMANSSON_DUAL_ORIGINAL_T_ZIP", + "PERMANSSON_DUAL_ORIGINAL_A2_ZIP"): + self.assertNotIn(original_only,replacement) + + def test_original_archive_gate_is_still_protected_and_staged_step_fails_closed(self): + original=( + subprocess.check_output( + ["git","show",PREACTIVATION_HEAD+ + ":.github/workflows/research-integrity.yml"], + cwd=ROOT,text=True,timeout=20) + if active30() else + (ROOT/".github/workflows/research-integrity.yml").read_text()) + if active30(): + # The installed v3 workflow replaces current-source delivery, + # while the immutable historical v2 archive fragment below stays + # separately asserted. Verify exact active-30 source identity. + report=audit_v2(ROOT,git("rev-parse","HEAD"),phase="active") + self.assertEqual(report["versioned_source_phase"],"active") + self.assertEqual(report["current_reviewed_workflow_count"],30) + installed=(ROOT/".github/workflows/research-integrity.yml").read_text( + encoding="utf-8") + runtime="python3 -B verification/research/pr113_historical_runtime_v3.py" + self.assertEqual(installed.count(runtime),2) + self.assertNotIn("pr113_required_dual_gate_v2.py",installed) + producer_name=" - name: Deliver genuine source-bound historical T and A2 artifacts\n" + verifier_name=" - name: Verify genuine reviewed original T/T2 and A2 source\n" + preserve_name=" - name: Preserve archival restoration reports\n" + for name in (producer_name,verifier_name,preserve_name): + self.assertEqual(installed.count(name),1) + producer,tail=installed.split(producer_name,1)[1].split(verifier_name,1) + verifier=tail.split(preserve_name,1)[0] + self.assertEqual(producer.count(runtime),1) + self.assertEqual(verifier.count(runtime),1) + self.assertRegex(producer, + r"(?m)^\s+python3 -B verification/research/" + r"pr113_historical_runtime_v3\.py \s*\\\s*$") + self.assertRegex(verifier, + r"(?m)^\s+python3 -B verification/research/" + r"pr113_historical_runtime_v3\.py \s*\\\s*$") + self.assertNotIn("--check-existing",producer) + self.assertRegex(verifier,r"(?m)^\s+--check-existing\s*$") + self.assertIn("verification.archive.offline_smoke",original) + self.assertIn("Execute archive adversaries",original) + replacement=(STAGED/"research_integrity_archive_step.yml.inc").read_text() + self.assertIn("pr113_required_dual_gate_v2.py",replacement) + for option in ("historical-T-zip","historical-A2-zip", + "PERMANSSON_DUAL_HISTORICAL_EVIDENCE","set -euo pipefail"): + self.assertIn(option,replacement) + self.assertNotIn("continue-on-error",replacement) + self.assertNotIn("|| true",replacement) + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_infrastructure_acceptance.py b/verification/research/test_pr113_infrastructure_acceptance.py new file mode 100644 index 00000000..ed9d2718 --- /dev/null +++ b/verification/research/test_pr113_infrastructure_acceptance.py @@ -0,0 +1,575 @@ +"""Synthetic infrastructure gate negatives. Never a real approval.""" +from __future__ import annotations + +import io +import json +import unittest +from unittest import mock +import zipfile + +import infrastructure_acceptance as infra +import release_target + + +H = "a" * 40 + + +def zipped(files): + dest = io.BytesIO() + with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as z: + for name, data in sorted(files.items()): + z.writestr(name, data) + return dest.getvalue() + + +def historical_files(): + old = { + "format": "Permansson.Hardening.Check.v1", + "context": {}, "status": "PASS", "check": "archive-reproducibility", + "details": { + "acceptance_scope": "accepted_artifact_replay_only", + "isolation": {"network_isolation": "ENFORCED", + "capabilities": "all_zero"} + } + } + current = { + "format": "Permansson.Hardening.Tests.v1", + "schema_version": 1, "status": "PASS", "suite": "workflow", + "context":{"repository":"HMarcusWH/PermanssonLean", + "checked_sha":H,"checked_tree":"9"*40,"event":"local"}, + "execution_tracked_clean": True, "methods_run": 5, + "failures": 0, "errors": 0, "skips": 0, + "covered_requirements": ["CI-06","CI-07"] + } + obj = lambda x: (json.dumps(x, sort_keys=True) + "\n").encode() + oldraw, newraw = obj(old), obj(current) + receipt = { + "status": "DUAL_T_AND_H_EXECUTED_UNREVIEWED", + "original_T_archive_unchanged": True, + "historical_T": release_target.T, "current_H": H, + "historical_T_report_sha256": infra.sha(oldraw), + "current_H_report_sha256": infra.sha(newraw), + "independent_security_review": False, + "versioned_successor_T2_approved": False, + } + dossier = { + "format": "Permansson.PR113.ToolingSecurityReviewDossier.v1", + "status": "COMPLETE_DUAL_EXECUTION_CANDIDATE_SECURITY_REVIEW_PENDING", + "T": release_target.T, "H": H, + "T_original_offline_restore_report_sha256": infra.sha(oldraw), + "H_original_workflow_test_report_sha256": infra.sha(newraw), + "legacy_T_did_not_accept_current_C": True, + "owner_authorization_present": False, + "independent_security_review_present": False, + "historical_required_CI_replaced": False, + "required_workflow_inventory_count": 25, + } + return { + "original_T_archive.json": oldraw, + "current_H_workflow.json": newraw, + "dual_tooling_unreviewed.json": obj(receipt), + "T2_security_review_candidate.json": obj(dossier), + } + + +def compiler(prefix, status, mutation_names): + commands = [] + content = {} + for n, label in enumerate( + ["compiler_version", "research_build", "signatures_axioms", + "positive_fixture", *mutation_names, "source_1", "source_2"], 1): + bad = label in mutation_names + text = b"error: type mismatch on false theorem\n" if bad else b"proof okay\n" + logfile = f"{n:02}_{label}.log" + content[f"{prefix}/{logfile}"] = text + commands.append({ + "label": label, "returncode": 1 if bad else 0, + "log": logfile, "log_sha256": infra.sha(text), + }) + row = { + "status": status, "source_unchanged": True, + "declarations": {"Some.target": ["propext"]}, + "mutations": {name: {"status": "SEMANTICALLY_REJECTED"} + for name in mutation_names}, + "commands": commands, + "artifacts": { + item.removeprefix(prefix + "/"): { + "bytes": len(raw), "sha256": infra.sha(raw)} + for item, raw in content.items() + }, + } + content[f"{prefix}/receipt.json"] = ( + json.dumps(row, sort_keys=True) + "\n").encode() + return content + + +def synthetic_lake_manifest(): + return { + "packagesDir": ".lake/packages", + "packages": [ + {"type": "git", "name": "mathlib", + "rev": "5ed2965256430c3649e86755f9576b54eca72435"}, + {"type": "git", "name": "aesop", "rev": "1"*40}, + ], + } + + +def fake_bridge(): + manifest = synthetic_lake_manifest() + raw = json.dumps(manifest, sort_keys=True).encode() + return { + "format": "Permansson.PR113.HistoricalA2DependencyBridge.v1", + "status": "PINNED_DEPENDENCIES_REUSED_WITHOUT_PROOF_BUILD_IMPORT", + "historical_head": infra.A2_HISTORICAL, + "current_head": H, + "manifest_sha256": infra.sha(raw), + "pinned_packages": {p["name"]:p["rev"] for p in manifest["packages"]}, + "original_verifier_modified": False, + "historical_project_proofs_reused": False, + "independent_security_review": False, + "mathematical_acceptance": False, + } + + +def a2_files(): + left = compiler( + "historical_original_A2", + "LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING", + infra.HISTORICAL_MUTATIONS) + right = compiler( + "pr113_successor_A2", + "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING", + infra.SUCCESSOR_MUTATIONS) + historical_receipt = left["historical_original_A2/receipt.json"] + overall = { + "format": "Permansson.PR113.DualA2CompilerCandidate.v2", + "status": "BOTH_ACTUAL_A2_COMPILER_PATHS_EXECUTED_UNREVIEWED", + "historical_source_commit": infra.A2_HISTORICAL, + "current_source_commit": H, "historical_test_executed": True, + "successor_test_executed": True, + "historical_semantic_mutants": 4, "successor_semantic_mutants": 4, + "original_verifier_modified": False, + "independent_security_approval": False, + "independent_mathematical_approval": False, + "merged_main_sha": None, "full_release_approved": False, + "historical_runtime_provenance": { + "imported_current_A2_gate": False, + "historical_checkout_sha": infra.A2_HISTORICAL, + "output_receipt_sha256": infra.sha(historical_receipt), + }, + } + return { + "historical_dependency_bridge.json": + (json.dumps(fake_bridge(), sort_keys=True)+"\n").encode(), + **left, **right, "unreviewed_dual_A2_candidate.json": + (json.dumps(overall, sort_keys=True) + "\n").encode(), + } + + +class HistoricalEvidenceTests(unittest.TestCase): + def test_T_reports_original_actual_bytes(self): + files = historical_files() + self.assertEqual( + infra.inspect_T(files, H)["T_report_sha256"], + infra.sha(files["original_T_archive.json"])) + files["current_H_workflow.json"] = b'{"status":"PASS"}' + with self.assertRaises(infra.InfrastructureError): + infra.inspect_T(files, H) + + def test_A2_has_4_meaningful_mutants_on_both_original_versions(self): + files = a2_files() + self.assertEqual( + infra.inspect_A2(files, H)["original_A2_sha256"], + infra.sha(files["historical_original_A2/receipt.json"])) + original = json.loads(files["historical_original_A2/receipt.json"]) + original["mutations"].pop(next(iter(original["mutations"]))) + files["historical_original_A2/receipt.json"] = json.dumps(original).encode() + with self.assertRaises(infra.InfrastructureError): + infra.inspect_A2(files, H) + + def test_borrowed_logged_mutant_or_parser_failure_cannot_count(self): + files = a2_files() + key = next(k for k in files if k.startswith( + "pr113_successor_A2/") and "omit_baseline_reporting_error.log" in k) + files[key] = b"error: parser error\n" + with self.assertRaises(infra.InfrastructureError): + infra.inspect_A2(files, H) + + def test_reviewed_workflow_hash_includes_exact_git_bytes(self): + source=(b"# Review-pending source-controlled historical verification\n" + b"historical-T-and-H-unreviewed\n" + b"historical-A2-and-H-unreviewed\n") + with mock.patch.object(infra.subprocess,"check_output", + return_value=source): + raw=infra.exact_committed_blob( + H,infra.ACTIVE_WORKFLOW,git=infra.real_git) + self.assertEqual(raw,source) + self.assertNotEqual(infra.sha(raw),infra.sha(raw.strip())) + self.assertEqual(infra.exact_committed_blob( + H,infra.ACTIVE_WORKFLOW,git=lambda *a:source),source) + + + def test_lake_manifest_full_committed_bytes_never_trimmed(self): + raw=b'{"packagesDir":".lake/packages"}\n' + with mock.patch.object(infra.subprocess,"check_output", + return_value=raw) as checked: + self.assertEqual(infra.exact_committed_lake_blob( + H,git=infra.real_git),raw) + self.assertEqual(checked.call_args.args[0], + ["git","show",H+":lake-manifest.json"]) + self.assertEqual(infra.exact_committed_lake_blob( + H,git=lambda *a:raw.decode("utf-8")),raw) + self.assertNotEqual(infra.sha(raw),infra.sha(raw.strip())) + + + def test_historical_dependency_bridge_rejects_forgery(self): + files = a2_files() + manifest=json.dumps(synthetic_lake_manifest(), sort_keys=True).encode() + evidence=infra.verify_A2_dependency_bridge(files,H,manifest_raw=manifest) + self.assertEqual(evidence,infra.sha( + files["historical_dependency_bridge.json"])) + for mutation in ( + lambda x: x.update(current_head="b"*40), + lambda x: x.update(manifest_sha256="0"*64), + lambda x: x.update(pinned_packages={"mathlib":"0"*40}), + lambda x: x.update(historical_project_proofs_reused=True), + lambda x: x.update(independent_security_review=True), + lambda x: x.update(original_verifier_modified=True), + ): + with self.subTest(mutation=mutation): + modified=a2_files() + obj=json.loads(modified["historical_dependency_bridge.json"]) + mutation(obj) + modified["historical_dependency_bridge.json"] = ( + json.dumps(obj, sort_keys=True)+"\n").encode() + with self.assertRaises(infra.InfrastructureError): + infra.verify_A2_dependency_bridge( + modified,H,manifest_raw=manifest) + with self.assertRaises(infra.InfrastructureError): + infra.verify_A2_dependency_bridge( + a2_files(),H,manifest_raw=b"{}") + + + def test_safe_zip_member_bounds(self): + raw = zipped(historical_files()) + self.assertIn("original_T_archive.json", infra.inspect_zip(raw)) + for bad in ("../T2.json", "bad\\path", "X/../../evil", + "folder/.", "bad/link/../../../f.txt"): + with self.subTest(bad=bad): + with self.assertRaises(infra.InfrastructureError): + infra.inspect_zip(zipped({bad: b"bad"})) + + def test_bare_status_fails_when_actual_original_reports_absent(self): + files = {"dual_tooling_unreviewed.json": b'{"status":"PASS"}'} + with self.assertRaises(infra.InfrastructureError): + infra.inspect_T(files, H) + with self.assertRaises(infra.InfrastructureError): + infra.inspect_A2(files, H) + + +class SourceArtifactTests(unittest.TestCase): + def evidence(self, *, kind="T"): + job, artifact_name = infra.EXPECTED_ARTIFACTS[kind] + payload = zipped(historical_files() if kind == "T" else a2_files()) + ids = dict(run_id=101, run_attempt=2, job_id=102, + check_run_id=103, artifact_id=104) + api = { + "/actions/runs/101": { + "id": 101, "head_sha": H, + "head_branch": "research/final-mathematical-closure", + "event": "push", "status": "completed", "conclusion": "success", + "run_attempt": 2, + "path": infra.ACTIVE_WORKFLOW + }, + "/actions/runs/101/jobs?per_page=100": { + "jobs": [{ + "id": 102, "run_id": 101, "run_attempt": 2, + "head_sha": H, "name": job, "status": "completed", + "conclusion": "success", + "check_run_url": "https://api.github.com/check-runs/103", + "started_at": "2026-10-25T00:00:00Z", + "completed_at": "2026-10-25T00:15:00Z", + }] + }, + "/check-runs/103": { + "id": 103, "head_sha": H, "name": job, + "status": "completed", "conclusion": "success", + "app": {"slug": "github-actions"} + }, + "/actions/artifacts/104": { + "id": 104, "name": artifact_name, "expired": False, + "digest": "sha256:" + infra.sha(payload), + "created_at": "2026-10-25T00:10:00Z", + "workflow_run": {"id": 101, "head_sha": H} + } + } + return ids, payload, api + + def test_genuine_green_job_artifact_bytes_authenticated(self): + ids, raw, api = self.evidence() + item = infra.authenticate_original_zip( + "T", ids, raw, H, live=api.__getitem__) + self.assertEqual(item["sha256"], infra.sha(raw)) + + def test_failed_job_even_with_real_uploaded_zip_is_blocked(self): + ids, raw, api = self.evidence() + api["/actions/runs/101/jobs?per_page=100"]["jobs"][0][ + "conclusion"] = "failure" + with self.assertRaises(infra.InfrastructureError): + infra.authenticate_original_zip( + "T", ids, raw, H, live=api.__getitem__) + + def test_foreign_head_attempt_artifact_and_missing_identity_block(self): + for edit in ( + lambda a: a["/actions/runs/101"].update(head_sha="b"*40), + lambda a: a["/actions/runs/101"].update(run_attempt=1), + lambda a: a["/actions/artifacts/104"].update(digest="sha256:"+"0"*64), + lambda a: a["/actions/artifacts/104"].update(expired=True), + ): + with self.subTest(edit=edit): + ids, raw, api = self.evidence() + edit(api) + with self.assertRaises(infra.InfrastructureError): + infra.authenticate_original_zip( + "T", ids, raw, H, live=api.__getitem__) + ids, raw, api = self.evidence() + ids["approval"] = True + with self.assertRaises(infra.InfrastructureError): + infra.authenticate_original_zip("T", ids, raw, H, live=api.__getitem__) + + +class IndependentReviewTests(unittest.TestCase): + def record(self): + attested = { + "head_sha": H, + "workflow_sha256": "0"*64, + "T_artifact_sha256": "1"*64, + "A2_artifact_sha256": "2"*64, + "original_base_sha": release_target.B113, + } + approved = { + "id": 987, "state": "APPROVED", "commit_id": H, + "submitted_at": "2026-10-25T00:20:00Z", + "user": {"login": "trusted-security-reviewer"}, + "body": infra.REVIEW_PREFIX + json.dumps(attested), + } + policy = {"reviewers": [ + {"login": "trusted-security-reviewer", "approved_by": "HMarcusWH", + "independence_disclosed": True}]} + return approved, policy, attested + + def test_real_independent_attestation_only_and_no_fake_approvals(self): + review, policy, payload = self.record() + self.assertTrue(infra.approved_security_review( + 987, "HMarcusWH", H, payload, policy, + paginate=lambda p: [review])) + changed = dict(payload, T_artifact_sha256="f"*64) + with self.assertRaises(infra.InfrastructureError): + infra.approved_security_review( + 987, "HMarcusWH", H, changed, policy, + paginate=lambda p: [review]) + + def test_no_owner_self_review_or_later_changes_requested(self): + review, policy, payload = self.record() + review["user"]["login"] = "HMarcusWH" + with self.assertRaises(infra.InfrastructureError): + infra.approved_security_review( + 987, "HMarcusWH", H, payload, policy, + paginate=lambda p: [review]) + review, policy, payload = self.record() + revoked = dict(review, id=988, state="CHANGES_REQUESTED", + submitted_at="2026-10-25T00:22:00Z") + with self.assertRaises(infra.InfrastructureError): + infra.approved_security_review( + 987, "HMarcusWH", H, payload, policy, + paginate=lambda p: [review, revoked]) + + +class SeparateGateTests(unittest.TestCase): + def test_cannot_promote_infrastructure_receipt_to_mathematical_release(self): + from final_closure_schema import validate_overlay + candidate = { + "format": "Permansson.PR113.InfrastructureAcceptanceReceipt.v1", + "status": "INFRASTRUCTURE_READY_FOR_SEPARATE_OWNER_MERGE_DECISION", + "independent_mathematical_review": False, + "publication_authorized": False, + } + with self.assertRaises(Exception): + validate_overlay(candidate) + + + def test_full_infrastructure_graph_synthetic_positive_never_math_release(self): + from test_pr113_release_target import fake_intent, fake_apis, fake_target + tzip, azip = zipped(historical_files()), zipped(a2_files()) + source_test = SourceArtifactTests() + tids, _, tapi = source_test.evidence(kind="T") + aids, _, aapi = source_test.evidence(kind="A2") + # The two original artifacts belong to two successful, separate jobs. + newids = { + "run_id": 201, "run_attempt": 2, "job_id": 202, + "check_run_id": 203, "artifact_id": 204, + } + aid_api = { + key.replace("101", "201").replace("102", "202") + .replace("103", "203").replace("104", "204"): value + for key, value in aapi.items() + } + # Fix the authenticated identity fields, not merely the URL keys. + arun = aid_api["/actions/runs/201"] + arun["id"] = 201 + job = aid_api["/actions/runs/201/jobs?per_page=100"]["jobs"][0] + job.update(id=202, run_id=201, + check_run_url="https://api.github.com/check-runs/203") + checked = aid_api["/check-runs/203"] + checked["id"] = 203 + art = aid_api["/actions/artifacts/204"] + art.update(id=204, digest="sha256:"+infra.sha(azip)) + art["workflow_run"].update(id=201) + tapi["/actions/artifacts/104"]["digest"] = "sha256:"+infra.sha(tzip) + api = {**tapi, **aid_api} + # Both distinct real-API shapes use the staged workflow identity. + for key in ("/actions/runs/101", "/actions/runs/201"): + api[key]["path"] = infra.ACTIVE_WORKFLOW + target_gh, target_git, t_api = fake_apis() + api.update(t_api) + workflow = ("# Review-pending source-controlled historical verification\n" + "name: PR113 Historical T and A2 candidates\n" + "jobs:\n historical-T-and-H-unreviewed:\n" + " historical-A2-and-H-unreviewed:\n") + policy = { + "format": "Permansson.InfrastructureReviewerAuthority.v1", + "owner": "HMarcusWH", + "status": "OWNER_AUTHORIZED", + "reviewers": [ + {"login":"external-security-reviewer", + "independence_disclosed":True, + "approved_by":"HMarcusWH"}] + } + def git(*args): + if args == ("show", H + ":" + infra.AUTHORITY_PATH): + return json.dumps(policy) + if args == ("show", H + ":" + infra.ACTIVE_WORKFLOW): + return workflow + if args == ("show", H + ":lake-manifest.json"): + return json.dumps(synthetic_lake_manifest(), sort_keys=True) + if args == ("show", H + ":" + release_target.INTENT_PATH): + return json.dumps(fake_intent()) + return target_git(*args) + checks = {name:{"id":n,"head_sha":H,"name":name} + for n,name in enumerate(sorted(infra.REQUIRED),1)} + receipt = { + "format":infra.FORMAT, "target":fake_target(), + "source_bound_ci":[{"name":name,"id":check["id"], + "head_sha":H} + for name,check in checks.items()], + "historical_T":tids, "historical_A2":newids, + "security_review_id":789, + } + attestation = { + "head_sha": H, "workflow_sha256": infra.sha(workflow.encode()), + "T_artifact_sha256": infra.sha(tzip), + "A2_artifact_sha256": infra.sha(azip), + "original_base_sha": release_target.B113, + } + approved_review = { + "id":789, "state":"APPROVED","commit_id":H, + "submitted_at":"2026-10-25T00:25:00Z", + "user":{"login":"external-security-reviewer"}, + "body":infra.REVIEW_PREFIX + json.dumps(attestation), + } + api["/pulls/113"]["user"]={"login":"HMarcusWH"} + with mock.patch.object(infra.release_target,"exact_intent", + wraps=infra.release_target.exact_intent): + verified = infra.check_evidence( + receipt,tzip,azip,git=git,live=api.__getitem__, + checks=lambda h: checks, + paginate=lambda path:[approved_review]) + self.assertEqual(verified["required_ci_successful"],12) + self.assertEqual( + verified["status"], + "INFRASTRUCTURE_READY_FOR_SEPARATE_OWNER_MERGE_DECISION") + self.assertFalse(verified["independent_mathematical_review"]) + self.assertFalse(verified["publication_authorized"]) + self.assertFalse(verified["merge_authorized"]) + + def test_historical_required_gate_cannot_self_require_ci_or_approve_merge(self): + from test_pr113_release_target import fake_target + payload = { + "format": infra.FORMAT, "target": fake_target(), + "source_bound_ci": [], "historical_T":{}, + "historical_A2":{}, "security_review_id":987, + } + report_target = {"head_sha": H,"phase":"infrastructure_premerge", + "synthetic_sha":"b"*40} + with mock.patch.object(infra.release_target,"candidate_bindings"), \ + mock.patch.object(infra.release_target,"exact_intent", + return_value={}), \ + mock.patch.object(infra.release_target,"bind", + return_value=report_target), \ + mock.patch.object(infra,"authenticate_original_zip", + side_effect=lambda kind,*args,**kw: + {"sha256":("1" if kind=="T" else "2")*64}), \ + mock.patch.object(infra,"inspect_zip",return_value={}), \ + mock.patch.object(infra,"inspect_T",return_value={"T":"PASS"}), \ + mock.patch.object(infra,"inspect_A2",return_value={"A2":"PASS"}), \ + mock.patch.object(infra,"verify_A2_dependency_bridge", + return_value="3"*64), \ + mock.patch.object(infra,"authority_policy", + return_value={"status":"OWNER_AUTHORIZED"}), \ + mock.patch.object(infra,"approved_security_review", + return_value=True), \ + mock.patch.object(infra,"authenticated_checks", + side_effect=AssertionError("self-dependent CI")): + def git(*args): + if args[0]=="show" and args[1].endswith(infra.ACTIVE_WORKFLOW): + return ("# Review-pending source-controlled historical verifier\n" + "historical-T-and-H-unreviewed\n" + "historical-A2-and-H-unreviewed\n") + if args[0]=="show" and args[1].endswith("lake-manifest.json"): + return "{}" + raise AssertionError(args) + def live(path): + if path=="/pulls/113": + return {"user":{"login":"HMarcusWH"}} + raise AssertionError(path) + receipt=infra.check_historical_only( + payload,b"original-T",b"original-A2", + git=git,live=live,paginate=lambda p:[]) + self.assertEqual(receipt["required_ci_successful"],0) + self.assertIn("HISTORICAL_T_AND_A2",receipt["status"]) + self.assertFalse(receipt["independent_mathematical_review"]) + self.assertFalse(receipt["merge_authorized"]) + self.assertFalse(receipt["publication_authorized"]) + with self.assertRaises(infra.InfrastructureError): + infra.check_evidence(payload,b"original-T",b"original-A2", + git=git,live=live,checks=lambda h: {}, + paginate=lambda p:[]) + + def test_all_12_exact_source_checks_required_and_missing_review_blocks(self): + good = { + "format": infra.FORMAT, + "target": {"head_sha": H}, + "source_bound_ci": [], + "historical_T": {}, + "historical_A2": {}, + "security_review_id": 0, + } + # All invalid external targets are rejected before any Git, GitHub + # or required-CI lookup, even when their head SHA looks syntactically + # valid. No fake aaaa... Git object should be requested. + with mock.patch.object(infra, "real_git", + side_effect=AssertionError("Git lookup")) as git, \ + mock.patch.object(infra, "real_live", + side_effect=AssertionError("GitHub lookup")) as live, \ + mock.patch.object(infra, "authenticated_checks", + side_effect=AssertionError("CI lookup")) as checks: + with self.assertRaises(release_target.TargetError): + infra.check_evidence(good, b"x", b"y", + git=git, live=live, checks=checks) + git.assert_not_called() + live.assert_not_called() + checks.assert_not_called() + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_inheritance_optimized.py b/verification/research/test_pr113_inheritance_optimized.py new file mode 100644 index 00000000..964d66f7 --- /dev/null +++ b/verification/research/test_pr113_inheritance_optimized.py @@ -0,0 +1,107 @@ +"""Non-authorizing -O checks of the inherited M113 historical gate. + +Every module in the child is a temporary synthetic stub: successful tests +are not evidence of an actual T/A2 producer, GitHub check or human review. +""" +import ast +import copy +import json +import subprocess +import sys +import unittest +import pr113_historical_inheritance_v3 as inheritance + +H="b"*40 +M="c"*40 +NAMES=tuple("mock-required-%02d"%i for i in range(12)) +GOOD_STATUS="MERGED_HISTORICAL_T_AND_A2_REVERIFIED_UNDER_REVIEWED_SOURCE" + + +def case(): + return { + "checks":{name:{"id":100+i,"head_sha":H,"status":"completed", + "conclusion":"success"} for i,name in enumerate(NAMES)}, + "receipt":{"status":GOOD_STATUS, + "independent_security_review_authenticated":True, + "required_ci_successful":0}, + } + + +def run_synthetic(record,producer=111,reviewer=333,optimized=True): + prelude='''import json,sys,types +data=json.loads(__DATA__) +def module(name, **values): + m=types.ModuleType(name) + m.__dict__.update(values) + sys.modules[name]=m +module("pr113_historical_artifact_delivery_v2", + collect=lambda h,run,**k: ( + {"T":b"PKt","A2":b"PKa"}, + {"T":{"run_id":run},"A2":{"run_id":run}},{})) +module("pr113_historical_postmerge_v2", + check_historical_merged=lambda *a,**kw:data["receipt"], + source_target=lambda target:target, + FORMAT="Permansson.PR113.PostmergeHistoricalEvidence.v2") +module("infrastructure_acceptance_v2", + exact_committed_lake_blob=lambda h:b"{}") +module("release_target",B113="a"*40) +module("final_closure_authenticate", + REQUIRED=frozenset(__NAMES__), + authenticated_checks=lambda h:data["checks"]) +''' + prelude=prelude.replace("__DATA__",repr(json.dumps(record))) + prelude=prelude.replace("__NAMES__",repr(NAMES)) + argv=[sys.executable]+(["-O"] if optimized else [])+[ + "-B","-c",prelude+inheritance.CHILD,H,M,str(producer),str(reviewer)] + return subprocess.run(argv,stdout=subprocess.PIPE,stderr=subprocess.PIPE, + timeout=20,check=False) + + +class OptimizedHistoricalSourceTests(unittest.TestCase): + def test_honest_synthetic_controls_under_regular_and_optimized_python(self): + for optimized in (False,True): + with self.subTest(optimized=optimized): + result=run_synthetic(case(),optimized=optimized) + self.assertEqual(result.returncode,0,result.stderr.decode(errors="replace")) + receipt=json.loads(result.stdout) + self.assertEqual(receipt["original_required_context_count"],12) + self.assertEqual(receipt["historical_status"],GOOD_STATUS) + self.assertFalse(receipt["new_mathematical_acceptance"]) + + def test_invalid_check_identity_or_outcome_is_rejected_under_optimization(self): + for mutation in ("missing","failed","queued","wrong_head","zero","boolean"): + record=case() + first=NAMES[0] + if mutation=="missing":record["checks"].pop(first) + elif mutation=="failed":record["checks"][first]["conclusion"]="failure" + elif mutation=="queued":record["checks"][first]["status"]="queued" + elif mutation=="wrong_head":record["checks"][first]["head_sha"]="f"*40 + elif mutation=="zero":record["checks"][first]["id"]=0 + elif mutation=="boolean":record["checks"][first]["id"]=True + with self.subTest(mutation=mutation): + result=run_synthetic(record) + self.assertNotEqual(result.returncode,0) + self.assertEqual(result.stdout,b"") + self.assertIn(b"ValueError:",result.stderr) + + def test_false_review_status_scope_and_ids_are_rejected_under_optimization(self): + for mutation in ("review","status","ci","boolean_ci","producer","reviewer"): + record=case() + producer=111 + reviewer=333 + if mutation=="review":record["receipt"]["independent_security_review_authenticated"]=False + elif mutation=="status":record["receipt"]["status"]="FORGED_SUCCESS" + elif mutation=="ci":record["receipt"]["required_ci_successful"]=12 + elif mutation=="boolean_ci":record["receipt"]["required_ci_successful"]=False + elif mutation=="producer":producer=0 + elif mutation=="reviewer":reviewer=0 + with self.subTest(mutation=mutation): + result=run_synthetic(record,producer,reviewer) + self.assertNotEqual(result.returncode,0) + self.assertEqual(result.stdout,b"") + self.assertIn(b"ValueError:",result.stderr) + + def test_child_contains_no_optimization_sensitive_assert_nodes(self): + self.assertEqual( + [node for node in ast.walk(ast.parse(inheritance.CHILD)) + if isinstance(node,ast.Assert)],[]) diff --git a/verification/research/test_pr113_legacy20_choice_delivery.py b/verification/research/test_pr113_legacy20_choice_delivery.py new file mode 100644 index 00000000..f486738e --- /dev/null +++ b/verification/research/test_pr113_legacy20_choice_delivery.py @@ -0,0 +1,282 @@ +"""Authenticated legacy reviewer choice binding; synthetic fixtures only.""" +import copy +import json +import unittest +import os +from unittest import mock + +import pr113_legacy20_choice_delivery as deliver +from pr113_legacy20_runtime_resolution import FORMAT_REPORT + + +H = "a" * 40 +IDS = {"run_id": 100, "run_attempt": 1, "job_id": 200, + "check_run_id": 300, "artifact_id": 400} +AUTHOR = "HMarcusWH" +REVIEWER = "external-math-reviewer" + + +def fixture(): + options = [] + for k in range(1, 21): + cid = f"C-{k:02}" + options.append({"case_id": cid, "options": [{ + "case_id": cid, "test_index": k, "observation_id": f"event-1-{k:06}", + "producing_artifact_id": 500+k, + "test_method": f"test_{k}", "original_provider": "P.lean", + }]}) + report = { + "format": FORMAT_REPORT, + "status": "ACTUAL_CAPTURE_OPTIONS_UNREVIEWED", + "source_head": H, "original_legacy_case_count": 20, + "cases": options, "all_cases_have_candidates": True, + "independent_case_review": False, "mathematical_acceptance": False, + } + member = (json.dumps(report, sort_keys=True)+"\n").encode() + # Real transport accepts only an original bounded ZIP. + import io, zipfile + buf=io.BytesIO() + with zipfile.ZipFile(buf,"w",zipfile.ZIP_DEFLATED) as z: + z.writestr(deliver.SURVEY_MEMBER,member) + raw = buf.getvalue() + att = { + "format":deliver.REVIEW_FORMAT, + "status":"RELEVANCE_CHOICES_ONLY_NOT_CASE_ACCEPTANCE", + "head_sha":H, + "survey_zip_sha256":deliver.digest(raw), + "survey_member_sha256":deliver.digest(member), + "observation_artifact_id":400, + "cases":[{k:opt[k] for k in deliver.SELECTION_FIELDS} + for entry in options for opt in entry["options"]], + } + comment = { + "id": 700, + "issue_url":"https://api.github.com/repos/HMarcusWH/PermanssonLean/issues/113", + "created_at":"2026-10-25T01:00:00Z", + "updated_at":"2026-10-25T01:00:00Z", + "user":{"login":REVIEWER}, + "body":deliver.PREFIX+json.dumps(att,sort_keys=True), + } + api={ + "/actions/runs/100":{ + "id":100,"head_sha":H, + "head_branch":"research/final-mathematical-closure", + "status":"completed","conclusion":"success", + "run_attempt":1,"event":"push", + "path":deliver.ACTIVE_WORKFLOW, + }, + "/actions/runs/100/jobs?per_page=100":{"jobs":[{ + "id":200,"run_id":100,"run_attempt":1,"head_sha":H, + "name":deliver.SURVEY_JOB,"status":"completed","conclusion":"success", + "check_run_url":"https://api.github.com/check-runs/300", + "started_at":"2026-10-25T00:00:00Z", + "completed_at":"2026-10-25T00:30:00Z", + }]}, + "/check-runs/300":{ + "id":300,"name":deliver.SURVEY_JOB,"head_sha":H, + "status":"completed","conclusion":"success", + "app":{"slug":"github-actions"}, + }, + "/actions/artifacts/400":{ + "id":400,"name":deliver.SURVEY_ARTIFACT,"expired":False, + "digest":"sha256:"+deliver.digest(raw), + "created_at":"2026-10-25T00:20:00Z", + "workflow_run":{"id":100,"head_sha":H}, + }, + "/issues/comments/700":comment, + "/pulls/113":{"number":113,"state":"open","merged":False, + "head":{"sha":H,"ref":"research/final-mathematical-closure"}, + "base":{"sha":deliver.B113}, + "user":{"login":AUTHOR}}, + } + policy={"format":"Permansson.PR113.LegacyEventReviewerAuthority.v1", + "status":"OWNER_AUTHORIZED","owner":AUTHOR, + "reviewers":[{"login":REVIEWER,"approved_by":AUTHOR, + "independence_disclosed":True}]} + return raw, member, api, policy, att + + +class LegacyDeliveryTests(unittest.TestCase): + def run_delivery(self, modify=None, *, cert=False): + raw, member, api, policy, att = fixture() + env = { + "GITHUB_ACTIONS": "true", + "GITHUB_REPOSITORY": "HMarcusWH/PermanssonLean", + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_SHA": H, + "GITHUB_REF": "refs/heads/research/final-mathematical-closure", + "PERMANSSON_CERT_PR_NUMBER": "", + } + cert_base, historical_head, merged = ("d"*40, "b"*40, "c"*40) + if cert: + env["GITHUB_REF"] = "refs/heads/research/cert-real" + env["PERMANSSON_CERT_PR_NUMBER"] = "115" + api["/actions/runs/100"]["head_branch"] = "research/cert-real" + api["/actions/runs/100"]["event"] = "workflow_dispatch" + api["/issues/comments/700"]["issue_url"] = ( + "https://api.github.com/repos/HMarcusWH/PermanssonLean/issues/115") + api["/pulls/113"] = { + "number":113, "state":"closed", "merged":True, + "head":{"sha":historical_head}, "merge_commit_sha":merged, + } + api["/pulls/115"] = { + "number":115, "state":"open", "merged":False, + "base":{"ref":"main","sha":cert_base}, + "head":{"sha":H,"ref":"research/cert-real", + "repo":{"full_name":"HMarcusWH/PermanssonLean"}}, + "user":{"login":AUTHOR}, + } + api["/branches/main"] = {"commit":{"sha":cert_base}} + if modify: modify(raw,member,api,policy,att) + def git(*args): + if args==("rev-parse","HEAD"): + return H + if args==("show",H+":"+deliver.POLICY): + return json.dumps(policy) + if args[:2]==("cat-file","-t"): + return "commit" + if args==("show","-s","--format=%P",merged): + return deliver.B113 + " " + historical_head + if args[0]=="rev-parse" and args[1].endswith("^{tree}"): + return "reviewed_tree" + if args[:2]==("merge-base","--is-ancestor"): + return "" + raise AssertionError(args) + # Selection matching is independently checked by original code. + with mock.patch.object(deliver,"decision_to_selection", + return_value={"cases":[]}) as binder: + result=deliver.deliver(H,IDS,raw,700,git=git, + live=api.__getitem__,env=env) + self.assertEqual(binder.call_count,1) + return result + + def test_full_genuine_selection_transport_remains_unapproved(self): + result = self.run_delivery() + selection=result["selection"] + self.assertEqual(len(selection["cases"]),20) + self.assertFalse(selection["independent_case_review"]) + self.assertFalse(selection["mathematical_acceptance"]) + self.assertFalse(result["diagnostic"]["publication_authorized"]) + + def test_real_cert_target_binds_review_to_actual_successor(self): + result = self.run_delivery(cert=True) + self.assertEqual(result["diagnostic"]["reviewed_pr_number"], 115) + self.assertEqual(result["diagnostic"]["reviewed_branch"], + "research/cert-real") + self.assertFalse(result["diagnostic"]["mathematical_acceptance"]) + + def test_cert_must_reject_old_pr113_issue_and_stale_survey_branch(self): + def wrong_issue(raw, member, api, policy, att): + api["/issues/comments/700"]["issue_url"] = ( + "https://api.github.com/repos/HMarcusWH/PermanssonLean/issues/113") + with self.assertRaises(deliver.ChoiceDeliveryError): + self.run_delivery(wrong_issue, cert=True) + def wrong_source(raw, member, api, policy, att): + api["/actions/runs/100"]["head_branch"] = ( + "research/final-mathematical-closure") + with self.assertRaises(deliver.ChoiceDeliveryError): + self.run_delivery(wrong_source, cert=True) + + def test_incomplete_or_unobserved_reviewer_selection_fails(self): + def missing(raw,member,api,policy,att): + att["cases"].pop() + api["/issues/comments/700"]["body"]=deliver.PREFIX+json.dumps(att) + with self.assertRaises(deliver.ChoiceDeliveryError): + self.run_delivery(missing) + def fake(raw,member,api,policy,att): + att["cases"][0]["observation_id"]="event-999-000001" + api["/issues/comments/700"]["body"]=deliver.PREFIX+json.dumps(att) + with self.assertRaises(deliver.ChoiceDeliveryError): + self.run_delivery(fake) + + def test_author_self_review_and_unauthorized_reviewer_fail(self): + def author(raw,member,api,policy,att): + api["/issues/comments/700"]["user"]["login"]=AUTHOR + with self.assertRaises(deliver.ChoiceDeliveryError): + self.run_delivery(author) + def no_policy(raw,member,api,policy,att): + policy["status"]="UNCONFIGURED" + with self.assertRaises(deliver.ChoiceDeliveryError): + self.run_delivery(no_policy) + + def test_wrong_survey_artifact_source_and_early_review_fail(self): + for mutate in ( + lambda api,att: api["/actions/runs/100"].update(conclusion="failure"), + lambda api,att: api["/actions/runs/100/jobs?per_page=100"]["jobs"][0].update(conclusion="failure"), + lambda api,att: api["/actions/artifacts/400"].update(digest="sha256:"+"0"*64), + lambda api,att: api["/actions/artifacts/400"].update(expired=True), + lambda api,att: api["/issues/comments/700"].update(created_at="2026-10-24T01:00:00Z"), + lambda api,att: att.update(head_sha="b"*40), + ): + def bad(raw,member,api,policy,att): + mutate(api,att) + api["/issues/comments/700"]["body"]=deliver.PREFIX+json.dumps(att) + with self.subTest(mutate=mutate),self.assertRaises( + deliver.ChoiceDeliveryError): + self.run_delivery(bad) + + def test_post_edit_reviewer_comment_is_never_source_authority(self): + for edited in ( + "2026-10-25T01:00:01Z", + "2026-10-25T09:30:00Z", + None, + ): + def tamper(raw,member,api,policy,att): + api["/issues/comments/700"]["updated_at"]=edited + with self.subTest(edited=edited), self.assertRaises( + deliver.ChoiceDeliveryError): + self.run_delivery(tamper) + + + def test_duplicate_reviewer_attestation_fields_are_rejected(self): + def duplicate(raw, member, api, policy, att): + text = json.dumps(att, sort_keys=True) + needle = '"status": "RELEVANCE_CHOICES_ONLY_NOT_CASE_ACCEPTANCE"' + self.assertIn(needle, text) + text = text.replace(needle, '"status": "FORGED_APPROVAL", ' + needle) + api["/issues/comments/700"]["body"] = deliver.PREFIX + text + with self.assertRaises(deliver.ChoiceDeliveryError): + self.run_delivery(duplicate) + + def test_reviewer_choice_output_rejects_parent_traversal_without_delivery(self): + import tempfile + from pathlib import Path + from unittest import mock + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / "unused.zip").write_bytes(b"PK-not-real") + (root / "out").mkdir() + args = [ + "--head", H, "--survey-zip", str(root / "unused.zip"), + "--run-id", "100", "--run-attempt", "1", "--job-id", "200", + "--check-run-id", "300", "--artifact-id", "400", + "--review-comment-id", "700", + "--out", str(root / "out" / ".." / "unexpected"), + ] + with mock.patch.object(deliver, "deliver") as producer: + self.assertEqual(deliver.main(args), 2) + producer.assert_not_called() + self.assertFalse((root / "unexpected").exists()) + + def test_reviewer_comment_body_digest_retained_but_unapproved(self): + result=self.run_delivery() + diagnostic=result["diagnostic"] + self.assertRegex(diagnostic["review_comment_body_sha256"],r"^[0-9a-f]{64}$") + self.assertEqual(diagnostic["review_comment_created_at"], + "2026-10-25T01:00:00Z") + self.assertFalse(diagnostic["mathematical_acceptance"]) + + def test_foreign_issue_or_stale_PR_rejected(self): + def wrong(raw,member,api,policy,att): + api["/issues/comments/700"]["issue_url"] = ( + "https://api.github.com/repos/HMarcusWH/PermanssonLean/issues/114") + with self.assertRaises(deliver.ChoiceDeliveryError): + self.run_delivery(wrong) + def stale(raw,member,api,policy,att): + api["/pulls/113"]["head"]["sha"]="b"*40 + with self.assertRaises(deliver.ChoiceDeliveryError): + self.run_delivery(stale) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_legacy20_runtime_resolution.py b/verification/research/test_pr113_legacy20_runtime_resolution.py new file mode 100644 index 00000000..14714ff7 --- /dev/null +++ b/verification/research/test_pr113_legacy20_runtime_resolution.py @@ -0,0 +1,196 @@ +"""Static-route/runtime-event separation: adversarial Python-only tests.""" +import copy +import hashlib +import json +import unittest +from unittest import mock + +import pr113_legacy20_runtime_resolution as r + +H="a"*40 + + +def digest(raw):return hashlib.sha256(raw).hexdigest() + + +def row(cid): + return { + "case_id":cid,"test_path":"verification/research/test_origin.py", + "test_method":"test_negative","original_provider": + "PermanssonResearch/Test/Original.lean", + } + + +def events(): + return {"C-%02d"%i:[{ + "case_id":"C-%02d"%i,"test_index":0, + "test_method":"test_negative", + "original_provider":"PermanssonResearch/Test/Original.lean", + "observation_id":"event-123-000001", + "producing_artifact_id":123, + "observation_sha256":"0"*64, + "false_input_sha256":"1"*64, + "unreviewed":True, + }] for i in range(1,21)} + + +def selections(options): + return {"format":r.FORMAT_DECISION,"head_sha":H, + "status":"DETACHED_EVENT_CHOICES_UNREVIEWED", + "independent_case_review":False, + "mathematical_acceptance":False, + "cases":[{ + "case_id":cid, + "test_index":options[cid][0]["test_index"], + "observation_id":options[cid][0]["observation_id"], + "producing_artifact_id":options[cid][0]["producing_artifact_id"], + } for cid in sorted(options)]} + + +class Source: + def __init__(self): + self.cases={} + def blob(self,path): + return b"import unittest\nclass T(unittest.TestCase):\n def test_negative(self):\n pass\n" + + +def original_case_map(_head): + cases={} + for cid in events(): + cases[cid]={ + "case_id":cid, + "test_method_candidates":[{ + "path":"verification/research/test_origin.py", + "method":"test_negative","role":"SEMANTIC_NEGATIVE", + }], + "historical_proof_declarations":[{ + "path":"PermanssonResearch/Test/Original.lean", + }], + "new_claim_source_candidates":[], + } + return cases,tuple(cases) + + +class LegacyEventDecisionTests(unittest.TestCase): + def test_static_intent_cannot_contain_future_artifact_ids(self): + intents={"format":r.FORMAT_INTENT, + "source_head_binding":"ACTUAL_CI_HEAD_AT_RUN", + "review_status":"REQUIRES_INDEPENDENT_MATHEMATICAL_REVIEW", + "cases":[row(cid) for cid in sorted(events())]} + with mock.patch.object(r,"original_case_map",side_effect=original_case_map): + mapping=r.parse_intent(intents,H,Source()) + self.assertEqual(len(mapping),20) + self.assertFalse(any("artifact_id" in x for x in mapping.values())) + + def test_unknown_method_provider_or_duplicate_fails_static_intent(self): + default={"format":r.FORMAT_INTENT, + "source_head_binding":"ACTUAL_CI_HEAD_AT_RUN", + "review_status":"REQUIRES_INDEPENDENT_MATHEMATICAL_REVIEW", + "cases":[row(cid) for cid in sorted(events())]} + with mock.patch.object(r,"original_case_map",side_effect=original_case_map): + for mutation in ("badmethod","provider","duplicate","futureid","review"): + payload=copy.deepcopy(default) + if mutation=="badmethod":payload["cases"][0]["test_method"]="test_unrelated" + if mutation=="provider":payload["cases"][0]["original_provider"]="PermanssonResearch/Other/Foreign.lean" + if mutation=="duplicate":payload["cases"][0]=payload["cases"][1] + if mutation=="futureid":payload["cases"][0]["artifact_id"]=123 + if mutation=="review":payload["review_status"]="APPROVED" + with self.subTest(mutation=mutation): + with self.assertRaises(r.RuntimeLegacyError): + r.parse_intent(payload,H,Source()) + + def test_detached_choices_only_select_observed_event_ids(self): + candidates=events() + choices=selections(candidates) + result=r.decision_to_selection(H,candidates,choices) + self.assertEqual(result["format"],r.FORMAT_SELECTION) + self.assertEqual(len(result["cases"]),20) + self.assertEqual(result["cases"][0]["test_method"],"test_negative") + self.assertTrue(all(set(row)=={ + "case_id","test_index","test_method","original_provider", + "observation_id","producing_artifact_id", + } for row in result["cases"])) + + def test_forged_or_stale_detached_choices_do_not_pass(self): + c=events() + for name,edit in ( + ("stale-head",lambda v:v.update(head_sha="b"*40)), + ("approval",lambda v:v.update(mathematical_acceptance=True)), + ("wrong-id",lambda v:v["cases"][0].update( + observation_id="event-999-999999")), + ("wrong-artifact",lambda v:v["cases"][0].update( + producing_artifact_id=7777)), + ("duplicate-case",lambda v:v["cases"][0].update( + case_id=v["cases"][1]["case_id"])), + ("missing-case",lambda v:v["cases"].pop()), + ): + value=selections(c) + edit(value) + with self.subTest(name=name),self.assertRaises(r.RuntimeLegacyError): + r.decision_to_selection(H,c,value) + + def test_exact_original_github_capture_and_semantic_stderr_are_required(self): + cid="C-01" + test="verification/research/test_origin.py" + test_bytes=b"python original method" + source=mock.Mock() + source.blob.return_value=test_bytes + obs=b"error: type mismatch\n" + original=b"captured false theorem input" + prefix=cid+"/test-00/raw_compiler/event-1-000001/" + members={ + cid+"/test-00/unreviewed_test_manifest.json":json.dumps({ + "format":"Permansson.LegacyCaseTestExecutionCandidate.v1", + "case_id":cid,"test_file":test, + "selected_methods":["test_negative"], + "source_test_sha256":digest(test_bytes), + "status":"EXECUTED_UNREVIEWED_NOT_AUTHENTICATED", + "test_exit_code":0, + }).encode(), + prefix+"observation.json":json.dumps({ + "format":"Permansson.LegacyLeanCompilerObservation.v1", + "case_id_unverified":cid, + "selected_method_unverified":"test_negative", + "status":"RAW_UNREVIEWED", + "returncode":1, + "source_sha256":digest(original), + "stderr_sha256":digest(obs), + }).encode(), + prefix+"source.lean":original, + prefix+"stderr.log":obs, + } + authenticated={"producers":{123:{"members":members}}} + actual=r.enumerate_events(H,{cid:row(cid)},source,authenticated,{123}) + self.assertEqual(len(actual[cid]),1) + members[prefix+"stderr.log"]=b"error: parser error\n" + bad=r.enumerate_events(H,{cid:row(cid)},source,authenticated,{123}) + self.assertEqual(bad[cid],[]) + + + def test_detached_legacy21_directories_reject_path_traversal_and_symlinks(self): + import tempfile + from pathlib import Path + with tempfile.TemporaryDirectory() as tmp: + root=Path(tmp) + original=root/"original" + original.mkdir() + self.assertEqual(r.original_detached_directory(original), + original.resolve()) + parent=root/"parent" + parent.mkdir() + with self.assertRaises(r.RuntimeLegacyError): + r.original_detached_directory(parent/".."/"original") + link=root/"alias" + link.symlink_to(original,target_is_directory=True) + with self.assertRaises(r.RuntimeLegacyError): + r.original_detached_directory(link) + + def test_duplicate_selected_legacy21_source_receipt_is_not_authority(self): + from pr113_boundary_io import strict_json_object, BoundaryError + with self.assertRaises(BoundaryError): + strict_json_object( + b'{"head_sha":"old","head_sha":"forged","cases":[]}') + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_lifecycle_source_v3.py b/verification/research/test_pr113_lifecycle_source_v3.py new file mode 100644 index 00000000..23201ca4 --- /dev/null +++ b/verification/research/test_pr113_lifecycle_source_v3.py @@ -0,0 +1,129 @@ +"""Source event topology adversaries. These tests never approve historical CI.""" +import json +import unittest + +from pr113_lifecycle_source_v3 import actual_source, LifecycleError +from release_target import B113 + +H113 = "b" * 40 +S113 = "c" * 40 +M113 = "d" * 40 +NEXT_H = "e" * 40 +NEXT_S = "f" * 40 +LATER_M = "a" * 40 + + +class LifecycleTests(unittest.TestCase): + def example(self, *, number=113, event_name="pull_request"): + self.current = S113 if number == 113 else NEXT_S + self.base = B113 if number == 113 else M113 + self.head = H113 if number == 113 else NEXT_H + if event_name == "push": + self.current = LATER_M + self.env = { + "GITHUB_ACTIONS": "true", + "GITHUB_REPOSITORY": "HMarcusWH/PermanssonLean", + "GITHUB_EVENT_NAME": event_name, + "GITHUB_REF": ("refs/pull/" + str(number) + "/merge" + if event_name == "pull_request" else "refs/heads/main"), + "GITHUB_SHA": self.current, + "GITHUB_JOB": "research", + "GITHUB_WORKFLOW": "Research Lean", + "GITHUB_RUN_ID": "1000", + "GITHUB_RUN_ATTEMPT": "1", + } + self.pr = { + "number": number, + "repository": {"full_name": "HMarcusWH/PermanssonLean"}, + "pull_request": { + "base": {"ref": "main", "sha": self.base, + "repo": {"full_name": "HMarcusWH/PermanssonLean"}}, + "head": {"ref": ("research/final-mathematical-closure" + if number == 113 else "research/cert-proof"), + "sha": self.head, + "repo": {"full_name": "HMarcusWH/PermanssonLean"}}, + }, + } + if event_name == "push": + self.pr = {"repository": {"full_name": "HMarcusWH/PermanssonLean"}, + "after": self.current, "ref": "refs/heads/main"} + + def git(self, *arguments): + if arguments == ("rev-parse", "HEAD"): + return self.current + if arguments[0:2] == ("cat-file", "-t"): + return "commit" + if arguments == ("show", "-s", "--format=%P", M113): + return B113 + " " + H113 + if arguments == ("show", "-s", "--format=%P", self.current): + return self.base + " " + self.head + if arguments[0:2] == ("rev-parse", M113 + "^{tree}"): + return "x" + if arguments[0:2] == ("rev-parse", H113 + "^{tree}"): + return "x" + if arguments[0:2] == ("rev-parse", self.current + "^{tree}"): + return "x" + if arguments[0:2] == ("rev-parse", self.head + "^{tree}"): + return "x" + if arguments[0:2] == ("merge-base", "--is-ancestor"): + return "" if arguments[2] in (M113, LATER_M) else "wrong" + raise ValueError("unexpected Git probe: " + repr(arguments)) + + def live(self, path): + if path == "/branches/main": + return {"commit": {"sha": (self.base + if self.env["GITHUB_EVENT_NAME"] == "pull_request" + else self.current)}} + if path == "/pulls/113": + if self.base == B113: + return {"number": 113, "merged": False, + "base": {"sha": B113}, "head": {"sha": H113}} + return {"number": 113, "state": "closed", "merged": True, + "head": {"sha": H113}, "merge_commit_sha": M113} + if path.startswith("/pulls/"): + return {"number": int(path.rsplit("/", 1)[1]), "merged": False, + "base": {"sha": self.base}, "head": {"sha": self.head}} + raise ValueError(path) + + def invoke(self): + return actual_source(env=self.env, + event_bytes=json.dumps(self.pr).encode(), + git=self.git, live=self.live) + + def test_original_required_PR113_is_source_only(self): + self.example() + receipt = self.invoke() + self.assertEqual(receipt["phase"], "initial_113_pr") + self.assertFalse(receipt["required_ci_accepted"]) + + def test_successor_requires_authentic_merge_ancestry(self): + self.example(number=115) + self.assertEqual(self.invoke()["phase"], "main_descendant_pr") + old_git = self.git + def invalid_git(*args): + if args[:2] == ("merge-base", "--is-ancestor"): + return "not an ancestor" + return old_git(*args) + with self.assertRaises(LifecycleError): + actual_source(env=self.env, event_bytes=json.dumps(self.pr).encode(), + git=invalid_git, live=self.live) + + def test_current_main_descendant_is_not_misclassified_as_M113(self): + self.example(number=115, event_name="push") + receipt = self.invoke() + self.assertEqual(receipt["phase"], "main_descendant_push") + self.assertFalse(receipt["publication_authorized"]) + + def test_foreign_repo_and_wrong_pr_ref_reject(self): + self.example(number=115) + self.pr["repository"]["full_name"] = "attacker/forged" + with self.assertRaises(LifecycleError): + self.invoke() + self.pr["repository"]["full_name"] = "HMarcusWH/PermanssonLean" + self.env["GITHUB_REF"] = "refs/pull/113/merge" + with self.assertRaises(LifecycleError): + self.invoke() + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_mandatory_v31.py b/verification/research/test_pr113_mandatory_v31.py new file mode 100644 index 00000000..b0ec5a61 --- /dev/null +++ b/verification/research/test_pr113_mandatory_v31.py @@ -0,0 +1,262 @@ +"""Adversarial mandatory v3.1 gate tests; mock CI is NOT evidence.""" +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +import final_closure_gate as gate +import final_closure_verify as verify +import pr113_mandatory_v31 as v31 + + +H = "a" * 40 +M = "b" * 40 +CLAIMS = ("Q1", "Q2", "Q3", "M1", "M2", "M3", "N1", + "N2", "N3", "D8", "D9") + + +def mocked_owners(): + return ( + [{"kind": "original_positive", "owner_id": f"C-{i:02}"} + for i in range(1, 46)] + + [{"kind": "legacy_negative", "owner_id": f"C-{i:02}"} + for i in range(1, 21)] + + [{"kind": "claim_pair", "owner_id": cid} for cid in CLAIMS] + ) + + +class MandatoryV31Tests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory(prefix="pr113-v31-mock-") + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) + self.zips = self.root / "original-zips" + self.zips.mkdir() + for id_ in (101, 102, 103): + (self.zips / (str(id_) + ".zip")).write_bytes(b"TEST_NOT_REAL_ZIP") + self.contract = self.root / "contract.json" + self.contract.write_text(json.dumps({"head_sha": H})) + self.blobs = self.root / "blobs.json" + self.blobs.write_text(json.dumps({"stub": "source-oid"})) + self.original = { + "format": "Permansson.FinalClosureDetachedReceipts.v3", + "head_sha": H, + "evidence_bundle_sha256": "d" * 64, + } + + def run_mocked_live_auth(self, *, bad_count=False): + owners = mocked_owners() + if bad_count: + owners.pop() + attributed = { + "status": "ALL_ADDITIONAL_CONTROLS_SOURCE_ATTRIBUTED_UNREVIEWED", + "head_sha": H, + "source_owned_controls": owners, + "schema_security_approved": False, + "independent_mathematical_review": False, + } + executed = { + "status": "ACTUAL_CONTROL_BYTES_REVALIDATED_UNREVIEWED", + "source_owner_count": 76, + "claim_positive_negative_directions": 22, + "canonical_routes_bound": True, + "legacy_original_captures_bound": True, + "execution_log_members_checked": 208, + } + claims = {"claims": [{"claim_id": x} for x in CLAIMS]} + legacy_cases = {f"C-{i:02}": {} for i in range(1, 46)} + with mock.patch.object(v31, "git", return_value="commit"), \ + mock.patch.object(v31, "authenticate_v3_sources", + return_value={ + "status": "REAL_PRODUCING_BYTES_UNREVIEWED", + "head_sha": H, "producer_count": 21, + }), \ + mock.patch.object(v31, "original_case_map", + return_value=(legacy_cases, set(list(legacy_cases)[:20]))), \ + mock.patch.object(v31, "committed_file_bytes", + return_value=json.dumps(claims).encode()), \ + mock.patch.object(v31, "candidate_registry", + return_value=[{"artifact_id": k} + for k in (101, 102, 103)]), \ + mock.patch.object(v31, "authenticated_extra_controls", + return_value=attributed) as authentic, \ + mock.patch.object(v31, "exact_source", + return_value="exact-H-routes"), \ + mock.patch.object(v31, "validate_executed_controls", + return_value=executed) as logs: + value = v31.authenticate_all_76( + H, self.original, {}, + additional_contract=self.contract, + additional_zips=self.zips, + workflow_blob_candidates=self.blobs) + self.assertEqual(authentic.call_count, 1) + self.assertEqual(logs.call_count, 1) + self.assertEqual(logs.call_args.kwargs["source"], + "exact-H-routes") + self.assertEqual( + logs.call_args.kwargs["original_producers"]["producer_count"], 21) + return value + + def test_full_76_owners_still_do_not_self_certify(self): + result = self.run_mocked_live_auth() + self.assertEqual(result["additional_owners"], 76) + self.assertEqual(result["original_artifacts"], 21) + self.assertEqual(result["claim_directions"], 22) + self.assertIs(result["independent_math_approval"], False) + self.assertIs(result["publication_authorized"], False) + + def test_75_owners_fails_even_with_mocked_green_github(self): + with self.assertRaises(v31.MandatoryV31Error): + self.run_mocked_live_auth(bad_count=True) + + def test_missing_original_producer_zip_fails_closed(self): + (self.zips / "102.zip").unlink() + with self.assertRaises(v31.MandatoryV31Error): + self.run_mocked_live_auth() + + def test_wrong_exact_source_commit_fails_before_github(self): + self.original["head_sha"] = "c" * 40 + with self.assertRaises(v31.MandatoryV31Error): + self.run_mocked_live_auth() + + def test_premerge_full_v3_only_cannot_claim_complete(self): + receipts = { + "format": gate.FORMAT_V3, + "phase": "premerge_review", "head_sha": H, + "merged_main_sha": None, + "evidence_bundle_sha256": "d" * 64, + } + verified = { + "accepted_case_count": 45, + "accepted_claim_count": 11, + "effective_cases": {}, + "effective_claims": [], + } + with mock.patch.object(gate, "validate_overlay"), \ + mock.patch.object(gate, "verify_premerge", + return_value={ + "H": H, "S": "e" * 40, + "checks": {}, "review_scopes": {}, + }), \ + mock.patch.object(gate, "load_detached_bundle", + return_value={}), \ + mock.patch.object(gate, "effective_acceptance", + return_value=verified), \ + mock.patch.object(gate, "read_json", + side_effect=[ + {"cases": []}, {"claims": []}, + {"cases": []}, {"claims": []}, + ]), \ + mock.patch.object(gate, "require_full_source", + return_value={}), \ + mock.patch.object(gate, "require_complete_obligation_receipts", + return_value={}): + with self.assertRaisesRegex(gate.GateError, "76 real v3.1"): + gate.premerge(receipts, None, full=True) + + def test_postmerge_full_release_rejects_v3_only_payload(self): + source = { + "format": verify.DETACHED_V3, + "phase": "postmerge_acceptance", "head_sha": H, + "evidence_bundle_sha256": "d" * 64, + } + with mock.patch.object(verify, "validate_overlay"), \ + mock.patch.object(verify, "audit", return_value={}), \ + mock.patch.object(verify, "verify_merged_v2", + return_value={ + "H": H, "M": M, "S": "c" * 40, + "checks": {}, "review_scopes": {}, + }), \ + mock.patch.object(verify, "load_detached_bundle", + return_value={}), \ + mock.patch.object(verify, "read_json", + side_effect=[ + {"cases": []}, {"claims": []}, + ]), \ + mock.patch.object(verify, "effective_acceptance", + return_value={ + "accepted_case_count": 45, + "accepted_claim_count": 11, + }): + with self.assertRaisesRegex(verify.GateError, "76 original v3.1"): + verify.strict(source, "full-release-strict", None, + bundle_path=self.root / "fake-original.zip") + + + def test_public_76_gate_calls_real_validator_with_original_legacy_captures(self): + """Synthetic data; only external GitHub I/O is stubbed, not validation.""" + from test_pr113_v31_execution_integrity import ( + CASE_IDS, complete, source, authentic_original_legacy, + ) + contract, binaries = complete() + original = authentic_original_legacy() + for path in self.zips.glob("*.zip"): + path.unlink() + for artifact_id, raw in binaries.items(): + (self.zips / f"{artifact_id}.zip").write_bytes(raw) + self.contract.write_text(json.dumps(contract)) + case_map = {cid: {} for cid in CASE_IDS} + claims = {"claims": [{"claim_id": cid} for cid in CLAIMS]} + attributed = { + "status": "ALL_ADDITIONAL_CONTROLS_SOURCE_ATTRIBUTED_UNREVIEWED", + "head_sha": H, + "source_owned_controls": mocked_owners(), + "schema_security_approved": False, + "independent_mathematical_review": False, + } + with mock.patch.object(v31, "git", return_value="commit"), \ + mock.patch.object(v31, "authenticate_v3_sources", + return_value=original), \ + mock.patch.object(v31, "original_case_map", + return_value=(case_map, set(CASE_IDS[:20]))), \ + mock.patch.object(v31, "committed_file_bytes", + return_value=json.dumps(claims).encode()), \ + mock.patch.object(v31, "candidate_registry", + return_value=contract["producers"]), \ + mock.patch.object(v31, "authenticated_extra_controls", + return_value=attributed), \ + mock.patch.object(v31, "exact_source", return_value=source()), \ + mock.patch.object(v31, "validate_executed_controls", + wraps=v31.validate_executed_controls) as validator: + result = v31.authenticate_all_76( + H, self.original, {}, + additional_contract=self.contract, + additional_zips=self.zips, + workflow_blob_candidates=self.blobs) + self.assertEqual(result["additional_owners"], 76) + self.assertEqual(validator.call_count, 1) + self.assertIs( + validator.call_args.kwargs["original_producers"], original) + self.assertFalse(result["publication_authorized"]) + + captured = original["producers"][555]["members"] + key = "C-01/test-00/raw_compiler/event-1-000001/source.lean" + captured[key] = b"forged old Lean capture" + with self.assertRaises(v31.MandatoryV31Error): + v31.authenticate_all_76( + H, self.original, {}, + additional_contract=self.contract, + additional_zips=self.zips, + workflow_blob_candidates=self.blobs) + + + + def test_owner_control_sources_reject_symlinked_ancestors(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + real = root / "real" + real.mkdir() + source = real / "owner.json" + source.write_text("{}") + link = root / "alias" + link.symlink_to(real, target_is_directory=True) + with self.assertRaises(v31.MandatoryV31Error): + v31.external(link / "owner.json") + with self.assertRaises(v31.MandatoryV31Error): + v31.external(root / "real" / ".." / "real" / "owner.json") + self.assertEqual(v31.external(source), source) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_original21_live_catalog.py b/verification/research/test_pr113_original21_live_catalog.py new file mode 100644 index 00000000..6fbeac35 --- /dev/null +++ b/verification/research/test_pr113_original21_live_catalog.py @@ -0,0 +1,213 @@ +"""Adversarial runtime catalog test: no one can precommit future GH artifact IDs.""" +import hashlib +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +import pr113_original21_live_catalog as source + +H="a"*40 + + +def fixture(): + runs={};jobs={};artifacts={};checks=[];binary={} + for i,(kind,spec) in enumerate(sorted(source.KINDS.items())): + runid=100+i + runs[runid]={ + "id":runid,"head_sha":H, + "head_branch":"research/final-mathematical-closure", + "event":"push","status":"completed","conclusion":"success", + "run_attempt":2,"path":spec["workflow"], + } + jobs[runid]=[];artifacts[runid]=[] + for shard in range(spec["count"]): + jobid=1000+100*i+shard + checkid=2000+100*i+shard + artifactid=3000+100*i+shard + artifact_name=spec["artifact"]+str(shard) + raw=b"Original-source-opaque-unreviewed-"+artifact_name.encode() + binary[artifactid]=raw + check={ + "id":checkid,"head_sha":H, + "app":{"slug":"github-actions"}, + "name":spec["job"]+f" ({shard})", + "status":"completed","conclusion":"success", + "details_url": + f"https://github.com/HMarcusWH/PermanssonLean/actions/runs/{runid}/job/{jobid}", + } + checks.append(check) + jobs[runid].append({ + "id":jobid,"name":check["name"], + "head_sha":H,"run_id":runid,"run_attempt":2, + "status":"completed","conclusion":"success", + "check_run_url": + f"https://api.github.com/repos/HMarcusWH/PermanssonLean/check-runs/{checkid}", + "started_at":"2026-10-24T12:00:00Z", + "completed_at":"2026-10-24T12:30:00Z", + }) + artifacts[runid].append({ + "id":artifactid,"name":artifact_name, + "expired":False, + "digest":"sha256:"+hashlib.sha256(raw).hexdigest(), + "workflow_run":{"id":runid,"head_sha":H}, + "created_at":"2026-10-24T12:05:00Z", + }) + return checks,runs,jobs,artifacts,binary + + +class ActualSourceProducerCatalogTests(unittest.TestCase): + def test_full_21_matrix_maps_are_exact(self): + checks,*_=fixture() + latest=source.classify_latest(checks,H) + self.assertEqual(len(latest),21) + self.assertEqual( + len([key for key in latest if key[0]=="compiler"]),4) + self.assertEqual( + len([key for key in latest if key[0]=="append"]),9) + self.assertEqual( + len([key for key in latest if key[0]=="legacy"]),8) + + def test_missing_one_or_latest_failed_never_borrows_old_success(self): + checks,*_=fixture() + with self.assertRaises(source.DiscoveryError): + source.classify_latest(checks[1:],H) + checks,*_=fixture() + latest=dict(checks[0],id=checks[0]["id"]+100000, + conclusion="failure") + with self.assertRaises(source.DiscoveryError): + source.classify_latest(checks+[latest],H) + + def test_wrong_head_job_attempt_or_workflow_rejected(self): + checks,runs,jobs,artifacts,binary=fixture() + first=checks[0];rid=int(source.RUN_URL.fullmatch( + first["details_url"]).group(1)) + kind,shard=next((key for key in source.classify_latest(checks,H) + if source.classify_latest(checks,H)[key] is first)) + for corruption in ("head","attempt","workflow","job","artifact"): + r=dict(runs[rid]);j=[dict(x) for x in jobs[rid]] + a=[dict(x) for x in artifacts[rid]] + if corruption=="head":r["head_sha"]="b"*40 + if corruption=="attempt":j[0]["run_attempt"]=1 + if corruption=="workflow":r["path"]=".github/workflows/research-lean.yml" + if corruption=="job":j[0]["conclusion"]="failure" + if corruption=="artifact": + a[0]["created_at"]="2026-10-24T13:00:00Z" + with self.subTest(corruption=corruption): + with self.assertRaises(source.DiscoveryError): + source.validate_run_job_artifact( + head=H,kind=kind,shard=shard,check=first, + run=r,jobs=j,artifacts=a) + + def test_complete_catalog_is_detached_and_never_approval(self): + checks,runs,jobs,artifacts,binary=fixture() + def pagination(path,*,key): + if path.startswith("/commits/"):return checks + rid=int(path.split("/")[3]) + return jobs[rid] if key=="jobs" else artifacts[rid] + def lookup(path):return runs[int(path.split("/")[-1])] + with tempfile.TemporaryDirectory(prefix="perm-catalog-fixture-") as td: + path=Path(td)/"out" + with mock.patch.object(source,"git_head",return_value=H): + result=source.collect( + H,path, checks=checks, lookup=lookup, + paginate_source=pagination, + download=lambda aid: binary[aid]) + self.assertEqual(len(result["artifacts"]),21) + self.assertEqual(len(list(path.glob("*.zip"))),21) + self.assertEqual( + json.loads((path/"source_execution_provenance.json").read_text()) + ["independent_case_review"],False) + self.assertEqual(json.loads( + (path/"original21_catalog.json").read_text())["head_sha"],H) + + def test_authentic_frozen_head_manual_dispatch_is_a_producer(self): + """workflow_dispatch is an original GH run, not manual approval.""" + checks, runs, jobs, artifacts, binary = fixture() + for run in runs.values(): + run["event"] = "workflow_dispatch" + + def pages(path, *, key): + if path.startswith("/commits/"): + return checks + run_id = int(path.split("/")[3]) + return jobs[run_id] if key == "jobs" else artifacts[run_id] + + def details(path): + return runs[int(path.split("/")[-1])] + + with tempfile.TemporaryDirectory(prefix="perm-dispatched21-") as td: + output = Path(td) / "unreviewed" + with mock.patch.object(source, "git_head", return_value=H): + report = source.collect( + H, output, checks=checks, lookup=details, + paginate_source=pages, download=lambda aid: binary[aid]) + self.assertEqual(len(report["artifacts"]), 21) + self.assertFalse(json.loads( + (output/"source_execution_provenance.json").read_bytes()) + ["independent_case_review"]) + self.assertEqual(len(list(output.glob("*.zip"))), 21) + self.assertEqual( + json.loads((output/"original21_catalog.json").read_bytes()) + ["head_sha"], H) + + def test_dispatch_still_rejects_foreign_event_head_and_branch(self): + checks, runs, jobs, artifacts, binary = fixture() + first = checks[0] + run_id = int(source.RUN_URL.fullmatch( + first["details_url"]).group(1)) + key = next(k for k, value in source.classify_latest( + checks, H).items() if value is first) + kind, shard = key + valid = dict(runs[run_id], event="workflow_dispatch") + candidate = source.validate_run_job_artifact( + head=H, kind=kind, shard=shard, check=first, + run=valid, jobs=jobs[run_id], artifacts=artifacts[run_id]) + self.assertEqual(candidate["run_id"], run_id) + self.assertEqual(candidate["kind"], kind) + for mutation in ( + lambda p: p.update(event="repository_dispatch"), + lambda p: p.update(event="pull_request"), + lambda p: p.update(event="schedule"), + lambda p: p.update(event=None), + lambda p: p.update(head_sha="0" * 40), + lambda p: p.update(head_branch="main"), + lambda p: p.update(path=".github/workflows/research-lean.yml"), + lambda p: p.update(conclusion="failure"), + lambda p: p.update(run_attempt=3), + ): + with self.subTest(mutation=mutation): + wrong = dict(valid) + mutation(wrong) + with self.assertRaises(source.DiscoveryError): + source.validate_run_job_artifact( + head=H, kind=kind, shard=shard, check=first, + run=wrong, jobs=jobs[run_id], + artifacts=artifacts[run_id]) + + def test_wrong_artifact_digest_aborts_without_output(self): + checks,runs,jobs,artifacts,binary=fixture() + def pagination(path,*,key): + if path.startswith("/commits/"):return checks + rid=int(path.split("/")[3]) + return jobs[rid] if key=="jobs" else artifacts[rid] + with tempfile.TemporaryDirectory(prefix="perm-digest-fixture-") as td: + destination=Path(td)/"out" + with mock.patch.object(source,"git_head",return_value=H): + with self.assertRaises(source.DiscoveryError): + source.collect( + H,destination,checks=checks, + lookup=lambda path:runs[int(path.split("/")[-1])], + paginate_source=pagination, + download=lambda aid: b"TAMPERED") + self.assertFalse(destination.exists()) + + def test_no_source_head_substitution(self): + checks,*_=fixture() + with self.assertRaises(source.DiscoveryError): + source.classify_latest(checks,"b"*40) + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_original_producer_trigger_candidate.py b/verification/research/test_pr113_original_producer_trigger_candidate.py new file mode 100644 index 00000000..4d217144 --- /dev/null +++ b/verification/research/test_pr113_original_producer_trigger_candidate.py @@ -0,0 +1,165 @@ +"""Adversarial tests for an inactive, byte-pinned original producer trigger plan.""" +import copy +import json +import unittest + +import pr113_original_producer_trigger_candidate as trg + +H="a"*40 + + +def original(name, job, count): + return ( + "name: PR113 "+name+"\n\n"+ + "on:\n"+ + " push:\n"+ + " branches: [research/final-mathematical-closure]\n"+ + " paths:\n"+ + " - 'verification/research/final_closure_*'\n"+ + " workflow_dispatch:\n\n"+ + "permissions:\n contents: read\n\n"+ + "jobs:\n "+job+":\n"+ + " strategy:\n matrix:\n"+ + " shard: ["+", ".join(str(i) for i in range(count))+"]\n"+ + " steps:\n"+ + " - run: python producer.py --shard-count "+str(count)+"\n" + ).encode() + + +def fixture(): + specs=[ + ("pr113-all-case-compiler.yml","All-Case Compiler Candidate", + "compiler-shard",4), + ("pr113-all-original-semantic-candidates.yml","All Original Semantic Candidate Evidence", + "semantic-candidate-shard",9), + ("pr113-legacy-semantic-evidence.yml","Legacy Original Semantic Compiler Candidates", + "legacy-original-shard",8), + ] + bytes_={} + records=[] + for name,label,job,n in specs: + old=".github/workflows/"+name + new="docs/operations/pr113_unapproved_workflows/trigger_candidates/"+name + source=original(label,job,n) + revised=trg.revised(source) + bytes_[old]=source;bytes_[new]=revised + records.append({ + "active":old,"proposal":new, + "original_blob":trg.blob_oid(source), + "proposed_blob":trg.blob_oid(revised), + "producer_shards":n,"expected_job":job, + }) + manifest={ + "format":"Permansson.PR113.StagedFullProducerTriggerReview.v1", + "status":"REVIEW_PENDING__ACTIVE_WORKFLOWS_UNCHANGED", + "original_requested_producer_jobs":21, + "independent_security_review":False, + "owner_approved_installation":False, + "mathematical_acceptance":False, + "publication_authorized":False, + "proposals":records, + } + return manifest,bytes_ + + +class FullProducerTriggerTests(unittest.TestCase): + def validate(self,manifest,bytes_): + return trg.validate_proposals(H,manifest,lambda h,path: bytes_[path]) + + def test_all_4_9_8_source_jobs_retained_without_approval(self): + m,b=fixture() + outcome=self.validate(m,b) + self.assertEqual(outcome["original_shard_count"],21) + self.assertEqual(outcome["original_producing_workflow_count"],3) + self.assertFalse(outcome["installed"]) + self.assertFalse(outcome["independent_security_review"]) + self.assertFalse(outcome["publication_authorized"]) + + def test_changed_body_rejected_even_with_new_blob_hash(self): + m,b=fixture() + row=m["proposals"][0] + b[row["proposal"]]=b[row["proposal"]].replace( + b"--shard-count 4",b"--shard-count 3") + row["proposed_blob"]=trg.blob_oid(b[row["proposal"]]) + with self.assertRaises(trg.TriggerReviewError): + self.validate(m,b) + + def test_missing_original_source_or_staged_file_rejected(self): + m,b=fixture() + del b[m["proposals"][1]["active"]] + with self.assertRaises(KeyError): + self.validate(m,b) + m,b=fixture() + del b[m["proposals"][1]["proposal"]] + with self.assertRaises(KeyError): + self.validate(m,b) + + def test_old_or_candidate_oid_substitution_rejected(self): + for key in ("original_blob","proposed_blob"): + m,b=fixture() + m["proposals"][0][key]="b"*40 + with self.subTest(key=key), self.assertRaises(trg.TriggerReviewError): + self.validate(m,b) + + def test_no_source_beyond_trigger_may_be_changed(self): + m,b=fixture() + path=m["proposals"][2]["proposal"] + b[path]=b[path].replace(b"permissions:\n contents: read", + b"permissions:\n contents: write") + m["proposals"][2]["proposed_blob"]=trg.blob_oid(b[path]) + with self.assertRaises(trg.TriggerReviewError): + self.validate(m,b) + + def test_review_status_cannot_be_self_promoted(self): + for key in ("independent_security_review","owner_approved_installation", + "mathematical_acceptance","publication_authorized"): + m,b=fixture() + m[key]=True + with self.subTest(key=key),self.assertRaises(trg.TriggerReviewError): + self.validate(m,b) + + def test_missing_duplicate_or_foreign_job_rejected(self): + m,b=fixture() + m["proposals"].pop() + with self.assertRaises(trg.TriggerReviewError): + self.validate(m,b) + m,b=fixture() + m["proposals"][1]=copy.deepcopy(m["proposals"][0]) + with self.assertRaises(trg.TriggerReviewError): + self.validate(m,b) + m,b=fixture() + m["proposals"][0]["expected_job"]="unreviewed-shortcut" + with self.assertRaises(trg.TriggerReviewError): + self.validate(m,b) + + def test_original_nondefault_branch_filter_cannot_be_kept(self): + m,b=fixture() + first=m["proposals"][0] + b[first["proposal"]]=b[first["active"]] + first["proposed_blob"]=trg.blob_oid(b[first["proposal"]]) + with self.assertRaises(trg.TriggerReviewError): + self.validate(m,b) + + def test_unexpected_legacy_matrix_count_cannot_be_substituted(self): + m,b=fixture() + m["proposals"][2]["producer_shards"]=7 + with self.assertRaises(trg.TriggerReviewError): + self.validate(m,b) + + + def test_committed_manifest_and_real_pinned_producer_sources(self): + """Read actual Git objects rather than a synthetic matching manifest.""" + head = trg.git("rev-parse", "HEAD").decode().strip() + original = json.loads(trg.committed(head, trg.MANIFEST)) + report = trg.validate_proposals(head, original) + self.assertEqual(report["original_shard_count"], 21) + self.assertFalse(report["installed"]) + self.assertFalse(report["independent_security_review"]) + invalid = copy.deepcopy(original) + invalid["proposals"][0]["expected_job"] = "all-case-compiler" + with self.assertRaises(trg.TriggerReviewError): + trg.validate_proposals(head, invalid) + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_owner_partition_v3.py b/verification/research/test_pr113_owner_partition_v3.py new file mode 100644 index 00000000..9ad6f6a1 --- /dev/null +++ b/verification/research/test_pr113_owner_partition_v3.py @@ -0,0 +1,50 @@ +"""Synthetic 76-owner coverage is independent of ZIP shard count.""" +import unittest +from copy import deepcopy + +from pr113_owner_partition_v3 import ( + original_owner_artifacts, OwnerPartitionError, FORMAT, CLAIMS, +) + + +def complete(): + cases = [f"C-{i:02d}" for i in range(1, 46)] + return { + "format": FORMAT, + "source_controls_accepted": False, + "independent_security_review": False, + "mathematical_approval": False, + "producers": [ + {"artifact_id": 101, "owners": [ + {"kind": "original_positive", "owner_id": case} for case in cases]}, + {"artifact_id": 102, "owners": [ + {"kind": "legacy_negative", "owner_id": case} for case in cases[:20]]}, + {"artifact_id": 103, "owners": [ + {"kind": "claim_pair", "owner_id": claim} for claim in sorted(CLAIMS)]}, + ], + } + + +class PartitionTests(unittest.TestCase): + def test_76_real_owner_obligations_can_live_in_three_artifacts(self): + self.assertEqual(original_owner_artifacts(complete()), [101, 102, 103]) + + def test_missing_owner_duplicate_artifact_and_false_approval_fail(self): + for edit in ( + lambda p: p["producers"][0]["owners"].pop(), + lambda p: p["producers"][2].update(artifact_id=102), + lambda p: p["producers"][1]["owners"].append( + dict(p["producers"][1]["owners"][0])), + lambda p: p.update(mathematical_approval=True), + lambda p: p["producers"][0]["owners"][0].update(kind=[]), + lambda p: p["producers"][1]["owners"][0].update(owner_id="FOREIGN"), + lambda p: p["producers"][2]["owners"][0].update(owner_id="NOT_A_CLAIM"), + ): + candidate=deepcopy(complete()) + edit(candidate) + with self.subTest(edit=edit), self.assertRaises(OwnerPartitionError): + original_owner_artifacts(candidate) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_pack_ci_positive_owners.py b/verification/research/test_pr113_pack_ci_positive_owners.py new file mode 100644 index 00000000..40d38a81 --- /dev/null +++ b/verification/research/test_pr113_pack_ci_positive_owners.py @@ -0,0 +1,121 @@ +"""Pure-Python verification of source-bound CI owner packaging.""" +import hashlib +import json +from pathlib import Path +import tempfile +import unittest + +from pr113_pack_ci_positive_owners import owner_manifest, ProducerError + + +HEAD = "a" * 40 +OUT = b"" +ERR = b"Ran 1 test in 0.001s\n\nOK\n" +LEAN = b"Lean source compiles; no errors.\n" + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def fixture(root, count=45): + source = root / "source" + source.mkdir() + records = [] + for index in range(count): + cid = f"Q-{index:02d}" + path = source / cid + path.mkdir() + logs = { + "unittest_stdout.log": OUT, + "unittest_stderr.log": ERR, + "source_positive.log": LEAN, + } + data = { + "format": "Permansson.PR113.OriginalCasePositiveCandidate.v1", + "status": "SOURCE_TEST_AND_LEAN_EXECUTED_UNREVIEWED", + "head_sha": HEAD, + "case_id": cid, + "positive_role": "COMPILER_SOURCE", + "source_positive_exit_code": 0, + "registered_method_exit_code": 0, + "actual_producing_check_run_id": None, + "authenticated_ci_artifact": False, + "independent_case_review": False, + "scientific_release_approved": False, + "unittest_stdout_sha256": sha(OUT), + "unittest_stderr_sha256": sha(ERR), + "source_positive_log_sha256": sha(LEAN), + } + records.append(data) + (path / "unreviewed_positive.json").write_text( + json.dumps(data, sort_keys=True)) + for name, raw in logs.items(): + (path / name).write_bytes(raw) + summary = { + "format": "Permansson.PR113.Original45PositiveRoutesCandidate.v1", + "status": "EXECUTED_NO_PRODUCING_CI_OR_MATH_REVIEW", + "head_sha": HEAD, + "actual_positive_cases": count, + "positive_route_records": records, + "authenticated_ci_artifact": False, + "scientific_release_approved": False, + } + (source / "unreviewed_45_positive_routes.json").write_text( + json.dumps(summary, sort_keys=True)) + return source + + +class SourceOwnedPositivePackerTests(unittest.TestCase): + def test_forty_five_original_bytes_but_no_approval(self): + with tempfile.TemporaryDirectory() as base: + root = Path(base) + src = fixture(root) + target = root / "output" + result = owner_manifest(HEAD, 123, 1, src, target) + self.assertEqual(len(result["owners"]), 45) + self.assertEqual(result["github_run_id"], 123) + self.assertFalse(result["independent_review"]) + self.assertEqual(result["accepted_cases"], 0) + self.assertEqual(result["accepted_claims"], 0) + self.assertEqual( + result["owners"][0]["receipt_member"], + "positive/Q-00/unreviewed_positive.json") + + def test_one_missing_case_rejects_total(self): + with tempfile.TemporaryDirectory() as base: + root = Path(base) + src = fixture(root, count=44) + with self.assertRaises(ProducerError): + owner_manifest(HEAD, 123, 1, src, root / "output") + + def test_tampered_lean_log_fails(self): + with tempfile.TemporaryDirectory() as base: + root = Path(base) + src = fixture(root) + (src / "Q-11" / "source_positive.log").write_bytes( + b"error: forged source proof\n") + with self.assertRaisesRegex(ProducerError, "digest"): + owner_manifest(HEAD, 123, 1, src, root / "output") + + def test_self_awarded_review_rejected(self): + with tempfile.TemporaryDirectory() as base: + root = Path(base) + src = fixture(root) + record = src / "Q-11" / "unreviewed_positive.json" + data = json.loads(record.read_text()) + data["independent_case_review"] = True + record.write_text(json.dumps(data)) + with self.assertRaises(ProducerError): + owner_manifest(HEAD, 123, 1, src, root / "output") + + def test_no_boolean_git_run_id(self): + with tempfile.TemporaryDirectory() as base: + root = Path(base) + src = fixture(root) + with self.assertRaises(ProducerError): + owner_manifest(HEAD, True, 1, src, root / "output") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_phase_aware_a2_consumer.py b/verification/research/test_pr113_phase_aware_a2_consumer.py new file mode 100644 index 00000000..ad195c9a --- /dev/null +++ b/verification/research/test_pr113_phase_aware_a2_consumer.py @@ -0,0 +1,180 @@ +"""Synthetic four-phase regression for the staged A2 required consumer. + +This tests the *real* check_stored validator and mocks only its external +GitHub producer/review recomputation. No fixture is an actual approval. +""" +from __future__ import annotations + +from contextlib import redirect_stdout +import importlib.util +import io +import json +import os +from pathlib import Path +import tempfile +import unittest +from unittest import mock + +import pr113_historical_runtime_v3 as runtime + +ROOT = Path(__file__).resolve().parents[2] +STAGED = ROOT / ( + "docs/operations/pr113_required_gate_replacement_unapproved/" + "test_localized_prefix_compiler.py") +SPEC = importlib.util.spec_from_file_location( + "pr113_staged_phase_aware_a2_consumer", STAGED) +if SPEC is None or SPEC.loader is None: + raise RuntimeError("staged A2 consumer source absent") +consumer = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(consumer) + +H = "b" * 40 +M = "c" * 40 +C = "d" * 40 +PHASES = ( + "initial_113_pr", "initial_113_main", + "main_descendant_pr", "main_descendant_push", +) + + +def event(phase): + return { + "phase": phase, "source_commit_sha": C, + "github_run_id": 222, "github_run_attempt": 1, + "base_sha": "17c315f7df7abf457eb94d393bd526d46da6f6f4", + "head_sha": H, "synthetic_sha": C, + "infrastructure_head_sha": H, + "infrastructure_merge_sha": M if phase != "initial_113_pr" else None, + } + + +def fabricated_result(phase): + observed = event(phase) + status = consumer.PHASE_STATUSES[phase] + source = {"status": status} + if phase.startswith("initial_113"): + source.update({ + "independent_security_review_authenticated": True, + "head_sha": H, "required_ci_successful": 0, + "mathematical_acceptance": False, + "owner_merge_authorized": False, + "publication_authorized": False, + }) + return { + "phase": phase, "event": observed, "required": source, + "envelope": {"format": "TEST_ONLY"}, + "transport": {"status": "TEST_ONLY_UNREVIEWED"}, + "zips": {"T": b"PKTEST-T", "A2": b"PKTEST-A2"}, + } + source.update({ + "historical_merged_main_sha": M, + "current_required_ci_accepted": False, + }) + return { + "phase": phase, "event": observed, "required": source, + "envelope": None, "transport": None, "zips": {}, + } + + +class PhaseAwareStagedA2ConsumerTests(unittest.TestCase): + def _execute(self, phase, *, altered=None, remove_zip=False): + result = fabricated_result(phase) + with tempfile.TemporaryDirectory(prefix="pr113-phase-a2-") as tmp: + output, _ = runtime.store(result, Path(tmp) / "receipts", 111, 333) + receipt = output / "historical_required.json" + if remove_zip: + for name in ("historical_T.zip", "historical_A2.zip"): + if (output / name).exists(): + (output / name).unlink() + if altered is not None: + altered(receipt) + env = {consumer.RECEIPT_ENV: str(receipt)} + # Legacy input names deliberately absent: descendants must never + # require them, while initials are checked via the real callee. + with mock.patch.dict(os.environ, env, clear=False), \ + mock.patch.object(runtime, "actual_source", return_value=event(phase)), \ + mock.patch.object(runtime, "produce", return_value=result), \ + redirect_stdout(io.StringIO()): + for key in ( + "PERMANSSON_DUAL_HISTORICAL_EVIDENCE", + "PERMANSSON_DUAL_ORIGINAL_T_ZIP", + "PERMANSSON_DUAL_ORIGINAL_A2_ZIP", + ): + os.environ.pop(key, None) + case = consumer.LocalizedCompilerIntegration( + "test_actual_final_source_and_semantic_mutations") + case.test_actual_final_source_and_semantic_mutations() + + def test_all_four_lifecycle_phases_use_real_reauthentication(self): + for phase in PHASES: + with self.subTest(phase=phase): + self._execute(phase) + + def test_initial_original_zip_missing_cannot_be_borrowed(self): + with self.assertRaises(ValueError): + self._execute("initial_113_pr", remove_zip=True) + + def test_descendant_does_not_require_initial_only_zip_variables(self): + self._execute("main_descendant_pr") + self._execute("main_descendant_push") + + def test_duplicate_json_keys_fail_at_intake(self): + def duplicate(path): + original = path.read_text(encoding="utf-8") + path.write_text('{"phase":"FORGED",' + original.lstrip()[1:], + encoding="utf-8") + with self.assertRaises(ValueError): + self._execute("main_descendant_pr", altered=duplicate) + + def test_forged_source_or_phase_does_not_reauthenticate(self): + def forged(path): + payload = json.loads(path.read_text(encoding="utf-8")) + payload["source_commit_sha"] = "f" * 40 + path.write_text(json.dumps(payload), encoding="utf-8") + with self.assertRaises(runtime.RuntimeError113): + self._execute("main_descendant_pr", altered=forged) + + def test_missing_phase_neutral_receipt_blocks(self): + with mock.patch.dict(os.environ, {}, clear=True): + case = consumer.LocalizedCompilerIntegration( + "test_actual_final_source_and_semantic_mutations") + with self.assertRaises(AssertionError): + case.test_actual_final_source_and_semantic_mutations() + + def test_runtime_exports_one_phase_neutral_receipt_to_next_ci_step(self): + """Synthetic event tests real main() output and GITHUB_ENV handoff.""" + for phase in PHASES: + with self.subTest(phase=phase): + result = fabricated_result(phase) + with tempfile.TemporaryDirectory(prefix="pr113-export-") as temp: + envfile = Path(temp) / "github-env" + args = [ + "--producer-run-id", "111", + "--security-review-id", "333", + "--out", str(Path(temp) / "material"), + ] + with mock.patch.dict( + os.environ, {"GITHUB_ENV": str(envfile)}, clear=False + ), mock.patch.object( + runtime, "produce", return_value=result + ), redirect_stdout(io.StringIO()): + self.assertEqual(runtime.main(args), 0) + lines = envfile.read_text(encoding="utf-8").splitlines() + paths = dict(line.split("=", 1) for line in lines) + self.assertEqual( + set(paths) >= {consumer.RECEIPT_ENV}, True) + receipt = Path(paths[consumer.RECEIPT_ENV]) + self.assertTrue(receipt.is_file()) + self.assertEqual( + json.loads(receipt.read_text())["phase"], phase) + original_vars = { + "PERMANSSON_DUAL_HISTORICAL_EVIDENCE", + "PERMANSSON_DUAL_ORIGINAL_T_ZIP", + "PERMANSSON_DUAL_ORIGINAL_A2_ZIP", + } + if phase.startswith("initial_113"): + self.assertTrue(original_vars <= set(paths)) + for key in original_vars: + self.assertTrue(Path(paths[key]).is_file()) + else: + self.assertTrue(original_vars.isdisjoint(paths)) diff --git a/verification/research/test_pr113_prepare_positive_selection.py b/verification/research/test_pr113_prepare_positive_selection.py new file mode 100644 index 00000000..872c3948 --- /dev/null +++ b/verification/research/test_pr113_prepare_positive_selection.py @@ -0,0 +1,99 @@ +"""Adversarial, Python-only tests for original 45-case route preparation.""" +import hashlib +import unittest + +from pr113_prepare_positive_selection import ( + SelectionError, choose_role, derive, method_class, FORMAT, +) + + +TEST_PATH = "verification/research/test_fixture.py" +PROOF_PATH = "PermanssonResearch/Fixture.lean" +TEST = b"""import unittest +class Legitimate(unittest.TestCase): + def test_valid(self): + self.assertTrue(True) +""" +PROOF = b"theorem actual_fact : True := by trivial\n" + + +def original_case(number, route="COMPILER_SOURCE"): + return { + "case_id": f"Z-{number:02d}", + "test_method_candidates": [{ + "path": TEST_PATH, + "method": "test_valid", + "role": route, + }], + "historical_proof_declarations": [{ + "path": PROOF_PATH, + "name": "actual_fact", + }], + } + + +class OriginalPositiveSelectionTests(unittest.TestCase): + def test_exact_45_distinct_source_selected_but_unapproved(self): + source_map = {"cases": [original_case(i) for i in range(45)]} + def original(_head, path): + return {TEST_PATH: TEST, PROOF_PATH: PROOF}[path] + selection, review = derive("a" * 40, source_map, original) + self.assertEqual(selection["format"], FORMAT) + self.assertEqual(len(selection["cases"]), 45) + self.assertEqual(set(row["test_class"] for row in selection["cases"]), + {"Legitimate"}) + self.assertEqual(set(row["proof_source_path"] for row + in selection["cases"]), {PROOF_PATH}) + self.assertEqual(review["selected_case_count"], 45) + self.assertFalse(review["reviewed"]) + self.assertFalse(review["executed"]) + self.assertEqual(review["accepted_cases"], 0) + self.assertEqual(review["source_sha256"][PROOF_PATH], + hashlib.sha256(PROOF).hexdigest()) + + def test_missing_one_original_is_not_completed(self): + with self.assertRaises(SelectionError): + derive("a" * 40, + {"cases": [original_case(i) for i in range(44)]}, + lambda _h, p: TEST) + + def test_duplicate_original_is_rejected(self): + cases = [original_case(i) for i in range(44)] + cases.append(original_case(4)) + with self.assertRaisesRegex(SelectionError, "duplicate"): + derive("a" * 40, {"cases": cases}, + lambda _h, p: TEST) + + def test_semantic_negative_cannot_be_used_as_positive(self): + with self.assertRaisesRegex(SelectionError, "source-positive"): + choose_role(original_case(1, route="SEMANTIC_NEGATIVE")) + + def test_compiler_route_preferred_over_exact_check(self): + case = original_case(1, route="EXACT_CHECK") + case["test_method_candidates"].append({ + "path": TEST_PATH, "method": "test_valid", + "role": "COMPILER_SOURCE", + }) + self.assertEqual(choose_role(case)["role"], "COMPILER_SOURCE") + + def test_unique_original_unittest_class_is_required(self): + self.assertEqual(method_class(TEST, "test_valid"), "Legitimate") + with self.assertRaises(SelectionError): + method_class(TEST, "test_does_not_exist") + ambiguous = TEST + b"""class Extra(unittest.TestCase): + def test_valid(self): + pass +""" + with self.assertRaisesRegex(SelectionError, "unique"): + method_class(ambiguous, "test_valid") + + def test_case_with_no_actual_proof_provider_cannot_be_selected(self): + cases = [original_case(i) for i in range(45)] + cases[5]["historical_proof_declarations"] = [] + with self.assertRaisesRegex(SelectionError, "provider"): + derive("a" * 40, {"cases": cases}, + lambda _h, p: TEST) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_preseal_input_carrier.py b/verification/research/test_pr113_preseal_input_carrier.py new file mode 100644 index 00000000..f97f97f1 --- /dev/null +++ b/verification/research/test_pr113_preseal_input_carrier.py @@ -0,0 +1,208 @@ +"""Pure-Python adversarial preseal input carrier tests (no release approval).""" +import io +import json +from pathlib import Path +import tempfile +import unittest +import zipfile + +from pr113_preseal_input_carrier import ( + EXPECTED_FILES, PresealInputError, digest, extract, + inspect_original_carrier, verified_transport, +) + +M = "a" * 40 + + +def zipped(override=None): + from pr113_owner_partition_v3 import FORMAT, CLAIMS + files = {name: b"source-bound-but-unreviewed-" + name.encode() + for name in EXPECTED_FILES} + cases = [f"C-{i:02d}" for i in range(1, 46)] + contract = { + "format": FORMAT, "source_controls_accepted": False, + "independent_security_review": False, "mathematical_approval": False, + "producers": [ + {"artifact_id": 101, "owners": [ + {"kind": "original_positive", "owner_id": cid} + for cid in cases]}, + {"artifact_id": 102, "owners": [ + {"kind": "legacy_negative", "owner_id": cid} + for cid in cases[:20]]}, + {"artifact_id": 103, "owners": [ + {"kind": "claim_pair", "owner_id": claim} + for claim in sorted(CLAIMS)]}, + ], + } + files["reviewed/additional_contract.json"] = ( + json.dumps(contract, sort_keys=True) + "\n").encode() + for original_id in (101, 102, 103): + files[f"reviewed/additional_zips/{original_id}.zip"] = ( + b"synthetic GH artifact bytes " + str(original_id).encode()) + if override: + files.update(override) + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z: + for name, data in sorted(files.items()): + z.writestr(name, data) + return buf.getvalue() + + +def originals(raw): + run = { + "id": 101, "head_sha": M, "head_branch": "main", + "status": "completed", "conclusion": "success", + "event": "workflow_dispatch", "run_attempt": 2, + "path": ".github/workflows/preseal-input-transport.yml", + } + job = { + "id": 102, "name": "review-evidence-transport", + "run_id": 101, "run_attempt": 2, "head_sha": M, + "status": "completed", "conclusion": "success", + "check_run_url": "https://api.github.com/repos/a/b/check-runs/103", + "started_at": "2026-10-24T12:00:00Z", + "completed_at": "2026-10-24T12:15:00Z", + } + check = { + "id": 103, "name": "review-evidence-transport", + "head_sha": M, "status": "completed", "conclusion": "success", + "app": {"slug": "github-actions"}, + } + artifact = { + "id": 104, "name": "pr113-preseal-review-inputs", + "digest": "sha256:" + digest(raw), "expired": False, + "created_at": "2026-10-24T12:10:00Z", + "workflow_run": {"id": 101, "head_sha": M}, + } + return {"merged": M, "run_id": 101, "attempt": 2, + "job_id": 102, "check_id": 103, "artifact_id": 104, + "meta": artifact, "run": run, + "jobs": {"jobs": [job]}, "check": check, "raw": raw} + + +class PresealOriginalTransportTests(unittest.TestCase): + def test_valid_transport_still_has_zero_approvals(self): + payload = originals(zipped()) + result = verified_transport(**payload) + self.assertEqual(result["source_commit_sha"], M) + self.assertEqual(result["status"], + "TRANSPORT_BYTES_BOUND_NOT_APPROVED") + + def test_failed_or_wrong_attempt_cannot_supply_evidence(self): + for name, edit in ( + ("failed run", lambda x: x["run"].update(conclusion="failure")), + ("wrong head", lambda x: x["run"].update(head_sha="b" * 40)), + ("wrong branch", lambda x: x["run"].update(head_branch="feature")), + ("stale attempt", lambda x: x["jobs"]["jobs"][0].update(run_attempt=1)), + ("failed job", lambda x: x["jobs"]["jobs"][0].update(conclusion="failure")), + ("failed check", lambda x: x["check"].update(conclusion="failure")), + ("wrong check", lambda x: x["check"].update(head_sha="c" * 40)), + ("expired", lambda x: x["meta"].update(expired=True)), + ("digest", lambda x: x["meta"].update(digest="sha256:" + "0" * 64)), + ("wrong artifact", lambda x: x["meta"].update(name="other")), + ("early artifact", lambda x: x["meta"].update( + created_at="2026-10-24T11:00:00Z")), + ("late artifact", lambda x: x["meta"].update( + created_at="2026-10-24T12:20:00Z")), + ("wrong run", lambda x: x["meta"]["workflow_run"].update(id=202)), + ): + with self.subTest(case=name): + payload = originals(zipped()) + edit(payload) + with self.assertRaises(PresealInputError): + verified_transport(**payload) + + def test_all_reviewed_file_names_are_required(self): + entries = inspect_original_carrier(zipped()) + self.assertEqual(set(EXPECTED_FILES).issubset(entries), True) + missing = dict((name, b"x") for name in EXPECTED_FILES + if not name.endswith("approved_claim_manifest.json")) + missing["reviewed/additional_zips/101.zip"] = b"zip" + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z: + for name, raw in missing.items(): + z.writestr(name, raw) + with self.assertRaises(PresealInputError): + inspect_original_carrier(buf.getvalue()) + + + def test_missing_actual_artifact_id_rejects_despite_76_owners(self): + raw = zipped() + out = io.BytesIO() + with zipfile.ZipFile(io.BytesIO(raw)) as source, zipfile.ZipFile( + out, "w", zipfile.ZIP_DEFLATED) as candidate: + for name in source.namelist(): + if name != "reviewed/additional_zips/103.zip": + candidate.writestr(name, source.read(name)) + with self.assertRaises(PresealInputError): + inspect_original_carrier(out.getvalue()) + + def test_arbitrary_zip_member_and_traversal_are_rejected(self): + for name in ( + "reviewed/../../evil.txt", + "reviewed/additional_zips/../../evil.zip", + "reviewed/additional_zips/abc.zip", + "reviewed/approved_manifest.json", + ): + with self.subTest(name=name): + with self.assertRaises(PresealInputError): + inspect_original_carrier(zipped({name: b"bad"})) + + def test_valid_transport_does_not_become_math_acceptance(self): + with tempfile.TemporaryDirectory(prefix="pr113-transport-") as td: + root = Path(td) + payload = originals(zipped()) + src = root / "upstream.zip" + src.write_bytes(payload.pop("raw")) + target = root / "out" + result = extract(src, target, **payload) + self.assertIs(result["independent_review"], False) + self.assertIs(result["mathematical_acceptance"], False) + self.assertIs(result["publication_authorized"], False) + self.assertEqual( + result["status"], + "GH_SOURCE_TRANSPORT_VERIFIED__RELEASE_REVIEW_PENDING") + persisted = json.loads( + (target / "transport_diagnostics.json").read_text()) + self.assertEqual(persisted, result) + self.assertEqual( + persisted["transport_artifact_sha256"], digest(src.read_bytes())) + self.assertTrue((target / "additional_zips/101.zip").is_file()) + self.assertFalse((target / "approved_claim_manifest.json").is_symlink()) + + + + def test_traversing_and_symlinked_carrier_paths_fail_before_intake(self): + from unittest import mock + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + original = root / "original.zip" + raw = zipped() + original.write_bytes(raw) + target_dir = root / "target" + target_dir.mkdir() + options = {key:value for key,value in originals(raw).items() + if key != "raw"} + with mock.patch("pr113_preseal_input_carrier.verified_transport") as authenticate: + with self.assertRaises(ValueError): + extract(original, target_dir / ".." / "other", **options) + authenticate.assert_not_called() + alias = root / "alias" + alias.symlink_to(target_dir, target_is_directory=True) + with self.assertRaises(ValueError): + extract(original, alias / "evidence", **options) + self.assertFalse((target_dir / "evidence").exists()) + + def test_staged_preseal_consumer_uses_exact_outer_status_and_digest(self): + yaml = (Path(__file__).resolve().parents[2] / + "docs/operations/pr113_unapproved_workflows/" + "pr113-preseal-fullscope.yml").read_text() + self.assertIn('input_report["status"]', yaml) + self.assertIn( + '"GH_SOURCE_TRANSPORT_VERIFIED__RELEASE_REVIEW_PENDING"', yaml) + self.assertIn('input_report["transport_artifact_sha256"]', yaml) + self.assertIn('input_report["independent_review"] is False', yaml) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_producer_target_v3.py b/verification/research/test_pr113_producer_target_v3.py new file mode 100644 index 00000000..ad05d02c --- /dev/null +++ b/verification/research/test_pr113_producer_target_v3.py @@ -0,0 +1,83 @@ +"""Original21 producer branch identity must come from real GitHub/Git ancestry.""" +import unittest + +import pr113_producer_target_v3 as subject +from release_target import B113 + +H113 = "b"*40 +M113 = "c"*40 +B_CERT = "d"*40 +H_CERT = "e"*40 + + +class ProducerBranchTests(unittest.TestCase): + def env(self, head, branch, cert=""): + return { + "GITHUB_ACTIONS":"true", + "GITHUB_REPOSITORY":"HMarcusWH/PermanssonLean", + "GITHUB_SHA":head, "GITHUB_EVENT_NAME":"workflow_dispatch", + "GITHUB_REF":"refs/heads/"+branch, + "PERMANSSON_CERT_PR_NUMBER":cert, + } + + def git(self, *args): + if args == ("rev-parse", "HEAD"): + return self.head + if args[:2] == ("cat-file", "-t"): + return "commit" + if args == ("show", "-s", "--format=%P", M113): + return B113+" "+H113 + if args[0] == "rev-parse" and args[1].endswith("^{tree}"): + return "reviewed_tree" + if args[:2] == ("merge-base", "--is-ancestor"): + return "" + raise AssertionError(args) + + def live(self, endpoint): + if endpoint == "/pulls/113": + if self.head == H113: + return {"number":113,"merged":False, + "head":{"sha":H113,"ref":subject.BRANCH113}, + "base":{"sha":B113}} + return {"number":113,"state":"closed","merged":True, + "merge_commit_sha":M113,"head":{"sha":H113}} + if endpoint == "/pulls/115": + return {"number":115,"state":"open","merged":False, + "head":{"sha":H_CERT,"ref":"research/cert-real", + "repo":{"full_name":"HMarcusWH/PermanssonLean"}}, + "base":{"sha":B_CERT,"ref":"main"}} + if endpoint == "/branches/main": + return {"commit":{"sha":B_CERT}} + raise AssertionError(endpoint) + + def test_original_and_successor_are_distinct_authentic_targets(self): + self.head = H113 + self.assertEqual(subject.authenticated_producer_branch( + H113, env=self.env(H113,subject.BRANCH113),git=self.git,live=self.live), + subject.BRANCH113) + self.head = H_CERT + self.assertEqual(subject.authenticated_producer_branch( + H_CERT,env=self.env(H_CERT,"research/cert-real","115"), + git=self.git,live=self.live), "research/cert-real") + + def test_missing_or_wrong_cert_PR_does_not_borrow_original_branch(self): + self.head = H_CERT + for env in (self.env(H_CERT,"research/cert-real"), + self.env(H_CERT,"research/cert-real","113"), + self.env(H_CERT,"research/cert-real","114"), + self.env(H_CERT,"research/cert-fake","115"), + self.env(H113,"research/cert-real","115")): + with self.subTest(env=env), self.assertRaises(subject.ProducerTargetError): + subject.authenticated_producer_branch( + H_CERT, env=env, git=self.git, live=self.live) + + def test_wrong_ancestry_must_fail_even_with_live_pr(self): + self.head = H_CERT + def wrong_git(*args): + if args[:2] == ("merge-base","--is-ancestor"): + return "not ancestor" + return self.git(*args) + with self.assertRaises(subject.ProducerTargetError): + subject.authenticated_producer_branch( + H_CERT,env=self.env(H_CERT,"research/cert-real","115"), + git=wrong_git,live=self.live) diff --git a/verification/research/test_pr113_regression_diagnostics.py b/verification/research/test_pr113_regression_diagnostics.py new file mode 100644 index 00000000..8fc1ad53 --- /dev/null +++ b/verification/research/test_pr113_regression_diagnostics.py @@ -0,0 +1,28 @@ +"""Regression diagnostics cannot make a failing unittest suite green.""" +import io +import unittest +import pr113_regression_diagnostics as diag + + +class DiagnosticTests(unittest.TestCase): + def test_failures_distinct_from_subtest_failures(self): + class Fixture(unittest.TestCase): + def test_ok(self): + self.assertTrue(True) + def test_bad(self): + self.assertEqual(1, 2) + def test_sub(self): + for x in (0, 1): + with self.subTest(x=x): + self.assertEqual(0, x) + result = unittest.TextTestRunner( + stream=io.StringIO(), resultclass=diag.Result).run( + unittest.defaultTestLoader.loadTestsFromTestCase(Fixture)) + report = diag.summary(result) + self.assertEqual(report["tests_run"], 3) + self.assertEqual(report["failure_count"], 2) + self.assertEqual(report["direct_failure_count"], 1) + self.assertEqual(report["subtest_failure_count"], 1) + self.assertEqual(len(report["subtest_failures"]), 1) + self.assertEqual(report["status"], "TESTS_FAILED") + self.assertFalse(report["mathematical_acceptance"]) diff --git a/verification/research/test_pr113_release_target.py b/verification/research/test_pr113_release_target.py new file mode 100644 index 00000000..0f08f96b --- /dev/null +++ b/verification/research/test_pr113_release_target.py @@ -0,0 +1,232 @@ +"""Synthetic Git/GitHub target verification; never owner review or approval.""" +import copy +import json +import unittest +from unittest import mock + +import release_target as target + + +B = target.B113 +H = "a" * 40 +S = "b" * 40 +M = "c" * 40 +BC = M +HC = "d" * 40 +SC = "e" * 40 + + +def fake_intent(): + return { + "format": target.FORMAT, + "repository": target.REPOSITORY, + "original_base_sha": B, + "historical_T_sha": target.T, + "infrastructure_pr": 113, + "certification_pr": None, + "status": "CERTIFICATION_NOT_YET_BOUND", + "independent_security_review": False, + "independent_mathematical_review": False, + "publication_authorized": False, + } + + +def fake_target(cert=False): + return { + "format": target.BINDING_FORMAT, + "phase": "certification_premerge" if cert else "infrastructure_premerge", + "repository": target.REPOSITORY, + "pr_number": 116 if cert else 113, + "branch": "research/certification-mathematical" if cert + else "research/final-mathematical-closure", + "base_sha": BC if cert else B, + "head_sha": HC if cert else H, + "synthetic_sha": SC if cert else S, + "infrastructure_merge_sha": M if cert else None, + } + + +def fake_apis(cert=False): + binding = fake_target(cert) + base, head, syn = (binding[k] for k in ( + "base_sha", "head_sha", "synthetic_sha")) + p = { + "number": binding["pr_number"], "state": "open", + "merged": False, "draft": True, + "base": {"ref": "main", "sha": base}, + "head": {"ref": binding["branch"], "sha": head, + "repo": {"full_name": target.REPOSITORY}}, + "merge_commit_sha": syn, + } + lookup = { + "/branches/main": {"commit": {"sha": base}}, + "/pulls/" + str(binding["pr_number"]): p, + "/git/commits/" + syn: { + "sha": syn, + "parents": [{"sha": base}, {"sha": head}], + "tree": {"sha": "same_tree"}, + }, + } + if cert: + lookup["/pulls/113"] = { + "number": 113, "merged": True, "merge_commit_sha": M, + "head": {"sha": H}, + } + + def gh(path): + return lookup[path] + + def git(*args): + if args[:2] == ("cat-file", "-t"): + return "commit" + if args[:3] == ("show", "-s", "--format=%P"): + return base + " " + head + if args[:2] == ("rev-parse", syn + "^{tree}"): + return "same_tree" + if args[:2] == ("rev-parse", head + "^{tree}"): + return "same_tree" + if args[:2] == ("rev-parse", M + "^{tree}"): + return "original_merged_tree" + if args[:2] == ("rev-parse", H + "^{tree}"): + return "original_merged_tree" + if args == ("merge-base", "--is-ancestor", M, BC): + return "" + raise AssertionError("unexpected Git op " + repr(args)) + return gh, git, lookup + + +class ReleaseTargetTests(unittest.TestCase): + def test_intent_is_unbound_and_never_self_approves(self): + target.intent_contract(fake_intent()) + for mutation in ( + lambda x: x.update(certification_pr=115), + lambda x: x.update(publication_authorized=True), + lambda x: x.update(original_base_sha="0" * 40), + lambda x: x.update(fake_approval=True), + lambda x: x.update(independent_security_review=True), + ): + with self.subTest(str(mutation)), self.assertRaises(target.TargetError): + obj = fake_intent() + mutation(obj) + target.intent_contract(obj) + + def test_genuine_infrastructure_target_is_bound_but_unaccepted(self): + gh, git, _ = fake_apis() + r = target.bind(fake_target(), intent=fake_intent(), git=git, live=gh) + self.assertEqual(r["status"], "GENUINE_PR_AND_GIT_IDENTITY_BOUND_NOT_REVIEWED") + self.assertEqual(r["pr_number"], 113) + self.assertFalse(r["independent_security_review"]) + self.assertFalse(r["merge_authorized"]) + + def test_github_synthetic_merge_can_have_distinct_commit_sha(self): + """Separate GitHub test-merge metadata from an independently built S.""" + gh, git, api = fake_apis() + p = api["/pulls/113"] + github_sha = "f" * 40 + p["merge_commit_sha"] = github_sha + api["/git/commits/" + github_sha] = { + "sha": github_sha, + "parents": [{"sha": B}, {"sha": H}], + "tree": {"sha": "same_tree"}, + } + result = target.bind(fake_target(), intent=fake_intent(), + git=git, live=gh) + self.assertEqual(result["synthetic_sha"], S) + self.assertNotEqual(S, github_sha) + self.assertFalse(result["merge_authorized"]) + for mutation in ( + lambda row: row["parents"].reverse(), + lambda row: row["parents"].append({"sha": "9" * 40}), + lambda row: row["tree"].update(sha="wrong"), + lambda row: row.update(sha="8" * 40), + ): + with self.subTest(mutation=mutation): + record = api["/git/commits/" + github_sha] + original = json.loads(json.dumps(record)) + mutation(record) + with self.assertRaises(target.TargetError): + target.bind(fake_target(), intent=fake_intent(), + git=git, live=gh) + api["/git/commits/" + github_sha] = original + + def test_certification_requires_real_M113_and_new_pr(self): + gh, git, api = fake_apis(cert=True) + r = target.bind(fake_target(True), intent=fake_intent(), git=git, live=gh) + self.assertEqual(r["phase"], "certification_premerge") + self.assertFalse(r["independent_mathematical_review"]) + for bad in (113, 114, 0): + row = fake_target(True) + row["pr_number"] = bad + with self.assertRaises(target.TargetError): + target.candidate_bindings(row) + api["/pulls/113"]["merged"] = False + with self.assertRaises(target.TargetError): + target.bind(fake_target(True), intent=fake_intent(), git=git, live=gh) + + def test_certification_wrong_parent_or_stale_pr_blocks(self): + gh, git, api = fake_apis(True) + api["/pulls/116"]["head"]["sha"] = "f" * 40 + with self.assertRaises(target.TargetError): + target.bind(fake_target(True), intent=fake_intent(), git=git, live=gh) + gh, git, api = fake_apis(True) + api["/pulls/113"]["merge_commit_sha"] = "0" * 40 + with self.assertRaises(target.TargetError): + target.bind(fake_target(True), intent=fake_intent(), git=git, live=gh) + + def test_pr_borrowing_base_branch_state_and_foreign_repo_block(self): + for modifier in ( + lambda p: p["head"].update(ref="research/foreign"), + lambda p: p["base"].update(sha="0" * 40), + lambda p: p["head"]["repo"].update(full_name="other/PermanssonLean"), + lambda p: p.update(merged=True), + lambda p: p.update(draft=False), + lambda p: p.update(merge_commit_sha="f" * 40), + ): + gh, git, api = fake_apis() + modifier(api["/pulls/113"]) + with self.subTest(modifier=modifier), self.assertRaises(target.TargetError): + target.bind(fake_target(), intent=fake_intent(), git=git, live=gh) + + def test_synthetic_parent_or_source_tree_mismatch_blocks(self): + gh, git, _ = fake_apis() + def wrong_parents(*args): + if args[:3] == ("show", "-s", "--format=%P"): + return H + " " + B + return git(*args) + with self.assertRaises(target.TargetError): + target.bind(fake_target(), intent=fake_intent(), + git=wrong_parents, live=gh) + def wrong_tree(*args): + if args == ("rev-parse", S + "^{tree}"): + return "changed" + return git(*args) + with self.assertRaises(target.TargetError): + target.bind(fake_target(), intent=fake_intent(), + git=wrong_tree, live=gh) + + def test_extra_runtime_approval_fields_never_accepted(self): + b = fake_target() + b["reviewer_approved"] = True + with self.assertRaises(target.TargetError): + target.candidate_bindings(b) + b = fake_target(True) + b["infrastructure_merge_sha"] = None + with self.assertRaises(target.TargetError): + target.candidate_bindings(b) + + def test_git_source_intent_not_dirty_checkout(self): + intent = fake_intent() + calls = [] + def fake_git(*args): + calls.append(args) + if args[:2] == ("cat-file", "-t"): + return "commit" + if args[:2] == ("show", B + ":" + target.INTENT_PATH): + return json.dumps(intent) + raise AssertionError(args) + self.assertEqual(target.exact_intent(B, git=fake_git), intent) + self.assertTrue(any(a[0] == "show" for a in calls)) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_required_dual_gate.py b/verification/research/test_pr113_required_dual_gate.py new file mode 100644 index 00000000..3806cb4e --- /dev/null +++ b/verification/research/test_pr113_required_dual_gate.py @@ -0,0 +1,153 @@ +"""Adversarial original-event binding tests; no real approvals manufactured.""" +import json +import unittest +from unittest import mock + +import pr113_required_dual_gate as gate + +B = "1"*40 +H = "2"*40 +S = "3"*40 + +def fake_event(): + return json.dumps({ + "number":113, + "repository":{"full_name":gate.REPOSITORY}, + "pull_request":{ + "base":{"sha":B,"ref":"main", + "repo":{"full_name":gate.REPOSITORY}}, + "head":{"sha":H,"ref":"research/final-mathematical-closure", + "repo":{"full_name":gate.REPOSITORY}}, + }, + }).encode() + +def fake_env(): + return { + "GITHUB_ACTIONS":"true", + "GITHUB_REPOSITORY":gate.REPOSITORY, + "GITHUB_EVENT_NAME":"pull_request", + "GITHUB_REF":"refs/pull/113/merge", + "GITHUB_SHA":S, + "GITHUB_RUN_ID":"2468", + "GITHUB_RUN_ATTEMPT":"2", + "GITHUB_JOB":"archive-reproducibility", + "GITHUB_WORKFLOW":"Research integrity", + } + +def payload(): + return {"target":{"base_sha":B,"head_sha":H,"synthetic_sha":S}} + +def original(): + return { + "status":"HISTORICAL_T_AND_A2_GENUINE_WITH_INDEPENDENT_SECURITY_REVIEW", + "source_head":H, + "historical_source_and_security_review_authenticated":True, + "required_ci_successful":0, + "merge_authorized":False, + "publication_authorized":False, + "independent_mathematical_review":False, + "genuine_artifacts":{"T":{"sha256":"a"*64},"A2":{"sha256":"b"*64}}, + "T_execution":{"status":"OLD_T_COMPILER_PASS"}, + "A2_execution":{"status":"OLD_A2_COMPILER_PASS"}, + } + +def git(*args): + if args==("rev-parse","HEAD"): + return S + raise AssertionError("unexpected Git operation") + +class RequiredDualGateTests(unittest.TestCase): + def check(self, *, env=None, event=None, target=None, source=None): + return gate.verify( + payload() if target is None else target, + b"real T ZIP",b"real A2 ZIP", + env=fake_env() if env is None else env, + event_data=fake_event() if event is None else event, + git=git, historical_gate=(lambda *args: original() if source is None else source)) + + def test_proposed_gate_is_still_not_mathematical_or_merge_approval(self): + r=self.check() + self.assertEqual(r["head_sha"],H) + self.assertEqual(r["synthetic_sha"],S) + self.assertEqual(r["github_job"],"archive-reproducibility") + self.assertRegex(r["event_sha256"],r"^[0-9a-f]{64}$") + for key in ("all_required_CI_accepted","mathematical_acceptance", + "owner_merge_authorized","publication_authorized"): + self.assertFalse(r[key]) + + def test_wrong_pr_event_or_borrowed_merge_rejected_before_gate(self): + for mutation in ( + lambda e:e.update(GITHUB_ACTIONS=""), + lambda e:e.update(GITHUB_EVENT_NAME="push"), + lambda e:e.update(GITHUB_REF="refs/heads/main"), + lambda e:e.update(GITHUB_SHA="9"*40), + lambda e:e.update(GITHUB_JOB="compiler-shard"), + lambda e:e.update(GITHUB_WORKFLOW="Other"), + lambda e:e.update(GITHUB_RUN_ATTEMPT="0"), + ): + env=fake_env() + mutation(env) + with self.subTest(mutation=mutation),self.assertRaises( + gate.RequiredGateError): + self.check(env=env) + wrong=payload() + wrong["target"]["head_sha"]="9"*40 + with self.assertRaises(gate.RequiredGateError): + self.check(target=wrong) + untrusted=json.loads(fake_event()) + untrusted["number"]=114 + with self.assertRaises(gate.RequiredGateError): + self.check(event=json.dumps(untrusted).encode()) + + def test_research_workflow_may_use_same_approved_both_legs(self): + env=fake_env() + env.update(GITHUB_JOB="research",GITHUB_WORKFLOW="Research Lean") + r=self.check(env=env) + self.assertEqual(r["github_job"],"research") + self.assertFalse(r["all_required_CI_accepted"]) + + def test_failed_original_historical_review_never_becomes_green(self): + for invalid in ( + lambda x:x.update(required_ci_successful=12), + lambda x:x.update(merge_authorized=True), + lambda x:x.update(independent_mathematical_review=True), + lambda x:x.update(historical_source_and_security_review_authenticated=False), + lambda x:x.update(status="CANDIDATE_ONLY"), + ): + value=original() + invalid(value) + with self.subTest(invalid=invalid),self.assertRaises( + gate.RequiredGateError): + self.check(source=value) + + def test_main_push_cannot_reuse_draft_only_historical_gate(self): + ctx=fake_env() + ctx.update(GITHUB_EVENT_NAME="push", + GITHUB_REF="refs/heads/main") + with self.assertRaises(gate.RequiredGateError): + gate.verify(payload(),b"T",b"A2",env=ctx, + event_data=fake_event(),git=git, + historical_gate=lambda *args:original()) + + def test_postmerge_uses_real_git_default_instead_of_none(self): + environ=fake_env() + environ.update(GITHUB_EVENT_NAME="push",GITHUB_REF="refs/heads/main") + with mock.patch("pr113_historical_postmerge.verify", + return_value={"status":"POSTMERGE_ONLY"}) as actual: + value=gate.verify(payload(),b"T",b"A2", + env=environ,event_data=fake_event()) + self.assertEqual(value["status"],"POSTMERGE_ONLY") + self.assertNotIn("git",actual.call_args.kwargs) + self.assertEqual(actual.call_args.kwargs["env"],environ) + + + def test_local_environment_cannot_pass_required_gate(self): + with mock.patch.dict(gate.os.environ,{},clear=True): + with self.assertRaises(gate.RequiredGateError): + gate.verify(payload(),b"T",b"A2", + event_data=fake_event(),git=git, + historical_gate=lambda *a:original()) + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_reviewed_producer_branch_v3.py b/verification/research/test_pr113_reviewed_producer_branch_v3.py new file mode 100644 index 00000000..9c8c2832 --- /dev/null +++ b/verification/research/test_pr113_reviewed_producer_branch_v3.py @@ -0,0 +1,105 @@ +"""Synthetic GitHub ancestry fixtures; never authorizes historical evidence.""" +import unittest + +import pr113_reviewed_producer_branch_v3 as target +from release_target import B113 + +H113 = "a"*40 +M113 = "b"*40 +BASE = "c"*40 +H = "d"*40 +M = "e"*40 +BRANCH = "research/cert-real" + + +class BoundBranchTests(unittest.TestCase): + def git(self, *args): + if args[:2] == ("cat-file", "-t"): + return "commit" + if args == ("show", "-s", "--format=%P", M113): + return B113+" "+H113 + if args == ("show", "-s", "--format=%P", M): + return BASE+" "+H + if args[:3] == ("show", "-s", "--format=%P"): + return "not an ordered merge" + if args[0] == "rev-parse" and args[1].endswith("^{tree}"): + return "reviewed_tree" + if args[:2] == ("merge-base", "--is-ancestor"): + return "" + raise AssertionError(args) + + def live(self, path): + if path == "/pulls/113": + return {"number":113,"state":"closed","merged":True, + "merge_commit_sha":M113,"head":{"sha":H113}} + if path == "/branches/main": + return {"commit":{"sha":BASE}} + if path == "/pulls/115": + return self.pr() + if path == "/commits/"+H+"/pulls?per_page=100": + return [self.pr()] + raise AssertionError(path) + + def pr(self): + return {"number":115, "state":"closed", "merged":True, + "merge_commit_sha":M, + "head":{"ref":BRANCH,"sha":H, + "repo":{"full_name":"HMarcusWH/PermanssonLean"}}, + "base":{"sha":BASE,"ref":"main", + "repo":{"full_name":"HMarcusWH/PermanssonLean"}}} + + def test_original_branch_behavior_remains_but_cert_needs_real_ancestry(self): + self.assertEqual(target.authentic_branch(H113,target.ORIGINAL_BRANCH), + target.ORIGINAL_BRANCH) + self.assertEqual(target.authentic_branch(H,BRANCH, + live=self.live,git=self.git),BRANCH) + + def test_merged_cert_remains_source_bound_after_mutable_base_advances(self): + def advanced(path): + if path == "/pulls/115": + pr = self.pr() + pr["base"]["sha"] = "f"*40 + return pr + return self.live(path) + self.assertEqual(target.authentic_branch( + H, BRANCH, live=advanced, git=self.git), BRANCH) + + def test_foreign_pr_wrong_number_branch_or_merge_fail(self): + for patch in ( + {"number":114}, + {"merged":False,"state":"closed"}, + {"merge_commit_sha":"f"*40}, + {"head":{"ref":BRANCH,"sha":"f"*40, + "repo":{"full_name":"HMarcusWH/PermanssonLean"}}}, + {"head":{"ref":BRANCH,"sha":H, + "repo":{"full_name":"attacker/repo"}}}, + ): + def invalid(path): + value=self.live(path) + if path.startswith("/commits/"): + value=[{**value[0],**patch}] + elif path == "/pulls/115": + value={**value,**patch} + return value + with self.subTest(patch=patch),self.assertRaises(target.ProducerBranchError): + target.authentic_branch(H,BRANCH,live=invalid,git=self.git) + + def test_exact_live_pr_disagrees_with_discovered_list_rejects(self): + def wrong(path): + if path == "/pulls/115": + return {**self.pr(), "merge_commit_sha": "f"*40} + return self.live(path) + with self.assertRaises(target.ProducerBranchError): + target.authentic_branch(H,BRANCH,live=wrong,git=self.git) + + def test_missing_m113_ancestry_rejects(self): + def wrong(*args): + if args[:2] == ("merge-base","--is-ancestor"): + return "not ancestor" + return self.git(*args) + with self.assertRaises(target.ProducerBranchError): + target.authentic_branch(H,BRANCH,live=self.live,git=wrong) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_secure_security_review_v3.py b/verification/research/test_pr113_secure_security_review_v3.py new file mode 100644 index 00000000..26c8b9b3 --- /dev/null +++ b/verification/research/test_pr113_secure_security_review_v3.py @@ -0,0 +1,101 @@ +"""Synthetic authorization fixtures; real GitHub review is still mandatory.""" +import json +import unittest + +import pr113_secure_security_review_v3 as gate +from infrastructure_acceptance import InfrastructureError +from release_target import B113 + +H = "a"*40 +REVIEWER = "external-security-reviewer" +AUTHOR = "HMarcusWH" +RID = 2026 + + +def fixture(): + att = {"head_sha": H, "workflow_sha256": "b"*64, + "T_artifact_sha256":"c"*64,"A2_artifact_sha256":"d"*64, + "original_base_sha": B113} + policy={"format":"Permansson.InfrastructureReviewerAuthority.v1", + "status":"OWNER_AUTHORIZED","owner":AUTHOR, + "reviewers":[{"login":REVIEWER,"approved_by":AUTHOR, + "independence_disclosed":True}]} + review={"id":RID,"state":"APPROVED","commit_id":H, + "submitted_at":"2026-10-27T01:00:00Z", + "user":{"login":REVIEWER}, + "body":gate.REVIEW_PREFIX + json.dumps(att)} + return att, policy, review + + +class ExactSecurityReviewTests(unittest.TestCase): + def check(self, changed=None): + att,policy,review=fixture() + if changed: changed(att,policy,review) + return gate.approved_security_review( + RID, AUTHOR, H, att, policy, + paginate=lambda endpoint:[review]) + + def test_full_source_authorization_shape_remains_live_review(self): + self.assertIs(self.check(), True) + + def test_no_bool_ids_self_reviews_or_unauthorized_roles(self): + att,policy,review=fixture() + with self.assertRaises(InfrastructureError): + gate.approved_security_review(True,AUTHOR,H,att,policy, + paginate=lambda endpoint:[review]) + for mutate in ( + lambda a,p,r: r["user"].update(login=AUTHOR), + lambda a,p,r: r.update(state="DISMISSED"), + lambda a,p,r: r.update(commit_id="e"*40), + lambda a,p,r: r.update(submitted_at=None), + lambda a,p,r: p.update(status="UNCONFIGURED"), + lambda a,p,r: p["reviewers"][0].update(independence_disclosed=False), + lambda a,p,r: p["reviewers"].append(dict(p["reviewers"][0])), + lambda a,p,r: a.update(workflow_sha256="f"*64), + ): + with self.subTest(mutate=str(mutate)), self.assertRaises( + InfrastructureError): + self.check(mutate) + + def test_duplicates_unknown_fields_and_incomplete_comment_reject(self): + att,policy,review=fixture() + record=json.dumps(att,sort_keys=True) + forged=record.replace('"head_sha": "'+H+'"', + '"head_sha": "f", "head_sha": "'+H+'"') + self.assertNotEqual(forged,record) + for body in ( + gate.REVIEW_PREFIX+forged, + gate.REVIEW_PREFIX+record+"\n"+gate.REVIEW_PREFIX+record, + gate.REVIEW_PREFIX+json.dumps({**att,"acceptance":True}), + gate.REVIEW_PREFIX+"{}", + "", + ): + def broken(a,p,r): + r["body"]=body + with self.subTest(body=body[:60]), self.assertRaises( + InfrastructureError): + self.check(broken) + + def test_latest_dismissal_cannot_borrow_an_earlier_green_review(self): + att,policy,approved=fixture() + newer=dict(approved,id=RID+1,state="DISMISSED", + submitted_at="2026-10-27T02:00:00Z") + with self.assertRaises(InfrastructureError): + gate.approved_security_review( + RID,AUTHOR,H,att,policy, + paginate=lambda endpoint:[approved,newer]) + + def test_committed_roster_duplicate_authority_keys_reject(self): + original='{"format":"Permansson.InfrastructureReviewerAuthority.v1",' + raw=(original+'"status":"UNCONFIGURED","status":"OWNER_AUTHORIZED",' + '"owner":"HMarcusWH","reviewers":[{"login":"x"}]}') + def git(*args): + if args[0]=="show": + return raw + raise AssertionError(args) + with self.assertRaises(InfrastructureError): + gate.authority_policy(H,git=git) + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_selection_candidate_survey.py b/verification/research/test_pr113_selection_candidate_survey.py new file mode 100644 index 00000000..38635fda --- /dev/null +++ b/verification/research/test_pr113_selection_candidate_survey.py @@ -0,0 +1,222 @@ +"""Selection survey adversaries; synthetic maps are never reviewed choices.""" +import json +from pathlib import Path +import subprocess +import tempfile +import unittest + +import pr113_selection_candidate_survey as survey +from pr113_v31_execution_integrity import CLAIMS + +H = "a" * 40 +ROUTES = "verification/research/test_fixture_routes.py" +CLAIM_TESTS = "verification/research/test_fixture_claims.py" +CASE_IDS = [f"C-{i:02}" for i in range(1, 46)] + + +def method(cid): + return "test_" + cid.replace("-", "").lower() + + +def proof(owner): + return "PermanssonResearch/Fixture/" + owner.replace("-", "") + ".lean" + + +def test_source(classes): + body = "" + for cls, names in classes.items(): + body += f"class {cls}:\n" + "".join( + f" def {n}(self):\n pass\n" for n in names) + return body.encode() + + +def fixture(): + cases = {"format": "fixture", "status": "UNREVIEWED", "cases": [{ + "case_id": c, + "test_method_candidates": [ + {"path": ROUTES, "method": method(c), "role": "COMPILER_SOURCE"}, + {"path": ROUTES, "method": method(c) + "_false", + "role": "SEMANTIC_NEGATIVE"}, + ], + "historical_proof_declarations": [{"path": proof(c), "name": "t"}], + } for c in CASE_IDS]} + claims = {"format": "fixture", "status": "UNREVIEWED", + "count": len(CLAIMS), "claims": [{ + "claim_id": c, "primary_path": proof(c), + "declarations": [{"path": proof(c), "declaration": "d"}], + "test_source_candidates": [CLAIM_TESTS], + "original_case_links": ["C-01"], + } for c in sorted(CLAIMS)]} + blobs = { + ROUTES: test_source({"Routes": [ + n for c in CASE_IDS for n in (method(c), method(c) + "_false")]}), + CLAIM_TESTS: test_source({"Claims": [ + "test_" + c.lower() + side for c in sorted(CLAIMS) + for side in ("_true", "_false")]}), + **{proof(c): b"theorem t : True := trivial\n" for c in CASE_IDS}, + **{proof(c): b"theorem d : True := trivial\n" for c in CLAIMS}, + } + return cases, claims, blobs + + +def run(cases, claims, blobs): + encode = lambda value: (json.dumps(value) + "\n").encode() + return survey.build_survey(H, encode(cases), encode(claims), blobs.get) + + +class SelectionSurveyTests(unittest.TestCase): + def test_complete_survey_is_deterministic_and_unreviewed(self): + cases, claims, blobs = fixture() + report = run(cases, claims, blobs) + self.assertEqual(report, run(*fixture())) + self.assertEqual(report["status"], "UNREVIEWED_SURVEY_COVERAGE_COMPLETE") + self.assertEqual(report["summary"]["original_cases"], 45) + self.assertEqual(report["summary"]["claims"], 11) + for field in ("selection_written", "independent_review", + "mathematical_relevance_established"): + self.assertIs(report[field], False) + self.assertEqual(report["accepted_cases"], 0) + self.assertEqual(report["accepted_claims"], 0) + self.assertEqual(report["cases"][0]["positive_routes"][0] + ["defining_classes"], ["Routes"]) + for claim in report["claims"]: + self.assertIsNone(claim["negative_lean_statement"]) + self.assertIn("POSITIVE_AND_NEGATIVE_TEST_METHODS_REQUIRE_REVIEW", + claim["flags"]) + self.assertIn("FALSE_LEAN_STATEMENT_NOT_PROVIDED", claim["flags"]) + self.assertIn("TEST_SOURCE_SHARED_WITH_OTHER_CLAIMS", claim["flags"]) + self.assertEqual(report["summary"]["claims_sharing_test_sources"], + sorted(CLAIMS)) + self.assertIn("UNREVIEWED", survey.markdown(report)) + + def test_claim_specific_test_source_is_not_flagged_shared(self): + cases, claims, blobs = fixture() + own = "verification/research/test_fixture_q1.py" + claims["claims"][[c["claim_id"] for c in claims["claims"]] + .index("Q1")]["test_source_candidates"] = [own] + blobs[own] = test_source({"Q1": ["test_q1_true", "test_q1_false"]}) + report = run(cases, claims, blobs) + q1 = next(c for c in report["claims"] if c["claim_id"] == "Q1") + self.assertNotIn("TEST_SOURCE_SHARED_WITH_OTHER_CLAIMS", q1["flags"]) + self.assertEqual(q1["test_sources"][0]["shared_with_claims"], []) + self.assertNotIn("Q1", report["summary"]["claims_sharing_test_sources"]) + + def test_multiple_eligible_routes_are_flagged_not_chosen(self): + cases, claims, blobs = fixture() + cases["cases"][1]["test_method_candidates"].append({ + "path": ROUTES, "method": method("C-03"), "role": "EXACT_CHECK"}) + report = run(cases, claims, blobs) + self.assertEqual( + report["summary"]["cases_with_multiple_eligible_positive_routes"], + ["C-02"]) + self.assertEqual(report["cases"][1]["eligible_positive_route_count"], 2) + + def test_missing_or_duplicate_case_owner_fails_closed(self): + for name, edit in ( + ("missing", lambda c: c["cases"].pop()), + ("duplicate", lambda c: c["cases"].__setitem__( + 1, dict(c["cases"][0]))), + ("malformed", lambda c: c["cases"][0].__setitem__( + "case_id", "../C-01")), + ("untyped", lambda c: c["cases"][0].__setitem__("case_id", 1)), + ): + with self.subTest(fault=name): + cases, claims, blobs = fixture() + edit(cases) + with self.assertRaises(survey.SurveyError): + run(cases, claims, blobs) + + def test_missing_duplicate_or_foreign_claim_owner_fails_closed(self): + for name, edit in ( + ("missing", lambda c: c["claims"].pop()), + ("duplicate", lambda c: c["claims"].__setitem__( + 1, dict(c["claims"][0]))), + ("foreign", lambda c: c["claims"][0].__setitem__( + "claim_id", "Z9")), + ("count", lambda c: c.__setitem__("count", 12)), + ): + with self.subTest(fault=name): + cases, claims, blobs = fixture() + edit(claims) + with self.assertRaises(survey.SurveyError): + run(cases, claims, blobs) + + def test_foreign_providers_and_links_fail_closed(self): + for name, edit in ( + ("case-provider-outside-lean-roots", lambda c, k: c["cases"][0] + ["historical_proof_declarations"][0].__setitem__( + "path", "verification/research/Fixture.lean")), + ("case-provider-escape", lambda c, k: c["cases"][0] + ["historical_proof_declarations"][0].__setitem__( + "path", "PermanssonResearch/../Fixture.lean")), + ("claim-primary-not-declared", lambda c, k: k["claims"][0] + .__setitem__("primary_path", proof("C-01"))), + ("claim-foreign-case-link", lambda c, k: k["claims"][0] + .__setitem__("original_case_links", ["Z-99"])), + ("foreign-test-path", lambda c, k: c["cases"][0] + ["test_method_candidates"][0].__setitem__( + "path", "verification/integrity/tests/test_x.py")), + ("unknown-role", lambda c, k: c["cases"][0] + ["test_method_candidates"][0].__setitem__("role", "ACCEPTED")), + ): + with self.subTest(fault=name): + cases, claims, blobs = fixture() + edit(cases, claims) + with self.assertRaises(survey.SurveyError): + run(cases, claims, blobs) + + def test_absent_method_is_ineligible_and_coverage_incomplete(self): + cases, claims, blobs = fixture() + blobs[ROUTES] = blobs[ROUTES].replace( + b"def " + method("C-04").encode() + b"(", b"def renamed(") + report = run(cases, claims, blobs) + case = report["cases"][3] + self.assertEqual(case["positive_routes"][0]["status"], "ABSENT_METHOD") + self.assertIn("NO_ELIGIBLE_POSITIVE_ROUTE", case["flags"]) + self.assertEqual(report["status"], + "UNREVIEWED_SURVEY_COVERAGE_INCOMPLETE") + self.assertEqual( + report["summary"]["cases_without_complete_coverage"], ["C-04"]) + + def test_ambiguous_class_and_absent_sources_are_incomplete(self): + cases, claims, blobs = fixture() + blobs[ROUTES] += test_source({"Other": [method("C-05")]}) + del blobs[proof("C-06")] + del blobs[CLAIM_TESTS] + report = run(cases, claims, blobs) + self.assertEqual(report["cases"][4]["positive_routes"][0]["status"], + "AMBIGUOUS_CLASS") + self.assertIn("NO_LEAN_PROVIDER_AT_HEAD", report["cases"][5]["flags"]) + self.assertEqual(report["summary"]["cases_without_complete_coverage"], + ["C-05", "C-06"]) + self.assertEqual(report["summary"]["claims_without_complete_coverage"], + sorted(CLAIMS)) + + def test_exact_head_cli_writes_only_detached_unreviewed_survey(self): + head = subprocess.run(["git", "rev-parse", "HEAD"], cwd=survey.ROOT, + capture_output=True, text=True).stdout.strip() + with tempfile.TemporaryDirectory() as temporary: + inside = survey.ROOT / "selection-survey-must-not-exist" + self.assertEqual(survey.main(["--head", head, + "--out", str(inside)]), 2) + self.assertFalse(inside.exists()) + reviewed = Path(temporary) / "reviewed_positive45_selection_v31" + self.assertEqual(survey.main(["--head", head, + "--out", str(reviewed)]), 2) + self.assertEqual(survey.main(["--head", "b" * 40, "--out", + str(Path(temporary) / "stale")]), 2) + out = Path(temporary) / "survey" + self.assertIn(survey.main(["--head", head, "--out", str(out)]), + (0, 1)) + self.assertEqual(sorted(p.name for p in out.iterdir()), + [survey.JSON_NAME, survey.MARKDOWN_NAME]) + report = json.loads((out / survey.JSON_NAME).read_text()) + self.assertEqual(report["source_head"], head) + self.assertEqual(report["summary"]["original_cases"], 45) + self.assertEqual(report["summary"]["claims"], 11) + self.assertIs(report["independent_review"], False) + self.assertEqual(report["accepted_cases"], 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_semantic_boundary_candidates.py b/verification/research/test_pr113_semantic_boundary_candidates.py new file mode 100644 index 00000000..d9e289e9 --- /dev/null +++ b/verification/research/test_pr113_semantic_boundary_candidates.py @@ -0,0 +1,62 @@ +"""Unreviewed PR113 original-case semantic-negative candidate checks. + +These checks retain the ORIGINAL Lean source and append a single false +mathematical claim drawn from its source-bound counterexample. They are not +independent case acceptance, a signed receipt, or evidence of an executed CI +job. A clean original compilation is required before negative rejection. +""" +from pathlib import Path +import shutil +import tempfile +import unittest + +import final_closure_original_semantic_batch as batch +import final_closure_mutation_evidence as evidence + +ROOT = Path(__file__).resolve().parents[2] +CASES = ("A-03", "C-01", "D-07", "E-01") + + +class OriginalBoundaryNegativeCandidates(unittest.TestCase): + def verify(self, case_id): + self.assertIn(case_id, CASES) + if shutil.which("lake") is None: + self.fail("pinned Lean/Lake compiler required; no mock certification") + entry, mutated = batch.frozen_spec(case_id) + source = ROOT / entry["source"] + original = source.read_bytes() + self.assertTrue(mutated.startswith(original)) + self.assertNotEqual(mutated, original) + self.assertEqual(mutated.count(b"\nimport "), original.count(b"\nimport ")) + positive = evidence.lean_result(source) + with tempfile.TemporaryDirectory(prefix="pr113-boundary-", + dir=ROOT / ".lake") as temporary: + mutant_path = Path(temporary) / "OriginalFalse.lean" + mutant_path.write_bytes(mutated) + negative = evidence.lean_result(mutant_path) + self.assertTrue(evidence.verify_negative( + positive, negative, original, mutated)) + + def test_A_03_unbounded_TV_does_not_bound_W1(self): + self.verify("A-03") + + def test_C_01_bounded_descriptor_occupation_nonconvergence(self): + self.verify("C-01") + + def test_D_07_finite_survival_does_not_imply_infinite_safety(self): + self.verify("D-07") + + def test_E_01_seam_invisibility_does_not_force_rank_one(self): + # The original exact rational rank-two matrix refutes the false + # rank-one universal conclusion independently of Lean diagnostics. + matrix = ((0, 1), (1, 2)) + self.assertTrue(all( + matrix[i][j] - matrix[i][0] - matrix[0][j] + matrix[0][0] == 0 + for i in range(2) for j in range(2))) + self.assertEqual(matrix[0][0] * matrix[1][1] - + matrix[0][1] * matrix[1][0], -1) + self.verify("E-01") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_semantic_factorization_candidates.py b/verification/research/test_pr113_semantic_factorization_candidates.py new file mode 100644 index 00000000..46de566d --- /dev/null +++ b/verification/research/test_pr113_semantic_factorization_candidates.py @@ -0,0 +1,61 @@ +"""A04/A05 conditional boundary candidates, not source-review acceptance. + +A04 is an explicit four-output input-necessity counterexample and must NOT +be misreported as a fully realized canonical four-path-law comparison. +A05 compares the SAME finite kernel's stationary mass with the literal +infinite physical survival event. It does NOT replace the original property +or construct a new empirical GR certificate. +""" +from fractions import Fraction as Q +from pathlib import Path +import shutil +import tempfile +import unittest + +import final_closure_original_semantic_batch as batch +import final_closure_mutation_evidence as evidence + +ROOT = Path(__file__).resolve().parents[2] + + +class FactorizationBoundaryCandidates(unittest.TestCase): + def verify(self, case_id): + self.assertIn(case_id, ("A-04", "A-05")) + self.assertIsNotNone(shutil.which("lake"), + "pinned Lean must execute; no fake compiler receipt") + entry, mutant = batch.frozen_spec(case_id) + original = (ROOT / entry["source"]).read_bytes() + self.assertTrue(mutant.startswith(original)) + self.assertEqual( + [x for x in mutant.splitlines() if x.startswith(b"import ")], + [x for x in original.splitlines() if x.startswith(b"import ")] + ) + positive = evidence.lean_result(ROOT / entry["source"]) + with tempfile.TemporaryDirectory(dir=ROOT / ".lake", + prefix="pr113-factor-") as tmp: + path = Path(tmp) / "FalseAssertion.lean" + path.write_bytes(mutant) + negative = evidence.lean_result(path) + self.assertTrue(evidence.verify_negative( + positive, negative, original, mutant)) + + def test_A_04_missing_fourth_output_changes_margin(self): + gap = lambda a,b,c,d: abs(abs(c-d)-abs(a-b)) + self.assertEqual(gap(Q(0),Q(1),Q(0),Q(0)),Q(1)) + self.assertEqual(gap(Q(0),Q(1),Q(0),Q(1)),Q(0)) + self.verify("A-04") + + def test_A_05_stationary_mass_is_not_forever_survival(self): + q = Q(99,100) + pi = (q,1-q) + P = ((q,1-q),(q,1-q)) + self.assertEqual(sum(pi),1) + self.assertTrue(all(sum(pi[i]*P[i][j] for i in range(2))==pi[j] + for j in range(2))) + self.assertEqual(q**1,q) + self.assertLess(q**100,q) + self.verify("A-05") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_semantic_receipt_v2.py b/verification/research/test_pr113_semantic_receipt_v2.py new file mode 100644 index 00000000..5016802a --- /dev/null +++ b/verification/research/test_pr113_semantic_receipt_v2.py @@ -0,0 +1,149 @@ +"""Source-bound current A2 semantic receipt validation (synthetic fixtures). + +Mocks no verifier. Real original historical T/A2 CI and human review remain +separate required evidence and none of these tests authorizes a migration. +""" +from __future__ import annotations + +import copy +import hashlib +import json +import unittest + +import infrastructure_acceptance as legacy +import pr113_semantic_receipt_v2 as current +from localized_prefix_gate import MUTATIONS as HIST, mutated_source as mutate_hist +from pr113_a2_successor_compiler import ( + MUTATIONS as SUCC, mutated_source as mutate_succ, +) + + +def digest(raw): + return hashlib.sha256(raw).hexdigest() + + +def fixture(prefix, mutations, mutate, *, mode="focused"): + # Artificial source containing only the four specific mutation anchors. + positive = "\n".join(old for old, new in mutations.values()).encode("utf-8") + all_files = {prefix + "/PositiveControl.lean": positive} + cmds = [] + items = {} + labels = (["compiler_version", "research_build", "source_1", "source_2", + "source_3", "source_4", "signatures_axioms", "positive_fixture"] + + (["original_default_build"] if mode == "full" else []) + + list(mutations)) + for i, label in enumerate(labels, 1): + mutant = label in mutations + raw = ( + ("/detached/" + label + ".lean:20:5: error: unsolved goals\n" + + "⊢ False\n").encode("utf-8") + if mutant else b"source verified\n") + filename = f"{i:02d}_{label}.log" + all_files[prefix + "/" + filename] = raw + cmds.append({ + "label": label, "returncode": 1 if mutant else 0, + "log": filename, "log_sha256": digest(raw), + }) + for label in mutations: + source = mutate(positive.decode("utf-8"), label).encode("utf-8") + all_files[prefix + "/" + label + ".lean"] = source + items[label] = { + "status": "SEMANTICALLY_REJECTED", + "positive_source_sha256": digest(positive), + "mutant_source_sha256": digest(source), + } + row = { + "status": ("LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING" + if prefix == "historical_original_A2" + else "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING"), + "mode": mode, "source_unchanged": True, + "declarations": {"synthetic.fixture": ["propext"]}, + "mutations": items, "commands": cmds, + "artifacts": {}, + } + for full_path, raw in all_files.items(): + row["artifacts"][full_path.removeprefix(prefix + "/")] = { + "sha256": digest(raw), "bytes": len(raw), + } + all_files[prefix + "/receipt.json"] = ( + json.dumps(row, sort_keys=True) + "\n").encode("utf-8") + return all_files + + +class A2ReceiptSemanticTests(unittest.TestCase): + def _valid(self, prefix="historical_original_A2", *, mode="focused"): + mutations, mutate = ( + (HIST, mutate_hist) if prefix == "historical_original_A2" + else (SUCC, mutate_succ)) + return fixture(prefix, mutations, mutate, mode=mode) + + def _check(self, files, prefix="historical_original_A2"): + expected = HIST if prefix == "historical_original_A2" else SUCC + return current.validate_compiler_receipt_v2( + files, prefix, + ("LOCAL_KERNEL_CHECKED__CI_ACCEPTANCE_PENDING" + if prefix == "historical_original_A2" + else "SUCCESSOR_KERNEL_CHECKED_REVIEW_PENDING"), + expected) + + def test_genuine_four_source_probes_are_required_and_accepted(self): + for prefix in ("historical_original_A2", "pr113_successor_A2"): + for mode in ("focused", "full"): + with self.subTest(prefix=prefix, mode=mode): + self._check(self._valid(prefix, mode=mode), prefix) + + def test_source_probe_missing_or_duplicate_rejected(self): + files = self._valid() + prefix = "historical_original_A2" + for change in ("drop", "duplicate"): + bad = copy.deepcopy(files) + receipt = json.loads(bad[prefix + "/receipt.json"]) + if change == "drop": + receipt["commands"] = [ + x for x in receipt["commands"] if x["label"] != "source_4"] + else: + receipt["commands"].append(receipt["commands"][0]) + bad[prefix + "/receipt.json"] = json.dumps(receipt).encode() + with self.subTest(change=change), self.assertRaises( + legacy.InfrastructureError): + self._check(bad) + + def test_mutant_source_must_be_exact_intended_single_change(self): + files = self._valid() + prefix = "historical_original_A2" + name = "wrong_reported_effect" + filename = prefix + "/" + name + ".lean" + files[filename] += b"\n-- unrelated source edit\n" + receipt = json.loads(files[prefix + "/receipt.json"]) + receipt["mutations"][name]["mutant_source_sha256"] = digest(files[filename]) + receipt["artifacts"][name + ".lean"] = { + "bytes": len(files[filename]), "sha256": digest(files[filename])} + files[prefix + "/receipt.json"] = json.dumps(receipt).encode() + with self.assertRaises(legacy.InfrastructureError): + self._check(files) + + def test_wrong_named_mutant_parser_or_import_error_not_accepted(self): + for output in ( + b"/detached/wrong_reported_effect.lean:20:5: error: unknown module\n", + b"/detached/wrong_reported_effect.lean:20:5: error: unexpected token\n", + b"some_other_file.lean:20:5: error: unsolved goals\n", + b"/detached/wrong_reported_effect.lean:20:5: error: timeout\n", + ): + files = self._valid() + prefix = "historical_original_A2" + row = json.loads(files[prefix + "/receipt.json"]) + entry = next(c for c in row["commands"] + if c["label"] == "wrong_reported_effect") + key = prefix + "/" + entry["log"] + files[key] = output + entry["log_sha256"] = digest(output) + row["artifacts"][entry["log"]] = { + "sha256": digest(output), "bytes": len(output)} + files[prefix + "/receipt.json"] = json.dumps(row).encode() + with self.subTest(output=output), self.assertRaises( + legacy.InfrastructureError): + self._check(files) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_semantic_second_candidates.py b/verification/research/test_pr113_semantic_second_candidates.py new file mode 100644 index 00000000..78cf2c3a --- /dev/null +++ b/verification/research/test_pr113_semantic_second_candidates.py @@ -0,0 +1,89 @@ +"""PR113 original-case mathematical negative candidates (unreviewed). + +Each test targets an original model/theorem and compiles its unchanged Lean +source as the positive control. The appended mathematically false assertion +must be rejected by the intended diagnostic, not by imports, tactics, timeouts, +or an unavailable compiler. No CI or scholarly approval is inferred here. +""" +from fractions import Fraction +from pathlib import Path +import shutil +import tempfile +import unittest + +import final_closure_original_semantic_batch as batch +import final_closure_mutation_evidence as evidence + +ROOT = Path(__file__).resolve().parents[2] +CASES = ("S-01", "M-01", "A-02", "D-01", "C11-01", "C11-02", "XPLUS-01") + + +class SecondOriginalNegativeCandidates(unittest.TestCase): + def verify(self, case_id): + self.assertIn(case_id, CASES) + self.assertIsNotNone(shutil.which("lake"), + "pinned Lean required for semantic evidence") + entry, mutant = batch.frozen_spec(case_id) + original = (ROOT / entry["source"]).read_bytes() + self.assertTrue(mutant.startswith(original)) + self.assertNotEqual(mutant, original) + self.assertEqual( + [line for line in mutant.splitlines() if line.startswith(b"import ")], + [line for line in original.splitlines() if line.startswith(b"import ")] + ) + positive = evidence.lean_result(ROOT / entry["source"]) + with tempfile.TemporaryDirectory(dir=ROOT / ".lake", + prefix="pr113-negative-") as folder: + file = Path(folder) / "FalseOriginalCase.lean" + file.write_bytes(mutant) + negative = evidence.lean_result(file) + try: + self.assertTrue(evidence.verify_negative( + positive, negative, original, mutant)) + except evidence.MutationEvidenceError as exc: + self.fail(str(exc) + "; actual negative Lean diagnostic:\n" + + (negative.stdout + "\n" + negative.stderr)[-3500:]) + + def test_S_01_no_terminal_correction_is_false(self): + # At horizon zero the omitted terminal term equals the initial V. + self.assertEqual(Fraction(9, 10) ** 0 * Fraction(1, 2), + Fraction(1, 2)) + self.verify("S-01") + + def test_M_01_null_key_is_not_free_initial_support(self): + # The source's different point-start treated path laws are NOT + # contradicted by its almost-everywhere baseline agreement. + self.verify("M-01") + + def test_A_02_identity_kernel_is_not_strictly_contractive(self): + # Two distinct Dirac initial laws are both invariant under identity. + # A nontrivial concrete state type is essential: the earlier + # polymorphic mutant failed in typeclass resolution, not mathematics. + self.assertIn("Kernel ℝ ℝ", batch.SPECS["A-02"]["body"]) + self.assertIn("identityKernel_isW1Contraction", + batch.SPECS["A-02"]["body"]) + ident = {0: {0: Fraction(1)}, 1: {1: Fraction(1)}} + for state in (0, 1): + self.assertEqual(ident[state], {state: Fraction(1)}) + self.verify("A-02") + + def test_D_01_hitting_is_not_residence(self): + word = (False, True, False) + self.assertTrue(any(word)) + self.assertFalse(any(all(word[i:i + 2]) + for i in range(len(word) - 1))) + self.verify("D-01") + + def test_C11_01_zero_dimensional_family_is_rejected(self): + self.verify("C11-01") + + def test_C11_02_corrupted_stationary_report_is_rejected(self): + self.verify("C11-02") + + def test_XPLUS_01_constructed_original_margin_is_not_zero(self): + self.assertEqual(Fraction(1) - Fraction(0), Fraction(1)) + self.verify("XPLUS-01") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_semantic_structural_candidates.py b/verification/research/test_pr113_semantic_structural_candidates.py new file mode 100644 index 00000000..be93d408 --- /dev/null +++ b/verification/research/test_pr113_semantic_structural_candidates.py @@ -0,0 +1,67 @@ +"""Three finite original-theory boundary negatives; candidate evidence only. + +S-02 checks the original ActionTrace.Row information order rather than +granting foresight to a behavioral policy. Q-01 refutes uniform deterministic +lumpability without claiming every projected single-start process non-Markov. +A-01 distinguishes bounded 1-Lipschitz tests from a spurious global +unit-supnorm restriction. Lean compilation/independent review remain required. +""" +from pathlib import Path +from fractions import Fraction +import shutil +import tempfile +import unittest + +import final_closure_original_semantic_batch as batch +import final_closure_mutation_evidence as evidence + +ROOT = Path(__file__).resolve().parents[2] +CASES = ("S-02", "Q-01", "A-01") + + +class StructuralOriginalNegativeCandidates(unittest.TestCase): + def verify(self, case_id): + self.assertIn(case_id, CASES) + self.assertIsNotNone(shutil.which("lake"), + "Lean/Lake required; mathematical evidence cannot be mocked") + entry, mutant = batch.frozen_spec(case_id) + original = (ROOT / entry["source"]).read_bytes() + self.assertTrue(mutant.startswith(original)) + self.assertNotEqual(mutant, original) + self.assertEqual( + [line for line in mutant.splitlines() if line.startswith(b"import ")], + [line for line in original.splitlines() if line.startswith(b"import ")] + ) + positive = evidence.lean_result(ROOT / entry["source"]) + with tempfile.TemporaryDirectory(dir=ROOT / ".lake", + prefix="pr113-structural-") as tmp: + false_path = Path(tmp) / "FalseOriginalCase.lean" + false_path.write_bytes(mutant) + negative = evidence.lean_result(false_path) + self.assertTrue(evidence.verify_negative( + positive, negative, original, mutant)) + + def test_S_02_clairvoyant_action_is_not_admissible_history_row(self): + # Any pre-draw action mixture matches an independent fair coin at 1/2; + # copying the yet-unobserved coin would instead match with certainty. + for prob in (Fraction(0), Fraction(1, 4), Fraction(1)): + self.assertEqual(prob / 2 + (1-prob) / 2, Fraction(1, 2)) + self.verify("S-02") + + def test_Q_01_fine_projection_does_not_always_lump(self): + q = lambda x: x == 1 + step = lambda x: 1 if x == 0 else 0 + self.assertEqual(q(0), q(2)) + self.assertNotEqual(q(step(0)), q(step(2))) + self.verify("Q-01") + + def test_A_01_unit_supnorm_is_not_correct_KR_test_class(self): + # Four-unit ground distance; a <=1 absolute-value restriction + # caps the two-point difference at 2. + self.assertEqual(abs(Fraction(4)-Fraction(0)), 4) + self.assertLess(2, 4) + self.verify("A-01") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_strict_release_json_v3.py b/verification/research/test_pr113_strict_release_json_v3.py new file mode 100644 index 00000000..79e6d04a --- /dev/null +++ b/verification/research/test_pr113_strict_release_json_v3.py @@ -0,0 +1,53 @@ +"""New external scholarly/producer input JSON is never last-key-wins. + +These fixtures are not GitHub identity, theorem or independent review evidence. +""" +import tempfile +from pathlib import Path +import unittest +from unittest import mock + +import final_closure_gate as gate +import final_closure_verify as verification + + +class StrictInputBoundaryTests(unittest.TestCase): + def test_duplicate_source_case_decision_is_rejected(self): + with tempfile.TemporaryDirectory() as temp: + record=Path(temp)/"cases.json" + record.write_bytes( + b'{"case_id":"X-01","decision":"PENDING",' + b'"decision":"VERIFIED_SELECTED_SCOPE"}') + with self.assertRaises(ValueError): + verification.read_json(record) + + def test_duplicate_detached_receipt_stops_before_github_review(self): + with tempfile.TemporaryDirectory() as temp: + receipts=Path(temp)/"receipts.json" + bundle=Path(temp)/"evidence.zip" + receipts.write_bytes( + b'{"format":"Permansson.FinalClosureDetachedReceipts.v3",' + b'"format":"REVIEW_ALREADY_ACCEPTED"}') + bundle.write_bytes(b"PK_fake_unreviewed") + with mock.patch.object(gate, "premerge") as authenticate: + result=gate.main([ + "--phase","premerge","--receipts",str(receipts), + "--bundle",str(bundle)]) + self.assertEqual(result,2) + authenticate.assert_not_called() + + def test_malformed_nonfinite_premerge_source_fails_closed(self): + with tempfile.TemporaryDirectory() as temp: + receipts=Path(temp)/"receipts.json" + bundle=Path(temp)/"evidence.zip" + receipts.write_bytes(b'{"value": NaN}') + bundle.write_bytes(b"PK_fake") + with mock.patch.object(gate, "premerge") as authenticate: + self.assertEqual(gate.main([ + "--phase","premerge","--receipts",str(receipts), + "--bundle",str(bundle)]),2) + authenticate.assert_not_called() + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_successor_workflow_profile.py b/verification/research/test_pr113_successor_workflow_profile.py new file mode 100644 index 00000000..89228ed2 --- /dev/null +++ b/verification/research/test_pr113_successor_workflow_profile.py @@ -0,0 +1,287 @@ +"""25+5 workflow profile mutation gates. No security approval is fabricated.""" +import ast +import copy +import importlib.util +import json +import os +from pathlib import Path +import subprocess +import sys +import unittest +import types +from unittest import mock + +import pr113_successor_workflow_profile as p + +H="a"*40 + + +def fixture(): + approved={f"original-{i:02}.yml":p.digest( + f"name: original {i}\n".encode()) for i in range(25)} + contents={".github/workflows/"+n: + f"name: original {i}\n".encode() + for i,n in enumerate(approved)} + rows=[] + proposed=sorted(p.PROPOSED) + for name in proposed: + staged=p.STAGED_PREFIX+name + data=( + "# REVIEW PENDING\n" + f"name: PR113 {name}\n" + "on:\n push:\n branches: [research/final-mathematical-closure]\n" + "permissions:\n contents: read\n" + "jobs:\n" + " fixture:\n" + " steps:\n" + " - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262\n" + ) + if name=="pr113-source-controls-v3_1.yml": + data+=( + " original-legacy20:\n" + " name: legacy\n" + " original-legacy20-replay:\n" + " if: github.event_name == 'workflow_dispatch'\n" + " run: pr113_legacy20_choice_delivery.py\n" + " review_comment_id:\n" + ) + raw=data.encode() + contents[staged]=raw + rows.append({ + "workflow_path":".github/workflows/"+name, + "staged_source_path":staged, + "git_blob_oid":p.git_blob_oid(raw), + "source_unchanged":False,"approved":False,"installed":False, + }) + manifest={ + "format":"Permansson.PR113.UnreviewedWorkflowProposals.v1", + "status":"SOURCE_STAGED_NONEXECUTABLE_PENDING_INDEPENDENT_SECURITY_REVIEW", + "original_approved_workflow_count":25, + "historic_T":p.T,"base_B":p.B113, + "independent_security_review":False, + "owner_authorized_activation":False, + "mathematical_acceptance":False, + "release_authorized":False, + "proposals":rows, + } + contents[p.MANIFEST]=json.dumps(manifest).encode() + listing=("\n".join(sorted(".github/workflows/"+n + for n in approved))+"\n").encode() + return approved,contents,listing + + +class WorkflowProfileTests(unittest.TestCase): + def test_explicit_25_plus_5_not_activation(self): + approved,files,tree=fixture() + report=p.audit(H,read=files.__getitem__, + tree=lambda:tree,approved=approved) + self.assertEqual(report["original_workflow_count"],25) + self.assertEqual(report["proposed_future_workflow_count"],30) + self.assertFalse(report["independent_security_review"]) + self.assertFalse(report["owner_activation_authorized"]) + self.assertFalse(report["publication_authorized"]) + + def test_new_active_file_not_granted_historical_status(self): + approved,files,tree=fixture() + tree+=b".github/workflows/unreviewed.yml\n" + with self.assertRaises(p.ProfileError): + p.audit(H,read=files.__getitem__,tree=lambda:tree,approved=approved) + + def test_original_workflow_bytes_remain_immutable(self): + approved,files,tree=fixture() + files[".github/workflows/original-00.yml"]=b"permissions: write\n" + with self.assertRaises(p.ProfileError): + p.audit(H,read=files.__getitem__,tree=lambda:tree,approved=approved) + + def test_modified_staged_bytes_and_unpinned_action_rejected(self): + approved,files,tree=fixture() + target=p.STAGED_PREFIX+"pr113-preseal-fullscope.yml" + files[target]+=b" checks: write\n" + with self.assertRaises(p.ProfileError): + p.audit(H,read=files.__getitem__,tree=lambda:tree,approved=approved) + approved,files,tree=fixture() + row=json.loads(files[p.MANIFEST]) + files[target]+=b" checks: write\n" + rec=next(x for x in row["proposals"] if + x["staged_source_path"]==target) + rec["git_blob_oid"]=p.git_blob_oid(files[target]) + files[p.MANIFEST]=json.dumps(row).encode() + with self.assertRaises(p.ProfileError): + p.audit(H,read=files.__getitem__,tree=lambda:tree,approved=approved) + + def test_direct_cli_imports_workflow_contract_without_pythonpath(self): + script=Path(p.__file__).resolve() + env=os.environ.copy() + env.pop("PYTHONPATH",None) + done=subprocess.run( + [sys.executable,"-B",str(script),"--head","invalid-head", + "--out",str(script.parent/"NEVER_WRITTEN.json")], + cwd=p.ROOT,env=env,capture_output=True,text=True,timeout=20) + self.assertEqual(done.returncode,2,done.stderr) + self.assertIn("WORKFLOW PROFILE NOT ACCEPTED",done.stderr) + self.assertNotIn("ModuleNotFoundError",done.stderr) + + + def test_real_immutable_historical_25_git_allowlist(self): + head=p.git_bytes("HEAD",op=("rev-parse","HEAD")).decode().strip() + historical=p.authenticated_historical_25(head) + self.assertEqual(len(historical),25) + self.assertTrue(all(len(v)==64 for v in historical.values())) + self.assertEqual(p.HISTORICAL_25_HEAD, + "5189f0c4a0db7535d57a9ff2a7aef6a3e76191b6") + self.assertEqual(p.HISTORICAL_CONTRACT_BLOB, + "f8fa8196a7a0e33d2f218faa0ba570c09aac9a3a") + self.assertNotIn("pr113-historical-migration-candidates.yml",historical) + self.assertNotEqual(next(iter(historical.values())), + "02e97e27e85b9d3353edf495eab1ee8cb8b9e642") + original=p.git_bytes(p.HISTORICAL_25_HEAD,p.HISTORICAL_CONTRACT) + self.assertEqual(p.parse_historical_25_contract(original),historical) + + def test_historical_contract_parser_rejects_24_26_duplicate_dynamic_and_oid(self): + original=p.git_bytes(p.HISTORICAL_25_HEAD,p.HISTORICAL_CONTRACT) + syntax=ast.parse(original) + table=next(item.value for item in syntax.body + if isinstance(item,ast.Assign) + and any(isinstance(t,ast.Name) and + t.id=="APPROVED_WORKFLOWS" for t in item.targets)) + constant=next(item.value for item in syntax.body + if isinstance(item,ast.Assign) + and any(isinstance(t,ast.Name) and + t.id=="APPROVED_WORKFLOW_SHA256" + for t in item.targets)) + self.assertIsInstance(table,ast.Dict) + self.assertEqual(len(table.keys),25) + for variant in ("24","26","duplicate","dynamic","git_oid","wrong_constant"): + altered=copy.deepcopy(syntax) + mapped=next(item.value for item in altered.body + if isinstance(item,ast.Assign) + and any(isinstance(t,ast.Name) and + t.id=="APPROVED_WORKFLOWS" for t in item.targets)) + literal=next(item.value for item in altered.body + if isinstance(item,ast.Assign) + and any(isinstance(t,ast.Name) and + t.id=="APPROVED_WORKFLOW_SHA256" + for t in item.targets)) + if variant=="24": + mapped.keys.pop() + mapped.values.pop() + elif variant=="26": + mapped.keys.append(copy.deepcopy(mapped.keys[0])) + mapped.values.append(copy.deepcopy(mapped.values[0])) + elif variant=="duplicate": + mapped.keys[-1]=copy.deepcopy(mapped.keys[0]) + elif variant=="dynamic": + mapped.values[0]=ast.Call( + func=ast.Name(id="eval",ctx=ast.Load()), + args=[ast.Constant(value="'untrusted'")],keywords=[]) + elif variant=="git_oid": + mapped.values[0]=ast.Constant(value="a"*40) + else: + literal.value="b"*40 + with self.subTest(variant=variant),self.assertRaises(p.ProfileError): + p.parse_historical_25_contract( + ast.unparse(ast.fix_missing_locations(altered)).encode()) + + def test_frozen_git_contract_rejects_spoofed_bytes_and_wrong_tree(self): + real=p.git_bytes + anchor=p.HISTORICAL_25_HEAD + def tamper(head,path=None,*,op=None): + if head==anchor and path==p.HISTORICAL_CONTRACT and op is None: + return real(head,path)+b"\n# unapproved historical change\n" + return real(head,path,op=op) + with mock.patch.object(p,"git_bytes",side_effect=tamper): + with self.assertRaisesRegex(p.ProfileError, + "Git blob byte identity changed"): + p.authenticated_historical_25() + def wrong_tree(head,path=None,*,op=None): + if op==("ls-tree","-r","-z",anchor,"--",".github/workflows"): + data=real(head,path,op=op) + self.assertIn(b"100644 blob ",data) + return data.replace(b"100644 blob ",b"100755 blob ",1) + return real(head,path,op=op) + with mock.patch.object(p,"git_bytes",side_effect=wrong_tree): + with self.assertRaisesRegex(p.ProfileError, + "Git mode unexpected"): + p.authenticated_historical_25() + + def test_historical_python_contract_only_at_exact_original_git_commit(self): + historical=p.HISTORICAL_25_HEAD + contract=p.HISTORICAL_CONTRACT + current=p.git_bytes("HEAD",op=("rev-parse","HEAD")).decode().strip() + self.assertNotEqual(current,historical) + raw=p.git_bytes(historical,contract) + self.assertTrue(raw.startswith(b'"""')) + self.assertEqual(p.git_blob_oid(raw),p.HISTORICAL_CONTRACT_BLOB) + bad=( + (current,contract), + ("a"*40,contract), + ("HEAD",contract), + (historical,"verification/integrity/other.py"), + (historical,"verification/integrity/tests/test_workflow.py"), + (historical,"verification/integrity/workflow_contract.py/extra"), + (historical,"verification/integrity/../integrity/workflow_contract.py"), + (historical,"/verification/integrity/workflow_contract.py"), + (historical,"verification\\integrity\\workflow_contract.py"), + (historical,"verification/integrity/workflow_contract.py:HEAD"), + (historical,".github/workflows/../research-lean.yml"), + (historical,".github/workflows/research-lean.yml:other"), + (historical,p.STAGED_PREFIX+"../other.yml"), + (historical,p.STAGED_PREFIX+"subfolder/manifest.json"), + (historical,p.STAGED_PREFIX+"manifest.json\\other"), + (historical,".github/workflows/evil.yml\\suffix"), + ) + for head,path in bad: + with self.subTest(head=head,path=path),self.assertRaisesRegex( + p.ProfileError,"untrusted workflow source path"): + p.git_bytes(head,path) + self.assertTrue(p.git_bytes(historical, + ".github/workflows/research-lean.yml")) + self.assertTrue(p.git_bytes(current,p.MANIFEST)) + + def test_historical_contract_does_not_accept_dynamic_alias_or_prefix(self): + historical=p.HISTORICAL_25_HEAD + old=p.git_bytes(historical,p.HISTORICAL_CONTRACT) + self.assertEqual(len(p.parse_historical_25_contract(old)),25) + for forged in ( + old.replace(b"APPROVED_WORKFLOWS = {", + b"APPROVED_WORKFLOWS = dict({",1), + old.replace(b"APPROVED_WORKFLOWS = {", + b"APPROVED_WORKFLOWS += {",1), + ): + with self.subTest(forged_digest=p.digest(forged)),self.assertRaises( + p.ProfileError): + p.parse_historical_25_contract(forged) + + def test_legacy_modules_import_with_current_v2_only_namespace(self): + """An installed 30 Git-OID contract may not masquerade as 25 SHA256s.""" + fake=types.ModuleType("verification.integrity.workflow_contract") + fake.APPROVED_WORKFLOW_BLOBS={"research-lean.yml":"a"*40} + real_path=Path(p.__file__).resolve() + atomic_path=real_path.with_name("pr113_atomic_migration_preview.py") + with mock.patch.dict(sys.modules,{ + "verification.integrity.workflow_contract":fake}): + for label,path in (("legacy_25",real_path), + ("atomic_preview",atomic_path)): + spec=importlib.util.spec_from_file_location( + "pr113_v2_import_canary_"+label,path) + self.assertIsNotNone(spec) + candidate=importlib.util.module_from_spec(spec) + spec.loader.exec_module(candidate) + self.assertTrue(callable(candidate.preview if + label=="atomic_preview" else candidate.audit)) + if label=="atomic_preview": + self.assertEqual( + candidate.preview.__kwdefaults__["old_audit"],None) + self.assertFalse(hasattr(fake,"APPROVED_WORKFLOWS")) + + def test_fake_owner_authorization_in_staged_manifest_rejected(self): + approved,files,tree=fixture() + record=json.loads(files[p.MANIFEST]) + record["owner_authorized_activation"]=True + files[p.MANIFEST]=json.dumps(record).encode() + with self.assertRaises(p.ProfileError): + p.audit(H,read=files.__getitem__,tree=lambda:tree,approved=approved) + + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_t2_intake_v2.py b/verification/research/test_pr113_t2_intake_v2.py new file mode 100644 index 00000000..08095484 --- /dev/null +++ b/verification/research/test_pr113_t2_intake_v2.py @@ -0,0 +1,148 @@ +"""V2 T2 receipt integration: real validators, synthetic inputs, no approvals.""" +from __future__ import annotations + +import copy +import hashlib +import json +import unittest + +from infrastructure_acceptance import InfrastructureError +from pr113_dual_tooling_candidate_v2 import validate_dual_reports_v2 +from pr113_historical_intake_v2 import inspect_T_v2 + +T = "71cc34fa0bc4b639c05d2ec5f39f0b88afd56388" +H = "a" * 40 +DIGEST = "b" * 64 + + +def encoded(obj): + return (json.dumps(obj, sort_keys=True) + "\n").encode() + + +def reports(): + old = { + "format": "Permansson.Hardening.Check.v1", + "status": "PASS", + "check": "archive-reproducibility", + "context": {"checked_sha": T}, + "details": { + "acceptance_scope": "accepted_artifact_replay_only", + "isolation": { + "network_isolation": "ENFORCED", + "capabilities": "all_zero", + }, + }, + } + new = { + "format": "Permansson.Hardening.Tests.v1", + "schema_version": 1, + "status": "PASS", + "suite": "workflow", + "execution_tracked_clean": True, + "methods_run": 12, + "failures": 0, "errors": 0, "skips": 0, + "covered_requirements": ["CI-06", "CI-07"], + "context": { + "repository": "HMarcusWH/PermanssonLean", + "checked_sha": H, + "event": "local", + "checked_tree": "c" * 40, + }, + } + profile = { + "format": "Permansson.PR113.ToolingMigrationCandidate.v2", + "reviewed_workflow_source_mode": "active", + "historical_original_workflow_count": 25, + "current_reviewed_workflow_count": 30, + "workflow_profile_sha256": DIGEST, + "independent_security_review": False, + } + return old, new, profile + + +def pieces(): + old, current, profile = reports() + checked = validate_dual_reports_v2( + old, current, T_source=T, H_source=H, profile=profile) + original_raw = encoded(old) + current_raw = encoded(current) + receipt = { + **checked, + "candidate_execution_context": "LOCAL_UNREVIEWED", + "historical_T_report_sha256": hashlib.sha256(original_raw).hexdigest(), + "current_H_report_sha256": hashlib.sha256(current_raw).hexdigest(), + "genuine_current_source_commit": H, + "original_T_archive_source_commit": T, + "original_T_archive_unchanged": True, + "versioned_successor_T2_approved": False, + } + dossier = { + "format": "Permansson.PR113.ToolingSecurityReviewDossier.v2", + "status": "COMPLETE_DUAL_EXECUTION_CANDIDATE_SECURITY_REVIEW_PENDING", + "T": T, "H": H, + "historical_original_workflow_count": 25, + "required_workflow_inventory_count": 30, + "current_workflow_profile_sha256": DIGEST, + "T_original_offline_restore_report_sha256": + hashlib.sha256(original_raw).hexdigest(), + "H_original_workflow_test_report_sha256": + hashlib.sha256(current_raw).hexdigest(), + "legacy_T_did_not_accept_current_C": True, + "owner_authorization_present": False, + "independent_security_review_present": False, + "historical_required_CI_replaced": False, + "mathematical_release_approved": False, + } + return { + "original_T_archive.json": original_raw, + "current_H_workflow.json": current_raw, + "dual_tooling_unreviewed.json": encoded(receipt), + "T2_security_review_candidate.json": encoded(dossier), + } + + +class V2IntakeTests(unittest.TestCase): + def test_exact_25_30_candidate_parses_without_claiming_approval(self): + observed = inspect_T_v2(pieces(), H, profile_sha256=DIGEST) + self.assertEqual(observed["current_reviewed_workflow_count"], 30) + self.assertEqual(observed["historical_original_workflow_count"], 25) + self.assertTrue(observed["historical_T_unchanged"]) + self.assertFalse(observed["independent_security_review"]) + self.assertFalse(observed["mathematical_acceptance"]) + + def test_v1_receipt_must_not_promote_to_v2(self): + elements = pieces() + receipt = json.loads(elements["dual_tooling_unreviewed.json"]) + receipt["format"] = "Permansson.PR113.DualToolingCandidate.v2" + elements["dual_tooling_unreviewed.json"] = encoded(receipt) + with self.assertRaises(InfrastructureError): + inspect_T_v2(elements, H, profile_sha256=DIGEST) + + def test_25_cannot_claim_current_30(self): + elements = pieces() + dossier = json.loads(elements["T2_security_review_candidate.json"]) + dossier["required_workflow_inventory_count"] = 25 + elements["T2_security_review_candidate.json"] = encoded(dossier) + with self.assertRaises(InfrastructureError): + inspect_T_v2(elements, H, profile_sha256=DIGEST) + + def test_foreign_profile_and_fabricated_review_rejected(self): + with self.assertRaises(InfrastructureError): + inspect_T_v2(pieces(), H, profile_sha256="0" * 64) + elements = pieces() + dossier = json.loads(elements["T2_security_review_candidate.json"]) + dossier["independent_security_review_present"] = True + elements["T2_security_review_candidate.json"] = encoded(dossier) + with self.assertRaises(InfrastructureError): + inspect_T_v2(elements, H, profile_sha256=DIGEST) + + def test_missing_genuine_isolation_and_ci07_rejected(self): + for corruption in ("isolation", "ci07"): + old, current, profile = reports() + if corruption == "isolation": + old["details"]["isolation"]["network_isolation"] = "DISABLED" + else: + current["covered_requirements"] = ["CI-06"] + with self.assertRaises(Exception): + validate_dual_reports_v2( + old, current, T_source=T, H_source=H, profile=profile) diff --git a/verification/research/test_pr113_tooling_migration.py b/verification/research/test_pr113_tooling_migration.py new file mode 100644 index 00000000..c23945e6 --- /dev/null +++ b/verification/research/test_pr113_tooling_migration.py @@ -0,0 +1,122 @@ +"""Synthetic Git regression tests for immutable PR113 tooling migration. + +Tests never authorize a historical rewrite, check-run acceptance or release. +""" +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +import subprocess +import tempfile +import unittest + +import pr113_tooling_migration as migration + + +def git(root: Path, *args: str) -> str: + return subprocess.check_output( + ["git", *args], cwd=root, text=True).strip() + + +def commit(root: Path, title: str) -> str: + subprocess.run(["git", "add", "-A"], cwd=root, check=True) + subprocess.run( + ["git", "-c", "user.name=SourceAudit", + "-c", "user.email=audit@example.invalid", + "commit", "-qm", title], + cwd=root, check=True) + return git(root, "rev-parse", "HEAD") + + +class SyntheticImmutableToolingTest(unittest.TestCase): + def fixture(self, root: Path): + subprocess.run(["git", "init", "-q"], cwd=root, check=True) + original = { + "verification/integrity/workflow_contract.py": b"old reviewed workflows\n", + "verification/integrity/tests/test_workflow.py": b"old reviewed tests\n", + "verification/archive/verify_bundle.py": b"old offline replay\n", + "archival/v0.2.0/README.md": b"frozen reproduction instructions\n", + } + for name, raw in original.items(): + path = root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(raw) + t = commit(root, "canonical historical tooling") + frozen = root / migration.SUPPLEMENT + frozen.mkdir(parents=True, exist_ok=True) + rows = [] + for name, raw in sorted(original.items()): + dest = frozen / "tooling" / name + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_bytes(raw) + rows.append({ + "path": name, + "blob": git(root, "rev-parse", t + ":" + name), + "sha256": hashlib.sha256(raw).hexdigest(), + }) + (frozen / "archive-manifest.json").write_text(json.dumps({ + "tooling": {"source_commit": t, "files": rows} + })) + for n in range(25): + path = root / ".github/workflows" / ("reviewed-%02d.yml" % n) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("name: historical successor profile\n") + b = commit(root, "sealed historical supplement and candidate workflows") + for name in migration.EXPECTED_CHANGES: + (root / name).write_text("new versioned candidate source\n") + h = commit(root, "separate candidate tooling version") + return t, b, h + + def test_exact_drift_produces_only_unapproved_proposal(self): + with tempfile.TemporaryDirectory(prefix="pr113-synthetic-tooling-") as td: + root = Path(td) + t, b, h = self.fixture(root) + report = migration.audit(root, h, historical_t=t, release_base=b) + self.assertEqual( + {x["path"] for x in report["changed_canonical_tooling"]}, + migration.EXPECTED_CHANGES) + self.assertTrue(report["historical_archive_unchanged"]) + self.assertEqual(report["historical_packaged_tooling_files_verified"], 4) + self.assertEqual(report["current_workflow_file_count"], 25) + self.assertFalse(report["new_successor_T_approved"]) + self.assertFalse(report["independent_tooling_review"]) + self.assertFalse(report["mathematical_acceptance"]) + self.assertFalse(report["full_release_approved"]) + self.assertIn("DRAFT", report["status"]) + + def test_extra_tooling_mutation_cannot_pass(self): + with tempfile.TemporaryDirectory(prefix="pr113-extra-tool-") as td: + root = Path(td) + t, b, h = self.fixture(root) + (root / "verification/archive/verify_bundle.py").write_text( + "unapproved replay change\n") + changed = commit(root, "new historical verifier mutation") + with self.assertRaises(migration.ToolingMigrationError): + migration.audit(root, changed, historical_t=t, release_base=b) + + def test_rewritten_historical_snapshot_cannot_pass(self): + with tempfile.TemporaryDirectory(prefix="pr113-forged-archive-") as td: + root = Path(td) + t, b, h = self.fixture(root) + path = (root / migration.SUPPLEMENT / "tooling" / + "verification/integrity/workflow_contract.py") + path.write_bytes(b"rewritten archived proof tooling\n") + changed = commit(root, "attempt to overwrite frozen T package") + with self.assertRaises(migration.ToolingMigrationError): + migration.audit(root, changed, historical_t=t, release_base=b) + + def test_pretend_checkout_and_missing_workflow_rejected(self): + with tempfile.TemporaryDirectory(prefix="pr113-wrong-head-") as td: + root = Path(td) + t, b, h = self.fixture(root) + with self.assertRaises(migration.ToolingMigrationError): + migration.audit(root, b, historical_t=t, release_base=b) + (root / ".github/workflows/reviewed-00.yml").unlink() + changed = commit(root, "drop successor check") + with self.assertRaises(migration.ToolingMigrationError): + migration.audit(root, changed, historical_t=t, release_base=b) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_tooling_migration_v2.py b/verification/research/test_pr113_tooling_migration_v2.py new file mode 100644 index 00000000..96066481 --- /dev/null +++ b/verification/research/test_pr113_tooling_migration_v2.py @@ -0,0 +1,193 @@ +"""Real source-bound PREACTIVATION tests for the versioned 25-to-30 bridge. + +No GitHub approval or active protected-workflow installation is performed. +""" +from __future__ import annotations +import copy +from pathlib import Path +from unittest import TestCase, mock + +from pr113_workflow_profile_v2 import ( + PREACTIVATION_HEAD, audit as workflow_audit, + audited_source_phase, source_phase, FORMAT, +) +from pr113_tooling_migration_v2 import audit as tooling_audit +from pr113_tooling_migration import ToolingMigrationError, git, ROOT + + +class VersionedT2SourceTests(TestCase): + @classmethod + def setUpClass(cls): + cls.head = git(ROOT, "rev-parse", "HEAD").decode().strip() + + def test_real_25_to_virtual_30_exact_git_source(self): + phase,result = audited_source_phase(ROOT, self.head) + self.assertIn(phase, ("staged", "active")) + self.assertEqual(result["versioned_source_phase"], phase) + self.assertEqual(result["format"], FORMAT) + self.assertEqual(result["historical_original_head"], PREACTIVATION_HEAD) + self.assertEqual(result["historical_original_workflow_count"], 25) + self.assertEqual(result["current_reviewed_workflow_count"], 30) + self.assertEqual([result[x] for x in ( + "retained_historical_count", "replacement_count", "addition_count")], + [23, 2, 5]) + self.assertEqual(len(result["expected_source_blobs"]), 30) + self.assertFalse(result["independent_security_review"]) + self.assertFalse(result["owner_activation_authorized"]) + self.assertFalse(result["all_actions_commit_pinned"]) + self.assertTrue(result["inherited_mutable_action_refs"]) + + def test_old_25_source_cannot_masquerade_as_active_30(self): + phase=source_phase(ROOT,self.head) + wrong="active" if phase=="staged" else "staged" + with self.assertRaises(ToolingMigrationError): + workflow_audit(ROOT, self.head, phase=wrong) + with self.assertRaisesRegex(ToolingMigrationError, + "invalid successor workflow phase"): + workflow_audit(ROOT, self.head, phase="future") + + def test_t2_tooling_checks_real_unchanged_original_archive(self): + phase=source_phase(ROOT,self.head) + result = tooling_audit(ROOT, self.head, phase=phase) + self.assertEqual(result["reviewed_workflow_source_mode"],phase) + self.assertEqual(result["status"], "T2_EXACT_SOURCE_REVIEW_PENDING") + self.assertEqual(result["historical_original_workflow_count"], 25) + self.assertEqual(result["current_reviewed_workflow_count"], 30) + self.assertEqual(len(result["changed_canonical_tooling"]), 2) + self.assertTrue(result["historical_archive_unchanged"]) + self.assertFalse(result["new_successor_T_approved"]) + self.assertFalse(result["independent_security_review"]) + + def test_frozen_archive_manifest_cannot_omit_or_duplicate_source(self): + from pr113_tooling_migration import SUPPLEMENT + import json + import pr113_tooling_migration_v2 as migration + phase = source_phase(ROOT, self.head) + original = migration.git + name = self.head + ":" + SUPPLEMENT + "/archive-manifest.json" + record = json.loads(original(ROOT, "show", name)) + for defect in ("omission", "duplicate", "unknown"): + modified = copy.deepcopy(record) + if defect == "omission": + modified["tooling"]["files"].pop() + elif defect == "duplicate": + modified["tooling"]["files"][-1] = modified["tooling"]["files"][0] + else: + modified["tooling"]["files"][-1]["path"] = "verification/archive/foreign.py" + def fake(root, *args): + if args == ("show", name): + return json.dumps(modified, sort_keys=True).encode("utf-8") + return original(root, *args) + with self.subTest(defect=defect), mock.patch.object( + migration, "git", side_effect=fake): + with self.assertRaises(ToolingMigrationError): + tooling_audit(ROOT, self.head, phase=phase) + + def test_historical_executable_modes_are_never_a_migration_exception(self): + import pr113_tooling_migration_v2 as migration + phase = source_phase(ROOT, self.head) + original = migration.exact_tree + target = "verification/integrity/workflow_contract.py" + def changed_mode(root, revision, *paths): + result = original(root, revision, *paths) + if revision == self.head and target in result: + result = dict(result) + result[target] = ("100755", result[target][1]) + return result + with mock.patch.object(migration, "exact_tree", side_effect=changed_mode): + with self.assertRaisesRegex(ToolingMigrationError, "mode changed"): + tooling_audit(ROOT, self.head, phase=phase) + + def test_forged_original_sha256_is_rejected_before_any_approval(self): + from pr113_tooling_migration import SUPPLEMENT + import json + import pr113_tooling_migration_v2 as migration + phase = source_phase(ROOT, self.head) + original = migration.git + name = self.head + ":" + SUPPLEMENT + "/archive-manifest.json" + record = json.loads(original(ROOT, "show", name)) + record["tooling"]["files"][0]["sha256"] = "0"*64 + def fake(root, *args): + if args == ("show", name): + return json.dumps(record, sort_keys=True).encode("utf-8") + return original(root, *args) + with mock.patch.object(migration, "git", side_effect=fake): + with self.assertRaises(ToolingMigrationError): + tooling_audit(ROOT, self.head, phase=phase) + + def test_no_direct_ad_hoc_source_pin_override(self): + from pr113_workflow_profile_v2 import _historical + old = _historical(ROOT, self.head) + self.assertEqual(len(old), 25) + with mock.patch("pr113_workflow_profile_v2._historical", + return_value={k:v for k,v in old.items() + if k != "a2-smoke.yml"}): + with self.assertRaises(ToolingMigrationError): + workflow_audit(ROOT, self.head, + phase=source_phase(ROOT,self.head)) + + def test_replacement_or_addition_missing_refused(self): + import pr113_workflow_profile_v2 as p + native = p._json + def missing(root, head, path): + data = native(root, head, path) + if path == p.FULL_MANIFEST: + data = copy.deepcopy(data) + data["full_replacements"] = data["full_replacements"][:1] + return data + with mock.patch.object(p, "_json", side_effect=missing): + with self.assertRaises(ToolingMigrationError): + workflow_audit(ROOT, self.head, + phase=source_phase(ROOT,self.head)) + + def test_fake_git_show_bytes_cannot_borrow_valid_blob_oid(self): + import pr113_workflow_profile_v2 as v2 + authentic=v2.git + target=v2.CONTRACT + def forged(root,*args): + if args == ("show",self.head+":"+target): + return authentic(root,*args)+b"\n# forged source\n" + return authentic(root,*args) + with mock.patch.object(v2,"git",side_effect=forged): + with self.assertRaisesRegex(ToolingMigrationError, + "Git object contents differ"): + v2._blob(ROOT,self.head,target) + + + def test_invalid_intermediate_inventory_and_nonfile_modes_fail_closed(self): + import pr113_workflow_profile_v2 as policy + for count in (24,26,29,31): + source={".github/workflows/generated-%02d.yml"%i: + ("100644","a"*40) for i in range(count)} + with self.subTest(count=count), mock.patch.object( + policy,"exact_tree",return_value=source): + with self.assertRaisesRegex( + ToolingMigrationError, + "phase must be exactly 25 staged or 30 active"): + source_phase(ROOT,self.head) + current=policy.exact_tree(ROOT,self.head,".github/workflows") + path=next(iter(current)) + changed=dict(current) + changed[path]=("100755",changed[path][1]) + with mock.patch.object(policy,"exact_tree",return_value=changed): + with self.assertRaisesRegex( + ToolingMigrationError,"phase must be exactly"): + source_phase(ROOT,self.head) + + def test_historical_25_source_is_anchored_even_after_activation(self): + import pr113_workflow_profile_v2 as policy + historical=policy._historical(ROOT,self.head) + self.assertEqual(len(historical),25) + self.assertEqual(len(set(historical)),25) + phase,source=audited_source_phase(ROOT,self.head) + self.assertEqual(source["historical_original_head"],PREACTIVATION_HEAD) + self.assertEqual(source["historical_original_workflow_count"],25) + self.assertEqual(source["current_reviewed_workflow_count"],30) + self.assertFalse(source["independent_security_review"]) + + def test_never_promote_v1_25_as_v2_30(self): + legacy = {"format": "Permansson.PR113.ToolingMigrationProposal.v1", + "current_workflow_file_count": 25} + self.assertNotEqual(legacy.get("format"), + "Permansson.PR113.ToolingMigrationCandidate.v2") + self.assertNotEqual(legacy.get("current_workflow_file_count"), 30) diff --git a/verification/research/test_pr113_v2_runtime.py b/verification/research/test_pr113_v2_runtime.py new file mode 100644 index 00000000..420d899c --- /dev/null +++ b/verification/research/test_pr113_v2_runtime.py @@ -0,0 +1,200 @@ +"""V2 T/T2+A2 runtime integration contracts (synthetic fixtures, NO approval). + +Never treat these mocks as authenticated historical producer executions. The +actual old T/A2 offline+Lean legs remain separately mandatory. +""" +from __future__ import annotations +import copy +import io +import json +import unittest +from unittest import mock +import zipfile + +import infrastructure_acceptance_v2 as infra +import pr113_required_dual_gate_v2 as gate +import pr113_historical_artifact_delivery_v2 as delivery +import pr113_historical_postmerge_v2 as postmerge + +B=infra.release_target.B113 +H="a"*40 +S="b"*40 +TREE="c"*40 +DIGEST="d"*64 + + +def git_adapter(*args, checked=S, parents=None, same_tree=True): + result={ + ("rev-parse","HEAD"):checked, + ("cat-file","-t",checked):"commit", + ("cat-file","-t",H):"commit", + ("rev-parse",checked+"^{tree}"):TREE if same_tree else "e"*40, + ("rev-parse",H+"^{tree}"):TREE, + ("show","-s","--format=%P",checked): + (B+" "+H if parents is None else parents), + } + return result[args] + + +def profile(repo,checked,*,phase): + assert checked in (H,S) + assert phase=="active" + return { + "head_sha":checked, + "current_reviewed_workflow_count":30, + "historical_original_workflow_count":25, + "independent_security_review":False, + "current_source_profile_sha256":DIGEST, + } + + +def zipbytes(): + stream=io.BytesIO() + with zipfile.ZipFile(stream,"w",zipfile.ZIP_DEFLATED) as archive: + archive.writestr("stub.json",'{"candidate":true}') + return stream.getvalue() + + +def producer_inputs(): + runid=817 + run={"id":runid,"head_sha":H, + "head_branch":"research/final-mathematical-closure", + "event":"push","status":"completed","conclusion":"success", + "run_attempt":2,"path":delivery.ACTIVE_WORKFLOW} + jobs=[] + artifacts=[] + bins={} + for i,kind in enumerate(("T","A2"),1): + job_name,artifact_name=delivery.EXPECTED_ARTIFACTS[kind] + id=925+i + aid=1025+i + checks=1100+i + jobs.append({ + "id":id,"name":job_name,"run_id":runid, + "run_attempt":2,"head_sha":H,"status":"completed", + "conclusion":"success","check_run_url": + "https://api.github.com/repos/"+delivery.REPOSITORY+ + "/check-runs/"+str(checks)}) + artifacts.append({"id":aid,"name":artifact_name, + "expired":False,"workflow_run":{"id":runid,"head_sha":H}}) + bins[aid]=zipbytes() + api={ + "/actions/runs/817":run, + "/actions/runs/817/jobs?per_page=100":{"jobs":jobs}, + "/actions/runs/817/artifacts?per_page=100":{"artifacts":artifacts}, + } + return api,bins + + +class V2RuntimeUnitContracts(unittest.TestCase): + def test_source_profile_requires_actual_H_or_ordered_S_and_equal_tree(self): + with mock.patch.object(infra,"workflow_audit",side_effect=profile): + for checked in (H,S): + observed=infra.checked_profile( + H,git=lambda *a:git_adapter(*a,checked=checked)) + self.assertEqual(observed["head_sha"],checked) + for parents in (H+" "+B,B+" "+"0"*40,B+" "+H+" "+"1"*40): + with self.subTest(parents=parents): + with self.assertRaises(infra.InfrastructureError): + infra.checked_profile( + H,git=lambda *a:git_adapter(*a,parents=parents)) + with self.assertRaises(infra.InfrastructureError): + infra.checked_profile( + H,git=lambda *a:git_adapter(*a,same_tree=False)) + with self.assertRaises(infra.InfrastructureError): + infra.checked_profile(H,git=lambda *a: + "blob" if a==("cat-file","-t",S) else git_adapter(*a)) + + def test_v2_transport_reads_only_v2_event_adapters(self): + self.assertIs(delivery.actual_ci_event,gate.actual_ci_event) + self.assertEqual(infra.FORMAT,"Permansson.PR113.InfrastructureEvidence.v2") + self.assertEqual(delivery.EVIDENCE_FORMAT,infra.FORMAT) + self.assertEqual(postmerge.FORMAT, + "Permansson.PR113.PostmergeHistoricalEvidence.v2") + + def test_v2_collector_requires_two_distinct_verified_producer_jobs(self): + api,bins=producer_inputs() + with mock.patch.object(delivery,"authenticate_original_zip", + side_effect=lambda kind,src,raw,head,live: + {"status":"UNAPPROVED_VERIFIED","kind":kind}),\ + mock.patch.object(delivery,"inspect_zip", + return_value={"test.json":b'{}'}),\ + mock.patch.object(delivery,"inspect_T", + return_value={"candidate":"T"}),\ + mock.patch.object(delivery,"inspect_A2", + return_value={"candidate":"A2"}),\ + mock.patch.object(delivery,"verify_A2_dependency_bridge", + return_value="f"*64): + zips, ids, reports = delivery.collect( + H,817,live=api.__getitem__,download=bins.__getitem__, + committed_manifest=b"pinned immutable manifest") + self.assertEqual(set(zips),{"T","A2"}) + self.assertNotEqual(ids["T"]["job_id"],ids["A2"]["job_id"]) + self.assertNotEqual(ids["T"]["artifact_id"],ids["A2"]["artifact_id"]) + self.assertEqual(reports["A2"]["scope"]["dependency_bridge_sha256"], + "f"*64) + altered=copy.deepcopy(api) + altered["/actions/runs/817"]["run_attempt"]=1 + with self.assertRaises(delivery.DeliveryError): + delivery.collect(H,817,live=altered.__getitem__, + download=bins.__getitem__, + committed_manifest=b"pinned immutable manifest") + altered=copy.deepcopy(api) + altered["/actions/runs/817/jobs?per_page=100"]["jobs"][1]["id"] = \ + altered["/actions/runs/817/jobs?per_page=100"]["jobs"][0]["id"] + with self.assertRaises(delivery.DeliveryError): + delivery.collect(H,817,live=altered.__getitem__, + download=bins.__getitem__, + committed_manifest=b"pinned immutable manifest") + + def test_v2_required_check_rejects_v1_receipt_even_with_status(self): + ctx={"original_base_sha":B,"head_sha":H, + "synthetic_sha":S,"github_run_id":123, + "github_run_attempt":1,"github_job":"research", + "event_sha256":"e"*64} + def candidate(): + return { + "format":"Permansson.PR113.HistoricalDualRequiredSourceReceipt.v2", + "status": + "HISTORICAL_T_AND_A2_GENUINE_WITH_INDEPENDENT_SECURITY_REVIEW", + "source_head":H, + "historical_original_workflow_count":25, + "current_reviewed_workflow_count":30, + "workflow_profile_sha256":DIGEST, + "historical_source_and_security_review_authenticated":True, + "required_ci_successful":0, + "merge_authorized":False, + "publication_authorized":False, + "independent_mathematical_review":False, + "genuine_artifacts":{"T":{},"A2":{}}, + "T_execution":{},"A2_execution":{}, + } + with mock.patch.object(gate,"actual_ci_event",return_value=ctx): + original=candidate() + result=gate.verify({"target":{"base_sha":B,"head_sha":H, + "synthetic_sha":S}}, + b"t",b"a", + env={"GITHUB_EVENT_NAME":"pull_request"}, + historical_gate=lambda *a:original) + self.assertFalse(result["all_required_CI_accepted"]) + for field,value in (("format", + "Permansson.PR113.HistoricalDualRequiredSourceReceipt.v1"), + ("current_reviewed_workflow_count",25), + ("historical_original_workflow_count",30), + ("workflow_profile_sha256","bad"), + ("required_ci_successful",1)): + forged=dict(candidate(),**{field:value}) + with self.subTest(field=field),\ + self.assertRaises(gate.RequiredGateError): + gate.verify({"target":{"base_sha":B,"head_sha":H, + "synthetic_sha":S}}, + b"t",b"a", + env={"GITHUB_EVENT_NAME":"pull_request"}, + historical_gate=lambda *a:forged) + + def test_v2_historical_review_requires_real_external_policy(self): + # An empty source-owned roster is NOT a nonauthor GitHub review. + with self.assertRaises(infra.InfrastructureError): + infra.authority_policy(H,git=lambda *a: + '{"format":"Permansson.InfrastructureReviewerAuthority.v1",' + '"status":"UNCONFIGURED","owner":"HMarcusWH","reviewers":[]}') diff --git a/verification/research/test_pr113_v2_workflow_contract_preview.py b/verification/research/test_pr113_v2_workflow_contract_preview.py new file mode 100644 index 00000000..4c8bcaca --- /dev/null +++ b/verification/research/test_pr113_v2_workflow_contract_preview.py @@ -0,0 +1,150 @@ +"""Execute proposed v2 contract against exact 30 virtual Git source blobs. + +No source-provided file, hash, or test grants approval. Full activation of +protected source is still separate from this preactivation adversarial QA. +""" +from __future__ import annotations +import ast +import importlib.util +import json +from pathlib import Path +import subprocess +import unittest +from unittest import mock +from pr113_workflow_profile_v2 import PREACTIVATION_HEAD + +ROOT=Path(__file__).resolve().parents[2] +STAGE=ROOT/"docs/operations/pr113_required_gate_replacement_unapproved" +TARGET=STAGE/"workflow_contract_v2.py" +TEST_STAGE=STAGE/"test_workflow_v2.py" + +def git(*args): + return subprocess.check_output( + ["git",*args],cwd=ROOT,stderr=subprocess.DEVNULL,timeout=25) + +def virtual_sources(): + H=git("rev-parse","HEAD").decode().strip() + full=json.loads(git("show",H+":"+ + "docs/operations/pr113_required_gate_replacement_unapproved/full_workflow_manifest.json")) + proposal=json.loads(git("show",H+":"+ + "docs/operations/pr113_unapproved_workflows/manifest.json")) + result={} + historical=git("ls-tree","-r","--name-only",PREACTIVATION_HEAD, + "--",".github/workflows").decode().splitlines() + for path in historical: + if path.startswith(".github/workflows/") and path.endswith((".yml",".yaml")): + result[Path(path).name]=git("show",PREACTIVATION_HEAD+":"+path).decode() + for item in full["full_replacements"]: + result[Path(item["target"]).name]=git( + "show",H+":"+item["staged_source"]).decode() + for item in proposal["proposals"]: + result[Path(item["workflow_path"]).name]=git( + "show",H+":"+item["staged_source_path"]).decode() + return result + +def load_staged_contract(): + spec=importlib.util.spec_from_file_location( + "pr113_staged_contract_under_review",TARGET) + module=importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + +class CandidateV2ContractTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.contract=load_staged_contract() + cls.sources=virtual_sources() + + def test_exact_thirty_virtual_git_blobs_and_required_job_names(self): + result=self.contract.validate_workflow_set(self.sources) + self.assertEqual(result["workflows"],30) + self.assertFalse(result["owner_activation_authorized"]) + self.assertFalse(result["independent_security_review"]) + self.assertFalse(result["all_workflow_actions_commit_pinned"]) + self.assertTrue(result["mutable_action_refs_pending_security_review"]) + self.assertIn("actions/checkout@v4", + result["retained_historical_mutable_action_refs"]["a2-smoke.yml"]) + self.assertEqual(set(self.sources),set( + self.contract.APPROVED_WORKFLOW_BLOBS)) + actual=self.contract.validate_workflow( + self.sources["research-integrity.yml"],self.sources) + self.assertEqual(len(actual["contexts"]),3) + + def test_only_exact_retained_history_may_keep_mutable_action_tags(self): + # Mutable inherited action tags are unresolved security debt, + # not fully pinned workflow actions or reviewer authorization. + self.assertEqual(len(self.contract.RETAINED_HISTORICAL_WORKFLOWS),23) + self.assertEqual(len(self.contract.REPLACEMENT_WORKFLOWS),2) + self.assertEqual(len(self.contract.ADDITION_WORKFLOWS),5) + for name in ("research-integrity.yml","pr113-preseal-fullscope.yml"): + with self.subTest(source=name): + src=self.sources[name] + import re + matcher=re.search(r"(?m)^\s*- uses:\s*actions/checkout@[a-f0-9]{40}", + src) + self.assertIsNotNone(matcher) + new_line=matcher.group(0).rsplit("@",1)[0]+"@v4" + changed=src[:matcher.start()]+new_line+src[matcher.end():] + modified=dict(self.sources) + modified[name]=changed + # Even a source-owned repin must not allow a new mutable tag. + new_blob=self.contract.git_blob_oid(changed.encode("utf-8")) + with mock.patch.dict(self.contract.APPROVED_WORKFLOW_BLOBS, + {name:new_blob}): + with self.assertRaisesRegex(Exception, + "must be full-SHA pinned"): + self.contract.validate_workflow_set(modified) + + def test_each_canonical_mutation_rejected_by_exact_source_gate(self): + self.assertEqual(len(self.sources),30) + for name in self.sources: + with self.subTest(file=name): + replaced=dict(self.sources) + replaced[name]+="\n# unauthorized footer\n" + with self.assertRaises(Exception): + self.contract.validate_workflow_set(replaced) + + def test_absent_colliding_or_privileged_workflow_refused(self): + one=dict(self.sources) + one.pop("research-lean.yml") + with self.assertRaises(Exception): + self.contract.validate_workflow_set(one) + two=dict(self.sources) + two["duplicate-required.yml"]="name: duplicate\n" + with self.assertRaises(Exception): + self.contract.validate_workflow_set(two) + for before,after in (("actions: read","actions: write"), + ("contents: read","contents: write"), + ("pull_request:","pull_request_target:"), + ("pr113_historical_runtime_v3.py", + "echo pr113_historical_runtime_v3.py")): + source=dict(self.sources) + self.assertIn(before,source["research-integrity.yml"]) + source["research-integrity.yml"]=source[ + "research-integrity.yml"].replace(before,after,1) + with self.assertRaises(Exception): + self.contract.validate_workflow_set(source) + + def test_v2_suite_preserves_all_ci06_ci07_mappings_before_install(self): + old_tests=git("show",PREACTIVATION_HEAD+ + ":verification/integrity/tests/test_workflow.py").decode() + staged=TEST_STAGE.read_text() + old_names={n.name for n in ast.walk(ast.parse(old_tests)) + if isinstance(n,ast.FunctionDef) and n.name.startswith("test_") + and n.name!="test_case"} + new_names={n.name for n in ast.walk(ast.parse(staged)) + if isinstance(n,ast.FunctionDef) and n.name.startswith("test_") + and n.name!="test_case"} + self.assertEqual(old_names,new_names) + coverage=json.loads((ROOT/"verification/integrity/test_coverage.json").read_bytes()) + methods=[m for x in coverage["cases"] + if x["id"] in ("CI-06","CI-07") for m in x["methods"]] + names={m.rsplit(".",1)[-1] for m in methods} + self.assertTrue(names <= new_names) + status=json.loads((STAGE/"workflow_contract_v2_manifest.json").read_bytes()) + self.assertFalse(status["owner_activation_authorized"]) + self.assertFalse(status["independent_security_review"]) + self.assertFalse(status["installed"]) + +if __name__=="__main__": + unittest.main() diff --git a/verification/research/test_pr113_v31_execution_integrity.py b/verification/research/test_pr113_v31_execution_integrity.py new file mode 100644 index 00000000..9ce8d6db --- /dev/null +++ b/verification/research/test_pr113_v31_execution_integrity.py @@ -0,0 +1,586 @@ +"""Synthetic negative tests for 76 source byte owners; never real approvals.""" +import hashlib +import io +import json +from pathlib import Path +import tempfile +import unittest +import zipfile + +from pr113_v31_execution_integrity import ( + ExactSource, RealControlError, validate_executed_controls, verify_owner, +) +from pr113_pack_ci_remaining_owners import manifest, PackError + +H = "a" * 40 +CLAIMS = ["Q1", "Q2", "Q3", "M1", "M2", "M3", "N1", "N2", "N3", "D8", "D9"] +ORIGINAL = b"theorem original : True := by trivial\n" +FALSE = ORIGINAL + b"\ntheorem false_goal : False := by trivial\n" +POSITIVE = b"Lean positive proof passed\n" +NEGATIVE = b"error: type mismatch\n" +TEST_OK = b"Ran 1 test in 0.001s\n\nOK\n" +ROUTES = "verification/research/test_fixture_routes.py" +CLAIM_TESTS = "verification/research/test_fixture_claims.py" + + +def method(cid): + return "test_" + cid.replace("-", "").lower() + + +def proof(cid): + return "PermanssonResearch/Fixture/" + cid.replace("-", "") + ".lean" + + +def ran(path, cls, name): + module = Path(path).stem + return (f"{name} ({module}.{cls}.{name}) ... ok\n\n" + + "-" * 70 + "\n").encode() + TEST_OK + + +def test_source(cls, names): + body = "".join(f" def {n}(self):\n pass\n" for n in names) + return f"import unittest\n\nclass {cls}(unittest.TestCase):\n{body}".encode() + + +CASE_IDS = [f"C-{i:02}" for i in range(1, 46)] +BLOBS = { + ROUTES: test_source("Routes", [method(c) for c in CASE_IDS] + + [method(c) + "_false" for c in CASE_IDS]), + CLAIM_TESTS: test_source("Claims", [ + "test_" + c.lower() + side for c in CLAIMS + for side in ("_true", "_false")]), + **{proof(c): b"theorem " + c.encode() + b" : True := trivial\n" + for c in CASE_IDS}, + **{proof(c): ORIGINAL for c in CLAIMS}, +} + + +def source(blobs=None, **selection): + cases = {"cases": [{ + "case_id": c, "effective_accepted": False, + "test_method_candidates": [ + {"path": ROUTES, "method": method(c), "role": "COMPILER_SOURCE"}, + {"path": ROUTES, "method": method(c) + "_false", + "role": "SEMANTIC_NEGATIVE"}, + ], + "historical_proof_declarations": [{"path": proof(c)}], + } for c in CASE_IDS]} + claims = {"claims": [{ + "claim_id": c, "declarations": [{"path": proof(c)}], + "test_source_candidates": [CLAIM_TESTS], + } for c in CLAIMS]} + table = dict(BLOBS if blobs is None else blobs) + return ExactSource(H, cases, claims, lambda path: table.get(path), + **selection) + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def serialized(row): + return (json.dumps(row, sort_keys=True) + "\n").encode() + + +def zipped(members): + target = io.BytesIO() + with zipfile.ZipFile(target, "w", compression=zipfile.ZIP_DEFLATED) as z: + for name, raw in sorted(members.items()): + z.writestr(name, raw) + return target.getvalue() + + +def make_positive(cid): + base = "positive/" + cid + "/" + stderr = ran(ROUTES, "Routes", method(cid)) + record = { + "format": "Permansson.PR113.OriginalCasePositiveCandidate.v1", + "status": "SOURCE_TEST_AND_LEAN_EXECUTED_UNREVIEWED", + "head_sha": H, "case_id": cid, + "test_path": ROUTES, "test_class": "Routes", + "test_name": method(cid), + "source_sha256": sha(BLOBS[ROUTES]), + "proof_source_path": proof(cid), + "proof_source_sha256": sha(BLOBS[proof(cid)]), + "positive_role": "COMPILER_SOURCE", + "source_positive_exit_code": 0, "registered_method_exit_code": 0, + "actual_producing_check_run_id": None, + "authenticated_ci_artifact": False, + "independent_case_review": False, + "scientific_release_approved": False, + "unittest_stdout_sha256": sha(b""), + "unittest_stderr_sha256": sha(stderr), + "source_positive_log_sha256": sha(POSITIVE), + } + owner = base + "unreviewed_positive.json" + return owner, { + owner: serialized(record), + base + "unittest_stdout.log": b"", + base + "unittest_stderr.log": stderr, + base + "source_positive.log": POSITIVE, + } + + +def original_legacy_capture(cid): + selected = method(cid) + "_false" + event = "event-1-000001" + inp = BLOBS[proof(cid)] + b"\ntheorem false_goal : False := by trivial\n" + manifest = { + "format": "Permansson.LegacyCaseTestExecutionCandidate.v1", + "status": "EXECUTED_UNREVIEWED_NOT_AUTHENTICATED", + "case_id": cid, "test_file": ROUTES, + "selected_methods": [selected], + "source_test_sha256": sha(BLOBS[ROUTES]), + "test_exit_code": 0, + "independent_review": False, "release_approved": False, + } + obs = { + "format": "Permansson.LegacyLeanCompilerObservation.v1", + "case_id_unverified": cid, + "selected_method_unverified": selected, + "source_sha256": sha(inp), + } + return manifest, obs, inp, event + + +def authentic_original_legacy(): + members = {} + for cid in [f"C-{i:02}" for i in range(1, 21)]: + manifest, obs, inp, event = original_legacy_capture(cid) + base = f"{cid}/test-00/" + members[base + "unreviewed_test_manifest.json"] = serialized(manifest) + members[base + "raw_compiler/" + event + "/observation.json"] = serialized(obs) + members[base + "raw_compiler/" + event + "/source.lean"] = inp + return { + "status": "REAL_PRODUCING_BYTES_UNREVIEWED", "head_sha": H, + "producer_count": 21, + "producers": {555: { + "kind": "legacy", "head_sha": H, "check_run_id": 777, + "members": members, + }}, + } + + +def make_legacy(cid): + base = "legacy/" + cid + "/" + original = BLOBS[proof(cid)] + mutated = original + b"\ntheorem false_goal : False := by trivial\n" + test_manifest, observation, captured, event = original_legacy_capture(cid) + selected = method(cid) + "_false" + mutation = { + "head_sha": H, "case_ids": [cid], "claim_ids": [], + "test_path": ROUTES, "test_name": selected, + "source_sha256": sha(BLOBS[ROUTES]), + "original_lean_source_path": proof(cid), + "outcome": "REJECTED_SEMANTIC", "compiler_exit_code": 1, + "positive_control": { + "compiled": True, "exit_code": 0, + "log_member": "pos.log", "log_sha256": sha(POSITIVE), + }, + "log_member": "neg.log", "log_sha256": sha(NEGATIVE), + "original_lean_source_member": "original.lean", + "original_lean_source_sha256": sha(original), + "mutant_source_member": "false.lean", + "mutant_source_sha256": sha(mutated), + } + source = { + "format": "Permansson.LegacyAppendOnlyReplayCandidate.v1", + "status": "REPLAYED_APPEND_ONLY_UNREVIEWED_UNATTRIBUTED", + "case_id": cid, "head_sha": H, + "selected_test_path": ROUTES, + "selected_method": selected, + "original_provider_path": proof(cid), + "original_observation_id": event, + "legacy_test_manifest_sha256": sha(serialized(test_manifest)), + "raw_observation_sha256": sha(serialized(observation)), + "captured_false_source_sha256": sha(captured), + "replayed_false_source_sha256": sha(mutated), + "detached_bundle_sha256": None, + "mutation_record": mutation, + "ci_check_run_id": None, + "independent_review": False, + "mathematical_acceptance": False, + } + compiler = zipped({ + "pos.log": POSITIVE, "neg.log": NEGATIVE, + "original.lean": original, "false.lean": mutated, + }) + source["detached_bundle_sha256"] = sha(compiler) + src_bytes = serialized(source) + record = { + "status": "SOURCE_APPENDED_REPLAYED_UNREVIEWED_NOT_CI_ATTRIBUTED", + "head_sha": H, "case_id": cid, + "historical_producing_artifact_id": 555, + "actual_ci_check_run_id": None, + "execution_receipt_member": base + "original_unreviewed_replay.json", + "execution_receipt_sha256": sha(src_bytes), + "semantic_zip_member": base + "semantic_replay_candidate.zip", + "semantic_zip_sha256": sha(compiler), + "independent_case_review": False, + "mathematical_acceptance": False, + "scientific_release_approved": False, + } + owner = base + "unreviewed_legacy_ci.json" + return owner, { + owner: serialized(record), + base + "original_unreviewed_replay.json": src_bytes, + base + "semantic_replay_candidate.zip": compiler, + }, source, compiler + + +def make_claim(cid): + base = "claim/" + cid + "/" + members = { + "original.lean": ORIGINAL, "appended_false.lean": FALSE, + "positive_lean.log": POSITIVE, + "negative_lean.log": NEGATIVE, + "positive_test.stdout": b"", + "positive_test.stderr": + ran(CLAIM_TESTS, "Claims", "test_" + cid.lower() + "_true"), + "negative_test.stdout": b"", + "negative_test.stderr": + ran(CLAIM_TESTS, "Claims", "test_" + cid.lower() + "_false"), + } + record = { + "format": "Permansson.PR113.ClaimOnlyControlCandidate.v1", + "status": "LEAN_POSITIVE_AND_FALSE_MUTANT_EXECUTED_UNREVIEWED", + "head_sha": H, "claim_id": cid, + "claim_ids": [cid], "case_ids": [], + "original_lean_source_path": proof(cid), + "positive_test_path": CLAIM_TESTS, + "positive_test_name": "test_" + cid.lower() + "_true", + "negative_test_path": CLAIM_TESTS, + "negative_test_name": "test_" + cid.lower() + "_false", + "source_test_hashes": {"positive": sha(BLOBS[CLAIM_TESTS]), + "negative": sha(BLOBS[CLAIM_TESTS])}, + "lean_positive_exit_code": 0, + "lean_negative_exit_code": 1, + "retained_member_sha256": + {name: sha(raw) for name, raw in members.items()}, + "actual_ci_check_run_id": None, + "independent_mathematical_review": False, + "accepted_claim": False, + "scientific_release_approved": False, + } + owner = base + "unreviewed_claim_control.json" + return owner, { + owner: serialized(record), + **{base + name: raw for name, raw in members.items()}, + }, record + + +def complete(): + jobs, zipfiles = [], {} + for kind, cases, fn, artifact in ( + ("original_positive", CASE_IDS, make_positive, 1), + ("legacy_negative", [f"C-{i:02}" for i in range(1, 21)], + make_legacy, 2), + ("claim_pair", CLAIMS, make_claim, 3), + ): + members = {} + owners = [] + for cid in cases: + owner, entries, *_rest = fn(cid) + members.update(entries) + owners.append({ + "kind": kind, "owner_id": cid, + "receipt_member": owner, + "receipt_sha256": sha(entries[owner]), + }) + jobs.append({"artifact_id": artifact, "owners": owners}) + zipfiles[artifact] = zipped(members) + return {"head_sha": H, "producers": jobs}, zipfiles + + +class ExecutedControlValidationTests(unittest.TestCase): + def test_all_76_genuine_member_shapes_still_unreviewed(self): + contract, binaries = complete() + result = validate_executed_controls(H, contract, binaries, + source=source(), + original_producers=authentic_original_legacy()) + self.assertEqual(result["source_owner_count"], 76) + self.assertTrue(result["canonical_routes_bound"]) + self.assertFalse(result["reviewed_positive_selection_bound"]) + self.assertEqual(result["claim_positive_negative_directions"], 22) + self.assertFalse(result["independent_review"]) + self.assertFalse(result["mathematical_acceptance"]) + self.assertFalse(result["publication_approved"]) + + def test_missing_true_log_blocks_original_positive(self): + name, members = make_positive("C-01") + del members["positive/C-01/source_positive.log"] + with self.assertRaises(RealControlError): + verify_owner("original_positive", "C-01", name, members, H) + + def test_parser_failure_is_not_semantic_negative(self): + name, members, _, _ = make_legacy("C-01") + nested = zipped({ + "pos.log": POSITIVE, + "neg.log": b"error: parser error\n", + "original.lean": ORIGINAL, + "false.lean": FALSE, + }) + # A correct hash cannot make a parser error a semantic refutation. + wrapper = json.loads(members[name]) + wrapper["semantic_zip_sha256"] = sha(nested) + members[name] = serialized(wrapper) + members[wrapper["semantic_zip_member"]] = nested + with self.assertRaises(RealControlError): + verify_owner("legacy_negative", "C-01", name, members, H) + + def test_claim_import_change_cannot_replace_false_goal(self): + name, members, row = make_claim("Q1") + false = ORIGINAL + b"\nimport Other\n" + row["retained_member_sha256"]["appended_false.lean"] = sha(false) + members[name] = serialized(row) + members["claim/Q1/appended_false.lean"] = false + with self.assertRaises(RealControlError): + verify_owner("claim_pair", "Q1", name, members, H) + + def test_partial_owner_partition_not_a_full_release(self): + contract, binaries = complete() + contract["producers"][2]["owners"].pop() + with self.assertRaises(RealControlError): + validate_executed_controls(H, contract, binaries, source=source(), + original_producers=authentic_original_legacy()) + + def test_full_validation_requires_exact_h_route_source(self): + contract, binaries = complete() + with self.assertRaises(RealControlError): + validate_executed_controls(H, contract, binaries, source=None) + + def assert_positive_rejected(self, edit, cid="C-02", blobs=None): + name, members = make_positive(cid) + row = json.loads(members[name]) + edit(row, members) + members[name] = serialized(row) + # Byte-only packing still accepts it; only route binding rejects. + verify_owner("original_positive", cid, name, members, H) + with self.assertRaises(RealControlError): + verify_owner("original_positive", cid, name, members, H, + source(blobs)) + + def test_owner_label_swap_fails_route_binding(self): + name, members = make_positive("C-02") + row = json.loads(members[name]) + row["case_id"] = "C-03" + members[name] = serialized(row) + verify_owner("original_positive", "C-03", name, members, H) + with self.assertRaises(RealControlError): + verify_owner("original_positive", "C-03", name, members, H, + source()) + + def test_unrelated_passing_test_is_not_case_route(self): + def unrelated(row, members): + stderr = ran(ROUTES, "Routes", method("C-07")) + members["positive/C-02/unittest_stderr.log"] = stderr + row["unittest_stderr_sha256"] = sha(stderr) + row["test_name"] = method("C-07") + self.assert_positive_rejected(unrelated) + + def test_semantic_negative_route_cannot_be_positive(self): + def negative(row, _): + row["test_name"] = method("C-02") + "_false" + self.assert_positive_rejected(negative) + + def test_log_of_other_method_fails_named_run(self): + def other_log(row, members): + stderr = ran(ROUTES, "Routes", method("C-07")) + members["positive/C-02/unittest_stderr.log"] = stderr + row["unittest_stderr_sha256"] = sha(stderr) + self.assert_positive_rejected(other_log) + + def test_foreign_original_provider_fails(self): + def foreign(row, _): + row["proof_source_path"] = proof("C-09") + row["proof_source_sha256"] = sha(BLOBS[proof("C-09")]) + self.assert_positive_rejected(foreign) + + def test_source_test_digest_substitution_fails(self): + def substituted(row, _): + row["source_sha256"] = sha(b"other test bytes") + self.assert_positive_rejected(substituted) + + def test_undefined_class_method_at_exact_h_fails(self): + blobs = dict(BLOBS) + blobs[ROUTES] = test_source("Routes", [method("C-01")]) + def rehash(row, _): + row["source_sha256"] = sha(blobs[ROUTES]) + self.assert_positive_rejected(rehash, blobs=blobs) + + def test_reviewed_selection_is_compared_exactly(self): + name, members = make_positive("C-02") + chosen = {"format": "Permansson.PR113.OriginalPositiveSelection.v1", + "cases": [{"case_id": "C-02", "test_path": ROUTES, + "test_class": "Routes", + "test_method": method("C-02"), + "proof_source_path": proof("C-03")}]} + with self.assertRaises(RealControlError): + verify_owner("original_positive", "C-02", name, members, H, + source(positive_selection=chosen)) + chosen["cases"][0]["proof_source_path"] = proof("C-02") + verify_owner("original_positive", "C-02", name, members, H, + source(positive_selection=chosen)) + + def assert_claim_rejected(self, edit, blobs=None): + name, members, row = make_claim("Q2") + edit(row, members) + members[name] = serialized(row) + verify_owner("claim_pair", "Q2", name, members, H) + with self.assertRaises(RealControlError): + verify_owner("claim_pair", "Q2", name, members, H, source(blobs)) + + def test_claim_foreign_provider_fails(self): + def foreign(row, _): + row["original_lean_source_path"] = proof("Q3") + self.assert_claim_rejected(foreign) + + def test_claim_original_bytes_must_equal_exact_h(self): + blobs = dict(BLOBS) + blobs[proof("Q2")] = ORIGINAL + b"-- edited at H\n" + self.assert_claim_rejected(lambda row, _: None, blobs=blobs) + + def test_claim_source_test_digest_substitution_fails(self): + def substituted(row, _): + row["source_test_hashes"]["negative"] = sha(b"other") + self.assert_claim_rejected(substituted) + + def test_claim_stderr_containing_ok_is_not_method_route(self): + def loose(row, members): + members["claim/Q2/negative_test.stderr"] = b"ok\n" + TEST_OK + row["retained_member_sha256"]["negative_test.stderr"] = sha( + b"ok\n" + TEST_OK) + self.assert_claim_rejected(loose) + + + def test_true_original_legacy_route_and_capture_is_bound(self): + name, members, _, _ = make_legacy("C-01") + self.assertEqual( + verify_owner("legacy_negative", "C-01", name, members, H, + source(), authentic_original_legacy())["owner_id"], + "C-01") + + def test_swapped_legacy_method_rejected_despite_valid_zip_hashes(self): + cid = "C-01" + name, members, _, _ = make_legacy(cid) + row = json.loads(members[name]) + receipt_name = row["execution_receipt_member"] + original = json.loads(members[receipt_name]) + original["selected_method"] = method("C-02") + "_false" + changed = serialized(original) + members[receipt_name] = changed + row["execution_receipt_sha256"] = sha(changed) + members[name] = serialized(row) + # The byte-only receipt path does not establish original route. + verify_owner("legacy_negative", cid, name, members, H) + with self.assertRaises(RealControlError): + verify_owner("legacy_negative", cid, name, members, H, + source(), authentic_original_legacy()) + + def test_foreign_legacy_original_capture_rejected(self): + cid = "C-01" + name, members, _, _ = make_legacy(cid) + changed = authentic_original_legacy() + target = changed["producers"][555]["members"] + event = "C-01/test-00/raw_compiler/event-1-000001/source.lean" + target[event] = target[ + "C-02/test-00/raw_compiler/event-1-000001/source.lean"] + # Original receipt bytes still pass local ZIP-log checks. + verify_owner("legacy_negative", cid, name, members, H) + with self.assertRaises(RealControlError): + verify_owner("legacy_negative", cid, name, members, H, + source(), changed) + + def test_borrowed_legacy_provider_rejected(self): + cid = "C-01" + name, members, _, _ = make_legacy(cid) + row = json.loads(members[name]) + receipt_name = row["execution_receipt_member"] + original = json.loads(members[receipt_name]) + original["original_provider_path"] = proof("C-02") + changed = serialized(original) + members[receipt_name] = changed + row["execution_receipt_sha256"] = sha(changed) + members[name] = serialized(row) + verify_owner("legacy_negative", cid, name, members, H) + with self.assertRaises(RealControlError): + verify_owner("legacy_negative", cid, name, members, H, + source(), authentic_original_legacy()) + + def test_original_legacy_packer_20_owners_no_approval(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "original-input" + source.mkdir() + records = [] + for i in range(1, 21): + cid = f"C-{i:02}" + _, _, report, binary = make_legacy(cid) + case = source / cid + case.mkdir() + report["detached_bundle_sha256"] = sha(binary) + (case / "unreviewed_replay.json").write_bytes( + serialized(report)) + (case / "semantic_replay_candidate.zip").write_bytes(binary) + records.append({ + "case_id": cid, + "original_legacy_producing_artifact_id": 555, + "replay_bundle_sha256": sha(binary), + "new_replay_producer_check_run_id": None, + "status": + "SOURCE_APPENDED_REPLAYED_UNREVIEWED_NOT_CI_ATTRIBUTED", + }) + summary = { + "format": "Permansson.PR113.LegacyBatchAppendOnlyCandidate.v1", + "status": + "TWENTY_REAL_LEAN_REPLAYS_UNREVIEWED_NOT_CI_BOUND", + "source_head": H, "replayed_case_count": 20, + "replays": records, + "new_source_bound_CI_acceptance": False, + "independent_case_reviews": 0, + "mathematical_release_approved": False, + } + (source / "unreviewed_batch_replays.json").write_bytes( + serialized(summary)) + result = manifest(H, 123, 1, "legacy_negative", + source, root / "packed") + self.assertEqual(len(result["owners"]), 20) + self.assertFalse(result["independent_review"]) + self.assertEqual(result["accepted_cases"], 0) + + def test_original_claim_packer_11_pairs_no_approval(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "original-input" + source.mkdir() + records = [] + for cid in CLAIMS: + _, entries, row = make_claim(cid) + case = source / cid + case.mkdir() + (case / "unreviewed_claim_control.json").write_bytes( + serialized(row)) + for name in row["retained_member_sha256"]: + (case / name).write_bytes(entries["claim/"+cid+"/"+name]) + records.append(row) + summary = { + "format": "Permansson.PR113.ClaimControlsCandidate.v1", + "status": "ALL_ELEVEN_EXECUTED_UNREVIEWED_NO_CI_ATTRIBUTION", + "source_head": H, + "actual_new_claims_executed": 11, + "claim_controls": records, + "actual_producing_ci_check_run_id": None, + "independent_claim_reviews": 0, + "mathematical_release_approved": False, + } + (source / "unreviewed_11_claim_control_summary.json").write_bytes( + serialized(summary)) + result = manifest(H, 124, 1, "claim_pair", + source, root / "packed") + self.assertEqual(len(result["owners"]), 11) + self.assertFalse(result["independent_review"]) + self.assertEqual(result["accepted_claims"], 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_v31_seal_entry.py b/verification/research/test_pr113_v31_seal_entry.py new file mode 100644 index 00000000..4a8a43c5 --- /dev/null +++ b/verification/research/test_pr113_v31_seal_entry.py @@ -0,0 +1,64 @@ +"""Full-release sealer CLI must pass actual v3.1 auth input, never flags.""" +import json +from pathlib import Path +import sys +import tempfile +import unittest +from unittest import mock + +RELEASE = Path(__file__).resolve().parents[2] / "release/mathematical-closure-1" +if str(RELEASE) not in sys.path: + sys.path.insert(0, str(RELEASE)) +import seal_approved as seal + + +class SourceControlledFullSealingTests(unittest.TestCase): + def test_full_candidate_cli_rejects_missing_76_control_inputs(self): + with tempfile.TemporaryDirectory(prefix="perm-v31-seal-") as td: + root = Path(td) + approved = root / "approved.json" + receipts = root / "receipts.json" + approved.write_text("{}") + receipts.write_text("{}") + argv = [ + "--draft-dir", str(root / "draft"), + "--approved-claim-manifest", str(approved), + "--source-receipts", str(receipts), + "--bundle", str(root / "nonexistent-evidence.zip"), + "--out", str(root / "candidate"), + "--full", + ] + with mock.patch.object(seal, "seal") as strict: + self.assertEqual(seal.main(argv), 2) + strict.assert_not_called() + + def test_full_sealing_forwards_real_v31_source_inputs_to_strict_gate(self): + with tempfile.TemporaryDirectory(prefix="perm-v31-seal-") as td: + base = Path(td) + draft = base / "draft" + draft.mkdir() + three = { + "additional_contract": base / "contract.json", + "additional_zips": base / "original-real-gh-zips", + "workflow_blob_candidates": base / "workflow-oid-candidates.json", + } + def blocked(*args, **kwargs): + self.assertEqual(args[1], "postmerge-preseal-strict") + self.assertEqual(kwargs.get("v31_attachment"), three) + raise seal.SealError("MOCK ONLY: no real GitHub security review") + with mock.patch.object(seal.draft_verifier, "verify", + return_value={"integrity": "PASS"}), \ + mock.patch.object(seal, "prepare_approved_manifest", + return_value=({}, {})), \ + mock.patch.object(seal, "strict_acceptance", + side_effect=blocked) as strict: + with self.assertRaisesRegex(seal.SealError, "MOCK ONLY"): + seal.seal(draft, {}, {}, base / "original-bundle.zip", + base / "candidate", full=True, + v31_attachment=three) + strict.assert_called_once() + self.assertFalse((base / "candidate").exists()) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_pr113_workflow_proposal_boundary.py b/verification/research/test_pr113_workflow_proposal_boundary.py new file mode 100644 index 00000000..1013d641 --- /dev/null +++ b/verification/research/test_pr113_workflow_proposal_boundary.py @@ -0,0 +1,189 @@ +"""Fail closed on unreviewed workflow proposals becoming silently executable. + +This test protects the original 25-workflow boundary. It checks exact Git +blobs, so text, file modes, or path substitutions cannot masquerade as review. +It never authorizes a new workflow or trust migration. +""" +from __future__ import annotations +import json +from pathlib import Path +import subprocess +import unittest + +ROOT = Path(__file__).resolve().parents[2] +PROPOSALS = ROOT / "docs/operations/pr113_unapproved_workflows" +ACTIVE = ".github/workflows/" + + +def active30(): + sources=[name for name in git("ls-tree","-r","--name-only","HEAD","--", + ".github/workflows").splitlines() + if name.startswith(ACTIVE) and name.endswith((".yml",".yaml"))] + if len(sources)==25: + return None + if len(sources)!=30: + raise AssertionError("unexpected workflow profile inventory") + from pr113_workflow_profile_v2 import audit + return audit(ROOT,git("rev-parse","HEAD"),phase="active") + + +def git(*args): + return subprocess.check_output( + ["git", *args], cwd=ROOT, stderr=subprocess.DEVNULL, timeout=20 + ).decode("utf-8").strip() + + +def source_manifest(): + manifest = json.loads((PROPOSALS / "manifest.json").read_bytes()) + if (manifest.get("format") != + "Permansson.PR113.UnreviewedWorkflowProposals.v1"): + raise ValueError("wrong workflow proposal manifest") + return manifest + + +class WorkflowProposalBoundary(unittest.TestCase): + def test_original_workflow_set_retained_without_silent_new_workflows(self): + profile=active30() + if profile is not None: + self.assertEqual(profile["historical_original_workflow_count"],25) + self.assertEqual(profile["current_reviewed_workflow_count"],30) + self.assertEqual(profile["retained_historical_count"],23) + self.assertFalse(profile["independent_security_review"]) + return + from verification.integrity.workflow_contract import APPROVED_WORKFLOWS + active = [x for x in git("ls-tree", "-r", "--name-only", "HEAD", + "--", ".github/workflows").splitlines() + if x.startswith(ACTIVE)] + self.assertEqual(len(active), 25) + self.assertEqual({x.removeprefix(ACTIVE) for x in active}, + set(APPROVED_WORKFLOWS)) + + def test_all_five_staged_proposals_unapproved_and_byte_identical(self): + manifest = source_manifest() + self.assertEqual(manifest["original_approved_workflow_count"], 25) + profile=active30() + if profile is None: + self.assertEqual(manifest["status"], + "SOURCE_STAGED_NONEXECUTABLE_PENDING_INDEPENDENT_SECURITY_REVIEW") + self.assertIs(manifest["independent_security_review"], False) + self.assertIs(manifest["owner_authorized_activation"], False) + self.assertIs(manifest["mathematical_acceptance"], False) + self.assertIs(manifest["release_authorized"], False) + rows = manifest["proposals"] + self.assertEqual(len(rows), 5) + self.assertEqual(len({r["workflow_path"] for r in rows}), 5) + self.assertEqual({Path(r["workflow_path"]).name for r in rows}, { + "pr113-historical-migration-candidates.yml", + "pr113-original-positive-controls-candidate.yml", + "pr113-postseal-replay.yml", + "pr113-source-controls-v3_1.yml", + "pr113-preseal-fullscope.yml", + }) + for row in rows: + old = row["workflow_path"] + staged = row["staged_source_path"] + self.assertTrue(old.startswith(ACTIVE)) + self.assertTrue(staged.startswith("docs/operations/pr113_unapproved_workflows/")) + self.assertEqual(Path(old).name, Path(staged).name) + if profile is None: + self.assertFalse((ROOT / old).exists(), old) + else: + self.assertTrue((ROOT / old).is_file(), old) + self.assertEqual(git("rev-parse","HEAD:"+old), + row["git_blob_oid"]) + self.assertTrue((ROOT / staged).is_file(), staged) + self.assertEqual(git("rev-parse", "HEAD:" + staged), + row["git_blob_oid"]) + self.assertIs(row["approved"], False) + self.assertIs(row["installed"], False) + # Changed since baseline: v3.1 controls, and postseal for the + # upstream preseal producer success check (P1-13). + self.assertIs( + row["source_unchanged"], + Path(old).name not in ("pr113-source-controls-v3_1.yml", + "pr113-postseal-replay.yml", + "pr113-preseal-fullscope.yml", + "pr113-historical-migration-candidates.yml")) + + def test_unreviewed_manifest_cannot_assert_success(self): + record = source_manifest() + self.assertNotIn("APPROVED", record["status"]) + for field in ("independent_security_review", + "owner_authorized_activation", + "mathematical_acceptance", "release_authorized"): + self.assertIs(record[field], False) + + + def test_fullscope_preseal_is_staged_and_matches_postseal_contract(self): + from verification.research.final_closure_postseal_auth import ( + UPSTREAM_WORKFLOW, UPSTREAM_JOB, UPSTREAM_ARTIFACT) + self.assertEqual( + UPSTREAM_WORKFLOW, + ".github/workflows/pr113-preseal-fullscope.yml") + self.assertEqual(UPSTREAM_JOB, "preseal-fullscope-candidate") + self.assertEqual(UPSTREAM_ARTIFACT, + "pr113-preseal-fullscope-candidate") + proposed = PROPOSALS / "pr113-preseal-fullscope.yml" + self.assertTrue(proposed.is_file()) + src = proposed.read_text(encoding="utf-8") + for critical in ( + "preseal-fullscope-candidate:", + "pr113-preseal-fullscope-candidate", + "seal_approved.py", + "--full", + "pr113_preseal_input_carrier.py", + "postseal_replay.py", + ): + self.assertIn(critical, src) + installed=ROOT / ACTIVE / "pr113-preseal-fullscope.yml" + if active30() is None: + self.assertFalse(installed.exists()) + else: + self.assertTrue(installed.is_file()) + self.assertEqual(git("rev-parse","HEAD:"+ACTIVE+ + "pr113-preseal-fullscope.yml"), + git("rev-parse","HEAD:docs/operations/"+ + "pr113_unapproved_workflows/pr113-preseal-fullscope.yml")) + + def test_full_v31_proposal_has_real_three_job_families_but_is_inactive(self): + path = PROPOSALS / "pr113-source-controls-v3_1.yml" + source = path.read_text(encoding="utf-8") + self.assertIn("original-positive45:", source) + self.assertIn("original-legacy20:", source) + self.assertIn("claim-true-false11:", source) + self.assertIn("research/final-mathematical-closure", source) + self.assertIn("reviewed_claim11_false_sources", source) + self.assertIn("legacy20_source_intent_v31.json", source) + self.assertIn("pr113_original21_live_catalog.py", source) + self.assertIn("pr113_legacy20_runtime_resolution.py", source) + self.assertIn("reviewed_legacy20_runtime_event_choices.json", source) + self.assertNotIn("reviewed_original21_catalog_v31.json", source) + installed=ROOT / ACTIVE / "pr113-source-controls-v3_1.yml" + if active30() is None: + self.assertFalse(installed.exists()) + else: + self.assertTrue(installed.is_file()) + self.assertEqual(git("rev-parse","HEAD:"+ACTIVE+ + "pr113-source-controls-v3_1.yml"), + git("rev-parse","HEAD:docs/operations/"+ + "pr113_unapproved_workflows/pr113-source-controls-v3_1.yml")) + # The observer must exit successfully and store the real survey. + # A separate explicit, independently reviewed resume consumes it. + self.assertIn("original-legacy20-replay:", source) + self.assertIn("if: github.event_name == 'workflow_dispatch'", source) + self.assertIn("pr113_legacy20_choice_delivery.py", source) + self.assertIn("survey_artifact_id:", source) + self.assertIn("review_comment_id:", source) + observer = source.split(" original-legacy20:", 1)[1].split( + " original-legacy20-replay:", 1)[0] + self.assertNotIn("test -s \"$RUNNER_TEMP/reviewed_legacy20_runtime_event_choices.json\"", + observer) + self.assertIn("pr113-v31-legacy20-observation-candidates", observer) + self.assertIn("Retain observed actual events", observer) + self.assertIn("reviewed_legacy20_runtime_event_choices.json", + source.split(" original-legacy20-replay:", 1)[1]) + + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_q05_actual_atom_closure_lean.py b/verification/research/test_q05_actual_atom_closure_lean.py new file mode 100644 index 00000000..cbeb8deb --- /dev/null +++ b/verification/research/test_q05_actual_atom_closure_lean.py @@ -0,0 +1,80 @@ +"""Q-05 unconditional typed source domination: actual kernel atoms, NOT only rates. + +The successful case compiles every proof and checks transitive axioms. The +false case makes a deliberately wrong nonzero original atom claim, preserving +the exact math types and original source imports. +""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +MODULE = "PermanssonResearch.Quotient.Q05ActualAtomClosure" +DECLS = ( + "fine_induced_singleton", + "coarse_induced_singleton", + "common_source_singleton", + "projected_common_singleton", + "fine_actual_atom_domination", + "coarse_actual_atom_domination", + "original_verified_atom_domination", + "original_point_witness", + "actual_original_B1_margin_bound", +) +ALLOW = {"propext", "Classical.choice", "Quot.sound"} + + +def compiled(source): + if shutil.which("lake") is None: + raise AssertionError("pinned Lean compiler required") + with tempfile.TemporaryDirectory(prefix="Q05-real-atom-", dir=ROOT/".lake") as d: + file = Path(d) / "Probe.lean" + file.write_text(source, encoding="utf-8") + return subprocess.run(["lake", "env", "lean", str(file)], + cwd=ROOT, capture_output=True, text=True, + timeout=900) + + +class Q05UnconditionalSourceClosure(unittest.TestCase): + def test_all_original_kernel_atoms_pointwitness_and_margin_compiler(self): + content = "import " + MODULE + "\n" + "".join( + "#check " + MODULE + "." + d + "\n#print axioms " + + MODULE + "." + d + "\n" for d in DECLS + ) + result = compiled(content) + log = result.stdout + "\n" + result.stderr + self.assertEqual(result.returncode, 0, log[-4500:]) + self.assertNotIn("error:", log.lower()) + self.assertNotIn("sorryAx", log) + for d in DECLS: + self.assertIn(MODULE + "." + d, log) + lists = re.findall(r"depends on axioms:\s*\[([^\]]*)\]", log) + self.assertTrue(lists, log) + for item in lists: + self.assertLessEqual( + {a.strip() for a in item.split(",") if a.strip()}, ALLOW) + + def test_actual_baseline_nonzero_atom_cannot_be_erased(self): + false_claim = ( + "import " + MODULE + "\n" + "open " + MODULE + "\n" + "open PermanssonResearch.Quotient.Q05ActualOriginalMarginWitnessFixture\n" + "example : (fineModel false).inducedKernel (false,false)" + " {(false,true)} = 0 := by\n" + " exact fine_induced_singleton false (false,false) (false,true)\n" + ) + result = compiled(false_claim) + log = (result.stdout + result.stderr).lower() + self.assertNotEqual(result.returncode, 0, "wrong actual atom proved") + for invalid in ("unknown identifier", "unknown module", + "failed to import", "unexpected token", "sorryax", + "maximum number of heartbeats", "out of memory"): + self.assertNotIn(invalid, log[-4000:]) + self.assertRegex(log, r"type mismatch|unsolved goals|application type mismatch") + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_q05_actual_source_lean.py b/verification/research/test_q05_actual_source_lean.py new file mode 100644 index 00000000..8546e223 --- /dev/null +++ b/verification/research/test_q05_actual_source_lean.py @@ -0,0 +1,93 @@ +"""Compile genuine Q-05 source in pinned Lean; reject mathematical false mutants.""" +from pathlib import Path +import re +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +MOD = "PermanssonResearch.Quotient.Q05ActualOriginalMarginWitnessFixture" +DECLS = ( + "fine_world_fixed", "coarse_world_fixed", + "admitted_fine_world_fixed", "admitted_coarse_world_fixed", + "twoStartDomain_has_distinct_starts", + "entire_original_comparison_domain", + "compression_noninjective", + "actual_two_start_effects", "exact_margin_gap", + "both_sides_every_strategic_row", + "discounted_defect_budget", "two_sided_original_margin_bound", + "treatment_reaches_other_strategic_state", + "commonMass_normalization", + "commonMass_dominated_every_source", + "commonTrueMass_le_fine", + "commonTrueMass_le_coarse", + "commonFalseMass_le_fine", + "commonFalseMass_le_coarse", + "commonSourceKernel_mass", + "commonEta_retention", + "commonSourceKernel_retention", + "commonEta_discounted_budget", + "commonSourceKernel_not_exact", +) + + +def compile_text(source): + with tempfile.TemporaryDirectory(dir=ROOT / ".lake", + prefix="q05-source-") as td: + name = Path(td) / "Q05.lean" + name.write_text(source, encoding="utf-8") + return subprocess.run(["lake", "env", "lean", str(name)], + cwd=ROOT, capture_output=True, text=True, + timeout=300) + + +class ActualQ05SourceCompiler(unittest.TestCase): + def test_actual_typed_primitive_models_and_axioms(self): + base = "import " + MOD + "\n" + probes = "".join( + f"#check {MOD}.{name}\n#print axioms {MOD}.{name}\n" + for name in DECLS) + run = compile_text(base + probes) + output = run.stdout + run.stderr + self.assertEqual(run.returncode, 0, output[-3000:]) + for name in DECLS: + self.assertIn(MOD + "." + name, output) + self.assertNotIn("sorryAx", output) + self.assertNotIn("error:", output.lower()) + + def test_wrong_original_effect_cannot_compile(self): + source = ( + f"import {MOD}\n" + f"open {MOD}\n" + "example : fineOriginalEffect false = (1/5 : ℚ) := by\n" + " exact (actual_two_start_effects false).1\n" + ) + run = compile_text(source) + output = (run.stdout + run.stderr).lower() + self.assertNotEqual(run.returncode, 0, output[-3000:]) + for invalid in ("unknown identifier", "unknown module", "object file", + "sorryax", "maximum number of heartbeats", + "failed to import", "unexpected token"): + self.assertNotIn(invalid, output) + self.assertTrue("type mismatch" in output or "application type mismatch" in output, + output[-2500:]) + + + def test_false_claim_common_kernel_is_markov_rejected(self): + source = ( + f"import {MOD}\n" + f"open {MOD}\n" + "example : commonSourceKernel false (false,false) Set.univ = 1 := by\n" + " exact commonSourceKernel_not_exact false (false,false)\n" + ) + run = compile_text(source) + log = (run.stdout + run.stderr).lower() + self.assertNotEqual(run.returncode, 0, log[-3000:]) + for invalid in ("unknown identifier", "unknown module", "failed to import", + "sorryax", "maximum number of heartbeats", "unexpected token"): + self.assertNotIn(invalid, log) + self.assertTrue("type mismatch" in log or "has type" in log, log[-2500:]) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_q05_nonidentity_source_fixture.py b/verification/research/test_q05_nonidentity_source_fixture.py new file mode 100644 index 00000000..9bdb55d2 --- /dev/null +++ b/verification/research/test_q05_nonidentity_source_fixture.py @@ -0,0 +1,193 @@ +"""Exact Q-05 candidate arithmetic for nonidentity strategic-state compression. + +Independent finite rational implementation of the proposed witness targets in +the repository-anchored v2 build plan. This is NOT a Lean source theorem, an +original ConstitutiveComparisonSet certificate, or an Exact GR/PR claim. + +Typed transition order: action-selection alpha -> physical world P (x' := a) +-> strategic update U (baseline s' := False, treatment s' := True). +The coarse system uses Unit strategic state and the SAME physical P. +""" +from __future__ import annotations + +from fractions import Fraction as F +import unittest + +BETA = F(1, 3) +SIDES = ("baseline", "treatment") +FINE_TRUE = { + "baseline": {False: F(1, 10), True: F(1, 5)}, + "treatment": {False: F(7, 10), True: F(4, 5)}, +} +COARSE_TRUE = {"baseline": F(3, 20), "treatment": F(3, 4)} +COMPARISON = ((False, False), (False, True)) +ALL_FINE = tuple((s, x) for s in (False, True) for x in (False, True)) +ALL_COARSE = (False, True) +ETA = F(1, 20) + + +def compression(y): + """A genuinely non-injective type-respecting state compression.""" + strategic_state, world = y + assert isinstance(strategic_state, bool) and isinstance(world, bool) + return (None, world) + + +def physical_world_next(action): + """Both fine/coarse models and both sides share the SAME physical kernel.""" + assert isinstance(action, bool) + return action + + +def next_strategic_state(side): + return side == "treatment" + + +def fine_transition(side, y): + s, x = y + assert side in SIDES and y in ALL_FINE + p = FINE_TRUE[side][s] + next_s = next_strategic_state(side) + # The update uses the actual realized physical transition, not old x. + return { + (next_s, physical_world_next(False)): 1 - p, + (next_s, physical_world_next(True)): p, + } + + +def coarse_transition(side, x): + assert side in SIDES and x in ALL_COARSE + p = COARSE_TRUE[side] + return {physical_world_next(False): 1 - p, + physical_world_next(True): p} + + +def fraction_linear_solve(A, b): + """Independent exact Gaussian elimination, no model-specific shortcuts.""" + n = len(b) + rows = [[F(v) for v in A[i]] + [F(b[i])] for i in range(n)] + for col in range(n): + pivot = next((i for i in range(col, n) if rows[i][col]), None) + if pivot is None: + raise ValueError("singular finite discounted resolvent") + rows[col], rows[pivot] = rows[pivot], rows[col] + q = rows[col][col] + rows[col] = [v / q for v in rows[col]] + for j in range(n): + if j == col: + continue + coeff = rows[j][col] + rows[j] = [u - coeff * v for u, v in zip(rows[j], rows[col])] + return [r[-1] for r in rows] + + +def discounted_values(side, fine): + """Evaluate (1-beta) sum beta^t 1{x_t=True} on the canonical chain.""" + states = ALL_FINE if fine else ALL_COARSE + step = fine_transition if fine else coarse_transition + matrix = [] + b = [] + for y in states: + dist = step(side, y) + assert sum(dist.values(), F(0)) == 1 + matrix.append([F(int(y == nxt)) + - BETA * dist.get(nxt, F(0)) + for nxt in states]) + world = y[1] if fine else y + b.append((1 - BETA) * int(world)) + ans = fraction_linear_solve(matrix, b) + return dict(zip(states, ans)) + + +def projected_overlap(side, s): + p = FINE_TRUE[side][s] + q = COARSE_TRUE[side] + return min(p, q) + min(1 - p, 1 - q) + + +def margin(values0, valuesJ, states): + return min(abs(valuesJ[y] - values0[y]) for y in states) + + +def common_mass_discounted_budget(beta=BETA, eta=ETA): + """(1-beta)*sum beta^n [1-(1-eta)^n], exactly.""" + assert 0 <= beta < 1 and 0 <= eta <= 1 + return beta * eta / (1 - beta + beta * eta) + + +class ProposedNonidentitySourceFixture(unittest.TestCase): + def test_type_respecting_q_nonidentity_and_original_two_starts(self): + self.assertEqual(len(COMPARISON), 2) + self.assertNotEqual(COMPARISON[0], COMPARISON[1]) + self.assertEqual(compression(COMPARISON[0]), (None, False)) + self.assertEqual(compression(COMPARISON[1]), (None, True)) + self.assertEqual(compression((False, True)), compression((True, True))) + self.assertNotEqual((False, True), (True, True)) + + def test_shared_physical_world_and_side_specific_update(self): + for side in SIDES: + for y in ALL_FINE: + fine = fine_transition(side, y) + self.assertEqual(sum(fine.values(), F(0)), 1) + self.assertEqual({s for s, _ in fine}, {side == "treatment"}) + self.assertEqual({x for _, x in fine}, {False, True}) + self.assertTrue(all(0 <= prob <= 1 for prob in fine.values())) + for x in ALL_COARSE: + self.assertEqual(sum(coarse_transition(side, x).values(), F(0)), 1) + self.assertFalse(next_strategic_state("baseline")) + self.assertTrue(next_strategic_state("treatment")) + + def test_exact_canonical_discounted_values_for_both_original_starts(self): + baseline = discounted_values("baseline", True) + treated = discounted_values("treatment", True) + coarse0 = discounted_values("baseline", False) + coarseJ = discounted_values("treatment", False) + expected = { + False: (F(1, 30), F(11, 45), F(1, 20), F(1, 4)), + True: (F(7, 10), F(41, 45), F(43, 60), F(11, 12)), + } + for x, values in expected.items(): + with self.subTest(world=x): + self.assertEqual((baseline[False, x], treated[False, x], + coarse0[x], coarseJ[x]), values) + self.assertEqual(margin(baseline, treated, COMPARISON), F(19, 90)) + self.assertEqual(margin(coarse0, coarseJ, ALL_COARSE), F(1, 5)) + self.assertEqual(abs(F(19, 90)-F(1, 5)), F(1, 90)) + + def test_actual_baseline_treatment_error_budgets_and_strict_margin(self): + b0 = discounted_values("baseline", True) + bJ = discounted_values("treatment", True) + c0 = discounted_values("baseline", False) + cJ = discounted_values("treatment", False) + self.assertEqual({abs(b0[False, x]-c0[x]) for x in ALL_COARSE}, + {F(1, 60)}) + self.assertEqual({abs(bJ[False, x]-cJ[x]) for x in ALL_COARSE}, + {F(1, 180)}) + self.assertEqual(common_mass_discounted_budget(), F(1, 41)) + self.assertLessEqual(F(1, 90), F(2, 41)) + self.assertGreater(F(19, 90) - F(2, 41), 0) + for side in SIDES: + for s in (False, True): + self.assertEqual(projected_overlap(side, s), F(19, 20)) + + def test_baseline_only_witness_misses_treatment_reachable_state(self): + from_starts_baseline = { + next_s for start in COMPARISON + for next_s, _ in fine_transition("baseline", start) + } + from_starts_treated = { + next_s for start in COMPARISON + for next_s, _ in fine_transition("treatment", start) + } + self.assertEqual(from_starts_baseline, {False}) + self.assertEqual(from_starts_treated, {True}) + self.assertNotEqual(from_starts_baseline, from_starts_treated) + # A normalized but changed treated row violates the stated eta budget. + altered_treated_p = F(19, 20) + self.assertEqual(altered_treated_p + (1-altered_treated_p), 1) + self.assertGreater(abs(altered_treated_p - COARSE_TRUE["treatment"]), + ETA) + + +if __name__ == "__main__": + unittest.main() diff --git a/verification/research/test_q05_original_margin_adapter_lean.py b/verification/research/test_q05_original_margin_adapter_lean.py new file mode 100644 index 00000000..4110e9a8 --- /dev/null +++ b/verification/research/test_q05_original_margin_adapter_lean.py @@ -0,0 +1,79 @@ +"""Compile genuine Q-05 original margin adapter, without filling domination holes. + +A syntactically assembled theorem does not constitute full Q-05 source review. +The tester audits the complete elaborated conditional theorem and detects an +invalid attempt to turn the conditional bound into an unconditional proof. +""" +import subprocess +import tempfile +from pathlib import Path +import unittest + +ROOT = Path(__file__).resolve().parents[2] +MOD = "PermanssonResearch.Quotient.Q05ActualOriginalMarginAdapter" +THEOREMS = ( + "sourcePointWitness", + "finiteMeasureLeOfAtoms", + "sourceDominationOfAtoms", + "sourcePointWitnessOfAtoms", + "fineJ_is_treated_source", + "coarseJ_is_treated_source", + "observedWorldScore_measurable", + "observedWorldScore_bounded", + "sourcePointWitness_budget", + "original_margin_of_verified_source_domination", +) + + +def compile_source(s): + with tempfile.TemporaryDirectory(prefix="q05-margin-", dir=ROOT / ".lake") as td: + path = Path(td) / "Q05Adapter.lean" + path.write_text(s, encoding="utf-8") + return subprocess.run(["lake", "env", "lean", str(path)], + cwd=ROOT, capture_output=True, text=True, + timeout=300) + + +class Q05OriginalMarginAdapterCompiler(unittest.TestCase): + def test_exact_original_margin_type_and_axiom_dependencies(self): + text = "import " + MOD + "\n" + "".join( + f"#check {MOD}.{name}\n#print axioms {MOD}.{name}\n" + for name in THEOREMS) + run = compile_source(text) + output = run.stdout + run.stderr + self.assertEqual(run.returncode, 0, output[-3500:]) + for name in THEOREMS: + self.assertIn(MOD + "." + name, output) + self.assertNotIn("error:", output.lower()) + self.assertNotIn("sorryAx", output) + + def test_unproved_source_domination_cannot_be_ignored(self): + # A genuine common-source row has mass < 1. Treating it as + # a defect-free probability kernel would erase the mandatory + # discounted transport budget; the actual nonexactness theorem + # refutes that false assertion, not an unfinished tactic. + mutant = ( + "import " + MOD + "\n" + "open " + MOD + "\n" + "open PermanssonResearch.Quotient.Q05ActualOriginalMarginWitnessFixture\n" + "example : (commonSourceKernel false) " + "(false, false) Set.univ = 1 := by\n" + " exact commonSourceKernel_not_exact false (false, false)\n" + ) + result = compile_source(mutant) + output = (result.stdout + result.stderr).lower() + self.assertNotEqual(result.returncode, 0, + "actual all-state domination was silently assumed") + self.assertIn("error:", output) + for fragment in ("unknown identifier", "unknown constant", + "unknown module", "parser error", "failed to synthesize", + "object file", "sorryax", "maximum number of heartbeats", + "timed out"): + self.assertNotIn(fragment, output) + self.assertTrue("type mismatch" in output or + "application type mismatch" in output or + "unsolved goals" in output, output[-3000:]) + + +if __name__ == "__main__": + unittest.main()