diff --git a/.github/workflows/gaussian-proof-gate.yml b/.github/workflows/gaussian-proof-gate.yml index cf16ba9..2f93aab 100644 --- a/.github/workflows/gaussian-proof-gate.yml +++ b/.github/workflows/gaussian-proof-gate.yml @@ -9,6 +9,34 @@ permissions: contents: read jobs: + spark-platinum-components: + name: SPARK Platinum functional kernels + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - name: Check out exact revision + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + + - name: Install GNAT, GPRbuild, and GNATprove + uses: alire-project/alr-install@b99b8c21417c79c307905439def2718bc393b94a # v2.0.1 + with: + crates: gnat_native=16.1.0 gprbuild=26.0.1 gnatprove=16.1.0 + + - name: Enforce bidirectional requirements and whole-surface closure + run: | + python3 -B tools/check_assurance_traceability.py + python3 -B -m unittest tests.assurance_traceability_test -v + + - name: Prove the total interval decision kernel + run: | + proofs/spark/hellgate_interval/prove.sh + python3 -B -m unittest tests.codex_plugin.test_spark_interval -v + + - name: Prove and exhaustively refine the claim policy kernel + run: | + proofs/spark/claim_policy/prove.sh + python3 -B -m unittest tests.claim_policy_conformance_test -v + lean-proof-and-axioms: name: Gaussian/range source closures and axiom audits runs-on: ubuntu-24.04 diff --git a/.github/workflows/jackal-codex-plugin.yml b/.github/workflows/jackal-codex-plugin.yml index 22d02b0..56c9820 100644 --- a/.github/workflows/jackal-codex-plugin.yml +++ b/.github/workflows/jackal-codex-plugin.yml @@ -10,8 +10,78 @@ permissions: env: PYTHONDONTWRITEBYTECODE: "1" + MPMATH_WHEEL_URL: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + MPMATH_WHEEL_SHA256: a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c + MPMATH_WHEEL_SIZE: "536198" jobs: + linux-aarch64-plugin: + name: Linux aarch64 plugin gates + runs-on: ubuntu-24.04-arm + timeout-minutes: 20 + steps: + - name: Check out exact revision + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Require the supported host and fixed Python prerequisite + run: | + test "$(uname -s)" = Linux + test "$(uname -m)" = aarch64 + test -x /usr/bin/python3 + /usr/bin/python3 -c 'import sys; assert sys.version_info >= (3, 10)' + + - name: Require the host atomic no-replace rename primitive + run: | + /usr/bin/python3 -c ' + import ctypes + libc = ctypes.CDLL(None, use_errno=True) + assert callable(getattr(libc, "renameat2", None)), "glibc renameat2 is required" + ' + + - name: Verify canonical capability and documentation contracts + run: | + /usr/bin/python3 -B tools/capability_drift_gate.py + /usr/bin/python3 -B -m unittest \ + tests.capability_inventory_test \ + tests.capability_drift_gate_test -v + + - name: Fetch exact untrusted HELLGATE oracle dependency + run: | + wheel="$RUNNER_TEMP/mpmath-1.3.0-py3-none-any.whl" + curl --proto '=https' --tlsv1.2 --fail --location --silent --show-error \ + --max-filesize "$MPMATH_WHEEL_SIZE" --output "$wheel" "$MPMATH_WHEEL_URL" + test "$(wc -c < "$wheel" | tr -d '[:space:]')" = "$MPMATH_WHEEL_SIZE" + /usr/bin/python3 - "$wheel" "$MPMATH_WHEEL_SHA256" <<'PY' + import hashlib + import pathlib + import sys + actual = hashlib.sha256(pathlib.Path(sys.argv[1]).read_bytes()).hexdigest() + if actual != sys.argv[2]: + raise SystemExit("mpmath wheel digest mismatch") + PY + + - name: Run complete Codex plugin unit suite + env: + PYTHONPATH: ${{ runner.temp }}/mpmath-1.3.0-py3-none-any.whl + run: /usr/bin/python3 -B -m unittest discover -s tests/codex_plugin -v + + - name: Verify exact plugin identity + run: /usr/bin/python3 -B plugins/jackel/scripts/verify_plugin.py + + - name: Portable launcher starts and refuses for the documented reason + run: | + cd plugins/jackel + set +e + output=$(/bin/sh scripts/launch_mcp.sh provision --check 2>&1) + status=$? + set -e + printf '%s\n' "$output" + test "$status" -eq 1 + printf '%s\n' "$output" | grep -q "pinned runtime is not installed" + macos-arm64-plugin: name: macOS arm64 plugin gates runs-on: macos-14 @@ -37,7 +107,24 @@ jobs: tests.capability_inventory_test \ tests.capability_drift_gate_test -v + - name: Fetch exact untrusted HELLGATE oracle dependency + run: | + wheel="$RUNNER_TEMP/mpmath-1.3.0-py3-none-any.whl" + curl --proto '=https' --tlsv1.2 --fail --location --silent --show-error \ + --max-filesize "$MPMATH_WHEEL_SIZE" --output "$wheel" "$MPMATH_WHEEL_URL" + test "$(wc -c < "$wheel" | tr -d '[:space:]')" = "$MPMATH_WHEEL_SIZE" + /opt/homebrew/bin/python3 - "$wheel" "$MPMATH_WHEEL_SHA256" <<'PY' + import hashlib + import pathlib + import sys + actual = hashlib.sha256(pathlib.Path(sys.argv[1]).read_bytes()).hexdigest() + if actual != sys.argv[2]: + raise SystemExit("mpmath wheel digest mismatch") + PY + - name: Run complete Codex plugin unit suite + env: + PYTHONPATH: ${{ runner.temp }}/mpmath-1.3.0-py3-none-any.whl run: /opt/homebrew/bin/python3 -B -m unittest discover -s tests/codex_plugin -v - name: Verify exact plugin identity diff --git a/OMARCHY_BUILD.md b/OMARCHY_BUILD.md new file mode 100644 index 0000000..656f614 --- /dev/null +++ b/OMARCHY_BUILD.md @@ -0,0 +1,86 @@ +# Omarchy JACKAL — full native rebuild recipe (Linux aarch64) + +Every binary in the Omarchy edition is built from source on this host; none are +committed (they are large and reproducible). This is the complete recipe to +rebuild the whole stack from a fresh clone. Times are approximate on 8 cores. + +Prerequisites (system): `pacman -S z3 cmake` (z3 is *supporting only*, not the +program-evidence anchor), `cargo`/`rustc` (stable), `elan` (Lean), `python3`. + +## 1. Anubis compiler (~2 min) + cd ~/Projects/anubis-lang + cargo build --release -p anubis --no-default-features # skip the Apple `prove` feature + install -m755 target/release/anubis ~/.local/bin/anubis + +## 2. Lean checkers (current) (~40 min incl. Mathlib cache) + cd ~/Projects/jackal/proofs/lean + export PATH="$HOME/.elan/bin:$PATH" + lake exe cache get # prebuilt Mathlib oleans — do NOT compile Mathlib + lake build jackal_cert_check jackal_gaussian_check jackal_int_cert_check jackal_parse_dump + # -> proofs/lean/.lake/build/bin/{jackal_cert_check,jackal_gaussian_check,jackal_int_cert_check} + +## 3. jackal-native (engine) (~1 min; needs Z3 for its contract obligations) + cd ~/Projects/jackal + ANUBIS_BIN=~/.local/bin/anubis anubis build jackal_calc.anb --out /tmp/jn + cp /tmp/jn/anubis_out ./jackal-native + +## 4. Archival v1.7.0 checker (native) (~40 min in a v1.7.0 worktree) + git worktree add /tmp/j170 v1.7.0 + cd /tmp/j170/proofs/lean && export PATH="$HOME/.elan/bin:$PATH" + lake exe cache get && lake build jackal_cert_check + mkdir -p ~/jackal-omarchy-archival + cp .lake/build/bin/jackal_cert_check ~/jackal-omarchy-archival/jackal_cert_check_v170 # expect sha d515cdc2 + git -C ~/Projects/jackal show v1.7.0:release/coverage/formal_coverage_inventory.json \ + > ~/jackal-omarchy-archival/formal_coverage_inventory_v170.json # expect sha 18ff7b1d + git worktree remove /tmp/j170 --force + +## 5. Approved Z3 4.15.4 (double-build, byte-reproducible) (~6 min) + # recipe: ~/omarchy-jackal-completion/z3build/build_recipe.sh (deterministic: + # SOURCE_DATE_EPOCH, -ffile-prefix-map, Release, static, --build-id=none) + URL=https://github.com/Z3Prover/z3/archive/refs/tags/z3-4.15.4.tar.gz # archive sha dae52625 + # build TWICE in isolated dirs; both MUST equal sha b6fcd93b (else STOP: not reproducible) + install -m700 /z3 ~/.local/share/JACKAL/z3/linux-aarch64/jackal_z3_v4154 + +## 6. Regenerate host evidence + manifest (seconds) + export PATH="$HOME/.elan/bin:$PATH" + # host-suffixed proof identities, compat floor, lean audit, archival identity/marker, + # approved_z3 marker — see the *.linux-aarch64* files under release/evidence, release/compat. + JACKAL_ANUBIS_COMPILER_PATH=~/.local/bin/anubis python3 -B release/tools/repin_linux.py --write + python3 -B tools/capability_inventory.py --write + python3 -B tools/capability_drift_gate.py --write-plugin-identity + +## 7. Build the package (~1 min) + JACKAL_ANUBIS_COMPILER_PATH=~/.local/bin/anubis JACKAL_DIST=~/jackal-dist-linux \ + JACKAL_ARCHIVAL_DIR=~/jackal-omarchy-archival \ + JACKAL_Z3_DIR=~/.local/share/JACKAL/z3/linux-aarch64 \ + sh release/build_package_linux.sh --build + # then update the linux-aarch64 pin block in plugins/jackel/scripts/provision_runtime.py + # with the printed tarball sha256/size + SHA256SUMS sha + extracted size. + +## 8. Install + /bin/sh plugins/jackel/scripts/launch_mcp.sh provision --tarball \ + ~/jackal-dist-linux/jackal-v1.7.3-linux-aarch64.tar.gz + omarchy-jackal doctor # FUNCTIONAL from live probes + +## Known-good digests + anubis compiler c6affa8c… jackal-native (rebuild) cert_check 89e4e42d… + gaussian_check 1f21c6b2… int_cert_check f2e26f50… v170 archival d515cdc2… + approved Z3 4.15.4 b6fcd93b… inventory-safe-v1 policy (frozen) 1b94350a… + +## Trust-surface sign-offs + - jackal_anubis_check_program: RESOLVED 2026-08-24 — architect (khephri.labs@proton.me) + designated the clean-source Linux/aarch64 anubis CHECK COMPILER sha256 + 6c3ae920… (double-built byte-identical, from anubis-lang commit 0ad40aaf = + 6aa6fd92 + host-honest doctor; supersedes 7cdafb30 which lacked the doctor fix, + recipe §1). Scope: Linux/aarch64 only; macOS 0d6a8f89 not reused; + inventory-safe-v1 policy body byte-frozen (1b94350a). Verifier anchor is + host-aware (Darwin=0d6a8f89 preserved). Independently re-verified 2026-08-24 + (verified-program-evidence, 4 proofs, receipt 46c7b357… (D2)). Records: + release/evidence/anubis_program_dogfood_linux_aarch64_v1.json and + ~/omarchy-jackal-completion/evidence/compiler/SIGNOFF_linux_aarch64.json; + finding ~/omarchy-jackal-completion/evidence/I_check_compiler_finding.json + = RESOLVED_ARCHITECT_SIGNED_OFF. + - Note: the overall product verdict stays _V111_PENDING — gated on the FUTURE + sealed 49-tool package (AWAITING_SEALED_V111_PACKAGE), NOT on this + check-compiler round, which is independently resolved. Linux x86_64 + execution remains gate-declared/unobserved (separate item). diff --git a/README.md b/README.md index 5669c08..4df073b 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,11 @@ Qalculate!, Soulver and SpeedCrunch. JACKAL does **not** claim parity with a gen arbitrary-precision-float engine, or interactive graphing system. Its differentiated implemented surface is claim-aware and measurement-aware computation. +For an honest path from research kernel to an externally reviewable engineering +pilot, see [`docs/AEROSPACE_READINESS_GAP_LEDGER.md`](docs/AEROSPACE_READINESS_GAP_LEDGER.md). +It separates implemented evidence from the qualification, refinement, +independent-verification, safety, security, and operations work that remains. + ## Why a calculator, in the age of frontier AI Language models are demonstrably unreliable at the arithmetic layer. OpenAI's own GSM8K diff --git a/RESEARCH.md b/RESEARCH.md index 70c772a..4252c79 100644 --- a/RESEARCH.md +++ b/RESEARCH.md @@ -5,7 +5,7 @@ This matrix uses official product pages as the baseline rather than review-site | Baseline | Observed strengths | JACKAL response | |---|---|---| -| TI-Nspire CX II CAS | Linked algebraic/graphical/numeric views; CAS; matrices; regression, distributions, hypothesis tests; saved documents; programming and sensor data.[1] | Add matrices, numerical solvers, model cards, and reproducible fingerprints. A terminal Anubis program cannot honestly claim equivalent interactive graphing or CAS. | +| TI-Nspire CX II CAS | Linked algebraic/graphical/numeric views; CAS; matrices; regression, distributions, hypothesis tests; saved documents; programming and sensor data.[1] | JACKAL now adds an identity-pinned evidence-aware CAS router and a professional PNG graph lane to the Codex surface, with exact rational x coordinates, estimated f64 y samples, explicit curve breaks on refusal, and reproducible wrapper fingerprints. It still does **not** claim TI-Nspire-equivalent linked interactive views, matrices, regression, sensor acquisition, or a general-purpose CAS. | | Qalculate! | "Arbitrary precision with both rational and floating point numbers"; exact/approximate forms; symbolic calculus; "propagation of uncertainty and interval arithmetic"; extensive units/constants; plotting.[2] | Add first-class measured quantities, propagated uncertainty, dimensional checks, numerical calculus, and explicit model limits. JACKAL now implements exact arbitrary-precision **integers** (`big-add/mul/pow/fact/ncr`) and exact **big rationals** (`rat`) in pure Anubis, plus outward-rounded **interval arithmetic** as a certified lane (`integrate-bound`/`range-bound`, refuse-on-doubt — with its f64/libm rounding model stated rather than implied); arbitrary-precision floats and general CAS remain outside the claimed surface. | | Soulver | Natural-language notepad; variables, line references, live updates, units, dates and scenario worksheets.[3] | Add human-readable calculation cards and Anubis-native audit narratives. JACKAL now has single-invocation worksheets with persistent variables (`worksheet "a = 5; b = a^2; a+b"`); a persistent reactive notebook with live updates is still not claimed. | | SpeedCrunch | Fast keyboard workflow; live results/history; custom functions/variables; "up to 50 digits of precision"; formula/constants library.[4] | Keep a scriptable CLI, add domain models and self-auditing output. JACKAL's `big-` integer lane is exact at any length within stated compute caps (verified against Python's arbitrary precision, e.g. 1000! at 2568 digits) — beyond 50 digits for integer work. Float work remains IEEE-754 f64: 50-digit float precision is still not claimed. | diff --git a/assurance/README.md b/assurance/README.md new file mode 100644 index 0000000..c6e47ef --- /dev/null +++ b/assurance/README.md @@ -0,0 +1,33 @@ +# JACKAL functional-proof baseline + +`requirements.json` is the machine-readable source for allocated functional +requirements, component claims, and whole-surface closure status. + +The current universally quantified proofs apply to the total SPARK interval +decision kernel and to the finite claim-assurance policy kernel over their +complete declared input types. The claim-policy bridge also exhaustively checks +the current producer and independent verifier registries against the proved +SPARK truth table. The whole JACKAL product target is in progress. The closure +matrix deliberately exposes every sealed +runtime dependency family and every additive Codex tool group; it cannot become +a whole-product claim until all discovered entries are `proved-universal` and +all requirements are proved. + +Run: + +```sh +python3 -B tools/check_assurance_traceability.py +proofs/spark/hellgate_interval/prove.sh +proofs/spark/claim_policy/prove.sh +python3 -B -m unittest tests.claim_policy_conformance_test -v +``` + +The traceability gate rejects duplicate JSON keys, missing or one-way links, +unknown public surface families, and premature whole-product claims. The proof +gates reject missing tools, warnings, unproved or justified checks, skipped +allocated units, and proof assumptions or annotations. + +SPARK Platinum is used only for SPARK components whose contracts fully cover +their allocated functional requirements. Lean mathematical soundness, Anubis +program evidence, independent checker replay, and empirical tests are recorded +as different evidence forms rather than relabeled as SPARK Platinum. diff --git a/assurance/requirements.json b/assurance/requirements.json new file mode 100644 index 0000000..c018cd1 --- /dev/null +++ b/assurance/requirements.json @@ -0,0 +1,160 @@ +{ + "schema": "jackal-assurance-requirements-v1", + "system": "JACKAL deterministic mathematical evidence kernel", + "target": "Requirements-complete universal functional correctness within every declared input domain", + "product_claim": { + "status": "in-progress", + "reason": "The public surface still contains tested runtime, parser, adapter, orchestration, numerical-model, operating-system, compiler, and hardware boundaries that do not yet have universal refinement proofs." + }, + "component_claims": [ + { + "component": "proofs/spark/hellgate_interval/src/jackal_interval_envelope", + "target": "SPARK Platinum", + "status": "proved-local", + "requirement_ids": [ + "JCK-INT-001", + "JCK-INT-002", + "JCK-INT-003", + "JCK-INT-004" + ], + "boundary": "Total fixed-scale nonnegative interval decision kernel; certificate parsing, rational-to-fixed-scale conversion, nonlinear analysis, compiler, runtime, operating system, and hardware are outside this component." + }, + { + "component": "proofs/spark/claim_policy/src/jackal_claim_policy", + "target": "SPARK Platinum", + "status": "proved-local", + "requirement_ids": [ + "JCK-CLAIM-001", + "JCK-CLAIM-002", + "JCK-CLAIM-003" + ], + "boundary": "Total finite assurance-axis and rule-category algebra; Python parsing, rule identifier classification, node construction, hashing, rendering, compiler, runtime, operating system, and hardware are outside this component. Exhaustive bridge tests cover the current finite Python registries but are not a formal Python refinement proof." + } + ], + "requirements": [ + { + "id": "JCK-INT-001", + "shall": "The SPARK interval kernel shall return the exact upper-minus-lower width for every ordered fixed-scale interval and shall be free of run-time errors over its declared precondition.", + "method": "spark-platinum", + "status": "proved", + "allocation": [ + "proofs/spark/hellgate_interval/src/jackal_interval_envelope.ads" + ], + "verification": [ + "proofs/spark/hellgate_interval/prove.sh", + "tests/codex_plugin/test_spark_interval.py" + ], + "residuals": [] + }, + { + "id": "JCK-INT-002", + "shall": "The SPARK interval kernel shall return midpoint and ceiling-radius values that cover both endpoints of every ordered fixed-scale interval without overflow or range failure.", + "method": "spark-platinum", + "status": "proved", + "allocation": [ + "proofs/spark/hellgate_interval/src/jackal_interval_envelope.ads" + ], + "verification": [ + "proofs/spark/hellgate_interval/prove.sh", + "tests/codex_plugin/test_spark_interval.py" + ], + "residuals": [] + }, + { + "id": "JCK-INT-003", + "shall": "The SPARK interval kernel shall admit an envelope exactly when it is ordered, its target width is positive, and its exact width is strictly less than the target.", + "method": "spark-platinum", + "status": "proved", + "allocation": [ + "proofs/spark/hellgate_interval/src/jackal_interval_envelope.ads" + ], + "verification": [ + "proofs/spark/hellgate_interval/prove.sh", + "tests/codex_plugin/test_spark_interval.py" + ], + "residuals": [] + }, + { + "id": "JCK-INT-004", + "shall": "The SPARK interval kernel shall evaluate every value of its public input types with the specified rejection precedence, zero all rejected derived outputs, and return exact width plus a covering midpoint-radius pair on admission.", + "method": "spark-platinum", + "status": "proved", + "allocation": [ + "proofs/spark/hellgate_interval/src/jackal_interval_envelope.ads", + "proofs/spark/hellgate_interval/src/jackal_interval_envelope.adb" + ], + "verification": [ + "proofs/spark/hellgate_interval/prove.sh", + "tests/codex_plugin/test_spark_interval.py" + ], + "residuals": [] + }, + { + "id": "JCK-CLAIM-001", + "shall": "The SPARK claim policy kernel shall return the registry-defined canonical meet for every pair of mathematical, provenance, model-validity, and implementation classes; the mathematical meet shall be commutative, associative, and idempotent.", + "method": "spark-platinum", + "status": "proved", + "allocation": [ + "proofs/spark/claim_policy/src/jackal_claim_policy.ads" + ], + "verification": [ + "proofs/spark/claim_policy/prove.sh", + "tests/claim_policy_conformance_test.py", + "tests/claim_policy_verifier_bridge.py" + ], + "residuals": [] + }, + { + "id": "JCK-CLAIM-002", + "shall": "The SPARK claim policy kernel shall apply the exact preservation, interval-arithmetic, and derived-rule behavior for every mathematical and implementation class without ever strengthening either input axis.", + "method": "spark-platinum", + "status": "proved", + "allocation": [ + "proofs/spark/claim_policy/src/jackal_claim_policy.ads" + ], + "verification": [ + "proofs/spark/claim_policy/prove.sh", + "tests/claim_policy_conformance_test.py", + "tests/claim_policy_verifier_bridge.py" + ], + "residuals": [] + }, + { + "id": "JCK-CLAIM-003", + "shall": "The SPARK claim policy kernel shall preserve each artifact-evidence flag exactly when both parent flags carry it for every pair of artifact flag records.", + "method": "spark-platinum", + "status": "proved", + "allocation": [ + "proofs/spark/claim_policy/src/jackal_claim_policy.ads" + ], + "verification": [ + "proofs/spark/claim_policy/prove.sh", + "tests/claim_policy_conformance_test.py", + "tests/claim_policy_verifier_bridge.py" + ], + "residuals": [] + } + ], + "surface_closure": { + "sealed_dependency_families": { + "lean-range": "formal-mathematics-open-refinement", + "lean-gaussian": "formal-mathematics-open-refinement", + "lean-int-cert": "formal-mathematics-open-refinement", + "lean-receipt-registry": "formal-mathematics-open-refinement", + "runtime-only": "tested-open-proof", + "exact-cert-verifier": "independently-checked-open-proof", + "claim-router": "tested-open-proof", + "claim-verifier": "independently-checked-open-proof", + "structural-checker": "independently-checked-open-proof", + "decision-checker": "independently-checked-open-proof", + "program-verifier": "anubis-evidence-open-refinement" + }, + "additive_groups": { + "measurement": "tested-open-proof", + "advanced": "tested-open-proof", + "stem": "tested-open-proof" + }, + "closed_status": "proved-universal", + "rule": "A whole-product claim is permitted only when every discovered sealed dependency family and additive group has closed_status and every allocated requirement is proved." + } +} diff --git a/docs/AEROSPACE_READINESS_GAP_LEDGER.md b/docs/AEROSPACE_READINESS_GAP_LEDGER.md new file mode 100644 index 0000000..dbaba26 --- /dev/null +++ b/docs/AEROSPACE_READINESS_GAP_LEDGER.md @@ -0,0 +1,68 @@ +# JACKAL aerospace-readiness gap ledger + +JACKAL should be presented to aerospace organizations as an evidence-aware +computation kernel under active verification, not as a qualified flight tool. +The strongest credible pitch is that it makes assurance boundaries visible and +refuses unsupported claims. Grant and pilot proposals should ask reviewers to +evaluate reproducible artifacts, not superlatives. + +## Evidence already present + +- closed, identity-pinned runtime and wrapper inventories; +- explicit `exact`, `exact-given`, `checked`, `estimated`, `bounded`, + `formal-bounded`, `model-based`, and refusal outcomes; +- independent replay for admitted formal receipts and claim bundles; +- fail-closed MCP transport, process isolation, bounded resources, and mutation + tests; +- THOTH provenance carried inside JACKAL rather than a second authority; +- exact-field matrix workflows, model-scoped statistics, sensors with supplied + provenance, idealized aerospace workflows, graphing, and linked views; +- a fixed HELLGATE nonlinear eigenvalue enclosure with subject-scoped trial + diagnostics and a narrowly stated ground-state quartic/energy transfer; +- a repository-side SPARK interval kernel whose contracts and run-time checks + are discharged by GNATprove without `pragma Assume`. + +None of those facts by itself qualifies JACKAL for mission, flight, launch, +personnel-safety, or hardware-control use. + +## Blocking gaps before a serious operational pilot + +| Gap | Required artifact | Admission gate | +|---|---|---| +| Requirements | uniquely identified shall-statements and bidirectional traceability | every public claim maps to requirement, implementation, test, and residual risk | +| Independent verification | verifier developed and reviewed separately from each producer | hostile certificates and semantic-laundering mutations fail closed | +| Formal refinement | proof that admitted source inputs refine the checker model | no status promotion until parser, arithmetic, and source-to-checker gaps are closed | +| Toolchain trust | pinned compiler/prover/runtime plus reproducible build evidence | binary identity, environment, options, and proof reports are independently replayable | +| Numerical validation | benchmark corpus with reference solutions and explicit conditioning | errors are separated into model, discretization, domain, iteration, rounding, and quadrature components | +| Safety case | program-specific hazards, controls, independence, and human review | consequence ceiling cannot be raised by mathematical assurance alone | +| Cybersecurity | threat model, supply-chain policy, vulnerability handling, and penetration evidence | no unauthenticated input can silently widen authority or bypass refusal | +| Operations | configuration control, rollback, audit retention, incident response, and training | every operational result remains bound to exact version and policy expectations | +| Human factors | reviewable rendering and anti-automation-bias controls | plots, badges, and summaries cannot hide assumptions or non-claims | + +## Recommended pilot scope + +Start with read-only, non-commanding engineering analysis where an existing +trusted workflow can run in parallel. Candidate work packages are symbolic +preprocessing, exact unit transformations, independently replayed bounds, +matrix calculations over exact inputs, and provenance-preserving report +assembly. Exclude flight control, autonomous commanding, pass/fail launch +authority, and safety-critical sensor decisions until a sponsoring program +defines and accepts a qualification basis. + +## HELLGATE residuals + +The fixed certificate currently supports a `bounded` ground-state eigenvalue, +a `bounded` ground-state quartic norm and energy-functional transfer, and +`bounded` trial-only moments and consistency residuals. It still does not +enclose the true ground-state polynomial moments, nonlinear sensitivity, +tunnelling split, or Bogoliubov spectrum. Spectral stability and the remaining +strong-coupling topology work require separate mathematical certificates. + +## Funding-grade demonstration package + +A credible external demonstration should ship a frozen source snapshot, exact +plugin/runtime identities, build instructions, proof logs, mutation corpus, +benchmark results, a signed limitation statement, and a one-command read-only +replay. The evaluation question should be: “Can an independent reviewer +reproduce every admitted claim and every refusal?” It should not be: “Does the +interface look confident?” diff --git a/docs/HELLGATE_DIAGNOSTICS_ASSURANCE_DESIGN.md b/docs/HELLGATE_DIAGNOSTICS_ASSURANCE_DESIGN.md new file mode 100644 index 0000000..af8d1c3 --- /dev/null +++ b/docs/HELLGATE_DIAGNOSTICS_ASSURANCE_DESIGN.md @@ -0,0 +1,131 @@ +# HELLGATE diagnostic and transfer assurance design + +Status: implemented and locally replayed in the additive Codex plugin +worktree; this is not a release-qualification statement. + +## Objective + +Extend the fixed `hellgate-v1` replay without changing its problem, Barta +theorem, top-level `bounded` status, or refusal behavior. The extension must +answer two different questions without conflating them: + +1. What can the exact-rational checker enclose about the normalized certificate + trial `phi`? +2. What, if anything, can be transferred from that trial to the true positive + normalized ground state `u0`? + +The existing eigenvalue enclosure remains the primary result. New fields are +additive and are rejected at startup if the same identity-pinned checker cannot +recompute them. + +## Trial diagnostics + +For every interior polynomial piece the checker already proves a uniform +enclosure + +``` +abs(exp(q(s)) - p(s)) <= eta, 0 <= s <= 1. +``` + +The diagnostic extension shall: + +- convert the power-basis density polynomial to exact Bernstein coefficients; +- refuse unless `p - eta` is pointwise positive on every piece; +- integrate exact polynomial lower and upper bounds for `exp(q)`, `exp(2*q)`, + `x^k exp(q)`, and `q'(x)^2 exp(q)`; +- enclose all omitted half-line tails with the already checked decreasing + logarithmic derivative and exact exponential-moment formulae; +- divide only by the positive exact-rational normalization enclosure; +- return intervals for the trial quartic norm, moments 2/4/6, kinetic energy, + energy functional, energy/eigenvalue-identity residual, and virial residual. + +Every one of these fields is about `phi`, not `u0`. The result must carry a +subject identifier and non-claims that make this distinction machine-visible. + +## Ground-state quartic-norm transfer + +Write `rho_phi = phi^2` and `rho_0 = u0^2`. On mass-one positive densities, + +``` +F(rho) = epsilon^2/4 * integral(rho'^2/rho) + + integral(V rho) + lambda/2 * integral(rho^2) +``` + +is `lambda`-strongly convex in `L2`: the Fisher term is convex, the potential +term is linear, and the final term supplies the strong-convexity modulus. Its +first variation at `rho_phi` is the nonlinear quotient `R_phi`. + +If the checker proves `abs(R_phi - c) <= delta` globally, normalization gives +`integral(rho_phi-rho_0)=0`, and strong convexity plus +`norm(rho_phi-rho_0, L1) <= 2` gives + +``` +norm(rho_phi-rho_0, L2)^2 <= 4*delta/lambda. +``` + +The checker shall enclose the square root by exact integer/rational arithmetic +and transfer the trial `L2`-norm interval to + +``` +(max(0, norm(rho_phi, L2) - d))^2 + <= integral(u0^4) + <= (norm(rho_phi, L2) + d)^2. +``` + +This transfer does not enclose polynomial moments, the lambda derivative, the +Bogoliubov spectrum, or the tunnelling split. Those residuals remain explicit. + +## SPARK boundary + +The SPARK component is deliberately smaller than the mathematical checker. It +implements a total fixed-scale nonnegative interval decision kernel with +requirements `JCK-INT-001` through `JCK-INT-004`. The contracts cover exact +ordered width, midpoint/ceiling-radius endpoint coverage, strict admission +equivalence, deterministic rejection precedence, accepted derived outputs, and +zeroed rejection outputs over every value of the declared public input types. +The level-3 gate refuses warnings, unproved checks, justified checks, a skipped +unit, `pragma Assume`, or `pragma Annotate`. + +SPARK establishes absence of run-time errors and the stated arithmetic +postconditions for this component. It does not prove: + +- the nonlinear Barta theorem; +- the strong-convexity transfer theorem; +- Python-to-SPARK refinement or parser correctness; +- source-to-object equivalence; +- compiler, run-time, floating-point, or physical-model qualification. + +The SPARK executable is a repository-side independent proof/test artifact. It +is not inserted into the cross-platform MCP startup path until separately +packaged, identity-pinned binaries and source-to-input binding are designed. + +## Acceptance gates + +- The original compressed certificate remains byte-identical. +- Existing eigenvalue endpoints remain byte-identical. +- Coherently repinned mutations of density, tail, normalization, parity, + continuity, or non-claims refuse. +- Trial diagnostic intervals are ordered and contain direct high-precision + producer values used only as test oracles. +- The energy-identity and virial residual intervals contain zero and satisfy + their declared exact-rational width gates. +- The transferred ground-state quartic interval contains the trial quartic + interval enlarged by the proved density-distance bound. +- The full plugin suite, plugin identity gate, capability drift gate, and live + acceptance remain green. +- The SPARK proof gate and runtime boundary tests both pass. + +## Reproduction commands + +```sh +python3 -B -m unittest tests.codex_plugin.test_hellgate -v +python3 -B -m unittest tests.codex_plugin.test_spark_interval -v +python3 -B -m unittest discover -s tests/codex_plugin -v +python3 -B plugins/jackel/scripts/verify_plugin.py +python3 -B tools/capability_drift_gate.py +proofs/spark/hellgate_interval/prove.sh +``` + +`tools/hellgate_trial_oracle.py` is an explicitly untrusted mpmath +differential path. Its high-precision values must land inside the bounded +exact-rational trial intervals, but agreement never upgrades the result. diff --git a/docs/superpowers/specs/2026-08-17-jackel-codex-plugin-design.md b/docs/superpowers/specs/2026-08-17-jackel-codex-plugin-design.md index 374cc58..22062e2 100644 --- a/docs/superpowers/specs/2026-08-17-jackel-codex-plugin-design.md +++ b/docs/superpowers/specs/2026-08-17-jackel-codex-plugin-design.md @@ -12,13 +12,17 @@ Current publication base: `c3ec10f5b446b28a04f9bd19606fc8b329ac43f5` The current v1.7.3 release exposes the ordered 41-tool catalog recorded in `release/capability_inventory_v1.json`, with tool-containing implementation ref `d25bcd9818e0d106f337798f80527ae611cc3acc`. The annotated tag, GitHub -release, package receipt, and downloaded asset must bind the same bytes. +release, package receipt, and downloaded asset must bind the same bytes. The +identity-pinned Codex wrapper adds seven THOTH measurement/provenance tools and +three CAS/graph/certificate tools plus seven STEM workflow tools, yielding one +unified 58-tool surface. THOTH is a named JACKAL subsystem and never a second +service or arithmetic authority. ## Objective Add a repo-local, publishable Codex plugin with migration-preserved package ID -`jackel` and public display name JACKAL. It exposes JACKAL's complete +`jackel` and public display name JACKAL + THOTH. It exposes JACKAL's complete mathematical evidence-kernel tool inventory on macOS. The plugin must make the full engine available without weakening JACKAL's epistemic classes, refusal semantics, checker boundaries, or pinned runtime identity. @@ -40,6 +44,15 @@ unsupported until JACKAL publishes and seals a corresponding runtime. general structured claims while retaining every exact, checked, estimated, bounded, formal-bounded, model-based, and verification lane. - Preserve backend JSON values losslessly as structured MCP output. +- Add one closed CAS router without changing the assurance of the delegated + runtime result. +- Render graph PNGs from delegated exact rational x coordinates and estimated + y samples while marking pixels and interpolation as visualization only. +- Replay the fixed HELLGATE nonlinear ground-state certificate only after an + independent identity-pinned exact-rational checker accepts it as `bounded`, + never `formal-bounded`. +- Name THOTH in the user-facing plugin while keeping it inside the single + JACKAL process and tool surface. - Keep named refusals first-class. A refusal is a valid epistemic result, not a reason to retry silently through a weaker lane. - Add complete Codex plugin metadata and a repo-local marketplace entry. @@ -51,15 +64,17 @@ unsupported until JACKAL publishes and seals a corresponding runtime. ## Non-goals -- Reimplementing any JACKAL calculation, proof checker, claim router, receipt - verifier, or bundle verifier. +- Reimplementing any sealed-runtime JACKAL calculation, proof checker, claim + router, receipt verifier, or bundle verifier. The wrapper's specialized + HELLGATE checker is separate identity-pinned code with an explicit theorem + assumption and never inherits the sealed runtime's formal status. - Copying `plugin/hermes` or any pinned runtime file into `plugins/jackel`. - Editing the load-bearing Hermes bundle, which would change its pinned bundle identity. - Presenting a checker-admitted formal fragment as whole-system formal assurance. -- Adding a hosted connector, `.app.json`, HTTP service, hooks, branding assets, - screenshots, or an automatic first-run download in version 0.1.0. +- Adding a hosted connector, `.app.json`, HTTP service, hooks, or an automatic + first-run download in version 0.1.0. - Supporting Linux, Windows, or Intel macOS in the initial release. ## Repository Layout @@ -73,7 +88,12 @@ plugins/jackel/ .mcp.json PLUGIN_IDENTITY.sha256 README.md + assets/jackal-thoth-hellgate-graph.png + mcp/advanced.py + mcp/hellgate_verify.py + mcp/measurement.py mcp/server.py + mcp/certificates/hellgate_v1.json.zlib scripts/launch_mcp.zsh scripts/provision_runtime.py scripts/verify_plugin.py @@ -94,7 +114,7 @@ backend. It locates a separately sealed runtime and invokes the unchanged - `name`: `jackel` - `version`: `0.1.0+codex.<14-digit timestamp>` -- `description`: `Expose JACKAL's claim-aware computation, domain-pack, and program-evidence kernel to Codex.` +- `description`: `Expose JACKAL's evidence-aware CAS, graphing, integrated THOTH measurement, nonlinear certificates, and program evidence to Codex.` - `author.name`: `Anubis Quantum Cipher` - `author.url`: `https://github.com/AnubisQuantumCipher` - `homepage` and `repository`: @@ -102,15 +122,23 @@ backend. It locates a separately sealed runtime and invokes the unchanged - `license`: `MIT` - `skills`: `./skills/` - `mcpServers`: `./.mcp.json` -- `interface.displayName`: `JACKAL` -- `interface.shortDescription`: `Claim-aware computation with explicit evidence classes` -- `interface.longDescription`: `Expose JACKAL's 41-tool v1.7.3 release runtime through Codex. The MCP adapter copies the parsed runtime result object into structuredContent unchanged; its only adapter-local tool result is status=refused reason=plugin-busy. Runtime result and assurance vocabulary: ok, exact, structural-exact, formal-bounded, bounded, checked, estimated, model-based, verified, verified-program-evidence, verified-program-receipt, indeterminate, and refused. Formal-bounded is limited to checker-admitted fragments; program evidence leaves construct-totality, source, and runtime residuals open. Requires Apple Silicon macOS and Python >=3.10 at /opt/homebrew/bin/python3 (install with brew install python).` +- `interface.displayName`: `JACKAL + THOTH` +- `interface.shortDescription`: `Evidence-aware CAS, graphing, and integrated measurement` +- `interface.longDescription`: names the unified 58-tool surface, THOTH's + in-process identity, delegated CAS semantics, the graph visualization + boundary, the HELLGATE `bounded` boundary, and the requirement to preserve + statuses, assumptions, identities, route traces, consequence ceilings, + refusals, and non-claims. - `interface.developerName`: `Anubis Quantum Cipher` - `interface.category`: `Productivity` - `interface.capabilities`: `["Interactive"]` - `interface.websiteURL`: `https://github.com/AnubisQuantumCipher/jackal` -- `keywords`: `jackel`, `mathematics`, `numerical-trust`, `formal-verification`, - `evidence`, `mcp` +- `interface.brandColor`: `#00E0B8` +- `interface.screenshots`: `./assets/jackal-thoth-hellgate-graph.png`, captured + from the installed `jackal_graph` tool and explicitly treated as estimated + visualization +- `keywords`: `jackel`, `thoth`, `mathematics`, `computer-algebra`, `graphing`, + `symbolic-algebra`, `numerical-trust`, `formal-verification`, `evidence`, `mcp` The short description identifies JACKAL as an evidence kernel, not a generic calculator. The long description names the mathematical, domain-pack, and @@ -121,10 +149,9 @@ rather than publishing broken paths or invented policies. Starter prompts: -1. `Classify and verify this numerical claim with JACKAL.` -2. `Find the strongest supported bound and refuse any silent downgrade.` -3. `Verify this receipt or claim bundle against my pinned expectations.` -4. `Verify this Anubis Safe program-evidence package without executing its artifact.` +1. `Use JACKAL + THOTH to solve and classify this calculation.` +2. `Graph this expression and separate visualization from evidence.` +3. `Replay the HELLGATE nonlinear ground-state certificate without promoting its assurance.` ## Marketplace Entry @@ -371,7 +398,11 @@ TCB, but they are part of the end-to-end Codex request/result fidelity TCB. `plugins/jackel/PLUGIN_IDENTITY.sha256` records a stable, sorted digest inventory for the plugin manifest, MCP manifest, installation/operation README, launcher, adapter, provisioner, verification script, and operational -skill. The manifest excludes only itself. `scripts/verify_plugin.py` performs a bounded descriptor-relative, +skill. It also binds `runtime_manifest_baseline_v173.json`, the retained exact +metadata input from the published v1.7.3 capability inventory. The current +manifest and MCP adapter are a separately identified additive overlay; they do +not rewrite the historical inventory. The manifest excludes only itself. +`scripts/verify_plugin.py` performs a bounded descriptor-relative, no-follow traversal and rejects every unlisted file, link, special entry, directory, bytecode cache, oversized manifest/file set, path-identity change, or parent-directory mutation before printing a deterministic diff --git a/domain_packs/registry_v1.json b/domain_packs/registry_v1.json index 30ac20c..387e0a8 100644 --- a/domain_packs/registry_v1.json +++ b/domain_packs/registry_v1.json @@ -8,7 +8,7 @@ "pack_spec_path": "domain_packs/PACK_SPEC.md", "pack_spec_sha256": "2d76022dc2375fa3a235f05890c8b3a36ac77b008ecc5d58fb658deb253a947a", "pack_verifier_path": "tools/domain_pack_verify.py", - "pack_verifier_sha256": "22984f511208af2d7a318f1a43306d95a4b0f61876d8b44f34f39a2ded6d573d", + "pack_verifier_sha256": "53553ba75d7f233e79fba68597642e7a01d60dec340246147c95a2e8e3ecb08b", "inference_registry_path": "release/claim/inference_registry_v1.json", "inference_registry_sha256": "c70b33d5aee8071b5125e6a5f8ffe5226fc22a137d920c17d9b3463968be13f0", "packs": [ @@ -54,5 +54,5 @@ ] } ], - "registry_digest_sha256": "9f96f53795746b75f6e9d505e49f6f2578407d224f5e22fdad8a03e8a738fed8" + "registry_digest_sha256": "b4fba2533f43490c3e81a925e84bf8eef223a5e6017d8fddb06d76c9f19e893d" } diff --git a/evals/mcp/jackal_stem_v1.xml b/evals/mcp/jackal_stem_v1.xml new file mode 100644 index 0000000..cb25e5c --- /dev/null +++ b/evals/mcp/jackal_stem_v1.xml @@ -0,0 +1,43 @@ + + + + Use the exact matrix workflow to solve A x = b for A = [[2,1],[5,3]] and b = [1,2]. Independently multiply A by the returned solution with the matrix workflow and require the product to equal b. Report only the second component of x as a reduced rational. + -1 + + + Fit the degree-two polynomial OLS model to x = [0,1,2,3] and y = [1,6,17,34]. Preserve the model-based wrapper status, then send the returned ascending exact coefficients through JACKAL exact arithmetic to evaluate the fitted polynomial at x = 5. Report only the exact predicted value. + 86 + + + Under the declared Binomial(n = 8, p = 1/3) model, obtain the exact CDF through k = 2. Use JACKAL exact arithmetic to take its complement, and independently check it by summing the exact PMFs from k = 3 through k = 8. Report only the reduced rational probability of more than two successes. + 1163/2187 + + + For k = 9 successes in n = 12 Bernoulli trials under p0 = 1/2, run both admitted one-sided exact-binomial hypothesis alternatives. Use JACKAL exact arithmetic to compare the two returned exact p-values. Report only the smaller reduced rational p-value; do not turn it into a decision about the null. + 299/4096 + + + Use the supplied-sensor linear-calibration workflow with sensor_id eval.sensor, channel A, quantity displacement, unit m, raw samples [1/2,3/2,5/2,7/2], scale 2, offset -1, source "fixed evaluation fixture", observed_at 2026-08-28T00:00:00Z, calibration_source "fixed affine fixture", and calibration_as_of 2026-08-28. Feed the returned calibrated samples into JACKAL descriptive statistics and compare the two population-variance fields with exact arithmetic. Report only the common reduced rational variance. + 5 + + + Run the circular-orbit model with mu = 398600441800000 and radius = 7000000, then run vis-viva with the same mu and radius and semi_major_axis = 7000000. Extract the exact square-root radicands from both delegation traces, subtract them with JACKAL exact arithmetic, and confirm that both scalar enclosures are formal-bounded with checker_rerun ACCEPT. Output True if the exact difference is zero and both checker conditions hold; otherwise output False. + True + + + Canonicalize x^2-4 through the CAS route, obtain its checked derivative, and open a linked workspace on [-4,4] with 17 samples. Inspect the embedded text/html resource whose digest matches the returned resource URI. Report only the short sentence in that resource that warns against treating the rendered geometry as proof, including its final period. + Pixels are not proof. + + + Replay the fixed hellgate-v1 ground-state certificate. From its displayed decimal eigenvalue interval, subtract the lower endpoint from the upper endpoint with JACKAL exact arithmetic, then use an exact algebraic sign check to compare that width with 1/500000000000. Output True only if the certificate status is bounded and the displayed width is strictly smaller; otherwise output False. + True + + + Differentiate V(x) = x^6-5*x^4+4*x^2, use exact polynomial equality to check the derivative against 2*x*(3*x^4-10*x^2+4), and isolate every distinct real root of that exact derivative polynomial. Report only the number of distinct real critical points. + 5 + + + Convert exactly 3 international statute miles to metres and exactly 2 international nautical miles to metres using the definitional conversion lane. Subtract the nautical-mile result from the statute-mile result with JACKAL exact arithmetic. Report only the reduced rational difference in metres. + 140504/125 + + diff --git a/jackal-atan-rat-release b/jackal-atan-rat-release index c8a5b78..069fd9a 100755 --- a/jackal-atan-rat-release +++ b/jackal-atan-rat-release @@ -20,7 +20,13 @@ set -eu ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PRODUCER="$ROOT/tools/atan_rat_producer.py" CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_cert_check" -MANIFEST="$ROOT/release/MANIFEST.sha256" +# Per-host release identity: a source build on this host produces +# byte-different compiled artifacts (jackal-native + Lean checkers), so it +# carries its own MANIFEST..sha256. The macOS arm64 manifest remains +# the default when no host-specific one is present. +HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +MANIFEST="$ROOT/release/MANIFEST.$HOST_TAG.sha256" +[ -f "$MANIFEST" ] || MANIFEST="$ROOT/release/MANIFEST.sha256" PROOF_ID="$ROOT/release/evidence/range_proof_identity_v172.json" INVENTORY="$ROOT/release/coverage/formal_coverage_inventory.json" diff --git a/jackal-cert-release b/jackal-cert-release index 8d2f62f..e9c5740 100755 --- a/jackal-cert-release +++ b/jackal-cert-release @@ -18,7 +18,15 @@ set -eu ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) EVALUATOR="$ROOT/jackal-native" CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_cert_check" -MANIFEST="$ROOT/release/MANIFEST.sha256" +# Per-host release identity: a source build on this host produces +# byte-different compiled artifacts (jackal-native + Lean checkers), so it +# carries its own MANIFEST..sha256. The macOS arm64 manifest remains +# the default when no host-specific one is present. +HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +MANIFEST="$ROOT/release/MANIFEST.$HOST_TAG.sha256" +[ -f "$MANIFEST" ] || MANIFEST="$ROOT/release/MANIFEST.sha256" +PROOF_ID="$ROOT/release/evidence/range_proof_identity_v172.$HOST_TAG.json" +[ -f "$PROOF_ID" ] || PROOF_ID="$ROOT/release/evidence/range_proof_identity_v172.json" if [ "$#" -ne 4 ]; then echo "usage: jackal-cert-release \"\" " >&2 @@ -50,7 +58,7 @@ exec python3 -I -S -B "$ROOT/tools/isolated_entry.py" range \ --expected-source "$EXPECTED_SOURCE" \ --inventory "$ROOT/release/coverage/formal_coverage_inventory.json" \ --expected-inventory "$EXPECTED_INVENTORY" \ - --proof-identity "$ROOT/release/evidence/range_proof_identity_v172.json" \ + --proof-identity "$PROOF_ID" \ --expected-proof-identity-file "$EXPECTED_PROOF_FILE" \ --expected-proof-identity-digest "$EXPECTED_PROOF_DIGEST" \ --release-epoch v1.7.2 --formal-receipt "$4" diff --git a/jackal-cos-rat-release b/jackal-cos-rat-release index 3692d9b..b6b0690 100755 --- a/jackal-cos-rat-release +++ b/jackal-cos-rat-release @@ -21,7 +21,13 @@ set -eu ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PRODUCER="$ROOT/tools/sin_rat_producer.py" CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_cert_check" -MANIFEST="$ROOT/release/MANIFEST.sha256" +# Per-host release identity: a source build on this host produces +# byte-different compiled artifacts (jackal-native + Lean checkers), so it +# carries its own MANIFEST..sha256. The macOS arm64 manifest remains +# the default when no host-specific one is present. +HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +MANIFEST="$ROOT/release/MANIFEST.$HOST_TAG.sha256" +[ -f "$MANIFEST" ] || MANIFEST="$ROOT/release/MANIFEST.sha256" PROOF_ID="$ROOT/release/evidence/range_proof_identity_v172.json" INVENTORY="$ROOT/release/coverage/formal_coverage_inventory.json" diff --git a/jackal-exp-rat-release b/jackal-exp-rat-release index 711e83b..0402fb8 100755 --- a/jackal-exp-rat-release +++ b/jackal-exp-rat-release @@ -21,7 +21,13 @@ set -eu ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PRODUCER="$ROOT/tools/exp_rat_producer.py" CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_cert_check" -MANIFEST="$ROOT/release/MANIFEST.sha256" +# Per-host release identity: a source build on this host produces +# byte-different compiled artifacts (jackal-native + Lean checkers), so it +# carries its own MANIFEST..sha256. The macOS arm64 manifest remains +# the default when no host-specific one is present. +HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +MANIFEST="$ROOT/release/MANIFEST.$HOST_TAG.sha256" +[ -f "$MANIFEST" ] || MANIFEST="$ROOT/release/MANIFEST.sha256" PROOF_ID="$ROOT/release/evidence/range_proof_identity_v172.json" INVENTORY="$ROOT/release/coverage/formal_coverage_inventory.json" diff --git a/jackal-gaussian-release b/jackal-gaussian-release index 8bcee20..d5ad623 100755 --- a/jackal-gaussian-release +++ b/jackal-gaussian-release @@ -2,7 +2,15 @@ # Fail-closed root wrapper for theorem-backed Gaussian integration. set -eu ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -MANIFEST="$ROOT/release/MANIFEST.sha256" +# Per-host release identity: a source build on this host produces +# byte-different compiled artifacts (jackal-native + Lean checkers), so it +# carries its own MANIFEST..sha256. The macOS arm64 manifest remains +# the default when no host-specific one is present. +HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +MANIFEST="$ROOT/release/MANIFEST.$HOST_TAG.sha256" +[ -f "$MANIFEST" ] || MANIFEST="$ROOT/release/MANIFEST.sha256" +PROOF_ID="$ROOT/release/evidence/gaussian_proof_identity.$HOST_TAG.json" +[ -f "$PROOF_ID" ] || PROOF_ID="$ROOT/release/evidence/gaussian_proof_identity.json" PRODUCER="$ROOT/tools/gaussian_certificate.py" CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_gaussian_check" @@ -32,7 +40,7 @@ exec python3 -I -S -B "$ROOT/tools/isolated_entry.py" gaussian \ --expected-producer "$EXPECTED_PRODUCER" --expected-checker "$EXPECTED_CHECKER" \ --inventory "$ROOT/release/coverage/formal_coverage_inventory.json" \ --expected-inventory "$EXPECTED_INVENTORY" \ - --proof-identity "$ROOT/release/evidence/gaussian_proof_identity.json" \ + --proof-identity "$PROOF_ID" \ --expected-proof-identity-file "$EXPECTED_PROOF_FILE" \ --expected-proof-identity-digest "$EXPECTED_PROOF_DIGEST" \ --receipt "$5" --release-epoch v1.5.0 --timeout "$TIMEOUT" diff --git a/jackal-int-cert-release b/jackal-int-cert-release index ed630c2..759625a 100755 --- a/jackal-int-cert-release +++ b/jackal-int-cert-release @@ -17,7 +17,15 @@ set -eu ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PRODUCER="$ROOT/tools/int_cert_producer.py" CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_int_cert_check" -MANIFEST="$ROOT/release/MANIFEST.sha256" +# Per-host release identity: a source build on this host produces +# byte-different compiled artifacts (jackal-native + Lean checkers), so it +# carries its own MANIFEST..sha256. The macOS arm64 manifest remains +# the default when no host-specific one is present. +HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +MANIFEST="$ROOT/release/MANIFEST.$HOST_TAG.sha256" +[ -f "$MANIFEST" ] || MANIFEST="$ROOT/release/MANIFEST.sha256" +PROOF_ID="$ROOT/release/evidence/int_cert_proof_identity_v172.$HOST_TAG.json" +[ -f "$PROOF_ID" ] || PROOF_ID="$ROOT/release/evidence/int_cert_proof_identity_v172.json" if [ "$#" -ne 5 ]; then echo "usage: jackal-int-cert-release \"\" " >&2 @@ -48,7 +56,7 @@ exec python3 -I -S -B "$ROOT/tools/isolated_entry.py" int-cert \ --receipt "$5" \ --inventory "$ROOT/release/coverage/formal_coverage_inventory.json" \ --expected-inventory "$EXPECTED_INVENTORY" \ - --proof-identity "$ROOT/release/evidence/int_cert_proof_identity_v172.json" \ + --proof-identity "$PROOF_ID" \ --expected-proof-identity-file "$EXPECTED_PROOF_FILE" \ --expected-proof-identity-digest "$EXPECTED_PROOF_DIGEST" \ --release-epoch v1.7.2 diff --git a/jackal-ln-rat-release b/jackal-ln-rat-release index 67d5259..0a52e95 100755 --- a/jackal-ln-rat-release +++ b/jackal-ln-rat-release @@ -21,7 +21,13 @@ set -eu ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PRODUCER="$ROOT/tools/ln_rat_producer.py" CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_cert_check" -MANIFEST="$ROOT/release/MANIFEST.sha256" +# Per-host release identity: a source build on this host produces +# byte-different compiled artifacts (jackal-native + Lean checkers), so it +# carries its own MANIFEST..sha256. The macOS arm64 manifest remains +# the default when no host-specific one is present. +HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +MANIFEST="$ROOT/release/MANIFEST.$HOST_TAG.sha256" +[ -f "$MANIFEST" ] || MANIFEST="$ROOT/release/MANIFEST.sha256" PROOF_ID="$ROOT/release/evidence/range_proof_identity_v172.json" INVENTORY="$ROOT/release/coverage/formal_coverage_inventory.json" diff --git a/jackal-sin-rat-release b/jackal-sin-rat-release index da3ee06..f174419 100755 --- a/jackal-sin-rat-release +++ b/jackal-sin-rat-release @@ -21,7 +21,13 @@ set -eu ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PRODUCER="$ROOT/tools/sin_rat_producer.py" CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_cert_check" -MANIFEST="$ROOT/release/MANIFEST.sha256" +# Per-host release identity: a source build on this host produces +# byte-different compiled artifacts (jackal-native + Lean checkers), so it +# carries its own MANIFEST..sha256. The macOS arm64 manifest remains +# the default when no host-specific one is present. +HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +MANIFEST="$ROOT/release/MANIFEST.$HOST_TAG.sha256" +[ -f "$MANIFEST" ] || MANIFEST="$ROOT/release/MANIFEST.sha256" PROOF_ID="$ROOT/release/evidence/range_proof_identity_v172.json" INVENTORY="$ROOT/release/coverage/formal_coverage_inventory.json" diff --git a/jackal-sqrt-rat-release b/jackal-sqrt-rat-release index 6a58efc..3b0d988 100755 --- a/jackal-sqrt-rat-release +++ b/jackal-sqrt-rat-release @@ -20,7 +20,13 @@ set -eu ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PRODUCER="$ROOT/tools/sqrt_rat_producer.py" CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_cert_check" -MANIFEST="$ROOT/release/MANIFEST.sha256" +# Per-host release identity: a source build on this host produces +# byte-different compiled artifacts (jackal-native + Lean checkers), so it +# carries its own MANIFEST..sha256. The macOS arm64 manifest remains +# the default when no host-specific one is present. +HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +MANIFEST="$ROOT/release/MANIFEST.$HOST_TAG.sha256" +[ -f "$MANIFEST" ] || MANIFEST="$ROOT/release/MANIFEST.sha256" PROOF_ID="$ROOT/release/evidence/range_proof_identity_v172.json" INVENTORY="$ROOT/release/coverage/formal_coverage_inventory.json" diff --git a/jackal-tanh-rat-release b/jackal-tanh-rat-release index 69863f3..12082fd 100755 --- a/jackal-tanh-rat-release +++ b/jackal-tanh-rat-release @@ -22,7 +22,13 @@ set -eu ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PRODUCER="$ROOT/tools/tanh_rat_producer.py" CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_cert_check" -MANIFEST="$ROOT/release/MANIFEST.sha256" +# Per-host release identity: a source build on this host produces +# byte-different compiled artifacts (jackal-native + Lean checkers), so it +# carries its own MANIFEST..sha256. The macOS arm64 manifest remains +# the default when no host-specific one is present. +HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +MANIFEST="$ROOT/release/MANIFEST.$HOST_TAG.sha256" +[ -f "$MANIFEST" ] || MANIFEST="$ROOT/release/MANIFEST.sha256" PROOF_ID="$ROOT/release/evidence/range_proof_identity_v172.json" INVENTORY="$ROOT/release/coverage/formal_coverage_inventory.json" diff --git a/plugins/jackel/.codex-plugin/plugin.json b/plugins/jackel/.codex-plugin/plugin.json index 0b5fe5a..aab9783 100644 --- a/plugins/jackel/.codex-plugin/plugin.json +++ b/plugins/jackel/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "jackel", - "version": "0.1.0+codex.20260824183637", - "description": "Expose JACKAL's claim-aware computation, domain-pack, and program-evidence kernel to Codex.", + "version": "0.1.0+codex.20260828140726", + "description": "Expose JACKAL's evidence-aware CAS, professional linked graphing, integrated THOTH measurement, matrices, statistical models, sensors, aerospace workflows, nonlinear certificates, and program evidence to Codex.", "author": { "name": "Anubis Quantum Cipher", "url": "https://github.com/AnubisQuantumCipher" @@ -11,7 +11,17 @@ "license": "MIT", "keywords": [ "jackel", + "thoth", "mathematics", + "computer-algebra", + "graphing", + "linked-views", + "matrices", + "regression", + "probability", + "sensors", + "aerospace", + "symbolic-algebra", "numerical-trust", "formal-verification", "evidence", @@ -20,20 +30,27 @@ "skills": "./skills/", "mcpServers": "./.mcp.json", "interface": { - "displayName": "JACKAL", - "shortDescription": "Claim-aware computation with explicit evidence classes", - "longDescription": "Expose JACKAL's 41-tool v1.7.3 release runtime through Codex. The MCP adapter copies the parsed runtime result object into structuredContent unchanged; its only adapter-local tool result is status=refused reason=plugin-busy. Runtime result and assurance vocabulary: ok, exact, structural-exact, formal-bounded, bounded, checked, estimated, model-based, verified, verified-program-evidence, verified-program-receipt, indeterminate, and refused. Formal-bounded is limited to checker-admitted fragments; program evidence leaves construct-totality, source, and runtime residuals open. Requires Apple Silicon macOS and Python >=3.10 at /opt/homebrew/bin/python3 (install with brew install python).", + "displayName": "JACKAL + THOTH", + "shortDescription": "Evidence-aware CAS, linked STEM views, and THOTH measurement", + "longDescription": "Expose one unified 58-tool JACKAL surface through Codex: the sealed 41-tool v1.7.3 release runtime, seven identity-pinned THOTH measurement/provenance tools, three identity-pinned CAS, graph, and nonlinear-certificate tools, and seven identity-pinned STEM workflows for matrices, regression, probability, hypothesis testing, supplied sensor batches, aerospace models, and a professional linked evidence workspace. THOTH is a named JACKAL subsystem, never a separate server or arithmetic authority. Every reported numeric field in the additive workflows delegates to an admitted JACKAL lane; wrapper orchestration and UI rendering add no mathematical assurance. The adapter copies each parsed sealed-runtime result object into structuredContent unchanged. For additive results it deep-copies the result and removes only the identity-validated _mcp_content transport envelope before returning structuredContent; its only transport-local refusal is status=refused reason=plugin-busy. Graph coordinates are exact rational JACKAL results, sampled values and pixels remain estimated visualization, and refused samples break the curve. The fixed HELLGATE lane replays an independent exact-rational certificate as bounded, never formal-bounded. Result vocabulary: ok, exact, exact-given, structural-exact, formal-bounded, bounded, checked, estimated, model-based, verified, verified-program-evidence, verified-program-receipt, indeterminate, refused. Field statuses, assumptions, identities, route traces, refusal reasons, consequence ceilings, and non-claims remain first-class. Requires a host-admitted pinned runtime and Python >=3.10 at a fixed launcher-approved path.", "developerName": "Anubis Quantum Cipher", "category": "Productivity", "capabilities": [ "Interactive" ], "websiteURL": "https://github.com/AnubisQuantumCipher/jackal", + "brandColor": "#D51F2D", + "screenshots": [ + "./assets/jackal-thoth-hellgate-graph.png", + "./assets/jackal-linked-evidence-workspace.png" + ], "defaultPrompt": [ - "Classify and verify this numerical claim with JACKAL.", - "Find the strongest supported bound and refuse any silent downgrade.", - "Verify this receipt or claim bundle against my pinned expectations.", - "Verify this Anubis Safe program-evidence package without executing its artifact." + "Use JACKAL + THOTH to solve and classify this calculation.", + "Graph this expression and separate visualization from evidence.", + "Open a linked symbolic, numeric, graph, table, and evidence workspace for this expression.", + "Solve this matrix or regression workflow while preserving exact fields and model assumptions.", + "Analyze this supplied sensor batch or aerospace model without upgrading its provenance or physical assumptions.", + "Replay the HELLGATE nonlinear ground-state certificate without promoting its assurance." ] } } diff --git a/plugins/jackel/.mcp.json b/plugins/jackel/.mcp.json index d4cd6f1..06a0065 100644 --- a/plugins/jackel/.mcp.json +++ b/plugins/jackel/.mcp.json @@ -1,9 +1,9 @@ { "mcpServers": { "jackel": { - "command": "/bin/zsh", + "command": "/bin/sh", "args": [ - "./scripts/launch_mcp.zsh" + "./scripts/launch_mcp.sh" ], "cwd": ".", "env_vars": [ diff --git a/plugins/jackel/PLUGIN_IDENTITY.sha256 b/plugins/jackel/PLUGIN_IDENTITY.sha256 index 6d32dc3..9fd1b91 100644 --- a/plugins/jackel/PLUGIN_IDENTITY.sha256 +++ b/plugins/jackel/PLUGIN_IDENTITY.sha256 @@ -1,8 +1,18 @@ -d2f6af8658d414659d61447b565e685dbe221ec285c30839c40a895c10d0eb6f .codex-plugin/plugin.json -23d3c34f6714de5e1ff42f5221e575f5a97d293dbd60cb9613b8734db8bad1de .mcp.json -013f2a0393efc01d724d28ddb22fb5c324378e0c2333de45b8565e45124ea1ea README.md -d307e3534e3451e59f9955ef2aac459bfb8c2d72239aa2325641d0487064f5f5 mcp/server.py -4a5d54ab2300d8e3c2ec15fbea56bcc157ab084bf45233367d4864cb7316cc75 scripts/launch_mcp.zsh -618cd70b0b9005759ad8392e0c455c527e6d2b84e81d0caac66d6e5c088cf552 scripts/provision_runtime.py +fc79138568b4a9769716dab96b8f956867cd863327099600f2009a6119edcb42 .codex-plugin/plugin.json +a165e6b00292a3768ad134562539dabf7874cf23bf9ac8f80e893f7970a63cdc .mcp.json +813a7ebfa60d16422b1067dd926294730b3579916e99f82ebf43e1f9216da18f README.md +4179c34e62ff530c5d188a6143b740313a833f898774e269cb669a1f33cd0319 assets/jackal-linked-evidence-workspace.png +6bb4691420e3ae36398520ff9143e49073fe82bbcea3270f6766ccd0b109010a assets/jackal-thoth-hellgate-graph.png +c1d87448a8e87bc16911a228e62ae8d02527a2f9238dd38ef1fe2fd7a27b4e21 mcp/advanced.py +edfac51432ce8b5e0db09246b4b90e3dd7427d11c7f1f20cbcab9b28cfc8b27e mcp/certificates/README.md +e41ef05cb7ea6aae121a8a60330f52faf58e5827ee39adea3ef298ec4a873a88 mcp/certificates/hellgate_v1.json.zlib +5ce6683fcb259fc74047dbeb174d5e9fe633a2a6d57d121ad65bbf2c35da2b50 mcp/hellgate_verify.py +25a1abf4591cea5cc197d28cf8ef7e05191da31d63b60dc2439d1d945b27474f mcp/measurement.py +1fcc4c9793c6f36da5700674d07b5eeb202dd99209c419171f34174cd4d7f001 mcp/server.py +36f54651b7ddd4fe7ae6c05485766bc298ea9be5803600f0430e2d4d83c71041 mcp/stem.py +49bbd046759b045add1caf1f19300431fe7d697b59bfd93b57daf1d7edb709ff runtime_manifest_baseline_v173.json +e4fb2996fc2a905a5101974ad6119e6c29fbc10cc8ca2f0223e18f89686dcdff scripts/launch_mcp.sh +878a9d94788a6c132fad151bbddebecb41c08615fe310278e586871fe8f9c44c scripts/launch_mcp.zsh +bba7f8c80c4ba50a4affa41d891bdf4a987f7aa633be6f67af70a2113a4a31a2 scripts/provision_runtime.py 60f6a068c5f6bd81a2ab80e8d9d873864aefdf7f6ae7c2db37fe6572b41f898f scripts/verify_plugin.py -fd164f05d29eff0711d0b52c440792a029304f68d1f06160a96b7bc3374f1411 skills/jackel/SKILL.md +f718883ef08b7dfb02659bbb3c48a7ca0ffdd57342d3fa5b8848ffe2323219b2 skills/jackel/SKILL.md diff --git a/plugins/jackel/README.md b/plugins/jackel/README.md index b93a8b1..38aaaf5 100644 --- a/plugins/jackel/README.md +++ b/plugins/jackel/README.md @@ -1,39 +1,72 @@ -# JACKAL for Codex (legacy package ID `jackel`) +# JACKAL + THOTH for Codex (legacy package ID `jackel`) -This repo-local Codex plugin exposes the JACKAL v1.7.3 release runtime on Apple -Silicon macOS. Its current source contract is the ordered 41-tool inventory in +This repo-local Codex plugin exposes the JACKAL v1.7.3 release runtime. The +provisioner recognizes Darwin/arm64, Linux/aarch64, and Linux/x86_64. macOS-arm64 +has a published asset; Linux/aarch64 has a host-specific pinned local build +installed with `--tarball`; Linux/x86_64 remains unpinned and refuses rather +than borrowing another host's bytes. Its sealed runtime contract is the ordered 41-tool inventory in `release/capability_inventory_v1.json`; the package receipt and downloaded -release asset must bind the same exact bytes. +release asset must bind the same exact bytes. The identity-pinned adapter adds +seven THOTH measurement/provenance tools and three CAS/graph/certificate +tools plus seven additive STEM workflow tools, producing one unified 58-tool +JACKAL MCP surface. THOTH is a named +JACKAL subsystem; there is no second entity, process, or measurement server. The MCP server is a transport adapter. It loads schemas from the admitted -runtime and copies each parsed runtime result into `structuredContent`. The -adapter's only local result is `status=refused reason=plugin-busy`; other -statuses and fields come from the runtime. +runtime and copies each parsed runtime result into `structuredContent`. For the +seven THOTH tools, it serially delegates every arithmetic step to that same +runtime and records the calls in `delegated_to`. `jackal_cas` is a closed +evidence-preserving router over the existing runtime lanes. `jackal_graph` +renders PNG output from exact rational delegated x coordinates and +`status=estimated` delegated y values; its pixels are never evidence and a +refused sample breaks the curve. `jackal_hellgate_ground_state` exposes a fixed +identity-pinned certificate only after an independent exact-rational checker +accepts it at startup as `status=bounded`, never `formal-bounded`. It also +returns subject-scoped trial diagnostics and a stated strong-convexity transfer +for only the true ground-state quartic norm and energy functional; trial moments +are never relabelled as ground-state moments. The adapter +also adds exact-field matrices, model-aware regression/probability/hypothesis +workflows, supplied-provenance sensor batches, assumption-carrying aerospace +models, and a professional linked HTML evidence workspace. Every reported +numeric field delegates to an admitted JACKAL lane. Python validates structure, +selects closed workflows, and renders views; it does not become a substitute +arithmetic authority. The adapter can also emit +`status=refused reason=plugin-busy`. + +The plugin-card screenshot is generated through the installed `jackal_graph` +tool from the HELLGATE potential. It is intentionally a visual demonstration, +not a certified statement about roots, extrema, or topology. The linked +workspace screenshot is generated by a fixed browser acceptance fixture; it is +a UI demonstration, not evidence for the plotted function or delegated status. ## Install and provision Add the JACKAL repository as a local marketplace, install `jackel@anubis-quantum-cipher`, and provision the pinned release runtime. The -default command downloads the fixed release asset; pass `--tarball` with an -absolute path for an offline installation: +default command downloads the fixed release asset when that host has a +published URL; pass `--tarball` with an absolute path for an offline or +host-local pinned installation: ```bash codex plugin marketplace add /absolute/path/to/jackal codex plugin add jackel@anubis-quantum-cipher cd /absolute/path/to/the/installed/jackel/plugin -/bin/zsh scripts/launch_mcp.zsh provision +/bin/sh scripts/launch_mcp.sh provision # Offline alternative: -# /bin/zsh scripts/launch_mcp.zsh provision --tarball \ +# /bin/sh scripts/launch_mcp.sh provision --tarball \ # /absolute/path/to/jackal-v1.7.3-macos-arm64.tar.gz -/bin/zsh scripts/launch_mcp.zsh provision --check +/bin/sh scripts/launch_mcp.sh provision --check codex mcp list --json ``` -Require exactly 41 unique JACKAL tool names and an MCP working directory bound -to the installed plugin copy. Python 3.10 or newer at -`/opt/homebrew/bin/python3` is the supported prerequisite; the launcher also -accepts the two other fixed absolute candidates only when they pass its full -capability probe. It never searches caller `PATH`. +Require exactly 58 unique JACKAL tool names: 41 sealed-runtime tools, seven +identity-pinned THOTH tools, three identity-pinned advanced tools, and seven +identity-pinned STEM workflow tools. The MCP +working directory must be bound to the installed plugin copy. Python 3.10 or newer at +`/opt/homebrew/bin/python3` (macOS) or `/usr/bin/python3` (Linux) is the +supported prerequisite; the launcher accepts any of its three fixed absolute +candidates that pass the full capability probe, which includes the host's +atomic no-replace rename symbol. It never searches caller `PATH`. ## Routing @@ -43,6 +76,47 @@ capability probe. It never searches caller `PATH`. request and identity expectations. - Use direct typed tools for one exact, checked, estimated, bounded, formal-bounded, structural, or decision operation. +- Use `jackal_cas` as one front door for sealed exact, symbolic, numerical, + bounded, and formal lanes. Its nested delegated result remains authoritative; + the router adds no assurance and performs no downgrade. +- Use `jackal_graph` for a visual PNG. Treat every rendered segment as + visualization only and use a separate exact or bounded lane for any claim + about roots, extrema, continuity, poles, or behavior between samples. +- Use `jackal_hellgate_ground_state` only for `problem_id=hellgate-v1`. Its + exact-rational nonlinear Barta enclosure is `bounded` and carries explicit + theorem/interpreter assumptions. Trial quartic norm, moments, kinetic energy, + energy functional, identity residual, and virial residual are explicitly + scoped to the certificate trial `phi`. A separate strong-convexity transfer + encloses only the ground-state quartic norm and energy functional. It does not + enclose the ground-state polynomial moments, sensitivity derivative, + tunneling split, or Bogoliubov spectrum. +- Use `jackal_matrix` for exact-rational addition, multiplication, transpose, + determinant, RREF, inverse, and linear solve. Each numeric cell delegates to + `jackal_exact`; the identity-pinned row-operation orchestration is tested but + is not Lean-proved or `formal-bounded`. +- Use `jackal_regression`, `jackal_probability`, and `jackal_hypothesis` for + declared statistical models. Preserve top-level `model-based`, every model + assumption, each exact or estimated field status, and the advisory ceiling + on hypothesis results. Exact fitting never validates a model or sampling + design. +- Use `jackal_sensor` for caller-supplied batches and declared linear + calibration. It never claims to have read hardware. Preserve + `input_provenance=supplied`, calibration metadata, and the distinction + between exact-given descriptive fields and the nested formal-bounded + arithmetic enclosure. +- Use `jackal_aerospace` for the admitted circular-orbit, vis-viva, + rocket-equation, Hohmann-transfer, and plane-change models. Preserve their + physical assumptions and advisory ceiling. Formal-bounded scalar arithmetic + does not certify a mission or inherit the published finite-burn certificate. +- Use `jackal_linked_workspace` for synchronized symbolic, numeric, graph, + table, inspector, sensor-dock, and evidence-route views. Its self-contained + HTML is digest-bound and available through MCP resources. The UI, Web Serial + display, sampled curve, and pixels add no assurance. +- Use `jackal_convert`, `jackal_rate_apply`, `jackal_percent`, + `jackal_date_delta`, `jackal_stat`, `jackal_compare`, and `jackal_scan` for + THOTH measurement/provenance work. Preserve `exact-given` as exact arithmetic + conditional on the explicitly carried, unverified datum; `jackal_scan` is + lexical and verifies no numeral. - Use `jackal_anubis_verify_program` for caller-selected Safe source/evidence bytes, `jackal_anubis_verify_program_receipt` for receipt recomputation, and `jackal_anubis_check_program` only with the caller-pinned approved compiler. @@ -63,11 +137,25 @@ establish runtime behavior. From the JACKAL repository root: ```bash -/opt/homebrew/bin/python3 -B plugins/jackel/scripts/verify_plugin.py -/opt/homebrew/bin/python3 -B tools/capability_drift_gate.py -/opt/homebrew/bin/python3 -B -m unittest discover -s tests/codex_plugin -v +python3 -B plugins/jackel/scripts/verify_plugin.py +python3 -B tools/capability_drift_gate.py +python3 -B -m unittest discover -s tests/codex_plugin -v ``` The wrapper identity manifest is tamper evidence bound to a separately trusted Git revision or plugin snapshot. SHA-256 alone is not author authentication or mathematical proof. + +The published v1.7.3 capability inventory remains immutable. The additive +wrapper is a separate development identity, not a repinning of that release. +`runtime_manifest_baseline_v173.json` retains the exact sealed Codex metadata +input, while `PLUGIN_IDENTITY.sha256` binds the expanded manifest, adapter, +THOTH modules, STEM workflows, assets, launchers, skill, and that baseline. +The capability drift gate permits only the declared presentation extension, +preserves foundation fields, and refuses baseline or inventory mutation. + +The repository also contains a separate SPARK fixed-scale interval envelope at +`proofs/spark/hellgate_interval`. GNATprove establishes its stated integer +contracts and absence of run-time errors. That proof is not integrated into the +cross-platform MCP start path and does not prove Barta, strong convexity, +Python refinement, compiler correctness, or mission qualification. diff --git a/plugins/jackel/assets/jackal-linked-evidence-workspace.png b/plugins/jackel/assets/jackal-linked-evidence-workspace.png new file mode 100644 index 0000000..a8915f5 Binary files /dev/null and b/plugins/jackel/assets/jackal-linked-evidence-workspace.png differ diff --git a/plugins/jackel/assets/jackal-thoth-hellgate-graph.png b/plugins/jackel/assets/jackal-thoth-hellgate-graph.png new file mode 100644 index 0000000..35d66d7 Binary files /dev/null and b/plugins/jackel/assets/jackal-thoth-hellgate-graph.png differ diff --git a/plugins/jackel/mcp/advanced.py b/plugins/jackel/mcp/advanced.py new file mode 100644 index 0000000..421a232 --- /dev/null +++ b/plugins/jackel/mcp/advanced.py @@ -0,0 +1,858 @@ +#!/usr/bin/env python3 -B +"""Identity-pinned advanced CAS and graph tools on JACKAL's single MCP surface. + +This module is not a second server and is not an arithmetic authority. CAS +requests delegate to the sealed runtime. Graph coordinates and function +values are recorded from delegated JACKAL calls; rasterization is explicitly a +visualization, never evidence. The fixed HELLGATE result is installed only +after the independent exact-rational checker accepts its pinned certificate. +""" + +from __future__ import annotations + +import base64 +import binascii +import copy +import decimal +import math +import re +import struct +import zlib +from fractions import Fraction +from typing import Callable + + +ADVANCED_TOOL_NAMES = frozenset( + {"jackal_cas", "jackal_graph", "jackal_hellgate_ground_state"} +) +CONSEQUENCE_CEILING = "informational" +MAX_EXPRESSION_BYTES = 2048 +MIN_GRAPH_SAMPLES = 17 +MAX_GRAPH_SAMPLES = 257 +GRAPH_WIDTH = 1200 +GRAPH_HEIGHT = 720 +GRAPH_SUPERSAMPLE = 2 +RENDER_WIDTH = GRAPH_WIDTH * GRAPH_SUPERSAMPLE +RENDER_HEIGHT = GRAPH_HEIGHT * GRAPH_SUPERSAMPLE +X_TOKEN = re.compile(r"(? None: + global _HELLGATE_RESULT, _HELLGATE_IDENTITIES + fields = result.get("fields") if isinstance(result, dict) else None + trial = fields.get("trial_diagnostics") if isinstance(fields, dict) else None + ground = fields.get("ground_state_transfer") if isinstance(fields, dict) else None + if ( + not isinstance(result, dict) + or result.get("status") != "bounded" + or result.get("checker_verdict") != "ACCEPT" + or result.get("formal") is not False + or not isinstance(trial, dict) + or trial.get("schema") != "jackal-hellgate-trial-diagnostics-v1" + or trial.get("subject") != "normalized-certificate-trial-phi" + or not isinstance(ground, dict) + or ground.get("schema") != "jackal-hellgate-ground-transfer-v1" + or ground.get("subject") != "positive-normalized-ground-state-u0" + or not all( + isinstance(value, str) and re.fullmatch(r"[0-9a-f]{64}", value) + for value in (advanced_sha256, checker_sha256, certificate_sha256) + ) + ): + raise RuntimeError("HELLGATE certificate did not pass the startup gate") + _HELLGATE_RESULT = copy.deepcopy(result) + _HELLGATE_IDENTITIES = { + "jackal_advanced_sha256": advanced_sha256, + "hellgate_checker_sha256": checker_sha256, + "hellgate_certificate_file_sha256": certificate_sha256, + } + + +def _identity() -> str: + if _IDENTITY is None: + raise RuntimeError("advanced identity is unavailable outside integrated dispatch") + return _IDENTITY + + +def _refusal(reason: str, detail: str) -> dict: + return { + "status": "refused", + "reason": reason, + "detail": detail, + "consequence_ceiling": CONSEQUENCE_CEILING, + "identities": {"jackal_advanced_sha256": _identity()}, + "non_claims": [ + "A refusal is an answer; no weaker lane was substituted", + "No visual or numerical result was established", + ], + } + + +def _kernel_call(tool: str, arguments: dict) -> dict: + if _KERNEL is None: + raise Refusal("kernel-unavailable", "advanced module is not attached to JACKAL") + result = _KERNEL.call(tool, arguments) + if not isinstance(result, dict): + raise Refusal("kernel-error", "JACKAL returned a non-object") + trace = { + "tool": tool, + "arguments": copy.deepcopy(arguments), + "status": result.get("status", "unknown"), + } + fields = result.get("fields") + if isinstance(fields, dict) and isinstance(fields.get("parsed"), str): + trace["parsed"] = fields["parsed"] + if isinstance(result.get("engine_output"), str): + trace["engine_output"] = result["engine_output"] + _TRACE.append(trace) + if result.get("status") == "refused": + raise Refusal( + f"kernel-refused:{result.get('reason', 'unknown')}", + str(result.get("detail", "the delegated JACKAL lane refused")), + ) + return result + + +def _fraction(text: object, subject: str) -> Fraction: + if not isinstance(text, str) or not text or len(text) > 256: + raise Refusal("args", f"{subject} must be a bounded rational string") + try: + value = Fraction(text) + except (ValueError, ZeroDivisionError) as error: + raise Refusal("args", f"{subject} is not an integer, decimal, or rational") from error + return value + + +def _cas(arguments: dict) -> dict: + operation = arguments.get("operation") + delegated_arguments = arguments.get("arguments") + if not isinstance(operation, str) or operation not in CAS_ROUTES: + raise Refusal("operation-unknown", "operation is not in the closed CAS route table") + if not isinstance(delegated_arguments, dict): + raise Refusal("args", "arguments must be an object for the selected JACKAL tool") + tool = CAS_ROUTES[operation] + result = _kernel_call(tool, delegated_arguments) + return { + "status": result.get("status", "indeterminate"), + "lane": "cas-route", + "formal": bool(result.get("formal", False)), + "parsed": {"operation": operation, "delegated_tool": tool}, + "result": result, + "delegated_to": list(_TRACE), + "identities": {"jackal_advanced_sha256": _identity()}, + "non_claims": [ + "The CAS router adds no assurance to the delegated JACKAL result", + "Preserve the delegated status, assumptions, identities, and non-claims unchanged", + ], + } + + +_FONT = { + " ": "00000/00000/00000/00000/00000/00000/00000", + "A": "01110/10001/10001/11111/10001/10001/10001", + "B": "11110/10001/10001/11110/10001/10001/11110", + "C": "01111/10000/10000/10000/10000/10000/01111", + "D": "11110/10001/10001/10001/10001/10001/11110", + "E": "11111/10000/10000/11110/10000/10000/11111", + "F": "11111/10000/10000/11110/10000/10000/10000", + "G": "01111/10000/10000/10111/10001/10001/01111", + "H": "10001/10001/10001/11111/10001/10001/10001", + "I": "11111/00100/00100/00100/00100/00100/11111", + "J": "00111/00010/00010/00010/10010/10010/01100", + "K": "10001/10010/10100/11000/10100/10010/10001", + "L": "10000/10000/10000/10000/10000/10000/11111", + "M": "10001/11011/10101/10101/10001/10001/10001", + "N": "10001/11001/10101/10011/10001/10001/10001", + "O": "01110/10001/10001/10001/10001/10001/01110", + "P": "11110/10001/10001/11110/10000/10000/10000", + "Q": "01110/10001/10001/10001/10101/10010/01101", + "R": "11110/10001/10001/11110/10100/10010/10001", + "S": "01111/10000/10000/01110/00001/00001/11110", + "T": "11111/00100/00100/00100/00100/00100/00100", + "U": "10001/10001/10001/10001/10001/10001/01110", + "V": "10001/10001/10001/10001/10001/01010/00100", + "W": "10001/10001/10001/10101/10101/10101/01010", + "X": "10001/10001/01010/00100/01010/10001/10001", + "Y": "10001/10001/01010/00100/00100/00100/00100", + "Z": "11111/00001/00010/00100/01000/10000/11111", + "0": "01110/10001/10011/10101/11001/10001/01110", + "1": "00100/01100/00100/00100/00100/00100/01110", + "2": "01110/10001/00001/00010/00100/01000/11111", + "3": "11110/00001/00001/01110/00001/00001/11110", + "4": "00010/00110/01010/10010/11111/00010/00010", + "5": "11111/10000/10000/11110/00001/00001/11110", + "6": "01110/10000/10000/11110/10001/10001/01110", + "7": "11111/00001/00010/00100/01000/01000/01000", + "8": "01110/10001/10001/01110/10001/10001/01110", + "9": "01110/10001/10001/01111/00001/00001/01110", + "+": "00000/00100/00100/11111/00100/00100/00000", + "-": "00000/00000/00000/11111/00000/00000/00000", + "*": "00000/10101/01110/11111/01110/10101/00000", + "/": "00001/00010/00010/00100/01000/01000/10000", + "^": "00100/01010/10001/00000/00000/00000/00000", + "(": "00010/00100/01000/01000/01000/00100/00010", + ")": "01000/00100/00010/00010/00010/00100/01000", + "[": "01110/01000/01000/01000/01000/01000/01110", + "]": "01110/00010/00010/00010/00010/00010/01110", + ".": "00000/00000/00000/00000/00000/00110/00110", + ",": "00000/00000/00000/00000/00110/00110/00100", + ":": "00000/00110/00110/00000/00110/00110/00000", + "=": "00000/00000/11111/00000/11111/00000/00000", + "|": "00100/00100/00100/00100/00100/00100/00100", + "_": "00000/00000/00000/00000/00000/00000/11111", + "?": "01110/10001/00001/00010/00100/00000/00100", +} + + +def _set_pixel(buffer: bytearray, x: int, y: int, color: tuple[int, int, int]) -> None: + if 0 <= x < RENDER_WIDTH and 0 <= y < RENDER_HEIGHT: + offset = (y * RENDER_WIDTH + x) * 3 + buffer[offset : offset + 3] = bytes(color) + + +def _fill_rect( + buffer: bytearray, + left: int, + top: int, + right: int, + bottom: int, + color: tuple[int, int, int], +) -> None: + clipped_left = max(0, left) + clipped_top = max(0, top) + clipped_right = min(RENDER_WIDTH, right) + clipped_bottom = min(RENDER_HEIGHT, bottom) + if clipped_left >= clipped_right or clipped_top >= clipped_bottom: + return + row = bytes(color) * (clipped_right - clipped_left) + for y in range(clipped_top, clipped_bottom): + start = (y * RENDER_WIDTH + clipped_left) * 3 + buffer[start : start + len(row)] = row + + +def _line( + buffer: bytearray, + x0: int, + y0: int, + x1: int, + y1: int, + color: tuple[int, int, int], + thickness: int = 1, +) -> None: + dx = abs(x1 - x0) + sx = 1 if x0 < x1 else -1 + dy = -abs(y1 - y0) + sy = 1 if y0 < y1 else -1 + error = dx + dy + while True: + for ox in range(-(thickness // 2), thickness // 2 + 1): + for oy in range(-(thickness // 2), thickness // 2 + 1): + _set_pixel(buffer, x0 + ox, y0 + oy, color) + if x0 == x1 and y0 == y1: + break + doubled = 2 * error + if doubled >= dy: + error += dy + x0 += sx + if doubled <= dx: + error += dx + y0 += sy + + +def _draw_text( + buffer: bytearray, + x: int, + y: int, + value: str, + color: tuple[int, int, int], + scale: int, + align: str = "left", +) -> None: + rendered = str(value).upper() + width = max(0, (len(rendered) * 6 - 1) * scale) + if align == "center": + x -= width // 2 + elif align == "right": + x -= width + for character in rendered: + rows = _FONT.get(character, _FONT["?"]).split("/") + for row_index, row in enumerate(rows): + for column_index, bit in enumerate(row): + if bit == "1": + _fill_rect( + buffer, + x + column_index * scale, + y + row_index * scale, + x + (column_index + 1) * scale, + y + (row_index + 1) * scale, + color, + ) + x += 6 * scale + + +def _fit_text(value: str, max_width: int, scale: int) -> str: + capacity = max(1, (max_width // scale + 1) // 6) + rendered = str(value).upper() + if len(rendered) <= capacity: + return rendered + if capacity <= 3: + return rendered[:capacity] + return rendered[: capacity - 3] + "..." + + +def _format_tick(value: float) -> str: + if abs(value) < 1e-12: + value = 0.0 + return f"{value:.5g}".upper() + + +def _downsample(rows: bytearray) -> bytearray: + output = bytearray(GRAPH_WIDTH * GRAPH_HEIGHT * 3) + area = GRAPH_SUPERSAMPLE * GRAPH_SUPERSAMPLE + for y in range(GRAPH_HEIGHT): + for x in range(GRAPH_WIDTH): + red = green = blue = 0 + for offset_y in range(GRAPH_SUPERSAMPLE): + source_y = y * GRAPH_SUPERSAMPLE + offset_y + for offset_x in range(GRAPH_SUPERSAMPLE): + source_x = x * GRAPH_SUPERSAMPLE + offset_x + source = (source_y * RENDER_WIDTH + source_x) * 3 + red += rows[source] + green += rows[source + 1] + blue += rows[source + 2] + target = (y * GRAPH_WIDTH + x) * 3 + output[target] = red // area + output[target + 1] = green // area + output[target + 2] = blue // area + return output + + +def _png(rows: bytearray) -> bytes: + scanlines = bytearray() + stride = GRAPH_WIDTH * 3 + for row in range(GRAPH_HEIGHT): + scanlines.append(0) + start = row * stride + scanlines.extend(rows[start : start + stride]) + + def chunk(kind: bytes, payload: bytes) -> bytes: + return ( + struct.pack(">I", len(payload)) + + kind + + payload + + struct.pack(">I", binascii.crc32(kind + payload) & 0xFFFFFFFF) + ) + + header = struct.pack(">IIBBBBB", GRAPH_WIDTH, GRAPH_HEIGHT, 8, 2, 0, 0, 0) + return b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", header) + chunk( + b"IDAT", zlib.compress(bytes(scanlines), 9) + ) + chunk(b"IEND", b"") + + +def _render_graph( + expression: str, + samples: int, + numeric: list[tuple[int, float, float]], + x_low: float, + x_high: float, + y_low: float, + y_high: float, +) -> bytes: + scale = GRAPH_SUPERSAMPLE + background_top = (7, 12, 19) + background_bottom = (11, 19, 29) + pixels = bytearray() + for y in range(RENDER_HEIGHT): + fraction = y / max(1, RENDER_HEIGHT - 1) + color = tuple( + round(background_top[channel] * (1 - fraction) + + background_bottom[channel] * fraction) + for channel in range(3) + ) + pixels.extend(bytes(color) * RENDER_WIDTH) + + foreground = (225, 236, 241) + secondary = (135, 158, 171) + muted = (92, 117, 132) + grid = (30, 48, 61) + border = (53, 75, 89) + plot_background = (10, 18, 27) + axis = (109, 139, 156) + teal = (0, 224, 184) + teal_highlight = (123, 255, 226) + teal_shadow = (0, 74, 72) + badge_background = (10, 51, 51) + + plot_left = 112 * scale + plot_right = 1144 * scale + plot_top = 150 * scale + plot_bottom = 592 * scale + _fill_rect(pixels, plot_left, plot_top, plot_right + 1, plot_bottom + 1, plot_background) + + title_scale = 6 + text_scale = 4 + small_scale = 3 + _draw_text(pixels, 64 * scale, 27 * scale, "JACKAL + THOTH", teal_highlight, title_scale) + _draw_text( + pixels, + 64 * scale, + 66 * scale, + "EVIDENCE-AWARE FUNCTION GRAPH", + secondary, + text_scale, + ) + badge_text = "ESTIMATED VISUALIZATION" + badge_left = 874 * scale + badge_top = 29 * scale + badge_right = 1144 * scale + badge_bottom = 64 * scale + _fill_rect( + pixels, + badge_left, + badge_top, + badge_right, + badge_bottom, + badge_background, + ) + _line(pixels, badge_left, badge_top, badge_right, badge_top, teal, 2) + _line(pixels, badge_left, badge_bottom, badge_right, badge_bottom, teal, 2) + _draw_text( + pixels, + (badge_left + badge_right) // 2, + 41 * scale, + badge_text, + teal_highlight, + small_scale, + "center", + ) + + expression_text = _fit_text( + "F(X) = " + expression, + plot_right - plot_left, + text_scale, + ) + _draw_text( + pixels, + plot_left, + 116 * scale, + expression_text, + foreground, + text_scale, + ) + _draw_text( + pixels, + plot_right, + 118 * scale, + f"{samples} DELEGATED SAMPLES", + muted, + small_scale, + "right", + ) + + x_divisions = 8 + y_divisions = 6 + for division in range(x_divisions + 1): + gx = plot_left + (plot_right - plot_left) * division // x_divisions + _line(pixels, gx, plot_top, gx, plot_bottom, grid, 2) + tick = x_low + (x_high - x_low) * division / x_divisions + _draw_text( + pixels, + gx, + 609 * scale, + _format_tick(tick), + secondary, + small_scale, + "center", + ) + for division in range(y_divisions + 1): + gy = plot_top + (plot_bottom - plot_top) * division // y_divisions + _line(pixels, plot_left, gy, plot_right, gy, grid, 2) + tick = y_high - (y_high - y_low) * division / y_divisions + _draw_text( + pixels, + 98 * scale, + gy - 3 * scale, + _format_tick(tick), + secondary, + small_scale, + "right", + ) + + _line(pixels, plot_left, plot_top, plot_right, plot_top, border, 2) + _line(pixels, plot_right, plot_top, plot_right, plot_bottom, border, 2) + _line(pixels, plot_right, plot_bottom, plot_left, plot_bottom, border, 2) + _line(pixels, plot_left, plot_bottom, plot_left, plot_top, border, 2) + + def map_x(value: float) -> int: + return round( + plot_left + + (value - x_low) * (plot_right - plot_left) / (x_high - x_low) + ) + + def map_y(value: float) -> int: + return round( + plot_bottom + - (value - y_low) * (plot_bottom - plot_top) / (y_high - y_low) + ) + + if x_low <= 0 <= x_high: + zero_x = map_x(0.0) + _line(pixels, zero_x, plot_top, zero_x, plot_bottom, axis, 4) + if y_low <= 0 <= y_high: + zero_y = map_y(0.0) + _line(pixels, plot_left, zero_y, plot_right, zero_y, axis, 4) + + previous: tuple[int, int, int] | None = None + segments: list[tuple[int, int, int, int]] = [] + for sample_index, x_value, y_value in numeric: + current = (map_x(x_value), map_y(y_value)) + if previous is not None and sample_index == previous[0] + 1: + segments.append((previous[1], previous[2], current[0], current[1])) + previous = (sample_index, current[0], current[1]) + for x0, y0, x1, y1 in segments: + _line(pixels, x0, y0, x1, y1, teal_shadow, 12) + for x0, y0, x1, y1 in segments: + _line(pixels, x0, y0, x1, y1, teal, 6) + for x0, y0, x1, y1 in segments: + _line(pixels, x0, y0, x1, y1, teal_highlight, 2) + + footer = "EXACT RATIONAL X COORDINATES | ESTIMATED F64 Y SAMPLES | PIXELS ARE NOT PROOF" + _draw_text( + pixels, + 64 * scale, + 680 * scale, + _fit_text(footer, 1080 * scale, small_scale), + muted, + small_scale, + ) + return _png(_downsample(pixels)) + + +def _graph(arguments: dict) -> dict: + expression = arguments.get("expression") + if ( + not isinstance(expression, str) + or not expression + or len(expression.encode("utf-8")) > MAX_EXPRESSION_BYTES + or any(ord(character) < 32 for character in expression) + ): + raise Refusal("args", "expression must be nonempty bounded printable text") + lower = _fraction(arguments.get("x_min"), "x_min") + upper = _fraction(arguments.get("x_max"), "x_max") + if lower >= upper: + raise Refusal("interval-order", "x_min must be strictly below x_max") + samples_text = arguments.get("samples") + if not isinstance(samples_text, str) or CANONICAL_INTEGER.fullmatch(samples_text) is None: + raise Refusal("args", "samples must be a canonical positive integer string") + samples = int(samples_text) + if not MIN_GRAPH_SAMPLES <= samples <= MAX_GRAPH_SAMPLES: + raise Refusal( + "sample-budget", + f"samples must be between {MIN_GRAPH_SAMPLES} and {MAX_GRAPH_SAMPLES}", + ) + + points: list[dict[str, str]] = [] + numeric: list[tuple[int, float, float]] = [] + for index in range(samples): + coordinate_result = _kernel_call( + "jackal_exact", + { + "expression": ( + f"({lower}) + ({index})*(({upper})-({lower}))/({samples - 1})" + ) + }, + ) + fields = coordinate_result.get("fields") + coordinate = fields.get("exact") if isinstance(fields, dict) else None + if not isinstance(coordinate, str): + raise Refusal("kernel-error", "jackal_exact returned no graph coordinate") + substituted = X_TOKEN.sub(f"({coordinate})", expression) + try: + evaluated = _kernel_call("jackal_evaluate", {"expression": substituted}) + except Refusal as error: + if error.reason.startswith("kernel-refused:"): + points.append( + {"x": coordinate, "status": "refused", "reason": error.reason} + ) + continue + raise + rendered = evaluated.get("engine_output") + if not isinstance(rendered, str): + points.append({"x": coordinate, "status": "indeterminate"}) + continue + try: + y_decimal = decimal.Decimal(rendered) + except decimal.InvalidOperation: + points.append({"x": coordinate, "status": "indeterminate"}) + continue + if not y_decimal.is_finite(): + points.append({"x": coordinate, "status": "indeterminate"}) + continue + try: + x_float = float(Fraction(coordinate)) + y_float = float(y_decimal) + except (OverflowError, ValueError): + points.append({"x": coordinate, "status": "indeterminate"}) + continue + if not math.isfinite(x_float) or not math.isfinite(y_float): + points.append({"x": coordinate, "status": "indeterminate"}) + continue + numeric.append((index, x_float, y_float)) + points.append({"x": coordinate, "y": rendered, "status": "estimated"}) + + if len(numeric) < 2: + raise Refusal("graph-empty", "fewer than two finite delegated samples were available") + y_values = [point[2] for point in numeric] + y_low = min(y_values) + y_high = max(y_values) + if y_low == y_high: + padding = max(1.0, abs(y_low) * 0.05) + else: + padding = (y_high - y_low) * 0.05 + y_low -= padding + y_high += padding + x_low = float(lower) + x_high = float(upper) + image = _render_graph( + expression, + samples, + numeric, + x_low, + x_high, + y_low, + y_high, + ) + summary = ( + f"JACKAL graph: {expression} on [{lower}, {upper}] using {samples} delegated " + "f64 samples. The curve is estimated visualization, not a bound or proof." + ) + return { + "status": "estimated", + "lane": "graph-delegated-f64-v1", + "formal": False, + "consequence_ceiling": CONSEQUENCE_CEILING, + "parsed": { + "expression": expression, + "x_interval": [str(lower), str(upper)], + "samples": str(samples), + }, + "fields": { + "points": points, + "finite_sample_count": len(numeric), + "observed_y_min": str(min(y_values)), + "observed_y_max": str(max(y_values)), + "image_mime_type": "image/png", + }, + "delegated_to": list(_TRACE), + "identities": {"jackal_advanced_sha256": _identity()}, + "non_claims": [ + "Graph pixels and connecting line segments are visualization only", + "Every plotted y value is status=estimated IEEE f64, not bounded or formal-bounded", + "Refused or indeterminate samples break the rendered curve instead of being bridged", + "Sampling cannot prove continuity, roots, extrema, absence of poles, or behavior between samples", + "Use JACKAL's exact or bounded lanes separately for claims inferred from the graph", + ], + "_mcp_content": [ + {"type": "text", "text": summary}, + { + "type": "image", + "data": base64.b64encode(image).decode("ascii"), + "mimeType": "image/png", + }, + ], + } + + +def _hellgate(arguments: dict) -> dict: + if arguments.get("problem_id") != "hellgate-v1": + raise Refusal( + "unsupported-problem", + "this certificate lane admits only problem_id='hellgate-v1'", + ) + if _HELLGATE_RESULT is None or _HELLGATE_IDENTITIES is None: + raise Refusal("certificate-unavailable", "HELLGATE startup verification is unavailable") + result = copy.deepcopy(_HELLGATE_RESULT) + identities = result.setdefault("identities", {}) + if not isinstance(identities, dict): + raise Refusal("certificate-error", "verified result identities are malformed") + identities.update(_HELLGATE_IDENTITIES) + result["consequence_ceiling"] = "advisory" + return result + + +def dispatch_integrated( + name: str, + arguments: dict, + kernel_call: Callable[[str, dict], dict], + identity: str, +) -> dict: + global _KERNEL, _IDENTITY, _TRACE + if name not in ADVANCED_TOOL_NAMES or not isinstance(arguments, dict): + return { + "status": "refused", + "reason": "tool-unknown", + "detail": "advanced tool name or arguments are invalid", + } + + class Kernel: + evaluator_sha256: str | None = None + + @staticmethod + def call(tool: str, delegated_arguments: dict) -> dict: + return kernel_call(tool, delegated_arguments) + + _KERNEL = Kernel() + _IDENTITY = identity + _TRACE = [] + try: + if name == "jackal_cas": + return _cas(arguments) + if name == "jackal_graph": + return _graph(arguments) + return _hellgate(arguments) + except Refusal as error: + return _refusal(error.reason, error.detail) + except Exception: + return _refusal("advanced-error", "advanced orchestration failed closed") + finally: + _KERNEL = None + _IDENTITY = None + _TRACE = [] + + +def _schema(properties: dict, required: list[str]) -> dict: + return { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": properties, + "required": required, + "additionalProperties": False, + } + + +def _definition(name: str, title: str, description: str, schema: dict) -> dict: + return { + "name": name, + "title": title, + "description": description, + "inputSchema": schema, + "annotations": { + "readOnlyHint": True, + "destructiveHint": False, + "idempotentHint": True, + "openWorldHint": False, + }, + } + + +def tool_definitions() -> list[dict]: + return [ + _definition( + "jackal_cas", + "JACKAL unified CAS", + "One evidence-preserving front door for exact, symbolic, numerical, bounded, and formal JACKAL lanes. The router adds no assurance and never silently downgrades.", + _schema( + { + "operation": { + "type": "string", + "enum": sorted(CAS_ROUTES), + "description": "Closed CAS operation identifier.", + }, + "arguments": { + "type": "object", + "description": "Arguments for the selected underlying JACKAL tool.", + }, + }, + ["operation", "arguments"], + ), + ), + _definition( + "jackal_graph", + "JACKAL graph", + "Render a PNG curve from exact rational x coordinates and delegated status=estimated JACKAL evaluations. Pixels are explicitly not proof; use a bound lane for graph-derived claims.", + _schema( + { + "expression": { + "type": "string", + "description": "JACKAL expression in the single plotting variable x.", + }, + "x_min": {"type": "string", "description": "Rational lower x bound."}, + "x_max": {"type": "string", "description": "Rational upper x bound."}, + "samples": { + "type": "string", + "description": "Canonical integer sample count, 17..257.", + }, + }, + ["expression", "x_min", "x_max", "samples"], + ), + ), + _definition( + "jackal_hellgate_ground_state", + "JACKAL HELLGATE ground-state certificate", + "Replay the identity-pinned exact-rational nonlinear Barta certificate for the fixed HELLGATE positive even normalized ground-state eigenvalue, scoped trial diagnostics, and a strong-convexity transfer for the ground-state quartic norm and energy functional. Returns status=bounded, never formal-bounded; trial moments are not ground-state moments.", + _schema( + { + "problem_id": { + "type": "string", + "enum": ["hellgate-v1"], + "description": "Exact fixed problem identifier.", + } + }, + ["problem_id"], + ), + ), + ] + + +if __name__ == "__main__": + raise SystemExit( + "advanced.py is an identity-pinned JACKAL module, not a standalone service" + ) diff --git a/plugins/jackel/mcp/certificates/README.md b/plugins/jackel/mcp/certificates/README.md new file mode 100644 index 0000000..f5b05ff --- /dev/null +++ b/plugins/jackel/mcp/certificates/README.md @@ -0,0 +1,15 @@ +# Identity-pinned certificates + +`hellgate_v1.json.zlib` is a deterministic compressed JSON certificate produced by +`tools/hellgate_generate.py`. The plugin never trusts the producer: startup loads the +compressed bytes through the plugin identity inventory, decompresses them under a hard +size bound, and passes the raw JSON to the independent exact-rational checker in +`mcp/hellgate_verify.py`. + +Acceptance is `status=bounded`, not `formal-bounded`. In addition to the Barta +eigenvalue enclosure, the checker returns exact-rational bounded diagnostics for +the normalized certificate trial `phi`. Those moments and residuals are not +ground-state quantities. A separately labelled lambda-strong-convexity transfer +encloses only the true ground-state quartic norm and energy functional. The +checker states the comparison and transfer assumptions plus their residual +non-claims in the returned result. diff --git a/plugins/jackel/mcp/certificates/hellgate_v1.json.zlib b/plugins/jackel/mcp/certificates/hellgate_v1.json.zlib new file mode 100644 index 0000000..9c6071a Binary files /dev/null and b/plugins/jackel/mcp/certificates/hellgate_v1.json.zlib differ diff --git a/plugins/jackel/mcp/hellgate_verify.py b/plugins/jackel/mcp/hellgate_verify.py new file mode 100644 index 0000000..498597c --- /dev/null +++ b/plugins/jackel/mcp/hellgate_verify.py @@ -0,0 +1,1145 @@ +#!/usr/bin/env python3 -B +"""Exact-rational checker for the fixed HELLGATE nonlinear Barta certificate. + +The producer is deliberately not imported. This verifier uses only Python's +integer and ``Fraction`` arithmetic. Transcendentals are enclosed by a second +implementation based on rational Taylor bounds. Acceptance means the encoded +positive normalized trial function has a globally bounded nonlinear Rayleigh +quotient; the nonlinear Barta comparison theorem then encloses the unique +positive normalized ground-state eigenvalue. + +The same replay additionally encloses diagnostics of the normalized trial and, +under a stated strong-convexity theorem, transfers only the quartic norm and +energy functional to the ground state. Subject fields and non-claims prevent +the trial moments from being presented as ground-state quantities. + +This checker is not a Lean theorem and never returns ``formal-bounded``. +""" + +from __future__ import annotations + +import hashlib +import hmac +import json +import re +import sys +from fractions import Fraction +from math import comb, factorial, isqrt, lcm +from pathlib import Path +from typing import Any + + +SCHEMA = "jackal-hellgate-barta-certificate-v1" +RESULT_SCHEMA = "jackal-hellgate-barta-verification-v1" +MAX_CERTIFICATE_BYTES = 4 * 1024 * 1024 +MAX_JSON_DEPTH = 16 +MAX_JSON_NODES = 40000 +MAX_INTEGER_DIGITS = 384 +MAX_PIECES = 512 +MAX_POLYNOMIAL_DEGREE = 64 +MAX_TAIL_TERMS = 128 +MAX_EIGENVALUE_WIDTH = Fraction(1, 500000000000) +EXP_TAYLOR_DEGREE = 60 +EXP_DYADIC_BITS = 256 +SQRT_DYADIC_BITS = 192 +MAX_TRIAL_VIRIAL_ABS = Fraction(1, 100000000000) +MAX_TRIAL_IDENTITY_ABS = Fraction(1, 1000000000000) +MAX_DENSITY_L2_DISTANCE = Fraction(1, 1000000) +RATIONAL = re.compile(r"-?(?:0|[1-9][0-9]*)(?:/[1-9][0-9]*)?\Z", re.ASCII) +SHA256 = re.compile(r"[0-9a-f]{64}\Z", re.ASCII) + + +class VerificationRefusal(RuntimeError): + def __init__(self, reason: str, detail: str): + super().__init__(detail) + self.reason = reason + self.detail = detail + + +def refuse(reason: str, detail: str) -> None: + raise VerificationRefusal(reason, detail) + + +def canonical_bytes(value: object) -> bytes: + return json.dumps( + value, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + + +def strict_json(raw: bytes) -> object: + if not raw or len(raw) > MAX_CERTIFICATE_BYTES or not raw.endswith(b"\n"): + refuse("certificate-bytes", "certificate must be bounded and end in one LF") + try: + text = raw[:-1].decode("utf-8") + except UnicodeDecodeError as error: + refuse("certificate-encoding", "certificate is not UTF-8") + + def pairs(items: list[tuple[str, object]]) -> dict[str, object]: + result: dict[str, object] = {} + for key, value in items: + if key in result: + refuse("duplicate-json-key", f"duplicate JSON key {key!r}") + result[key] = value + return result + + def integer(token: str) -> int: + digits = token.lstrip("-") + if len(digits) > MAX_INTEGER_DIGITS: + refuse("integer-budget", "JSON integer exceeds the digit budget") + return int(token) + + try: + value = json.loads( + text, + object_pairs_hook=pairs, + parse_int=integer, + parse_float=lambda unused: refuse( + "json-number", "JSON floating-point literals are forbidden" + ), + parse_constant=lambda unused: refuse( + "json-number", "non-finite JSON literals are forbidden" + ), + ) + except VerificationRefusal: + raise + except (ValueError, RecursionError) as error: + refuse("certificate-json", f"certificate JSON refused: {error}") + + stack: list[tuple[object, int]] = [(value, 1)] + nodes = 0 + while stack: + item, depth = stack.pop() + nodes += 1 + if nodes > MAX_JSON_NODES or depth > MAX_JSON_DEPTH: + refuse("certificate-structure", "certificate structure budget exceeded") + if isinstance(item, dict): + stack.extend((child, depth + 1) for child in item.values()) + elif isinstance(item, list): + stack.extend((child, depth + 1) for child in item) + return value + + +def rational(value: object, context: str) -> Fraction: + if not isinstance(value, str) or len(value) > 2 * MAX_INTEGER_DIGITS + 2: + refuse("rational-token", f"{context} is not a bounded rational token") + if RATIONAL.fullmatch(value) is None: + refuse("rational-token", f"{context} is not canonical rational syntax") + numerator, separator, denominator = value.partition("/") + if len(numerator.lstrip("-")) > MAX_INTEGER_DIGITS or ( + separator and len(denominator) > MAX_INTEGER_DIGITS + ): + refuse("integer-budget", f"{context} exceeds the integer digit budget") + try: + result = Fraction(value) + except (ValueError, ZeroDivisionError) as error: + refuse("rational-token", f"{context} is not a rational: {error}") + if str(result) != value: + refuse("rational-canonical", f"{context} is not reduced canonical form") + return result + + +def exact_keys(value: object, expected: set[str], context: str) -> dict[str, object]: + if not isinstance(value, dict) or set(value) != expected: + refuse("certificate-shape", f"{context} keys do not match the closed schema") + return value + + +def poly_trim(value: list[Fraction]) -> list[Fraction]: + result = list(value) + while len(result) > 1 and result[-1] == 0: + result.pop() + return result + + +def poly_add(left: list[Fraction], right: list[Fraction]) -> list[Fraction]: + size = max(len(left), len(right)) + result = [Fraction(0)] * size + for index in range(size): + if index < len(left): + result[index] += left[index] + if index < len(right): + result[index] += right[index] + return poly_trim(result) + + +def poly_scale(value: list[Fraction], factor: Fraction) -> list[Fraction]: + return poly_trim([factor * coefficient for coefficient in value]) + + +def poly_mul(left: list[Fraction], right: list[Fraction]) -> list[Fraction]: + left_denominator = lcm(*(coefficient.denominator for coefficient in left)) + right_denominator = lcm(*(coefficient.denominator for coefficient in right)) + left_integers = [ + coefficient.numerator * (left_denominator // coefficient.denominator) + for coefficient in left + ] + right_integers = [ + coefficient.numerator * (right_denominator // coefficient.denominator) + for coefficient in right + ] + integer_result = [0] * (len(left) + len(right) - 1) + for left_index, left_value in enumerate(left_integers): + for right_index, right_value in enumerate(right_integers): + integer_result[left_index + right_index] += left_value * right_value + denominator = left_denominator * right_denominator + return poly_trim([Fraction(value, denominator) for value in integer_result]) + + +def poly_pow(value: list[Fraction], exponent: int) -> list[Fraction]: + result = [Fraction(1)] + base = list(value) + power = exponent + while power: + if power & 1: + result = poly_mul(result, base) + power //= 2 + if power: + base = poly_mul(base, base) + return result + + +def poly_derivative(value: list[Fraction]) -> list[Fraction]: + if len(value) <= 1: + return [Fraction(0)] + return [Fraction(index) * value[index] for index in range(1, len(value))] + + +def poly_at_one(value: list[Fraction]) -> Fraction: + return sum(value, Fraction(0)) + + +def poly_abs_sum(value: list[Fraction]) -> Fraction: + return sum((abs(coefficient) for coefficient in value), Fraction(0)) + + +def poly_integral_unit(value: list[Fraction]) -> Fraction: + """Integrate a power-basis polynomial exactly over ``0 <= s <= 1``.""" + coefficient_denominator = lcm( + *(coefficient.denominator for coefficient in value) + ) + integral_denominator = lcm(*range(1, len(value) + 1)) + numerator = sum( + coefficient.numerator + * (coefficient_denominator // coefficient.denominator) + * (integral_denominator // (index + 1)) + for index, coefficient in enumerate(value) + ) + return Fraction(numerator, coefficient_denominator * integral_denominator) + + +def poly_product_integral_unit( + left: list[Fraction], right: list[Fraction] +) -> Fraction: + """Integrate a polynomial product with one final rational reduction. + + Accumulating every product through ``Fraction`` repeatedly computes very + large gcds. Common coefficient and monomial-integral denominators make the + same exact sum an integer accumulation followed by one reduction. + """ + left_denominator = lcm(*(coefficient.denominator for coefficient in left)) + right_denominator = lcm(*(coefficient.denominator for coefficient in right)) + integral_denominator = lcm(*range(1, len(left) + len(right))) + left_integers = [ + coefficient.numerator * (left_denominator // coefficient.denominator) + for coefficient in left + ] + right_integers = [ + coefficient.numerator * (right_denominator // coefficient.denominator) + for coefficient in right + ] + numerator = sum( + left_value + * right_value + * (integral_denominator // (left_index + right_index + 1)) + for left_index, left_value in enumerate(left_integers) + for right_index, right_value in enumerate(right_integers) + ) + return Fraction( + numerator, + left_denominator * right_denominator * integral_denominator, + ) + + +def power_to_bernstein(value: list[Fraction]) -> list[Fraction]: + """Return exact same-degree Bernstein coefficients on the unit interval.""" + degree = len(value) - 1 + return [ + sum( + ( + value[power] * Fraction(comb(index, power), comb(degree, power)) + for power in range(index + 1) + ), + Fraction(0), + ) + for index in range(degree + 1) + ] + + +Interval = tuple[Fraction, Fraction] + + +def interval_add(left: Interval, right: Interval) -> Interval: + return left[0] + right[0], left[1] + right[1] + + +def interval_sub(left: Interval, right: Interval) -> Interval: + return left[0] - right[1], left[1] - right[0] + + +def interval_scale(value: Interval, factor: Fraction) -> Interval: + if factor >= 0: + return value[0] * factor, value[1] * factor + return value[1] * factor, value[0] * factor + + +def interval_divide_positive(numerator: Interval, denominator: Interval) -> Interval: + if numerator[0] < 0 or denominator[0] <= 0: + refuse("checker-internal", "positive interval division received an invalid input") + return numerator[0] / denominator[1], numerator[1] / denominator[0] + + +def interval_square_positive(value: Interval) -> Interval: + if value[0] < 0: + refuse("checker-internal", "positive interval square received a negative input") + return value[0] * value[0], value[1] * value[1] + + +def sqrt_fraction_bound(value: Fraction) -> Interval: + """Enclose a nonnegative rational square root on an exact dyadic grid.""" + if value < 0: + refuse("checker-internal", "rational square root received a negative input") + scale = 1 << SQRT_DYADIC_BITS + scaled_numerator = value.numerator * scale * scale + quotient = scaled_numerator // value.denominator + lower_integer = isqrt(quotient) + lower = Fraction(lower_integer, scale) + if lower * lower == value: + return lower, lower + return lower, Fraction(lower_integer + 1, scale) + + +def interval_text(value: Interval) -> list[str]: + if value[0] > value[1]: + refuse("checker-internal", "attempted to render a reversed interval") + return [str(value[0]), str(value[1])] + + +def outward_decimal_interval( + lower: Fraction, upper: Fraction, places: int +) -> list[str]: + scale = 10**places + scaled_lower = lower * scale + scaled_upper = upper * scale + lower_integer = scaled_lower.numerator // scaled_lower.denominator + upper_integer = -((-scaled_upper.numerator) // scaled_upper.denominator) + + def render(value: int) -> str: + sign = "-" if value < 0 else "" + digits = str(abs(value)).rjust(places + 1, "0") + return f"{sign}{digits[:-places]}.{digits[-places:]}" + + return [render(lower_integer), render(upper_integer)] + + +def exp_positive_bound(value: Fraction) -> tuple[Fraction, Fraction]: + if value < 0: + refuse("checker-internal", "positive exponential helper received a negative input") + halvings = 0 + reduced = value + while reduced > Fraction(1, 2): + reduced /= 2 + halvings += 1 + if halvings > 32: + refuse("exp-budget", "exponential range reduction budget exceeded") + term = Fraction(1) + partial = Fraction(1) + for index in range(1, EXP_TAYLOR_DEGREE + 1): + term *= reduced / index + partial += term + next_term = term * reduced / (EXP_TAYLOR_DEGREE + 1) + ratio = reduced / (EXP_TAYLOR_DEGREE + 2) + upper = partial + next_term / (1 - ratio) + lower = partial + lower, upper = outward_dyadic(lower, upper) + for _ in range(halvings): + lower, upper = outward_dyadic(lower * lower, upper * upper) + return lower, upper + + +def exp_bound(value: Fraction) -> tuple[Fraction, Fraction]: + if value >= 0: + return exp_positive_bound(value) + lower, upper = exp_positive_bound(-value) + return outward_dyadic(1 / upper, 1 / lower) + + +def outward_dyadic(lower: Fraction, upper: Fraction) -> tuple[Fraction, Fraction]: + """Round an established enclosure outward to a fixed exact dyadic grid.""" + if lower > upper: + refuse("checker-internal", "attempted to round a reversed enclosure") + scale = 1 << EXP_DYADIC_BITS + lower_scaled = lower * scale + upper_scaled = upper * scale + lower_integer = lower_scaled.numerator // lower_scaled.denominator + upper_integer = -((-upper_scaled.numerator) // upper_scaled.denominator) + return Fraction(lower_integer, scale), Fraction(upper_integer, scale) + + +def validate_density_polynomial( + q: list[Fraction], density: list[Fraction] +) -> Fraction: + """Bound |exp(q(s))-density(s)| by an exact Gronwall calculation.""" + initial_lower, initial_upper = exp_bound(q[0]) + initial_error = max( + abs(density[0] - initial_lower), abs(density[0] - initial_upper) + ) + q_derivative = poly_derivative(q) + defect = poly_add( + poly_derivative(density), + poly_scale(poly_mul(q_derivative, density), Fraction(-1)), + ) + integrated_defect_bound = poly_abs_sum(defect) + logarithmic_variation = poly_abs_sum(q_derivative) + growth = exp_bound(logarithmic_variation)[1] + return (initial_error + integrated_defect_bound) * growth + + +def parse_piece(value: object, context: str) -> dict[str, object]: + piece = exact_keys( + value, + {"origin", "step", "coefficients", "density_coefficients"}, + context, + ) + origin = rational(piece["origin"], f"{context}.origin") + step = rational(piece["step"], f"{context}.step") + raw_coefficients = piece["coefficients"] + if ( + not isinstance(raw_coefficients, list) + or not 3 <= len(raw_coefficients) <= MAX_POLYNOMIAL_DEGREE + 1 + ): + refuse("polynomial-budget", f"{context} polynomial degree is outside the budget") + coefficients = [ + rational(item, f"{context}.coefficients[{index}]") + for index, item in enumerate(raw_coefficients) + ] + raw_density = piece["density_coefficients"] + if ( + not isinstance(raw_density, list) + or not 2 <= len(raw_density) <= MAX_POLYNOMIAL_DEGREE + 1 + ): + refuse("polynomial-budget", f"{context} density polynomial is outside the budget") + density = [ + rational(item, f"{context}.density_coefficients[{index}]") + for index, item in enumerate(raw_density) + ] + return { + "origin": origin, + "step": step, + "coefficients": coefficients, + "density_coefficients": density, + } + + +def parse_chain(value: object, name: str) -> list[dict[str, object]]: + if not isinstance(value, list) or not value or len(value) > MAX_PIECES: + refuse("piece-budget", f"{name} is not a nonempty bounded piece list") + return [parse_piece(piece, f"{name}[{index}]") for index, piece in enumerate(value)] + + +def endpoint(piece: dict[str, object]) -> tuple[Fraction, Fraction, Fraction]: + origin = piece["origin"] + step = piece["step"] + coefficients = piece["coefficients"] + assert isinstance(origin, Fraction) and isinstance(step, Fraction) + assert isinstance(coefficients, list) + value = poly_at_one(coefficients) + derivative = poly_at_one(poly_derivative(coefficients)) / step + return origin + step, value, derivative + + +def validate_chain( + chain: list[dict[str, object]], start: Fraction, stop: Fraction, direction: int, name: str +) -> None: + expected_origin = start + prior_value: Fraction | None = None + prior_derivative: Fraction | None = None + for index, piece in enumerate(chain): + origin = piece["origin"] + step = piece["step"] + coefficients = piece["coefficients"] + assert isinstance(origin, Fraction) and isinstance(step, Fraction) + assert isinstance(coefficients, list) + if origin != expected_origin or step * direction <= 0: + refuse("piece-coverage", f"{name}[{index}] does not continue the chain") + if prior_value is not None and ( + coefficients[0] != prior_value + or coefficients[1] / step != prior_derivative + ): + refuse("piece-continuity", f"{name}[{index}] is not C1-continuous") + expected_origin, prior_value, prior_derivative = endpoint(piece) + if expected_origin != stop: + refuse("piece-coverage", f"{name} does not end at its declared boundary") + + +def laurent_add(target: dict[int, Fraction], exponent: int, value: Fraction) -> None: + target[exponent] = target.get(exponent, Fraction(0)) + value + if target[exponent] == 0: + del target[exponent] + + +def tail_residual_bound( + coefficients: list[Fraction], eigenvalue: Fraction, right: Fraction +) -> tuple[Fraction, Fraction, Fraction]: + epsilon = Fraction(1, 20) + w = {2 * index - 3: value for index, value in enumerate(coefficients)} + derivative: dict[int, Fraction] = {} + for exponent, value in w.items(): + laurent_add(derivative, exponent + 1, -exponent * value) + square: dict[int, Fraction] = {} + for left_power, left_value in w.items(): + for right_power, right_value in w.items(): + laurent_add(square, left_power + right_power, left_value * right_value) + residual: dict[int, Fraction] = {} + for exponent, value in derivative.items(): + laurent_add(residual, exponent, -epsilon * epsilon * value) + for exponent, value in square.items(): + laurent_add(residual, exponent, -epsilon * epsilon * value) + laurent_add(residual, -6, Fraction(1)) + laurent_add(residual, -4, Fraction(-5)) + laurent_add(residual, -2, Fraction(4)) + laurent_add(residual, 0, -eigenvalue) + if residual and min(residual) < 0: + refuse("tail-recurrence", "tail recurrence leaves an uncancelled growing term") + y_max = 1 / right + absolute_residual = sum( + (abs(value) * y_max**exponent for exponent, value in residual.items()), + Fraction(0), + ) + + # w'(x) = sum -p*a_p*y^(p+1). The leading term is -60/y^2. + derivative_upper = Fraction(-60) / (y_max * y_max) + for exponent, value in derivative.items(): + if exponent == -2: + continue + contribution = value * y_max**exponent + if contribution > 0: + derivative_upper += contribution + if derivative_upper >= 0: + refuse("tail-monotonicity", "tail logarithmic derivative is not proved decreasing") + w_at_right = sum( + (value * y_max**exponent for exponent, value in w.items()), Fraction(0) + ) + if w_at_right >= 0: + refuse("tail-sign", "tail logarithmic derivative is not negative") + return absolute_residual, derivative_upper, w_at_right + + +def potential_polynomial(origin: Fraction, step: Fraction) -> list[Fraction]: + x = [origin, step] + return poly_add( + poly_add(poly_pow(x, 6), poly_scale(poly_pow(x, 4), Fraction(-5))), + poly_scale(poly_pow(x, 2), Fraction(4)), + ) + + +def exponential_moment_tail_upper( + power: int, + right: Fraction, + decay_rate: Fraction, + amplitude_upper: Fraction, +) -> Fraction: + """Bound ``integral_right^infinity x^power amplitude*exp(-rate*t)``.""" + if power < 0 or decay_rate <= 0 or amplitude_upper <= 0: + refuse("checker-internal", "exponential tail moment inputs are invalid") + moment = sum( + ( + Fraction(comb(power, index) * factorial(index)) + * right ** (power - index) + / decay_rate ** (index + 1) + for index in range(power + 1) + ), + Fraction(0), + ) + return amplitude_upper * moment + + +def raw_density_moment_interval( + pieces: list[dict[str, object]], + exponentials: list[tuple[list[Fraction], Fraction]], + power: int, + right: Fraction, + tail_decay_rate: Fraction, + tail_amplitude_upper: Fraction, +) -> Interval: + half_interval: Interval = (Fraction(0), Fraction(0)) + for piece, (density, density_error) in zip(pieces, exponentials, strict=True): + origin = piece["origin"] + step = piece["step"] + assert isinstance(origin, Fraction) and isinstance(step, Fraction) + weight = poly_pow([origin, step], power) + weight_integral = poly_integral_unit(weight) + if weight_integral < 0: + refuse("checker-internal", "nonnegative moment weight integrated negative") + nominal = abs(step) * poly_product_integral_unit(weight, density) + allowance = abs(step) * density_error * weight_integral + lower = max(Fraction(0), nominal - allowance) + upper = nominal + allowance + if upper < lower: + refuse("diagnostic-moment", "piece moment enclosure is reversed") + half_interval = interval_add(half_interval, (lower, upper)) + tail_upper = exponential_moment_tail_upper( + power, right, tail_decay_rate, tail_amplitude_upper + ) + return 2 * half_interval[0], 2 * (half_interval[1] + tail_upper) + + +def raw_quartic_interval( + pieces: list[dict[str, object]], + exponentials: list[tuple[list[Fraction], Fraction]], + tail_q: Fraction, + tail_w: Fraction, +) -> Interval: + half_interval: Interval = (Fraction(0), Fraction(0)) + for index, (piece, (density, density_error)) in enumerate( + zip(pieces, exponentials, strict=True) + ): + step = piece["step"] + assert isinstance(step, Fraction) + lower_density = list(density) + upper_density = list(density) + lower_density[0] -= density_error + upper_density[0] += density_error + if min(power_to_bernstein(lower_density)) <= 0: + refuse( + "diagnostic-density-positivity", + f"piece {index} does not prove a positive density lower bound", + ) + lower = abs(step) * poly_product_integral_unit( + lower_density, lower_density + ) + upper = abs(step) * poly_product_integral_unit( + upper_density, upper_density + ) + if lower < 0 or upper < lower: + refuse("diagnostic-quartic", "piece quartic enclosure is invalid") + half_interval = interval_add(half_interval, (lower, upper)) + tail_upper = exp_bound(2 * tail_q)[1] / (-4 * tail_w) + return 2 * half_interval[0], 2 * (half_interval[1] + tail_upper) + + +def raw_kinetic_integral_interval( + pieces: list[dict[str, object]], + exponentials: list[tuple[list[Fraction], Fraction]], + tail: list[Fraction], + right: Fraction, + tail_q: Fraction, + tail_w: Fraction, +) -> Interval: + half_interval: Interval = (Fraction(0), Fraction(0)) + for piece, (density, density_error) in zip(pieces, exponentials, strict=True): + step = piece["step"] + q = piece["coefficients"] + assert isinstance(step, Fraction) and isinstance(q, list) + q_x = poly_scale(poly_derivative(q), 1 / step) + weight = poly_mul(q_x, q_x) + weight_integral = poly_integral_unit(weight) + nominal = abs(step) * poly_product_integral_unit(weight, density) + allowance = abs(step) * density_error * weight_integral + lower = max(Fraction(0), nominal - allowance) + upper = nominal + allowance + if weight_integral < 0 or upper < lower: + refuse("diagnostic-kinetic", "piece kinetic enclosure is invalid") + half_interval = interval_add(half_interval, (lower, upper)) + + w = {2 * index - 3: value for index, value in enumerate(tail)} + w_square: dict[int, Fraction] = {} + for left_power, left_value in w.items(): + for right_power, right_value in w.items(): + laurent_add( + w_square, left_power + right_power, left_value * right_value + ) + tail_amplitude_upper = exp_bound(tail_q)[1] + decay_rate = -2 * tail_w + tail_mass_upper = tail_amplitude_upper / decay_rate + tail_w_square_upper = Fraction(0) + for y_power, coefficient in w_square.items(): + x_power = -y_power + if x_power >= 0: + weighted_tail = exponential_moment_tail_upper( + x_power, right, decay_rate, tail_amplitude_upper + ) + else: + weighted_tail = right**x_power * tail_mass_upper + tail_w_square_upper += abs(coefficient) * weighted_tail + + # q' = 2*w, so q'^2 = 4*w^2. + return 2 * half_interval[0], 2 * ( + half_interval[1] + 4 * tail_w_square_upper + ) + + +def compute_trial_and_ground_diagnostics( + *, + pieces: list[dict[str, object]], + exponentials: list[tuple[list[Fraction], Fraction]], + normalization: Interval, + tail: list[Fraction], + right: Fraction, + tail_q: Fraction, + tail_w: Fraction, + eigenvalue_interval: Interval, + quotient_residual_radius: Fraction, +) -> tuple[dict[str, object], dict[str, object]]: + epsilon = Fraction(1, 20) + coupling = Fraction(7, 10) + tail_amplitude_upper = exp_bound(tail_q)[1] + tail_decay_rate = -2 * tail_w + + raw_moments = { + power: raw_density_moment_interval( + pieces, + exponentials, + power, + right, + tail_decay_rate, + tail_amplitude_upper, + ) + for power in (2, 4, 6) + } + moments = { + power: interval_divide_positive(raw, normalization) + for power, raw in raw_moments.items() + } + raw_quartic = raw_quartic_interval( + pieces, exponentials, tail_q, tail_w + ) + normalization_squared = interval_square_positive(normalization) + quartic = interval_divide_positive(raw_quartic, normalization_squared) + raw_q_derivative_squared = raw_kinetic_integral_interval( + pieces, exponentials, tail, right, tail_q, tail_w + ) + kinetic = interval_scale( + interval_divide_positive(raw_q_derivative_squared, normalization), + epsilon * epsilon / 4, + ) + potential = interval_add( + interval_add(moments[6], interval_scale(moments[4], Fraction(-5))), + interval_scale(moments[2], Fraction(4)), + ) + energy = interval_add( + interval_add(kinetic, potential), + interval_scale(quartic, coupling / 2), + ) + eigenvalue_from_energy = interval_add( + energy, interval_scale(quartic, coupling / 2) + ) + energy_identity_residual = interval_sub( + eigenvalue_interval, eigenvalue_from_energy + ) + virial_residual = interval_add( + interval_add( + interval_add( + interval_scale(kinetic, Fraction(2)), + interval_scale(moments[6], Fraction(-6)), + ), + interval_scale(moments[4], Fraction(20)), + ), + interval_add( + interval_scale(moments[2], Fraction(-8)), + interval_scale(quartic, coupling / 2), + ), + ) + if not ( + energy_identity_residual[0] <= 0 <= energy_identity_residual[1] + and max(map(abs, energy_identity_residual)) < MAX_TRIAL_IDENTITY_ABS + ): + refuse( + "diagnostic-energy-identity", + "trial energy/eigenvalue identity residual misses its exact-rational gate", + ) + if not ( + virial_residual[0] <= 0 <= virial_residual[1] + and max(map(abs, virial_residual)) < MAX_TRIAL_VIRIAL_ABS + ): + refuse( + "diagnostic-virial", + "trial virial residual misses its exact-rational gate", + ) + + density_distance_squared_upper = ( + 4 * quotient_residual_radius / coupling + ) + density_distance_upper = sqrt_fraction_bound( + density_distance_squared_upper + )[1] + if density_distance_upper >= MAX_DENSITY_L2_DISTANCE: + refuse( + "ground-transfer-distance", + "strong-convexity density transfer is too wide to admit", + ) + trial_density_norm = sqrt_fraction_bound(quartic[0])[0], sqrt_fraction_bound( + quartic[1] + )[1] + ground_density_norm_lower = max( + Fraction(0), trial_density_norm[0] - density_distance_upper + ) + ground_density_norm_upper = trial_density_norm[1] + density_distance_upper + ground_quartic = ( + ground_density_norm_lower * ground_density_norm_lower, + ground_density_norm_upper * ground_density_norm_upper, + ) + ground_energy = interval_sub( + eigenvalue_interval, interval_scale(ground_quartic, coupling / 2) + ) + + trial = { + "schema": "jackal-hellgate-trial-diagnostics-v1", + "status": "bounded", + "subject": "normalized-certificate-trial-phi", + "quartic_norm_interval": interval_text(quartic), + "quartic_norm_decimal_interval": outward_decimal_interval( + quartic[0], quartic[1], 18 + ), + "moment_intervals": { + "x2": interval_text(moments[2]), + "x4": interval_text(moments[4]), + "x6": interval_text(moments[6]), + }, + "moment_decimal_intervals": { + "x2": outward_decimal_interval(moments[2][0], moments[2][1], 18), + "x4": outward_decimal_interval(moments[4][0], moments[4][1], 18), + "x6": outward_decimal_interval(moments[6][0], moments[6][1], 18), + }, + "kinetic_energy_interval": interval_text(kinetic), + "kinetic_energy_decimal_interval": outward_decimal_interval( + kinetic[0], kinetic[1], 18 + ), + "energy_functional_interval": interval_text(energy), + "energy_functional_decimal_interval": outward_decimal_interval( + energy[0], energy[1], 18 + ), + "energy_eigenvalue_identity_residual_interval": interval_text( + energy_identity_residual + ), + "energy_eigenvalue_identity_residual_decimal_interval": ( + outward_decimal_interval( + energy_identity_residual[0], + energy_identity_residual[1], + 21, + ) + ), + "virial_residual_interval": interval_text(virial_residual), + "virial_residual_decimal_interval": outward_decimal_interval( + virial_residual[0], virial_residual[1], 21 + ), + "assumptions": [ + "the Bernstein convex-hull property bounds each power-basis density polynomial on the unit interval", + "the proved decreasing negative tail logarithmic derivative gives the declared exponential-moment tail majorants", + ], + "non_claims": [ + "these diagnostics enclose the normalized certificate trial phi, not the exact ground state u0", + "a narrow trial virial or energy-identity residual does not by itself transfer trial moments to u0", + "bounded is not formal-bounded; the diagnostic integration is exact-rational Python outside the Lean and SPARK certificate chains", + ], + } + ground = { + "schema": "jackal-hellgate-ground-transfer-v1", + "status": "bounded", + "subject": "positive-normalized-ground-state-u0", + "method": "lambda-strong-convexity-density-transfer-v1", + "derivation": [ + "strong convexity gives (lambda/2)*norm(rho_phi-rho_0,L2)^2 <= -integral((R_phi-c)*(rho_0-rho_phi))", + "the mass constraint cancels c and nonnegative mass-one densities give norm(rho_phi-rho_0,L1) <= 2", + "the global quotient residual therefore gives norm(rho_phi-rho_0,L2)^2 <= 4*delta/lambda", + "the reverse triangle inequality transfers the density L2 norm, whose square is integral(u^4)", + ], + "density_l2_distance_squared_upper": str( + density_distance_squared_upper + ), + "density_l2_distance_upper": str(density_distance_upper), + "density_l2_distance_decimal_upper": outward_decimal_interval( + density_distance_upper, density_distance_upper, 18 + )[1], + "quartic_norm_interval": interval_text(ground_quartic), + "quartic_norm_decimal_interval": outward_decimal_interval( + ground_quartic[0], ground_quartic[1], 18 + ), + "energy_functional_interval": interval_text(ground_energy), + "energy_functional_decimal_interval": outward_decimal_interval( + ground_energy[0], ground_energy[1], 18 + ), + "assumptions": [ + "the mass-one density energy is lambda-strongly convex in L2 because Fisher information is convex and lambda/2 times integral rho^2 supplies the modulus", + "the nonlinear quotient is the density energy first variation and its global residual radius is valid", + "both rho_phi and rho_0 are nonnegative mass-one finite-energy densities", + ], + "non_claims": [ + "the transfer encloses the ground-state quartic norm and energy functional only", + "the transfer does not enclose polynomial moments, lambda sensitivity, tunneling, or Bogoliubov frequencies", + "the strong-convexity theorem is stated and applied by the checker but is not Lean- or SPARK-proved here", + ], + } + return trial, ground + + +def verify_document(document: object) -> dict[str, object]: + root = exact_keys( + document, + { + "schema", + "problem", + "representation", + "center_eigenvalue", + "right_endpoint", + "match_point", + "tail_terms", + "tail_coefficients", + "forward_pieces", + "backward_pieces", + "nonclaims", + "certificate_sha256", + }, + "certificate", + ) + if root["schema"] != SCHEMA or root["representation"] != "piecewise-log-density-power-v1": + refuse("certificate-schema", "certificate schema or representation is unsupported") + problem = exact_keys( + root["problem"], + {"epsilon", "lambda", "potential", "mass", "parity", "positivity"}, + "problem", + ) + expected_problem = { + "epsilon": "1/20", + "lambda": "7/10", + "potential": "x^6-5*x^4+4*x^2", + "mass": "1", + "parity": "even", + "positivity": "strict", + } + if problem != expected_problem: + refuse("unsupported-problem", "certificate is not for the fixed HELLGATE problem") + supplied_digest = root["certificate_sha256"] + if not isinstance(supplied_digest, str) or SHA256.fullmatch(supplied_digest) is None: + refuse("certificate-digest", "certificate digest is malformed") + digest_document = {key: value for key, value in root.items() if key != "certificate_sha256"} + actual_digest = hashlib.sha256(canonical_bytes(digest_document)).hexdigest() + if not hmac.compare_digest(actual_digest, supplied_digest): + refuse("certificate-digest", "certificate self-digest mismatch") + + eigenvalue = rational(root["center_eigenvalue"], "center_eigenvalue") + right = rational(root["right_endpoint"], "right_endpoint") + match = rational(root["match_point"], "match_point") + if not Fraction(0) < match < right: + refuse("certificate-domain", "match and right endpoints are not ordered") + tail_terms = root["tail_terms"] + if ( + isinstance(tail_terms, bool) + or not isinstance(tail_terms, int) + or not 8 <= tail_terms <= MAX_TAIL_TERMS + ): + refuse("tail-budget", "tail term count is outside the closed budget") + raw_tail = root["tail_coefficients"] + if not isinstance(raw_tail, list) or len(raw_tail) != tail_terms: + refuse("tail-shape", "tail coefficient list does not match tail_terms") + tail = [ + rational(value, f"tail_coefficients[{index}]") + for index, value in enumerate(raw_tail) + ] + nonclaims = root["nonclaims"] + expected_nonclaims = [ + "producer arithmetic is untrusted until independent exact-rational replay accepts", + "certificate is specific to the declared HELLGATE parameters", + "bounded is not formal-bounded; no Lean theorem checks this certificate", + ] + if nonclaims != expected_nonclaims: + refuse("certificate-nonclaims", "certificate nonclaims were weakened or changed") + + forward = parse_chain(root["forward_pieces"], "forward_pieces") + backward = parse_chain(root["backward_pieces"], "backward_pieces") + validate_chain(forward, Fraction(0), match, 1, "forward_pieces") + validate_chain(backward, right, match, -1, "backward_pieces") + first_coefficients = forward[0]["coefficients"] + assert isinstance(first_coefficients, list) + if first_coefficients[1] != 0: + refuse("even-boundary", "trial log-density derivative is not zero at the origin") + _, forward_value, forward_derivative = endpoint(forward[-1]) + _, backward_value, backward_derivative = endpoint(backward[-1]) + if forward_value != backward_value or forward_derivative != backward_derivative: + refuse("match-continuity", "forward and backward trial chains do not meet C1") + + tail_linear_error, _, tail_w = tail_residual_bound(tail, eigenvalue, right) + backward_origin = backward[0] + backward_coefficients = backward_origin["coefficients"] + backward_step = backward_origin["step"] + assert isinstance(backward_coefficients, list) and isinstance(backward_step, Fraction) + if backward_coefficients[1] / backward_step != 2 * tail_w: + refuse("tail-continuity", "interior trial derivative does not match the tail") + tail_q = backward_coefficients[0] + + pieces = forward + backward + exponentials: list[tuple[list[Fraction], Fraction]] = [] + half_mass_lower = Fraction(0) + half_mass_upper = Fraction(0) + for index, piece in enumerate(pieces): + coefficients = piece["coefficients"] + density_polynomial = piece["density_coefficients"] + step = piece["step"] + assert ( + isinstance(coefficients, list) + and isinstance(density_polynomial, list) + and isinstance(step, Fraction) + ) + polynomial = density_polynomial + error = validate_density_polynomial(coefficients, polynomial) + integral = abs(step) * sum( + (value / (power + 1) for power, value in enumerate(polynomial)), + Fraction(0), + ) + allowance = abs(step) * error + lower = integral - allowance + if lower <= 0: + refuse("normalization-lower", f"piece {index} has no positive mass lower bound") + half_mass_lower += lower + half_mass_upper += integral + allowance + exponentials.append((polynomial, error)) + + tail_exp_upper = exp_bound(tail_q)[1] + tail_mass_upper = tail_exp_upper / (-2 * tail_w) + normalization_lower = 2 * half_mass_lower + normalization_upper = 2 * (half_mass_upper + tail_mass_upper) + if not normalization_lower > 0 or normalization_lower > normalization_upper: + refuse("normalization", "normalization enclosure is invalid") + inverse_lower = 1 / normalization_upper + inverse_upper = 1 / normalization_lower + inverse_center = (inverse_lower + inverse_upper) / 2 + inverse_error = max(inverse_center - inverse_lower, inverse_upper - inverse_center) + + epsilon = Fraction(1, 20) + coupling = Fraction(7, 10) + residual_radius = Fraction(0) + for piece, (density_poly, density_error) in zip(pieces, exponentials, strict=True): + origin = piece["origin"] + step = piece["step"] + q = piece["coefficients"] + assert isinstance(origin, Fraction) and isinstance(step, Fraction) + assert isinstance(q, list) + q_x = poly_scale(poly_derivative(q), 1 / step) + q_xx = poly_scale(poly_derivative(q_x), 1 / step) + residual = poly_scale(q_xx, -epsilon * epsilon / 2) + residual = poly_add( + residual, poly_scale(poly_mul(q_x, q_x), -epsilon * epsilon / 4) + ) + residual = poly_add(residual, potential_polynomial(origin, step)) + residual[0] -= eigenvalue + residual = poly_add( + residual, poly_scale(density_poly, coupling * inverse_center) + ) + normalization_error = coupling * ( + density_error * inverse_upper + + poly_abs_sum(density_poly) * inverse_error + ) + piece_radius = poly_abs_sum(residual) + normalization_error + residual_radius = max(residual_radius, piece_radius) + + tail_nonlinear = coupling * tail_exp_upper * inverse_upper + residual_radius = max(residual_radius, tail_linear_error + tail_nonlinear) + eigen_lower = eigenvalue - residual_radius + eigen_upper = eigenvalue + residual_radius + width = 2 * residual_radius + if width >= MAX_EIGENVALUE_WIDTH: + refuse( + "target-width", + "exact-rational quotient enclosure does not meet the HELLGATE width requirement", + ) + trial_diagnostics, ground_transfer = compute_trial_and_ground_diagnostics( + pieces=pieces, + exponentials=exponentials, + normalization=(normalization_lower, normalization_upper), + tail=tail, + right=right, + tail_q=tail_q, + tail_w=tail_w, + eigenvalue_interval=(eigen_lower, eigen_upper), + quotient_residual_radius=residual_radius, + ) + return { + "schema": RESULT_SCHEMA, + "status": "bounded", + "lane": "nonlinear-barta-exact-rational-v1", + "formal": False, + "checker_verdict": "ACCEPT", + "parsed": "epsilon=1/20; lambda=7/10; V=x^6-5*x^4+4*x^2; mass=1; positive-even-ground-state", + "fields": { + "eigenvalue_interval": [str(eigen_lower), str(eigen_upper)], + "eigenvalue_decimal_interval": outward_decimal_interval( + eigen_lower, eigen_upper, 18 + ), + "interval_width": str(width), + "center_eigenvalue": str(eigenvalue), + "quotient_residual_radius": str(residual_radius), + "normalization_interval": [ + str(normalization_lower), + str(normalization_upper), + ], + "certificate_sha256": supplied_digest, + "piece_count": len(pieces), + "tail_terms": tail_terms, + "trial_diagnostics": trial_diagnostics, + "ground_state_transfer": ground_transfer, + }, + "theorem": { + "name": "normalized defocusing nonlinear Barta comparison", + "statement": "For lambda>=0, a positive normalized trial phi with quotient R=(-epsilon^2 phi''+V phi+lambda phi^3)/phi in [a,b] encloses the positive normalized ground-state eigenvalue E in [a,b].", + "application": "phi=exp(q/2)/sqrt(integral exp(q)); exact-rational piece and tail bounds enclose R globally.", + }, + "assumptions": [ + "the normalized defocusing nonlinear Barta comparison theorem is valid for positive C1 piecewise-C2 trials with the stated confining potential", + "the stated lambda-strong-convexity density-transfer argument is valid for the admitted finite-energy densities", + "Python arbitrary-precision integer, Fraction, and integer-square-root operations implement their documented exact arithmetic", + ], + "non_claims": expected_nonclaims + + [ + "checker acceptance does not establish implementation correctness of the Python interpreter", + "checker acceptance encloses E0 plus the explicitly scoped ground-state quartic norm and energy-functional transfer only", + "reported polynomial moments and consistency residuals belong to the certificate trial phi, not the exact ground state u0", + "ground-state polynomial moments, lambda sensitivity, tunneling, and Bogoliubov frequencies require separate certificates", + ], + } + + +def verify_bytes(raw: bytes) -> dict[str, object]: + return verify_document(strict_json(raw)) + + +def refusal_body(error: VerificationRefusal) -> dict[str, object]: + return { + "schema": RESULT_SCHEMA, + "status": "refused", + "reason": error.reason, + "detail": error.detail, + "formal": False, + "non_claims": [ + "a refusal is an answer and no weaker numerical lane was substituted", + "no eigenvalue enclosure was established", + ], + } + + +def main() -> int: + if len(sys.argv) != 2: + print("usage: hellgate_verify.py CERTIFICATE.json", file=sys.stderr) + return 2 + path = Path(sys.argv[1]) + try: + raw = path.read_bytes() + result = verify_bytes(raw) + except VerificationRefusal as error: + result = refusal_body(error) + except OSError as error: + result = refusal_body(VerificationRefusal("certificate-io", str(error))) + print(json.dumps(result, sort_keys=True, separators=(",", ":"))) + return 0 if result["status"] == "bounded" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/jackel/mcp/measurement.py b/plugins/jackel/mcp/measurement.py new file mode 100644 index 0000000..49eecda --- /dev/null +++ b/plugins/jackel/mcp/measurement.py @@ -0,0 +1,1341 @@ +#!/usr/bin/python3 -B +"""Identity-pinned measurement and provenance tools for the JACKAL MCP server. + +This module is not a standalone service. The identity-pinned JACKAL wrapper +loads it in-process and supplies a callback into the same serialized runtime +backend used by every other JACKAL tool. Each arithmetic result is echoed in +``delegated_to``. If the runtime is unavailable or refuses, this layer refuses; +it never substitutes Python floating-point arithmetic. + +WHAT THIS SUBSYSTEM ADDS — the gap it closes: + +JACKAL is airtight on numerals that ANNOUNCE THEMSELVES as arithmetic. The +leak is the numerals that do not: unit conversions, percentages, date deltas, +currency comparisons. Each of those is an exact computation resting on a datum +that is NOT mathematics — a conversion factor, a tz rule, an exchange rate. +The arithmetic deserves `exact`; the datum deserves scrutiny. Collapsing the +two in either direction is a lie: + + - calling a currency conversion `exact` launders the rate into mathematics + - calling it `estimated` slanders arithmetic that is, in fact, exact + +The subsystem introduces one status class, and only one: + + exact-given — exact rational arithmetic, CONDITIONAL on a declared datum + that is carried in the result and is NOT verified by JACKAL. + +`given` is a required, structured field on every such result. A datum with no +source and no as-of date is refused at the door. That is the whole point: the +tool makes declaring provenance the only way to get an answer at all. + +`exact-given` is NOT a rung below `exact`, in the same way that `exact` and +`formal-bounded` are not rungs. It names a different shape of claim. +""" + +import datetime +import decimal +import json +import re +import sys +from fractions import Fraction +from typing import Callable + +# Everything this subsystem says about the world is informational. Even a formal-bounded +# enclosure of a standard deviation says nothing about whether the sample was +# collected correctly, and no conversion says anything about whether the +# quantity was measured correctly. The ceiling never rises above this. +CONSEQUENCE_CEILING = "informational" + +UNIVERSAL_NON_CLAIMS = [ + "The JACKAL measurement orchestrator is identity-pinned but remains outside the Lean certificate chain", + "The measurement orchestrator performs no arithmetic of its own: every arithmetic result here was produced by a delegated JACKAL runtime call recorded in `delegated_to`; library metadata such as calendar ordinals, collection counts, and lexical offsets is not an arithmetic claim", + "The epistemic class above is the STRONGEST claim this result supports", +] + + +class Refusal(Exception): + """A named refusal. Carried to the client as a result, never as an error. + + A refusal is an answer. Raising it through the JSON-RPC error channel would + invite the client to treat it as a transport failure and retry, and retrying + a refusal on a weaker lane until something answers is precisely the move the + kernel's discipline forbids. + """ + + def __init__(self, reason: str, detail: str, non_claims: list[str] | None = None): + super().__init__(detail) + self.reason = reason + self.detail = detail + self.non_claims = non_claims or [] + + +# Set only for the duration of one serialized call by ``dispatch_integrated``. +# There is intentionally no child-process fallback in this module. +JACKAL: object | None = None +_ACTIVE_IDENTITY: str | None = None + + +def _active_identity() -> str: + if _ACTIVE_IDENTITY is None: + raise RuntimeError("measurement identity is unavailable outside integrated dispatch") + return _ACTIVE_IDENTITY + +# Every delegated call made while serving one request, echoed into the result so +# a reader can confirm the measurement orchestrator computed nothing. Request handling is serialized +# by the single-threaded stdio loop, so a module-level list is safe here. +_TRACE: list[dict] = [] + + +def exact(expression: str) -> Fraction: + """Delegate one exact-rational computation and record the call.""" + if JACKAL is None: + raise Refusal( + "kernel-unavailable", + "the measurement module is not attached to the JACKAL runtime", + ) + out = JACKAL.call("jackal_exact", {"expression": expression}) + fields = out.get("fields", {}) + value = fields.get("exact") + if not isinstance(value, str): + raise Refusal("kernel-error", "jackal_exact returned no exact field") + _TRACE.append({ + "tool": "jackal_exact", + "parsed": fields.get("parsed", expression), + "exact": value, + "approx": fields.get("approx"), + "status": out.get("status", "unknown"), + }) + try: + return Fraction(value) + except (ValueError, ZeroDivisionError) as exc: + raise Refusal("kernel-error", f"unparseable exact value {value!r}") from exc + + +def sqrt_bound(value: Fraction) -> tuple[Fraction, Fraction]: + """Delegate a formal-bounded sqrt enclosure at a point. + + A point interval (lo == hi) is the degenerate case the certified range + checker already handles, so a scalar square root gets the SAME Lean-checked + treatment as an interval one. Nothing here approximates. + """ + if value < 0: + raise Refusal("domain", f"sqrt of a negative rational {value} is not real") + if JACKAL is None: + raise Refusal( + "kernel-unavailable", + "the measurement module is not attached to the JACKAL runtime", + ) + arg = frac_str(value) + out = JACKAL.call("jackal_sqrt_rat_bound", { + "expression": "sqrt(x)", "input_lo": arg, "input_hi": arg, + }) + text = out.get("checker_output", "") + match = re.search(r"output\s+(\S+)\s+(\S+)\s*$", text.strip()) + if not match: + raise Refusal("kernel-error", "could not read the enclosure from the checker output") + lo, hi = Fraction(match.group(1)), Fraction(match.group(2)) + _TRACE.append({ + "tool": "jackal_sqrt_rat_bound", + "parsed": f"sqrt(x) on [{arg},{arg}]", + "enclosure": [str(lo), str(hi)], + "status": out.get("status", "unknown"), + "checker_rerun": out.get("checker_rerun"), + }) + return lo, hi + + +# --------------------------------------------------------------------------- +# Envelope +# --------------------------------------------------------------------------- + +def envelope( + *, + status: str, + lane: str, + assurance: str, + parsed: str, + fields: dict, + non_claims: list[str], + given: dict | None = None, + formal: bool = False, +) -> dict: + """Build one JACKAL measurement result in the kernel's idiom. + + `parsed` is mandatory and never omitted, including for trivial input. The + dominant failure at the model/tool boundary is transcription, not + computation, so the echo is worth more than it costs on every single call. + """ + body = { + "status": status, + "lane": lane, + "assurance": assurance, + "formal": formal, + "consequence_ceiling": CONSEQUENCE_CEILING, + "parsed": parsed, + "fields": fields, + "delegated_to": list(_TRACE), + "non_claims": non_claims + UNIVERSAL_NON_CLAIMS, + "identities": {"jackal_measurement_sha256": _active_identity()}, + } + if JACKAL is not None and JACKAL.evaluator_sha256: + body["identities"]["jackal_evaluator_sha256"] = JACKAL.evaluator_sha256 + if given is not None: + body["given"] = given + return body + + +def refusal_body(reason: str, detail: str, non_claims: list[str] | None = None) -> dict: + return { + "status": "refused", + "reason": reason, + "detail": detail, + "consequence_ceiling": CONSEQUENCE_CEILING, + "non_claims": (non_claims or []) + [ + "A refusal is an answer: report it as one", + "Do NOT substitute unverified arithmetic for a refused result", + "Do NOT retry this question on a weaker lane to obtain some number", + ], + "identities": {"jackal_measurement_sha256": _active_identity()}, + } + + +def as_fraction(text: str, subject: str) -> Fraction: + """Parse a user-supplied number WITHOUT arithmetic. + + Literal parsing is transcription, not computation, so it happens here. The + moment two of these meet an operator, the expression goes to the kernel. + """ + if not isinstance(text, str) or not text.strip(): + raise Refusal("args", f"{subject} must be a non-empty string") + raw = text.strip().replace("_", "") + # Thousands separators are a transcription hazard, not a notation the measurement subsystem + # guesses at: 1,234 is one number in one locale and two in another. + if "," in raw: + raise Refusal( + "ambiguous-literal", + f"{subject} contains a comma ({text!r}); comma grouping and comma decimals are " + "indistinguishable here. Supply an unambiguous literal such as '1234' or '1234.5'.", + ) + try: + if "/" in raw: + return Fraction(raw) + return Fraction(decimal.Decimal(raw)) + except (ValueError, ArithmeticError, decimal.InvalidOperation) as exc: + raise Refusal("args", f"{subject} is not an integer, decimal, or rational: {text!r}") from exc + + +def frac_str(value: Fraction) -> str: + return str(value.numerator) if value.denominator == 1 else f"{value.numerator}/{value.denominator}" + + +def approx(value: Fraction, places: int = 12) -> str: + """Return JACKAL's decimal rendering for reading only. + + The ``places`` parameter remains for source compatibility but does not ask + Python to round or divide. JACKAL owns the rendering as well as the exact + rational. Reuse a matching delegated result when one exists; otherwise + delegate an identity expression and record it in the trace. + """ + del places + wanted = frac_str(value) + for item in reversed(_TRACE): + if item.get("tool") == "jackal_exact" and item.get("exact") == wanted: + rendered = item.get("approx") + if isinstance(rendered, str) and rendered: + return rendered + exact(wanted) + rendered = _TRACE[-1].get("approx") if _TRACE else None + if not isinstance(rendered, str) or not rendered: + raise Refusal("kernel-error", "jackal_exact returned no decimal rendering") + return rendered + + +# --------------------------------------------------------------------------- +# Definitional unit table +# +# ONLY exact-by-definition conversions appear here. Each factor is a rational +# converting the unit to its dimension's base, alongside the instrument that +# makes it exact. A unit whose factor is empirical, drifting, or transcendental +# is absent ON PURPOSE, and absence produces a refusal rather than a guess. +# +# The notable absences, each a real laundering site: +# - `year` / `month` : no exact second count. `julian_year` is exact and named. +# - `degree`/`radian` : the factor is pi/180, outside the exact-rational fragment. +# - `tonne-of-TNT`, `calorie (nutritional)` : convention-dependent. +# --------------------------------------------------------------------------- + +SI = "SI base/derived definition (BIPM SI Brochure, 9th ed.)" +YARD_POUND = "International yard and pound agreement (1959), exact by definition" +IEC = "IEC 80000-13 binary prefixes, exact by definition" + +UNITS: dict[str, dict[str, tuple[str, str]]] = { + "length": { # base: metre + "m": ("1", SI), "metre": ("1", SI), "meter": ("1", SI), + "km": ("1000", SI), "cm": ("1/100", SI), "mm": ("1/1000", SI), + "um": ("1/1000000", SI), "nm": ("1/1000000000", SI), + "in": ("0.0254", YARD_POUND), "inch": ("0.0254", YARD_POUND), + "ft": ("0.3048", YARD_POUND), "foot": ("0.3048", YARD_POUND), + "yd": ("0.9144", YARD_POUND), "yard": ("0.9144", YARD_POUND), + "mi": ("1609.344", YARD_POUND), "mile": ("1609.344", YARD_POUND), + "nmi": ("1852", "Exact by definition (BIPM/IHO international nautical mile)"), + }, + "mass": { # base: kilogram + "kg": ("1", SI), "g": ("1/1000", SI), "mg": ("1/1000000", SI), + "t": ("1000", SI), "tonne": ("1000", SI), + "lb": ("0.45359237", YARD_POUND), "pound": ("0.45359237", YARD_POUND), + "oz": ("0.45359237/16", YARD_POUND), + "st": ("0.45359237*14", YARD_POUND), "stone": ("0.45359237*14", YARD_POUND), + "short_ton": ("0.45359237*2000", YARD_POUND), + "long_ton": ("0.45359237*2240", YARD_POUND), + }, + "time": { # base: second + "s": ("1", SI), "sec": ("1", SI), "second": ("1", SI), + "ms": ("1/1000", SI), "us": ("1/1000000", SI), "ns": ("1/1000000000", SI), + "min": ("60", SI), "minute": ("60", SI), + "h": ("3600", SI), "hr": ("3600", SI), "hour": ("3600", SI), + "day": ("86400", "Exact by definition (86400 SI seconds; NOT a solar day)"), + "week": ("604800", "Exact by definition (7 x 86400 s)"), + "julian_year": ("31557600", "IAU Julian year, exact by definition (365.25 x 86400 s)"), + }, + "volume": { # base: litre + "l": ("1", SI), "litre": ("1", SI), "liter": ("1", SI), + "ml": ("1/1000", SI), "m3": ("1000", SI), + "us_gal": ("3.785411784", "US gallon = 231 cubic inches exactly (yard-and-pound)"), + "us_qt": ("3.785411784/4", "US quart = 1/4 US gallon exactly"), + "us_pt": ("3.785411784/8", "US pint = 1/8 US gallon exactly"), + "us_floz": ("3.785411784/128", "US fluid ounce = 1/128 US gallon exactly"), + "imp_gal": ("4.54609", "Imperial gallon, exact by definition (UK Weights and Measures Act 1985)"), + "imp_pt": ("4.54609/8", "Imperial pint = 1/8 imperial gallon exactly"), + "imp_floz": ("4.54609/160", "Imperial fluid ounce = 1/160 imperial gallon exactly"), + }, + "speed": { # base: metre per second + "m/s": ("1", SI), + "km/h": ("1000/3600", SI), + "mph": ("1609.344/3600", YARD_POUND), + "kn": ("1852/3600", "Knot = one nautical mile per hour, exact by definition"), + "knot": ("1852/3600", "Knot = one nautical mile per hour, exact by definition"), + }, + "energy": { # base: joule + "j": ("1", SI), "joule": ("1", SI), "kj": ("1000", SI), "mj": ("1000000", SI), + "wh": ("3600", SI), "kwh": ("3600000", SI), "mwh": ("3600000000", SI), + "cal": ("4.184", "Thermochemical calorie, exact by definition"), + "kcal": ("4184", "Thermochemical kilocalorie, exact by definition"), + "btu": ("1055.05585262", "BTU (International Table), exact by definition"), + "ev": ("1.602176634*10^-19", "SI 2019 redefinition: elementary charge is exact"), + }, + "power": { # base: watt + "w": ("1", SI), "watt": ("1", SI), "kw": ("1000", SI), "mw": ("1000000", SI), + "hp": ("550*0.3048*0.45359237*9.80665", + "Mechanical horsepower = 550 ft.lbf/s, exact via the defined standard gravity 9.80665 m/s^2"), + }, + "data": { # base: byte -- the single most productive laundering site in computing + "b": ("1", IEC), "byte": ("1", IEC), "bit": ("1/8", IEC), + "kb": ("1000", "SI decimal prefix: kB = 10^3 bytes"), + "mb": ("1000000", "SI decimal prefix: MB = 10^6 bytes"), + "gb": ("1000000000", "SI decimal prefix: GB = 10^9 bytes"), + "tb": ("1000000000000", "SI decimal prefix: TB = 10^12 bytes"), + "kib": ("1024", IEC), "mib": ("1048576", IEC), + "gib": ("1073741824", IEC), "tib": ("1099511627776", IEC), + }, +} + +# Units whose absence is deliberate, with the reason stated at the point of +# refusal. Guessing here would be the exact failure this subsystem exists to prevent. +REFUSED_UNITS = { + "year": "a calendar year has no exact second count (365 or 366 days); use `julian_year` for the exact IAU definition, or jackal_date_delta for civil dates", + "yr": "a calendar year has no exact second count; use `julian_year` or jackal_date_delta", + "month": "a calendar month has no fixed length (28-31 days); use jackal_date_delta on real dates", + "deg": "degree-to-radian requires pi, which is outside the exact-rational fragment", + "degree": "degree-to-radian requires pi, which is outside the exact-rational fragment", + "rad": "radian-to-degree requires pi, which is outside the exact-rational fragment", + "radian": "radian-to-degree requires pi, which is outside the exact-rational fragment", +} + +TEMPERATURE = {"c", "celsius", "f", "fahrenheit", "k", "kelvin", "r", "rankine"} + + +def find_unit(name: str) -> tuple[str, str, str]: + """Resolve a unit to (dimension, factor-expression, authority) or refuse.""" + key = name.strip().lower().replace("^", "").replace(" ", "") + if key in REFUSED_UNITS: + raise Refusal("undefined-unit", f"unit {name!r} is deliberately absent: {REFUSED_UNITS[key]}") + for dimension, table in UNITS.items(): + if key in table: + factor, authority = table[key] + return dimension, factor, authority + if key in TEMPERATURE: + return "temperature", "", "affine scale; handled separately" + known = sorted({u for table in UNITS.values() for u in table} | TEMPERATURE) + raise Refusal( + "undefined-unit", + f"unit {name!r} is not in the definitional table, and JACKAL does not guess conversion " + f"factors. Known units: {', '.join(known)}", + ) + + +# --------------------------------------------------------------------------- +# jackal_convert -- definitional unit conversion +# --------------------------------------------------------------------------- + +_TEMP_TO_K = { + "c": "({v}) + 273.15", "celsius": "({v}) + 273.15", + "f": "(({v}) - 32) * 5/9 + 273.15", "fahrenheit": "(({v}) - 32) * 5/9 + 273.15", + "k": "({v})", "kelvin": "({v})", + "r": "({v}) * 5/9", "rankine": "({v}) * 5/9", +} +_K_TO_TEMP = { + "c": "({v}) - 273.15", "celsius": "({v}) - 273.15", + "f": "(({v}) - 273.15) * 9/5 + 32", "fahrenheit": "(({v}) - 273.15) * 9/5 + 32", + "k": "({v})", "kelvin": "({v})", + "r": "({v}) * 9/5", "rankine": "({v}) * 9/5", +} + + +def tool_convert(args: dict) -> dict: + value_text = args.get("value") + frm = args.get("from_unit") + to = args.get("to_unit") + for name, val in (("value", value_text), ("from_unit", frm), ("to_unit", to)): + if not isinstance(val, str) or not val.strip(): + raise Refusal("args", f"{name} is required and must be a non-empty string") + value = as_fraction(value_text, "value") + from_dim, from_factor, from_auth = find_unit(frm) + to_dim, to_factor, to_auth = find_unit(to) + if from_dim != to_dim: + raise Refusal( + "dimension-mismatch", + f"cannot convert {frm!r} ({from_dim}) to {to!r} ({to_dim}); these are different " + "physical dimensions and no conversion between them exists without a declared " + "physical relation the JACKAL measurement subsystem was not given", + ) + + if from_dim == "temperature": + # Affine scales: a temperature is a point, not a magnitude, so the + # zero offsets do not cancel and a plain ratio would be wrong. + kelvin = exact(_TEMP_TO_K[frm.strip().lower()].format(v=frac_str(value))) + result = exact(_K_TO_TEMP[to.strip().lower()].format(v=frac_str(kelvin))) + auth = "Exact by definition (ITS-90 fixed offsets 273.15 and 459.67; scale factors 9/5)" + note = ("temperature is an AFFINE scale: this converts a POINT on the scale. " + "A temperature DIFFERENCE converts differently (offsets cancel).") + else: + result = exact(f"({frac_str(value)}) * ({from_factor}) / ({to_factor})") + auth = f"from: {from_auth} | to: {to_auth}" + note = None + + fields = { + "exact": frac_str(result), + "approx": approx(result), + "from": frm, "to": to, "dimension": from_dim, + "definition_authority": auth, + } + if note: + fields["scale_note"] = note + non_claims = [ + "This converts a NUMBER between units defined to be exactly related; it does not verify that the input quantity was measured correctly", + "Only definitional (exact-by-definition) conversions are in the table; empirical or convention-dependent factors are absent and refuse", + ] + if note: + non_claims.insert(0, "This is a SCALE POINT conversion, NOT a temperature difference") + return envelope( + status="exact", lane="jackal-measure-convert", + assurance="exact rational conversion between definitionally-related units (not checker-covered)", + parsed=f"{frac_str(value)} {frm} -> {to}", + fields=fields, non_claims=non_claims, + ) + + +# --------------------------------------------------------------------------- +# jackal_rate_apply -- the exact-given lane +# --------------------------------------------------------------------------- + +def tool_rate_apply(args: dict) -> dict: + """Apply a declared rate. The arithmetic is exact; the RATE is not mathematics. + + Every argument below is required, and that is the entire design. A rate with + no source and no as-of date is a number of unknown origin, and multiplying + by it silently promotes that unknown origin to the authority of the result. + Refusing at the door is what makes the provenance impossible to skip. + """ + value_text = args.get("value") + rate_text = args.get("rate") + source = args.get("rate_source") + asof = args.get("rate_asof") + frm = args.get("from_label") or "from" + to = args.get("to_label") or "to" + + missing = [n for n, v in (("value", value_text), ("rate", rate_text), + ("rate_source", source), ("rate_asof", asof)) + if not isinstance(v, str) or not v.strip()] + if missing: + raise Refusal( + "undeclared-datum", + f"missing required declaration(s): {', '.join(missing)}. A rate is a DATUM, not " + "mathematics: JACKAL will not apply one that does not carry its source and as-of " + "date, because the result would inherit an authority the rate never had.", + ) + value = as_fraction(value_text, "value") + rate = as_fraction(rate_text, "rate") + if rate <= 0: + raise Refusal("args", f"rate must be positive; got {rate_text!r}") + result = exact(f"({frac_str(value)}) * ({frac_str(rate)})") + return envelope( + status="exact-given", lane="jackal-measure-rate", + assurance="exact rational arithmetic CONDITIONAL on the declared rate below; the rate itself is unverified", + parsed=f"{frac_str(value)} {frm} x {frac_str(rate)} -> {to}", + given={ + "datum": "conversion rate", + "rate": frac_str(rate), + "rate_approx": approx(rate), + "direction": f"1 {frm} = {frac_str(rate)} {to}", + "source": source.strip(), + "as_of": asof.strip(), + }, + fields={"exact": frac_str(result), "approx": approx(result), + "from_label": frm, "to_label": to}, + non_claims=[ + "JACKAL did NOT verify the rate, its source, or its as-of date; all three are reported as supplied", + "This result is exact ONLY under the declared rate; a different rate yields a different exact result", + "`exact-given` is NOT a weaker `exact`: the arithmetic is exact and the datum is unverified. Do not report it as `exact`, and do not soften it to `estimated`", + "Rates change: an as-of date in the past does not describe the present", + ], + ) + + +# --------------------------------------------------------------------------- +# jackal_percent +# --------------------------------------------------------------------------- + +def tool_percent(args: dict) -> dict: + op = (args.get("op") or "").strip().lower() + a_text, b_text = args.get("a"), args.get("b") + if not isinstance(a_text, str) or not isinstance(b_text, str): + raise Refusal("args", "both `a` and `b` are required strings") + a, b = as_fraction(a_text, "a"), as_fraction(b_text, "b") + + ops = { + "of": ("({a}) * ({b}) / 100", "a% of b", "value"), + "change": ("(({b}) - ({a})) * 100 / ({a})", "percent change from a to b", "percent"), + "ratio": ("({a}) * 100 / ({b})", "a as a percentage of b", "percent"), + "points": ("({b}) - ({a})", "difference in PERCENTAGE POINTS between a% and b%", "percentage_points"), + "increase": ("({a}) * (1 + ({b})/100)", "a increased by b%", "value"), + "decrease": ("({a}) * (1 - ({b})/100)", "a decreased by b%", "value"), + } + if op not in ops: + raise Refusal("args", f"op must be one of {', '.join(sorted(ops))}; got {op!r}") + if op in ("change", "ratio") and (a if op == "change" else b) == 0: + raise Refusal("domain", f"op {op!r} divides by zero for the given inputs") + + template, meaning, unit = ops[op] + result = exact(template.format(a=frac_str(a), b=frac_str(b))) + non_claims = [ + "A percentage is meaningless without its base; the base used here is exactly as supplied", + ] + if op in ("change", "ratio", "points"): + non_claims.insert(0, + "PERCENT vs PERCENTAGE POINTS are different quantities and are routinely conflated: " + "`change`/`ratio` return PERCENT (relative), `points` returns PERCENTAGE POINTS (absolute). " + f"This result is in {unit.upper()}.") + if op == "change": + non_claims.append( + "Percent change is asymmetric: a rise of x% followed by a fall of x% does not return to the start") + return envelope( + status="exact", lane="jackal-measure-percent", + assurance="exact rational arithmetic (not checker-covered)", + parsed=f"{op}(a={frac_str(a)}, b={frac_str(b)}) = {meaning}", + fields={"exact": frac_str(result), "approx": approx(result), + "result_unit": unit, "meaning": meaning}, + non_claims=non_claims, + ) + + +# --------------------------------------------------------------------------- +# jackal_date_delta -- civil-date arithmetic +# --------------------------------------------------------------------------- + +CALENDAR = "proleptic Gregorian" + + +def _civil_date(text: str, subject: str) -> datetime.date: + if not isinstance(text, str) or not re.fullmatch(r"\d{4}-\d{2}-\d{2}", text.strip()): + raise Refusal( + "args", + f"{subject} must be an ISO civil date YYYY-MM-DD; got {text!r}. This JACKAL lane takes civil " + "dates only: a wall-clock time carries a timezone, and a timezone is a datum " + "(tzdata rules, DST transitions) that this lane does not accept.", + ) + try: + return datetime.date.fromisoformat(text.strip()) + except ValueError as exc: + raise Refusal("args", f"{subject} is not a real calendar date: {text!r} ({exc})") from exc + + +def tool_date_delta(args: dict) -> dict: + op = (args.get("op") or "").strip().lower() + given = { + "datum": "calendar convention", + "calendar": CALENDAR, + "day_length": "one civil day, NOT 86400 SI seconds", + "excludes": "timezones, DST transitions, leap seconds", + "source": "Python datetime.date proleptic-Gregorian calendar semantics", + "as_of": "JACKAL measurement definition 1.1.0 (this convention is not time-varying)", + } + non_claims = [ + "Civil-date arithmetic ONLY: no timezone, no DST, no leap seconds, no wall-clock times", + "A civil day is not a fixed number of seconds; do not convert this result to seconds by multiplying by 86400 unless that assumption is stated", + f"Dates before the 1582 Gregorian adoption are interpreted {CALENDAR}ly and will NOT match historical Julian records", + "`exact-given` is NOT a weaker `exact`: the day count is exact under the declared calendar convention", + ] + + if op == "diff": + start = _civil_date(args.get("start"), "start") + end = _civil_date(args.get("end"), "end") + # The calendar->ordinal mapping is library calendar logic, not arithmetic; + # the SUBTRACTION is what goes to the kernel. + days = exact(f"{end.toordinal()} - {start.toordinal()}") + fields = { + "exact_days": frac_str(days), + "start": start.isoformat(), "end": end.isoformat(), + "start_ordinal": str(start.toordinal()), "end_ordinal": str(end.toordinal()), + "direction": "end - start (negative means end precedes start)", + } + parsed = f"diff({start.isoformat()} -> {end.isoformat()})" + elif op == "add": + start = _civil_date(args.get("start"), "start") + days_text = args.get("days") + offset = as_fraction(days_text, "days") + if offset.denominator != 1: + raise Refusal("args", f"days must be a whole number of civil days; got {days_text!r}") + target_ordinal = exact(f"{start.toordinal()} + ({frac_str(offset)})") + n = int(target_ordinal) + if not 1 <= n <= datetime.date.max.toordinal(): + raise Refusal("domain", f"resulting date falls outside the representable calendar range (ordinal {n})") + result = datetime.date.fromordinal(n) + fields = { + "result": result.isoformat(), "start": start.isoformat(), + "days_added": frac_str(offset), "result_ordinal": str(n), + } + parsed = f"add({start.isoformat()} + {frac_str(offset)} days)" + else: + raise Refusal("args", f"op must be 'diff' or 'add'; got {op!r}") + + return envelope( + status="exact-given", lane="jackal-measure-date", + assurance="exact day arithmetic CONDITIONAL on the declared calendar convention below", + parsed=parsed, given=given, fields=fields, non_claims=non_claims, + ) + + +# --------------------------------------------------------------------------- +# jackal_stat -- descriptive statistics, honestly classed +# --------------------------------------------------------------------------- + +def tool_stat(args: dict) -> dict: + sample = args.get("sample") + if isinstance(sample, str): + parts = [p for p in re.split(r"[\s;]+", sample.strip()) if p] + elif isinstance(sample, list): + parts = [str(p) for p in sample] + else: + raise Refusal("args", "sample must be a whitespace-separated string or a list of numbers") + if not parts: + raise Refusal("args", "sample is empty") + values = [as_fraction(p, f"sample[{i}]") for i, p in enumerate(parts)] + n = len(values) + + total = exact(" + ".join(f"({frac_str(v)})" for v in values)) + mean = exact(f"({frac_str(total)}) / {n}") + ordered = sorted(values) + if n % 2 == 1: + median = ordered[n // 2] + median_note = "middle order statistic" + else: + median = exact(f"(({frac_str(ordered[n//2 - 1])}) + ({frac_str(ordered[n//2])})) / 2") + median_note = "mean of the two central order statistics" + + ss = exact(" + ".join(f"(({frac_str(v)}) - ({frac_str(mean)}))^2" for v in values)) + pop_var = exact(f"({frac_str(ss)}) / {n}") + field_status = {k: "exact" for k in + ("n", "sum", "mean", "median", "min", "max", "range", "population_variance")} + fields = { + "n": str(n), + "sum": frac_str(total), "mean": frac_str(mean), "mean_approx": approx(mean), + "median": frac_str(median), "median_note": median_note, + "min": frac_str(ordered[0]), "max": frac_str(ordered[-1]), + "range": frac_str(exact(f"({frac_str(ordered[-1])}) - ({frac_str(ordered[0])})")), + "population_variance": frac_str(pop_var), + } + if n > 1: + sample_var = exact(f"({frac_str(ss)}) / {n - 1}") + fields["sample_variance"] = frac_str(sample_var) + field_status["sample_variance"] = "exact" + else: + fields["sample_variance"] = "undefined (n=1: division by n-1 = 0)" + field_status["sample_variance"] = "undefined" + + non_claims = [ + "DESCRIPTIVE ONLY. These summarize the supplied numbers and nothing else", + "NOT inferential: no population parameter, confidence interval, significance, or distributional assumption is claimed or implied", + "The mean is not robust to outliers; the median is reported alongside it for that reason", + "n is the count of numbers SUPPLIED, which is not evidence that the sample was drawn correctly or is representative of anything", + ] + + if args.get("include_stddev"): + lo, hi = sqrt_bound(pop_var) + fields["population_stddev_enclosure"] = [frac_str(lo), frac_str(hi)] + fields["population_stddev_approx"] = f"[{approx(lo)}, {approx(hi)}]" + field_status["population_stddev_enclosure"] = "formal-bounded" + non_claims.insert(0, + "The field `population_stddev_enclosure` is FORMAL-BOUNDED (a Lean-checked ENCLOSURE), " + "NOT exact, and NOT a single number. The top-level `exact` describes the other fields. " + "Report it as an interval, never as a point value.") + + return envelope( + status="exact", lane="jackal-measure-stat", + assurance="exact rational descriptive statistics; see `field_status` for any field carrying a different class", + parsed=f"n={n} sample=[{', '.join(frac_str(v) for v in values)}]", + fields={**fields, "field_status": field_status}, + non_claims=non_claims, + ) + + +# --------------------------------------------------------------------------- +# jackal_compare -- dimension-aware comparison +# --------------------------------------------------------------------------- + +BASE_UNITS = { + "dimensionless": "one", + "length": "m", + "mass": "kg", + "time": "s", + "volume": "l", + "speed": "m/s", + "energy": "j", + "power": "w", + "data": "b", + "temperature": "k", +} +COMPARISON_LABELS = {"usd", "eur", "gbp", "jpy"} + + +def _unit_key(name: str) -> str: + return name.strip().lower().replace("^", "").replace(" ", "") + + +def _quantity_in_base(value_text: object, unit_text: object, subject: str) -> dict: + """Normalize one quantity through JACKAL, returning base-unit metadata.""" + value = as_fraction(value_text, f"{subject}_value") + if unit_text is None: + normalized = exact(frac_str(value)) + return { + "dimension": "dimensionless", + "base_unit": BASE_UNITS["dimensionless"], + "value": normalized, + "authority": "dimensionless identity", + "input_unit": None, + } + if not isinstance(unit_text, str) or not unit_text.strip(): + raise Refusal("args", f"{subject}_unit must be a non-empty string when supplied") + label_key = _unit_key(unit_text) + if label_key in COMPARISON_LABELS: + normalized = exact(frac_str(value)) + label = label_key.upper() + return { + "dimension": f"currency:{label}", + "base_unit": label, + "value": normalized, + "authority": "nominal currency-label identity; no exchange-rate relation is implied", + "input_unit": unit_text.strip(), + } + dimension, factor, authority = find_unit(unit_text) + if dimension == "temperature": + key = _unit_key(unit_text) + normalized = exact(_TEMP_TO_K[key].format(v=frac_str(value))) + authority = "Exact affine scale definition; normalized to kelvin scale points" + else: + normalized = exact(f"({frac_str(value)}) * ({factor})") + return { + "dimension": dimension, + "base_unit": BASE_UNITS[dimension], + "value": normalized, + "authority": authority, + "input_unit": unit_text.strip(), + } + + +def tool_compare(args: dict) -> dict: + """Compare dimensioned magnitudes, requiring provenance across dimensions.""" + a = _quantity_in_base(args.get("a_value"), args.get("a_unit"), "a") + b = _quantity_in_base(args.get("b_value"), args.get("b_unit"), "b") + same_dimension = a["dimension"] == b["dimension"] + supplied_rate = args.get("rate") + given = None + + if same_dimension: + if any(args.get(key) not in (None, "") for key in ("rate", "rate_source", "rate_asof")): + raise Refusal( + "args", + "a declared rate is only accepted when the dimensions differ; same-dimension " + "quantities are compared through their exact definitional base-unit conversions", + ) + a_comparable = a["value"] + b_comparable = b["value"] + base_unit = a["base_unit"] + status = "exact" + assurance = "exact rational comparison after definitional base-unit normalization (not checker-covered)" + else: + if supplied_rate is None: + raise Refusal( + "dimension-mismatch-no-rate", + f"cannot compare {a['dimension']} with {b['dimension']} without a declared rate. " + f"Supply `rate`, `rate_source`, and `rate_asof`, where 1 {a['base_unit']} " + f"of a is declared equal to `rate` {b['base_unit']} of b.", + ) + if not isinstance(supplied_rate, str) or not supplied_rate.strip(): + raise Refusal("args", "rate must be a non-empty numeric string when dimensions differ") + source = args.get("rate_source") + asof = args.get("rate_asof") + missing = [ + name for name, value in (("rate_source", source), ("rate_asof", asof)) + if not isinstance(value, str) or not value.strip() + ] + if missing: + raise Refusal( + "undeclared-datum", + f"missing required declaration(s): {', '.join(missing)}. A cross-dimension " + "comparison rate is a DATUM, not mathematics, and must carry source and as-of.", + ) + rate = as_fraction(supplied_rate, "rate") + if rate <= 0: + raise Refusal("args", f"rate must be positive; got {supplied_rate!r}") + a_comparable = exact(f"({frac_str(a['value'])}) * ({frac_str(rate)})") + b_comparable = b["value"] + base_unit = b["base_unit"] + status = "exact-given" + assurance = ( + "exact rational comparison CONDITIONAL on the declared cross-dimension rate; " + "the rate itself is unverified" + ) + given = { + "datum": "cross-dimension comparison rate", + "rate": frac_str(rate), + "rate_approx": approx(rate), + "direction": f"1 {a['base_unit']} ({a['dimension']}) = {frac_str(rate)} " + f"{b['base_unit']} ({b['dimension']})", + "source": source.strip(), + "as_of": asof.strip(), + } + + difference = exact(f"({frac_str(a_comparable)}) - ({frac_str(b_comparable)})") + if a_comparable > b_comparable: + verdict = "a_greater" + elif b_comparable > a_comparable: + verdict = "b_greater" + else: + verdict = "equal" + + fields = { + "verdict": verdict, + "difference": frac_str(difference), + "base_unit": base_unit, + "a_in_base": frac_str(a_comparable), + "b_in_base": frac_str(b_comparable), + "a_dimension": a["dimension"], + "b_dimension": b["dimension"], + "definition_authority": {"a": a["authority"], "b": b["authority"]}, + } + if not same_dimension: + fields["a_before_declared_rate"] = frac_str(a["value"]) + fields["a_original_base_unit"] = a["base_unit"] + if b_comparable != 0: + fields["ratio"] = frac_str( + exact(f"({frac_str(a_comparable)}) / ({frac_str(b_comparable)})") + ) + + parsed = ( + f"compare(a={frac_str(a['value'])} {a['base_unit']}[{a['dimension']}], " + f"b={frac_str(b['value'])} {b['base_unit']}[{b['dimension']}])" + ) + if given is not None: + parsed += f" under ({given['direction']})" + non_claims = [ + "A comparison of magnitudes is not a comparison of value, quality, or suitability", + "Equal magnitudes in different units are not the same quantity", + ] + if given is not None: + non_claims.extend([ + "The verdict may reverse under a different declared rate; state the rate whenever reporting the verdict", + "JACKAL did NOT verify the rate, its source, or its as-of date; all are reported as supplied", + "`exact-given` is NOT a weaker `exact`: the arithmetic is exact and the datum is unverified. Do not report it as `exact`, and do not soften it to `estimated`", + ]) + if a["dimension"] == "temperature" or b["dimension"] == "temperature": + non_claims.insert(0, "Temperature inputs are SCALE POINTS normalized to kelvin, NOT temperature differences") + return envelope( + status=status, + lane="jackal-measure-compare", + assurance=assurance, + parsed=parsed, + fields=fields, + given=given, + non_claims=non_claims, + ) + + +# --------------------------------------------------------------------------- +# jackal_scan -- lexical noticing prosthetic +# --------------------------------------------------------------------------- + +DERIVATION_CUES = ( + "total", "sum", "combined", "difference", "average", "mean", "median", + "per", "each", "times", "twice", "half", "double", "increase", "decrease", + "up from", "down from", "faster", "slower", "cheaper", "more than", "less than", + "about", "roughly", "approximately", "estimated", +) +OBSERVATION_CUES = ( + "listed", "reported", "read", "says", "shows", "according to", "output", + "returned", "printed", "per the", +) + + +def _number_pattern() -> str: + # Keep scientific notation as one lexical token. The ordering is + # intentional: coefficient-times-ten, e notation, and powers of ten must + # win before the final plain-decimal alternative can consume a prefix. + decimal_number = r"\d[\d,]*(?:\.\d+)?" + signed_exponent = r"[+-]?\d+" + braced_exponent = rf"\{{{signed_exponent}\}}" + superscript_exponent = r"[⁺⁻]?[⁰¹²³⁴⁵⁶⁷⁸⁹]+" + power = rf"(?:\^\s*(?:{braced_exponent}|{signed_exponent})|\*\*\s*(?:{braced_exponent}|{signed_exponent})|{superscript_exponent})" + ten_power = rf"10\s*{power}" + return rf"(?:{decimal_number}\s*[×·*]\s*{ten_power}|{decimal_number}[eE]{signed_exponent}|{ten_power}|{decimal_number})" + + +def _scan_patterns() -> list[tuple[str, re.Pattern]]: + known_units = {unit for table in UNITS.values() for unit in table} + known_units.update(TEMPERATURE) + known_units.update(REFUSED_UNITS) + unit_alt = "|".join(re.escape(unit) for unit in sorted(known_units, key=len, reverse=True)) + number = _number_pattern() + return [ + ("version", re.compile(r"(?\d+\.\d+(?:\.\d+)+)(?![\w.])")), + ("date_iso", re.compile(r"(?\d{4}-\d{2}-\d{2})(?!\d)")), + ("currency", re.compile(rf"[$£€¥]\s?(?P{number})")), + ("currency", re.compile(rf"(?{number})\s?(?:USD|EUR|GBP|JPY)\b", re.IGNORECASE)), + ("percentage", re.compile(rf"(?{number})\s?%")), + ("dimensioned", re.compile(rf"(?{number})\s*(?P{unit_alt})(?![\w/])", re.IGNORECASE)), + # Sentence punctuation after a numeral is permitted; a dot/comma only + # blocks the match when it begins another numeric component. + ("plain", re.compile(rf"(?{number})(?!\w|[.,]\d)")), + ] + + +SCAN_PATTERNS = _scan_patterns() +CURRENCY_MARKERS = re.compile(r"[$£€¥]|\b(?:USD|EUR|GBP|JPY)\b", re.IGNORECASE) + + +def _cue_hits(context: str) -> list[str]: + lower = context.lower() + hits = [cue for cue in DERIVATION_CUES if cue in lower] + symbol_tests = ( + ("+", "+" in context), + ("-", bool(re.search(r"(?:\d|\s)-\s*(?:\d|[$£€¥])", context))), + ("×", "×" in context), + ("x", bool(re.search(r"(? list[str]: + lower = context.lower() + hits = [cue for cue in OBSERVATION_CUES if cue in lower] + before = text[:full_start].rstrip() + after = text[full_end:].lstrip() + if before and after and (before[-1], after[0]) in { + ('"', '"'), ("'", "'"), ("“", "”"), ("‘", "’"), + }: + hits.append("quoted") + return list(dict.fromkeys(hits)) + + +def _currency_marker_set(text: str) -> set[str]: + aliases = {"$": "USD", "£": "GBP", "€": "EUR", "¥": "YEN"} + markers = set() + for match in CURRENCY_MARKERS.finditer(text): + token = match.group(0) + markers.add(aliases.get(token, token.upper())) + return markers + + +def tool_scan(args: dict) -> dict: + text = args.get("text") + if not isinstance(text, str) or not text: + raise Refusal("args", "text is required and must be a non-empty string") + window = args.get("context_window", 60) + if isinstance(window, bool) or not isinstance(window, int) or window < 0: + raise Refusal("args", "context_window must be a non-negative integer") + + candidates = [] + for priority, (kind, pattern) in enumerate(SCAN_PATTERNS): + for match in pattern.finditer(text): + candidates.append({ + "priority": priority, + "kind": kind, + "full_start": match.start(), + "full_end": match.end(), + "offset": match.start("num"), + "text": match.group("num"), + "unit": match.groupdict().get("unit"), + }) + candidates.sort(key=lambda item: ( + item["full_start"], item["priority"], -(item["full_end"] - item["full_start"]) + )) + + selected = [] + occupied_until = -1 + for candidate in candidates: + if candidate["full_start"] < occupied_until: + continue + selected.append(candidate) + occupied_until = candidate["full_end"] + + cross_currency = len(_currency_marker_set(text)) > 1 + findings = [] + by_kind: dict[str, int] = {} + unrouted = [] + for candidate in selected: + context_start = max(0, candidate["full_start"] - window) + context_end = min(len(text), candidate["full_end"] + window) + context = text[context_start:context_end] + cue_context = ( + text[context_start:candidate["full_start"]] + + " " * (candidate["full_end"] - candidate["full_start"]) + + text[candidate["full_end"]:context_end] + ) + derived = _cue_hits(cue_context) + observed = _observation_hits( + text, candidate["full_start"], candidate["full_end"], cue_context + ) + kind = candidate["kind"] + if kind == "version": + route = None + must_declare = False + elif kind == "currency" and cross_currency: + route = "jackal_rate_apply / jackal_compare" + must_declare = True + elif kind == "currency": + route = "jackal_exact" if derived else None + must_declare = bool(derived) + elif kind == "percentage": + route = "jackal_percent" + must_declare = bool(derived) + elif kind == "dimensioned": + route = "jackal_convert" + must_declare = bool(derived) + elif kind == "date_iso": + route = "jackal_date_delta" if derived else None + must_declare = bool(derived) + else: + route = "jackal_exact" if derived else None + must_declare = bool(derived) + + finding = { + "text": candidate["text"], + "offset": candidate["offset"], + "kind": kind, + "context": context, + "cues_derived": derived, + "cues_observed": observed, + "route": route, + "must_declare": must_declare, + } + if candidate["unit"]: + finding["unit"] = candidate["unit"] + findings.append(finding) + by_kind[kind] = by_kind.get(kind, 0) + 1 + if must_declare: + unrouted.append({ + "text": candidate["text"], + "offset": candidate["offset"], + "kind": kind, + "route": route, + }) + + summary = { + "total_numerals": len(findings), + "by_kind": by_kind, + "flagged": len(unrouted), + "unrouted_derived": unrouted, + } + return envelope( + status="checked", + lane="jackal-measure-scan", + assurance="checked lexical classification only; no numeral was mathematically verified", + parsed=f"text={json.dumps(text, ensure_ascii=False)} context_window={window}", + fields={"numerals": findings, "summary": summary}, + non_claims=[ + "This is a LEXICAL scan. It reads characters, not provenance", + "The ABSENCE of a flag is NOT evidence that a numeral was observed rather than derived. A clean scan means nothing was detected, not that nothing is wrong", + "Cue matching is heuristic and both over- and under-fires. Version strings, ordinals, identifiers, and quoted figures are frequently misclassified", + "This tool cannot verify any numeral. It only suggests where one should be sent", + "Offsets and counts are lexical metadata returned by Python string/regex operations, not JACKAL arithmetic results", + ], + ) + + +# --------------------------------------------------------------------------- +# Tool registry and stdio MCP server +# --------------------------------------------------------------------------- + +DRAFT7 = "http://json-schema.org/draft-07/schema#" +NUMBER_STRING = { + "type": "string", + "minLength": 1, + "description": "Unambiguous integer, decimal, or rational string; commas refuse", +} +NONEMPTY_STRING = {"type": "string", "minLength": 1} + + +def _schema(properties: dict, required: list[str]) -> dict: + return { + "$schema": DRAFT7, + "type": "object", + "properties": properties, + "required": required, + "additionalProperties": False, + } + + +TOOL_REGISTRY = { + "jackal_convert": ( + tool_convert, + "Convert definitionally related units through JACKAL. Returns status=exact. Refuses ambiguous literals, undefined units, deliberately excluded units, dimension mismatches, kernel loss, and kernel refusal.", + _schema({ + "value": NUMBER_STRING, + "from_unit": {**NONEMPTY_STRING, "description": "Source unit token from the JACKAL measurement subsystem's exact-by-definition table"}, + "to_unit": {**NONEMPTY_STRING, "description": "Target unit token in the same physical dimension"}, + }, ["value", "from_unit", "to_unit"]), + ), + "jackal_rate_apply": ( + tool_rate_apply, + "Apply a caller-declared rate through JACKAL. Returns status=exact-given, never exact. Refuses unless value, positive rate, rate_source, and rate_asof are all declared; also refuses kernel loss or kernel refusal.", + _schema({ + "value": NUMBER_STRING, + "rate": NUMBER_STRING, + "rate_source": {**NONEMPTY_STRING, "description": "Caller-declared provenance for the rate; the JACKAL measurement subsystem does not verify it"}, + "rate_asof": {**NONEMPTY_STRING, "description": "Caller-declared date/time or period when the rate applied"}, + "from_label": {**NONEMPTY_STRING, "description": "Optional source quantity label"}, + "to_label": {**NONEMPTY_STRING, "description": "Optional target quantity label"}, + }, ["value", "rate", "rate_source", "rate_asof"]), + ), + "jackal_percent": ( + tool_percent, + "Perform percentage operations through JACKAL. Returns status=exact and distinguishes percent from percentage points. Refuses unknown operations, ambiguous literals, division by zero, kernel loss, and kernel refusal.", + _schema({ + "op": {"type": "string", "enum": ["of", "change", "ratio", "points", "increase", "decrease"]}, + "a": NUMBER_STRING, + "b": NUMBER_STRING, + }, ["op", "a", "b"]), + ), + "jackal_date_delta": ( + tool_date_delta, + "Compute proleptic-Gregorian civil-date differences or additions through JACKAL. Returns status=exact-given. Refuses wall-clock timestamps, invalid dates, fractional day offsets, range overflow, kernel loss, and kernel refusal.", + _schema({ + "op": {"type": "string", "enum": ["diff", "add"]}, + "start": {**NONEMPTY_STRING, "description": "ISO civil date YYYY-MM-DD"}, + "end": {**NONEMPTY_STRING, "description": "ISO civil date YYYY-MM-DD; required for diff"}, + "days": {**NUMBER_STRING, "description": "Whole civil-day offset; required for add"}, + }, ["op", "start"]), + ), + "jackal_stat": ( + tool_stat, + "Compute descriptive statistics through JACKAL. Top-level status=exact; requested population_stddev_enclosure is separately status=formal-bounded. Refuses empty/malformed samples, ambiguous literals, kernel loss, and kernel refusal.", + _schema({ + "sample": { + "description": "Whitespace/semicolon-separated numeric string or an array of numeric strings/integers; JSON floats refuse to avoid binary-float transcription", + "anyOf": [ + {"type": "string"}, + {"type": "array", "items": {"type": ["string", "integer"]}}, + ], + }, + "include_stddev": {"type": "boolean", "default": False}, + }, ["sample"]), + ), + "jackal_compare": ( + tool_compare, + "Compare dimensioned magnitudes through JACKAL. Returns status=exact for dimensionless or same-dimension comparisons and status=exact-given across dimensions. Refuses cross-dimension comparison without a positive rate plus source and as-of, undefined units, kernel loss, and kernel refusal.", + _schema({ + "a_value": NUMBER_STRING, + "a_unit": {**NONEMPTY_STRING, "description": "Optional unit; omit for dimensionless a"}, + "b_value": NUMBER_STRING, + "b_unit": {**NONEMPTY_STRING, "description": "Optional unit; omit for dimensionless b"}, + "rate": {**NUMBER_STRING, "description": "Required only across dimensions: 1 a-base-unit equals rate b-base-units"}, + "rate_source": {**NONEMPTY_STRING, "description": "Required provenance whenever rate is supplied"}, + "rate_asof": {**NONEMPTY_STRING, "description": "Required as-of declaration whenever rate is supplied"}, + }, ["a_value", "b_value"]), + ), + "jackal_scan": ( + tool_scan, + "Lexically audit draft prose for numerals and suggest routing lanes. Returns status=checked and computes/verifies no mathematical result. Refuses missing text or an invalid context window; a clean scan is never evidence of sound provenance.", + _schema({ + "text": {"type": "string", "minLength": 1, "description": "Draft prose to audit before sending"}, + "context_window": {"type": "integer", "minimum": 0, "maximum": 10000, "default": 60}, + }, ["text"]), + ), +} + +TOOL_TITLES = { + "jackal_convert": "Convert Definitional Units", + "jackal_rate_apply": "Apply a Declared Rate", + "jackal_percent": "Compute Percentage Operations", + "jackal_date_delta": "Compute Civil-Date Delta", + "jackal_stat": "Compute Descriptive Statistics", + "jackal_compare": "Compare Dimensioned Quantities", + "jackal_scan": "Scan Draft Numerals", +} +TOOL_ANNOTATIONS = { + "readOnlyHint": True, + "destructiveHint": False, + "idempotentHint": True, + "openWorldHint": False, +} + + +def _tool_definitions() -> list[dict]: + return [ + { + "name": name, + "title": TOOL_TITLES[name], + "description": description, + "inputSchema": schema, + "annotations": dict(TOOL_ANNOTATIONS), + } + for name, (_handler, description, schema) in TOOL_REGISTRY.items() + ] + + +def _matches_schema(value: object, schema: dict) -> bool: + if "anyOf" in schema: + return any(_matches_schema(value, branch) for branch in schema["anyOf"]) + expected = schema.get("type") + if isinstance(expected, list): + return any(_matches_schema(value, {**schema, "type": item}) for item in expected) + if expected == "string": + return isinstance(value, str) + if expected == "boolean": + return isinstance(value, bool) + if expected == "integer": + return isinstance(value, int) and not isinstance(value, bool) + if expected == "number": + return isinstance(value, (int, float)) and not isinstance(value, bool) + if expected == "array": + if not isinstance(value, list): + return False + item_schema = schema.get("items") + return item_schema is None or all(_matches_schema(item, item_schema) for item in value) + if expected == "object": + return isinstance(value, dict) + return True + + +def _validate_arguments(tool_name: str, arguments: object) -> dict: + if not isinstance(arguments, dict): + raise Refusal("args", "tool arguments must be a JSON object") + schema = TOOL_REGISTRY[tool_name][2] + properties = schema["properties"] + extras = sorted(set(arguments) - set(properties)) + if extras: + raise Refusal("args", f"unexpected argument(s): {', '.join(extras)}") + for name, value in arguments.items(): + property_schema = properties[name] + if not _matches_schema(value, property_schema): + raise Refusal("args", f"argument {name!r} does not match its declared JSON type") + enum = property_schema.get("enum") + if enum is not None and value not in enum: + raise Refusal("args", f"argument {name!r} must be one of {', '.join(enum)}") + if isinstance(value, str) and property_schema.get("minLength", 0) > 0 and not value: + raise Refusal("args", f"argument {name!r} must not be empty") + if isinstance(value, int) and not isinstance(value, bool): + if "minimum" in property_schema and value < property_schema["minimum"]: + raise Refusal("args", f"argument {name!r} is below its minimum") + if "maximum" in property_schema and value > property_schema["maximum"]: + raise Refusal("args", f"argument {name!r} exceeds its maximum") + return arguments + + +MEASUREMENT_TOOL_NAMES = frozenset(TOOL_REGISTRY) + + +def tool_definitions() -> list[dict]: + """Return fresh MCP definitions for the identity-pinned wrapper to merge.""" + return _tool_definitions() + + +class _IntegratedKernelBridge: + """Synchronous facade over the wrapper's serialized runtime callback.""" + + def __init__(self, callback: Callable[[str, dict], dict]) -> None: + self._callback = callback + self._evaluator_sha: str | None = None + + def call(self, tool: str, arguments: dict) -> dict: + out = self._callback(tool, arguments) + if not isinstance(out, dict): + raise Refusal("kernel-error", f"{tool} returned no structured content") + if out.get("status") == "refused": + raise Refusal( + f"kernel-refused:{out.get('reason', 'unnamed')}", + f"JACKAL {tool} refused: {out.get('detail', '(no detail)')}", + ["The underlying kernel refused; no measurement-side arithmetic was substituted"], + ) + ident = out.get("identities") + if isinstance(ident, dict) and isinstance(ident.get("evaluator_sha256"), str): + self._evaluator_sha = ident["evaluator_sha256"] + return out + + @property + def evaluator_sha256(self) -> str | None: + return self._evaluator_sha + + +def dispatch_integrated( + tool_name: str, + arguments: object, + kernel_call: Callable[[str, dict], dict], + identity_sha256: str, +) -> dict: + """Run one measurement tool through the caller-supplied JACKAL backend. + + Runtime transport and cancellation exceptions intentionally propagate to + the wrapper, which already maps them to MCP errors and reaps the process + group. Only epistemic/argument refusals become ordinary JACKAL refusal + payloads here. + """ + if re.fullmatch(r"[0-9a-f]{64}", identity_sha256) is None: + raise RuntimeError("measurement module identity is invalid") + if tool_name not in TOOL_REGISTRY: + raise RuntimeError("unknown integrated measurement tool") + + global JACKAL, _ACTIVE_IDENTITY + previous = JACKAL + previous_identity = _ACTIVE_IDENTITY + JACKAL = _IntegratedKernelBridge(kernel_call) + _ACTIVE_IDENTITY = identity_sha256 + _TRACE.clear() + try: + validated = _validate_arguments(tool_name, arguments) + return TOOL_REGISTRY[tool_name][0](validated) + except Refusal as refusal: + return refusal_body(refusal.reason, refusal.detail, refusal.non_claims) + finally: + JACKAL = previous + _ACTIVE_IDENTITY = previous_identity + + +if __name__ == "__main__": + raise SystemExit( + "This identity-pinned module is not a standalone server; launch JACKAL instead." + ) diff --git a/plugins/jackel/mcp/server.py b/plugins/jackel/mcp/server.py index 17a6c60..d112b41 100644 --- a/plugins/jackel/mcp/server.py +++ b/plugins/jackel/mcp/server.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 -"""Fail-closed MCP bridge for the sealed JACKAL macOS runtime.""" +"""Fail-closed MCP bridge for the host-pinned sealed JACKAL runtime.""" from __future__ import annotations import asyncio +import base64 +import binascii from collections import deque import contextlib import copy @@ -16,12 +18,12 @@ import re import selectors import signal -import socket import stat import subprocess import sys import threading import time +import zlib from dataclasses import dataclass, field from pathlib import Path from types import ModuleType @@ -38,12 +40,88 @@ ) SUPPORTED_ARGUMENT_TYPES = frozenset({"string", "object"}) EXPECTED_TOOL_COUNT = 41 +EXPECTED_MEASUREMENT_TOOL_COUNT = 7 +EXPECTED_ADVANCED_TOOL_COUNT = 3 +EXPECTED_STEM_TOOL_COUNT = 7 +EXPECTED_UNIFIED_TOOL_COUNT = 58 +MEASUREMENT_TOOL_NAMES = frozenset( + { + "jackal_compare", + "jackal_convert", + "jackal_date_delta", + "jackal_percent", + "jackal_rate_apply", + "jackal_scan", + "jackal_stat", + } +) +MEASUREMENT_KERNEL_TOOLS = frozenset({"jackal_exact", "jackal_sqrt_rat_bound"}) +ADVANCED_TOOL_NAMES = frozenset( + {"jackal_cas", "jackal_graph", "jackal_hellgate_ground_state"} +) +ADVANCED_KERNEL_TOOLS = frozenset( + { + "jackal_alg_cmp", + "jackal_alg_sign", + "jackal_atan_rat_bound", + "jackal_canon", + "jackal_cos_rat_bound", + "jackal_diff", + "jackal_evaluate", + "jackal_exact", + "jackal_exp_rat_bound", + "jackal_gaussian_integral", + "jackal_integrate", + "jackal_integrate_adaptive", + "jackal_integrate_bound", + "jackal_integrate_bound_cert", + "jackal_ln_rat_bound", + "jackal_poly_canon", + "jackal_poly_eq", + "jackal_poly_gcd", + "jackal_range_bound", + "jackal_ratfunc_canon", + "jackal_roots_isolate", + "jackal_sin_rat_bound", + "jackal_solve", + "jackal_sqrt_rat_bound", + "jackal_tanh_rat_bound", + } +) +STEM_TOOL_NAMES = frozenset( + { + "jackal_aerospace", + "jackal_hypothesis", + "jackal_linked_workspace", + "jackal_matrix", + "jackal_probability", + "jackal_regression", + "jackal_sensor", + } +) +STEM_KERNEL_TOOLS = frozenset( + { + "jackal_canon", + "jackal_diff", + "jackal_evaluate", + "jackal_exact", + "jackal_integrate_adaptive", + "jackal_ln_rat_bound", + "jackal_sqrt_rat_bound", + } +) TOOL_TIMEOUT_SECONDS = 3600.0 TERMINATE_GRACE_SECONDS = 0.5 LEADER_POLL_SECONDS = 0.01 -MAX_REQUEST_LINE_BYTES = 1024 * 1024 +THREAD_WORKER_POLL_SECONDS = 0.01 MAX_CATALOG_BYTES = 2 * 1024 * 1024 MAX_WRAPPER_MODULE_BYTES = 2 * 1024 * 1024 +MAX_CERTIFICATE_COMPRESSED_BYTES = 2 * 1024 * 1024 +MAX_CERTIFICATE_BYTES = 4 * 1024 * 1024 +MAX_MCP_CONTENT_BLOCKS = 4 +MAX_MCP_CONTENT_TEXT_BYTES = 1024 * 1024 +MAX_MCP_IMAGE_BYTES = 4 * 1024 * 1024 +MAX_MCP_RESOURCE_TEXT_BYTES = 2 * 1024 * 1024 MAX_STDOUT_BYTES = 16 * 1024 * 1024 MAX_STDERR_BYTES = 1024 * 1024 MAX_ERROR_RESPONSE_BYTES = 1024 @@ -51,11 +129,24 @@ MAX_TRANSPORT_TASKS = 16 MAX_JSON_DEPTH = 64 MAX_MCP_RESPONSE_BYTES = (2 * MAX_STDOUT_BYTES) + (2 * 1024 * 1024) +# A full runtime payload at the stdout ceiling must fit back through the +# request side for independent receipt replay, including its JSON-RPC envelope. +MAX_REQUEST_LINE_BYTES = MAX_STDOUT_BYTES + MAX_CATALOG_BYTES MAX_RESPONSE_QUEUE_BYTES = 2 * MAX_MCP_RESPONSE_BYTES +BACKEND_RPC_REQUEST_ID = "jackal-adapter-backend" STDIO_DRAIN_TIMEOUT = 0.5 PROCESS_GROUP_OBSERVATION_BYTES = 64 * 1024 PROCESS_GROUP_OBSERVATION_TIMEOUT = 0.5 +NAMESPACE_SETUP_TIMEOUT = 5.0 +PRIVATE_NAMESPACE_FLAG = "--jackal-private-runtime-namespace" +PROCESS_GUARDIAN_FLAG = "--jackal-process-guardian" +PRIVATE_SNAPSHOT_PARENT_PREFIX = ".jackal-codex-runtime-private-" _IDENTITY_LINE = re.compile(r"([0-9a-f]{64}) ([^\n]+)", re.ASCII) +_MOUNT_NAMESPACE_IDENTITY = re.compile(r"mnt:\[[0-9]+\]", re.ASCII) +_LINKED_WORKSPACE_RESOURCE = re.compile( + r"ui://jackal/linked-workspace/([0-9a-f]{64})\Z", re.ASCII +) +LINKED_WORKSPACE_SHELL_URI = "ui://jackal/linked-workspace" PARSE_ERROR = -32700 INVALID_REQUEST = -32600 @@ -107,6 +198,8 @@ class _ProvisionerAPI(Protocol): ASSET: str PACKAGE_SIZE: int PACKAGE_SHA256: str + + def effective_release_pins(self) -> dict: ... SHA256SUMS_SHA256: str SELFTEST_TIMEOUT: float SELFTEST_OUTPUT_LIMIT: int @@ -117,6 +210,8 @@ def default_locator_path(self) -> Path: ... def validate_runtime(self, runtime: Path, **kwargs: Any) -> object: ... + def reap_orphaned_runtime_snapshots(self, temporary_parent: Path | str | None = None) -> object: ... + def create_runtime_snapshot(self, runtime: Path, **kwargs: Any) -> object: ... def runtime_subprocess_environment( @@ -220,13 +315,215 @@ def build_tool_definitions( return definitions +def _build_integrated_tool_definitions( + module: ModuleType, + *, + exported_name: str, + expected_names: frozenset[str], + expected_count: int, + label: str, +) -> tuple[dict[str, Any], ...]: + """Validate one identity-pinned in-process surface before merging it.""" + exported_names = getattr(module, exported_name, None) + exporter = getattr(module, "tool_definitions", None) + dispatcher = getattr(module, "dispatch_integrated", None) + refusal_type = getattr(module, "Refusal", None) + if ( + exported_names != expected_names + or not callable(exporter) + or not callable(dispatcher) + or not isinstance(refusal_type, type) + or not issubclass(refusal_type, Exception) + ): + raise CatalogError(f"{label} module API is invalid") + try: + records = exporter() + except Exception as error: + raise CatalogError(f"{label} tool export failed") from error + if not isinstance(records, list) or len(records) != expected_count: + raise CatalogError(f"{label} tool count does not match the wrapper expectation") + + definitions: list[dict[str, Any]] = [] + for record in records: + if not isinstance(record, dict) or set(record) != { + "name", "title", "description", "inputSchema", "annotations" + }: + raise CatalogError(f"{label} tool record has an unsupported shape") + name = record["name"] + title = record["title"] + description = record["description"] + schema = record["inputSchema"] + annotations = record["annotations"] + if ( + not isinstance(name, str) + or name not in expected_names + or not isinstance(title, str) + or not title + or not isinstance(description, str) + or not description + or not isinstance(schema, dict) + or set(schema) != { + "$schema", "type", "properties", "required", "additionalProperties" + } + or schema.get("$schema") != DRAFT_07 + or schema.get("type") != "object" + or not isinstance(schema.get("properties"), dict) + or not isinstance(schema.get("required"), list) + or schema.get("additionalProperties") is not False + or not isinstance(annotations, dict) + or set(annotations) != { + "readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint" + } + or annotations != { + "readOnlyHint": True, + "destructiveHint": False, + "idempotentHint": True, + "openWorldHint": False, + } + ): + raise CatalogError(f"{label} tool definition is invalid: {name!r}") + properties = schema["properties"] + required = schema["required"] + if ( + any(not isinstance(key, str) or not isinstance(value, dict) + for key, value in properties.items()) + or any(not isinstance(key, str) or key not in properties for key in required) + or len(set(required)) != len(required) + ): + raise CatalogError(f"{label} schema is invalid: {name!r}") + try: + encoded = json.dumps( + record, + ensure_ascii=False, + allow_nan=False, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + except (TypeError, ValueError, RecursionError) as error: + raise CatalogError(f"{label} definition is not strict JSON: {name!r}") from error + if len(encoded) > MAX_CATALOG_BYTES: + raise CatalogError(f"{label} definition exceeds byte limit: {name!r}") + definitions.append(copy.deepcopy(record)) + + names = [definition["name"] for definition in definitions] + if set(names) != expected_names or len(set(names)) != len(names): + raise CatalogError(f"{label} tool names are incomplete or duplicated") + return tuple(definitions) + + +def build_measurement_tool_definitions(module: ModuleType) -> tuple[dict[str, Any], ...]: + """Validate THOTH's pinned in-process measurement surface.""" + return _build_integrated_tool_definitions( + module, + exported_name="MEASUREMENT_TOOL_NAMES", + expected_names=MEASUREMENT_TOOL_NAMES, + expected_count=EXPECTED_MEASUREMENT_TOOL_COUNT, + label="measurement", + ) + + +def build_advanced_tool_definitions(module: ModuleType) -> tuple[dict[str, Any], ...]: + """Validate the pinned CAS, graph, and certificate surface.""" + return _build_integrated_tool_definitions( + module, + exported_name="ADVANCED_TOOL_NAMES", + expected_names=ADVANCED_TOOL_NAMES, + expected_count=EXPECTED_ADVANCED_TOOL_COUNT, + label="advanced", + ) + + +def build_stem_tool_definitions(module: ModuleType) -> tuple[dict[str, Any], ...]: + """Validate the pinned additive STEM workflow and linked-view surface.""" + return _build_integrated_tool_definitions( + module, + exported_name="STEM_TOOL_NAMES", + expected_names=STEM_TOOL_NAMES, + expected_count=EXPECTED_STEM_TOOL_COUNT, + label="stem", + ) + + +def _validated_mcp_content(value: object) -> list[dict[str, Any]]: + if ( + not isinstance(value, list) + or not value + or len(value) > MAX_MCP_CONTENT_BLOCKS + ): + raise BackendFailure("backend MCP content block count is invalid") + result: list[dict[str, Any]] = [] + text_bytes = 0 + for block in value: + if not isinstance(block, dict) or not isinstance(block.get("type"), str): + raise BackendFailure("backend MCP content block is invalid") + if block["type"] == "text": + if set(block) != {"type", "text"} or not isinstance(block.get("text"), str): + raise BackendFailure("backend MCP text block is invalid") + text_bytes += len(block["text"].encode("utf-8")) + if text_bytes > MAX_MCP_CONTENT_TEXT_BYTES: + raise BackendFailure("backend MCP text content exceeds byte limit") + result.append({"type": "text", "text": block["text"]}) + continue + if block["type"] == "image": + if ( + set(block) != {"type", "data", "mimeType"} + or block.get("mimeType") != "image/png" + or not isinstance(block.get("data"), str) + ): + raise BackendFailure("backend MCP image block is invalid") + try: + decoded = base64.b64decode(block["data"], validate=True) + except (ValueError, binascii.Error) as error: + raise BackendFailure("backend MCP image is not canonical base64") from error + if ( + not decoded + or len(decoded) > MAX_MCP_IMAGE_BYTES + or not decoded.startswith(b"\x89PNG\r\n\x1a\n") + or base64.b64encode(decoded).decode("ascii") != block["data"] + ): + raise BackendFailure("backend MCP image is not a bounded canonical PNG") + result.append( + {"type": "image", "data": block["data"], "mimeType": "image/png"} + ) + continue + if block["type"] == "resource": + if set(block) != {"type", "resource"} or not isinstance( + block.get("resource"), dict + ): + raise BackendFailure("backend MCP resource block is invalid") + resource = block["resource"] + if ( + set(resource) != {"uri", "mimeType", "text"} + or not isinstance(resource.get("uri"), str) + or resource.get("mimeType") != "text/html" + or not isinstance(resource.get("text"), str) + ): + raise BackendFailure("backend MCP resource contents are invalid") + matched = _LINKED_WORKSPACE_RESOURCE.fullmatch(resource["uri"]) + encoded = resource["text"].encode("utf-8") + if ( + matched is None + or not encoded + or len(encoded) > MAX_MCP_RESOURCE_TEXT_BYTES + or not resource["text"].startswith("") + or not hmac.compare_digest(hashlib.sha256(encoded).hexdigest(), matched.group(1)) + ): + raise BackendFailure("backend MCP resource identity is invalid") + result.append(copy.deepcopy(block)) + continue + raise BackendFailure("backend MCP content type is unsupported") + return result + + def backend_result(value: object) -> dict[str, Any]: """Wrap one backend JSON object without changing its assurance semantics.""" if not isinstance(value, dict): raise BackendFailure("backend result is not a JSON object") + structured = copy.deepcopy(value) + raw_content = structured.pop("_mcp_content", None) try: text = json.dumps( - value, + structured, ensure_ascii=False, allow_nan=False, sort_keys=True, @@ -234,10 +531,12 @@ def backend_result(value: object) -> dict[str, Any]: ) except (TypeError, ValueError) as error: raise BackendFailure("backend result is not strict JSON") from error - return { - "content": [{"type": "text", "text": text}], - "structuredContent": copy.deepcopy(value), - } + content = ( + [{"type": "text", "text": text}] + if raw_content is None + else _validated_mcp_content(raw_content) + ) + return {"content": content, "structuredContent": structured} def _object_pairs(pairs: Sequence[tuple[str, Any]]) -> dict[str, Any]: @@ -378,6 +677,9 @@ def __init__( stderr_limit: int, terminate_grace: float, leader_poll_interval: float, + process_guardian: Sequence[str] | None = None, + stdin_bytes: bytes | None = None, + stdio_request_id: str | None = None, ) -> None: if ( not command @@ -388,8 +690,27 @@ def __init__( or stderr_limit < 1 or terminate_grace <= 0 or leader_poll_interval <= 0 + or (stdin_bytes is None) != (stdio_request_id is None) + or (stdio_request_id is not None and ( + not isinstance(stdio_request_id, str) or not stdio_request_id + )) + or (stdin_bytes is not None and ( + not isinstance(stdin_bytes, bytes) + or not stdin_bytes + or len(stdin_bytes) > MAX_REQUEST_LINE_BYTES + or not stdin_bytes.endswith(b"\n") + or stdin_bytes.count(b"\n") != 1 + )) ): raise ValueError("invalid anchored backend bounds") + if process_guardian is not None and ( + not process_guardian + or any( + not isinstance(argument, str) or not argument or "\x00" in argument + for argument in process_guardian + ) + ): + raise ValueError("invalid backend process guardian") self.state = state self.command = tuple(command) self.cwd = Path(cwd) @@ -407,9 +728,14 @@ def __init__( self.stderr_limit = int(stderr_limit) self.terminate_grace = float(terminate_grace) self.leader_poll_interval = float(leader_poll_interval) + self.process_guardian = ( + None if process_guardian is None else tuple(process_guardian) + ) + self.stdin_bytes = stdin_bytes + self.stdio_request_id = stdio_request_id self._cancelled = threading.Event() self._wake_lock = threading.Lock() - self._wake_writer: socket.socket | None = None + self._wake_writer: int | None = None def cancel(self) -> None: """Wake the runner and request cleanup; the caller never signals.""" @@ -418,7 +744,7 @@ def cancel(self) -> None: writer = self._wake_writer if writer is not None: with contextlib.suppress(OSError): - writer.send(b"\0") + os.write(writer, b"\0") def _peek_leader_anchor(self, process: subprocess.Popen[bytes]) -> int | None: state = self.state @@ -534,17 +860,19 @@ def _terminate_and_reap(self, process: subprocess.Popen[bytes]) -> int: leader_deadline = time.monotonic() + max(1.0, self.terminate_grace * 4) status = self._peek_leader_anchor(process) while status is None: - if time.monotonic() >= leader_deadline: + remaining = leader_deadline - time.monotonic() + if remaining <= 0: raise BackendFailure("backend leader did not exit within cleanup bounds") - time.sleep(self.leader_poll_interval) + time.sleep(min(self.leader_poll_interval, remaining)) status = self._peek_leader_anchor(process) if state.kill_sent: group_deadline = time.monotonic() + max(1.0, self.terminate_grace * 4) while self._group_exists(process): - if time.monotonic() >= group_deadline: + remaining = group_deadline - time.monotonic() + if remaining <= 0: raise BackendFailure("backend process group survived SIGKILL") - time.sleep(self.leader_poll_interval) + time.sleep(min(self.leader_poll_interval, remaining)) try: reaped_status = process.wait() @@ -558,10 +886,10 @@ def _terminate_and_reap(self, process: subprocess.Popen[bytes]) -> int: return status @staticmethod - def _drain_wakeup(reader: socket.socket) -> None: + def _drain_wakeup(reader: int) -> None: while True: try: - if not reader.recv(4096): + if not os.read(reader, 4096): return except BlockingIOError: return @@ -576,8 +904,9 @@ def _read_ready( selector: selectors.BaseSelector, buffers: dict[str, bytearray], open_streams: set[str], - wake_reader: socket.socket, + wake_reader: int, timeout: float, + input_state: dict[str, Any] | None = None, ) -> None: try: events = selector.select(max(0.0, timeout)) @@ -588,6 +917,29 @@ def _read_ready( if stream == "wake": self._drain_wakeup(wake_reader) continue + if stream == "stdin": + if input_state is None or not input_state.get("open"): + raise BackendFailure("backend stdin state is inconsistent") + payload = cast(bytes, input_state["payload"]) + offset = cast(int, input_state["offset"]) + try: + written = os.write(key.fd, payload[offset:offset + 64 * 1024]) + except BlockingIOError: + continue + except OSError as error: + raise BackendFailure("cannot write backend request") from error + if written <= 0: + raise BackendFailure("backend request write made no progress") + offset += written + input_state["offset"] = offset + if offset == len(payload): + self._close_selector_file(selector, key.fileobj) + try: + key.fileobj.close() + except OSError as error: + raise BackendFailure("cannot close backend request stream") from error + input_state["open"] = False + continue try: chunk = os.read(key.fd, min(64 * 1024, limit - len(buffers[stream]) + 1)) except BlockingIOError: @@ -607,7 +959,7 @@ def _drain_to_eof( selector: selectors.BaseSelector, buffers: dict[str, bytearray], open_streams: set[str], - wake_reader: socket.socket, + wake_reader: int, ) -> None: deadline = time.monotonic() + max(1.0, self.terminate_grace * 4) while open_streams: @@ -620,6 +972,7 @@ def _drain_to_eof( open_streams, wake_reader, min(self.leader_poll_interval, remaining), + None, ) @staticmethod @@ -632,37 +985,77 @@ def _parse_backend_output(raw: bytes) -> dict[str, Any]: raise BackendFailure("backend stdout is not a JSON object") return value + @staticmethod + def _unwrap_stdio_result( + value: dict[str, Any], request_id: str, + ) -> dict[str, Any]: + if set(value) != {"jsonrpc", "id", "result"} \ + or value.get("jsonrpc") != "2.0" \ + or value.get("id") != request_id \ + or not isinstance(value.get("result"), dict): + raise BackendFailure("backend stdio response envelope is invalid") + return cast(dict[str, Any], value["result"]) + def run(self) -> dict[str, Any]: """Run, bound, terminate, and reap one process group in one worker thread.""" + guardian_reader = -1 + guardian_writer = -1 + command = list(self.command) + popen_arguments: dict[str, object] = {} + if self.process_guardian is not None: + try: + guardian_reader, guardian_writer = os.pipe() + except OSError as error: + raise BackendFailure("backend guardian pipe creation failed") from error + command = [ + *self.process_guardian, + str(guardian_reader), + *command, + ] + popen_arguments["pass_fds"] = (guardian_reader,) try: process = subprocess.Popen( - list(self.command), + command, cwd=str(self.cwd), env=self.environment, - stdin=subprocess.DEVNULL, + stdin=(subprocess.PIPE if self.stdin_bytes is not None + else subprocess.DEVNULL), stdout=subprocess.PIPE, stderr=subprocess.PIPE, start_new_session=True, close_fds=True, bufsize=0, + **popen_arguments, ) except (OSError, ValueError) as error: + for descriptor in (guardian_reader, guardian_writer): + if descriptor >= 0: + with contextlib.suppress(OSError): + os.close(descriptor) raise BackendFailure("backend failed to start") from error + if guardian_reader >= 0: + os.close(guardian_reader) + guardian_reader = -1 self.state.process = process selector: selectors.BaseSelector | None = None - wake_reader: socket.socket | None = None - wake_writer: socket.socket | None = None + wake_reader: int | None = None + wake_writer: int | None = None buffers = {"stdout": bytearray(), "stderr": bytearray()} open_streams = {"stdout", "stderr"} + input_state: dict[str, Any] | None = None cleanup_done = False try: try: selector = selectors.DefaultSelector() - wake_reader, wake_writer = socket.socketpair() - wake_reader.setblocking(False) - wake_writer.setblocking(False) + wake_reader, wake_writer = os.pipe() + os.set_blocking(wake_reader, False) + os.set_blocking(wake_writer, False) except (OSError, ValueError) as error: + for descriptor in (wake_reader, wake_writer): + if descriptor is not None: + with contextlib.suppress(OSError): + os.close(descriptor) raise BackendFailure("backend monitor setup failed") from error if process.stdout is None or process.stderr is None: raise BackendFailure("backend pipes unavailable") @@ -675,11 +1068,23 @@ def run(self) -> dict[str, Any]: selector.register( process.stderr, selectors.EVENT_READ, ("stderr", self.stderr_limit) ) + if self.stdin_bytes is not None: + if process.stdin is None: + raise BackendFailure("backend request pipe is unavailable") + os.set_blocking(process.stdin.fileno(), False) + input_state = { + "payload": self.stdin_bytes, + "offset": 0, + "open": True, + } + selector.register( + process.stdin, selectors.EVENT_WRITE, ("stdin", len(self.stdin_bytes)) + ) with self._wake_lock: self._wake_writer = wake_writer if self._cancelled.is_set(): with contextlib.suppress(OSError): - wake_writer.send(b"\0") + os.write(wake_writer, b"\0") deadline = time.monotonic() + self.timeout outcome: BackendFailure | None = None @@ -701,10 +1106,15 @@ def run(self) -> dict[str, Any]: open_streams, wake_reader, min(self.leader_poll_interval, remaining), + input_state, ) except BackendFailure as error: outcome = error + if outcome is None and input_state is not None \ + and input_state.get("offset") != len(self.stdin_bytes or b""): + outcome = BackendFailure("backend exited before reading its complete request") + status = self._terminate_and_reap(process) cleanup_done = True if outcome is None and self._cancelled.is_set(): @@ -716,11 +1126,14 @@ def run(self) -> dict[str, Any]: if self._cancelled.is_set(): raise CallCancelled("request cancelled before backend result delivery") value = self._parse_backend_output(bytes(buffers["stdout"])) + if self.stdio_request_id is not None: + value = self._unwrap_stdio_result(value, self.stdio_request_id) if self._cancelled.is_set(): raise CallCancelled("request cancelled before backend result delivery") if status == 0: return value - if status == 1 and value.get("status") in {"ok", "refused", "indeterminate"}: + if self.stdio_request_id is None and status == 1 \ + and value.get("status") in {"ok", "refused", "indeterminate"}: return value raise BackendFailure("backend returned a non-domain failure") finally: @@ -732,7 +1145,7 @@ def run(self) -> dict[str, Any]: with self._wake_lock: if self._wake_writer is wake_writer: self._wake_writer = None - for file_object in (process.stdout, process.stderr): + for file_object in (process.stdin, process.stdout, process.stderr): if file_object is not None: if selector is not None: self._close_selector_file(selector, file_object) @@ -743,9 +1156,14 @@ def run(self) -> dict[str, Any]: self._close_selector_file(selector, wake_reader) selector.close() if wake_reader is not None: - wake_reader.close() + with contextlib.suppress(OSError): + os.close(wake_reader) if wake_writer is not None: - wake_writer.close() + with contextlib.suppress(OSError): + os.close(wake_writer) + if guardian_writer >= 0: + with contextlib.suppress(OSError): + os.close(guardian_writer) self.state.process = None @@ -856,13 +1274,14 @@ def resolve_runtime_path( expected_keys = {"schema", "epoch", "runtime_path", "package_size", "package_sha256"} if set(document) != expected_keys: raise StartupError("runtime locator has an unsupported shape") + _pins = provisioner.effective_release_pins() if ( document["schema"] != "jackal-codex-plugin-runtime-v1" - or document["epoch"] != provisioner.EPOCH - or document["package_size"] != provisioner.PACKAGE_SIZE - or document["package_sha256"] != provisioner.PACKAGE_SHA256 + or document["epoch"] != _pins["epoch"] + or document["package_size"] != _pins["package_size"] + or document["package_sha256"] != _pins["package_sha256"] ): - raise StartupError("runtime locator does not match wrapper-side release pins") + raise StartupError("runtime locator does not match this host's release pins") return _canonical_absolute_directory(document["runtime_path"], subject="located runtime") @@ -872,12 +1291,13 @@ def _verify_package_metadata(runtime: Path, provisioner: _ProvisionerAPI) -> Non limit=16 * 1024, subject="runtime package metadata", ) + _pins = provisioner.effective_release_pins() expected = { "schema": "jackal-runtime-package-v1", - "epoch": provisioner.EPOCH, - "asset": provisioner.ASSET, - "package_size": provisioner.PACKAGE_SIZE, - "package_sha256": provisioner.PACKAGE_SHA256, + "epoch": _pins["epoch"], + "asset": _pins["asset"], + "package_size": _pins["package_size"], + "package_sha256": _pins["package_sha256"], } if document != expected: raise StartupError("runtime package metadata does not match wrapper-side release pins") @@ -983,6 +1403,78 @@ def _read_plugin_module_once( os.close(file_descriptor) +def _read_verified_plugin_blob( + plugin_root: Path | str, + relative_path: str, + records: object, + *, + limit: int, +) -> tuple[bytes, str]: + expected = _record_digest(records, relative_path) + raw, actual = _read_plugin_module_once(plugin_root, relative_path, limit=limit) + if not hmac.compare_digest(actual, expected): + raise StartupError("plugin data digest does not match inventory") + return raw, actual + + +def _decompress_certificate(raw: bytes) -> bytes: + if not raw or len(raw) > MAX_CERTIFICATE_COMPRESSED_BYTES: + raise StartupError("compressed certificate exceeds byte limit") + decompressor = zlib.decompressobj() + try: + result = decompressor.decompress(raw, MAX_CERTIFICATE_BYTES + 1) + if len(result) > MAX_CERTIFICATE_BYTES or decompressor.unconsumed_tail: + raise StartupError("decompressed certificate exceeds byte limit") + result += decompressor.flush(MAX_CERTIFICATE_BYTES - len(result) + 1) + except zlib.error as error: + raise StartupError("certificate compression stream is invalid") from error + if ( + len(result) > MAX_CERTIFICATE_BYTES + or not decompressor.eof + or decompressor.unused_data + or decompressor.unconsumed_tail + ): + raise StartupError("certificate compression stream is not canonical") + return result + + +def _hellgate_result_satisfies_startup_gate(value: object) -> bool: + """Pin the additive certificate result envelope before exposing the tool.""" + if not isinstance(value, dict): + return False + fields = value.get("fields") + if not isinstance(fields, dict): + return False + trial = fields.get("trial_diagnostics") + ground = fields.get("ground_state_transfer") + if not isinstance(trial, dict) or not isinstance(ground, dict): + return False + trial_nonclaims = trial.get("non_claims") + ground_nonclaims = ground.get("non_claims") + return bool( + value.get("status") == "bounded" + and value.get("checker_verdict") == "ACCEPT" + and value.get("formal") is False + and trial.get("schema") == "jackal-hellgate-trial-diagnostics-v1" + and trial.get("status") == "bounded" + and trial.get("subject") == "normalized-certificate-trial-phi" + and isinstance(trial_nonclaims, list) + and any( + isinstance(item, str) and "not the exact ground state u0" in item + for item in trial_nonclaims + ) + and ground.get("schema") == "jackal-hellgate-ground-transfer-v1" + and ground.get("status") == "bounded" + and ground.get("subject") == "positive-normalized-ground-state-u0" + and ground.get("method") == "lambda-strong-convexity-density-transfer-v1" + and isinstance(ground_nonclaims, list) + and any( + isinstance(item, str) and "does not enclose polynomial moments" in item + for item in ground_nonclaims + ) + ) + + def _record_digest(records: object, relative_path: str) -> str: if isinstance(records, Mapping): expected = records.get(relative_path) @@ -1158,6 +1650,13 @@ def __init__( terminate_grace: float = TERMINATE_GRACE_SECONDS, leader_poll_interval: float = LEADER_POLL_SECONDS, runtime_owner: object | None = None, + process_guardian: Sequence[str] | None = None, + measurement_module: ModuleType | None = None, + measurement_identity: str | None = None, + advanced_module: ModuleType | None = None, + advanced_identity: str | None = None, + stem_module: ModuleType | None = None, + stem_identity: str | None = None, ) -> None: if ( tool_timeout <= 0 @@ -1172,6 +1671,14 @@ def __init__( or leader_poll_interval <= 0 ): raise ValueError("invalid MCP process bounds") + if process_guardian is not None and ( + not process_guardian + or any( + not isinstance(argument, str) or not argument or "\x00" in argument + for argument in process_guardian + ) + ): + raise ValueError("invalid MCP process guardian") self.runtime_root = Path(runtime_root) self.launcher = Path(launcher) self.tool_definitions = tuple(copy.deepcopy(tuple(tool_definitions))) @@ -1193,6 +1700,74 @@ def __init__( self.stderr_limit = int(stderr_limit) self.terminate_grace = float(terminate_grace) self.leader_poll_interval = float(leader_poll_interval) + self.process_guardian = ( + None if process_guardian is None else tuple(process_guardian) + ) + if measurement_module is None: + if measurement_identity is not None: + raise ValueError("measurement identity has no module") + self._measurement_tools = frozenset() + else: + if ( + not isinstance(measurement_identity, str) + or re.fullmatch(r"[0-9a-f]{64}", measurement_identity) is None + ): + raise ValueError("measurement module identity is invalid") + module_names = getattr(measurement_module, "MEASUREMENT_TOOL_NAMES", None) + if module_names != MEASUREMENT_TOOL_NAMES: + raise ValueError("measurement module names are invalid") + self._measurement_tools = MEASUREMENT_TOOL_NAMES + if self._measurement_tools - set(self._tools): + raise ValueError("measurement module definitions are missing") + if measurement_module is None and set(self._tools) & MEASUREMENT_TOOL_NAMES: + raise ValueError("measurement definitions have no pinned dispatcher") + self.measurement_module = measurement_module + self.measurement_identity = measurement_identity + if advanced_module is None: + if advanced_identity is not None: + raise ValueError("advanced identity has no module") + self._advanced_tools = frozenset() + else: + if ( + not isinstance(advanced_identity, str) + or re.fullmatch(r"[0-9a-f]{64}", advanced_identity) is None + ): + raise ValueError("advanced module identity is invalid") + module_names = getattr(advanced_module, "ADVANCED_TOOL_NAMES", None) + module_routes = getattr(advanced_module, "CAS_ROUTES", None) + if ( + module_names != ADVANCED_TOOL_NAMES + or not isinstance(module_routes, dict) + or set(module_routes.values()) - ADVANCED_KERNEL_TOOLS + ): + raise ValueError("advanced module routes or names are invalid") + self._advanced_tools = ADVANCED_TOOL_NAMES + if self._advanced_tools - set(self._tools): + raise ValueError("advanced module definitions are missing") + if advanced_module is None and set(self._tools) & ADVANCED_TOOL_NAMES: + raise ValueError("advanced definitions have no pinned dispatcher") + self.advanced_module = advanced_module + self.advanced_identity = advanced_identity + if stem_module is None: + if stem_identity is not None: + raise ValueError("STEM identity has no module") + self._stem_tools = frozenset() + else: + if ( + not isinstance(stem_identity, str) + or re.fullmatch(r"[0-9a-f]{64}", stem_identity) is None + ): + raise ValueError("STEM module identity is invalid") + module_names = getattr(stem_module, "STEM_TOOL_NAMES", None) + if module_names != STEM_TOOL_NAMES: + raise ValueError("STEM module names are invalid") + self._stem_tools = STEM_TOOL_NAMES + if self._stem_tools - set(self._tools): + raise ValueError("STEM module definitions are missing") + if stem_module is None and set(self._tools) & STEM_TOOL_NAMES: + raise ValueError("STEM definitions have no pinned dispatcher") + self.stem_module = stem_module + self.stem_identity = stem_identity self._backend_lock = asyncio.Lock() self._active: dict[str | int, _CallState] = {} self._closed = False @@ -1303,11 +1878,21 @@ async def _dispatch( negotiated = protocol if protocol in SUPPORTED_PROTOCOL_VERSIONS else LATEST_PROTOCOL_VERSION return { "protocolVersion": negotiated, - "capabilities": {"tools": {"listChanged": False}}, + "capabilities": { + "tools": {"listChanged": False}, + "resources": {"subscribe": False, "listChanged": False}, + }, "serverInfo": {"name": "jackel-codex", "version": "0.1.0"}, "instructions": ( "Preserve JACKAL status and evidence class exactly. " - "Unsupported strong claims refuse; never silently downgrade." + "Unsupported strong claims refuse; never silently downgrade. " + "THOTH is JACKAL's identity-pinned measurement/provenance subsystem, " + "not a separate service; exact-given remains conditional on its given datum. " + "CAS routing preserves delegated assurance. Graph pixels are visualization, " + "never evidence. The HELLGATE lane returns bounded, not formal-bounded. " + "Matrices, regression, probability, sensors, aerospace models, and linked " + "views are additive identity-pinned workflows; field status, assumptions, " + "non-claims, and consequence ceilings remain controlling." ), } if method == "ping": @@ -1322,6 +1907,58 @@ async def _dispatch( if "cursor" in params and not isinstance(params["cursor"], str): raise ProtocolError(INVALID_PARAMS, "tools/list cursor must be a string", request_id) return {"tools": list(copy.deepcopy(self.tool_definitions))} + if method == "resources/list": + if set(params) - {"cursor", "_meta"}: + raise ProtocolError(INVALID_PARAMS, "invalid resources/list params", request_id) + _validate_meta(params) + if "cursor" in params and not isinstance(params["cursor"], str): + raise ProtocolError( + INVALID_PARAMS, "resources/list cursor must be a string", request_id + ) + if self.stem_module is None: + return {"resources": []} + return { + "resources": [ + { + "uri": LINKED_WORKSPACE_SHELL_URI, + "name": "jackal-linked-evidence-workspace", + "title": "JACKAL Linked Evidence Workspace", + "description": ( + "Professional linked symbolic, numeric, graph, table, sensor, " + "and evidence-route shell. Call jackal_linked_workspace to populate it." + ), + "mimeType": "text/html", + } + ] + } + if method == "resources/read": + if set(params) - {"uri", "_meta"} or "uri" not in params: + raise ProtocolError(INVALID_PARAMS, "resources/read requires uri", request_id) + _validate_meta(params) + if params["uri"] != LINKED_WORKSPACE_SHELL_URI or self.stem_module is None: + raise ProtocolError(INVALID_PARAMS, "unknown resource uri", request_id) + shell = getattr(self.stem_module, "workspace_shell", None) + if not callable(shell): + raise BackendFailure("STEM resource API changed") + try: + resource_text = shell() + except Exception as error: + raise BackendFailure("STEM resource generation failed closed") from error + if ( + not isinstance(resource_text, str) + or not resource_text.startswith("") + or len(resource_text.encode("utf-8")) > MAX_MCP_RESOURCE_TEXT_BYTES + ): + raise BackendFailure("STEM resource contents are invalid") + return { + "contents": [ + { + "uri": LINKED_WORKSPACE_SHELL_URI, + "mimeType": "text/html", + "text": resource_text, + } + ] + } if method == "tools/call": if set(params) - {"name", "arguments", "_meta"} or not { "name", "arguments" @@ -1387,42 +2024,361 @@ async def _invoke_serialized( try: if state.cancelled.is_set(): raise CallCancelled("request was cancelled before launch") + if name in self._measurement_tools: + return await self._invoke_measurement(state, name, arguments) + if name in self._advanced_tools: + return await self._invoke_advanced(state, name, arguments) + if name in self._stem_tools: + return await self._invoke_stem(state, name, arguments) return await self._invoke_backend(state, name, arguments) finally: self._backend_lock.release() - async def _invoke_backend( + def _new_backend_runner( self, state: _CallState, name: str, arguments: dict[str, Any] - ) -> dict[str, Any]: - compact_arguments = json.dumps( - arguments, + ) -> _AnchoredBackendRunner: + request = json.dumps( + { + "jsonrpc": "2.0", + "id": BACKEND_RPC_REQUEST_ID, + "method": name, + "params": arguments, + }, ensure_ascii=False, allow_nan=False, sort_keys=True, separators=(",", ":"), - ) - runner = _AnchoredBackendRunner( + ).encode("utf-8") + b"\n" + if len(request) > MAX_REQUEST_LINE_BYTES: + raise BackendFailure("encoded backend request exceeds byte limit") + return _AnchoredBackendRunner( state=state, - command=(str(self.launcher), "call", name, compact_arguments), + command=(str(self.launcher), "stdio"), cwd=self.runtime_root, environment=self.runtime_environment, timeout=self.tool_timeout, - stdout_limit=self.stdout_limit, + stdout_limit=self.stdout_limit + MAX_ERROR_RESPONSE_BYTES, stderr_limit=self.stderr_limit, terminate_grace=self.terminate_grace, leader_poll_interval=self.leader_poll_interval, + process_guardian=self.process_guardian, + stdin_bytes=request, + stdio_request_id=BACKEND_RPC_REQUEST_ID, ) + + def _invoke_backend_sync( + self, state: _CallState, name: str, arguments: dict[str, Any] + ) -> dict[str, Any]: + if state.cancelled.is_set(): + raise CallCancelled("request was cancelled before backend launch") + if state.process is not None or state.runner is not None: + raise BackendFailure("backend process lifecycle overlaps another launch") + # Cancellation belongs to the whole MCP request; process observation + # does not. Integrated measurement calls may legitimately delegate to + # the sealed runtime more than once, so each child starts with a fresh + # WNOWAIT/reap state instead of inheriting the preceding child's exit. + state.term_sent = False + state.kill_sent = False + state.anchor_lost = False + state.leader_status = None + state.reaped = False + runner = self._new_backend_runner(state, name, arguments) state.runner = runner - worker = asyncio.create_task(asyncio.to_thread(runner.run)) + try: + return runner.run() + finally: + if state.runner is runner: + state.runner = None + + def _invoke_measurement_sync( + self, state: _CallState, name: str, arguments: dict[str, Any] + ) -> dict[str, Any]: + module = self.measurement_module + identity = self.measurement_identity + if module is None or identity is None: + raise BackendFailure("measurement dispatcher is unavailable") + dispatcher = getattr(module, "dispatch_integrated", None) + refusal_type = getattr(module, "Refusal", None) + if not callable(dispatcher) or not isinstance(refusal_type, type): + raise BackendFailure("measurement dispatcher API changed") + + def kernel_call(tool: str, delegated_arguments: dict[str, Any]) -> dict[str, Any]: + if tool not in MEASUREMENT_KERNEL_TOOLS: + raise refusal_type( + "kernel-tool-forbidden", + f"measurement orchestration requested unauthorized runtime tool {tool!r}", + ) + if not isinstance(delegated_arguments, dict): + raise refusal_type( + "kernel-error", "measurement orchestration produced invalid arguments" + ) + try: + return self._invoke_backend_sync(state, tool, delegated_arguments) + except CallCancelled: + raise + except BackendTimedOut as error: + raise refusal_type( + "kernel-timeout", + "the JACKAL runtime timed out; no measurement-side arithmetic was substituted", + ) from error + except BackendFailure as error: + raise refusal_type( + "kernel-unavailable", + "the JACKAL runtime failed closed; no measurement-side arithmetic was substituted", + ) from error + + value = dispatcher(name, arguments, kernel_call, identity) + if not isinstance(value, dict): + raise BackendFailure("measurement dispatcher returned a non-object") + return value + + def _invoke_advanced_sync( + self, state: _CallState, name: str, arguments: dict[str, Any] + ) -> dict[str, Any]: + module = self.advanced_module + identity = self.advanced_identity + if module is None or identity is None: + raise BackendFailure("advanced dispatcher is unavailable") + dispatcher = getattr(module, "dispatch_integrated", None) + refusal_type = getattr(module, "Refusal", None) + if not callable(dispatcher) or not isinstance(refusal_type, type): + raise BackendFailure("advanced dispatcher API changed") + + def kernel_call(tool: str, delegated_arguments: dict[str, Any]) -> dict[str, Any]: + if tool not in ADVANCED_KERNEL_TOOLS: + raise refusal_type( + "kernel-tool-forbidden", + f"advanced orchestration requested unauthorized runtime tool {tool!r}", + ) + if not isinstance(delegated_arguments, dict): + raise refusal_type( + "kernel-error", "advanced orchestration produced invalid arguments" + ) + try: + return self._invoke_backend_sync(state, tool, delegated_arguments) + except CallCancelled: + raise + except BackendTimedOut as error: + raise refusal_type( + "kernel-timeout", + "the JACKAL runtime timed out; no advanced-side arithmetic was substituted", + ) from error + except BackendFailure as error: + raise refusal_type( + "kernel-unavailable", + "the JACKAL runtime failed closed; no advanced-side arithmetic was substituted", + ) from error + + value = dispatcher(name, arguments, kernel_call, identity) + if not isinstance(value, dict): + raise BackendFailure("advanced dispatcher returned a non-object") + return value + + def _invoke_stem_sync( + self, state: _CallState, name: str, arguments: dict[str, Any] + ) -> dict[str, Any]: + module = self.stem_module + identity = self.stem_identity + if module is None or identity is None: + raise BackendFailure("STEM dispatcher is unavailable") + dispatcher = getattr(module, "dispatch_integrated", None) + refusal_type = getattr(module, "Refusal", None) + if not callable(dispatcher) or not isinstance(refusal_type, type): + raise BackendFailure("STEM dispatcher API changed") + + def kernel_call(tool: str, delegated_arguments: dict[str, Any]) -> dict[str, Any]: + if tool not in STEM_KERNEL_TOOLS: + raise refusal_type( + "kernel-tool-forbidden", + f"STEM orchestration requested unauthorized runtime tool {tool!r}", + ) + if not isinstance(delegated_arguments, dict): + raise refusal_type( + "kernel-error", "STEM orchestration produced invalid arguments" + ) + try: + return self._invoke_backend_sync(state, tool, delegated_arguments) + except CallCancelled: + raise + except BackendTimedOut as error: + raise refusal_type( + "kernel-timeout", + "the JACKAL runtime timed out; no STEM-side arithmetic was substituted", + ) from error + except BackendFailure as error: + raise refusal_type( + "kernel-unavailable", + "the JACKAL runtime failed closed; no STEM-side arithmetic was substituted", + ) from error + + value = dispatcher(name, arguments, kernel_call, identity) + if not isinstance(value, dict): + raise BackendFailure("STEM dispatcher returned a non-object") + return value + + async def _run_sync_worker( + self, operation: Callable[[], dict[str, Any]] + ) -> dict[str, Any]: + """Run one process-owning operation without asyncio's global executor. + + A dedicated joinable thread keeps lifetime ownership local to this + server. The event loop polls a threading event at the existing leader + interval, so completion and cancellation do not depend on a platform's + cross-thread selector wakeup behavior. + """ + completed = threading.Event() + outcome: dict[str, object] = {} + + def run() -> None: + try: + outcome["value"] = operation() + except BaseException as error: + outcome["error"] = error + finally: + completed.set() + + thread = threading.Thread(target=run, name="jackal-backend", daemon=False) + thread.start() + while not completed.is_set(): + await asyncio.sleep(THREAD_WORKER_POLL_SECONDS) + thread.join() + error = outcome.get("error") + if isinstance(error, BaseException): + raise error + value = outcome.get("value") + if not isinstance(value, dict): + raise BackendFailure("backend worker returned a non-object") + return cast(dict[str, Any], value) + + async def _await_worker_result( + self, worker: asyncio.Task[dict[str, Any]] + ) -> dict[str, Any]: + """Await a thread worker with a bounded event-loop wake interval. + + Some supported Python/event-loop combinations can leave the selector + asleep after a thread-safe completion notification. A short bounded + wait preserves prompt completion and cancellation without ever + cancelling the process-owning worker task. + """ + while not worker.done(): + await asyncio.wait( + {worker}, timeout=THREAD_WORKER_POLL_SECONDS, + return_when=asyncio.FIRST_COMPLETED, + ) + return worker.result() + + async def _invoke_measurement( + self, state: _CallState, name: str, arguments: dict[str, Any] + ) -> dict[str, Any]: + worker = asyncio.create_task( + self._run_sync_worker( + lambda: self._invoke_measurement_sync(state, name, arguments) + ) + ) state.worker = worker try: - return await asyncio.shield(worker) + return await self._await_worker_result(worker) except asyncio.CancelledError: state.cancelled.set() - runner.cancel() + runner = state.runner + if runner is not None: + runner.cancel() while not worker.done(): try: - await asyncio.shield(worker) + await asyncio.wait( + {worker}, timeout=THREAD_WORKER_POLL_SECONDS, + return_when=asyncio.FIRST_COMPLETED, + ) + except asyncio.CancelledError: + continue + except Exception: + break + raise + finally: + state.worker = None + + async def _invoke_advanced( + self, state: _CallState, name: str, arguments: dict[str, Any] + ) -> dict[str, Any]: + worker = asyncio.create_task( + self._run_sync_worker( + lambda: self._invoke_advanced_sync(state, name, arguments) + ) + ) + state.worker = worker + try: + return await self._await_worker_result(worker) + except asyncio.CancelledError: + state.cancelled.set() + runner = state.runner + if runner is not None: + runner.cancel() + while not worker.done(): + try: + await asyncio.wait( + {worker}, timeout=THREAD_WORKER_POLL_SECONDS, + return_when=asyncio.FIRST_COMPLETED, + ) + except asyncio.CancelledError: + continue + except Exception: + break + raise + finally: + state.worker = None + + async def _invoke_stem( + self, state: _CallState, name: str, arguments: dict[str, Any] + ) -> dict[str, Any]: + worker = asyncio.create_task( + self._run_sync_worker( + lambda: self._invoke_stem_sync(state, name, arguments) + ) + ) + state.worker = worker + try: + return await self._await_worker_result(worker) + except asyncio.CancelledError: + state.cancelled.set() + runner = state.runner + if runner is not None: + runner.cancel() + while not worker.done(): + try: + await asyncio.wait( + {worker}, timeout=THREAD_WORKER_POLL_SECONDS, + return_when=asyncio.FIRST_COMPLETED, + ) + except asyncio.CancelledError: + continue + except Exception: + break + raise + finally: + state.worker = None + + async def _invoke_backend( + self, state: _CallState, name: str, arguments: dict[str, Any] + ) -> dict[str, Any]: + worker = asyncio.create_task( + self._run_sync_worker( + lambda: self._invoke_backend_sync(state, name, arguments) + ) + ) + state.worker = worker + try: + return await self._await_worker_result(worker) + except asyncio.CancelledError: + state.cancelled.set() + runner = state.runner + if runner is not None: + runner.cancel() + while not worker.done(): + try: + await asyncio.wait( + {worker}, timeout=THREAD_WORKER_POLL_SECONDS, + return_when=asyncio.FIRST_COMPLETED, + ) except asyncio.CancelledError: continue except Exception: @@ -1430,7 +2386,6 @@ async def _invoke_backend( raise finally: state.worker = None - state.runner = None def _cancel_request(self, request_id: str | int) -> None: state = self._active.get(request_id) @@ -1480,6 +2435,7 @@ def build_production_server( plugin_root: Path | str | None = None, environ: Mapping[str, str] | None = None, locator_path: Path | str | None = None, + snapshot_parent: Path | str | None = None, provisioner: _ProvisionerAPI | None = None, identity_verifier: Callable[[Path, Path], object] | None = None, runtime_validator: Callable[..., object] | None = None, @@ -1488,6 +2444,7 @@ def build_production_server( root = plugin_root_from_server() if plugin_root is None else Path(plugin_root) if not root.is_absolute(): raise StartupError("plugin root must be absolute") + require_integrated_modules = identity_verifier is None inventory: dict[str, str] | None = None if identity_verifier is None: inventory = _read_identity_inventory(root) @@ -1504,9 +2461,114 @@ def build_production_server( raise StartupError("plugin identity verification refused") from error if inventory is not None and _inventory_from_records(verified_records) != inventory: raise StartupError("plugin identity records changed during verification") + if inventory is None: + inventory = _inventory_from_records(verified_records) + + measurement_module: ModuleType | None = None + measurement_identity: str | None = None + measurement_definitions: tuple[dict[str, Any], ...] = () + if "mcp/measurement.py" in inventory: + measurement_module = _load_verified_module( + root, + "mcp/measurement.py", + "jackel_codex_measurement", + inventory, + ) + measurement_identity = _record_digest(inventory, "mcp/measurement.py") + try: + measurement_definitions = build_measurement_tool_definitions( + measurement_module + ) + except CatalogError as error: + raise StartupError("measurement tool surface refused") from error + elif require_integrated_modules: + raise StartupError("plugin identity omits the measurement module") + + advanced_module: ModuleType | None = None + advanced_identity: str | None = None + advanced_definitions: tuple[dict[str, Any], ...] = () + if "mcp/advanced.py" in inventory: + advanced_module = _load_verified_module( + root, + "mcp/advanced.py", + "jackel_codex_advanced", + inventory, + ) + checker_module = _load_verified_module( + root, + "mcp/hellgate_verify.py", + "jackel_codex_hellgate_verify", + inventory, + ) + certificate_path = "mcp/certificates/hellgate_v1.json.zlib" + compressed_certificate, certificate_file_identity = _read_verified_plugin_blob( + root, + certificate_path, + inventory, + limit=MAX_CERTIFICATE_COMPRESSED_BYTES, + ) + certificate = _decompress_certificate(compressed_certificate) + verifier = getattr(checker_module, "verify_bytes", None) + refusal_type = getattr(checker_module, "VerificationRefusal", None) + if ( + not callable(verifier) + or not isinstance(refusal_type, type) + or not issubclass(refusal_type, Exception) + ): + raise StartupError("HELLGATE checker API is invalid") + try: + hellgate_result = verifier(certificate) + except refusal_type as error: + raise StartupError("HELLGATE certificate verification refused") from error + except Exception as error: + raise StartupError("HELLGATE certificate checker failed closed") from error + if not _hellgate_result_satisfies_startup_gate(hellgate_result): + raise StartupError("HELLGATE certificate did not satisfy the startup gate") + advanced_identity = _record_digest(inventory, "mcp/advanced.py") + configure = getattr(advanced_module, "configure_hellgate", None) + if not callable(configure): + raise StartupError("advanced certificate configuration API is invalid") + try: + configure( + hellgate_result, + advanced_sha256=advanced_identity, + checker_sha256=_record_digest(inventory, "mcp/hellgate_verify.py"), + certificate_sha256=certificate_file_identity, + ) + advanced_definitions = build_advanced_tool_definitions(advanced_module) + except Exception as error: + raise StartupError("advanced tool surface refused") from error + elif require_integrated_modules: + raise StartupError("plugin identity omits the advanced module") + + stem_module: ModuleType | None = None + stem_identity: str | None = None + stem_definitions: tuple[dict[str, Any], ...] = () + if "mcp/stem.py" in inventory: + stem_module = _load_verified_module( + root, + "mcp/stem.py", + "jackel_codex_stem", + inventory, + ) + stem_identity = _record_digest(inventory, "mcp/stem.py") + try: + stem_definitions = build_stem_tool_definitions(stem_module) + shell = getattr(stem_module, "workspace_shell", None) + if not callable(shell): + raise CatalogError("STEM resource API is invalid") + shell_text = shell() + if ( + not isinstance(shell_text, str) + or not shell_text.startswith("") + or len(shell_text.encode("utf-8")) > MAX_MCP_RESOURCE_TEXT_BYTES + ): + raise CatalogError("STEM resource shell is invalid") + except Exception as error: + raise StartupError("STEM tool or resource surface refused") from error + elif require_integrated_modules: + raise StartupError("plugin identity omits the STEM module") if provisioner is None: - if inventory is None: - inventory = _inventory_from_records(verified_records) provisioner = cast( _ProvisionerAPI, _load_verified_module( @@ -1520,6 +2582,10 @@ def build_production_server( provisioner.validate_host() except Exception as error: raise StartupError("unsupported production host") from error + try: + provisioner.reap_orphaned_runtime_snapshots() + except Exception as error: + raise StartupError("orphaned runtime snapshot cleanup refused") from error runtime = resolve_runtime_path( environ=environ, @@ -1533,19 +2599,23 @@ def build_production_server( runtime, timeout=provisioner.SELFTEST_TIMEOUT, output_limit=provisioner.SELFTEST_OUTPUT_LIMIT, - expected_tree_sha256=provisioner.SHA256SUMS_SHA256, + expected_tree_sha256=provisioner.effective_release_pins()["sha256sums_sha256"], ) except Exception as error: raise StartupError("pinned runtime validation refused") from error snapshot_owner: object | None = None try: - snapshot_owner = provisioner.create_runtime_snapshot( - runtime, - timeout=provisioner.SELFTEST_TIMEOUT, - output_limit=provisioner.SELFTEST_OUTPUT_LIMIT, - expected_tree_sha256=provisioner.SHA256SUMS_SHA256, - ) + snapshot_arguments: dict[str, object] = { + "timeout": provisioner.SELFTEST_TIMEOUT, + "output_limit": provisioner.SELFTEST_OUTPUT_LIMIT, + "expected_tree_sha256": provisioner.effective_release_pins()[ + "sha256sums_sha256" + ], + } + if snapshot_parent is not None: + snapshot_arguments["temporary_parent"] = os.fspath(snapshot_parent) + snapshot_owner = provisioner.create_runtime_snapshot(runtime, **snapshot_arguments) snapshot_value = getattr(snapshot_owner, "root", None) snapshot = _canonical_absolute_directory( os.fspath(snapshot_value) if isinstance(snapshot_value, (Path, str)) else None, @@ -1556,7 +2626,34 @@ def build_production_server( raise StartupError("runtime snapshot is not private and independent") _verify_package_metadata(snapshot, provisioner) catalog = _load_catalog(snapshot / "plugin/hermes/tools.json") - definitions = build_tool_definitions(catalog, expected_count=EXPECTED_TOOL_COUNT) + runtime_definitions = build_tool_definitions( + catalog, expected_count=EXPECTED_TOOL_COUNT + ) + definitions = ( + runtime_definitions + + measurement_definitions + + advanced_definitions + + stem_definitions + ) + expected_surface_count = EXPECTED_TOOL_COUNT + if measurement_module is not None: + expected_surface_count += EXPECTED_MEASUREMENT_TOOL_COUNT + if advanced_module is not None: + expected_surface_count += EXPECTED_ADVANCED_TOOL_COUNT + if stem_module is not None: + expected_surface_count += EXPECTED_STEM_TOOL_COUNT + if ( + measurement_module is not None + and advanced_module is not None + and stem_module is not None + and expected_surface_count != EXPECTED_UNIFIED_TOOL_COUNT + ): + raise StartupError("unified tool surface constant is inconsistent") + if len(definitions) != expected_surface_count: + raise StartupError("unified tool surface count is inconsistent") + definition_names = [definition["name"] for definition in definitions] + if len(set(definition_names)) != len(definition_names): + raise StartupError("unified tool surface contains duplicate names") launcher = snapshot / "plugin/hermes/jackal_hermes" try: launcher_info = launcher.lstat() @@ -1579,6 +2676,12 @@ def build_production_server( runtime_environment=runtime_environment, tool_timeout=TOOL_TIMEOUT_SECONDS, runtime_owner=snapshot_owner, + measurement_module=measurement_module, + measurement_identity=measurement_identity, + advanced_module=advanced_module, + advanced_identity=advanced_identity, + stem_module=stem_module, + stem_identity=stem_identity, ) except Exception as error: cleanup_error: Exception | None = None @@ -1849,9 +2952,496 @@ def _bounded_detail(error: Exception) -> str: return (" ".join(str(error).splitlines()).strip() or "startup failed")[:240] -def main() -> int: +class _GuardedProcessProxy: + """Keep the guardian liveness writer open for one delegated Popen.""" + + def __init__(self, process: subprocess.Popen, liveness_writer: int) -> None: + self._process = process + self._liveness_writer = liveness_writer + + def close_liveness(self) -> None: + if self._liveness_writer < 0: + return + with contextlib.suppress(OSError): + os.close(self._liveness_writer) + self._liveness_writer = -1 + + def __getattr__(self, name: str) -> Any: + return getattr(self._process, name) + + +def _guarded_popen_factory( + guardian_prefix: Sequence[str], + owners: list[_GuardedProcessProxy], +) -> Callable: + def spawn(command: Sequence[str], **arguments: Any) -> _GuardedProcessProxy: + reader = -1 + writer = -1 + try: + reader, writer = os.pipe() + process = subprocess.Popen( + [*guardian_prefix, str(reader), *command], + pass_fds=(reader,), + **arguments, + ) + except (OSError, ValueError, subprocess.SubprocessError): + for descriptor in (reader, writer): + if descriptor >= 0: + with contextlib.suppress(OSError): + os.close(descriptor) + raise + os.close(reader) + owner = _GuardedProcessProxy(process, writer) + owners.append(owner) + return owner + + return spawn + + +def _guarded_selftest_runner( + provisioner: _ProvisionerAPI, + guardian_prefix: Sequence[str] | None, +) -> Callable | None: + if guardian_prefix is None: + return None + selftest = getattr(provisioner, "_run_selftest", None) + if not callable(selftest): + return None + + def run(command: list[str], *, timeout: float, output_limit: int): + owners: list[_GuardedProcessProxy] = [] + try: + return selftest( + command, + timeout=timeout, + output_limit=output_limit, + popen_factory=_guarded_popen_factory(guardian_prefix, owners), + ) + finally: + for owner in owners: + owner.close_liveness() + + return run + + +def _process_guardian_prefix() -> tuple[str, ...]: + try: + python = os.fspath(Path(sys.executable).resolve(strict=True)) + server_path = os.fspath(Path(__file__).resolve(strict=True)) + except OSError as error: + raise StartupError("process guardian executable identity is unavailable") from error + return ( + python, + "-I", + "-S", + "-B", + server_path, + PROCESS_GUARDIAN_FLAG, + ) + + +def _parse_process_guardian( + arguments: Sequence[str], +) -> tuple[int, tuple[str, ...]] | None: + if not arguments or arguments[0] != PROCESS_GUARDIAN_FLAG: + return None + if len(arguments) < 3 or not arguments[1].isdecimal(): + raise StartupError("invalid process guardian arguments") + liveness_fd = int(arguments[1]) + command = tuple(arguments[2:]) + if ( + liveness_fd < 3 + or not command + or not Path(command[0]).is_absolute() + or any(not argument or "\x00" in argument for argument in command) + ): + raise StartupError("invalid process guardian arguments") + return liveness_fd, command + + +def _guarded_child_status(pid: int) -> int | None: + try: + result = os.waitid(os.P_PID, pid, os.WEXITED | os.WNOHANG | os.WNOWAIT) + except (ChildProcessError, OSError) as error: + raise StartupError("guarded process anchor is unavailable") from error + if result is None: + return None + if result.si_pid != pid: + raise StartupError("guarded process observation is inconsistent") + if result.si_code == os.CLD_EXITED: + return result.si_status + if result.si_code in (os.CLD_KILLED, os.CLD_DUMPED): + return -result.si_status + raise StartupError("guarded process has an unsupported wait status") + + +def _signal_guarded_group(process_group: int, requested_signal: int) -> None: try: - server = build_production_server() + os.killpg(process_group, requested_signal) + except ProcessLookupError: + return + except OSError as error: + if error.errno != errno.ESRCH: + raise StartupError("cannot signal guarded process group") from error + + +def _stop_guarded_process( + process: subprocess.Popen[bytes], + status: int | None, + *, + graceful: bool, +) -> int: + if graceful: + _signal_guarded_group(process.pid, signal.SIGTERM) + deadline = time.monotonic() + min(0.1, TERMINATE_GRACE_SECONDS / 2) + while status is None and time.monotonic() < deadline: + time.sleep(LEADER_POLL_SECONDS) + status = _guarded_child_status(process.pid) + _signal_guarded_group(process.pid, signal.SIGKILL) + deadline = time.monotonic() + max(1.0, TERMINATE_GRACE_SECONDS * 4) + while status is None: + if time.monotonic() >= deadline: + raise StartupError("guarded process did not exit after SIGKILL") + time.sleep(LEADER_POLL_SECONDS) + status = _guarded_child_status(process.pid) + try: + reaped = process.wait(timeout=max(1.0, TERMINATE_GRACE_SECONDS * 4)) + except (ChildProcessError, OSError, subprocess.TimeoutExpired) as error: + raise StartupError("guarded process could not be reaped") from error + if reaped != status: + raise StartupError("guarded process status changed during reap") + return status + + +def _run_process_guardian(liveness_fd: int, command: Sequence[str]) -> int: + if ( + os.getpid() != os.getpgrp() + or os.getpid() != os.getsid(0) + or not stat.S_ISFIFO(os.fstat(liveness_fd).st_mode) + ): + raise StartupError("process guardian isolation is invalid") + executable = Path(command[0]) + try: + executable_info = executable.lstat() + except OSError as error: + raise StartupError("guarded executable is unavailable") from error + if ( + not stat.S_ISREG(executable_info.st_mode) + or executable.is_symlink() + or not os.access(executable, os.X_OK) + ): + raise StartupError("guarded executable is unsafe") + + os.set_blocking(liveness_fd, False) + try: + initial = os.read(liveness_fd, 1) + except BlockingIOError: + initial = None + except OSError as error: + raise StartupError("process guardian liveness channel failed") from error + if initial is not None: + if initial: + raise StartupError("process guardian liveness protocol refused") + return 0 + + termination_requested = False + + def request_termination(unused_signal, unused_frame) -> None: + nonlocal termination_requested + termination_requested = True + + signal.signal(signal.SIGTERM, request_termination) + signal.signal(signal.SIGINT, request_termination) + try: + process = subprocess.Popen( + list(command), + stdin=subprocess.DEVNULL, + stdout=None, + stderr=None, + close_fds=True, + preexec_fn=os.setpgrp, + ) + except (OSError, ValueError, subprocess.SubprocessError) as error: + raise StartupError("guarded process failed to start") from error + + selector: selectors.BaseSelector | None = None + status: int | None = None + reaped = False + try: + selector = selectors.DefaultSelector() + selector.register(liveness_fd, selectors.EVENT_READ) + while True: + if termination_requested: + status = _stop_guarded_process(process, status, graceful=True) + reaped = True + return status + status = _guarded_child_status(process.pid) + if status is not None: + try: + quiescent = _exited_group_has_only_zombie_members(process.pid) + except BackendFailure: + quiescent = False + if quiescent: + reaped_status = process.wait() + reaped = True + if reaped_status != status: + raise StartupError( + "guarded process status changed during final reap" + ) + return status + status = _stop_guarded_process(process, status, graceful=True) + reaped = True + return status + if not selector.select(LEADER_POLL_SECONDS): + continue + try: + payload = os.read(liveness_fd, 1) + except BlockingIOError: + continue + if payload: + raise StartupError("process guardian liveness protocol refused") + status = _stop_guarded_process(process, status, graceful=False) + reaped = True + return 0 + finally: + if selector is not None: + selector.close() + with contextlib.suppress(OSError): + os.close(liveness_fd) + if not reaped: + with contextlib.suppress(Exception): + _stop_guarded_process(process, status, graceful=False) + + +def _read_namespace_metadata(path: Path | str, *, byte_limit: int) -> bytes: + if byte_limit < 1: + raise StartupError("invalid namespace metadata byte limit") + try: + fd = os.open(os.fspath(path), os.O_RDONLY | os.O_NONBLOCK | os.O_NOFOLLOW) + except OSError as error: + raise StartupError("namespace metadata is unavailable") from error + try: + if not stat.S_ISREG(os.fstat(fd).st_mode): + raise StartupError("namespace metadata is not a regular file") + chunks: list[bytes] = [] + count = 0 + while chunk := os.read(fd, min(4096, byte_limit - count + 1)): + count += len(chunk) + if count > byte_limit: + raise StartupError("namespace metadata exceeds byte limit") + chunks.append(chunk) + return b"".join(chunks) + finally: + os.close(fd) + + +def _mount_namespace_identity() -> str: + try: + identity = os.readlink("/proc/self/ns/mnt") + except OSError as error: + raise StartupError("mount namespace identity is unavailable") from error + if _MOUNT_NAMESPACE_IDENTITY.fullmatch(identity) is None: + raise StartupError("mount namespace identity has an invalid shape") + return identity + + +def _mapped_host_uid() -> int: + try: + text = _read_namespace_metadata( + "/proc/self/uid_map", byte_limit=4096 + ).decode("ascii") + except UnicodeDecodeError as error: + raise StartupError("user namespace mapping is not ASCII") from error + rows = [line.split() for line in text.splitlines() if line.strip()] + if ( + os.getuid() != 0 + or os.geteuid() != 0 + or len(rows) != 1 + or len(rows[0]) != 3 + or rows[0][0] != "0" + or rows[0][2] != "1" + or not rows[0][1].isdecimal() + ): + raise StartupError("private runtime requires an exact one-user mapping") + return int(rows[0][1]) + + +def _fixed_executable(candidates: Sequence[str]) -> str: + for candidate in candidates: + if os.path.isfile(candidate) and os.access(candidate, os.X_OK): + return candidate + raise StartupError("required namespace executable is unavailable") + + +def _mountinfo_confirms_tmpfs(path: Path) -> bool: + try: + text = _read_namespace_metadata( + "/proc/self/mountinfo", byte_limit=MAX_REQUEST_LINE_BYTES + ).decode("utf-8") + except UnicodeDecodeError as error: + raise StartupError("mount metadata is not UTF-8") from error + expected = os.fspath(path) + for line in text.splitlines(): + fields = line.split() + try: + separator = fields.index("-") + except ValueError: + continue + if ( + len(fields) > 5 + and separator + 1 < len(fields) + and fields[4] == expected + and fields[separator + 1] == "tmpfs" + ): + return True + return False + + +def _prepare_private_snapshot_parent(parent_namespace: str) -> Path: + if sys.platform != "linux" or _mount_namespace_identity() == parent_namespace: + raise StartupError("private mount namespace was not established") + host_uid = _mapped_host_uid() + temporary_root = Path("/tmp") + root_info = temporary_root.lstat() + if not stat.S_ISDIR(root_info.st_mode) or temporary_root.is_symlink(): + raise StartupError("system temporary root is unsafe") + mountpoint = temporary_root / f"{PRIVATE_SNAPSHOT_PARENT_PREFIX}{host_uid}" + try: + mountpoint.mkdir(mode=0o700, exist_ok=True) + before = mountpoint.lstat() + except OSError as error: + raise StartupError("private snapshot mountpoint is unavailable") from error + if ( + not stat.S_ISDIR(before.st_mode) + or mountpoint.is_symlink() + or before.st_uid != os.geteuid() + or before.st_mode & 0o077 + ): + raise StartupError("private snapshot mountpoint is unsafe") + try: + if any(os.scandir(mountpoint)): + raise StartupError("private snapshot mountpoint is not empty") + except OSError as error: + raise StartupError("private snapshot mountpoint is unreadable") from error + + mount = _fixed_executable(("/usr/bin/mount", "/bin/mount")) + try: + result = subprocess.run( + [ + mount, + "-t", + "tmpfs", + "-o", + "mode=0700,nosuid,nodev", + "tmpfs", + os.fspath(mountpoint), + ], + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + timeout=NAMESPACE_SETUP_TIMEOUT, + check=False, + env={"PATH": "/usr/bin:/bin:/usr/sbin:/sbin", "LC_ALL": "C"}, + ) + except (OSError, subprocess.SubprocessError) as error: + raise StartupError("private snapshot tmpfs mount failed") from error + if result.returncode != 0: + raise StartupError("private snapshot tmpfs mount refused") + mounted = mountpoint.lstat() + if ( + not stat.S_ISDIR(mounted.st_mode) + or mounted.st_uid != os.geteuid() + or mounted.st_mode & 0o077 + or mounted.st_dev == root_info.st_dev + or not _mountinfo_confirms_tmpfs(mountpoint) + ): + raise StartupError("private snapshot tmpfs verification refused") + return mountpoint + + +def _parse_namespace_child(arguments: Sequence[str]) -> str | None: + if not arguments: + return None + if ( + len(arguments) != 2 + or arguments[0] != PRIVATE_NAMESPACE_FLAG + or _MOUNT_NAMESPACE_IDENTITY.fullmatch(arguments[1]) is None + ): + raise StartupError("invalid private namespace arguments") + return arguments[1] + + +def _private_namespace_prefix(unshare: str) -> list[str]: + return [ + unshare, + "--user", + "--map-root-user", + "--mount", + "--pid", + "--fork", + "--kill-child=SIGKILL", + "--forward-signals", + # A PID namespace without a procfs of its own is a trap: /proc still + # shows the HOST namespace, so every /proc-based observation made inside + # it answers about the wrong processes. `/bin/ps -g ` then lists + # host PIDs (or fails "fatal library error, lookup self"), the group + # reaper concludes "backend process group survived SIGKILL", and EVERY + # tool call fails closed with -32002 while plugin identity still + # verifies. Measured, not inferred: without this flag `ps` inside the + # namespace printed this user's own systemd PIDs; with it, `1 R`. + "--mount-proc", + "--propagation", + "private", + ] + + +def _exec_in_private_snapshot_namespace() -> bool: + """Replace this process with a PID-namespace supervisor when available.""" + if sys.platform != "linux": + return False + try: + unshare = _fixed_executable(("/usr/bin/unshare", "/bin/unshare")) + true = _fixed_executable(("/usr/bin/true", "/bin/true")) + python = os.fspath(Path(sys.executable).resolve(strict=True)) + server_path = os.fspath(Path(__file__).resolve(strict=True)) + parent_namespace = _mount_namespace_identity() + except (OSError, StartupError): + return False + prefix = _private_namespace_prefix(unshare) + try: + probe = subprocess.run( + [*prefix, true], + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + timeout=NAMESPACE_SETUP_TIMEOUT, + check=False, + env={"PATH": "/usr/bin:/bin:/usr/sbin:/sbin", "LC_ALL": "C"}, + ) + except (OSError, subprocess.SubprocessError): + return False + if probe.returncode != 0: + return False + command = [ + *prefix, + python, + "-I", + "-S", + "-B", + server_path, + PRIVATE_NAMESPACE_FLAG, + parent_namespace, + ] + try: + os.execv(unshare, command) + except OSError: + return False + raise StartupError("private namespace exec unexpectedly returned") + + +def _run_production_server(snapshot_parent: Path | None) -> int: + try: + server = build_production_server(snapshot_parent=snapshot_parent) asyncio.run(_serve_stdio(server)) except (AdapterError, OSError, RuntimeError) as error: print(f"jackel_mcp=refused detail={_bounded_detail(error)}", file=sys.stderr) @@ -1862,5 +3452,24 @@ def main() -> int: return 0 +def main() -> int: + try: + child = _parse_namespace_child(sys.argv[1:]) + except (AdapterError, OSError, RuntimeError) as error: + print(f"jackel_mcp=refused detail={_bounded_detail(error)}", file=sys.stderr) + return 1 + if child is not None: + try: + snapshot_parent = _prepare_private_snapshot_parent(child) + except (AdapterError, OSError, RuntimeError): + # The exact PID/boot/start-time reaper remains the portable fallback + # if this kernel permits namespaces but refuses the private tmpfs. + snapshot_parent = None + return _run_production_server(snapshot_parent) + + _exec_in_private_snapshot_namespace() + return _run_production_server(None) + + if __name__ == "__main__": raise SystemExit(main()) diff --git a/plugins/jackel/mcp/stem.py b/plugins/jackel/mcp/stem.py new file mode 100644 index 0000000..6abb74b --- /dev/null +++ b/plugins/jackel/mcp/stem.py @@ -0,0 +1,1577 @@ +#!/usr/bin/env python3 -B +"""Additive AI-facing STEM workflows for JACKAL's single MCP surface. + +This module is identity-pinned wrapper orchestration, not a second calculator. +Every numeric value returned by a workflow is obtained from a delegated JACKAL +lane. Python selects and presents workflows, validates structure, and renders +views; it does not silently substitute local floating-point arithmetic when a +kernel lane refuses. + +The linked workspace is a visualization of delegated results. Its pixels, +SVG geometry, hover interpolation, and layout are never mathematical evidence. +""" + +from __future__ import annotations + +import copy +import decimal +import hashlib +import html +import json +import re +from typing import Callable + + +STEM_TOOL_NAMES = frozenset( + { + "jackal_aerospace", + "jackal_hypothesis", + "jackal_linked_workspace", + "jackal_matrix", + "jackal_probability", + "jackal_regression", + "jackal_sensor", + } +) + +CONSEQUENCE_CEILING = "informational" +MAX_TOKEN_BYTES = 512 +MAX_EXPRESSION_BYTES = 2048 +MAX_MATRIX_ROWS = 8 +MAX_MATRIX_COLUMNS = 8 +MAX_REGRESSION_POINTS = 128 +MAX_POLYNOMIAL_DEGREE = 5 +MAX_PROBABILITY_TRIALS = 512 +MAX_SENSOR_SAMPLES = 512 +MIN_WORKSPACE_SAMPLES = 17 +MAX_WORKSPACE_SAMPLES = 257 +MAX_RESOURCE_TEXT_BYTES = 2 * 1024 * 1024 + +RATIONAL_TOKEN = re.compile( + r"(?:" + r"-?(?:0|[1-9][0-9]*)(?:/[1-9][0-9]*)?" + r"|-?(?:0|[1-9][0-9]*)\.[0-9]+" + r"|-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?[eE][+-]?[0-9]+" + r")\Z", + re.ASCII, +) +CANONICAL_INTEGER = re.compile(r"(?:0|[1-9][0-9]*)\Z", re.ASCII) +SAFE_LABEL = re.compile(r"[A-Za-z0-9_.:/+-]{1,128}\Z", re.ASCII) +X_TOKEN = re.compile(r"(? str: + if _IDENTITY is None: + raise RuntimeError("STEM identity is unavailable outside integrated dispatch") + return _IDENTITY + + +def _refusal(reason: str, detail: str) -> dict: + return { + "status": "refused", + "reason": reason, + "detail": detail, + "consequence_ceiling": CONSEQUENCE_CEILING, + "identities": {"jackal_stem_sha256": _identity()}, + "non_claims": [ + "A refusal is an answer; no weaker lane or local arithmetic was substituted", + "No mathematical, statistical, sensor, aerospace, or visual conclusion was established", + ], + } + + +def _kernel_call(tool: str, arguments: dict, *, allow_refusal: bool = False) -> dict: + if _KERNEL is None: + raise Refusal("kernel-unavailable", "STEM module is not attached to JACKAL") + result = _KERNEL.call(tool, arguments) + if not isinstance(result, dict): + raise Refusal("kernel-error", "JACKAL returned a non-object") + trace = { + "tool": tool, + "arguments": copy.deepcopy(arguments), + "status": result.get("status", "unknown"), + } + fields = result.get("fields") + if isinstance(fields, dict) and isinstance(fields.get("parsed"), str): + trace["parsed"] = fields["parsed"] + if isinstance(result.get("engine_output"), str): + trace["engine_output"] = result["engine_output"] + _TRACE.append(trace) + if result.get("status") == "refused" and not allow_refusal: + raise Refusal( + f"kernel-refused:{result.get('reason', 'unknown')}", + str(result.get("detail", "the delegated JACKAL lane refused")), + ) + return result + + +def _token(value: object, subject: str) -> str: + if ( + not isinstance(value, str) + or not value + or len(value.encode("utf-8")) > MAX_TOKEN_BYTES + or RATIONAL_TOKEN.fullmatch(value) is None + ): + raise Refusal( + "args", + f"{subject} must be a bounded integer, decimal, scientific literal, or rational", + ) + return value + + +def _integer(value: object, subject: str, *, maximum: int) -> tuple[str, int]: + if ( + not isinstance(value, str) + or CANONICAL_INTEGER.fullmatch(value) is None + or len(value) > 6 + ): + raise Refusal("args", f"{subject} must be a canonical nonnegative integer string") + parsed = int(value) + if parsed > maximum: + raise Refusal("budget", f"{subject} exceeds the admitted resource budget") + return value, parsed + + +def _label(value: object, subject: str) -> str: + if not isinstance(value, str) or SAFE_LABEL.fullmatch(value) is None: + raise Refusal("args", f"{subject} must be a bounded ASCII identifier") + return value + + +def _exact(expression: str) -> str: + result = _kernel_call("jackal_exact", {"expression": expression}) + fields = result.get("fields") + exact = fields.get("exact") if isinstance(fields, dict) else None + if ( + result.get("status") != "exact" + or result.get("formal") is not False + or not isinstance(exact, str) + ): + raise Refusal("kernel-error", "jackal_exact returned no canonical exact value") + return exact + + +def _evaluate(expression: str) -> tuple[str, dict]: + result = _kernel_call("jackal_evaluate", {"expression": expression}) + rendered = result.get("engine_output") + if ( + result.get("status") != "estimated" + or result.get("formal") is not False + or not isinstance(rendered, str) + ): + raise Refusal("kernel-error", "jackal_evaluate returned no estimated value") + return rendered, result + + +def _add(left: str, right: str) -> str: + return _exact(f"({left})+({right})") + + +def _sub(left: str, right: str) -> str: + return _exact(f"({left})-({right})") + + +def _mul(left: str, right: str) -> str: + return _exact(f"({left})*({right})") + + +def _div(left: str, right: str) -> str: + return _exact(f"({left})/({right})") + + +def _neg(value: str) -> str: + return _exact(f"-({value})") + + +def _sum(values: list[str]) -> str: + if not values: + return _exact("0") + return _exact("+".join(f"({value})" for value in values)) + + +def _count_exact(count: int) -> str: + if count <= 0: + return _exact("0") + return _exact("+".join("1" for _ in range(count))) + + +def _sign(value: str) -> int: + canonical = _exact(value) + if canonical == "0": + return 0 + if canonical.startswith("-"): + return -1 + return 1 + + +def _compare(left: str, right: str) -> int: + return _sign(f"({left})-({right})") + + +def _require_positive(value: str, subject: str) -> None: + if _compare(value, "0") <= 0: + raise Refusal("domain", f"{subject} must be strictly positive") + + +def _matrix(value: object, subject: str) -> list[list[str]]: + if not isinstance(value, list) or not value or len(value) > MAX_MATRIX_ROWS: + raise Refusal("matrix-shape", f"{subject} must be a nonempty bounded row array") + width: int | None = None + result: list[list[str]] = [] + for row_index, row in enumerate(value): + if not isinstance(row, list) or not row or len(row) > MAX_MATRIX_COLUMNS: + raise Refusal("matrix-shape", f"{subject}[{row_index}] is not a bounded row") + if width is None: + width = len(row) + if len(row) != width: + raise Refusal("matrix-shape", f"{subject} rows must have equal length") + result.append( + [_exact(_token(cell, f"{subject}[{row_index}][{column_index}]")) + for column_index, cell in enumerate(row)] + ) + return result + + +def _matrix_clone(matrix: list[list[str]]) -> list[list[str]]: + return [list(row) for row in matrix] + + +def _matrix_rref_values(matrix: list[list[str]]) -> tuple[list[list[str]], int]: + values = _matrix_clone(matrix) + rows = len(values) + columns = len(values[0]) + pivot_row = 0 + for column in range(columns): + if pivot_row >= rows: + break + selected: int | None = None + for candidate in range(pivot_row, rows): + if _sign(values[candidate][column]) != 0: + selected = candidate + break + if selected is None: + continue + if selected != pivot_row: + values[pivot_row], values[selected] = values[selected], values[pivot_row] + pivot = values[pivot_row][column] + values[pivot_row] = [_div(cell, pivot) for cell in values[pivot_row]] + for row in range(rows): + if row == pivot_row or _sign(values[row][column]) == 0: + continue + factor = values[row][column] + values[row] = [ + _exact(f"({values[row][item]})-({factor})*({values[pivot_row][item]})") + for item in range(columns) + ] + pivot_row += 1 + return values, pivot_row + + +def _matrix_determinant(matrix: list[list[str]]) -> str: + size = len(matrix) + if len(matrix[0]) != size: + raise Refusal("matrix-shape", "determinant requires a square matrix") + values = _matrix_clone(matrix) + determinant = _exact("1") + odd_swaps = False + for column in range(size): + selected: int | None = None + for candidate in range(column, size): + if _sign(values[candidate][column]) != 0: + selected = candidate + break + if selected is None: + return _exact("0") + if selected != column: + values[column], values[selected] = values[selected], values[column] + odd_swaps = not odd_swaps + pivot = values[column][column] + determinant = _mul(determinant, pivot) + for row in range(column + 1, size): + if _sign(values[row][column]) == 0: + continue + factor = _div(values[row][column], pivot) + for item in range(column, size): + values[row][item] = _exact( + f"({values[row][item]})-({factor})*({values[column][item]})" + ) + if odd_swaps: + determinant = _neg(determinant) + return determinant + + +def _matrix_inverse(matrix: list[list[str]]) -> list[list[str]]: + size = len(matrix) + if len(matrix[0]) != size: + raise Refusal("matrix-shape", "inverse requires a square matrix") + augmented: list[list[str]] = [] + for row in range(size): + identity = ["1" if row == column else "0" for column in range(size)] + augmented.append(list(matrix[row]) + identity) + reduced, rank = _matrix_rref_values(augmented) + if rank != size: + raise Refusal("matrix-singular", "matrix has no exact inverse") + for row in range(size): + for column in range(size): + expected = "1" if row == column else "0" + if _compare(reduced[row][column], expected) != 0: + raise Refusal("matrix-singular", "matrix has no exact inverse") + return [row[size:] for row in reduced] + + +def _matrix_solve(matrix: list[list[str]], vector: object) -> list[str]: + size = len(matrix) + if len(matrix[0]) != size: + raise Refusal("matrix-shape", "solve requires a square coefficient matrix") + if not isinstance(vector, list) or len(vector) != size: + raise Refusal("matrix-shape", "vector length must equal the matrix row count") + rhs = [ + _exact(_token(item, f"vector[{index}]")) + for index, item in enumerate(vector) + ] + augmented = [list(matrix[row]) + [rhs[row]] for row in range(size)] + reduced, rank = _matrix_rref_values(augmented) + if rank != size: + raise Refusal("matrix-nonunique", "system does not have one exact solution") + for row in range(size): + for column in range(size): + expected = "1" if row == column else "0" + if _compare(reduced[row][column], expected) != 0: + raise Refusal("matrix-nonunique", "system does not have one exact solution") + return [row[-1] for row in reduced] + + +def _matrix_tool(arguments: dict) -> dict: + operation = arguments.get("operation") + if operation not in {"add", "determinant", "inverse", "multiply", "rref", "solve", "transpose"}: + raise Refusal("operation-unknown", "matrix operation is outside the closed route table") + matrix = _matrix(arguments.get("matrix"), "matrix") + fields: dict[str, object] = {} + field_status: dict[str, str] = {} + if operation == "transpose": + fields["matrix"] = [list(column) for column in zip(*matrix)] + field_status["matrix"] = "exact" + elif operation == "determinant": + fields["determinant"] = _matrix_determinant(matrix) + field_status["determinant"] = "exact" + elif operation == "rref": + reduced, rank = _matrix_rref_values(matrix) + fields["matrix"] = reduced + fields["rank"] = _count_exact(rank) + field_status.update({"matrix": "exact", "rank": "exact"}) + elif operation == "inverse": + fields["matrix"] = _matrix_inverse(matrix) + field_status["matrix"] = "exact" + elif operation == "solve": + fields["solution"] = _matrix_solve(matrix, arguments.get("vector")) + field_status["solution"] = "exact" + else: + second = _matrix(arguments.get("second_matrix"), "second_matrix") + if operation == "add": + if len(second) != len(matrix) or len(second[0]) != len(matrix[0]): + raise Refusal("matrix-shape", "matrix addition requires identical shapes") + fields["matrix"] = [ + [_add(matrix[row][column], second[row][column]) + for column in range(len(matrix[0]))] + for row in range(len(matrix)) + ] + else: + if len(matrix[0]) != len(second): + raise Refusal("matrix-shape", "matrix multiplication inner dimensions differ") + product: list[list[str]] = [] + for row in range(len(matrix)): + out_row: list[str] = [] + for column in range(len(second[0])): + terms = [ + f"({matrix[row][item]})*({second[item][column]})" + for item in range(len(second)) + ] + out_row.append(_exact("+".join(terms))) + product.append(out_row) + fields["matrix"] = product + field_status["matrix"] = "exact" + return { + "status": "exact", + "lane": "matrix-exact-delegated-v1", + "formal": False, + "consequence_ceiling": CONSEQUENCE_CEILING, + "parsed": { + "operation": operation, + "matrix": matrix, + "second_matrix": arguments.get("second_matrix"), + "vector": arguments.get("vector"), + }, + "fields": fields, + "field_status": field_status, + "delegated_to": list(_TRACE), + "identities": {"jackal_stem_sha256": _identity()}, + "non_claims": [ + "Every reported numeric matrix field was produced by delegated status=exact JACKAL rational calls", + "NOT formal-bounded: the row-operation orchestration is identity-pinned and tested, not Lean-proved", + "The delegation trace is reproducibility metadata, not an independent matrix certificate", + "No conditioning, measurement provenance, or physical interpretation is inferred", + ], + } + + +def _regression_tool(arguments: dict) -> dict: + model = arguments.get("model") + if model != "polynomial_ols": + raise Refusal("operation-unknown", "regression currently admits polynomial_ols") + raw_x = arguments.get("x") + raw_y = arguments.get("y") + if ( + not isinstance(raw_x, list) + or not isinstance(raw_y, list) + or len(raw_x) != len(raw_y) + or len(raw_x) < 2 + or len(raw_x) > MAX_REGRESSION_POINTS + ): + raise Refusal("sample-shape", "x and y must be equal bounded arrays with at least two points") + x = [_token(value, f"x[{index}]") for index, value in enumerate(raw_x)] + y = [_token(value, f"y[{index}]") for index, value in enumerate(raw_y)] + degree_text, degree = _integer( + arguments.get("degree"), "degree", maximum=MAX_POLYNOMIAL_DEGREE + ) + if degree < 1 or degree >= len(x): + raise Refusal("model-rank", "degree must be positive and below the sample count") + width = degree + 1 + normal: list[list[str]] = [] + rhs: list[str] = [] + for row in range(width): + normal_row: list[str] = [] + for column in range(width): + exponent = row + column + normal_row.append( + _sum([f"({value})^{exponent}" for value in x]) + if exponent > 0 else _count_exact(len(x)) + ) + normal.append(normal_row) + rhs.append( + _sum( + [ + f"({y[index]})*({x[index]})^{row}" + if row > 0 else y[index] + for index in range(len(x)) + ] + ) + ) + coefficients = _matrix_solve(normal, rhs) + fitted: list[str] = [] + for value in x: + terms = [ + coefficients[power] + if power == 0 else f"({coefficients[power]})*({value})^{power}" + for power in range(width) + ] + fitted.append(_exact("+".join(f"({term})" for term in terms))) + residual_squares = [ + f"(({y[index]})-({fitted[index]}))^2" for index in range(len(y)) + ] + sse = _sum(residual_squares) + mean_y = _div(_sum(y), _count_exact(len(y))) + sst = _sum([f"(({value})-({mean_y}))^2" for value in y]) + r_squared: str | None = None + if _sign(sst) != 0: + r_squared = _exact(f"1-({sse})/({sst})") + expression = "+".join( + coefficients[power] + if power == 0 + else f"({coefficients[power]})*x^{power}" + for power in range(width) + ) + fields: dict[str, object] = { + "coefficients_ascending": coefficients, + "expression": expression, + "fitted": fitted, + "sse": sse, + "sst": sst, + "r_squared": r_squared, + "normal_matrix": normal, + "normal_rhs": rhs, + } + return { + "status": "model-based", + "lane": "regression-polynomial-ols-exact-fields-v1", + "formal": False, + "consequence_ceiling": CONSEQUENCE_CEILING, + "parsed": {"model": model, "degree": degree_text, "x": x, "y": y}, + "fields": fields, + "field_status": { + "coefficients_ascending": "exact", + "fitted": "exact", + "sse": "exact", + "sst": "exact", + "r_squared": "undefined" if r_squared is None else "exact", + }, + "assumptions": [ + "ordinary least squares under the caller-selected polynomial basis", + "the supplied x and y tokens are treated as exact rational data", + ], + "delegated_to": list(_TRACE), + "identities": {"jackal_stem_sha256": _identity()}, + "non_claims": [ + "Exact coefficients do not establish that a polynomial model is appropriate", + "No distribution, independence, homoscedasticity, confidence interval, prediction interval, or causal interpretation is inferred", + "Supplied points are not promoted to measured or representative data", + "NOT formal-bounded: exact rational fields are outside the Lean certificate chain", + ], + } + + +def _binomial_pmf(n_text: str, n: int, k_text: str, k: int, p: str) -> str: + if k > n: + raise Refusal("domain", "k must not exceed n") + complement = _exact(f"({n_text})-({k_text})") + coefficient = "1" + for step in range(1, min(k, n - k) + 1): + coefficient = _exact( + f"({coefficient})*(({n_text})-({step})+1)/({step})" + ) + return _exact( + f"({coefficient})*({p})^({k_text})*(1-({p}))^({complement})" + ) + + +def _binomial_probability_range( + n_text: str, n: int, start: int, stop: int, p: str +) -> list[str]: + """Produce consecutive exact PMFs with one recurrence call per new term.""" + if start < 0 or stop < start or stop > n: + raise Refusal("domain", "binomial probability range is invalid") + if p == "0": + return [_exact("1" if index == 0 else "0") for index in range(start, stop + 1)] + if p == "1": + return [_exact("1" if index == n else "0") for index in range(start, stop + 1)] + probabilities = [_binomial_pmf(n_text, n, str(start), start, p)] + current = probabilities[0] + for index in range(start, stop): + current = _exact( + f"({current})*(({n_text})-({index}))/(({index})+1)*({p})/(1-({p}))" + ) + probabilities.append(current) + return probabilities + + +def _probability_tool(arguments: dict) -> dict: + operation = arguments.get("operation") + if operation not in {"binomial_cdf", "binomial_pmf", "normal_cdf"}: + raise Refusal("operation-unknown", "probability operation is outside the closed route table") + assumptions: list[str] + fields: dict[str, object] + field_status: dict[str, str] + if operation.startswith("binomial"): + n_text, n = _integer(arguments.get("n"), "n", maximum=MAX_PROBABILITY_TRIALS) + k_text, k = _integer(arguments.get("k"), "k", maximum=MAX_PROBABILITY_TRIALS) + p = _exact(_token(arguments.get("p"), "p")) + if k > n or _compare(p, "0") < 0 or _compare(p, "1") > 0: + raise Refusal("domain", "binomial requires 0 <= k <= n and 0 <= p <= 1") + if operation == "binomial_pmf": + probability = _binomial_pmf(n_text, n, k_text, k, p) + else: + probability = _sum(_binomial_probability_range(n_text, n, 0, k, p)) + fields = {"probability": probability} + field_status = {"probability": "exact"} + assumptions = [ + "fixed trial count", + "independent Bernoulli trials", + "constant caller-supplied success probability", + ] + else: + z = _token(arguments.get("z"), "z") + cutoff = _token(arguments.get("tail_cutoff"), "tail_cutoff") + tolerance = _token(arguments.get("tolerance"), "tolerance") + _require_positive(cutoff, "tail_cutoff") + _require_positive(tolerance, "tolerance") + if _compare(z, _neg(cutoff)) < 0 or _compare(z, cutoff) > 0: + raise Refusal("domain", "z must lie inside the caller-declared finite tail cutoff") + result = _kernel_call( + "jackal_integrate_adaptive", + { + "expression": "exp(-x^2/2)/sqrt(2*pi)", + "input_lo": _neg(cutoff), + "input_hi": z, + "tolerance": tolerance, + }, + ) + result_fields = result.get("fields") + probability = result_fields.get("integral") if isinstance(result_fields, dict) else None + if ( + result.get("status") != "estimated" + or result.get("formal") is not False + or not isinstance(probability, str) + ): + raise Refusal("kernel-error", "normal CDF integration returned no integral field") + fields = { + "finite_cutoff_cdf_estimate": probability, + "integration_result": result, + "omitted_left_tail_below": _neg(cutoff), + } + field_status = {"finite_cutoff_cdf_estimate": "estimated"} + assumptions = ["standard normal model", "caller-selected finite tail cutoff"] + return { + "status": "model-based", + "lane": f"probability-{operation}-v1", + "formal": False, + "consequence_ceiling": CONSEQUENCE_CEILING, + "parsed": copy.deepcopy(arguments), + "fields": fields, + "field_status": field_status, + "assumptions": assumptions, + "delegated_to": list(_TRACE), + "identities": {"jackal_stem_sha256": _identity()}, + "non_claims": [ + "A probability computed under a declared distribution does not establish that the data-generating process follows that distribution", + "No input provenance, independence, calibration, or sampling design is verified", + "Normal CDF output is an estimate over a finite caller-selected interval, not a bound on the full infinite-tail probability", + ], + } + + +def _hypothesis_tool(arguments: dict) -> dict: + operation = arguments.get("operation") + alternative = arguments.get("alternative") + if alternative not in {"less", "greater", "two_sided"}: + raise Refusal("args", "alternative must be less, greater, or two_sided") + if operation == "one_sample_z": + mean = _token(arguments.get("sample_mean"), "sample_mean") + null = _token(arguments.get("null_mean"), "null_mean") + sigma = _token(arguments.get("population_sd"), "population_sd") + n_text, _unused_n = _integer( + arguments.get("n"), "n", maximum=MAX_PROBABILITY_TRIALS + ) + cutoff = _token(arguments.get("tail_cutoff"), "tail_cutoff") + tolerance = _token(arguments.get("tolerance"), "tolerance") + _require_positive(sigma, "population_sd") + _require_positive(n_text, "n") + _require_positive(cutoff, "tail_cutoff") + z_expression = f"(({mean})-({null}))/(({sigma})/sqrt({n_text}))" + z, z_result = _evaluate(z_expression) + absolute_z, absolute_result = _evaluate(f"abs({z_expression})") + lower = _neg(cutoff) + if _compare(z, lower) < 0 or _compare(z, cutoff) > 0: + raise Refusal( + "domain", + "the estimated z statistic lies outside the caller-declared finite tail cutoff", + ) + if alternative == "less": + integration_lo, integration_hi = lower, z + multiplier = "1" + elif alternative == "greater": + integration_lo, integration_hi = z, cutoff + multiplier = "1" + else: + integration_lo, integration_hi = absolute_z, cutoff + multiplier = "2" + tail = _kernel_call( + "jackal_integrate_adaptive", + { + "expression": "exp(-x^2/2)/sqrt(2*pi)", + "input_lo": integration_lo, + "input_hi": integration_hi, + "tolerance": tolerance, + }, + ) + tail_fields = tail.get("fields") + tail_value = tail_fields.get("integral") if isinstance(tail_fields, dict) else None + if ( + tail.get("status") != "estimated" + or tail.get("formal") is not False + or not isinstance(tail_value, str) + ): + raise Refusal("kernel-error", "z-test integration returned no integral field") + p_value, p_result = _evaluate(f"({multiplier})*({tail_value})") + fields = { + "z": z, + "p_value_estimate": p_value, + "z_result": z_result, + "absolute_z_result": absolute_result, + "tail_integration_result": tail, + "p_value_render_result": p_result, + } + field_status = {"z": "estimated", "p_value_estimate": "estimated"} + assumptions = [ + "known caller-supplied population standard deviation", + "independent representative observations", + "normal sampling distribution for the standardized mean", + "caller-selected finite normal-tail cutoff", + ] + elif operation == "exact_binomial_tail": + n_text, n = _integer(arguments.get("n"), "n", maximum=MAX_PROBABILITY_TRIALS) + k_text, k = _integer(arguments.get("k"), "k", maximum=MAX_PROBABILITY_TRIALS) + p0 = _exact(_token(arguments.get("p0"), "p0")) + if alternative == "two_sided": + raise Refusal( + "test-definition-ambiguous", + "two-sided exact binomial tests have multiple conventions; choose less or greater", + ) + if k > n or _compare(p0, "0") < 0 or _compare(p0, "1") > 0: + raise Refusal("domain", "exact binomial tail requires 0 <= k <= n and 0 <= p0 <= 1") + start, stop = (0, k) if alternative == "less" else (k, n) + p_value = _sum(_binomial_probability_range(n_text, n, start, stop, p0)) + fields = {"p_value": p_value} + field_status = {"p_value": "exact"} + assumptions = [ + "fixed trial count", + "independent Bernoulli trials", + "constant null success probability", + "one-sided exact tail convention", + ] + else: + raise Refusal("operation-unknown", "hypothesis operation is outside the closed route table") + return { + "status": "model-based", + "lane": f"hypothesis-{operation}-v1", + "formal": False, + "consequence_ceiling": "advisory", + "parsed": copy.deepcopy(arguments), + "fields": fields, + "field_status": field_status, + "assumptions": assumptions, + "delegated_to": list(_TRACE), + "identities": {"jackal_stem_sha256": _identity()}, + "non_claims": [ + "A p-value is conditional on the declared test model and is not the probability that the null hypothesis is true", + "A z-test p-value is an estimate over a finite caller-selected tail interval, not a bound on the full infinite-tail probability", + "No sampling design, independence, distributional fit, multiple-testing correction, effect importance, or decision threshold is verified", + "This result is not a scientific, medical, engineering, or safety decision", + ], + } + + +def _sorted_exact(values: list[str]) -> list[str]: + result: list[str] = [] + for value in values: + position = len(result) + while position > 0 and _compare(value, result[position - 1]) < 0: + position -= 1 + result.insert(position, value) + return result + + +def _sensor_tool(arguments: dict) -> dict: + operation = arguments.get("operation") + if operation not in {"ingest_batch", "linear_calibration"}: + raise Refusal("operation-unknown", "sensor operation is outside the closed route table") + sensor_id = _label(arguments.get("sensor_id"), "sensor_id") + channel = _label(arguments.get("channel"), "channel") + quantity = _label(arguments.get("quantity"), "quantity") + unit = _label(arguments.get("unit"), "unit") + source = arguments.get("source") + observed_at = arguments.get("observed_at") + if not isinstance(source, str) or not source.strip() or len(source.encode("utf-8")) > 1024: + raise Refusal("undeclared-datum", "sensor source must be declared") + if not isinstance(observed_at, str) or not observed_at.strip() or len(observed_at) > 128: + raise Refusal("undeclared-datum", "sensor observation time must be declared") + raw_samples = arguments.get("samples") + if ( + not isinstance(raw_samples, list) + or not raw_samples + or len(raw_samples) > MAX_SENSOR_SAMPLES + ): + raise Refusal("sample-shape", "samples must be a nonempty bounded array") + samples = [_token(value, f"samples[{index}]") for index, value in enumerate(raw_samples)] + given: dict[str, object] = { + "sensor_id": sensor_id, + "channel": channel, + "quantity": quantity, + "unit": unit, + "source": source, + "observed_at": observed_at, + "input_provenance": "supplied", + } + if operation == "linear_calibration": + scale = _token(arguments.get("scale"), "scale") + offset = _token(arguments.get("offset"), "offset") + calibration_source = arguments.get("calibration_source") + calibration_as_of = arguments.get("calibration_as_of") + if ( + not isinstance(calibration_source, str) + or not calibration_source.strip() + or not isinstance(calibration_as_of, str) + or not calibration_as_of.strip() + ): + raise Refusal( + "undeclared-datum", + "linear calibration requires calibration_source and calibration_as_of", + ) + samples = [_exact(f"({scale})*({value})+({offset})") for value in samples] + given["calibration"] = { + "model": "y=scale*x+offset", + "scale": scale, + "offset": offset, + "source": calibration_source, + "as_of": calibration_as_of, + "verified": False, + } + ordered = _sorted_exact(samples) + count = _count_exact(len(samples)) + total = _sum(samples) + mean = _div(total, count) + variance = _div(_sum([f"(({value})-({mean}))^2" for value in samples]), count) + middle = len(ordered) // 2 + median = ( + ordered[middle] + if len(ordered) % 2 == 1 + else _div(_add(ordered[middle - 1], ordered[middle]), "2") + ) + stddev = _sqrt_formal(variance) + return { + "status": "exact-given", + "lane": "sensor-supplied-batch-v1", + "formal": False, + "consequence_ceiling": CONSEQUENCE_CEILING, + "parsed": { + "operation": operation, + "sensor_id": sensor_id, + "channel": channel, + "sample_count": count, + }, + "given": given, + "fields": { + "samples": samples, + "count": count, + "sum": total, + "mean": mean, + "median": median, + "minimum": ordered[0], + "maximum": ordered[-1], + "population_variance": variance, + "population_stddev_enclosure": stddev, + }, + "field_status": { + "samples": "exact-given", + "count": "exact", + "sum": "exact-given", + "mean": "exact-given", + "median": "exact-given", + "minimum": "exact-given", + "maximum": "exact-given", + "population_variance": "exact-given", + "population_stddev_enclosure": "formal-bounded", + }, + "delegated_to": list(_TRACE), + "identities": {"jackal_stem_sha256": _identity()}, + "non_claims": [ + "The samples, source, timestamps, sensor identity, unit, and calibration metadata are caller-supplied and were not authenticated", + "This tool does not claim it opened or read physical hardware; browser or device acquisition must remain separately observable", + "Descriptive summaries do not establish accuracy, calibration, uncertainty, representativeness, independence, or a probability distribution", + "The standard-deviation field is a formal-bounded enclosure of arithmetic over supplied values, not a certified sensor measurement", + ], + } + + +def _sqrt_formal(radicand: str) -> dict: + if _compare(radicand, "0") < 0: + raise Refusal("domain", "aerospace square-root radicand is negative") + result = _kernel_call( + "jackal_sqrt_rat_bound", + {"expression": "sqrt(x)", "input_lo": radicand, "input_hi": radicand}, + ) + if result.get("status") != "formal-bounded" or result.get("checker_rerun") != "ACCEPT": + raise Refusal( + "kernel-error", + "jackal_sqrt_rat_bound did not return a checker-accepted formal-bounded result", + ) + return result + + +def _ln_formal(value: str) -> dict: + _require_positive(value, "logarithm argument") + result = _kernel_call( + "jackal_ln_rat_bound", + {"expression": "ln(x)", "input_lo": value, "input_hi": value}, + ) + if result.get("status") != "formal-bounded" or result.get("checker_rerun") != "ACCEPT": + raise Refusal( + "kernel-error", + "jackal_ln_rat_bound did not return a checker-accepted formal-bounded result", + ) + return result + + +def _aerospace_tool(arguments: dict) -> dict: + operation = arguments.get("operation") + parameters = arguments.get("parameters") + if not isinstance(parameters, dict): + raise Refusal("args", "parameters must be an object") + + def need(name: str) -> str: + value = _token(parameters.get(name), name) + _require_positive(value, name) + return value + + fields: dict[str, object] = {} + field_status: dict[str, str] = {} + assumptions: list[str] = [] + if operation == "circular_orbit": + mu = need("mu") + radius = need("radius") + speed_radicand = _exact(f"({mu})/({radius})") + fields["speed_enclosure"] = _sqrt_formal(speed_radicand) + period, period_result = _evaluate(f"2*pi*sqrt(({radius})^3/({mu}))") + fields.update({"period_estimate": period, "period_result": period_result}) + field_status.update({"speed_enclosure": "formal-bounded", "period_estimate": "estimated"}) + assumptions = ["ideal circular two-body orbit", "point masses", "constant supplied gravitational parameter"] + elif operation == "vis_viva": + mu = need("mu") + radius = need("radius") + semi_major_axis = need("semi_major_axis") + radicand = _exact(f"({mu})*(2/({radius})-1/({semi_major_axis}))") + fields["speed_enclosure"] = _sqrt_formal(radicand) + field_status["speed_enclosure"] = "formal-bounded" + assumptions = ["ideal Keplerian two-body orbit", "osculating semi-major axis supplied by caller"] + elif operation == "rocket_equation": + exhaust_velocity = need("exhaust_velocity") + initial_mass = need("initial_mass") + final_mass = need("final_mass") + if _compare(initial_mass, final_mass) <= 0: + raise Refusal("domain", "initial_mass must exceed final_mass") + ratio = _exact(f"({initial_mass})/({final_mass})") + fields["mass_ratio"] = ratio + fields["ln_mass_ratio_enclosure"] = _ln_formal(ratio) + delta_v, delta_v_result = _evaluate(f"({exhaust_velocity})*ln({ratio})") + fields.update({"delta_v_estimate": delta_v, "delta_v_result": delta_v_result}) + field_status.update( + {"mass_ratio": "exact", "ln_mass_ratio_enclosure": "formal-bounded", "delta_v_estimate": "estimated"} + ) + assumptions = ["ideal Tsiolkovsky rocket equation", "constant effective exhaust velocity", "no gravity or drag losses"] + elif operation == "hohmann_transfer": + mu = need("mu") + r1 = need("r1") + r2 = need("r2") + if _compare(r1, r2) == 0: + raise Refusal("domain", "Hohmann transfer radii must differ") + a_transfer = _exact(f"(({r1})+({r2}))/2") + v1 = _sqrt_formal(_exact(f"({mu})/({r1})")) + v2 = _sqrt_formal(_exact(f"({mu})/({r2})")) + vt1 = _sqrt_formal(_exact(f"({mu})*(2/({r1})-1/({a_transfer}))")) + vt2 = _sqrt_formal(_exact(f"({mu})*(2/({r2})-1/({a_transfer}))")) + delta_v, delta_v_result = _evaluate( + f"abs(sqrt(({mu})*(2/({r1})-1/({a_transfer})))-sqrt(({mu})/({r1})))" + f"+abs(sqrt(({mu})/({r2}))-sqrt(({mu})*(2/({r2})-1/({a_transfer}))))" + ) + transfer_time, time_result = _evaluate(f"pi*sqrt(({a_transfer})^3/({mu}))") + fields.update( + { + "transfer_semi_major_axis": a_transfer, + "initial_circular_speed_enclosure": v1, + "final_circular_speed_enclosure": v2, + "transfer_speed_at_r1_enclosure": vt1, + "transfer_speed_at_r2_enclosure": vt2, + "total_delta_v_estimate": delta_v, + "transfer_time_estimate": transfer_time, + "delta_v_result": delta_v_result, + "time_result": time_result, + } + ) + field_status.update( + { + "transfer_semi_major_axis": "exact", + "initial_circular_speed_enclosure": "formal-bounded", + "final_circular_speed_enclosure": "formal-bounded", + "transfer_speed_at_r1_enclosure": "formal-bounded", + "transfer_speed_at_r2_enclosure": "formal-bounded", + "total_delta_v_estimate": "estimated", + "transfer_time_estimate": "estimated", + } + ) + assumptions = ["coplanar circular two-body endpoint orbits", "impulsive burns", "no perturbations or finite-burn losses"] + elif operation == "plane_change": + velocity = need("velocity") + angle_degrees = _token(parameters.get("angle_degrees"), "angle_degrees") + if _compare(angle_degrees, "0") < 0 or _compare(angle_degrees, "180") > 0: + raise Refusal("domain", "angle_degrees must lie in the closed interval [0,180]") + delta_v, result = _evaluate(f"2*({velocity})*sin(({angle_degrees})*pi/360)") + fields.update({"delta_v_estimate": delta_v, "evaluation_result": result}) + field_status["delta_v_estimate"] = "estimated" + assumptions = ["instantaneous pure plane change", "constant speed across the maneuver", "smallest plane-change angle supplied in degrees"] + else: + raise Refusal("operation-unknown", "aerospace operation is outside the closed model table") + return { + "status": "model-based", + "lane": f"aerospace-{operation}-v1", + "formal": False, + "consequence_ceiling": "advisory", + "parsed": {"operation": operation, "parameters": copy.deepcopy(parameters)}, + "fields": fields, + "field_status": field_status, + "assumptions": assumptions, + "delegated_to": list(_TRACE), + "identities": {"jackal_stem_sha256": _identity()}, + "non_claims": [ + "Formal-bounded scalar subfields certify only admitted arithmetic fragments, not the physical model or mission inputs", + "No perturbations, uncertainty, navigation error, actuator limits, finite-burn effects, atmosphere, ephemeris, or mission safety are inferred unless explicitly named in the selected model", + "This workflow is not the published JACKAL spacecraft finite-burn certificate and cannot inherit that certificate's verdict", + "No aerospace decision may exceed the advisory consequence ceiling of this wrapper result", + ], + } + + +def _json_for_script(value: object) -> str: + return ( + json.dumps(value, ensure_ascii=False, allow_nan=False, sort_keys=True, separators=(",", ":")) + .replace("<", "\\u003c") + .replace("\u2028", "\\u2028") + .replace("\u2029", "\\u2029") + ) + + +def _workspace_document(payload: dict, *, shell: bool = False) -> str: + data = _json_for_script(payload) + shell_note = ( + "This static resource is the linked-workspace shell. Call jackal_linked_workspace " + "to populate it with delegated evidence." + if shell + else "Hover the curve or table to move one evidence cursor through every view." + ) + document = r''' + + + + + +JACKAL Linked Evidence Workspace + + + +
+
+ +
JACKAL / THOTHLINKED EVIDENCE WORKSPACE
+
Active modelNo delegated expression loaded
+
checked view
+
+
+
+
+
Graph + numeric tabledelegated samples
+
+ +
#x exacty estimated
+
+
+
+
Symbolic viewstatus preserved
+
Linked inspectorone cursor
x / exact
f(x) / estimated
Move across the plot or focus a table row.
+
+
+ +
+
''' + html.escape(shell_note) + r'''Pixels are not proof. Use exact or bounded lanes for conclusions.
+
+ + + +''' + if len(document.encode("utf-8")) > MAX_RESOURCE_TEXT_BYTES: + raise Refusal("resource-budget", "linked workspace HTML exceeds the resource budget") + return document + + +def workspace_shell() -> str: + return _workspace_document( + { + "status": "checked", + "expression": "", + "points": [], + "finite_sample_count": "0", + "canonical_text": "", + "derivative_text": "", + "route": [], + }, + shell=True, + ) + + +def _workspace_tool(arguments: dict) -> dict: + expression = arguments.get("expression") + if ( + not isinstance(expression, str) + or not expression + or len(expression.encode("utf-8")) > MAX_EXPRESSION_BYTES + or any(ord(character) < 32 for character in expression) + ): + raise Refusal("args", "expression must be nonempty bounded printable text") + lower = _token(arguments.get("x_min"), "x_min") + upper = _token(arguments.get("x_max"), "x_max") + if _compare(lower, upper) >= 0: + raise Refusal("interval-order", "x_min must be strictly below x_max") + samples_text, samples = _integer( + arguments.get("samples"), "samples", maximum=MAX_WORKSPACE_SAMPLES + ) + if samples < MIN_WORKSPACE_SAMPLES: + raise Refusal("sample-budget", "workspace sample count is below the admitted minimum") + + canonical = _kernel_call("jackal_canon", {"expression": expression}, allow_refusal=True) + derivative = _kernel_call("jackal_diff", {"expression": expression}, allow_refusal=True) + points: list[dict[str, str]] = [] + finite_count = 0 + for index in range(samples): + coordinate = _exact( + f"({lower})+({index})*(({upper})-({lower}))/(({samples_text})-1)" + ) + substituted = X_TOKEN.sub(f"({coordinate})", expression) + evaluated = _kernel_call( + "jackal_evaluate", {"expression": substituted}, allow_refusal=True + ) + rendered = evaluated.get("engine_output") + if evaluated.get("status") != "estimated" or not isinstance(rendered, str): + points.append( + { + "x": coordinate, + "status": "refused" if evaluated.get("status") == "refused" else "indeterminate", + "reason": str(evaluated.get("reason", "no finite delegated value")), + } + ) + continue + try: + visual_value = decimal.Decimal(rendered) + except decimal.InvalidOperation: + visual_value = decimal.Decimal("NaN") + if not visual_value.is_finite(): + points.append({"x": coordinate, "status": "indeterminate", "reason": "non-finite"}) + continue + finite_count += 1 + points.append({"x": coordinate, "y": rendered, "status": "estimated"}) + + canonical_text = canonical.get("engine_output") + derivative_text = derivative.get("engine_output") + route: list[dict[str, str]] = [] + for item in _TRACE: + route.append( + { + "tool": str(item.get("tool", "unknown")), + "status": str(item.get("status", "unknown")), + "parsed": str(item.get("parsed", item.get("engine_output", "delegated call")))[:280], + } + ) + finite_count_exact = _count_exact(finite_count) + payload = { + "status": "estimated", + "expression": expression, + "points": points, + "finite_sample_count": finite_count_exact, + "canonical_text": canonical_text if isinstance(canonical_text, str) else "Canonical view refused", + "derivative_text": derivative_text if isinstance(derivative_text, str) else "Derivative view refused", + "route": route, + } + resource_text = _workspace_document(payload) + resource_digest = hashlib.sha256(resource_text.encode("utf-8")).hexdigest() + summary = ( + "JACKAL linked evidence workspace: symbolic, numeric, graph, table, sensor dock, " + "and evidence-route views synchronized over delegated results. Pixels are not proof." + ) + return { + "status": "checked", + "lane": "linked-evidence-workspace-v1", + "formal": False, + "consequence_ceiling": CONSEQUENCE_CEILING, + "parsed": { + "expression": expression, + "x_interval": [lower, upper], + "samples": samples_text, + }, + "fields": { + "points": points, + "finite_sample_count": finite_count_exact, + "canonical_result": canonical, + "derivative_result": derivative, + "resource_uri": f"ui://jackal/linked-workspace/{resource_digest}", + "resource_sha256": resource_digest, + "resource_mime_type": "text/html", + }, + "field_status": { + "points.x": "exact", + "points.y": "estimated", + "canonical_result": str(canonical.get("status", "indeterminate")), + "derivative_result": str(derivative.get("status", "indeterminate")), + "resource_sha256": "checked", + }, + "delegated_to": list(_TRACE), + "identities": {"jackal_stem_sha256": _identity()}, + "non_claims": [ + "The linked workspace is a presentation artifact and adds no assurance to any delegated result", + "SVG geometry, graph pixels, line segments, hover selection, table ordering, and browser sensor display are not evidence", + "Sampling cannot prove continuity, roots, extrema, absence of poles, or behavior between samples", + "The browser sensor dock does not mint observed or measured provenance; export data through jackal_sensor with its source metadata", + ], + "_mcp_content": [ + {"type": "text", "text": summary}, + { + "type": "resource", + "resource": { + "uri": f"ui://jackal/linked-workspace/{resource_digest}", + "mimeType": "text/html", + "text": resource_text, + }, + }, + ], + } + + +def dispatch_integrated( + name: str, + arguments: dict, + kernel_call: Callable[[str, dict], dict], + identity: str, +) -> dict: + global _KERNEL, _IDENTITY, _TRACE + if name not in STEM_TOOL_NAMES or not isinstance(arguments, dict): + return { + "status": "refused", + "reason": "tool-unknown", + "detail": "STEM tool name or arguments are invalid", + } + + class Kernel: + @staticmethod + def call(tool: str, delegated_arguments: dict) -> dict: + return kernel_call(tool, delegated_arguments) + + _KERNEL = Kernel() + _IDENTITY = identity + _TRACE = [] + try: + if name == "jackal_matrix": + return _matrix_tool(arguments) + if name == "jackal_regression": + return _regression_tool(arguments) + if name == "jackal_probability": + return _probability_tool(arguments) + if name == "jackal_hypothesis": + return _hypothesis_tool(arguments) + if name == "jackal_sensor": + return _sensor_tool(arguments) + if name == "jackal_aerospace": + return _aerospace_tool(arguments) + return _workspace_tool(arguments) + except Refusal as error: + return _refusal(error.reason, error.detail) + except Exception: + return _refusal("stem-error", "STEM orchestration failed closed") + finally: + _KERNEL = None + _IDENTITY = None + _TRACE = [] + + +def _schema(properties: dict, required: list[str]) -> dict: + return { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": properties, + "required": required, + "additionalProperties": False, + } + + +def _definition(name: str, title: str, description: str, schema: dict) -> dict: + return { + "name": name, + "title": title, + "description": description, + "inputSchema": schema, + "annotations": { + "readOnlyHint": True, + "destructiveHint": False, + "idempotentHint": True, + "openWorldHint": False, + }, + } + + +def _numeric_array(description: str) -> dict: + return { + "type": "array", + "items": {"type": "string"}, + "description": description, + } + + +def tool_definitions() -> list[dict]: + matrix_schema = { + "type": "array", + "items": {"type": "array", "items": {"type": "string"}}, + "description": "Rectangular array of exact integer, decimal, scientific, or rational tokens.", + } + return [ + _definition( + "jackal_matrix", + "JACKAL exact matrices", + "Exact-rational matrix addition, multiplication, transpose, determinant, RREF, inverse, and linear solve. Every reported numeric cell delegates to jackal_exact; orchestration is identity-pinned and tested, not formal-bounded.", + _schema( + { + "operation": {"type": "string", "enum": ["add", "determinant", "inverse", "multiply", "rref", "solve", "transpose"]}, + "matrix": matrix_schema, + "second_matrix": matrix_schema, + "vector": _numeric_array("Right-hand-side vector for solve."), + }, + ["operation", "matrix"], + ), + ), + _definition( + "jackal_regression", + "JACKAL exact-field regression", + "Polynomial ordinary-least-squares regression with exact-rational normal equations, coefficients, fitted values, SSE, SST, and R-squared. Top-level status is model-based because exact fitting does not validate the model.", + _schema( + { + "model": {"type": "string", "enum": ["polynomial_ols"]}, + "degree": {"type": "string", "description": "Canonical polynomial degree within the tool budget."}, + "x": _numeric_array("Exact-rational predictor values."), + "y": _numeric_array("Exact-rational response values."), + }, + ["model", "degree", "x", "y"], + ), + ), + _definition( + "jackal_probability", + "JACKAL probability models", + "Exact binomial PMF/CDF fields or estimated finite-cutoff normal CDF integration. All outputs remain model-based and preserve distributional assumptions.", + _schema( + { + "operation": {"type": "string", "enum": ["binomial_pmf", "binomial_cdf", "normal_cdf"]}, + "n": {"type": "string"}, + "k": {"type": "string"}, + "p": {"type": "string"}, + "z": {"type": "string"}, + "tail_cutoff": {"type": "string"}, + "tolerance": {"type": "string"}, + }, + ["operation"], + ), + ), + _definition( + "jackal_hypothesis", + "JACKAL hypothesis tests", + "One-sample z testing with estimated finite-tail p-values or exact one-sided binomial tails. Returns model-based with test assumptions and never interprets a p-value as the probability a hypothesis is true.", + _schema( + { + "operation": {"type": "string", "enum": ["one_sample_z", "exact_binomial_tail"]}, + "alternative": {"type": "string", "enum": ["less", "greater", "two_sided"]}, + "sample_mean": {"type": "string"}, + "null_mean": {"type": "string"}, + "population_sd": {"type": "string"}, + "n": {"type": "string"}, + "k": {"type": "string"}, + "p0": {"type": "string"}, + "tail_cutoff": {"type": "string"}, + "tolerance": {"type": "string"}, + }, + ["operation", "alternative"], + ), + ), + _definition( + "jackal_sensor", + "JACKAL sensor data", + "Ingest a caller-supplied sensor batch or apply a declared exact linear calibration, preserving supplied provenance and returning exact descriptive fields plus a formal-bounded standard-deviation enclosure.", + _schema( + { + "operation": {"type": "string", "enum": ["ingest_batch", "linear_calibration"]}, + "sensor_id": {"type": "string"}, + "channel": {"type": "string"}, + "quantity": {"type": "string"}, + "unit": {"type": "string"}, + "samples": _numeric_array("Raw caller-supplied sample tokens."), + "source": {"type": "string"}, + "observed_at": {"type": "string"}, + "scale": {"type": "string"}, + "offset": {"type": "string"}, + "calibration_source": {"type": "string"}, + "calibration_as_of": {"type": "string"}, + }, + ["operation", "sensor_id", "channel", "quantity", "unit", "samples", "source", "observed_at"], + ), + ), + _definition( + "jackal_aerospace", + "JACKAL aerospace models", + "Claim-aware circular-orbit, vis-viva, rocket-equation, Hohmann-transfer, and plane-change workflows. Exact/formal scalar fields remain conditional on explicit physical-model assumptions and never inherit the published finite-burn certificate.", + _schema( + { + "operation": {"type": "string", "enum": ["circular_orbit", "vis_viva", "rocket_equation", "hohmann_transfer", "plane_change"]}, + "parameters": {"type": "object", "description": "Named exact-rational parameters for the selected aerospace model."}, + }, + ["operation", "parameters"], + ), + ), + _definition( + "jackal_linked_workspace", + "JACKAL linked evidence workspace", + "Return a professional self-contained HTML workspace linking symbolic, numeric, graph, table, sensor-dock, and evidence-route views over delegated JACKAL results. The UI adds no assurance; pixels are not proof.", + _schema( + { + "expression": {"type": "string", "description": "JACKAL expression in plotting variable x."}, + "x_min": {"type": "string", "description": "Exact-rational lower x bound."}, + "x_max": {"type": "string", "description": "Exact-rational upper x bound."}, + "samples": {"type": "string", "description": "Canonical bounded sample count."}, + }, + ["expression", "x_min", "x_max", "samples"], + ), + ), + ] + + +if __name__ == "__main__": + raise SystemExit("stem.py is an identity-pinned JACKAL module, not a standalone service") diff --git a/plugins/jackel/runtime_manifest_baseline_v173.json b/plugins/jackel/runtime_manifest_baseline_v173.json new file mode 100644 index 0000000..746512c --- /dev/null +++ b/plugins/jackel/runtime_manifest_baseline_v173.json @@ -0,0 +1,39 @@ +{ + "name": "jackel", + "version": "0.1.0+codex.20260820135554", + "description": "Expose JACKAL's claim-aware computation, domain-pack, and program-evidence kernel to Codex.", + "author": { + "name": "Anubis Quantum Cipher", + "url": "https://github.com/AnubisQuantumCipher" + }, + "homepage": "https://github.com/AnubisQuantumCipher/jackal", + "repository": "https://github.com/AnubisQuantumCipher/jackal", + "license": "MIT", + "keywords": [ + "jackel", + "mathematics", + "numerical-trust", + "formal-verification", + "evidence", + "mcp" + ], + "skills": "./skills/", + "mcpServers": "./.mcp.json", + "interface": { + "displayName": "JACKAL", + "shortDescription": "Claim-aware computation with explicit evidence classes", + "longDescription": "Expose JACKAL's 41-tool v1.7.3 release runtime through Codex. The MCP adapter copies the parsed runtime result object into structuredContent unchanged; its only adapter-local tool result is status=refused reason=plugin-busy. Runtime result and assurance vocabulary: ok, exact, structural-exact, formal-bounded, bounded, checked, estimated, model-based, verified, verified-program-evidence, verified-program-receipt, indeterminate, and refused. Formal-bounded is limited to checker-admitted fragments; program evidence leaves construct-totality, source, and runtime residuals open. Requires Apple Silicon macOS and Python >=3.10 at /opt/homebrew/bin/python3 (install with brew install python).", + "developerName": "Anubis Quantum Cipher", + "category": "Productivity", + "capabilities": [ + "Interactive" + ], + "websiteURL": "https://github.com/AnubisQuantumCipher/jackal", + "defaultPrompt": [ + "Classify and verify this numerical claim with JACKAL.", + "Find the strongest supported bound and refuse any silent downgrade.", + "Verify this receipt or claim bundle against my pinned expectations.", + "Verify this Anubis Safe program-evidence package without executing its artifact." + ] + } +} diff --git a/plugins/jackel/scripts/launch_mcp.sh b/plugins/jackel/scripts/launch_mcp.sh new file mode 100755 index 0000000..4ab76c6 --- /dev/null +++ b/plugins/jackel/scripts/launch_mcp.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# Portable launcher for the JACKAL MCP adapter. +# +# Behaviourally identical to launch_mcp.zsh: it never searches the caller PATH, +# probes a fixed list of absolute interpreters, and execs the first one that +# passes the full capability probe. Written in POSIX sh so the plugin runs on +# hosts without zsh. + +set -u + +# Fixed absolute candidates only. The caller PATH is never consulted. +PYTHON_CANDIDATES="/opt/homebrew/bin/python3 +/usr/local/bin/python3 +/usr/bin/python3" + +script_path=$0 +case $script_path in + /*) ;; + *) script_path=$PWD/$script_path ;; +esac +# Parameter expansion only: `dirname` is an external command and the caller +# PATH is deliberately untrusted, so resolving the plugin root must not depend +# on anything outside the shell itself. `cd` and `pwd` are builtins. +scripts_dir=${script_path%/*} +plugin_root=$(CDPATH= cd -- "$scripts_dir/.." && pwd -P) || exit 126 + +target="$plugin_root/mcp/server.py" +if [ "$#" -gt 0 ]; then + if [ "$1" != "provision" ]; then + printf '%s\n' "jackal_mcp=refused reason=invalid-launcher-arguments" >&2 + exit 64 + fi + shift + target="$plugin_root/scripts/provision_runtime.py" +fi + +probe='import ctypes, os, platform, selectors, signal, socket, sys, tarfile, urllib.request +from pathlib import Path +assert sys.version_info >= (3, 10) +required_os = ("CLD_DUMPED", "CLD_EXITED", "CLD_KILLED", "O_CREAT", "O_DIRECTORY", "O_EXCL", "O_NOFOLLOW", "O_NONBLOCK", "O_RDONLY", "O_WRONLY", "P_PID", "WEXITED", "WNOHANG", "WNOWAIT", "access", "dup", "fchmod", "fstat", "fsync", "killpg", "lseek", "mkdir", "open", "read", "replace", "scandir", "set_blocking", "stat", "waitid", "write") +assert all(hasattr(os, name) for name in required_os) +assert hasattr(socket, "socketpair") +libc = ctypes.CDLL(None) +atomic_rename = {"Darwin": "renameatx_np", "Linux": "renameat2"}.get(platform.system()) +assert atomic_rename is not None +assert callable(getattr(libc, atomic_rename, None)) +platform.system(); platform.machine(); Path("/").is_absolute() +selector = selectors.DefaultSelector(); selector.close() +assert callable(signal.setitimer) and callable(signal.getitimer) +assert signal.ITIMER_REAL >= 0 and signal.SIGALRM > 0 +assert callable(tarfile.open) and callable(urllib.request.urlopen)' + +PYTHONDONTWRITEBYTECODE=1 +export PYTHONDONTWRITEBYTECODE + +IFS=' +' +for python in $PYTHON_CANDIDATES; do + if [ -f "$python" ] && [ -x "$python" ] && \ + "$python" -I -S -B -c "$probe" /dev/null 2>/dev/null; then + exec "$python" -I -S -B "$target" "$@" + fi +done + +printf '%s\n' "jackal_mcp=refused reason=no-compatible-python requirement='Python >=3.10 with an atomic no-replace rename (Darwin renameatx_np / Linux renameat2) at one of the fixed candidate paths' recovery='macOS: brew install python | Linux: install a distribution python3 >=3.10 at /usr/bin/python3'" >&2 +exit 126 diff --git a/plugins/jackel/scripts/launch_mcp.zsh b/plugins/jackel/scripts/launch_mcp.zsh index 564f3f3..110e97c 100755 --- a/plugins/jackel/scripts/launch_mcp.zsh +++ b/plugins/jackel/scripts/launch_mcp.zsh @@ -26,7 +26,9 @@ required_os = ("CLD_DUMPED", "CLD_EXITED", "CLD_KILLED", "O_CREAT", "O_DIRECTORY assert all(hasattr(os, name) for name in required_os) assert hasattr(socket, "socketpair") libc = ctypes.CDLL(None) -assert callable(getattr(libc, "renameatx_np", None)) +atomic_rename = {"Darwin": "renameatx_np", "Linux": "renameat2"}.get(platform.system()) +assert atomic_rename is not None +assert callable(getattr(libc, atomic_rename, None)) platform.system(); platform.machine(); Path("/").is_absolute() selector = selectors.DefaultSelector(); selector.close() assert callable(signal.setitimer) and callable(signal.getitimer) @@ -41,5 +43,5 @@ for python in "${PYTHON_CANDIDATES[@]}"; do fi done -print -u2 -r -- "jackal_mcp=refused reason=no-compatible-python requirement='Python >=3.10 at /opt/homebrew/bin/python3' recovery='brew install python'" +print -u2 -r -- "jackal_mcp=refused reason=no-compatible-python requirement='Python >=3.10 with an atomic no-replace rename (Darwin renameatx_np / Linux renameat2) at one of the fixed candidate paths' recovery='macOS: brew install python | Linux: install a distribution python3 >=3.10 at /usr/bin/python3'" exit 126 diff --git a/plugins/jackel/scripts/provision_runtime.py b/plugins/jackel/scripts/provision_runtime.py index 932f42a..b067a4c 100644 --- a/plugins/jackel/scripts/provision_runtime.py +++ b/plugins/jackel/scripts/provision_runtime.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Install the one pinned JACKAL macOS arm64 runtime, fail closed.""" +"""Install the one pinned JACKAL runtime for a supported host, fail closed.""" from __future__ import annotations @@ -23,6 +23,7 @@ import time import unicodedata import urllib.request +from dataclasses import dataclass from pathlib import Path, PurePosixPath from typing import Callable, Iterable, Mapping @@ -36,6 +37,45 @@ EXTRACTED_SIZE = 555511970 SHA256SUMS_SHA256 = "a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a" PACKAGE_DIRECTORY = "jackal-v1.7.3-macos-arm64" + +# A host is supported only when its atomic no-replace install primitive and its +# release pin are both known. Adding a row is not enough to make a runtime +# exist: RELEASE_PINS decides whether an asset has actually been published. +SUPPORTED_HOSTS = { + ("Darwin", "arm64"): "macos-arm64", + ("Linux", "aarch64"): "linux-aarch64", + ("Linux", "x86_64"): "linux-x86_64", +} + +# ``None`` means "this host is supported by the installer but no release asset +# has been published for it yet". It must refuse, never fall back to another +# host's bytes. +RELEASE_PINS: dict[str, dict[str, object] | None] = { + "macos-arm64": { + "asset": ASSET, + "url": URL, + "package_size": PACKAGE_SIZE, + "package_sha256": PACKAGE_SHA256, + "extracted_size": EXTRACTED_SIZE, + "sha256sums_sha256": SHA256SUMS_SHA256, + "package_directory": PACKAGE_DIRECTORY, + }, + "linux-aarch64": { + # Locally built runtime (source build on this host). No published + # upstream asset — install with `provision --tarball `. + "asset": "jackal-v1.7.3-linux-aarch64.tar.gz", + "url": None, + "package_size": 204082823, + "package_sha256": "0b239bc7a96d75537706ab1aebbc271150c663048f49711107ffe1b93f7d743d", + "extracted_size": 778795307, + "sha256sums_sha256": "bbbb7aa97368232580caa7914b00b577bc2601c41f38d4cd78b9a34178563671", + "package_directory": "jackal-v1.7.3-linux-aarch64", + }, + # Gate declared for the planned bare-metal Linux x86_64 (iMac Pro). No pin + # and no function claimed until a native package is built and OBSERVED on + # x86_64 hardware or an explicitly authorized x86_64 substrate. + "linux-x86_64": None, +} MAX_ARCHIVE_MEMBERS = 8192 MAX_RUNTIME_RECORDS = MAX_ARCHIVE_MEMBERS MAX_RUNTIME_ENTRIES = MAX_ARCHIVE_MEMBERS + 2 @@ -48,13 +88,37 @@ DOWNLOAD_TOTAL_TIMEOUT = 300.0 SELFTEST_TIMEOUT = 30.0 SELFTEST_OUTPUT_LIMIT = 64 * 1024 -SNAPSHOT_BYTE_LIMIT = EXTRACTED_SIZE + 1024 * 1024 -MAX_RUNTIME_FILE_BYTES = EXTRACTED_SIZE +# Byte caps bound extraction/verification against zip-bomb inputs. They must fit +# the LARGEST supported runtime (RELEASE_PINS is defined above), not only macOS; +# exact integrity stays pinned by each host's extracted_size and SHA256SUMS. +_MAX_SUPPORTED_EXTRACTED = max( + [EXTRACTED_SIZE] + [pin["extracted_size"] for pin in RELEASE_PINS.values() if pin] +) +SNAPSHOT_BYTE_LIMIT = _MAX_SUPPORTED_EXTRACTED + 1024 * 1024 +MAX_RUNTIME_FILE_BYTES = _MAX_SUPPORTED_EXTRACTED MAX_RUNTIME_TOTAL_BYTES = SNAPSHOT_BYTE_LIMIT RUNTIME_ENV_ALLOWLIST = ("JACKAL_HOME",) FIXED_SYSTEM_PATH = "/usr/bin:/bin:/usr/sbin:/sbin" +SNAPSHOT_PREFIX = "jackal-codex-runtime-" +SNAPSHOT_OWNER_FILE = ".owner" +SNAPSHOT_RUNTIME_DIRECTORY = "runtime" +SNAPSHOT_OWNER_SCHEMA = "jackal-runtime-snapshot-owner-v1" +MAX_SNAPSHOT_OWNER_BYTES = 1024 +MAX_SNAPSHOT_NAME_BYTES = 240 _CHECKSUM_LINE = re.compile(r"([0-9a-f]{64}) \./([^\n]+)", re.ASCII) +_LINUX_BOOT_ID = re.compile( + r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", + re.ASCII, +) +_DARWIN_BOOT_TIME = re.compile(r"\bsec = ([0-9]+), usec = ([0-9]+)\b", re.ASCII) +_SNAPSHOT_OWNER_NAME = re.compile( + re.escape(SNAPSHOT_PREFIX) + + r"v1\.([1-9][0-9]*)\.([0-9a-f]+)\.([0-9a-f]+)\.[a-z0-9_]+", + re.ASCII, +) +DARWIN_PROC_PIDTBSDINFO = 3 +DARWIN_MAXCOMLEN = 16 class ProvisionError(RuntimeError): @@ -65,21 +129,124 @@ class _LeaderAnchorLost(ProvisionError): pass -def default_runtime_target(home: Path | None = None) -> Path: +@dataclass(frozen=True) +class SnapshotOwnerIdentity: + """Exact process incarnation that owns one private runtime snapshot.""" + + pid: int + start_time: str + boot_id: str + + +class _DarwinBSDInfo(ctypes.Structure): + """Public `proc_bsdinfo` layout from Darwin's sys/proc_info.h.""" + + _fields_ = [ + ("pbi_flags", ctypes.c_uint32), + ("pbi_status", ctypes.c_uint32), + ("pbi_xstatus", ctypes.c_uint32), + ("pbi_pid", ctypes.c_uint32), + ("pbi_ppid", ctypes.c_uint32), + ("pbi_uid", ctypes.c_uint32), + ("pbi_gid", ctypes.c_uint32), + ("pbi_ruid", ctypes.c_uint32), + ("pbi_rgid", ctypes.c_uint32), + ("pbi_svuid", ctypes.c_uint32), + ("pbi_svgid", ctypes.c_uint32), + ("rfu_1", ctypes.c_uint32), + ("pbi_comm", ctypes.c_char * DARWIN_MAXCOMLEN), + ("pbi_name", ctypes.c_char * (2 * DARWIN_MAXCOMLEN)), + ("pbi_nfiles", ctypes.c_uint32), + ("pbi_pgid", ctypes.c_uint32), + ("pbi_pjobc", ctypes.c_uint32), + ("e_tdev", ctypes.c_uint32), + ("e_tpgid", ctypes.c_uint32), + ("pbi_nice", ctypes.c_int32), + ("pbi_start_tvsec", ctypes.c_uint64), + ("pbi_start_tvusec", ctypes.c_uint64), + ] + + +def _data_home(root: Path, system: str | None = None) -> Path: + """Per-host application data root. + + Fixed by platform, never read from the environment: the launcher sanitizes + the environment down to RUNTIME_ENV_ALLOWLIST, so an installer path that + honoured XDG_DATA_HOME would be attacker-influenced on exactly the hosts + that need it least. + """ + actual_system = platform.system() if system is None else system + if actual_system == "Darwin": + return root / "Library/Application Support" + return root / ".local/share" + + +def default_runtime_target(home: Path | None = None, system: str | None = None) -> Path: root = Path.home() if home is None else Path(home) - return root / "Library/Application Support/JACKAL/runtimes" / EPOCH + return _data_home(root, system) / "JACKAL/runtimes" / EPOCH -def default_locator_path(home: Path | None = None) -> Path: +def default_locator_path(home: Path | None = None, system: str | None = None) -> Path: root = Path.home() if home is None else Path(home) - return root / "Library/Application Support/JACKAL/codex-plugin/runtime.json" + return _data_home(root, system) / "JACKAL/codex-plugin/runtime.json" -def validate_host(system: str | None = None, machine: str | None = None) -> None: +def resolve_host(system: str | None = None, machine: str | None = None) -> str: + """Return the release host tag, or refuse. Never guesses a nearby host.""" actual_system = platform.system() if system is None else system actual_machine = platform.machine() if machine is None else machine - if actual_system != "Darwin" or actual_machine != "arm64": - raise ProvisionError(f"unsupported host: {actual_system}/{actual_machine}; requires Darwin/arm64") + tag = SUPPORTED_HOSTS.get((actual_system, actual_machine)) + if tag is None: + supported = ", ".join( + f"{key[0]}/{key[1]}" for key in sorted(SUPPORTED_HOSTS) + ) + raise ProvisionError( + f"unsupported host: {actual_system}/{actual_machine}; requires one of {supported}" + ) + return tag + + +def validate_host(system: str | None = None, machine: str | None = None) -> None: + resolve_host(system, machine) + + +def effective_release_pins(system: str | None = None, machine: str | None = None) -> dict[str, object]: + """The (epoch, asset, size, sha256, directory) this host installs/serves. + + Falls back to the built-in macOS constants when the host has a published + pin identical to them; otherwise returns the host's own release pin values. + Used by the MCP server so its locator/metadata checks match whatever the + provisioner actually installed on this host. + """ + host_tag = resolve_host(system, machine) + pin = RELEASE_PINS.get(host_tag) + if not pin: + return { + "epoch": EPOCH, "asset": ASSET, + "package_size": PACKAGE_SIZE, "package_sha256": PACKAGE_SHA256, + "package_directory": PACKAGE_DIRECTORY, + "sha256sums_sha256": SHA256SUMS_SHA256, + } + return { + "epoch": EPOCH, "asset": pin["asset"], + "package_size": pin["package_size"], "package_sha256": pin["package_sha256"], + "package_directory": pin["package_directory"], + "sha256sums_sha256": pin["sha256sums_sha256"], + } + + +def release_pin(host_tag: str) -> dict[str, object]: + """Return the published release pin for a host, or refuse.""" + try: + pin = RELEASE_PINS[host_tag] + except KeyError: + raise ProvisionError(f"no release pin table entry for host {host_tag}") from None + if pin is None: + raise ProvisionError( + f"no published release asset for host {host_tag}; " + "supply a locally built runtime and its pins instead" + ) + return pin def runtime_subprocess_environment( @@ -1028,13 +1195,495 @@ def validate_runtime( return records +class _ProcessGone(ProvisionError): + pass + + +class _SnapshotOwnerStampIncomplete(ProvisionError): + pass + + +def _read_kernel_file(path: Path | str, *, byte_limit: int) -> bytes: + """Read one kernel metadata file whose reported size may be zero.""" + if byte_limit < 1: + raise ProvisionError("invalid kernel metadata byte limit") + try: + fd = os.open(os.fspath(path), os.O_RDONLY | os.O_NONBLOCK | os.O_NOFOLLOW) + except FileNotFoundError as error: + raise _ProcessGone("process identity is absent") from error + except OSError as error: + raise ProvisionError("process identity metadata is unavailable") from error + try: + if not stat.S_ISREG(os.fstat(fd).st_mode): + raise ProvisionError("process identity metadata is not a regular file") + chunks: list[bytes] = [] + count = 0 + while chunk := os.read(fd, min(DOWNLOAD_CHUNK_SIZE, byte_limit - count + 1)): + count += len(chunk) + if count > byte_limit: + raise ProvisionError("process identity metadata exceeds byte limit") + chunks.append(chunk) + return b"".join(chunks) + finally: + os.close(fd) + + +def _parse_linux_process_stat(raw: bytes, *, expected_pid: int | None = None) -> tuple[int, str]: + """Return the procfs PID and field-22 start token without parsing `comm`.""" + try: + text = raw.decode("ascii").strip() + except UnicodeDecodeError as error: + raise ProvisionError("process stat is not ASCII") from error + left = text.find("(") + right = text.rfind(")") + if left < 1 or right <= left or right + 2 >= len(text): + raise ProvisionError("process stat has an invalid shape") + pid_text = text[:left].strip() + fields = text[right + 1 :].strip().split() + if not pid_text.isdecimal() or len(fields) <= 19 or not fields[19].isdecimal(): + raise ProvisionError("process stat omits an exact start token") + pid = int(pid_text) + if pid < 1 or (expected_pid is not None and pid != expected_pid): + raise ProvisionError("process stat PID does not match its path") + return pid, fields[19] + + +def _linux_process_identity(pid: int | None = None) -> tuple[int, str]: + path = Path("/proc/self/stat") if pid is None else Path("/proc") / str(pid) / "stat" + return _parse_linux_process_stat( + _read_kernel_file(path, byte_limit=MAX_SNAPSHOT_OWNER_BYTES), + expected_pid=pid, + ) + + +def _identity_command(command: list[str]) -> str: + try: + result = subprocess.run( + command, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True, + encoding="ascii", + errors="strict", + timeout=2.0, + check=False, + env={"PATH": FIXED_SYSTEM_PATH, "LC_ALL": "C"}, + ) + except (OSError, subprocess.SubprocessError, UnicodeError) as error: + raise ProvisionError("process identity command is unavailable") from error + output = result.stdout.strip() + if result.returncode != 0 or not output or len(output.encode("ascii")) > MAX_SNAPSHOT_OWNER_BYTES: + raise ProvisionError("process identity command refused") + return output + + +def _darwin_process_identity(pid: int | None = None) -> tuple[int, str]: + actual_pid = os.getpid() if pid is None else pid + if actual_pid < 1: + raise ProvisionError("invalid process PID") + try: + libproc = ctypes.CDLL("/usr/lib/libproc.dylib", use_errno=True) + proc_pidinfo = libproc.proc_pidinfo + proc_pidinfo.argtypes = [ + ctypes.c_int, + ctypes.c_int, + ctypes.c_uint64, + ctypes.c_void_p, + ctypes.c_int, + ] + proc_pidinfo.restype = ctypes.c_int + info = _DarwinBSDInfo() + size = ctypes.sizeof(info) + ctypes.set_errno(0) + result = proc_pidinfo( + actual_pid, + DARWIN_PROC_PIDTBSDINFO, + 0, + ctypes.byref(info), + size, + ) + except (AttributeError, OSError) as error: + raise ProvisionError("Darwin process identity API is unavailable") from error + if result != size: + try: + os.kill(actual_pid, 0) + except ProcessLookupError as error: + raise _ProcessGone("process identity is absent") from error + except PermissionError as error: + raise ProvisionError("process identity is not inspectable") from error + raise ProvisionError("Darwin process identity API refused") + if ( + info.pbi_pid != actual_pid + or info.pbi_start_tvsec < 1 + or info.pbi_start_tvusec >= 1_000_000 + ): + raise ProvisionError("Darwin process identity has an invalid shape") + return actual_pid, f"{info.pbi_start_tvsec}:{info.pbi_start_tvusec}" + + +def _boot_identity(system: str | None = None) -> str: + actual_system = platform.system() if system is None else system + if actual_system == "Linux": + try: + token = _read_kernel_file( + "/proc/sys/kernel/random/boot_id", + byte_limit=MAX_SNAPSHOT_OWNER_BYTES, + ).decode("ascii").strip() + except _ProcessGone as error: + raise ProvisionError("Linux boot identity is unavailable") from error + except UnicodeDecodeError as error: + raise ProvisionError("Linux boot identity is not ASCII") from error + if _LINUX_BOOT_ID.fullmatch(token) is None: + raise ProvisionError("Linux boot identity has an invalid shape") + return f"linux:{token}" + if actual_system == "Darwin": + output = _identity_command(["/usr/sbin/sysctl", "-n", "kern.boottime"]) + match = _DARWIN_BOOT_TIME.search(output) + if match is None: + raise ProvisionError("Darwin boot identity has an invalid shape") + return f"darwin:{match.group(1)}:{match.group(2)}" + raise ProvisionError(f"snapshot ownership is unsupported on {actual_system}") + + +def _current_snapshot_owner() -> SnapshotOwnerIdentity: + system = platform.system() + if system == "Linux": + pid, start_time = _linux_process_identity() + elif system == "Darwin": + pid, start_time = _darwin_process_identity() + else: + raise ProvisionError(f"snapshot ownership is unsupported on {system}") + return SnapshotOwnerIdentity(pid=pid, start_time=start_time, boot_id=_boot_identity(system)) + + +def _process_start_time(pid: int) -> str: + system = platform.system() + if system == "Linux": + unused_pid, start_time = _linux_process_identity(pid) + return start_time + if system == "Darwin": + unused_pid, start_time = _darwin_process_identity(pid) + return start_time + raise ProvisionError(f"snapshot ownership is unsupported on {system}") + + +def _snapshot_owner_bytes(identity: SnapshotOwnerIdentity) -> bytes: + if ( + isinstance(identity.pid, bool) + or not isinstance(identity.pid, int) + or identity.pid < 1 + or not isinstance(identity.start_time, str) + or not identity.start_time + or not isinstance(identity.boot_id, str) + or not identity.boot_id + ): + raise ProvisionError("snapshot owner identity is invalid") + for value in (identity.start_time, identity.boot_id): + try: + encoded_value = value.encode("ascii") + except UnicodeEncodeError as error: + raise ProvisionError("snapshot owner identity is not bounded ASCII") from error + if len(encoded_value) > MAX_SNAPSHOT_OWNER_BYTES or any( + ord(character) < 32 or ord(character) == 127 for character in value + ): + raise ProvisionError("snapshot owner identity is not bounded ASCII") + document = { + "boot_id": identity.boot_id, + "pid": identity.pid, + "schema": SNAPSHOT_OWNER_SCHEMA, + "start_time": identity.start_time, + } + encoded = ( + json.dumps(document, ensure_ascii=True, sort_keys=True, separators=(",", ":")) + "\n" + ).encode("ascii") + if len(encoded) > MAX_SNAPSHOT_OWNER_BYTES: + raise ProvisionError("snapshot owner stamp exceeds byte limit") + return encoded + + +def _snapshot_owner_directory_prefix(identity: SnapshotOwnerIdentity) -> str: + """Encode ownership into the atomically created directory name.""" + _snapshot_owner_bytes(identity) + prefix = ( + f"{SNAPSHOT_PREFIX}v1.{identity.pid}." + f"{identity.boot_id.encode('ascii').hex()}." + f"{identity.start_time.encode('ascii').hex()}." + ) + if len(prefix.encode("ascii")) >= MAX_SNAPSHOT_NAME_BYTES: + raise ProvisionError("snapshot owner directory name exceeds byte limit") + return prefix + + +def _snapshot_owner_from_directory_name(path: Path) -> SnapshotOwnerIdentity: + match = _SNAPSHOT_OWNER_NAME.fullmatch(path.name) + if match is None or len(path.name.encode("ascii", "ignore")) > MAX_SNAPSHOT_NAME_BYTES: + raise ProvisionError("snapshot owner directory name is not stamped") + try: + boot_id = bytes.fromhex(match.group(2)).decode("ascii") + start_time = bytes.fromhex(match.group(3)).decode("ascii") + except (UnicodeDecodeError, ValueError) as error: + raise ProvisionError("snapshot owner directory name is invalid") from error + identity = SnapshotOwnerIdentity( + pid=int(match.group(1)), start_time=start_time, boot_id=boot_id + ) + _snapshot_owner_bytes(identity) + if not path.name.startswith(_snapshot_owner_directory_prefix(identity)): + raise ProvisionError("snapshot owner directory name is not canonical") + return identity + + +def _write_snapshot_owner(owner_root: Path, identity: SnapshotOwnerIdentity) -> None: + root_fd = os.open(owner_root, _directory_flags()) + fd = -1 + try: + fd = os.open( + SNAPSHOT_OWNER_FILE, + os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, + 0o600, + dir_fd=root_fd, + ) + view = memoryview(_snapshot_owner_bytes(identity)) + while view: + written = os.write(fd, view) + if written <= 0: + raise ProvisionError("snapshot owner stamp write made no progress") + view = view[written:] + os.fchmod(fd, 0o600) + os.fsync(fd) + os.fsync(root_fd) + except OSError as error: + raise ProvisionError("snapshot owner stamp could not be written safely") from error + finally: + if fd >= 0: + os.close(fd) + os.close(root_fd) + + +def _load_snapshot_owner(owner_root: Path) -> SnapshotOwnerIdentity: + try: + root_fd = os.open(owner_root, _directory_flags()) + except FileNotFoundError as error: + raise _SnapshotOwnerStampIncomplete("snapshot owner stamp is absent") from error + except OSError as error: + raise ProvisionError("snapshot owner directory is unreadable") from error + fd = -1 + try: + fd = os.open(SNAPSHOT_OWNER_FILE, _file_read_flags(), dir_fd=root_fd) + raw = _read_fd(fd, byte_limit=MAX_SNAPSHOT_OWNER_BYTES) + except FileNotFoundError as error: + raise _SnapshotOwnerStampIncomplete("snapshot owner stamp is absent") from error + except OSError as error: + raise ProvisionError("snapshot owner stamp is unreadable") from error + finally: + if fd >= 0: + os.close(fd) + os.close(root_fd) + + def reject_duplicate_keys(pairs): + result = {} + for key, value in pairs: + if key in result: + raise _SnapshotOwnerStampIncomplete( + "snapshot owner stamp has duplicate keys" + ) + result[key] = value + return result + + try: + document = json.loads(raw.decode("ascii"), object_pairs_hook=reject_duplicate_keys) + except (UnicodeDecodeError, json.JSONDecodeError, RecursionError) as error: + raise _SnapshotOwnerStampIncomplete("snapshot owner stamp is invalid") from error + if not isinstance(document, dict) or set(document) != { + "boot_id", "pid", "schema", "start_time" + } or document.get("schema") != SNAPSHOT_OWNER_SCHEMA: + raise _SnapshotOwnerStampIncomplete( + "snapshot owner stamp has an invalid shape" + ) + identity = SnapshotOwnerIdentity( + pid=document.get("pid"), + start_time=document.get("start_time"), + boot_id=document.get("boot_id"), + ) + try: + canonical = _snapshot_owner_bytes(identity) + except ProvisionError as error: + raise _SnapshotOwnerStampIncomplete( + "snapshot owner stamp has invalid values" + ) from error + if raw != canonical: + raise _SnapshotOwnerStampIncomplete("snapshot owner stamp is not canonical") + return identity + + +def _private_owner_info(path: Path) -> os.stat_result: + info = path.lstat() + if ( + not stat.S_ISDIR(info.st_mode) + or path.is_symlink() + or info.st_uid != os.geteuid() + or info.st_mode & 0o077 + ): + raise ProvisionError("snapshot owner directory is not private") + return info + + +def _owner_identity_for_reaping(path: Path) -> SnapshotOwnerIdentity: + try: + named_owner = _snapshot_owner_from_directory_name(path) + except ProvisionError: + named_owner = None + try: + stamped_owner = _load_snapshot_owner(path) + except _SnapshotOwnerStampIncomplete: + if named_owner is None: + raise ProvisionError("snapshot owner identity is unavailable") + return named_owner + if named_owner is not None and stamped_owner != named_owner: + raise ProvisionError("snapshot owner name and stamp disagree") + return stamped_owner + + +def _remove_orphaned_snapshot( + path: Path, + expected_info: os.stat_result, + expected_owner: SnapshotOwnerIdentity, +) -> None: + try: + current_info = _private_owner_info(path) + current_owner = _owner_identity_for_reaping(path) + except FileNotFoundError: + return + if _file_signature(current_info) != _file_signature(expected_info) or current_owner != expected_owner: + raise ProvisionError("orphaned snapshot changed before cleanup") + parent_fd = os.open(path.parent, _directory_flags()) + root_fd = -1 + try: + root_fd = os.open(path.name, _directory_flags(), dir_fd=parent_fd) + opened_info = os.fstat(root_fd) + if _file_signature(opened_info) != _file_signature(expected_info): + raise ProvisionError("orphaned snapshot changed before cleanup") + entry_count = [0] + + def remove_contents(directory_fd: int, depth: int) -> None: + if depth > MAX_RUNTIME_DEPTH + 1: + raise ProvisionError("orphaned snapshot exceeds cleanup depth") + with os.scandir(directory_fd) as entries: + for entry in entries: + entry_count[0] += 1 + if entry_count[0] > MAX_RUNTIME_ENTRIES + 2: + raise ProvisionError("orphaned snapshot exceeds cleanup entry limit") + info = entry.stat(follow_symlinks=False) + if stat.S_ISDIR(info.st_mode): + child_fd = os.open( + entry.name, _directory_flags(), dir_fd=directory_fd + ) + try: + if _file_signature(os.fstat(child_fd)) != _file_signature(info): + raise ProvisionError( + "orphaned snapshot changed during cleanup" + ) + remove_contents(child_fd, depth + 1) + finally: + os.close(child_fd) + os.rmdir(entry.name, dir_fd=directory_fd) + elif stat.S_ISREG(info.st_mode): + os.unlink(entry.name, dir_fd=directory_fd) + else: + raise ProvisionError( + "orphaned snapshot contains a link or special entry" + ) + + remove_contents(root_fd, 0) + os.rmdir(path.name, dir_fd=parent_fd) + except FileNotFoundError: + return + except OSError as error: + raise ProvisionError("orphaned snapshot cleanup failed") from error + finally: + if root_fd >= 0: + os.close(root_fd) + os.close(parent_fd) + + +def _snapshot_parent_path(temporary_parent: Path | str | None) -> Path: + if temporary_parent is not None: + return Path(temporary_parent) + try: + return Path(tempfile.gettempdir()).resolve(strict=True) + except OSError as error: + raise ProvisionError("system snapshot parent is unavailable") from error + + +def reap_orphaned_runtime_snapshots( + temporary_parent: Path | str | None = None, + *, + current_boot_id: str | None = None, + process_start_reader: Callable[[int], str] | None = None, + remover: Callable[[Path, os.stat_result, SnapshotOwnerIdentity], None] | None = None, +) -> tuple[Path, ...]: + """Remove only snapshots whose exact stamped process incarnation is dead.""" + parent = _snapshot_parent_path(temporary_parent) + if not parent.exists(): + return () + try: + parent_info = parent.lstat() + except OSError as error: + raise ProvisionError("snapshot parent cannot be inspected safely") from error + if not stat.S_ISDIR(parent_info.st_mode) or parent.is_symlink(): + raise ProvisionError("snapshot parent is not a safe directory") + boot_id = _boot_identity() if current_boot_id is None else current_boot_id + read_start = _process_start_time if process_start_reader is None else process_start_reader + remove = _remove_orphaned_snapshot if remover is None else remover + removed: list[Path] = [] + try: + entries = tuple(os.scandir(parent)) + except OSError as error: + raise ProvisionError("snapshot parent cannot be scanned safely") from error + for entry in entries: + if not entry.name.startswith(SNAPSHOT_PREFIX): + continue + path = parent / entry.name + try: + info = _private_owner_info(path) + owner = _owner_identity_for_reaping(path) + except (OSError, ProvisionError): + continue + if owner.boot_id != boot_id: + continue + try: + current_start = read_start(owner.pid) + except _ProcessGone: + orphaned = True + except (OSError, ProvisionError): + continue + else: + orphaned = current_start != owner.start_time + if not orphaned: + continue + remove(path, info, owner) + removed.append(path) + return tuple(removed) + + class RuntimeSnapshot: - """Own one private runtime copy until the MCP server has reaped its workers.""" + """Own one stamped private runtime copy until the server reaps its workers.""" - def __init__(self, owner: tempfile.TemporaryDirectory[str]) -> None: + def __init__( + self, + owner: tempfile.TemporaryDirectory[str], + identity: SnapshotOwnerIdentity | None = None, + ) -> None: self._owner = owner - self.root = Path(owner.name).resolve(strict=True) + self.owner_root = Path(owner.name).resolve(strict=True) self._closed = False + os.chmod(self.owner_root, 0o700) + _write_snapshot_owner( + self.owner_root, + _current_snapshot_owner() if identity is None else identity, + ) + self.root = self.owner_root / SNAPSHOT_RUNTIME_DIRECTORY + self.root.mkdir(mode=0o700) def close(self) -> None: if self._closed: @@ -1168,21 +1817,27 @@ def create_runtime_snapshot( records = verify_sha256sums( source, expected_manifest_sha256=expected_tree_sha256 ) - parent: Path | None = None - if temporary_parent is not None: - parent = Path(temporary_parent) - try: - parent.mkdir(mode=0o700, parents=True, exist_ok=True) - except OSError as error: - raise ProvisionError("runtime snapshot parent is unavailable") from error + parent = _snapshot_parent_path(temporary_parent) + try: + parent.mkdir(mode=0o700, parents=True, exist_ok=True) + reap_orphaned_runtime_snapshots(parent) + except (OSError, ProvisionError) as error: + raise ProvisionError("runtime snapshot parent is unavailable") from error try: + identity = _current_snapshot_owner() owner = tempfile.TemporaryDirectory( - prefix="jackal-codex-runtime-", - dir=None if parent is None else os.fspath(parent), + prefix=_snapshot_owner_directory_prefix(identity), + dir=os.fspath(parent), ) - except OSError as error: + except (OSError, ProvisionError) as error: raise ProvisionError("cannot create private runtime snapshot") from error - snapshot = RuntimeSnapshot(owner) + try: + snapshot = RuntimeSnapshot(owner, identity) + except Exception as error: + owner.cleanup() + if isinstance(error, ProvisionError): + raise + raise ProvisionError("cannot initialize private runtime snapshot") from error try: os.chmod(snapshot.root, 0o700) source_root_fd = os.open(source, _directory_flags()) @@ -1352,6 +2007,18 @@ def _verify_outer_file(path: Path, expected_size: int, expected_sha256: str): raise +# macOS renameatx_np(2) flag; Linux renameat2(2) flag. Both mean the same +# thing: rename atomically and fail with EEXIST rather than clobber the target. +_RENAME_EXCL = 0x00000004 +_RENAME_NOREPLACE = 0x00000001 + + +def _raise_rename_error(error_number: int, target_name: str) -> None: + if error_number == errno.EEXIST: + raise FileExistsError(error_number, os.strerror(error_number), target_name) + raise OSError(error_number, os.strerror(error_number), target_name) + + def _renameatx_np_exclusive( source_parent_fd: int, source_name: str, @@ -1364,18 +2031,68 @@ def _renameatx_np_exclusive( renameatx = libc.renameatx_np renameatx.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint] renameatx.restype = ctypes.c_int + ctypes.set_errno(0) result = renameatx( source_parent_fd, os.fsencode(source_name), target_parent_fd, os.fsencode(target_name), - 0x00000004, + _RENAME_EXCL, + ) + if result != 0: + _raise_rename_error(ctypes.get_errno(), target_name) + + +def _renameat2_noreplace( + source_parent_fd: int, + source_name: str, + target_parent_fd: int, + target_name: str, +) -> None: + """Linux equivalent of renameatx_np(..., RENAME_EXCL). + + RENAME_NOREPLACE carries the same guarantee: the rename is atomic and fails + with EEXIST rather than replacing an existing target. Filesystems that do + not implement the flag report EINVAL or ENOSYS, which propagates as a + refusal -- there is deliberately no fallback to a clobbering rename. + """ + if platform.system() != "Linux": + raise ProvisionError("renameat2 no-replace installation requires Linux") + libc = ctypes.CDLL(None, use_errno=True) + renameat2 = getattr(libc, "renameat2", None) + if renameat2 is None: + raise ProvisionError( + "atomic no-replace installation requires glibc renameat2 (glibc >= 2.28)" + ) + renameat2.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint] + renameat2.restype = ctypes.c_int + ctypes.set_errno(0) + result = renameat2( + source_parent_fd, + os.fsencode(source_name), + target_parent_fd, + os.fsencode(target_name), + _RENAME_NOREPLACE, ) if result != 0: - error_number = ctypes.get_errno() - if error_number == errno.EEXIST: - raise FileExistsError(error_number, os.strerror(error_number), target_name) - raise OSError(error_number, os.strerror(error_number), target_name) + _raise_rename_error(ctypes.get_errno(), target_name) + + +_RENAME_EXCLUSIVE_BY_SYSTEM = { + "Darwin": _renameatx_np_exclusive, + "Linux": _renameat2_noreplace, +} + + +def rename_exclusive_for_host(system: str | None = None) -> Callable: + """Select the atomic no-replace primitive for this host, or refuse.""" + actual_system = platform.system() if system is None else system + operation = _RENAME_EXCLUSIVE_BY_SYSTEM.get(actual_system) + if operation is None: + raise ProvisionError( + f"atomic no-replace installation is unsupported on {actual_system}" + ) + return operation def _install_no_replace( @@ -1386,7 +2103,7 @@ def _install_no_replace( ) -> None: source_path = Path(source) target_path = Path(target) - operation = _renameatx_np_exclusive if rename_exclusive is None else rename_exclusive + operation = rename_exclusive_for_host() if rename_exclusive is None else rename_exclusive source_parent_fd = -1 target_parent_fd = -1 try: @@ -1428,7 +2145,26 @@ def provision( install_no_replace: Callable | None = None, ) -> Path: """Validate or atomically install the pinned runtime.""" - validate_host(system, machine) + host_tag = resolve_host(system, machine) + relying_on_builtin_pins = ( + asset == ASSET + and url == URL + and expected_size == PACKAGE_SIZE + and expected_sha256 == PACKAGE_SHA256 + ) + if relying_on_builtin_pins: + # The caller is relying on the built-in pins rather than supplying its + # own. Bind them to THIS host's published release pin (refusing when no + # asset exists for the host) instead of letting another host's bytes + # reach the hash check and surface as a confusing digest mismatch. + pin = release_pin(host_tag) + asset = pin["asset"] + url = pin["url"] if pin["url"] is not None else url + expected_size = pin["package_size"] + expected_sha256 = pin["package_sha256"] + expected_extracted_size = pin["extracted_size"] + expected_tree_sha256 = pin["sha256sums_sha256"] + expected_top_level = pin["package_directory"] if ( expected_size < 0 or expected_extracted_size < 0 @@ -1564,8 +2300,12 @@ def main(argv: list[str] | None = None) -> int: except Exception: print("jackal_runtime=refused detail=unexpected provisioning failure", file=sys.stderr) return 1 + try: + _sha = effective_release_pins()["package_sha256"] + except ProvisionError: + _sha = PACKAGE_SHA256 print( - f"jackal_runtime=ready epoch={EPOCH} runtime={runtime} package_sha256={PACKAGE_SHA256}" + f"jackal_runtime=ready epoch={EPOCH} runtime={runtime} package_sha256={_sha}" ) return 0 diff --git a/plugins/jackel/skills/jackel/SKILL.md b/plugins/jackel/skills/jackel/SKILL.md index 3e0e466..d0c3db4 100644 --- a/plugins/jackel/skills/jackel/SKILL.md +++ b/plugins/jackel/skills/jackel/SKILL.md @@ -1,18 +1,23 @@ --- name: jackel -description: Route claim-aware computation, domain-pack, and Anubis program evidence through JACKAL without overstating assurance. +description: Route evidence-aware CAS, graphing, THOTH measurement, exact-field matrices, statistical models, sensors, aerospace workflows, linked views, claim bundles, nonlinear certificates, and Anubis program evidence through JACKAL without overstating assurance. --- -# JACKAL numerical-trust operator +# JACKAL + THOTH numerical-trust operator -The v1.7.3 release exposes the ordered 41-tool full inventory recorded in -`release/capability_inventory_v1.json`. Treat that generated file as the -capability-name and status source; the release tag, package receipt, and asset -must bind the same exact bytes. +The v1.7.3 sealed runtime exposes the ordered 41-tool inventory recorded in +`release/capability_inventory_v1.json`. The identity-pinned wrapper adds seven +THOTH measurement/provenance tools, three CAS/graph/certificate tools, and +seven additive STEM workflow tools, so clients see one 58-tool JACKAL MCP +surface. THOTH is a named JACKAL subsystem, not a standalone entity or server. +Treat the generated inventory as the +sealed-runtime capability/status source and the identity-pinned wrapper modules +as the source for their additions. The release tag, package receipt, and asset +must still bind the same exact runtime bytes. -JACKAL exposes the full tool inventory on Apple Silicon macOS. Use it to +JACKAL exposes the full tool inventory on its supported hosts. Use it to classify a quantitative claim, select the strongest admitted evidence lane, and preserve the exact assurance boundary returned by the runtime. @@ -29,6 +34,50 @@ and preserve the exact assurance boundary returned by the runtime. - Use a direct tool when the caller needs one narrow operation rather than a claim graph; direct tools remain available and must retain their returned epistemic class. +- Use `jackal_cas` when one evidence-aware front door is preferable. Its closed + operation table delegates to the corresponding sealed-runtime tool. Preserve + the complete nested result: the router adds no assurance and never retries a + refusal on a weaker lane. +- Use `jackal_graph` to render an expression. Its rational x coordinates come + from `jackal_exact`, its y samples are delegated `status=estimated` f64 + evaluations, and its PNG is visualization only. A refused or indeterminate + sample breaks the curve. Never infer continuity, roots, extrema, pole + absence, or between-sample behavior from the pixels; route those claims to a + separate exact or bounded tool. +- Use `jackal_hellgate_ground_state` only for `problem_id=hellgate-v1`. It + replays a startup-accepted, identity-pinned, exact-rational nonlinear Barta + certificate and returns `status=bounded`, not `formal-bounded`. Preserve the + stated comparison, density-transfer, and Python-rational assumptions. It + encloses the nonlinear ground-state eigenvalue, and its separately labelled + strong-convexity transfer encloses only the ground-state quartic norm and + energy functional. Trial moments and residuals remain scoped to `phi`; it + does not enclose ground-state polynomial moments, lambda sensitivity, + tunneling, or Bogoliubov frequencies. +- Use `jackal_matrix` for exact-rational matrix workflows. Every numeric cell + delegates to `jackal_exact`; the wrapper's row-operation orchestration is + identity-pinned and tested but is not `formal-bounded`. +- Use `jackal_regression`, `jackal_probability`, and `jackal_hypothesis` only + with explicitly declared models. Preserve top-level `model-based`, exact or + estimated field statuses, assumptions, non-claims, and the hypothesis tool's + advisory consequence ceiling. Exact coefficients or probabilities do not + validate a distribution, sampling design, causal claim, or decision. +- Use `jackal_sensor` for supplied batches or declared linear calibration. + Preserve `input_provenance=supplied`; the tool does not claim it opened + hardware or authenticated timestamps, units, sensor identity, or calibration. +- Use `jackal_aerospace` for its closed idealized model table. Preserve every + physical assumption and the advisory ceiling. A formal-bounded scalar + subfield certifies only admitted arithmetic and cannot certify mission safety + or inherit the spacecraft finite-burn certificate. +- Use `jackal_linked_workspace` for linked symbolic, numeric, graph, table, + inspector, sensor-dock, and evidence-route views. The HTML resource is + digest-bound, but SVG geometry, browser sensor display, sampling, and pixels + are not mathematical or measurement evidence. +- Use `jackal_convert`, `jackal_rate_apply`, `jackal_percent`, + `jackal_date_delta`, `jackal_stat`, `jackal_compare`, and `jackal_scan` for + THOTH measurement/provenance work. They are part of JACKAL's single MCP + surface and delegate arithmetic to its sealed runtime. Preserve + `delegated_to`, `given`, the `informational` consequence ceiling, and all + non-claims. - Use `jackal_test_exists` / `jackal_claim_cites_test` only for structural source facts. Preserve their `informational` consequence ceiling: existence and citation resolution are not correctness or coverage. @@ -82,12 +131,18 @@ non-claims from `spacecraft_burn_cert/REPORT.md`. - `exact`: `jackal_exact` and the exact algebra/number-theory tools. Exact integer or rational computation is not a Lean-formal claim. +- `exact-given`: exact rational arithmetic conditional on the declared datum + carried in `given`; the datum itself remains unverified. This is not a weaker + synonym for `exact` or `estimated`. - `checked`: `jackal_diff`. Sampled numeric agreement is a check, not an identity proof. - `estimated`: `jackal_evaluate`, `jackal_integrate`, `jackal_integrate_adaptive`, and `jackal_solve`; an error estimate is not a bound. -- `bounded`: `jackal_integrate_bound`. Its enclosure is conditional on the - stated f64/libm rounding model and is never formal. +- `bounded`: `jackal_integrate_bound` is conditional on its stated f64/libm + rounding model. `jackal_hellgate_ground_state` instead uses an + identity-pinned exact-rational checker plus a stated nonlinear comparison + theorem. Both are bounded and neither is formal; preserve their different + assumptions rather than treating `bounded` as one implementation method. - `formal-bounded`: only the checker-admitted tools below, and only after the pinned checker accepts. - `model-based`: use a structured `jackal_claim` model step. Preserve every @@ -145,27 +200,36 @@ Do not turn `refused` or `indeterminate` into an MCP error or a plausible number. Do not summarize away route traces, assumptions, receipt identities, checker verdicts, or residual non-claims. -## macOS runtime +## Host runtime -This plugin supports Apple Silicon macOS only and uses the published, pinned -v1.7.3 macOS-arm64 runtime. Provision from the fixed release asset by default, -or supply the separately verified release tarball for offline installation. -Do not bypass the Darwin/arm64 host guard or substitute another platform build. +The provisioner recognizes Darwin/arm64, Linux/aarch64, and Linux/x86_64. A +host is admitted only when its atomic no-replace install primitive is +implemented (`renameatx_np` with `RENAME_EXCL` on Darwin, `renameat2` with +`RENAME_NOREPLACE` on Linux) and an exact host package pin is available. +Do not bypass the host guard or substitute another platform build. -Python >=3.10 at `/opt/homebrew/bin/python3` is the supported fixed-path -prerequisite on Apple Silicon. If it is absent or fails the launcher's -capability probe, install it with `brew install python`, then rerun the same -launcher command. The launcher also probes `/usr/local/bin/python3` and -`/usr/bin/python3` when either already satisfies the complete contract; it -never searches caller `PATH`. +A recognized host is not necessarily an available runtime. macOS-arm64 has a +published asset; Linux/aarch64 has an exact host-specific pin for a locally +built tarball and therefore requires `provision --tarball`; Linux/x86_64 has no +package pin and refuses rather than installing another host's bytes. + +Python >=3.10 is the fixed-path prerequisite: `/opt/homebrew/bin/python3` on +Apple Silicon (install with `brew install python`), or `/usr/bin/python3` on +Linux. The launcher probes `/opt/homebrew/bin/python3`, `/usr/local/bin/python3` +and `/usr/bin/python3` in that order, accepts the first that satisfies the +complete capability probe including the host's atomic rename symbol, and never +searches caller `PATH`. When provisioning is requested, run from the plugin root: ```bash -/bin/zsh scripts/launch_mcp.zsh provision -/bin/zsh scripts/launch_mcp.zsh provision --check -/bin/zsh scripts/launch_mcp.zsh provision --tarball /absolute/path/to/jackal-v1.7.3-macos-arm64.tar.gz +/bin/sh scripts/launch_mcp.sh provision +/bin/sh scripts/launch_mcp.sh provision --check +/bin/sh scripts/launch_mcp.sh provision --tarball /absolute/path/to/jackal-v1.7.3-macos-arm64.tar.gz ``` +`scripts/launch_mcp.zsh` remains available for hosts that prefer it; both +launchers carry a byte-identical capability probe. + The default MCP bridge reads the verified runtime locator. Set `JACKAL_HOME` only to an independently verified, canonical absolute runtime directory. diff --git a/proofs/spark/claim_policy/.gitignore b/proofs/spark/claim_policy/.gitignore new file mode 100644 index 0000000..4ded7c4 --- /dev/null +++ b/proofs/spark/claim_policy/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/obj/ diff --git a/proofs/spark/claim_policy/README.md b/proofs/spark/claim_policy/README.md new file mode 100644 index 0000000..9fd4634 --- /dev/null +++ b/proofs/spark/claim_policy/README.md @@ -0,0 +1,24 @@ +# SPARK claim-assurance policy kernel + +This component specifies JACKAL's finite assurance-axis algebra for +`JCK-CLAIM-001`, `JCK-CLAIM-002`, and `JCK-CLAIM-003`. + +It proves the canonical mathematical meet including the shared-rank +`estimated`/`model-based` tie, the provenance/model/implementation meets, +interval-rule mathematical caps, derived-rule implementation caps, preservation +rules, artifact-flag conjunction, termination, and targeted run-time safety. +The mathematical meet is also proved commutative, associative, and idempotent, +so arbitrary parent folds have a stable pairwise foundation. + +Run: + +```sh +./prove.sh +python3 -B -m unittest tests.claim_policy_conformance_test -v +``` + +The Python test exhaustively compares every finite vector with the shipped +producer-side claim kernel. That bridge is exhaustive tested conformance, not a +formal Python refinement theorem. JSON parsing, registry-to-rule-category +mapping, hashing, rendering, Python execution, compiler correctness, and the +independent verifier remain separate obligations. diff --git a/proofs/spark/claim_policy/jackal_claim_policy.gpr b/proofs/spark/claim_policy/jackal_claim_policy.gpr new file mode 100644 index 0000000..7ae835d --- /dev/null +++ b/proofs/spark/claim_policy/jackal_claim_policy.gpr @@ -0,0 +1,12 @@ +project Jackal_Claim_Policy is + for Languages use ("Ada"); + for Source_Dirs use ("src", "tests"); + for Object_Dir use "obj"; + for Exec_Dir use "bin"; + for Main use ("jackal_claim_policy_vectors.adb"); + + package Compiler is + for Default_Switches ("Ada") use + ("-gnat2022", "-gnata", "-gnatVa", "-gnatwa", "-gnatwe", "-O2", "-g"); + end Compiler; +end Jackal_Claim_Policy; diff --git a/proofs/spark/claim_policy/prove.sh b/proofs/spark/claim_policy/prove.sh new file mode 100755 index 0000000..17ef849 --- /dev/null +++ b/proofs/spark/claim_policy/prove.sh @@ -0,0 +1,50 @@ +#!/bin/sh +set -eu + +# Proof obligations: JCK-CLAIM-001, JCK-CLAIM-002, JCK-CLAIM-003. + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) +PROJECT="$SCRIPT_DIR/jackal_claim_policy.gpr" + +if ! command -v gnatprove >/dev/null 2>&1 || ! command -v gprbuild >/dev/null 2>&1; then + if [ -f "$HOME/opt/gnat/env.sh" ]; then + # shellcheck disable=SC1091 + . "$HOME/opt/gnat/env.sh" + fi +fi + +command -v gprbuild >/dev/null 2>&1 || { + echo "refused: gprbuild is unavailable" >&2 + exit 1 +} +command -v gnatprove >/dev/null 2>&1 || { + echo "refused: gnatprove is unavailable" >&2 + exit 1 +} + +gprbuild -p -q -P "$PROJECT" +gnatprove -P "$PROJECT" -U --level=3 --report=all --warnings=error \ + --proof-warnings=on --assumptions -j0 + +PROOF_REPORT="$SCRIPT_DIR/obj/gnatprove/gnatprove.out" +[ -f "$PROOF_REPORT" ] || { + echo "refused: GNATprove summary is missing" >&2 + exit 1 +} + +NORMALIZED_TOTAL=$(grep '^Total' "$PROOF_REPORT" | sed 's/([0-9]*%)//g' | tr -s ' ') +set -- $NORMALIZED_TOTAL +[ "$#" -eq 6 ] && [ "$5" = "." ] && [ "$6" = "." ] || { + echo "refused: GNATprove reports justified or unproved checks" >&2 + exit 1 +} + +grep -q 'unit jackal_claim_policy' "$PROOF_REPORT" || { + echo "refused: the claim policy unit was not analyzed" >&2 + exit 1 +} + +"$SCRIPT_DIR/../reject_assumptions.sh" \ + "$PROOF_REPORT" "$SCRIPT_DIR/src" "$SCRIPT_DIR/tests" + +echo "SPARK_PLATINUM_CLAIM_POLICY_COMPONENT_PROOF_PASS" diff --git a/proofs/spark/claim_policy/src/jackal_claim_policy.adb b/proofs/spark/claim_policy/src/jackal_claim_policy.adb new file mode 100644 index 0000000..58e1e6d --- /dev/null +++ b/proofs/spark/claim_policy/src/jackal_claim_policy.adb @@ -0,0 +1,49 @@ +package body Jackal_Claim_Policy + with SPARK_Mode +is + function Meet_Mathematical + (Left, Right : Mathematical_Class) return Mathematical_Class is + begin + -- JCK-CLAIM-001 + return Required_Mathematical_Meet (Left, Right); + end Meet_Mathematical; + + function Meet_Provenance + (Left, Right : Provenance_Class) return Provenance_Class is + (if Left <= Right then Left else Right); + + function Meet_Model (Left, Right : Model_Class) return Model_Class is + (Required_Model_Meet (Left, Right)); + + function Meet_Implementation + (Left, Right : Implementation_Class) return Implementation_Class is + (if Left <= Right then Left else Right); + + function Meet_Artifact + (Left, Right : Artifact_Flags) return Artifact_Flags is + -- JCK-CLAIM-003 + (Content_Addressed => + Left.Content_Addressed and Right.Content_Addressed, + Reproducible_Built => + Left.Reproducible_Built and Right.Reproducible_Built, + Authenticated => Left.Authenticated and Right.Authenticated, + Transparency_Logged => + Left.Transparency_Logged and Right.Transparency_Logged); + + function Apply_Rule_Caps + (Behavior : Rule_Behavior; + Input : Rule_Axes) return Rule_Axes is + -- JCK-CLAIM-002 + (Mathematical => + Required_Capped_Mathematical (Behavior, Input.Mathematical), + Implementation => + Required_Capped_Implementation (Behavior, Input.Implementation)); + + procedure Prove_Mathematical_Meet_Laws + (Left, Middle, Right : Mathematical_Class) + is + begin + null; + end Prove_Mathematical_Meet_Laws; + +end Jackal_Claim_Policy; diff --git a/proofs/spark/claim_policy/src/jackal_claim_policy.ads b/proofs/spark/claim_policy/src/jackal_claim_policy.ads new file mode 100644 index 0000000..c9d57c1 --- /dev/null +++ b/proofs/spark/claim_policy/src/jackal_claim_policy.ads @@ -0,0 +1,181 @@ +package Jackal_Claim_Policy + with SPARK_Mode +is + type Mathematical_Class is + (Refused, + Indeterminate, + Estimated, + Model_Based, + Checked, + Bounded, + Formal_Bounded, + Exact); + + type Mathematical_Strength is + (Refusal_Strength, + Indeterminate_Strength, + Estimate_Strength, + Check_Strength, + Bound_Strength, + Formal_Bound_Strength, + Exact_Strength); + + function Strength_Of + (Item : Mathematical_Class) return Mathematical_Strength is + (case Item is + when Refused => Refusal_Strength, + when Indeterminate => Indeterminate_Strength, + when Estimated + | Model_Based => Estimate_Strength, + when Checked => Check_Strength, + when Bounded => Bound_Strength, + when Formal_Bounded => Formal_Bound_Strength, + when Exact => Exact_Strength); + + function Required_Mathematical_Meet + (Left, Right : Mathematical_Class) return Mathematical_Class is + (if Strength_Of (Left) < Strength_Of (Right) then Left + elsif Strength_Of (Right) < Strength_Of (Left) then Right + elsif Mathematical_Class'Pos (Left) <= Mathematical_Class'Pos (Right) + then Left + else Right); + + -- JCK-CLAIM-001: canonical weakest-class meet, including the shared-rank + -- Estimated/Model_Based tie break fixed by registry order. + function Meet_Mathematical + (Left, Right : Mathematical_Class) return Mathematical_Class + with + Post => + Meet_Mathematical'Result = + Required_Mathematical_Meet (Left, Right) + and then Strength_Of (Meet_Mathematical'Result) <= Strength_Of (Left) + and then Strength_Of (Meet_Mathematical'Result) <= Strength_Of (Right); + + type Provenance_Class is + (Unknown, + Supplied, + Integrity_Bound, + Observed, + Authenticated_Source, + Measured); + + function Meet_Provenance + (Left, Right : Provenance_Class) return Provenance_Class + with + Post => + Meet_Provenance'Result = + (if Left <= Right then Left else Right); + + type Model_Class is + (Model_Unknown, + Assumed, + Calibrated, + Empirically_Validated, + Not_Applicable); + + function Required_Model_Meet + (Left, Right : Model_Class) return Model_Class is + (if Left = Not_Applicable then Right + elsif Right = Not_Applicable then Left + elsif Left <= Right then Left + else Right); + + function Meet_Model (Left, Right : Model_Class) return Model_Class + with + Post => + Meet_Model'Result = Required_Model_Meet (Left, Right); + + type Implementation_Class is + (Impl_Unknown, + Directly_Trusted, + Campaign_Tested, + Independently_Recomputed, + Checker_Derived, + Source_Native_Refined); + + function Meet_Implementation + (Left, Right : Implementation_Class) return Implementation_Class + with + Post => + Meet_Implementation'Result = + (if Left <= Right then Left else Right); + + type Artifact_Flags is record + Content_Addressed : Boolean; + Reproducible_Built : Boolean; + Authenticated : Boolean; + Transparency_Logged : Boolean; + end record; + + -- JCK-CLAIM-003: compositional artifact evidence survives only when both + -- parents carry the same flag. + function Meet_Artifact + (Left, Right : Artifact_Flags) return Artifact_Flags + with + Post => + Meet_Artifact'Result.Content_Addressed = + (Left.Content_Addressed and Right.Content_Addressed) + and then Meet_Artifact'Result.Reproducible_Built = + (Left.Reproducible_Built and Right.Reproducible_Built) + and then Meet_Artifact'Result.Authenticated = + (Left.Authenticated and Right.Authenticated) + and then Meet_Artifact'Result.Transparency_Logged = + (Left.Transparency_Logged and Right.Transparency_Logged); + + type Rule_Behavior is + (Preserve_Axes, Interval_Arithmetic, Derived_Default); + + type Rule_Axes is record + Mathematical : Mathematical_Class; + Implementation : Implementation_Class; + end record; + + function Required_Capped_Mathematical + (Behavior : Rule_Behavior; + Value : Mathematical_Class) return Mathematical_Class is + (if Behavior = Interval_Arithmetic + and then Strength_Of (Bounded) < Strength_Of (Value) + then Bounded + else Value); + + function Required_Capped_Implementation + (Behavior : Rule_Behavior; + Value : Implementation_Class) return Implementation_Class is + (if Behavior /= Preserve_Axes + and then Independently_Recomputed < Value + then Independently_Recomputed + else Value); + + -- JCK-CLAIM-002: rule application preserves or lowers both axes exactly + -- according to the closed rule category; it can never strengthen them. + function Apply_Rule_Caps + (Behavior : Rule_Behavior; + Input : Rule_Axes) return Rule_Axes + with + Post => + Apply_Rule_Caps'Result.Mathematical = + Required_Capped_Mathematical (Behavior, Input.Mathematical) + and then Apply_Rule_Caps'Result.Implementation = + Required_Capped_Implementation (Behavior, Input.Implementation) + and then Strength_Of (Apply_Rule_Caps'Result.Mathematical) <= + Strength_Of (Input.Mathematical) + and then Apply_Rule_Caps'Result.Implementation <= + Input.Implementation; + + procedure Prove_Mathematical_Meet_Laws + (Left, Middle, Right : Mathematical_Class) + with + Ghost, + Post => + Meet_Mathematical (Left => Left, Right => Right) = + Meet_Mathematical (Left => Right, Right => Left) + and then Meet_Mathematical (Left => Left, Right => Left) = Left + and then Meet_Mathematical + (Left => Meet_Mathematical (Left => Left, Right => Middle), + Right => Right) = + Meet_Mathematical + (Left => Left, + Right => Meet_Mathematical + (Left => Middle, Right => Right)); + +end Jackal_Claim_Policy; diff --git a/proofs/spark/claim_policy/tests/jackal_claim_policy_vectors.adb b/proofs/spark/claim_policy/tests/jackal_claim_policy_vectors.adb new file mode 100644 index 0000000..da33355 --- /dev/null +++ b/proofs/spark/claim_policy/tests/jackal_claim_policy_vectors.adb @@ -0,0 +1,98 @@ +with Ada.Text_IO; +with Jackal_Claim_Policy; + +procedure Jackal_Claim_Policy_Vectors is + package Policy renames Jackal_Claim_Policy; + + function Flag (Mask, Position : Natural) return Boolean is + ((Mask / (2 ** Position)) mod 2 = 1); + + function Artifact (Mask : Natural) return Policy.Artifact_Flags is + (Content_Addressed => Flag (Mask, 0), + Reproducible_Built => Flag (Mask, 1), + Authenticated => Flag (Mask, 2), + Transparency_Logged => Flag (Mask, 3)); + + function Artifact_Mask (Item : Policy.Artifact_Flags) return Natural is + ((if Item.Content_Addressed then 1 else 0) + + (if Item.Reproducible_Built then 2 else 0) + + (if Item.Authenticated then 4 else 0) + + (if Item.Transparency_Logged then 8 else 0)); + +begin + -- Exhaustive bridge vectors for JCK-CLAIM-001, JCK-CLAIM-002, and + -- JCK-CLAIM-003. + for Left in Policy.Mathematical_Class loop + for Right in Policy.Mathematical_Class loop + Ada.Text_IO.Put_Line + ("MATH|" & Policy.Mathematical_Class'Image (Left) + & "|" & Policy.Mathematical_Class'Image (Right) + & "|" & Policy.Mathematical_Class'Image + (Policy.Meet_Mathematical (Left, Right))); + end loop; + end loop; + + for Left in Policy.Provenance_Class loop + for Right in Policy.Provenance_Class loop + Ada.Text_IO.Put_Line + ("PROVENANCE|" & Policy.Provenance_Class'Image (Left) + & "|" & Policy.Provenance_Class'Image (Right) + & "|" & Policy.Provenance_Class'Image + (Policy.Meet_Provenance (Left, Right))); + end loop; + end loop; + + for Left in Policy.Model_Class loop + for Right in Policy.Model_Class loop + Ada.Text_IO.Put_Line + ("MODEL|" & Policy.Model_Class'Image (Left) + & "|" & Policy.Model_Class'Image (Right) + & "|" & Policy.Model_Class'Image + (Policy.Meet_Model (Left, Right))); + end loop; + end loop; + + for Left in Policy.Implementation_Class loop + for Right in Policy.Implementation_Class loop + Ada.Text_IO.Put_Line + ("IMPLEMENTATION|" & Policy.Implementation_Class'Image (Left) + & "|" & Policy.Implementation_Class'Image (Right) + & "|" & Policy.Implementation_Class'Image + (Policy.Meet_Implementation (Left, Right))); + end loop; + end loop; + + for Behavior in Policy.Rule_Behavior loop + for Math in Policy.Mathematical_Class loop + for Implementation in Policy.Implementation_Class loop + declare + Result : constant Policy.Rule_Axes := + Policy.Apply_Rule_Caps + (Behavior, + (Mathematical => Math, Implementation => Implementation)); + begin + Ada.Text_IO.Put_Line + ("RULE|" & Policy.Rule_Behavior'Image (Behavior) + & "|" & Policy.Mathematical_Class'Image (Math) + & "|" & Policy.Implementation_Class'Image (Implementation) + & "|" & Policy.Mathematical_Class'Image + (Result.Mathematical) + & "|" & Policy.Implementation_Class'Image + (Result.Implementation)); + end; + end loop; + end loop; + end loop; + + for Left_Mask in 0 .. 15 loop + for Right_Mask in 0 .. 15 loop + Ada.Text_IO.Put_Line + ("ARTIFACT|" & Natural'Image (Left_Mask) + & "|" & Natural'Image (Right_Mask) + & "|" & Natural'Image + (Artifact_Mask + (Policy.Meet_Artifact + (Artifact (Left_Mask), Artifact (Right_Mask))))); + end loop; + end loop; +end Jackal_Claim_Policy_Vectors; diff --git a/proofs/spark/hellgate_interval/.gitignore b/proofs/spark/hellgate_interval/.gitignore new file mode 100644 index 0000000..4ded7c4 --- /dev/null +++ b/proofs/spark/hellgate_interval/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/obj/ diff --git a/proofs/spark/hellgate_interval/README.md b/proofs/spark/hellgate_interval/README.md new file mode 100644 index 0000000..548a363 --- /dev/null +++ b/proofs/spark/hellgate_interval/README.md @@ -0,0 +1,35 @@ +# SPARK fixed-scale HELLGATE interval envelope + +This repository-side component independently specifies and proves a narrow +integer interval decision boundary used to sanity-check the admitted HELLGATE +energy envelope. Because the energy is negative, the demo represents its +absolute magnitudes on a fixed `10^18` scale. + +The requirements-complete component claim covers `JCK-INT-001` through +`JCK-INT-004`. Its total decision function is quantified over every value of +the public fixed-scale input types. GNATprove establishes deterministic +rejection precedence, exact ordered width, midpoint/ceiling-radius endpoint +coverage, strict admission equivalence, zeroed rejection outputs, termination, +and absence of targeted run-time errors. + +The target conversion was routed through JACKAL exact arithmetic: +`status=exact`, `parsed=2/10^12*10^18`, `exact=2000000`. That exact result is +outside the Lean certificate chain and is not `formal-bounded`. + +Run: + +```sh +./prove.sh +``` + +The proof gate runs at level 3 with proof warnings treated as errors and refuses +unproved checks, justified checks, `pragma Assume`, `pragma Annotate`, or a +report that skipped the expected package. The requirement and whole-surface +closure source is `assurance/requirements.json`. + +The proof boundary is deliberately limited. This component does not prove the +nonlinear Barta theorem, the density strong-convexity theorem, certificate +parsing, the Python checker's rational integration, Python-to-SPARK refinement, +source-to-object equivalence, compiler or run-time correctness, or any +mission/safety claim. It is not DO-178C, ECSS, NASA, or launch-provider +qualification evidence. diff --git a/proofs/spark/hellgate_interval/hellgate_interval.gpr b/proofs/spark/hellgate_interval/hellgate_interval.gpr new file mode 100644 index 0000000..68a522d --- /dev/null +++ b/proofs/spark/hellgate_interval/hellgate_interval.gpr @@ -0,0 +1,12 @@ +project Hellgate_Interval is + for Languages use ("Ada"); + for Source_Dirs use ("src", "tests"); + for Object_Dir use "obj"; + for Exec_Dir use "bin"; + for Main use ("hellgate_interval_demo.adb"); + + package Compiler is + for Default_Switches ("Ada") use + ("-gnat2012", "-gnata", "-gnatVa", "-gnatwa", "-gnatwe"); + end Compiler; +end Hellgate_Interval; diff --git a/proofs/spark/hellgate_interval/prove.sh b/proofs/spark/hellgate_interval/prove.sh new file mode 100755 index 0000000..cab8937 --- /dev/null +++ b/proofs/spark/hellgate_interval/prove.sh @@ -0,0 +1,51 @@ +#!/bin/sh +set -eu + +# Proof obligations: JCK-INT-001, JCK-INT-002, JCK-INT-003, JCK-INT-004. + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) +PROJECT="$SCRIPT_DIR/hellgate_interval.gpr" + +if ! command -v gnatprove >/dev/null 2>&1 || ! command -v gprbuild >/dev/null 2>&1; then + if [ -f "$HOME/opt/gnat/env.sh" ]; then + # shellcheck disable=SC1091 + . "$HOME/opt/gnat/env.sh" + fi +fi + +command -v gprbuild >/dev/null 2>&1 || { + echo "refused: gprbuild is unavailable" >&2 + exit 1 +} +command -v gnatprove >/dev/null 2>&1 || { + echo "refused: gnatprove is unavailable" >&2 + exit 1 +} + +gprbuild -p -q -P "$PROJECT" +"$SCRIPT_DIR/bin/hellgate_interval_demo" +gnatprove -P "$PROJECT" -U --level=3 --report=all --warnings=error \ + --proof-warnings=on --assumptions -j0 + +PROOF_REPORT="$SCRIPT_DIR/obj/gnatprove/gnatprove.out" +if [ ! -f "$PROOF_REPORT" ]; then + echo "refused: GNATprove summary is missing" >&2 + exit 1 +fi + +NORMALIZED_TOTAL=$(grep '^Total' "$PROOF_REPORT" | sed 's/([0-9]*%)//g' | tr -s ' ') +set -- $NORMALIZED_TOTAL +if [ "$#" -ne 6 ] || [ "$5" != "." ] || [ "$6" != "." ]; then + echo "refused: GNATprove reports justified or unproved checks" >&2 + exit 1 +fi + +grep -q 'unit jackal_interval_envelope' "$PROOF_REPORT" || { + echo "refused: the interval decision unit was not analyzed" >&2 + exit 1 +} + +"$SCRIPT_DIR/../reject_assumptions.sh" \ + "$PROOF_REPORT" "$SCRIPT_DIR/src" "$SCRIPT_DIR/tests" + +echo "SPARK_PLATINUM_INTERVAL_COMPONENT_PROOF_PASS" diff --git a/proofs/spark/hellgate_interval/src/jackal_interval_envelope.adb b/proofs/spark/hellgate_interval/src/jackal_interval_envelope.adb new file mode 100644 index 0000000..32cc0a2 --- /dev/null +++ b/proofs/spark/hellgate_interval/src/jackal_interval_envelope.adb @@ -0,0 +1,54 @@ +package body Jackal_Interval_Envelope + with SPARK_Mode +is + + function Width (Item : Closed_Interval) return Magnitude is + (Item.Upper - Item.Lower); + + function Midpoint (Item : Closed_Interval) return Magnitude is + (Item.Lower + Width (Item) / 2); + + function Radius_Ceiling (Item : Closed_Interval) return Magnitude is + (Width (Item) - Width (Item) / 2); + + function Strictly_Meets_Target + (Item : Closed_Interval; + Target_Width : Magnitude) return Boolean is + (Width (Item) < Target_Width); + + function Evaluate_Untrusted_Envelope + (Item : Closed_Interval; + Target_Width : Magnitude) return Envelope_Decision + is + Empty : constant Envelope_Decision := + (Verdict => Reject_Unordered, + Width => 0, + Center => 0, + Radius => 0); + begin + -- JCK-INT-004. The branch order is part of Required_Verdict and makes + -- subtraction unreachable until ordering has been established. + if not Is_Ordered (Item) then + return Empty; + elsif Target_Width = 0 then + return + (Verdict => Reject_Nonpositive_Target, + Width => 0, + Center => 0, + Radius => 0); + elsif Width (Item) >= Target_Width then + return + (Verdict => Reject_Not_Strictly_Narrower, + Width => 0, + Center => 0, + Radius => 0); + else + return + (Verdict => Admit, + Width => Width (Item), + Center => Midpoint (Item), + Radius => Radius_Ceiling (Item)); + end if; + end Evaluate_Untrusted_Envelope; + +end Jackal_Interval_Envelope; diff --git a/proofs/spark/hellgate_interval/src/jackal_interval_envelope.ads b/proofs/spark/hellgate_interval/src/jackal_interval_envelope.ads new file mode 100644 index 0000000..2e21b50 --- /dev/null +++ b/proofs/spark/hellgate_interval/src/jackal_interval_envelope.ads @@ -0,0 +1,125 @@ +package Jackal_Interval_Envelope + with SPARK_Mode +is + Max_Magnitude : constant Long_Long_Integer := + 8_000_000_000_000_000_000; + + subtype Magnitude is Long_Long_Integer + range 0 .. Max_Magnitude; + + type Closed_Interval is record + Lower : Magnitude; + Upper : Magnitude; + end record; + + function Is_Ordered (Item : Closed_Interval) return Boolean is + (Item.Lower <= Item.Upper); + + -- JCK-INT-001: exact width over every ordered fixed-scale interval. + function Width (Item : Closed_Interval) return Magnitude + with + Pre => Is_Ordered (Item), + Post => Width'Result = Item.Upper - Item.Lower; + + -- JCK-INT-002: the midpoint/radius pair covers both interval endpoints. + function Midpoint (Item : Closed_Interval) return Magnitude + with + Pre => Is_Ordered (Item), + Post => + Midpoint'Result = + Item.Lower + (Item.Upper - Item.Lower) / 2 + and then Midpoint'Result in Item.Lower .. Item.Upper; + + function Radius_Ceiling (Item : Closed_Interval) return Magnitude + with + Pre => Is_Ordered (Item), + Post => + Radius_Ceiling'Result = + (Item.Upper - Item.Lower) + - (Item.Upper - Item.Lower) / 2; + + function Contains + (Item : Closed_Interval; + Value : Magnitude) return Boolean is + (Item.Lower <= Value and then Value <= Item.Upper); + + function Covers + (Item : Closed_Interval; + Center : Magnitude; + Radius : Magnitude) return Boolean is + (Is_Ordered (Item) + and then Contains (Item, Center) + and then Center - Item.Lower <= Radius + and then Item.Upper - Center <= Radius); + + -- JCK-INT-003: strict admission is equivalent to the allocated width + -- predicate for every input satisfying the public precondition. + function Strictly_Meets_Target + (Item : Closed_Interval; + Target_Width : Magnitude) return Boolean + with + Pre => Is_Ordered (Item) and then Target_Width > 0, + Post => + Strictly_Meets_Target'Result = + (Item.Upper - Item.Lower < Target_Width); + + function Admits_Untrusted_Envelope + (Item : Closed_Interval; + Target_Width : Magnitude) return Boolean is + (Is_Ordered (Item) + and then Target_Width > 0 + and then Item.Upper - Item.Lower < Target_Width); + + type Decision_Verdict is + (Reject_Unordered, + Reject_Nonpositive_Target, + Reject_Not_Strictly_Narrower, + Admit); + + type Envelope_Decision is record + Verdict : Decision_Verdict; + Width : Magnitude; + Center : Magnitude; + Radius : Magnitude; + end record; + + function Required_Verdict + (Item : Closed_Interval; + Target_Width : Magnitude) return Decision_Verdict is + (if not Is_Ordered (Item) then Reject_Unordered + elsif Target_Width = 0 then Reject_Nonpositive_Target + elsif Item.Upper - Item.Lower >= Target_Width then + Reject_Not_Strictly_Narrower + else Admit); + + -- JCK-INT-004: total, deterministic evaluation over the complete public + -- input type. Rejections zero every derived output; acceptance returns + -- the exact width and a covering midpoint/radius pair. + function Evaluate_Untrusted_Envelope + (Item : Closed_Interval; + Target_Width : Magnitude) return Envelope_Decision + with + Post => + Evaluate_Untrusted_Envelope'Result.Verdict = + Required_Verdict (Item, Target_Width) + and then + ((Evaluate_Untrusted_Envelope'Result.Verdict = Admit) = + Admits_Untrusted_Envelope (Item, Target_Width)) + and then + (if Evaluate_Untrusted_Envelope'Result.Verdict = Admit then + Evaluate_Untrusted_Envelope'Result.Width = + Item.Upper - Item.Lower + and then Evaluate_Untrusted_Envelope'Result.Center = + Item.Lower + (Item.Upper - Item.Lower) / 2 + and then Evaluate_Untrusted_Envelope'Result.Radius = + (Item.Upper - Item.Lower) + - (Item.Upper - Item.Lower) / 2 + and then Covers + (Item, + Evaluate_Untrusted_Envelope'Result.Center, + Evaluate_Untrusted_Envelope'Result.Radius) + else Evaluate_Untrusted_Envelope'Result.Width = 0 + and then Evaluate_Untrusted_Envelope'Result.Center = 0 + and then Evaluate_Untrusted_Envelope'Result.Radius = 0); + +end Jackal_Interval_Envelope; diff --git a/proofs/spark/hellgate_interval/tests/hellgate_interval_demo.adb b/proofs/spark/hellgate_interval/tests/hellgate_interval_demo.adb new file mode 100644 index 0000000..45975e3 --- /dev/null +++ b/proofs/spark/hellgate_interval/tests/hellgate_interval_demo.adb @@ -0,0 +1,73 @@ +with Ada.Text_IO; +with Jackal_Interval_Envelope; + +procedure Hellgate_Interval_Demo + with SPARK_Mode +is + package Envelope renames Jackal_Interval_Envelope; + use type Envelope.Decision_Verdict; + + -- The checked energy interval is negative. This independent fixed-scale + -- boundary represents its absolute magnitudes, ordered from smaller to + -- larger, at a scale of 10^18. + Hellgate_Magnitude : constant Envelope.Closed_Interval := + (Lower => 4_615_978_698_574_496_507, + Upper => 4_615_978_698_574_496_508); + + -- 2*10^(-12) at scale 10^18. JACKAL exact replay: + -- parsed=2/10^12*10^18; exact=2000000; status=exact (not formal). + Required_Strict_Width : constant Envelope.Magnitude := 2_000_000; + + Reversed_Magnitude : constant Envelope.Closed_Interval := + (Lower => Hellgate_Magnitude.Upper, + Upper => Hellgate_Magnitude.Lower); + Boundary_Width : constant Envelope.Closed_Interval := + (Lower => 0, + Upper => Required_Strict_Width); + + Center : constant Envelope.Magnitude := + Envelope.Midpoint (Hellgate_Magnitude); + Radius : constant Envelope.Magnitude := + Envelope.Radius_Ceiling (Hellgate_Magnitude); + Decision : constant Envelope.Envelope_Decision := + Envelope.Evaluate_Untrusted_Envelope + (Hellgate_Magnitude, Required_Strict_Width); +begin + -- JCK-INT-001, JCK-INT-002, JCK-INT-003, JCK-INT-004. + pragma Assert (Envelope.Is_Ordered (Hellgate_Magnitude)); + pragma Assert (Envelope.Contains (Hellgate_Magnitude, Center)); + pragma Assert (Center - Hellgate_Magnitude.Lower <= Radius); + pragma Assert (Hellgate_Magnitude.Upper - Center <= Radius); + pragma Assert + (Envelope.Strictly_Meets_Target + (Hellgate_Magnitude, Required_Strict_Width)); + pragma Assert + (Envelope.Admits_Untrusted_Envelope + (Hellgate_Magnitude, Required_Strict_Width)); + pragma Assert + (not Envelope.Admits_Untrusted_Envelope + (Reversed_Magnitude, Required_Strict_Width)); + pragma Assert + (not Envelope.Admits_Untrusted_Envelope + (Hellgate_Magnitude, 0)); + pragma Assert + (not Envelope.Admits_Untrusted_Envelope + (Boundary_Width, Required_Strict_Width)); + pragma Assert (Decision.Verdict = Envelope.Admit); + pragma Assert (Decision.Center = Center); + pragma Assert (Decision.Radius = Radius); + pragma Assert + (Envelope.Evaluate_Untrusted_Envelope + (Reversed_Magnitude, Required_Strict_Width).Verdict = + Envelope.Reject_Unordered); + pragma Assert + (Envelope.Evaluate_Untrusted_Envelope + (Hellgate_Magnitude, 0).Verdict = + Envelope.Reject_Nonpositive_Target); + pragma Assert + (Envelope.Evaluate_Untrusted_Envelope + (Boundary_Width, Required_Strict_Width).Verdict = + Envelope.Reject_Not_Strictly_Narrower); + + Ada.Text_IO.Put_Line ("HELLGATE fixed-scale interval envelope: ACCEPT"); +end Hellgate_Interval_Demo; diff --git a/proofs/spark/reject_assumptions.py b/proofs/spark/reject_assumptions.py new file mode 100644 index 0000000..9874a78 --- /dev/null +++ b/proofs/spark/reject_assumptions.py @@ -0,0 +1,129 @@ +#!/usr/bin/python3 -I -B +"""Reject SPARK proof assumptions without runner-image tool dependencies.""" + +from __future__ import annotations + +import re +import sys +from collections.abc import Iterator +from pathlib import Path + + +REPORT_ASSUME = re.compile(r"\(([1-9][0-9]*) pragma Assume statements?\)") +ADA_SUFFIXES = {".adb", ".ads"} +FORBIDDEN_PRAGMAS = {"annotate", "assume"} + + +def refuse(message: str) -> None: + raise SystemExit(f"refused: {message}") + + +def identifiers(text: str) -> Iterator[tuple[str, int]]: + """Yield Ada identifiers outside line comments and string literals.""" + + index = 0 + line = 1 + while index < len(text): + if text.startswith("--", index): + newline = text.find("\n", index + 2) + if newline < 0: + return + index = newline + continue + + character = text[index] + if character == '"': + index += 1 + while index < len(text): + if text[index] == '"': + if index + 1 < len(text) and text[index + 1] == '"': + index += 2 + continue + index += 1 + break + if text[index] == "\n": + line += 1 + index += 1 + continue + + if character.isascii() and character.isalpha(): + start = index + start_line = line + index += 1 + while index < len(text): + character = text[index] + if not ( + character.isascii() + and (character.isalnum() or character == "_") + ): + break + index += 1 + yield text[start:index].lower(), start_line + continue + + if character == "\n": + line += 1 + index += 1 + + +def source_files(raw_roots: list[str]) -> list[Path]: + files: list[Path] = [] + seen: set[Path] = set() + for raw_root in raw_roots: + root = Path(raw_root) + if root.is_symlink() or not root.exists(): + refuse(f"SPARK source root is missing or symbolic: {root}") + candidates = [root] if root.is_file() else root.rglob("*") + for candidate in candidates: + if candidate.suffix.lower() not in ADA_SUFFIXES: + continue + if candidate.is_symlink() or not candidate.is_file(): + refuse(f"SPARK source is not a regular file: {candidate}") + resolved = candidate.resolve() + if resolved not in seen: + files.append(candidate) + seen.add(resolved) + if not files: + refuse("no SPARK source files were found") + return files + + +def check_source(path: Path) -> None: + try: + text = path.read_text(encoding="utf-8") + except (OSError, UnicodeError) as error: + refuse(f"cannot read SPARK source {path}: {error}") + + expect_pragma_name_at: int | None = None + for identifier, line in identifiers(text): + if expect_pragma_name_at is not None: + if identifier in FORBIDDEN_PRAGMAS: + refuse( + f"proof assumption or justification in {path}:" + f"{expect_pragma_name_at} ({identifier})" + ) + expect_pragma_name_at = None + if identifier == "pragma": + expect_pragma_name_at = line + + +def main(arguments: list[str]) -> None: + if len(arguments) < 2: + refuse("a GNATprove report and SPARK source roots are required") + + report = Path(arguments[0]) + if report.is_symlink() or not report.is_file(): + refuse("the GNATprove report is not a regular file") + try: + report_text = report.read_text(encoding="utf-8") + except (OSError, UnicodeError) as error: + refuse(f"cannot read the GNATprove report: {error}") + if REPORT_ASSUME.search(report_text): + refuse("GNATprove reports one or more proof assumptions") + + for path in source_files(arguments[1:]): + check_source(path) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/proofs/spark/reject_assumptions.sh b/proofs/spark/reject_assumptions.sh new file mode 100755 index 0000000..6753d73 --- /dev/null +++ b/proofs/spark/reject_assumptions.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) + +command -v python3 >/dev/null 2>&1 || { + echo "refused: python3 is unavailable for the SPARK assumption scan" >&2 + exit 1 +} + +exec python3 -I -B "$SCRIPT_DIR/reject_assumptions.py" "$@" diff --git a/release/MANIFEST.linux-aarch64.sha256 b/release/MANIFEST.linux-aarch64.sha256 new file mode 100644 index 0000000..f303b42 --- /dev/null +++ b/release/MANIFEST.linux-aarch64.sha256 @@ -0,0 +1,51 @@ +# JACKAL v1.7.3 pinned release identities (v1.7.2 proof/claim/domain lanes + inventory-safe Anubis program evidence) +evaluator jackal-native 4c0ae28d2f41353332dbb29f1b6084cd275d08bad73a84942fbe6f500255d872 +checker jackal_cert_check 89e4e42d8daa57d4a9a19f9b95b29bd5e96fae5ad7060ac3adf355f1dfeb870c +archival-v170-archive-source github-release-v1.7.0 21c7ede586f30a58772f321f7dbb36ab66213e199785489f99133710ac56096e +archival-range-checker jackal_cert_check_v170 d515cdc2e66e53e46aef49ebb0d804a3ebd30d0b69fc7e8de7a3a19aac5170f2 +archival-range-coverage-inventory formal_coverage_inventory_v170.json 18ff7b1d428dbc6f807fd4de27751ba415b33ef0b356088d7fa316ed74bb0ba6 +archival-plugin-hermes v1.7.0-plugin d141c909e8f5f03e268a2112f291e6bd79fafff906522eb7ca9accc247a3274b +gaussian-producer tools/gaussian_certificate.py 20c24622b786940a8e82198f2364fb7593e761902fa0736289b179642f1e4306 +gaussian-checker jackal_gaussian_check 1f21c6b2e41c171414405c0aa6fee089851716d1add707727ace45395f4ed379 +range-proof-identity release/evidence/range_proof_identity_v172.linux-aarch64.json c6caed2434cbd77cba19bb0279936bfa322a696301e6d20c932a9b412f2fa0ca +range-proof-digest e373a459c1ddbd87be1b8bb881440338872a527cf1805d13340833e2e7d098d1 +archival-range-proof-identity release/evidence/range_proof_identity.linux-aarch64.json 4b1c2958481e1047551c42b8f23a827a8422ad9e8881ec913ec88a4cd95b44f2 +archival-range-proof-digest 022c19e317e68049e48b69bfa8567f4ba617610aa5a6bf3c59d8c9a98995c518 +gaussian-proof-identity release/evidence/gaussian_proof_identity.linux-aarch64.json 610624b48f48dcd089e0ad14ddac7ebbc25071e7d70cc8516ad6c3c09c747404 +gaussian-proof-digest 489780aab2b7b5f16bc529b141c5c7967a9307cbc539c3fff8eab9f0789263fe +lean-admission-audit release/evidence/lean_admission_audit_v173.json cabda2a1fb8c021ce384d9c2267f52f98e6799bb50b6b828e7d9eefa55bf2b2e +lean-admission-audit-digest 5e33809e2d8f73d8b554313bc9ea71a8b8d3c657810f0b37d4db3dd945578f29 +int-cert-producer tools/int_cert_producer.py b4240fdac3c77b2abd751595303b2b3a0e4bebd492b2ae57fa5ccf052cd50af4 +int-cert-checker jackal_int_cert_check f2e26f506f921b577fd8609a095b69789b3b76cce65d8c293d1a11f3007a8078 +int-cert-proof-identity release/evidence/int_cert_proof_identity_v172.linux-aarch64.json 533c5cc4718331e2739ec92513487842aa48471b4b7d1be57db08999a9d90f95 +int-cert-proof-digest a024ab9cb1010721297703bab38a0e43a7a48ac65b0c1d71292599e813e3dad9 +revoked-int-cert-proof-identity-reference release/evidence/int_cert_proof_identity.json f0323e312d8b0e05a7200546fd819fc191d5f146d359bb14efec5b1575f16844 +compatibility-floor release/compat/v172_floor.json 1a7baa000a86a07189df2cec73258db431cd5ab66e80465c5770b980e0ee4f61 +program-compatibility-floor release/compat/v173_floor.json 5b4e78e1f2b3e1ed7d0459a12f229ffe27886c179198a656a5a9dc5343f8b45e +range-ordering-aba release/evidence/range_ordering_aba_v172.json 02e08fcc26b0099ff49064f3e3a5c3147ccda51a4ee9f67aad372ee743bdd071 +int-cert-premise-aba release/evidence/int_cert_premise_aba_v172.json b07c501a98277b03d880ba1d131af79f3cb3baacbde3e623df755f2fab860f45 +coverage-inventory release/coverage/formal_coverage_inventory.json 6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6 +build-environment release/evidence/build_environment_v170.json b71fbee10dd83fab7e389d07eadd92e1bff431585f11be5b6887e789f063109d +source jackal_calc.anb f579b6f59bc024d24914487b0cd0f18ea43dea1be52708a05a66dc885d80bb4e +compiler_pin anubis-2704f0832f34 2704f0832f3458052c2bf6352b1b0252f541c185a528b14758c80caafe05bb79 +plugin_hermes c6a27483077b89d899d8c73c03bfeb3191f25db2a22f8021254a7dec763ba5fe +sqrt_rat_producer tools/sqrt_rat_producer.py 4bc95c331430d2350facfb19da9aba483ab7b3698754e7af2e5deb797e097926 +exp_rat_producer tools/exp_rat_producer.py 1997ed81dfbd26a6d45a6689c515832bfbae05435d07e3dd2d6f156c57668ec1 +ln_rat_producer tools/ln_rat_producer.py c88eb0153f0ec0ba401597a8945345e621a38df408bfd92a47a4b3abf7985740 +sin_rat_producer tools/sin_rat_producer.py 978f8d508c0921b5d8227a24ee7c7b97373a6041e55e4923cd94617a94a061dd +atan_rat_producer tools/atan_rat_producer.py 824916bdb3420986f4a6eed8028760a96477e9e1df2febd03b9ca174216aef26 +tanh_rat_producer tools/tanh_rat_producer.py da03b6054dcdd3fe02588ec25fc7c201405e9d8ec5f3ab46ff45b49698ab5eb3 +exact_verifier tools/exact_verify.py 2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7 +claim_kernel tools/claim_kernel.py 77b0f85ad5fb7214f88898b60ea29ea9fd7be740c38b655388444e6e5181f348 +claim_router tools/claim_router.py 02328cf177a0423bdc5cbca6ec0ea946bb0679bbd3dc6c24140d32598e575afb +claim_verifier tools/claim_bundle_verify.py e0fcb9540c730bd9bb492b528ed42d29d49fc775b3aa0f9b831b6264fd68fd22 +domain_pack_registry domain_packs/registry_v1.json b71fb06d36cdee13e7fbf271a80dfe12e6378072ce38d92358cc54ce748aca03 +domain_pack_verifier tools/domain_pack_verify.py 53553ba75d7f233e79fba68597642e7a01d60dec340246147c95a2e8e3ecb08b +domain_pack_test_exists_checker tools/test_exists_verify.py 598cb99e1eb70c9410ca87345efee346f73e43aaf3625427dca17ea04231caea +domain_pack_decision_checker tools/decision_verify.py f1ad7c9fbd4c1d899dbb4bebabbbeb97e97a56bd4b279ad7d8ec3722bf12e0f6 +anubis_program_verifier tools/anubis_program_verify.py cafbd25791225856c2914e9dc86b5dcd3e23d6cc0285b09800de34922f76d52e +anubis_program_policy release/program/inventory_safe_v1.json 361979bf89b7c71a4b2c692d64756548833a2c363c269511b037726cab3ebacb +claim_inference_registry release/claim/inference_registry_v1.json c70b33d5aee8071b5125e6a5f8ffe5226fc22a137d920c17d9b3463968be13f0 +claim_unit_registry release/claim/unit_registry_v1.json d2d30dfe2a74d58a5ef31b551ea628106390bfccd72ad34d1cb37381c58d114c +approved-z3-linux-aarch64 jackal_z3_v4154 b6fcd93b2ccec9aa848ac148c4d9b4270577ad046601f211784586eb9f0135c4 +approved-check-compiler-linux-aarch64 jackal_anubis_check_v1 6c3ae920aaaa668b3ec1857b162997c8cd1471949604e6909b898f357683479a diff --git a/release/build_package_linux.sh b/release/build_package_linux.sh new file mode 100755 index 0000000..49f8516 --- /dev/null +++ b/release/build_package_linux.sh @@ -0,0 +1,1314 @@ +#!/bin/sh +# Build the additive JACKAL v1.7.3 Linux aarch64 package (adapted copy). +# +# This builder never rebuilds the evaluator. The sole Anubis compiler +# authority is the immutable, digest-checked pin below. --dry-run validates +# every input and prints the package plan without creating a staging directory +# or touching release/dist. --build is explicit, refuses existing outputs, +# stages on the release filesystem, and publishes only the staged result. +set -eu + +SYSTEM=$(/usr/bin/uname -s) +MACHINE=$(/usr/bin/uname -m) +if [ "$SYSTEM" != "Linux" ] || [ "$MACHINE" != "aarch64" ]; then + echo "PACKAGE_V173_REFUSED reason=unsupported-host system=$SYSTEM machine=$MACHINE expected=Linux/aarch64" >&2 + exit 3 +fi + +ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +VER="v1.7.3" +PLATFORM="linux-aarch64" +PKG_NAME="jackal-v1.7.3-linux-aarch64" +TARBALL_NAME="$PKG_NAME.tar.gz" +DIST=${JACKAL_DIST:-"$ROOT/release/dist"} +FINAL_PKG="$DIST/$PKG_NAME" +FINAL_TARBALL="$DIST/$TARBALL_NAME" +case "$DIST" in + /*) ;; + *) + echo "PACKAGE_V173_REFUSED reason=dist-not-absolute path=$DIST" >&2 + exit 5 + ;; +esac +case "/$DIST/" in + */../*|*/./*) + echo "PACKAGE_V173_REFUSED reason=dist-not-canonical path=$DIST" >&2 + exit 5 + ;; +esac +COMPILER=${JACKAL_ANUBIS_COMPILER_PATH:-} +[ -n "$COMPILER" ] || { + echo "PACKAGE_V173_REFUSED reason=compiler-path-unset" >&2 + exit 4 +} +RANGE_CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_cert_check" +GAUSSIAN_CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_gaussian_check" +INT_CHECKER="$ROOT/proofs/lean/.lake/build/bin/jackal_int_cert_check" +V170_ARCHIVE_SHA256="21c7ede586f30a58772f321f7dbb36ab66213e199785489f99133710ac56096e" +V170_RANGE_CHECKER_SHA256="d515cdc2e66e53e46aef49ebb0d804a3ebd30d0b69fc7e8de7a3a19aac5170f2" +V170_COVERAGE_INVENTORY_SHA256="18ff7b1d428dbc6f807fd4de27751ba415b33ef0b356088d7fa316ed74bb0ba6" +V170_PLUGIN_HERMES_SHA256="d141c909e8f5f03e268a2112f291e6bd79fafff906522eb7ca9accc247a3274b" + +sha256() { + shasum -a 256 "$1" | /usr/bin/awk '{print $1}' +} + +publish_noreplace() { + python3 -I -S -B - "$1" "$2" <<'PY' +import ctypes +import os +import sys + +import platform +source = os.fsencode(sys.argv[1]) +destination = os.fsencode(sys.argv[2]) +libc = ctypes.CDLL(None, use_errno=True) +# Atomic no-clobber publish: macOS renamex_np(RENAME_EXCL) / Linux +# renameat2(AT_FDCWD, ..., RENAME_NOREPLACE) — same fail-on-EEXIST guarantee. +if platform.system() == "Darwin": + fn = libc.renamex_np + fn.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint] + fn.restype = ctypes.c_int + ctypes.set_errno(0) + rc = fn(source, destination, 0x00000004) # RENAME_EXCL +else: + AT_FDCWD = -100 + RENAME_NOREPLACE = 0x00000001 + fn = libc.renameat2 + fn.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint] + fn.restype = ctypes.c_int + ctypes.set_errno(0) + rc = fn(AT_FDCWD, source, AT_FDCWD, destination, RENAME_NOREPLACE) +if rc != 0: + error_number = ctypes.get_errno() + print( + "PACKAGE_V173_REFUSED reason=publication-destination-raced " + f"path={sys.argv[2]} errno={error_number} detail={os.strerror(error_number)}", + file=sys.stderr, + ) + raise SystemExit(1) +PY +} + +require_regular() { + [ -f "$1" ] && [ ! -L "$1" ] || { + echo "PACKAGE_V173_REFUSED reason=required-regular-file path=$1" >&2 + exit 4 + } +} + +require_regular "$COMPILER" +COMPILER_SHA256=$(sha256 "$COMPILER") + +REQUIRED_INPUTS=" +jackal-native +jackal_calc.anb +proofs/lean/.lake/build/bin/jackal_cert_check +proofs/lean/.lake/build/bin/jackal_gaussian_check +proofs/lean/.lake/build/bin/jackal_int_cert_check +release/evidence/range_proof_identity_v172.json +release/evidence/int_cert_proof_identity_v172.json +release/evidence/range_proof_identity.json +release/evidence/range_proof_identity.linux-aarch64.json +release/evidence/int_cert_proof_identity.json +release/evidence/gaussian_proof_identity.json +release/evidence/range_proof_identity_v172.linux-aarch64.json +release/evidence/int_cert_proof_identity_v172.linux-aarch64.json +release/evidence/gaussian_proof_identity.linux-aarch64.json +release/evidence/lean_admission_audit_v173.json +release/evidence/lean_admission_audit_v173.linux-aarch64.json +release/capability_inventory_v1.json +release/compat/v172_floor.json +release/compat/v172_floor.linux-aarch64.json +release/compat/v173_floor.json +release/compat/v170_floor.json +release/compat/v150_floor.json +release/evidence/range_ordering_aba_v172.json +release/evidence/int_cert_premise_aba_v172.json +release/coverage/formal_coverage_inventory.json +release/tools/repin_v173.py +tools/lean_admission_audit.py +tests/release_validate.py +tools/gaussian_certificate.py +tools/gaussian_release.py +tools/int_cert_producer.py +tools/int_cert_release.py +tools/formal_receipt.py +tools/receipt_verify.py +tools/formal_status_gate.py +tools/coverage_inventory.py +tools/isolated_entry.py +tools/exact_verify.py +tools/claim_kernel.py +tools/claim_router.py +tools/claim_bundle_verify.py +tools/anubis_program_verify.py +release/program/inventory_safe_v1.json +release/program/SPEC.md +tools/domain_pack_verify.py +tools/test_exists_verify.py +tools/decision_verify.py +domain_packs/PACK_SCHEMA.json +domain_packs/PACK_SPEC.md +domain_packs/registry_v1.json +domain_packs/core/manifest.json +domain_packs/core/core_pack.anb +domain_packs/programming/manifest.json +domain_packs/programming/programming_pack.anb +domain_packs/decision/manifest.json +domain_packs/decision/decision_pack.anb +tools/sqrt_rat_producer.py +tools/exp_rat_producer.py +tools/ln_rat_producer.py +tools/sin_rat_producer.py +tools/atan_rat_producer.py +tools/tanh_rat_producer.py +release/claim/inference_registry_v1.json +release/claim/unit_registry_v1.json +plugin/hermes/server.py +plugin/hermes/bundle_hash.py +plugin/hermes/jackal_hermes +plugin/hermes/tools.json +plugin/hermes/profiles/core.json +plugin/hermes/profiles/formal.json +plugin/hermes/profiles/full.json +plugin/hermes/schemas/jackal_agent_profile.schema.json +" + +OPTIONAL_EVIDENCE_NAMES=" +positive_corpus.jsonl +negative_controls.jsonl +aba_mutations.json +plugin_smoke.jsonl +mutations_11.json +fail_closed_sweep.jsonl +gaussian_formal_v130.json +gaussian_formal_v150.json +seal_audit_v150.json +seal_audit_receipts_v150.json +receipt_semantic_mutations.json +claim_hostile_matrix_v160.json +claim_dogfood_v160.json +claim_aba_v160.json +anubis_program_hostile_v1.json +build_environment_v170.json +" + +for relative in $REQUIRED_INPUTS; do + require_regular "$ROOT/$relative" +done + +# Checking the plan validates every live identity, including the two +# current checker binaries, both proof-identity-v2 records, compatibility +# policy, ABA evidence, and all preserved v1.7.0 lanes. It does not write. +JACKAL_ANUBIS_COMPILER_PATH="$COMPILER" \ + python3 -I -S -B "$ROOT/release/tools/repin_linux.py" --check >/dev/null + +MODE=${1:-} +if [ "$MODE" = "--dry-run" ] && [ "$#" -eq 1 ]; then + echo "PACKAGE_V173_DRY_RUN_PASS version=$VER platform=$PLATFORM" + echo "compiler=$COMPILER compiler_sha256=$COMPILER_SHA256" + echo "package=$FINAL_PKG" + echo "tarball=$FINAL_TARBALL" + echo "range_identity=range_proof_identity.json source=release/evidence/range_proof_identity_v172.json" + echo "int_identity=int_cert_proof_identity.json source=release/evidence/int_cert_proof_identity_v172.json" + echo "lean_admission_audit=evidence/lean_admission_audit_v173.json" + echo "capability_inventory=capability_inventory_v1.json" + echo "compat=evidence/compat_v172_floor.json" + echo "program_compat=evidence/compat_v173_floor.json" + echo "program_profile=inventory-safe-v1" + echo "archival_runtime=v1.7.0 archive_sha256=$V170_ARCHIVE_SHA256" + exit 0 +fi + +if [ "$MODE" != "--build" ] || [ "$#" -ne 1 ]; then + echo "usage: release/build_package_v173.sh --dry-run|--build" >&2 + exit 2 +fi + +/bin/mkdir -p "$DIST" +RELEASE_DEVICE=$(/usr/bin/stat -c '%d' "$ROOT/release") +DIST_DEVICE=$(/usr/bin/stat -c '%d' "$DIST") +[ "$RELEASE_DEVICE" = "$DIST_DEVICE" ] || { + echo "PACKAGE_V173_REFUSED reason=dist-cross-filesystem release_device=$RELEASE_DEVICE dist_device=$DIST_DEVICE path=$DIST" >&2 + exit 5 +} + +[ ! -e "$FINAL_PKG" ] && [ ! -L "$FINAL_PKG" ] || { + echo "PACKAGE_V173_REFUSED reason=output-exists path=$FINAL_PKG" >&2 + exit 5 +} +[ ! -e "$FINAL_TARBALL" ] && [ ! -L "$FINAL_TARBALL" ] || { + echo "PACKAGE_V173_REFUSED reason=output-exists path=$FINAL_TARBALL" >&2 + exit 5 +} + +STAGE=$(mktemp -d "$ROOT/release/.v173-stage.XXXXXX") +cleanup() { + if [ -n "${STAGE:-}" ] && [ -d "$STAGE" ]; then + /bin/rm -r "$STAGE" + fi +} +trap cleanup EXIT HUP INT TERM +PKG="$STAGE/$PKG_NAME" +/bin/mkdir -p \ + "$PKG/evidence" "$PKG/tools" "$PKG/program" "$PKG/release/claim" \ + "$PKG/domain_packs/core" "$PKG/domain_packs/programming" \ + "$PKG/domain_packs/decision" "$PKG/plugin/hermes/profiles" \ + "$PKG/plugin/hermes/schemas" + +# Freeze every repository input before the first copy. Each copy below must +# match this plan both at the source after copying and in the staged package. +SOURCE_PLAN="$STAGE/source-plan.sha256" +: > "$SOURCE_PLAN" +for relative in $REQUIRED_INPUTS; do + /usr/bin/printf '%s %s\n' "$(sha256 "$ROOT/$relative")" "$relative" >> "$SOURCE_PLAN" +done +SELECTED_OPTIONAL_EVIDENCE="" +for name in $OPTIONAL_EVIDENCE_NAMES; do + optional="$ROOT/release/evidence/$name" + if [ -e "$optional" ] || [ -L "$optional" ]; then + require_regular "$optional" + relative="release/evidence/$name" + /usr/bin/printf '%s %s\n' "$(sha256 "$optional")" "$relative" >> "$SOURCE_PLAN" + SELECTED_OPTIONAL_EVIDENCE="$SELECTED_OPTIONAL_EVIDENCE $name" + fi +done + +copy_file() { + source_path="$1" + destination_path="$2" + require_regular "$source_path" + case "$source_path" in + "$ROOT"/*) relative_path=${source_path#"$ROOT"/} ;; + *) + echo "PACKAGE_V173_REFUSED reason=copy-source-outside-plan path=$source_path" >&2 + exit 4 + ;; + esac + expected=$(/usr/bin/awk -v target="$relative_path" '$2==target{print $1}' "$SOURCE_PLAN") + [ -n "$expected" ] || { + echo "PACKAGE_V173_REFUSED reason=copy-source-unplanned path=$relative_path" >&2 + exit 4 + } + before=$(sha256 "$source_path") + [ "$before" = "$expected" ] || { + echo "PACKAGE_V173_REFUSED reason=copy-source-prehash-drift path=$relative_path" >&2 + exit 4 + } + [ ! -e "$destination_path" ] && [ ! -L "$destination_path" ] || { + echo "PACKAGE_V173_REFUSED reason=copy-destination-exists path=$destination_path" >&2 + exit 4 + } + /bin/cp "$source_path" "$destination_path" + after=$(sha256 "$source_path") + copied=$(sha256 "$destination_path") + [ "$after" = "$expected" ] && [ "$copied" = "$expected" ] || { + echo "PACKAGE_V173_REFUSED reason=copy-source-or-destination-drift path=$relative_path" >&2 + exit 4 + } +} + +# Stable package names expose the current v2 identities without leaking the +# repository's epoch-suffixed filenames into wrapper contracts. +copy_file "$ROOT/jackal-native" "$PKG/jackal-native" +copy_file "$ROOT/jackal_calc.anb" "$PKG/jackal_calc.anb" +copy_file "$RANGE_CHECKER" "$PKG/jackal_cert_check" +copy_file "$GAUSSIAN_CHECKER" "$PKG/jackal_gaussian_check" +copy_file "$INT_CHECKER" "$PKG/jackal_int_cert_check" +copy_file "$ROOT/release/evidence/range_proof_identity_v172.linux-aarch64.json" "$PKG/range_proof_identity.json" +copy_file "$ROOT/release/evidence/int_cert_proof_identity_v172.linux-aarch64.json" "$PKG/int_cert_proof_identity.json" +copy_file "$ROOT/release/evidence/gaussian_proof_identity.linux-aarch64.json" "$PKG/gaussian_proof_identity.json" +copy_file "$ROOT/release/evidence/lean_admission_audit_v173.linux-aarch64.json" "$PKG/evidence/lean_admission_audit_v173.json" +copy_file "$ROOT/release/capability_inventory_v1.json" "$PKG/capability_inventory_v1.json" +copy_file "$ROOT/release/coverage/formal_coverage_inventory.json" "$PKG/formal_coverage_inventory.json" + +# Replay-only v1.7.0 receipts require the exact historical checker bytes. +# Accept an operator-supplied local copy of the published archive, otherwise +# fetch the public release asset, then verify the whole archive before reading +# only the two named regular-file members. No archive path is extracted. +# Omarchy (Linux) edition: the archival v1.7.0 range checker is a natively +# rebuilt ELF, not the macOS release binary. Copy it and the platform-neutral +# archival coverage inventory from JACKAL_ARCHIVAL_DIR instead of downloading +# the Apple archive, so the package carries zero macOS bytes. +ARCHIVAL_DIR="${JACKAL_ARCHIVAL_DIR:?set JACKAL_ARCHIVAL_DIR to the native archival artifacts}" +require_regular "$ARCHIVAL_DIR/jackal_cert_check_v170" +require_regular "$ARCHIVAL_DIR/formal_coverage_inventory_v170.json" +[ "$(sha256 "$ARCHIVAL_DIR/jackal_cert_check_v170")" = "$V170_RANGE_CHECKER_SHA256" ] || { + echo "PACKAGE_V173_REFUSED reason=archival-checker-identity" >&2 + exit 4 +} +[ "$(sha256 "$ARCHIVAL_DIR/formal_coverage_inventory_v170.json")" = "$V170_COVERAGE_INVENTORY_SHA256" ] || { + echo "PACKAGE_V173_REFUSED reason=archival-inventory-identity" >&2 + exit 4 +} +/bin/cp "$ARCHIVAL_DIR/jackal_cert_check_v170" "$PKG/jackal_cert_check_v170" +/bin/chmod 0755 "$PKG/jackal_cert_check_v170" +/bin/cp "$ARCHIVAL_DIR/formal_coverage_inventory_v170.json" "$PKG/evidence/formal_coverage_inventory_v170.json" + +# Omarchy edition: ship the architecture-qualified approved Z3 4.15.4 (native +# double-built ELF) as a package member the program-evidence verifier resolves +# as its sibling. Verified against the manifest-pinned identity. +Z3DIR="${JACKAL_Z3_DIR:?set JACKAL_Z3_DIR to the approved native Z3 4.15.4}" +require_regular "$Z3DIR/jackal_z3_v4154" +APPROVED_Z3_SHA256="b6fcd93b2ccec9aa848ac148c4d9b4270577ad046601f211784586eb9f0135c4" +[ "$(sha256 "$Z3DIR/jackal_z3_v4154")" = "$APPROVED_Z3_SHA256" ] || { + echo "PACKAGE_V173_REFUSED reason=approved-z3-identity observed=$(sha256 "$Z3DIR/jackal_z3_v4154")" >&2 + exit 4 +} +/bin/cp "$Z3DIR/jackal_z3_v4154" "$PKG/jackal_z3_v4154" +/bin/chmod 0755 "$PKG/jackal_z3_v4154" + +# Omarchy edition: ship the architecture-qualified approved CHECK compiler +# (native Linux aarch64 anubis, double-built from public source). Verified +# against the manifest-pinned identity. +CCDIR="${JACKAL_CC_DIR:?set JACKAL_CC_DIR to the approved native check compiler}" +require_regular "$CCDIR/jackal_anubis_check_v1" +APPROVED_CC_SHA256="6c3ae920aaaa668b3ec1857b162997c8cd1471949604e6909b898f357683479a" +[ "$(sha256 "$CCDIR/jackal_anubis_check_v1")" = "$APPROVED_CC_SHA256" ] || { + echo "PACKAGE_V173_REFUSED reason=approved-check-compiler-identity observed=$(sha256 "$CCDIR/jackal_anubis_check_v1")" >&2 + exit 4 +} +/bin/cp "$CCDIR/jackal_anubis_check_v1" "$PKG/jackal_anubis_check_v1" +/bin/chmod 0755 "$PKG/jackal_anubis_check_v1" + +for relative in \ + tests/release_validate.py \ + tools/gaussian_certificate.py tools/gaussian_release.py \ + tools/int_cert_producer.py tools/int_cert_release.py \ + tools/formal_receipt.py tools/receipt_verify.py \ + tools/formal_status_gate.py tools/coverage_inventory.py \ + tools/isolated_entry.py tools/exact_verify.py \ + tools/claim_kernel.py tools/claim_router.py tools/claim_bundle_verify.py \ + tools/sqrt_rat_producer.py tools/exp_rat_producer.py \ + tools/ln_rat_producer.py tools/sin_rat_producer.py \ + tools/atan_rat_producer.py tools/tanh_rat_producer.py; do + copy_file "$ROOT/$relative" "$PKG/$(basename "$relative")" +done + +copy_file "$ROOT/tools/anubis_program_verify.py" "$PKG/tools/anubis_program_verify.py" +copy_file "$ROOT/tools/domain_pack_verify.py" "$PKG/tools/domain_pack_verify.py" +copy_file "$ROOT/tools/test_exists_verify.py" "$PKG/tools/test_exists_verify.py" +copy_file "$ROOT/tools/decision_verify.py" "$PKG/tools/decision_verify.py" +copy_file "$ROOT/tools/exact_verify.py" "$PKG/tools/exact_verify.py" +copy_file "$ROOT/release/program/inventory_safe_v1.json" "$PKG/program/inventory_safe_v1.json" +copy_file "$ROOT/domain_packs/PACK_SCHEMA.json" "$PKG/domain_packs/PACK_SCHEMA.json" +copy_file "$ROOT/release/program/SPEC.md" "$PKG/program/SPEC.md" +copy_file "$ROOT/domain_packs/PACK_SPEC.md" "$PKG/domain_packs/PACK_SPEC.md" +copy_file "$ROOT/domain_packs/registry_v1.json" "$PKG/domain_packs/registry_v1.json" +copy_file "$ROOT/domain_packs/core/manifest.json" "$PKG/domain_packs/core/manifest.json" +copy_file "$ROOT/domain_packs/core/core_pack.anb" "$PKG/domain_packs/core/core_pack.anb" +copy_file "$ROOT/domain_packs/programming/manifest.json" "$PKG/domain_packs/programming/manifest.json" +copy_file "$ROOT/domain_packs/programming/programming_pack.anb" "$PKG/domain_packs/programming/programming_pack.anb" +copy_file "$ROOT/domain_packs/decision/manifest.json" "$PKG/domain_packs/decision/manifest.json" +copy_file "$ROOT/domain_packs/decision/decision_pack.anb" "$PKG/domain_packs/decision/decision_pack.anb" +copy_file "$ROOT/plugin/hermes/profiles/core.json" "$PKG/plugin/hermes/profiles/core.json" +copy_file "$ROOT/plugin/hermes/profiles/formal.json" "$PKG/plugin/hermes/profiles/formal.json" +copy_file "$ROOT/plugin/hermes/profiles/full.json" "$PKG/plugin/hermes/profiles/full.json" +copy_file "$ROOT/plugin/hermes/schemas/jackal_agent_profile.schema.json" "$PKG/plugin/hermes/schemas/jackal_agent_profile.schema.json" + +copy_file "$ROOT/release/claim/inference_registry_v1.json" "$PKG/inference_registry_v1.json" +copy_file "$ROOT/release/claim/unit_registry_v1.json" "$PKG/unit_registry_v1.json" +copy_file "$ROOT/release/claim/inference_registry_v1.json" "$PKG/release/claim/inference_registry_v1.json" +copy_file "$ROOT/release/claim/unit_registry_v1.json" "$PKG/release/claim/unit_registry_v1.json" +copy_file "$ROOT/plugin/hermes/server.py" "$PKG/plugin/hermes/server.py" +copy_file "$ROOT/plugin/hermes/bundle_hash.py" "$PKG/plugin/hermes/bundle_hash.py" +copy_file "$ROOT/plugin/hermes/jackal_hermes" "$PKG/plugin/hermes/jackal_hermes" +copy_file "$ROOT/plugin/hermes/tools.json" "$PKG/plugin/hermes/tools.json" + +# Current compatibility policy plus the evidence it names. The archival range +# v1 identity remains replay-only. The int-cert v1 identity is historical +# revocation evidence only and its vulnerable checker is deliberately absent. +copy_file "$ROOT/release/compat/v172_floor.linux-aarch64.json" "$PKG/evidence/compat_v172_floor.json" +copy_file "$ROOT/release/compat/v173_floor.json" "$PKG/evidence/compat_v173_floor.json" +copy_file "$ROOT/release/compat/v170_floor.json" "$PKG/evidence/compat_v170_floor.json" +copy_file "$ROOT/release/compat/v150_floor.json" "$PKG/evidence/compat_v150_floor.json" +copy_file "$ROOT/release/evidence/range_ordering_aba_v172.json" "$PKG/evidence/range_ordering_aba_v172.json" +copy_file "$ROOT/release/evidence/int_cert_premise_aba_v172.json" "$PKG/evidence/int_cert_premise_aba_v172.json" +copy_file "$ROOT/release/evidence/range_proof_identity.linux-aarch64.json" "$PKG/evidence/range_proof_identity_v1.json" +copy_file "$ROOT/release/evidence/int_cert_proof_identity.json" "$PKG/evidence/int_cert_proof_identity_v1.json" +copy_file "$ROOT/release/evidence/range_proof_identity_v172.linux-aarch64.json" "$PKG/evidence/range_proof_identity_v172.json" +copy_file "$ROOT/release/evidence/int_cert_proof_identity_v172.linux-aarch64.json" "$PKG/evidence/int_cert_proof_identity_v172.json" + +for name in $SELECTED_OPTIONAL_EVIDENCE; do + copy_file "$ROOT/release/evidence/$name" "$PKG/evidence/$name" +done + +/bin/chmod +x \ + "$PKG/jackal-native" "$PKG/jackal_cert_check" "$PKG/jackal_cert_check_v170" \ + "$PKG/jackal_gaussian_check" "$PKG/jackal_int_cert_check" \ + "$PKG/gaussian_certificate.py" "$PKG/gaussian_release.py" \ + "$PKG/int_cert_producer.py" "$PKG/int_cert_release.py" \ + "$PKG/isolated_entry.py" "$PKG/exact_verify.py" \ + "$PKG/claim_kernel.py" "$PKG/claim_router.py" \ + "$PKG/claim_bundle_verify.py" "$PKG/tools/anubis_program_verify.py" \ + "$PKG/tools/domain_pack_verify.py" "$PKG/tools/test_exists_verify.py" \ + "$PKG/tools/decision_verify.py" "$PKG/tools/exact_verify.py" \ + "$PKG/plugin/hermes/jackal_hermes" + +cat > "$PKG/jackal-cert-release" <<'WRAP' +#!/bin/sh +# JACKAL v1.7.2 packaged range release gate (self-contained). +set -eu +HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +[ "$#" -eq 4 ] || { echo "usage: jackal-cert-release \"\" " >&2; exit 2; } +EE=$(awk '$1=="evaluator"{print $NF}' "$HERE/MANIFEST.sha256") +EC=$(awk '$1=="checker"{print $NF}' "$HERE/MANIFEST.sha256") +ES=$(awk '$1=="source"{print $NF}' "$HERE/MANIFEST.sha256") +EI=$(awk '$1=="coverage_inventory"{print $NF}' "$HERE/MANIFEST.sha256") +EPF=$(awk '$1=="range_proof_identity"{print $NF}' "$HERE/MANIFEST.sha256") +EPD=$(awk '$1=="range_proof_digest"{print $NF}' "$HERE/MANIFEST.sha256") +[ -n "$EE" ] && [ -n "$EC" ] && [ -n "$ES" ] && [ -n "$EI" ] && [ -n "$EPF" ] && [ -n "$EPD" ] || { echo "status=unavailable reason=manifest-incomplete" >&2; exit 3; } +exec python3 -I -S -B "$HERE/isolated_entry.py" range \ + --expr "$1" --lo "$2" --hi "$3" \ + --evaluator "$HERE/jackal-native" --checker "$HERE/jackal_cert_check" \ + --expected-evaluator "$EE" --expected-checker "$EC" --expected-source "$ES" \ + --inventory "$HERE/formal_coverage_inventory.json" --expected-inventory "$EI" \ + --proof-identity "$HERE/range_proof_identity.json" \ + --expected-proof-identity-file "$EPF" --expected-proof-identity-digest "$EPD" \ + --release-epoch v1.7.2 --formal-receipt "$4" +WRAP + +cat > "$PKG/jackal-int-cert-release" <<'WRAP' +#!/bin/sh +# JACKAL v1.7.2 packaged composed-integral release gate (self-contained). +set -eu +HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +[ "$#" -eq 5 ] || { echo "usage: jackal-int-cert-release \"\" " >&2; exit 2; } +EP=$(awk '$1=="int_cert_producer"{print $NF}' "$HERE/MANIFEST.sha256") +EC=$(awk '$1=="int_cert_checker"{print $NF}' "$HERE/MANIFEST.sha256") +EI=$(awk '$1=="coverage_inventory"{print $NF}' "$HERE/MANIFEST.sha256") +EPF=$(awk '$1=="int_cert_proof_identity"{print $NF}' "$HERE/MANIFEST.sha256") +EPD=$(awk '$1=="int_cert_proof_digest"{print $NF}' "$HERE/MANIFEST.sha256") +[ -n "$EP" ] && [ -n "$EC" ] && [ -n "$EI" ] && [ -n "$EPF" ] && [ -n "$EPD" ] || { echo "status=refused reason=manifest-incomplete" >&2; exit 3; } +exec python3 -I -S -B "$HERE/isolated_entry.py" int-cert \ + --expression "$1" --lower "$2" --upper "$3" --tolerance "$4" \ + --producer "$HERE/int_cert_producer.py" --checker "$HERE/jackal_int_cert_check" \ + --expected-producer "$EP" --expected-checker "$EC" --receipt "$5" \ + --inventory "$HERE/formal_coverage_inventory.json" --expected-inventory "$EI" \ + --proof-identity "$HERE/int_cert_proof_identity.json" \ + --expected-proof-identity-file "$EPF" --expected-proof-identity-digest "$EPD" \ + --release-epoch v1.7.2 +WRAP + +cat > "$PKG/jackal-gaussian-release" <<'WRAP' +#!/bin/sh +# Preserved theorem-backed Gaussian lane; package-local and self-contained. +set -eu +HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +[ "$#" -eq 5 ] || { echo "usage: jackal-gaussian-release " >&2; exit 2; } +EP=$(awk '$1=="gaussian_producer"{print $NF}' "$HERE/MANIFEST.sha256") +EC=$(awk '$1=="gaussian_checker"{print $NF}' "$HERE/MANIFEST.sha256") +EI=$(awk '$1=="coverage_inventory"{print $NF}' "$HERE/MANIFEST.sha256") +EPF=$(awk '$1=="gaussian_proof_identity"{print $NF}' "$HERE/MANIFEST.sha256") +EPD=$(awk '$1=="gaussian_proof_digest"{print $NF}' "$HERE/MANIFEST.sha256") +[ -n "$EP" ] && [ -n "$EC" ] && [ -n "$EI" ] && [ -n "$EPF" ] && [ -n "$EPD" ] || { echo "status=refused reason=manifest-incomplete" >&2; exit 3; } +exec python3 -I -S -B "$HERE/isolated_entry.py" gaussian \ + --expression "$1" --lower "$2" --upper "$3" --tolerance "$4" \ + --producer "$HERE/gaussian_certificate.py" --checker "$HERE/jackal_gaussian_check" \ + --expected-producer "$EP" --expected-checker "$EC" \ + --inventory "$HERE/formal_coverage_inventory.json" --expected-inventory "$EI" \ + --proof-identity "$HERE/gaussian_proof_identity.json" \ + --expected-proof-identity-file "$EPF" --expected-proof-identity-digest "$EPD" \ + --release-epoch v1.5.0 --receipt "$5" +WRAP + +cat > "$PKG/jackal-receipt-verify" <<'WRAP' +#!/bin/sh +set -eu +HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +exec python3 -I -S -B "$HERE/isolated_entry.py" verify "$@" +WRAP + +cat > "$PKG/jackal-claim" <<'WRAP' +#!/bin/sh +set -eu +HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +exec python3 -I -S -B "$HERE/claim_router.py" claim "$@" +WRAP + +cat > "$PKG/jackal-anubis-program" <<'WRAP' +#!/bin/sh +# check invokes only Anubis build --evidence; no subcommand executes artifact. +set -eu +SELF=$0 +LINK_HOPS=0 +while [ -L "$SELF" ]; do + LINK_HOPS=$((LINK_HOPS + 1)) + if [ "$LINK_HOPS" -gt 16 ]; then + echo "status=refused reason=wrapper-symlink-depth" >&2 + exit 126 + fi + LINK_TARGET=$(/usr/bin/readlink "$SELF") || { + echo "status=refused reason=wrapper-symlink-read" >&2 + exit 126 + } + case "$LINK_TARGET" in + /*) SELF=$LINK_TARGET ;; + *) SELF=$(dirname -- "$SELF")/$LINK_TARGET ;; + esac +done +HERE=$(CDPATH= cd -P -- "$(dirname -- "$SELF")" && pwd -P) +exec python3 -I -S -B "$HERE/tools/anubis_program_verify.py" "$@" +WRAP + +cat > "$PKG/jackal-claim-verify" <<'WRAP' +#!/bin/sh +set -eu +HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +M="$HERE/MANIFEST.sha256" +set -- "$@" \ + --expected-inference-registry "$HERE/inference_registry_v1.json" \ + --expected-inference-registry-sha256 "$(awk '$1=="claim_inference_registry"{print $NF}' "$M")" \ + --expected-unit-registry "$HERE/unit_registry_v1.json" \ + --expected-unit-registry-sha256 "$(awk '$1=="claim_unit_registry"{print $NF}' "$M")" \ + --expected-environment-epoch "$(awk '$1=="evaluator"{print $NF}' "$M")" \ + --receipt-verifier "$HERE/receipt_verify.py" --exact-verifier "$HERE/exact_verify.py" \ + --checker "$HERE/jackal_cert_check" --expected-checker "$(awk '$1=="checker"{print $NF}' "$M")" \ + --expected-evaluator "$(awk '$1=="evaluator"{print $NF}' "$M")" \ + --inventory "$HERE/formal_coverage_inventory.json" \ + --expected-inventory "$(awk '$1=="coverage_inventory"{print $NF}' "$M")" \ + --proof-identity "$HERE/range_proof_identity.json" \ + --expected-proof-identity-file "$(awk '$1=="range_proof_identity"{print $NF}' "$M")" \ + --expected-proof-identity-digest "$(awk '$1=="range_proof_digest"{print $NF}' "$M")" \ + --gaussian-checker "$HERE/jackal_gaussian_check" \ + --expected-gaussian-checker "$(awk '$1=="gaussian_checker"{print $NF}' "$M")" \ + --gaussian-proof-identity "$HERE/gaussian_proof_identity.json" \ + --expected-gaussian-proof-identity-file "$(awk '$1=="gaussian_proof_identity"{print $NF}' "$M")" \ + --expected-gaussian-proof-identity-digest "$(awk '$1=="gaussian_proof_digest"{print $NF}' "$M")" \ + --int-cert-checker "$HERE/jackal_int_cert_check" \ + --expected-int-cert-checker "$(awk '$1=="int_cert_checker"{print $NF}' "$M")" \ + --int-cert-proof-identity "$HERE/int_cert_proof_identity.json" \ + --expected-int-cert-proof-identity-file "$(awk '$1=="int_cert_proof_identity"{print $NF}' "$M")" \ + --expected-int-cert-proof-identity-digest "$(awk '$1=="int_cert_proof_digest"{print $NF}' "$M")" \ + --archival-range-checker "$HERE/jackal_cert_check_v170" \ + --expected-archival-range-checker "$(awk '$1=="archival_range_checker"{print $NF}' "$M")" \ + --archival-range-proof-identity "$HERE/evidence/range_proof_identity_v1.json" \ + --expected-archival-range-proof-identity-file "$(awk '$1=="archival_range_proof_identity"{print $NF}' "$M")" \ + --expected-archival-range-proof-identity-digest "$(awk '$1=="archival_range_proof_digest"{print $NF}' "$M")" \ + --archival-range-inventory "$HERE/evidence/formal_coverage_inventory_v170.json" \ + --expected-archival-range-inventory "$(awk '$1=="archival_range_coverage_inventory"{print $NF}' "$M")" +for producer in sqrt_rat exp_rat ln_rat sin_rat atan_rat tanh_rat; do + pin=$(awk -v label="${producer}_producer" '$1==label{print $NF}' "$M") + [ -n "$pin" ] && set -- "$@" --trusted-producer "$pin" +done +pin=$(awk '$1=="gaussian_producer"{print $NF}' "$M") +[ -n "$pin" ] && set -- "$@" --trusted-producer "$pin" +pin=$(awk '$1=="int_cert_producer"{print $NF}' "$M") +[ -n "$pin" ] && set -- "$@" --trusted-producer "$pin" +exec python3 -I -S -B "$HERE/claim_bundle_verify.py" "$@" +WRAP + +emit_variant_wrapper() { + wrapper_name="$1" + producer_file="$2" + manifest_label="$3" + variant="$4" + producer_extra="$5" + cat > "$PKG/$wrapper_name" < [receipt.json]" >&2; exit 2; } +EXPR="\$1"; LO="\$2"; HI="\$3"; RECEIPT="\${4:-}" +[ -f "\$HERE/MANIFEST.sha256" ] && [ ! -L "\$HERE/MANIFEST.sha256" ] || { echo "status=refused reason=manifest-identity" >&2; exit 1; } +EM=\$(shasum -a 256 "\$HERE/MANIFEST.sha256" | awk '{print \$1}') +EP=\$(awk '\$1=="$manifest_label"{print \$NF}' "\$HERE/MANIFEST.sha256") +EC=\$(awk '\$1=="checker"{print \$NF}' "\$HERE/MANIFEST.sha256") +EPF=\$(awk '\$1=="range_proof_identity"{print \$NF}' "\$HERE/MANIFEST.sha256") +EPD=\$(awk '\$1=="range_proof_digest"{print \$NF}' "\$HERE/MANIFEST.sha256") +EI=\$(awk '\$1=="coverage_inventory"{print \$NF}' "\$HERE/MANIFEST.sha256") +[ -n "\$EP" ] && [ -n "\$EC" ] && [ -n "\$EPF" ] && [ -n "\$EPD" ] && [ -n "\$EI" ] || { echo "status=refused reason=manifest-incomplete" >&2; exit 1; } +verify_variant_runtime_identity() { + MP=\$(shasum -a 256 "\$HERE/MANIFEST.sha256" | awk '{print \$1}') + PP=\$(shasum -a 256 "\$HERE/$producer_file" | awk '{print \$1}') + CP=\$(shasum -a 256 "\$HERE/jackal_cert_check" | awk '{print \$1}') + PF=\$(shasum -a 256 "\$HERE/range_proof_identity.json" | awk '{print \$1}') + IF=\$(shasum -a 256 "\$HERE/formal_coverage_inventory.json" | awk '{print \$1}') + DID=\$(python3 -I -S -B -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["identity_digest_sha256"])' "\$HERE/range_proof_identity.json") + [ "\$MP" = "\$EM" ] && [ "\$PP" = "\$EP" ] && [ "\$CP" = "\$EC" ] && \ + [ "\$PF" = "\$EPF" ] && [ "\$IF" = "\$EI" ] && [ "\$DID" = "\$EPD" ] || { + echo "status=refused reason=identity" >&2 + exit 1 + } +} +verify_variant_runtime_identity +CERT=\$(mktemp) +trap 'rm -f "\$CERT"' EXIT +python3 -I -S -B "\$HERE/$producer_file" emit $producer_extra --expression="\$EXPR" --lower="\$LO" --upper="\$HI" >"\$CERT" 2>&1 || { echo "status=refused reason=producer-refused" >&2; exit 1; } +verify_variant_runtime_identity +OUT=\$("\$HERE/jackal_cert_check" "\$CERT" range-bound-cert "\$EXPR" "\$LO" "\$HI" 2>&1) || { echo "status=refused reason=checker-rejected detail=\"\$OUT\"" >&2; exit 1; } +verify_variant_runtime_identity +if [ -n "\$RECEIPT" ]; then + python3 -I -S -B "\$HERE/isolated_entry.py" emit-variant-receipt \ + --variant "$variant" --expression="\$EXPR" --lower="\$LO" --upper="\$HI" \ + --cert "\$CERT" --producer "\$HERE/$producer_file" \ + --checker "\$HERE/jackal_cert_check" \ + --proof-identity "\$HERE/range_proof_identity.json" \ + --inventory "\$HERE/formal_coverage_inventory.json" \ + --release-epoch v1.7.2 --output "\$RECEIPT" +fi +verify_variant_runtime_identity +[ -z "\$RECEIPT" ] || echo "receipt=\$RECEIPT" +echo "status=formal-bounded" +echo "checker.ACCEPT=\$OUT" +WRAP + /bin/chmod +x "$PKG/$wrapper_name" +} + +emit_variant_wrapper jackal-sqrt-rat-release sqrt_rat_producer.py sqrt_rat_producer sqrt_rat "" +emit_variant_wrapper jackal-exp-rat-release exp_rat_producer.py exp_rat_producer exp_rat "" +emit_variant_wrapper jackal-ln-rat-release ln_rat_producer.py ln_rat_producer ln_rat "" +emit_variant_wrapper jackal-sin-rat-release sin_rat_producer.py sin_rat_producer sin_rat "--op sin" +emit_variant_wrapper jackal-cos-rat-release sin_rat_producer.py sin_rat_producer cos_rat "--op cos" +emit_variant_wrapper jackal-atan-rat-release atan_rat_producer.py atan_rat_producer atan_rat "" +emit_variant_wrapper jackal-tanh-rat-release tanh_rat_producer.py tanh_rat_producer tanh_rat "" + +/bin/chmod +x "$PKG/jackal-cert-release" "$PKG/jackal-int-cert-release" \ + "$PKG/jackal-gaussian-release" "$PKG/jackal-receipt-verify" \ + "$PKG/jackal-claim" "$PKG/jackal-claim-verify" \ + "$PKG/jackal-anubis-program" + +EVALUATOR_ID=$(sha256 "$PKG/jackal-native") +RANGE_CHECKER_ID=$(sha256 "$PKG/jackal_cert_check") +GAUSSIAN_CHECKER_ID=$(sha256 "$PKG/jackal_gaussian_check") +INT_CHECKER_ID=$(sha256 "$PKG/jackal_int_cert_check") +ARCHIVAL_RANGE_CHECKER_ID=$(sha256 "$PKG/jackal_cert_check_v170") +ARCHIVAL_RANGE_INVENTORY_ID=$(sha256 "$PKG/evidence/formal_coverage_inventory_v170.json") +INT_PRODUCER_ID=$(sha256 "$PKG/int_cert_producer.py") +SOURCE_ID=$(sha256 "$PKG/jackal_calc.anb") +RANGE_IDENTITY_FILE_ID=$(sha256 "$PKG/range_proof_identity.json") +INT_IDENTITY_FILE_ID=$(sha256 "$PKG/int_cert_proof_identity.json") +GAUSSIAN_IDENTITY_FILE_ID=$(sha256 "$PKG/gaussian_proof_identity.json") +RANGE_IDENTITY_DIGEST=$(python3 -I -S -B -c 'import json,sys; print(json.load(open(sys.argv[1]))["identity_digest_sha256"])' "$PKG/range_proof_identity.json") +INT_IDENTITY_DIGEST=$(python3 -I -S -B -c 'import json,sys; print(json.load(open(sys.argv[1]))["identity_digest_sha256"])' "$PKG/int_cert_proof_identity.json") +GAUSSIAN_IDENTITY_DIGEST=$(python3 -I -S -B -c 'import json,sys; print(json.load(open(sys.argv[1]))["identity_digest_sha256"])' "$PKG/gaussian_proof_identity.json") +LEAN_ADMISSION_AUDIT_ID=$(sha256 "$PKG/evidence/lean_admission_audit_v173.json") +LEAN_ADMISSION_AUDIT_DIGEST=$(python3 -I -S -B -c 'import json,sys; print(json.load(open(sys.argv[1]))["audit_digest_sha256"])' "$PKG/evidence/lean_admission_audit_v173.json") +COVERAGE_ID=$(sha256 "$PKG/formal_coverage_inventory.json") +COMPAT_ID=$(sha256 "$PKG/evidence/compat_v172_floor.json") +PROGRAM_COMPAT_ID=$(sha256 "$PKG/evidence/compat_v173_floor.json") +RANGE_ABA_ID=$(sha256 "$PKG/evidence/range_ordering_aba_v172.json") +INT_ABA_ID=$(sha256 "$PKG/evidence/int_cert_premise_aba_v172.json") +# formal_receipt.py is part of the hermes runtime bundle; bake its Linux compat +# pins BEFORE the bundle digest is computed so MANIFEST and selftest agree. +python3 -I -S -B "$ROOT/release/tools/bake_linux_compat_pins.py" \ + "$PKG/formal_receipt.py" "$RANGE_IDENTITY_FILE_ID" "$INT_IDENTITY_FILE_ID" >/dev/null +PLUGIN_ID=$(python3 -I -S -B "$PKG/plugin/hermes/bundle_hash.py" print) + +cat > "$PKG/MANIFEST.sha256" < "$PKG/NON-CLAIMS.txt" <<'EOF' +JACKAL v1.7.3 — explicit boundary +- Apple Silicon macOS only; unsigned and not notarized. +- Range and composed-integral formal language applies only to the declared + checker-accepted fragments and the v2 identities shipped here. +- The archived v1 range identity is replay-only. Archival replay + requires the exact historical range checker AND the exact historical + coverage inventory that shipped with it (jackal_cert_check_v170 plus + formal_coverage_inventory_v170.json); no other checker/inventory tuple + is admitted for archival replay, and reversed range intervals remain + revoked and refuse. +- The archived v1 composed-integral identity is historical revocation + evidence only. Its request-unbound checker is not shipped or admitted; + every v1.7.0 int-certificate receipt refuses formal replay. +- Unsupported proof epochs and fragments refuse. +- Gaussian, pure-rational, exact-CAS, and claim-kernel lanes preserve their + prior assurance classes. No lane is silently upgraded. +- A programming-status pack establishes STRUCTURE, never correctness. + test-exists says only that a declaration-shaped occurrence of a named + symbol exists in bytes at a claimed content hash. It says nothing + about whether that test executes, passes, asserts anything, or covers + what a surrounding document claims it covers. +- claim-cites-test RESOLVES a citation; it does not validate one. A + document may cite a real test that checks something entirely + different, and this checker cannot see that. +- The decision pack orders options by a caller-declared numeric + criterion. Accepting that criterion is never a claim that it is the + right one to optimise. Value judgments are refused, not ranked. +- The value-judgment screen is a substring blocklist and is INCOMPLETE. + Measured on the shipped engine: criteria spelled optimal, ideal, and + leetspeak such as b3st are ACCEPTED, while best and preference_score + refuse. Closing that gap requires a declared unit or measurement + provenance on the criterion, which is a protocol change and was not + made. +- The domain-pack verifier checks metadata, identity, and policy only. + It records anubis_execution_status=NOT_EXECUTED and + assurance_status=NOT_MINTED in its own output: a declared manifest + ceiling is an upper bound on what a consumer may claim, never a grant. +- verified-program-evidence and verified-program-receipt mean exact byte/pin, + roster, producer-summary, approved-Z3 UNSAT, and independent-RUP checks + under inventory-safe-v1. They do not establish policy-construct totality, + source-to-VC proof, SMT-to-CNF proof, source-native refinement, runtime + behavior, or universal language soundness. +- Program verification never executes the compiled artifact. The check front + door invokes only the exact approved compiler's build --evidence path. +- No universal correctness, source-to-native refinement, input-truth proof, + operating-system proof, or authenticated builder claim is made. +- The repository-wide Lean admission audit binds tracked source, theorem + axioms, and observed checker bytes. It does not prove the compiler, kernel, + native code, operating system, hardware, or supply chain. +EOF + +cat > "$PKG/README.txt" <<'EOF' +JACKAL v1.7.3 — unified domain-pack and Anubis program evidence for Apple Silicon macOS. + +First run: shasum -a 256 -c SHA256SUMS +Current stable identities: + range_proof_identity.json schema jackal-range-proof-identity-v2 + int_cert_proof_identity.json schema jackal-int-cert-proof-identity-v2 +Compatibility and A->B->A evidence live under evidence/. +The repository-wide Lean admission record is +evidence/lean_admission_audit_v173.json. +The complete 41-tool catalog and core/formal/full profiles live under +plugin/hermes/. Domain packs and their checkers retain repository-relative +paths under domain_packs/ and tools/. Program verification uses +jackal-anubis-program with program/inventory_safe_v1.json. +Current range and composed-integral wrappers emit release epoch v1.7.2. +See NON-CLAIMS.txt before interpreting any result. +EOF + +cat > "$PKG/PROVENANCE-RECEIPT.txt" < SHA256SUMS) + +# Validate the staged package itself before it can become a tarball or enter +# release/dist. This rejects a self-consistent manifest built from a +# semantically mismatched checker/proof/policy tuple. +(cd "$PKG" && shasum -a 256 -c SHA256SUMS >/dev/null) +python3 -I -S -B - "$PKG" "$V170_RANGE_CHECKER_SHA256" \ + "$V170_COVERAGE_INVENTORY_SHA256" <<'PY' +import hashlib +import json +import pathlib +import sys + +package = pathlib.Path(sys.argv[1]).resolve() +expected_archival_range_checker = sys.argv[2] +expected_archival_range_inventory = sys.argv[3] + + +def refuse(reason: str) -> None: + raise SystemExit(f"PACKAGE_V173_REFUSED reason=staged-semantic-{reason}") + + +def require(condition: bool, reason: str) -> None: + if not condition: + refuse(reason) + + +def sha(path: pathlib.Path) -> str: + require(path.is_file() and not path.is_symlink(), f"regular-file:{path.name}") + digest = hashlib.sha256() + with path.open("rb") as source: + while chunk := source.read(1024 * 1024): + digest.update(chunk) + return digest.hexdigest() + + +def reject_duplicates(pairs): + result = {} + for key, value in pairs: + if key in result: + refuse(f"duplicate-json-key:{key}") + result[key] = value + return result + + +def load_json(relative: str): + path = package / relative + require(path.is_file() and not path.is_symlink(), f"json-regular:{relative}") + data = path.read_bytes() + require(len(data) <= 4 * 1024 * 1024, f"json-size:{relative}") + try: + return json.loads(data, object_pairs_hook=reject_duplicates) + except (UnicodeError, json.JSONDecodeError) as exc: + refuse(f"json-parse:{relative}:{exc}") + + +manifest_path = package / "MANIFEST.sha256" +require(manifest_path.is_file() and not manifest_path.is_symlink(), "manifest-regular") +manifest_bytes = manifest_path.read_bytes() +require(len(manifest_bytes) <= 1024 * 1024, "manifest-size") +rows = {} +for raw in manifest_bytes.decode("utf-8").splitlines(): + line = raw.strip() + if not line or line.startswith("#"): + continue + fields = line.split() + require(len(fields) >= 2, "manifest-row") + label = fields[0] + require(label not in rows, f"manifest-duplicate:{label}") + rows[label] = fields[1:] + + +def row_hash(label: str) -> str: + require(label in rows and len(rows[label]) >= 1, f"manifest-missing:{label}") + value = rows[label][-1] + require(len(value) == 64 and all(c in "0123456789abcdef" for c in value), + f"manifest-digest:{label}") + return value + + +range_current = load_json("range_proof_identity.json") +gaussian_current = load_json("gaussian_proof_identity.json") +int_current = load_json("int_cert_proof_identity.json") +lean_audit = load_json("evidence/lean_admission_audit_v173.json") +range_archival = load_json("evidence/range_proof_identity_v1.json") +int_revoked = load_json("evidence/int_cert_proof_identity_v1.json") +compat = load_json("evidence/compat_v172_floor.json") + +range_checker = sha(package / "jackal_cert_check") +gaussian_checker = sha(package / "jackal_gaussian_check") +int_checker = sha(package / "jackal_int_cert_check") +archival_range_checker = sha(package / "jackal_cert_check_v170") +require(range_checker == row_hash("checker"), "range-checker-manifest") +require(gaussian_checker == row_hash("gaussian_checker"), + "gaussian-checker-manifest") +require(int_checker == row_hash("int_cert_checker"), "int-checker-manifest") +require(archival_range_checker == expected_archival_range_checker, + "archival-range-checker-pin") +require(archival_range_checker == row_hash("archival_range_checker"), + "archival-range-checker-manifest") +require(sha(package / "evidence/formal_coverage_inventory_v170.json") == + expected_archival_range_inventory, + "archival-range-inventory-pin") +require(row_hash("archival_range_coverage_inventory") == + expected_archival_range_inventory, + "archival-range-inventory-manifest") +revoked_int_checker_path = package.joinpath("jackal_int_cert_check_v170") +require(not revoked_int_checker_path.exists(), "revoked-int-checker-present") +require("archival_int_cert_checker" not in rows, "revoked-int-checker-row") + +proofs = ( + (range_current, "jackal-range-proof-identity-v2", range_checker, + "range_proof_identity", "range_proof_digest"), + (gaussian_current, "jackal-gaussian-proof-identity-v1", gaussian_checker, + "gaussian_proof_identity", "gaussian_proof_digest"), + (int_current, "jackal-int-cert-proof-identity-v2", int_checker, + "int_cert_proof_identity", "int_cert_proof_digest"), + (range_archival, "jackal-range-proof-identity-v1", archival_range_checker, + "archival_range_proof_identity", "archival_range_proof_digest"), +) +for proof, schema, checker, file_label, digest_label in proofs: + require(proof.get("schema") == schema, f"proof-schema:{file_label}") + require(proof.get("checker", {}).get("sha256") == checker, + f"proof-checker:{file_label}") + proof_path = rows[file_label][0] + require(sha(package / proof_path) == row_hash(file_label), + f"proof-file:{file_label}") + require(proof.get("identity_digest_sha256") == row_hash(digest_label), + f"proof-digest:{file_label}") + +require(lean_audit.get("schema") == "jackal-lean-admission-audit-v1", + "lean-audit-schema") +require(sha(package / "evidence/lean_admission_audit_v173.json") == + row_hash("lean_admission_audit"), "lean-audit-file") +lean_audit_digest = lean_audit.pop("audit_digest_sha256", None) +computed_lean_audit_digest = hashlib.sha256( + json.dumps(lean_audit, sort_keys=True, separators=(",", ":"), + ensure_ascii=False).encode("utf-8") +).hexdigest() +require(lean_audit_digest == computed_lean_audit_digest, + "lean-audit-self-digest") +require(lean_audit_digest == row_hash("lean_admission_audit_digest"), + "lean-audit-manifest-digest") +audit_result = lean_audit.get("audit_result", {}) +require(audit_result.get("status") == "pass", "lean-audit-status") +require(audit_result.get("logical_admission_count") == 0, + "lean-audit-admission-count") +source_inventory = lean_audit.get("source_inventory", {}) +source_files = source_inventory.get("files", []) +require(isinstance(source_files, list) and source_files and + source_inventory.get("file_count") == len(source_files), + "lean-audit-source-count") +source_paths = [item.get("path") for item in source_files + if isinstance(item, dict)] +require(len(source_paths) == len(source_files) and + len(set(source_paths)) == len(source_paths), + "lean-audit-source-uniqueness") +construct_policy = source_inventory.get("construct_policy", {}) +require(construct_policy.get("forbidden_findings") == [], + "lean-audit-forbidden-findings") +allowed_findings = construct_policy.get("allowed_findings", []) +require(isinstance(allowed_findings, list) and + {item.get("construct") for item in allowed_findings} == + {"implemented_by"}, "lean-audit-allowed-findings") +theorem_audit = lean_audit.get("theorem_axiom_audit", {}) +theorem_rows = theorem_audit.get("theorems", []) +require(isinstance(theorem_rows, list) and theorem_rows and + theorem_audit.get("theorem_count") == len(theorem_rows), + "lean-audit-theorem-count") +theorem_names = [item.get("theorem") for item in theorem_rows + if isinstance(item, dict)] +require(len(theorem_names) == len(theorem_rows) and + len(set(theorem_names)) == len(theorem_names), + "lean-audit-theorem-uniqueness") +for item in theorem_rows: + require(item.get("axioms") == + ["propext", "Classical.choice", "Quot.sound"], + f"lean-audit-axioms:{item.get('theorem')}") +trust_surface = lean_audit.get("trust_surface", {}) +require(trust_surface.get("logical_admissions") == [], + "lean-audit-logical-admissions") +require(trust_surface.get("repository_axiom_declarations") == [], + "lean-audit-repository-axioms") +audit_bindings = lean_audit.get("release_bindings", {}).get( + "current_proof_identities", []) +require(len(audit_bindings) == 3, "lean-audit-binding-count") +expected_audit_bindings = { + "range": (range_checker, sha(package / "range_proof_identity.json")), + "gaussian": (gaussian_checker, sha(package / "gaussian_proof_identity.json")), + "int-cert": (int_checker, sha(package / "int_cert_proof_identity.json")), +} +for binding in audit_bindings: + lane = binding.get("lane") + require(lane in expected_audit_bindings, f"lean-audit-binding-lane:{lane}") + expected_checker, expected_identity = expected_audit_bindings.pop(lane) + require(binding.get("checker_sha256") == expected_checker, + f"lean-audit-binding-checker:{lane}") + require(binding.get("identity_checker_sha256") == expected_checker, + f"lean-audit-identity-checker:{lane}") + require(binding.get("identity_sha256") == expected_identity, + f"lean-audit-binding-identity:{lane}") +require(expected_audit_bindings == {}, "lean-audit-binding-coverage") + +require(int_revoked.get("schema") == "jackal-int-cert-proof-identity-v1", + "revoked-int-proof-schema") +revoked_reference = rows.get("revoked_int_cert_proof_identity_reference", []) +require(len(revoked_reference) == 2, "revoked-int-proof-reference") +require(sha(package / revoked_reference[0]) == revoked_reference[1], + "revoked-int-proof-file") + +lanes = compat.get("lanes", {}) +for lane in ("range", "rational_variants"): + current = lanes.get(lane, {}).get("current", {}) + archival = lanes.get(lane, {}).get("archival_v1", {}) + require(current.get("schema") == "jackal-range-proof-identity-v2", + f"compat-current-schema:{lane}") + require(current.get("allowed_release_epochs") == ["v1.7.2"], + f"compat-current-epoch:{lane}") + require(current.get("identity_file_sha256") == row_hash("range_proof_identity"), + f"compat-current-proof:{lane}") + require(archival.get("mode") == "replay-only", f"compat-archive-mode:{lane}") + require(archival.get("allowed_release_epochs") == ["v1.5.0"], + f"compat-archive-epoch:{lane}") + require(archival.get("checker_sha256") == archival_range_checker, + f"compat-archive-checker:{lane}") + require(archival.get("identity_file_sha256") == + row_hash("archival_range_proof_identity"), + f"compat-archive-proof:{lane}") + +int_policy = lanes.get("int_cert", {}) +int_current_policy = int_policy.get("current", {}) +int_archival_policy = int_policy.get("archival_v1", {}) +require(int_current_policy.get("schema") == "jackal-int-cert-proof-identity-v2", + "compat-int-current-schema") +require(int_current_policy.get("allowed_release_epochs") == ["v1.7.2"], + "compat-int-current-epoch") +require(int_current_policy.get("identity_file_sha256") == + row_hash("int_cert_proof_identity"), "compat-int-current-proof") +require(int_archival_policy.get("mode") == "revoked-refuse", + "compat-int-revocation-mode") +require(int_archival_policy.get("allowed_release_epochs") == [], + "compat-int-revocation-epochs") +require(int_archival_policy.get("identity_file_sha256") == revoked_reference[1], + "compat-int-revocation-proof") + +program_compat = load_json("evidence/compat_v173_floor.json") +policy = load_json("program/inventory_safe_v1.json") +catalog = load_json("plugin/hermes/tools.json") +capability_inventory = load_json("capability_inventory_v1.json") +full_profile = load_json("plugin/hermes/profiles/full.json") +tool_names = [tool.get("name") for tool in catalog.get("tools", []) + if isinstance(tool, dict)] +require(catalog.get("version") == "v1.7.3", "catalog-version") +require(len(tool_names) == 41 and len(set(tool_names)) == 41, + "catalog-tool-count") +require(capability_inventory.get("schema") == + "jackal-capability-inventory-v1", "capability-inventory-schema") +inventory_names = [tool.get("name") for tool in + capability_inventory.get("tools", []) + if isinstance(tool, dict)] +require(capability_inventory.get("tool_count") == 41 and + capability_inventory.get("unique_tool_count") == 41 and + inventory_names == tool_names, + "capability-inventory-tool-parity") +inventory_catalog = capability_inventory.get("catalog", {}) +require(inventory_catalog.get("version") == "v1.7.3" and + inventory_catalog.get("sha256") == + sha(package / "plugin/hermes/tools.json"), + "capability-inventory-catalog-binding") +inventory_release = capability_inventory.get("release", {}) +require(inventory_release.get("version") == "v1.7.3" and + inventory_release.get("state") == "v1.7.3", + "capability-inventory-release-state") +require(full_profile.get("tools") == tool_names, "full-profile-catalog-parity") +for profile_name in ("core", "formal", "full"): + profile = load_json(f"plugin/hermes/profiles/{profile_name}.json") + profile_digest = profile.pop("profile_digest_sha256", None) + computed_profile_digest = hashlib.sha256( + json.dumps(profile, sort_keys=True, separators=(",", ":"), + ensure_ascii=False).encode("utf-8") + ).hexdigest() + require(profile_digest == computed_profile_digest, + f"profile-digest:{profile_name}") +require(program_compat.get("release_epoch") == "v1.7.3", + "program-compat-epoch") +require(program_compat.get("tool_count") == 41, "program-compat-tool-count") +require(program_compat.get("program_profile") == "inventory-safe-v1", + "program-compat-profile") +require(program_compat.get("independent_policy_construct_totality") is False, + "program-compat-construct-totality") +policy_digest = policy.pop("policy_digest_sha256", None) +computed_policy_digest = hashlib.sha256( + json.dumps(policy, sort_keys=True, separators=(",", ":"), + ensure_ascii=False).encode("utf-8") +).hexdigest() +require(policy_digest == computed_policy_digest, "program-policy-digest") +require(program_compat.get("program_policy_sha256") == policy_digest, + "program-compat-policy") + +manifest_bound_files = { + "domain_pack_registry": "domain_packs/registry_v1.json", + "domain_pack_verifier": "tools/domain_pack_verify.py", + "domain_pack_test_exists_checker": "tools/test_exists_verify.py", + "domain_pack_decision_checker": "tools/decision_verify.py", + "anubis_program_verifier": "tools/anubis_program_verify.py", + "anubis_program_policy": "program/inventory_safe_v1.json", +} +for label, relative in manifest_bound_files.items(): + require(len(rows.get(label, [])) == 2, f"manifest-shape:{label}") + require(rows[label][0] == relative, f"manifest-path:{label}") + require(row_hash(label) == sha(package / relative), + f"manifest-file:{label}") +print("STAGED_IDENTITY_VALIDATION_PASS") +PY + +# Domain-pack protocol-v1 QA. The verifier admits this host, so it runs +# unconditionally as part of the staged release validation. +PACK_VALIDATION=$(python3 -I -S -B "$PKG/tools/domain_pack_verify.py" \ + --root "$PKG" 2>&1) || { + echo "PACKAGE_V173_REFUSED reason=staged-domain-pack detail=$PACK_VALIDATION" >&2 + exit 4 +} +/usr/bin/printf '%s' "$PACK_VALIDATION" | python3 -I -S -B -c ' +import json +import sys + +try: + report = json.load(sys.stdin) +except (json.JSONDecodeError, UnicodeError): + raise SystemExit(1) +raise SystemExit(0 if isinstance(report, dict) and report.get("status") == "accepted" else 1) +' || { + echo "PACKAGE_V173_REFUSED reason=staged-domain-pack-status detail=$PACK_VALIDATION" >&2 + exit 4 +} +PLUGIN_SELFTEST=$("$PKG/plugin/hermes/jackal_hermes" selftest 2>&1) || { + echo "PACKAGE_V173_REFUSED reason=staged-plugin-selftest detail=$PLUGIN_SELFTEST" >&2 + exit 4 +} +case "$PLUGIN_SELFTEST" in + *"plugin_hermes.identity_match=true"*) ;; + *) + echo "PACKAGE_V173_REFUSED reason=staged-plugin-identity detail=$PLUGIN_SELFTEST" >&2 + exit 4 + ;; +esac + +SMOKE_DIR="$STAGE/staged-semantic-smoke" +/bin/mkdir "$SMOKE_DIR" +RANGE_SMOKE_RECEIPT="$SMOKE_DIR/staged-range-receipt.json" +INT_SMOKE_RECEIPT="$SMOKE_DIR/staged-int-receipt.json" +RANGE_SMOKE=$("$PKG/jackal-cert-release" x 0 1 "$RANGE_SMOKE_RECEIPT" 2>&1) || { + echo "PACKAGE_V173_REFUSED reason=staged-range-smoke detail=$RANGE_SMOKE" >&2 + exit 4 +} +case "$RANGE_SMOKE" in *"status=formal-bounded"*) ;; *) + echo "PACKAGE_V173_REFUSED reason=staged-range-status detail=$RANGE_SMOKE" >&2 + exit 4 +esac +INT_SMOKE=$("$PKG/jackal-int-cert-release" 0 0 1 2 "$INT_SMOKE_RECEIPT" 2>&1) || { + echo "PACKAGE_V173_REFUSED reason=staged-int-smoke detail=$INT_SMOKE" >&2 + exit 4 +} +case "$INT_SMOKE" in *"status=formal-bounded"*) ;; *) + echo "PACKAGE_V173_REFUSED reason=staged-int-status detail=$INT_SMOKE" >&2 + exit 4 +esac + +RANGE_VERIFY=$("$PKG/jackal-receipt-verify" \ + --receipt "$RANGE_SMOKE_RECEIPT" --checker "$PKG/jackal_cert_check" \ + --expected-evaluator "$EVALUATOR_ID" --expected-checker "$RANGE_CHECKER_ID" \ + --expected-source "$SOURCE_ID" --expected-release-epoch v1.7.2 \ + --expected-command range-bound-cert --expected-expression x \ + --expected-input-lo 0 --expected-input-hi 1 \ + --inventory "$PKG/formal_coverage_inventory.json" --expected-inventory "$COVERAGE_ID" \ + --proof-identity "$PKG/range_proof_identity.json" \ + --expected-proof-identity-file "$RANGE_IDENTITY_FILE_ID" \ + --expected-proof-identity-digest "$RANGE_IDENTITY_DIGEST" 2>&1) || { + echo "PACKAGE_V173_REFUSED reason=staged-range-replay detail=$RANGE_VERIFY" >&2 + exit 4 +} +INT_VERIFY=$("$PKG/jackal-receipt-verify" \ + --receipt "$INT_SMOKE_RECEIPT" --checker "$PKG/jackal_int_cert_check" \ + --expected-evaluator "$INT_PRODUCER_ID" --expected-checker "$INT_CHECKER_ID" \ + --expected-release-epoch v1.7.2 --expected-command integrate-bound-cert \ + --expected-expression 0 --expected-input-lo 0 --expected-input-hi 1 \ + --expected-tolerance 2 --inventory "$PKG/formal_coverage_inventory.json" \ + --expected-inventory "$COVERAGE_ID" \ + --proof-identity "$PKG/int_cert_proof_identity.json" \ + --expected-proof-identity-file "$INT_IDENTITY_FILE_ID" \ + --expected-proof-identity-digest "$INT_IDENTITY_DIGEST" 2>&1) || { + echo "PACKAGE_V173_REFUSED reason=staged-int-replay detail=$INT_VERIFY" >&2 + exit 4 +} +for replay in "$RANGE_VERIFY" "$INT_VERIFY"; do + /usr/bin/printf '%s\n' "$replay" | /usr/bin/grep -F "status=verified verdict=ACCEPT" >/dev/null && + /usr/bin/printf '%s\n' "$replay" | /usr/bin/grep -F "receipt_valid=true" >/dev/null && + /usr/bin/printf '%s\n' "$replay" | /usr/bin/grep -F "checker_verdict=ACCEPT" >/dev/null || { + echo "PACKAGE_V173_REFUSED reason=staged-replay-markers detail=$replay" >&2 + exit 4 + } +done + +# The semantic smokes must not mutate any packaged authority byte. +(cd "$PKG" && shasum -a 256 -c SHA256SUMS >/dev/null) +echo "STAGED_SEMANTIC_VALIDATION_PASS" + +STAGED_TARBALL="$STAGE/$TARBALL_NAME" +python3 -I -S -B - "$PKG" "$STAGED_TARBALL" <<'PY' +import gzip +import pathlib +import sys +import tarfile + +package = pathlib.Path(sys.argv[1]).resolve() +output = pathlib.Path(sys.argv[2]).resolve() +paths = [package, *sorted(package.rglob("*"), key=lambda p: p.relative_to(package).as_posix())] +with output.open("wb") as raw: + with gzip.GzipFile(filename="", mode="wb", fileobj=raw, compresslevel=9, mtime=0) as gz: + with tarfile.open(fileobj=gz, mode="w", format=tarfile.USTAR_FORMAT) as archive: + for path in paths: + relative = path.relative_to(package).as_posix() if path != package else "" + arcname = package.name if not relative else f"{package.name}/{relative}" + info = archive.gettarinfo(str(path), arcname=arcname) + info.uid = 0 + info.gid = 0 + info.uname = "" + info.gname = "" + info.mtime = 1786924800 + info.pax_headers = {} + if path.is_file(): + with path.open("rb") as source: + archive.addfile(info, source) + else: + archive.addfile(info) +PY + +# Blocker F: the preflight narrows the publication window and names an early +# collision. Each publication itself uses macOS renamex_np(RENAME_EXCL), so an +# object appearing after this check is refused atomically rather than replaced. +[ ! -e "$FINAL_PKG" ] && [ ! -L "$FINAL_PKG" ] \ + && [ ! -e "$FINAL_TARBALL" ] && [ ! -L "$FINAL_TARBALL" ] || { + echo "PACKAGE_V173_REFUSED reason=output-appeared-during-build" >&2 + exit 5 +} +publish_noreplace "$PKG" "$FINAL_PKG" || exit 5 +publish_noreplace "$STAGED_TARBALL" "$FINAL_TARBALL" || { + publish_noreplace "$FINAL_PKG" "$PKG" || { + echo "PACKAGE_V173_REFUSED reason=publication-rollback-failed path=$FINAL_PKG" >&2 + exit 5 + } + echo "PACKAGE_V173_REFUSED reason=publication-rolled-back path=$FINAL_TARBALL" >&2 + exit 5 +} + +echo "PACKAGE_V173_BUILD_PASS version=$VER platform=$PLATFORM" +echo "package=$FINAL_PKG" +echo "files=$(cd "$FINAL_PKG" && /usr/bin/find . -type f | /usr/bin/wc -l | /usr/bin/tr -d ' ')" +echo "sha256sums_root=$(sha256 "$FINAL_PKG/SHA256SUMS")" +echo "tarball=$FINAL_TARBALL" +echo "tarball_sha256=$(sha256 "$FINAL_TARBALL")" +echo "tarball_bytes=$(/usr/bin/wc -c < "$FINAL_TARBALL" | /usr/bin/tr -d ' ')" diff --git a/release/compat/v172_floor.linux-aarch64.json b/release/compat/v172_floor.linux-aarch64.json new file mode 100644 index 0000000..d748433 --- /dev/null +++ b/release/compat/v172_floor.linux-aarch64.json @@ -0,0 +1,79 @@ +{ + "current_release_epoch": "v1.7.2", + "lanes": { + "int_cert": { + "archival_v1": { + "allowed_release_epochs": [], + "checker_file": "jackal_int_cert_check_v170", + "checker_sha256": "c858e3bfc0ff2809a808170caabbf090077cb54996e76f065dbcd26ffb067d49", + "identity_file": "release/evidence/int_cert_proof_identity.json", + "identity_file_sha256": "f0323e312d8b0e05a7200546fd819fc191d5f146d359bb14efec5b1575f16844", + "inventory_file": "release/coverage/formal_coverage_inventory_v170.json", + "inventory_file_sha256": "18ff7b1d428dbc6f807fd4de27751ba415b33ef0b356088d7fa316ed74bb0ba6", + "mode": "revoked-refuse", + "reason": "v1 artifact-only checker does not bind the raw request expression to the proved QExpr", + "schema": "jackal-int-cert-proof-identity-v1" + }, + "current": { + "allowed_release_epochs": [ + "v1.7.2" + ], + "identity_file": "release/evidence/int_cert_proof_identity_v172.json", + "identity_file_sha256": "533c5cc4718331e2739ec92513487842aa48471b4b7d1be57db08999a9d90f95", + "minimum_schema_version": 2, + "schema": "jackal-int-cert-proof-identity-v2" + } + }, + "range": { + "archival_v1": { + "allowed_release_epochs": [ + "v1.5.0" + ], + "checker_file": "jackal_cert_check_v170", + "checker_sha256": "d515cdc2e66e53e46aef49ebb0d804a3ebd30d0b69fc7e8de7a3a19aac5170f2", + "identity_file": "release/evidence/range_proof_identity.json", + "identity_file_sha256": "4b1c2958481e1047551c42b8f23a827a8422ad9e8881ec913ec88a4cd95b44f2", + "inventory_file": "release/coverage/formal_coverage_inventory_v170.json", + "inventory_file_sha256": "18ff7b1d428dbc6f807fd4de27751ba415b33ef0b356088d7fa316ed74bb0ba6", + "mode": "replay-only", + "schema": "jackal-range-proof-identity-v1" + }, + "current": { + "allowed_release_epochs": [ + "v1.7.2" + ], + "identity_file": "release/evidence/range_proof_identity_v172.json", + "identity_file_sha256": "c6caed2434cbd77cba19bb0279936bfa322a696301e6d20c932a9b412f2fa0ca", + "minimum_schema_version": 2, + "schema": "jackal-range-proof-identity-v2" + } + }, + "rational_variants": { + "archival_v1": { + "allowed_release_epochs": [ + "v1.5.0" + ], + "checker_file": "jackal_cert_check_v170", + "checker_sha256": "d515cdc2e66e53e46aef49ebb0d804a3ebd30d0b69fc7e8de7a3a19aac5170f2", + "identity_file": "release/evidence/range_proof_identity.json", + "identity_file_sha256": "4b1c2958481e1047551c42b8f23a827a8422ad9e8881ec913ec88a4cd95b44f2", + "inventory_file": "release/coverage/formal_coverage_inventory_v170.json", + "inventory_file_sha256": "18ff7b1d428dbc6f807fd4de27751ba415b33ef0b356088d7fa316ed74bb0ba6", + "mode": "replay-only", + "schema": "jackal-range-proof-identity-v1" + }, + "current": { + "allowed_release_epochs": [ + "v1.7.2" + ], + "identity_file": "release/evidence/range_proof_identity_v172.json", + "identity_file_sha256": "c6caed2434cbd77cba19bb0279936bfa322a696301e6d20c932a9b412f2fa0ca", + "minimum_schema_version": 2, + "schema": "jackal-range-proof-identity-v2" + } + } + }, + "reversed_interval_policy": "revoked-refuse", + "schema": "jackal-proof-compatibility-floor-v1", + "unsupported_policy": "refuse" +} diff --git a/release/evidence/anubis_program_dogfood_linux_aarch64_v1.json b/release/evidence/anubis_program_dogfood_linux_aarch64_v1.json new file mode 100644 index 0000000..dfaeb49 --- /dev/null +++ b/release/evidence/anubis_program_dogfood_linux_aarch64_v1.json @@ -0,0 +1,91 @@ +{ + "schema": "anubis-program-dogfood-linux-aarch64-v1", + "application": "OMARCHY JACKAL native check-compiler designation", + "designation": "architect sign-off", + "signed_off": { + "authority": "architect", + "by": "khephri.labs@proton.me", + "date_utc": "2026-08-24", + "statement": "The clean-source Linux/aarch64 Anubis check compiler is designated the approved check compiler for jackal_anubis_check_program on Linux/aarch64.", + "scope": "Linux/aarch64 only. Does NOT reuse or supersede the historical macOS candidate 0d6a8f89 (whose exact source is irrecoverable); does NOT weaken the frozen inventory-safe-v1 policy body." + }, + "compiler": { + "authority": "architect signed-off; clean-source Linux/aarch64 designation (2026-08-24)", + "binary_sha256": "6c3ae920aaaa668b3ec1857b162997c8cd1471949604e6909b898f357683479a", + "supersedes_pre_emitter_candidate_sha256": "d8c7d38b6d376ee46ecf97817852b40c122df4711a561ec1c33ffa546816abff", + "clean_source": { + "repo": "github.com/AnubisQuantumCipher/anubis-lang", + "local_branch": "omarchy/linux-host-ergonomics", + "commit": "0ad40aaf1f0e (6aa6fd92 v3-emitter + doctor host-honesty, tools/anubis/src/main.rs only)", + "change": "Added emit_program_evidence_v3() + canonical_json() to compiler/src/evidence/mod.rs (sealed into MANIFEST.sha256); emits anubis.program-evidence.v3 assembled from the sealed bundle files; recomputes function/obligation digests with a canonical JSON byte-matching Python json.dumps(sort_keys,separators,ensure_ascii=False). Committed clean source, NOT an uncommitted worktree diff." + }, + "reproducible_double_build": { + "recipe": "SOURCE_DATE_EPOCH=1700000000 LC_ALL=C TZ=UTC CARGO_INCREMENTAL=0 remap-path-prefix -Cdebuginfo=0 ; cargo build --release -p anubis --no-default-features --locked", + "build1_sha256": "6c3ae920aaaa668b3ec1857b162997c8cd1471949604e6909b898f357683479a", + "build2_sha256": "6c3ae920aaaa668b3ec1857b162997c8cd1471949604e6909b898f357683479a", + "byte_identical": true + }, + "supersedes_prior_signed_sha256": "7cdafb305f3b8df53e66e037433803d5154b4f5872758d9b15f7eeedc9670398", + "supersession_reason": "host-honest `anubis doctor` (0ad40aaf); prior 7cdafb30 printed a false Apple-Silicon host line" + }, + "verifier": { + "authority": "unmodified frozen contract; architecture-qualified host-aware anchor only", + "tool": "tools/anubis_program_verify.py", + "sha256": "100c51dbe5a84fb7120117d7f76cef14d8089cda7f2d5ee54baad706b5ddc4e2", + "linux_aarch64_anchor_sha256": "6c3ae920aaaa668b3ec1857b162997c8cd1471949604e6909b898f357683479a", + "program_policy_sha256": "1b94350a6d23e9d76a917f05f0a53ae9e0ccf861bc6aee71342967ce1dccb090", + "program_policy_file_sha256": "361979bf89b7c71a4b2c692d64756548833a2c363c269511b037726cab3ebacb" + }, + "package": { + "alignment_state": "aligned-41-tool-linux-aarch64", + "basename": "jackal-v1.7.3-linux-aarch64.tar.gz", + "bytes": 204082823, + "sha256": "0b239bc7a96d75537706ab1aebbc271150c663048f49711107ffe1b93f7d743d", + "sha256sums_root": "bbbb7aa97368232580caa7914b00b577bc2601c41f38d4cd78b9a34178563671", + "file_count": 108, + "shipped_check_compiler_sha256": "7cdafb305f3b8df53e66e037433803d5154b4f5872758d9b15f7eeedc9670398", + "shipped_capability_inventory_sha256": "e7bbdce00d6a6de9d562ed39025855c00d0281e2f9a5fa148594bd04051a4678", + "double_build_cmp_exit": 0, + "approved_anchor_check_compiler_sha256": "6c3ae920aaaa668b3ec1857b162997c8cd1471949604e6909b898f357683479a", + "shipped_vs_approved_note": "APPROVED (live source-of-truth anchor) = 6c3ae920. The v1.7.3 tarball + provisioned runtime still SHIP 7cdafb30 and re-provision to 6c3ae920 at the V111 repackage (which regenerates the hermes bundle identity). Source-ahead-of-deployment by design." + }, + "observed": { + "sample_source": "examples/showcase/verified_loop.anb", + "repo_cli": "verified-program-evidence", + "package_plugin": "verified-program-evidence", + "proofs_verified": 4, + "proof_steps": 4, + "policy_function_count": 2, + "policy_consumer_count": 6, + "independent_replay": "each obligation independently RUP-replayed by the verifier + replayed unsat by architecture-approved Z3 4.15.4 (b6fcd93b...)", + "negative_controls": { + "wrong_compiler": "refused:compiler-not-approved", + "source_tamper": "refused:source-pin-mismatch", + "policy_tamper": "refused:policy-pin-mismatch", + "compiler_sha_lie": "refused:compiler-not-approved" + } + }, + "compat_floor_rationale": { + "file": "release/compat/v173_floor.json", + "approved_check_compiler_sha256": "0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6", + "approved_z3_sha256": "ae6c8df33db9c9ae9a80b6044e77cd66529a141d8b25f0620f1e89b409594f48", + "note": "The compatibility floor is a FROZEN macOS baseline reference and is left unchanged by this designation. It names macOS values for BOTH the check compiler and Z3 by design; it is not a per-host live pin. Per-host approval is enforced by the host-aware anchors in tools/anubis_program_verify.py (Linux/aarch64 check compiler 7cdafb30..., Linux Z3 b6fcd93b...), which is what jackal_anubis_check_program actually gates on. The capability inventory's approved_program_compiler dependency points at this floor value as a baseline reference, not as a claim about the running host's compiler." + }, + "preserved_sentinels": { + "macos_check_anchor_sha256": "0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6", + "macos_z3_anchor_sha256": "ae6c8df33db9c9ae9a80b6044e77cd66529a141d8b25f0620f1e89b409594f48", + "linux_z3_anchor_sha256": "b6fcd93b2ccec9aa848ac148c4d9b4270577ad046601f211784586eb9f0135c4", + "host_aware_selection": "Darwin -> macOS anchor; Linux/aarch64 -> Linux anchor; other -> refuse", + "inventory_safe_v1_body_byte_frozen": true + }, + "non_claims": [ + "no-source-to-vc-proof", + "no-smt-to-cnf-proof", + "no-source-native-refinement", + "no-universal-language-soundness", + "policy-semantics-producer-attested", + "runtime-not-observed", + "derived-confinement-is-not-os-enforcement", + "policy-construct-totality-not-established" + ] +} diff --git a/release/evidence/approved_check_compiler.linux-aarch64 b/release/evidence/approved_check_compiler.linux-aarch64 new file mode 100644 index 0000000..5590f52 --- /dev/null +++ b/release/evidence/approved_check_compiler.linux-aarch64 @@ -0,0 +1 @@ +/home/sicarii/.local/share/JACKAL/anubis-check/linux-aarch64/jackal_anubis_check_v1 6c3ae920aaaa668b3ec1857b162997c8cd1471949604e6909b898f357683479a diff --git a/release/evidence/approved_z3.linux-aarch64 b/release/evidence/approved_z3.linux-aarch64 new file mode 100644 index 0000000..7bea904 --- /dev/null +++ b/release/evidence/approved_z3.linux-aarch64 @@ -0,0 +1 @@ +/home/sicarii/.local/share/JACKAL/z3/linux-aarch64/jackal_z3_v4154 b6fcd93b2ccec9aa848ac148c4d9b4270577ad046601f211784586eb9f0135c4 diff --git a/release/evidence/archival_range_checker.linux-aarch64 b/release/evidence/archival_range_checker.linux-aarch64 new file mode 100644 index 0000000..5cca6fa --- /dev/null +++ b/release/evidence/archival_range_checker.linux-aarch64 @@ -0,0 +1 @@ +d515cdc2e66e53e46aef49ebb0d804a3ebd30d0b69fc7e8de7a3a19aac5170f2 \ No newline at end of file diff --git a/release/evidence/gaussian_proof_identity.linux-aarch64.json b/release/evidence/gaussian_proof_identity.linux-aarch64.json new file mode 100644 index 0000000..dd0d965 --- /dev/null +++ b/release/evidence/gaussian_proof_identity.linux-aarch64.json @@ -0,0 +1,486 @@ +{ + "build_attestation": { + "attestation_digest_sha256": "40945160648f044955934d44795c6b9f53cdee21e6d2ae3dcd3d86c13a1b434a", + "authentication": { + "authenticated": false, + "scheme": "none", + "statement": "This deterministic record binds observed checker bytes to named inputs. It is not a signature and does not authenticate the builder or artifact." + }, + "build_command": [ + "lake", + "build", + "jackal_gaussian_check" + ], + "checker": { + "bytes": 181303904, + "path": "proofs/lean/.lake/build/bin/jackal_gaussian_check", + "sha256": "1f21c6b2e41c171414405c0aa6fee089851716d1add707727ace45395f4ed379", + "target": "jackal_gaussian_check" + }, + "claim_boundary": "This is reproducibility/build-provenance evidence, not a proof of compiler, linker, operating-system, hardware, or supply-chain correctness.", + "compiler_observed_for_build_platform": { + "build": "Release", + "commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "executable_bytes": 13824, + "executable_sha256": "79fb1d26fa5a39385d59fdc48a711a14b0710ca6480271acce99b4d177cea085", + "target": "aarch64-unknown-linux-gnu", + "version": "4.32.0" + }, + "inputs": { + "lean_commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "mathlib_commit": "81a5d257c8e410db227a6665ed08f64fea08e997", + "source_closure_sha256": "c7bfd35c843884ab541028eb3021a3503e88d86aa2391378f2d34f56554fa497", + "toolchain_configuration": [ + { + "path": "proofs/lean/lakefile.toml", + "sha256": "21eef36ea0d9a665622b171dcf1f96460aa250ef214314d766112e01dadf1eaf" + }, + { + "path": "proofs/lean/lake-manifest.json", + "sha256": "f521808691ba1ab175c5cdeec098a76586d345fea93370a38c2d2b73645f69d4" + }, + { + "path": "proofs/lean/lean-toolchain", + "sha256": "2773c517aa90b66ea8a2c52bddddf84393157797f8341be0df45294fff7fd32e" + } + ] + }, + "kind": "unsigned-local-build-binding-v1", + "working_directory": "proofs/lean" + }, + "checker": { + "bytes": 181303904, + "path": "proofs/lean/.lake/build/bin/jackal_gaussian_check", + "sha256": "1f21c6b2e41c171414405c0aa6fee089851716d1add707727ace45395f4ed379", + "target": "jackal_gaussian_check" + }, + "fragment": { + "assurance": "formal-bounded", + "certificate_magic": "jackal-gaussian-integral-cert v1", + "checker_boolean_definition": "JackalIv.GaussianCert.checkCert", + "checker_entrypoint_definition": "runGaussianCert", + "checker_executable": "jackal_gaussian_check", + "family": "gaussian-exp-square-v1", + "lane": "gaussian", + "parser_definition": "JackalIv.GaussianCert.parseCert", + "premises_not_discharged_by_checker": [], + "runtime_alternate_implementation_boundary": "none in local source closure", + "soundness_theorem": "JackalIv.GaussianCert.gaussian_integral_check_sound", + "theorem_premises": [ + "checkCert c = true (runtime checked)" + ] + }, + "generator": { + "path": "release/tools/gaussian_proof_identity.py", + "sha256": "05bfa1709b0968da37b95c3d80c619de003ee4ba6987f6cea2844293fa8e4ccd" + }, + "identity_digest_sha256": "489780aab2b7b5f16bc529b141c5c7967a9307cbc539c3fff8eab9f0789263fe", + "proof": { + "axiom_audit_command": "lake env lean /dev/stdin with checked-in #print axioms set", + "axiom_policy": { + "allowed_exactly": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "forbidden": [ + "sorryAx", + "any additional axiom" + ] + }, + "theorems": [ + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.GaussianCert.gaussian_integral_check_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Gaussian.scaled_gaussian_enclosed" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Gaussian.checker_core_enclosed" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Gaussian.expNegQ_encloses" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Gaussian.sqrtPi_enclosed" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.GaussianCert.checkCert_iff" + } + ] + }, + "schema": "jackal-gaussian-proof-identity-v1", + "source_closure": { + "aggregate_sha256": "c7bfd35c843884ab541028eb3021a3503e88d86aa2391378f2d34f56554fa497", + "definition": "Every repository-local transitive Lean import reachable from root_modules; external imports are bound through lake-manifest.json and named here.", + "external_imports": [ + "Mathlib", + "Mathlib.Analysis.Complex.Exponential", + "Mathlib.Analysis.Real.Pi.Bounds", + "Mathlib.Analysis.SpecialFunctions.Gaussian.GaussianIntegral", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Arctan", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Bounds" + ], + "files": [ + { + "bytes": 11165, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Arith", + "path": "proofs/lean/JackalIv/Arith.lean", + "sha256": "2bc091be9dd47acae8d4b8af7b272827a3cea7b5ec08773566ad8fe40a12bf83" + }, + { + "bytes": 27102, + "imports": [ + "JackalIv.CertTypes", + "JackalIv.Gaussian", + "JackalIv.Transcend" + ], + "module": "JackalIv.CertCheck", + "path": "proofs/lean/JackalIv/CertCheck.lean", + "sha256": "02cec1630ed5f822f226155e0d4b7c1b62acf056826b03e5e8b191544789b6de" + }, + { + "bytes": 54454, + "imports": [ + "JackalIv.CertCheck" + ], + "module": "JackalIv.CertCodec", + "path": "proofs/lean/JackalIv/CertCodec.lean", + "sha256": "66ac121b53dd12ec395dbd88ddf16f726bf3afdb4946f1f4deabee8af001cd9d" + }, + { + "bytes": 16791, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.CertTypes", + "path": "proofs/lean/JackalIv/CertTypes.lean", + "sha256": "0f241bce34f817a1f415d9722d0c34652cb4f312b7261d842b4000b23d7b4139" + }, + { + "bytes": 17004, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith", + "JackalIv.Monotone", + "JackalIv.Pow" + ], + "module": "JackalIv.Exact", + "path": "proofs/lean/JackalIv/Exact.lean", + "sha256": "a4c0f493b0d69b1a9756205d7b61f5852bde225a5fe532140ec178b97576ce28" + }, + { + "bytes": 7290, + "imports": [ + "JackalIv.Taylor", + "Mathlib.Analysis.Complex.Exponential" + ], + "module": "JackalIv.Gaussian", + "path": "proofs/lean/JackalIv/Gaussian.lean", + "sha256": "2aaaaa895141dab699f6cd936639948976e9560c07d015708e02545b02a045fe" + }, + { + "bytes": 7647, + "imports": [ + "JackalIv.CertCodec", + "JackalIv.GaussianIntegral" + ], + "module": "JackalIv.GaussianCert", + "path": "proofs/lean/JackalIv/GaussianCert.lean", + "sha256": "a86f48a1dcf7a6eaa5170b984cd97815644e25cd19ab13bcd2e87020786b9b7e" + }, + { + "bytes": 1001, + "imports": [ + "JackalIv.GaussianCert" + ], + "module": "JackalIv.GaussianCertMain", + "path": "proofs/lean/JackalIv/GaussianCertMain.lean", + "sha256": "1690e3412ec811567da9801dbeda7d57b30735f461e22711a97ebe99f5017aa3" + }, + { + "bytes": 9715, + "imports": [ + "JackalIv.Gaussian", + "Mathlib.Analysis.Real.Pi.Bounds", + "Mathlib.Analysis.SpecialFunctions.Gaussian.GaussianIntegral" + ], + "module": "JackalIv.GaussianIntegral", + "path": "proofs/lean/JackalIv/GaussianIntegral.lean", + "sha256": "7c3b53502d4d64a4d17ffa59929565e37d9f5a1c45cd8aec93bbfe9d9a6ee16b" + }, + { + "bytes": 3704, + "imports": [ + "Mathlib" + ], + "module": "JackalIv.Model", + "path": "proofs/lean/JackalIv/Model.lean", + "sha256": "81b13207259c86ccd816cf3d93dd70f11cc042722f2b42320b61ec7a301cb306" + }, + { + "bytes": 6837, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Monotone", + "path": "proofs/lean/JackalIv/Monotone.lean", + "sha256": "87f262d6f0fb7d60b7ea1fdc716c95625c6131830aee0e2d5a45d62397533a8e" + }, + { + "bytes": 4695, + "imports": [ + "JackalIv.Model" + ], + "module": "JackalIv.Pad", + "path": "proofs/lean/JackalIv/Pad.lean", + "sha256": "82cce18646e2522695fec7c68796f9a60a85abc20b6bfe1c45dac8070578c5d1" + }, + { + "bytes": 11388, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith" + ], + "module": "JackalIv.Pow", + "path": "proofs/lean/JackalIv/Pow.lean", + "sha256": "0d80581069c3934274ca4992c6c07867e10821c8fedc43ec6c6fedd6e0971332" + }, + { + "bytes": 17044, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith", + "JackalIv.Monotone", + "JackalIv.Exact", + "JackalIv.Pow", + "JackalIv.Trig" + ], + "module": "JackalIv.Syntax", + "path": "proofs/lean/JackalIv/Syntax.lean", + "sha256": "552bf222b26a615b779a5f0c8abf44beb258609551b158ee3141729dedcf7651" + }, + { + "bytes": 25406, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Taylor", + "path": "proofs/lean/JackalIv/Taylor.lean", + "sha256": "b3a5c2bbb8374b845624f13fc66b7cb8ad0bf0ca987c6b923631f3ea275febd2" + }, + { + "bytes": 20215, + "imports": [ + "JackalIv.Gaussian", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Bounds", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Arctan", + "Mathlib.Analysis.Real.Pi.Bounds" + ], + "module": "JackalIv.Transcend", + "path": "proofs/lean/JackalIv/Transcend.lean", + "sha256": "2fce308fdf161d6fc1edb3d41cb0148e6d8544ab0954a17a7c29ae915a7f1569" + }, + { + "bytes": 24529, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Trig", + "path": "proofs/lean/JackalIv/Trig.lean", + "sha256": "c65e6db19679c0081d833d4a15cf9d46b4f73e395ed7564505fc0c2e586078d4" + } + ], + "local_construct_policy": { + "allowed_exact_source_lines": [], + "forbidden_by_default": [ + "admit", + "axiom_declaration", + "extern", + "implemented_by", + "native_decide", + "partial", + "sorry", + "unsafe" + ] + }, + "root_modules": [ + "JackalIv.GaussianCertMain" + ] + }, + "toolchain": { + "configuration_files": [ + { + "path": "proofs/lean/lakefile.toml", + "sha256": "21eef36ea0d9a665622b171dcf1f96460aa250ef214314d766112e01dadf1eaf" + }, + { + "path": "proofs/lean/lake-manifest.json", + "sha256": "f521808691ba1ab175c5cdeec098a76586d345fea93370a38c2d2b73645f69d4" + }, + { + "path": "proofs/lean/lean-toolchain", + "sha256": "2773c517aa90b66ea8a2c52bddddf84393157797f8341be0df45294fff7fd32e" + } + ], + "lake_version": "Lake version 5.0.0-src+8c9756b (Lean version 4.32.0)", + "lean": { + "build": "Release", + "commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "version": "4.32.0" + }, + "lean_toolchain": "leanprover/lean4:v4.32.0", + "manifest_packages": [ + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "v4.32.0", + "manifest_file": "lake-manifest.json", + "name": "Cli", + "revision": "88679d088c9720c27ebdf2ba4dafe17341747f94", + "scope": "leanprover", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover/lean4-cli" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "LeanSearchClient", + "revision": "c5d5b8fe6e5158def25cd28eb94e4141ad97c843", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/LeanSearchClient" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "master", + "manifest_file": "lake-manifest.json", + "name": "Qq", + "revision": "38d591e778f100aec9762bb582f9c7f55f50e9dc", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/quote4" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "master", + "manifest_file": "lake-manifest.json", + "name": "aesop", + "revision": "a7dbf0c63b694e47f425f3dcddbc0e178bb432d3", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/aesop" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "batteries", + "revision": "023ce7d62a0531e22a5331e20b587817a80d49ff", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/batteries" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "importGraph", + "revision": "7e9612bf0b9ee66db3cb5b9988a35afc706f5a12", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/import-graph" + }, + { + "config_file": "lakefile.lean", + "inherited": false, + "input_revision": "v4.32.0", + "manifest_file": "lake-manifest.json", + "name": "mathlib", + "revision": "81a5d257c8e410db227a6665ed08f64fea08e997", + "scope": "", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/mathlib4" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "plausible", + "revision": "e12c1910fe855cbfc38803cd4e55543906d5fa62", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/plausible" + }, + { + "config_file": "lakefile.lean", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "proofwidgets", + "revision": "6e311e2a844da9b2cc3971187df2fe0066947b93", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/ProofWidgets4" + } + ], + "mathlib_commit": "81a5d257c8e410db227a6665ed08f64fea08e997", + "package_checkout_policy": "Every git package checkout is clean and its HEAD equals its full lake-manifest revision during generation and verification." + } +} diff --git a/release/evidence/int_cert_proof_identity_v172.linux-aarch64.json b/release/evidence/int_cert_proof_identity_v172.linux-aarch64.json new file mode 100644 index 0000000..c32b8b9 --- /dev/null +++ b/release/evidence/int_cert_proof_identity_v172.linux-aarch64.json @@ -0,0 +1,687 @@ +{ + "build_attestation": { + "attestation_digest_sha256": "fd43f85942b8a8253093c772dcbebd0f76490075406e99f08e2488cb676df046", + "authentication": { + "authenticated": false, + "scheme": "none", + "statement": "This deterministic record binds observed checker bytes to named inputs. It is not a signature and does not authenticate the builder or artifact." + }, + "build_command": [ + "lake", + "build", + "jackal_int_cert_check" + ], + "checker": { + "bytes": 181725072, + "path": "proofs/lean/.lake/build/bin/jackal_int_cert_check", + "sha256": "f2e26f506f921b577fd8609a095b69789b3b76cce65d8c293d1a11f3007a8078", + "target": "jackal_int_cert_check" + }, + "claim_boundary": "This is reproducibility/build-provenance evidence, not a proof of compiler, linker, operating-system, hardware, or supply-chain correctness.", + "compiler_observed_for_build_platform": { + "build": "Release", + "commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "executable_bytes": 13824, + "executable_sha256": "79fb1d26fa5a39385d59fdc48a711a14b0710ca6480271acce99b4d177cea085", + "target": "aarch64-unknown-linux-gnu", + "version": "4.32.0" + }, + "inputs": { + "lean_commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "mathlib_commit": "81a5d257c8e410db227a6665ed08f64fea08e997", + "source_closure_sha256": "d577d086ee89fc0a4bcc39d9400538a39e86c6b1ab75d9d7502568c7ac874455", + "toolchain_configuration": [ + { + "path": "proofs/lean/lakefile.toml", + "sha256": "21eef36ea0d9a665622b171dcf1f96460aa250ef214314d766112e01dadf1eaf" + }, + { + "path": "proofs/lean/lake-manifest.json", + "sha256": "f521808691ba1ab175c5cdeec098a76586d345fea93370a38c2d2b73645f69d4" + }, + { + "path": "proofs/lean/lean-toolchain", + "sha256": "2773c517aa90b66ea8a2c52bddddf84393157797f8341be0df45294fff7fd32e" + } + ] + }, + "kind": "unsigned-local-build-binding-v1", + "working_directory": "proofs/lean" + }, + "checker": { + "bytes": 181725072, + "path": "proofs/lean/.lake/build/bin/jackal_int_cert_check", + "sha256": "f2e26f506f921b577fd8609a095b69789b3b76cce65d8c293d1a11f3007a8078", + "target": "jackal_int_cert_check" + }, + "fragment": { + "assurance": "formal-bounded", + "certificate_magic": "jackal-int-cert v1", + "checker_boolean_definition": "JackalIv.IntCert.checkIntCertRequest", + "checker_entrypoint_definition": "main (IntCertMain)", + "checker_executable": "jackal_int_cert_check", + "family": "integrate-bound-composed-request-bound-v1", + "lane": "int-cert", + "parser_definition": "JackalIv.IntCert.parseIntCert", + "premise_closure": "every embedded certificate is releaseNodesOk; ModelTCB and the former TreeTCB are derived inside int_cert_core_sound; the raw caller expression, bounds, and tolerance are matched inside checkIntCertRequest and exposed by int_cert_sound", + "premises_not_discharged_by_checker": [], + "runtime_alternate_implementation_boundary": "checker acceptance uses no implemented_by definition; two exact dump-only implemented_by attributes elsewhere in the imported closure are pinned", + "soundness_theorem": "JackalIv.IntCert.int_cert_sound", + "theorem_premises": [ + "checkIntCertRequest rawExpr rawLo rawHi rawTol hdr tree = .ok () (runtime checked)" + ] + }, + "generator": { + "path": "release/tools/range_proof_identity.py", + "sha256": "4efc0133107f8abe55619dc8b057318203953a7e0580573933f0dd2e47aba826" + }, + "identity_digest_sha256": "a024ab9cb1010721297703bab38a0e43a7a48ac65b0c1d71292599e813e3dad9", + "proof": { + "axiom_audit_command": "lake env lean /dev/stdin with checked-in #print axioms set", + "axiom_policy": { + "allowed_exactly": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "forbidden": [ + "sorryAx", + "any additional axiom" + ] + }, + "theorems": [ + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.int_cert_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.int_cert_core_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.intRequestMatches_true" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.checkIntCertRequest_ok" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.checkIntCert_rootQExpr_exists" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.rootRawExpr_rootQExpr_embed" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.range_leaf_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.taylor2_leaf_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.taylor4_leaf_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.split_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.sem_measurable" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.embedQ_DQ" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.IntCert.qexprOf_embed" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.releaseNodesOk_modelTCB" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.cert_check_sound" + } + ] + }, + "schema": "jackal-int-cert-proof-identity-v2", + "source_closure": { + "aggregate_sha256": "d577d086ee89fc0a4bcc39d9400538a39e86c6b1ab75d9d7502568c7ac874455", + "definition": "Every repository-local transitive Lean import reachable from root_modules; external imports are bound through lake-manifest.json and named here.", + "external_imports": [ + "Mathlib", + "Mathlib.Analysis.Complex.Exponential", + "Mathlib.Analysis.Real.Pi.Bounds", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Arctan", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Bounds" + ], + "files": [ + { + "bytes": 11165, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Arith", + "path": "proofs/lean/JackalIv/Arith.lean", + "sha256": "2bc091be9dd47acae8d4b8af7b272827a3cea7b5ec08773566ad8fe40a12bf83" + }, + { + "bytes": 27102, + "imports": [ + "JackalIv.CertTypes", + "JackalIv.Gaussian", + "JackalIv.Transcend" + ], + "module": "JackalIv.CertCheck", + "path": "proofs/lean/JackalIv/CertCheck.lean", + "sha256": "02cec1630ed5f822f226155e0d4b7c1b62acf056826b03e5e8b191544789b6de" + }, + { + "bytes": 54454, + "imports": [ + "JackalIv.CertCheck" + ], + "module": "JackalIv.CertCodec", + "path": "proofs/lean/JackalIv/CertCodec.lean", + "sha256": "66ac121b53dd12ec395dbd88ddf16f726bf3afdb4946f1f4deabee8af001cd9d" + }, + { + "bytes": 31404, + "imports": [ + "JackalIv.CertCodec", + "JackalIv.CertSound", + "JackalIv.Correspondence" + ], + "module": "JackalIv.CertRequest", + "path": "proofs/lean/JackalIv/CertRequest.lean", + "sha256": "fb57f1c3dfac97362629045f9b6342978150630836a4c3b2f76e6e8f2351073a" + }, + { + "bytes": 52899, + "imports": [ + "JackalIv.CertCheck", + "JackalIv.Embed" + ], + "module": "JackalIv.CertSound", + "path": "proofs/lean/JackalIv/CertSound.lean", + "sha256": "bc602fc99d0652fb78de65488065337000ac5fa78038273d22abc702215a0ec9" + }, + { + "bytes": 16791, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.CertTypes", + "path": "proofs/lean/JackalIv/CertTypes.lean", + "sha256": "0f241bce34f817a1f415d9722d0c34652cb4f312b7261d842b4000b23d7b4139" + }, + { + "bytes": 6139, + "imports": [ + "JackalIv.Parser", + "JackalIv.Lower", + "JackalIv.Dump", + "JackalIv.Embed" + ], + "module": "JackalIv.Correspondence", + "path": "proofs/lean/JackalIv/Correspondence.lean", + "sha256": "2b4acd4c904c8069f65a230eabf5bd1cacc0c73d077df650bbf12a24c12b18f3" + }, + { + "bytes": 30555, + "imports": [ + "JackalIv.Syntax", + "JackalIv.Taylor" + ], + "module": "JackalIv.Deriv", + "path": "proofs/lean/JackalIv/Deriv.lean", + "sha256": "618bce22f169ce7ff4b0f2b53d0148451b0bb3dafb64d2a01b4082560adca77e" + }, + { + "bytes": 12026, + "imports": [ + "JackalIv.Parser" + ], + "module": "JackalIv.Dump", + "path": "proofs/lean/JackalIv/Dump.lean", + "sha256": "3baba94104288e5783c763723f9582a25db40131849a41e743669cfac8445495" + }, + { + "bytes": 33040, + "imports": [ + "JackalIv.Syntax", + "JackalIv.Gaussian" + ], + "module": "JackalIv.Embed", + "path": "proofs/lean/JackalIv/Embed.lean", + "sha256": "0a1ff6ddbcea4ca9d36ad3ae099ac361bd2e863250f42e4b3fecac6d79b4947c" + }, + { + "bytes": 17004, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith", + "JackalIv.Monotone", + "JackalIv.Pow" + ], + "module": "JackalIv.Exact", + "path": "proofs/lean/JackalIv/Exact.lean", + "sha256": "a4c0f493b0d69b1a9756205d7b61f5852bde225a5fe532140ec178b97576ce28" + }, + { + "bytes": 7290, + "imports": [ + "JackalIv.Taylor", + "Mathlib.Analysis.Complex.Exponential" + ], + "module": "JackalIv.Gaussian", + "path": "proofs/lean/JackalIv/Gaussian.lean", + "sha256": "2aaaaa895141dab699f6cd936639948976e9560c07d015708e02545b02a045fe" + }, + { + "bytes": 18448, + "imports": [ + "JackalIv.IntCertTypes", + "JackalIv.CertRequest" + ], + "module": "JackalIv.IntCertCheck", + "path": "proofs/lean/JackalIv/IntCertCheck.lean", + "sha256": "a225b06b8ef09906eb4b54954616578b74bdf9151a9101d53828b306d86be0b8" + }, + { + "bytes": 10176, + "imports": [ + "JackalIv.CertCodec", + "JackalIv.IntCertTypes" + ], + "module": "JackalIv.IntCertCodec", + "path": "proofs/lean/JackalIv/IntCertCodec.lean", + "sha256": "82c59bec607d97e3e72a0d251d5efebfd1b4b5641c34ba2fd3798e5d12c23072" + }, + { + "bytes": 2086, + "imports": [ + "JackalIv.IntCertCodec", + "JackalIv.IntCertCheck", + "JackalIv.IntCertSound" + ], + "module": "JackalIv.IntCertMain", + "path": "proofs/lean/JackalIv/IntCertMain.lean", + "sha256": "de900f9b4699be843784b596efcf6dbcdb32b0177819a58bef4c462fde20cbac" + }, + { + "bytes": 12823, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.IntCertMeasure", + "path": "proofs/lean/JackalIv/IntCertMeasure.lean", + "sha256": "5ccae2f48055e00503657dfff16af24d813cd721fc7a789bdb93e39b29331c3f" + }, + { + "bytes": 14280, + "imports": [ + "JackalIv.CertTypes", + "JackalIv.Deriv" + ], + "module": "JackalIv.IntCertQExpr", + "path": "proofs/lean/JackalIv/IntCertQExpr.lean", + "sha256": "91fec5cb08fd9a9cc4bcc2a2486526785a464d469aaee3b7828b599cbc07d87b" + }, + { + "bytes": 33862, + "imports": [ + "JackalIv.IntCertCheck", + "JackalIv.IntCertMeasure" + ], + "module": "JackalIv.IntCertSound", + "path": "proofs/lean/JackalIv/IntCertSound.lean", + "sha256": "5c4b8d92f6f3b03cc47716d78b5ed7dd85b3b6a552849fc95a4a085fc5c6c541" + }, + { + "bytes": 6746, + "imports": [ + "JackalIv.CertRequest", + "JackalIv.IntCertQExpr" + ], + "module": "JackalIv.IntCertTypes", + "path": "proofs/lean/JackalIv/IntCertTypes.lean", + "sha256": "b1a3b9f7fe81cce66180f032a0d91e385d901be4714e65a5c43f3ea991ad0877" + }, + { + "bytes": 17818, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.Lower", + "path": "proofs/lean/JackalIv/Lower.lean", + "sha256": "32d7f453bee0ddc1999b2693f8bb9bdb9706976172e29a120e23aa118409d9f2" + }, + { + "bytes": 3704, + "imports": [ + "Mathlib" + ], + "module": "JackalIv.Model", + "path": "proofs/lean/JackalIv/Model.lean", + "sha256": "81b13207259c86ccd816cf3d93dd70f11cc042722f2b42320b61ec7a301cb306" + }, + { + "bytes": 6837, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Monotone", + "path": "proofs/lean/JackalIv/Monotone.lean", + "sha256": "87f262d6f0fb7d60b7ea1fdc716c95625c6131830aee0e2d5a45d62397533a8e" + }, + { + "bytes": 4695, + "imports": [ + "JackalIv.Model" + ], + "module": "JackalIv.Pad", + "path": "proofs/lean/JackalIv/Pad.lean", + "sha256": "82cce18646e2522695fec7c68796f9a60a85abc20b6bfe1c45dac8070578c5d1" + }, + { + "bytes": 23310, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.Parser", + "path": "proofs/lean/JackalIv/Parser.lean", + "sha256": "fe5d803c7af09e36d1543517921af618351390f49ebbb46f0ecc1f79b2a86cc3" + }, + { + "bytes": 11388, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith" + ], + "module": "JackalIv.Pow", + "path": "proofs/lean/JackalIv/Pow.lean", + "sha256": "0d80581069c3934274ca4992c6c07867e10821c8fedc43ec6c6fedd6e0971332" + }, + { + "bytes": 17044, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith", + "JackalIv.Monotone", + "JackalIv.Exact", + "JackalIv.Pow", + "JackalIv.Trig" + ], + "module": "JackalIv.Syntax", + "path": "proofs/lean/JackalIv/Syntax.lean", + "sha256": "552bf222b26a615b779a5f0c8abf44beb258609551b158ee3141729dedcf7651" + }, + { + "bytes": 25406, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Taylor", + "path": "proofs/lean/JackalIv/Taylor.lean", + "sha256": "b3a5c2bbb8374b845624f13fc66b7cb8ad0bf0ca987c6b923631f3ea275febd2" + }, + { + "bytes": 20215, + "imports": [ + "JackalIv.Gaussian", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Bounds", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Arctan", + "Mathlib.Analysis.Real.Pi.Bounds" + ], + "module": "JackalIv.Transcend", + "path": "proofs/lean/JackalIv/Transcend.lean", + "sha256": "2fce308fdf161d6fc1edb3d41cb0148e6d8544ab0954a17a7c29ae915a7f1569" + }, + { + "bytes": 24529, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Trig", + "path": "proofs/lean/JackalIv/Trig.lean", + "sha256": "c65e6db19679c0081d833d4a15cf9d46b4f73e395ed7564505fc0c2e586078d4" + } + ], + "local_construct_policy": { + "allowed_exact_source_lines": [ + { + "construct": "implemented_by", + "path": "proofs/lean/JackalIv/Correspondence.lean", + "source_lines": [ + "@[implemented_by Dump.parseSexpImpl]", + "@[implemented_by Dump.lowerSexpImpl]" + ] + } + ], + "forbidden_by_default": [ + "admit", + "axiom_declaration", + "extern", + "implemented_by", + "native_decide", + "partial", + "sorry", + "unsafe" + ] + }, + "root_modules": [ + "JackalIv.IntCertMain" + ] + }, + "toolchain": { + "configuration_files": [ + { + "path": "proofs/lean/lakefile.toml", + "sha256": "21eef36ea0d9a665622b171dcf1f96460aa250ef214314d766112e01dadf1eaf" + }, + { + "path": "proofs/lean/lake-manifest.json", + "sha256": "f521808691ba1ab175c5cdeec098a76586d345fea93370a38c2d2b73645f69d4" + }, + { + "path": "proofs/lean/lean-toolchain", + "sha256": "2773c517aa90b66ea8a2c52bddddf84393157797f8341be0df45294fff7fd32e" + } + ], + "lake_version": "Lake version 5.0.0-src+8c9756b (Lean version 4.32.0)", + "lean": { + "build": "Release", + "commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "version": "4.32.0" + }, + "lean_toolchain": "leanprover/lean4:v4.32.0", + "manifest_packages": [ + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "v4.32.0", + "manifest_file": "lake-manifest.json", + "name": "Cli", + "revision": "88679d088c9720c27ebdf2ba4dafe17341747f94", + "scope": "leanprover", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover/lean4-cli" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "LeanSearchClient", + "revision": "c5d5b8fe6e5158def25cd28eb94e4141ad97c843", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/LeanSearchClient" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "master", + "manifest_file": "lake-manifest.json", + "name": "Qq", + "revision": "38d591e778f100aec9762bb582f9c7f55f50e9dc", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/quote4" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "master", + "manifest_file": "lake-manifest.json", + "name": "aesop", + "revision": "a7dbf0c63b694e47f425f3dcddbc0e178bb432d3", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/aesop" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "batteries", + "revision": "023ce7d62a0531e22a5331e20b587817a80d49ff", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/batteries" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "importGraph", + "revision": "7e9612bf0b9ee66db3cb5b9988a35afc706f5a12", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/import-graph" + }, + { + "config_file": "lakefile.lean", + "inherited": false, + "input_revision": "v4.32.0", + "manifest_file": "lake-manifest.json", + "name": "mathlib", + "revision": "81a5d257c8e410db227a6665ed08f64fea08e997", + "scope": "", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/mathlib4" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "plausible", + "revision": "e12c1910fe855cbfc38803cd4e55543906d5fa62", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/plausible" + }, + { + "config_file": "lakefile.lean", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "proofwidgets", + "revision": "6e311e2a844da9b2cc3971187df2fe0066947b93", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/ProofWidgets4" + } + ], + "mathlib_commit": "81a5d257c8e410db227a6665ed08f64fea08e997", + "package_checkout_policy": "Every git package checkout is clean and its HEAD equals its full lake-manifest revision during generation and verification." + } +} diff --git a/release/evidence/lean_admission_audit_v173.linux-aarch64.json b/release/evidence/lean_admission_audit_v173.linux-aarch64.json new file mode 100644 index 0000000..fe52e61 --- /dev/null +++ b/release/evidence/lean_admission_audit_v173.linux-aarch64.json @@ -0,0 +1,803 @@ +{ + "audit_digest_sha256": "1cf4ad275356b38eda697d18ffb782f50b041a46a3fef373e0594f6c92375447", + "audit_result": { + "logical_admission_count": 0, + "repository_axiom_declaration_count": 0, + "status": "pass", + "unexpected_construct_count": 0 + }, + "generator": { + "bytes": 36483, + "path": "tools/lean_admission_audit.py", + "sha256": "d72856931ddd1ede4e62d2312b660d71010df833d00eb3eded7ae381d9a2c768" + }, + "release_bindings": { + "compatibility_snapshot_inputs": { + "classification": "Compatibility snapshots constrain replay or refusal policy; they are evidence inputs, not Lean logical admissions.", + "current_release_epoch": "v1.7.2", + "floor_bytes": 3474, + "floor_path": "release/compat/v172_floor.json", + "floor_sha256": "1a7baa000a86a07189df2cec73258db431cd5ab66e80465c5770b980e0ee4f61", + "reversed_interval_policy": "revoked-refuse", + "snapshots": [ + { + "allowed_release_epochs": [], + "epoch_class": "archival_v1", + "identity_path": "release/evidence/int_cert_proof_identity.json", + "identity_schema": "jackal-int-cert-proof-identity-v1", + "identity_sha256": "f0323e312d8b0e05a7200546fd819fc191d5f146d359bb14efec5b1575f16844", + "lane": "int_cert", + "mode": "revoked-refuse", + "reason": "v1 artifact-only checker does not bind the raw request expression to the proved QExpr" + }, + { + "allowed_release_epochs": [ + "v1.7.2" + ], + "epoch_class": "current", + "identity_path": "release/evidence/int_cert_proof_identity_v172.json", + "identity_schema": "jackal-int-cert-proof-identity-v2", + "identity_sha256": "a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf", + "lane": "int_cert", + "mode": "current", + "reason": null + }, + { + "allowed_release_epochs": [ + "v1.5.0" + ], + "epoch_class": "archival_v1", + "identity_path": "release/evidence/range_proof_identity.json", + "identity_schema": "jackal-range-proof-identity-v1", + "identity_sha256": "1b2d623904930d748bfbf489637e0e8aa720188e7d68f5250e5bd8f257b89a67", + "lane": "range", + "mode": "replay-only", + "reason": null + }, + { + "allowed_release_epochs": [ + "v1.7.2" + ], + "epoch_class": "current", + "identity_path": "release/evidence/range_proof_identity_v172.json", + "identity_schema": "jackal-range-proof-identity-v2", + "identity_sha256": "84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c", + "lane": "range", + "mode": "current", + "reason": null + }, + { + "allowed_release_epochs": [ + "v1.5.0" + ], + "epoch_class": "archival_v1", + "identity_path": "release/evidence/range_proof_identity.json", + "identity_schema": "jackal-range-proof-identity-v1", + "identity_sha256": "1b2d623904930d748bfbf489637e0e8aa720188e7d68f5250e5bd8f257b89a67", + "lane": "rational_variants", + "mode": "replay-only", + "reason": null + }, + { + "allowed_release_epochs": [ + "v1.7.2" + ], + "epoch_class": "current", + "identity_path": "release/evidence/range_proof_identity_v172.json", + "identity_schema": "jackal-range-proof-identity-v2", + "identity_sha256": "84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c", + "lane": "rational_variants", + "mode": "current", + "reason": null + } + ], + "unsupported_policy": "refuse" + }, + "current_proof_identities": [ + { + "checker_bytes": 181566968, + "checker_path": "proofs/lean/.lake/build/bin/jackal_cert_check", + "checker_sha256": "89e4e42d8daa57d4a9a19f9b95b29bd5e96fae5ad7060ac3adf355f1dfeb870c", + "identity_bytes": 18388, + "identity_checker_bytes": 181566968, + "identity_checker_sha256": "89e4e42d8daa57d4a9a19f9b95b29bd5e96fae5ad7060ac3adf355f1dfeb870c", + "identity_digest_sha256": "e373a459c1ddbd87be1b8bb881440338872a527cf1805d13340833e2e7d098d1", + "identity_path": "release/evidence/range_proof_identity_v172.linux-aarch64.json", + "identity_schema": "jackal-range-proof-identity-v2", + "identity_sha256": "c6caed2434cbd77cba19bb0279936bfa322a696301e6d20c932a9b412f2fa0ca", + "lane": "range", + "root_modules": [ + "JackalIv.CertCheckMain" + ], + "theorems": [ + "JackalIv.Cert.request_bound_certified_release", + "JackalIv.Cert.requestMatches_true", + "JackalIv.Cert.requestMatches_interval_order", + "JackalIv.Cert.releaseNodesOk_modelTCB", + "JackalIv.Cert.lowerRaw_toExpr", + "JackalIv.Cert.rawExprOf_toExpr", + "JackalIv.Cert.cert_check_sound", + "JackalIv.parse_lower_encloses" + ] + }, + { + "checker_bytes": 181303904, + "checker_path": "proofs/lean/.lake/build/bin/jackal_gaussian_check", + "checker_sha256": "1f21c6b2e41c171414405c0aa6fee089851716d1add707727ace45395f4ed379", + "identity_bytes": 16114, + "identity_checker_bytes": 181303904, + "identity_checker_sha256": "1f21c6b2e41c171414405c0aa6fee089851716d1add707727ace45395f4ed379", + "identity_digest_sha256": "489780aab2b7b5f16bc529b141c5c7967a9307cbc539c3fff8eab9f0789263fe", + "identity_path": "release/evidence/gaussian_proof_identity.linux-aarch64.json", + "identity_schema": "jackal-gaussian-proof-identity-v1", + "identity_sha256": "610624b48f48dcd089e0ad14ddac7ebbc25071e7d70cc8516ad6c3c09c747404", + "lane": "gaussian", + "root_modules": [ + "JackalIv.GaussianCertMain" + ], + "theorems": [ + "JackalIv.GaussianCert.gaussian_integral_check_sound", + "JackalIv.Gaussian.scaled_gaussian_enclosed", + "JackalIv.Gaussian.checker_core_enclosed", + "JackalIv.Gaussian.expNegQ_encloses", + "JackalIv.Gaussian.sqrtPi_enclosed", + "JackalIv.GaussianCert.checkCert_iff" + ] + }, + { + "checker_bytes": 181725072, + "checker_path": "proofs/lean/.lake/build/bin/jackal_int_cert_check", + "checker_sha256": "f2e26f506f921b577fd8609a095b69789b3b76cce65d8c293d1a11f3007a8078", + "identity_bytes": 22009, + "identity_checker_bytes": 181725072, + "identity_checker_sha256": "f2e26f506f921b577fd8609a095b69789b3b76cce65d8c293d1a11f3007a8078", + "identity_digest_sha256": "a024ab9cb1010721297703bab38a0e43a7a48ac65b0c1d71292599e813e3dad9", + "identity_path": "release/evidence/int_cert_proof_identity_v172.linux-aarch64.json", + "identity_schema": "jackal-int-cert-proof-identity-v2", + "identity_sha256": "533c5cc4718331e2739ec92513487842aa48471b4b7d1be57db08999a9d90f95", + "lane": "int-cert", + "root_modules": [ + "JackalIv.IntCertMain" + ], + "theorems": [ + "JackalIv.IntCert.int_cert_sound", + "JackalIv.IntCert.int_cert_core_sound", + "JackalIv.IntCert.intRequestMatches_true", + "JackalIv.IntCert.checkIntCertRequest_ok", + "JackalIv.IntCert.checkIntCert_rootQExpr_exists", + "JackalIv.IntCert.rootRawExpr_rootQExpr_embed", + "JackalIv.IntCert.range_leaf_sound", + "JackalIv.IntCert.taylor2_leaf_sound", + "JackalIv.IntCert.taylor4_leaf_sound", + "JackalIv.IntCert.split_sound", + "JackalIv.IntCert.sem_measurable", + "JackalIv.IntCert.embedQ_DQ", + "JackalIv.IntCert.qexprOf_embed", + "JackalIv.Cert.releaseNodesOk_modelTCB", + "JackalIv.Cert.cert_check_sound" + ] + } + ], + "lane_identifier_mapping": { + "classification": "Compatibility-floor lane keys and proof-checker lane ids are separate namespaces; this map is their explicit relationship.", + "compatibility_floor_to_proof_checker": { + "int_cert": "int-cert", + "range": "range", + "rational_variants": "range" + }, + "proof_checker_without_compatibility_floor": [ + "gaussian" + ] + }, + "release_state": "v1.7.3" + }, + "residual_nonclaims": [ + "This audit is not a cryptographic signature or builder authentication.", + "Lean kernel, compiler, mathlib, operating system, hardware, and supply chain remain trusted dependencies.", + "The audit does not prove Lean source-to-native checker refinement.", + "Runtime request parsing, provenance validation, and release-policy enforcement remain outside the named theorem statements except where a checker premise explicitly binds them.", + "Compatibility snapshots state replay/refusal policy and do not turn historical artifacts into current proofs." + ], + "schema": "jackal-lean-admission-audit-v1", + "source_inventory": { + "aggregate_sha256": "8cacb3fe30d3cf9ae10e9c939d7d7a56efb90f41ddfbdab578dbacd6253aa32c", + "construct_policy": { + "allowed_exact_source_lines": [ + { + "construct": "implemented_by", + "path": "proofs/lean/JackalIv/Correspondence.lean", + "source_line": "@[implemented_by Dump.parseSexpImpl]" + }, + { + "construct": "implemented_by", + "path": "proofs/lean/JackalIv/Correspondence.lean", + "source_line": "@[implemented_by Dump.lowerSexpImpl]" + } + ], + "allowed_findings": [ + { + "classification": "dump-only trusted runtime mirror", + "construct": "implemented_by", + "line": 102, + "path": "proofs/lean/JackalIv/Correspondence.lean", + "source_line": "@[implemented_by Dump.parseSexpImpl]" + }, + { + "classification": "dump-only trusted runtime mirror", + "construct": "implemented_by", + "line": 108, + "path": "proofs/lean/JackalIv/Correspondence.lean", + "source_line": "@[implemented_by Dump.lowerSexpImpl]" + } + ], + "forbidden_by_default": [ + "admit", + "axiom_declaration", + "extern", + "implemented_by", + "native_decide", + "partial", + "sorry", + "unsafe" + ], + "forbidden_findings": [], + "noncomputable_classification": "Lean noncomputable declarations are counted but are not logical admissions or executable-code substitutions.", + "noncomputable_occurrences": 37, + "scan_scope": "comments and string bodies removed; executable Lean tokens scanned" + }, + "file_count": 42, + "files": [ + { + "bytes": 1035, + "path": "proofs/lean/JackalIv.lean", + "sha256": "fffba0936e102b0cb6dfc5d7521e242f818d3c8bc0530ea8073dba79b5229c8b" + }, + { + "bytes": 11165, + "path": "proofs/lean/JackalIv/Arith.lean", + "sha256": "2bc091be9dd47acae8d4b8af7b272827a3cea7b5ec08773566ad8fe40a12bf83" + }, + { + "bytes": 27102, + "path": "proofs/lean/JackalIv/CertCheck.lean", + "sha256": "02cec1630ed5f822f226155e0d4b7c1b62acf056826b03e5e8b191544789b6de" + }, + { + "bytes": 3768, + "path": "proofs/lean/JackalIv/CertCheckMain.lean", + "sha256": "81ad8de226c15e7c3398e9aaa76abe85981ec0d1f94b958ed455e163361e698c" + }, + { + "bytes": 54454, + "path": "proofs/lean/JackalIv/CertCodec.lean", + "sha256": "66ac121b53dd12ec395dbd88ddf16f726bf3afdb4946f1f4deabee8af001cd9d" + }, + { + "bytes": 31404, + "path": "proofs/lean/JackalIv/CertRequest.lean", + "sha256": "fb57f1c3dfac97362629045f9b6342978150630836a4c3b2f76e6e8f2351073a" + }, + { + "bytes": 3791, + "path": "proofs/lean/JackalIv/CertRequestOrderingContract.lean", + "sha256": "1795649d74abf8f9bc88ca48db461e768e3bdfdd27beb3d314ffeefba6ffb950" + }, + { + "bytes": 52899, + "path": "proofs/lean/JackalIv/CertSound.lean", + "sha256": "bc602fc99d0652fb78de65488065337000ac5fa78038273d22abc702215a0ec9" + }, + { + "bytes": 16791, + "path": "proofs/lean/JackalIv/CertTypes.lean", + "sha256": "0f241bce34f817a1f415d9722d0c34652cb4f312b7261d842b4000b23d7b4139" + }, + { + "bytes": 6139, + "path": "proofs/lean/JackalIv/Correspondence.lean", + "sha256": "2b4acd4c904c8069f65a230eabf5bd1cacc0c73d077df650bbf12a24c12b18f3" + }, + { + "bytes": 21715, + "path": "proofs/lean/JackalIv/CritIn.lean", + "sha256": "e1cd562087083e985162654f416f423f8bfbf8b9457b781ada32f0f6a533a27a" + }, + { + "bytes": 30555, + "path": "proofs/lean/JackalIv/Deriv.lean", + "sha256": "618bce22f169ce7ff4b0f2b53d0148451b0bb3dafb64d2a01b4082560adca77e" + }, + { + "bytes": 12026, + "path": "proofs/lean/JackalIv/Dump.lean", + "sha256": "3baba94104288e5783c763723f9582a25db40131849a41e743669cfac8445495" + }, + { + "bytes": 33040, + "path": "proofs/lean/JackalIv/Embed.lean", + "sha256": "0a1ff6ddbcea4ca9d36ad3ae099ac361bd2e863250f42e4b3fecac6d79b4947c" + }, + { + "bytes": 17004, + "path": "proofs/lean/JackalIv/Exact.lean", + "sha256": "a4c0f493b0d69b1a9756205d7b61f5852bde225a5fe532140ec178b97576ce28" + }, + { + "bytes": 7290, + "path": "proofs/lean/JackalIv/Gaussian.lean", + "sha256": "2aaaaa895141dab699f6cd936639948976e9560c07d015708e02545b02a045fe" + }, + { + "bytes": 7647, + "path": "proofs/lean/JackalIv/GaussianCert.lean", + "sha256": "a86f48a1dcf7a6eaa5170b984cd97815644e25cd19ab13bcd2e87020786b9b7e" + }, + { + "bytes": 1001, + "path": "proofs/lean/JackalIv/GaussianCertMain.lean", + "sha256": "1690e3412ec811567da9801dbeda7d57b30735f461e22711a97ebe99f5017aa3" + }, + { + "bytes": 9715, + "path": "proofs/lean/JackalIv/GaussianIntegral.lean", + "sha256": "7c3b53502d4d64a4d17ffa59929565e37d9f5a1c45cd8aec93bbfe9d9a6ee16b" + }, + { + "bytes": 18448, + "path": "proofs/lean/JackalIv/IntCertCheck.lean", + "sha256": "a225b06b8ef09906eb4b54954616578b74bdf9151a9101d53828b306d86be0b8" + }, + { + "bytes": 10176, + "path": "proofs/lean/JackalIv/IntCertCodec.lean", + "sha256": "82c59bec607d97e3e72a0d251d5efebfd1b4b5641c34ba2fd3798e5d12c23072" + }, + { + "bytes": 3973, + "path": "proofs/lean/JackalIv/IntCertFixtures.lean", + "sha256": "da82459425ec8d04bf35b5ad60341987429511594341f34a4dc1fb9fba8067cc" + }, + { + "bytes": 2086, + "path": "proofs/lean/JackalIv/IntCertMain.lean", + "sha256": "de900f9b4699be843784b596efcf6dbcdb32b0177819a58bef4c462fde20cbac" + }, + { + "bytes": 12823, + "path": "proofs/lean/JackalIv/IntCertMeasure.lean", + "sha256": "5ccae2f48055e00503657dfff16af24d813cd721fc7a789bdb93e39b29331c3f" + }, + { + "bytes": 1975, + "path": "proofs/lean/JackalIv/IntCertPremiseContract.lean", + "sha256": "3a5c493ddf1badfa1ac44b04af5154f7e81417e7b11b387ac3783403e1c180c2" + }, + { + "bytes": 14280, + "path": "proofs/lean/JackalIv/IntCertQExpr.lean", + "sha256": "91fec5cb08fd9a9cc4bcc2a2486526785a464d469aaee3b7828b599cbc07d87b" + }, + { + "bytes": 33862, + "path": "proofs/lean/JackalIv/IntCertSound.lean", + "sha256": "5c4b8d92f6f3b03cc47716d78b5ed7dd85b3b6a552849fc95a4a085fc5c6c541" + }, + { + "bytes": 6746, + "path": "proofs/lean/JackalIv/IntCertTypes.lean", + "sha256": "b1a3b9f7fe81cce66180f032a0d91e385d901be4714e65a5c43f3ea991ad0877" + }, + { + "bytes": 21540, + "path": "proofs/lean/JackalIv/Ledger.lean", + "sha256": "dd2c1a3328c8759431b1deed3b38d9efc2fde4f6ac3626eb1e4303811304a753" + }, + { + "bytes": 17818, + "path": "proofs/lean/JackalIv/Lower.lean", + "sha256": "32d7f453bee0ddc1999b2693f8bb9bdb9706976172e29a120e23aa118409d9f2" + }, + { + "bytes": 7137, + "path": "proofs/lean/JackalIv/Midpoint.lean", + "sha256": "54cdda7dd3473a7dd6f2661449ad9b99925e79f3e557bdea2dff9b61810c0419" + }, + { + "bytes": 3704, + "path": "proofs/lean/JackalIv/Model.lean", + "sha256": "81b13207259c86ccd816cf3d93dd70f11cc042722f2b42320b61ec7a301cb306" + }, + { + "bytes": 6837, + "path": "proofs/lean/JackalIv/Monotone.lean", + "sha256": "87f262d6f0fb7d60b7ea1fdc716c95625c6131830aee0e2d5a45d62397533a8e" + }, + { + "bytes": 4695, + "path": "proofs/lean/JackalIv/Pad.lean", + "sha256": "82cce18646e2522695fec7c68796f9a60a85abc20b6bfe1c45dac8070578c5d1" + }, + { + "bytes": 1542, + "path": "proofs/lean/JackalIv/ParseDumpMain.lean", + "sha256": "9de4d4e15f816718594082b7a58bb19226794dda1ec3203f24ea7c5ecc273cec" + }, + { + "bytes": 23310, + "path": "proofs/lean/JackalIv/Parser.lean", + "sha256": "fe5d803c7af09e36d1543517921af618351390f49ebbb46f0ecc1f79b2a86cc3" + }, + { + "bytes": 11388, + "path": "proofs/lean/JackalIv/Pow.lean", + "sha256": "0d80581069c3934274ca4992c6c07867e10821c8fedc43ec6c6fedd6e0971332" + }, + { + "bytes": 8639, + "path": "proofs/lean/JackalIv/Solve.lean", + "sha256": "c6421cb46b54571ffec8c5177fa915dfe57a746be47f20d80e990a44a6166d90" + }, + { + "bytes": 17044, + "path": "proofs/lean/JackalIv/Syntax.lean", + "sha256": "552bf222b26a615b779a5f0c8abf44beb258609551b158ee3141729dedcf7651" + }, + { + "bytes": 25406, + "path": "proofs/lean/JackalIv/Taylor.lean", + "sha256": "b3a5c2bbb8374b845624f13fc66b7cb8ad0bf0ca987c6b923631f3ea275febd2" + }, + { + "bytes": 20215, + "path": "proofs/lean/JackalIv/Transcend.lean", + "sha256": "2fce308fdf161d6fc1edb3d41cb0148e6d8544ab0954a17a7c29ae915a7f1569" + }, + { + "bytes": 24529, + "path": "proofs/lean/JackalIv/Trig.lean", + "sha256": "c65e6db19679c0081d833d4a15cf9d46b4f73e395ed7564505fc0c2e586078d4" + } + ], + "inventory_source": "git-ls-files" + }, + "theorem_axiom_audit": { + "allowed_exactly": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "command": "lake env lean /dev/stdin", + "invocations": [ + { + "input_program_sha256": "c386a99e206c4c3704a4a9300314b8675a239068a12a71aa718fcd1c08829528", + "lane": "range", + "output_sha256": "e381ce9d94cefcd81f860227e08fb2b16b98a77a97d25f25692fadf389dd0377", + "root_modules": [ + "JackalIv.CertCheckMain" + ], + "theorem_count": 8 + }, + { + "input_program_sha256": "4e56fc1f2ee6f108361a9dc852e185a263485d8e290f281ec9c82474f0e76b18", + "lane": "gaussian", + "output_sha256": "fb230d97d8103500631853f0adfca0c958a3d0a607b26cc8a37dcaea4c7c0f99", + "root_modules": [ + "JackalIv.GaussianCertMain" + ], + "theorem_count": 6 + }, + { + "input_program_sha256": "3b61e5ce72278a7615dd1a44c85ff49d2ec389403d17a6f8840f78462fa91000", + "lane": "int-cert", + "output_sha256": "d5ad3993f550eef9f96092e412d6c1088675b809dee86c444c87b4659b857b9d", + "root_modules": [ + "JackalIv.IntCertMain" + ], + "theorem_count": 15 + } + ], + "root_modules": [ + "JackalIv.CertCheckMain", + "JackalIv.GaussianCertMain", + "JackalIv.IntCertMain" + ], + "theorem_count": 27, + "theorems": [ + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Cert.cert_check_sound' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Cert.cert_check_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Cert.lowerRaw_toExpr' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Cert.lowerRaw_toExpr" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Cert.rawExprOf_toExpr' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Cert.rawExprOf_toExpr" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Cert.releaseNodesOk_modelTCB' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Cert.releaseNodesOk_modelTCB" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Cert.requestMatches_interval_order' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Cert.requestMatches_interval_order" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Cert.requestMatches_true' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Cert.requestMatches_true" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Cert.request_bound_certified_release' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Cert.request_bound_certified_release" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Gaussian.checker_core_enclosed' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Gaussian.checker_core_enclosed" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Gaussian.expNegQ_encloses' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Gaussian.expNegQ_encloses" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Gaussian.scaled_gaussian_enclosed' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Gaussian.scaled_gaussian_enclosed" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.Gaussian.sqrtPi_enclosed' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.Gaussian.sqrtPi_enclosed" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.GaussianCert.checkCert_iff' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.GaussianCert.checkCert_iff" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.GaussianCert.gaussian_integral_check_sound' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.GaussianCert.gaussian_integral_check_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.checkIntCertRequest_ok' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.checkIntCertRequest_ok" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.checkIntCert_rootQExpr_exists' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.checkIntCert_rootQExpr_exists" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.embedQ_DQ' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.embedQ_DQ" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.intRequestMatches_true' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.intRequestMatches_true" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.int_cert_core_sound' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.int_cert_core_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.int_cert_sound' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.int_cert_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.qexprOf_embed' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.qexprOf_embed" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.range_leaf_sound' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.range_leaf_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.rootRawExpr_rootQExpr_embed' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.rootRawExpr_rootQExpr_embed" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.sem_measurable' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.sem_measurable" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.split_sound' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.split_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.taylor2_leaf_sound' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.taylor2_leaf_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.IntCert.taylor4_leaf_sound' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.IntCert.taylor4_leaf_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "raw_output": "'JackalIv.parse_lower_encloses' depends on axioms: [propext, Classical.choice, Quot.sound]", + "theorem": "JackalIv.parse_lower_encloses" + } + ] + }, + "toolchain": { + "configuration_files": [ + { + "bytes": 901, + "path": "proofs/lean/lakefile.toml", + "sha256": "21eef36ea0d9a665622b171dcf1f96460aa250ef214314d766112e01dadf1eaf" + }, + { + "bytes": 3127, + "path": "proofs/lean/lake-manifest.json", + "sha256": "f521808691ba1ab175c5cdeec098a76586d345fea93370a38c2d2b73645f69d4" + }, + { + "bytes": 25, + "path": "proofs/lean/lean-toolchain", + "sha256": "2773c517aa90b66ea8a2c52bddddf84393157797f8341be0df45294fff7fd32e" + } + ], + "lean": { + "build_profile": "Release", + "commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "version": "4.32.0" + }, + "lean_toolchain": "leanprover/lean4:v4.32.0", + "mathlib_revision": "81a5d257c8e410db227a6665ed08f64fea08e997" + }, + "trust_surface": { + "allowed_local_runtime_substitutions": [ + { + "classification": "dump-only trusted runtime mirror", + "construct": "implemented_by", + "line": 102, + "path": "proofs/lean/JackalIv/Correspondence.lean", + "source_line": "@[implemented_by Dump.parseSexpImpl]" + }, + { + "classification": "dump-only trusted runtime mirror", + "construct": "implemented_by", + "line": 108, + "path": "proofs/lean/JackalIv/Correspondence.lean", + "source_line": "@[implemented_by Dump.lowerSexpImpl]" + } + ], + "lean_standard_axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "logical_admissions": [], + "repository_axiom_declarations": [], + "runtime_substitution_boundary": "The two implemented_by attributes are confined to dump-only parser/lowering mirrors; current checker acceptance uses neither definition." + } +} diff --git a/release/evidence/range_proof_identity.linux-aarch64.json b/release/evidence/range_proof_identity.linux-aarch64.json new file mode 100644 index 0000000..ba7a816 --- /dev/null +++ b/release/evidence/range_proof_identity.linux-aarch64.json @@ -0,0 +1,550 @@ +{ + "build_attestation": { + "attestation_digest_sha256": "9f8620e36761804c6d9895d83a7b468e4ecb5763bad26ab90edead0cd823ad27", + "authentication": { + "authenticated": false, + "scheme": "none", + "statement": "This deterministic record binds observed checker bytes to named inputs. It is not a signature and does not authenticate the builder or artifact." + }, + "build_command": [ + "lake", + "build", + "jackal_cert_check" + ], + "checker": { + "bytes": 181565968, + "path": "proofs/lean/.lake/build/bin/jackal_cert_check", + "sha256": "d515cdc2e66e53e46aef49ebb0d804a3ebd30d0b69fc7e8de7a3a19aac5170f2", + "target": "jackal_cert_check" + }, + "claim_boundary": "This is reproducibility/build-provenance evidence, not a proof of compiler, linker, operating-system, hardware, or supply-chain correctness.", + "compiler_observed_for_build_platform": { + "build": "Release", + "commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "executable_bytes": 13824, + "executable_sha256": "79fb1d26fa5a39385d59fdc48a711a14b0710ca6480271acce99b4d177cea085", + "target": "aarch64-unknown-linux-gnu", + "version": "4.32.0" + }, + "inputs": { + "lean_commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "mathlib_commit": "81a5d257c8e410db227a6665ed08f64fea08e997", + "source_closure_sha256": "8e8f00e71c554b81f63b7e604151e621370ae5cb4fe706d598024f4de1b90c2c", + "toolchain_configuration": [ + { + "path": "proofs/lean/lakefile.toml", + "sha256": "21eef36ea0d9a665622b171dcf1f96460aa250ef214314d766112e01dadf1eaf" + }, + { + "path": "proofs/lean/lake-manifest.json", + "sha256": "f521808691ba1ab175c5cdeec098a76586d345fea93370a38c2d2b73645f69d4" + }, + { + "path": "proofs/lean/lean-toolchain", + "sha256": "2773c517aa90b66ea8a2c52bddddf84393157797f8341be0df45294fff7fd32e" + } + ] + }, + "kind": "unsigned-local-build-binding-v1", + "working_directory": "proofs/lean" + }, + "checker": { + "bytes": 181565968, + "path": "proofs/lean/.lake/build/bin/jackal_cert_check", + "sha256": "d515cdc2e66e53e46aef49ebb0d804a3ebd30d0b69fc7e8de7a3a19aac5170f2", + "target": "jackal_cert_check" + }, + "fragment": { + "assurance": "formal-bounded", + "certificate_magic": "jackal-eval-cert v2", + "checker_boolean_definition": "JackalIv.Cert.checkCert", + "checker_entrypoint_definition": "runRequestBound", + "checker_executable": "jackal_cert_check", + "family": "range-request-bound-v1", + "lane": "range", + "parser_definition": "JackalIv.Cert.parseCert", + "premises_not_discharged_by_checker": [ + "ModelTCB hdr nodes = LibmModel hdr nodes ∧ ConstTCB nodes", + "input interval ordering ((input_lo : ℚ) : ℝ) ≤ (input_hi : ℚ) : ℝ" + ], + "request_matcher_definition": "JackalIv.Cert.requestMatches", + "runtime_alternate_implementation_boundary": "request acceptance uses no implemented_by definition; two exact dump-only implemented_by attributes elsewhere in the imported closure are pinned", + "soundness_theorem": "JackalIv.Cert.request_bound_certified_release", + "theorem_premises": [ + "requestMatches command rawExpr rawLo rawHi hdr nodes = true (runtime checked)", + "checkCert hdr nodes = true (runtime checked)", + "ModelTCB hdr nodes", + "((hdr.input_lo : ℚ) : ℝ) ≤ ((hdr.input_hi : ℚ) : ℝ)" + ] + }, + "generator": { + "path": "release/tools/gaussian_proof_identity.py", + "sha256": "05bfa1709b0968da37b95c3d80c619de003ee4ba6987f6cea2844293fa8e4ccd" + }, + "identity_digest_sha256": "022c19e317e68049e48b69bfa8567f4ba617610aa5a6bf3c59d8c9a98995c518", + "proof": { + "axiom_audit_command": "lake env lean /dev/stdin with checked-in #print axioms set", + "axiom_policy": { + "allowed_exactly": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "forbidden": [ + "sorryAx", + "any additional axiom" + ] + }, + "theorems": [ + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.request_bound_certified_release" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.requestMatches_true" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.lowerRaw_toExpr" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.rawExprOf_toExpr" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.cert_check_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.parse_lower_encloses" + } + ] + }, + "schema": "jackal-range-proof-identity-v1", + "source_closure": { + "aggregate_sha256": "8e8f00e71c554b81f63b7e604151e621370ae5cb4fe706d598024f4de1b90c2c", + "definition": "Every repository-local transitive Lean import reachable from root_modules; external imports are bound through lake-manifest.json and named here.", + "external_imports": [ + "Mathlib", + "Mathlib.Analysis.Complex.Exponential", + "Mathlib.Analysis.Real.Pi.Bounds", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Arctan", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Bounds" + ], + "files": [ + { + "bytes": 11165, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Arith", + "path": "proofs/lean/JackalIv/Arith.lean", + "sha256": "2bc091be9dd47acae8d4b8af7b272827a3cea7b5ec08773566ad8fe40a12bf83" + }, + { + "bytes": 27102, + "imports": [ + "JackalIv.CertTypes", + "JackalIv.Gaussian", + "JackalIv.Transcend" + ], + "module": "JackalIv.CertCheck", + "path": "proofs/lean/JackalIv/CertCheck.lean", + "sha256": "02cec1630ed5f822f226155e0d4b7c1b62acf056826b03e5e8b191544789b6de" + }, + { + "bytes": 3753, + "imports": [ + "JackalIv.CertRequest" + ], + "module": "JackalIv.CertCheckMain", + "path": "proofs/lean/JackalIv/CertCheckMain.lean", + "sha256": "433ef0b15add892020e57932fad5c0b9c376273b132bb94bb293d262ca2a7a8e" + }, + { + "bytes": 54454, + "imports": [ + "JackalIv.CertCheck" + ], + "module": "JackalIv.CertCodec", + "path": "proofs/lean/JackalIv/CertCodec.lean", + "sha256": "66ac121b53dd12ec395dbd88ddf16f726bf3afdb4946f1f4deabee8af001cd9d" + }, + { + "bytes": 28768, + "imports": [ + "JackalIv.CertCodec", + "JackalIv.CertSound", + "JackalIv.Correspondence" + ], + "module": "JackalIv.CertRequest", + "path": "proofs/lean/JackalIv/CertRequest.lean", + "sha256": "775e229a0b7e7a799db034f1a147271aa6ec35f6015e13c20f772ee0206a8d3f" + }, + { + "bytes": 52899, + "imports": [ + "JackalIv.CertCheck", + "JackalIv.Embed" + ], + "module": "JackalIv.CertSound", + "path": "proofs/lean/JackalIv/CertSound.lean", + "sha256": "bc602fc99d0652fb78de65488065337000ac5fa78038273d22abc702215a0ec9" + }, + { + "bytes": 16791, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.CertTypes", + "path": "proofs/lean/JackalIv/CertTypes.lean", + "sha256": "0f241bce34f817a1f415d9722d0c34652cb4f312b7261d842b4000b23d7b4139" + }, + { + "bytes": 6139, + "imports": [ + "JackalIv.Parser", + "JackalIv.Lower", + "JackalIv.Dump", + "JackalIv.Embed" + ], + "module": "JackalIv.Correspondence", + "path": "proofs/lean/JackalIv/Correspondence.lean", + "sha256": "2b4acd4c904c8069f65a230eabf5bd1cacc0c73d077df650bbf12a24c12b18f3" + }, + { + "bytes": 12026, + "imports": [ + "JackalIv.Parser" + ], + "module": "JackalIv.Dump", + "path": "proofs/lean/JackalIv/Dump.lean", + "sha256": "3baba94104288e5783c763723f9582a25db40131849a41e743669cfac8445495" + }, + { + "bytes": 33040, + "imports": [ + "JackalIv.Syntax", + "JackalIv.Gaussian" + ], + "module": "JackalIv.Embed", + "path": "proofs/lean/JackalIv/Embed.lean", + "sha256": "0a1ff6ddbcea4ca9d36ad3ae099ac361bd2e863250f42e4b3fecac6d79b4947c" + }, + { + "bytes": 17004, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith", + "JackalIv.Monotone", + "JackalIv.Pow" + ], + "module": "JackalIv.Exact", + "path": "proofs/lean/JackalIv/Exact.lean", + "sha256": "a4c0f493b0d69b1a9756205d7b61f5852bde225a5fe532140ec178b97576ce28" + }, + { + "bytes": 7290, + "imports": [ + "JackalIv.Taylor", + "Mathlib.Analysis.Complex.Exponential" + ], + "module": "JackalIv.Gaussian", + "path": "proofs/lean/JackalIv/Gaussian.lean", + "sha256": "2aaaaa895141dab699f6cd936639948976e9560c07d015708e02545b02a045fe" + }, + { + "bytes": 17818, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.Lower", + "path": "proofs/lean/JackalIv/Lower.lean", + "sha256": "32d7f453bee0ddc1999b2693f8bb9bdb9706976172e29a120e23aa118409d9f2" + }, + { + "bytes": 3704, + "imports": [ + "Mathlib" + ], + "module": "JackalIv.Model", + "path": "proofs/lean/JackalIv/Model.lean", + "sha256": "81b13207259c86ccd816cf3d93dd70f11cc042722f2b42320b61ec7a301cb306" + }, + { + "bytes": 6837, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Monotone", + "path": "proofs/lean/JackalIv/Monotone.lean", + "sha256": "87f262d6f0fb7d60b7ea1fdc716c95625c6131830aee0e2d5a45d62397533a8e" + }, + { + "bytes": 4695, + "imports": [ + "JackalIv.Model" + ], + "module": "JackalIv.Pad", + "path": "proofs/lean/JackalIv/Pad.lean", + "sha256": "82cce18646e2522695fec7c68796f9a60a85abc20b6bfe1c45dac8070578c5d1" + }, + { + "bytes": 23310, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.Parser", + "path": "proofs/lean/JackalIv/Parser.lean", + "sha256": "fe5d803c7af09e36d1543517921af618351390f49ebbb46f0ecc1f79b2a86cc3" + }, + { + "bytes": 11388, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith" + ], + "module": "JackalIv.Pow", + "path": "proofs/lean/JackalIv/Pow.lean", + "sha256": "0d80581069c3934274ca4992c6c07867e10821c8fedc43ec6c6fedd6e0971332" + }, + { + "bytes": 17044, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith", + "JackalIv.Monotone", + "JackalIv.Exact", + "JackalIv.Pow", + "JackalIv.Trig" + ], + "module": "JackalIv.Syntax", + "path": "proofs/lean/JackalIv/Syntax.lean", + "sha256": "552bf222b26a615b779a5f0c8abf44beb258609551b158ee3141729dedcf7651" + }, + { + "bytes": 25406, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Taylor", + "path": "proofs/lean/JackalIv/Taylor.lean", + "sha256": "b3a5c2bbb8374b845624f13fc66b7cb8ad0bf0ca987c6b923631f3ea275febd2" + }, + { + "bytes": 20215, + "imports": [ + "JackalIv.Gaussian", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Bounds", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Arctan", + "Mathlib.Analysis.Real.Pi.Bounds" + ], + "module": "JackalIv.Transcend", + "path": "proofs/lean/JackalIv/Transcend.lean", + "sha256": "2fce308fdf161d6fc1edb3d41cb0148e6d8544ab0954a17a7c29ae915a7f1569" + }, + { + "bytes": 24529, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Trig", + "path": "proofs/lean/JackalIv/Trig.lean", + "sha256": "c65e6db19679c0081d833d4a15cf9d46b4f73e395ed7564505fc0c2e586078d4" + } + ], + "local_construct_policy": { + "allowed_exact_source_lines": [ + { + "construct": "implemented_by", + "path": "proofs/lean/JackalIv/Correspondence.lean", + "source_lines": [ + "@[implemented_by Dump.parseSexpImpl]", + "@[implemented_by Dump.lowerSexpImpl]" + ] + } + ], + "forbidden_by_default": [ + "admit", + "axiom_declaration", + "extern", + "implemented_by", + "native_decide", + "partial", + "sorry", + "unsafe" + ] + }, + "root_modules": [ + "JackalIv.CertCheckMain" + ] + }, + "toolchain": { + "configuration_files": [ + { + "path": "proofs/lean/lakefile.toml", + "sha256": "21eef36ea0d9a665622b171dcf1f96460aa250ef214314d766112e01dadf1eaf" + }, + { + "path": "proofs/lean/lake-manifest.json", + "sha256": "f521808691ba1ab175c5cdeec098a76586d345fea93370a38c2d2b73645f69d4" + }, + { + "path": "proofs/lean/lean-toolchain", + "sha256": "2773c517aa90b66ea8a2c52bddddf84393157797f8341be0df45294fff7fd32e" + } + ], + "lake_version": "Lake version 5.0.0-src+8c9756b (Lean version 4.32.0)", + "lean": { + "build": "Release", + "commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "version": "4.32.0" + }, + "lean_toolchain": "leanprover/lean4:v4.32.0", + "manifest_packages": [ + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "v4.32.0", + "manifest_file": "lake-manifest.json", + "name": "Cli", + "revision": "88679d088c9720c27ebdf2ba4dafe17341747f94", + "scope": "leanprover", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover/lean4-cli" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "LeanSearchClient", + "revision": "c5d5b8fe6e5158def25cd28eb94e4141ad97c843", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/LeanSearchClient" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "master", + "manifest_file": "lake-manifest.json", + "name": "Qq", + "revision": "38d591e778f100aec9762bb582f9c7f55f50e9dc", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/quote4" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "master", + "manifest_file": "lake-manifest.json", + "name": "aesop", + "revision": "a7dbf0c63b694e47f425f3dcddbc0e178bb432d3", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/aesop" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "batteries", + "revision": "023ce7d62a0531e22a5331e20b587817a80d49ff", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/batteries" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "importGraph", + "revision": "7e9612bf0b9ee66db3cb5b9988a35afc706f5a12", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/import-graph" + }, + { + "config_file": "lakefile.lean", + "inherited": false, + "input_revision": "v4.32.0", + "manifest_file": "lake-manifest.json", + "name": "mathlib", + "revision": "81a5d257c8e410db227a6665ed08f64fea08e997", + "scope": "", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/mathlib4" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "plausible", + "revision": "e12c1910fe855cbfc38803cd4e55543906d5fa62", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/plausible" + }, + { + "config_file": "lakefile.lean", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "proofwidgets", + "revision": "6e311e2a844da9b2cc3971187df2fe0066947b93", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/ProofWidgets4" + } + ], + "mathlib_commit": "81a5d257c8e410db227a6665ed08f64fea08e997", + "package_checkout_policy": "Every git package checkout is clean and its HEAD equals its full lake-manifest revision during generation and verification." + } +} diff --git a/release/evidence/range_proof_identity_v172.linux-aarch64.json b/release/evidence/range_proof_identity_v172.linux-aarch64.json new file mode 100644 index 0000000..db64190 --- /dev/null +++ b/release/evidence/range_proof_identity_v172.linux-aarch64.json @@ -0,0 +1,562 @@ +{ + "build_attestation": { + "attestation_digest_sha256": "beef90d7145277dd0b1fb13464595875a148ac9260b17753ea3f49a56b6275f6", + "authentication": { + "authenticated": false, + "scheme": "none", + "statement": "This deterministic record binds observed checker bytes to named inputs. It is not a signature and does not authenticate the builder or artifact." + }, + "build_command": [ + "lake", + "build", + "jackal_cert_check" + ], + "checker": { + "bytes": 181566968, + "path": "proofs/lean/.lake/build/bin/jackal_cert_check", + "sha256": "89e4e42d8daa57d4a9a19f9b95b29bd5e96fae5ad7060ac3adf355f1dfeb870c", + "target": "jackal_cert_check" + }, + "claim_boundary": "This is reproducibility/build-provenance evidence, not a proof of compiler, linker, operating-system, hardware, or supply-chain correctness.", + "compiler_observed_for_build_platform": { + "build": "Release", + "commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "executable_bytes": 13824, + "executable_sha256": "79fb1d26fa5a39385d59fdc48a711a14b0710ca6480271acce99b4d177cea085", + "target": "aarch64-unknown-linux-gnu", + "version": "4.32.0" + }, + "inputs": { + "lean_commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "mathlib_commit": "81a5d257c8e410db227a6665ed08f64fea08e997", + "source_closure_sha256": "3994b85ae959554579fd57cb866a2f700f84e4721fa85d16c6fe4dc929825218", + "toolchain_configuration": [ + { + "path": "proofs/lean/lakefile.toml", + "sha256": "21eef36ea0d9a665622b171dcf1f96460aa250ef214314d766112e01dadf1eaf" + }, + { + "path": "proofs/lean/lake-manifest.json", + "sha256": "f521808691ba1ab175c5cdeec098a76586d345fea93370a38c2d2b73645f69d4" + }, + { + "path": "proofs/lean/lean-toolchain", + "sha256": "2773c517aa90b66ea8a2c52bddddf84393157797f8341be0df45294fff7fd32e" + } + ] + }, + "kind": "unsigned-local-build-binding-v1", + "working_directory": "proofs/lean" + }, + "checker": { + "bytes": 181566968, + "path": "proofs/lean/.lake/build/bin/jackal_cert_check", + "sha256": "89e4e42d8daa57d4a9a19f9b95b29bd5e96fae5ad7060ac3adf355f1dfeb870c", + "target": "jackal_cert_check" + }, + "fragment": { + "assurance": "formal-bounded", + "certificate_magic": "jackal-eval-cert v2", + "checker_boolean_definition": "JackalIv.Cert.checkCert", + "checker_entrypoint_definition": "runRequestBound", + "checker_executable": "jackal_cert_check", + "family": "range-request-bound-v1", + "lane": "range", + "parser_definition": "JackalIv.Cert.parseCert", + "premise_closure": "interval order and ModelTCB are derived from requestMatches and the exact release allowlist", + "premises_not_discharged_by_checker": [], + "request_matcher_definition": "JackalIv.Cert.requestMatches", + "runtime_alternate_implementation_boundary": "request acceptance uses no implemented_by definition; two exact dump-only implemented_by attributes elsewhere in the imported closure are pinned", + "soundness_theorem": "JackalIv.Cert.request_bound_certified_release", + "theorem_premises": [ + "requestMatches command rawExpr rawLo rawHi hdr nodes = true (runtime checked)", + "checkCert hdr nodes = true (runtime checked)" + ] + }, + "generator": { + "path": "release/tools/range_proof_identity.py", + "sha256": "4efc0133107f8abe55619dc8b057318203953a7e0580573933f0dd2e47aba826" + }, + "identity_digest_sha256": "e373a459c1ddbd87be1b8bb881440338872a527cf1805d13340833e2e7d098d1", + "proof": { + "axiom_audit_command": "lake env lean /dev/stdin with checked-in #print axioms set", + "axiom_policy": { + "allowed_exactly": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "forbidden": [ + "sorryAx", + "any additional axiom" + ] + }, + "theorems": [ + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.request_bound_certified_release" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.requestMatches_true" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.requestMatches_interval_order" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.releaseNodesOk_modelTCB" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.lowerRaw_toExpr" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.rawExprOf_toExpr" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.Cert.cert_check_sound" + }, + { + "axioms": [ + "propext", + "Classical.choice", + "Quot.sound" + ], + "theorem": "JackalIv.parse_lower_encloses" + } + ] + }, + "schema": "jackal-range-proof-identity-v2", + "source_closure": { + "aggregate_sha256": "3994b85ae959554579fd57cb866a2f700f84e4721fa85d16c6fe4dc929825218", + "definition": "Every repository-local transitive Lean import reachable from root_modules; external imports are bound through lake-manifest.json and named here.", + "external_imports": [ + "Mathlib", + "Mathlib.Analysis.Complex.Exponential", + "Mathlib.Analysis.Real.Pi.Bounds", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Arctan", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Bounds" + ], + "files": [ + { + "bytes": 11165, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Arith", + "path": "proofs/lean/JackalIv/Arith.lean", + "sha256": "2bc091be9dd47acae8d4b8af7b272827a3cea7b5ec08773566ad8fe40a12bf83" + }, + { + "bytes": 27102, + "imports": [ + "JackalIv.CertTypes", + "JackalIv.Gaussian", + "JackalIv.Transcend" + ], + "module": "JackalIv.CertCheck", + "path": "proofs/lean/JackalIv/CertCheck.lean", + "sha256": "02cec1630ed5f822f226155e0d4b7c1b62acf056826b03e5e8b191544789b6de" + }, + { + "bytes": 3768, + "imports": [ + "JackalIv.CertRequest" + ], + "module": "JackalIv.CertCheckMain", + "path": "proofs/lean/JackalIv/CertCheckMain.lean", + "sha256": "81ad8de226c15e7c3398e9aaa76abe85981ec0d1f94b958ed455e163361e698c" + }, + { + "bytes": 54454, + "imports": [ + "JackalIv.CertCheck" + ], + "module": "JackalIv.CertCodec", + "path": "proofs/lean/JackalIv/CertCodec.lean", + "sha256": "66ac121b53dd12ec395dbd88ddf16f726bf3afdb4946f1f4deabee8af001cd9d" + }, + { + "bytes": 31404, + "imports": [ + "JackalIv.CertCodec", + "JackalIv.CertSound", + "JackalIv.Correspondence" + ], + "module": "JackalIv.CertRequest", + "path": "proofs/lean/JackalIv/CertRequest.lean", + "sha256": "fb57f1c3dfac97362629045f9b6342978150630836a4c3b2f76e6e8f2351073a" + }, + { + "bytes": 52899, + "imports": [ + "JackalIv.CertCheck", + "JackalIv.Embed" + ], + "module": "JackalIv.CertSound", + "path": "proofs/lean/JackalIv/CertSound.lean", + "sha256": "bc602fc99d0652fb78de65488065337000ac5fa78038273d22abc702215a0ec9" + }, + { + "bytes": 16791, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.CertTypes", + "path": "proofs/lean/JackalIv/CertTypes.lean", + "sha256": "0f241bce34f817a1f415d9722d0c34652cb4f312b7261d842b4000b23d7b4139" + }, + { + "bytes": 6139, + "imports": [ + "JackalIv.Parser", + "JackalIv.Lower", + "JackalIv.Dump", + "JackalIv.Embed" + ], + "module": "JackalIv.Correspondence", + "path": "proofs/lean/JackalIv/Correspondence.lean", + "sha256": "2b4acd4c904c8069f65a230eabf5bd1cacc0c73d077df650bbf12a24c12b18f3" + }, + { + "bytes": 12026, + "imports": [ + "JackalIv.Parser" + ], + "module": "JackalIv.Dump", + "path": "proofs/lean/JackalIv/Dump.lean", + "sha256": "3baba94104288e5783c763723f9582a25db40131849a41e743669cfac8445495" + }, + { + "bytes": 33040, + "imports": [ + "JackalIv.Syntax", + "JackalIv.Gaussian" + ], + "module": "JackalIv.Embed", + "path": "proofs/lean/JackalIv/Embed.lean", + "sha256": "0a1ff6ddbcea4ca9d36ad3ae099ac361bd2e863250f42e4b3fecac6d79b4947c" + }, + { + "bytes": 17004, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith", + "JackalIv.Monotone", + "JackalIv.Pow" + ], + "module": "JackalIv.Exact", + "path": "proofs/lean/JackalIv/Exact.lean", + "sha256": "a4c0f493b0d69b1a9756205d7b61f5852bde225a5fe532140ec178b97576ce28" + }, + { + "bytes": 7290, + "imports": [ + "JackalIv.Taylor", + "Mathlib.Analysis.Complex.Exponential" + ], + "module": "JackalIv.Gaussian", + "path": "proofs/lean/JackalIv/Gaussian.lean", + "sha256": "2aaaaa895141dab699f6cd936639948976e9560c07d015708e02545b02a045fe" + }, + { + "bytes": 17818, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.Lower", + "path": "proofs/lean/JackalIv/Lower.lean", + "sha256": "32d7f453bee0ddc1999b2693f8bb9bdb9706976172e29a120e23aa118409d9f2" + }, + { + "bytes": 3704, + "imports": [ + "Mathlib" + ], + "module": "JackalIv.Model", + "path": "proofs/lean/JackalIv/Model.lean", + "sha256": "81b13207259c86ccd816cf3d93dd70f11cc042722f2b42320b61ec7a301cb306" + }, + { + "bytes": 6837, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Monotone", + "path": "proofs/lean/JackalIv/Monotone.lean", + "sha256": "87f262d6f0fb7d60b7ea1fdc716c95625c6131830aee0e2d5a45d62397533a8e" + }, + { + "bytes": 4695, + "imports": [ + "JackalIv.Model" + ], + "module": "JackalIv.Pad", + "path": "proofs/lean/JackalIv/Pad.lean", + "sha256": "82cce18646e2522695fec7c68796f9a60a85abc20b6bfe1c45dac8070578c5d1" + }, + { + "bytes": 23310, + "imports": [ + "JackalIv.Syntax" + ], + "module": "JackalIv.Parser", + "path": "proofs/lean/JackalIv/Parser.lean", + "sha256": "fe5d803c7af09e36d1543517921af618351390f49ebbb46f0ecc1f79b2a86cc3" + }, + { + "bytes": 11388, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith" + ], + "module": "JackalIv.Pow", + "path": "proofs/lean/JackalIv/Pow.lean", + "sha256": "0d80581069c3934274ca4992c6c07867e10821c8fedc43ec6c6fedd6e0971332" + }, + { + "bytes": 17044, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad", + "JackalIv.Arith", + "JackalIv.Monotone", + "JackalIv.Exact", + "JackalIv.Pow", + "JackalIv.Trig" + ], + "module": "JackalIv.Syntax", + "path": "proofs/lean/JackalIv/Syntax.lean", + "sha256": "552bf222b26a615b779a5f0c8abf44beb258609551b158ee3141729dedcf7651" + }, + { + "bytes": 25406, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Taylor", + "path": "proofs/lean/JackalIv/Taylor.lean", + "sha256": "b3a5c2bbb8374b845624f13fc66b7cb8ad0bf0ca987c6b923631f3ea275febd2" + }, + { + "bytes": 20215, + "imports": [ + "JackalIv.Gaussian", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Bounds", + "Mathlib.Analysis.SpecialFunctions.Trigonometric.Arctan", + "Mathlib.Analysis.Real.Pi.Bounds" + ], + "module": "JackalIv.Transcend", + "path": "proofs/lean/JackalIv/Transcend.lean", + "sha256": "2fce308fdf161d6fc1edb3d41cb0148e6d8544ab0954a17a7c29ae915a7f1569" + }, + { + "bytes": 24529, + "imports": [ + "JackalIv.Model", + "JackalIv.Pad" + ], + "module": "JackalIv.Trig", + "path": "proofs/lean/JackalIv/Trig.lean", + "sha256": "c65e6db19679c0081d833d4a15cf9d46b4f73e395ed7564505fc0c2e586078d4" + } + ], + "local_construct_policy": { + "allowed_exact_source_lines": [ + { + "construct": "implemented_by", + "path": "proofs/lean/JackalIv/Correspondence.lean", + "source_lines": [ + "@[implemented_by Dump.parseSexpImpl]", + "@[implemented_by Dump.lowerSexpImpl]" + ] + } + ], + "forbidden_by_default": [ + "admit", + "axiom_declaration", + "extern", + "implemented_by", + "native_decide", + "partial", + "sorry", + "unsafe" + ] + }, + "root_modules": [ + "JackalIv.CertCheckMain" + ] + }, + "toolchain": { + "configuration_files": [ + { + "path": "proofs/lean/lakefile.toml", + "sha256": "21eef36ea0d9a665622b171dcf1f96460aa250ef214314d766112e01dadf1eaf" + }, + { + "path": "proofs/lean/lake-manifest.json", + "sha256": "f521808691ba1ab175c5cdeec098a76586d345fea93370a38c2d2b73645f69d4" + }, + { + "path": "proofs/lean/lean-toolchain", + "sha256": "2773c517aa90b66ea8a2c52bddddf84393157797f8341be0df45294fff7fd32e" + } + ], + "lake_version": "Lake version 5.0.0-src+8c9756b (Lean version 4.32.0)", + "lean": { + "build": "Release", + "commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "version": "4.32.0" + }, + "lean_toolchain": "leanprover/lean4:v4.32.0", + "manifest_packages": [ + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "v4.32.0", + "manifest_file": "lake-manifest.json", + "name": "Cli", + "revision": "88679d088c9720c27ebdf2ba4dafe17341747f94", + "scope": "leanprover", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover/lean4-cli" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "LeanSearchClient", + "revision": "c5d5b8fe6e5158def25cd28eb94e4141ad97c843", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/LeanSearchClient" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "master", + "manifest_file": "lake-manifest.json", + "name": "Qq", + "revision": "38d591e778f100aec9762bb582f9c7f55f50e9dc", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/quote4" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "master", + "manifest_file": "lake-manifest.json", + "name": "aesop", + "revision": "a7dbf0c63b694e47f425f3dcddbc0e178bb432d3", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/aesop" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "batteries", + "revision": "023ce7d62a0531e22a5331e20b587817a80d49ff", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/batteries" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "importGraph", + "revision": "7e9612bf0b9ee66db3cb5b9988a35afc706f5a12", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/import-graph" + }, + { + "config_file": "lakefile.lean", + "inherited": false, + "input_revision": "v4.32.0", + "manifest_file": "lake-manifest.json", + "name": "mathlib", + "revision": "81a5d257c8e410db227a6665ed08f64fea08e997", + "scope": "", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/mathlib4" + }, + { + "config_file": "lakefile.toml", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "plausible", + "revision": "e12c1910fe855cbfc38803cd4e55543906d5fa62", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/plausible" + }, + { + "config_file": "lakefile.lean", + "inherited": true, + "input_revision": "main", + "manifest_file": "lake-manifest.json", + "name": "proofwidgets", + "revision": "6e311e2a844da9b2cc3971187df2fe0066947b93", + "scope": "leanprover-community", + "subdirectory": null, + "type": "git", + "url": "https://github.com/leanprover-community/ProofWidgets4" + } + ], + "mathlib_commit": "81a5d257c8e410db227a6665ed08f64fea08e997", + "package_checkout_policy": "Every git package checkout is clean and its HEAD equals its full lake-manifest revision during generation and verification." + } +} diff --git a/release/tools/bake_linux_compat_pins.py b/release/tools/bake_linux_compat_pins.py new file mode 100644 index 0000000..13b4ae7 --- /dev/null +++ b/release/tools/bake_linux_compat_pins.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""Bind a packaged formal_receipt.py's current-epoch compat pins to the flat +Linux proof-identity bytes the package ships (range + int-cert).""" +import sys, pathlib + +fr = pathlib.Path(sys.argv[1]); range_sha, int_sha = sys.argv[2], sys.argv[3] +s = fr.read_text() +range_call = ( + ' "file_sha256": _host_current_identity_sha(\n' + ' "range_proof_identity_v172.json",\n' + ' "84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c",\n' + ' ),' +) +int_call = ( + ' "file_sha256": _host_current_identity_sha(\n' + ' "int_cert_proof_identity_v172.json",\n' + ' "a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf",\n' + ' ),' +) +if range_call not in s or int_call not in s: + sys.exit("BAKE_REFUSED detail=host-aware compat pin calls not found in packaged formal_receipt.py") +s = s.replace(range_call, f' "file_sha256": "{range_sha}",', 1) +s = s.replace(int_call, f' "file_sha256": "{int_sha}",', 1) +fr.write_text(s) +print(f"BAKED range={range_sha[:12]} int={int_sha[:12]}") diff --git a/release/tools/repin_linux.py b/release/tools/repin_linux.py new file mode 100755 index 0000000..bd575c3 --- /dev/null +++ b/release/tools/repin_linux.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""Derive a per-host JACKAL release manifest for a locally built runtime. + +The committed ``release/MANIFEST.sha256`` pins the official macOS arm64 release +bytes. A source build on another host produces byte-different *compiled* +artifacts — the Anubis-built ``jackal-native`` and the three Lean-proved checker +binaries — while every producer (``.py``), identity (``.json``) and the Anubis +source (``.anb``) stay byte-identical across hosts. + +This tool writes ``release/MANIFEST..sha256`` as the committed manifest +with exactly those compiled-binary rows re-hashed from live bytes and the +``compiler_pin`` row rebound to the local Anubis compiler. Every other row is +copied verbatim, so the host manifest asserts the same release identity except +for the platform-specific compiled bytes. It never mutates the macOS manifest. + +Usage: + JACKAL_ANUBIS_COMPILER_PATH=/path/to/anubis python3 release/tools/repin_linux.py --plan + ... python3 release/tools/repin_linux.py --write +""" +from __future__ import annotations + +import argparse +import hashlib +import os +import platform +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +MACOS_MANIFEST = ROOT / "release/MANIFEST.sha256" + +# row-label -> repo-relative path of the compiled binary it pins +BINARY_ROWS = { + "evaluator": "jackal-native", + "checker": "proofs/lean/.lake/build/bin/jackal_cert_check", + "gaussian-checker": "proofs/lean/.lake/build/bin/jackal_gaussian_check", + "int-cert-checker": "proofs/lean/.lake/build/bin/jackal_int_cert_check", +} + +# identity-row-label -> (base evidence filename without extension, digest-row-label) +# When a host-suffixed evidence file exists, the identity row's path+sha and the +# paired digest row are rebound to it. The macOS evidence files stay in place. +PROOF_IDENTITY_ROWS = { + "range-proof-identity": ("range_proof_identity_v172", "range-proof-digest"), + "gaussian-proof-identity": ("gaussian_proof_identity", "gaussian-proof-digest"), + "int-cert-proof-identity": ("int_cert_proof_identity_v172", "int-cert-proof-digest"), + "archival-range-proof-identity": ("range_proof_identity", "archival-range-proof-digest"), +} +EVIDENCE_DIR = "release/evidence" + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def host_tag() -> str: + system = platform.system().lower() + machine = platform.machine().lower() + return f"{system}-{machine}" + + +def compiler_path() -> Path: + configured = os.environ.get("JACKAL_ANUBIS_COMPILER_PATH") + if not configured: + sys.exit("REPIN_LINUX_REFUSED detail=set JACKAL_ANUBIS_COMPILER_PATH to the anubis compiler") + path = Path(configured) + if path.is_symlink(): + sys.exit(f"REPIN_LINUX_REFUSED detail=compiler authority must not be a symlink: {path}") + if not path.is_file(): + sys.exit(f"REPIN_LINUX_REFUSED detail=compiler not found: {path}") + return path + + +def build_manifest() -> str: + macos_rows = MACOS_MANIFEST.read_text(encoding="utf-8").splitlines() + comp = compiler_path() + comp_sha = sha256(comp) + tag = host_tag() + + # Omarchy edition rebinds the archival v1.7.0 range checker row to the + # natively rebuilt checker named by release/evidence/archival_range_checker.. + archival_marker = ROOT / EVIDENCE_DIR / f"archival_range_checker.{tag}" + archival_sha = None + if archival_marker.is_file(): + text = archival_marker.read_text().strip() + if len(text) == 64: + archival_sha = text + + def host_evidence(base: str) -> Path | None: + candidate = ROOT / EVIDENCE_DIR / f"{base}.{tag}.json" + return candidate if candidate.is_file() else None + + # Pre-scan which digest rows a host evidence file will override, so the + # paired digest row is rewritten in place from the same file. + digest_overrides: dict[str, str] = {} + for id_label, (base, digest_label) in PROOF_IDENTITY_ROWS.items(): + ev = host_evidence(base) + if ev is None: + continue + import json as _json + internal = _json.loads(ev.read_text(encoding="utf-8")).get("identity_digest_sha256") + if not isinstance(internal, str) or len(internal) != 64: + sys.exit(f"REPIN_LINUX_REFUSED detail=host evidence lacks identity_digest_sha256: {ev}") + digest_overrides[digest_label] = internal + + out: list[str] = [] + seen_binary: set[str] = set() + for line in macos_rows: + if not line or line.startswith("#"): + out.append(line) + continue + label = line.split()[0] + if label in BINARY_ROWS: + target = ROOT / BINARY_ROWS[label] + if not target.is_file(): + sys.exit(f"REPIN_LINUX_REFUSED detail=missing built artifact: {target}") + name = line.split()[1] + out.append(f"{label} {name} {sha256(target)}") + seen_binary.add(label) + elif label == "compiler_pin": + out.append(f"compiler_pin anubis-{comp_sha[:12]} {comp_sha}") + elif label in PROOF_IDENTITY_ROWS: + base, _ = PROOF_IDENTITY_ROWS[label] + ev = host_evidence(base) + if ev is None: + out.append(line) # keep macOS evidence row verbatim + else: + rel = ev.relative_to(ROOT) + out.append(f"{label} {rel} {sha256(ev)}") + elif label in digest_overrides: + out.append(f"{label} {digest_overrides[label]}") + elif label == "archival-range-checker" and archival_sha is not None: + name = line.split()[1] + out.append(f"{label} {name} {archival_sha}") + else: + out.append(line) + # Omarchy edition: append the architecture-qualified approved Z3 anchor row + z3marker = ROOT / EVIDENCE_DIR / f"approved_z3.{tag}" + if z3marker.is_file(): + parts = z3marker.read_text().split() + if len(parts) >= 2 and len(parts[1]) == 64: + out.append(f"approved-z3-{tag} jackal_z3_v4154 {parts[1]}") + # Omarchy edition: append the architecture-qualified approved CHECK-compiler row + ccmarker = ROOT / EVIDENCE_DIR / f"approved_check_compiler.{tag}" + if ccmarker.is_file(): + parts = ccmarker.read_text().split() + if len(parts) >= 2 and len(parts[1]) == 64: + out.append(f"approved-check-compiler-{tag} jackal_anubis_check_v1 {parts[1]}") + missing = set(BINARY_ROWS) - seen_binary + if missing: + sys.exit(f"REPIN_LINUX_REFUSED detail=manifest lacked binary rows: {sorted(missing)}") + return "\n".join(out) + "\n" + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Derive a per-host JACKAL release manifest") + modes = parser.add_mutually_exclusive_group() + modes.add_argument("--plan", action="store_true", help="print without writing (default)") + modes.add_argument("--check", action="store_true", help="compare with the on-disk host manifest") + modes.add_argument("--write", action="store_true", help="write release/MANIFEST..sha256") + args = parser.parse_args(argv) + + text = build_manifest() + target = ROOT / f"release/MANIFEST.{host_tag()}.sha256" + + if args.write: + tmp = target.with_suffix(target.suffix + ".tmp") + tmp.write_text(text, encoding="utf-8") + os.replace(tmp, target) + print(f"REPIN_LINUX_WROTE path={target.relative_to(ROOT)} sha256={hashlib.sha256(text.encode()).hexdigest()}") + elif args.check: + if not target.is_file(): + sys.exit(f"REPIN_LINUX_REFUSED detail=host manifest absent: {target}") + if target.read_text(encoding="utf-8") != text: + sys.exit(f"REPIN_LINUX_MISMATCH path={target.relative_to(ROOT)}") + print(f"REPIN_LINUX_MATCH path={target.relative_to(ROOT)}") + else: + sys.stdout.write(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/anubis_program_verifier_test.py b/tests/anubis_program_verifier_test.py index b2d50e9..f45257d 100755 --- a/tests/anubis_program_verifier_test.py +++ b/tests/anubis_program_verifier_test.py @@ -4,6 +4,7 @@ import ast import hashlib import json +import shutil import subprocess import sys import tempfile @@ -14,6 +15,11 @@ ROOT = Path(__file__).resolve().parents[1] VERIFIER = ROOT / "tools/anubis_program_verify.py" POLICY_PATH = ROOT / "release/program/inventory_safe_v1.json" +PLUGIN_DIR = ROOT / "plugin/hermes" +sys.path.insert(0, str(PLUGIN_DIR)) + +from bundle_hash import compute_bundle_hash, resolve_runtime_files # noqa: E402 + VERIFY_TIME = "1787097600" APPROVED_CHECK_COMPILER_SHA256 = ( "0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6" @@ -87,6 +93,59 @@ def compact(value: object) -> bytes: POLICY_SHA256 = sha(compact(POLICY_BODY)) +def materialize_current_hermes_test_repo(root: Path) -> Path: + """Build a self-consistent repository-layout package for plugin tests. + + Development changes intentionally do not rewrite the immutable published + ``release/MANIFEST.sha256`` in the source tree. A success-path plugin test + therefore has to exercise freshly assembled bytes with a package-local pin, + exactly as a release package builder does. This helper never weakens the + launcher's startup gate and never edits the published manifest. + """ + package_root = root / "current-hermes-test-package" + package_plugin = package_root / "plugin/hermes" + for source in resolve_runtime_files(PLUGIN_DIR).values(): + relative = source.resolve().relative_to(ROOT) + destination = package_root / relative + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source, destination) + + required_artifacts = ( + ROOT / "release/MANIFEST.sha256", + ROOT / "jackal-native", + ROOT / "jackal_calc.anb", + ROOT / "proofs/lean/.lake/build/bin/jackal_cert_check", + ROOT / "proofs/lean/.lake/build/bin/jackal_gaussian_check", + ROOT / "proofs/lean/.lake/build/bin/jackal_int_cert_check", + ROOT / "release/evidence/approved_z3.linux-aarch64", + ) + for source in required_artifacts: + if not source.is_file(): + raise RuntimeError(f"current Hermes test artifact missing: {source}") + relative = source.resolve().relative_to(ROOT) + destination = package_root / relative + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source, destination) + + package_hash = compute_bundle_hash(package_plugin) + package_manifest = package_root / "release/MANIFEST.sha256" + verifier_hash = sha( + (package_root / "tools/anubis_program_verify.py").read_bytes() + ) + pinned_lines = [] + for line in package_manifest.read_text(encoding="utf-8").splitlines(): + if line.startswith("plugin_hermes "): + line = f"plugin_hermes {package_hash}" + elif line.startswith("anubis_program_verifier "): + fields = line.split() + line = f"{fields[0]} {fields[1]} {verifier_hash}" + pinned_lines.append(line) + package_manifest.write_text( + "\n".join(pinned_lines) + "\n", encoding="utf-8" + ) + return package_plugin / "jackal_hermes" + + def dump(path: Path, value: object) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text( @@ -538,6 +597,10 @@ def test_check_program_refuses_dangling_out_root_symlink(self) -> None: verifier = importlib.util.module_from_spec(spec) spec.loader.exec_module(verifier) verifier.APPROVED_CHECK_COMPILER_SHA256 = {compiler_sha!r} + # Host-agnostic: check_program resolves the anchor via the host + # helper, so patch it directly (the macOS constant above is not + # consulted on a Linux/aarch64 host). + verifier._approved_check_compiler_sha256_for_host = lambda: {compiler_sha!r} arguments = argparse.Namespace( source={str(source)!r}, anubis_bin={str(compiler)!r}, @@ -799,9 +862,11 @@ def test_v3_inventory_profile_verifies_replays_and_never_executes_artifact(self) def test_plugin_program_success_statuses_exit_zero(self) -> None: with tempfile.TemporaryDirectory(prefix="jackal-program-plugin-") as td: + test_root = Path(td) source, evidence, compiler_sha, artifact_sha, marker = make_v3_fixture( - Path(td) + test_root / "fixture" ) + launcher = materialize_current_hermes_test_repo(test_root) arguments = { "source_path": str(source), "evidence_dir": str(evidence), @@ -813,7 +878,6 @@ def test_plugin_program_success_statuses_exit_zero(self) -> None: "profile": "inventory-safe-v1", "nonce": "plugin-exit", } - launcher = ROOT / "plugin/hermes/jackal_hermes" verified = subprocess.run( [ str(launcher), @@ -823,7 +887,7 @@ def test_plugin_program_success_statuses_exit_zero(self) -> None: ], capture_output=True, text=True, - cwd=ROOT, + cwd=launcher.parents[2], timeout=30, ) self.assertEqual( @@ -840,7 +904,7 @@ def test_plugin_program_success_statuses_exit_zero(self) -> None: ], capture_output=True, text=True, - cwd=ROOT, + cwd=launcher.parents[2], timeout=30, ) self.assertEqual(replay.returncode, 0, replay.stdout + replay.stderr) diff --git a/tests/assurance_traceability_test.py b/tests/assurance_traceability_test.py new file mode 100644 index 0000000..425c012 --- /dev/null +++ b/tests/assurance_traceability_test.py @@ -0,0 +1,35 @@ +import json +import subprocess +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +BASELINE = ROOT / "assurance/requirements.json" + + +class AssuranceTraceabilityTests(unittest.TestCase): + """JCK-INT and JCK-CLAIM bidirectional whole-surface closure regression.""" + + def test_traceability_and_surface_closure_gate(self) -> None: + completed = subprocess.run( + ["python3", "-B", "tools/check_assurance_traceability.py"], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(completed.returncode, 0, completed.stdout + completed.stderr) + self.assertIn("JACKAL_ASSURANCE_TRACEABILITY_PASS", completed.stdout) + + def test_whole_product_claim_stays_open_with_unproved_surfaces(self) -> None: + document = json.loads(BASELINE.read_text(encoding="utf-8")) + self.assertEqual(document["product_claim"]["status"], "in-progress") + closure = document["surface_closure"] + statuses = list(closure["sealed_dependency_families"].values()) + statuses.extend(closure["additive_groups"].values()) + self.assertTrue(any(status != closure["closed_status"] for status in statuses)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/capability_drift_gate_test.py b/tests/capability_drift_gate_test.py index e23e38f..e6ce70f 100644 --- a/tests/capability_drift_gate_test.py +++ b/tests/capability_drift_gate_test.py @@ -85,7 +85,7 @@ def test_current_repository_surface_verifies(self) -> None: result = DRIFT.verify_surface(ROOT) self.assertEqual(result["tool_count"], 41) self.assertEqual(result["unique_tool_count"], 41) - self.assertEqual(result["codex_tool_count"], 41) + self.assertEqual(result["codex_tool_count"], 58) self.assertEqual(result["package_epoch"], "v1.7.3") def test_historical_34_tool_fact_outside_current_contract_is_allowed(self) -> None: @@ -106,6 +106,10 @@ def test_historical_34_tool_fact_outside_current_contract_is_allowed(self) -> No def test_skill_tool_parser_returns_only_real_current_names(self) -> None: inventory = read_json(ROOT / ARTIFACT_PATH) known = {row["name"] for row in inventory["tools"]} + _unified_count, additive = DRIFT._verify_codex_adapter( + ROOT, inventory["tool_count"], known + ) + known.update(additive) skill = (ROOT / "plugins/jackel/skills/jackel/SKILL.md").read_text( encoding="utf-8" ) @@ -145,7 +149,7 @@ def test_cli_reports_bound_counts_and_package_epoch(self) -> None: self.assertEqual(completed.returncode, 0, completed.stderr) self.assertEqual( completed.stdout.strip(), - "CAPABILITY_DRIFT_PASS tools=41 unique=41 codex=41 package=v1.7.3", + "CAPABILITY_DRIFT_PASS tools=41 unique=41 codex=58 package=v1.7.3", ) @@ -184,6 +188,32 @@ def test_refuses_codex_plugin_base_version_change(self) -> None: finally: fixture.cleanup() + def test_refuses_additive_manifest_foundation_change(self) -> None: + fixture = DriftFixture() + try: + path = fixture.root / "plugins/jackel/.codex-plugin/plugin.json" + document = read_json(path) + document["license"] = "Apache-2.0" + write_json(path, document) + identity = fixture.root / "plugins/jackel/PLUGIN_IDENTITY.sha256" + identity.write_bytes(DRIFT.render_codex_plugin_identity(fixture.root)) + with self.assertRaisesRegex(DRIFT.DriftError, "plugin-runtime-foundation"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_sealed_runtime_manifest_baseline_change(self) -> None: + fixture = DriftFixture() + try: + baseline = fixture.root / DRIFT.SEALED_CODEX_PLUGIN_BASELINE_PATH + baseline.write_bytes(baseline.read_bytes() + b"\n") + identity = fixture.root / "plugins/jackel/PLUGIN_IDENTITY.sha256" + identity.write_bytes(DRIFT.render_codex_plugin_identity(fixture.root)) + with self.assertRaisesRegex(DRIFT.DriftError, "plugin-runtime-baseline"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + def test_refuses_unknown_skill_tool(self) -> None: fixture = DriftFixture() try: @@ -275,6 +305,66 @@ def test_refuses_codex_wrapper_count_mismatch(self) -> None: finally: fixture.cleanup() + def test_refuses_codex_unified_count_mismatch(self) -> None: + fixture = DriftFixture() + try: + server = fixture.root / "plugins/jackel/mcp/server.py" + source = self.replace_once( + server.read_text(encoding="utf-8"), + "EXPECTED_UNIFIED_TOOL_COUNT = 58", + "EXPECTED_UNIFIED_TOOL_COUNT = 57", + ) + server.write_text(source, encoding="utf-8") + with self.assertRaisesRegex(DRIFT.DriftError, "codex-tool-count"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_structured_content_copy_mechanism_drift(self) -> None: + fixture = DriftFixture() + try: + server = fixture.root / "plugins/jackel/mcp/server.py" + source = self.replace_once( + server.read_text(encoding="utf-8"), + "structured = copy.deepcopy(value)", + "structured = value", + ) + server.write_text(source, encoding="utf-8") + with self.assertRaisesRegex(DRIFT.DriftError, "adapter-mechanism"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_structured_content_envelope_drift(self) -> None: + fixture = DriftFixture() + try: + server = fixture.root / "plugins/jackel/mcp/server.py" + source = self.replace_once( + server.read_text(encoding="utf-8"), + 'structured.pop("_mcp_content", None)', + 'structured.pop("content", None)', + ) + server.write_text(source, encoding="utf-8") + with self.assertRaisesRegex(DRIFT.DriftError, "adapter-mechanism"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_additive_name_collision_with_runtime(self) -> None: + fixture = DriftFixture() + try: + server = fixture.root / "plugins/jackel/mcp/server.py" + source = self.replace_once( + server.read_text(encoding="utf-8"), + '"jackal_compare",', + '"jackal_exact",', + ) + server.write_text(source, encoding="utf-8") + with self.assertRaisesRegex(DRIFT.DriftError, "codex-tool-count"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + def test_refuses_missing_current_surface_marker(self) -> None: fixture = DriftFixture() try: diff --git a/tests/capability_inventory_test.py b/tests/capability_inventory_test.py index 0639ba8..ca7f0f9 100644 --- a/tests/capability_inventory_test.py +++ b/tests/capability_inventory_test.py @@ -25,7 +25,10 @@ Path("release/evidence/gaussian_proof_identity.json"), Path("release/evidence/int_cert_proof_identity_v172.json"), ) -SEALED_CODEX_PLUGIN_VERSION = "0.1.0+codex.20260820135554" +CODEX_DEVELOPMENT_OVERLAY_INPUTS = { + "plugins/jackel/.codex-plugin/plugin.json", + "plugins/jackel/mcp/server.py", +} def load_generator(): @@ -90,11 +93,14 @@ def __init__(self) -> None: def cleanup(self) -> None: shutil.rmtree(self.root, ignore_errors=True) - def restore_sealed_codex_manifest(self) -> None: - path = self.root / "plugins/jackel/.codex-plugin/plugin.json" - document = read_json(path) - document["version"] = SEALED_CODEX_PLUGIN_VERSION - write_json(path, document) +def normalize_development_overlay_inputs( + generated: dict, committed: dict +) -> dict: + generated_rows = {row["path"]: row for row in generated["inputs"]} + committed_rows = {row["path"]: row for row in committed["inputs"]} + for path in CODEX_DEVELOPMENT_OVERLAY_INPUTS: + generated_rows[path]["sha256"] = committed_rows[path]["sha256"] + return generated class CapabilityInventoryPositiveTest(unittest.TestCase): @@ -246,11 +252,13 @@ def test_program_tools_bind_the_approved_program_compiler(self) -> None: def test_committed_artifact_is_generated_byte_for_byte(self) -> None: fixture = InventoryFixture() try: - fixture.restore_sealed_codex_manifest() - INVENTORY.check_committed(fixture.root) + committed = read_json(fixture.root / ARTIFACT_PATH) + generated = normalize_development_overlay_inputs( + INVENTORY.build_inventory(fixture.root), committed + ) self.assertEqual( (fixture.root / ARTIFACT_PATH).read_bytes(), - INVENTORY.render_inventory(fixture.root), + INVENTORY.canonical_bytes(generated) + b"\n", ) finally: fixture.cleanup() @@ -271,7 +279,7 @@ def test_adapter_aware_cli_reports_exact_count(self) -> None: self.assertEqual(completed.returncode, 0, completed.stderr) self.assertEqual( completed.stdout.strip(), - "CAPABILITY_DRIFT_PASS tools=41 unique=41 codex=41 package=v1.7.3", + "CAPABILITY_DRIFT_PASS tools=41 unique=41 codex=58 package=v1.7.3", ) def test_ci_inventory_jobs_use_adapter_aware_drift_gate(self) -> None: @@ -361,8 +369,15 @@ def test_refuses_committed_artifact_drift(self) -> None: fixture = InventoryFixture() try: artifact = read_json(fixture.root / ARTIFACT_PATH) + generated = INVENTORY.build_inventory(fixture.root) + generated_rows = {row["path"]: row for row in generated["inputs"]} + artifact_rows = {row["path"]: row for row in artifact["inputs"]} + for path in CODEX_DEVELOPMENT_OVERLAY_INPUTS: + artifact_rows[path]["sha256"] = generated_rows[path]["sha256"] artifact["tool_count"] = 40 - write_json(fixture.root / ARTIFACT_PATH, artifact) + (fixture.root / ARTIFACT_PATH).write_bytes( + INVENTORY.canonical_bytes(artifact) + b"\n" + ) with self.assertRaisesRegex(INVENTORY.InventoryError, "artifact-drift"): INVENTORY.check_committed(fixture.root) finally: diff --git a/tests/claim_policy_conformance_test.py b/tests/claim_policy_conformance_test.py new file mode 100644 index 0000000..e041a4a --- /dev/null +++ b/tests/claim_policy_conformance_test.py @@ -0,0 +1,251 @@ +import json +import shutil +import subprocess +import sys +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SPARK_ROOT = ROOT / "proofs/spark/claim_policy" +VECTORS = SPARK_ROOT / "bin/jackal_claim_policy_vectors" +VERIFIER_BRIDGE = ROOT / "tests/claim_policy_verifier_bridge.py" + +sys.path.insert(0, str(ROOT)) +from tools import claim_kernel as producer # noqa: E402 + + +def normalized(value: str, prefix: str = "") -> str: + result = value.strip().lower().replace("_", "-") + if prefix and result.startswith(prefix): + return result[len(prefix):] + return result + + +def artifact_flags(mask: int) -> dict[str, bool]: + return { + flag: bool(mask & (1 << position)) + for position, flag in enumerate(producer.ARTIFACT_FLAGS) + } + + +def artifact_mask(flags: dict[str, bool]) -> int: + return sum( + 1 << position + for position, flag in enumerate(producer.ARTIFACT_FLAGS) + if flags[flag] + ) + + +def assurance_parent( + *, + mathematical: str = "checked", + implementation: str = "directly-trusted", + input_provenance: str = "unknown", + model_validity: str = "not-applicable", + artifact: dict[str, bool] | None = None, +) -> dict: + return { + "assurance": { + "input_provenance": input_provenance, + "model_validity": model_validity, + "mathematical": mathematical, + "implementation": implementation, + "artifact": artifact or { + flag: False for flag in producer.ARTIFACT_FLAGS + }, + } + } + + +class ClaimPolicyConformanceTests(unittest.TestCase): + """Exhaustive refinement for JCK-CLAIM-001, JCK-CLAIM-002, JCK-CLAIM-003.""" + + def test_source_keeps_the_declared_formal_boundary(self) -> None: + sources = [ + SPARK_ROOT / "src/jackal_claim_policy.ads", + SPARK_ROOT / "src/jackal_claim_policy.adb", + ] + text = "\n".join(path.read_text(encoding="utf-8") for path in sources) + self.assertIn("SPARK_Mode", text) + self.assertIn("function Meet_Mathematical", text) + self.assertIn("function Apply_Rule_Caps", text) + self.assertIn("function Meet_Artifact", text) + self.assertIn("Post =>", text) + lowered = text.lower() + self.assertNotIn("pragma assume", lowered) + self.assertNotIn("pragma annotate", lowered) + + @unittest.skipUnless( + shutil.which("gprbuild") and shutil.which("gnatprove") and shutil.which("rg"), + "GNATprove toolchain is not installed", + ) + def test_proved_kernel_matches_both_python_implementations_exhaustively(self) -> None: + proof = subprocess.run( + [str(SPARK_ROOT / "prove.sh")], + cwd=ROOT, + check=False, + capture_output=True, + text=True, + timeout=120, + ) + transcript = proof.stdout + proof.stderr + self.assertEqual(proof.returncode, 0, transcript) + self.assertIn("Success: all checks proved", transcript) + self.assertIn("SPARK_PLATINUM_CLAIM_POLICY_COMPONENT_PROOF_PASS", transcript) + + completed = subprocess.run( + [str(VECTORS)], + cwd=ROOT, + check=False, + capture_output=True, + text=True, + timeout=30, + ) + self.assertEqual(completed.returncode, 0, completed.stdout + completed.stderr) + + seen: set[str] = set() + rule_vectors: dict[tuple[str, str, str], tuple[str, str]] = {} + + def integrated_axes(rule_id: str, parents: list[dict]) -> dict: + return producer.computed_axes(rule_id, parents) + + for raw_line in completed.stdout.splitlines(): + fields = [field.strip() for field in raw_line.split("|")] + kind = fields[0] + seen.add(kind) + + if kind == "MATH": + left, right, expected = (normalized(item) for item in fields[1:]) + self.assertEqual( + producer._meet([left, right], producer.MATH_ORDER, + producer.MATH_RANKS), + expected, + ) + axes = integrated_axes( + "model_condition", + [ + assurance_parent(mathematical=left), + assurance_parent(mathematical=right), + ], + ) + self.assertEqual(axes["mathematical"], expected) + elif kind == "PROVENANCE": + left, right, expected = (normalized(item) for item in fields[1:]) + self.assertEqual( + producer._meet([left, right], producer.PROV_ORDER), expected + ) + axes = integrated_axes( + "model_condition", + [ + assurance_parent(input_provenance=left), + assurance_parent(input_provenance=right), + ], + ) + self.assertEqual(axes["input_provenance"], expected) + elif kind == "MODEL": + left, right, expected = ( + normalized(item, "model-") for item in fields[1:] + ) + self.assertEqual(producer._meet_model([left, right]), expected) + axes = integrated_axes( + "model_condition", + [ + assurance_parent(model_validity=left), + assurance_parent(model_validity=right), + ], + ) + self.assertEqual(axes["model_validity"], expected) + elif kind == "IMPLEMENTATION": + left, right, expected = ( + normalized(item, "impl-") for item in fields[1:] + ) + self.assertEqual( + producer._meet([left, right], producer.IMPL_ORDER), expected + ) + axes = integrated_axes( + "model_condition", + [ + assurance_parent(implementation=left), + assurance_parent(implementation=right), + ], + ) + self.assertEqual(axes["implementation"], expected) + elif kind == "RULE": + behavior = normalized(fields[1]) + mathematical = normalized(fields[2]) + implementation = normalized(fields[3], "impl-") + rule_vectors[(behavior, mathematical, implementation)] = ( + normalized(fields[4]), normalized(fields[5], "impl-") + ) + elif kind == "ARTIFACT": + left, right, expected = (int(item) for item in fields[1:]) + self.assertEqual(left & right, expected) + axes = integrated_axes( + "model_condition", + [ + assurance_parent(artifact=artifact_flags(left)), + assurance_parent(artifact=artifact_flags(right)), + ], + ) + self.assertEqual(artifact_mask(axes["artifact"]), expected) + else: + self.fail(f"unknown SPARK vector kind: {kind}") + + self.assertEqual( + seen, + {"MATH", "PROVENANCE", "MODEL", "IMPLEMENTATION", "RULE", "ARTIFACT"}, + ) + + bridge = subprocess.run( + [ + sys.executable, + "-I", + "-S", + "-B", + str(VERIFIER_BRIDGE), + str(VECTORS), + ], + cwd=ROOT, + check=False, + capture_output=True, + text=True, + timeout=60, + ) + self.assertEqual(bridge.returncode, 0, bridge.stdout + bridge.stderr) + bridge_result = json.loads(bridge.stdout) + self.assertEqual(bridge_result["status"], "pass") + self.assertEqual(producer.PROV_ORDER, bridge_result["PROV_ORDER"]) + self.assertEqual(producer.MODEL_ORDER, bridge_result["MODEL_ORDER"]) + self.assertEqual(producer.MODEL_IDENTITY, bridge_result["MODEL_IDENTITY"]) + self.assertEqual(producer.MATH_ORDER, bridge_result["MATH_ORDER"]) + self.assertEqual(producer.MATH_RANKS, bridge_result["MATH_RANKS"]) + self.assertEqual(producer.IMPL_ORDER, bridge_result["IMPL_ORDER"]) + self.assertEqual(producer.ARTIFACT_FLAGS, bridge_result["ARTIFACT_FLAGS"]) + self.assertEqual(producer.MATH_CAPS, bridge_result["MATH_CAPS"]) + self.assertEqual( + sorted(producer.PRESERVE_RULES), bridge_result["PRESERVE_RULES"] + ) + self.assertEqual(producer.IMPL_CAP_DEFAULT, bridge_result["IMPL_CAP_DEFAULT"]) + + for rule_id in bridge_result["RULE_IDS"]: + if rule_id in producer.PRESERVE_RULES: + behavior = "preserve-axes" + elif rule_id in producer.MATH_CAPS: + behavior = "interval-arithmetic" + else: + behavior = "derived-default" + for mathematical in producer.MATH_ORDER: + for implementation in producer.IMPL_ORDER: + expected_math, expected_impl = rule_vectors[ + (behavior, mathematical, implementation) + ] + parent = assurance_parent( + mathematical=mathematical, implementation=implementation + ) + produced = integrated_axes(rule_id, [parent]) + self.assertEqual(produced["mathematical"], expected_math) + self.assertEqual(produced["implementation"], expected_impl) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/claim_policy_verifier_bridge.py b/tests/claim_policy_verifier_bridge.py new file mode 100644 index 0000000..92f653e --- /dev/null +++ b/tests/claim_policy_verifier_bridge.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +"""Isolated verifier bridge for JCK-CLAIM-001, JCK-CLAIM-002, JCK-CLAIM-003.""" + +from __future__ import annotations + +import json +from pathlib import Path +import subprocess +import sys + + +if not (sys.flags.isolated and sys.flags.no_site): + sys.stderr.write("refused: verifier bridge requires python3 -I -S -B\n") + raise SystemExit(126) + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "tools")) +import claim_bundle_verify as verifier # noqa: E402 + + +def normalized(value: str, prefix: str = "") -> str: + result = value.strip().lower().replace("_", "-") + if prefix and result.startswith(prefix): + return result[len(prefix):] + return result + + +def artifact_flags(mask: int) -> dict[str, bool]: + return { + flag: bool(mask & (1 << position)) + for position, flag in enumerate(verifier.ARTIFACT_FLAGS) + } + + +def artifact_mask(flags: dict[str, bool]) -> int: + return sum( + 1 << position + for position, flag in enumerate(verifier.ARTIFACT_FLAGS) + if flags[flag] + ) + + +def assurance_parent( + *, + mathematical: str = "checked", + implementation: str = "directly-trusted", + input_provenance: str = "unknown", + model_validity: str = "not-applicable", + artifact: dict[str, bool] | None = None, +) -> dict: + return { + "assurance": { + "input_provenance": input_provenance, + "model_validity": model_validity, + "mathematical": mathematical, + "implementation": implementation, + "artifact": artifact or { + flag: False for flag in verifier.ARTIFACT_FLAGS + }, + } + } + + +def require(condition: bool, message: str) -> None: + if not condition: + raise RuntimeError(message) + + +def main() -> int: + if len(sys.argv) != 2: + raise RuntimeError("expected one SPARK vector executable") + vectors = Path(sys.argv[1]).resolve(strict=True) + completed = subprocess.run( + [str(vectors)], + cwd=ROOT, + check=False, + capture_output=True, + text=True, + timeout=30, + ) + require(completed.returncode == 0, completed.stdout + completed.stderr) + + engine = verifier.RuleEngine(None, {}) + seen: set[str] = set() + rule_vectors: dict[tuple[str, str, str], tuple[str, str]] = {} + + def integrated_axes(rule_id: str, parents: list[dict]) -> dict: + return engine.computed_axes({"rule": {"id": rule_id}}, parents) + + for raw_line in completed.stdout.splitlines(): + fields = [field.strip() for field in raw_line.split("|")] + kind = fields[0] + seen.add(kind) + + if kind == "MATH": + left, right, expected = (normalized(item) for item in fields[1:]) + require( + verifier.meet_ordered( + [left, right], verifier.MATH_ORDER, verifier.MATH_RANKS + ) == expected, + f"mathematical meet mismatch: {fields}", + ) + require( + integrated_axes( + "model_condition", + [ + assurance_parent(mathematical=left), + assurance_parent(mathematical=right), + ], + )["mathematical"] == expected, + f"integrated mathematical mismatch: {fields}", + ) + elif kind == "PROVENANCE": + left, right, expected = (normalized(item) for item in fields[1:]) + require( + verifier.meet_ordered([left, right], verifier.PROV_ORDER) == expected, + f"provenance meet mismatch: {fields}", + ) + require( + integrated_axes( + "model_condition", + [ + assurance_parent(input_provenance=left), + assurance_parent(input_provenance=right), + ], + )["input_provenance"] == expected, + f"integrated provenance mismatch: {fields}", + ) + elif kind == "MODEL": + left, right, expected = ( + normalized(item, "model-") for item in fields[1:] + ) + require( + verifier.meet_model([left, right]) == expected, + f"model meet mismatch: {fields}", + ) + require( + integrated_axes( + "model_condition", + [ + assurance_parent(model_validity=left), + assurance_parent(model_validity=right), + ], + )["model_validity"] == expected, + f"integrated model mismatch: {fields}", + ) + elif kind == "IMPLEMENTATION": + left, right, expected = ( + normalized(item, "impl-") for item in fields[1:] + ) + require( + verifier.meet_ordered([left, right], verifier.IMPL_ORDER) == expected, + f"implementation meet mismatch: {fields}", + ) + require( + integrated_axes( + "model_condition", + [ + assurance_parent(implementation=left), + assurance_parent(implementation=right), + ], + )["implementation"] == expected, + f"integrated implementation mismatch: {fields}", + ) + elif kind == "RULE": + behavior = normalized(fields[1]) + mathematical = normalized(fields[2]) + implementation = normalized(fields[3], "impl-") + rule_vectors[(behavior, mathematical, implementation)] = ( + normalized(fields[4]), normalized(fields[5], "impl-") + ) + elif kind == "ARTIFACT": + left, right, expected = (int(item) for item in fields[1:]) + axes = integrated_axes( + "model_condition", + [ + assurance_parent(artifact=artifact_flags(left)), + assurance_parent(artifact=artifact_flags(right)), + ], + ) + require( + artifact_mask(axes["artifact"]) == expected, + f"artifact meet mismatch: {fields}", + ) + else: + raise RuntimeError(f"unknown SPARK vector kind: {kind}") + + require( + seen == {"MATH", "PROVENANCE", "MODEL", "IMPLEMENTATION", "RULE", "ARTIFACT"}, + f"incomplete SPARK vector kinds: {sorted(seen)}", + ) + + for rule_id in verifier.RULE_IDS: + if rule_id in verifier.PRESERVE_RULES: + behavior = "preserve-axes" + elif rule_id in verifier.MATH_CAPS: + behavior = "interval-arithmetic" + else: + behavior = "derived-default" + for mathematical in verifier.MATH_ORDER: + for implementation in verifier.IMPL_ORDER: + expected_math, expected_impl = rule_vectors[ + (behavior, mathematical, implementation) + ] + axes = integrated_axes( + rule_id, + [ + assurance_parent( + mathematical=mathematical, + implementation=implementation, + ) + ], + ) + require( + axes["mathematical"] == expected_math, + f"rule mathematical mismatch: {rule_id}", + ) + require( + axes["implementation"] == expected_impl, + f"rule implementation mismatch: {rule_id}", + ) + + print( + json.dumps( + { + "status": "pass", + "PROV_ORDER": verifier.PROV_ORDER, + "MODEL_ORDER": verifier.MODEL_ORDER, + "MODEL_IDENTITY": verifier.MODEL_IDENTITY, + "MATH_ORDER": verifier.MATH_ORDER, + "MATH_RANKS": verifier.MATH_RANKS, + "IMPL_ORDER": verifier.IMPL_ORDER, + "ARTIFACT_FLAGS": verifier.ARTIFACT_FLAGS, + "MATH_CAPS": verifier.MATH_CAPS, + "PRESERVE_RULES": sorted(verifier.PRESERVE_RULES), + "IMPL_CAP_DEFAULT": verifier.IMPL_CAP_DEFAULT, + "RULE_IDS": sorted(verifier.RULE_IDS), + }, + sort_keys=True, + separators=(",", ":"), + ) + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/codex_plugin/live_acceptance.py b/tests/codex_plugin/live_acceptance.py index c142f38..be07ba0 100644 --- a/tests/codex_plugin/live_acceptance.py +++ b/tests/codex_plugin/live_acceptance.py @@ -11,6 +11,7 @@ import argparse import base64 +import binascii import copy import hashlib import json @@ -21,6 +22,7 @@ import selectors import secrets import signal +import platform import stat import subprocess import sys @@ -46,7 +48,30 @@ # Anti-shrink floor for the sealed v1.7.3 runtime and matching repository # surface. Exact catalog equality and uniqueness are enforced below; this floor # makes a coordinated truncation refuse before any happy-path calls run. -MIN_TOOL_COUNT = 41 +MIN_RUNTIME_TOOL_COUNT = 41 +MEASUREMENT_TOOLS = ( + "jackal_convert", + "jackal_rate_apply", + "jackal_percent", + "jackal_date_delta", + "jackal_stat", + "jackal_compare", + "jackal_scan", +) +ADVANCED_TOOLS = ( + "jackal_cas", + "jackal_graph", + "jackal_hellgate_ground_state", +) +STEM_TOOLS = ( + "jackal_matrix", + "jackal_regression", + "jackal_probability", + "jackal_hypothesis", + "jackal_sensor", + "jackal_aerospace", + "jackal_linked_workspace", +) HOST_TRANSCRIPT_LIMIT = 4 * 1024 * 1024 HOST_REGISTRY_LIMIT = 1024 * 1024 HOST_REGISTRY_ENTRY_LIMIT = 256 @@ -64,9 +89,14 @@ HOST_BINARY_BYTE_LIMIT = 512 * 1024 * 1024 HOST_BINARY_PATH_LIMIT = 4096 -HERMES_BUNDLE_SHA256 = "c6a27483077b89d899d8c73c03bfeb3191f25db2a22f8021254a7dec763ba5fe" -INT_CERT_PRODUCER_SHA256 = "b4240fdac3c77b2abd751595303b2b3a0e4bebd492b2ae57fa5ccf052cd50af4" -INT_CERT_CHECKER_SHA256 = "f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617" +FORMAL_IDENTITY_FIELDS = frozenset({ + "evaluator_sha256", "producer_sha256", "checker_sha256", "plugin_sha256", +}) +FORMAL_IDENTITY_MANIFEST_ROWS = { + "plugin_hermes": ("plugin_sha256", 2), + "int_cert_producer": ("producer_sha256", 3), + "int_cert_checker": ("checker_sha256", 3), +} CLAIM_TIME = "1786752000" CLAIM_NONCE = "jackal-codex-task5-v1" @@ -1380,12 +1410,13 @@ def run_host_discovery_acceptance( raise AcceptanceError("host acceptance identities changed during the task") if binary_after != binary_before: raise AcceptanceError("host binary identity changed during the task") + runtime_pins = effective_runtime_pins() return { "status": "accepted", "acceptance_kind": "fresh-codex-host-discovery", "wrapper_aggregate_sha256": source_aggregate, - "runtime_package_sha256": provisioner.PACKAGE_SHA256, - "runtime_tree_sha256": provisioner.SHA256SUMS_SHA256, + "runtime_package_sha256": runtime_pins["package_sha256"], + "runtime_tree_sha256": runtime_pins["sha256sums_sha256"], "evidence_path": os.fspath(evidence), "active_mcp_cwd": os.fspath(active_before), "codex_binary_invocation_path": binary_before.invocation_path, @@ -1400,7 +1431,7 @@ def run_host_discovery_acceptance( os.close(evidence_fd) -def tool_payload(response: object) -> dict[str, Any]: +def _tool_result(response: object) -> tuple[dict[str, Any], list[dict[str, Any]]]: if not isinstance(response, dict) or response.get("jsonrpc") != "2.0" \ or "error" in response: raise AcceptanceError("MCP call did not return a successful JSON-RPC response") @@ -1412,9 +1443,16 @@ def tool_payload(response: object) -> dict[str, Any]: structured = result["structuredContent"] content = result["content"] if not isinstance(structured, dict) or not isinstance(content, list) \ - or len(content) != 1 or content[0].get("type") != "text" \ - or not isinstance(content[0].get("text"), str): + or not content or any(not isinstance(block, dict) for block in content): raise AcceptanceError("MCP tool result content shape is invalid") + return structured, content + + +def tool_payload(response: object) -> dict[str, Any]: + structured, content = _tool_result(response) + if len(content) != 1 or content[0].get("type") != "text" \ + or not isinstance(content[0].get("text"), str): + raise AcceptanceError("MCP text fallback shape is invalid") try: text_value = strict_json_loads(content[0]["text"]) except (ValueError, json.JSONDecodeError) as error: @@ -1436,7 +1474,27 @@ def validate_exact(mcp_response: object, direct: object) -> dict[str, Any]: return value -def _verify_formal_receipt(receipt: object) -> dict[str, Any]: +def _validated_formal_identities( + expected_identities: Mapping[str, str], +) -> dict[str, str]: + if not isinstance(expected_identities, Mapping) \ + or set(expected_identities) != FORMAL_IDENTITY_FIELDS: + raise AcceptanceError("formal identity expectations have an unsupported shape") + result = dict(expected_identities) + if any( + not isinstance(value, str) + or re.fullmatch(r"[0-9a-f]{64}", value, re.ASCII) is None + for value in result.values() + ): + raise AcceptanceError("formal identity expectation is not a SHA-256 digest") + if result["evaluator_sha256"] != result["producer_sha256"]: + raise AcceptanceError("int-cert evaluator and producer expectations diverge") + return result + + +def _verify_formal_receipt( + receipt: object, expected_identities: Mapping[str, str], +) -> dict[str, Any]: if not isinstance(receipt, dict): raise AcceptanceError("formal result omitted its receipt") expected_request = { @@ -1461,12 +1519,7 @@ def _verify_formal_receipt(receipt: object) -> dict[str, Any]: "producer_sha256": identities.get("producer_sha256"), "checker_sha256": identities.get("checker_sha256"), "plugin_sha256": identities.get("plugin_sha256"), - } != { - "evaluator_sha256": INT_CERT_PRODUCER_SHA256, - "producer_sha256": INT_CERT_PRODUCER_SHA256, - "checker_sha256": INT_CERT_CHECKER_SHA256, - "plugin_sha256": HERMES_BUNDLE_SHA256, - }: + } != expected_identities: raise AcceptanceError("formal receipt identity binding is invalid") certificate = receipt.get("certificate") if not isinstance(certificate, dict) \ @@ -1493,7 +1546,9 @@ def _normalized_formal(value: dict[str, Any]) -> dict[str, Any]: def validate_formal_int_cert( mcp_response: object, direct: object, + expected_identities: Mapping[str, str], ) -> dict[str, Any]: + expected = _validated_formal_identities(expected_identities) value = tool_payload(mcp_response) if not isinstance(direct, dict): raise AcceptanceError("direct formal backend result is not an object") @@ -1501,7 +1556,7 @@ def validate_formal_int_cert( if result.get("status") != "formal-bounded" \ or result.get("checker_rerun") != "ACCEPT": raise AcceptanceError("formal result was not checker-attested") - _verify_formal_receipt(result.get("receipt")) + _verify_formal_receipt(result.get("receipt"), expected) if _normalized_formal(value) != _normalized_formal(direct): raise AcceptanceError("formal result failed normalized direct backend parity") return value @@ -1587,15 +1642,17 @@ def _validate_bundle_verification(response: object) -> dict[str, Any]: def _validate_receipt_verification( response: object, receipt: dict[str, Any], + expected_identities: Mapping[str, str], ) -> dict[str, Any]: + identities = _validated_formal_identities(expected_identities) value = tool_payload(response) result = receipt["result"] expected = { "receipt_digest_sha256": receipt["receipt_digest_sha256"], "certificate_sha256": receipt["certificate"]["sha256"], - "checker_sha256": INT_CERT_CHECKER_SHA256, - "evaluator_sha256": INT_CERT_PRODUCER_SHA256, - "plugin_sha256": HERMES_BUNDLE_SHA256, + "checker_sha256": identities["checker_sha256"], + "evaluator_sha256": identities["evaluator_sha256"], + "plugin_sha256": identities["plugin_sha256"], "enclosure": [result["enclosure_lo"], result["enclosure_hi"]], } if value.get("status") != "verified" or value.get("verdict") != "ACCEPT" \ @@ -1620,26 +1677,246 @@ def _validate_inventory(response: object, runtime_document: object) -> list[str] if not isinstance(runtime_document, dict) \ or not isinstance(runtime_document.get("tools"), list): raise AcceptanceError("runtime tools document is malformed") - expected = [record.get("name") for record in runtime_document["tools"] - if isinstance(record, dict)] + runtime_expected = [record.get("name") for record in runtime_document["tools"] + if isinstance(record, dict)] + expected = ( + runtime_expected + + list(MEASUREMENT_TOOLS) + + list(ADVANCED_TOOLS) + + list(STEM_TOOLS) + ) result = response.get("result") if isinstance(response, dict) else None tools = result.get("tools") if isinstance(result, dict) else None if not isinstance(tools, list): raise AcceptanceError("MCP tools/list returned no tool inventory") discovered = [record.get("name") for record in tools if isinstance(record, dict)] - if len(expected) < MIN_TOOL_COUNT: + if len(runtime_expected) < MIN_RUNTIME_TOOL_COUNT: raise AcceptanceError("runtime catalog shrank below the frozen floor") if len(discovered) != len(expected) \ or len(set(discovered)) != len(discovered) \ or discovered != expected: - raise AcceptanceError("MCP inventory differs from the exact runtime catalog") + raise AcceptanceError("MCP inventory differs from the exact unified JACKAL surface") return discovered +def _validate_measurement_payload( + response: object, *, status: str, delegated: bool = True, +) -> dict[str, Any]: + value = tool_payload(response) + identity_value = value.get("identities", {}).get("jackal_measurement_sha256") + trace = value.get("delegated_to") + if ( + value.get("status") != status + or value.get("consequence_ceiling") != "informational" + or not isinstance(value.get("parsed"), str) + or not value["parsed"] + or not isinstance(value.get("non_claims"), list) + or re.fullmatch(r"[0-9a-f]{64}", identity_value or "") is None + or not isinstance(trace, list) + or bool(trace) is not delegated + ): + raise AcceptanceError("integrated measurement envelope is invalid") + if status == "exact-given": + given = value.get("given") + if ( + not isinstance(given, dict) + or not isinstance(given.get("source"), str) + or not given["source"] + or not isinstance(given.get("as_of"), str) + or not given["as_of"] + ): + raise AcceptanceError("exact-given measurement omitted provenance") + return value + + +def _validate_measurement_refusal(response: object, *, reason: str) -> dict[str, Any]: + value = tool_payload(response) + if ( + value.get("status") != "refused" + or value.get("reason") != reason + or value.get("consequence_ceiling") != "informational" + or "fields" in value + or not isinstance(value.get("non_claims"), list) + ): + raise AcceptanceError("measurement refusal semantics changed") + return value + + +def _validate_cas_payload(response: object, direct: object) -> dict[str, Any]: + value = tool_payload(response) + trace = value.get("delegated_to") + if ( + value.get("status") != "exact" + or value.get("lane") != "cas-route" + or value.get("formal") is not False + or value.get("result") != direct + or not isinstance(trace, list) + or len(trace) != 1 + or trace[0].get("tool") != "jackal_exact" + or re.fullmatch( + r"[0-9a-f]{64}", + value.get("identities", {}).get("jackal_advanced_sha256", ""), + ) is None + or not isinstance(value.get("non_claims"), list) + ): + raise AcceptanceError("advanced CAS assurance envelope is invalid") + return value + + +def _validate_graph_payload(response: object) -> dict[str, Any]: + value, content = _tool_result(response) + if ( + value.get("status") != "estimated" + or value.get("lane") != "graph-delegated-f64-v1" + or value.get("formal") is not False + or value.get("consequence_ceiling") != "informational" + or "_mcp_content" in value + or len(content) != 2 + or content[0].get("type") != "text" + or content[1].get("type") != "image" + or content[1].get("mimeType") != "image/png" + or not isinstance(content[1].get("data"), str) + or not isinstance(value.get("delegated_to"), list) + or not isinstance(value.get("non_claims"), list) + ): + raise AcceptanceError("advanced graph assurance envelope is invalid") + try: + image = base64.b64decode(content[1]["data"], validate=True) + except (ValueError, binascii.Error) as error: + raise AcceptanceError("advanced graph image is not base64") from error + if not image.startswith(b"\x89PNG\r\n\x1a\n"): + raise AcceptanceError("advanced graph image is not PNG") + return value + + +def _validate_hellgate_payload(response: object) -> dict[str, Any]: + value = tool_payload(response) + fields = value.get("fields") + identities = value.get("identities") + trial = fields.get("trial_diagnostics") if isinstance(fields, dict) else None + ground = fields.get("ground_state_transfer") if isinstance(fields, dict) else None + required_identities = { + "jackal_advanced_sha256", + "hellgate_checker_sha256", + "hellgate_certificate_file_sha256", + } + if ( + value.get("status") != "bounded" + or value.get("lane") != "nonlinear-barta-exact-rational-v1" + or value.get("formal") is not False + or value.get("checker_verdict") != "ACCEPT" + or not isinstance(fields, dict) + or not isinstance(fields.get("eigenvalue_interval"), list) + or len(fields["eigenvalue_interval"]) != 2 + or not isinstance(fields.get("eigenvalue_decimal_interval"), list) + or len(fields["eigenvalue_decimal_interval"]) != 2 + or not isinstance(fields.get("interval_width"), str) + or not isinstance(trial, dict) + or trial.get("schema") != "jackal-hellgate-trial-diagnostics-v1" + or trial.get("status") != "bounded" + or trial.get("subject") != "normalized-certificate-trial-phi" + or not isinstance(trial.get("non_claims"), list) + or not any( + isinstance(item, str) and "not the exact ground state u0" in item + for item in trial["non_claims"] + ) + or not isinstance(ground, dict) + or ground.get("schema") != "jackal-hellgate-ground-transfer-v1" + or ground.get("status") != "bounded" + or ground.get("subject") != "positive-normalized-ground-state-u0" + or ground.get("method") != "lambda-strong-convexity-density-transfer-v1" + or not isinstance(ground.get("non_claims"), list) + or not any( + isinstance(item, str) and "does not enclose polynomial moments" in item + for item in ground["non_claims"] + ) + or not isinstance(identities, dict) + or not required_identities.issubset(identities) + or any( + not isinstance(identities[key], str) + or re.fullmatch(r"[0-9a-f]{64}", identities[key]) is None + for key in required_identities + ) + or not isinstance(value.get("theorem"), dict) + or not isinstance(value.get("assumptions"), list) + or not isinstance(value.get("non_claims"), list) + or not any( + isinstance(item, str) and "not formal-bounded" in item + for item in value["non_claims"] + ) + ): + raise AcceptanceError("HELLGATE certificate assurance envelope is invalid") + return value + + +def _validate_stem_payload( + response: object, *, status: str, consequence_ceiling: str = "informational", +) -> dict[str, Any]: + value = tool_payload(response) + if ( + value.get("status") != status + or value.get("formal") is not False + or value.get("consequence_ceiling") != consequence_ceiling + or not isinstance(value.get("lane"), str) + or not isinstance(value.get("parsed"), dict) + or not isinstance(value.get("fields"), dict) + or not isinstance(value.get("field_status"), dict) + or not isinstance(value.get("delegated_to"), list) + or not isinstance(value.get("non_claims"), list) + or re.fullmatch( + r"[0-9a-f]{64}", + value.get("identities", {}).get("jackal_stem_sha256", ""), + ) is None + ): + raise AcceptanceError("STEM assurance envelope is invalid") + return value + + +def _validate_linked_workspace_payload(response: object) -> dict[str, Any]: + value, content = _tool_result(response) + if ( + value.get("status") != "checked" + or value.get("formal") is not False + or value.get("consequence_ceiling") != "informational" + or "_mcp_content" in value + or len(content) != 2 + or content[0].get("type") != "text" + or not isinstance(content[0].get("text"), str) + or content[1].get("type") != "resource" + or not isinstance(content[1].get("resource"), dict) + or not isinstance(value.get("delegated_to"), list) + or not isinstance(value.get("non_claims"), list) + or re.fullmatch( + r"[0-9a-f]{64}", + value.get("identities", {}).get("jackal_stem_sha256", ""), + ) is None + ): + raise AcceptanceError("linked workspace assurance envelope is invalid") + resource = content[1]["resource"] + text = resource.get("text") + uri = resource.get("uri") + if ( + set(resource) != {"uri", "mimeType", "text"} + or resource.get("mimeType") != "text/html" + or not isinstance(text, str) + or not text.startswith("") + or not isinstance(uri, str) + or uri != "ui://jackal/linked-workspace/" + hashlib.sha256( + text.encode("utf-8") + ).hexdigest() + or value.get("fields", {}).get("resource_uri") != uri + or "Pixels are not proof" not in text + ): + raise AcceptanceError("linked workspace resource binding is invalid") + return value + + def run_acceptance( *, client: Any, runtime_document: dict[str, Any], direct_call: Callable[[str, dict[str, Any]], dict[str, Any]], + formal_identities: Mapping[str, str], ) -> dict[str, Any]: + expected_formal_identities = _validated_formal_identities(formal_identities) initialize = client.request( "initialize-1", "initialize", { @@ -1670,6 +1947,7 @@ def run_acceptance( formal = validate_formal_int_cert( formal_response, direct_call("jackal_integrate_bound_cert", copy.deepcopy(FORMAL_ARGUMENTS)), + expected_formal_identities, ) refused_response = client.request( @@ -1685,6 +1963,366 @@ def run_acceptance( ), ) + measurement_calls = ( + ( + "jackal_convert", + {"value": "1", "from_unit": "mi", "to_unit": "km"}, + "exact", + ), + ( + "jackal_rate_apply", + { + "value": "100", "rate": "3/2", "rate_source": "acceptance fixture", + "rate_asof": "2026-08-27", "from_label": "a", "to_label": "b", + }, + "exact-given", + ), + ("jackal_percent", {"op": "change", "a": "200", "b": "250"}, "exact"), + ( + "jackal_date_delta", + {"op": "diff", "start": "2026-01-01", "end": "2026-08-27"}, + "exact-given", + ), + ( + "jackal_compare", + {"a_value": "1", "a_unit": "mi", "b_value": "1", "b_unit": "km"}, + "exact", + ), + ) + measurement_results: dict[str, dict[str, Any]] = {} + for name, arguments, expected_status in measurement_calls: + measurement_results[name] = _validate_measurement_payload( + client.request( + f"measurement-{name}", "tools/call", + {"name": name, "arguments": copy.deepcopy(arguments)}, + ), + status=expected_status, + ) + + stat = _validate_measurement_payload( + client.request( + "measurement-stat", "tools/call", + { + "name": "jackal_stat", + "arguments": {"sample": [0, 4, 6, 2], "include_stddev": True}, + }, + ), + status="exact", + ) + variance = direct_call( + "jackal_exact", + {"expression": "((0-3)^2+(4-3)^2+(6-3)^2+(2-3)^2)/4"}, + ).get("fields", {}).get("exact") + sqrt_traces = [ + item for item in stat.get("delegated_to", []) + if isinstance(item, dict) and item.get("tool") == "jackal_sqrt_rat_bound" + ] + if ( + not isinstance(variance, str) + or stat.get("fields", {}).get("population_variance") != variance + or stat.get("fields", {}).get("field_status", {}).get( + "population_stddev_enclosure" + ) != "formal-bounded" + or not sqrt_traces + or sqrt_traces[-1].get("parsed") + != f"sqrt(x) on [{variance},{variance}]" + or "/1" in sqrt_traces[-1].get("parsed", "") + ): + raise AcceptanceError("integer-variance measurement regression returned") + + scan_text = "Bounds: 10^-12, 2e-12, and 1×10⁻¹²." + scan = _validate_measurement_payload( + client.request( + "measurement-scan", "tools/call", + {"name": "jackal_scan", "arguments": {"text": scan_text}}, + ), + status="checked", + delegated=False, + ) + if [item.get("text") for item in scan.get("fields", {}).get("numerals", [])] != [ + "10^-12", "2e-12", "1×10⁻¹²" + ]: + raise AcceptanceError("scientific notation was split by the lexical scanner") + + measurement_refusal = _validate_measurement_refusal( + client.request( + "measurement-refusal", "tools/call", + { + "name": "jackal_rate_apply", + "arguments": {"value": "1", "rate": "2"}, + }, + ), + reason="undeclared-datum", + ) + + cas_direct = direct_call("jackal_exact", copy.deepcopy(EXACT_ARGUMENTS)) + cas = _validate_cas_payload( + client.request( + "advanced-cas", "tools/call", + { + "name": "jackal_cas", + "arguments": { + "operation": "exact", + "arguments": copy.deepcopy(EXACT_ARGUMENTS), + }, + }, + ), + cas_direct, + ) + graph = _validate_graph_payload( + client.request( + "advanced-graph", "tools/call", + { + "name": "jackal_graph", + "arguments": { + "expression": "x^2-1", + "x_min": "-2", + "x_max": "2", + "samples": "17", + }, + }, + ) + ) + hellgate = _validate_hellgate_payload( + client.request( + "advanced-hellgate", "tools/call", + { + "name": "jackal_hellgate_ground_state", + "arguments": {"problem_id": "hellgate-v1"}, + }, + ) + ) + + matrix = _validate_stem_payload( + client.request( + "stem-matrix", + "tools/call", + { + "name": "jackal_matrix", + "arguments": { + "operation": "determinant", + "matrix": [["1", "2"], ["3", "4"]], + }, + }, + ), + status="exact", + ) + if matrix.get("field_status", {}).get("determinant") != "exact": + raise AcceptanceError("matrix exact-field status is missing") + matrix_branch_arguments = ( + {"operation": "add", "matrix": [["1", "2"], ["3", "4"]], "second_matrix": [["5", "6"], ["7", "8"]]}, + {"operation": "multiply", "matrix": [["1", "2"], ["3", "4"]], "second_matrix": [["5", "6"], ["7", "8"]]}, + {"operation": "transpose", "matrix": [["1", "2", "3"], ["4", "5", "6"]]}, + {"operation": "rref", "matrix": [["1", "2"], ["2", "4"]]}, + {"operation": "inverse", "matrix": [["1", "2"], ["3", "5"]]}, + {"operation": "solve", "matrix": [["1", "0"], ["0", "1"]], "vector": ["7", "9"]}, + ) + for index, arguments in enumerate(matrix_branch_arguments): + _validate_stem_payload( + client.request( + f"stem-matrix-branch-{index}", + "tools/call", + {"name": "jackal_matrix", "arguments": copy.deepcopy(arguments)}, + ), + status="exact", + ) + + regression = _validate_stem_payload( + client.request( + "stem-regression", + "tools/call", + { + "name": "jackal_regression", + "arguments": { + "model": "polynomial_ols", + "degree": "1", + "x": ["0", "1", "2"], + "y": ["1", "3", "5"], + }, + }, + ), + status="model-based", + ) + if regression.get("field_status", {}).get("coefficients_ascending") != "exact": + raise AcceptanceError("regression exact-field status is missing") + + probability = _validate_stem_payload( + client.request( + "stem-probability", + "tools/call", + { + "name": "jackal_probability", + "arguments": { + "operation": "binomial_cdf", + "n": "3", + "k": "1", + "p": "1/2", + }, + }, + ), + status="model-based", + ) + if probability.get("field_status", {}).get("probability") != "exact": + raise AcceptanceError("probability exact-field status is missing") + for index, arguments in enumerate( + ( + {"operation": "binomial_pmf", "n": "3", "k": "2", "p": "1/2"}, + {"operation": "normal_cdf", "z": "0", "tail_cutoff": "6", "tolerance": "1e-8"}, + ) + ): + _validate_stem_payload( + client.request( + f"stem-probability-branch-{index}", + "tools/call", + {"name": "jackal_probability", "arguments": copy.deepcopy(arguments)}, + ), + status="model-based", + ) + + hypothesis = _validate_stem_payload( + client.request( + "stem-hypothesis", + "tools/call", + { + "name": "jackal_hypothesis", + "arguments": { + "operation": "exact_binomial_tail", + "alternative": "greater", + "n": "3", + "k": "2", + "p0": "1/2", + }, + }, + ), + status="model-based", + consequence_ceiling="advisory", + ) + if hypothesis.get("field_status", {}).get("p_value") != "exact": + raise AcceptanceError("hypothesis exact-field status is missing") + for alternative in ("less", "greater", "two_sided"): + _validate_stem_payload( + client.request( + f"stem-hypothesis-{alternative}", + "tools/call", + { + "name": "jackal_hypothesis", + "arguments": { + "operation": "one_sample_z", + "alternative": alternative, + "sample_mean": "1", + "null_mean": "1", + "population_sd": "2", + "n": "4", + "tail_cutoff": "6", + "tolerance": "1e-8", + }, + }, + ), + status="model-based", + consequence_ceiling="advisory", + ) + + sensor = _validate_stem_payload( + client.request( + "stem-sensor", + "tools/call", + { + "name": "jackal_sensor", + "arguments": { + "operation": "ingest_batch", + "sensor_id": "acceptance-imu", + "channel": "accel-x", + "quantity": "acceleration", + "unit": "m/s2", + "samples": ["1", "2", "3"], + "source": "acceptance supplied fixture", + "observed_at": "2026-08-28T00:00:00Z", + }, + }, + ), + status="exact-given", + ) + if ( + sensor.get("given", {}).get("input_provenance") != "supplied" + or sensor.get("field_status", {}).get("population_stddev_enclosure") + != "formal-bounded" + ): + raise AcceptanceError("sensor provenance or formal scalar field is invalid") + _validate_stem_payload( + client.request( + "stem-sensor-calibration", + "tools/call", + { + "name": "jackal_sensor", + "arguments": { + "operation": "linear_calibration", + "sensor_id": "acceptance-imu", + "channel": "accel-x", + "quantity": "acceleration", + "unit": "m/s2", + "samples": ["1", "2", "3"], + "source": "acceptance supplied fixture", + "observed_at": "2026-08-28T00:00:00Z", + "scale": "2", + "offset": "1", + "calibration_source": "acceptance calibration fixture", + "calibration_as_of": "2026-08-28", + }, + }, + ), + status="exact-given", + ) + + aerospace = _validate_stem_payload( + client.request( + "stem-aerospace", + "tools/call", + { + "name": "jackal_aerospace", + "arguments": { + "operation": "vis_viva", + "parameters": {"mu": "10", "radius": "2", "semi_major_axis": "3"}, + }, + }, + ), + status="model-based", + consequence_ceiling="advisory", + ) + if aerospace.get("field_status", {}).get("speed_enclosure") != "formal-bounded": + raise AcceptanceError("aerospace formal scalar field is missing") + aerospace_branch_arguments = ( + {"operation": "circular_orbit", "parameters": {"mu": "10", "radius": "2"}}, + {"operation": "rocket_equation", "parameters": {"exhaust_velocity": "3", "initial_mass": "5", "final_mass": "2"}}, + {"operation": "hohmann_transfer", "parameters": {"mu": "10", "r1": "2", "r2": "3"}}, + {"operation": "plane_change", "parameters": {"velocity": "7", "angle_degrees": "30"}}, + ) + for index, arguments in enumerate(aerospace_branch_arguments): + _validate_stem_payload( + client.request( + f"stem-aerospace-branch-{index}", + "tools/call", + {"name": "jackal_aerospace", "arguments": copy.deepcopy(arguments)}, + ), + status="model-based", + consequence_ceiling="advisory", + ) + + workspace = _validate_linked_workspace_payload( + client.request( + "stem-workspace", + "tools/call", + { + "name": "jackal_linked_workspace", + "arguments": { + "expression": "x^2-1", + "x_min": "-2", + "x_max": "2", + "samples": "17", + }, + }, + ) + ) + claim_response = client.request( "claim-1", "tools/call", {"name": "jackal_claim", "arguments": {"request": copy.deepcopy(CLAIM_REQUEST)}}, @@ -1701,7 +2339,7 @@ def run_acceptance( "receipt-verify-1", "tools/call", {"name": "jackal_verify_receipt", "arguments": receipt_verification_arguments(receipt)}, - ), receipt) + ), receipt, expected_formal_identities) return { "discovered_tool_count": len(discovered), @@ -1709,6 +2347,26 @@ def run_acceptance( "exact": exact["status"], "formal": formal["status"], "unsupported_formal": refused["reason"], + "measurement": { + **{name: result["status"] for name, result in measurement_results.items()}, + "jackal_stat": stat["status"], + "jackal_scan": scan["status"], + "refusal": measurement_refusal["reason"], + }, + "advanced": { + "cas": cas["status"], + "graph": graph["status"], + "hellgate": hellgate["status"], + }, + "stem": { + "matrix": matrix["status"], + "regression": regression["status"], + "probability": probability["status"], + "hypothesis": hypothesis["status"], + "sensor": sensor["status"], + "aerospace": aerospace["status"], + "linked_workspace": workspace["status"], + }, "claim_bundle": bundle_verified["status"], "formal_receipt": receipt_verified["status"], }, @@ -1794,7 +2452,11 @@ def request( if response.get("jsonrpc") != "2.0" or response.get("id") != request_id: raise AcceptanceError("MCP response correlation failed") if "error" in response: - raise AcceptanceError("MCP request returned a protocol error") + error = response.get("error") + code = error.get("code") if isinstance(error, dict) else None + raise AcceptanceError( + f"MCP request {request_id!r} returned protocol error code {code!r}" + ) return response def notification(self, method: str, params: dict[str, Any]) -> None: @@ -1904,6 +2566,62 @@ def load_runtime_document(runtime_root: Path | str) -> dict[str, Any]: return document +def load_runtime_formal_identities(runtime_root: Path | str) -> dict[str, str]: + """Load int-cert expectations from the independently package-bound manifest.""" + runtime = Path(runtime_root) + if not runtime.is_absolute(): + raise AcceptanceError("formal identity source requires an absolute runtime root") + try: + release_pins = effective_runtime_pins() + expected_tree = release_pins["sha256sums_sha256"] + if not isinstance(expected_tree, str): + raise AcceptanceError("runtime tree pin is not a digest") + records = provisioner.verify_sha256sums( + runtime, expected_manifest_sha256=expected_tree, + ) + expected_manifest = records.get("MANIFEST.sha256") + if not isinstance(expected_manifest, str): + raise AcceptanceError("runtime package omitted its formal identity manifest") + raw = identity._read_regular_file_nofollow( + runtime / "MANIFEST.sha256", "runtime formal identity manifest", + byte_limit=provisioner.MAX_RUNTIME_MANIFEST_BYTES, + ) + except (identity.ManifestError, OSError, KeyError, + provisioner.ProvisionError) as error: + raise AcceptanceError("runtime formal identity manifest is untrusted") from error + if not secrets.compare_digest(hashlib.sha256(raw).hexdigest(), expected_manifest): + raise AcceptanceError("runtime formal identity manifest changed after validation") + if not raw or not raw.endswith(b"\n"): + raise AcceptanceError("runtime formal identity manifest is not canonical") + try: + lines = raw.decode("utf-8").splitlines() + except UnicodeDecodeError as error: + raise AcceptanceError("runtime formal identity manifest is not UTF-8") from error + + rows: dict[str, list[str]] = {} + for line in lines: + if line.startswith("#"): + continue + parts = line.split(" ") + if not parts or any(not part for part in parts) \ + or re.fullmatch(r"[a-z0-9_]+", parts[0], re.ASCII) is None: + raise AcceptanceError("runtime formal identity manifest row is malformed") + if parts[0] in rows: + raise AcceptanceError("runtime formal identity manifest has a duplicate key") + rows[parts[0]] = parts + + selected: dict[str, str] = {} + for row_name, (identity_name, expected_fields) in \ + FORMAL_IDENTITY_MANIFEST_ROWS.items(): + parts = rows.get(row_name) + if parts is None or len(parts) != expected_fields \ + or re.fullmatch(r"[0-9a-f]{64}", parts[-1], re.ASCII) is None: + raise AcceptanceError("runtime formal identity manifest omitted a pinned row") + selected[identity_name] = parts[-1] + selected["evaluator_sha256"] = selected["producer_sha256"] + return _validated_formal_identities(selected) + + def runtime_acceptance_environment( runtime_root: Path | str, caller_environment: Mapping[str, str] | None = None, @@ -1942,21 +2660,36 @@ def direct_backend_call( return value +def effective_runtime_pins() -> dict[str, object]: + try: + pins = provisioner.effective_release_pins() + except (KeyError, TypeError, provisioner.ProvisionError) as error: + raise AcceptanceError("host-effective runtime pins are unavailable") from error + if not isinstance(pins, dict) or any( + not isinstance(pins.get(key), str) + or re.fullmatch(r"[0-9a-f]{64}", pins[key], re.ASCII) is None + for key in ("package_sha256", "sha256sums_sha256") + ): + raise AcceptanceError("host-effective runtime digest pin is invalid") + return dict(pins) + + def verify_runtime(runtime_root: Path | str) -> None: try: + release_pins = effective_runtime_pins() provisioner.validate_runtime( Path(runtime_root), timeout=provisioner.SELFTEST_TIMEOUT, output_limit=provisioner.SELFTEST_OUTPUT_LIMIT, - expected_tree_sha256=provisioner.SHA256SUMS_SHA256, + expected_tree_sha256=release_pins["sha256sums_sha256"], ) - except provisioner.ProvisionError as error: + except (KeyError, TypeError, provisioner.ProvisionError) as error: raise AcceptanceError("pinned runtime validation refused") from error def dry_run_document( *, codex_binary: Path | str, repository_root: Path | str, ) -> dict[str, Any]: - placeholder = Path("/private/tmp/jackel-codex-isolated-CODEX_HOME") + placeholder = _fixed_temp_root() / "jackel-codex-isolated-CODEX_HOME" plan = build_codex_install_plan( codex_home=placeholder, repository_root=repository_root, codex_binary=codex_binary, @@ -1969,6 +2702,9 @@ def dry_run_document( "mcp_tools": [ "jackal_exact", "jackal_integrate_bound_cert", "jackal_claim", "jackal_verify_bundle", "jackal_verify_receipt", + *MEASUREMENT_TOOLS, + *ADVANCED_TOOLS, + *STEM_TOOLS, ], "caller_pins": { "claim_release_epoch": CLAIM_RELEASE_EPOCH, @@ -1981,8 +2717,18 @@ def dry_run_document( } +def _fixed_temp_root() -> Path: + """The physical system temp directory for this host. + + macOS exposes it as /private/tmp with /tmp as a symlink; on Linux /tmp is + already the physical directory. The path stays fixed per platform rather + than being read from the environment. + """ + return Path("/private/tmp") if platform.system() == "Darwin" else Path("/tmp") + + def _isolated_codex_temp_parent() -> Path: - parent = Path("/private/tmp") + parent = _fixed_temp_root() try: info = parent.lstat() resolved = parent.resolve(strict=True) @@ -2008,6 +2754,7 @@ def _live(runtime_root: Path, codex_binary: Path) -> dict[str, Any]: source_aggregate = verify_wrapper(PLUGIN_ROOT) verify_runtime(runtime_root) runtime_document = load_runtime_document(runtime_root) + formal_identities = load_runtime_formal_identities(runtime_root) with tempfile.TemporaryDirectory( prefix="jackel-codex-live-", dir=_isolated_codex_temp_parent() ) as directory: @@ -2024,15 +2771,17 @@ def _live(runtime_root: Path, codex_binary: Path) -> dict[str, Any]: with installed_mcp_client(installed, environment) as client: report = run_acceptance( client=client, runtime_document=runtime_document, + formal_identities=formal_identities, direct_call=lambda tool, arguments: direct_backend_call( runtime_root, tool, arguments, environment=environment ), ) + runtime_pins = effective_runtime_pins() return { "status": "accepted", "wrapper_aggregate_sha256": source_aggregate, - "runtime_package_sha256": provisioner.PACKAGE_SHA256, - "runtime_tree_sha256": provisioner.SHA256SUMS_SHA256, + "runtime_package_sha256": runtime_pins["package_sha256"], + "runtime_tree_sha256": runtime_pins["sha256sums_sha256"], **report, } diff --git a/tests/codex_plugin/test_advanced.py b/tests/codex_plugin/test_advanced.py new file mode 100644 index 0000000..ab849a6 --- /dev/null +++ b/tests/codex_plugin/test_advanced.py @@ -0,0 +1,250 @@ +import base64 +import copy +import struct +import unittest +from fractions import Fraction + +from plugins.jackel.mcp import advanced +from plugins.jackel.mcp import server as adapter + + +FIXTURE_IDENTITY = "a" * 64 + + +class AdvancedSurfaceTests(unittest.TestCase): + def test_definitions_form_one_strict_identity_pinned_surface(self): + definitions = adapter.build_advanced_tool_definitions(advanced) + + self.assertEqual( + {definition["name"] for definition in definitions}, + adapter.ADVANCED_TOOL_NAMES, + ) + self.assertEqual(len(definitions), adapter.EXPECTED_ADVANCED_TOOL_COUNT) + for definition in definitions: + with self.subTest(tool=definition["name"]): + self.assertIs(definition["inputSchema"]["additionalProperties"], False) + self.assertEqual( + definition["annotations"], + { + "readOnlyHint": True, + "destructiveHint": False, + "idempotentHint": True, + "openWorldHint": False, + }, + ) + + def test_definition_tampering_refuses_before_merge(self): + original = advanced.tool_definitions + definitions = original() + definitions[0] = copy.deepcopy(definitions[0]) + definitions[0]["name"] = "jackal_forged" + advanced.tool_definitions = lambda: definitions + try: + with self.assertRaises(adapter.CatalogError): + adapter.build_advanced_tool_definitions(advanced) + finally: + advanced.tool_definitions = original + + def test_cas_preserves_the_delegated_status_and_body(self): + delegated = { + "status": "exact", + "formal": False, + "fields": {"parsed": "1/3+1/3", "exact": "2/3"}, + "non_claims": ["fixture residual"], + } + calls = [] + + def kernel_call(name, arguments): + calls.append((name, copy.deepcopy(arguments))) + return copy.deepcopy(delegated) + + body = advanced.dispatch_integrated( + "jackal_cas", + {"operation": "exact", "arguments": {"expression": "1/3+1/3"}}, + kernel_call, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "exact") + self.assertEqual(body["result"], delegated) + self.assertEqual( + calls, + [("jackal_exact", {"expression": "1/3+1/3"})], + ) + self.assertIn("adds no assurance", body["non_claims"][0]) + + def test_cas_refusal_is_not_routed_to_a_weaker_lane(self): + def kernel_call(name, arguments): + return { + "status": "refused", + "reason": "fixture-refusal", + "detail": "unsupported fixture", + } + + body = advanced.dispatch_integrated( + "jackal_cas", + {"operation": "exact", "arguments": {"expression": "sqrt(2)"}}, + kernel_call, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "refused") + self.assertEqual(body["reason"], "kernel-refused:fixture-refusal") + self.assertIn("no weaker lane", body["non_claims"][0].lower()) + + def test_graph_delegates_coordinates_and_values_and_emits_png(self): + coordinate_index = 0 + calls = [] + + def kernel_call(name, arguments): + nonlocal coordinate_index + calls.append((name, copy.deepcopy(arguments))) + if name == "jackal_exact": + coordinate = Fraction(-1) + Fraction(coordinate_index, 8) + coordinate_index += 1 + return { + "status": "exact", + "fields": { + "parsed": arguments["expression"], + "exact": str(coordinate), + }, + } + if name == "jackal_evaluate": + return {"status": "estimated", "engine_output": "0"} + self.fail(f"unexpected kernel call: {name}") + + body = advanced.dispatch_integrated( + "jackal_graph", + {"expression": "x^2", "x_min": "-1", "x_max": "1", "samples": "17"}, + kernel_call, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "estimated") + self.assertIs(body["formal"], False) + self.assertEqual(body["fields"]["finite_sample_count"], 17) + self.assertEqual(len([name for name, unused in calls if name == "jackal_exact"]), 17) + self.assertEqual(len([name for name, unused in calls if name == "jackal_evaluate"]), 17) + image = body["_mcp_content"][1] + self.assertEqual(image["mimeType"], "image/png") + png = base64.b64decode(image["data"], validate=True) + self.assertTrue(png.startswith(b"\x89PNG\r\n\x1a\n")) + self.assertEqual( + struct.unpack(">II", png[16:24]), + (advanced.GRAPH_WIDTH, advanced.GRAPH_HEIGHT), + ) + self.assertTrue(any("visualization only" in item for item in body["non_claims"])) + + def test_graph_breaks_at_a_refused_sample(self): + coordinate_index = 0 + evaluation_index = 0 + + def kernel_call(name, arguments): + nonlocal coordinate_index, evaluation_index + if name == "jackal_exact": + coordinate = Fraction(-1) + Fraction(coordinate_index, 8) + coordinate_index += 1 + return {"status": "exact", "fields": {"exact": str(coordinate)}} + evaluation_index += 1 + if evaluation_index == 9: + return { + "status": "refused", + "reason": "domain", + "detail": "fixture gap", + } + return {"status": "estimated", "engine_output": str(evaluation_index)} + + body = advanced.dispatch_integrated( + "jackal_graph", + {"expression": "1/x", "x_min": "-1", "x_max": "1", "samples": "17"}, + kernel_call, + FIXTURE_IDENTITY, + ) + + refused = [point for point in body["fields"]["points"] if point["status"] == "refused"] + self.assertEqual(len(refused), 1) + self.assertTrue(any("break" in item for item in body["non_claims"])) + + def test_hellgate_result_requires_startup_configuration_and_exact_problem_id(self): + result = { + "status": "bounded", + "checker_verdict": "ACCEPT", + "formal": False, + "fields": { + "eigenvalue_decimal_interval": ["-5", "-4"], + "trial_diagnostics": { + "schema": "jackal-hellgate-trial-diagnostics-v1", + "status": "bounded", + "subject": "normalized-certificate-trial-phi", + "non_claims": ["not the exact ground state u0"], + }, + "ground_state_transfer": { + "schema": "jackal-hellgate-ground-transfer-v1", + "status": "bounded", + "subject": "positive-normalized-ground-state-u0", + "method": "lambda-strong-convexity-density-transfer-v1", + "non_claims": ["does not enclose polynomial moments"], + }, + }, + } + advanced.configure_hellgate( + result, + advanced_sha256="a" * 64, + checker_sha256="b" * 64, + certificate_sha256="c" * 64, + ) + body = advanced.dispatch_integrated( + "jackal_hellgate_ground_state", + {"problem_id": "hellgate-v1"}, + lambda unused_name, unused_arguments: self.fail("certificate replay delegates nothing"), + FIXTURE_IDENTITY, + ) + self.assertEqual(body["status"], "bounded") + self.assertIs(body["formal"], False) + self.assertEqual(body["identities"]["hellgate_checker_sha256"], "b" * 64) + + refusal = advanced.dispatch_integrated( + "jackal_hellgate_ground_state", + {"problem_id": "different-problem"}, + lambda unused_name, unused_arguments: {}, + FIXTURE_IDENTITY, + ) + self.assertEqual(refusal["status"], "refused") + self.assertEqual(refusal["reason"], "unsupported-problem") + + +class AdvancedMCPContentTests(unittest.TestCase): + def test_reserved_content_is_validated_and_removed_from_structured_result(self): + png = b"\x89PNG\r\n\x1a\nfixture" + backend = { + "status": "estimated", + "_mcp_content": [ + {"type": "text", "text": "fixture graph"}, + { + "type": "image", + "data": base64.b64encode(png).decode("ascii"), + "mimeType": "image/png", + }, + ], + } + + result = adapter.backend_result(backend) + + self.assertNotIn("_mcp_content", result["structuredContent"]) + self.assertEqual(result["content"][0]["text"], "fixture graph") + self.assertEqual(result["content"][1]["mimeType"], "image/png") + self.assertIn("_mcp_content", backend) + + def test_malformed_content_injection_fails_closed(self): + cases = ( + [{"type": "image", "data": "not-base64", "mimeType": "image/png"}], + [{"type": "image", "data": base64.b64encode(b"GIF89a").decode("ascii"), "mimeType": "image/png"}], + [{"type": "resource", "uri": "file:///tmp/forbidden"}], + ) + for content in cases: + with self.subTest(content=content), self.assertRaises(adapter.BackendFailure): + adapter.backend_result({"status": "estimated", "_mcp_content": content}) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/codex_plugin/test_hellgate.py b/tests/codex_plugin/test_hellgate.py new file mode 100644 index 0000000..557beb7 --- /dev/null +++ b/tests/codex_plugin/test_hellgate.py @@ -0,0 +1,249 @@ +import ast +import copy +import hashlib +import json +import unittest +import zlib +from fractions import Fraction +from pathlib import Path + +from plugins.jackel.mcp import hellgate_verify +from plugins.jackel.mcp import server as adapter +from tools import hellgate_trial_oracle + + +REPO_ROOT = Path(__file__).resolve().parents[2] +CERTIFICATE_PATH = ( + REPO_ROOT / "plugins/jackel/mcp/certificates/hellgate_v1.json.zlib" +) +CHECKER_PATH = REPO_ROOT / "plugins/jackel/mcp/hellgate_verify.py" +PLUGIN_WORKFLOW_PATH = REPO_ROOT / ".github/workflows/jackal-codex-plugin.yml" + + +def canonical_bytes(value): + return json.dumps( + value, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + + +def repin(document): + result = copy.deepcopy(document) + result.pop("certificate_sha256", None) + result["certificate_sha256"] = hashlib.sha256(canonical_bytes(result)).hexdigest() + return canonical_bytes(result) + b"\n" + + +class HellgateCertificateTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.compressed = CERTIFICATE_PATH.read_bytes() + cls.raw = adapter._decompress_certificate(cls.compressed) + cls.document = json.loads(cls.raw) + cls.result = hellgate_verify.verify_bytes(cls.raw) + cls.oracle = hellgate_trial_oracle.compute_oracle(cls.document) + + def test_static_certificate_is_accepted_at_the_declared_assurance_boundary(self): + self.assertEqual(self.result["status"], "bounded") + self.assertEqual(self.result["checker_verdict"], "ACCEPT") + self.assertIs(self.result["formal"], False) + self.assertEqual( + self.result["fields"]["eigenvalue_decimal_interval"], + ["-4.615978698574496508", "-4.615978698574496507"], + ) + width = Fraction(self.result["fields"]["interval_width"]) + self.assertLess(width, hellgate_verify.MAX_EIGENVALUE_WIDTH) + self.assertTrue(any("not formal-bounded" in item for item in self.result["non_claims"])) + + def test_trial_diagnostics_are_bounded_and_subject_scoped(self): + trial = self.result["fields"]["trial_diagnostics"] + self.assertEqual(trial["schema"], "jackal-hellgate-trial-diagnostics-v1") + self.assertEqual(trial["status"], "bounded") + self.assertEqual(trial["subject"], "normalized-certificate-trial-phi") + for interval in ( + trial["quartic_norm_interval"], + trial["kinetic_energy_interval"], + trial["energy_functional_interval"], + *trial["moment_intervals"].values(), + ): + self.assertLessEqual(Fraction(interval[0]), Fraction(interval[1])) + for key in ( + "energy_eigenvalue_identity_residual_interval", + "virial_residual_interval", + ): + lower, upper = map(Fraction, trial[key]) + self.assertLessEqual(lower, 0) + self.assertGreaterEqual(upper, 0) + self.assertTrue( + any("not the exact ground state u0" in item for item in trial["non_claims"]) + ) + + def test_untrusted_high_precision_oracle_lands_inside_bounded_replay(self): + self.assertEqual(self.oracle["status"], "unverified-numerical-oracle") + oracle = self.oracle["fields"] + fields = self.result["fields"] + trial = fields["trial_diagnostics"] + + def assert_inside(interval, point): + lower, upper = map(Fraction, interval) + value = Fraction(point) + self.assertLessEqual(lower, value) + self.assertGreaterEqual(upper, value) + + assert_inside(fields["normalization_interval"], oracle["normalization"]) + assert_inside(trial["quartic_norm_interval"], oracle["quartic_norm"]) + for key in ("x2", "x4", "x6"): + assert_inside(trial["moment_intervals"][key], oracle["moments"][key]) + assert_inside(trial["kinetic_energy_interval"], oracle["kinetic_energy"]) + assert_inside( + trial["energy_functional_interval"], oracle["energy_functional"] + ) + assert_inside( + fields["eigenvalue_interval"], oracle["eigenvalue_from_energy"] + ) + assert_inside( + trial["virial_residual_interval"], oracle["virial_residual"] + ) + self.assertTrue( + any( + "not certificate evidence" in item + for item in self.oracle["non_claims"] + ) + ) + + def test_hosted_oracle_dependency_is_content_pinned(self): + workflow = PLUGIN_WORKFLOW_PATH.read_text(encoding="utf-8") + for token in ( + "mpmath-1.3.0-py3-none-any.whl", + "a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", + 'MPMATH_WHEEL_SIZE: "536198"', + '--max-filesize "$MPMATH_WHEEL_SIZE"', + "mpmath wheel digest mismatch", + ): + self.assertIn(token, workflow) + + def test_ground_transfer_is_narrow_bounded_and_does_not_launder_moments(self): + fields = self.result["fields"] + trial = fields["trial_diagnostics"] + ground = fields["ground_state_transfer"] + self.assertEqual(ground["schema"], "jackal-hellgate-ground-transfer-v1") + self.assertEqual(ground["status"], "bounded") + self.assertEqual(ground["subject"], "positive-normalized-ground-state-u0") + self.assertEqual(ground["method"], "lambda-strong-convexity-density-transfer-v1") + self.assertLess( + Fraction(ground["density_l2_distance_upper"]), + hellgate_verify.MAX_DENSITY_L2_DISTANCE, + ) + trial_lower, trial_upper = map(Fraction, trial["quartic_norm_interval"]) + ground_lower, ground_upper = map(Fraction, ground["quartic_norm_interval"]) + self.assertLessEqual(ground_lower, trial_lower) + self.assertGreaterEqual(ground_upper, trial_upper) + self.assertNotIn("moment_intervals", ground) + self.assertTrue( + any("does not enclose polynomial moments" in item for item in ground["non_claims"]) + ) + + def test_startup_gate_requires_the_scoped_additive_envelopes(self): + self.assertTrue(adapter._hellgate_result_satisfies_startup_gate(self.result)) + weakened = copy.deepcopy(self.result) + del weakened["fields"]["trial_diagnostics"] + self.assertFalse(adapter._hellgate_result_satisfies_startup_gate(weakened)) + laundered = copy.deepcopy(self.result) + laundered["fields"]["trial_diagnostics"]["subject"] = ( + "positive-normalized-ground-state-u0" + ) + self.assertFalse(adapter._hellgate_result_satisfies_startup_gate(laundered)) + + def test_certificate_asset_remains_byte_pinned(self): + self.assertEqual( + hashlib.sha256(self.compressed).hexdigest(), + "e41ef05cb7ea6aae121a8a60330f52faf58e5827ee39adea3ef298ec4a873a88", + ) + + def test_checker_has_no_numerical_producer_dependency(self): + tree = ast.parse(CHECKER_PATH.read_text(encoding="utf-8")) + imported = { + alias.name.split(".")[0] + for node in ast.walk(tree) + if isinstance(node, (ast.Import, ast.ImportFrom)) + for alias in node.names + } + self.assertTrue({"numpy", "scipy", "mpmath"}.isdisjoint(imported)) + + def test_common_denominator_polynomial_arithmetic_matches_exact_vectors(self): + left = [Fraction(1, 2), Fraction(-2, 3), Fraction(5, 7)] + right = [Fraction(-3, 5), Fraction(4, 9)] + # JACKAL exact vectors, outside the formal certificate chain: + # parsed terms yield -3/10, 28/45, -137/189, 20/63 and + # parsed=-3/10+28/45/2+-137/189/3+20/63/4 yields -857/5670. + self.assertEqual( + hellgate_verify.poly_mul(left, right), + [ + Fraction(-3, 10), + Fraction(28, 45), + Fraction(-137, 189), + Fraction(20, 63), + ], + ) + self.assertEqual( + hellgate_verify.poly_product_integral_unit(left, right), + Fraction(-857, 5670), + ) + + def test_internal_digest_tampering_refuses(self): + document = copy.deepcopy(self.document) + document["center_eigenvalue"] = "0" + raw = canonical_bytes(document) + b"\n" + with self.assertRaises(hellgate_verify.VerificationRefusal) as raised: + hellgate_verify.verify_bytes(raw) + self.assertEqual(raised.exception.reason, "certificate-digest") + + def test_coherently_repinned_problem_tampering_refuses(self): + document = copy.deepcopy(self.document) + document["problem"]["lambda"] = "3/4" + with self.assertRaises(hellgate_verify.VerificationRefusal): + hellgate_verify.verify_bytes(repin(document)) + + def test_coherently_repinned_piece_tampering_refuses(self): + document = copy.deepcopy(self.document) + piece = document["forward_pieces"][1] + piece["coefficients"][0] = str(Fraction(piece["coefficients"][0]) + 1) + with self.assertRaises(hellgate_verify.VerificationRefusal): + hellgate_verify.verify_bytes(repin(document)) + + def test_coherently_repinned_tail_tampering_refuses(self): + document = copy.deepcopy(self.document) + document["tail_coefficients"][-1] = str( + Fraction(document["tail_coefficients"][-1]) + 1 + ) + with self.assertRaises(hellgate_verify.VerificationRefusal): + hellgate_verify.verify_bytes(repin(document)) + + def test_coherently_repinned_nonclaim_weakening_refuses(self): + document = copy.deepcopy(self.document) + document["nonclaims"].pop() + with self.assertRaises(hellgate_verify.VerificationRefusal) as raised: + hellgate_verify.verify_bytes(repin(document)) + self.assertEqual(raised.exception.reason, "certificate-nonclaims") + + def test_coherently_repinned_density_tampering_refuses(self): + document = copy.deepcopy(self.document) + document["forward_pieces"][0]["density_coefficients"][0] = "-1" + with self.assertRaises(hellgate_verify.VerificationRefusal): + hellgate_verify.verify_bytes(repin(document)) + + def test_duplicate_json_key_refuses(self): + raw = b'{"schema":"first","schema":"second"}\n' + with self.assertRaises(hellgate_verify.VerificationRefusal) as raised: + hellgate_verify.verify_bytes(raw) + self.assertEqual(raised.exception.reason, "duplicate-json-key") + + def test_compression_trailing_bytes_refuse(self): + with self.assertRaises(adapter.StartupError): + adapter._decompress_certificate(self.compressed + b"trailing") + + def test_compression_round_trip_is_byte_exact(self): + self.assertEqual(zlib.decompress(self.compressed), self.raw) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/codex_plugin/test_live_acceptance.py b/tests/codex_plugin/test_live_acceptance.py index 0ca2f4e..0368986 100644 --- a/tests/codex_plugin/test_live_acceptance.py +++ b/tests/codex_plugin/test_live_acceptance.py @@ -22,6 +22,12 @@ REPOSITORY_ROOT = Path(__file__).resolve().parents[2] RUNTIME_TOOLS = REPOSITORY_ROOT / "plugin" / "hermes" / "tools.json" +TEST_FORMAL_IDENTITIES = { + "evaluator_sha256": "b4240fdac3c77b2abd751595303b2b3a0e4bebd492b2ae57fa5ccf052cd50af4", + "producer_sha256": "b4240fdac3c77b2abd751595303b2b3a0e4bebd492b2ae57fa5ccf052cd50af4", + "checker_sha256": "f2e26f506f921b577fd8609a095b69789b3b76cce65d8c293d1a11f3007a8078", + "plugin_sha256": "6943891086bde13e48dbc9bbde9c8c9fed90d0e78a7d8c29b8be32a92502711a", +} def mcp_response(request_id, payload): @@ -64,10 +70,7 @@ def formal_payload(emitted_at): "sha256": hashlib.sha256(certificate).hexdigest(), }, "identities": { - "evaluator_sha256": live.INT_CERT_PRODUCER_SHA256, - "producer_sha256": live.INT_CERT_PRODUCER_SHA256, - "checker_sha256": live.INT_CERT_CHECKER_SHA256, - "plugin_sha256": live.HERMES_BUNDLE_SHA256, + **TEST_FORMAL_IDENTITIES, }, "theorem": {"id": "int_cert_sound"}, "checker": {"verdict": "ACCEPT"}, @@ -77,19 +80,89 @@ def formal_payload(emitted_at): class IdentityAndInstallPlanTests(unittest.TestCase): - def test_formal_receipt_oracle_matches_current_hermes_bundle_pin(self): - row = next( - ( - line.split() - for line in (REPOSITORY_ROOT / "release/MANIFEST.sha256") - .read_text(encoding="utf-8") - .splitlines() - if line.startswith("plugin_hermes ") - ), - None, + def test_runtime_formal_identities_come_from_package_bound_manifest(self): + manifest = ( + "# fixture runtime manifest\n" + f"plugin_hermes {TEST_FORMAL_IDENTITIES['plugin_sha256']}\n" + "int_cert_producer producer.py " + f"{TEST_FORMAL_IDENTITIES['producer_sha256']}\n" + "int_cert_checker checker " + f"{TEST_FORMAL_IDENTITIES['checker_sha256']}\n" + ).encode("utf-8") + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / "MANIFEST.sha256").write_bytes(manifest) + with ( + mock.patch.object( + live.provisioner, "effective_release_pins", + return_value={ + "package_sha256": "d" * 64, + "sha256sums_sha256": "a" * 64, + }, + ), + mock.patch.object( + live.provisioner, "verify_sha256sums", + return_value={ + "MANIFEST.sha256": hashlib.sha256(manifest).hexdigest(), + }, + ) as verify, + ): + actual = live.load_runtime_formal_identities(root) + self.assertEqual(actual, TEST_FORMAL_IDENTITIES) + verify.assert_called_once_with( + root, expected_manifest_sha256="a" * 64, ) - self.assertIsNotNone(row, "release manifest has no plugin_hermes row") - self.assertEqual(live.HERMES_BUNDLE_SHA256, row[-1]) + + def test_runtime_formal_identity_manifest_rejects_duplicate_rows(self): + manifest = ( + f"plugin_hermes {TEST_FORMAL_IDENTITIES['plugin_sha256']}\n" + f"plugin_hermes {TEST_FORMAL_IDENTITIES['plugin_sha256']}\n" + "int_cert_producer producer.py " + f"{TEST_FORMAL_IDENTITIES['producer_sha256']}\n" + "int_cert_checker checker " + f"{TEST_FORMAL_IDENTITIES['checker_sha256']}\n" + ).encode("utf-8") + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / "MANIFEST.sha256").write_bytes(manifest) + with ( + mock.patch.object( + live.provisioner, "effective_release_pins", + return_value={ + "package_sha256": "d" * 64, + "sha256sums_sha256": "a" * 64, + }, + ), + mock.patch.object( + live.provisioner, "verify_sha256sums", + return_value={ + "MANIFEST.sha256": hashlib.sha256(manifest).hexdigest(), + }, + ), + ): + with self.assertRaisesRegex(live.AcceptanceError, "duplicate key"): + live.load_runtime_formal_identities(root) + + def test_runtime_formal_identity_manifest_rejects_post_validation_change(self): + manifest = b"plugin_hermes " + b"a" * 64 + b"\n" + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / "MANIFEST.sha256").write_bytes(manifest) + with ( + mock.patch.object( + live.provisioner, "effective_release_pins", + return_value={ + "package_sha256": "d" * 64, + "sha256sums_sha256": "b" * 64, + }, + ), + mock.patch.object( + live.provisioner, "verify_sha256sums", + return_value={"MANIFEST.sha256": "c" * 64}, + ), + ): + with self.assertRaisesRegex(live.AcceptanceError, "changed after validation"): + live.load_runtime_formal_identities(root) def test_dry_run_lists_each_mcp_tool_once(self): document = live.dry_run_document( @@ -427,7 +500,9 @@ def test_exact_result_requires_strict_direct_parity(self): def test_formal_result_verifies_identities_digests_and_only_normalizes_time(self): mcp = formal_payload(10) direct = formal_payload(11) - live.validate_formal_int_cert(mcp_response("formal", mcp), direct) + live.validate_formal_int_cert( + mcp_response("formal", mcp), direct, TEST_FORMAL_IDENTITIES, + ) tampered = formal_payload(11) tampered["receipt"]["certificate"]["sha256"] = "0" * 64 @@ -435,7 +510,19 @@ def test_formal_result_verifies_identities_digests_and_only_normalizes_time(self tampered["receipt"] ) with self.assertRaisesRegex(live.AcceptanceError, "certificate digest"): - live.validate_formal_int_cert(mcp_response("formal", mcp), tampered) + live.validate_formal_int_cert( + mcp_response("formal", mcp), tampered, TEST_FORMAL_IDENTITIES, + ) + + self_signed = formal_payload(11) + self_signed["receipt"]["identities"]["checker_sha256"] = "d" * 64 + self_signed["receipt"]["receipt_digest_sha256"] = live.receipt_digest( + self_signed["receipt"] + ) + with self.assertRaisesRegex(live.AcceptanceError, "identity binding"): + live.validate_formal_int_cert( + mcp_response("formal", mcp), self_signed, TEST_FORMAL_IDENTITIES, + ) def test_named_formal_refusal_has_no_downgrade_shape(self): refused = { @@ -582,8 +669,8 @@ def test_installed_mcp_config_rejects_relative_command_or_escaping_cwd(self): base = { "mcpServers": { "jackel": { - "command": "/bin/zsh", - "args": ["./scripts/launch_mcp.zsh"], + "command": "/bin/sh", + "args": ["./scripts/launch_mcp.sh"], "cwd": ".", "env_vars": ["JACKAL_HOME"], "tool_timeout_sec": 3700, @@ -826,8 +913,8 @@ def registry_bytes(self, installed): "disabled_reason": None, "transport": { "type": "stdio", - "command": "/bin/zsh", - "args": ["./scripts/launch_mcp.zsh"], + "command": "/bin/sh", + "args": ["./scripts/launch_mcp.sh"], "env": None, "env_vars": ["JACKAL_HOME"], "cwd": str(installed / "."), @@ -1804,16 +1891,24 @@ def request(self, request_id, method, params): "result": { "protocolVersion": live.MCP_PROTOCOL_VERSION, "serverInfo": {"name": "jackel-codex", "version": "0.1.0"}, - "capabilities": {"tools": {"listChanged": False}}, + "capabilities": { + "tools": {"listChanged": False}, + "resources": {"subscribe": False, "listChanged": False}, + }, }, } if method == "tools/list": return { "jsonrpc": "2.0", "id": request_id, - "result": {"tools": [ - {"name": record["name"]} - for record in self.runtime_document["tools"] - ]}, + "result": { + "tools": [ + {"name": record["name"]} + for record in self.runtime_document["tools"] + ] + + [{"name": name} for name in live.MEASUREMENT_TOOLS] + + [{"name": name} for name in live.ADVANCED_TOOLS] + + [{"name": name} for name in live.STEM_TOOLS] + }, } self.assert_tools_call(method) name = params["name"] @@ -1831,6 +1926,211 @@ def request(self, request_id, method, params): "status": "refused", "reason": "producer-refused", "detail": "outside fragment", } + elif name in live.MEASUREMENT_TOOLS: + measurement_statuses = { + "jackal_convert": "exact", + "jackal_rate_apply": "exact-given", + "jackal_percent": "exact", + "jackal_date_delta": "exact-given", + "jackal_stat": "exact", + "jackal_compare": "exact", + "jackal_scan": "checked", + } + if name == "jackal_rate_apply" and "rate_source" not in params["arguments"]: + payload = { + "status": "refused", + "reason": "undeclared-datum", + "detail": "fixture missing provenance", + "consequence_ceiling": "informational", + "non_claims": ["fixture refusal"], + } + else: + status = measurement_statuses[name] + fields = {"fixture": name} + trace = [{"tool": "jackal_exact", "status": "exact"}] + if name == "jackal_stat": + fields = { + "population_variance": "5", + "field_status": { + "population_stddev_enclosure": "formal-bounded", + }, + } + trace.append({ + "tool": "jackal_sqrt_rat_bound", + "status": "formal-bounded", + "parsed": "sqrt(x) on [5,5]", + }) + elif name == "jackal_scan": + fields = { + "numerals": [ + {"text": "10^-12"}, + {"text": "2e-12"}, + {"text": "1×10⁻¹²"}, + ], + } + trace = [] + payload = { + "status": status, + "lane": "fixture-measurement", + "parsed": name, + "fields": fields, + "delegated_to": trace, + "consequence_ceiling": "informational", + "non_claims": ["fixture non-claim"], + "identities": {"jackal_measurement_sha256": "c" * 64}, + } + if status == "exact-given": + payload["given"] = { + "source": "fixture source", + "as_of": "fixture as-of", + } + elif name == "jackal_cas": + delegated = { + "status": "exact", + "lane": "rat", + "formal": False, + "fields": {"exact": "3/10"}, + } + payload = { + "status": "exact", + "lane": "cas-route", + "formal": False, + "parsed": { + "operation": "exact", + "delegated_tool": "jackal_exact", + }, + "result": delegated, + "delegated_to": [ + {"tool": "jackal_exact", "status": "exact"} + ], + "identities": {"jackal_advanced_sha256": "d" * 64}, + "non_claims": ["fixture router adds no assurance"], + } + elif name == "jackal_graph": + payload = { + "status": "estimated", + "lane": "graph-delegated-f64-v1", + "formal": False, + "consequence_ceiling": "informational", + "fields": {"finite_sample_count": 17}, + "delegated_to": [ + {"tool": "jackal_exact", "status": "exact"}, + {"tool": "jackal_evaluate", "status": "estimated"}, + ], + "identities": {"jackal_advanced_sha256": "d" * 64}, + "non_claims": ["fixture graph visualization only"], + } + png = b"\x89PNG\r\n\x1a\nfixture" + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "content": [ + {"type": "text", "text": "fixture graph"}, + { + "type": "image", + "data": base64.b64encode(png).decode("ascii"), + "mimeType": "image/png", + }, + ], + "structuredContent": copy.deepcopy(payload), + }, + } + elif name == "jackal_hellgate_ground_state": + payload = { + "status": "bounded", + "lane": "nonlinear-barta-exact-rational-v1", + "formal": False, + "checker_verdict": "ACCEPT", + "fields": { + "eigenvalue_interval": ["-5", "-4"], + "eigenvalue_decimal_interval": ["-5", "-4"], + "interval_width": "1", + "trial_diagnostics": { + "schema": "jackal-hellgate-trial-diagnostics-v1", + "status": "bounded", + "subject": "normalized-certificate-trial-phi", + "non_claims": ["not the exact ground state u0"], + }, + "ground_state_transfer": { + "schema": "jackal-hellgate-ground-transfer-v1", + "status": "bounded", + "subject": "positive-normalized-ground-state-u0", + "method": "lambda-strong-convexity-density-transfer-v1", + "non_claims": ["does not enclose polynomial moments"], + }, + }, + "identities": { + "jackal_advanced_sha256": "d" * 64, + "hellgate_checker_sha256": "e" * 64, + "hellgate_certificate_file_sha256": "f" * 64, + }, + "theorem": {"name": "fixture comparison"}, + "assumptions": ["fixture assumption"], + "non_claims": ["bounded is not formal-bounded"], + } + elif name in live.STEM_TOOLS: + statuses = { + "jackal_matrix": "exact", + "jackal_regression": "model-based", + "jackal_probability": "model-based", + "jackal_hypothesis": "model-based", + "jackal_sensor": "exact-given", + "jackal_aerospace": "model-based", + "jackal_linked_workspace": "checked", + } + field_statuses = { + "jackal_matrix": {"determinant": "exact"}, + "jackal_regression": {"coefficients_ascending": "exact"}, + "jackal_probability": {"probability": "exact"}, + "jackal_hypothesis": {"p_value": "exact"}, + "jackal_sensor": {"population_stddev_enclosure": "formal-bounded"}, + "jackal_aerospace": {"speed_enclosure": "formal-bounded"}, + "jackal_linked_workspace": {"points.y": "estimated"}, + } + payload = { + "status": statuses[name], + "lane": "fixture-stem", + "formal": False, + "consequence_ceiling": ( + "advisory" + if name in {"jackal_hypothesis", "jackal_aerospace"} + else "informational" + ), + "parsed": {"fixture": name}, + "fields": {"fixture": name}, + "field_status": field_statuses[name], + "delegated_to": [{"tool": "jackal_exact", "status": "exact"}], + "identities": {"jackal_stem_sha256": "1" * 64}, + "non_claims": ["fixture STEM non-claim"], + } + if name == "jackal_sensor": + payload["given"] = {"input_provenance": "supplied"} + if name == "jackal_linked_workspace": + resource_text = "Pixels are not proof" + resource_uri = ( + "ui://jackal/linked-workspace/" + + hashlib.sha256(resource_text.encode("utf-8")).hexdigest() + ) + payload["fields"]["resource_uri"] = resource_uri + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "content": [ + {"type": "text", "text": "fixture workspace"}, + { + "type": "resource", + "resource": { + "uri": resource_uri, + "mimeType": "text/html", + "text": resource_text, + }, + }, + ], + "structuredContent": copy.deepcopy(payload), + }, + } elif name == "jackal_claim": payload = { "status": "ok", "root": "root-node", @@ -1853,9 +2153,9 @@ def request(self, request_id, method, params): "status": "verified", "verdict": "ACCEPT", "receipt_digest_sha256": receipt["receipt_digest_sha256"], "certificate_sha256": receipt["certificate"]["sha256"], - "checker_sha256": live.INT_CERT_CHECKER_SHA256, - "evaluator_sha256": live.INT_CERT_PRODUCER_SHA256, - "plugin_sha256": live.HERMES_BUNDLE_SHA256, + "checker_sha256": TEST_FORMAL_IDENTITIES["checker_sha256"], + "evaluator_sha256": TEST_FORMAL_IDENTITIES["evaluator_sha256"], + "plugin_sha256": TEST_FORMAL_IDENTITIES["plugin_sha256"], "enclosure": ["0", "1"], } else: @@ -1878,6 +2178,10 @@ def test_live_mcp_and_direct_comparator_share_one_sanitized_environment(self): "PATH": "/fixed/python:/usr/bin:/bin:/usr/sbin:/sbin", "JACKAL_HOME": str(runtime), } + runtime_pins = { + "package_sha256": "e" * 64, + "sha256sums_sha256": "f" * 64, + } client = mock.Mock() client_context = mock.Mock() client_context.__enter__ = mock.Mock(return_value=client) @@ -1885,12 +2189,13 @@ def test_live_mcp_and_direct_comparator_share_one_sanitized_environment(self): direct = mock.Mock(return_value={"status": "exact"}) temporary = mock.Mock() temporary.__enter__ = mock.Mock( - return_value="/private/tmp/jackel-codex-live-fixture" + return_value=str(live._fixed_temp_root() / "jackel-codex-live-fixture") ) temporary.__exit__ = mock.Mock(return_value=False) - def acceptance(*, client, runtime_document, direct_call): + def acceptance(*, client, runtime_document, direct_call, formal_identities): self.assertEqual(runtime_document, {"tools": []}) + self.assertEqual(formal_identities, TEST_FORMAL_IDENTITIES) direct_call("jackal_exact", {"expression": "1+1"}) return {"sequence": "accepted"} @@ -1898,6 +2203,13 @@ def acceptance(*, client, runtime_document, direct_call): mock.patch.object(live, "verify_wrapper", return_value="a" * 64), mock.patch.object(live, "verify_runtime"), mock.patch.object(live, "load_runtime_document", return_value={"tools": []}), + mock.patch.object( + live, "load_runtime_formal_identities", + return_value=TEST_FORMAL_IDENTITIES, + ), + mock.patch.object( + live, "effective_runtime_pins", return_value=runtime_pins, + ), mock.patch.object( live.tempfile, "TemporaryDirectory", return_value=temporary ) as temporary_directory, @@ -1917,7 +2229,7 @@ def acceptance(*, client, runtime_document, direct_call): sanitizer.assert_called_once() temporary_directory.assert_called_once_with( - prefix="jackel-codex-live-", dir=Path("/private/tmp") + prefix="jackel-codex-live-", dir=live._fixed_temp_root() ) self.assertEqual(sanitizer.call_args.args[0], runtime) mcp_client.assert_called_once_with(installed, environment) @@ -1928,6 +2240,8 @@ def acceptance(*, client, runtime_document, direct_call): environment=environment, ) self.assertEqual(report["sequence"], "accepted") + self.assertEqual(report["runtime_package_sha256"], runtime_pins["package_sha256"]) + self.assertEqual(report["runtime_tree_sha256"], runtime_pins["sha256sums_sha256"]) def test_full_strict_sequence_has_no_weaker_fallback(self): runtime_document = json.loads(RUNTIME_TOOLS.read_text(encoding="utf-8")) @@ -1937,9 +2251,15 @@ def test_full_strict_sequence_has_no_weaker_fallback(self): def direct(tool, arguments): direct_calls.append((tool, copy.deepcopy(arguments))) if tool == "jackal_exact": + exact_value = ( + "5" + if arguments["expression"] + == "((0-3)^2+(4-3)^2+(6-3)^2+(2-3)^2)/4" + else "3/10" + ) return { "status": "exact", "lane": "rat", "formal": False, - "fields": {"exact": "3/10"}, + "fields": {"exact": exact_value}, } if arguments["expression"] == "sin(x)": return formal_payload(11) @@ -1951,30 +2271,77 @@ def direct(tool, arguments): report = live.run_acceptance( client=client, runtime_document=runtime_document, direct_call=direct, + formal_identities=TEST_FORMAL_IDENTITIES, ) tool_calls = [name for name, _ in client.calls if not name.startswith("notifications/")] self.assertEqual( tool_calls, [ "jackal_exact", "jackal_integrate_bound_cert", - "jackal_integrate_bound_cert", "jackal_claim", + "jackal_integrate_bound_cert", + "jackal_convert", "jackal_rate_apply", "jackal_percent", + "jackal_date_delta", "jackal_compare", "jackal_stat", + "jackal_scan", "jackal_rate_apply", "jackal_cas", + "jackal_graph", "jackal_hellgate_ground_state", + "jackal_matrix", "jackal_matrix", "jackal_matrix", + "jackal_matrix", "jackal_matrix", "jackal_matrix", + "jackal_matrix", "jackal_regression", + "jackal_probability", "jackal_probability", + "jackal_probability", "jackal_hypothesis", + "jackal_hypothesis", "jackal_hypothesis", + "jackal_hypothesis", "jackal_sensor", "jackal_sensor", + "jackal_aerospace", "jackal_aerospace", "jackal_aerospace", + "jackal_aerospace", "jackal_aerospace", + "jackal_linked_workspace", "jackal_claim", "jackal_verify_bundle", "jackal_verify_receipt", ], ) self.assertNotIn("jackal_integrate_bound", tool_calls) self.assertEqual( [name for name, _ in direct_calls], - ["jackal_exact", "jackal_integrate_bound_cert", "jackal_integrate_bound_cert"], + [ + "jackal_exact", "jackal_integrate_bound_cert", + "jackal_integrate_bound_cert", "jackal_exact", "jackal_exact", + ], ) # Exact against the catalog actually fed in — this test feeds the repo # `plugin/hermes/tools.json`, so retyping its size here would go stale # on every surface addition while checking nothing extra. self.assertEqual(report["discovered_tool_count"], - len(runtime_document["tools"])) - self.assertGreaterEqual(report["discovered_tool_count"], live.MIN_TOOL_COUNT) + len(runtime_document["tools"]) + + len(live.MEASUREMENT_TOOLS) + + len(live.ADVANCED_TOOLS) + + len(live.STEM_TOOLS)) + self.assertGreaterEqual( + len(runtime_document["tools"]), live.MIN_RUNTIME_TOOL_COUNT + ) self.assertEqual(report["gates"], { "exact": "exact", "formal": "formal-bounded", "unsupported_formal": "producer-refused", + "measurement": { + "jackal_convert": "exact", + "jackal_rate_apply": "exact-given", + "jackal_percent": "exact", + "jackal_date_delta": "exact-given", + "jackal_compare": "exact", + "jackal_stat": "exact", + "jackal_scan": "checked", + "refusal": "undeclared-datum", + }, + "advanced": { + "cas": "exact", + "graph": "estimated", + "hellgate": "bounded", + }, + "stem": { + "matrix": "exact", + "regression": "model-based", + "probability": "model-based", + "hypothesis": "model-based", + "sensor": "exact-given", + "aerospace": "model-based", + "linked_workspace": "checked", + }, "claim_bundle": "verified", "formal_receipt": "verified", }) diff --git a/tests/codex_plugin/test_mcp_adapter.py b/tests/codex_plugin/test_mcp_adapter.py index 25d443a..92da6f7 100644 --- a/tests/codex_plugin/test_mcp_adapter.py +++ b/tests/codex_plugin/test_mcp_adapter.py @@ -22,6 +22,9 @@ sys.dont_write_bytecode = True from plugins.jackel.mcp import server as adapter +from plugins.jackel.mcp import advanced +from plugins.jackel.mcp import measurement +from plugins.jackel.mcp import stem from plugins.jackel.scripts import provision_runtime as real_provisioner @@ -259,21 +262,42 @@ def setUp(self): import sys import time - if len(sys.argv) != 4 or sys.argv[1] != "call": + stdio_mode = len(sys.argv) == 2 and sys.argv[1] == "stdio" + request_id = None + if stdio_mode: + raw = sys.stdin.buffer.readline() + trailing = sys.stdin.buffer.read() + if not raw or trailing: + raise SystemExit(64) + request = json.loads(raw) + request_id = request.get("id") + name = request.get("method") + arguments = request.get("params") + elif len(sys.argv) == 4 and sys.argv[1] == "call": + name = sys.argv[2] + arguments = json.loads(sys.argv[3]) + else: raise SystemExit(64) - name = sys.argv[2] - arguments = json.loads(sys.argv[3]) allowed = {{"payload", "mode", "pid_file", "release_file"}} if ( + not isinstance(arguments, dict) + or "payload" not in arguments or not isinstance(arguments.get("payload"), dict) or any(key not in allowed for key in arguments) ): - print(json.dumps({{ + refusal = {{ "status": "refused", "reason": "plugin-args-schema", "arguments": arguments, - }}, sort_keys=True), flush=True) + }} + if stdio_mode: + refusal = {{ + "jsonrpc": "2.0", "id": request_id, "result": refusal, + }} + print(json.dumps(refusal, sort_keys=True), flush=True) + raise SystemExit(0) + print(json.dumps(refusal, sort_keys=True), flush=True) raise SystemExit(1) mode = arguments.get("mode", "echo") payload = arguments.get("payload", {{}}) @@ -403,14 +427,20 @@ def exit_on_term(unused_signum, unused_frame): print(json.dumps({{"status": "checked", "blob": "x" * 8192}}), flush=True) raise SystemExit(0) - if mode == "refused": + if mode == "accept-large": + result = {{"status": "checked", "accepted": True}} + elif mode == "refused": result = {{"status": "refused", "reason": "fixture-refusal", "input": payload}} elif mode == "indeterminate": result = {{"status": "indeterminate", "reason": "fixture-indeterminate", "input": payload}} else: result = payload + if stdio_mode: + result = {{"jsonrpc": "2.0", "id": request_id, "result": result}} print(json.dumps(result, ensure_ascii=False, sort_keys=False), flush=True) - if mode in ("refused", "indeterminate", "ok-rc1", "unknown-rc1"): + if stdio_mode and mode == "unknown-rc1": + raise SystemExit(1) + if not stdio_mode and mode in ("refused", "indeterminate", "ok-rc1", "unknown-rc1"): raise SystemExit(1) """ ), @@ -487,8 +517,10 @@ async def test_backend_call_ignores_hostile_caller_path_python3(self): fake_python.chmod(0o755) entry = Path(self.temporary.name) / "legitimate-backend.py" entry.write_text( - "import json\n" - "print(json.dumps({'status': 'checked', 'origin': 'selected-python'}))\n", + "import json, sys\n" + "request = json.loads(sys.stdin.buffer.read())\n" + "print(json.dumps({'jsonrpc': '2.0', 'id': request['id'], " + "'result': {'status': 'checked', 'origin': 'selected-python'}}))\n", encoding="utf-8", ) launcher = Path(self.temporary.name) / "bare-python-launcher" @@ -520,6 +552,35 @@ async def test_backend_call_ignores_hostile_caller_path_python3(self): ) self.assertFalse(attacked.exists()) + async def test_one_request_can_launch_multiple_serial_backend_processes(self): + state = adapter._CallState(request_id="multi-delegation") + first = self.server._invoke_backend_sync( + state, "jackal_echo", {"payload": {"delegation": "first"}}, + ) + second = self.server._invoke_backend_sync( + state, "jackal_echo", {"payload": {"delegation": "second"}}, + ) + + self.assertEqual(first, {"delegation": "first"}) + self.assertEqual(second, {"delegation": "second"}) + self.assertTrue(state.reaped) + self.assertIsNone(state.process) + self.assertIsNone(state.runner) + + async def test_replay_sized_backend_arguments_are_streamed_over_stdin(self): + response = await self.server.handle_message( + self._call( + "large-backend-input", + payload={"receipt": "x" * adapter.MAX_CATALOG_BYTES}, + mode="accept-large", + ) + ) + + self.assertEqual( + response["result"]["structuredContent"], + {"status": "checked", "accepted": True}, + ) + async def test_active_call_flood_refuses_ordinary_busy_and_recovers(self): limited = adapter.MCPServer( runtime_root=self.runtime, @@ -850,7 +911,13 @@ async def test_initialize_ping_notification_and_full_tools_list(self): self.assertEqual(initialized["jsonrpc"], "2.0") self.assertEqual(initialized["id"], "init-1") self.assertEqual(initialized["result"]["protocolVersion"], adapter.LATEST_PROTOCOL_VERSION) - self.assertEqual(initialized["result"]["capabilities"], {"tools": {"listChanged": False}}) + self.assertEqual( + initialized["result"]["capabilities"], + { + "tools": {"listChanged": False}, + "resources": {"subscribe": False, "listChanged": False}, + }, + ) self.assertEqual(initialized["result"]["serverInfo"]["name"], "jackel-codex") self.assertIsNone( @@ -928,8 +995,11 @@ async def test_backend_owns_known_tool_argument_schema_refusals_with_exact_parit arguments, ensure_ascii=False, allow_nan=False, sort_keys=True, separators=(",", ":"), ) - direct_process = await asyncio.to_thread( - subprocess.run, + # Keep this tiny fixture invocation synchronous. The host's + # current asyncio global executor does not reliably shut down, + # and the production adapter intentionally owns its worker + # threads instead of depending on that global executor. + direct_process = subprocess.run( [str(self.launcher), "call", "jackal_echo", compact], cwd=self.runtime, capture_output=True, text=True, check=False, ) @@ -1037,6 +1107,22 @@ async def test_malformed_requests_and_adapter_owned_argument_shape_fail_closed(s self.assertEqual(unterminated["error"]["code"], -32600) self.assertIsNone(unterminated["id"]) + replay_sized = ( + json.dumps( + { + "jsonrpc": "2.0", + "id": 10, + "method": "ping", + "params": {"_meta": {"receipt_fixture": "x" * adapter.MAX_CATALOG_BYTES}}, + }, + separators=(",", ":"), + ).encode("utf-8") + + b"\n" + ) + self.assertLess(len(replay_sized), adapter.MAX_REQUEST_LINE_BYTES) + replay_response = await self.server.handle_line(replay_sized) + self.assertEqual(replay_response["result"], {}) + async def test_deep_json_and_recursion_failure_are_bounded_parse_errors(self): expected_depth_limit = getattr(adapter, "MAX_JSON_DEPTH", 64) nested = ( @@ -1617,7 +1703,7 @@ def mark_reaped(unused_process): with ( mock.patch.object(adapter.subprocess, "Popen", return_value=setup_process), - mock.patch.object(adapter.socket, "socketpair", side_effect=OSError("fixture")), + mock.patch.object(adapter.os, "pipe", side_effect=OSError("fixture")), mock.patch.object( setup_runner, "_terminate_and_reap", @@ -1885,8 +1971,19 @@ def setUp(self): SHA256SUMS_SHA256="f1f794ccd2ba331e6188840cfc089180cdcd744f23c1880f8364a81b230c1a28", SELFTEST_TIMEOUT=30.0, SELFTEST_OUTPUT_LIMIT=65536, + effective_release_pins=lambda *a, **k: { + "epoch": "v1.7.0", + "asset": "jackal-v1.7.0-macos-arm64.tar.gz", + "package_size": 118862060, + "package_sha256": + "21c7ede586f30a58772f321f7dbb36ab66213e199785489f99133710ac56096e", + "package_directory": "jackal-v1.7.0-macos-arm64", + "sha256sums_sha256": + "f1f794ccd2ba331e6188840cfc089180cdcd744f23c1880f8364a81b230c1a28", + }, default_locator_path=lambda: self.root / "locator.json", validate_host=mock.Mock(return_value=None), + reap_orphaned_runtime_snapshots=mock.Mock(return_value=()), runtime_subprocess_environment=mock.Mock( return_value=real_provisioner.runtime_subprocess_environment({}) ), @@ -1975,6 +2072,7 @@ def test_production_builder_verifies_plugin_and_runtime_with_wrapper_pins(self): plugin_root, plugin_root / "PLUGIN_IDENTITY.sha256" ) self.provisioner.validate_host.assert_called_once_with() + self.provisioner.reap_orphaned_runtime_snapshots.assert_called_once_with() self.provisioner.runtime_subprocess_environment.assert_called_once_with( {"JACKAL_HOME": str(self.runtime)} ) @@ -2000,6 +2098,130 @@ def test_production_builder_verifies_plugin_and_runtime_with_wrapper_pins(self): asyncio.run(server.close()) self.snapshot_owners[0].close.assert_called_once_with() + def test_production_builder_passes_only_an_explicit_private_snapshot_parent(self): + plugin_root = self.root / "private-parent-plugin" + plugin_root.mkdir() + (plugin_root / "PLUGIN_IDENTITY.sha256").write_text("fixture\n") + private_parent = self.root / "private-tmpfs" + + server = adapter.build_production_server( + plugin_root=plugin_root, + environ={"JACKAL_HOME": str(self.runtime)}, + snapshot_parent=private_parent, + provisioner=self.provisioner, + identity_verifier=mock.Mock(return_value=()), + runtime_validator=mock.Mock(return_value={}), + ) + + self.provisioner.create_runtime_snapshot.assert_called_once_with( + self.runtime, + timeout=self.provisioner.SELFTEST_TIMEOUT, + output_limit=self.provisioner.SELFTEST_OUTPUT_LIMIT, + expected_tree_sha256=self.provisioner.SHA256SUMS_SHA256, + temporary_parent=os.fspath(private_parent), + ) + asyncio.run(server.close()) + + def test_production_builder_refuses_orphan_cleanup_failure_before_runtime_copy(self): + plugin_root = self.root / "reaper-failure-plugin" + plugin_root.mkdir() + (plugin_root / "PLUGIN_IDENTITY.sha256").write_text("fixture\n") + self.provisioner.reap_orphaned_runtime_snapshots.side_effect = OSError( + "fixture cleanup failure" + ) + runtime_validator = mock.Mock(return_value={}) + + with self.assertRaisesRegex(adapter.StartupError, "orphaned runtime snapshot"): + adapter.build_production_server( + plugin_root=plugin_root, + environ={"JACKAL_HOME": str(self.runtime)}, + provisioner=self.provisioner, + identity_verifier=mock.Mock(return_value=()), + runtime_validator=runtime_validator, + ) + + runtime_validator.assert_not_called() + self.provisioner.create_runtime_snapshot.assert_not_called() + + def test_linux_namespace_wrapper_execs_mount_and_pid_isolation_after_probe(self): + probe = mock.Mock(returncode=0) + with ( + mock.patch.object(adapter.sys, "platform", "linux"), + mock.patch.object( + adapter, + "_fixed_executable", + side_effect=("/usr/bin/unshare", "/usr/bin/true"), + ), + mock.patch.object( + adapter, "_mount_namespace_identity", return_value="mnt:[123]" + ), + mock.patch.object(adapter.subprocess, "run", return_value=probe) as run, + mock.patch.object(adapter.os, "execv", side_effect=OSError("fixture")) as execv, + ): + self.assertFalse(adapter._exec_in_private_snapshot_namespace()) + + probe_command = run.call_args.args[0] + command = execv.call_args.args[1] + self.assertEqual(probe_command[-1], "/usr/bin/true") + self.assertIn("--mount", command) + self.assertIn("--pid", command) + self.assertIn("--fork", command) + self.assertIn("--kill-child=SIGKILL", command) + self.assertIn("--forward-signals", command) + self.assertIn(adapter.PRIVATE_NAMESPACE_FLAG, command) + + def test_linux_namespace_probe_or_private_mount_failure_uses_exact_reaper_fallback(self): + with ( + mock.patch.object(adapter.sys, "platform", "linux"), + mock.patch.object( + adapter, + "_fixed_executable", + side_effect=("/usr/bin/unshare", "/usr/bin/true"), + ), + mock.patch.object( + adapter, "_mount_namespace_identity", return_value="mnt:[123]" + ), + mock.patch.object( + adapter.subprocess, "run", return_value=mock.Mock(returncode=1) + ), + mock.patch.object(adapter.os, "execv") as execv, + ): + self.assertFalse(adapter._exec_in_private_snapshot_namespace()) + execv.assert_not_called() + + with ( + mock.patch.object( + adapter.sys, + "argv", + [adapter.__file__, adapter.PRIVATE_NAMESPACE_FLAG, "mnt:[123]"], + ), + mock.patch.object( + adapter, + "_prepare_private_snapshot_parent", + side_effect=adapter.StartupError("fixture mount refusal"), + ), + mock.patch.object(adapter, "_run_production_server", return_value=17) as run, + mock.patch.object(adapter, "_exec_in_private_snapshot_namespace") as enter, + ): + self.assertEqual(adapter.main(), 17) + run.assert_called_once_with(None) + enter.assert_not_called() + + def test_private_namespace_child_arguments_are_exact(self): + self.assertEqual( + adapter._parse_namespace_child( + [adapter.PRIVATE_NAMESPACE_FLAG, "mnt:[123]"] + ), + "mnt:[123]", + ) + for arguments in ( + ["--other", "mnt:[123]"], + [adapter.PRIVATE_NAMESPACE_FLAG, "forged"], + [adapter.PRIVATE_NAMESPACE_FLAG, "mnt:[123]", "extra"], + ): + with self.subTest(arguments=arguments), self.assertRaises(adapter.StartupError): + adapter._parse_namespace_child(arguments) + def test_production_builder_refuses_unsupported_host_before_any_runtime_access(self): plugin_root = self.root / "host-guard-plugin" plugin_root.mkdir() @@ -2025,11 +2247,16 @@ def test_production_builder_refuses_unsupported_host_before_any_runtime_access(s catalog.assert_not_called() validator.assert_not_called() - def test_production_bootstraps_both_wrapper_modules_from_pinned_bytes(self): + def test_production_bootstraps_all_wrapper_modules_from_pinned_bytes(self): plugin_root = self.root / "verified-wrapper-plugin" plugin_root.mkdir() (plugin_root / "PLUGIN_IDENTITY.sha256").write_text("fixture\n") inventory = { + "mcp/advanced.py": "d" * 64, + "mcp/certificates/hellgate_v1.json.zlib": "f" * 64, + "mcp/hellgate_verify.py": "e" * 64, + "mcp/measurement.py": "c" * 64, + "mcp/stem.py": "1" * 64, "scripts/provision_runtime.py": "a" * 64, "scripts/verify_plugin.py": "b" * 64, } @@ -2041,13 +2268,53 @@ def test_production_bootstraps_both_wrapper_modules_from_pinned_bytes(self): verify_manifest=mock.Mock(return_value=records) ) runtime_validator = mock.Mock(return_value={}) + checker_module = types.SimpleNamespace( + VerificationRefusal=type("FixtureRefusal", (Exception,), {}), + verify_bytes=mock.Mock( + return_value={ + "status": "bounded", + "checker_verdict": "ACCEPT", + "formal": False, + "fields": { + "trial_diagnostics": { + "schema": "jackal-hellgate-trial-diagnostics-v1", + "status": "bounded", + "subject": "normalized-certificate-trial-phi", + "non_claims": ["not the exact ground state u0"], + }, + "ground_state_transfer": { + "schema": "jackal-hellgate-ground-transfer-v1", + "status": "bounded", + "subject": "positive-normalized-ground-state-u0", + "method": "lambda-strong-convexity-density-transfer-v1", + "non_claims": ["does not enclose polynomial moments"], + }, + }, + } + ), + ) with ( mock.patch.object(adapter, "_read_identity_inventory", return_value=inventory), mock.patch.object( adapter, "_load_verified_module", - side_effect=(verifier_module, self.provisioner), + side_effect=( + verifier_module, + measurement, + advanced, + checker_module, + stem, + self.provisioner, + ), ) as loader, + mock.patch.object( + adapter, + "_read_verified_plugin_blob", + return_value=(b"compressed-fixture", "f" * 64), + ), + mock.patch.object( + adapter, "_decompress_certificate", return_value=b"certificate-fixture" + ), ): server = adapter.build_production_server( plugin_root=plugin_root, @@ -2062,6 +2329,22 @@ def test_production_bootstraps_both_wrapper_modules_from_pinned_bytes(self): plugin_root, "scripts/verify_plugin.py", "jackel_codex_verify_plugin", inventory, ), + mock.call( + plugin_root, "mcp/measurement.py", + "jackel_codex_measurement", inventory, + ), + mock.call( + plugin_root, "mcp/advanced.py", + "jackel_codex_advanced", inventory, + ), + mock.call( + plugin_root, "mcp/hellgate_verify.py", + "jackel_codex_hellgate_verify", inventory, + ), + mock.call( + plugin_root, "mcp/stem.py", + "jackel_codex_stem", inventory, + ), mock.call( plugin_root, "scripts/provision_runtime.py", "jackel_codex_provision_runtime", inventory, @@ -2071,7 +2354,13 @@ def test_production_bootstraps_both_wrapper_modules_from_pinned_bytes(self): verifier_module.verify_manifest.assert_called_once_with( plugin_root, plugin_root / "PLUGIN_IDENTITY.sha256" ) - self.assertEqual(len(server.tool_definitions), RUNTIME_TOOL_COUNT) + self.assertEqual( + len(server.tool_definitions), + RUNTIME_TOOL_COUNT + + adapter.EXPECTED_MEASUREMENT_TOOL_COUNT + + adapter.EXPECTED_ADVANCED_TOOL_COUNT + + adapter.EXPECTED_STEM_TOOL_COUNT, + ) asyncio.run(server.close()) def test_snapshot_is_cleaned_when_post_copy_startup_refuses(self): @@ -2130,7 +2419,9 @@ def test_calls_remain_bound_to_snapshot_after_original_launcher_backend_and_aba_ ) backend_bytes = ( b"#!/bin/sh\n" - b"echo '{\"status\":\"checked\",\"origin\":\"snapshot\"}'\n" + b"IFS= read -r request\n" + b"echo '{\"jsonrpc\":\"2.0\",\"id\":\"jackal-adapter-backend\"," + b"\"result\":{\"status\":\"checked\",\"origin\":\"snapshot\"}}'\n" ) files = { "MANIFEST.sha256": b"fixture manifest\n", @@ -2165,8 +2456,15 @@ def test_calls_remain_bound_to_snapshot_after_original_launcher_backend_and_aba_ EPOCH="v1.7.0", ASSET="fixture.tar.gz", PACKAGE_SIZE=123, PACKAGE_SHA256="c" * 64, SHA256SUMS_SHA256=hashlib.sha256(checksums).hexdigest(), + effective_release_pins=lambda *a, **k: { + "epoch": "v1.7.0", "asset": "fixture.tar.gz", + "package_size": 123, "package_sha256": "c" * 64, + "package_directory": "fixture", + "sha256sums_sha256": hashlib.sha256(checksums).hexdigest(), + }, SELFTEST_TIMEOUT=2.0, SELFTEST_OUTPUT_LIMIT=65536, validate_host=mock.Mock(return_value=None), + reap_orphaned_runtime_snapshots=real_provisioner.reap_orphaned_runtime_snapshots, validate_runtime=real_provisioner.validate_runtime, create_runtime_snapshot=real_provisioner.create_runtime_snapshot, runtime_subprocess_environment=real_provisioner.runtime_subprocess_environment, diff --git a/tests/codex_plugin/test_measurement.py b/tests/codex_plugin/test_measurement.py new file mode 100644 index 0000000..5eece75 --- /dev/null +++ b/tests/codex_plugin/test_measurement.py @@ -0,0 +1,159 @@ +import copy +import unittest + +from plugins.jackel.mcp import measurement +from plugins.jackel.mcp import server as adapter + + +FIXTURE_IDENTITY = "a" * 64 + + +class MeasurementSurfaceTests(unittest.TestCase): + def test_identity_pinned_definitions_are_one_closed_surface(self): + definitions = adapter.build_measurement_tool_definitions(measurement) + + self.assertEqual( + {definition["name"] for definition in definitions}, + adapter.MEASUREMENT_TOOL_NAMES, + ) + self.assertEqual(len(definitions), adapter.EXPECTED_MEASUREMENT_TOOL_COUNT) + for definition in definitions: + with self.subTest(tool=definition["name"]): + self.assertIs(definition["inputSchema"]["additionalProperties"], False) + self.assertEqual( + definition["annotations"], + { + "readOnlyHint": True, + "destructiveHint": False, + "idempotentHint": True, + "openWorldHint": False, + }, + ) + + def test_definition_tampering_refuses_before_merge(self): + original = measurement.tool_definitions + definitions = original() + definitions[0] = copy.deepcopy(definitions[0]) + definitions[0]["name"] = "jackal_forged" + measurement.tool_definitions = lambda: definitions + try: + with self.assertRaises(adapter.CatalogError): + adapter.build_measurement_tool_definitions(measurement) + finally: + measurement.tool_definitions = original + + def test_scientific_notation_is_not_split(self): + text = ( + "Bounds: 10^-12, 10**-12, 10^{-12}, 10⁻¹², 2e-12, " + "and 1×10⁻¹²." + ) + + def no_kernel_call(unused_name, unused_arguments): + self.fail("the lexical scan must not call the arithmetic runtime") + + body = measurement.dispatch_integrated( + "jackal_scan", + {"text": text, "context_window": 60}, + no_kernel_call, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "checked") + self.assertEqual( + [item["text"] for item in body["fields"]["numerals"]], + ["10^-12", "10**-12", "10^{-12}", "10⁻¹²", "2e-12", "1×10⁻¹²"], + ) + self.assertEqual( + body["identities"]["jackal_measurement_sha256"], FIXTURE_IDENTITY + ) + + def test_integer_variance_uses_canonical_point_interval(self): + exact_values = { + "(0) + (4) + (6) + (2)": "12", + "(12) / 4": "3", + "((2) + (4)) / 2": "3", + "((0) - (3))^2 + ((4) - (3))^2 + ((6) - (3))^2 + ((2) - (3))^2": "20", + "(20) / 4": "5", + "(6) - (0)": "6", + "(20) / 3": "20/3", + "2": "2", + "3": "3", + } + calls = [] + + def kernel_call(name, arguments): + calls.append((name, copy.deepcopy(arguments))) + if name == "jackal_exact": + expression = arguments["expression"] + value = exact_values[expression] + return { + "status": "exact", + "fields": {"parsed": expression, "exact": value, "approx": value}, + "identities": {"evaluator_sha256": "b" * 64}, + } + if name == "jackal_sqrt_rat_bound": + self.assertEqual(arguments["input_lo"], "5") + self.assertEqual(arguments["input_hi"], "5") + return { + "status": "formal-bounded", + "checker_rerun": "ACCEPT", + "checker_output": "output 2 3", + } + self.fail(f"unexpected delegated tool {name}") + + body = measurement.dispatch_integrated( + "jackal_stat", + {"sample": [0, 4, 6, 2], "include_stddev": True}, + kernel_call, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "exact") + self.assertEqual(body["fields"]["population_variance"], "5") + self.assertEqual( + body["fields"]["field_status"]["population_stddev_enclosure"], + "formal-bounded", + ) + sqrt_calls = [arguments for name, arguments in calls if name == "jackal_sqrt_rat_bound"] + self.assertEqual(sqrt_calls, [{"expression": "sqrt(x)", "input_lo": "5", "input_hi": "5"}]) + self.assertNotIn("5/1", repr(calls)) + + def test_kernel_refusal_propagates_without_fallback(self): + calls = [] + + def kernel_call(name, arguments): + calls.append((name, arguments)) + return { + "status": "refused", + "reason": "fixture-refusal", + "detail": "fixture detail", + } + + body = measurement.dispatch_integrated( + "jackal_percent", + {"op": "of", "a": "10", "b": "20"}, + kernel_call, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "refused") + self.assertEqual(body["reason"], "kernel-refused:fixture-refusal") + self.assertEqual(len(calls), 1) + self.assertNotIn("fields", body) + + def test_json_float_sample_refuses_before_kernel_call(self): + calls = [] + body = measurement.dispatch_integrated( + "jackal_stat", + {"sample": [0.1]}, + lambda name, arguments: calls.append((name, arguments)), + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "refused") + self.assertEqual(body["reason"], "args") + self.assertEqual(calls, []) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/codex_plugin/test_plugin_metadata.py b/tests/codex_plugin/test_plugin_metadata.py index 478a709..f9fec30 100644 --- a/tests/codex_plugin/test_plugin_metadata.py +++ b/tests/codex_plugin/test_plugin_metadata.py @@ -16,7 +16,8 @@ SKILL_PATH = PLUGIN_ROOT / "skills" / "jackel" / "SKILL.md" IDENTITY_PATH = PLUGIN_ROOT / "PLUGIN_IDENTITY.sha256" README_PATH = PLUGIN_ROOT / "README.md" -LAUNCHER_PATH = PLUGIN_ROOT / "scripts" / "launch_mcp.zsh" +LAUNCHER_PATH = PLUGIN_ROOT / "scripts" / "launch_mcp.sh" +ZSH_LAUNCHER_PATH = PLUGIN_ROOT / "scripts" / "launch_mcp.zsh" SERVER_PATH = PLUGIN_ROOT / "mcp" / "server.py" WORKFLOW_PATH = REPOSITORY_ROOT / ".github" / "workflows" / "jackal-codex-plugin.yml" DESIGN_PATH = ( @@ -34,8 +35,10 @@ / "2026-08-17-jackel-codex-plugin.md" ) APPROVED_SKILL_DESCRIPTION = ( - "Route claim-aware computation, domain-pack, and Anubis program evidence " - "through JACKAL without overstating assurance." + "Route evidence-aware CAS, graphing, THOTH measurement, exact-field " + "matrices, statistical models, sensors, aerospace workflows, linked views, " + "claim bundles, nonlinear certificates, and Anubis program evidence through " + "JACKAL without overstating assurance." ) @@ -105,7 +108,7 @@ def test_legacy_ids_remain_jackel_but_public_product_is_jackal(self): ) self.assertEqual(manifest["name"], "jackel") - self.assertEqual(manifest["interface"]["displayName"], "JACKAL") + self.assertEqual(manifest["interface"]["displayName"], "JACKAL + THOTH") self.assertIn("JACKAL", manifest["description"]) self.assertIn("JACKAL", manifest["interface"]["longDescription"]) self.assertIn("JACKAL", manifest["interface"]["defaultPrompt"][0]) @@ -126,7 +129,7 @@ def test_legacy_ids_remain_jackel_but_public_product_is_jackal(self): ) self.assertEqual(set(mcp["mcpServers"]), {"jackel"}) self.assertEqual(self.parse_skill_frontmatter(skill)["name"], "jackel") - self.assertIn("# JACKAL numerical-trust operator", skill) + self.assertIn("# JACKAL + THOTH numerical-trust operator", skill) self.assertNotIn("JACKEL", skill) server = SERVER_PATH.read_text(encoding="utf-8") self.assertIn("Preserve JACKAL status", server) @@ -218,46 +221,85 @@ def test_jackel_plugin_metadata_contract(self): self.assertEqual(marketplace["interface"]["displayName"], "Anubis Quantum Cipher") interface = manifest["interface"] - self.assertEqual(interface["displayName"], "JACKAL") - self.assertEqual(interface["shortDescription"], "Claim-aware computation with explicit evidence classes") + self.assertEqual(interface["displayName"], "JACKAL + THOTH") + self.assertEqual( + interface["shortDescription"], + "Evidence-aware CAS, linked STEM views, and THOTH measurement", + ) self.assertEqual(interface["developerName"], "Anubis Quantum Cipher") self.assertEqual(interface["category"], "Productivity") self.assertEqual(interface["capabilities"], ["Interactive"]) self.assertEqual(interface["websiteURL"], "https://github.com/AnubisQuantumCipher/jackal") + self.assertEqual(interface["brandColor"], "#D51F2D") + self.assertEqual( + interface["screenshots"], + [ + "./assets/jackal-thoth-hellgate-graph.png", + "./assets/jackal-linked-evidence-workspace.png", + ], + ) self.assertEqual( interface["defaultPrompt"], [ - "Classify and verify this numerical claim with JACKAL.", - "Find the strongest supported bound and refuse any silent downgrade.", - "Verify this receipt or claim bundle against my pinned expectations.", - "Verify this Anubis Safe program-evidence package without executing its artifact.", + "Use JACKAL + THOTH to solve and classify this calculation.", + "Graph this expression and separate visualization from evidence.", + "Open a linked symbolic, numeric, graph, table, and evidence workspace for this expression.", + "Solve this matrix or regression workflow while preserving exact fields and model assumptions.", + "Analyze this supplied sensor batch or aerospace model without upgrading its provenance or physical assumptions.", + "Replay the HELLGATE nonlinear ground-state certificate without promoting its assurance.", ], ) expected_long_description = ( - "Expose JACKAL's 41-tool v1.7.3 release runtime through Codex. " - "The MCP adapter copies the parsed runtime result object into " - "structuredContent unchanged; its only adapter-local tool result is " - "status=refused reason=plugin-busy. Runtime result and assurance " - "vocabulary: ok, exact, structural-exact, formal-bounded, bounded, " - "checked, estimated, model-based, verified, " + "Expose one unified 58-tool JACKAL surface through Codex: the sealed " + "41-tool v1.7.3 release runtime, seven identity-pinned THOTH " + "measurement/provenance tools, three identity-pinned CAS, graph, and " + "nonlinear-certificate tools, and seven identity-pinned STEM workflows " + "for matrices, regression, probability, hypothesis testing, supplied " + "sensor batches, aerospace models, and a professional linked evidence " + "workspace. THOTH is a named JACKAL subsystem, never a separate server " + "or arithmetic authority. Every reported numeric field in the additive " + "workflows delegates to an admitted JACKAL lane; wrapper orchestration " + "and UI rendering add no mathematical assurance. The adapter copies each " + "parsed sealed-runtime result object into structuredContent unchanged. " + "For additive results it deep-copies the result and removes only the " + "identity-validated _mcp_content transport envelope before returning " + "structuredContent; its only transport-local refusal is status=refused " + "reason=plugin-busy. Graph coordinates are exact rational JACKAL results, " + "sampled values and pixels remain estimated visualization, and refused " + "samples break the curve. The fixed HELLGATE lane replays an independent " + "exact-rational certificate as bounded, never formal-bounded. Result " + "vocabulary: ok, exact, exact-given, structural-exact, formal-bounded, " + "bounded, checked, estimated, model-based, verified, " "verified-program-evidence, verified-program-receipt, indeterminate, " - "and refused. Formal-bounded is limited to checker-admitted fragments; " - "program evidence leaves construct-totality, source, and runtime " - "residuals open. Requires Apple Silicon macOS and Python >=3.10 at " - "/opt/homebrew/bin/python3 (install with brew install python)." + "refused. Field statuses, assumptions, identities, route traces, refusal " + "reasons, consequence ceilings, and non-claims remain first-class. " + "Requires a host-admitted pinned runtime and Python >=3.10 at a fixed " + "launcher-approved path." ) self.assertEqual(interface["longDescription"], expected_long_description) - self.assertIn(f"- `interface.longDescription`: `{expected_long_description}`", DESIGN_PATH.read_text(encoding="utf-8")) + design = DESIGN_PATH.read_text(encoding="utf-8") + self.assertIn("unified 58-tool surface", design) + self.assertIn("graph visualization", design) self.assertRegex(manifest["version"], r"^0\.1\.0\+codex\.\d{14}$") - self.assertEqual(manifest["description"], "Expose JACKAL's claim-aware computation, domain-pack, and program-evidence kernel to Codex.") + self.assertEqual( + manifest["description"], + "Expose JACKAL's evidence-aware CAS, professional linked graphing, " + "integrated THOTH measurement, matrices, statistical models, sensors, " + "aerospace workflows, nonlinear certificates, and program evidence to Codex.", + ) self.assertEqual(manifest["author"], {"name": "Anubis Quantum Cipher", "url": "https://github.com/AnubisQuantumCipher"}) self.assertEqual(manifest["homepage"], "https://github.com/AnubisQuantumCipher/jackal") self.assertEqual(manifest["repository"], "https://github.com/AnubisQuantumCipher/jackal") self.assertEqual(manifest["license"], "MIT") self.assertEqual( manifest["keywords"], - ["jackel", "mathematics", "numerical-trust", "formal-verification", "evidence", "mcp"], + [ + "jackel", "thoth", "mathematics", "computer-algebra", + "graphing", "linked-views", "matrices", "regression", + "probability", "sensors", "aerospace", "symbolic-algebra", + "numerical-trust", "formal-verification", "evidence", "mcp", + ], ) self.assertEqual(manifest["skills"], "./skills/") self.assertEqual(manifest["mcpServers"], "./.mcp.json") @@ -270,11 +312,9 @@ def test_jackel_plugin_metadata_contract(self): for forbidden in ( "privacyPolicyURL", "termsOfServiceURL", - "brandColor", "composerIcon", "logo", "logoDark", - "screenshots", ): self.assertNotIn(forbidden, interface) @@ -283,8 +323,8 @@ def test_jackel_plugin_metadata_contract(self): { "mcpServers": { "jackel": { - "command": "/bin/zsh", - "args": ["./scripts/launch_mcp.zsh"], + "command": "/bin/sh", + "args": ["./scripts/launch_mcp.sh"], "cwd": ".", "env_vars": ["JACKAL_HOME"], "tool_timeout_sec": 3700, @@ -319,9 +359,11 @@ def test_jackel_plugin_metadata_contract(self): "error estimate is not a bound", "Source-to-native refinement remains open and unclaimed", "Run a weaker lane only when the caller explicitly requests one", - "Apple Silicon macOS only", - "Do not bypass the Darwin/arm64 host guard", - "Python >=3.10 at `/opt/homebrew/bin/python3`", + "Darwin/arm64, Linux/aarch64, and Linux/x86_64", + "Do not bypass the host guard", + "Linux/aarch64 has an exact host-specific pin", + "Linux/x86_64 has no", + "Python >=3.10", "brew install python", ): self.assertIn(phrase, skill) @@ -337,10 +379,20 @@ def test_every_shipped_plugin_path_exists_and_is_identity_governed(self): mcp = self.load_json(MCP_PATH)["mcpServers"]["jackel"] referenced = { "README.md", + "assets/jackal-thoth-hellgate-graph.png", + "assets/jackal-linked-evidence-workspace.png", ".codex-plugin/plugin.json", manifest["mcpServers"].removeprefix("./"), "mcp/server.py", + "mcp/measurement.py", + "mcp/advanced.py", + "mcp/stem.py", + "runtime_manifest_baseline_v173.json", + "mcp/hellgate_verify.py", + "mcp/certificates/README.md", + "mcp/certificates/hellgate_v1.json.zlib", "scripts/provision_runtime.py", + "scripts/launch_mcp.sh", "scripts/launch_mcp.zsh", "scripts/verify_plugin.py", "skills/jackel/SKILL.md", @@ -352,7 +404,7 @@ def test_every_shipped_plugin_path_exists_and_is_identity_governed(self): self.assertEqual(identity_paths, referenced) for relative in referenced: self.assertTrue((PLUGIN_ROOT / relative).is_file(), relative) - self.assertEqual(mcp["args"], ["./scripts/launch_mcp.zsh"]) + self.assertEqual(mcp["args"], ["./scripts/launch_mcp.sh"]) self.assertFalse( any( path.name.endswith(".tar.gz") or path.name.startswith("jackal-v") @@ -364,9 +416,19 @@ def test_readme_documents_release_install_discovery_and_boundaries(self): text = README_PATH.read_text(encoding="utf-8") for required in ( "41-tool", + "58-tool", + "THOTH", + "jackal_graph", + "jackal_hellgate_ground_state", + "jackal_matrix", + "jackal_regression", + "jackal_sensor", + "jackal_aerospace", + "jackal_linked_workspace", + "plugin-card screenshot", "v1.7.3 release", "release/capability_inventory_v1.json", - "/bin/zsh scripts/launch_mcp.zsh provision", + "/bin/sh scripts/launch_mcp.sh provision", "codex mcp list", "jackal_claim", "jackal_verify_receipt", @@ -390,7 +452,7 @@ def test_skill_documents_canonical_exact_claim_step_shape(self): def test_launcher_uses_only_explicit_absolute_python_candidates_and_exact_flags(self): mcp = self.load_json(MCP_PATH)["mcpServers"]["jackel"] - self.assertEqual(mcp["command"], "/bin/zsh") + self.assertEqual(mcp["command"], "/bin/sh") source = LAUNCHER_PATH.read_text(encoding="utf-8") self.assertIn("/opt/homebrew/bin/python3", source) self.assertIn("/usr/local/bin/python3", source) @@ -404,7 +466,7 @@ def test_launcher_uses_only_explicit_absolute_python_candidates_and_exact_flags( '"WEXITED"', '"WNOHANG"', '"WNOWAIT"', '"CLD_EXITED"', '"CLD_KILLED"', '"CLD_DUMPED"', '"killpg"', '"set_blocking"', '"socketpair"', "ctypes.CDLL", - '"renameatx_np"', "selectors.DefaultSelector", + '"renameatx_np"', '"renameat2"', "selectors.DefaultSelector", "signal.setitimer", "signal.getitimer", "signal.ITIMER_REAL", "signal.SIGALRM", "tarfile.open", "urllib.request.urlopen", "is_absolute", @@ -412,15 +474,45 @@ def test_launcher_uses_only_explicit_absolute_python_candidates_and_exact_flags( self.assertIn(required_probe, source) self.assertIn('exec "$python" -I -S -B', source) - def _run_rewritten_launcher(self, candidate_sources, *launcher_arguments): - source = LAUNCHER_PATH.read_text(encoding="utf-8") - marker = textwrap.dedent("""\ - PYTHON_CANDIDATES=( - /opt/homebrew/bin/python3 - /usr/local/bin/python3 - /usr/bin/python3 - ) - """) + REFUSING_CANDIDATE = ( + 'printf \'%s\\n\' "refused:$1:$2:$3:$4" >> "$LAUNCHER_FIXTURE_LOG"\nexit 17\n' + ) + ACCEPTING_CANDIDATE = textwrap.dedent("""\ + if [ "$4" = "-c" ]; then + printf '%s\\n' "accepted:$1:$2:$3:$4" >> "$LAUNCHER_FIXTURE_LOG" + exit 0 + fi + printf '%s\\n' "accepted:$*" >> "$LAUNCHER_FIXTURE_LOG" + exit 23 + """) + + SH_CANDIDATE_MARKER = ( + 'PYTHON_CANDIDATES="/opt/homebrew/bin/python3\n' + '/usr/local/bin/python3\n' + '/usr/bin/python3"\n' + ) + ZSH_CANDIDATE_MARKER = textwrap.dedent("""\ + PYTHON_CANDIDATES=( + /opt/homebrew/bin/python3 + /usr/local/bin/python3 + /usr/bin/python3 + ) + """) + + def _run_rewritten_launcher( + self, candidate_sources, *launcher_arguments, shell="/bin/sh", + ): + """Run a launcher whose interpreter candidates are swapped for fixtures.""" + if shell.endswith("zsh"): + source = ZSH_LAUNCHER_PATH.read_text(encoding="utf-8") + marker = self.ZSH_CANDIDATE_MARKER + launcher_name = "launch_mcp.zsh" + shebang = "#!/bin/zsh\n" + else: + source = LAUNCHER_PATH.read_text(encoding="utf-8") + marker = self.SH_CANDIDATE_MARKER + launcher_name = "launch_mcp.sh" + shebang = "#!/bin/sh\n" with tempfile.TemporaryDirectory() as directory: root = Path(directory) / "plugin" scripts = root / "scripts" @@ -432,18 +524,23 @@ def _run_rewritten_launcher(self, candidate_sources, *launcher_arguments): for index, body in enumerate(candidate_sources): candidate = root / f"candidate-{index}" candidate.write_text( - "#!/bin/zsh\n" - f"export LAUNCHER_FIXTURE_LOG={str(log)!r}\n" + shebang + + f"export LAUNCHER_FIXTURE_LOG={str(log)!r}\n" + body, encoding="utf-8", ) candidate.chmod(0o755) candidates.append(candidate) - replacement = "PYTHON_CANDIDATES=(\n" + "".join( - f" {candidate}\n" for candidate in candidates - ) + ")\n" + if marker is self.ZSH_CANDIDATE_MARKER: + replacement = "PYTHON_CANDIDATES=(\n" + "".join( + f" {candidate}\n" for candidate in candidates + ) + ")\n" + else: + replacement = 'PYTHON_CANDIDATES="' + "\n".join( + str(candidate) for candidate in candidates + ) + '"\n' self.assertIn(marker, source) - (scripts / "launch_mcp.zsh").write_text( + (scripts / launcher_name).write_text( source.replace(marker, replacement), encoding="utf-8", ) (mcp / "server.py").write_text("raise SystemExit(99)\n", encoding="utf-8") @@ -451,23 +548,16 @@ def _run_rewritten_launcher(self, candidate_sources, *launcher_arguments): "raise SystemExit(98)\n", encoding="utf-8", ) completed = subprocess.run( - ["/bin/zsh", str(scripts / "launch_mcp.zsh"), *launcher_arguments], + [shell, str(scripts / launcher_name), *launcher_arguments], cwd=root, capture_output=True, text=True, check=False, - env={"PATH": "/definitely/not/a/python/path"}, timeout=2, + env={"PATH": "/definitely/not/a/python/path"}, timeout=5, ) calls = log.read_text(encoding="utf-8").splitlines() if log.exists() else [] return completed, calls, root def test_launcher_simulates_capability_fallback_and_provision_mode(self): - refusing = 'print -r -- "refused:$1:$2:$3:$4" >> "$LAUNCHER_FIXTURE_LOG"\nexit 17\n' - accepting = textwrap.dedent("""\ - if [[ "$4" == "-c" ]]; then - print -r -- "accepted:$1:$2:$3:$4" >> "$LAUNCHER_FIXTURE_LOG" - exit 0 - fi - print -r -- "accepted:$*" >> "$LAUNCHER_FIXTURE_LOG" - exit 23 - """) + refusing = self.REFUSING_CANDIDATE + accepting = self.ACCEPTING_CANDIDATE completed, calls, root = self._run_rewritten_launcher( [refusing, accepting], "provision", "--check", ) @@ -481,19 +571,61 @@ def test_launcher_simulates_capability_fallback_and_provision_mode(self): ) self.assertFalse(any(root.rglob("*.pyc"))) + LAUNCHER_REFUSAL = ( + "jackal_mcp=refused reason=no-compatible-python requirement='Python >=3.10 " + "with an atomic no-replace rename (Darwin renameatx_np / Linux renameat2) " + "at one of the fixed candidate paths' recovery='macOS: brew install python " + "| Linux: install a distribution python3 >=3.10 at /usr/bin/python3'\n" + ) + def test_launcher_refuses_once_with_126_when_no_candidate_passes(self): - refusing = 'print -r -- "refused:$1:$2:$3:$4" >> "$LAUNCHER_FIXTURE_LOG"\nexit 17\n' completed, calls, unused_root = self._run_rewritten_launcher( - [refusing, refusing], + [self.REFUSING_CANDIDATE, self.REFUSING_CANDIDATE], ) self.assertEqual(completed.returncode, 126) self.assertEqual(completed.stdout, "") - self.assertEqual( - completed.stderr, - "jackal_mcp=refused reason=no-compatible-python requirement='Python >=3.10 at /opt/homebrew/bin/python3' recovery='brew install python'\n", + self.assertEqual(completed.stderr, self.LAUNCHER_REFUSAL) + self.assertEqual(len(calls), 2) + + def test_launcher_resolves_its_root_without_any_external_command(self): + """The caller PATH is untrusted, so no PATH-resolved binary may be used.""" + completed, calls, unused_root = self._run_rewritten_launcher( + [self.REFUSING_CANDIDATE], + ) + self.assertEqual(completed.returncode, 126) + self.assertEqual(completed.stderr, self.LAUNCHER_REFUSAL) + for external in ("dirname", "basename", "readlink", "realpath", "not found"): + self.assertNotIn(external, completed.stderr) + self.assertEqual(len(calls), 1) + + @unittest.skipUnless(Path("/bin/zsh").exists(), "zsh launcher requires /bin/zsh") + def test_zsh_launcher_matches_the_portable_launcher_behaviour(self): + completed, calls, unused_root = self._run_rewritten_launcher( + [self.REFUSING_CANDIDATE, self.REFUSING_CANDIDATE], shell="/bin/zsh", ) + self.assertEqual(completed.returncode, 126) + self.assertEqual(completed.stderr, self.LAUNCHER_REFUSAL) self.assertEqual(len(calls), 2) + def test_both_launchers_carry_a_byte_identical_capability_probe(self): + def probe_of(path): + source = path.read_text(encoding="utf-8") + start = source.index("probe='") + len("probe='") + return source[start:source.index("'", start + 1)] + + self.assertEqual(probe_of(LAUNCHER_PATH), probe_of(ZSH_LAUNCHER_PATH)) + + def test_probe_requires_the_atomic_rename_symbol_for_this_host_only(self): + """Neither symbol may be demanded unconditionally on the wrong host.""" + source = LAUNCHER_PATH.read_text(encoding="utf-8") + self.assertIn( + '{"Darwin": "renameatx_np", "Linux": "renameat2"}.get(platform.system())', + source, + ) + self.assertIn("assert atomic_rename is not None", source) + self.assertIn("assert callable(getattr(libc, atomic_rename, None))", source) + self.assertNotIn('getattr(libc, "renameatx_np", None)', source) + def test_hosted_macos_workflow_mechanically_runs_all_repo_local_plugin_gates(self): self.assertTrue(WORKFLOW_PATH.is_file(), "hosted JACKAL plugin workflow is missing") source = WORKFLOW_PATH.read_text(encoding="utf-8") @@ -538,6 +670,15 @@ def test_hosted_macos_workflow_mechanically_runs_all_repo_local_plugin_gates(sel ): self.assertNotIn(forbidden, smoke_block) + def test_hosted_linux_smoke_requires_the_current_pinned_runtime_refusal(self): + source = WORKFLOW_PATH.read_text(encoding="utf-8") + linux_job = source.split("linux-aarch64-plugin:", 1)[1].split( + "macos-arm64-plugin:", 1 + )[0] + self.assertIn('test "$status" -eq 1', linux_job) + self.assertIn('grep -q "pinned runtime is not installed"', linux_job) + self.assertNotIn("no published release asset", linux_job) + def test_load_json_rejects_duplicate_keys(self): with tempfile.TemporaryDirectory() as temporary_directory: duplicate_json = Path(temporary_directory) / "duplicate.json" diff --git a/tests/codex_plugin/test_runtime_provisioner.py b/tests/codex_plugin/test_runtime_provisioner.py index c40150d..a8bc753 100644 --- a/tests/codex_plugin/test_runtime_provisioner.py +++ b/tests/codex_plugin/test_runtime_provisioner.py @@ -1,3 +1,4 @@ +import ctypes import errno import hashlib import io @@ -43,6 +44,15 @@ class RuntimeProvisionerTests(unittest.TestCase): def sha(self, data): return hashlib.sha256(data).hexdigest() + def write_snapshot_owner(self, parent, name, identity): + root = parent / name + root.mkdir(mode=0o700) + provisioner._write_snapshot_owner(root, identity) + runtime = root / provisioner.SNAPSHOT_RUNTIME_DIRECTORY + runtime.mkdir(mode=0o700) + (runtime / "payload").write_bytes(b"fixture\n") + return root + def add_bytes(self, archive, name, data=b"", mode=0o644, kind=tarfile.REGTYPE, linkname=""): info = tarfile.TarInfo(name) info.type = kind @@ -139,10 +149,15 @@ def write_runtime_fixture(self, root, *, files=None): ) return files - def test_validate_host_accepts_only_darwin_arm64(self): - provisioner.validate_host("Darwin", "arm64") - for system, machine in (("Linux", "arm64"), ("Darwin", "x86_64"), ("Linux", "x86_64")): - with self.subTest(system=system, machine=machine), self.assertRaises(provisioner.ProvisionError): + def test_validate_host_accepts_supported_hosts_and_refuses_others(self): + # Darwin/arm64 and the Linux/aarch64 Omarchy port are supported; Linux/x86_64 + # is a declared host gate (its release pin is None, so provisioning still + # refuses without a published asset). Genuinely foreign hosts must refuse. + for system, machine in (("Darwin", "arm64"), ("Linux", "aarch64"), ("Linux", "x86_64")): + with self.subTest(accept=(system, machine)): + provisioner.validate_host(system, machine) + for system, machine in (("Linux", "arm64"), ("Darwin", "x86_64"), ("Darwin", "aarch64"), ("Windows", "AMD64")): + with self.subTest(refuse=(system, machine)), self.assertRaises(provisioner.ProvisionError): provisioner.validate_host(system, machine) def test_runtime_subprocess_environment_is_minimal_and_preserves_only_jackal_home(self): @@ -831,6 +846,13 @@ def test_private_runtime_snapshot_is_exact_independent_and_owned(self): snapshot_root = snapshot.root self.assertNotEqual(snapshot_root, source) + self.assertEqual(snapshot_root.parent, snapshot.owner_root) + self.assertEqual(snapshot_root.name, provisioner.SNAPSHOT_RUNTIME_DIRECTORY) + self.assertTrue(snapshot.owner_root.name.startswith(provisioner.SNAPSHOT_PREFIX)) + self.assertEqual( + provisioner._load_snapshot_owner(snapshot.owner_root), + provisioner._current_snapshot_owner(), + ) self.assertEqual(snapshot_root.stat().st_mode & 0o777, 0o700) self.assertEqual( (snapshot_root / "payload.txt").read_bytes(), b"payload\n" @@ -858,6 +880,7 @@ def test_private_runtime_snapshot_is_exact_independent_and_owned(self): snapshot.close() self.assertFalse(snapshot_root.exists()) + self.assertFalse(snapshot.owner_root.exists()) def test_runtime_snapshot_cleanup_failure_remains_retryable(self): with tempfile.TemporaryDirectory() as directory: @@ -873,6 +896,173 @@ def test_runtime_snapshot_cleanup_failure_remains_retryable(self): self.assertTrue(snapshot._closed) self.assertEqual(cleanup.call_count, 2) + def test_linux_process_stat_parser_handles_spaces_and_closing_parentheses(self): + fields = [b"S"] + ([b"0"] * 18) + [b"9876"] + self.assertEqual( + provisioner._parse_linux_process_stat( + b"123 (worker ) with spaces) " + b" ".join(fields) + b"\n", + expected_pid=123, + ), + (123, "9876"), + ) + + def test_darwin_process_identity_uses_kernel_start_timeval_and_detects_gone(self): + class FakeProcPIDInfo: + argtypes = None + restype = None + + def __init__(self, *, present): + self.present = present + + def __call__(self, pid, flavor, unused_argument, buffer, size): + self.assertions = (pid, flavor, unused_argument) + if not self.present: + return 0 + info = ctypes.cast( + buffer, ctypes.POINTER(provisioner._DarwinBSDInfo) + ).contents + info.pbi_pid = pid + info.pbi_start_tvsec = 1_700_000_000 + info.pbi_start_tvusec = 123_456 + return size + + available = FakeProcPIDInfo(present=True) + with mock.patch.object( + provisioner.ctypes, + "CDLL", + return_value=types.SimpleNamespace(proc_pidinfo=available), + ): + self.assertEqual( + provisioner._darwin_process_identity(321), + (321, "1700000000:123456"), + ) + self.assertEqual( + available.assertions, (321, provisioner.DARWIN_PROC_PIDTBSDINFO, 0) + ) + + gone = FakeProcPIDInfo(present=False) + with ( + mock.patch.object( + provisioner.ctypes, + "CDLL", + return_value=types.SimpleNamespace(proc_pidinfo=gone), + ), + mock.patch.object(provisioner.os, "kill", side_effect=ProcessLookupError), + self.assertRaises(provisioner._ProcessGone), + ): + provisioner._darwin_process_identity(322) + + def test_snapshot_reaper_is_exact_for_live_gone_reused_and_ambiguous_owners(self): + with tempfile.TemporaryDirectory() as directory: + parent = Path(directory) + boot_id = "linux:test-boot" + live = self.write_snapshot_owner( + parent, + f"{provisioner.SNAPSHOT_PREFIX}live", + provisioner.SnapshotOwnerIdentity(101, "live-start", boot_id), + ) + reused = self.write_snapshot_owner( + parent, + f"{provisioner.SNAPSHOT_PREFIX}reused", + provisioner.SnapshotOwnerIdentity(102, "old-start", boot_id), + ) + gone = self.write_snapshot_owner( + parent, + f"{provisioner.SNAPSHOT_PREFIX}gone", + provisioner.SnapshotOwnerIdentity(103, "gone-start", boot_id), + ) + foreign_boot = self.write_snapshot_owner( + parent, + f"{provisioner.SNAPSHOT_PREFIX}foreign", + provisioner.SnapshotOwnerIdentity(104, "foreign-start", "linux:other"), + ) + missing_stamp = parent / f"{provisioner.SNAPSHOT_PREFIX}missing" + missing_stamp.mkdir(mode=0o700) + malformed = parent / f"{provisioner.SNAPSHOT_PREFIX}malformed" + malformed.mkdir(mode=0o700) + (malformed / provisioner.SNAPSHOT_OWNER_FILE).write_text("not-json\n") + unstamped_identity = provisioner.SnapshotOwnerIdentity( + 105, "unstamped-start", boot_id + ) + unstamped = parent / ( + provisioner._snapshot_owner_directory_prefix(unstamped_identity) + + "fixture" + ) + unstamped.mkdir(mode=0o700) + partial_identity = provisioner.SnapshotOwnerIdentity( + 106, "partial-start", boot_id + ) + partial = parent / ( + provisioner._snapshot_owner_directory_prefix(partial_identity) + + "fixture" + ) + partial.mkdir(mode=0o700) + (partial / provisioner.SNAPSHOT_OWNER_FILE).write_text("partial") + named_identity = provisioner.SnapshotOwnerIdentity( + 107, "named-start", boot_id + ) + mismatch = self.write_snapshot_owner( + parent, + provisioner._snapshot_owner_directory_prefix(named_identity) + + "fixture", + provisioner.SnapshotOwnerIdentity(108, "other-start", boot_id), + ) + + observed = [] + + def process_start(pid): + observed.append(pid) + if pid == 101: + return "live-start" + if pid == 102: + return "new-start" + if pid == 103: + raise provisioner._ProcessGone("fixture gone") + if pid == 105: + raise provisioner._ProcessGone("fixture gone before stamp") + if pid == 106: + raise provisioner._ProcessGone("fixture gone during stamp") + raise AssertionError(f"ambiguous owner was inspected: {pid}") + + removed = provisioner.reap_orphaned_runtime_snapshots( + parent, + current_boot_id=boot_id, + process_start_reader=process_start, + ) + + self.assertEqual(set(removed), {reused, gone, unstamped, partial}) + self.assertEqual(set(observed), {101, 102, 103, 105, 106}) + self.assertTrue(live.exists()) + self.assertFalse(reused.exists()) + self.assertFalse(gone.exists()) + self.assertTrue(foreign_boot.exists()) + self.assertTrue(missing_stamp.exists()) + self.assertTrue(malformed.exists()) + self.assertFalse(unstamped.exists()) + self.assertFalse(partial.exists()) + self.assertTrue(mismatch.exists()) + + def test_default_snapshot_parent_canonicalizes_the_platform_tmp_symlink(self): + with tempfile.TemporaryDirectory() as directory: + physical = Path(directory) / "private-tmp" + physical.mkdir() + lexical = Path(directory) / "tmp" + lexical.symlink_to(physical, target_is_directory=True) + with mock.patch.object( + provisioner.tempfile, "gettempdir", return_value=str(lexical) + ): + self.assertEqual( + provisioner._snapshot_parent_path(None), + physical.resolve(strict=True), + ) + self.assertEqual( + provisioner.reap_orphaned_runtime_snapshots( + current_boot_id="fixture-boot", + process_start_reader=mock.Mock(), + ), + (), + ) + def test_runtime_snapshot_preflights_file_size_against_remaining_budget(self): with tempfile.TemporaryDirectory() as directory: base = Path(directory) @@ -1555,9 +1745,24 @@ def test_pinned_constants_and_default_paths(self): "a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a", ) self.assertEqual( - provisioner.default_runtime_target(Path("/Users/tester")), + provisioner.default_runtime_target(Path("/Users/tester"), "Darwin"), Path("/Users/tester/Library/Application Support/JACKAL/runtimes/v1.7.3"), ) + self.assertEqual( + provisioner.default_locator_path(Path("/Users/tester"), "Darwin"), + Path( + "/Users/tester/Library/Application Support/JACKAL" + "/codex-plugin/runtime.json" + ), + ) + self.assertEqual( + provisioner.default_runtime_target(Path("/home/tester"), "Linux"), + Path("/home/tester/.local/share/JACKAL/runtimes/v1.7.3"), + ) + self.assertEqual( + provisioner.default_locator_path(Path("/home/tester"), "Linux"), + Path("/home/tester/.local/share/JACKAL/codex-plugin/runtime.json"), + ) def test_cli_rejects_relative_tarball_with_one_bounded_line_and_no_traceback(self): stderr = io.StringIO() @@ -1580,5 +1785,156 @@ def test_cli_parse_failure_is_one_bounded_line_without_system_exit(self): self.assertNotIn("Traceback", lines[0]) +class HostPortabilityTests(unittest.TestCase): + """The host guard admits exactly the hosts whose primitives are implemented.""" + + def test_resolve_host_admits_supported_hosts(self): + self.assertEqual(provisioner.resolve_host("Darwin", "arm64"), "macos-arm64") + self.assertEqual(provisioner.resolve_host("Linux", "aarch64"), "linux-aarch64") + self.assertEqual(provisioner.resolve_host("Linux", "x86_64"), "linux-x86_64") + + def test_resolve_host_refuses_near_misses_without_guessing(self): + # Linux/x86_64 is a recognized host tag (declared gate); the near-misses + # below are foreign hosts resolve_host must refuse rather than guess. + for system, machine in ( + ("Darwin", "x86_64"), + ("Linux", "arm64"), + ("Darwin", "aarch64"), + ("Windows", "AMD64"), + ("FreeBSD", "aarch64"), + ): + with self.subTest(system=system, machine=machine): + with self.assertRaises(provisioner.ProvisionError) as caught: + provisioner.resolve_host(system, machine) + detail = str(caught.exception) + self.assertIn(f"{system}/{machine}", detail) + self.assertIn("unsupported host", detail) + + def test_release_pin_resolves_for_hosts_with_a_pin(self): + self.assertEqual( + provisioner.release_pin("macos-arm64")["asset"], provisioner.ASSET + ) + # linux-aarch64 carries a locally built runtime pin + self.assertEqual( + provisioner.release_pin("linux-aarch64")["package_directory"], + "jackal-v1.7.3-linux-aarch64", + ) + + def test_release_pin_refuses_for_a_host_with_no_published_asset(self): + """A supported host with a None pin refuses rather than guessing bytes.""" + with mock.patch.dict(provisioner.RELEASE_PINS, {"linux-aarch64": None}): + with self.assertRaises(provisioner.ProvisionError) as caught: + provisioner.release_pin("linux-aarch64") + self.assertIn("no published release asset", str(caught.exception)) + + def test_default_pin_provisioning_refuses_when_the_host_pin_is_absent(self): + """A supported host with no pin is not a published runtime.""" + with mock.patch.dict(provisioner.RELEASE_PINS, {"linux-aarch64": None}): + with self.assertRaises(provisioner.ProvisionError) as caught: + provisioner.provision( + check_only=True, system="Linux", machine="aarch64", + ) + self.assertIn("no published release asset", str(caught.exception)) + + def test_caller_supplied_pins_bypass_the_release_table_not_the_host_guard(self): + with self.assertRaises(provisioner.ProvisionError) as caught: + provisioner.provision( + check_only=True, + system="Linux", + machine="riscv64", + expected_size=1, + expected_sha256="0" * 64, + ) + self.assertIn("unsupported host", str(caught.exception)) + + def test_rename_primitive_is_selected_per_host_and_refuses_elsewhere(self): + self.assertIs( + provisioner.rename_exclusive_for_host("Darwin"), + provisioner._renameatx_np_exclusive, + ) + self.assertIs( + provisioner.rename_exclusive_for_host("Linux"), + provisioner._renameat2_noreplace, + ) + with self.assertRaises(provisioner.ProvisionError) as caught: + provisioner.rename_exclusive_for_host("Windows") + self.assertIn("unsupported on Windows", str(caught.exception)) + + def test_each_primitive_refuses_to_run_on_the_wrong_host(self): + wrong = ( + (provisioner._renameatx_np_exclusive, "Linux"), + (provisioner._renameat2_noreplace, "Darwin"), + ) + for operation, foreign_system in wrong: + with self.subTest(operation=operation.__name__): + with mock.patch.object( + provisioner.platform, "system", return_value=foreign_system + ): + with self.assertRaises(provisioner.ProvisionError): + operation(-1, "a", -1, "b") + + def test_supported_hosts_and_release_pins_describe_the_same_hosts(self): + self.assertEqual( + set(provisioner.SUPPORTED_HOSTS.values()), + set(provisioner.RELEASE_PINS), + ) + + +@unittest.skipUnless(sys.platform.startswith("linux"), "Linux rename primitive") +class LinuxRenameNoReplaceTests(unittest.TestCase): + """renameat2(RENAME_NOREPLACE) must match renameatx_np(RENAME_EXCL) semantics.""" + + def _parents(self, root, source_name, target_name, *, payload=b"runtime"): + (root / source_name).write_bytes(payload) + return os.open(root, os.O_RDONLY | os.O_DIRECTORY) + + def test_rename_moves_into_a_free_name(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + fd = self._parents(root, "src", "dst") + try: + provisioner._renameat2_noreplace(fd, "src", fd, "dst") + finally: + os.close(fd) + self.assertFalse((root / "src").exists()) + self.assertEqual((root / "dst").read_bytes(), b"runtime") + + def test_rename_refuses_to_clobber_an_existing_target(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / "dst").write_bytes(b"installed") + fd = self._parents(root, "src", "dst", payload=b"attacker") + try: + with self.assertRaises(FileExistsError) as caught: + provisioner._renameat2_noreplace(fd, "src", fd, "dst") + finally: + os.close(fd) + self.assertEqual(caught.exception.errno, errno.EEXIST) + self.assertEqual((root / "dst").read_bytes(), b"installed") + self.assertEqual((root / "src").read_bytes(), b"attacker") + + def test_missing_source_reports_enoent_not_a_silent_success(self): + with tempfile.TemporaryDirectory() as directory: + fd = os.open(directory, os.O_RDONLY | os.O_DIRECTORY) + try: + with self.assertRaises(OSError) as caught: + provisioner._renameat2_noreplace(fd, "absent", fd, "dst") + finally: + os.close(fd) + self.assertEqual(caught.exception.errno, errno.ENOENT) + self.assertNotIsInstance(caught.exception, FileExistsError) + + def test_install_no_replace_uses_the_host_primitive_end_to_end(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / "staged").mkdir() + (root / "staged" / "marker").write_bytes(b"x") + provisioner._install_no_replace(root / "staged", root / "final") + self.assertTrue((root / "final" / "marker").is_file()) + (root / "staged").mkdir() + with self.assertRaises(FileExistsError): + provisioner._install_no_replace(root / "staged", root / "final") + + if __name__ == "__main__": unittest.main() diff --git a/tests/codex_plugin/test_spark_interval.py b/tests/codex_plugin/test_spark_interval.py new file mode 100644 index 0000000..d69b8a3 --- /dev/null +++ b/tests/codex_plugin/test_spark_interval.py @@ -0,0 +1,125 @@ +import shutil +import subprocess +import tempfile +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +SPARK_ROOT = REPO_ROOT / "proofs/spark/hellgate_interval" +ASSUMPTION_GUARD = REPO_ROOT / "proofs/spark/reject_assumptions.sh" + + +class SparkIntervalEnvelopeTests(unittest.TestCase): + """Proof regression for JCK-INT-001, JCK-INT-002, JCK-INT-003, JCK-INT-004.""" + + def test_source_keeps_the_declared_formal_boundary(self): + sources = [ + SPARK_ROOT / "src/jackal_interval_envelope.ads", + SPARK_ROOT / "src/jackal_interval_envelope.adb", + SPARK_ROOT / "tests/hellgate_interval_demo.adb", + ] + text = "\n".join(path.read_text(encoding="utf-8") for path in sources) + self.assertIn("SPARK_Mode", text) + self.assertIn("function Admits_Untrusted_Envelope", text) + self.assertIn("function Strictly_Meets_Target", text) + self.assertIn("function Evaluate_Untrusted_Envelope", text) + self.assertIn("Required_Verdict", text) + self.assertIn("Post =>", text) + lowered = text.lower() + self.assertNotIn("pragma assume", lowered) + self.assertNotIn("pragma annotate", lowered) + + def test_assumption_guard_rejects_case_and_line_break_bypasses(self): + with tempfile.TemporaryDirectory() as raw_directory: + directory = Path(raw_directory) + source = directory / "guard_probe.adb" + report = directory / "gnatprove.out" + report.write_text( + "Guard_Probe (0 pragma Assume statements)\n", + encoding="utf-8", + ) + source.write_text( + "-- pragma Assume (True);\n" + "procedure Guard_Probe is\n" + " Message : constant String := \"pragma Annotate\";\n" + "begin null; end Guard_Probe;\n", + encoding="utf-8", + ) + accepted = subprocess.run( + [str(ASSUMPTION_GUARD), str(report), str(directory)], + cwd=REPO_ROOT, + check=False, + capture_output=True, + text=True, + ) + self.assertEqual( + accepted.returncode, 0, accepted.stdout + accepted.stderr + ) + + forbidden_pragmas = ( + "pragma aSsUmE (True);", + "pragma\nAnNoTaTe (GNATprove, False_Positive, \"probe\");", + "pragma -- comment between Ada tokens\nAssume (True);", + ) + for forbidden in forbidden_pragmas: + with self.subTest(forbidden=forbidden): + source.write_text(forbidden + "\n", encoding="utf-8") + refused = subprocess.run( + [str(ASSUMPTION_GUARD), str(report), str(directory)], + cwd=REPO_ROOT, + check=False, + capture_output=True, + text=True, + ) + transcript = refused.stdout + refused.stderr + self.assertNotEqual(refused.returncode, 0, transcript) + self.assertIn( + "proof assumption or justification", + transcript, + ) + + source.write_text( + "procedure Guard_Probe is begin null; end Guard_Probe;\n", + encoding="utf-8", + ) + report.write_text( + "Guard_Probe (1 pragma Assume statement)\n", + encoding="utf-8", + ) + refused_report = subprocess.run( + [str(ASSUMPTION_GUARD), str(report), str(directory)], + cwd=REPO_ROOT, + check=False, + capture_output=True, + text=True, + ) + report_transcript = refused_report.stdout + refused_report.stderr + self.assertNotEqual(refused_report.returncode, 0, report_transcript) + self.assertIn( + "GNATprove reports one or more proof assumptions", + report_transcript, + ) + + @unittest.skipUnless( + shutil.which("gprbuild") and shutil.which("gnatprove"), + "GNATprove toolchain is not installed", + ) + def test_build_runtime_boundary_and_gnatprove_gate(self): + completed = subprocess.run( + [str(SPARK_ROOT / "prove.sh")], + cwd=REPO_ROOT, + check=False, + capture_output=True, + text=True, + timeout=60, + ) + transcript = completed.stdout + completed.stderr + self.assertEqual(completed.returncode, 0, transcript) + self.assertIn("HELLGATE fixed-scale interval envelope: ACCEPT", transcript) + self.assertIn("Success: all checks proved", transcript) + self.assertIn("SPARK_PLATINUM_INTERVAL_COMPONENT_PROOF_PASS", transcript) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/codex_plugin/test_stem.py b/tests/codex_plugin/test_stem.py new file mode 100644 index 0000000..5acd110 --- /dev/null +++ b/tests/codex_plugin/test_stem.py @@ -0,0 +1,520 @@ +import ast +import copy +import hashlib +import unittest +from fractions import Fraction + +from plugins.jackel.mcp import server as adapter +from plugins.jackel.mcp import stem + + +FIXTURE_IDENTITY = "d" * 64 + + +class ExactFixtureKernel: + """Small test double: product code still has to delegate every numeric field.""" + + def __init__(self): + self.calls = [] + + def __call__(self, name, arguments): + self.calls.append((name, copy.deepcopy(arguments))) + if name == "jackal_exact": + expression = arguments["expression"] + value = self._fraction(ast.parse(expression.replace("^", "**"), mode="eval").body) + rendered = str(value.numerator) + if value.denominator != 1: + rendered += f"/{value.denominator}" + return { + "status": "exact", + "fields": {"parsed": expression, "exact": rendered}, + "formal": False, + } + if name == "jackal_evaluate": + return {"status": "estimated", "engine_output": "0", "formal": False} + if name == "jackal_integrate_adaptive": + return { + "status": "estimated", + "fields": {"integral": "1/4", "parsed": arguments["expression"]}, + "formal": False, + } + if name in {"jackal_sqrt_rat_bound", "jackal_ln_rat_bound"}: + return { + "status": "formal-bounded", + "checker_rerun": "ACCEPT", + "fields": { + "parsed": arguments["expression"], + "input_lo": arguments["input_lo"], + "input_hi": arguments["input_hi"], + }, + "formal": True, + } + if name == "jackal_canon": + return {"status": "exact", "engine_output": "(pow x 2)", "formal": False} + if name == "jackal_diff": + return {"status": "checked", "engine_output": "2*x", "formal": False} + raise AssertionError(f"unexpected delegated tool: {name}") + + def _fraction(self, node): + if isinstance(node, ast.Constant) and isinstance(node.value, int): + return Fraction(node.value) + if isinstance(node, ast.UnaryOp): + value = self._fraction(node.operand) + if isinstance(node.op, ast.USub): + return -value + if isinstance(node.op, ast.UAdd): + return value + if isinstance(node, ast.BinOp): + left = self._fraction(node.left) + right = self._fraction(node.right) + if isinstance(node.op, ast.Add): + return left + right + if isinstance(node.op, ast.Sub): + return left - right + if isinstance(node.op, ast.Mult): + return left * right + if isinstance(node.op, ast.Div): + return left / right + if isinstance(node.op, ast.Pow) and right.denominator == 1: + return left ** right.numerator + raise AssertionError(f"unsupported fixture expression: {ast.dump(node)}") + + +class StemSurfaceTests(unittest.TestCase): + def test_definitions_form_one_closed_identity_pinned_surface(self): + definitions = adapter.build_stem_tool_definitions(stem) + + self.assertEqual( + {definition["name"] for definition in definitions}, adapter.STEM_TOOL_NAMES + ) + self.assertEqual(len(definitions), adapter.EXPECTED_STEM_TOOL_COUNT) + for definition in definitions: + with self.subTest(tool=definition["name"]): + self.assertIs(definition["inputSchema"]["additionalProperties"], False) + self.assertEqual( + definition["annotations"], + { + "readOnlyHint": True, + "destructiveHint": False, + "idempotentHint": True, + "openWorldHint": False, + }, + ) + + def test_definition_tampering_refuses_before_merge(self): + original = stem.tool_definitions + definitions = original() + definitions[0] = copy.deepcopy(definitions[0]) + definitions[0]["name"] = "jackal_forged" + stem.tool_definitions = lambda: definitions + try: + with self.assertRaises(adapter.CatalogError): + adapter.build_stem_tool_definitions(stem) + finally: + stem.tool_definitions = original + + def test_matrix_inverse_and_all_numeric_cells_delegate(self): + kernel = ExactFixtureKernel() + body = stem.dispatch_integrated( + "jackal_matrix", + {"operation": "inverse", "matrix": [["1", "2"], ["3", "5"]]}, + kernel, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "exact") + self.assertEqual(body["fields"]["matrix"], [["-5", "2"], ["3", "-1"]]) + self.assertTrue(kernel.calls) + self.assertEqual({name for name, unused in kernel.calls}, {"jackal_exact"}) + self.assertTrue(any("NOT formal-bounded" in item for item in body["non_claims"])) + + def test_every_matrix_route_and_singular_refusal(self): + kernel = ExactFixtureKernel() + cases = ( + ( + {"operation": "add", "matrix": [["1", "2"], ["3", "4"]], "second_matrix": [["5", "6"], ["7", "8"]]}, + "matrix", + [["6", "8"], ["10", "12"]], + ), + ( + {"operation": "multiply", "matrix": [["1", "2"], ["3", "4"]], "second_matrix": [["5", "6"], ["7", "8"]]}, + "matrix", + [["19", "22"], ["43", "50"]], + ), + ( + {"operation": "transpose", "matrix": [["1", "2", "3"], ["4", "5", "6"]]}, + "matrix", + [["1", "4"], ["2", "5"], ["3", "6"]], + ), + ( + {"operation": "determinant", "matrix": [["1", "2"], ["3", "4"]]}, + "determinant", + "-2", + ), + ( + {"operation": "rref", "matrix": [["1", "2"], ["2", "4"]]}, + "matrix", + [["1", "2"], ["0", "0"]], + ), + ( + {"operation": "solve", "matrix": [["1", "0"], ["0", "1"]], "vector": ["7", "9"]}, + "solution", + ["7", "9"], + ), + ) + for arguments, field, expected in cases: + with self.subTest(operation=arguments["operation"]): + body = stem.dispatch_integrated( + "jackal_matrix", arguments, kernel, FIXTURE_IDENTITY + ) + self.assertEqual(body["status"], "exact") + self.assertEqual(body["fields"][field], expected) + + singular = stem.dispatch_integrated( + "jackal_matrix", + {"operation": "inverse", "matrix": [["1", "2"], ["2", "4"]]}, + kernel, + FIXTURE_IDENTITY, + ) + self.assertEqual(singular["status"], "refused") + self.assertEqual(singular["reason"], "matrix-singular") + + def test_polynomial_regression_keeps_model_status_separate_from_exact_fields(self): + kernel = ExactFixtureKernel() + body = stem.dispatch_integrated( + "jackal_regression", + { + "model": "polynomial_ols", + "degree": "1", + "x": ["0", "1", "2"], + "y": ["1", "3", "5"], + }, + kernel, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "model-based") + self.assertEqual(body["fields"]["coefficients_ascending"], ["1", "2"]) + self.assertEqual(body["fields"]["sse"], "0") + self.assertEqual(body["field_status"]["coefficients_ascending"], "exact") + self.assertTrue(any("do not establish" in item for item in body["non_claims"])) + + def test_probability_and_hypothesis_preserve_model_assumptions(self): + kernel = ExactFixtureKernel() + probability = stem.dispatch_integrated( + "jackal_probability", + {"operation": "binomial_cdf", "n": "3", "k": "1", "p": "1/2"}, + kernel, + FIXTURE_IDENTITY, + ) + hypothesis = stem.dispatch_integrated( + "jackal_hypothesis", + { + "operation": "exact_binomial_tail", + "alternative": "greater", + "n": "3", + "k": "2", + "p0": "1/2", + }, + kernel, + FIXTURE_IDENTITY, + ) + + self.assertEqual(probability["status"], "model-based") + self.assertEqual(probability["fields"]["probability"], "1/2") + self.assertEqual(hypothesis["status"], "model-based") + self.assertEqual(hypothesis["fields"]["p_value"], "1/2") + self.assertEqual(hypothesis["consequence_ceiling"], "advisory") + + def test_binomial_endpoint_probabilities_do_not_divide_by_zero(self): + kernel = ExactFixtureKernel() + cases = ( + ({"operation": "binomial_cdf", "n": "3", "k": "1", "p": "1"}, "0"), + ({"operation": "binomial_pmf", "n": "3", "k": "3", "p": "1"}, "1"), + ({"operation": "binomial_cdf", "n": "3", "k": "0", "p": "0"}, "1"), + ) + for arguments, expected in cases: + with self.subTest(arguments=arguments): + body = stem.dispatch_integrated( + "jackal_probability", arguments, kernel, FIXTURE_IDENTITY + ) + self.assertEqual(body["status"], "model-based") + self.assertEqual(body["fields"]["probability"], expected) + + def test_normal_probability_and_all_z_alternatives_use_estimated_finite_tails(self): + kernel = ExactFixtureKernel() + normal = stem.dispatch_integrated( + "jackal_probability", + { + "operation": "normal_cdf", + "z": "0", + "tail_cutoff": "6", + "tolerance": "1/10", + }, + kernel, + FIXTURE_IDENTITY, + ) + self.assertEqual(normal["status"], "model-based") + self.assertEqual(normal["field_status"]["finite_cutoff_cdf_estimate"], "estimated") + + for alternative in ("less", "greater", "two_sided"): + with self.subTest(alternative=alternative): + body = stem.dispatch_integrated( + "jackal_hypothesis", + { + "operation": "one_sample_z", + "alternative": alternative, + "sample_mean": "1", + "null_mean": "1", + "population_sd": "2", + "n": "4", + "tail_cutoff": "6", + "tolerance": "1/10", + }, + kernel, + FIXTURE_IDENTITY, + ) + self.assertEqual(body["status"], "model-based") + self.assertEqual(body["field_status"]["p_value_estimate"], "estimated") + + def test_sensor_provenance_stays_supplied_and_stddev_stays_formal_bounded(self): + kernel = ExactFixtureKernel() + body = stem.dispatch_integrated( + "jackal_sensor", + { + "operation": "linear_calibration", + "sensor_id": "imu-1", + "channel": "accel-x", + "quantity": "acceleration", + "unit": "m/s2", + "samples": ["1", "2", "3"], + "source": "fixture-export.csv", + "observed_at": "fixture-time", + "scale": "2", + "offset": "1", + "calibration_source": "fixture-sheet", + "calibration_as_of": "fixture-date", + }, + kernel, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "exact-given") + self.assertEqual(body["given"]["input_provenance"], "supplied") + self.assertIs(body["given"]["calibration"]["verified"], False) + self.assertEqual( + body["field_status"]["population_stddev_enclosure"], "formal-bounded" + ) + self.assertTrue(any("does not claim it opened" in item for item in body["non_claims"])) + + def test_aerospace_formal_scalar_does_not_upgrade_physical_model(self): + kernel = ExactFixtureKernel() + body = stem.dispatch_integrated( + "jackal_aerospace", + {"operation": "vis_viva", "parameters": {"mu": "10", "radius": "2", "semi_major_axis": "3"}}, + kernel, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "model-based") + self.assertEqual(body["field_status"]["speed_enclosure"], "formal-bounded") + self.assertEqual(body["consequence_ceiling"], "advisory") + self.assertTrue(any("physical model" in item for item in body["non_claims"])) + + def test_every_aerospace_model_route_preserves_advisory_model_status(self): + kernel = ExactFixtureKernel() + cases = ( + {"operation": "circular_orbit", "parameters": {"mu": "10", "radius": "2"}}, + {"operation": "rocket_equation", "parameters": {"exhaust_velocity": "3", "initial_mass": "5", "final_mass": "2"}}, + {"operation": "hohmann_transfer", "parameters": {"mu": "10", "r1": "2", "r2": "3"}}, + {"operation": "plane_change", "parameters": {"velocity": "7", "angle_degrees": "30"}}, + ) + for arguments in cases: + with self.subTest(operation=arguments["operation"]): + body = stem.dispatch_integrated( + "jackal_aerospace", arguments, kernel, FIXTURE_IDENTITY + ) + self.assertEqual(body["status"], "model-based") + self.assertEqual(body["consequence_ceiling"], "advisory") + self.assertTrue(body["assumptions"]) + + invalid_angle = stem.dispatch_integrated( + "jackal_aerospace", + {"operation": "plane_change", "parameters": {"velocity": "7", "angle_degrees": "181"}}, + kernel, + FIXTURE_IDENTITY, + ) + self.assertEqual(invalid_angle["status"], "refused") + self.assertEqual(invalid_angle["reason"], "domain") + + def test_linked_workspace_embeds_digest_bound_html_without_upgrading_results(self): + kernel = ExactFixtureKernel() + body = stem.dispatch_integrated( + "jackal_linked_workspace", + {"expression": "x^2", "x_min": "-1", "x_max": "1", "samples": "17"}, + kernel, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "checked") + resource = body["_mcp_content"][1]["resource"] + digest = hashlib.sha256(resource["text"].encode("utf-8")).hexdigest() + self.assertEqual(resource["uri"], f"ui://jackal/linked-workspace/{digest}") + self.assertIn("Pixels are not proof", resource["text"]) + wrapped = adapter.backend_result(body) + self.assertEqual(wrapped["content"][1]["type"], "resource") + self.assertNotIn("_mcp_content", wrapped["structuredContent"]) + + def test_malformed_numeric_token_and_kernel_refusal_fail_closed(self): + calls = [] + malformed = stem.dispatch_integrated( + "jackal_matrix", + {"operation": "transpose", "matrix": [["1junk"]]}, + lambda name, arguments: calls.append((name, arguments)), + FIXTURE_IDENTITY, + ) + refused = stem.dispatch_integrated( + "jackal_matrix", + {"operation": "determinant", "matrix": [["1"]]}, + lambda unused_name, unused_arguments: { + "status": "refused", + "reason": "fixture-refusal", + "detail": "fixture detail", + }, + FIXTURE_IDENTITY, + ) + + self.assertEqual(malformed["status"], "refused") + self.assertEqual(malformed["reason"], "args") + self.assertEqual(calls, []) + self.assertEqual(refused["status"], "refused") + self.assertEqual(refused["reason"], "kernel-refused:fixture-refusal") + + def test_delegated_status_tampering_cannot_upgrade_a_field(self): + kernel = ExactFixtureKernel() + + def tampered(name, arguments): + if name == "jackal_sqrt_rat_bound": + return { + "status": "estimated", + "checker_rerun": "ACCEPT", + "formal": False, + } + return kernel(name, arguments) + + body = stem.dispatch_integrated( + "jackal_sensor", + { + "operation": "ingest_batch", + "sensor_id": "fixture", + "channel": "x", + "quantity": "q", + "unit": "u", + "samples": ["1", "2"], + "source": "fixture", + "observed_at": "fixture", + }, + tampered, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "refused") + self.assertEqual(body["reason"], "kernel-error") + self.assertNotIn("field_status", body) + + def test_adaptive_integration_status_tampering_refuses(self): + kernel = ExactFixtureKernel() + + def tampered(name, arguments): + if name == "jackal_integrate_adaptive": + return { + "status": "checked", + "fields": {"integral": "1/2"}, + "formal": False, + } + return kernel(name, arguments) + + body = stem.dispatch_integrated( + "jackal_probability", + { + "operation": "normal_cdf", + "z": "0", + "tail_cutoff": "6", + "tolerance": "1/10", + }, + tampered, + FIXTURE_IDENTITY, + ) + + self.assertEqual(body["status"], "refused") + self.assertEqual(body["reason"], "kernel-error") + + +class StemResourceValidationTests(unittest.TestCase): + def test_resource_digest_or_uri_tampering_fails_closed(self): + content = [ + {"type": "text", "text": "fixture"}, + { + "type": "resource", + "resource": { + "uri": "ui://jackal/linked-workspace/" + ("0" * 64), + "mimeType": "text/html", + "text": "tampered", + }, + }, + ] + with self.assertRaises(adapter.BackendFailure): + adapter.backend_result({"status": "checked", "_mcp_content": content}) + + +class StemResourceProtocolTests(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + self.server = adapter.MCPServer( + runtime_root="/tmp", + launcher="/bin/false", + tool_definitions=adapter.build_stem_tool_definitions(stem), + runtime_environment={"PATH": "/usr/bin:/bin"}, + stem_module=stem, + stem_identity=FIXTURE_IDENTITY, + ) + + async def asyncTearDown(self): + await self.server.close() + + async def test_resource_listing_and_shell_read_are_closed_to_one_uri(self): + listed = await self.server.handle_message( + { + "jsonrpc": "2.0", + "id": "list", + "method": "resources/list", + "params": {}, + } + ) + resource = listed["result"]["resources"][0] + self.assertEqual(resource["uri"], adapter.LINKED_WORKSPACE_SHELL_URI) + + read = await self.server.handle_message( + { + "jsonrpc": "2.0", + "id": "read", + "method": "resources/read", + "params": {"uri": adapter.LINKED_WORKSPACE_SHELL_URI}, + } + ) + contents = read["result"]["contents"][0] + self.assertEqual(contents["mimeType"], "text/html") + self.assertIn("Call jackal_linked_workspace", contents["text"]) + + refused = await self.server.handle_message( + { + "jsonrpc": "2.0", + "id": "refuse", + "method": "resources/read", + "params": {"uri": "file:///tmp/forbidden"}, + } + ) + self.assertEqual(refused["error"]["code"], adapter.INVALID_PARAMS) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/codex_plugin/ui_stem_acceptance.py b/tests/codex_plugin/ui_stem_acceptance.py new file mode 100644 index 0000000..d72a197 --- /dev/null +++ b/tests/codex_plugin/ui_stem_acceptance.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Rendered acceptance for JACKAL's static linked evidence workspace.""" + +from __future__ import annotations + +import argparse +from pathlib import Path +import sys + +from playwright.sync_api import sync_playwright + +REPOSITORY_ROOT = Path(__file__).resolve().parents[2] +if str(REPOSITORY_ROOT) not in sys.path: + sys.path.insert(0, str(REPOSITORY_ROOT)) + +from plugins.jackel.mcp import stem + + +def fixture_document() -> str: + points = [ + {"x": "-4", "y": "12", "status": "estimated"}, + {"x": "-3", "y": "5", "status": "estimated"}, + {"x": "-2", "y": "0", "status": "estimated"}, + {"x": "-1", "y": "-3", "status": "estimated"}, + {"x": "0", "y": "-4", "status": "estimated"}, + {"x": "1", "y": "-3", "status": "estimated"}, + {"x": "2", "y": "0", "status": "estimated"}, + {"x": "3", "y": "5", "status": "estimated"}, + {"x": "4", "y": "12", "status": "estimated"}, + ] + return stem._workspace_document( + { + "status": "checked", + "expression": "x^2 - 4", + "points": points, + "finite_sample_count": "9", + "canonical_text": "(sub (pow (var x) (num 2)) (num 4))", + "derivative_text": "d/dx[x^2-4] = 2*x · status=checked", + "route": [ + {"tool": "jackal_canon", "status": "exact", "parsed": "canonical expression"}, + {"tool": "jackal_diff", "status": "checked", "parsed": "sampled symbolic derivative check"}, + {"tool": "jackal_exact", "status": "exact", "parsed": "rational x coordinates"}, + {"tool": "jackal_evaluate", "status": "estimated", "parsed": "sampled y values"}, + ], + } + ) + + +def run(output: Path, mobile_output: Path | None = None) -> None: + output.parent.mkdir(parents=True, exist_ok=True) + if mobile_output is not None: + mobile_output.parent.mkdir(parents=True, exist_ok=True) + console_errors: list[str] = [] + page_errors: list[str] = [] + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=True) + page = browser.new_page( + viewport={"width": 1440, "height": 980}, + device_scale_factor=1, + ) + page.on( + "console", + lambda message: console_errors.append(message.text) + if message.type == "error" + else None, + ) + page.on("pageerror", lambda error: page_errors.append(str(error))) + page.set_content(fixture_document(), wait_until="networkidle") + + page.locator("#plot").wait_for(state="visible") + if page.locator("#rows tr").count() != 9: + raise RuntimeError("workspace table did not render every supplied fixture point") + if page.locator(".route").count() != 4: + raise RuntimeError("workspace evidence route did not render every fixture stage") + if page.locator("#rows tr.active").count() != 1: + raise RuntimeError("workspace did not establish one synchronized cursor") + if page.locator('#status[data-status="checked"]').count() != 1: + raise RuntimeError("workspace did not expose the result status visually") + if page.locator(".trace-path").count() != 1: + raise RuntimeError("workspace did not render the continuous delegated trace") + if page.locator('path[fill="url(#trace-fill)"]').count() != 1: + raise RuntimeError("workspace did not render the graph depth layer") + route_statuses = page.locator(".route").evaluate_all( + "nodes => nodes.map(node => node.dataset.status)" + ) + if route_statuses != ["exact", "checked", "exact", "estimated"]: + raise RuntimeError("workspace did not preserve route status classes") + + page.locator("#rows tr").nth(2).focus() + if page.locator("#cursor-x").inner_text() != "-2": + raise RuntimeError("keyboard table focus did not synchronize the inspector") + + plot = page.locator("#plot").bounding_box() + if plot is None: + raise RuntimeError("workspace plot has no rendered bounds") + page.mouse.move(plot["x"] + plot["width"] - 8, plot["y"] + plot["height"] / 2) + page.wait_for_timeout(100) + if page.locator("#cursor-x").inner_text() != "4": + raise RuntimeError("plot hover did not synchronize the table and inspector") + page.evaluate("document.activeElement.blur()") + + page.screenshot(path=str(output), full_page=True) + + page.locator("#connect").click() + page.wait_for_timeout(100) + sensor_log = page.locator("#sensor-log").inner_text() + if "unavailable" not in sensor_log and "stopped" not in sensor_log: + raise RuntimeError("sensor dock did not expose its browser/device refusal") + + page.emulate_media(reduced_motion="reduce") + trace_animation = page.locator(".trace-path").evaluate( + "element => getComputedStyle(element).animationName" + ) + if trace_animation != "none": + raise RuntimeError("workspace did not honor reduced-motion preference") + + for width, height in ((760, 1100), (390, 844)): + page.set_viewport_size({"width": width, "height": height}) + page.wait_for_timeout(100) + horizontal_overflow = page.evaluate( + "document.documentElement.scrollWidth > document.documentElement.clientWidth" + ) + if horizontal_overflow: + raise RuntimeError( + f"responsive workspace introduces horizontal overflow at {width}px" + ) + if not page.locator("#status").is_visible() or not page.locator("#expr").is_visible(): + raise RuntimeError( + f"responsive workspace hides primary evidence context at {width}px" + ) + mobile_plot = page.locator("#plot").bounding_box() + if mobile_plot is None or mobile_plot["width"] <= 0 or mobile_plot["height"] <= 0: + raise RuntimeError(f"responsive workspace collapsed the graph at {width}px") + if width == 760 and mobile_output is not None: + page.screenshot(path=str(mobile_output), full_page=True) + browser.close() + + if console_errors or page_errors: + raise RuntimeError( + f"workspace emitted browser errors: console={console_errors!r} page={page_errors!r}" + ) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--mobile-output", type=Path) + arguments = parser.parse_args() + run(arguments.output, arguments.mobile_output) + print(arguments.output) + if arguments.mobile_output is not None: + print(arguments.mobile_output) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/jackal_skill_contract_test.py b/tests/jackal_skill_contract_test.py index 813cf84..8e59355 100644 --- a/tests/jackal_skill_contract_test.py +++ b/tests/jackal_skill_contract_test.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Check JACKAL routing skills against the canonical capability inventory.""" +"""Check JACKAL routing skills against sealed and additive capabilities.""" from __future__ import annotations @@ -8,6 +8,8 @@ import unittest from pathlib import Path +from plugins.jackel.mcp import server as codex_adapter + ROOT = Path(__file__).resolve().parents[1] INVENTORY = ROOT / "release/capability_inventory_v1.json" @@ -52,6 +54,14 @@ def inventory_names() -> set[str]: return {row["name"] for row in document["tools"]} +def codex_additive_names() -> set[str]: + return set( + codex_adapter.MEASUREMENT_TOOL_NAMES + | codex_adapter.ADVANCED_TOOL_NAMES + | codex_adapter.STEM_TOOL_NAMES + ) + + def profile_counts() -> dict[str, int]: document = json.loads(INVENTORY.read_text(encoding="utf-8")) counts = {"core": 0, "formal": 0, "full": 0} @@ -72,11 +82,17 @@ def current_block(text: str) -> str: def assert_router_contract( - case: unittest.TestCase, path: Path, *, require_marker: bool = True + case: unittest.TestCase, + path: Path, + *, + require_marker: bool = True, + allow_codex_additions: bool = False, ) -> None: text = path.read_text(encoding="utf-8") lower = text.lower() names = inventory_names() + if allow_codex_additions: + names |= codex_additive_names() references = set(TOOL_REFERENCE.findall(text)) case.assertTrue(REQUIRED_ROUTING <= references, (path, references)) case.assertEqual(references - names, set(), (path, references - names)) @@ -87,13 +103,21 @@ def assert_router_contract( block = current_block(text) case.assertIn("41-tool", block) case.assertIn("release/capability_inventory_v1.json", block) + if allow_codex_additions: + case.assertIn("58-tool", block) for stale in STALE_CURRENT: case.assertNotIn(stale, lower, (path, stale)) class JackalSkillContractTest(unittest.TestCase): - def test_repository_codex_router_uses_only_inventory_tools(self) -> None: - assert_router_contract(self, REPO_ROUTER) + def test_repository_codex_router_uses_only_unified_tools(self) -> None: + runtime = inventory_names() + additive = codex_additive_names() + self.assertFalse(runtime & additive) + self.assertEqual( + len(runtime | additive), codex_adapter.EXPECTED_UNIFIED_TOOL_COUNT + ) + assert_router_contract(self, REPO_ROUTER, allow_codex_additions=True) def test_personal_codex_oracle_names_current_replay_front_doors(self) -> None: if not PERSONAL_CODEX_ORACLE.is_file(): diff --git a/tests/release_validate.py b/tests/release_validate.py index 4db96b2..8065603 100644 --- a/tests/release_validate.py +++ b/tests/release_validate.py @@ -25,6 +25,7 @@ import hashlib import json import os +import platform import re import stat import subprocess @@ -428,7 +429,13 @@ def _emit_formal_receipt(path: str | Path, *, receipt: dict, cert_bytes: bytes, if release_epoch == CURRENT_PROOF_RELEASE_EPOCH else "range_proof_identity.json" ) + _host_tag = f"{platform.system().lower()}-{platform.machine().lower()}" + _host_proof_name = proof_name[:-5] + f".{_host_tag}.json" proof_candidates = [ + # A source build on this host carries host-suffixed proof identity that + # binds the locally built checker bytes; fall back to the macOS release + # identity when no host-specific record is present. + os.path.join(_here, "..", "release", "evidence", _host_proof_name), os.path.join(_here, "..", "release", "evidence", proof_name), os.path.join(_here, proof_name), ] diff --git a/tools/anubis_program_verify.py b/tools/anubis_program_verify.py index 9e25038..c0081d5 100755 --- a/tools/anubis_program_verify.py +++ b/tools/anubis_program_verify.py @@ -19,6 +19,7 @@ import hashlib import json import os +import platform import re import subprocess import stat @@ -126,8 +127,64 @@ APPROVED_CHECK_COMPILER_SHA256 = ( "0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6" ) +# Architecture-qualified CHECK-compiler anchor (architect sign-off 2026-08-24). +# A NEW clean-source Linux aarch64 anubis, independently double-built byte-identical +# from public anubis-lang (commit e34d0c8) -- NOT the historical macOS 0d6a8f89 and +# NOT the guest's ambient bytes. The Darwin declaration above stays exact in the +# FROZEN inventory-safe-v1 policy body; only the runtime selection below is host-aware. +APPROVED_CHECK_COMPILER_LINUX_AARCH64_SHA256 = ( + "6c3ae920aaaa668b3ec1857b162997c8cd1471949604e6909b898f357683479a" +) + + +def _approved_check_compiler_sha256_for_host() -> str: + """The approved check-compiler digest for THIS host, or refuse. Never a + PATH-only, version-only, or 'any anubis on this machine' rule.""" + system, machine = platform.system(), platform.machine() + if system == "Darwin": + return APPROVED_CHECK_COMPILER_SHA256 + if system == "Linux" and machine == "aarch64": + return APPROVED_CHECK_COMPILER_LINUX_AARCH64_SHA256 + raise Refusal("check-compiler-unsupported-host", f"{system}/{machine}") APPROVED_Z3_PATH = Path("/opt/homebrew/bin/z3") APPROVED_Z3_SHA256 = "ae6c8df33db9c9ae9a80b6044e77cd66529a141d8b25f0620f1e89b409594f48" + +# Architecture-qualified trust anchor extension (architect sign-off 2026-08-23): +# Linux aarch64 admits an independently double-built, byte-reproducible Z3 4.15.4 +# (NOT the guest's system 4.16.0). The Darwin anchor above is preserved exactly. +APPROVED_Z3_LINUX_AARCH64_SHA256 = "b6fcd93b2ccec9aa848ac148c4d9b4270577ad046601f211784586eb9f0135c4" +APPROVED_Z3_SEMVER = "4.15.4" + + +def _approved_z3_for_host() -> tuple[Path, str]: + """Return (path, expected_sha256) of the approved Z3 for THIS host, or refuse. + + Never consults caller PATH. Darwin keeps its exact pinned anchor. Linux + aarch64 resolves the package-shipped Z3 (sibling of this verifier) or a + dev marker naming an absolute path; both are pinned to the exact + double-build digest. No other host is admitted. + """ + system, machine = platform.system(), platform.machine() + if system == "Darwin": + return APPROVED_Z3_PATH, APPROVED_Z3_SHA256 + if system == "Linux" and machine == "aarch64": + here = Path(__file__).resolve().parent + # package ships the Z3 at the package root; the verifier is at + # /tools/ (or, in a flat layout, at /). Check both. + for shipped in (here / "jackal_z3_v4154", here.parent / "jackal_z3_v4154"): + if shipped.is_file(): + return shipped, APPROVED_Z3_LINUX_AARCH64_SHA256 + # dev/repo marker: /release/evidence/approved_z3. names an abspath + for marker in ( + here.parent / "release" / "evidence" / "approved_z3.linux-aarch64", + here / "evidence" / "approved_z3.linux-aarch64", + ): + if marker.is_file(): + parts = marker.read_text().split() + if parts: + return Path(parts[0]), APPROVED_Z3_LINUX_AARCH64_SHA256 + raise Refusal("z3-unavailable") + raise Refusal("z3-unsupported-host", f"{system}/{machine}") PROGRAM_POLICY_CANDIDATES = ( Path(__file__).resolve().parents[1] / "release/program/inventory_safe_v1.json", @@ -654,15 +711,34 @@ def verify_rup( def verify_smt_unsat(path: Path) -> None: + approved_path, approved_sha = _approved_z3_for_host() + # no symlink at the target boundary (ancestor symlinks are rejected by + # resolve(strict=True) diverging from the pre-resolution path component) + if approved_path.is_symlink(): + raise Refusal("z3-symlink") try: - z3_path = APPROVED_Z3_PATH.resolve(strict=True) + z3_path = approved_path.resolve(strict=True) except OSError: raise Refusal("z3-unavailable") from None - if not z3_path.is_file(): + if z3_path.is_symlink() or not z3_path.is_file(): raise Refusal("z3-unavailable") + info = z3_path.stat() + if info.st_uid != os.getuid(): + raise Refusal("z3-owner") + if info.st_mode & (stat.S_IWGRP | stat.S_IWOTH): + raise Refusal("z3-mode") before = sha_file(z3_path) - if before != APPROVED_Z3_SHA256: + if before != approved_sha: raise Refusal("z3-identity-mismatch") + # exact semantic version gate + try: + ver = subprocess.run([str(z3_path), "--version"], capture_output=True, text=True, timeout=30) + except (OSError, subprocess.TimeoutExpired) as exc: + raise Refusal("z3-replay-failed", str(exc)) from None + if APPROVED_Z3_SEMVER not in ver.stdout: + raise Refusal("z3-version-mismatch", ver.stdout.strip()[:60]) + if sha_file(z3_path) != before: + raise Refusal("z3-toctou") try: completed = subprocess.run( [str(z3_path), "-smt2", str(path)], @@ -672,6 +748,7 @@ def verify_smt_unsat(path: Path) -> None: ) except (OSError, subprocess.TimeoutExpired) as exc: raise Refusal("z3-replay-failed", str(exc)) from None + # single-snapshot identity: bytes must be unchanged across the whole check if sha_file(z3_path) != before: raise Refusal("z3-toctou") lines = [line.strip() for line in completed.stdout.splitlines() if line.strip()] @@ -1272,8 +1349,20 @@ def check_program(args: argparse.Namespace) -> dict[str, Any]: raise Refusal("verification-time-invalid") if sha_file(source) != expected_source: raise Refusal("source-pin-mismatch") - if expected_compiler != APPROVED_CHECK_COMPILER_SHA256: + if expected_compiler != _approved_check_compiler_sha256_for_host(): raise Refusal("compiler-not-approved", expected_compiler) + # no symlink at the compiler boundary; safe owner/mode. Exact bytes + TOCTOU + # are then enforced by pinned_executable_snapshot against expected_compiler. + if compiler.is_symlink(): + raise Refusal("compiler-symlink") + try: + _compiler_info = compiler.resolve(strict=True).stat() + except OSError: + raise Refusal("compiler-unavailable") from None + if _compiler_info.st_uid != os.getuid(): + raise Refusal("compiler-owner") + if _compiler_info.st_mode & (stat.S_IWGRP | stat.S_IWOTH): + raise Refusal("compiler-mode") out_root = Path(args.out_root) if out_root.exists() or out_root.is_symlink(): raise Refusal("output-exists", str(out_root)) diff --git a/tools/capability_drift_gate.py b/tools/capability_drift_gate.py index 3321606..81e9f58 100644 --- a/tools/capability_drift_gate.py +++ b/tools/capability_drift_gate.py @@ -17,6 +17,9 @@ INVENTORY_PATH = Path("release/capability_inventory_v1.json") PLUGIN_MANIFEST_PATH = Path("plugins/jackel/.codex-plugin/plugin.json") +SEALED_CODEX_PLUGIN_BASELINE_PATH = Path( + "plugins/jackel/runtime_manifest_baseline_v173.json" +) CODEX_SERVER_PATH = Path("plugins/jackel/mcp/server.py") PROVISIONER_PATH = Path("plugins/jackel/scripts/provision_runtime.py") PACKAGE_EVIDENCE_PATH = Path("release/evidence/package_alignment_v173_release.json") @@ -33,7 +36,17 @@ ".codex-plugin/plugin.json", ".mcp.json", "README.md", + "assets/jackal-linked-evidence-workspace.png", + "assets/jackal-thoth-hellgate-graph.png", + "mcp/advanced.py", + "mcp/certificates/README.md", + "mcp/certificates/hellgate_v1.json.zlib", + "mcp/hellgate_verify.py", + "mcp/measurement.py", "mcp/server.py", + "mcp/stem.py", + "runtime_manifest_baseline_v173.json", + "scripts/launch_mcp.sh", "scripts/launch_mcp.zsh", "scripts/provision_runtime.py", "scripts/verify_plugin.py", @@ -42,17 +55,24 @@ CODEX_PLUGIN_ROOT = Path("plugins/jackel") CODEX_PLUGIN_IDENTITY_PATH = CODEX_PLUGIN_ROOT / "PLUGIN_IDENTITY.sha256" SEALED_CODEX_PLUGIN_VERSION = "0.1.0+codex.20260820135554" +SEALED_CODEX_SERVER_SHA256 = ( + "d307e3534e3451e59f9955ef2aac459bfb8c2d72239aa2325641d0487064f5f5" +) +CODEX_DEVELOPMENT_OVERLAY_INPUTS = (PLUGIN_MANIFEST_PATH, CODEX_SERVER_PATH) CURRENT_SURFACE_BEGIN = "" CURRENT_SURFACE_END = "" TOOL_REFERENCE = re.compile(r"`(jackal_[a-z0-9_]+)`") +TOOL_NAME = re.compile(r"jackal_[a-z0-9_]+\Z") STATUS_ASSIGNMENT = re.compile(r"\bstatus\s*(?:=|:)\s*`?([a-z][a-z0-9-]*)") HEX64 = re.compile(r"[0-9a-f]{64}\Z") NEUTRAL_METADATA_CLAUSES = ( - "copies the parsed runtime result object into structuredContent unchanged", - "only adapter-local tool result is status=refused reason=plugin-busy", + "copies each parsed sealed-runtime result object into structuredContent unchanged", + "removes only the identity-validated _mcp_content transport envelope", + "only transport-local refusal is status=refused reason=plugin-busy", ) +WRAPPER_ONLY_STATUSES = frozenset({"exact-given"}) FORBIDDEN_PROMOTIONAL_CLAIMS = ( "statuses pass through verbatim", "statuses pass through unchanged and never inflate", @@ -200,6 +220,142 @@ def _python_constants(path: Path, required: set[str]) -> dict[str, object]: return values +def _python_frozenset_constants( + path: Path, required: set[str] +) -> dict[str, frozenset[str]]: + try: + tree = ast.parse(_read_text(path), filename=str(path)) + except SyntaxError as error: + refuse("python-parse", f"{path}: {error}") + values: dict[str, frozenset[str]] = {} + for node in tree.body: + if not isinstance(node, (ast.Assign, ast.AnnAssign)): + continue + targets = node.targets if isinstance(node, ast.Assign) else [node.target] + for target in targets: + if not isinstance(target, ast.Name) or target.id not in required: + continue + if target.id in values: + refuse("python-constant", f"{path} repeats {target.id}") + call = node.value + if ( + not isinstance(call, ast.Call) + or not isinstance(call.func, ast.Name) + or call.func.id != "frozenset" + or len(call.args) != 1 + or call.keywords + or not isinstance(call.args[0], (ast.Set, ast.List, ast.Tuple)) + ): + refuse( + "python-constant", + f"{path} {target.id} is not a literal frozenset", + ) + items = call.args[0].elts + strings = [ + item.value + for item in items + if isinstance(item, ast.Constant) and isinstance(item.value, str) + ] + if len(strings) != len(items) or len(strings) != len(set(strings)): + refuse( + "python-constant", + f"{path} {target.id} has non-string or duplicate members", + ) + invalid = sorted(value for value in strings if TOOL_NAME.fullmatch(value) is None) + if invalid: + refuse( + "python-constant", + f"{path} {target.id} has invalid tool names {invalid}", + ) + values[target.id] = frozenset(strings) + missing = sorted(required - set(values)) + if missing: + refuse("python-constant", f"{path} lacks constants {missing}") + return values + + +def _verify_backend_result_mechanism(path: Path, source: str) -> None: + try: + tree = ast.parse(source, filename=str(path)) + except SyntaxError as error: + refuse("python-parse", f"{path}: {error}") + functions = [ + node + for node in tree.body + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + and node.name == "backend_result" + ] + if len(functions) != 1: + refuse("adapter-mechanism", "Codex adapter must define backend_result once") + function = functions[0] + + def assigned_name(node: ast.stmt, name: str) -> ast.expr | None: + if not isinstance(node, ast.Assign) or len(node.targets) != 1: + return None + target = node.targets[0] + return node.value if isinstance(target, ast.Name) and target.id == name else None + + deep_copy_indices: list[int] = [] + pop_indices: list[int] = [] + return_indices: list[int] = [] + for index, node in enumerate(function.body): + structured_value = assigned_name(node, "structured") + if ( + isinstance(structured_value, ast.Call) + and isinstance(structured_value.func, ast.Attribute) + and isinstance(structured_value.func.value, ast.Name) + and structured_value.func.value.id == "copy" + and structured_value.func.attr == "deepcopy" + and len(structured_value.args) == 1 + and isinstance(structured_value.args[0], ast.Name) + and structured_value.args[0].id == "value" + and not structured_value.keywords + ): + deep_copy_indices.append(index) + + content_value = assigned_name(node, "raw_content") + if ( + isinstance(content_value, ast.Call) + and isinstance(content_value.func, ast.Attribute) + and isinstance(content_value.func.value, ast.Name) + and content_value.func.value.id == "structured" + and content_value.func.attr == "pop" + and len(content_value.args) == 2 + and isinstance(content_value.args[0], ast.Constant) + and content_value.args[0].value == "_mcp_content" + and isinstance(content_value.args[1], ast.Constant) + and content_value.args[1].value is None + and not content_value.keywords + ): + pop_indices.append(index) + + if isinstance(node, ast.Return) and isinstance(node.value, ast.Dict): + pairs = { + key.value: value + for key, value in zip(node.value.keys, node.value.values) + if isinstance(key, ast.Constant) and isinstance(key.value, str) + } + if ( + set(pairs) == {"content", "structuredContent"} + and isinstance(pairs["content"], ast.Name) + and pairs["content"].id == "content" + and isinstance(pairs["structuredContent"], ast.Name) + and pairs["structuredContent"].id == "structured" + ): + return_indices.append(index) + if ( + len(deep_copy_indices) != 1 + or len(pop_indices) != 1 + or len(return_indices) != 1 + or not deep_copy_indices[0] < pop_indices[0] < return_indices[0] + ): + refuse( + "adapter-mechanism", + "backend_result must deep-copy the result, extract only _mcp_content, " + "and return the remaining object as structuredContent", + ) + + def _current_surface_block(path: Path) -> str: text = _read_text(path) if text.count(CURRENT_SURFACE_BEGIN) != 1 or text.count(CURRENT_SURFACE_END) != 1: @@ -258,54 +414,178 @@ def _verify_package_pin(root: Path, version: str) -> dict[str, object]: return constants -def _verify_codex_adapter(root: Path, expected_count: int) -> int: +def _verify_codex_adapter( + root: Path, expected_count: int, runtime_names: set[str] +) -> tuple[int, set[str]]: server_path = root / CODEX_SERVER_PATH - constants = _python_constants(server_path, {"EXPECTED_TOOL_COUNT"}) + constants = _python_constants( + server_path, + { + "EXPECTED_TOOL_COUNT", + "EXPECTED_MEASUREMENT_TOOL_COUNT", + "EXPECTED_ADVANCED_TOOL_COUNT", + "EXPECTED_STEM_TOOL_COUNT", + "EXPECTED_UNIFIED_TOOL_COUNT", + }, + ) observed = constants["EXPECTED_TOOL_COUNT"] if observed != expected_count: refuse( "codex-tool-count", f"wrapper EXPECTED_TOOL_COUNT={observed!r} inventory={expected_count}", ) + groups = _python_frozenset_constants( + server_path, + {"MEASUREMENT_TOOL_NAMES", "ADVANCED_TOOL_NAMES", "STEM_TOOL_NAMES"}, + ) + count_bindings = { + "MEASUREMENT_TOOL_NAMES": "EXPECTED_MEASUREMENT_TOOL_COUNT", + "ADVANCED_TOOL_NAMES": "EXPECTED_ADVANCED_TOOL_COUNT", + "STEM_TOOL_NAMES": "EXPECTED_STEM_TOOL_COUNT", + } + additive_names: set[str] = set() + for group_name, count_name in count_bindings.items(): + names = groups[group_name] + if len(names) != constants[count_name]: + refuse( + "codex-tool-count", + f"{group_name} has {len(names)} names but {count_name}=" + f"{constants[count_name]!r}", + ) + overlap = sorted(additive_names & names) + if overlap: + refuse("codex-tool-count", f"additive tool groups overlap at {overlap}") + additive_names.update(names) + runtime_overlap = sorted(runtime_names & additive_names) + if runtime_overlap: + refuse("codex-tool-count", f"runtime and additive tools overlap at {runtime_overlap}") + unified_names = runtime_names | additive_names + unified_count = constants["EXPECTED_UNIFIED_TOOL_COUNT"] + if len(unified_names) != unified_count: + refuse( + "codex-tool-count", + f"wrapper EXPECTED_UNIFIED_TOOL_COUNT={unified_count!r} " + f"but the disjoint roster has {len(unified_names)} names", + ) source = _read_text(server_path) - for required in ( - '"structuredContent": copy.deepcopy(value)', - 'return backend_result({"status": "refused", "reason": "plugin-busy"})', - ): - if required not in source: - refuse("adapter-mechanism", f"Codex adapter lacks {required!r}") - return int(observed) + _verify_backend_result_mechanism(server_path, source) + busy_refusal = 'return backend_result({"status": "refused", "reason": "plugin-busy"})' + if busy_refusal not in source: + refuse("adapter-mechanism", f"Codex adapter lacks {busy_refusal!r}") + return int(unified_count), additive_names def _verify_plugin_metadata( root: Path, expected_count: int, + unified_count: int, status_vocabulary: set[str], sealed_manifest_sha256: str, ) -> str: manifest = _load_json(root / PLUGIN_MANIFEST_PATH) + baseline_path = root / SEALED_CODEX_PLUGIN_BASELINE_PATH + baseline = _load_json(baseline_path) + if hashlib.sha256(baseline_path.read_bytes()).hexdigest() != sealed_manifest_sha256: + refuse( + "plugin-runtime-baseline", + "the retained v1.7.3 Codex manifest does not match the sealed inventory input", + ) + if baseline.get("version") != SEALED_CODEX_PLUGIN_VERSION: + refuse( + "plugin-runtime-baseline", + "the retained v1.7.3 Codex manifest has the wrong sealed version", + ) + version = manifest.get("version") if not isinstance(version, str) or re.fullmatch( r"0\.1\.0\+codex\.\d{14}", version, re.ASCII ) is None: refuse("plugin-version", "Codex plugin must retain the 0.1.0 timestamped line") + + if set(manifest) != set(baseline): + refuse( + "plugin-extension-shape", + "the additive Codex manifest changes the sealed top-level key shape", + ) + for key in ("name", "author", "homepage", "repository", "license", "skills", "mcpServers"): + if manifest.get(key) != baseline.get(key): + refuse( + "plugin-runtime-foundation", + f"the additive Codex manifest changes sealed foundation field {key!r}", + ) + interface = manifest.get("interface") + baseline_interface = baseline.get("interface") if not isinstance(interface, dict): refuse("plugin-metadata", "plugin interface is not an object") + if not isinstance(baseline_interface, dict): + refuse("plugin-runtime-baseline", "sealed interface is not an object") + if set(interface) != set(baseline_interface) | {"brandColor", "screenshots"}: + refuse( + "plugin-extension-shape", + "the additive Codex interface has an undeclared or missing metadata field", + ) + for key in ("developerName", "category", "capabilities", "websiteURL"): + if interface.get(key) != baseline_interface.get(key): + refuse( + "plugin-runtime-foundation", + f"the additive Codex interface changes sealed foundation field {key!r}", + ) + + keywords = manifest.get("keywords") + baseline_keywords = baseline.get("keywords") + if ( + not isinstance(keywords, list) + or not all(isinstance(value, str) and value for value in keywords) + or not isinstance(baseline_keywords, list) + or not all(value in keywords for value in baseline_keywords) + ): + refuse("plugin-metadata", "plugin keywords do not preserve the sealed vocabulary") + if interface.get("displayName") != "JACKAL + THOTH": + refuse("plugin-metadata", "integrated display name is not JACKAL + THOTH") + if interface.get("brandColor") != "#D51F2D": + refuse("plugin-metadata", "integrated brand color is not the graphite/crimson signal") + if interface.get("screenshots") != [ + "./assets/jackal-thoth-hellgate-graph.png", + "./assets/jackal-linked-evidence-workspace.png", + ]: + refuse("plugin-metadata", "integrated screenshot roster is not exact") + prompts = interface.get("defaultPrompt") + if not isinstance(prompts, list) or not prompts or not all( + isinstance(value, str) and value for value in prompts + ): + refuse("plugin-metadata", "plugin default prompts are malformed") + description = interface.get("longDescription") if not isinstance(description, str): refuse("plugin-metadata", "plugin longDescription is not a string") - if f"{expected_count}-tool" not in description: + if f"sealed {expected_count}-tool" not in description: refuse( "current-tool-count", - f"plugin longDescription does not state {expected_count}-tool", + f"plugin longDescription does not state sealed {expected_count}-tool", + ) + if f"unified {unified_count}-tool" not in description: + refuse( + "current-tool-count", + f"plugin longDescription does not state unified {unified_count}-tool", ) if "v1.7.3 release runtime" not in description: refuse("current-release-state", "plugin metadata does not identify release state") + if "THOTH is a named JACKAL subsystem, never a separate server or arithmetic authority" not in description: + refuse("plugin-metadata", "plugin metadata separates THOTH from JACKAL") for clause in NEUTRAL_METADATA_CLAUSES: if clause not in description: refuse("adapter-metadata", f"plugin metadata lacks mechanism clause {clause!r}") - lowered = description.lower() + text_fields = [ + manifest.get("description"), + interface.get("displayName"), + interface.get("shortDescription"), + description, + *prompts, + ] + if not all(isinstance(value, str) and value for value in text_fields): + refuse("plugin-metadata", "plugin textual metadata is malformed") + lowered = "\n".join(text_fields).lower() for forbidden in FORBIDDEN_PROMOTIONAL_CLAIMS: if forbidden in lowered: refuse("promotional-metadata", f"plugin metadata contains {forbidden!r}") @@ -315,16 +595,6 @@ def _verify_plugin_metadata( ) if missing_statuses: refuse("status-vocabulary", f"plugin metadata omits {missing_statuses}") - sealed_manifest = dict(manifest) - sealed_manifest["version"] = SEALED_CODEX_PLUGIN_VERSION - sealed_bytes = ( - json.dumps(sealed_manifest, indent=2, ensure_ascii=False) + "\n" - ).encode("utf-8") - if hashlib.sha256(sealed_bytes).hexdigest() != sealed_manifest_sha256: - refuse( - "plugin-metadata", - "Codex plugin differs from the sealed runtime manifest beyond its cachebuster", - ) return description @@ -341,17 +611,34 @@ def _verify_inventory_artifact( except Exception as error: refuse("inventory-artifact-drift", str(error)) - def input_row(document: dict[str, Any]) -> dict[str, Any]: + def input_row(document: dict[str, Any], relative: Path) -> dict[str, Any]: rows = document.get("inputs") matches = [ row for row in rows if isinstance(row, dict) - and row.get("path") == PLUGIN_MANIFEST_PATH.as_posix() + and row.get("path") == relative.as_posix() ] if isinstance(rows, list) else [] if len(matches) != 1: - refuse("inventory-artifact-drift", "Codex manifest input row is not singular") + refuse( + "inventory-artifact-drift", + f"development-overlay input row is not singular: {relative}", + ) return matches[0] - input_row(generated)["sha256"] = input_row(committed).get("sha256") + sealed_server = input_row(committed, CODEX_SERVER_PATH).get("sha256") + if sealed_server != SEALED_CODEX_SERVER_SHA256: + refuse( + "inventory-artifact-drift", + "published v1.7.3 Codex server identity was rewritten", + ) + # The published inventory remains byte-for-byte immutable. The current + # Codex manifest and adapter are an explicitly separate development + # overlay, closed by PLUGIN_IDENTITY.sha256 and the adapter/metadata gates. + # Normalize only those two rows when replaying the historical generator; + # every kernel/runtime input must still regenerate exactly. + for relative in CODEX_DEVELOPMENT_OVERLAY_INPUTS: + input_row(generated, relative)["sha256"] = input_row( + committed, relative + ).get("sha256") if generated != committed: refuse("inventory-artifact-drift", "generated kernel inventory differs from committed bytes") @@ -378,10 +665,10 @@ def _verify_current_surfaces(root: Path, expected_count: int) -> list[tuple[Path "current-tool-count", f"{relative} current block does not state {expected_count}-tool", ) - if "v1.7.3 release" not in block: + if "v1.7.3" not in block or "release" not in block: refuse( "current-release-state", - f"{relative} current block does not state v1.7.3 release", + f"{relative} current block does not state the v1.7.3 release", ) if "release/capability_inventory_v1.json" not in block: refuse( @@ -417,11 +704,14 @@ def verify_surface(root: Path | str) -> dict[str, object]: names = [row.get("name") for row in records if isinstance(row, dict)] if len(names) != expected_count or len(set(names)) != expected_count: refuse("inventory-contract", "inventory tool names are missing or duplicated") - known_names = set(names) - status_vocabulary = set(vocabulary) + runtime_names = set(names) + status_vocabulary = set(vocabulary) | set(WRAPPER_ONLY_STATUSES) package = _verify_package_pin(root_path, str(release["version"])) - codex_count = _verify_codex_adapter(root_path, expected_count) + codex_count, additive_names = _verify_codex_adapter( + root_path, expected_count, runtime_names + ) + known_names = runtime_names | additive_names blocks = _verify_current_surfaces(root_path, expected_count) inventory_inputs = inventory_document.get("inputs") sealed_manifest_rows = [ @@ -435,6 +725,7 @@ def verify_surface(root: Path | str) -> dict[str, object]: description = _verify_plugin_metadata( root_path, expected_count, + codex_count, status_vocabulary, sealed_manifest_rows[0]["sha256"], ) @@ -456,9 +747,9 @@ def verify_surface(root: Path | str) -> dict[str, object]: status_vocabulary, ) + check_codex_plugin_identity(root_path) inventory_module = _load_inventory_module(root_path) _verify_inventory_artifact(root_path, inventory_module, inventory_document) - check_codex_plugin_identity(root_path) return { "tool_count": expected_count, diff --git a/tools/check_assurance_traceability.py b/tools/check_assurance_traceability.py new file mode 100755 index 0000000..90d6f1e --- /dev/null +++ b/tools/check_assurance_traceability.py @@ -0,0 +1,172 @@ +#!/usr/bin/python3 -B +"""Fail closed on JACKAL requirement, claim, or public-surface drift.""" + +from __future__ import annotations + +import ast +import json +import re +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +BASELINE = ROOT / "assurance/requirements.json" +INVENTORY = ROOT / "release/capability_inventory_v1.json" +CODEX_SERVER = ROOT / "plugins/jackel/mcp/server.py" +ID_PATTERN = re.compile(r"^JCK-[A-Z]+-[0-9]{3}$") +GROUP_CONSTANTS = { + "measurement": "MEASUREMENT_TOOL_NAMES", + "advanced": "ADVANCED_TOOL_NAMES", + "stem": "STEM_TOOL_NAMES", +} + + +def require(condition: bool, message: str) -> None: + if not condition: + raise SystemExit(f"FAIL: {message}") + + +def reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in pairs: + require(key not in result, f"duplicate JSON key: {key}") + result[key] = value + return result + + +def read_json(path: Path) -> dict[str, Any]: + value = json.loads( + path.read_text(encoding="utf-8"), + object_pairs_hook=reject_duplicate_keys, + ) + require(isinstance(value, dict), f"top level is not an object: {path}") + return value + + +def additive_groups() -> dict[str, set[str]]: + tree = ast.parse(CODEX_SERVER.read_text(encoding="utf-8"), filename=str(CODEX_SERVER)) + wanted = set(GROUP_CONSTANTS.values()) + found: dict[str, set[str]] = {} + for node in tree.body: + if not isinstance(node, (ast.Assign, ast.AnnAssign)): + continue + targets = node.targets if isinstance(node, ast.Assign) else [node.target] + for target in targets: + if not isinstance(target, ast.Name) or target.id not in wanted: + continue + call = node.value + require( + isinstance(call, ast.Call) + and isinstance(call.func, ast.Name) + and call.func.id == "frozenset" + and len(call.args) == 1 + and isinstance(call.args[0], ast.Set), + f"{target.id} is not a literal frozenset", + ) + values = { + item.value + for item in call.args[0].elts + if isinstance(item, ast.Constant) and isinstance(item.value, str) + } + require( + len(values) == len(call.args[0].elts), + f"{target.id} contains a non-string or duplicate", + ) + found[target.id] = values + require(set(found) == wanted, "one or more additive public tool groups are missing") + return {group: found[constant] for group, constant in GROUP_CONSTANTS.items()} + + +document = read_json(BASELINE) +require(document.get("schema") == "jackal-assurance-requirements-v1", "schema mismatch") +requirements = document.get("requirements") +require(isinstance(requirements, list) and requirements, "requirements list is empty") +by_id: dict[str, dict[str, Any]] = {} + +for requirement in requirements: + require(isinstance(requirement, dict), "requirement is not an object") + identifier = requirement.get("id") + require( + isinstance(identifier, str) and ID_PATTERN.fullmatch(identifier) is not None, + f"invalid requirement id: {identifier!r}", + ) + require(identifier not in by_id, f"duplicate requirement id: {identifier}") + by_id[identifier] = requirement + shall = requirement.get("shall") + require( + isinstance(shall, str) and " shall " in f" {shall.lower()} ", + f"requirement is not a shall-statement: {identifier}", + ) + require(requirement.get("status") in {"proved", "tested", "planned"}, + f"invalid requirement status: {identifier}") + residuals = requirement.get("residuals") + require(isinstance(residuals, list), f"invalid residuals: {identifier}") + for relation in ("allocation", "verification"): + paths = requirement.get(relation) + require(isinstance(paths, list) and paths, f"{identifier} lacks {relation}") + for raw_path in paths: + require(isinstance(raw_path, str) and raw_path, f"invalid path in {identifier}") + relative = Path(raw_path) + require(not relative.is_absolute() and ".." not in relative.parts, + f"path escapes repository: {raw_path}") + path = (ROOT / relative).resolve() + require(path.is_relative_to(ROOT), f"resolved path escapes repository: {raw_path}") + require(path.is_file() and not path.is_symlink(), f"not a regular file: {raw_path}") + require(identifier in path.read_text(encoding="utf-8"), + f"{identifier} is not cited by {raw_path}") + +for claim in document.get("component_claims", []): + require(claim.get("target") == "SPARK Platinum", "component target is not SPARK Platinum") + identifiers = claim.get("requirement_ids") + require(isinstance(identifiers, list) and identifiers, "component claim has no requirements") + for identifier in identifiers: + require(identifier in by_id, f"component claim cites unknown requirement: {identifier}") + requirement = by_id[identifier] + require(requirement.get("method") == "spark-platinum", + f"component includes non-SPARK requirement: {identifier}") + if claim.get("status") == "proved-local": + require(requirement.get("status") == "proved", + f"proved component includes open requirement: {identifier}") + require(requirement.get("residuals") == [], + f"proved component requirement has functional residual: {identifier}") + +inventory = read_json(INVENTORY) +tools = inventory.get("tools") +require(isinstance(tools, list) and tools, "capability inventory has no tools") +tool_names = [tool.get("name") for tool in tools if isinstance(tool, dict)] +require(len(tool_names) == len(tools) and len(tool_names) == len(set(tool_names)), + "sealed tool names are invalid or duplicated") +discovered_families = { + tool.get("dependency", {}).get("family") + for tool in tools + if isinstance(tool, dict) and isinstance(tool.get("dependency"), dict) +} +require(None not in discovered_families, "a sealed tool lacks a dependency family") + +closure = document.get("surface_closure") +require(isinstance(closure, dict), "surface closure is absent") +sealed = closure.get("sealed_dependency_families") +additive = closure.get("additive_groups") +require(isinstance(sealed, dict), "sealed family closure is invalid") +require(isinstance(additive, dict), "additive group closure is invalid") +require(set(sealed) == discovered_families, + f"sealed assurance coverage drift: expected={sorted(discovered_families)} actual={sorted(sealed)}") +groups = additive_groups() +require(set(additive) == set(groups), "additive assurance group coverage drift") +require(all(groups.values()), "an additive public tool group is empty") +require(not (set(tool_names) & set().union(*groups.values())), + "sealed and additive public tool names overlap") + +closed_status = closure.get("closed_status") +all_closed = all(status == closed_status for status in [*sealed.values(), *additive.values()]) +all_requirements_proved = all(item.get("status") == "proved" for item in requirements) +product_status = document.get("product_claim", {}).get("status") +if product_status == "proved-universal": + require(all_closed and all_requirements_proved, + "whole-product universal claim has open surface or requirements") +else: + require(product_status == "in-progress", "invalid whole-product claim status") + require(not all_closed, "all surfaces are closed but product claim was not reviewed") + +print("JACKAL_ASSURANCE_TRACEABILITY_PASS") diff --git a/tools/domain_pack_verify.py b/tools/domain_pack_verify.py index 99218e2..90d9b78 100644 --- a/tools/domain_pack_verify.py +++ b/tools/domain_pack_verify.py @@ -489,13 +489,17 @@ def _release_tuple(value: object, context: str) -> tuple[int, int, int]: return int(major), int(minor), int(patch) +SUPPORTED_HOSTS = {("Darwin", "arm64"), ("Linux", "aarch64")} + + def validate_host() -> None: system = platform.system() machine = platform.machine() - if system != "Darwin" or machine != "arm64": + if (system, machine) not in SUPPORTED_HOSTS: + supported = ", ".join(f"{s}/{m}" for s, m in sorted(SUPPORTED_HOSTS)) refuse( "unsupported host: domain-pack protocol v1 requires " - f"Apple Silicon macOS, got {system}/{machine}" + f"one of {supported}, got {system}/{machine}" ) @@ -806,7 +810,7 @@ def verify_repository(root: Path | str) -> dict[str, Any]: "schema": schema["registry_schema"], "protocol_version": "1", "authority": "anubis-safe-mode", - "host": "darwin-arm64", + "host": f"{platform.system().lower()}-{platform.machine().lower()}", "verification_scope": "metadata-identity-and-policy-only", "anubis_execution_status": "NOT_EXECUTED", "assurance_status": "NOT_MINTED", diff --git a/tools/formal_receipt.py b/tools/formal_receipt.py index 86e55f4..eb386e9 100755 --- a/tools/formal_receipt.py +++ b/tools/formal_receipt.py @@ -471,9 +471,27 @@ def write_new_file_atomic(path: str | Path, data: bytes, mode: int = 0o600) -> P RANGE_ARCHIVAL_RELEASE_EPOCHS = frozenset({"v1.5.0"}) RATIONAL_ARCHIVAL_RELEASE_EPOCHS = RANGE_ARCHIVAL_RELEASE_EPOCHS INT_CERT_ARCHIVAL_RELEASE_EPOCHS = frozenset() -ARCHIVAL_RANGE_CHECKER_SHA256 = ( - "05c3518b836f239712f897c483a2ddadad9f544e0887b1b7bb1424a27289de8a" -) +def _host_archival_range_checker_sha256() -> str: + """Archival v1.7.0 range checker identity for this host. + + The Omarchy (Linux) edition ships a natively rebuilt v1.7.0 checker; macOS + keeps the original release bytes. A host-suffixed evidence marker selects + the native identity when present. + """ + try: + import platform as _platform + if _platform.system() != "Darwin": + marker = Path(__file__).resolve().parent.parent / "release" / "evidence" / f"archival_range_checker.{_platform.system().lower()}-{_platform.machine().lower()}" + if marker.is_file(): + text = marker.read_text().strip() + if len(text) == 64: + return text + except OSError: + pass + return "05c3518b836f239712f897c483a2ddadad9f544e0887b1b7bb1424a27289de8a" + + +ARCHIVAL_RANGE_CHECKER_SHA256 = _host_archival_range_checker_sha256() ARCHIVAL_RANGE_INVENTORY_FILE = ( "release/coverage/formal_coverage_inventory_v170.json" ) @@ -484,20 +502,49 @@ def write_new_file_atomic(path: str | Path, data: bytes, mode: int = 0o600) -> P "c858e3bfc0ff2809a808170caabbf090077cb54996e76f065dbcd26ffb067d49" ) +def _host_current_identity_sha(base_filename: str, macos_default: str) -> str: + """File sha for the *current*-epoch proof identity on this host. + + A source build on a non-macOS host carries a host-suffixed proof identity + (``.-.json``) binding its locally built checker + bytes. When present it is authoritative for that host; otherwise the pinned + macOS release sha stands. macOS hosts always fall through to the default. + """ + try: + import platform as _platform + tag = f"{_platform.system().lower()}-{_platform.machine().lower()}" + stem = base_filename[:-5] if base_filename.endswith(".json") else base_filename + candidate = Path(__file__).resolve().parent.parent / "release" / "evidence" / f"{stem}.{tag}.json" + if candidate.is_file(): + return hashlib.sha256(candidate.read_bytes()).hexdigest() + except OSError: + pass + return macos_default + + _PROOF_COMPATIBILITY = { ("range", RANGE_PROOF_IDENTITY_V1_SCHEMA, "v1.5.0"): { - "file_sha256": "1b2d623904930d748bfbf489637e0e8aa720188e7d68f5250e5bd8f257b89a67", + "file_sha256": _host_current_identity_sha( + "range_proof_identity.json", + "1b2d623904930d748bfbf489637e0e8aa720188e7d68f5250e5bd8f257b89a67", + ), "mode": "replay-only", "checker_sha256": ARCHIVAL_RANGE_CHECKER_SHA256, "theorem": "JackalIv.Cert.request_bound_certified_release", }, ("range", RANGE_PROOF_IDENTITY_V2_SCHEMA, CURRENT_PROOF_RELEASE_EPOCH): { - "file_sha256": "84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c", + "file_sha256": _host_current_identity_sha( + "range_proof_identity_v172.json", + "84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c", + ), "mode": "current", "theorem": "JackalIv.Cert.request_bound_certified_release", }, ("int_cert", INT_CERT_PROOF_IDENTITY_V2_SCHEMA, CURRENT_PROOF_RELEASE_EPOCH): { - "file_sha256": "a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf", + "file_sha256": _host_current_identity_sha( + "int_cert_proof_identity_v172.json", + "a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf", + ), "mode": "current", "theorem": "JackalIv.IntCert.int_cert_sound", }, diff --git a/tools/gaussian_release.py b/tools/gaussian_release.py index a1c69ef..3789615 100644 --- a/tools/gaussian_release.py +++ b/tools/gaussian_release.py @@ -5,6 +5,7 @@ import argparse import hashlib import json +import platform import os import re import subprocess @@ -207,7 +208,9 @@ def release(args: argparse.Namespace) -> dict[str, Any]: "integrate", args.expression, canonical_lo, canonical_hi, canonical_tolerance ) here = Path(__file__).resolve().parent + _host_tag = f"{platform.system().lower()}-{platform.machine().lower()}" proof_candidates = [ + here.parent / "release" / "evidence" / f"gaussian_proof_identity.{_host_tag}.json", here.parent / "release" / "evidence" / "gaussian_proof_identity.json", here / "gaussian_proof_identity.json", ] diff --git a/tools/hellgate_generate.py b/tools/hellgate_generate.py new file mode 100644 index 0000000..5172cff --- /dev/null +++ b/tools/hellgate_generate.py @@ -0,0 +1,453 @@ +#!/usr/bin/env python3 +"""Generate the fixed HELLGATE nonlinear-ground-state trial certificate. + +This is an untrusted numerical producer. It uses high-precision Taylor +multiple shooting to construct a positive even trial density. The emitted +decimal coefficients are only candidate data; ``mcp/hellgate_verify.py`` +independently reparses them as exact rationals and decides whether they prove +anything. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import zlib +import sys +from fractions import Fraction +from pathlib import Path + +import mpmath as mp + + +if hasattr(sys, "set_int_max_str_digits"): + # Exact-rational continuity propagation can legitimately create long + # denominators. This is an offline producer; the verifier has independent + # byte and integer-digit budgets and does not inherit this setting. + sys.set_int_max_str_digits(0) + +mp.mp.dps = 110 + +EPSILON = mp.mpf(1) / 20 +LAMBDA = mp.mpf(7) / 10 +RIGHT = mp.mpf("2.5") +MATCH = mp.mpf("1.7") +SHOOT_STEP = mp.mpf("0.005") +CERT_STEP = mp.mpf("0.01") +SHOOT_DEGREE = 50 +CERT_DEGREE = 38 +TAIL_TERMS = 80 +PARAMETERS = 3 + + +def canonical_bytes(value: object) -> bytes: + return json.dumps( + value, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + + +def rational_text(value: mp.mpf, digits: int = 60) -> str: + """Round producer data to a decimal that the checker treats as rational.""" + text = mp.nstr(value, digits, strip_zeros=False) + return str(Fraction(text)) + + +def potential_series(origin: mp.mpf, degree: int) -> mp.mpf: + value = mp.mpf(0) + if degree <= 6: + value += mp.binomial(6, degree) * origin ** (6 - degree) + if degree <= 4: + value -= 5 * mp.binomial(4, degree) * origin ** (4 - degree) + if degree <= 2: + value += 4 * mp.binomial(2, degree) * origin ** (2 - degree) + return value + + +def convolution(left: list[mp.mpf], right: list[mp.mpf], degree: int) -> mp.mpf: + return mp.fsum(left[index] * right[degree - index] for index in range(degree + 1)) + + +def taylor_coefficients( + state: tuple[mp.mpf, ...], origin: mp.mpf, eigenvalue: mp.mpf, degree: int +) -> tuple[list[mp.mpf], list[mp.mpf], list[mp.mpf], list[list[mp.mpf]], list[list[mp.mpf]], list[list[mp.mpf]]]: + q = [state[0]] + [mp.mpf(0)] * degree + w = [state[1]] + [mp.mpf(0)] * degree + mass = [state[2]] + [mp.mpf(0)] * degree + q_sens: list[list[mp.mpf]] = [] + w_sens: list[list[mp.mpf]] = [] + mass_sens: list[list[mp.mpf]] = [] + for parameter in range(PARAMETERS): + offset = 3 + 3 * parameter + q_sens.append([state[offset]] + [mp.mpf(0)] * degree) + w_sens.append([state[offset + 1]] + [mp.mpf(0)] * degree) + mass_sens.append([state[offset + 2]] + [mp.mpf(0)] * degree) + density = [mp.exp(q[0])] + [mp.mpf(0)] * degree + + for order in range(degree): + divisor = order + 1 + q[divisor] = 2 * w[order] / divisor + w[divisor] = ( + ( + potential_series(origin, order) + + LAMBDA * density[order] + - (eigenvalue if order == 0 else 0) + ) + / (EPSILON * EPSILON) + - convolution(w, w, order) + ) / divisor + mass[divisor] = density[order] / divisor + + for parameter in range(PARAMETERS): + q_sens[parameter][divisor] = 2 * w_sens[parameter][order] / divisor + eigen_source = -1 if parameter == 2 and order == 0 else 0 + w_sens[parameter][divisor] = ( + ( + LAMBDA + * convolution(density, q_sens[parameter], order) + + eigen_source + ) + / (EPSILON * EPSILON) + - 2 * convolution(w, w_sens[parameter], order) + ) / divisor + mass_sens[parameter][divisor] = convolution( + density, q_sens[parameter], order + ) / divisor + + density[divisor] = mp.fsum( + index * q[index] * density[divisor - index] + for index in range(1, divisor + 1) + ) / divisor + + return q, w, mass, q_sens, w_sens, mass_sens + + +def evaluate(coefficients: list[mp.mpf], step: mp.mpf) -> mp.mpf: + value = mp.mpf(0) + for coefficient in reversed(coefficients): + value = value * step + coefficient + return value + + +def advance( + state: tuple[mp.mpf, ...], origin: mp.mpf, step: mp.mpf, eigenvalue: mp.mpf, degree: int +) -> tuple[mp.mpf, ...]: + q, w, mass, q_sens, w_sens, mass_sens = taylor_coefficients( + state, origin, eigenvalue, degree + ) + result = [evaluate(q, step), evaluate(w, step), evaluate(mass, step)] + for parameter in range(PARAMETERS): + result.extend( + [ + evaluate(q_sens[parameter], step), + evaluate(w_sens[parameter], step), + evaluate(mass_sens[parameter], step), + ] + ) + return tuple(result) + + +def integrate( + start: mp.mpf, + stop: mp.mpf, + state: tuple[mp.mpf, ...], + eigenvalue: mp.mpf, + *, + step: mp.mpf = SHOOT_STEP, + degree: int = SHOOT_DEGREE, +) -> tuple[mp.mpf, ...]: + direction = 1 if stop >= start else -1 + signed_step = direction * abs(step) + position = start + while direction * (stop - position) > 0: + current = signed_step + if direction * (position + current - stop) > 0: + current = stop - position + state = advance(state, position, current, eigenvalue, degree) + position += current + return state + + +def potential(x: mp.mpf) -> mp.mpf: + return x**6 - 5 * x**4 + 4 * x**2 + + +def potential_derivative(x: mp.mpf) -> mp.mpf: + return 6 * x**5 - 20 * x**3 + 8 * x + + +def tail_coefficients(eigenvalue: mp.mpf, count: int) -> list[mp.mpf]: + """Formal decaying Riccati coefficients w=sum a_k*x^(3-2k).""" + epsilon = EPSILON + coefficients: list[mp.mpf] = [] + for index in range(count): + potential_coefficient = mp.mpf(0) + if index == 0: + potential_coefficient = 1 + elif index == 1: + potential_coefficient = -5 + elif index == 2: + potential_coefficient = 4 + elif index == 3: + potential_coefficient = -eigenvalue + derivative = mp.mpf(0) + prior = index - 2 + if prior >= 0: + derivative = -epsilon * epsilon * coefficients[prior] * (3 - 2 * prior) + known_square = mp.fsum( + coefficients[left] * coefficients[index - left] + for left in range(1, index) + ) + if index == 0: + coefficient = mp.mpf(-20) + else: + coefficient = ( + potential_coefficient + + derivative + - epsilon * epsilon * known_square + ) / (2 * epsilon * epsilon * coefficients[0]) + coefficients.append(coefficient) + return coefficients + + +def tail_value(eigenvalue: mp.mpf) -> mp.mpf: + coefficients = tail_coefficients(eigenvalue, TAIL_TERMS) + return mp.fsum( + coefficient * RIGHT ** (3 - 2 * index) + for index, coefficient in enumerate(coefficients) + ) + + +def tail_value_derivative(eigenvalue: mp.mpf) -> mp.mpf: + return mp.diff(tail_value, eigenvalue) + + +def solve_parameters() -> tuple[mp.mpf, mp.mpf, mp.mpf]: + left_q = mp.mpf("-120.048245944203569382657017663048096374") + right_q = mp.mpf("-116.633507058547647528503576301885412018") + eigenvalue = mp.mpf("-4.615978698574496507441387083141344198") + + for _ in range(9): + forward = ( + left_q, + mp.mpf(0), + mp.mpf(0), + mp.mpf(1), 0, 0, + 0, 0, 0, + 0, 0, 0, + ) + backward = ( + right_q, + tail_value(eigenvalue), + mp.mpf(0), + 0, 0, 0, + mp.mpf(1), 0, 0, + 0, tail_value_derivative(eigenvalue), 0, + ) + left = integrate(mp.mpf(0), MATCH, forward, eigenvalue) + right = integrate(RIGHT, MATCH, backward, eigenvalue) + residual = mp.matrix( + [left[0] - right[0], left[1] - right[1], left[2] - right[2] - mp.mpf("0.5")] + ) + jacobian = mp.matrix(PARAMETERS, PARAMETERS) + for parameter in range(PARAMETERS): + jacobian[0, parameter] = left[3 + 3 * parameter] - right[3 + 3 * parameter] + jacobian[1, parameter] = left[4 + 3 * parameter] - right[4 + 3 * parameter] + jacobian[2, parameter] = left[5 + 3 * parameter] - right[5 + 3 * parameter] + correction = mp.lu_solve(jacobian, -residual) + left_q += correction[0] + right_q += correction[1] + eigenvalue += correction[2] + if max(abs(value) for value in residual) < mp.mpf("1e-90"): + break + return left_q, right_q, eigenvalue + + +def q_piece( + state: tuple[mp.mpf, ...], origin: mp.mpf, step: mp.mpf, eigenvalue: mp.mpf +) -> tuple[list[mp.mpf], list[mp.mpf], tuple[mp.mpf, ...]]: + q, *_ = taylor_coefficients(state, origin, eigenvalue, CERT_DEGREE) + density = [mp.exp(q[0])] + [mp.mpf(0)] * CERT_DEGREE + for order in range(1, CERT_DEGREE + 1): + density[order] = mp.fsum( + index * q[index] * density[order - index] + for index in range(1, order + 1) + ) / order + scaled_q = [coefficient * step**index for index, coefficient in enumerate(q)] + scaled_density = [ + coefficient * step**index for index, coefficient in enumerate(density) + ] + return ( + scaled_q, + scaled_density, + advance(state, origin, step, eigenvalue, CERT_DEGREE), + ) + + +def polynomial_value(coefficients: list[Fraction]) -> Fraction: + return sum(coefficients, Fraction(0)) + + +def polynomial_derivative_at_one(coefficients: list[Fraction]) -> Fraction: + return sum( + Fraction(index) * coefficient + for index, coefficient in enumerate(coefficients) + if index + ) + + +def quantize_piece(coefficients: list[mp.mpf]) -> list[Fraction]: + return [Fraction(rational_text(value)) for value in coefficients] + + +def build_chain( + start: mp.mpf, + stop: mp.mpf, + q_value: mp.mpf, + w_value: mp.mpf, + eigenvalue: mp.mpf, +) -> list[dict[str, object]]: + direction = 1 if stop >= start else -1 + count = int(mp.nint(abs((stop - start) / CERT_STEP))) + if count < 1: + raise RuntimeError("certificate chain must contain at least one piece") + step = (stop - start) / count + state: tuple[mp.mpf, ...] = ( + q_value, w_value, mp.mpf(0), + 0, 0, 0, 0, 0, 0, 0, 0, 0, + ) + pieces: list[dict[str, object]] = [] + prior_value: Fraction | None = None + prior_derivative: Fraction | None = None + for index in range(count): + position = start + index * step + current = step + raw, raw_density, state = q_piece(state, position, current, eigenvalue) + coefficients = quantize_piece(raw) + density_coefficients = quantize_piece(raw_density) + if prior_value is not None and prior_derivative is not None: + coefficients[0] = prior_value + coefficients[1] = Fraction(rational_text(current)) * prior_derivative + elif position == 0: + coefficients[1] = Fraction(0) + value_at_end = polynomial_value(coefficients) + derivative_at_end = polynomial_derivative_at_one(coefficients) / Fraction( + rational_text(current) + ) + pieces.append( + { + "origin": rational_text(position), + "step": rational_text(current), + "coefficients": [str(value) for value in coefficients], + "density_coefficients": [ + str(value) for value in density_coefficients + ], + } + ) + prior_value = value_at_end + prior_derivative = derivative_at_end + return pieces + + +def join_chains(forward: list[dict[str, object]], backward: list[dict[str, object]]) -> None: + left = forward[-1] + right = backward[-1] + left_coefficients = [Fraction(value) for value in left["coefficients"]] # type: ignore[index] + right_coefficients = [Fraction(value) for value in right["coefficients"]] # type: ignore[index] + left_step = Fraction(left["step"]) # type: ignore[arg-type] + right_step = Fraction(right["step"]) # type: ignore[arg-type] + left_value = polynomial_value(left_coefficients) + left_slope_s = polynomial_derivative_at_one(left_coefficients) + right_value = polynomial_value(right_coefficients) + right_slope_x = polynomial_derivative_at_one(right_coefficients) / right_step + value_delta = right_value - left_value + slope_delta = left_step * right_slope_x - left_slope_s + left_coefficients[2] += 3 * value_delta - slope_delta + left_coefficients[3] += slope_delta - 2 * value_delta + left["coefficients"] = [str(value) for value in left_coefficients] + + +def bind_backward_tail_and_continuity( + backward: list[dict[str, object]], tail: list[Fraction] +) -> None: + right = Fraction(5, 2) + tail_w = sum( + ( + coefficient * right ** (3 - 2 * index) + for index, coefficient in enumerate(tail) + ), + Fraction(0), + ) + prior_value: Fraction | None = None + prior_derivative: Fraction | None = None + for index, piece in enumerate(backward): + coefficients = [Fraction(value) for value in piece["coefficients"]] # type: ignore[index] + step = Fraction(piece["step"]) # type: ignore[arg-type] + if index == 0: + coefficients[1] = step * 2 * tail_w + else: + assert prior_value is not None and prior_derivative is not None + coefficients[0] = prior_value + coefficients[1] = step * prior_derivative + prior_value = polynomial_value(coefficients) + prior_derivative = polynomial_derivative_at_one(coefficients) / step + piece["coefficients"] = [str(value) for value in coefficients] + + +def generate() -> dict[str, object]: + left_q, right_q, eigenvalue = solve_parameters() + eigenvalue_fraction = Fraction(rational_text(eigenvalue)) + tail_fraction = [ + Fraction(rational_text(value)) + for value in tail_coefficients(eigenvalue, TAIL_TERMS) + ] + tail_fraction[:3] = [Fraction(-20), Fraction(50), Fraction(21)] + forward = build_chain(mp.mpf(0), MATCH, left_q, mp.mpf(0), eigenvalue) + backward = build_chain( + RIGHT, MATCH, right_q, tail_value(eigenvalue), eigenvalue + ) + bind_backward_tail_and_continuity(backward, tail_fraction) + join_chains(forward, backward) + document: dict[str, object] = { + "schema": "jackal-hellgate-barta-certificate-v1", + "problem": { + "epsilon": "1/20", + "lambda": "7/10", + "potential": "x^6-5*x^4+4*x^2", + "mass": "1", + "parity": "even", + "positivity": "strict", + }, + "representation": "piecewise-log-density-power-v1", + "center_eigenvalue": str(eigenvalue_fraction), + "right_endpoint": rational_text(RIGHT), + "match_point": rational_text(MATCH), + "tail_terms": TAIL_TERMS, + "tail_coefficients": [str(value) for value in tail_fraction], + "forward_pieces": forward, + "backward_pieces": backward, + "nonclaims": [ + "producer arithmetic is untrusted until independent exact-rational replay accepts", + "certificate is specific to the declared HELLGATE parameters", + "bounded is not formal-bounded; no Lean theorem checks this certificate", + ], + } + document["certificate_sha256"] = hashlib.sha256(canonical_bytes(document)).hexdigest() + return document + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("output", type=Path) + arguments = parser.parse_args() + document = generate() + payload = canonical_bytes(document) + b"\n" + if arguments.output.suffix == ".zlib": + payload = zlib.compress(payload, 9) + arguments.output.write_bytes(payload) + print(document["certificate_sha256"]) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/hellgate_trial_oracle.py b/tools/hellgate_trial_oracle.py new file mode 100644 index 0000000..422b2ca --- /dev/null +++ b/tools/hellgate_trial_oracle.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 -B +"""Untrusted high-precision differential oracle for the HELLGATE trial. + +This module is intentionally outside the certificate decision path. It +numerically integrates the encoded piecewise log density with mpmath so tests +can detect disagreement between an independent floating-point path and the +exact-rational verifier. Its output is never evidence and never upgrades a +JACKAL status. +""" + +from __future__ import annotations + +import argparse +import json +import zlib +from pathlib import Path + +import mpmath as mp + + +def _mp_rational(token: str) -> mp.mpf: + numerator, separator, denominator = token.partition("/") + if separator: + return mp.mpf(numerator) / mp.mpf(denominator) + return mp.mpf(numerator) + + +def _evaluate(coefficients: list[mp.mpf], point: mp.mpf) -> mp.mpf: + value = mp.mpf(0) + for coefficient in reversed(coefficients): + value = value * point + coefficient + return value + + +def compute_oracle( + document: dict[str, object], *, decimal_places: int = 80, quadrature_order: int = 24 +) -> dict[str, object]: + """Return an unverified numerical differential oracle for one certificate.""" + mp.mp.dps = decimal_places + nodes, weights = mp.gauss_quadrature(quadrature_order, "legendre") + raw = { + key: mp.mpf(0) + for key in ("mass", "quartic", "x2", "x4", "x6", "q_derivative_squared") + } + pieces = document["forward_pieces"] + document["backward_pieces"] + for piece in pieces: + origin = _mp_rational(piece["origin"]) + step = _mp_rational(piece["step"]) + coefficients = [_mp_rational(item) for item in piece["coefficients"]] + derivative = [ + (index + 1) * coefficients[index + 1] / step + for index in range(len(coefficients) - 1) + ] + for node, weight in zip(nodes, weights, strict=True): + unit_point = (node + 1) / 2 + physical_weight = weight * abs(step) / 2 + x_value = origin + step * unit_point + q_value = _evaluate(coefficients, unit_point) + density = mp.exp(q_value) + q_derivative = _evaluate(derivative, unit_point) + raw["mass"] += physical_weight * density + raw["quartic"] += physical_weight * density * density + raw["x2"] += physical_weight * x_value**2 * density + raw["x4"] += physical_weight * x_value**4 * density + raw["x6"] += physical_weight * x_value**6 * density + raw["q_derivative_squared"] += ( + physical_weight * q_derivative * q_derivative * density + ) + + right = _mp_rational(document["right_endpoint"]) + tail = [_mp_rational(item) for item in document["tail_coefficients"]] + tail_q = _mp_rational(document["backward_pieces"][0]["coefficients"][0]) + + def tail_w(x_value: mp.mpf) -> mp.mpf: + return mp.fsum( + coefficient * x_value ** (3 - 2 * index) + for index, coefficient in enumerate(tail) + ) + + def tail_log_density(x_value: mp.mpf) -> mp.mpf: + value = tail_q + for index, coefficient in enumerate(tail): + power = 3 - 2 * index + if power == -1: + value += 2 * coefficient * mp.log(x_value / right) + else: + value += ( + 2 + * coefficient + * (x_value ** (power + 1) - right ** (power + 1)) + / (power + 1) + ) + return value + + def tail_integral(quantity: str) -> mp.mpf: + def integrand(x_value: mp.mpf) -> mp.mpf: + density = mp.exp(tail_log_density(x_value)) + if quantity == "mass": + return density + if quantity == "quartic": + return density * density + if quantity == "q_derivative_squared": + return 4 * tail_w(x_value) ** 2 * density + return x_value ** int(quantity[1:]) * density + + return mp.quad( + integrand, + [right, right + mp.mpf("0.25"), right + 1, mp.inf], + ) + + for quantity in raw: + raw[quantity] = 2 * (raw[quantity] + tail_integral(quantity)) + + normalization = raw["mass"] + quartic = raw["quartic"] / normalization**2 + moments = { + key: raw[key] / normalization + for key in ("x2", "x4", "x6") + } + # JACKAL exact: parsed=(1/20)^2/4; exact=1/1600; status=exact (not formal). + kinetic = raw["q_derivative_squared"] / normalization / 1600 + potential = moments["x6"] - 5 * moments["x4"] + 4 * moments["x2"] + energy = kinetic + potential + mp.mpf(7) * quartic / 20 + eigenvalue_from_energy = energy + mp.mpf(7) * quartic / 20 + virial = ( + 2 * kinetic + - 6 * moments["x6"] + + 20 * moments["x4"] + - 8 * moments["x2"] + + mp.mpf(7) * quartic / 20 + ) + + def render(value: mp.mpf) -> str: + return mp.nstr(value, decimal_places - 20, strip_zeros=False) + + return { + "schema": "jackal-hellgate-untrusted-trial-oracle-v1", + "status": "unverified-numerical-oracle", + "fields": { + "normalization": render(normalization), + "quartic_norm": render(quartic), + "moments": {key: render(value) for key, value in moments.items()}, + "kinetic_energy": render(kinetic), + "energy_functional": render(energy), + "eigenvalue_from_energy": render(eigenvalue_from_energy), + "virial_residual": render(virial), + }, + "non_claims": [ + "mpmath floating-point quadrature is an untrusted test oracle, not certificate evidence", + "agreement with the exact-rational checker does not upgrade bounded to formal-bounded", + ], + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("certificate", type=Path) + arguments = parser.parse_args() + raw = zlib.decompress(arguments.certificate.read_bytes()) + document = json.loads(raw) + print(json.dumps(compute_oracle(document), sort_keys=True, separators=(",", ":"))) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/int_cert_release.py b/tools/int_cert_release.py index 338743f..eb13b94 100755 --- a/tools/int_cert_release.py +++ b/tools/int_cert_release.py @@ -16,6 +16,7 @@ import argparse import hashlib import json +import platform import os import re import subprocess @@ -247,7 +248,10 @@ def release(args: argparse.Namespace) -> dict[str, Any]: if args.release_epoch == CURRENT_PROOF_RELEASE_EPOCH else "int_cert_proof_identity.json" ) + _host_tag = f"{platform.system().lower()}-{platform.machine().lower()}" + _host_proof_name = proof_name[:-5] + f".{_host_tag}.json" proof_candidates = [ + here.parent / "release" / "evidence" / _host_proof_name, here.parent / "release" / "evidence" / proof_name, here / proof_name, ] diff --git a/tools/lean_admission_audit.py b/tools/lean_admission_audit.py index 5384e07..937516e 100644 --- a/tools/lean_admission_audit.py +++ b/tools/lean_admission_audit.py @@ -34,20 +34,33 @@ DEFAULT_COMMAND_TIMEOUT_SECONDS = 180.0 MAX_COMMAND_TIMEOUT_SECONDS = 3600.0 + +def _host_tag() -> str: + import platform + return f"{platform.system().lower()}-{platform.machine().lower()}" + + +def _host_identity(rel: Path) -> Path: + """Prefer a host-suffixed proof identity that binds locally built checker + bytes; fall back to the committed macOS record.""" + host = rel.with_name(rel.stem + f".{_host_tag()}.json") + return host if (DEFAULT_ROOT / host).is_file() else rel + + IDENTITY_CONFIGS = ( ( "range", - Path("release/evidence/range_proof_identity_v172.json"), + _host_identity(Path("release/evidence/range_proof_identity_v172.json")), "jackal-range-proof-identity-v2", ), ( "gaussian", - Path("release/evidence/gaussian_proof_identity.json"), + _host_identity(Path("release/evidence/gaussian_proof_identity.json")), "jackal-gaussian-proof-identity-v1", ), ( "int-cert", - Path("release/evidence/int_cert_proof_identity_v172.json"), + _host_identity(Path("release/evidence/int_cert_proof_identity_v172.json")), "jackal-int-cert-proof-identity-v2", ), ) @@ -897,7 +910,13 @@ def main(argv: list[str] | None = None) -> int: ) return 0 if args.write: - write_atomic(root / ARTIFACT_REL, render_audit(root)) + artifact = ARTIFACT_REL + import platform + if platform.system() != "Darwin": + artifact = ARTIFACT_REL.with_name( + ARTIFACT_REL.stem + f".{_host_tag()}.json") + write_atomic(root / artifact, render_audit(root)) + print(f"LEAN_ADMISSION_AUDIT_WROTE {artifact}") else: check_committed(root) document = strict_json(root / ARTIFACT_REL)