diff --git a/.github/workflows/gaussian-proof-gate.yml b/.github/workflows/gaussian-proof-gate.yml index a9213ac..24d1cc6 100644 --- a/.github/workflows/gaussian-proof-gate.yml +++ b/.github/workflows/gaussian-proof-gate.yml @@ -32,6 +32,12 @@ jobs: python3 release/tools/range_proof_identity.py check --lane range --proof-only python3 release/tools/range_proof_identity.py check --lane int-cert --proof-only + - name: Reject repository-wide Lean admissions and trust bypasses + run: | + python3 -B tools/lean_admission_audit.py --source-check + python3 -B -m unittest \ + tests.lean_admission_audit_test.LeanAdmissionAuditMutationTest -v + # The committed checker hashes are for the recorded macOS/arm64 builds. # A release seal must additionally run the full, platform-matching gate: # python3 release/tools/gaussian_proof_identity.py check --lane gaussian @@ -45,16 +51,24 @@ jobs: steps: - name: Check out exact revision uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 - - name: Mechanical 38-tool inventory and pack-reachability lock + - name: Mechanical 41-tool inventory and pack-reachability lock run: | python3 - <<'PY' import json tools = json.load(open("plugin/hermes/tools.json"))["tools"] names = sorted(t["name"] for t in tools) - assert len(names) == 38, f"expected 38 tools, found {len(names)}" + assert len(names) == 41, f"expected 41 tools, found {len(names)}" assert "jackal_claim" in names and "jackal_verify_bundle" in names assert "jackal_integrate_bound_cert" in names + program_tools = { + "jackal_anubis_check_program", + "jackal_anubis_verify_program", + "jackal_anubis_verify_program_receipt", + } + assert program_tools <= set(names), "program-evidence tool omission" # Every domain-pack operation must be reachable through some tool. # `core.exact.mod_pow.v1` routes to the `mod-pow` engine command that # `jackal_mod_pow` already exposes, so it is exempt BY NAME. @@ -69,11 +83,18 @@ jobs: assert len(owners) == 1, f"{op} exposed by {owners}, expected 1" full = json.load(open("plugin/hermes/profiles/full.json"))["tools"] assert full == [t["name"] for t in tools], "full profile != tools.json order" - print(f"38-tool inventory locked: {len(names)} tools; " + print(f"41-tool inventory locked: {len(names)} tools; " f"{sum(len(p['operation_ids']) for p in registry['packs'])} " "pack operations reachable") PY + - name: Canonical capability inventory + run: | + python3 -B tools/capability_inventory.py --check + python3 -B -m unittest tests.capability_inventory_test -v + python3 -B tools/capability_drift_gate.py + python3 -B -m unittest tests.capability_drift_gate_test -v + - name: v1.5.0 compatibility floor (additive-only surface lock) run: python3 tools/compat_floor.py --check diff --git a/.github/workflows/jackal-codex-plugin.yml b/.github/workflows/jackal-codex-plugin.yml index d684887..9385323 100644 --- a/.github/workflows/jackal-codex-plugin.yml +++ b/.github/workflows/jackal-codex-plugin.yml @@ -19,6 +19,9 @@ jobs: 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: | @@ -27,6 +30,14 @@ jobs: test -x /opt/homebrew/bin/python3 /opt/homebrew/bin/python3 -c 'import sys; assert sys.version_info >= (3, 10)' + - name: Verify canonical capability and documentation contracts + run: | + /opt/homebrew/bin/python3 -B tools/capability_inventory.py --check + /opt/homebrew/bin/python3 -B tools/capability_drift_gate.py + /opt/homebrew/bin/python3 -B -m unittest \ + tests.capability_inventory_test \ + tests.capability_drift_gate_test -v + - name: Run complete Codex plugin unit suite run: /opt/homebrew/bin/python3 -B -m unittest discover -s tests/codex_plugin -v diff --git a/GETTING-STARTED.md b/GETTING-STARTED.md index 725ce48..50dfe54 100644 --- a/GETTING-STARTED.md +++ b/GETTING-STARTED.md @@ -10,24 +10,30 @@ loudly, with a named reason — instead of printing something plausible. This guide takes you from install to reading your first certified result. + +The v1.7.3 release agent surface contains 41-tool entries, derived and checked +in `release/capability_inventory_v1.json`. The annotated tag, GitHub release, +package receipt, and downloaded asset must all bind the same release identity. + + ## 1. Install ### Option A — download the release package (Apple Silicon macOS) -The GitHub release ships one sealed package, -`jackal-v1.7.0-macos-arm64.tar.gz`; the `jackal-native` binary lives INSIDE -it, next to every pinned checker and wrapper. Verify the tarball against the +The current public release ships +`jackal-v1.7.3-macos-arm64.tar.gz`; the `jackal-native` binary lives inside it, +next to every pinned checker and wrapper. Verify the tarball against the release `SHA256SUMS`, extract it, and check the binary against [`PROVENANCE.md`](PROVENANCE.md): ```bash git clone https://github.com/AnubisQuantumCipher/jackal.git cd jackal -# download jackal-v1.7.0-macos-arm64.tar.gz (+ SHA256SUMS) from the +# download jackal-v1.7.3-macos-arm64.tar.gz (+ SHA256SUMS) from the # Releases page into this directory, then: shasum -a 256 -c SHA256SUMS --ignore-missing -tar -xzf jackal-v1.7.0-macos-arm64.tar.gz -cp jackal-v1.7.0-macos-arm64/jackal-native . +tar -xzf jackal-v1.7.3-macos-arm64.tar.gz +cp jackal-v1.7.3-macos-arm64/jackal-native . shasum -a 256 jackal-native # compare against PROVENANCE.md chmod +x jackal-native ./jackal self-test @@ -252,10 +258,11 @@ embedded certificate, tested end to end in ## 5c. The Hermes / MCP-style plugin -The bundled `plugin/hermes/jackal_hermes` exposes thirty-four tools — the -eleven formal wrappers, twenty-one weaker-lane adapters, and the two v1.6.0 -claim-kernel front doors — that an MCP-speaking host -can call over stdio JSON-RPC or a small HTTP wrapper. A recomputed +The bundled `plugin/hermes/jackal_hermes` exposes forty-one tools: eleven +formal wrappers, twenty-one weaker-lane adapters, two claim-kernel front doors, +four domain-pack lanes, and three inventory-safe-v1 Anubis program-evidence +lanes. An MCP-speaking host can call them over stdio JSON-RPC or a small HTTP +wrapper. A recomputed bundle hash MUST equal the pinned value in `release/MANIFEST.sha256` under `plugin_hermes` before the plugin accepts any request: @@ -270,24 +277,24 @@ plugin/hermes/jackal_hermes call jackal_verify_receipt \ "$(< /tmp/formal-receipt.json)" ``` -Thirty-four tools total: eleven proof-carrying (`jackal_range_bound`, +Forty-one tools total: eleven proof-carrying (`jackal_range_bound`, `jackal_gaussian_integral`, `jackal_integrate_bound_cert`, -`jackal_sqrt_rat_bound`, `jackal_exp_rat_bound`, -`jackal_ln_rat_bound`, `jackal_sin_rat_bound`, `jackal_cos_rat_bound`, -`jackal_atan_rat_bound`, `jackal_tanh_rat_bound`, `jackal_verify_receipt`), -twenty-one weaker-lane adapters — the seven numeric lanes -(`jackal_exact`, `jackal_evaluate`, `jackal_diff`, `jackal_integrate`, -`jackal_integrate_adaptive`, `jackal_integrate_bound`, `jackal_solve`) and -the fourteen exact-CAS lanes (`jackal_canon`, `jackal_poly_canon`, -`jackal_poly_eq`, `jackal_poly_gcd`, `jackal_ratfunc_canon`, -`jackal_roots_isolate`, `jackal_alg_sign`, `jackal_alg_cmp`, `jackal_xgcd`, -`jackal_mod_pow`, `jackal_mod_inv`, `jackal_crt`, `jackal_divides`, -`jackal_prime_cert`) — plus the two v1.6.0 claim-kernel front doors -(`jackal_claim`, `jackal_verify_bundle`) — -that thread through the same pinned engine and return the engine's -honest inventory-derived class with `formal: false` — status inflation -is structurally impossible. See `plugin/hermes/README.md` for the full -refusal-class table. +`jackal_sqrt_rat_bound`, `jackal_exp_rat_bound`, `jackal_ln_rat_bound`, +`jackal_sin_rat_bound`, `jackal_cos_rat_bound`, `jackal_atan_rat_bound`, +`jackal_tanh_rat_bound`, `jackal_verify_receipt`); twenty-one weaker-lane +adapters — seven numeric and fourteen exact-CAS tools; two v1.6.0 claim-kernel +front doors (`jackal_claim`, `jackal_verify_bundle`); four domain-pack tools +(`jackal_test_exists`, `jackal_claim_cites_test`, `jackal_decision_rank`, +`jackal_decision_rank_v2`); and three program tools +(`jackal_anubis_check_program`, `jackal_anubis_verify_program`, +`jackal_anubis_verify_program_receipt`). + +Domain-pack structural results stay consequence-capped at `informational`; +decision results stay capped at `decision-boundary`. Program success is only +`verified-program-evidence` / `verified-program-receipt` under +`inventory-safe-v1`, with policy-construct-totality, source-to-VC, SMT-to-CNF, +source-native, runtime, and universal-soundness residuals preserved. See +`plugin/hermes/README.md` for the full refusal-class table. ## 5d. Claim bundles — composing lanes into replayable evidence graphs diff --git a/PROVENANCE.md b/PROVENANCE.md index 5d84089..e840abf 100644 --- a/PROVENANCE.md +++ b/PROVENANCE.md @@ -8,6 +8,138 @@ measurement stated as failed rather than papered over. source → compiler pin → deterministic build → binary hash → gate receipts → adjudication ``` + +The v1.7.3 release binds an ordered 41-tool surface through +`release/capability_inventory_v1.json`. The package identity is recorded in +`release/evidence/package_alignment_v173_release.json`; the annotated tag, +GitHub asset, and read-back receipt must match those exact bytes. + + +## v1.7.3 — unified domain-pack and Anubis program evidence — 2026-08-20 to 2026-08-22 + +This release line starts from merged PR #11 commit +`73854110cb82d78b2843d5028e1e0d5970b0ad5a`; `git show --stat` on that +commit names the W3/W4/W6/W10 merge. The new program accept conditions and +domain-pack compatibility minimum were initially held for architect review; +the approval is recorded in +`release/evidence/architect_release_authorization_v173.json` before +merge/tag/publication. + +### Surface and trust contract + +`python3 tools/profile_verify.py` observed: + +```text +core=3 +formal=13 +full=41 +profile_verification=verified tools_declared=41 nesting=core<=formal<=full OK +``` + +The three additive tools are `jackal_anubis_check_program`, +`jackal_anubis_verify_program`, and +`jackal_anubis_verify_program_receipt`. Success vocabulary is limited to +`verified-program-evidence` / `verified-program-receipt`. + +The prototype profile name `contracted-safe-v1` was not inherited. The v3 +producer exports a producer-attested whole-function inventory but no +independently checkable construct-total walker coverage. The shipped release +therefore uses `inventory-safe-v1` and records +`policy-construct-totality-not-established`, plus open source-to-VC, +SMT-to-CNF, source-native, runtime, and universal-soundness boundaries. + +The caller pins source, compiler, artifact, and policy identities. Only Safe +mode, one source leaf, the exact twelve-stage and six-consumer rosters, nonzero +one-to-one proof paths, approved Z3 UNSAT, and independent RUP replay are +admitted. No program-verifier command executes the compiled artifact. + +The domain-pack manifests now declare +`v1.7.3 <= release < v2.0.0`; their self-digests and the registry self-digest +were regenerated from bytes. `python3 -I -S -B tools/domain_pack_verify.py +--root .` returned `status=accepted`, `pack_count=3`, and +`operation_count=5`. The release manifest independently pins the registry, +pack verifier, and three operation checkers. + +### Package receipts + +Two independent invocations of `release/build_package_v173.sh --build`, each +with its own absolute `JACKAL_DIST`, produced byte-identical tarballs; `cmp` +exited 0: + +The producing source was the clean commit +`aaf7058ce98bf84ecd7b587f1ffff5f6a923f878` (tree +`e5f02743d121acbc1d9128d6c3ceaaf81542d583`). This provenance record is a +later derived checkpoint and is not represented as the commit that produced +the package. + +```text +basename jackal-v1.7.3-macos-arm64.tar.gz +sha256 68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae +bytes 158363786 +files 106 +extracted-file-bytes 555511970 +SHA256SUMS sha256 a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a +``` + +Tar member file bytes and fresh-extraction file bytes independently summed to +`555511970`. `tests/package_unified_v173_test.py` checks exact checksum +inventory, extra/missing/tampered-file controls, missing-pack isolation, +declared-but-unreachable program-tool refusal, stale source/binary refusal, +profile/catalog parity, and the selected release window. + +The repaired canonical parity gate targeted `build_package_v173.sh`, not the +superseded v1.7.0 builder: + +```text +python3 -B tests/claim_package_parity_test.py +CLAIM_PACKAGE_PARITY_PASS rows=60 failures=0 +``` + +Its mutation test points the instrument back at +`build_package_v170.sh` and observes `superseded-builder`. + +### COVENANT replay + +Caller-selected COVENANT source bytes: + +```text +source sha256 037f63a2b2ca72d29a74503db09d5a0d1e0d4fb84a0cd778226751f22acb83ad +compiler sha256 0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6 +artifact sha256 e10f5550344c2e002e08139b4a1658d9151ccb707ddcb4adc6dfd73a31c555ff +receipt sha256 8341ec180add6475f193f47e218b7af88fe2ef6437474c92ede4dfe1ecc02423 +proof objects 9 +RUP additions 615 +``` + +Repository CLI, repository plugin, fresh-package CLI, and fresh-package plugin +all returned the bounded program success status. Receipt replay returned +`verified-program-receipt`. An assurance edit with a recomputed outer digest +returned `receipt-semantic-mismatch`; pristine replay then returned +`verified-program-receipt` again. The structured record is +`release/evidence/anubis_program_dogfood_v1.json`. + +### Plugin and evaluation receipts + +The candidate Codex plugin pins the package and complete `SHA256SUMS`. A fresh isolated +Codex install observed 41 registered tools; exact, formal, refusal, claim +bundle, and formal-receipt gates returned their bounded expected statuses. A +second fresh MCP process replayed the program receipt and refused its semantic +tamper. The candidate-wrapper aggregate is +`c69610183dd207fe8c476e686554f51454b83d96815ad63bea0e4f39dbadf0ac`; +it is the SHA-256 of the generated eight-row +`plugins/jackel/PLUGIN_IDENTITY.sha256` for this candidate, not an identity +from the public default branch. + +Live transcript-bearing Codex sessions attempted the W3/W10 tools, but the +noninteractive host cancelled every MCP call, including with +`approval_policy=never`. `evals/v2/runner.py` still does not invoke a model or +emit profile identity, and no adapter converts Codex JSONL events into +protocol-admissible autonomous rows. `tools/eval_v2_gate.py` therefore returned +`EVAL_V2_GATE_NOT_MEASURABLE` exit 3; this is not a pass and no JACKAL accuracy +claim is made. + +--- + ## Branch record — `feat/domain-pack-protocol`, 2026-08-19 — UNSEALED — domain-pack protocol: programming-status and decision packs + the `lcm`/`gcd` epistemic-class repair **This is not a seal.** No package was built, no binary was produced, and no diff --git a/README.md b/README.md index cdc8f30..4c5382f 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ plausible. for Apple Silicon macOS, or build from source with an Anubis compiler), first commands, and how to read the trust labels. License: [MIT](LICENSE). + +**Current agent surface:** the v1.7.3 release exports an ordered 41-tool catalog +whose schemas, profiles, status classes, dependencies, and host exposure are +generated in `release/capability_inventory_v1.json`. The annotated `v1.7.3` +tag, GitHub release, package receipt, and downloadable asset must bind the +same exact release bytes. + + JACKAL is written in **Anubis Safe mode**. It does not try to win by adding another wall of buttons. It treats a serious calculation as a bounded scientific claim: value, units, uncertainty, method, assumptions, sensitivity, residual, non-claims, and a reproducible @@ -159,7 +167,7 @@ checker on this machine** — recomputing the outer digest alone is not sufficient. The Hermes/MCP-style plugin (`plugin/hermes/jackal_hermes`) threads every call through the same shared validator, the same formal-status gate, the same pinned executables, and additionally bind the plugin's OWN bundle hash into the -receipt via `identities.plugin_sha256`. The Hermes plugin exposes thirty-eight +receipt via `identities.plugin_sha256`. The Hermes plugin exposes forty-one tools — eleven formal (`jackal_range_bound`, `jackal_gaussian_integral`, `jackal_integrate_bound_cert`, `jackal_sqrt_rat_bound`, `jackal_exp_rat_bound`, `jackal_ln_rat_bound`, @@ -183,6 +191,19 @@ manifest-pinned independent checker over the emitted certificate so only an `ACCEPT` verdict returns success. A `test-exists-cert` is an exact statement about bytes and never evidence that the code under test is correct. +The remaining three tools are the inventory-safe-v1 Anubis program-evidence +lane: `jackal_anubis_check_program`, `jackal_anubis_verify_program`, and +`jackal_anubis_verify_program_receipt`. They require caller-pinned +source/compiler/artifact/policy identities, Safe mode, strict +`anubis.program-evidence.v3` rosters, evidence-tree closure, approved Z3 UNSAT +replay, and independent RUP replay. They never execute the compiled artifact +and can emit only `verified-program-evidence` or +`verified-program-receipt`. The policy binds a producer-attested whole-function +inventory but does not establish construct-total walker coverage, so the +receipt names `policy-construct-totality-not-established` alongside the open +source-to-VC, SMT-to-CNF, source-native, runtime, and universal-soundness +boundaries. + The eleven-category A→B→A mutation harness (`tests/cert_mutations_11.py`) plus the receipt-semantic mutation harness (`tests/receipt_semantic_mutations.py`, 42/42 including the two §487 audit locks for U+2028 parser-differential @@ -614,10 +635,12 @@ The kernel is deliberately small and closed: revoked v1.7.0 receipts refuse. The claim kernel's own hull arithmetic still caps at `bounded`. -Hermes exposes the kernel as two additive tools — `jackal_claim` and -`jackal_verify_bundle` — alongside the 31 unchanged v1.5.0 tools (33 at -the v1.6.0 seal; the v1.7.0 `jackal_integrate_bound_cert` brings the -inventory to thirty-four). Hostile controls +The v1.7.3 release exposes one ordered 41-tool catalog across JACKAL, +Hermes, and Codex. It includes the claim/bundle front doors, the current +request-bound `jackal_integrate_bound_cert` lane, four domain-pack routes, +and three caller-pinned Anubis program-evidence routes; the generated +`release/capability_inventory_v1.json` is the count and schema authority. +Hostile controls (108-row matrix: serialization, graph identity, laundering, units, consequence floors, freshness/replay, machine arithmetic, legacy compatibility, rendering), A→B→A tamper gates over the seven claim trust @@ -639,7 +662,7 @@ never degrades to a weaker lane behind the caller's back. The protocol, its v1 ceilings and its mandatory nonclaims are specified in [`domain_packs/PACK_SPEC.md`](domain_packs/PACK_SPEC.md) and bound by `domain_packs/registry_v1.json`; the declared compatibility window is -`v1.8.0 <= release < v2.0.0`. +`v1.7.3 <= release < v2.0.0`. One route ABI for every pack: diff --git a/docs/JACKAL_PRODUCTION_ALIGNMENT_TRACKER_2026-08-22.md b/docs/JACKAL_PRODUCTION_ALIGNMENT_TRACKER_2026-08-22.md new file mode 100644 index 0000000..229aea3 --- /dev/null +++ b/docs/JACKAL_PRODUCTION_ALIGNMENT_TRACKER_2026-08-22.md @@ -0,0 +1,286 @@ +# JACKAL Production Alignment Tracker — 2026-08-22 + +Checkpoint state: `AUTHORIZED_RELEASE_PROMOTION`. + +The architect explicitly approved both previously blocked trust-surface +decisions and instructed Codex to merge and finish the release work. The +authorization record is +`release/evidence/architect_release_authorization_v173.json`, SHA-256 +`f81446a5e115d99690c125d806d3406565ea83b6626c802e666f1cea3791cc94`. +This checkpoint binds the final reproducible package and release pins; merge, +tag, GitHub-release read-back, Hermes promotion, installation, and upstream +PR disposition remain subsequent recorded actions, not claims of this row. + +## Authority and repository binding + +| Surface | Exact identity | State | +|---|---|---| +| Architect goal | 12,611 bytes; 172 lines; SHA-256 `8025fb5570587258ec3cf6c808df71451af5b8815a7a5778f7d1e48e296dad7d` | VERIFIED | +| JACKAL public base | `AnubisQuantumCipher/jackal` `master` at `73854110cb82d78b2843d5028e1e0d5970b0ad5a` | latest public release line | +| JACKAL package-producing source | `mission/jackal-unified-completion-20260820` commit `aaf7058ce98bf84ecd7b587f1ffff5f6a923f878`, tree `e5f02743d121acbc1d9128d6c3ceaaf81542d583` | clean source; package reproduced twice | +| JACKAL release-pin closure | descendant of `aaf7058…` | package receipt, Codex pins, and this intentionally non-self-referential tracker | +| JACKAL PR | [AnubisQuantumCipher/jackal#12](https://github.com/AnubisQuantumCipher/jackal/pull/12) | open and mergeable at this checkpoint; authorization resolved | +| Hermes v6 candidate | `AnubisQuantumCipher/hermes-jackal-verified` `mission/production-alignment-v6` at `936dab4458d4618f4ecf56c2da5c9f5cdbb9aef4`, tree `6315b680be8e5d3b109014910b774b84b565040d` | pushed; no tag or release | +| Hermes upstream index | `AnubisQuantumCipher/hermes-agent` `feat/index-jackal-verified` at `28f5455001ce4784d8e584fbb521c442740f8e64`, tree `bce913a075326a5c32eb1d50bc5010b51b08a540` | [NousResearch/hermes-agent#88446](https://github.com/NousResearch/hermes-agent/pull/88446), open and mergeable; one tracked file changed | +| Public README clarification | docs head `798b63148cdc01b0c17fb2bd888478cbbe037ffd`; merged as `6a42656df135eab1b2abfdf2b873b02df8efb6e9` | [plugin PR #4](https://github.com/AnubisQuantumCipher/hermes-jackal-verified/pull/4) merged after both CI runs passed | + +Ambient JACKAL and Hermes checkouts under `$HOME` contain user-owned state and +were not mutation targets. Source builds, package checks, and candidate work +used isolated worktrees and fresh temporary roots. + +## Canonical capability surface + +`release/capability_inventory_v1.json` has SHA-256 +`19930922418aa0f751c8ee3476f31677368e0c29c5f1c5ea8942ea7fb597d60c`. +It contains 41 rows and 41 unique names. Kernel catalog, profiles, package, +Hermes schemas/discovery, Codex schemas/discovery, and skill-name gates agree +on this ordered roster: + +```text +jackal_range_bound +jackal_gaussian_integral +jackal_integrate_bound_cert +jackal_verify_receipt +jackal_sqrt_rat_bound +jackal_exp_rat_bound +jackal_ln_rat_bound +jackal_sin_rat_bound +jackal_cos_rat_bound +jackal_atan_rat_bound +jackal_tanh_rat_bound +jackal_exact +jackal_evaluate +jackal_diff +jackal_integrate +jackal_integrate_adaptive +jackal_integrate_bound +jackal_solve +jackal_canon +jackal_poly_canon +jackal_poly_eq +jackal_poly_gcd +jackal_ratfunc_canon +jackal_roots_isolate +jackal_alg_sign +jackal_alg_cmp +jackal_xgcd +jackal_mod_pow +jackal_mod_inv +jackal_crt +jackal_divides +jackal_prime_cert +jackal_claim +jackal_verify_bundle +jackal_test_exists +jackal_claim_cites_test +jackal_decision_rank +jackal_decision_rank_v2 +jackal_anubis_check_program +jackal_anubis_verify_program +jackal_anubis_verify_program_receipt +``` + +The inventory distinguishes exact, checked, estimated, bounded, +formal-bounded, model-based, structural-exact, verified, +verified-program-evidence, verified-program-receipt, indeterminate, and +refused outcomes. Unsupported strong requests refuse; no adapter silently +substitutes a weaker lane. + +## Reproducible v1.7.3 release package + +| Property | Exact value | +|---|---| +| Builder SHA-256 | `686be8b66b7fccef3419eb032be8c8632619814b51f74814d1b85449f25cb58d` | +| Release manifest SHA-256 | `ac52dafc0e9edbf74dde56b358c3c55ab5b705d3b66811558156c480b3530509` | +| Program verifier SHA-256 | `4b80e29bdffc0737f05a6e215fce8cce3b6b828c24afbf55c68443399e5119dc` | +| Package | `jackal-v1.7.3-macos-arm64.tar.gz` | +| Package SHA-256 | `68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae` | +| Package bytes | `158363786` | +| Regular files | `106` | +| Complete extracted tree entries | `119` | +| Extracted regular-file bytes | `555511970` | +| Root `SHA256SUMS` SHA-256 | `a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a` | +| Mode/size/digest roster aggregate | `f88ba8a9988afe4b41ab247d5c75cb3da03159defba1bd8985c37190fa595654` | +| Alignment receipt SHA-256 | `c15a3d174b847b02226f62ad26b217b887aa7102c9c21a7f392a9642e4e9a7bb` | + +Two distinct clean detached source worktrees at source commit `aaf7058…`, each +with its own destination, produced byte-identical tarballs, checksum lists, +complete extracted trees, and file rosters. `cmp` and the directory comparison +both exited 0. The approved compiler SHA-256 was +`a733565f237df171e7cf93b9b37700a42d8713576818fd92f8cd23a8ad7a69e2`; +the builder requires the operator to provide its path explicitly. + +Pinned runtime inputs were: + +| Input | SHA-256 | +|---|---| +| `jackal-native` | `f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655` | +| range checker | `f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545` | +| Gaussian checker | `ccac690bf916f71a4e3baeb0622dac19aa47e3ca4af858c0800c295581ecfacb` | +| composed-integral checker | `f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617` | + +These are the authorized release bytes. At this checkpoint, publication and +download read-back have not yet been claimed. Offline provisioning of this +exact tarball passed, after which the isolated Codex live gate discovered 41 +tools and passed exact, formal, refusal, claim-bundle, and receipt replay. + +## Verification ledger + +Every command below exited 0 unless the row explicitly describes an expected +refusal. + +| Command or gate | Result | Boundary | +|---|---|---| +| `python3 -B tools/capability_inventory.py --check` | `tools=41 unique=41` | generated inventory | +| `python3 -B tools/capability_drift_gate.py` | `tools=41 unique=41 codex=41 package=v1.7.3` | marked current surfaces; historical releases excluded | +| inventory plus drift unittests | 31/31 pass | positive and mutation controls | +| `release/tools/repin_v173.py --compiler-path --check` | 49 rows pass | fixed compiler identity; no machine fallback | +| program-verifier suite | 15/15 pass | verifier-owned compiler snapshot, Z3/RUP replay, path and pin refusals | +| `tools/lean_admission_audit.py --source-check` plus Lean tests | source pass; 26/26 tests | tracked sources, theorem output, mutation controls | +| package-unification suite over a fresh package root | 15/15 pass; zero skips | complete closure and package semantics | +| `tests/claim_package_parity_test.py` | 60/60 pass | rebuild twice, every packaged tool family, receipt replay, tamper refusal | +| Codex repository discovery suite | 218/218 pass | repository candidate | +| isolated Codex live acceptance | 41 tools; accepted | exact=`exact`; formal=`formal-bounded`; claim bundle and formal receipt=`verified`; unsupported formal=`producer-refused` | +| Codex wrapper identity | eight files; aggregate `a1d04cf92b1c56cd5833c43fb87ab8d129a6115d645ad98ddfa47e5e38f1c8dc` | verified candidate wrapper | +| claim hostile matrix | 108 rows pass | complete 64-character roots and refusal boundaries | +| claim A-to-B-to-A campaign | 7 layers pass | mutation restoration and identity binding | +| plugin smoke | pass | manifest-current Hermes bundle `c6a27483077b89d899d8c73c03bfeb3191f25db2a22f8021254a7dec763ba5fe` | +| JACKAL hosted workflows | runs [32583685425](https://github.com/AnubisQuantumCipher/jackal/actions/runs/32583685425), [32583685444](https://github.com/AnubisQuantumCipher/jackal/actions/runs/32583685444), [32583687658](https://github.com/AnubisQuantumCipher/jackal/actions/runs/32583687658), and [32583687710](https://github.com/AnubisQuantumCipher/jackal/actions/runs/32583687710) all success | exact head `0bca7da…`; push and PR events | +| Hermes epoch generation | 41 tools, 53 selected package identities | package-derived | +| Hermes production/unit/poison suites | 7/7; 22/22; 48/48 normal; 48/48 optimized | candidate adapter | +| Hermes split/ABA controls | 8/8; 4/4 | part discovery and four forgeries | +| Hermes fresh install and doctor | pass; 41 tools, zero hooks | exact vendored candidate | +| Hermes manifest/release audit | manifest pass; zero forbidden paths or secret matches | manifest SHA-256 `bd67cb69c6a1ee15c0fa38ad7d01111e181722744fe623367584fbe14b58a7e0` | +| Hermes hosted CI | [run 32584006653](https://github.com/AnubisQuantumCipher/hermes-jackal-verified/actions/runs/32584006653) success | exact candidate head `936dab4…` | +| upstream plugin-index suite | 38/38 pass through `scripts/run_tests.sh` | PR #88446 one-file JSON diff | +| public README PR CI | [push 32584187543](https://github.com/AnubisQuantumCipher/hermes-jackal-verified/actions/runs/32584187543) and [PR 32584190455](https://github.com/AnubisQuantumCipher/hermes-jackal-verified/actions/runs/32584190455) success | docs plus required manifest reseal | + +An earlier broad upstream Hermes wrapper attempt reported 78 failures across 21 +unrelated test files plus one collection timeout/error from missing providers +or baseline environment assumptions. No unrelated upstream file was changed to +mask those results. The focused 38-test index suite is the applicable diff-level +gate; no full-upstream-green claim is made. + +## Lean admission and axiom audit + +| Property | Result | +|---|---| +| Audit artifact SHA-256 | `7c14c616dabdaa1e1424b04b647be79dddfab7861c61e2d8a0b28064d10fea3d` | +| Semantic audit digest | `264701eaa4c4721d0b734653cd8082d20e1b57d252e80e6dd9394ac50010af98` | +| Tracked Lean files | 42 | +| Audited theorem names | 27 unique | +| Logical admissions | 0 | +| Repository axiom declarations | 0 | +| Unexpected constructs | 0 | +| Exact observed theorem axioms | `propext`, `Classical.choice`, `Quot.sound` | +| Classified runtime substitutions | two allowlisted dump-only `implemented_by` mirrors outside current checker acceptance | + +The byte-compared audit record contains platform-neutral Lean 4.32.0 and +mathlib identities rather than host triples or resolved shim identities. This +is a checker-source and named-theorem audit. It does not prove compiler +correctness, source-to-native refinement, operating-system correctness, +arbitrary-expression mathematics, or universal language soundness. + +## Skill census + +| Skill surface | SHA-256 / disposition | +|---|---| +| Personal Codex `jackal-assurance-oracle` | `69fe32e1212c42c77f96ceee81a0040ac5daaa8ee3760cbf41c261b3148e2454`; current claim, receipt, program, and refuse-never-downgrade routing | +| Personal Hermes `jackal-verified-computation` | `d8445e050c5e7cc333f493117aa42216dfa27e812224e022f23c1a72b7da765e`; default plus four profile copies byte-equal | +| Personal Hermes `jackal-trust-boundary-reseal` | `3c00bcf75a10197b112bbbf26d3338fd8ce029e0a74ed6f95fa3c3b310d5732b`; default plus four profile copies byte-equal | +| `gbrain-evidence-memory` | `27a921cdb7f4da025a67183b7531b72d4efa81d02db7983096a4ed8c9a951f30`; incidental integration, no count/version/pin claim | +| `adversarial-calculator-audit` | `bbbcc1a6389b6fd521833d81cf82d3ade319e15fb880cc3a3d15a2987930e92b`; no stale count or pin | +| `independent-oracle-mutation-audit` | `941d6ae02742bb78febcdf8c1f239c455651742381ce082404800b8dc57b8e9e`; no stale count or pin | +| `receipt-semantic-replay-verification` | `f5e1c7adc556d8739c7f39de2185750d927454130271f7523509c50d0d10a8fc`; no stale count or pin | +| `rigorous-evidence-report` | default `a755564d8dd0e09e0573f287d9ee3778bf94d53d6ca6ef92fa23457340cd0b00`; four profile copies `8592f6f2d203afc71ba413702f00acab6da228d3e62bafffe88e6f78fde3f1c5`; pre-existing PDF-rendering guidance difference only, with identical JACKAL wording and no count/version/pin claim | +| Repository Codex router | `1fb8f70356bd022daf9fc36b739c9ce671b2ba59d303542393d01e15cdb4070a` | +| Hermes v6 bundled router | `7536b78eddf7d72e4d392cdac3977253ac381097b360af8e65d1b9741201e4c1` | +| Normally installed Hermes v5 router | `a5e2fcf14c2a775acb776b5ae63a3be38515d0af16236c6466f9846f8239f31f`; intentionally historical until authorized promotion | + +Repository contract tests bind current skill tool names and routing facts to +the canonical inventory. Local skill files were read and hashed directly; +`hermes skills audit` output for an unrelated hub-installed copy is not used as +evidence. + +## Independent review ledger + +The hosted CodeRabbit review on PR #12 posted 14 actionable findings at +[review 5000442938](https://github.com/AnubisQuantumCipher/jackal/pull/12#pullrequestreview-5000442938). +Every finding was verified against source, fixed in `957ac893…` or rebound in +`0bca7da…`, answered with exact evidence, acknowledged by the bot, and resolved. +The accepted fixes include: + +- removal of workstation paths from committed documentation; +- final package-pin alignment; +- verifier-owned compiler execution snapshots; +- unpublished-release network refusal; +- early cross-filesystem package refusal; +- complete evidence roots; +- regenerated Lean and Hermes evidence; +- correct repin documentation and diagnostics; +- complete transcript-to-summary binding; +- NUL-safe tracked-file parsing; +- deterministic Lean axiom output and platform-neutral audit bytes. + +Selected low-risk nitpicks were also fixed: CI credential persistence, +deterministic Lean fixtures and symlink controls, raw-string scanning, +configurable bounded audit timeouts, derived audit counts, profile-count and +runtime-literal test independence, wrapper symlink resolution, parsed package +status, explicit compiler authority, durable manifest replacement, one-read +repin hashing, and early termination after a derived empty RUP clause. + +The following suggestions were not silently folded into this candidate: + +- widening the approved Z3 path is a trust-surface decision; +- redesigning `unit_conflict` around an index/queue is a large performance + refactor outside this bounded correctness patch; +- duplicate packaged registry paths remain compatibility surfaces; +- streaming the large checker hash and other broad test-oracle refactors were + not required to close an observed correctness gap. + +Five earlier local CodeRabbit passes also produced fixes. A further local pass +could not start because the account review allowance was exhausted. Hermes +local CodeRabbit attempts ended in WebSocket/quota failures. Green tests are not +presented as a substitute for those unavailable extra reviews, and no +zero-finding second full review is claimed. + +## Public metadata and PR state + +- JACKAL repository description now leads with the mechanically aligned + 41-tool v1.7.3 candidate and states that public sign-off is pending. +- Hermes repository description now leads with the 41-tool v6 candidate and + states that v5.0.0 remains latest. +- The default Hermes README at merge commit `6a42656…` leads with the immutable + 41-tool candidate. Every remaining 34-tool section is explicitly under + `Published release reference — v5.0.0 (34 tools)`. +- JACKAL latest release remains v1.7.2. Hermes latest release remains v5.0.0 + with 34 tools. Historical release records were not rewritten. +- The normal installed Hermes plugin remains v5.0.0 with 34 tools; candidate + verification used isolated state. +- PR #88446 title/body/index use neutral 41-tool candidate language and pin + exact plugin commit `936dab4458d4618f4ecf56c2da5c9f5cdbb9aef4`. +- PR #88446 verification comment: + [issuecomment-5381364416](https://github.com/NousResearch/hermes-agent/pull/88446#issuecomment-5381364416). +- PR #88446 is open and mergeable, changes only + `hermes_cli/data/plugin_index.json`, reports no hosted checks, and was not + merged. + +## Authorization resolution and remaining promotion steps + +The architect approved the `inventory-safe-v1` accept conditions, status +meaning, v1.7.3 domain-pack compatibility minimum, and promotion boundary. +The durable authorization receipt records the exact user instructions and +explicitly does not override third-party permissions or branch protection. +The remaining work is mechanical: merge and read back JACKAL v1.7.3; repin, +merge, release, and install Hermes v6; then update and exhaust the permitted +actions on upstream PR #88446. + +## Final nonclaims + +- At this checkpoint, no public JACKAL v1.7.3 or Hermes v6 release read-back is + asserted; later tracker revisions must bind those identities if published. +- No upstream maintainer approval or merge is asserted. +- No arbitrary-expression, compiler-correctness, source-native-refinement, or + unrestricted formal-correctness claim is made. +- The architect authorization is an operator record, not a cryptographic + signature, proof of third-party authority, or expansion of formal coverage. diff --git a/docs/W3_W4_W6_W10_COMPLETION_RECORD.md b/docs/W3_W4_W6_W10_COMPLETION_RECORD.md index 33e26d3..9fcd315 100644 --- a/docs/W3_W4_W6_W10_COMPLETION_RECORD.md +++ b/docs/W3_W4_W6_W10_COMPLETION_RECORD.md @@ -716,3 +716,77 @@ suites that require build artifacts absent from this worktree — measured as `claim_inference_registry`) need a seal-time repin on a machine that has them. Neither was hand-edited: hand-editing a manifest row to make a gate green is the same class of act as repinning frozen evidence. + +--- + +## 7. Post-PR #11 unified reconciliation — 2026-08-20 + +This section supersedes only the open seal-time rows in §5; the earlier branch +history remains evidence of what was true before reconciliation. + +Observed current surface: + +```text +python3 tools/profile_verify.py +core=3 formal=13 full=41 +profile_verification=verified tools_declared=41 nesting=core<=formal<=full OK +``` + +The four domain-pack tools remain present. Three Anubis program-evidence tools +were added to `full` only. `core` and `formal` membership and digests did not +move. + +Mechanically closed package rows: + +- `domain_packs/`, all three pack sources/manifests, `PACK_SCHEMA`, + `PACK_SPEC`, the pack verifier, and all three operation checkers are shipped; +- the program verifier, `inventory-safe-v1` policy, specification, wrapper, + profiles, and profile schema are shipped; +- `release/MANIFEST.sha256` is generated by + `release/tools/repin_v173.py`; it now pins the domain registry/verifier/ + checkers and program verifier/policy; +- the package plugin lists all 41 tools from a fresh extraction; +- missing pack bytes refuse pack calls without breaking `jackal_exact`; +- a missing program verifier refuses plugin startup rather than leaving a + declared unreachable tool. + +`release/build_package_v173.sh --build` produced two byte-identical tarballs: + +```text +tarball sha256 68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae +tarball bytes 158363786 +files 106 +SHA256SUMS sha256 a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a +``` + +The canonical package parity instrument was re-pointed from the superseded +v1.7.0 builder. Its mutation control restores that old target and observes +`superseded-builder`; the live gate reported +`CLAIM_PACKAGE_PARITY_PASS rows=60 failures=0`. + +Program policy decision: `contracted-safe-v1` is refused. The admitted +`inventory-safe-v1` profile checks producer-attested function/policy +inventories but explicitly leaves construct-total walker coverage open. +COVENANT replay used nine proof objects and 615 RUP additions; repository and +package CLI/plugin paths reproduced receipt +`8341ec180add6475f193f47e218b7af88fe2ef6437474c92ede4dfe1ecc02423`. +A self-consistent assurance edit refused `receipt-semantic-mismatch`, and the +following pristine replay verified again. + +W10 remains `NOT_MEASURABLE`, not PASS. Three fresh Codex JSONL transcripts +record attempted W3/W10 MCP calls. The noninteractive host cancelled every MCP +call; the deterministic runner still emits no model/profile identity and has no +Codex-event adapter. `tools/eval_v2_gate.py` returned exit 3. + +W6 units, linear algebra, statistics, and ODE/PDE packs remain out of scope. +The current governing record already classified them as not done rather than +mandatory for this release (§5 row 524); no new pack was added here. + +PR #10 remains an open draft and was not merged, closed, rewritten, or absorbed. +If its chronology accept-condition change lands first, the claim verifier, +plugin bundle, manifest, package, and all package hashes above must be +regenerated; this candidate cannot silently absorb it. + +Terminal state remains `SIGNOFF_REQUIRED`: architect review is required for the +new program-verifier accept conditions and the domain-pack minimum-release +change from v1.8.0 to v1.7.3 before merge/tag/publication. diff --git a/docs/superpowers/plans/2026-08-22-jackal-production-alignment.md b/docs/superpowers/plans/2026-08-22-jackal-production-alignment.md new file mode 100644 index 0000000..4559f0a --- /dev/null +++ b/docs/superpowers/plans/2026-08-22-jackal-production-alignment.md @@ -0,0 +1,511 @@ +# JACKAL Production Alignment Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Produce one mechanically consistent 41-tool JACKAL candidate across kernel, Hermes, Codex, skills, releases, public metadata, and NousResearch PR #88446 without changing verifier accept conditions or overstating an unapproved release. + +**Architecture:** `plugin/hermes/tools.json` remains the executable roster. A deterministic capability-inventory generator binds each catalog record to profiles, schemas, proof/checker dependencies, supported fragments, refusal boundaries, and integration exposure. A semantic drift gate makes that inventory the shared contract for kernel/Codex, the standalone Hermes plugin, and routing skills. Release and PR mutation occur only after clean package replay, independent review, and explicit trust-surface authority are evidenced. + +**Tech Stack:** Python 3.14 at `/opt/homebrew/bin/python3`, JSON, unittest, Lean 4/Lake, Anubis pinned compiler, Bash/Zsh release builders, Git/GitHub CLI, Hermes native plugin API, Codex portable plugin/MCP. + +--- + +## Execution rules + +- Work only in clean isolated worktrees/clones. Never edit the dirty Desktop JACKAL checkout, dirty Hermes main checkout, installed Hermes v5.0.0 checkout, or installed Codex cache. +- Add behavior tests first and record the expected red output before implementation. +- Never hand-edit `release/MANIFEST.sha256`, `PLUGIN_IDENTITY.sha256`, `MANIFEST.json`, evidence JSON, or package hashes. Use the owning generator and its `--check` mode. +- Stage named files only. Every pushed checkpoint is a new commit; never amend or force-push. +- Treat `inventory-safe-v1` promotion, checker accept conditions, release identity, and tag creation as sign-off surfaces. + +### Task 1: Canonical 41-tool capability inventory + +**Files:** +- Create: `tests/capability_inventory_test.py` +- Create: `tools/capability_inventory.py` +- Create: `release/capability_inventory_v1.json` +- Modify: `.github/workflows/gaussian-proof-gate.yml` + +- [ ] **Step 1: Write the failing inventory contract tests** + +Add tests that import `tools/capability_inventory.py` and assert: + +```python +document = inventory.build_inventory(ROOT) +records = document["tools"] +catalog = json.loads((ROOT / "plugin/hermes/tools.json").read_text())["tools"] +self.assertEqual([row["name"] for row in records], [row["name"] for row in catalog]) +self.assertEqual(len(records), 41) +self.assertEqual(len({row["name"] for row in records}), 41) +self.assertEqual({row["schema_sha256"] for row in records}, { + hashlib.sha256(inventory.canonical_bytes(row)).hexdigest() for row in catalog +}) +for row in records: + self.assertEqual(row["exposure"], {"kernel": True, "hermes": True, "codex": True}) + self.assertTrue(row["status_classes"]) + self.assertIn("refused", row["status_classes"]) + self.assertTrue(row["supported_fragment"]) + self.assertTrue(row["refusal_boundary"]) + self.assertIn(row["release_state"], {"v1.7.3-candidate", "v1.7.3"}) +``` + +Add mutation tests for duplicate catalog names, an unmapped tool, unknown status vocabulary, missing checker identity, and a committed artifact differing from generated canonical bytes. + +- [ ] **Step 2: Run RED and preserve the expected failure** + +Run: + +```bash +/opt/homebrew/bin/python3 -B -m unittest tests.capability_inventory_test -v +``` + +Expected: import/file failure because the generator and artifact do not exist. + +- [ ] **Step 3: Implement the deterministic generator** + +Implement the public API `canonical_bytes(value)`, `build_inventory(root)`, +`render_inventory(root)`, `check_committed(root)`, and `main(argv)`. The status +vocabulary is exactly: + +```python +ALLOWED_STATUSES = frozenset({ + "ok", "exact", "structural-exact", "formal-bounded", "bounded", + "checked", "estimated", "model-based", "verified", + "verified-program-evidence", "verified-program-receipt", + "indeterminate", "refused", +}) + +def canonical_bytes(value: object) -> bytes: + return json.dumps(value, sort_keys=True, separators=(",", ":"), + ensure_ascii=False).encode("utf-8") +``` + +Parse each tool's `returns.status` by exact ` | ` tokens. Use complete named sets for the three Lean checker families, exact-cert verifier family, structural checkers, decision checker, claim verifier, and program verifier. Reject any tool that belongs to zero or multiple incompatible dependency families. Derive profile membership from the three profile JSON files and schema identity from canonical catalog-record bytes. + +Use `v1.7.3-candidate` as release state until an annotated public v1.7.3 tag is verified. Record the containing candidate commit `d25bcd9818e0d106f337798f80527ae611cc3acc` and input-tree digests; do not introduce a self-referential final commit hash. + +- [ ] **Step 4: Generate the artifact with the owning tool** + +Run: + +```bash +/opt/homebrew/bin/python3 -B tools/capability_inventory.py --write +/opt/homebrew/bin/python3 -B tools/capability_inventory.py --check +``` + +Expected: `CAPABILITY_INVENTORY_PASS tools=41 unique=41` and byte-identical `--check`. + +- [ ] **Step 5: Add the engine-free CI invocation** + +Add this before claim-bundle admission replay: + +```yaml +- name: Canonical capability inventory + run: | + /opt/homebrew/bin/python3 -B tools/capability_inventory.py --check + /opt/homebrew/bin/python3 -B -m unittest tests.capability_inventory_test -v +``` + +Use `python3` rather than the Homebrew absolute path in Ubuntu jobs; keep the fixed path only in macOS plugin jobs. + +- [ ] **Step 6: Run GREEN and regression gates** + +```bash +/opt/homebrew/bin/python3 -B -m unittest \ + tests.capability_inventory_test \ + tests.profile_contract_test \ + tests.unified_surface_contract_test -v +/opt/homebrew/bin/python3 -B tools/capability_inventory.py --check +``` + +- [ ] **Step 7: Commit and push the non-final inventory checkpoint** + +Stage the four named paths and use a receipt body containing the exact test count, inventory digest, and `tools=41 unique=41` output. + +### Task 2: Semantic documentation, plugin, and skill drift gate + +**Files:** +- Create: `tests/capability_drift_gate_test.py` +- Create: `tools/capability_drift_gate.py` +- Modify: `docs/superpowers/specs/2026-08-17-jackel-codex-plugin-design.md` +- Modify: `README.md` +- Modify: `GETTING-STARTED.md` +- Modify: `PROVENANCE.md` +- Modify: `plugins/jackel/.codex-plugin/plugin.json` +- Modify: `plugins/jackel/skills/jackel/SKILL.md` +- Modify: `.github/workflows/jackal-codex-plugin.yml` + +- [ ] **Step 1: Write RED tests against known live drift** + +Create fixtures that replace a current count with 34, add an unknown `jackal_*` name to a skill, replace the package SHA, and introduce an unknown status word. Require stable refusal names: + +```python +with self.assertRaisesRegex(DriftError, "current-tool-count"): + drift.verify_surface(fixture_root) +with self.assertRaisesRegex(DriftError, "unknown-skill-tool"): + drift.verify_surface(fixture_root) +with self.assertRaisesRegex(DriftError, "package-pin-mismatch"): + drift.verify_surface(fixture_root) +with self.assertRaisesRegex(DriftError, "status-vocabulary"): + drift.verify_surface(fixture_root) +``` + +The positive test must initially fail on the stale 34-tool sentence in the 2026-08-17 Codex design spec. + +- [ ] **Step 2: Run RED** + +```bash +/opt/homebrew/bin/python3 -B -m unittest tests.capability_drift_gate_test -v +``` + +Expected: `current-tool-count` identifies the exact stale current-surface file and line. + +- [ ] **Step 3: Implement the drift verifier** + +The verifier must: + +```python +CURRENT_SURFACES = ( + "README.md", "GETTING-STARTED.md", "PROVENANCE.md", + "plugins/jackel/skills/jackel/SKILL.md", + "docs/superpowers/specs/2026-08-17-jackel-codex-plugin-design.md", +) + +def skill_tool_names(markdown: str) -> set[str]: + return set(re.findall(r"`(jackal_[a-z0-9_]+)`", markdown)) +``` + +Check structured JSON semantically. For Markdown, require current count/version/package identity in named current-surface sections, verify every referenced tool exists, and allow explicitly labeled migration/historical paragraphs. Do not globally ban `34`, v1.7.0, or old tag names. + +- [ ] **Step 4: Correct current-surface drift** + +Replace the Codex design's current 34-tool statement with the generated 41-tool roster contract. Remove promotional self-assessment wording from current metadata and state the adapter mechanism: it returns the runtime result object unchanged except for the adapter-local `plugin-busy` refusal. + +- [ ] **Step 5: Add the gate to both workflows** + +Run `capability_inventory.py --check` and `capability_drift_gate.py` in the engine-free proof job and the macOS Codex plugin job. + +- [ ] **Step 6: Run GREEN** + +```bash +/opt/homebrew/bin/python3 -B tools/capability_drift_gate.py +/opt/homebrew/bin/python3 -B -m unittest \ + tests.capability_drift_gate_test tests.codex_plugin.test_plugin_metadata -v +``` + +- [ ] **Step 7: Commit and push named files** + +The receipt must distinguish corrected live claims from intentionally preserved historical migration facts. + +### Task 3: Lean/admission and trust-assumption audit artifact + +**Files:** +- Create: `tests/lean_admission_audit_test.py` +- Create: `tools/lean_admission_audit.py` +- Create: `release/evidence/lean_admission_audit_v173.json` +- Modify: `release/tools/repin_v173.py` +- Modify: `release/build_package_v173.sh` +- Modify: `release/MANIFEST.sha256` only through `repin_v173.py --write` + +- [ ] **Step 1: Write RED tests** + +Require the audit to enumerate every tracked `.lean` file, reject `sorry`, `admit`, `axiom`, and `unsafe` outside an explicit classification, and bind the exact output of theorem axiom queries. Mutation fixtures must insert `sorry` and a new axiom declaration and observe named failures. + +- [ ] **Step 2: Run RED** + +```bash +/opt/homebrew/bin/python3 -B -m unittest tests.lean_admission_audit_test -v +``` + +- [ ] **Step 3: Implement read-only audit generation** + +The artifact schema must contain source file SHA-256 values, token-scan findings, theorem names, `#print axioms` output, checker build identities, explicit trusted assumptions, and residual non-claims. It must distinguish Lean standard axioms from repository declarations and admitted snapshot inputs. + +- [ ] **Step 4: Run Lean build and theorem queries** + +```bash +cd proofs/lean +lake build jackal_gaussian_check jackal_cert_check jackal_int_cert_check +cd ../.. +python3 release/tools/gaussian_proof_identity.py check --lane gaussian --proof-only +python3 release/tools/range_proof_identity.py check --lane range --proof-only +python3 release/tools/range_proof_identity.py check --lane int-cert --proof-only +/opt/homebrew/bin/python3 -B tools/lean_admission_audit.py --write +/opt/homebrew/bin/python3 -B tools/lean_admission_audit.py --check +``` + +These are the exact checker build targets and engine-free proof-identity commands +used by `.github/workflows/gaussian-proof-gate.yml`. The full macOS release gate +later reruns the same identity scripts without `--proof-only`. + +- [ ] **Step 5: Repin through the generator and re-check** + +```bash +/opt/homebrew/bin/python3 -B release/tools/repin_v173.py \ + --compiler-path "" --write +/opt/homebrew/bin/python3 -B release/tools/repin_v173.py \ + --compiler-path "" --check +``` + +Any change to checker identity or accepted theorem set is a trust-surface blocker, not an automatic repin. + +- [ ] **Step 6: Run GREEN and commit** + +Run the new audit tests plus the existing Gaussian/range source-closure workflow commands. Commit only after exact axiom and `sorry`/admission observations are recorded. + +### Task 4: In-repository and personal skill alignment + +**Files:** +- Create: `tests/jackal_skill_contract_test.py` +- Modify: `plugins/jackel/skills/jackel/SKILL.md` +- Audit/update: `/jackal-assurance-oracle/` +- Audit/update: `/skills/software-development/jackal-verified-computation/` +- Audit/update: `/skills/software-development/jackal-trust-boundary-reseal/` +- Audit/classify without forced edits: the five other JACKAL-mentioning personal Hermes skills listed in the tracker + +- [ ] **Step 1: Read every selected `SKILL.md` and referenced JACKAL file completely** + +Record SHA-256, version, classification (`direct-router`, `claim-router`, `audit-procedure`, `incidental`), every referenced tool name, and every current version/count/pin claim in the tracker. + +- [ ] **Step 2: Write RED fixtures for real tool names and routing rules** + +Require direct/claim routers to reference only inventory names and to contain these semantic clauses: + +```python +REQUIRED_ROUTING = { + "jackal_claim", "jackal_verify_bundle", "jackal_verify_receipt", + "jackal_anubis_verify_program", "jackal_anubis_verify_program_receipt", +} +REQUIRED_WORDING = { + "caller-pinned", "refused", "indeterminate", "no silent downgrade", +} +``` + +Tests must fail on the personal Hermes v1.7.0/34-tool router before it is updated. + +- [ ] **Step 3: Update routers and reseal procedure** + +Use neutral capability facts from `release/capability_inventory_v1.json`. Preserve host-specific install instructions. Update the reseal procedure to the current repo name, all three checker families, program verifier residuals, package v1.7.3 candidate state, and generated-manifest rules. + +- [ ] **Step 4: Run skill tests and Hermes skill audit** + +```bash +/opt/homebrew/bin/python3 -B -m unittest tests.jackal_skill_contract_test -v +cd "$HOME/.hermes/hermes-agent" +python -m hermes_cli.main skills audit +``` + +If the local Hermes command shape differs, use `hermes skills audit` as documented and record the exact installed command/version. + +- [ ] **Step 5: Commit repository skill changes and separately receipt personal-file changes** + +Never sweep unrelated personal skills into a Git commit. Record before/after hashes and exact paths in the central tracker. + +### Task 5: Fresh v1.7.3 package and Codex release-pin replay + +**Files:** +- Modify generated package/manifest files only through `release/build_package_v173.sh` and `release/tools/repin_v173.py` +- Update: `docs/JACKAL_PRODUCTION_ALIGNMENT_TRACKER_2026-08-22.md` + +- [ ] **Step 1: Freeze source inputs and record pre-build hashes** + +Record `git rev-parse HEAD`, worktree status, builder digest, repin digest, checker identities, catalog digest, inventory digest, and current package digest. + +- [ ] **Step 2: Build twice in separate clean output roots** + +```bash +env -i PATH=/opt/homebrew/bin:/usr/bin:/bin HOME="$HOME" \ + JACKAL_ANUBIS_COMPILER_PATH="" \ + JACKAL_DIST="/jackal-v173-build-a" \ + /bin/zsh release/build_package_v173.sh --build +env -i PATH=/opt/homebrew/bin:/usr/bin:/bin HOME="$HOME" \ + JACKAL_ANUBIS_COMPILER_PATH="" \ + JACKAL_DIST="/jackal-v173-build-b" \ + /bin/zsh release/build_package_v173.sh --build +``` + +Use the builder's actual supported output argument discovered from its source. If it has no output option, run in two fresh clones rather than editing the builder. + +- [ ] **Step 3: Compare complete outputs** + +Require identical tarball SHA-256, byte size, extracted file roster, per-file SHA256SUMS, and aggregate inventory. Do not treat matching tarballs alone as sufficient. + +- [ ] **Step 4: Run the five previously skipped package tests** + +```bash +JACKAL_TEST_PACKAGE_ROOT="/jackal-v173-build-a/jackal-v1.7.3-macos-arm64" \ + /opt/homebrew/bin/python3 -B -m unittest tests.package_unified_v173_test -v +``` + +Expected: zero skips. + +- [ ] **Step 5: Run exact Codex install/discovery/call/skill tests** + +Provision only into an isolated temporary Codex home, list exactly 41 MCP tools, exercise every family, replay a program receipt, tamper it, and replay the pristine receipt again. Verify wrapper and runtime pins before and after. + +- [ ] **Step 6: Update tracker and commit only generated, derived changes** + +If package bytes differ from the candidate pins, regenerate every dependent plugin identity and repeat all dependent gates. + +### Task 6: Build the production-equivalent Hermes plugin candidate + +**Files in fresh `AnubisQuantumCipher/hermes-jackal-verified` checkout:** +- Modify: `plugin.yaml`, `__init__.py`, `schemas.py`, `tools.py` +- Modify: `README.md`, `PROVENANCE.md`, `SECURITY.md`, `CHANGELOG.md`, `THIRD_PARTY_NOTICES.md` +- Modify: `skills/jackal-verified-computation/SKILL.md`, `skills/AGENTS-SNIPPET.md` +- Modify: `tests/test_plugin.py`, `tests/test_plugin_v2.py`, `tests/aba_recheck_gate.py` +- Modify: `scripts/gen_schemas.py`, `scripts/fresh_install_smoke.py`, `scripts/release_audit.py` +- Regenerate: `EPOCH.json`, `MANIFEST.json`, split package parts through owning scripts + +- [ ] **Step 1: Clone/isolate and record exact state** + +Create `mission/jackal-production-alignment-20260822` from public `main` `e157e4dc98ffc127bb9abca4ae2ea6cdd699db56`, then record status, remotes, tags, releases, and prior package-part convention. + +- [ ] **Step 2: Write RED tests for 41 tools and new families** + +Change no production bytes yet. Tests must expect exact inventory equality with the new package, three program tools, four domain-pack tools, neutral metadata wording, and real skill tool names. Run them against v5.0.0 and observe the expected 34-versus-41 failures. + +- [ ] **Step 3: Import the exact verified package and generate schemas** + +Split only because GitHub's per-blob limit requires it. Record whole-package SHA-256 and part SHA-256 values. Generate schemas from the package catalog; do not retype schemas. + +- [ ] **Step 4: Update adapter and bundled skill without changing status semantics** + +The handler returns the parsed runtime object as JSON. The only plugin-local admission outcome remains the bounded busy/refusal path. Add program-evidence residual preservation and current installation/discovery commands. + +- [ ] **Step 5: Regenerate all identities** + +Run schema generator, epoch generator, manifest generator, and each `--check`/verify mode. Typed hashes are forbidden. + +- [ ] **Step 6: Run full Hermes plugin gates** + +```bash +python -m unittest discover -s tests -v +python scripts/verify_manifest.py +python scripts/release_audit.py +python scripts/fresh_install_smoke.py +hermes plugins doctor . --ci +``` + +Also install the exact commit into a temporary `HERMES_HOME`, verify discovery of 41 tools and one reviewed namespaced skill, exercise every family, and run pristine→tamper→pristine receipt replay. + +- [ ] **Step 7: Commit and push non-final candidate** + +Do not tag or publish while the kernel is untagged or trust-surface sign-off is unproven. + +### Task 7: Independent adversarial review and trust-surface disposition + +**Files:** +- Create: `release/evidence/production_alignment_review_v173.json` +- Modify: central tracker with every finding and disposition + +- [ ] **Step 1: Freeze exact review bytes** + +Record base/head for kernel and Hermes plugin and SHA-256 of `git diff --binary BASE..HEAD` for each. + +- [ ] **Step 2: Obtain an independent read-only review** + +Review tools, schemas, skills, neutral wording, package pins, proof/admission evidence, receipt replay, and release commands. The reviewer must not edit the worktree and must receive exact base/head/diff identities. + +- [ ] **Step 3: Reproduce every actionable finding** + +For each finding, run a focused command or construct a hostile fixture. False positives receive evidence-backed dispositions; real defects enter a new TDD red-green cycle and a new commit. + +- [ ] **Step 4: Decide the trust-surface lane** + +Search current user/architect authority for explicit approval of `inventory-safe-v1` accept conditions and v1.7.3 release promotion. A 41-tool count correction, green checks, or `READY_FOR_SIGNOFF` is not by itself approval. If absent, mark release/tag/merge rows `BLOCKED_TRUST_SURFACE` and continue orthogonal PR preparation without publishing a false release. + +`BLOCKED_TRUST_SURFACE` is an internal row status, not a competing terminal +label. When it is the only residual at completion, map it exactly to the +prompt-defined terminal label `BLOCKED_JACKAL_TRUST_SURFACE`. + +### Task 8: Seal and read back authorized releases + +**Files:** +- Release artifacts and generated evidence only after Task 7 authorizes the lane + +- [ ] **Step 1: Re-probe both repositories and GitHub immediately before mutation** + +Fetch tags/prune, confirm clean trees, exact candidate heads, no newly landed conflicting PR, and hosted check state. + +- [ ] **Step 2: Merge/tag kernel in repository convention only if authorized** + +Use the observed merge style. Create an annotated `v1.7.3` tag, verify `tag^{commit}`, publish assets plus SHA256SUMS, download them to a fresh directory, and compare every byte/hash to the local sealed artifacts. + +- [ ] **Step 3: Rebind and rerun the Hermes plugin after the kernel tag** + +The kernel pin bump invalidates previous dependent evidence. Regenerate plugin identities, rerun every plugin gate, merge in convention, create a new annotated plugin tag/version, publish, download, and compare. + +- [ ] **Step 4: Update repository descriptions from executable reality** + +Use neutral descriptions with the verified count and explicit formal/program boundaries. Read back both repository descriptions and release metadata through GitHub. + +### Task 9: Update existing NousResearch PR #88446 + +**Files in fresh architect-fork Hermes checkout:** +- Modify: `hermes_cli/data/plugin_index.json` +- Add/modify focused tests only if the index schema or search expectations require them + +- [ ] **Step 1: Clone the exact fork branch and record state** + +Bind fork remote, upstream remote, branch `feat/index-jackal-verified`, head `08eb5173033e15117f51ac5abc9ca3d8bab313fe`, upstream base, status, and current PR metadata. + +- [ ] **Step 2: Write RED index assertions** + +Require the exact new immutable plugin commit, neutral description, mechanically verified tool count, `capabilities.tools == True`, `capabilities.skills == True`, and search/resolve behavior. Run against the old entry and observe failure. + +- [ ] **Step 3: Update only the existing entry and generated timestamp** + +Do not broaden the PR beyond the index seed unless upstream tests require a focused fixture. Preserve schema shape. + +- [ ] **Step 4: Run focused and broader applicable tests** + +```bash +python -m pytest tests/hermes_cli/test_plugin_index_search.py -q +python -m pytest tests/hermes_cli -q +python -m json.tool hermes_cli/data/plugin_index.json >/dev/null +git diff --check +``` + +If the broader suite has environment failures, isolate and record them; do not call it green. + +- [ ] **Step 5: Commit and push a new PR-branch commit** + +Never amend the existing pushed commit. Read back the PR head. + +- [ ] **Step 6: Update title/body and add the reproducible verification comment** + +The comment must include exact plugin ref, kernel tag/commit, tool-count commands, Lean axiom/admission commands and results, skills audit hash/count, package/manifest hashes, test counts, and explicit non-claims. Record the comment URL. + +- [ ] **Step 7: Recheck PR diff, mergeability, review threads, and hosted checks** + +Do not claim maintainer approval and do not merge NousResearch upstream. + +### Task 10: Completion audit and terminal receipt + +**Files:** +- Update: `docs/JACKAL_PRODUCTION_ALIGNMENT_TRACKER_2026-08-22.md` +- Create: `release/evidence/JACKAL_PRODUCTION_ALIGNMENT_RECEIPT_2026-08-22.md` + +- [ ] **Step 1: Re-read all 172 lines of the architect goal** + +Map every imperative, named artifact, gate, release, metadata surface, and terminal field to tracker evidence. + +- [ ] **Step 2: Run the complete clean-checkout gate battery** + +Run inventory/drift, full test families, Lean/admission, claim hostile/ABA, fresh package, Hermes install, Codex install, deterministic build, manifests, independent review, and GitHub read-back. Record command, exit, count, hash, and scope for every row. + +- [ ] **Step 3: Audit residuals and authority** + +Any missing evidence remains incomplete. If the only residual is the unapproved trust surface, use `BLOCKED_JACKAL_TRUST_SURFACE`; otherwise choose the exact permitted partial/blocked status. Use `COMPLETE_JACKAL_PRODUCTION_ALIGNMENT` only if every requirement is current and proven. + +- [ ] **Step 4: Commit/push the final receipt where authorized** + +The receipt lists every repo/branch/commit/tag/release, 41-tool roster evidence, Hermes/Codex equality, Lean/admission result, commands/exits/counts/hashes, skills, PR #88446 head/comment/checks, public descriptions, independent review, residuals, and non-claims. + +## Plan self-review + +- **Spec coverage:** Tasks 1–10 cover all prompt sections, all 11 minimum gates, release/pin identity, skills, public descriptions, PR #88446, independent review, and terminal receipt fields. +- **Placeholder scan:** every task names concrete files, behavior, commands, expected observations, and terminal evidence; no deferred implementation marker remains. +- **Type consistency:** canonical inventory is always a JSON object with ordered `tools`; schema identity is `schema_sha256`; integration exposure is the three-key `exposure` object. +- **Authority consistency:** no task equates candidate, green checks, signoff, tag, release, and read-back. 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 e009e10..8e07f92 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 @@ -8,6 +8,13 @@ Original design base: `7d9b5bee0ce52fb6bbe24e4c50f5661f5bad2318` 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. + + ## Objective Add a repo-local, publishable Codex plugin with migration-preserved package ID @@ -17,17 +24,18 @@ full engine available without weakening JACKAL's epistemic classes, refusal semantics, checker boundaries, or pinned runtime identity. The plugin is installed from a JACKAL repository checkout. Its computation -runtime is the separately sealed JACKAL v1.7.0 macOS release package, not a -copy assembled from source-tree fragments. The plugin does not duplicate or -modify `plugin/hermes`; an explicit provisioner installs the hash-pinned -release package in the user's macOS Application Support directory. The first -verified platform is Apple Silicon macOS. Intel macOS remains unsupported -until JACKAL publishes and seals a corresponding runtime. +runtime is the separately pinned JACKAL v1.7.3 release macOS package, not a +copy assembled from source-tree fragments. The operator may download the fixed +release URL or provision the same exact package from a local tarball. The plugin +does not duplicate or modify `plugin/hermes`; an explicit provisioner installs +the hash-pinned release package in the user's macOS Application Support +directory. The verified platform is Apple Silicon macOS. Intel macOS remains +unsupported until JACKAL publishes and seals a corresponding runtime. ## Goals - Expose every tool declared by `plugin/hermes/tools.json`; the current base - revision declares 34 tools. + revision declares 41 tools in catalog order with no duplicate aliases. - Make `jackal_claim` and `jackal_verify_bundle` the preferred front doors for general structured claims while retaining every exact, checked, estimated, bounded, formal-bounded, model-based, and verification lane. @@ -64,6 +72,7 @@ plugins/jackel/ .codex-plugin/plugin.json .mcp.json PLUGIN_IDENTITY.sha256 + README.md mcp/server.py scripts/launch_mcp.zsh scripts/provision_runtime.py @@ -85,7 +94,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 mathematical evidence kernel to Codex.` +- `description`: `Expose JACKAL's claim-aware computation, domain-pack, and program-evidence kernel to Codex.` - `author.name`: `Anubis Quantum Cipher` - `author.url`: `https://github.com/AnubisQuantumCipher` - `homepage` and `repository`: @@ -95,7 +104,7 @@ backend. It locates a separately sealed runtime and invokes the unchanged - `mcpServers`: `./.mcp.json` - `interface.displayName`: `JACKAL` - `interface.shortDescription`: `Claim-aware computation with explicit evidence classes` -- `interface.longDescription`: `Use JACKAL's complete mathematical evidence kernel from Codex, with exact, checked, estimated, bounded, formal-bounded, model-based, verified, indeterminate, and refused results preserved at their original assurance level. Formal-bounded applies only to checker-admitted fragments. Requires Apple Silicon macOS, Python >=3.10 at /opt/homebrew/bin/python3 (install with brew install python), and the pinned sealed v1.7.0 runtime.` +- `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.developerName`: `Anubis Quantum Cipher` - `interface.category`: `Productivity` - `interface.capabilities`: `["Interactive"]` @@ -103,9 +112,10 @@ backend. It locates a separately sealed runtime and invokes the unchanged - `keywords`: `jackel`, `mathematics`, `numerical-trust`, `formal-verification`, `evidence`, `mcp` -The short description identifies JACKAL as a mathematical evidence kernel, -not a generic calculator. The long description names its claim classes and -states that formal-bounded results are limited to checker-admitted fragments. +The short description identifies JACKAL as an evidence kernel, not a generic +calculator. The long description names the mathematical, domain-pack, and +program-evidence classes; it keeps formal fragments and program residuals +explicit. The initial manifest omits app, hook, asset, privacy-policy, and terms fields rather than publishing broken paths or invented policies. @@ -114,6 +124,7 @@ 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.` ## Marketplace Entry @@ -136,22 +147,29 @@ checker bytes execute. ## Runtime Provisioning `plugins/jackel/scripts/provision_runtime.py` is an explicit operator command, -not an automatic install hook. Version 0.1.0 pins: +not an automatic install hook. Version 0.1.0 release pins: -- release epoch: `v1.7.0` -- asset: `jackal-v1.7.0-macos-arm64.tar.gz` +- release epoch: `v1.7.3` +- asset: `jackal-v1.7.3-macos-arm64.tar.gz` - release URL: - `https://github.com/AnubisQuantumCipher/jackal/releases/download/v1.7.0/jackal-v1.7.0-macos-arm64.tar.gz` + `https://github.com/AnubisQuantumCipher/jackal/releases/download/v1.7.3/jackal-v1.7.3-macos-arm64.tar.gz` - package SHA-256: - `21c7ede586f30a58772f321f7dbb36ab66213e199785489f99133710ac56096e` -- package size: `118862060` bytes + `68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae` +- package size: `158363786` bytes +- extracted `SHA256SUMS` SHA-256: + `a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a` + +With `RELEASE_STATE` set to `published`, the provisioner uses the fixed release +URL when no `--tarball` is supplied. An explicit absolute `--tarball` remains +available for offline installation; both paths enforce the same size, package +digest, and embedded `SHA256SUMS` pin. The provisioner: 1. Refuses unless the host is macOS on `arm64`. 2. Downloads into a newly created temporary directory or accepts an explicit local tarball path for offline installation. -3. Rejects a declared or streamed body larger than 118862060 bytes, then +3. Rejects a declared or streamed body larger than 158363786 bytes, then requires exactly that size and the fixed package SHA-256 before extraction. The per-operation network timeout is supplemented by a monotonic total download deadline, so a peer cannot keep the transfer alive indefinitely @@ -162,7 +180,7 @@ The provisioner: `SHA256SUMS` and `MANIFEST.sha256`-governed selftest. 6. Writes a package marker binding the epoch, original tarball digest, and verified internal identities, then atomically installs the package at - `~/Library/Application Support/JACKAL/runtimes/v1.7.0/`. + `~/Library/Application Support/JACKAL/runtimes/v1.7.3/`. 7. Atomically writes a locator containing the release epoch, runtime path, and package digest at `~/Library/Application Support/JACKAL/codex-plugin/runtime.json`. @@ -184,7 +202,7 @@ The MCP adapter resolves runtime roots in this order: 2. the provisioner's macOS locator. Every candidate must be a provisioner-verified extraction with the package -marker, identify epoch v1.7.0, match the pinned original package digest, pass +marker, identify epoch v1.7.3, match the pinned original package digest, pass all internal `SHA256SUMS`/manifest identities, and pass the backend bundle selftest. A source checkout containing a few untracked binaries is not a valid runtime candidate. Ambiguous or divergent candidates refuse and require an @@ -351,9 +369,9 @@ if defective or malicious. They are not part of JACKAL's mathematical checker 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, launcher, adapter, -provisioner, verification script, and operational skill. The manifest excludes -only itself. `scripts/verify_plugin.py` performs a bounded descriptor-relative, +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, 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 @@ -459,7 +477,9 @@ status; the adapter is not authorized to create or promote that status. ### Provisioner tests - Reject non-macOS and non-arm64 hosts before downloading. -- Verify the fixed v1.7.0 URL, epoch, filename, exact 118862060-byte length, +- Exercise the published release's pinned default network path through a + controlled opener before staging real bytes. +- Verify the fixed v1.7.3 URL, epoch, filename, exact 158363786-byte length, bounded streaming download, and expected package SHA-256. - Exercise offline local-tarball provisioning with a fixture archive. - Reject digest mismatch, path traversal, absolute paths, device entries, @@ -487,7 +507,7 @@ status; the adapter is not authorized to create or promote that status. ### Live installed-plugin checks -Using the pinned v1.7.0 runtime and an isolated temporary `CODEX_HOME`: +Using the pinned v1.7.3 release runtime and an isolated temporary `CODEX_HOME`: The installer derives forbidden state roots from both the passwd account home and the process home, canonicalizes them independently of caller `HOME`, and @@ -510,7 +530,7 @@ process racing inside one validation-to-exec interval. 5. Parse the installed copy's `.mcp.json` and launch its exact configured absolute command, arguments, and installed-copy working directory. Perform a real MCP `initialize` and `tools/list`; require the discovered names to - equal the pinned runtime's 34-tool `plugin/hermes/tools.json` inventory. + equal the pinned runtime's ordered 41-tool `plugin/hermes/tools.json` inventory. 6. Call `jackal_exact` on a supported rational expression and require `status: exact`. 7. Call a supported formal fragment and require semantic deep equality with a @@ -581,7 +601,7 @@ wrapper-identity checks, provisioner tests, adapter unit tests, real marketplace installation, fresh-task MCP discovery, one supported computation, one checker-attested call, one fail-closed control, backend selftest, and the repository evidence verifier all pass against the same source revision and -pinned v1.7.0 runtime bytes. +pinned v1.7.3 release runtime bytes. If sealed runtime artifacts are absent, source and unit work may be complete, but the plugin must be reported as integration-blocked rather than ready. diff --git a/docs/superpowers/specs/2026-08-22-jackal-production-alignment-design.md b/docs/superpowers/specs/2026-08-22-jackal-production-alignment-design.md new file mode 100644 index 0000000..514ffce --- /dev/null +++ b/docs/superpowers/specs/2026-08-22-jackal-production-alignment-design.md @@ -0,0 +1,97 @@ +# JACKAL Production Alignment Design + +**Architect-approved source:** `JACKAL_PRODUCTION_ALIGNMENT_CODEX_GOAL_2026-08-22.md` +**Source identity:** 12,611 bytes, 172 lines, SHA-256 `8025fb5570587258ec3cf6c808df71451af5b8815a7a5778f7d1e48e296dad7d` +**Initial tool-containing implementation ref:** `d25bcd9818e0d106f337798f80527ae611cc3acc` +**Live candidate authority:** `AnubisQuantumCipher/jackal` PR #12; its moving head is bound in `docs/JACKAL_PRODUCTION_ALIGNMENT_TRACKER_2026-08-22.md` rather than duplicated here. + +## Objective and non-negotiable boundary + +Align the current JACKAL kernel, Hermes plugin, Codex plugin, every JACKAL-routing skill, release metadata, public repository metadata, and NousResearch Hermes PR #88446 to one mechanically verified capability surface. The expected full surface is 41 unique tools, but the count is accepted only when current registry bytes, Hermes discovery, and Codex discovery independently agree. + +No alignment change may alter what a verifier accepts, expand a proof checker trust assumption, weaken a refusal, or silently promote `estimated`, `bounded`, `structural-exact`, `verified-program-evidence`, `indeterminate`, or `refused`. An accept-condition change is isolated from release work and reported as `BLOCKED_TRUST_SURFACE` until explicit sign-off exists. + +## Approaches considered + +1. **Extend the clean PR #12 candidate and derive downstream surfaces from it — selected.** This preserves the already reviewed 41-tool implementation, exact v1.7.3 package bytes, Codex adapter, program-evidence residuals, and existing gates. New work adds canonical inventory and anti-drift enforcement before updating downstream releases. +2. **Rebuild from public `master`.** Public `master` exposes the merged 38-tool line, so this would replay three existing candidate commits and increase the chance of a second, divergent v1.7.3 implementation. +3. **Edit only public metadata and PR prose.** Rejected because the public Hermes plugin still contains v1.7.0/34-tool executable bytes, tests, schemas, skills, and release pins. Prose-only alignment would make the catalog claim false. + +## Repository topology and ownership + +- **Kernel plus Codex plugin:** `AnubisQuantumCipher/jackal`. The existing clean PR #12 worktree is the integration source. The stale dirty Desktop checkout is evidence only and remains untouched. +- **Hermes plugin:** `AnubisQuantumCipher/hermes-jackal-verified`. The installed detached v5.0.0 checkout is immutable evidence; implementation occurs in a fresh isolated clone or worktree from public `main`. +- **Hermes upstream index PR:** `NousResearch/hermes-agent#88446`, backed by the architect-owned fork branch `feat/index-jackal-verified`. The dirty local Hermes main checkout is not used for changes; the PR branch is edited in a fresh isolated checkout. +- **Skills:** in-repository Codex and Hermes plugin skills are released with their plugins. Personal Hermes/Codex JACKAL skills are audited separately and changed only when they actually route JACKAL work or make current capability claims. + +## Canonical capability inventory + +Add a deterministic generator and committed JSON artifact with one record per exported tool in `plugin/hermes/tools.json` declaration order. Each record contains: + +- tool name and SHA-256 of its canonical schema record; +- kernel, Hermes, and Codex exposure expectations; +- admitted result/status classes and consequence ceiling; +- checker/proof dependency or an explicit `none` value; +- supported fragment summary and explicit refusal/non-coverage summary; +- profile membership and the candidate/release identity containing the tool. + +The generator reads current catalog, profile, manifest, proof-identity, and integration bytes. It rejects missing or duplicate tools, unknown status vocabulary, unknown checker identities, unbound tool facts, wrong profile membership, and any output not in catalog order. A `--check` mode reproduces the committed artifact byte-for-byte. It never rewrites proof evidence or release manifests. + +The inventory records `v1.7.3-candidate` until a v1.7.3 annotated tag and release are authorized, created, and read back. It must not describe an untagged candidate as a public release. + +## Drift prevention + +Add one repository drift gate that consumes the canonical inventory and checks: + +- kernel catalog count and unique name set; +- Hermes profile/discovery roster; +- Codex MCP discovery roster and wrapper expected count; +- plugin manifests, schemas, compatibility floors, release builders, and pinned package identities; +- in-repository skills and user-facing docs for real tool names, current count/version, status vocabulary, and forbidden stale pins/counts; +- generated inventory and plugin identity manifests in `--check` mode. + +The gate is semantic where possible. It does not ban the text `34` globally because migration history and tests can truthfully mention old epochs. Current-surface files have an explicit allowlist and exact expected values; historical files are labeled historical rather than rewritten. + +## Hermes plugin alignment + +Build a new Hermes plugin version from the exact verified v1.7.3 package candidate rather than modifying v5.0.0 or its tag. Generate the Hermes schema set from the package catalog, expose exactly the same 41 names, preserve raw result JSON/statuses, and update the bundled skill, manifest, provenance, security boundary, tests, and release audit. + +The plugin release remains unsealed while the kernel candidate is untagged or while the `inventory-safe-v1` trust-surface sign-off is not proven. A non-final branch may contain prepared bytes and passing gates; its metadata must say candidate, not release. + +## Skills alignment + +Every JACKAL-routing skill is classified as one of: + +- **direct router:** names typed tools and must be checked against the inventory; +- **claim router:** sends mixed/policy-bearing/consequential work through `jackal_claim` and independent replay through dedicated verifiers; +- **audit procedure:** discusses JACKAL but does not advertise a live tool roster; +- **incidental mention:** no routing/capability claim and no update required. + +Direct and claim routers receive fixtures that parse every backticked `jackal_*` name and require it to exist in the canonical inventory. They must preserve status, refusal, residual, expected-identity independence, and host-specific installation steps. + +## Release and PR state machine + +The release sequence is `candidate -> audited -> independently reviewed -> sign-off proven -> tagged -> published -> read-back verified`. A stage does not imply the next stage. + +After kernel and plugin release identities are immutable, update the existing PR #88446 branch rather than opening another PR. The index entry, PR title/body, capabilities, and exact 40-character plugin ref must match the reviewed release. The verification comment records commands, exits, counts, Lean axiom/admission evidence, skill audit, hashes, and non-claims. Upstream merge remains outside authorization. + +## Error handling and evidence + +- A surface mismatch is a failing gate, not a prose warning. +- A missing checker or unverified pin is `refused`/blocked, never a weaker success. +- A non-measurable evaluation remains exit 3 and is not counted as pass or fail. +- Package tests skipped for lack of a fresh package root stay open until run against a fresh build. +- Every long tranche ends in a named-path commit with command-derived receipts; no blanket staging or pushed-history rewrite. + +## Testing and independent review + +Implementation follows red-green TDD. New drift and inventory tests must fail against the current stale surfaces before production changes. Existing mutation suites validate that the instruments can turn red. + +Final gates cover unique roster equality, positive family smoke, hostile/refusal controls, claim A-to-B-to-A tamper replay, Lean build and axiom/admission scan, fresh-package Hermes and Codex discovery, deterministic repin/build checks, clean-checkout replay, and an independent adversarial review of code, skills, public wording, pins, receipts, and trust-surface non-claims. + +## Design self-review + +- **Placeholder scan:** no deferred implementation placeholder is present. +- **Consistency:** 41 is an expected value that must be recomputed; it is not used as a prose-only override. +- **Scope:** kernel/Codex, Hermes plugin, skills, release metadata, and PR #88446 are separate tranches joined by one canonical inventory. +- **Ambiguity:** existing `inventory-safe-v1` accept conditions are audited but not promoted through merge/tag/release unless explicit sign-off is evidenced. diff --git a/domain_packs/core/manifest.json b/domain_packs/core/manifest.json index a315cc5..42e791c 100644 --- a/domain_packs/core/manifest.json +++ b/domain_packs/core/manifest.json @@ -5,7 +5,7 @@ "pack_version": "1.0.0", "description": "Closed Anubis routing for existing exact arithmetic evidence lanes.", "compatibility": { - "jackal_release_min": "v1.8.0", + "jackal_release_min": "v1.7.3", "jackal_release_max_exclusive": "v2.0.0", "protocol_min": "1", "protocol_max": "1" @@ -78,5 +78,5 @@ ] } ], - "manifest_digest_sha256": "ab851801be878e9b2ac366a992d6e1a3c709527caaffeafd8c5f3e8752f7f0b4" + "manifest_digest_sha256": "9d3d0085ba44bae6779af400945b5518568bb095962c79215f44eb3cd09a3582" } diff --git a/domain_packs/decision/manifest.json b/domain_packs/decision/manifest.json index 086b228..d1fda20 100644 --- a/domain_packs/decision/manifest.json +++ b/domain_packs/decision/manifest.json @@ -5,7 +5,7 @@ "pack_version": "1.1.0", "description": "Closed Anubis routing for deterministic decision matrices. Orders options by a caller-declared, recomputable numeric criterion; refuses to rank on preference, goodness, or worth. `decision.matrix.rank.v1` decides criterion admissibility with a fixed word list and is retained unchanged for its callers; `decision.matrix.rank.v2` additionally requires a declared unit from the closed vocabulary of release/claim/unit_registry_v1.json, which is the lane where a value-judgment criterion cannot be spelled around.", "compatibility": { - "jackal_release_min": "v1.8.0", + "jackal_release_min": "v1.7.3", "jackal_release_max_exclusive": "v2.0.0", "protocol_min": "1", "protocol_max": "1" @@ -277,5 +277,5 @@ ] } ], - "manifest_digest_sha256": "9dde3c3c60920aef6bb13d131f2a9b76b57925eea7d46fd6376749c316710b04" + "manifest_digest_sha256": "239cfbd2475a3aeaf391c37f8dadc969690b6b7f90c5b1f0341fc58930d18283" } diff --git a/domain_packs/programming/manifest.json b/domain_packs/programming/manifest.json index 3da7822..dc84f57 100644 --- a/domain_packs/programming/manifest.json +++ b/domain_packs/programming/manifest.json @@ -5,7 +5,7 @@ "pack_version": "1.0.0", "description": "Closed Anubis routing for byte-exact structural facts about source files. Structure only: nothing here is evidence that any code is correct.", "compatibility": { - "jackal_release_min": "v1.8.0", + "jackal_release_min": "v1.7.3", "jackal_release_max_exclusive": "v2.0.0", "protocol_min": "1", "protocol_max": "1" @@ -173,5 +173,5 @@ ] } ], - "manifest_digest_sha256": "4bad241e732829bbe2c0c88790addcdb63329e9b19c7ba7c520fc910d330b890" + "manifest_digest_sha256": "09fb54de11b7d59c203574fda13c06f0b37d1171c12ce1742113b903d90e45cb" } diff --git a/domain_packs/registry_v1.json b/domain_packs/registry_v1.json index 9a2b542..30ac20c 100644 --- a/domain_packs/registry_v1.json +++ b/domain_packs/registry_v1.json @@ -16,7 +16,7 @@ "pack_id": "jackal.core.exact", "pack_version": "1.0.0", "manifest_path": "domain_packs/core/manifest.json", - "manifest_sha256": "427b51d6527be15286efce9ec5c90b369950f57ddb438c93e2616233e290190d", + "manifest_sha256": "8babbc2966f7a269a0aefe15495e83c70351795f63cc3468f75c36bb5046ee8a", "entry_source_path": "jackal_calc.anb", "entry_source_sha256": "f579b6f59bc024d24914487b0cd0f18ea43dea1be52708a05a66dc885d80bb4e", "route_source_path": "domain_packs/core/core_pack.anb", @@ -29,7 +29,7 @@ "pack_id": "jackal.programming.source", "pack_version": "1.0.0", "manifest_path": "domain_packs/programming/manifest.json", - "manifest_sha256": "59480b72f8ea51739859599c243b86167fb66ed3ffc519e492ca5d9f7c6605ba", + "manifest_sha256": "a8668782c1be9553e80b044a70327a3f767779b6552a2d3401454d01aa1b43ba", "entry_source_path": "jackal_calc.anb", "entry_source_sha256": "f579b6f59bc024d24914487b0cd0f18ea43dea1be52708a05a66dc885d80bb4e", "route_source_path": "domain_packs/programming/programming_pack.anb", @@ -43,7 +43,7 @@ "pack_id": "jackal.decision.matrix", "pack_version": "1.1.0", "manifest_path": "domain_packs/decision/manifest.json", - "manifest_sha256": "e7a8187c367a64f092c6e54bf3dd2b593265e93b8727fdffbc4bba220830e911", + "manifest_sha256": "79b5d90d50c2343fdc93f1f92f0ea36bfa1e6c1c239ab6572aea8ad237ccbd65", "entry_source_path": "jackal_calc.anb", "entry_source_sha256": "f579b6f59bc024d24914487b0cd0f18ea43dea1be52708a05a66dc885d80bb4e", "route_source_path": "domain_packs/decision/decision_pack.anb", @@ -54,5 +54,5 @@ ] } ], - "registry_digest_sha256": "3da40d85c885626f01030152d23a010e0d354e822a53ac2e49e9f82a7f43a9d8" + "registry_digest_sha256": "9f96f53795746b75f6e9d505e49f6f2578407d224f5e22fdad8a03e8a738fed8" } diff --git a/evals/v2/receipts/README.md b/evals/v2/receipts/README.md index 05ef3be..a01156a 100644 --- a/evals/v2/receipts/README.md +++ b/evals/v2/receipts/README.md @@ -109,6 +109,30 @@ corpus_aggregate_digest engine_identity mode timestamp_utc usable for order-of-magnitude comparison on the same host and not for anything finer. +## Live Codex transcripts — 2026-08-20 + +The three `codex_w*_2026-08-20.jsonl` files are captured `codex exec +--ephemeral --json` event streams, not `jackal-eval-v2-results-v1` receipts. +The autonomous W3 stream has one command-execution event whose private local +command and output payloads are explicitly redacted while its event type, +identity, status, and exit code remain. Their exact post-redaction hashes, +content state, and adjudication are in `live_tool_sessions_2026-08-20.json`. + +Observed: + +- autonomous W3 attempted `jackal_range_bound` instead of the receipt verifier; +- forced W3 attempted the correct `jackal_verify_receipt`; +- autonomous W10 attempted `jackal_decision_rank_v2` and disclosed that its + fallback arithmetic answer was not verifier-established; +- the noninteractive host cancelled every MCP call, including with + `approval_policy=never`. + +Therefore attempted invocation is recorded, but successful verifier use remains +`NOT_MEASURABLE`. The deterministic runner also still lacks profile identity and +an adapter from Codex JSONL events to protocol-admissible autonomous rows. These +transcripts must not be passed to `metrics.py --verify-receipts` or counted as a +model/tool accuracy comparison. + ## Reproducing a receipt ``` diff --git a/evals/v2/receipts/codex_w10_comparison_2026-08-20.jsonl b/evals/v2/receipts/codex_w10_comparison_2026-08-20.jsonl new file mode 100644 index 0000000..3357156 --- /dev/null +++ b/evals/v2/receipts/codex_w10_comparison_2026-08-20.jsonl @@ -0,0 +1,10 @@ +{"type":"thread.started","thread_id":"01a01f85-57f5-7321-927a-4eedb1e4aecf"} +{"type":"item.completed","item":{"id":"item_0","type":"error","message":"Under-development features enabled: chronicle. Under-development features are incomplete and may behave unpredictably. To suppress this warning, set `suppress_unstable_features_warning = true` in [REDACTED_PRIVATE_LOCAL_PATH]."}} +{"type":"turn.started"} +{"type":"item.completed","item":{"id":"item_1","type":"error","message":"Skill descriptions were shortened to fit the 2% skills context budget. Codex can still see every skill, but some descriptions are shorter. Disable unused skills or plugins to leave more room for the rest."}} +{"type":"item.completed","item":{"id":"item_2","type":"agent_message","text":"I’m using the registered JACKAL routing skill because this is an assurance comparison. I’ll inspect the available JACKAL tool interface without invoking a shell, then use verifier backing only if the claim class requires it."}} +{"type":"item.completed","item":{"id":"item_3","type":"agent_message","text":"A verifier-backed comparison is appropriate here because you asked for an assurance status and consequence ceiling, not merely the obvious arithmetic ordering. The v2 decision lane also binds the canonical unit `ms` and independently rechecks the ranking certificate."}} +{"type":"item.started","item":{"id":"item_4","type":"mcp_tool_call","server":"jackel","tool":"jackal_decision_rank_v2","arguments":{"criterion":"latency_ms","decision_id":"latency_comparison","options":"alpha 120 beta 95 gamma 210","sense":"min","unit":"ms"},"result":null,"error":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_4","type":"mcp_tool_call","server":"jackel","tool":"jackal_decision_rank_v2","arguments":{"criterion":"latency_ms","decision_id":"latency_comparison","options":"alpha 120 beta 95 gamma 210","sense":"min","unit":"ms"},"result":null,"error":{"message":"user cancelled MCP tool call"},"status":"failed"}} +{"type":"item.completed","item":{"id":"item_5","type":"agent_message","text":"- Selected: `beta` — 95 ms\n- Runner-up: `alpha` — 120 ms\n- Exact margin: `25 ms`\n- Assurance status: not verifier-established; the JACKAL call was cancelled\n- Consequence ceiling: `decision-boundary`\n- Residual: `ms` and all values are caller-declared; declaring units does not establish that the values were measured or that latency is the right criterion."}} +{"type":"turn.completed","usage":{"input_tokens":169287,"cached_input_tokens":139008,"cache_write_input_tokens":0,"output_tokens":851,"reasoning_output_tokens":384}} diff --git a/evals/v2/receipts/codex_w3_autonomous_2026-08-20.jsonl b/evals/v2/receipts/codex_w3_autonomous_2026-08-20.jsonl new file mode 100644 index 0000000..af82ccb --- /dev/null +++ b/evals/v2/receipts/codex_w3_autonomous_2026-08-20.jsonl @@ -0,0 +1,26 @@ +{"type":"thread.started","thread_id":"01a01f80-02b7-7cd0-88fd-2f0f1b0e7ffe"} +{"type":"item.completed","item":{"id":"item_0","type":"error","message":"Under-development features enabled: chronicle. Under-development features are incomplete and may behave unpredictably. To suppress this warning, set `suppress_unstable_features_warning = true` in [REDACTED_PRIVATE_LOCAL_PATH]."}} +{"type":"turn.started"} +{"type":"item.completed","item":{"id":"item_1","type":"error","message":"Skill descriptions were shortened to fit the 2% skills context budget. Codex can still see every skill, but some descriptions are shorter. Disable unused skills or plugins to leave more room for the rest."}} +{"type":"item.completed","item":{"id":"item_2","type":"agent_message","text":"I’m using the registered JACKAL verification skill because this receipt explicitly requires re-verification; I’ll bind the check to the caller’s independent expectations."}} +{"type":"item.started","item":{"id":"item_3","type":"command_execution","command":"[REDACTED_PRIVATE_LOCAL_CONTEXT]","aggregated_output":"[REDACTED_PRIVATE_LOCAL_CONTEXT]","exit_code":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_3","type":"command_execution","command":"[REDACTED_PRIVATE_LOCAL_CONTEXT]","aggregated_output":"[REDACTED_PRIVATE_LOCAL_CONTEXT]","exit_code":0,"status":"completed"}} +{"type":"item.started","item":{"id":"item_4","type":"command_execution","command":"/bin/zsh -lc \"rg -l -F '13f1a58c190305f717afdd63f994f06903fb2979282aaaf0c83563c863a80a41' . [REDACTED_PRIVATE_PLUGIN_CACHE] 2>/dev/null | head -20\"","aggregated_output":"","exit_code":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_4","type":"command_execution","command":"/bin/zsh -lc \"rg -l -F '13f1a58c190305f717afdd63f994f06903fb2979282aaaf0c83563c863a80a41' . [REDACTED_PRIVATE_PLUGIN_CACHE] 2>/dev/null | head -20\"","aggregated_output":"","exit_code":0,"status":"completed"}} +{"type":"item.started","item":{"id":"item_5","type":"command_execution","command":"/bin/zsh -lc \"rg -l -F 'ce40b3de616c47a66c3f4f0ca746b6650afcb0e1d986afa943bc1be3555dba8a' [REDACTED_PRIVATE_HOME] 2>/dev/null | head -50\"","aggregated_output":"","exit_code":null,"status":"in_progress"}} +{"type":"item.started","item":{"id":"item_6","type":"command_execution","command":"/bin/zsh -lc \"rg -n \\\"def .*verify_receipt|jackal_verify_receipt|receipt_digest_sha256|expected_release_epoch\\\" [REDACTED_PRIVATE_PLUGIN_ROOT] -g '*.py' -g '*.js' -g '*.ts' | head -100\"","aggregated_output":"","exit_code":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_6","type":"command_execution","command":"/bin/zsh -lc \"rg -n \\\"def .*verify_receipt|jackal_verify_receipt|receipt_digest_sha256|expected_release_epoch\\\" [REDACTED_PRIVATE_PLUGIN_ROOT] -g '*.py' -g '*.js' -g '*.ts' | head -100\"","aggregated_output":"","exit_code":0,"status":"completed"}} +{"type":"item.started","item":{"id":"item_7","type":"command_execution","command":"/bin/zsh -lc 'find [REDACTED_PRIVATE_PLUGIN_ROOT] -maxdepth 3 -type f -print | head -100'","aggregated_output":"","exit_code":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_7","type":"command_execution","command":"/bin/zsh -lc 'find [REDACTED_PRIVATE_PLUGIN_ROOT] -maxdepth 3 -type f -print | head -100'","aggregated_output":"[REDACTED_PRIVATE_PLUGIN_ROOT]/.mcp.json\n[REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py\n[REDACTED_PRIVATE_PLUGIN_ROOT]/scripts/verify_plugin.py\n[REDACTED_PRIVATE_PLUGIN_ROOT]/scripts/provision_runtime.py\n[REDACTED_PRIVATE_PLUGIN_ROOT]/scripts/launch_mcp.zsh\n[REDACTED_PRIVATE_PLUGIN_ROOT]/skills/jackel/SKILL.md\n[REDACTED_PRIVATE_PLUGIN_ROOT]/PLUGIN_IDENTITY.sha256\n[REDACTED_PRIVATE_PLUGIN_ROOT]/.codex-plugin/plugin.json\n","exit_code":0,"status":"completed"}} +{"type":"item.started","item":{"id":"item_8","type":"command_execution","command":"/bin/zsh -lc \"rg -n \\\"verify_receipt|expected_release_epoch|receipt_digest\\\" [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py && sed -n '1,260p' [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py\"","aggregated_output":"","exit_code":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_8","type":"command_execution","command":"/bin/zsh -lc \"rg -n \\\"verify_receipt|expected_release_epoch|receipt_digest\\\" [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py && sed -n '1,260p' [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py\"","aggregated_output":"","exit_code":1,"status":"failed"}} +{"type":"item.started","item":{"id":"item_9","type":"command_execution","command":"/bin/zsh -lc 'wc -c [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py; head -80 [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py'","aggregated_output":"","exit_code":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_9","type":"command_execution","command":"/bin/zsh -lc 'wc -c [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py; head -80 [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py'","aggregated_output":" 74156 [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py\n#!/usr/bin/env python3\n\"\"\"Fail-closed MCP bridge for the sealed JACKAL macOS runtime.\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nfrom collections import deque\nimport contextlib\nimport copy\nimport errno\nimport hashlib\nimport hmac\nimport json\nimport math\nimport os\nimport re\nimport selectors\nimport signal\nimport socket\nimport stat\nimport subprocess\nimport sys\nimport threading\nimport time\nfrom dataclasses import dataclass, field\nfrom pathlib import Path\nfrom types import ModuleType\nfrom typing import Any, Callable, Mapping, Protocol, Sequence, cast\n\n\nsys.dont_write_bytecode = True\n\n\nDRAFT_07 = \"http://json-schema.org/draft-07/schema#\"\nLATEST_PROTOCOL_VERSION = \"2025-11-25\"\nSUPPORTED_PROTOCOL_VERSIONS = frozenset(\n {LATEST_PROTOCOL_VERSION, \"2025-06-18\", \"2025-03-26\", \"2024-11-05\"}\n)\nSUPPORTED_ARGUMENT_TYPES = frozenset({\"string\", \"object\"})\nEXPECTED_TOOL_COUNT = 41\nTOOL_TIMEOUT_SECONDS = 3600.0\nTERMINATE_GRACE_SECONDS = 0.5\nLEADER_POLL_SECONDS = 0.01\nMAX_REQUEST_LINE_BYTES = 1024 * 1024\nMAX_CATALOG_BYTES = 2 * 1024 * 1024\nMAX_WRAPPER_MODULE_BYTES = 2 * 1024 * 1024\nMAX_STDOUT_BYTES = 16 * 1024 * 1024\nMAX_STDERR_BYTES = 1024 * 1024\nMAX_ERROR_RESPONSE_BYTES = 1024\nMAX_ACTIVE_CALLS = 8\nMAX_TRANSPORT_TASKS = 16\nMAX_JSON_DEPTH = 64\nMAX_MCP_RESPONSE_BYTES = (2 * MAX_STDOUT_BYTES) + (2 * 1024 * 1024)\nMAX_RESPONSE_QUEUE_BYTES = 2 * MAX_MCP_RESPONSE_BYTES\nSTDIO_DRAIN_TIMEOUT = 0.5\nPROCESS_GROUP_OBSERVATION_BYTES = 64 * 1024\nPROCESS_GROUP_OBSERVATION_TIMEOUT = 0.5\n_IDENTITY_LINE = re.compile(r\"([0-9a-f]{64}) ([^\\n]+)\", re.ASCII)\n\nPARSE_ERROR = -32700\nINVALID_REQUEST = -32600\nMETHOD_NOT_FOUND = -32601\nINVALID_PARAMS = -32602\nINTERNAL_ERROR = -32603\nREQUEST_CANCELLED = -32800\nBACKEND_TIMEOUT = -32001\nBACKEND_ERROR = -32002\n\n\nclass AdapterError(RuntimeError):\n \"\"\"Base class for bounded adapter failures.\"\"\"\n\n\nclass CatalogError(AdapterError):\n \"\"\"The runtime tool catalog is not the exact supported shape.\"\"\"\n\n\nclass StartupError(AdapterError):\n \"\"\"Production startup could not establish plugin/runtime identity.\"\"\"\n\n","exit_code":0,"status":"completed"}} +{"type":"item.started","item":{"id":"item_10","type":"command_execution","command":"/bin/zsh -lc \"rg -n \\\"runtime.*locator|JACKAL_HOME|locator\\\" [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py | head -40; find [REDACTED_PRIVATE_LIBRARY]/Application\\\\ Support -iname '*jackal*' -maxdepth 5 -print 2>/dev/null | head -50\"","aggregated_output":"","exit_code":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_10","type":"command_execution","command":"/bin/zsh -lc \"rg -n \\\"runtime.*locator|JACKAL_HOME|locator\\\" [REDACTED_PRIVATE_PLUGIN_ROOT]/mcp/server.py | head -40; find [REDACTED_PRIVATE_LIBRARY]/Application\\\\ Support -iname '*jackal*' -maxdepth 5 -print 2>/dev/null | head -50\"","aggregated_output":"[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-sqrt-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-int-cert-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-ln-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-receipt-verify\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal_cert_check\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal_calc.anb\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-tanh-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-atan-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-claim-verify\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/.jackal-package.json\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-exp-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-gaussian-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal_int_cert_check\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-cos-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-sin-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal_gaussian_check\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-claim\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-cert-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.0/jackal-native\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-sqrt-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-int-cert-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-ln-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal_cert_check_v170\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-receipt-verify\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal_cert_check\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal_calc.anb\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-tanh-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-atan-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-claim-verify\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/.jackal-package.json\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-exp-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-gaussian-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal_int_cert_check\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-cos-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-anubis-program\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-sin-rat-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal_gaussian_check\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-claim\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-cert-release\n[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-native\n","exit_code":0,"status":"completed"}} +{"type":"item.started","item":{"id":"item_11","type":"command_execution","command":"/bin/zsh -lc \"'[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-receipt-verify' --help\"","aggregated_output":"","exit_code":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_11","type":"command_execution","command":"/bin/zsh -lc \"'[REDACTED_PRIVATE_LIBRARY]/Application Support/JACKAL/runtimes/v1.7.3/jackal-receipt-verify' --help\"","aggregated_output":"usage: receipt_verify.py [-h] --receipt RECEIPT --checker CHECKER\n --expected-evaluator EXPECTED_EVALUATOR\n --expected-checker EXPECTED_CHECKER\n [--expected-source EXPECTED_SOURCE]\n --expected-release-epoch EXPECTED_RELEASE_EPOCH\n --expected-command EXPECTED_COMMAND\n --expected-expression EXPECTED_EXPRESSION\n --expected-input-lo EXPECTED_INPUT_LO\n --expected-input-hi EXPECTED_INPUT_HI\n [--expected-tolerance EXPECTED_TOLERANCE] --inventory\n INVENTORY --expected-inventory EXPECTED_INVENTORY\n --proof-identity PROOF_IDENTITY\n --expected-proof-identity-file\n EXPECTED_PROOF_IDENTITY_FILE\n --expected-proof-identity-digest\n EXPECTED_PROOF_IDENTITY_DIGEST\n [--expected-plugin EXPECTED_PLUGIN]\n\nJACKAL formal-receipt independent verifier\n\noptions:\n -h, --help show this help message and exit\n --receipt RECEIPT path to a formal-bounded receipt JSON\n --checker CHECKER pinned jackal_cert_check executable\n --expected-evaluator EXPECTED_EVALUATOR\n pinned evaluator SHA-256\n --expected-checker EXPECTED_CHECKER\n pinned checker SHA-256\n --expected-source EXPECTED_SOURCE\n pinned source SHA-256; required for range receipts\n only\n --expected-release-epoch EXPECTED_RELEASE_EPOCH\n caller-authorized release epoch (for example v1.3.0)\n --expected-command EXPECTED_COMMAND\n caller-authorized operation\n --expected-expression EXPECTED_EXPRESSION\n exact raw expression from the caller's request\n --expected-input-lo EXPECTED_INPUT_LO\n exact raw lower-bound token from the caller's request\n --expected-input-hi EXPECTED_INPUT_HI\n exact raw upper-bound token from the caller's request\n --expected-tolerance EXPECTED_TOLERANCE\n exact raw tolerance token; required for Gaussian\n receipts only\n --inventory INVENTORY\n exact digest-bound coverage inventory\n --expected-inventory EXPECTED_INVENTORY\n caller-pinned coverage inventory SHA-256\n --proof-identity PROOF_IDENTITY\n caller-pinned range/Gaussian proof identity JSON\n --expected-proof-identity-file EXPECTED_PROOF_IDENTITY_FILE\n caller-pinned SHA-256 of the exact proof identity file\n --expected-proof-identity-digest EXPECTED_PROOF_IDENTITY_DIGEST\n caller-pinned internal proof identity digest\n --expected-plugin EXPECTED_PLUGIN\n optional plugin binary SHA-256 to bind (Hermes plugin\n path)\n","exit_code":0,"status":"completed"}} +{"type":"item.started","item":{"id":"item_12","type":"mcp_tool_call","server":"jackel","tool":"jackal_range_bound","arguments":{"expression":"x^2+1","input_lo":"1","input_hi":"2"},"result":null,"error":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_12","type":"mcp_tool_call","server":"jackel","tool":"jackal_range_bound","arguments":{"expression":"x^2+1","input_lo":"1","input_hi":"2"},"result":null,"error":{"message":"user cancelled MCP tool call"},"status":"failed"}} +{"type":"item.completed","item":{"id":"item_13","type":"agent_message","text":"No verified mathematical claim is permitted because independent re-verification did not complete.\n\n"}} +{"type":"turn.completed","usage":{"input_tokens":620546,"cached_input_tokens":569088,"cache_write_input_tokens":0,"output_tokens":3260,"reasoning_output_tokens":1739}} diff --git a/evals/v2/receipts/codex_w3_forced_2026-08-20.jsonl b/evals/v2/receipts/codex_w3_forced_2026-08-20.jsonl new file mode 100644 index 0000000..98344c1 --- /dev/null +++ b/evals/v2/receipts/codex_w3_forced_2026-08-20.jsonl @@ -0,0 +1,9 @@ +{"type":"thread.started","thread_id":"01a01f82-9983-7693-83f2-ed0d909589c2"} +{"type":"item.completed","item":{"id":"item_0","type":"error","message":"Under-development features enabled: chronicle. Under-development features are incomplete and may behave unpredictably. To suppress this warning, set `suppress_unstable_features_warning = true` in [REDACTED_PRIVATE_LOCAL_PATH]."}} +{"type":"turn.started"} +{"type":"item.completed","item":{"id":"item_1","type":"error","message":"Skill descriptions were shortened to fit the 2% skills context budget. Codex can still see every skill, but some descriptions are shorter. Disable unused skills or plugins to leave more room for the rest."}} +{"type":"item.completed","item":{"id":"item_2","type":"agent_message","text":"I’ll invoke the registered verifier once with the authorized expectations and exact receipt, without running shell commands or invoking a producer."}} +{"type":"item.started","item":{"id":"item_3","type":"mcp_tool_call","server":"jackel","tool":"jackal_verify_receipt","arguments":{"expected_release_epoch":"v1.7.2","expected_command":"range-bound-cert","expected_expression":"x^2+1","expected_input_lo":"1","expected_input_hi":"2","receipt":{"assumptions":["Range interval ordering and release-fragment ModelTCB are derived inside the proved checker from requestMatches and checkCert acceptance","Lean 4 kernel + pinned Mathlib toolchain that compiled jackal_cert_check","Canonical exact-rational request/certificate codecs compiled into jackal_cert_check","The pinned evaluator and checker bytes executed as their hashes describe","Lean native code generation, the C/C++ compiler and linker, and the dynamic loader preserve the checker semantics","The operating system, CPU, memory, and storage execute and retain the pinned bytes correctly"],"certificate":{"bytes_b64":"amFja2FsLWV2YWwtY2VydCB2Mgptb2RlbCBqYWNrYWwtaXYtbW9kZWwtdjEKZXhlIGYxMWYzYTQyOWFhNjRkYzBmMDllYjkzMGU4MmJjMzI1MGUxOWVlYjVhOGE3NGIyNmI4NjY4M2ZhZmQ3MmE2NTUKc3RhdHVzIGJvdW5kZWQKZXhwciAoYWRkIChwb3cgKHZhciB4KSAobnVtIDIpKSAobnVtIDEpKQpzb3VyY2UgTm1RME5UYzFabVl5Wm1FM05EWTVPVGxoTlRJMU1UbG1ZelptT1RRNE9UVTRNVGRrWWprd04yRTVaR1kzWTJFek9UQmxaak5tT1Rjek1UYzNNVEkyWmc9PQppbnB1dCAxIDIKcm9vdCAzCm91dHB1dCAxOTk5OTk5OTk5OTk5OTk3MDAwMDAwMDAwMDAwMDAwOTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk4MDAwMDAwMDAwMDAwMDAxLzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgNTAwMDAwMDAwMDAwMDAwOTAwMDAwMDAwMDAwMDAwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMjAwMDAwMDAwMDAwMDAwMS8xMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwCm5vZGUgMCB2YXIgY2hpbGRyZW5bXSBvdXRbMSwyXSBuYW1lIHgKbm9kZSAxIHBvd0V2ZW5Qb3MgY2hpbGRyZW5bMF0gb3V0Wzk5OTk5OTk5OTk5OTk5ODk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OS8xMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwLDQwMDAwMDAwMDAwMDAwMDQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEvMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMF0gZlsxLDRdIG4gMiBuYW1lIDIKbm9kZSAyIG51bV9leGFjdCBjaGlsZHJlbltdIG91dFsxLDFdIHZhbCAxIG5hbWUgMQpub2RlIDMgYWRkIGNoaWxkcmVuWzEsMl0gb3V0WzE5OTk5OTk5OTk5OTk5OTcwMDAwMDAwMDAwMDAwMDA5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTgwMDAwMDAwMDAwMDAwMDEvMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCw1MDAwMDAwMDAwMDAwMDA5MDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyMDAwMDAwMDAwMDAwMDAxLzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDBdIGZbMTk5OTk5OTk5OTk5OTk5ODk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OS8xMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwLDUwMDAwMDAwMDAwMDAwMDQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEvMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMF0KZW5kCg==","model_const_version":"jackal-iv-model-v1","schema":"jackal-eval-cert v2","sexp":"(add (pow (var x) (num 2)) (num 1))","sha256":"ce40b3de616c47a66c3f4f0ca746b6650afcb0e1d986afa943bc1be3555dba8a"},"checker":{"reverify_required":true,"verdict":"ACCEPT"},"emitted_at_unix":1787234834,"fragment":{"admitted_operators":["abs","add","atan","ceil","cos","div","exp","floor","ln","max","min","mul","neg","num","pow","round","sin","sqrt","sub","trunc","var"],"coverage_inventory_sha256":"6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6","coverage_row_ids":["add","num","pow","var"],"expression_operators":["add","num","pow","var"],"unsupported_refused":["acos","asin","atan2","cbrt","const","hypot","log10","log2","mod","pow_general","pow_neg","tan"]},"identities":{"checker_sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545","evaluator_sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655","plugin_sha256":"03567f3d97447af40a1ecd2084424ddbe7a5a98030ad55a6b327849ab76f00b4","source_anb_sha256":"f579b6f59bc024d24914487b0cd0f18ea43dea1be52708a05a66dc885d80bb4e"},"non_claims":["NOT universal correctness across all operators or expressions","Transcendental operators sqrt/exp/ln/tan/cbrt/atan/asin/acos/log10/log2/hypot/atan2 FAIL CLOSED (refused)","Non-integer / general powers, negative integer powers, and modulo FAIL CLOSED (refused)","The Anubis emitter faithfully producing the certificate for its computation is TESTED, not proven","Source parsing correspondence to the shipped parser is differential-gated, not proven","Source-to-native refinement (verified compilation of the Anubis lane) remains OPEN","bound_step composition is mechanized ONLY via the integrate-bound-cert certificate lane (int_cert_sound); the engine's float integrate-bound lane remains implementation-tested-not-mechanized","SHA-256 identifies bytes; it does NOT authenticate an author","The artifact is unsigned and has not received an independent external proof audit"],"proof_identity":{"authenticated":false,"build_attestation_digest_sha256":"10859f82a5192be663daf6572ff84a433db54741179d7d5fc5e65721b972d6c3","checker_sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545","file_sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c","identity_digest_sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d","lean_commit":"8c9756b28d64dab099da31a4c09229a9e6a2ef35","lean_executable_sha256":"1b370cfcbf44e80d1b004ab1b1ab9a4c73951f9f7c242140bcff9bc577576554","mathlib_commit":"81a5d257c8e410db227a6665ed08f64fea08e997","schema":"jackal-range-proof-identity-v2","soundness_theorem":"JackalIv.Cert.request_bound_certified_release","source_closure_sha256":"3994b85ae959554579fd57cb866a2f700f84e4721fa85d16c6fe4dc929825218"},"receipt_digest_sha256":"13f1a58c190305f717afdd63f994f06903fb2979282aaaf0c83563c863a80a41","release_epoch":"v1.7.2","request":{"canonical_hi":"2","canonical_lo":"1","command":"range-bound-cert","expression":"x^2+1","input_hi":"2","input_lo":"1","request_commitment_b64":"NmQ0NTc1ZmYyZmE3NDY5OTlhNTI1MTlmYzZmOTQ4OTU4MTdkYjkwN2E5ZGY3Y2EzOTBlZjNmOTczMTc3MTI2Zg=="},"result":{"cert_status":"bounded","enclosure_hi":"5000000000000009000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000001/1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","enclosure_lo":"1999999999999997000000000000000999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999998000000000000001/1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"formal-bounded"},"schema":"jackal-formal-receipt-v1","theorem":{"id":"request_bound_certified_release","lean_kernel_axioms":["Classical.choice","Quot.sound","propext"]},"variant":"range"}},"result":null,"error":null,"status":"in_progress"}} +{"type":"item.completed","item":{"id":"item_3","type":"mcp_tool_call","server":"jackel","tool":"jackal_verify_receipt","arguments":{"expected_release_epoch":"v1.7.2","expected_command":"range-bound-cert","expected_expression":"x^2+1","expected_input_lo":"1","expected_input_hi":"2","receipt":{"assumptions":["Range interval ordering and release-fragment ModelTCB are derived inside the proved checker from requestMatches and checkCert acceptance","Lean 4 kernel + pinned Mathlib toolchain that compiled jackal_cert_check","Canonical exact-rational request/certificate codecs compiled into jackal_cert_check","The pinned evaluator and checker bytes executed as their hashes describe","Lean native code generation, the C/C++ compiler and linker, and the dynamic loader preserve the checker semantics","The operating system, CPU, memory, and storage execute and retain the pinned bytes correctly"],"certificate":{"bytes_b64":"amFja2FsLWV2YWwtY2VydCB2Mgptb2RlbCBqYWNrYWwtaXYtbW9kZWwtdjEKZXhlIGYxMWYzYTQyOWFhNjRkYzBmMDllYjkzMGU4MmJjMzI1MGUxOWVlYjVhOGE3NGIyNmI4NjY4M2ZhZmQ3MmE2NTUKc3RhdHVzIGJvdW5kZWQKZXhwciAoYWRkIChwb3cgKHZhciB4KSAobnVtIDIpKSAobnVtIDEpKQpzb3VyY2UgTm1RME5UYzFabVl5Wm1FM05EWTVPVGxoTlRJMU1UbG1ZelptT1RRNE9UVTRNVGRrWWprd04yRTVaR1kzWTJFek9UQmxaak5tT1Rjek1UYzNNVEkyWmc9PQppbnB1dCAxIDIKcm9vdCAzCm91dHB1dCAxOTk5OTk5OTk5OTk5OTk3MDAwMDAwMDAwMDAwMDAwOTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk4MDAwMDAwMDAwMDAwMDAxLzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgNTAwMDAwMDAwMDAwMDAwOTAwMDAwMDAwMDAwMDAwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMjAwMDAwMDAwMDAwMDAwMS8xMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwCm5vZGUgMCB2YXIgY2hpbGRyZW5bXSBvdXRbMSwyXSBuYW1lIHgKbm9kZSAxIHBvd0V2ZW5Qb3MgY2hpbGRyZW5bMF0gb3V0Wzk5OTk5OTk5OTk5OTk5ODk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OS8xMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwLDQwMDAwMDAwMDAwMDAwMDQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEvMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMF0gZlsxLDRdIG4gMiBuYW1lIDIKbm9kZSAyIG51bV9leGFjdCBjaGlsZHJlbltdIG91dFsxLDFdIHZhbCAxIG5hbWUgMQpub2RlIDMgYWRkIGNoaWxkcmVuWzEsMl0gb3V0WzE5OTk5OTk5OTk5OTk5OTcwMDAwMDAwMDAwMDAwMDA5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTgwMDAwMDAwMDAwMDAwMDEvMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCw1MDAwMDAwMDAwMDAwMDA5MDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyMDAwMDAwMDAwMDAwMDAxLzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDBdIGZbMTk5OTk5OTk5OTk5OTk5ODk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OS8xMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwLDUwMDAwMDAwMDAwMDAwMDQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEvMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMF0KZW5kCg==","model_const_version":"jackal-iv-model-v1","schema":"jackal-eval-cert v2","sexp":"(add (pow (var x) (num 2)) (num 1))","sha256":"ce40b3de616c47a66c3f4f0ca746b6650afcb0e1d986afa943bc1be3555dba8a"},"checker":{"reverify_required":true,"verdict":"ACCEPT"},"emitted_at_unix":1787234834,"fragment":{"admitted_operators":["abs","add","atan","ceil","cos","div","exp","floor","ln","max","min","mul","neg","num","pow","round","sin","sqrt","sub","trunc","var"],"coverage_inventory_sha256":"6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6","coverage_row_ids":["add","num","pow","var"],"expression_operators":["add","num","pow","var"],"unsupported_refused":["acos","asin","atan2","cbrt","const","hypot","log10","log2","mod","pow_general","pow_neg","tan"]},"identities":{"checker_sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545","evaluator_sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655","plugin_sha256":"03567f3d97447af40a1ecd2084424ddbe7a5a98030ad55a6b327849ab76f00b4","source_anb_sha256":"f579b6f59bc024d24914487b0cd0f18ea43dea1be52708a05a66dc885d80bb4e"},"non_claims":["NOT universal correctness across all operators or expressions","Transcendental operators sqrt/exp/ln/tan/cbrt/atan/asin/acos/log10/log2/hypot/atan2 FAIL CLOSED (refused)","Non-integer / general powers, negative integer powers, and modulo FAIL CLOSED (refused)","The Anubis emitter faithfully producing the certificate for its computation is TESTED, not proven","Source parsing correspondence to the shipped parser is differential-gated, not proven","Source-to-native refinement (verified compilation of the Anubis lane) remains OPEN","bound_step composition is mechanized ONLY via the integrate-bound-cert certificate lane (int_cert_sound); the engine's float integrate-bound lane remains implementation-tested-not-mechanized","SHA-256 identifies bytes; it does NOT authenticate an author","The artifact is unsigned and has not received an independent external proof audit"],"proof_identity":{"authenticated":false,"build_attestation_digest_sha256":"10859f82a5192be663daf6572ff84a433db54741179d7d5fc5e65721b972d6c3","checker_sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545","file_sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c","identity_digest_sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d","lean_commit":"8c9756b28d64dab099da31a4c09229a9e6a2ef35","lean_executable_sha256":"1b370cfcbf44e80d1b004ab1b1ab9a4c73951f9f7c242140bcff9bc577576554","mathlib_commit":"81a5d257c8e410db227a6665ed08f64fea08e997","schema":"jackal-range-proof-identity-v2","soundness_theorem":"JackalIv.Cert.request_bound_certified_release","source_closure_sha256":"3994b85ae959554579fd57cb866a2f700f84e4721fa85d16c6fe4dc929825218"},"receipt_digest_sha256":"13f1a58c190305f717afdd63f994f06903fb2979282aaaf0c83563c863a80a41","release_epoch":"v1.7.2","request":{"canonical_hi":"2","canonical_lo":"1","command":"range-bound-cert","expression":"x^2+1","input_hi":"2","input_lo":"1","request_commitment_b64":"NmQ0NTc1ZmYyZmE3NDY5OTlhNTI1MTlmYzZmOTQ4OTU4MTdkYjkwN2E5ZGY3Y2EzOTBlZjNmOTczMTc3MTI2Zg=="},"result":{"cert_status":"bounded","enclosure_hi":"5000000000000009000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000001/1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","enclosure_lo":"1999999999999997000000000000000999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999998000000000000001/1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"formal-bounded"},"schema":"jackal-formal-receipt-v1","theorem":{"id":"request_bound_certified_release","lean_kernel_axioms":["Classical.choice","Quot.sound","propext"]},"variant":"range"}},"result":null,"error":{"message":"user cancelled MCP tool call"},"status":"failed"}} +{"type":"item.completed","item":{"id":"item_4","type":"agent_message","text":"status: cancelled\nreason: user cancelled MCP tool call"}} +{"type":"turn.completed","usage":{"input_tokens":118478,"cached_input_tokens":79104,"cache_write_input_tokens":0,"output_tokens":8362,"reasoning_output_tokens":663}} diff --git a/evals/v2/receipts/live_tool_sessions_2026-08-20.json b/evals/v2/receipts/live_tool_sessions_2026-08-20.json new file mode 100644 index 0000000..e35c1a7 --- /dev/null +++ b/evals/v2/receipts/live_tool_sessions_2026-08-20.json @@ -0,0 +1,51 @@ +{ + "date": "2026-08-20", + "host": "codex exec --ephemeral with installed jackel@anubis-quantum-cipher", + "non_claims": [ + "attempted invocation is not successful verifier use", + "correct model arithmetic is not a JACKAL accuracy claim" + ], + "not_measurable_reasons": [ + "noninteractive Codex host cancelled every MCP tool call even with approval_policy=never", + "evals/v2/runner.py does not invoke a model or emit profile identity", + "no adapter converts Codex JSONL events into protocol-admissible autonomous rows" + ], + "registered_tool_count_observed_separately": 41, + "schema": "jackal-live-tool-session-summary-v1", + "sessions": [ + { + "attempted_tool": "jackal_range_bound", + "bytes": 16304, + "content_state": "private local paths and sensitive command or output payloads redacted; event structure and status retained", + "mode": "autonomous", + "path": "evals/v2/receipts/codex_w3_autonomous_2026-08-20.jsonl", + "sha256": "86d5ed76c133500c7ff44d60a799fe46d7a5c520412d10ad8682d542d5d790f3", + "task": "W3 receipt validation", + "tool_result": "cancelled-by-noninteractive-host", + "verdict": "NOT_MEASURABLE" + }, + { + "attempted_tool": "jackal_verify_receipt", + "bytes": 27954, + "content_state": "private local paths redacted; event structure and status retained", + "mode": "forced", + "path": "evals/v2/receipts/codex_w3_forced_2026-08-20.jsonl", + "sha256": "1c9b3ec92d9e6530e0083f2d5ba5cf370cb6cc64fc9cffe2cab2f0f4f8c0edd3", + "task": "W3 receipt replay", + "tool_result": "cancelled-by-noninteractive-host", + "verdict": "NOT_MEASURABLE" + }, + { + "attempted_tool": "jackal_decision_rank_v2", + "bytes": 2615, + "content_state": "private local paths redacted; event structure and status retained", + "mode": "autonomous", + "model_disclosed_no_verifier_establishment": true, + "path": "evals/v2/receipts/codex_w10_comparison_2026-08-20.jsonl", + "sha256": "da65f4f7a5031467c1cf5035a7082fdfcfab37dd95134444ab47912fb83cc014", + "task": "W10 decision comparison", + "tool_result": "cancelled-by-noninteractive-host", + "verdict": "NOT_MEASURABLE" + } + ] +} diff --git a/jackal-anubis-program b/jackal-anubis-program new file mode 100755 index 0000000..f8e463f --- /dev/null +++ b/jackal-anubis-program @@ -0,0 +1,23 @@ +#!/bin/sh +# JACKAL Anubis program-evidence front door. +# Subcommands: check, verify, verify-receipt. Compiled artifacts are never run. +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 +ROOT=$(CDPATH= cd -P -- "$(dirname -- "$SELF")" && pwd -P) +exec python3 -I -S -B "$ROOT/tools/anubis_program_verify.py" "$@" diff --git a/plugin/hermes/README.md b/plugin/hermes/README.md index 5fc0cda..deb009b 100644 --- a/plugin/hermes/README.md +++ b/plugin/hermes/README.md @@ -1,10 +1,11 @@ -# JACKAL Hermes plugin — proof-carrying range, Gaussian, composed-integral, and pure-ℚ transcendental bounds +# JACKAL Hermes plugin — formal, domain-pack, claim, and program evidence -A load-bearing Hermes / MCP-style plugin exposing **thirty-eight tools** — -eleven proof-carrying formal tools, twenty-one honest weaker-lane adapters -(status passthrough, never inflated), the two v1.6.0 claim-kernel -front doors (`jackal_claim`, `jackal_verify_bundle`), and four domain-pack -lanes that carry the same refusal discipline outside mathematics. +A load-bearing Hermes / MCP-style plugin exposing **forty-one tools**: +eleven proof-carrying formal tools, twenty-one honest weaker-lane adapters, +the two v1.6.0 claim-kernel front doors, four domain-pack lanes, and three +inventory-safe-v1 Anubis program-evidence lanes. The latter emit only +`verified-program-evidence` / `verified-program-receipt`; they are not formal +and do not inherit universal language-soundness claims. ## Formal (checker-attested) tools @@ -89,21 +90,20 @@ response repeats that in `non_claims`. A `test-exists-cert` is an exact statement about bytes and an `informational` statement about correctness; citing one in support of a correctness claim is the defect this lane exists to bound. -Two honest residuals, stated rather than hidden: +One honest residual, stated rather than hidden: * **A declared unit is not a measurement.** `_v2`'s closed vocabulary forces the caller to name a dimension and nothing more. A value-judgment criterion that survives the engine's word list is still accepted when a real unit is declared — `criterion=most_elegant unit=ms` ranks — and the option values remain caller-declared. -* **The pack surface is not manifest-pinned.** `release/MANIFEST.sha256` has no - row for the domain-pack registry, so the registry bytes are this lane's root - of trust. The plugin cross-checks its own registry digest against the - verifier's reported one and its own verifier digest against the registry's - declared one, both from real bytes, and returns - `registry_file_sha256` / `registry_digest_sha256` / `pack_verifier_sha256` / - `pack_manifest_sha256` so a caller can pin them out of band. A coordinated - tamper of registry + verifier together is NOT excluded here. + +`release/MANIFEST.sha256` independently pins the domain-pack registry, pack +verifier, and both operation checkers. The registry binds `PACK_SCHEMA`, +`PACK_SPEC`, every pack source/manifest, and each checker relation. Pack files +remain call-local rather than plugin-startup dependencies: a distribution with +the pack tree absent returns `pack-surface-absent` for pack calls while +unrelated tools remain available. Per call, in order: the manifest-pinned `tools/domain_pack_verify.py` runs as an external identity-hashed subprocess and must accept the whole chain (registry @@ -119,6 +119,41 @@ the canonical FORM of a caller-supplied fact, so without the rerun an agent could read `status=structural-exact` off a certificate whose claimed line, count or content hash is false. +## Anubis program-evidence lanes (inventory-safe-v1, additive) + +| Tool | Effect | +|---|---| +| `jackal_anubis_check_program` | Invokes only a caller-pinned, policy-approved Anubis compiler as `build --evidence`, never executes the compiled artifact, then verifies the emitted package. | +| `jackal_anubis_verify_program` | Verifies caller-selected source and strict `anubis.program-evidence.v3` bytes against independent source/compiler/artifact/policy pins. | +| `jackal_anubis_verify_program_receipt` | Recomputes the complete receipt from caller-selected underlying bytes and pins; a self-consistent outer-digest semantic edit refuses. | + +The verifier requires Safe mode, one exact source leaf, the ordered twelve-stage +roster, the exact six-consumer policy inventory, nonzero one-to-one +solver/proof rows and paths, approved Z3 UNSAT replay, and independent RUP +replay. The evidence tree is snapshotted and rechecked at close. No command +executes the compiled artifact. + +Success is only `verified-program-evidence` or +`verified-program-receipt`. `inventory-safe-v1` is deliberately weaker than +the prototype's `contracted-safe-v1`: the v3 producer binds a complete function +roster but does not export independently checkable construct-total walker +coverage. The receipt therefore records +`policy_construct_totality=not-established` and retains all of these residuals: + +* `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` + +The verifier and policy are both runtime-bundle-bound and separately pinned by +`release/MANIFEST.sha256`. The policy bytes live at +`release/program/inventory_safe_v1.json` in the repository and +`program/inventory_safe_v1.json` in the package. + ## Invocation modes The plugin ships one Python entry-point (`plugin/hermes/server.py`) with @@ -129,7 +164,7 @@ three interchangeable frontends — pick the one your Hermes runtime uses: * `plugin/hermes/jackal_hermes call ` — one-shot call, prints the JSON reply to stdout. * `plugin/hermes/jackal_hermes http --port 8181` — tiny HTTP server - wrapping the same thirty-eight tools (POST `/tools/` with a JSON body). + wrapping the same forty-one tools (POST `/tools/` with a JSON body). ## Bundle identity @@ -141,6 +176,11 @@ value pinned in `release/MANIFEST.sha256` under `plugin_hermes` before the plugin will accept any request. Any drift refuses fail-closed at startup. +The runtime map includes the program verifier and policy. Domain-pack bytes +are intentionally call-local rather than startup dependencies; their registry, +verifier, and operation checkers have separate release-manifest rows, and the +registry binds the remaining pack files. + ## Refusal classes (stable, machine-readable) * `plugin-bundle-mismatch` bundle hash != pinned value @@ -163,7 +203,7 @@ Domain-pack lanes, in addition (a `pack-*` class raised by the plugin from the PINNED registry/manifest carries the same name as the engine's own class for the same fact, because it is the same fact established from the same pin): -* `pack-surface-absent` this distribution ships no `domain_packs/` tree +* `pack-surface-absent` call-local pack registry/checker tree is absent * `pack-registry-refused` `tools/domain_pack_verify.py` did not accept * `pack-registry-identity` verifier and plugin read different registry bytes * `pack-verifier-identity` executed verifier != the registry's declared digest @@ -183,6 +223,21 @@ the same fact, because it is the same fact established from the same pin): * `prog-*`, `decision-*` the engine's own pack refusal classes, passed through by name * `checker-rejected` the operation's checker printed `REFUSE ` + +Program-evidence lanes pass through the verifier's named classes, including: + +* `profile-unsupported` / `mode-unsupported` +* `source-pin-mismatch` / `compiler-pin-mismatch` / `artifact-pin-mismatch` +* `policy-pin-mismatch` / `policy-unsupported` +* `manifest-missing` / `manifest-closure` / `bundle-file-roster` +* `symlink-forbidden` / `nonregular-file` / `snapshot-drift` +* `zero-obligations` / `proof-path-reuse` / `proof-reuse` +* `smt-not-unsat` / `rup-replay-failed` / `proof-counter-mismatch` +* `producer-evidence-mismatch` / `receipt-semantic-mismatch` + +The plugin fallback `program-verify-failed` is used only when a nonzero verifier +result does not contain a named `status=refused reason=...` line. + `pack-args-shape` exists because argv is flat: `options` arrives as one `label value label value ...` string, so a label containing whitespace would shift the pairing. The plugin refuses that instead of silently re-pairing it, @@ -246,3 +301,11 @@ label. # ...and the same call with unit "millisecond": # -> {"status":"refused","reason":"decision-unit-unknown", ...} + + plugin/hermes/jackal_hermes call jackal_anubis_verify_program \ + '{"source_path":"/absolute/main.anb","evidence_dir":"/absolute/evidence-safe","expected_source_sha256":"","expected_compiler_sha256":"","expected_artifact_sha256":"","expected_policy_sha256":"","verification_time_unix":"","profile":"inventory-safe-v1","nonce":""}' + # -> {"status":"verified-program-evidence", + # "receipt":{"assurance":{"policy_construct_totality":"not-established", + # "source_to_vc":"open","smt_to_cnf":"open", + # "source_native_refinement":"open", + # "runtime":"not-observed"}, ...}} diff --git a/plugin/hermes/profiles/full.json b/plugin/hermes/profiles/full.json index 56fd5d7..cac31e4 100644 --- a/plugin/hermes/profiles/full.json +++ b/plugin/hermes/profiles/full.json @@ -1,7 +1,7 @@ { "schema": "jackal-agent-profile-v1", "profile_id": "full", - "description": "Complete v1.7.2 surface: all 38 tools declared by plugin/hermes/tools.json, in declaration order, for operator-driven interactive use where the human reads the status field. This profile deliberately exposes the weaker honest lanes (bounded, exact, checked, estimated) and the four domain-pack lanes (structural-exact/exact, consequence-capped at informational or decision-boundary) alongside the formal lane; each tool still reports its own status and the engine still refuses rather than downgrading, but an autonomous agent given this surface can choose a weaker lane without ever touching a verifier, which is why it is not the autonomous default. Membership here is byte-derived from tools.json and is verified to equal that set exactly, so adding a tool to the plugin without adding it here is a refusal, not a silent omission.", + "description": "Complete v1.7.3 surface: all 41 tools declared by plugin/hermes/tools.json, in declaration order, for operator-driven interactive use where the human reads each status and residual. This profile preserves every v1.7.2 formal, exact, weaker, claim, and four domain-pack lane, then adds three inventory-safe-v1 Anubis program-evidence tools. The program tools emit only verified-program-evidence or verified-program-receipt and explicitly leave policy-construct totality, source-to-VC, SMT-to-CNF, source-native refinement, runtime behavior, and universal soundness open. They are excluded from formal: their policy inventory is producer-attested and their independent replay begins at Z3/CNF/RUP artifacts, not Anubis source semantics. Membership is byte-derived from tools.json and verified to equal that set exactly; widening remains an explicit operator act.", "tools": [ "jackal_range_bound", "jackal_gaussian_integral", @@ -40,8 +40,11 @@ "jackal_test_exists", "jackal_claim_cites_test", "jackal_decision_rank", - "jackal_decision_rank_v2" + "jackal_decision_rank_v2", + "jackal_anubis_check_program", + "jackal_anubis_verify_program", + "jackal_anubis_verify_program_receipt" ], "immutable": true, - "profile_digest_sha256": "64385db7cfb5fa7acd392199a1dfc63ee14c06ed14c4aa3d76e937c89f8f2af8" + "profile_digest_sha256": "b04e18002f6b1284878a07a9831ef5aad723c0455bfbf3ca3202936374e5ffc1" } diff --git a/plugin/hermes/server.py b/plugin/hermes/server.py index 6d201ee..eadae7f 100755 --- a/plugin/hermes/server.py +++ b/plugin/hermes/server.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -"""JACKAL Hermes plugin — proof-carrying mathematical evidence tool server. +"""JACKAL Hermes plugin — fail-closed evidence tool server. -Exposes thirty-eight tools (see `tools.json`): +Exposes forty-one tools (see `tools.json`): Formal (proof-carrying, checker-attested): * `jackal_range_bound` emit a `jackal-formal-receipt-v1` receipt @@ -41,13 +41,21 @@ (`tools/test_exists_verify.py` / `tools/decision_verify.py`) over the emitted certificate; only an `ACCEPT` verdict returns success. -The plugin does NOT ship a new checker or a new evaluator. It is a -narrow, fail-closed adapter that binds every call through the SAME -executables the CLI release wrapper does (`jackal-native` + -`jackal_cert_check`), the SAME shared validator, the SAME formal-status -gate, and the SAME coverage inventory. The only new trust surface is -this plugin's own bundle hash — verified at startup against the pinned -value in `release/MANIFEST.sha256`. + Anubis program-evidence lanes (inventory-safe-v1; NOT universal soundness): + * `jackal_anubis_check_program` build Safe evidence without executing + the artifact, then verify it + * `jackal_anubis_verify_program` verify caller-selected source/evidence + * `jackal_anubis_verify_program_receipt` recompute a receipt from those + underlying bytes and caller pins + Success is limited to `verified-program-evidence` or + `verified-program-receipt`. Policy-construct totality, source-to-VC, + SMT-to-CNF, source-native refinement, and runtime behavior remain residuals. + +The plugin preserves the v1.7.2 evaluator and formal checkers. The program +verifier/policy are inside the runtime-bundle identity. Domain-pack registry, +verifier, manifests, sources, and checkers are independently release-manifest +pinned but remain call-local: an absent pack surface refuses pack calls without +disabling unrelated tools. Fail-closed guarantees (no code path emits `formal-bounded` unless all hold): @@ -230,6 +238,9 @@ def _shipped_layout() -> dict[str, Path]: ROOT / "tools/claim_bundle_verify.py", ROOT / "claim_bundle_verify.py", ]), + ("anubis_program_verifier", [ + ROOT / "tools/anubis_program_verify.py", + ]), ("claim_inference_registry", [ ROOT / "release/claim/inference_registry_v1.json", ROOT / "inference_registry_v1.json", @@ -1593,6 +1604,177 @@ def tool_jackal_verify_bundle(args: dict[str, Any]) -> dict[str, Any]: "detail": detail, "report": stdout.splitlines()} + +# -- Anubis program-evidence verification (v1.7.3, additive) ----------------- +# +# These tools never execute a compiled artifact. `check` invokes only the +# caller-pinned, release-approved Anubis compiler's `build --evidence` path; +# `verify` and `verify-receipt` read caller-selected source/evidence bytes. +# Success is limited to `verified-program-evidence` or +# `verified-program-receipt` under inventory-safe-v1. The receipt keeps +# source-to-VC, SMT-to-CNF, policy-construct-totality, source-native, and +# runtime boundaries explicit. + + +def _program_verifier_call(method: str, args: dict[str, Any]) -> dict[str, Any]: + common = { + "source_path", + "expected_source_sha256", + "expected_compiler_sha256", + "expected_policy_sha256", + "verification_time_unix", + "profile", + "nonce", + } + if method == "check": + allowed = common | {"anubis_bin", "out_root"} + elif method == "verify": + allowed = common | {"evidence_dir", "expected_artifact_sha256"} + elif method == "verify-receipt": + allowed = common | { + "receipt", + "evidence_dir", + "expected_artifact_sha256", + } + else: + raise PluginRefusal("plugin-internal", f"unknown program method {method!r}") + if not isinstance(args, dict) or set(args) != allowed: + raise PluginRefusal( + "plugin-args-schema", f"expected fields {sorted(allowed)}" + ) + for key, value in args.items(): + if key == "receipt": + if not isinstance(value, dict): + raise PluginRefusal( + "plugin-args-schema", "receipt must be an object" + ) + elif not isinstance(value, str) or not value: + raise PluginRefusal( + "plugin-args-schema", f"invalid string field {key}" + ) + + verifier_path, verifier_pin = _claim_component("anubis_program_verifier") + with tempfile.TemporaryDirectory(prefix="jackal-plugin-program-") as td: + receipt_path = Path(td) / "program-receipt.json" + argv = [sys.executable, "-I", "-S", "-B", str(verifier_path)] + if method == "check": + argv += [ + "check", + "--source", + args["source_path"], + "--anubis-bin", + args["anubis_bin"], + "--expected-source-sha256", + args["expected_source_sha256"], + "--expected-compiler-sha256", + args["expected_compiler_sha256"], + "--expected-policy-sha256", + args["expected_policy_sha256"], + "--verification-time-unix", + args["verification_time_unix"], + "--profile", + args["profile"], + "--nonce", + args["nonce"], + "--out-root", + args["out_root"], + "--emit-receipt", + str(receipt_path), + ] + else: + argv += [ + method, + "--source", + args["source_path"], + "--evidence-dir", + args["evidence_dir"], + "--expected-source-sha256", + args["expected_source_sha256"], + "--expected-compiler-sha256", + args["expected_compiler_sha256"], + "--expected-artifact-sha256", + args["expected_artifact_sha256"], + "--expected-policy-sha256", + args["expected_policy_sha256"], + "--verification-time-unix", + args["verification_time_unix"], + "--profile", + args["profile"], + "--nonce", + args["nonce"], + ] + if method == "verify": + argv += ["--emit-receipt", str(receipt_path)] + else: + supplied_path = Path(td) / "supplied-receipt.json" + supplied_path.write_text( + json.dumps( + args["receipt"], + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + ), + encoding="utf-8", + ) + argv += [ + "--receipt", + str(supplied_path), + "--emit-receipt", + str(receipt_path), + ] + try: + proc = subprocess.run( + argv, capture_output=True, text=True, timeout=3600 + ) + except subprocess.TimeoutExpired: + return _refuse("plugin-subprocess", "program verifier timeout") + _claim_toctou("anubis_program_verifier", verifier_path, verifier_pin) + stdout = proc.stdout or "" + if proc.returncode != 0: + reason, detail = "program-verify-failed", stdout.strip()[:300] + for line in stdout.splitlines(): + if line.startswith("status=refused"): + if "reason=" in line: + reason = line.split("reason=", 1)[1].split()[0] + if 'detail="' in line: + detail = line.split('detail="', 1)[1].rstrip('"') + break + return _refuse(reason, detail) + try: + receipt = _strict_json_loads( + receipt_path.read_text(encoding="utf-8") + ) + except (OSError, ValueError) as exc: + return _refuse("program-receipt-read", str(exc)[:200]) + status = ( + "verified-program-receipt" + if method == "verify-receipt" + else "verified-program-evidence" + ) + return { + "status": status, + "receipt": receipt, + "report": stdout.splitlines(), + } + + +def tool_jackal_anubis_check_program( + args: dict[str, Any], +) -> dict[str, Any]: + return _program_verifier_call("check", args) + + +def tool_jackal_anubis_verify_program( + args: dict[str, Any], +) -> dict[str, Any]: + return _program_verifier_call("verify", args) + + +def tool_jackal_anubis_verify_program_receipt( + args: dict[str, Any], +) -> dict[str, Any]: + return _program_verifier_call("verify-receipt", args) + # -- domain-pack lanes (jackal-domain-pack-protocol v1, additive) ------------- # # These four tools expose the operations declared by @@ -1627,18 +1809,19 @@ def tool_jackal_verify_bundle(args: dict[str, Any]) -> dict[str, Any]: # on disk. Without the rerun an agent could read `status=structural-exact` # off a certificate whose claimed line, count or content hash is false. # -# Residual, stated rather than hidden: `release/MANIFEST.sha256` carries no row -# for the domain-pack surface yet, so the registry bytes are this lane's root of -# trust. The plugin cross-checks its own registry digest against the verifier's -# reported one and its own verifier digest against the registry's declared one, -# both from real bytes, and returns all of them so a caller can pin them -# out-of-band. A coordinated tamper of registry+verifier together is NOT -# excluded by this lane; that is what a manifest row would add. +# The release manifest independently pins the registry, pack verifier, and both +# operation checkers. The registry then binds PACK_SCHEMA/PACK_SPEC, every pack +# source/manifest, and each operation-to-checker relation. All identities are +# checked again before and after subprocess execution. _PACK_ROUTE_COMMAND = "pack-route" _PACK_REGISTRY_RELATIVE = "domain_packs/registry_v1.json" _PACK_VERIFIER_RELATIVE = "tools/domain_pack_verify.py" _PACK_MAX_REGISTRY_BYTES = 1_048_576 +_PACK_CHECKER_MANIFEST_LABELS = { + "tools/test_exists_verify.py": "domain_pack_test_exists_checker", + "tools/decision_verify.py": "domain_pack_decision_checker", +} # Engine-printed epistemic classes a pack lane may legitimately carry, mapped to # the mathematical-axis class its pinned manifest is allowed to declare as @@ -1777,6 +1960,14 @@ def _run_pack_verifier(registry_sha256: str) -> dict[str, Any]: raise PluginRefusal("pack-surface-absent", f"{_PACK_VERIFIER_RELATIVE} is not present under {ROOT}") pre = hashlib.sha256(verifier.read_bytes()).hexdigest() + pinned = _manifest_alias( + {"domain_pack_verifier"}, "domain-pack verifier" + ) + if pre != pinned: + raise PluginRefusal( + "pack-verifier-identity", + f"{_PACK_VERIFIER_RELATIVE} is {pre}, release manifest pins {pinned}", + ) proc = subprocess.run( [sys.executable, "-I", "-S", "-B", str(verifier), "--root", str(ROOT)], capture_output=True, text=True, timeout=600, @@ -1820,6 +2011,15 @@ def _pack_operation(pack_id: str, operation_id: str) -> dict[str, Any]: if len(registry_raw) > _PACK_MAX_REGISTRY_BYTES: raise PluginRefusal("pack-registry-refused", "registry exceeds protocol bound") registry_sha = hashlib.sha256(registry_raw).hexdigest() + pinned_registry = _manifest_alias( + {"domain_pack_registry"}, "domain-pack registry" + ) + if registry_sha != pinned_registry: + raise PluginRefusal( + "pack-registry-identity", + f"{_PACK_REGISTRY_RELATIVE} is {registry_sha}, " + f"release manifest pins {pinned_registry}", + ) report = _run_pack_verifier(registry_sha) registry = _strict_json_loads(registry_raw) if not isinstance(registry, dict) or not isinstance(registry.get("packs"), list): @@ -1959,10 +2159,20 @@ def _pack_run_checker(operation: dict[str, Any], cert_line: str, path = ROOT / relative if not path.is_file(): raise PluginRefusal("pack-surface-absent", f"{relative} is not present") + label = _PACK_CHECKER_MANIFEST_LABELS.get(relative) + if label is None: + raise PluginRefusal( + "pack-registry-refused", + f"checker {relative!r} has no release-manifest binding", + ) + release_expected = _manifest_alias({label}, f"domain-pack checker {relative}") pre = hashlib.sha256(path.read_bytes()).hexdigest() - if pre != expected: - raise PluginRefusal("pack-checker-identity", - f"{relative} is {pre}, pinned {expected}") + if pre != expected or pre != release_expected: + raise PluginRefusal( + "pack-checker-identity", + f"{relative} is {pre}, pack manifest pins {expected}, " + f"release manifest pins {release_expected}", + ) proc = subprocess.run( [sys.executable, "-I", "-S", "-B", str(path), *checker_flags], input=cert_line, capture_output=True, text=True, timeout=600, @@ -2043,9 +2253,6 @@ def tool(args: dict[str, Any]) -> dict[str, Any]: f"The consequence ceiling of this result is " f"{operation['consequence_ceiling']!r} and is NOT raised by its " f"{operation['assurance_ceiling']!r} assurance ceiling", - "pack_surface_is_not_manifest_pinned: release/MANIFEST.sha256 " - "carries no row for the domain-pack registry; the registry and " - "verifier digests above are reported for out-of-band pinning", *operation.get("nonclaims", []), ], } @@ -2073,6 +2280,11 @@ def tool(args: dict[str, Any]) -> dict[str, Any]: TOOLS[_tool_name] = _make_weak_tool(_tool_name, _spec) for _tool_name, _spec in _PACK_LANE_TOOLS.items(): TOOLS[_tool_name] = _make_pack_tool(_tool_name, _spec) +TOOLS["jackal_anubis_check_program"] = tool_jackal_anubis_check_program +TOOLS["jackal_anubis_verify_program"] = tool_jackal_anubis_verify_program +TOOLS["jackal_anubis_verify_program_receipt"] = ( + tool_jackal_anubis_verify_program_receipt +) def _dispatch(method: str, params: Any) -> dict[str, Any]: @@ -2158,9 +2370,11 @@ def _serve_call(tool: str, arg_json: str) -> int: result = _dispatch(tool, params) sys.stdout.write(json.dumps(result, sort_keys=True, indent=2) + "\n") return 0 if result.get("status") in { - "formal-bounded", "verified", + "formal-bounded", "verified", "ok", # Weaker-lane classes: success at their honest epistemic level. "exact", "checked", "estimated", "bounded", "model-based", + # Program evidence statuses are bounded successes, not formal claims. + "verified-program-evidence", "verified-program-receipt", # Domain-pack programming lane: a byte-exact STRUCTURAL fact. Success # here means the checker recomputed the claim from real bytes, not that # anything is correct — see the tool's consequence_ceiling. diff --git a/plugin/hermes/tools.json b/plugin/hermes/tools.json index edfcb2d..b097bf2 100644 --- a/plugin/hermes/tools.json +++ b/plugin/hermes/tools.json @@ -1,8 +1,8 @@ { "schema": "jackal-hermes-plugin-v1", "plugin_id": "jackal_range_bound", - "version": "v1.7.2", - "description": "JACKAL mathematical evidence kernel: formal-bounded range/Gaussian/composed-integral/pure-Q fragment enclosures, independent receipt verification, exact CAS lanes with independently re-checkable certificates, honest weaker-lane calculation (exact/checked/estimated/bounded) with status passthrough that never inflates, and the claim-kernel front doors (jackal_claim, jackal_verify_bundle) for compiling and independently replaying content-addressed claim bundles. v1.7.2 closes the range-ordering and composed-integral proof premises and binds current formal tools to the closed-premise v2 proof identities. Additive since v1.7.2: four domain-pack lanes (jackal-domain-pack-protocol v1) carry the same refusal discipline OUTSIDE mathematics — jackal_test_exists and jackal_claim_cites_test state byte-exact structural facts about source files at consequence ceiling informational, and jackal_decision_rank / jackal_decision_rank_v2 order options by a caller-declared recomputable numeric criterion at consequence ceiling decision-boundary. Every pack lane routes through pack-route, re-runs its manifest-pinned independent checker over the emitted certificate, and is NOT formal: assurance ceiling exact, no Lean checker, no theorem, and a consequence ceiling that never rises with the assurance class.", + "version": "v1.7.3", + "description": "JACKAL evidence kernel: the preserved v1.7.2 formal, exact, weaker, and claim lanes; four manifest-pinned domain-pack tools for structural source facts and caller-criterion decisions; and three Anubis Safe program-evidence tools. The program tools close a strict anubis.program-evidence.v3 tree, bind caller-pinned source/compiler/artifact/policy identities, replay approved Z3 UNSAT plus independent RUP proofs, and emit only verified-program-evidence or verified-program-receipt. Their inventory-safe-v1 policy deliberately does not claim independent policy-construct totality, source-to-VC proof, SMT-to-CNF proof, source-native refinement, runtime behavior, or universal language soundness. Domain-pack assurance and consequence ceilings remain separate: test existence stays informational and decision ranking stays decision-boundary. No lane is promoted to formal by this additive surface.", "bundle_files": [ "server.py", "bundle_hash.py", @@ -137,6 +137,13 @@ "runtime/unit_registry_v1.json": [ "../../release/claim/unit_registry_v1.json", "../../unit_registry_v1.json" + ], + "runtime/anubis_program_verify.py": [ + "../../tools/anubis_program_verify.py" + ], + "runtime/anubis_program_policy.json": [ + "../../release/program/inventory_safe_v1.json", + "../../program/inventory_safe_v1.json" ] }, "tools": [ @@ -779,7 +786,7 @@ "checker_rerun": "ACCEPT on success (tools/test_exists_verify.py, digest pinned by the pack manifest)", "certificate": "the verbatim jackal-test-exists-cert-v1 envelope line", "identities": "{registry_file_sha256, registry_digest_sha256, pack_verifier_sha256, pack_manifest_sha256, evaluator_sha256, checker_sha256, plugin_sha256}", - "non_claims": "the operation's pinned nonclaims plus the consequence-ceiling and not-manifest-pinned statements", + "non_claims": "the operation's pinned nonclaims plus the informational consequence ceiling; registry, verifier, and checker identities are release-manifest pinned", "reason": "stable refusal class on refusal (prog-path, prog-hex64, prog-symbol, prog-uint, prog-absent, pack-request-arity, checker-rejected, ...)", "detail": "short human-readable string on refusal" } @@ -865,6 +872,73 @@ "reason": "stable refusal class on refusal (decision-unit-missing, decision-unit-unknown, decision-value-judgment, decision-sense-unknown, decision-duplicate-label, decision-margin-zero, pack-request-arity, pack-args-shape, checker-rejected, ...)", "detail": "short human-readable string on refusal" } + }, + { + "name": "jackal_anubis_check_program", + "description": "Run a caller-pinned Anubis compiler only as `build --evidence` in Safe mode, never execute the compiled artifact, then independently close and replay the resulting strict anubis.program-evidence.v3 package under inventory-safe-v1. Emits only verified-program-evidence or a named refusal. This profile checks producer-attested function/policy inventories but explicitly does not establish policy-construct totality, source-to-VC proof, SMT-to-CNF proof, source-native refinement, runtime behavior, or universal soundness.", + "arguments": { + "source_path": {"type": "string", "required": true, "help": "Local regular non-symlink Anubis source path."}, + "anubis_bin": {"type": "string", "required": true, "help": "Local regular non-symlink Anubis executable; caller-pinned and hashed before/after build."}, + "expected_source_sha256": {"type": "string", "required": true, "help": "Caller-pinned exact source SHA-256."}, + "expected_compiler_sha256": {"type": "string", "required": true, "help": "Caller-pinned approved Anubis executable SHA-256."}, + "expected_policy_sha256": {"type": "string", "required": true, "help": "Caller-pinned inventory-safe-v1 policy digest."}, + "verification_time_unix": {"type": "string", "required": true, "help": "Caller-supplied Unix verification time bound into the receipt."}, + "profile": {"type": "string", "required": true, "help": "Must be inventory-safe-v1; contracted-safe-v1 refuses."}, + "nonce": {"type": "string", "required": true, "help": "Caller nonce bound into the receipt."}, + "out_root": {"type": "string", "required": true, "help": "New local output root; existing paths refuse."} + }, + "returns": { + "status": "verified-program-evidence | refused", + "receipt": "jackal-anubis-program-receipt-v1 on success", + "residual_non_claims": "includes policy-construct-totality-not-established and every source/proof/runtime residual", + "reason": "stable refusal reason", + "detail": "bounded refusal detail" + } + }, + { + "name": "jackal_anubis_verify_program", + "description": "Independently verify caller-selected Anubis Safe source and evidence bytes under inventory-safe-v1: strict v3 roster, exact manifest closure, source/compiler/artifact/policy pins, producer-summary reconciliation, one-to-one solver/proof paths and counters, approved Z3 UNSAT replay, and independent RUP replay. Never executes the artifact and never claims independent policy-construct totality or source-native refinement.", + "arguments": { + "source_path": {"type": "string", "required": true, "help": "Caller-selected regular non-symlink Anubis source path."}, + "evidence_dir": {"type": "string", "required": true, "help": "Caller-selected strict anubis.program-evidence.v3 directory."}, + "expected_source_sha256": {"type": "string", "required": true, "help": "Caller-pinned exact source SHA-256."}, + "expected_compiler_sha256": {"type": "string", "required": true, "help": "Caller-pinned producer executable SHA-256."}, + "expected_artifact_sha256": {"type": "string", "required": true, "help": "Caller-pinned sealed artifact SHA-256; artifact bytes are read but never executed."}, + "expected_policy_sha256": {"type": "string", "required": true, "help": "Caller-pinned inventory-safe-v1 policy digest."}, + "verification_time_unix": {"type": "string", "required": true, "help": "Caller-supplied Unix verification time bound into the receipt."}, + "profile": {"type": "string", "required": true, "help": "Must be inventory-safe-v1."}, + "nonce": {"type": "string", "required": true, "help": "Caller nonce bound into the receipt."} + }, + "returns": { + "status": "verified-program-evidence | refused", + "receipt": "jackal-anubis-program-receipt-v1 on success", + "residual_non_claims": "strict residual roster including policy-construct-totality-not-established", + "reason": "stable refusal reason", + "detail": "bounded refusal detail" + } + }, + { + "name": "jackal_anubis_verify_program_receipt", + "description": "Recompute a jackal-anubis-program-receipt-v1 from caller-selected source/evidence bytes and independent caller pins, rejecting outer-digest-consistent semantic laundering. Success is only verified-program-receipt; it does not raise the underlying inventory-safe-v1 assurance ceiling.", + "arguments": { + "receipt": {"type": "object", "required": true, "help": "Program receipt object to replay; its own pins are never trusted."}, + "source_path": {"type": "string", "required": true, "help": "Caller-selected regular source path."}, + "evidence_dir": {"type": "string", "required": true, "help": "Caller-selected evidence directory."}, + "expected_source_sha256": {"type": "string", "required": true, "help": "Caller-pinned source SHA-256."}, + "expected_compiler_sha256": {"type": "string", "required": true, "help": "Caller-pinned compiler SHA-256."}, + "expected_artifact_sha256": {"type": "string", "required": true, "help": "Caller-pinned artifact SHA-256."}, + "expected_policy_sha256": {"type": "string", "required": true, "help": "Caller-pinned inventory-safe-v1 policy digest."}, + "verification_time_unix": {"type": "string", "required": true, "help": "Caller-supplied Unix verification time bound into the receipt."}, + "profile": {"type": "string", "required": true, "help": "Must be inventory-safe-v1."}, + "nonce": {"type": "string", "required": true, "help": "Caller nonce; never copied from the receipt."} + }, + "returns": { + "status": "verified-program-receipt | refused", + "receipt": "fully recomputed receipt on success", + "residual_non_claims": "identical to the recomputed inventory-safe-v1 receipt", + "reason": "stable refusal reason", + "detail": "bounded refusal detail" + } } ] } diff --git a/plugins/jackel/.codex-plugin/plugin.json b/plugins/jackel/.codex-plugin/plugin.json index d937fa7..746512c 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.20260817222758", - "description": "Expose JACKAL's claim-aware mathematical evidence kernel to Codex.", + "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" @@ -22,7 +22,7 @@ "interface": { "displayName": "JACKAL", "shortDescription": "Claim-aware computation with explicit evidence classes", - "longDescription": "Use JACKAL's complete mathematical evidence kernel from Codex, with exact, checked, estimated, bounded, formal-bounded, model-based, verified, indeterminate, and refused results preserved at their original assurance level. Formal-bounded applies only to checker-admitted fragments. Requires Apple Silicon macOS, Python >=3.10 at /opt/homebrew/bin/python3 (install with brew install python), and the pinned sealed v1.7.0 runtime.", + "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": [ @@ -32,7 +32,8 @@ "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 receipt or claim bundle against my pinned expectations.", + "Verify this Anubis Safe program-evidence package without executing its artifact." ] } } diff --git a/plugins/jackel/PLUGIN_IDENTITY.sha256 b/plugins/jackel/PLUGIN_IDENTITY.sha256 index 3e63ed0..82f0076 100644 --- a/plugins/jackel/PLUGIN_IDENTITY.sha256 +++ b/plugins/jackel/PLUGIN_IDENTITY.sha256 @@ -1,7 +1,8 @@ -6e4584d28e7f5b68b9a4572c19c54fa41b86ac9def271e6fae03c068cc8d4e96 .codex-plugin/plugin.json +49bbd046759b045add1caf1f19300431fe7d697b59bfd93b57daf1d7edb709ff .codex-plugin/plugin.json 23d3c34f6714de5e1ff42f5221e575f5a97d293dbd60cb9613b8734db8bad1de .mcp.json -eefecec0bc75462c2805f34b9a24b687787e0ceaef962b88c153f47093a5505a mcp/server.py +013f2a0393efc01d724d28ddb22fb5c324378e0c2333de45b8565e45124ea1ea README.md +d307e3534e3451e59f9955ef2aac459bfb8c2d72239aa2325641d0487064f5f5 mcp/server.py 4a5d54ab2300d8e3c2ec15fbea56bcc157ab084bf45233367d4864cb7316cc75 scripts/launch_mcp.zsh -d2ee3d161fd2b51911bf518a7af6d0773731a9ab602c48ef1c3bad3163b810a7 scripts/provision_runtime.py +618cd70b0b9005759ad8392e0c455c527e6d2b84e81d0caac66d6e5c088cf552 scripts/provision_runtime.py 60f6a068c5f6bd81a2ab80e8d9d873864aefdf7f6ae7c2db37fe6572b41f898f scripts/verify_plugin.py -d4d9ed46671905a3c317085c1a98d94a4c5463bd82de2305315b52645fc23bee skills/jackel/SKILL.md +75211a141c502c2f6c4ca54f88f07b2d13b87475f4837b3335f43c15f7da42dc skills/jackel/SKILL.md diff --git a/plugins/jackel/README.md b/plugins/jackel/README.md new file mode 100644 index 0000000..b93a8b1 --- /dev/null +++ b/plugins/jackel/README.md @@ -0,0 +1,73 @@ +# JACKAL 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 +`release/capability_inventory_v1.json`; the package receipt and downloaded +release asset must bind the same exact bytes. + +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. + +## 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: + +```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 +# Offline alternative: +# /bin/zsh scripts/launch_mcp.zsh provision --tarball \ +# /absolute/path/to/jackal-v1.7.3-macos-arm64.tar.gz +/bin/zsh scripts/launch_mcp.zsh 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`. + +## Routing + +- Use `jackal_claim` for mixed or policy-bearing claim graphs and + `jackal_verify_bundle` for caller-pinned independent replay. +- Use `jackal_verify_receipt` for formal receipt replay against independent + request and identity expectations. +- Use direct typed tools for one exact, checked, estimated, bounded, + formal-bounded, structural, or decision operation. +- 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. + None executes the artifact. + +No silent downgrade is permitted. `refused` and `indeterminate` are terminal +outcomes unless the caller explicitly requests a separate weaker lane. Never +copy an `expected_*` value from the receipt or bundle being verified. + +Program evidence under `inventory-safe-v1` must retain +`policy-construct-totality-not-established`, `no-source-to-vc-proof`, +`no-smt-to-cnf-proof`, `no-source-native-refinement`, `runtime-not-observed`, +and `no-universal-language-soundness`. It is not `formal-bounded` and does not +establish runtime behavior. + +## Verify source bytes + +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 +``` + +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. diff --git a/plugins/jackel/mcp/server.py b/plugins/jackel/mcp/server.py index 122fc7c..17a6c60 100644 --- a/plugins/jackel/mcp/server.py +++ b/plugins/jackel/mcp/server.py @@ -37,7 +37,7 @@ {LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05"} ) SUPPORTED_ARGUMENT_TYPES = frozenset({"string", "object"}) -EXPECTED_TOOL_COUNT = 34 +EXPECTED_TOOL_COUNT = 41 TOOL_TIMEOUT_SECONDS = 3600.0 TERMINATE_GRACE_SECONDS = 0.5 LEADER_POLL_SECONDS = 0.01 diff --git a/plugins/jackel/scripts/provision_runtime.py b/plugins/jackel/scripts/provision_runtime.py index 8b28bea..932f42a 100644 --- a/plugins/jackel/scripts/provision_runtime.py +++ b/plugins/jackel/scripts/provision_runtime.py @@ -27,14 +27,15 @@ from typing import Callable, Iterable, Mapping -EPOCH = "v1.7.0" -ASSET = "jackal-v1.7.0-macos-arm64.tar.gz" -URL = "https://github.com/AnubisQuantumCipher/jackal/releases/download/v1.7.0/jackal-v1.7.0-macos-arm64.tar.gz" -PACKAGE_SIZE = 118862060 -PACKAGE_SHA256 = "21c7ede586f30a58772f321f7dbb36ab66213e199785489f99133710ac56096e" -EXTRACTED_SIZE = 416736385 -SHA256SUMS_SHA256 = "f1f794ccd2ba331e6188840cfc089180cdcd744f23c1880f8364a81b230c1a28" -PACKAGE_DIRECTORY = "jackal-v1.7.0-macos-arm64" +EPOCH = "v1.7.3" +ASSET = "jackal-v1.7.3-macos-arm64.tar.gz" +URL = "https://github.com/AnubisQuantumCipher/jackal/releases/download/v1.7.3/jackal-v1.7.3-macos-arm64.tar.gz" +RELEASE_STATE = "published" +PACKAGE_SIZE = 158363786 +PACKAGE_SHA256 = "68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae" +EXTRACTED_SIZE = 555511970 +SHA256SUMS_SHA256 = "a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a" +PACKAGE_DIRECTORY = "jackal-v1.7.3-macos-arm64" MAX_ARCHIVE_MEMBERS = 8192 MAX_RUNTIME_RECORDS = MAX_ARCHIVE_MEMBERS MAX_RUNTIME_ENTRIES = MAX_ARCHIVE_MEMBERS + 2 @@ -1460,6 +1461,11 @@ def provision( return target if check_only: raise ProvisionError("pinned runtime is not installed") + if tarball is None and RELEASE_STATE != "published": + raise ProvisionError( + "release network provisioning is disabled; provide --tarball " + "with the pinned package" + ) target.parent.mkdir(parents=True, exist_ok=True) with tempfile.TemporaryDirectory(prefix=f".{epoch}.stage-", dir=target.parent) as temporary_directory: diff --git a/plugins/jackel/skills/jackel/SKILL.md b/plugins/jackel/skills/jackel/SKILL.md index 9c3f7c5..c0447aa 100644 --- a/plugins/jackel/skills/jackel/SKILL.md +++ b/plugins/jackel/skills/jackel/SKILL.md @@ -1,10 +1,17 @@ --- name: jackel -description: Route claim-aware mathematical evidence work through JACKAL without overstating assurance. +description: Route claim-aware computation, domain-pack, and Anubis program evidence through JACKAL without overstating assurance. --- # JACKAL 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. + + JACKAL exposes the full tool inventory on Apple Silicon macOS. Use it to classify a quantitative claim, select the strongest admitted evidence lane, and preserve the exact assurance boundary returned by the runtime. @@ -22,6 +29,16 @@ 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_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. +- Use `jackal_decision_rank_v2` for a caller-declared numeric criterion with a + canonical unit. A declared unit is not a measurement; never present the + selected option as an intrinsic value judgment. +- Use `jackal_anubis_verify_program` for caller-selected Safe source/evidence + bytes, `jackal_anubis_verify_program_receipt` to recompute a receipt, and + `jackal_anubis_check_program` only when the caller supplies the approved + compiler and a new output root. None executes the compiled artifact. Verification expectations are authorization, not data discovery. Expected bundle and receipt values must come from the caller or separately trusted source, not evidence under review. @@ -34,8 +51,8 @@ For receipt replay, the exact expected command depends on the receipt: - Gaussian receipts: `integrate` - composed `int_cert` receipts: `integrate-bound-cert` -The sealed v1.7.0 catalog help omits the last value; `integrate-bound-cert` is -the correct caller-pinned command for `int_cert`. +Current formal range and composed-integral receipts use release epoch v1.7.2; +the additive package/runtime epoch is v1.7.3. ## Select the assurance lane @@ -53,6 +70,12 @@ the correct caller-pinned command for `int_cert`. model assumption; mathematical exactness does not establish model validity. - `verified`: a replay verdict against fixed expectations, not a replacement for the evidence graph's mathematical class, assumptions, or non-claims. +- `structural-exact`: byte-exact source structure, consequence-capped at + `informational`; never code correctness. +- `verified-program-evidence` / `verified-program-receipt`: inventory-safe-v1 + program evidence or its replay. These statuses are not formal-bounded and do + not close policy-construct totality, source-to-VC, SMT-to-CNF, + source-native, runtime, or universal-soundness residuals. ## Formal-bounded admitted fragments @@ -69,8 +92,26 @@ JACKAL's formal-bounded applies only to checker-admitted fragments. Do not claim formal coverage for arbitrary expressions or material outside that admitted fragment. Source-to-native refinement remains open and unclaimed. +## Anubis program-evidence boundary + +Require profile `inventory-safe-v1`, Safe mode, one exact source leaf, strict +v3 stage/file/consumer rosters, nonzero one-to-one proof paths, approved Z3 +UNSAT replay, and independent RUP replay. Always preserve: + +- `no-source-to-vc-proof` +- `no-smt-to-cnf-proof` +- `policy-construct-totality-not-established` +- `no-source-native-refinement` +- `runtime-not-observed` +- `no-universal-language-soundness` + +Refuse `contracted-safe-v1`; the producer-attested whole-function roster is not +independent construct-total walker coverage. Never execute the compiled +artifact to strengthen the status. + ## Preserve refusal and residuals +No silent downgrade is permitted. You must preserve every returned status/assumption/non-claim/residual/refusal verbatim. You must never promote assurance or silently downgrade a requested claim. If a strong lane refuses, return its named reason. Run a weaker lane only when the caller explicitly requests one, @@ -82,9 +123,10 @@ checker verdicts, or residual non-claims. ## macOS runtime -This plugin supports Apple Silicon macOS only and uses the pinned sealed -v1.7.0 macOS-arm64 runtime. Do not bypass the Darwin/arm64 host guard or -substitute another platform build. +This plugin supports Apple Silicon macOS only and uses the pinned v1.7.3 +candidate macOS-arm64 runtime. Until that candidate is published, provision it +from the separately verified local tarball path. +Do not bypass the Darwin/arm64 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 @@ -98,7 +140,7 @@ 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.0-macos-arm64.tar.gz +/bin/zsh scripts/launch_mcp.zsh provision --tarball /absolute/path/to/jackal-v1.7.3-macos-arm64.tar.gz ``` The default MCP bridge reads the verified runtime locator. Set `JACKAL_HOME` diff --git a/release/MANIFEST.sha256 b/release/MANIFEST.sha256 index dccaf0c..d129b12 100644 --- a/release/MANIFEST.sha256 +++ b/release/MANIFEST.sha256 @@ -1,5 +1,5 @@ -# JACKAL v1.7.2 pinned release identities (closed-premise range/int proof identity schema v2 + v1.7.0 computation/claim/gaussian lanes) -evaluator jackal-native 20b80827d3c5c2a5d0d5d6f5a84c692f230fb0f55b9c7d1fcad02a1d0b3a1083 +# JACKAL v1.7.3 pinned release identities (v1.7.2 proof/claim/domain lanes + inventory-safe Anubis program evidence) +evaluator jackal-native f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655 checker jackal_cert_check f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545 archival-v170-archive-source github-release-v1.7.0 21c7ede586f30a58772f321f7dbb36ab66213e199785489f99133710ac56096e archival-range-checker jackal_cert_check_v170 05c3518b836f239712f897c483a2ddadad9f544e0887b1b7bb1424a27289de8a @@ -13,19 +13,22 @@ archival-range-proof-identity release/evidence/range_proof_identity.json 1b2d623 archival-range-proof-digest 5d6a1d70289794a7653cd6d854f1239b9f1de6767bad90aab34cc17fb3f7372e gaussian-proof-identity release/evidence/gaussian_proof_identity.json 7d2ff9ed4934604eba30f3111a147d7e295fd79302f640f57aacd986a23e243c gaussian-proof-digest 828ed69d9e04784e1f750536ebe8c12f4563dd62a37a85397f2aa08769fce1fd +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 f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617 int-cert-proof-identity release/evidence/int_cert_proof_identity_v172.json a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf int-cert-proof-digest 8ce4d8ceeaf2eaa47b97838bfcb2e35880f4fc20fb744c6ef2bf175f0b32f91e 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 638d28dc9811bb9359af27a1bcc5427717cdf894902011fbb230dc18bac63776 +source jackal_calc.anb f579b6f59bc024d24914487b0cd0f18ea43dea1be52708a05a66dc885d80bb4e compiler_pin anubis-a733565f237d a733565f237df171e7cf93b9b37700a42d8713576818fd92f8cd23a8ad7a69e2 -plugin_hermes e8fadc24b17884d9fc4a8458b4e4a70ac60ad0d88768b82a684c665e2a9e0202 +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 @@ -36,5 +39,11 @@ exact_verifier tools/exact_verify.py 2c07e6257ce1524de3e31374371c6d5859dce710767 claim_kernel tools/claim_kernel.py 77b0f85ad5fb7214f88898b60ea29ea9fd7be740c38b655388444e6e5181f348 claim_router tools/claim_router.py 02328cf177a0423bdc5cbca6ec0ea946bb0679bbd3dc6c24140d32598e575afb claim_verifier tools/claim_bundle_verify.py e0fcb9540c730bd9bb492b528ed42d29d49fc775b3aa0f9b831b6264fd68fd22 -claim_inference_registry release/claim/inference_registry_v1.json e7134ec30f3b5dce71014fa1bbfc6b15e6dd8f42bfecd900fd3a61cf6b895082 +domain_pack_registry domain_packs/registry_v1.json 1a3b2c95dcdc7c7337fbe0ecb34043b70c3697752d6dc585f45f3c7d4f1b0706 +domain_pack_verifier tools/domain_pack_verify.py 22984f511208af2d7a318f1a43306d95a4b0f61876d8b44f34f39a2ded6d573d +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 4b80e29bdffc0737f05a6e215fce8cce3b6b828c24afbf55c68443399e5119dc +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 diff --git a/release/build_package_v173.sh b/release/build_package_v173.sh new file mode 100755 index 0000000..aacb3b7 --- /dev/null +++ b/release/build_package_v173.sh @@ -0,0 +1,1302 @@ +#!/bin/sh +# Build the additive JACKAL v1.7.3 Apple Silicon macOS package. +# +# 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" != "Darwin" ] || [ "$MACHINE" != "arm64" ]; then + echo "PACKAGE_V173_REFUSED reason=unsupported-host system=$SYSTEM machine=$MACHINE expected=Darwin/arm64" >&2 + exit 3 +fi + +ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +VER="v1.7.3" +PLATFORM="macos-arm64" +PKG_NAME="jackal-v1.7.3-macos-arm64" +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_SHA256="a733565f237df171e7cf93b9b37700a42d8713576818fd92f8cd23a8ad7a69e2" +COMPILER=${JACKAL_ANUBIS_COMPILER_PATH:-} +[ -n "$COMPILER" ] || { + echo "PACKAGE_V173_REFUSED reason=compiler-path-unset expected_sha256=$COMPILER_SHA256" >&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_URL="https://github.com/AnubisQuantumCipher/jackal/releases/download/v1.7.0/jackal-v1.7.0-macos-arm64.tar.gz" +V170_ARCHIVE_SHA256="21c7ede586f30a58772f321f7dbb36ab66213e199785489f99133710ac56096e" +V170_RANGE_CHECKER_SHA256="05c3518b836f239712f897c483a2ddadad9f544e0887b1b7bb1424a27289de8a" +V170_COVERAGE_INVENTORY_SHA256="18ff7b1d428dbc6f807fd4de27751ba415b33ef0b356088d7fa316ed74bb0ba6" +V170_PLUGIN_HERMES_SHA256="d141c909e8f5f03e268a2112f291e6bd79fafff906522eb7ca9accc247a3274b" + +sha256() { + /usr/bin/shasum -a 256 "$1" | /usr/bin/awk '{print $1}' +} + +publish_noreplace() { + python3 -I -S -B - "$1" "$2" <<'PY' +import ctypes +import os +import sys + +RENAME_EXCL = 0x00000004 +source = os.fsencode(sys.argv[1]) +destination = os.fsencode(sys.argv[2]) +libc = ctypes.CDLL(None, use_errno=True) +renamex_np = libc.renamex_np +renamex_np.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint] +renamex_np.restype = ctypes.c_int +if renamex_np(source, destination, RENAME_EXCL) != 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" +OBSERVED_COMPILER_SHA256=$(sha256 "$COMPILER") +[ "$OBSERVED_COMPILER_SHA256" = "$COMPILER_SHA256" ] || { + echo "PACKAGE_V173_REFUSED reason=compiler-authority-drift observed=$OBSERVED_COMPILER_SHA256 expected=$COMPILER_SHA256" >&2 + exit 4 +} + +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/int_cert_proof_identity.json +release/evidence/gaussian_proof_identity.json +release/evidence/lean_admission_audit_v173.json +release/capability_inventory_v1.json +release/compat/v172_floor.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. +python3 -I -S -B "$ROOT/release/tools/repin_v173.py" \ + --compiler-path "$COMPILER" --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 -f '%d' "$ROOT/release") +DIST_DEVICE=$(/usr/bin/stat -f '%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.json" "$PKG/range_proof_identity.json" +copy_file "$ROOT/release/evidence/int_cert_proof_identity_v172.json" "$PKG/int_cert_proof_identity.json" +copy_file "$ROOT/release/evidence/gaussian_proof_identity.json" "$PKG/gaussian_proof_identity.json" +copy_file "$ROOT/release/evidence/lean_admission_audit_v173.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. +V170_ARCHIVE="$STAGE/jackal-v1.7.0-macos-arm64.tar.gz" +if [ -n "${JACKAL_V170_ARCHIVE:-}" ]; then + require_regular "$JACKAL_V170_ARCHIVE" + /bin/cp "$JACKAL_V170_ARCHIVE" "$V170_ARCHIVE" +else + /usr/bin/curl --fail --location --silent --show-error \ + --proto '=https' --tlsv1.2 \ + --connect-timeout 20 --max-time 900 \ + --retry 3 --retry-max-time 900 --retry-connrefused \ + "$V170_ARCHIVE_URL" --output "$V170_ARCHIVE" +fi +[ "$(sha256 "$V170_ARCHIVE")" = "$V170_ARCHIVE_SHA256" ] || { + echo "PACKAGE_V173_REFUSED reason=archival-archive-identity" >&2 + exit 4 +} +python3 -I -S -B - "$V170_ARCHIVE" "$PKG" \ + "$V170_RANGE_CHECKER_SHA256" "$V170_COVERAGE_INVENTORY_SHA256" <<'PY' +import hashlib +import os +import pathlib +import sys +import tarfile + +archive = pathlib.Path(sys.argv[1]) +package = pathlib.Path(sys.argv[2]) +expected = { + "jackal-v1.7.0-macos-arm64/jackal_cert_check": ( + package / "jackal_cert_check_v170", sys.argv[3], 0o755 + ), + "jackal-v1.7.0-macos-arm64/formal_coverage_inventory.json": ( + package / "evidence/formal_coverage_inventory_v170.json", sys.argv[4], + 0o644 + ), +} +with tarfile.open(archive, "r:gz") as bundle: + members = {member.name: member for member in bundle.getmembers()} + for name, (destination, digest, mode) in expected.items(): + member = members.get(name) + if member is None or not member.isfile() or member.size > 256 * 1024 * 1024: + raise SystemExit(f"archival-checker-member-refused:{name}") + source = bundle.extractfile(member) + if source is None: + raise SystemExit(f"archival-checker-read-refused:{name}") + data = source.read(256 * 1024 * 1024 + 1) + if len(data) != member.size or hashlib.sha256(data).hexdigest() != digest: + raise SystemExit(f"archival-checker-identity-refused:{name}") + with destination.open("xb") as output: + output.write(data) + output.flush() + os.fsync(output.fileno()) + destination.chmod(mode) +PY + +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.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.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.json" "$PKG/evidence/range_proof_identity_v172.json" +copy_file "$ROOT/release/evidence/int_cert_proof_identity_v172.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") +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" && /usr/bin/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 + +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" && /usr/bin/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/capability_inventory_v1.json b/release/capability_inventory_v1.json new file mode 100644 index 0000000..8d66f97 --- /dev/null +++ b/release/capability_inventory_v1.json @@ -0,0 +1 @@ +{"catalog":{"path":"plugin/hermes/tools.json","sha256":"53c823f07db512b82e01a4f132ff43be426b4b227c436e8853c5144ae0504e87","version":"v1.7.3"},"inputs":[{"path":"tools/capability_inventory.py","sha256":"a966660ef94d7b788be9f92451a78e43b41e12ae2c4e942834c1703994c2ccc0"},{"path":"plugin/hermes/tools.json","sha256":"53c823f07db512b82e01a4f132ff43be426b4b227c436e8853c5144ae0504e87"},{"path":"plugin/hermes/profiles/core.json","sha256":"49f33ba23cca5ab940f1929604f61491bc914d092f291cda4fe4f06b37d042d3"},{"path":"plugin/hermes/profiles/formal.json","sha256":"9be2b3144486311d9ba7f1d41c5033eb8e2553e9d12b71d46e512401f57a084b"},{"path":"plugin/hermes/profiles/full.json","sha256":"0db937da01737bbc0341a591ecd23e55008d8ffc02368517c7d1e7da8b309dec"},{"path":"plugin/hermes/server.py","sha256":"4c42725d797ac78ed20d3e843e602b1c60c88bd13f74e06c65a6b4016b3b7daf"},{"path":"plugins/jackel/.codex-plugin/plugin.json","sha256":"49bbd046759b045add1caf1f19300431fe7d697b59bfd93b57daf1d7edb709ff"},{"path":"plugins/jackel/mcp/server.py","sha256":"d307e3534e3451e59f9955ef2aac459bfb8c2d72239aa2325641d0487064f5f5"},{"path":"release/MANIFEST.sha256","sha256":"ac52dafc0e9edbf74dde56b358c3c55ab5b705d3b66811558156c480b3530509"},{"path":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"path":"release/evidence/gaussian_proof_identity.json","sha256":"7d2ff9ed4934604eba30f3111a147d7e295fd79302f640f57aacd986a23e243c"},{"path":"release/evidence/int_cert_proof_identity_v172.json","sha256":"a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf"}],"release":{"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"state":"v1.7.3","statement":"Published release identity; the annotated v1.7.3 tag and GitHub release must bind these exact bytes.","version":"v1.7.3"},"schema":"jackal-capability-inventory-v1","status_vocabulary":["bounded","checked","estimated","exact","formal-bounded","indeterminate","model-based","ok","refused","structural-exact","verified","verified-program-evidence","verified-program-receipt"],"tool_count":41,"tools":[{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-range","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"coverage-inventory","locator":"release/coverage/formal_coverage_inventory.json","sha256":"6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_range_bound","profiles":["formal","full"],"refusal_boundary":"Only the catalog-declared expression and canonical-rational interval fragment is admitted. Unsupported syntax, invalid intervals, missing or mismatched pins, producer/checker rejection, or identity drift refuses; there is no weaker-lane fallback.","release_state":"v1.7.3","schema_sha256":"826bed7e2645735d5d333040eaa8c28425df90b6e87f8dd6aa94df1cc40ace45","status_classes":["formal-bounded","refused"],"supported_fragment":"Emit a formal-bounded receipt with the certificate embedded (or refuse). Inputs: expression: Anubis expression in the single variable x, restricted to the certified fragment.; input_lo: Interval lower bound as an integer or reduced ℚ (e.g. '1', '2/3').; input_hi: Interval upper bound (canonical rational, hi >= lo)."},{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-gaussian","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"gaussian-checker","locator":"jackal_gaussian_check","sha256":"ccac690bf916f71a4e3baeb0622dac19aa47e3ca4af858c0800c295581ecfacb"},{"label":"gaussian-proof-identity","locator":"release/evidence/gaussian_proof_identity.json","sha256":"7d2ff9ed4934604eba30f3111a147d7e295fd79302f640f57aacd986a23e243c"},{"label":"gaussian-proof-digest","locator":null,"sha256":"828ed69d9e04784e1f750536ebe8c12f4563dd62a37a85397f2aa08769fce1fd"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_gaussian_integral","profiles":["formal","full"],"refusal_boundary":"Only the exact catalog-declared Gaussian form and canonical rational bounds/tolerance are admitted. Any other form, failed enclosure, checker rejection, or pin/identity mismatch refuses without downgrade.","release_state":"v1.7.3","schema_sha256":"4a43f67d68610c9e7b13f8bc8195c6c3e9ea24493494eb5aca8e768d707adb13","status_classes":["formal-bounded","refused"],"supported_fragment":"Emit a zero-libm formal-bounded Gaussian integral receipt, rerun the pinned checker, or refuse without downgrade. Inputs: expression: Exact canonical exp(-A*(x-mu)^2) expression; all other expressions refuse.; input_lo: Integration lower bound as a canonical rational.; input_hi: Integration upper bound as a canonical rational.; tolerance: Maximum enclosure width as a positive canonical rational."},{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-int-cert","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"int-cert-checker","locator":"jackal_int_cert_check","sha256":"f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617"},{"label":"int-cert-proof-identity","locator":"release/evidence/int_cert_proof_identity_v172.json","sha256":"a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf"},{"label":"int-cert-proof-digest","locator":null,"sha256":"8ce4d8ceeaf2eaa47b97838bfcb2e35880f4fc20fb744c6ef2bf175f0b32f91e"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_integrate_bound_cert","profiles":["formal","full"],"refusal_boundary":"Only the request-bound v1.7.2 composed-integral fragment and canonical bounds/tolerance are admitted. Request-unbound v1.7.0 evidence, unsupported syntax, failed subdivision, checker rejection, or identity mismatch refuses without using the weaker float lane.","release_state":"v1.7.3","schema_sha256":"1229ede1ee518a9b19d3a201268e598fac6fd52fd2eef1bb379f920d6093b9cd","status_classes":["formal-bounded","refused"],"supported_fragment":"Emit a request-bound certified composed definite-integral formal receipt (v1.7.2): the untrusted exact-rational producer mirrors the engine's adaptive subdivision, the pinned Lean-proved jackal_int_cert_check binds the exact raw expression/bounds/tolerance and re-checks the whole subdivision-tree certificate (theorem int_cert_sound), and the receipt is independently re-verified before returning. Request-unbound v1.7.0 receipts are revoked. Certified fragment: num/var/neg/add/sub/mul/div/pow(0..4096)/sin/cos/abs in x. Everything else refuses. The weaker float lane jackal_integrate_bound stays status=bounded and is NOT this tool. Inputs: expression: Integrand in the single variable x, restricted to the certified fragment (num/var/neg/add/sub/mul/div/pow/sin/cos/abs).; input_lo: Integration lower bound as an integer or reduced canonical rational (e.g. '0', '2/3').; input_hi: Integration upper bound as a canonical rational, strictly above input_lo.; tolerance: Maximum enclosure width as a positive canonical rational (e.g. '1/100')."},{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-receipt-registry","identities":[{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"archival-range-checker","locator":"jackal_cert_check_v170","sha256":"05c3518b836f239712f897c483a2ddadad9f544e0887b1b7bb1424a27289de8a"},{"label":"archival-range-coverage-inventory","locator":"formal_coverage_inventory_v170.json","sha256":"18ff7b1d428dbc6f807fd4de27751ba415b33ef0b356088d7fa316ed74bb0ba6"},{"label":"archival-range-proof-identity","locator":"release/evidence/range_proof_identity.json","sha256":"1b2d623904930d748bfbf489637e0e8aa720188e7d68f5250e5bd8f257b89a67"},{"label":"archival-range-proof-digest","locator":null,"sha256":"5d6a1d70289794a7653cd6d854f1239b9f1de6767bad90aab34cc17fb3f7372e"},{"label":"gaussian-checker","locator":"jackal_gaussian_check","sha256":"ccac690bf916f71a4e3baeb0622dac19aa47e3ca4af858c0800c295581ecfacb"},{"label":"gaussian-proof-identity","locator":"release/evidence/gaussian_proof_identity.json","sha256":"7d2ff9ed4934604eba30f3111a147d7e295fd79302f640f57aacd986a23e243c"},{"label":"gaussian-proof-digest","locator":null,"sha256":"828ed69d9e04784e1f750536ebe8c12f4563dd62a37a85397f2aa08769fce1fd"},{"label":"int-cert-checker","locator":"jackal_int_cert_check","sha256":"f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617"},{"label":"int-cert-proof-identity","locator":"release/evidence/int_cert_proof_identity_v172.json","sha256":"a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf"},{"label":"int-cert-proof-digest","locator":null,"sha256":"8ce4d8ceeaf2eaa47b97838bfcb2e35880f4fc20fb744c6ef2bf175f0b32f91e"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_verify_receipt","profiles":["core","formal","full"],"refusal_boundary":"Only closed-registry range/rational, Gaussian, and current request-bound int-cert receipts matching independent caller expectations are replayed. Unknown epochs/variants, copied rather than caller-pinned expectations, revoked int-cert evidence, or checker/pin mismatch refuses.","release_state":"v1.7.3","schema_sha256":"988b83705212769a01eb2e5f5b44322b5728d5fd86eef4f64f7a4a37e378fc1f","status_classes":["verified","refused"],"supported_fragment":"Re-run the matching pinned Lean-proved checker over an embedded certificate using a closed epoch/variant registry: current v1.7.2 range/rational and request-bound int_cert, replay-only v1.5.0 range/rational, or Gaussian v1.5.0. Request-unbound v1.7.0 int_cert receipts refuse. Inputs: receipt: A jackal-formal-receipt-v1 JSON document.; expected_release_epoch: Caller-authorized release epoch, not copied from the receipt.; expected_command: Caller-authorized operation: range-bound-cert or integrate.; expected_expression: Exact raw expression supplied by the caller.; expected_input_lo: Exact raw lower-bound token supplied by the caller.; expected_input_hi: Exact raw upper-bound token supplied by the caller.; expected_tolerance: Exact raw tolerance token; required for Gaussian and int_cert receipts and forbidden for range receipts."},{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-range","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"coverage-inventory","locator":"release/coverage/formal_coverage_inventory.json","sha256":"6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_sqrt_rat_bound","profiles":["formal","full"],"refusal_boundary":"Only the catalog-declared expression and canonical-rational interval fragment is admitted. Unsupported syntax, invalid intervals, missing or mismatched pins, producer/checker rejection, or identity drift refuses; there is no weaker-lane fallback.","release_state":"v1.7.3","schema_sha256":"bc611de421d812aa6d6d8772fb986c17bcde74d7fe70ea31ff22433335d67c6d","status_classes":["formal-bounded","refused"],"supported_fragment":"Emit a formal-bounded pure-Q sqrt(x) enclosure via the untrusted producer + Lean-proved checker. NO libm on the proof-decision path. Admits ONLY the exact form 'sqrt(x)' on a canonical rational interval; every other expression refuses without downgrade. v1.4.0 fragment extension. Inputs: expression: Must equal 'sqrt(x)' after whitespace normalization.; input_lo: Interval lower bound as an integer or reduced rational (e.g. '2', '1/4'); must be >= 0.; input_hi: Interval upper bound (canonical rational, hi >= lo)."},{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-range","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"coverage-inventory","locator":"release/coverage/formal_coverage_inventory.json","sha256":"6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_exp_rat_bound","profiles":["formal","full"],"refusal_boundary":"Only the catalog-declared expression and canonical-rational interval fragment is admitted. Unsupported syntax, invalid intervals, missing or mismatched pins, producer/checker rejection, or identity drift refuses; there is no weaker-lane fallback.","release_state":"v1.7.3","schema_sha256":"9cc7bee07eebc566a9e267ad1b30c10a73cb25c18090ed2f9a3e9ec48550b928","status_classes":["formal-bounded","refused"],"supported_fragment":"Emit a formal-bounded pure-Q exp(x) enclosure via the untrusted producer + Lean-proved checker. NO libm on the proof-decision path. Admits ONLY the exact form 'exp(x)' on a canonical rational interval [lo, hi] (general-sign since v1.5.0); every other expression refuses without downgrade. v1.4.1 fragment extension. Inputs: expression: Must equal 'exp(x)' after whitespace normalization.; input_lo: Interval lower bound as an integer or reduced rational; any sign (general-sign since v1.5.0).; input_hi: Interval upper bound (canonical rational, hi >= lo); the producer picks a Taylor degree with 2*hi <= n+1."},{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-range","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"coverage-inventory","locator":"release/coverage/formal_coverage_inventory.json","sha256":"6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_ln_rat_bound","profiles":["formal","full"],"refusal_boundary":"Only the catalog-declared expression and canonical-rational interval fragment is admitted. Unsupported syntax, invalid intervals, missing or mismatched pins, producer/checker rejection, or identity drift refuses; there is no weaker-lane fallback.","release_state":"v1.7.3","schema_sha256":"16dfb2b64dc9e290e1c062eba898233b04b187f1b244f6cc06433c48d1b7da45","status_classes":["formal-bounded","refused"],"supported_fragment":"Emit a formal-bounded pure-Q ln(x) enclosure via the untrusted producer + Lean-proved checker. NO libm on the proof-decision path. Admits ONLY the exact form 'ln(x)' on a canonical rational interval with lo > 0; every other expression refuses without downgrade. v1.5.0 fragment extension. Inputs: expression: Must equal 'ln(x)' after whitespace normalization.; input_lo: Interval lower bound as an integer or reduced rational; must be > 0.; input_hi: Interval upper bound (canonical rational, hi >= lo)."},{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-range","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"coverage-inventory","locator":"release/coverage/formal_coverage_inventory.json","sha256":"6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_sin_rat_bound","profiles":["formal","full"],"refusal_boundary":"Only the catalog-declared expression and canonical-rational interval fragment is admitted. Unsupported syntax, invalid intervals, missing or mismatched pins, producer/checker rejection, or identity drift refuses; there is no weaker-lane fallback.","release_state":"v1.7.3","schema_sha256":"d578799057048871ff13eca207bda2f9564261fce975801904d8cb34370cc7fe","status_classes":["formal-bounded","refused"],"supported_fragment":"Emit a formal-bounded pure-Q sin(x) enclosure via the untrusted producer + Lean-proved checker. NO libm on the proof-decision path. Admits ONLY the exact form 'sin(x)' on a canonical rational interval whose midpoint m satisfies |m| <= 1; every other expression refuses without downgrade. v1.5.0 fragment extension. Inputs: expression: Must equal 'sin(x)' after whitespace normalization.; input_lo: Interval lower bound as an integer or reduced rational.; input_hi: Interval upper bound (canonical rational, hi >= lo); midpoint (lo+hi)/2 must satisfy |m| <= 1."},{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-range","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"coverage-inventory","locator":"release/coverage/formal_coverage_inventory.json","sha256":"6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_cos_rat_bound","profiles":["formal","full"],"refusal_boundary":"Only the catalog-declared expression and canonical-rational interval fragment is admitted. Unsupported syntax, invalid intervals, missing or mismatched pins, producer/checker rejection, or identity drift refuses; there is no weaker-lane fallback.","release_state":"v1.7.3","schema_sha256":"d0f575ac0dbfe5e64aede075ae30bb1a625f05963e772febca6ac180b29f062b","status_classes":["formal-bounded","refused"],"supported_fragment":"Emit a formal-bounded pure-Q cos(x) enclosure via the untrusted producer + Lean-proved checker. NO libm on the proof-decision path. Admits ONLY the exact form 'cos(x)' on a canonical rational interval whose midpoint m satisfies |m| <= 1; every other expression refuses without downgrade. Shares the sin_rat producer (--op cos). v1.5.0 fragment extension. Inputs: expression: Must equal 'cos(x)' after whitespace normalization.; input_lo: Interval lower bound as an integer or reduced rational.; input_hi: Interval upper bound (canonical rational, hi >= lo); midpoint (lo+hi)/2 must satisfy |m| <= 1."},{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-range","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"coverage-inventory","locator":"release/coverage/formal_coverage_inventory.json","sha256":"6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_atan_rat_bound","profiles":["formal","full"],"refusal_boundary":"Only the catalog-declared expression and canonical-rational interval fragment is admitted. Unsupported syntax, invalid intervals, missing or mismatched pins, producer/checker rejection, or identity drift refuses; there is no weaker-lane fallback.","release_state":"v1.7.3","schema_sha256":"f16e48c7a37e284e124fe20766c7db2b90b4eed8fb748c2efebc9ee7c4b80767","status_classes":["formal-bounded","refused"],"supported_fragment":"Emit a formal-bounded pure-Q atan(x) enclosure via the untrusted producer + Lean-proved checker. NO libm on the proof-decision path. Admits ONLY the exact form 'atan(x)' on a canonical rational interval; every other expression refuses without downgrade. v1.5.0 fragment extension. Inputs: expression: Must equal 'atan(x)' after whitespace normalization.; input_lo: Interval lower bound as an integer or reduced rational.; input_hi: Interval upper bound (canonical rational, hi >= lo)."},{"assurance_classes":["formal-bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"lean-range","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"coverage-inventory","locator":"release/coverage/formal_coverage_inventory.json","sha256":"6373641cd7833bb46a08f44acf683a119e0a637c8acb88d22797b81188d896b6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_tanh_rat_bound","profiles":["formal","full"],"refusal_boundary":"Only the catalog-declared expression and canonical-rational interval fragment is admitted. Unsupported syntax, invalid intervals, missing or mismatched pins, producer/checker rejection, or identity drift refuses; there is no weaker-lane fallback.","release_state":"v1.7.3","schema_sha256":"6f9b24d732cc6fc2b0ad9ef7d51fa8cd88f2a344b3152c54f288f6a25a8fe05a","status_classes":["formal-bounded","refused"],"supported_fragment":"Emit a formal-bounded pure-Q tanh enclosure via the untrusted composite producer + Lean-proved checker. NO libm on the proof-decision path. tanh is not an engine grammar token: admits ONLY the literal composite expression '1-2/(exp(2*x)+1)' (= tanh(x) mathematically) on a canonical rational interval with |lo|,|hi| <= 20; every other expression refuses without downgrade. v1.5.0 fragment extension. Inputs: expression: Must equal '1-2/(exp(2*x)+1)' after whitespace normalization (the frozen tanh-defining composite).; input_lo: Interval lower bound as an integer or reduced rational; |lo| <= 20.; input_hi: Interval upper bound (canonical rational, hi >= lo); |hi| <= 20."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_exact","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"42319cf6d3e3f7a8da025e880b8dfc02eb2c6e3128f8b93c11f41545d8bf1d8d","status_classes":["exact","refused"],"supported_fragment":"Exact big-rational arithmetic (status=exact). NOT formal: computationally exact, outside the Lean certificate chain. Inputs: expression: Integers, decimals, + - * / ^ (integer exponents), parentheses. Everything else refuses."},{"assurance_classes":["estimated"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_evaluate","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"5baa3a344309d714a8b66c9fb4a14a97c8182b3cfe1326faa2e9967840eee17c","status_classes":["estimated","refused"],"supported_fragment":"IEEE f64 expression evaluation (status=estimated). A plain number, honestly labeled: no bound, no proof. Inputs: expression: Full expression grammar incl. transcendental functions and constants."},{"assurance_classes":["checked"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_diff","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"605972a25cde65779e921b8a27b7f1a0c19d540f5330791ec0fb571cccf858ad","status_classes":["checked","refused"],"supported_fragment":"Symbolic d/dx with numeric self-verification (status=checked). Sampled agreement is a check, not a proof of identity. Inputs: expression: Expression in x; non-differentiable functions fail closed."},{"assurance_classes":["estimated"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_integrate","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"6d05a705ff580a831f75cbcd2079fe88c3b5d9c858bc6b0c3182ec55aa27cd27","status_classes":["estimated","refused"],"supported_fragment":"Fixed-grid Simpson + Richardson error estimate (status=estimated). Grid-limited: NOT a bound. Inputs: expression: Integrand in x.; input_lo: Lower limit.; input_hi: Upper limit.; panels: Panel count (e.g. '200')."},{"assurance_classes":["estimated"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_integrate_adaptive","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"174132e83bff6d18972cf5d88b072b12d7102cad8fdf05197165c64bfac4fea1","status_classes":["estimated","refused"],"supported_fragment":"Adaptive Simpson with refusal semantics (status=estimated). Refuses when unconverged; agreement is still not a bound. Inputs: expression: Integrand in x.; input_lo: Lower limit.; input_hi: Upper limit.; tolerance: Local tolerance (e.g. '1e-9')."},{"assurance_classes":["bounded"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_integrate_bound","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"9932617903499a2ed2baa4ac213f061f422bd8f2e60bca5bf05225a9f9f788a0","status_classes":["bounded","refused"],"supported_fragment":"Certified interval enclosure of an integral (status=bounded). CONDITIONAL on the stated f64/libm rounding model; implementation campaign-tested, NOT mechanized. Never labeled formal. For a Lean-checked formal-bounded composed enclosure over the certified fragment, use jackal_integrate_bound_cert instead. Inputs: expression: Integrand in x.; input_lo: Lower limit.; input_hi: Upper limit.; tolerance: Max enclosure width (e.g. '1e-6')."},{"assurance_classes":["estimated"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_solve","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"caf1f55ca8ad1fef2e0fb4f5f5bb600edb147f257427de650335500979c13501","status_classes":["estimated","refused"],"supported_fragment":"Bisection root with residual + first-order conditioning diagnostics (status=estimated). Inputs: expression: f(x) whose root is sought.; input_lo: Bracket lower bound (sign change required).; input_hi: Bracket upper bound."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_canon","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"ea4ca54f74334d678c3daf9c6686fca186041d27bb2f02dabca21a7747e7d416","status_classes":["exact","refused"],"supported_fragment":"Canonical s-expression + SHA-256 of any parsed expression (status=exact). NOT formal: exact computation outside the Lean certificate chain; no exact-cert emitted. Inputs: expression: Any expression the engine grammar parses."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"exact-cert-verifier","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_poly_canon","profiles":["full"],"refusal_boundary":"Only the catalog-declared exact fragment and budgets are admitted. Invalid grammar, side conditions, limits, certificate mismatch, or independent verifier rejection refuses; exact is not relabeled formal.","release_state":"v1.7.3","schema_sha256":"199fd63f51398c6e883a407d8e2435e42903e9c76f0227d815f3456b3568d9bc","status_classes":["exact","refused"],"supported_fragment":"Dense Q[x] canonical form, degree <= 64 (status=exact). Emits a jackal-exact-cert-v1 certificate independently re-checkable by tools/exact_verify.py. NOT formal. Inputs: expression: Polynomial expression in x within the poly fragment."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"exact-cert-verifier","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_poly_eq","profiles":["full"],"refusal_boundary":"Only the catalog-declared exact fragment and budgets are admitted. Invalid grammar, side conditions, limits, certificate mismatch, or independent verifier rejection refuses; exact is not relabeled formal.","release_state":"v1.7.3","schema_sha256":"76d9390f705758c6ec2d5fe77cd6a1391908f93ef218ebfd5111c067dc32a819","status_classes":["exact","refused"],"supported_fragment":"Decidable polynomial identity over Q[x] (status=exact). Emits a jackal-exact-cert-v1 certificate independently re-checkable by tools/exact_verify.py. NOT formal. Inputs: lhs: Left polynomial expression in x.; rhs: Right polynomial expression in x."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"exact-cert-verifier","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_poly_gcd","profiles":["full"],"refusal_boundary":"Only the catalog-declared exact fragment and budgets are admitted. Invalid grammar, side conditions, limits, certificate mismatch, or independent verifier rejection refuses; exact is not relabeled formal.","release_state":"v1.7.3","schema_sha256":"dfff2a285e732d52520ea6fe5efabdab5bd6d956c224f30d163ccbbdbd406a5c","status_classes":["exact","refused"],"supported_fragment":"Monic polynomial gcd over Q[x] via Euclid (status=exact). Emits a jackal-exact-cert-v1 certificate independently re-checkable by tools/exact_verify.py. NOT formal. Inputs: lhs: Left polynomial expression in x.; rhs: Right polynomial expression in x."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"exact-cert-verifier","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_ratfunc_canon","profiles":["full"],"refusal_boundary":"Only the catalog-declared exact fragment and budgets are admitted. Invalid grammar, side conditions, limits, certificate mismatch, or independent verifier rejection refuses; exact is not relabeled formal.","release_state":"v1.7.3","schema_sha256":"3d4c7d73d13aa88606e9e005b7731c19ac63f9fe559b38e6ff777f979afd3b40","status_classes":["exact","refused"],"supported_fragment":"Rational-function canonical form P/Q, gcd-reduced, monic denominator, explicit denominator-nonzero side condition (status=exact). Emits a jackal-exact-cert-v1 certificate independently re-checkable by tools/exact_verify.py. NOT formal. Inputs: expression: Rational-function expression in x within the ratfunc fragment."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"exact-cert-verifier","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_roots_isolate","profiles":["full"],"refusal_boundary":"Only the catalog-declared exact fragment and budgets are admitted. Invalid grammar, side conditions, limits, certificate mismatch, or independent verifier rejection refuses; exact is not relabeled formal.","release_state":"v1.7.3","schema_sha256":"ea98289e6b6319b7c0b3e6e9832e07ff4a9dd06be1d7862956c50df6e1785978","status_classes":["exact","refused"],"supported_fragment":"Sturm-sequence isolation of all distinct real roots (status=exact). Emits a jackal-exact-cert-v1 certificate independently re-checkable by tools/exact_verify.py. NOT formal. Inputs: expression: Polynomial expression in x within the poly fragment."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_alg_sign","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"b4ad3c632887bc8875f8334dea3391017d73f30fa4895d988062882e89c2c1a5","status_classes":["exact","refused"],"supported_fragment":"Exact sign of a Q[x] polynomial at a rational point (status=exact). NOT formal; no exact-cert emitted. Inputs: expression: Polynomial expression in x within the poly fragment.; point: Rational evaluation point (e.g. '3/2')."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_alg_cmp","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"bfea42d3f97e6f0403d11155d667dfec7000719e2aa3af67c8897989a9e2eee4","status_classes":["exact","refused"],"supported_fragment":"Order decision between two isolated real algebraic numbers (status=exact). NOT formal; no exact-cert emitted. Inputs: p: First defining polynomial in x.; a1: First isolating interval lower bound (rational).; b1: First isolating interval upper bound (rational).; q: Second defining polynomial in x.; a2: Second isolating interval lower bound (rational).; b2: Second isolating interval upper bound (rational)."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"exact-cert-verifier","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_xgcd","profiles":["full"],"refusal_boundary":"Only the catalog-declared exact fragment and budgets are admitted. Invalid grammar, side conditions, limits, certificate mismatch, or independent verifier rejection refuses; exact is not relabeled formal.","release_state":"v1.7.3","schema_sha256":"1c7fb606e43e71eeb4ab288c08ea4a6c89efd95beb3f802e084d5d3c1a3beaf6","status_classes":["exact","refused"],"supported_fragment":"Extended gcd with Bezout certificate (status=exact). Emits a jackal-exact-cert-v1 certificate independently re-checkable by tools/exact_verify.py. NOT formal. Inputs: a: First integer.; b: Second integer."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"exact-cert-verifier","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_mod_pow","profiles":["full"],"refusal_boundary":"Only the catalog-declared exact fragment and budgets are admitted. Invalid grammar, side conditions, limits, certificate mismatch, or independent verifier rejection refuses; exact is not relabeled formal.","release_state":"v1.7.3","schema_sha256":"b3edc74b3901989b9248f8b89feaed2cc396e218abf9ccfca7ab53a99a9c7bc1","status_classes":["exact","refused"],"supported_fragment":"Modular exponentiation via square-and-multiply (status=exact). Emits a jackal-exact-cert-v1 certificate independently re-checkable by tools/exact_verify.py. NOT formal. Inputs: base: Base integer.; exp: Nonnegative exponent integer.; mod: Modulus integer >= 1."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"exact-cert-verifier","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_mod_inv","profiles":["full"],"refusal_boundary":"Only the catalog-declared exact fragment and budgets are admitted. Invalid grammar, side conditions, limits, certificate mismatch, or independent verifier rejection refuses; exact is not relabeled formal.","release_state":"v1.7.3","schema_sha256":"d0826f19759afacd20d4a5f0d6704a52bce158c6950bb852a5e0774886a4abd7","status_classes":["exact","refused"],"supported_fragment":"Modular inverse with product certificate (status=exact). Emits a jackal-exact-cert-v1 certificate independently re-checkable by tools/exact_verify.py. NOT formal. Inputs: a: Integer to invert.; m: Modulus integer >= 2; gcd(a, m) must be 1."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"exact-cert-verifier","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_crt","profiles":["full"],"refusal_boundary":"Only the catalog-declared exact fragment and budgets are admitted. Invalid grammar, side conditions, limits, certificate mismatch, or independent verifier rejection refuses; exact is not relabeled formal.","release_state":"v1.7.3","schema_sha256":"d44aebbbcb3b00ad0d192581533c5cfdb164e26431f3ed850406c8ecfa5044b4","status_classes":["exact","refused"],"supported_fragment":"Chinese remainder reconstruction over pairwise-coprime moduli, up to 16 pairs (status=exact). Emits a jackal-exact-cert-v1 certificate independently re-checkable by tools/exact_verify.py. NOT formal. Inputs: args: Space-separated residue/modulus pairs: 'r1 m1 r2 m2 [...]' (e.g. '2 3 3 5 2 7')."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"runtime-only","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_divides","profiles":["full"],"refusal_boundary":"Only the catalog-declared engine command, grammar, side conditions, and budgets are admitted. Parse/domain/validation/non-convergence failures and unsupported fragments return refused; no other lane is substituted and the returned status is not promoted.","release_state":"v1.7.3","schema_sha256":"1191b2b4ce3a9a5c3a1dafb0917fad46ca9c3375c73f4ae59ff7a389d513adb4","status_classes":["exact","refused"],"supported_fragment":"Exact divisibility decision (status=exact). NOT formal; no exact-cert emitted. Inputs: a: Candidate divisor integer.; b: Dividend integer."},{"assurance_classes":["exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"exact-cert-verifier","identities":[{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_prime_cert","profiles":["full"],"refusal_boundary":"Only the catalog-declared exact fragment and budgets are admitted. Invalid grammar, side conditions, limits, certificate mismatch, or independent verifier rejection refuses; exact is not relabeled formal.","release_state":"v1.7.3","schema_sha256":"3ff03881b24f8fd94db68b210874010e66ce0e3d32666d98a21c1bc40ccc5646","status_classes":["exact","refused"],"supported_fragment":"Pratt primality certificate or composite divisor witness, budgeted and fail-closed (status=exact; n <= 10^60, Pratt tree depth <= 64, nodes <= 512). Emits a jackal-exact-cert-v1 certificate independently re-checkable by tools/exact_verify.py. NOT formal. Inputs: n: Integer >= 2, at most 61 digits."},{"assurance_classes":["estimated","model-based","checked","bounded","formal-bounded","exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"claim-router","identities":[{"label":"claim_kernel","locator":"tools/claim_kernel.py","sha256":"77b0f85ad5fb7214f88898b60ea29ea9fd7be740c38b655388444e6e5181f348"},{"label":"claim_router","locator":"tools/claim_router.py","sha256":"02328cf177a0423bdc5cbca6ec0ea946bb0679bbd3dc6c24140d32598e575afb"},{"label":"evaluator","locator":"jackal-native","sha256":"f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655"},{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"archival-range-checker","locator":"jackal_cert_check_v170","sha256":"05c3518b836f239712f897c483a2ddadad9f544e0887b1b7bb1424a27289de8a"},{"label":"archival-range-coverage-inventory","locator":"formal_coverage_inventory_v170.json","sha256":"18ff7b1d428dbc6f807fd4de27751ba415b33ef0b356088d7fa316ed74bb0ba6"},{"label":"archival-range-proof-identity","locator":"release/evidence/range_proof_identity.json","sha256":"1b2d623904930d748bfbf489637e0e8aa720188e7d68f5250e5bd8f257b89a67"},{"label":"archival-range-proof-digest","locator":null,"sha256":"5d6a1d70289794a7653cd6d854f1239b9f1de6767bad90aab34cc17fb3f7372e"},{"label":"gaussian-checker","locator":"jackal_gaussian_check","sha256":"ccac690bf916f71a4e3baeb0622dac19aa47e3ca4af858c0800c295581ecfacb"},{"label":"gaussian-proof-identity","locator":"release/evidence/gaussian_proof_identity.json","sha256":"7d2ff9ed4934604eba30f3111a147d7e295fd79302f640f57aacd986a23e243c"},{"label":"gaussian-proof-digest","locator":null,"sha256":"828ed69d9e04784e1f750536ebe8c12f4563dd62a37a85397f2aa08769fce1fd"},{"label":"int-cert-checker","locator":"jackal_int_cert_check","sha256":"f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617"},{"label":"int-cert-proof-identity","locator":"release/evidence/int_cert_proof_identity_v172.json","sha256":"a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf"},{"label":"int-cert-proof-digest","locator":null,"sha256":"8ce4d8ceeaf2eaa47b97838bfcb2e35880f4fc20fb744c6ef2bf175f0b32f91e"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"},{"label":"claim_inference_registry","locator":"release/claim/inference_registry_v1.json","sha256":"c70b33d5aee8071b5125e6a5f8ffe5226fc22a137d920c17d9b3463968be13f0"},{"label":"claim_unit_registry","locator":"release/claim/unit_registry_v1.json","sha256":"d2d30dfe2a74d58a5ef31b551ea628106390bfccd72ad34d1cb37381c58d114c"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_claim","profiles":["core","formal","full"],"refusal_boundary":"Only jackal-claim-request-v1 and its closed step vocabulary are compiled. Policy, identity, schema, route, or assurance failures refuse; fallback is off by default and any caller-enabled fallback remains explicit in the route trace rather than silently changing assurance.","release_state":"v1.7.3","schema_sha256":"4d2703ff772433f39ac8b6555727e856685b0e5042b9c2cc8fcaa4402d6b9153","status_classes":["ok","refused"],"supported_fragment":"Compile a structured jackal-claim-request-v1 into a canonical, content-addressed jackal-claim-bundle-v1 evidence graph through the deterministic policy router. Routes through exact, current/archival range-family, Gaussian, request-bound composed-integral, machine, and unit lanes; emits a route trace naming candidates and refusal reasons; and refuses rather than silently downgrading (allow_fallback defaults false). The bundle is independently replayable via jackal_verify_bundle. Inputs: request: jackal-claim-request-v1 object: {schema, steps:[{id, op, ...}], root, policy?, nonce?, emitted_at_unix?, max_age_seconds?, expires_at_unix?}. Step ops: input, exact, enclose, gaussian, integrate_cert, machine, interval_add/sub/mul/div, threshold, decision, convert, and, model, passthrough, attach."},{"assurance_classes":["estimated","model-based","checked","bounded","formal-bounded","exact"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"claim-verifier","identities":[{"label":"claim_verifier","locator":"tools/claim_bundle_verify.py","sha256":"e0fcb9540c730bd9bb492b528ed42d29d49fc775b3aa0f9b831b6264fd68fd22"},{"label":"checker","locator":"jackal_cert_check","sha256":"f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545"},{"label":"range-proof-identity","locator":"release/evidence/range_proof_identity_v172.json","sha256":"84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c"},{"label":"range-proof-digest","locator":null,"sha256":"7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d"},{"label":"archival-range-checker","locator":"jackal_cert_check_v170","sha256":"05c3518b836f239712f897c483a2ddadad9f544e0887b1b7bb1424a27289de8a"},{"label":"archival-range-coverage-inventory","locator":"formal_coverage_inventory_v170.json","sha256":"18ff7b1d428dbc6f807fd4de27751ba415b33ef0b356088d7fa316ed74bb0ba6"},{"label":"archival-range-proof-identity","locator":"release/evidence/range_proof_identity.json","sha256":"1b2d623904930d748bfbf489637e0e8aa720188e7d68f5250e5bd8f257b89a67"},{"label":"archival-range-proof-digest","locator":null,"sha256":"5d6a1d70289794a7653cd6d854f1239b9f1de6767bad90aab34cc17fb3f7372e"},{"label":"gaussian-checker","locator":"jackal_gaussian_check","sha256":"ccac690bf916f71a4e3baeb0622dac19aa47e3ca4af858c0800c295581ecfacb"},{"label":"gaussian-proof-identity","locator":"release/evidence/gaussian_proof_identity.json","sha256":"7d2ff9ed4934604eba30f3111a147d7e295fd79302f640f57aacd986a23e243c"},{"label":"gaussian-proof-digest","locator":null,"sha256":"828ed69d9e04784e1f750536ebe8c12f4563dd62a37a85397f2aa08769fce1fd"},{"label":"int-cert-checker","locator":"jackal_int_cert_check","sha256":"f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617"},{"label":"int-cert-proof-identity","locator":"release/evidence/int_cert_proof_identity_v172.json","sha256":"a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf"},{"label":"int-cert-proof-digest","locator":null,"sha256":"8ce4d8ceeaf2eaa47b97838bfcb2e35880f4fc20fb744c6ef2bf175f0b32f91e"},{"label":"exact_verifier","locator":"tools/exact_verify.py","sha256":"2c07e6257ce1524de3e31374371c6d5859dce710767156de2566ec77fa1883a7"},{"label":"claim_inference_registry","locator":"release/claim/inference_registry_v1.json","sha256":"c70b33d5aee8071b5125e6a5f8ffe5226fc22a137d920c17d9b3463968be13f0"},{"label":"claim_unit_registry","locator":"release/claim/unit_registry_v1.json","sha256":"d2d30dfe2a74d58a5ef31b551ea628106390bfccd72ad34d1cb37381c58d114c"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_verify_bundle","profiles":["core","formal","full"],"refusal_boundary":"Only canonical bundles matching separately caller-pinned epoch, policy, root proposition, time, and nonce are replayed. Semantic, graph, freshness, evidence, checker, or pin ambiguity returns refused or indeterminate exactly as declared; it is never converted to success.","release_state":"v1.7.3","schema_sha256":"9e3acec17637a352bc88e3b08e8679a34f1efae3539fe1cce05646212be4cb5f","status_classes":["verified","refused","indeterminate"],"supported_fragment":"Independently replay a jackal-claim-bundle-v1 against caller-pinned expectations. The standalone dependency-free verifier recomputes every canonical byte and hash, revalidates the DAG, selects only the closed current/archival range, Gaussian, or current request-bound int-cert checker/proof tuple, re-runs embedded evidence, recomputes machine/unit mathematics, re-evaluates every inference rule and assurance-axis propagation, enforces consequence-class floors and policy, and recomputes the deterministic rendering. Returns verified | refused | indeterminate with exact reasons — never a generic green badge. Inputs: bundle: The jackal-claim-bundle-v1 object to replay.; expected_release_epoch: Caller-pinned epoch (e.g. 'v1.6.0'); never copied from the bundle.; expected_policy_sha256: Caller-pinned SHA-256 of the canonical policy bytes.; expected_root_proposition: Caller-pinned canonical root proposition IR object.; verification_time_unix: Caller-supplied verification time (unix seconds) for freshness/expiry checks.; expected_nonce: Caller nonce; the bundle root must bind exactly this nonce."},{"assurance_classes":["structural-exact"],"consequence_ceiling":"informational","containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"structural-checker","identities":[{"label":"domain_pack_registry","locator":"domain_packs/registry_v1.json","sha256":"1a3b2c95dcdc7c7337fbe0ecb34043b70c3697752d6dc585f45f3c7d4f1b0706"},{"label":"domain_pack_verifier","locator":"tools/domain_pack_verify.py","sha256":"22984f511208af2d7a318f1a43306d95a4b0f61876d8b44f34f39a2ded6d573d"},{"label":"domain_pack_test_exists_checker","locator":"tools/test_exists_verify.py","sha256":"598cb99e1eb70c9410ca87345efee346f73e43aaf3625427dca17ea04231caea"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_test_exists","profiles":["full"],"refusal_boundary":"Only byte-exact source/citation structure described by the schema is accepted after independent file-byte recomputation. Path traversal, malformed symbols/hashes, missing text/declarations, or checker mismatch refuses; the result never asserts test execution or correctness.","release_state":"v1.7.3","schema_sha256":"9685ecf3bcf78a3a26916455cc528cdeac137420e8694447e2865b33890f9d46","status_classes":["structural-exact","refused"],"supported_fragment":"Domain pack jackal.programming.source, operation programming.source.test_exists.v1, routed through pack-route. States one byte-exact STRUCTURAL fact: a declaration-shaped occurrence of `symbol` exists at `declaration_line` in a file whose content hash is exactly `file_sha256`, and the file contains exactly `declaration_count` such occurrences. The engine validates the canonical FORM; the manifest-pinned independent checker tools/test_exists_verify.py then recomputes every claimed field from the real bytes on disk and only an ACCEPT verdict returns success, so a misstated hash, line or count refuses instead of minting a certificate. ASSURANCE ceiling exact; CONSEQUENCE ceiling informational, and the second does not rise with the first: a test-exists-cert is NEVER evidence that the code under test is correct, that the test executes, that it is collected by any runner, or that it asserts anything at all. NOT formal: no Lean checker and no theorem are involved. Do not cite this certificate in support of a correctness claim. Inputs: file_path: Repository-relative path token; absolute paths and parent traversal refuse `prog-path`.; file_sha256: Exactly 64 lowercase hex characters; the checker recomputes it from the file bytes.; symbol: Identifier of the declaration; a non-identifier refuses `prog-symbol`.; declaration_line: 1-based line of the declaration as a canonical positive integer.; declaration_count: Total declaration-shaped occurrences of `symbol` in the file; `0` refuses `prog-absent`."},{"assurance_classes":["structural-exact"],"consequence_ceiling":"informational","containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"structural-checker","identities":[{"label":"domain_pack_registry","locator":"domain_packs/registry_v1.json","sha256":"1a3b2c95dcdc7c7337fbe0ecb34043b70c3697752d6dc585f45f3c7d4f1b0706"},{"label":"domain_pack_verifier","locator":"tools/domain_pack_verify.py","sha256":"22984f511208af2d7a318f1a43306d95a4b0f61876d8b44f34f39a2ded6d573d"},{"label":"domain_pack_test_exists_checker","locator":"tools/test_exists_verify.py","sha256":"598cb99e1eb70c9410ca87345efee346f73e43aaf3625427dca17ea04231caea"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_claim_cites_test","profiles":["full"],"refusal_boundary":"Only byte-exact source/citation structure described by the schema is accepted after independent file-byte recomputation. Path traversal, malformed symbols/hashes, missing text/declarations, or checker mismatch refuses; the result never asserts test execution or correctness.","release_state":"v1.7.3","schema_sha256":"69a533155bde04bfd5dedd43e9d63acd02f3e2773757936e9e1714458e20abd4","status_classes":["structural-exact","refused"],"supported_fragment":"Domain pack jackal.programming.source, operation programming.source.claim_cites_test.v1, routed through pack-route. Resolves a citation: `claim_text` occurs verbatim in the document at `doc_path`/`doc_sha256`, and `symbol` has a declaration-shaped occurrence in the cited test file at `test_path`/`test_sha256`. Resolution is all this establishes. It does NOT establish that the cited test covers, exercises or supports the claim — the cited test may check something entirely different, which is the exact defect this operation exists to bound rather than hide. The manifest-pinned tools/test_exists_verify.py re-runs over the certificate and only ACCEPT returns success, so a claim text absent from the document or a dangling citation refuses. ASSURANCE ceiling exact; CONSEQUENCE ceiling informational, and the second does not rise with the first. NOT formal: no Lean checker and no theorem are involved. Inputs: doc_path: Repository-relative path of the document making the claim.; doc_sha256: Exactly 64 lowercase hex characters for the document bytes.; claim_text: The claim sentence, verbatim, 1..2048 bytes; it must occur in the document.; test_path: Repository-relative path of the cited test file.; test_sha256: Exactly 64 lowercase hex characters for the cited test file bytes.; symbol: Identifier the citation resolves to inside the cited test file."},{"assurance_classes":["exact"],"consequence_ceiling":"decision-boundary","containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"decision-checker","identities":[{"label":"domain_pack_registry","locator":"domain_packs/registry_v1.json","sha256":"1a3b2c95dcdc7c7337fbe0ecb34043b70c3697752d6dc585f45f3c7d4f1b0706"},{"label":"domain_pack_verifier","locator":"tools/domain_pack_verify.py","sha256":"22984f511208af2d7a318f1a43306d95a4b0f61876d8b44f34f39a2ded6d573d"},{"label":"domain_pack_decision_checker","locator":"tools/decision_verify.py","sha256":"f1ad7c9fbd4c1d899dbb4bebabbbeb97e97a56bd4b279ad7d8ec3722bf12e0f6"},{"label":"claim_unit_registry","locator":"release/claim/unit_registry_v1.json","sha256":"d2d30dfe2a74d58a5ef31b551ea628106390bfccd72ad34d1cb37381c58d114c"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_decision_rank","profiles":["full"],"refusal_boundary":"Only 2..6 schema-valid options under a caller-declared admissible numeric criterion are ranked. Invalid shape/sense/unit, value-judgment criteria, zero top margin, or checker mismatch refuses; caller values are not treated as measurements or confidence intervals.","release_state":"v1.7.3","schema_sha256":"843fe08464d40d97094eeb6424c0c29a3e2617172ba56262ca4122468a8bb92f","status_classes":["exact","refused"],"supported_fragment":"Domain pack jackal.decision.matrix, operation decision.matrix.rank.v1, routed through pack-route. Orders 2..6 labelled options by a caller-declared numeric criterion and emits a jackal-decision-cert-v1 naming the selected option, the runner-up and the exact margin; the manifest-pinned tools/decision_verify.py recomputes the whole ordering from the certificate's own option values and only ACCEPT returns success. Criterion admissibility is decided by the engine against a fixed word list, so a bare value judgment refuses `decision-value-judgment`; a top-two tie refuses `decision-margin-zero`. ASSURANCE ceiling exact; CONSEQUENCE ceiling decision-boundary. The arithmetic is exact and the choice of criterion is not: the declared criterion and the declared option values remain the caller's, this result is not a claim that the criterion is the right one, the values are not measurements, and the margin is not a confidence interval. For the closed-unit lane that additionally requires a declared unit, use jackal_decision_rank_v2. NOT formal: no Lean checker and no theorem are involved. Inputs: decision_id: Caller's identifier for this decision, bound into the certificate. Bytes are restricted to [A-Za-z0-9_]; anything else refuses `prog-symbol`.; criterion: The declared numeric criterion (e.g. 'latency_ms'); a value judgment refuses `decision-value-judgment`.; sense: Exactly 'min' or 'max'; anything else refuses `decision-sense-unknown`.; options: 2..6 whitespace-separated `label value` pairs, e.g. 'a 10 b 20 c 30'. Values are canonical integers. A label containing whitespace shifts the pairing and refuses `pack-args-shape`; it is never silently re-paired."},{"assurance_classes":["exact"],"consequence_ceiling":"decision-boundary","containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"decision-checker","identities":[{"label":"domain_pack_registry","locator":"domain_packs/registry_v1.json","sha256":"1a3b2c95dcdc7c7337fbe0ecb34043b70c3697752d6dc585f45f3c7d4f1b0706"},{"label":"domain_pack_verifier","locator":"tools/domain_pack_verify.py","sha256":"22984f511208af2d7a318f1a43306d95a4b0f61876d8b44f34f39a2ded6d573d"},{"label":"domain_pack_decision_checker","locator":"tools/decision_verify.py","sha256":"f1ad7c9fbd4c1d899dbb4bebabbbeb97e97a56bd4b279ad7d8ec3722bf12e0f6"},{"label":"claim_unit_registry","locator":"release/claim/unit_registry_v1.json","sha256":"d2d30dfe2a74d58a5ef31b551ea628106390bfccd72ad34d1cb37381c58d114c"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_decision_rank_v2","profiles":["full"],"refusal_boundary":"Only 2..6 schema-valid options under a caller-declared admissible numeric criterion are ranked. Invalid shape/sense/unit, value-judgment criteria, zero top margin, or checker mismatch refuses; caller values are not treated as measurements or confidence intervals.","release_state":"v1.7.3","schema_sha256":"cdc8abba09426977a5319b52410204f21e2e1eb7b38a9de31de751fcb862e78a","status_classes":["exact","refused"],"supported_fragment":"Domain pack jackal.decision.matrix, operation decision.matrix.rank.v2, routed through pack-route. Same deterministic ordering as jackal_decision_rank, plus a REQUIRED declared unit drawn from a closed vocabulary: the 65 canonical ids of release/claim/unit_registry_v1.json excluding the dimensionless identity `one` (66 ids in the registry, `one` is not admitted here). Matching is exact-token and case-sensitive; an alias, a spelled-out name such as 'millisecond', a different case, or the dimensionless identity all refuse `decision-unit-unknown`, and an empty unit refuses `decision-unit-missing`. Emits jackal-decision-cert-v2, re-checked by the manifest-pinned tools/decision_verify.py; only ACCEPT returns success. ASSURANCE ceiling exact; CONSEQUENCE ceiling decision-boundary. Honest residual: a declared unit is NOT a measurement. The closed vocabulary forces the caller to name a dimension, and nothing more — a value-judgment criterion that survives the engine's word list is still accepted when a real unit is declared (`most_elegant` in `ms` ranks), and the values themselves remain caller-declared. NOT formal: no Lean checker and no theorem are involved. Inputs: decision_id: Caller's identifier for this decision, bound into the certificate. Bytes are restricted to [A-Za-z0-9_]; anything else refuses `prog-symbol`.; criterion: The declared numeric criterion; a value judgment refuses `decision-value-judgment` even with an admissible unit.; unit: One canonical unit id from release/claim/unit_registry_v1.json, excluding `one`. Exact token, case-sensitive: 'ms' is admitted, 'millisecond' and 'MS' refuse `decision-unit-unknown`.; sense: Exactly 'min' or 'max'; anything else refuses `decision-sense-unknown`.; options: 2..6 whitespace-separated `label value` pairs, e.g. 'a 10 b 20 c 30'. Values are canonical integers. A label containing whitespace shifts the pairing and refuses `pack-args-shape`; it is never silently re-paired."},{"assurance_classes":["verified-program-evidence"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"program-verifier","identities":[{"label":"anubis_program_verifier","locator":"tools/anubis_program_verify.py","sha256":"4b80e29bdffc0737f05a6e215fce8cce3b6b828c24afbf55c68443399e5119dc"},{"label":"anubis_program_policy","locator":"release/program/inventory_safe_v1.json","sha256":"361979bf89b7c71a4b2c692d64756548833a2c363c269511b037726cab3ebacb"},{"label":"program-compatibility-floor","locator":"release/compat/v173_floor.json","sha256":"5b4e78e1f2b3e1ed7d0459a12f229ffe27886c179198a656a5a9dc5343f8b45e"},{"label":"approved_program_compiler","locator":"release/compat/v173_floor.json#approved_check_compiler_sha256","sha256":"0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_anubis_check_program","profiles":["full"],"refusal_boundary":"Only caller-pinned Safe-source anubis.program-evidence.v3 under inventory-safe-v1 is admitted. Any source/compiler/artifact/policy mismatch, roster or proof-path discrepancy, replay failure, symlink/path violation, or unsupported profile refuses. Artifacts are never executed, and success does not establish construct totality, source-to-VC, SMT-to-CNF, source-native refinement, runtime behavior, or universal soundness.","release_state":"v1.7.3","schema_sha256":"177cfcf9dfcd6bbc6e657eb83b88f04bfedaa0f28f81307077a776e2052df1e2","status_classes":["verified-program-evidence","refused"],"supported_fragment":"Run a caller-pinned Anubis compiler only as `build --evidence` in Safe mode, never execute the compiled artifact, then independently close and replay the resulting strict anubis.program-evidence.v3 package under inventory-safe-v1. Emits only verified-program-evidence or a named refusal. This profile checks producer-attested function/policy inventories but explicitly does not establish policy-construct totality, source-to-VC proof, SMT-to-CNF proof, source-native refinement, runtime behavior, or universal soundness. Inputs: source_path: Local regular non-symlink Anubis source path.; anubis_bin: Local regular non-symlink Anubis executable; caller-pinned and hashed before/after build.; expected_source_sha256: Caller-pinned exact source SHA-256.; expected_compiler_sha256: Caller-pinned approved Anubis executable SHA-256.; expected_policy_sha256: Caller-pinned inventory-safe-v1 policy digest.; verification_time_unix: Caller-supplied Unix verification time bound into the receipt.; profile: Must be inventory-safe-v1; contracted-safe-v1 refuses.; nonce: Caller nonce bound into the receipt.; out_root: New local output root; existing paths refuse."},{"assurance_classes":["verified-program-evidence"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"program-verifier","identities":[{"label":"anubis_program_verifier","locator":"tools/anubis_program_verify.py","sha256":"4b80e29bdffc0737f05a6e215fce8cce3b6b828c24afbf55c68443399e5119dc"},{"label":"anubis_program_policy","locator":"release/program/inventory_safe_v1.json","sha256":"361979bf89b7c71a4b2c692d64756548833a2c363c269511b037726cab3ebacb"},{"label":"program-compatibility-floor","locator":"release/compat/v173_floor.json","sha256":"5b4e78e1f2b3e1ed7d0459a12f229ffe27886c179198a656a5a9dc5343f8b45e"},{"label":"approved_program_compiler","locator":"release/compat/v173_floor.json#approved_check_compiler_sha256","sha256":"0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_anubis_verify_program","profiles":["full"],"refusal_boundary":"Only caller-pinned Safe-source anubis.program-evidence.v3 under inventory-safe-v1 is admitted. Any source/compiler/artifact/policy mismatch, roster or proof-path discrepancy, replay failure, symlink/path violation, or unsupported profile refuses. Artifacts are never executed, and success does not establish construct totality, source-to-VC, SMT-to-CNF, source-native refinement, runtime behavior, or universal soundness.","release_state":"v1.7.3","schema_sha256":"45c7aae87c329925f6bd6b9100efc7d099d70fcaeaba915ee59937f019e1153d","status_classes":["verified-program-evidence","refused"],"supported_fragment":"Independently verify caller-selected Anubis Safe source and evidence bytes under inventory-safe-v1: strict v3 roster, exact manifest closure, source/compiler/artifact/policy pins, producer-summary reconciliation, one-to-one solver/proof paths and counters, approved Z3 UNSAT replay, and independent RUP replay. Never executes the artifact and never claims independent policy-construct totality or source-native refinement. Inputs: source_path: Caller-selected regular non-symlink Anubis source path.; evidence_dir: Caller-selected strict anubis.program-evidence.v3 directory.; expected_source_sha256: Caller-pinned exact source SHA-256.; expected_compiler_sha256: Caller-pinned producer executable SHA-256.; expected_artifact_sha256: Caller-pinned sealed artifact SHA-256; artifact bytes are read but never executed.; expected_policy_sha256: Caller-pinned inventory-safe-v1 policy digest.; verification_time_unix: Caller-supplied Unix verification time bound into the receipt.; profile: Must be inventory-safe-v1.; nonce: Caller nonce bound into the receipt."},{"assurance_classes":["verified-program-receipt"],"consequence_ceiling":null,"containing_ref":{"kind":"surface-origin-commit","value":"d25bcd9818e0d106f337798f80527ae611cc3acc"},"dependency":{"family":"program-verifier","identities":[{"label":"anubis_program_verifier","locator":"tools/anubis_program_verify.py","sha256":"4b80e29bdffc0737f05a6e215fce8cce3b6b828c24afbf55c68443399e5119dc"},{"label":"anubis_program_policy","locator":"release/program/inventory_safe_v1.json","sha256":"361979bf89b7c71a4b2c692d64756548833a2c363c269511b037726cab3ebacb"},{"label":"program-compatibility-floor","locator":"release/compat/v173_floor.json","sha256":"5b4e78e1f2b3e1ed7d0459a12f229ffe27886c179198a656a5a9dc5343f8b45e"},{"label":"approved_program_compiler","locator":"release/compat/v173_floor.json#approved_check_compiler_sha256","sha256":"0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6"}]},"exposure":{"codex":true,"hermes":true,"kernel":true},"name":"jackal_anubis_verify_program_receipt","profiles":["full"],"refusal_boundary":"Only caller-pinned Safe-source anubis.program-evidence.v3 under inventory-safe-v1 is admitted. Any source/compiler/artifact/policy mismatch, roster or proof-path discrepancy, replay failure, symlink/path violation, or unsupported profile refuses. Artifacts are never executed, and success does not establish construct totality, source-to-VC, SMT-to-CNF, source-native refinement, runtime behavior, or universal soundness.","release_state":"v1.7.3","schema_sha256":"6bb75d81aebf6f2b3b20900357204e9ef0858cdac412ea016868028cdd2a67d3","status_classes":["verified-program-receipt","refused"],"supported_fragment":"Recompute a jackal-anubis-program-receipt-v1 from caller-selected source/evidence bytes and independent caller pins, rejecting outer-digest-consistent semantic laundering. Success is only verified-program-receipt; it does not raise the underlying inventory-safe-v1 assurance ceiling. Inputs: receipt: Program receipt object to replay; its own pins are never trusted.; source_path: Caller-selected regular source path.; evidence_dir: Caller-selected evidence directory.; expected_source_sha256: Caller-pinned source SHA-256.; expected_compiler_sha256: Caller-pinned compiler SHA-256.; expected_artifact_sha256: Caller-pinned artifact SHA-256.; expected_policy_sha256: Caller-pinned inventory-safe-v1 policy digest.; verification_time_unix: Caller-supplied Unix verification time bound into the receipt.; profile: Must be inventory-safe-v1.; nonce: Caller nonce; never copied from the receipt."}],"unique_tool_count":41} diff --git a/release/compat/v173_floor.json b/release/compat/v173_floor.json new file mode 100644 index 0000000..e3193bb --- /dev/null +++ b/release/compat/v173_floor.json @@ -0,0 +1,26 @@ +{ + "schema": "jackal-program-compatibility-v1", + "release_epoch": "v1.7.3", + "tool_count": 41, + "mathematical_receipt_epoch": "v1.7.2", + "claim_bundle_epoch": "v1.6.0", + "domain_pack_schema": "jackal-domain-pack-registry-v1", + "program_evidence_schema": "anubis.program-evidence.v3", + "program_receipt_schema": "jackal-anubis-program-receipt-v1", + "program_profile": "inventory-safe-v1", + "approved_check_compiler_sha256": "0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6", + "approved_z3_sha256": "ae6c8df33db9c9ae9a80b6044e77cd66529a141d8b25f0620f1e89b409594f48", + "program_policy_sha256": "1b94350a6d23e9d76a917f05f0a53ae9e0ccf861bc6aee71342967ce1dccb090", + "admitted_modes": [ + "safe" + ], + "admitted_proof_kinds": [ + "rup_refutation" + ], + "multi_source": "refused", + "runtime_execution": false, + "source_to_vc": "open", + "smt_to_cnf": "open", + "independent_policy_construct_totality": false, + "source_native_refinement": "open" +} diff --git a/release/evidence/anubis_program_dogfood_v1.json b/release/evidence/anubis_program_dogfood_v1.json new file mode 100644 index 0000000..abe3aa7 --- /dev/null +++ b/release/evidence/anubis_program_dogfood_v1.json @@ -0,0 +1,115 @@ +{ + "application": "COVENANT community-grant triage", + "artifact_sha256": "e10f5550344c2e002e08139b4a1658d9151ccb707ddcb4adc6dfd73a31c555ff", + "compiler": { + "authority": "caller-pinned candidate; architect sign-off required before release", + "binary_sha256": "0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6", + "prototype_base_commit": "d6ca528110285973da106596411a7f4da7c49305", + "prototype_worktree_diff_sha256": "5765d17307f5c6a96d286c19539e316b7b81d9d98ff081a0ed541d2f18b24a4a" + }, + "evidence_manifest_sha256": "4b60df01a9c7bbc72703d24d498d44cfc7ab3e0b44a645976289f77e74edafe2", + "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" + ], + "observed": { + "consistent_outer_digest_tamper": "refused:receipt-semantic-mismatch", + "package_cli": "verified-program-evidence", + "package_plugin": "verified-program-evidence", + "package_receipt_replay": "verified-program-receipt", + "pristine_after_tamper": "verified-program-receipt", + "proof_steps": 615, + "proofs_verified": 9, + "repo_cli": "verified-program-evidence", + "repo_plugin": "verified-program-evidence", + "repo_receipt_replay": "verified-program-receipt" + }, + "package": { + "alignment_state": "historical-pre-alignment-candidate", + "basename": "jackal-v1.7.3-macos-arm64.tar.gz", + "bytes": 158353643, + "double_build_cmp_exit": 0, + "file_count": 104, + "sha256": "b2c0819b2c631939217583dc420cc67ba9e4acf613b4b49c208f020ba1bd1175", + "sha256sums_root": "2c1605dc1b0ad01801418f741d54c92a4a44d1362a35a09a47fcf0752aee3a42", + "superseded_by": { + "path": "release/evidence/package_alignment_v173_release.json", + "package_sha256": "68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae" + } + }, + "plugin_hermes_sha256": "03567f3d97447af40a1ecd2084424ddbe7a5a98030ad55a6b327849ab76f00b4", + "program_evidence_sha256": "2e58f6986457a6491c1ad5c0c29afce5ddc26512c1f0810782c2d3ddcd73da6f", + "program_policy_digest_sha256": "1b94350a6d23e9d76a917f05f0a53ae9e0ccf861bc6aee71342967ce1dccb090", + "program_policy_file_sha256": "361979bf89b7c71a4b2c692d64756548833a2c363c269511b037726cab3ebacb", + "program_verifier_sha256": "a0dbf14b6157de3f2f789fa54190e015575bafc2e1182ba3d30186afcb45e89a", + "receipt": { + "artifact": { + "sha256": "e10f5550344c2e002e08139b4a1658d9151ccb707ddcb4adc6dfd73a31c555ff" + }, + "assurance": { + "evidence_closure": "verified", + "policy_construct_totality": "not-established", + "policy_semantics": "producer-attested-inventory-checked", + "proof_replay": "independently-recomputed", + "runtime": "not-observed", + "smt_replay": "approved-z3-unsat", + "smt_to_cnf": "open", + "source_binding": "verified", + "source_native_refinement": "open", + "source_to_vc": "open" + }, + "compiler": { + "path_basename": "anubis", + "sha256": "0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6", + "tool": "anubis 0.1.0" + }, + "evidence_manifest_sha256": "4b60df01a9c7bbc72703d24d498d44cfc7ab3e0b44a645976289f77e74edafe2", + "nonce": "unified-v173-covenant", + "policy": { + "consumer_count": 6, + "function_count": 11, + "independent_construct_totality": false, + "inventory_authority": "producer-attested-function-roster", + "policy_digest_sha256": "1b94350a6d23e9d76a917f05f0a53ae9e0ccf861bc6aee71342967ce1dccb090", + "policy_file_sha256": "361979bf89b7c71a4b2c692d64756548833a2c363c269511b037726cab3ebacb", + "profile": "inventory-safe-v1" + }, + "policy_sha256": "1b94350a6d23e9d76a917f05f0a53ae9e0ccf861bc6aee71342967ce1dccb090", + "profile": "inventory-safe-v1", + "program_evidence_sha256": "2e58f6986457a6491c1ad5c0c29afce5ddc26512c1f0810782c2d3ddcd73da6f", + "proof_replay": { + "kind": "approved-z3-plus-independent-rup", + "smt_verified": 9, + "steps": 615, + "verified": 9 + }, + "receipt_digest_sha256": "8341ec180add6475f193f47e218b7af88fe2ef6437474c92ede4dfe1ecc02423", + "residual_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" + ], + "schema": "jackal-anubis-program-receipt-v1", + "source": { + "bytes": 7923, + "sha256": "037f63a2b2ca72d29a74503db09d5a0d1e0d4fb84a0cd778226751f22acb83ad" + }, + "stage_count": 12, + "status": "verified-program-evidence", + "verification_time_unix": "1787227290" + }, + "release_candidate": "v1.7.3", + "schema": "jackal-anubis-program-dogfood-v1", + "source_sha256": "037f63a2b2ca72d29a74503db09d5a0d1e0d4fb84a0cd778226751f22acb83ad" +} diff --git a/release/evidence/anubis_program_hostile_v1.json b/release/evidence/anubis_program_hostile_v1.json new file mode 100644 index 0000000..5394f64 --- /dev/null +++ b/release/evidence/anubis_program_hostile_v1.json @@ -0,0 +1,82 @@ +{ + "schema": "jackal-anubis-program-hostile-v1", + "rows": [ + { + "id": "stage-partial", + "ok": true, + "observed": "bad=stage-not-pass A2=0" + }, + { + "id": "consumer-omitted", + "ok": true, + "observed": "bad=policy-consumer-roster A2=0" + }, + { + "id": "consistent-rup-lie", + "ok": true, + "observed": "bad=rup-replay-failed A2=0" + }, + { + "id": "manifested-extra-file", + "ok": true, + "observed": "bad=bundle-file-roster A2=0" + }, + { + "id": "zero-obligation-launder", + "ok": true, + "observed": "bad=zero-obligations A2=0" + }, + { + "id": "symlink-hir", + "ok": true, + "observed": "bad=nonregular-file A2=0" + }, + { + "id": "producer-verdict-launder", + "ok": true, + "observed": "bad=producer-evidence-mismatch A2=0" + }, + { + "id": "solver-smt-decouple", + "ok": true, + "observed": "bad=solver-smt-mismatch A2=0" + }, + { + "id": "proof-path-reuse", + "ok": true, + "observed": "bad=proof-path-reuse A2=0" + }, + { + "id": "proof-tuple-reuse", + "ok": true, + "observed": "bad=proof-reuse A2=0" + }, + { + "id": "manifest-missing", + "ok": true, + "observed": "bad=manifest-missing A2=0" + }, + { + "id": "missing-canonical-file", + "ok": true, + "observed": "bad=bundle-file-roster A2=0" + }, + { + "id": "multi-source-launder", + "ok": true, + "observed": "bad=multi-source-unsupported A2=0" + }, + { + "id": "z3-trailing-error", + "ok": true, + "observed": "bad=smt-not-unsat A2=0" + }, + { + "id": "root-symlink", + "ok": true, + "observed": "bad=input-path A2=0" + } + ], + "passed": 15, + "failed": 0 +} diff --git a/release/evidence/architect_release_authorization_v173.json b/release/evidence/architect_release_authorization_v173.json new file mode 100644 index 0000000..1cfb18d --- /dev/null +++ b/release/evidence/architect_release_authorization_v173.json @@ -0,0 +1,23 @@ +{ + "schema": "jackal-release-authorization-v1", + "release": "v1.7.3", + "observed_date": "2026-08-22", + "authority": "architect", + "source": "direct-user-instructions-in-codex-session", + "instructions": [ + "Merge and do whatever else you have to do. Don’t leave nothing undone", + "Use your best jusment on all desisions" + ], + "decisions": { + "domain_pack_compatibility_minimum_approved": true, + "inventory_safe_v1_accept_conditions_approved": true, + "jackal_merge_tag_release_approved": true, + "hermes_merge_tag_release_install_approved": true, + "upstream_pr_update_approved": true + }, + "non_claims": [ + "not-a-cryptographic-signature", + "does-not-override-third-party-permissions-or-branch-protection", + "does-not-expand-the-formal-or-program-evidence-coverage-boundary" + ] +} diff --git a/release/evidence/claim_aba_v160.json b/release/evidence/claim_aba_v160.json index 3ff4486..1fb2570 100644 --- a/release/evidence/claim_aba_v160.json +++ b/release/evidence/claim_aba_v160.json @@ -1,5 +1,5 @@ { - "inference_registry_sha256": "e7134ec30f3b5dce71014fa1bbfc6b15e6dd8f42bfecd900fd3a61cf6b895082", + "inference_registry_sha256": "c70b33d5aee8071b5125e6a5f8ffe5226fc22a137d920c17d9b3463968be13f0", "layers": [ { "case_failed_under_poison": true, @@ -23,9 +23,9 @@ "observed_under_poison": "refused/registry-semantics-mismatch", "ok": true, "restored_byte_identical": true, - "sha_a": "e7134ec30f3b5dce71014fa1bbfc6b15e6dd8f42bfecd900fd3a61cf6b895082", - "sha_poisoned": "10cf9210739719d564ada052a61bbce1db4ea54378cbcfb3109410b697e00b4c", - "sha_restored": "e7134ec30f3b5dce71014fa1bbfc6b15e6dd8f42bfecd900fd3a61cf6b895082" + "sha_a": "c70b33d5aee8071b5125e6a5f8ffe5226fc22a137d920c17d9b3463968be13f0", + "sha_poisoned": "c429f39b6d3fd877ad8daeb0a6c5221efe1dd400871baf1692d584d1340954f3", + "sha_restored": "c70b33d5aee8071b5125e6a5f8ffe5226fc22a137d920c17d9b3463968be13f0" }, { "case_failed_under_poison": true, @@ -85,7 +85,7 @@ "intent": "dropping the provenance condition from the permitted rendering hides a load-bearing axis", "layer": "renderer", "matrix_failed_under_poison": true, - "observed_under_poison": "claim-verify=verified bundle.digest=2446431d98672e1545acaa238ed7c5aa2d207f3400a556b7396044d5e0e7c893 root=75b36d4cac3f1dfb1bb2cb3abc1749f70a04a758a5d802bf400190", + "observed_under_poison": "claim-verify=verified bundle.digest=57e84cb38776f91af01a31c6ba7e68f628d72828da5657316eae2c568d0f5996 root=8a9de448bc45ea604c5d86a17885ee5e695eda465018f59c5b8757f5db8bf871 root.proposition_sha256=6e578ca265d697cbc5f4b163a679692c92fd87603ce1034330e9436ca84905ed nodes=1 depth=1 axes: input_provenance=supplied model_validity=not-applicable mathematical=checked implementation=directly-trusted artifact:", "ok": true, "restored_byte_identical": true, "sha_a": "e0fcb9540c730bd9bb492b528ed42d29d49fc775b3aa0f9b831b6264fd68fd22", diff --git a/release/evidence/claim_dogfood_v160.json b/release/evidence/claim_dogfood_v160.json index 058a629..40d2d91 100644 --- a/release/evidence/claim_dogfood_v160.json +++ b/release/evidence/claim_dogfood_v160.json @@ -1,20 +1,20 @@ { "bundle_digests": { - "dog1": "361b8daf3221e9a75eb2c15afcb493bfb6d17bcf6aa49eb2fbbe455e0ca3853d", - "dog10": "17938c0c1f4d48f729dc5c8dd38b7825deb51a027c666e91650a047caa9f5408", - "dog11": "0547ba0445f4b0fa9a34aff547958d29170e16124208742d36ea75d20871e2da", - "dog12": "d0cc97979b0eb626a9921c50dab23a44d27c402e9f8c28cb9a42cd5f69f1520d", - "dog2": "185523481cce8a761165d47371cc2be96454fade55ff5c2ad6c5ceb3a85e8a6f", - "dog3": "0b910683bee3b60e8115f67accc6e817dbced31925db1a8a0fb0417089e4515e", - "dog4": "2f71150805303adf54fece8ee54247ce9479e13fb504ad0530f2915414da4374", - "dog5": "7217fd94d3ef24f0f235c8db55d81216deb920e459b470e7405baa9cf403479e", - "dog6-checked": "4142f6c6d3095e39ed63c523263cf9e78049ef8afa3998e0b68fb0457b847c6a", - "dog6-wrap": "e8a176f2b3ae47bc2e1df53765de3475a9b8ae1ec9d686dbd5ce94c92708b965", - "dog7": "e1744d46210212544e8f5ad614bd3f9237cae766bfad4208b25422a0661fa0dd", - "dog8": "28c6293e47841dac38aaa0ea55ffdce650160a40126fc7aa1d5b63d5d22dd503", - "dog9": "8b4fbcebfe48e48ddf5b197be547695e12029713fb143abd28b04f02ef6e29fe" + "dog1": "e808ea32c5e7f1569d2bec4cb5f8da9c748660a956de58dfb962e8132559b09b", + "dog10": "2ec3f8266c581fddddef71f57999b6d0694de7303b326b4789d0af43864b67fc", + "dog11": "fa00440cf64d496e9d0093dbc66291efa63de060699481e25eccee3e51483bbd", + "dog12": "be9197cabb4c4a4a143ad2fa55b6a41a07d4328e23f06dc14384b9c92b47a724", + "dog2": "f96e87aaf47b4296198edfa8e2c79b79b73bfc45e665c7d92206e28512dede30", + "dog3": "068d5d5cfc6998d55ed7588bc5f66dd563129928cacd43fcefdb5196b773ffcf", + "dog4": "3e610487372186e6aa5220225f4780cf4adac7b919fbf1f145729be5ce18316d", + "dog5": "52a638651b6d10267278a29da6b99a555e64fc88da899050cd982bfaf5e1acb9", + "dog6-checked": "58dc51f87a8c35017573d848355bade2b979106cc119b5fbb147900fc61bcc2c", + "dog6-wrap": "bc90211e3d4edbd879654f8decb2da68e70823e690706bd379751c181fe382f3", + "dog7": "171827fcfc52fa856f04f87cfcd915a6039958bb10e1f86c60d2c1c186129983", + "dog8": "fdbb7b7f1bfca33d6369e4750bfae1f477d5fc82b38bc02a7ba979bc3cf21bfa", + "dog9": "b91e36a3c161e10a573b03834c377afb9bd45e6afb3c2564f95d424e3d804ff3" }, - "engine": "20b80827d3c5c2a5d0d5d6f5a84c692f230fb0f55b9c7d1fcad02a1d0b3a1083", + "engine": "f11f3a429aa64dc0f09eb930e82bc3250e19eeb5a8a74b26b86683fafd72a655", "release_epoch": "v1.6.0", "router": "02328cf177a0423bdc5cbca6ec0ea946bb0679bbd3dc6c24140d32598e575afb", "rows": [ @@ -105,7 +105,7 @@ { "expect": "identical root and bundle digest", "id": "dog10-repo-plugin-parity", - "observed": "cli=17938c0c1f4d48f7 plugin=17938c0c1f4d48f7", + "observed": "cli=2ec3f8266c581fdd plugin=2ec3f8266c581fdd", "ok": true }, { diff --git a/release/evidence/claim_hostile_matrix_v160.json b/release/evidence/claim_hostile_matrix_v160.json index db33e77..dd9b34c 100644 --- a/release/evidence/claim_hostile_matrix_v160.json +++ b/release/evidence/claim_hostile_matrix_v160.json @@ -116,9 +116,9 @@ "ok": true }, { - "expect": "bundle.digest=2446431d98672e15", + "expect": "bundle.digest=57e84cb38776f91a", "id": "S-canonical-roundtrip", - "observed": "claim-verify=verified bundle.digest=2446431d98672e1545acaa238ed7c5aa2d207f3400a556b7396044d5e0e7c893 root=75b36d4cac3f1dfb1bb2cb3abc1749f70a04a758a5d802bf400190", + "observed": "claim-verify=verified bundle.digest=57e84cb38776f91af01a31c6ba7e68f628d72828da5657316eae2c568d0f5996 root=8a9de448bc45ea604c5d86a17885ee5e695eda465018f59c5b8757f5db8bf871 root.proposition_sha256=6e578ca265d697cbc5f4b163a679692c92fd87603ce1034330e9436ca84905ed nodes=1 depth=1 axes: input_provenance=supplied model_validity=not-applicable mathematical=checked implementation=directly-trusted artifact:", "ok": true }, { @@ -646,7 +646,7 @@ { "expect": "rendering with conditions", "id": "R-pos-conditions-present", - "observed": "claim-verify=verified bundle.digest=2446431d98672e1545acaa238ed7c5aa2d207f3400a556b7396044d5e0e7c893 root=75b36d4cac3f1dfb1bb2cb3abc1749f70a04a758a5d802bf400190", + "observed": "claim-verify=verified bundle.digest=57e84cb38776f91af01a31c6ba7e68f628d72828da5657316eae2c568d0f5996 root=8a9de448bc45ea604c5d86a17885ee5e695eda465018f59c5b8757f5db8bf871 root.proposition_sha256=6e578ca265d697cbc5f4b163a679692c92fd87603ce1034330e9436ca84905ed nodes=1 depth=1 axes: input_provenance=supplied model_validity=not-applicable mathematical=checked implementation=directly-trusted artifact:", "ok": true } ], diff --git a/release/evidence/lean_admission_audit_v173.json b/release/evidence/lean_admission_audit_v173.json new file mode 100644 index 0000000..ab042db --- /dev/null +++ b/release/evidence/lean_admission_audit_v173.json @@ -0,0 +1,803 @@ +{ + "audit_digest_sha256": "5e33809e2d8f73d8b554313bc9ea71a8b8d3c657810f0b37d4db3dd945578f29", + "audit_result": { + "logical_admission_count": 0, + "repository_axiom_declaration_count": 0, + "status": "pass", + "unexpected_construct_count": 0 + }, + "generator": { + "bytes": 35766, + "path": "tools/lean_admission_audit.py", + "sha256": "c620393d0dbef7701f298ef8ee71f5563d3ce61ac76f3328331bafa6e24e3bf8" + }, + "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": 137959552, + "checker_path": "proofs/lean/.lake/build/bin/jackal_cert_check", + "checker_sha256": "f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545", + "identity_bytes": 18387, + "identity_checker_bytes": 137959552, + "identity_checker_sha256": "f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545", + "identity_digest_sha256": "7b5e76c38fb9bf60f247b42bca75ab378d7dbe4f32c07858759a62470de53d6d", + "identity_path": "release/evidence/range_proof_identity_v172.json", + "identity_schema": "jackal-range-proof-identity-v2", + "identity_sha256": "84963be9b0a8851a03a38ae71da558b3e9d2c37d9d55ad7da31afbd23188499c", + "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": 137737104, + "checker_path": "proofs/lean/.lake/build/bin/jackal_gaussian_check", + "checker_sha256": "ccac690bf916f71a4e3baeb0622dac19aa47e3ca4af858c0800c295581ecfacb", + "identity_bytes": 16113, + "identity_checker_bytes": 137737104, + "identity_checker_sha256": "ccac690bf916f71a4e3baeb0622dac19aa47e3ca4af858c0800c295581ecfacb", + "identity_digest_sha256": "828ed69d9e04784e1f750536ebe8c12f4563dd62a37a85397f2aa08769fce1fd", + "identity_path": "release/evidence/gaussian_proof_identity.json", + "identity_schema": "jackal-gaussian-proof-identity-v1", + "identity_sha256": "7d2ff9ed4934604eba30f3111a147d7e295fd79302f640f57aacd986a23e243c", + "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": 138102064, + "checker_path": "proofs/lean/.lake/build/bin/jackal_int_cert_check", + "checker_sha256": "f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617", + "identity_bytes": 22008, + "identity_checker_bytes": 138102064, + "identity_checker_sha256": "f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617", + "identity_digest_sha256": "8ce4d8ceeaf2eaa47b97838bfcb2e35880f4fc20fb744c6ef2bf175f0b32f91e", + "identity_path": "release/evidence/int_cert_proof_identity_v172.json", + "identity_schema": "jackal-int-cert-proof-identity-v2", + "identity_sha256": "a8aefff85666d35cfd5412b10ae3d404260e91a98de53d5f0d2bb9f88f4ffbdf", + "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/package_alignment_v173_release.json b/release/evidence/package_alignment_v173_release.json new file mode 100644 index 0000000..ee65303 --- /dev/null +++ b/release/evidence/package_alignment_v173_release.json @@ -0,0 +1,70 @@ +{ + "schema": "jackal-package-alignment-v1", + "release_state": "v1.7.3", + "release_candidate": "v1.7.3", + "observed_date": "2026-08-22", + "source": { + "repository": "AnubisQuantumCipher/jackal", + "branch": "mission/jackal-unified-completion-20260820", + "commit": "aaf7058ce98bf84ecd7b587f1ffff5f6a923f878", + "tree": "e5f02743d121acbc1d9128d6c3ceaaf81542d583", + "worktree_status_at_build": "clean", + "builder_sha256": "686be8b66b7fccef3419eb032be8c8632619814b51f74814d1b85449f25cb58d", + "manifest_sha256": "ac52dafc0e9edbf74dde56b358c3c55ab5b705d3b66811558156c480b3530509", + "capability_inventory_sha256": "e2a4984329b3fd2fecc8de738dce20a5f046e0a876119569e72e41a04192a8f5", + "compiler_sha256": "a733565f237df171e7cf93b9b37700a42d8713576818fd92f8cd23a8ad7a69e2", + "checker_sha256": { + "range": "f7a82524d082b51a8d66f9bed653b9c8da51b5424386659c9048b9c0ae276545", + "gaussian": "ccac690bf916f71a4e3baeb0622dac19aa47e3ca4af858c0800c295581ecfacb", + "int_cert": "f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617" + } + }, + "package": { + "basename": "jackal-v1.7.3-macos-arm64.tar.gz", + "bytes": 158363786, + "extracted_file_bytes": 555511970, + "file_count": 106, + "tree_entries": 119, + "roster_aggregate_sha256": "f88ba8a9988afe4b41ab247d5c75cb3da03159defba1bd8985c37190fa595654", + "sha256": "68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae", + "sha256sums_root": "a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a" + }, + "comparisons": { + "directory_diff_exit": 0, + "tarball_cmp_exit": 0 + }, + "gates": { + "package_unified_tests": { + "exit": 0, + "passed": 15, + "skipped": 0 + }, + "claim_package_parity": { + "exit": 0, + "failures": 0, + "rows": 60 + }, + "codex_repository_tests": { + "exit": 0, + "passed": 218 + }, + "codex_live_acceptance": { + "status": "accepted", + "discovered_tool_count": 41, + "wrapper_aggregate_sha256": "d4b6cdc32e55335eade1ca6d7cbc385c133c2dbecf4296a894877fe297fe27c3", + "runtime_package_sha256": "68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae", + "runtime_tree_sha256": "a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a", + "gates": { + "claim_bundle": "verified", + "exact": "exact", + "formal": "formal-bounded", + "formal_receipt": "verified", + "unsupported_formal": "producer-refused" + } + } + }, + "non_claims": [ + "not-a-cryptographic-signature", + "no-upstream-merge-assertion" + ] +} diff --git a/release/evidence/plugin_smoke.jsonl b/release/evidence/plugin_smoke.jsonl index 508570c..0eceda8 100644 --- a/release/evidence/plugin_smoke.jsonl +++ b/release/evidence/plugin_smoke.jsonl @@ -1,6 +1,6 @@ -{"id": "S1-bundle-hash-pin-matches", "note": "computed=e8fadc24b17884d9fc4a8458b4e4a70ac60ad0d88768b82a684c665e2a9e0202 pinned=e8fadc24b17884d9fc4a8458b4e4a70ac60ad0d88768b82a684c665e2a9e0202", "ok": true} -{"id": "S2-server-selftest", "note": "out=plugin_hermes.bundle_sha256=e8fadc24b17884d9fc4a8458b4e4a70ac60ad0d88768b82a684c665e2a9e0202\nplugin_hermes.pinned_sha256=e8fadc24b17884d9fc4a8458b4e4a70ac60ad0d88768b82a684c665e2a9e0202\nplugin_her", "ok": true} -{"id": "S3-range-bound-emit", "note": "status=formal-bounded plugin_sha256=e8fadc24b17884d9fc4a8458b4e4a70ac60ad0d88768b82a684c665e2a9e0202", "ok": true} +{"id": "S1-bundle-hash-pin-matches", "note": "computed=c6a27483077b89d899d8c73c03bfeb3191f25db2a22f8021254a7dec763ba5fe pinned=c6a27483077b89d899d8c73c03bfeb3191f25db2a22f8021254a7dec763ba5fe", "ok": true} +{"id": "S2-server-selftest", "note": "out=plugin_hermes.bundle_sha256=c6a27483077b89d899d8c73c03bfeb3191f25db2a22f8021254a7dec763ba5fe\nplugin_hermes.pinned_sha256=c6a27483077b89d899d8c73c03bfeb3191f25db2a22f8021254a7dec763ba5fe\nplugin_her", "ok": true} +{"id": "S3-range-bound-emit", "note": "status=formal-bounded plugin_sha256=c6a27483077b89d899d8c73c03bfeb3191f25db2a22f8021254a7dec763ba5fe", "ok": true} {"id": "S4-refuse:'exp(x)'", "note": "status=refused reason=evaluator-refused", "ok": true} {"id": "S4-refuse:'sqrt(x)'", "note": "status=refused reason=evaluator-refused", "ok": true} {"id": "S4-refuse:'ln(x)'", "note": "status=refused reason=evaluator-refused", "ok": true} diff --git a/release/program/SPEC.md b/release/program/SPEC.md new file mode 100644 index 0000000..77b7fe0 --- /dev/null +++ b/release/program/SPEC.md @@ -0,0 +1,155 @@ +# JACKAL Anubis program-evidence contract v1 + +## Status vocabulary + +A successful program operation returns only `verified-program-evidence` or +`verified-program-receipt`. These statuses describe the checks in this document. +They are not universal soundness, source-native refinement, or runtime claims. + +## Public tools + +- `jackal_anubis_check_program`: invoke a caller-pinned, policy-approved Anubis + compiler as `build --evidence`, never run the compiled artifact, then verify + the emitted evidence. +- `jackal_anubis_verify_program`: verify caller-selected source and evidence + bytes. +- `jackal_anubis_verify_program_receipt`: recompute the receipt from those + underlying bytes and independent caller pins. + +CLI: `jackal-anubis-program check|verify|verify-receipt`. + +## Admitted producer contract + +Only `anubis.program-evidence.v3`, `version=3`, and `mode=safe` are admitted. +PCA v2 alone, Research/Exploit modes, partial or unknown stages, zero +obligations, unsupported DRAT/RAT features, and unregistered files refuse +without downgrade. + +The admitted profile is `inventory-safe-v1`. The prototype name +`contracted-safe-v1` is refused. The v3 producer exports a whole-function roster +and producer-attested policy-consumer rows, but it does not export independently +checkable construct-total walker coverage. Naming that stronger profile would +therefore outrun the evidence. + +The profile admits one sealed source leaf. Multi-source Merkle programs refuse +`multi-source-unsupported` until every source leaf can be independently replayed +against caller-selected bytes. + +## Filesystem and manifest + +The verifier canonicalizes a bounded regular-file tree and refuses symlinks, +devices, traversal, case-collision aliases, duplicate manifest rows, malformed +hashes, missing files, unlisted files, and registered files outside the closed +roster. `MANIFEST.sha256` must cover every admitted evidence file except itself +exactly once, and every digest must match. + +The original evidence tree is copied into a private snapshot after its manifest +is checked. Verification uses the snapshot. Before returning, the original tree +is walked and manifest-verified again; closing drift refuses `snapshot-drift`. + +## Caller pins + +The caller supplies, rather than copying from the receipt: + +- exact source SHA-256; +- Anubis producer executable SHA-256; +- compiled artifact SHA-256; +- `inventory-safe-v1` policy digest; +- profile, nonce, and verification time. + +`check` hashes the approved Anubis executable before and after producer +execution, writes only to a new output root, and invokes `build --evidence`. +Neither `check`, `verify`, nor `verify-receipt` executes the compiled artifact. + +## Required stage roster + +Exactly these twelve rows, in order, all `PASS`: + +1. `parse` +2. `typecheck` +3. `monomorphization` +4. `policy-effects` +5. `policy-capability` +6. `policy-information-flow` +7. `policy-declassification` +8. `symbolic` +9. `solver` +10. `source-binding` +11. `artifact-binding` +12. `evidence-closure` + +## Policy inventory + +The verifier recomputes function IDs from strict HIR JSON and requires the exact +consumer roster: + +`effects, capability, information-flow, declassification, mode, contracts`. + +Every consumer must be `PASS`. Function-oriented consumers bind the complete +function-ID list. Taint, monomorphization, MIR, declassification, capability, +and contract counts reconcile with their sealed artifacts. These checks bind a +producer-attested inventory; they do not independently establish Anubis policy +semantics or construct-total walker coverage. + +The policy document is `release/program/inventory_safe_v1.json`, copied to +`program/inventory_safe_v1.json` in the package. Its self-digest, file digest, +verifier digest, and compatibility floor are release-manifest bound. + +## Solver and proof replay + +`solver.json`, `analysis/proofs.json`, and the v3 obligation inventory must have +the same nonzero length and exact row order. Names, statuses, unique paths, +SHA-256 digests, content-derived obligation IDs, and counters must match. +Duplicate paths and duplicate `(SMT, CNF, proof)` digest tuples refuse. + +Every proof kind is `rup_refutation`. The approved, byte-pinned Z3 executable +must parse each exact SMT file and report only UNSAT (including the producer's +known `get-model`-after-UNSAT diagnostic shape). A trailing error or additional +output refuses. + +The dependency-free checker parses bounded DIMACS CNF and independently replays +each proof addition by reverse unit propagation. Each addition must be RUP under +the accumulated clauses and the proof must derive the empty clause. Deletion +lines, RAT-only steps, malformed or tautological clauses, oversized inputs, and +proofs without an empty clause refuse. + +Z3 replay and RUP replay do not prove the producer's SMT-to-CNF translation or +its source-to-VC lowering. Both remain explicit residuals. + +## Producer-summary reconciliation + +The verifier cross-checks `evidence.json`, its byte-identical `manifest.json`, +`pca.json`, the exact program inventory, and the sealed artifacts. Source, +artifact, tool, mode, verdict, required check names/details, build-log, +environment, source-tree, SARIF, bounty-report, manifest-summary, parse/typecheck, +and obligation totals must reconcile. A producer-written `PASS` is never enough +on its own. + +## Receipt + +`jackal-anubis-program-receipt-v1` binds source/compiler/artifact identities, +evidence-manifest and program-evidence hashes, policy digest and file identity, +stage/policy/proof counters, caller nonce, verification time, assurance vector, +and residuals. `receipt_digest_sha256` is SHA-256 over canonical receipt bytes +with that digest field omitted. + +Receipt replay first validates the supplied outer digest, then recomputes the +entire expected receipt from caller-selected source/evidence bytes and caller +pins. An assurance edit with a recomputed outer digest refuses +`receipt-semantic-mismatch`. + +## Mandatory residuals + +Every successful receipt carries: + +- `no-source-to-vc-proof` +- `no-smt-to-cnf-proof` +- `policy-construct-totality-not-established` +- `no-source-native-refinement` +- `no-universal-language-soundness` +- `policy-semantics-producer-attested` +- `runtime-not-observed` +- `derived-confinement-is-not-os-enforcement` + +The tool cannot emit `formal-bounded`, `source-native-refined`, +`runtime-verified`, or universal safety language. diff --git a/release/program/inventory_safe_v1.json b/release/program/inventory_safe_v1.json new file mode 100644 index 0000000..1fb9a8f --- /dev/null +++ b/release/program/inventory_safe_v1.json @@ -0,0 +1,48 @@ +{ + "schema": "jackal-anubis-program-policy-v1", + "profile": "inventory-safe-v1", + "mode": "safe", + "source_leaves": 1, + "minimum_obligations": 1, + "proof_kinds": [ + "rup_refutation" + ], + "required_stages": [ + "parse", + "typecheck", + "monomorphization", + "policy-effects", + "policy-capability", + "policy-information-flow", + "policy-declassification", + "symbolic", + "solver", + "source-binding", + "artifact-binding", + "evidence-closure" + ], + "required_consumers": [ + "effects", + "capability", + "information-flow", + "declassification", + "mode", + "contracts" + ], + "approved_check_compiler_sha256": "0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6", + "approved_z3_sha256": "ae6c8df33db9c9ae9a80b6044e77cd66529a141d8b25f0620f1e89b409594f48", + "runtime_execution": false, + "policy_inventory_authority": "producer-attested-function-roster", + "independent_policy_construct_totality": false, + "receipt_residual_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" + ], + "policy_digest_sha256": "1b94350a6d23e9d76a917f05f0a53ae9e0ccf861bc6aee71342967ce1dccb090" +} diff --git a/release/tools/repin_v173.py b/release/tools/repin_v173.py new file mode 100755 index 0000000..8eea29f --- /dev/null +++ b/release/tools/repin_v173.py @@ -0,0 +1,775 @@ +#!/usr/bin/env python3 +"""Derive the additive JACKAL v1.7.3 release-manifest plan from live bytes. + +The default and ``--plan`` modes print the complete proposed manifest without +mutating ``release/MANIFEST.sha256``. ``--check`` compares the proposal with +that file. ``--write`` atomically replaces the manifest only when an operator +explicitly selects that mode. +""" + +from __future__ import annotations + +import argparse +import difflib +import hashlib +import json +import os +import re +import stat +import subprocess +import sys +import tempfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +MANIFEST = ROOT / "release/MANIFEST.sha256" +COMPILER_PATH_ENV = "JACKAL_ANUBIS_COMPILER_PATH" +_CONFIGURED_COMPILER_PATH = os.environ.get(COMPILER_PATH_ENV) +COMPILER_PATH = ( + Path(_CONFIGURED_COMPILER_PATH) if _CONFIGURED_COMPILER_PATH else None +) +COMPILER_SHA256 = ( + "a733565f237df171e7cf93b9b37700a42d8713576818fd92f8cd23a8ad7a69e2" +) +V170_ARCHIVE_SHA256 = ( + "21c7ede586f30a58772f321f7dbb36ab66213e199785489f99133710ac56096e" +) +V170_RANGE_CHECKER_SHA256 = ( + "05c3518b836f239712f897c483a2ddadad9f544e0887b1b7bb1424a27289de8a" +) +V170_COVERAGE_INVENTORY_SHA256 = ( + "18ff7b1d428dbc6f807fd4de27751ba415b33ef0b356088d7fa316ed74bb0ba6" +) +V170_INT_CHECKER_SHA256 = ( + "c858e3bfc0ff2809a808170caabbf090077cb54996e76f065dbcd26ffb067d49" +) +V170_PLUGIN_HERMES_SHA256 = ( + "d141c909e8f5f03e268a2112f291e6bd79fafff906522eb7ca9accc247a3274b" +) +MAX_IDENTITY_BYTES = 512 * 1024 * 1024 + +HEADER = ( + "# JACKAL v1.7.3 pinned release identities (v1.7.2 proof/claim/domain lanes + " + "inventory-safe Anubis program evidence)" +) + +FILE_ROWS = [ + ("evaluator", "jackal-native"), + ("checker", "proofs/lean/.lake/build/bin/jackal_cert_check"), + ("gaussian-producer", "tools/gaussian_certificate.py"), + ("gaussian-checker", "proofs/lean/.lake/build/bin/jackal_gaussian_check"), + ("range-proof-identity", "release/evidence/range_proof_identity_v172.json"), + ("archival-range-proof-identity", "release/evidence/range_proof_identity.json"), + ("gaussian-proof-identity", "release/evidence/gaussian_proof_identity.json"), + ( + "lean-admission-audit", + "release/evidence/lean_admission_audit_v173.json", + ), + ("int-cert-producer", "tools/int_cert_producer.py"), + ("int-cert-checker", "proofs/lean/.lake/build/bin/jackal_int_cert_check"), + ( + "int-cert-proof-identity", + "release/evidence/int_cert_proof_identity_v172.json", + ), + ( + "revoked-int-cert-proof-identity-reference", + "release/evidence/int_cert_proof_identity.json", + ), + ("compatibility-floor", "release/compat/v172_floor.json"), + ("program-compatibility-floor", "release/compat/v173_floor.json"), + ("range-ordering-aba", "release/evidence/range_ordering_aba_v172.json"), + ( + "int-cert-premise-aba", + "release/evidence/int_cert_premise_aba_v172.json", + ), + ("coverage-inventory", "release/coverage/formal_coverage_inventory.json"), + ("build-environment", "release/evidence/build_environment_v170.json"), + ("source", "jackal_calc.anb"), + ("sqrt_rat_producer", "tools/sqrt_rat_producer.py"), + ("exp_rat_producer", "tools/exp_rat_producer.py"), + ("ln_rat_producer", "tools/ln_rat_producer.py"), + ("sin_rat_producer", "tools/sin_rat_producer.py"), + ("atan_rat_producer", "tools/atan_rat_producer.py"), + ("tanh_rat_producer", "tools/tanh_rat_producer.py"), + ("exact_verifier", "tools/exact_verify.py"), + ("claim_kernel", "tools/claim_kernel.py"), + ("claim_router", "tools/claim_router.py"), + ("claim_verifier", "tools/claim_bundle_verify.py"), + ("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", "release/program/inventory_safe_v1.json"), + ("claim_inference_registry", "release/claim/inference_registry_v1.json"), + ("claim_unit_registry", "release/claim/unit_registry_v1.json"), +] + +DISPLAY = { + "checker": "jackal_cert_check", + "gaussian-checker": "jackal_gaussian_check", + "int-cert-checker": "jackal_int_cert_check", +} + +ORDER = [ + "#", + "evaluator", + "checker", + "archival-v170-archive-source", + "archival-range-checker", + "archival-range-coverage-inventory", + "archival-plugin-hermes", + "gaussian-producer", + "gaussian-checker", + "range-proof-identity", + "range-proof-digest", + "archival-range-proof-identity", + "archival-range-proof-digest", + "gaussian-proof-identity", + "gaussian-proof-digest", + "lean-admission-audit", + "lean-admission-audit-digest", + "int-cert-producer", + "int-cert-checker", + "int-cert-proof-identity", + "int-cert-proof-digest", + "revoked-int-cert-proof-identity-reference", + "compatibility-floor", + "program-compatibility-floor", + "range-ordering-aba", + "int-cert-premise-aba", + "coverage-inventory", + "build-environment", + "source", + "compiler_pin", + "plugin_hermes", + "sqrt_rat_producer", + "exp_rat_producer", + "ln_rat_producer", + "sin_rat_producer", + "atan_rat_producer", + "tanh_rat_producer", + "exact_verifier", + "claim_kernel", + "claim_router", + "claim_verifier", + "domain_pack_registry", + "domain_pack_verifier", + "domain_pack_test_exists_checker", + "domain_pack_decision_checker", + "anubis_program_verifier", + "anubis_program_policy", + "claim_inference_registry", + "claim_unit_registry", +] + + +class PlanRefusal(RuntimeError): + """Fail-closed manifest-plan refusal.""" + + +def read_regular(path: Path, maximum: int = MAX_IDENTITY_BYTES) -> bytes: + before = path.lstat() + if not stat.S_ISREG(before.st_mode) or before.st_size > maximum: + raise PlanRefusal(f"not a bounded regular file: {path}") + descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)) + try: + opened = os.fstat(descriptor) + if (opened.st_dev, opened.st_ino) != (before.st_dev, before.st_ino): + raise PlanRefusal(f"path identity changed before read: {path}") + chunks: list[bytes] = [] + total = 0 + while True: + chunk = os.read(descriptor, min(1024 * 1024, maximum + 1 - total)) + if not chunk: + break + total += len(chunk) + if total > maximum: + raise PlanRefusal(f"file exceeds byte bound: {path}") + chunks.append(chunk) + after = os.fstat(descriptor) + finally: + os.close(descriptor) + current = path.lstat() + identity = lambda value: ( # noqa: E731 + value.st_dev, + value.st_ino, + value.st_mode, + value.st_size, + value.st_mtime_ns, + value.st_ctime_ns, + ) + if identity(opened) != identity(after) or identity(after) != identity(current): + raise PlanRefusal(f"file changed while reading: {path}") + return b"".join(chunks) + + +def sha256(path: Path) -> str: + return hashlib.sha256(read_regular(path)).hexdigest() + + +def identity_digest(path: Path, raw: bytes) -> str: + value = decode_json(path, raw) + digest = value.get("identity_digest_sha256") + if not isinstance(digest, str) or len(digest) != 64: + raise PlanRefusal(f"identity digest missing or malformed: {path}") + return digest + + +def audit_digest(path: Path, raw: bytes) -> str: + value = decode_json(path, raw) + digest = value.get("audit_digest_sha256") + if not isinstance(digest, str) or re.fullmatch(r"[0-9a-f]{64}", digest) is None: + raise PlanRefusal(f"audit digest missing or malformed: {path}") + body = {key: item for key, item in value.items() if key != "audit_digest_sha256"} + computed = hashlib.sha256( + json.dumps( + body, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + ).hexdigest() + require_equal(digest, computed, "Lean admission audit self-digest") + return digest + + +def decode_json(path: Path, raw: bytes) -> dict[str, object]: + value = json.loads(raw.decode("utf-8")) + if not isinstance(value, dict): + raise PlanRefusal(f"JSON record is not an object: {path}") + return value + + +def read_json(path: Path) -> dict[str, object]: + return decode_json(path, read_regular(path)) + + +def nested(value: dict[str, object], *keys: str) -> object: + current: object = value + for key in keys: + if not isinstance(current, dict) or key not in current: + raise PlanRefusal(f"missing JSON binding: {'.'.join(keys)}") + current = current[key] + return current + + +def require_equal(observed: object, expected: object, label: str) -> None: + if observed != expected: + raise PlanRefusal( + f"binding mismatch {label}: observed={observed!r} " + f"expected={expected!r}" + ) + + +def validate_v172_contract() -> dict[str, str]: + range_checker = ROOT / "proofs/lean/.lake/build/bin/jackal_cert_check" + int_checker = ROOT / "proofs/lean/.lake/build/bin/jackal_int_cert_check" + range_identity_path = ROOT / "release/evidence/range_proof_identity_v172.json" + int_identity_path = ROOT / "release/evidence/int_cert_proof_identity_v172.json" + archival_range_identity_path = ROOT / "release/evidence/range_proof_identity.json" + archival_int_identity_path = ROOT / "release/evidence/int_cert_proof_identity.json" + compatibility_path = ROOT / "release/compat/v172_floor.json" + range_aba_path = ROOT / "release/evidence/range_ordering_aba_v172.json" + int_aba_path = ROOT / "release/evidence/int_cert_premise_aba_v172.json" + + range_checker_digest = sha256(range_checker) + int_checker_digest = sha256(int_checker) + range_identity_digest = sha256(range_identity_path) + int_identity_digest = sha256(int_identity_path) + range_identity = read_json(range_identity_path) + int_identity = read_json(int_identity_path) + archival_range_identity = read_json(archival_range_identity_path) + archival_int_identity = read_json(archival_int_identity_path) + compatibility = read_json(compatibility_path) + range_aba = read_json(range_aba_path) + int_aba = read_json(int_aba_path) + + require_equal( + range_identity.get("schema"), + "jackal-range-proof-identity-v2", + "range identity schema", + ) + require_equal( + int_identity.get("schema"), + "jackal-int-cert-proof-identity-v2", + "int-cert identity schema", + ) + require_equal( + nested(range_identity, "checker", "sha256"), + range_checker_digest, + "range identity checker", + ) + require_equal( + nested(range_identity, "build_attestation", "checker", "sha256"), + range_checker_digest, + "range build attestation checker", + ) + require_equal( + nested(int_identity, "checker", "sha256"), + int_checker_digest, + "int-cert identity checker", + ) + require_equal( + nested(int_identity, "build_attestation", "checker", "sha256"), + int_checker_digest, + "int-cert build attestation checker", + ) + require_equal( + nested(archival_range_identity, "checker", "sha256"), + V170_RANGE_CHECKER_SHA256, + "archival range checker", + ) + require_equal( + nested(archival_int_identity, "checker", "sha256"), + V170_INT_CHECKER_SHA256, + "archival int-cert checker", + ) + require_equal( + nested(range_identity, "fragment", "premises_not_discharged_by_checker"), + [], + "range closed premises", + ) + require_equal( + nested(int_identity, "fragment", "premises_not_discharged_by_checker"), + [], + "int-cert closed premises", + ) + + require_equal( + compatibility.get("current_release_epoch"), + "v1.7.2", + "compatibility epoch", + ) + require_equal( + compatibility.get("reversed_interval_policy"), + "revoked-refuse", + "reversed interval policy", + ) + require_equal( + compatibility.get("unsupported_policy"), + "refuse", + "unsupported policy", + ) + for lane, expected_path, expected_schema, expected_file_digest in ( + ( + "range", + "release/evidence/range_proof_identity_v172.json", + "jackal-range-proof-identity-v2", + range_identity_digest, + ), + ( + "int_cert", + "release/evidence/int_cert_proof_identity_v172.json", + "jackal-int-cert-proof-identity-v2", + int_identity_digest, + ), + ): + require_equal( + nested(compatibility, "lanes", lane, "current", "identity_file"), + expected_path, + f"{lane} compatibility identity path", + ) + require_equal( + nested( + compatibility, + "lanes", + lane, + "current", + "identity_file_sha256", + ), + expected_file_digest, + f"{lane} compatibility identity bytes", + ) + require_equal( + nested(compatibility, "lanes", lane, "current", "schema"), + expected_schema, + f"{lane} compatibility schema", + ) + require_equal( + nested( + compatibility, + "lanes", + lane, + "current", + "allowed_release_epochs", + ), + ["v1.7.2"], + f"{lane} compatibility epochs", + ) + + for lane, epoch, checker_file, checker_digest in ( + ( + "range", + "v1.5.0", + "jackal_cert_check_v170", + V170_RANGE_CHECKER_SHA256, + ), + ( + "rational_variants", + "v1.5.0", + "jackal_cert_check_v170", + V170_RANGE_CHECKER_SHA256, + ), + ): + require_equal( + nested( + compatibility, + "lanes", + lane, + "archival_v1", + "allowed_release_epochs", + ), + [epoch], + f"{lane} archival epoch", + ) + require_equal( + nested(compatibility, "lanes", lane, "archival_v1", "checker_file"), + checker_file, + f"{lane} archival checker path", + ) + require_equal( + nested(compatibility, "lanes", lane, "archival_v1", "checker_sha256"), + checker_digest, + f"{lane} archival checker bytes", + ) + + revoked_int = nested(compatibility, "lanes", "int_cert", "archival_v1") + require_equal(revoked_int.get("allowed_release_epochs"), [], + "int-cert archival epochs revoked") + require_equal(revoked_int.get("mode"), "revoked-refuse", + "int-cert archival mode") + if "does not bind the raw request" not in str(revoked_int.get("reason", "")): + raise PlanRefusal("int-cert archival revocation reason missing") + require_equal(revoked_int.get("checker_sha256"), + V170_INT_CHECKER_SHA256, + "int-cert historical checker bytes") + + require_equal(range_aba.get("status"), "passed", "range ABA status") + require_equal(int_aba.get("status"), "passed", "int-cert ABA status") + for phase in ("canonical_pre_sha256", "canonical_post_sha256"): + require_equal( + nested(range_aba, phase, "jackal_cert_check"), + range_checker_digest, + f"range ABA {phase}", + ) + require_equal( + nested(int_aba, phase, "jackal_int_cert_check"), + int_checker_digest, + f"int-cert ABA {phase}", + ) + + return { + "release_epoch": "v1.7.2", + "range_checker_sha256": range_checker_digest, + "int_cert_checker_sha256": int_checker_digest, + "range_identity_file_sha256": range_identity_digest, + "int_cert_identity_file_sha256": int_identity_digest, + "range_aba_status": str(range_aba["status"]), + "int_cert_aba_status": str(int_aba["status"]), + } + + +def validate_compiler(compiler_path: Path | None = COMPILER_PATH) -> str: + if compiler_path is None: + raise PlanRefusal( + f"compiler-path-unset: set {COMPILER_PATH_ENV} or pass --compiler-path" + ) + if compiler_path.is_symlink(): + raise PlanRefusal(f"compiler authority must not be a symlink: {compiler_path}") + observed = sha256(compiler_path) + if observed != COMPILER_SHA256: + raise PlanRefusal( + f"compiler authority drift: path={compiler_path} observed={observed} " + f"expected={COMPILER_SHA256}" + ) + return observed + + +def validate_unified_contract() -> None: + catalog = read_json(ROOT / "plugin/hermes/tools.json") + names = [ + row.get("name") + for row in catalog.get("tools", []) + if isinstance(row, dict) + ] + require_equal(catalog.get("version"), "v1.7.3", "plugin release epoch") + require_equal(len(names), 41, "plugin tool count") + require_equal(len(set(names)), 41, "plugin tool uniqueness") + full = read_json(ROOT / "plugin/hermes/profiles/full.json") + require_equal(full.get("tools"), names, "full profile catalog parity") + + compatibility = read_json(ROOT / "release/compat/v173_floor.json") + require_equal(compatibility.get("release_epoch"), "v1.7.3", "compat epoch") + require_equal(compatibility.get("tool_count"), 41, "compat tool count") + require_equal( + compatibility.get("program_profile"), + "inventory-safe-v1", + "compat program profile", + ) + require_equal( + compatibility.get("independent_policy_construct_totality"), + False, + "compat construct-totality boundary", + ) + + policy = read_json(ROOT / "release/program/inventory_safe_v1.json") + policy_digest = policy.get("policy_digest_sha256") + policy_body = { + key: value + for key, value in policy.items() + if key != "policy_digest_sha256" + } + computed_policy_digest = hashlib.sha256( + json.dumps( + policy_body, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + ).encode("utf-8") + ).hexdigest() + require_equal(policy_digest, computed_policy_digest, "program policy digest") + require_equal( + compatibility.get("program_policy_sha256"), + policy_digest, + "compat program policy", + ) + + profile_check = subprocess.run( + [sys.executable, "-I", "-S", "-B", "tools/profile_verify.py"], + cwd=ROOT, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=30, + check=False, + ) + if profile_check.returncode != 0: + raise PlanRefusal( + f"profile verification refused: " + f"{(profile_check.stderr or profile_check.stdout)[:512]}" + ) + + pack_check = subprocess.run( + [ + sys.executable, + "-I", + "-S", + "-B", + "tools/domain_pack_verify.py", + "--root", + str(ROOT), + ], + cwd=ROOT, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=120, + check=False, + ) + if pack_check.returncode != 0: + raise PlanRefusal( + f"domain-pack verification refused: " + f"{(pack_check.stderr or pack_check.stdout)[:512]}" + ) + try: + pack_report = json.loads(pack_check.stdout) + except json.JSONDecodeError as error: + raise PlanRefusal(f"domain-pack verifier output malformed: {error}") from None + require_equal(pack_report.get("status"), "accepted", "domain-pack status") + + lean_audit_check = subprocess.run( + [ + sys.executable, + "-I", + "-S", + "-B", + "tools/lean_admission_audit.py", + "--check", + "--root", + str(ROOT), + ], + cwd=ROOT, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=180, + check=False, + ) + if lean_audit_check.returncode != 0: + raise PlanRefusal( + "Lean admission audit refused: " + f"{(lean_audit_check.stderr or lean_audit_check.stdout)[:512]}" + ) + + +def plugin_bundle_digest() -> str: + completed = subprocess.run( + [ + sys.executable, + "-I", + "-S", + "-B", + "plugin/hermes/bundle_hash.py", + "print", + ], + cwd=ROOT, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=30, + check=False, + ) + if completed.returncode != 0: + raise PlanRefusal(f"plugin bundle hash refused: {completed.stderr[:512]}") + digest = completed.stdout.strip() + if len(digest) != 64: + raise PlanRefusal("plugin bundle hash is malformed") + return digest + + +def build_rows(compiler_path: Path | None = COMPILER_PATH) -> list[str]: + validate_compiler(compiler_path) + validate_unified_contract() + contract = validate_v172_contract() + rows = [HEADER] + for label, relative in FILE_ROWS: + path = ROOT / relative + raw = read_regular(path) + observed = hashlib.sha256(raw).hexdigest() + if label == "checker": + require_equal( + observed, contract["range_checker_sha256"], "range checker TOCTOU" + ) + elif label == "int-cert-checker": + require_equal( + observed, + contract["int_cert_checker_sha256"], + "int-cert checker TOCTOU", + ) + elif label == "range-proof-identity": + require_equal( + observed, + contract["range_identity_file_sha256"], + "range identity TOCTOU", + ) + elif label == "int-cert-proof-identity": + require_equal( + observed, + contract["int_cert_identity_file_sha256"], + "int-cert identity TOCTOU", + ) + rows.append(f"{label} {DISPLAY.get(label, relative)} {observed}") + if label in { + "range-proof-identity", + "archival-range-proof-identity", + "gaussian-proof-identity", + "int-cert-proof-identity", + }: + rows.append( + f"{label.rsplit('-', 1)[0]}-digest {identity_digest(path, raw)}" + ) + elif label == "lean-admission-audit": + rows.append( + f"lean-admission-audit-digest {audit_digest(path, raw)}" + ) + rows.extend( + [ + f"archival-v170-archive-source github-release-v1.7.0 {V170_ARCHIVE_SHA256}", + f"archival-range-checker jackal_cert_check_v170 {V170_RANGE_CHECKER_SHA256}", + "archival-range-coverage-inventory " + f"formal_coverage_inventory_v170.json {V170_COVERAGE_INVENTORY_SHA256}", + f"archival-plugin-hermes v1.7.0-plugin {V170_PLUGIN_HERMES_SHA256}", + ] + ) + rows.append(f"compiler_pin anubis-a733565f237d {COMPILER_SHA256}") + rows.append(f"plugin_hermes {plugin_bundle_digest()}") + keyed = { + row.split()[0] if not row.startswith("#") else "#": row for row in rows + } + missing = [label for label in ORDER if label not in keyed] + extras = sorted(set(keyed) - set(ORDER)) + if missing or extras: + raise PlanRefusal(f"manifest row mismatch missing={missing} extras={extras}") + return [keyed[label] for label in ORDER] + + +def manifest_text(compiler_path: Path | None = COMPILER_PATH) -> str: + return "\n".join(build_rows(compiler_path)) + "\n" + + +def write_atomic(path: Path, data: str) -> None: + destination_mode = stat.S_IMODE(path.stat().st_mode) if path.exists() else 0o644 + descriptor, temporary = tempfile.mkstemp(prefix=".MANIFEST.v173.", dir=path.parent) + try: + os.fchmod(descriptor, destination_mode) + with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as handle: + handle.write(data) + handle.flush() + os.fsync(handle.fileno()) + os.replace(temporary, path) + directory_fd = os.open(path.parent, os.O_RDONLY) + try: + os.fsync(directory_fd) + finally: + os.close(directory_fd) + except BaseException: + try: + os.unlink(temporary) + except FileNotFoundError: + pass + raise + + +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + modes = parser.add_mutually_exclusive_group() + modes.add_argument("--plan", action="store_true", help="print without writing") + modes.add_argument("--check", action="store_true", help="compare with manifest") + modes.add_argument("--write", action="store_true", help="explicitly replace manifest") + parser.add_argument( + "--compiler-path", + default=_CONFIGURED_COMPILER_PATH, + required=_CONFIGURED_COMPILER_PATH is None, + help=( + "path to the exact compiler authority; may also be set with " + f"{COMPILER_PATH_ENV}" + ), + ) + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(sys.argv[1:] if argv is None else argv) + proposed = manifest_text(Path(args.compiler_path)) + if args.check: + current = MANIFEST.read_text(encoding="utf-8") + if current != proposed: + sys.stdout.writelines( + difflib.unified_diff( + current.splitlines(keepends=True), + proposed.splitlines(keepends=True), + fromfile="release/MANIFEST.sha256", + tofile="v1.7.3-plan", + ) + ) + print("REPIN_V173_CHECK_FAIL") + return 1 + print(f"REPIN_V173_CHECK_PASS rows={len(proposed.splitlines())}") + return 0 + if args.write: + write_atomic(MANIFEST, proposed) + print(f"REPIN_V173_WRITTEN rows={len(proposed.splitlines())} manifest={MANIFEST}") + return 0 + sys.stdout.write(proposed) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (OSError, PlanRefusal, ValueError, subprocess.SubprocessError) as error: + print(f"REPIN_V173_REFUSED detail={str(error)[:512]}", file=sys.stderr) + raise SystemExit(1) from None diff --git a/tests/anubis_program_hostile_test.py b/tests/anubis_program_hostile_test.py new file mode 100755 index 0000000..92f42fa --- /dev/null +++ b/tests/anubis_program_hostile_test.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import hashlib +import json +import shutil +import tempfile +from pathlib import Path + +from anubis_program_verifier_test import make_v3_fixture, reseal, run_verify + +ROOT = Path(__file__).resolve().parents[1] +OUT = ROOT / "release/evidence/anubis_program_hostile_v1.json" +ROWS: list[dict] = [] + + +def sha(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def dump(path: Path, value: object) -> None: + path.write_text( + json.dumps(value, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + + +def record(name: str, ok: bool, observed: str) -> None: + ROWS.append({"id": name, "ok": ok, "observed": observed[:300]}) + print(f"{'PASS' if ok else 'FAIL'} {name} {observed[:160]}") + + +def reason(result) -> str: + text = result.stdout + result.stderr + marker = "reason=" + return text.split(marker, 1)[1].split()[0] if marker in text else text[:80] + + +def run_case(name: str, mutate, expected: str) -> None: + with tempfile.TemporaryDirectory(prefix=f"jackal-program-{name}-") as td: + source, pristine, compiler_sha, artifact_sha, _marker = make_v3_fixture( + Path(td) / "a" + ) + clean = run_verify(source, pristine, compiler_sha, artifact_sha) + poisoned = Path(td) / "b" + shutil.copytree(pristine, poisoned, symlinks=True) + mutate(source, poisoned, compiler_sha, artifact_sha) + bad = run_verify(source, poisoned, compiler_sha, artifact_sha) + shutil.rmtree(poisoned) + shutil.copytree(pristine, poisoned, symlinks=True) + restored = run_verify(source, poisoned, compiler_sha, artifact_sha) + observed = reason(bad) + record( + name, + clean.returncode == 0 + and bad.returncode == 1 + and observed == expected + and restored.returncode == 0, + f"bad={observed} A2={restored.returncode}", + ) + + +def main() -> int: + def refresh_solver_artifact(evidence: Path, program: dict) -> None: + solver_path = evidence / "solver.json" + program["artifacts"]["solver"]["sha256"] = sha(solver_path.read_bytes()) + program["artifacts"]["solver"]["bytes"] = solver_path.stat().st_size + + def refresh_obligation_id(row: dict) -> None: + stable = { + "name": row["name"], + "smt_sha256": row["smt_sha256"], + "cnf_sha256": row["cnf_sha256"], + "proof_sha256": row["proof_sha256"], + } + row["id"] = sha( + json.dumps(stable, sort_keys=True, separators=(",", ":")).encode() + ) + + def stage_partial(_source, evidence, _compiler, _artifact): + path = evidence / "program-evidence.json" + value = json.loads(path.read_text()) + value["stages"][5]["status"] = "PARTIAL" + dump(path, value) + reseal(evidence) + + def consumer_omitted(_source, evidence, _compiler, _artifact): + path = evidence / "program-evidence.json" + value = json.loads(path.read_text()) + value["policy_inventory"]["consumers"].pop(1) + dump(path, value) + reseal(evidence) + + def rup_lie(_source, evidence, _compiler, _artifact): + cnf = evidence / "analysis/proofs/obligation_0000.cnf" + cnf.write_text("p cnf 1 1\n1 0\n") + program_path = evidence / "program-evidence.json" + program = json.loads(program_path.read_text()) + row = program["solver_inventory"]["obligations"][0] + row["cnf_sha256"] = sha(cnf.read_bytes()) + stable = { + "name": row["name"], + "smt_sha256": row["smt_sha256"], + "cnf_sha256": row["cnf_sha256"], + "proof_sha256": row["proof_sha256"], + } + row["id"] = sha(json.dumps(stable, sort_keys=True, separators=(",", ":")).encode()) + dump(program_path, program) + reseal(evidence) + + def extra_file(_source, evidence, _compiler, _artifact): + (evidence / "green-looking-extra.json").write_text("{}\n") + reseal(evidence) + + def zero_obligations(_source, evidence, _compiler, _artifact): + dump(evidence / "solver.json", []) + dump(evidence / "analysis/proofs.json", {"note": "forged empty", "obligations": []}) + program_path = evidence / "program-evidence.json" + program = json.loads(program_path.read_text()) + program["artifacts"]["solver"]["sha256"] = sha((evidence / "solver.json").read_bytes()) + program["artifacts"]["solver"]["bytes"] = (evidence / "solver.json").stat().st_size + program["solver_inventory"] = {"count": 0, "obligations": []} + for consumer in program["policy_inventory"]["consumers"]: + if consumer["id"] == "contracts": + consumer["subjects"] = {"solver_obligation_count": 0} + dump(program_path, program) + for path in (evidence / "analysis/proofs").iterdir(): + path.unlink() + reseal(evidence) + + def symlink_hir(_source, evidence, _compiler, _artifact): + hir = evidence / "hir.json" + copy = evidence.parent / "outside-hir.json" + copy.write_bytes(hir.read_bytes()) + hir.unlink() + hir.symlink_to(copy) + reseal(evidence) + + def producer_verdict_launder(_source, evidence, _compiler, _artifact): + path = evidence / "evidence.json" + value = json.loads(path.read_text()) + value["verdict"] = "FAIL" + dump(path, value) + reseal(evidence) + + def solver_smt_decouple(_source, evidence, _compiler, _artifact): + solver_path = evidence / "solver.json" + solver = json.loads(solver_path.read_text()) + solver[0]["smt"] += "(assert true)\n" + dump(solver_path, solver) + program_path = evidence / "program-evidence.json" + program = json.loads(program_path.read_text()) + refresh_solver_artifact(evidence, program) + dump(program_path, program) + reseal(evidence) + + def proof_path_reuse(_source, evidence, _compiler, _artifact): + solver_path = evidence / "solver.json" + solver = json.loads(solver_path.read_text()) + second_solver = dict(solver[0]) + second_solver["name"] = "ensures:id-duplicate-proof" + solver.append(second_solver) + dump(solver_path, solver) + + proof_index_path = evidence / "analysis/proofs.json" + proof_index = json.loads(proof_index_path.read_text()) + second_proof = dict(proof_index["obligations"][0]) + second_proof["obligation"] = second_solver["name"] + proof_index["obligations"].append(second_proof) + dump(proof_index_path, proof_index) + + program_path = evidence / "program-evidence.json" + program = json.loads(program_path.read_text()) + second_row = dict(program["solver_inventory"]["obligations"][0]) + second_row["name"] = second_solver["name"] + refresh_obligation_id(second_row) + program["solver_inventory"]["obligations"].append(second_row) + program["solver_inventory"]["count"] = 2 + for consumer in program["policy_inventory"]["consumers"]: + if consumer["id"] == "contracts": + consumer["subjects"] = {"solver_obligation_count": 2} + refresh_solver_artifact(evidence, program) + dump(program_path, program) + reseal(evidence) + + def proof_tuple_reuse(_source, evidence, _compiler, _artifact): + proof_dir = evidence / "analysis/proofs" + for suffix in ("smt2", "cnf", "drat"): + shutil.copy2( + proof_dir / f"obligation_0000.{suffix}", + proof_dir / f"obligation_0001.{suffix}", + ) + solver_path = evidence / "solver.json" + solver = json.loads(solver_path.read_text()) + second_solver = dict(solver[0]) + second_solver["name"] = "ensures:id-duplicate-bytes" + solver.append(second_solver) + dump(solver_path, solver) + + proof_index_path = evidence / "analysis/proofs.json" + proof_index = json.loads(proof_index_path.read_text()) + second_proof = dict(proof_index["obligations"][0]) + second_proof["obligation"] = second_solver["name"] + second_proof["smt"] = "analysis/proofs/obligation_0001.smt2" + second_proof["cnf_dimacs"] = "analysis/proofs/obligation_0001.cnf" + second_proof["proof_drat"] = "analysis/proofs/obligation_0001.drat" + proof_index["obligations"].append(second_proof) + dump(proof_index_path, proof_index) + + program_path = evidence / "program-evidence.json" + program = json.loads(program_path.read_text()) + second_row = dict(program["solver_inventory"]["obligations"][0]) + second_row["name"] = second_solver["name"] + second_row["smt_path"] = second_proof["smt"] + second_row["cnf_path"] = second_proof["cnf_dimacs"] + second_row["proof_path"] = second_proof["proof_drat"] + refresh_obligation_id(second_row) + program["solver_inventory"]["obligations"].append(second_row) + program["solver_inventory"]["count"] = 2 + for consumer in program["policy_inventory"]["consumers"]: + if consumer["id"] == "contracts": + consumer["subjects"] = {"solver_obligation_count": 2} + refresh_solver_artifact(evidence, program) + dump(program_path, program) + reseal(evidence) + + def manifest_missing(_source, evidence, _compiler, _artifact): + (evidence / "MANIFEST.sha256").unlink() + + def missing_canonical_file(_source, evidence, _compiler, _artifact): + (evidence / "checks.sarif").unlink() + reseal(evidence) + + def multi_source_launder(_source, evidence, _compiler, _artifact): + path = evidence / "program-evidence.json" + value = json.loads(path.read_text()) + value["source"]["merkle"] = "f" * 64 + dump(path, value) + reseal(evidence) + + def z3_trailing_error(_source, evidence, _compiler, _artifact): + smt_path = evidence / "analysis/proofs/obligation_0000.smt2" + smt_path.write_text(smt_path.read_text() + "(bad-command)\n") + solver_path = evidence / "solver.json" + solver = json.loads(solver_path.read_text()) + solver[0]["smt"] = smt_path.read_text() + dump(solver_path, solver) + program_path = evidence / "program-evidence.json" + program = json.loads(program_path.read_text()) + row = program["solver_inventory"]["obligations"][0] + row["smt_sha256"] = sha(smt_path.read_bytes()) + refresh_obligation_id(row) + refresh_solver_artifact(evidence, program) + dump(program_path, program) + reseal(evidence) + + run_case("stage-partial", stage_partial, "stage-not-pass") + run_case("consumer-omitted", consumer_omitted, "policy-consumer-roster") + run_case("consistent-rup-lie", rup_lie, "rup-replay-failed") + run_case("manifested-extra-file", extra_file, "bundle-file-roster") + run_case("zero-obligation-launder", zero_obligations, "zero-obligations") + run_case("symlink-hir", symlink_hir, "nonregular-file") + run_case("producer-verdict-launder", producer_verdict_launder, + "producer-evidence-mismatch") + run_case("solver-smt-decouple", solver_smt_decouple, "solver-smt-mismatch") + run_case("proof-path-reuse", proof_path_reuse, "proof-path-reuse") + run_case("proof-tuple-reuse", proof_tuple_reuse, "proof-reuse") + run_case("manifest-missing", manifest_missing, "manifest-missing") + run_case("missing-canonical-file", missing_canonical_file, "bundle-file-roster") + run_case("multi-source-launder", multi_source_launder, + "multi-source-unsupported") + run_case("z3-trailing-error", z3_trailing_error, "smt-not-unsat") + + with tempfile.TemporaryDirectory(prefix="jackal-program-root-symlink-") as td: + root = Path(td) + source, pristine, compiler_sha, artifact_sha, _marker = make_v3_fixture( + root / "a" + ) + link = root / "evidence-link" + link.symlink_to(pristine, target_is_directory=True) + clean = run_verify(source, pristine, compiler_sha, artifact_sha) + bad = run_verify(source, link, compiler_sha, artifact_sha) + restored = run_verify(source, pristine, compiler_sha, artifact_sha) + observed = reason(bad) + record( + "root-symlink", + clean.returncode == 0 + and bad.returncode == 1 + and observed == "input-path" + and restored.returncode == 0, + f"bad={observed} A2={restored.returncode}", + ) + + document = { + "schema": "jackal-anubis-program-hostile-v1", + "rows": ROWS, + "passed": sum(1 for row in ROWS if row["ok"]), + "failed": sum(1 for row in ROWS if not row["ok"]), + } + OUT.parent.mkdir(parents=True, exist_ok=True) + dump(OUT, document) + print(f"evidence={OUT} sha256={sha(OUT.read_bytes())}") + if document["failed"]: + print("ANUBIS_PROGRAM_HOSTILE_FAIL") + return 1 + print(f"ANUBIS_PROGRAM_HOSTILE_PASS rows={len(ROWS)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/anubis_program_verifier_test.py b/tests/anubis_program_verifier_test.py new file mode 100755 index 0000000..b2d50e9 --- /dev/null +++ b/tests/anubis_program_verifier_test.py @@ -0,0 +1,923 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +import hashlib +import json +import subprocess +import sys +import tempfile +import textwrap +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +VERIFIER = ROOT / "tools/anubis_program_verify.py" +POLICY_PATH = ROOT / "release/program/inventory_safe_v1.json" +VERIFY_TIME = "1787097600" +APPROVED_CHECK_COMPILER_SHA256 = ( + "0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6" +) +APPROVED_Z3_SHA256 = ( + "ae6c8df33db9c9ae9a80b6044e77cd66529a141d8b25f0620f1e89b409594f48" +) +REQUIRED_STAGES = [ + "parse", + "typecheck", + "monomorphization", + "policy-effects", + "policy-capability", + "policy-information-flow", + "policy-declassification", + "symbolic", + "solver", + "source-binding", + "artifact-binding", + "evidence-closure", +] +REQUIRED_CONSUMERS = [ + "effects", + "capability", + "information-flow", + "declassification", + "mode", + "contracts", +] +PRODUCER_RESIDUALS = [ + "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", +] +RECEIPT_RESIDUALS = [ + *PRODUCER_RESIDUALS, + "policy-construct-totality-not-established", +] +POLICY_BODY = { + "schema": "jackal-anubis-program-policy-v1", + "profile": "inventory-safe-v1", + "mode": "safe", + "source_leaves": 1, + "minimum_obligations": 1, + "proof_kinds": ["rup_refutation"], + "required_stages": REQUIRED_STAGES, + "required_consumers": REQUIRED_CONSUMERS, + "approved_check_compiler_sha256": APPROVED_CHECK_COMPILER_SHA256, + "approved_z3_sha256": APPROVED_Z3_SHA256, + "runtime_execution": False, + "policy_inventory_authority": "producer-attested-function-roster", + "independent_policy_construct_totality": False, + "receipt_residual_non_claims": RECEIPT_RESIDUALS, +} + + +def sha(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def compact(value: object) -> bytes: + return json.dumps( + value, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + + +POLICY_SHA256 = sha(compact(POLICY_BODY)) + + +def dump(path: Path, value: object) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps(value, indent=2, ensure_ascii=False, sort_keys=True) + "\n", + encoding="utf-8", + ) + + +def reseal(evidence: Path) -> None: + rows = [] + for path in sorted(evidence.rglob("*")): + if path.is_file() and path.name != "MANIFEST.sha256": + relative = path.relative_to(evidence).as_posix() + rows.append(f"{sha(path.read_bytes())} {relative}\n") + (evidence / "MANIFEST.sha256").write_text("".join(rows), encoding="ascii") + + +def _summary_hashes(evidence: Path) -> dict[str, str]: + return { + "build_log_hash": sha((evidence / "build.log").read_bytes()), + "environment_hash": sha((evidence / "environment.json").read_bytes()), + "source_tree_hash": sha((evidence / "source-tree.json").read_bytes()), + "sarif_hash": sha((evidence / "checks.sarif").read_bytes()), + "bounty_report_hash": sha((evidence / "bounty-report.md").read_bytes()), + } + + +def make_v3_fixture(root: Path) -> tuple[Path, Path, str, str, Path]: + root.mkdir(parents=True, exist_ok=True) + source = root / "main.anb" + source.write_text( + "fn id(x: i64) -> i64 ensures(result == x) { return x; }\n" + "fn main() { assert(id(7) == 7); }\n", + encoding="utf-8", + ) + marker = root / "ARTIFACT_EXECUTED" + evidence = root / "evidence" + proof_dir = evidence / "analysis" / "proofs" + proof_dir.mkdir(parents=True) + (evidence / "source.anubis").write_bytes(source.read_bytes()) + artifact = evidence / "artifact" + artifact.write_text( + f'#!/bin/sh\nprintf executed > "{marker}"\n', encoding="utf-8" + ) + artifact.chmod(0o755) + + function = { + "effects": [], + "mode": "safe", + "module": None, + "name": "id", + "params": [], + "span": [0, 60], + "symbols": [], + } + function_id = sha(compact(function)) + hir = {"functions": [function], "imports": [], "modules": []} + mir: list[object] = [] + taint: list[object] = [] + mono: list[object] = [] + smt = "(set-logic QF_BV)\n(assert false)\n(check-sat)\n(get-model)\n" + obligation_name = "ensures:id" + solver = [ + { + "detail": "proved", + "model": None, + "name": obligation_name, + "smt": smt, + "status": "PASS", + } + ] + dump(evidence / "hir.json", hir) + dump(evidence / "mir.json", mir) + dump(evidence / "taint-traces.json", taint) + dump(evidence / "mono_specializations.json", mono) + dump(evidence / "solver.json", solver) + (proof_dir / "obligation_0000.smt2").write_text(smt, encoding="utf-8") + (proof_dir / "obligation_0000.cnf").write_text( + "p cnf 1 2\n1 0\n-1 0\n", encoding="ascii" + ) + (proof_dir / "obligation_0000.drat").write_text("0\n", encoding="ascii") + proof_row = { + "obligation": obligation_name, + "status": "PASS", + "proof": "rup_refutation", + "smt": "analysis/proofs/obligation_0000.smt2", + "cnf_dimacs": "analysis/proofs/obligation_0000.cnf", + "proof_drat": "analysis/proofs/obligation_0000.drat", + "num_vars": 1, + "num_clauses": 2, + "steps": 1, + "checker": "test-rup-checker", + "checker_version": "1", + "replay": "test-rup-replay", + } + dump( + evidence / "analysis/proofs.json", + {"note": "test", "obligations": [proof_row]}, + ) + + stable_obligation = { + "name": obligation_name, + "smt_sha256": sha((proof_dir / "obligation_0000.smt2").read_bytes()), + "cnf_sha256": sha((proof_dir / "obligation_0000.cnf").read_bytes()), + "proof_sha256": sha((proof_dir / "obligation_0000.drat").read_bytes()), + } + obligation_id = sha(compact(stable_obligation)) + compiler_sha = "a" * 64 + artifact_sha = sha(artifact.read_bytes()) + consumers = [] + for consumer_id in ("effects", "capability", "information-flow"): + consumers.append( + { + "id": consumer_id, + "status": "PASS", + "authority": "anubis-typecheck-producer-attested", + "subjects": [function_id], + } + ) + consumers.append( + { + "id": "declassification", + "status": "PASS", + "authority": "anubis-source-walker-producer-attested", + "subjects": {"count": 0}, + } + ) + consumers.append( + { + "id": "mode", + "status": "PASS", + "authority": "anubis-typecheck-producer-attested", + "subjects": [function_id], + } + ) + consumers.append( + { + "id": "contracts", + "status": "PASS", + "authority": "anubis-typecheck-producer-attested", + "subjects": {"solver_obligation_count": 1}, + } + ) + program = { + "schema": "anubis.program-evidence.v3", + "version": 3, + "mode": "safe", + "source": { + "path": "source.anubis", + "sha256": sha(source.read_bytes()), + "merkle": sha(source.read_bytes()), + "bytes": len(source.read_bytes()), + }, + "compiler": { + "tool": "anubis 0.1.0", + "path_basename": "anubis", + "sha256": compiler_sha, + }, + "artifacts": { + "hir": { + "path": "hir.json", + "sha256": sha((evidence / "hir.json").read_bytes()), + "bytes": (evidence / "hir.json").stat().st_size, + }, + "mir": { + "path": "mir.json", + "sha256": sha((evidence / "mir.json").read_bytes()), + "bytes": (evidence / "mir.json").stat().st_size, + }, + "taint": { + "path": "taint-traces.json", + "sha256": sha((evidence / "taint-traces.json").read_bytes()), + "bytes": (evidence / "taint-traces.json").stat().st_size, + }, + "solver": { + "path": "solver.json", + "sha256": sha((evidence / "solver.json").read_bytes()), + "bytes": (evidence / "solver.json").stat().st_size, + }, + "monomorphization": { + "path": "mono_specializations.json", + "sha256": sha((evidence / "mono_specializations.json").read_bytes()), + "bytes": (evidence / "mono_specializations.json").stat().st_size, + }, + "native": {"path": "artifact", "sha256": artifact_sha}, + }, + "stages": [ + {"id": value, "status": "PASS", "authority": "test"} + for value in REQUIRED_STAGES + ], + "solver_inventory": { + "count": 1, + "obligations": [ + { + "id": obligation_id, + "name": obligation_name, + "status": "PASS", + "proof_kind": "rup_refutation", + "smt_path": proof_row["smt"], + "smt_sha256": stable_obligation["smt_sha256"], + "cnf_path": proof_row["cnf_dimacs"], + "cnf_sha256": stable_obligation["cnf_sha256"], + "proof_path": proof_row["proof_drat"], + "proof_sha256": stable_obligation["proof_sha256"], + "num_vars": 1, + "num_clauses": 2, + "steps": 1, + "checker": "test-rup-checker", + "checker_version": "1", + } + ], + }, + "policy_inventory": { + "functions": [ + { + "id": function_id, + "name": "id", + "module": None, + "mode": "safe", + "effects": [], + "param_count": 0, + "symbol_count": 0, + } + ], + "consumers": consumers, + "capabilities_present_count": 0, + "taint_trace_count": 0, + "monomorphization_count": 0, + "mir_function_count": 0, + }, + "residual_non_claims": PRODUCER_RESIDUALS, + } + dump(evidence / "program-evidence.json", program) + (evidence / "build.log").write_text("synthetic build log\n", encoding="utf-8") + (evidence / "bounty-report.md").write_text("# Synthetic report\n", encoding="utf-8") + (evidence / "validate.sh").write_text("#!/bin/sh\nexit 0\n", encoding="utf-8") + (evidence / "program.entitlements").write_text("\n", encoding="utf-8") + (evidence / "analysis/solver.smt2").write_text(smt, encoding="utf-8") + dump( + evidence / "analysis/solver_replay.json", + {"replay_valid": True, "status": "counterexample_replayed"}, + ) + dump(evidence / "checks.sarif", {"runs": [], "version": "2.1.0"}) + dump(evidence / "confinement_manifest.json", {"capabilities_present": []}) + dump(evidence / "declassify_audit.json", {"declassifications": []}) + dump(evidence / "entitlement_profile.json", {"entitlements": []}) + dump(evidence / "environment.json", {"anubis": "0.1.0"}) + dump(evidence / "source-tree.json", []) + dump(evidence / "summaries.json", {"functions": []}) + + summary_hashes = _summary_hashes(evidence) + source_hash = sha(source.read_bytes()) + build_log_hash = summary_hashes["build_log_hash"] + check_details = { + "source_hash": source_hash, + "build_log_hash": build_log_hash, + "artifact_hash": artifact_sha, + } + checks = [ + { + "name": value, + "status": "PASS", + "detail": check_details.get(value, "test"), + } + for value in ( + "parse", + "typecheck", + "monomorphization", + "symbolic", + "solver", + "source_hash", + "build_log_hash", + "artifact", + "artifact_hash", + ) + ] + manifest_summary = sha( + f"{source_hash}:{build_log_hash}:{summary_hashes['source_tree_hash']}:PASS".encode() + ) + evidence_summary = { + "timestamp": "test", + "tool": "anubis 0.1.0", + "mode": "safe", + "source_hash": source_hash, + "build_log_hash": build_log_hash, + "artifact_hash": artifact_sha, + "lane": "safe", + **summary_hashes, + "manifest_sha256": manifest_summary, + "checks": checks, + "verdict": "PASS", + "security": {"mode": "safe", "note": "test"}, + } + dump(evidence / "evidence.json", evidence_summary) + dump(evidence / "manifest.json", evidence_summary) + dump( + evidence / "pca.json", + { + "pca_version": 2, + "source_sha256": source_hash, + "mode": "safe", + "tier": "checked", + "rejection": None, + "parse_ok": True, + "typecheck_ok": True, + "solver_obligations": 1, + "solver_all_discharged": True, + "solver_backend": "z3", + "zk_present": False, + "zk_image_id": None, + "zk_receipt_sha256": None, + "zk_journal_sha256": None, + "verdict": "PASS", + "tool": "anubis 0.1.0", + }, + ) + reseal(evidence) + return source, evidence, compiler_sha, artifact_sha, marker + + +def run_verify( + source: Path, + evidence: Path, + compiler_sha: str, + artifact_sha: str | None = None, + *, + profile: str = "inventory-safe-v1", + expected_source_sha256: str | None = None, + expected_policy_sha256: str = POLICY_SHA256, +) -> subprocess.CompletedProcess[str]: + command = [ + sys.executable, + "-I", + "-S", + "-B", + str(VERIFIER), + "verify", + "--source", + str(source), + "--evidence-dir", + str(evidence), + "--expected-source-sha256", + expected_source_sha256 or sha(source.read_bytes()), + "--expected-compiler-sha256", + compiler_sha, + "--expected-policy-sha256", + expected_policy_sha256, + "--verification-time-unix", + VERIFY_TIME, + "--profile", + profile, + "--nonce", + "test-nonce", + ] + if artifact_sha is not None: + command += ["--expected-artifact-sha256", artifact_sha] + return subprocess.run( + command, capture_output=True, text=True, cwd=ROOT, timeout=30 + ) + + +class AnubisProgramVerifierTest(unittest.TestCase): + def test_program_wrapper_resolves_a_symlinked_invocation(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-wrapper-link-") as td: + link = Path(td) / "jackal-anubis-program" + link.symlink_to(ROOT / "jackal-anubis-program") + completed = subprocess.run( + [str(link), "--help"], + capture_output=True, + text=True, + cwd=ROOT, + timeout=30, + ) + self.assertEqual(completed.returncode, 0, completed.stdout + completed.stderr) + self.assertIn("verify-receipt", completed.stdout) + + def test_compiler_execution_uses_a_private_pinned_snapshot(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-compiler-snapshot-") as td: + root = Path(td) + compiler = root / "anubis-test" + compiler.write_bytes(b"#!/bin/sh\nexit 0\n") + compiler.chmod(0o755) + original_digest = sha(compiler.read_bytes()) + probe = textwrap.dedent( + f""" + import hashlib + import importlib.util + from pathlib import Path + + verifier_path = Path({str(VERIFIER)!r}) + compiler = Path({str(compiler)!r}) + spec = importlib.util.spec_from_file_location( + "anubis_program_verify_snapshot_test", verifier_path + ) + verifier = importlib.util.module_from_spec(spec) + spec.loader.exec_module(verifier) + try: + with verifier.pinned_executable_snapshot( + compiler, {original_digest!r} + ) as snapshot: + assert snapshot != compiler + assert snapshot.name == compiler.name + assert hashlib.sha256(snapshot.read_bytes()).hexdigest() == {original_digest!r} + compiler.write_bytes(b"#!/bin/sh\\nexit 99\\n") + assert hashlib.sha256(snapshot.read_bytes()).hexdigest() == {original_digest!r} + except verifier.Refusal as refusal: + print(refusal.reason) + else: + raise SystemExit("compiler mutation was not reported") + """ + ) + completed = subprocess.run( + [sys.executable, "-I", "-S", "-B", "-c", probe], + capture_output=True, + text=True, + cwd=ROOT, + timeout=30, + ) + self.assertEqual(completed.returncode, 0, completed.stdout + completed.stderr) + self.assertEqual(completed.stdout.strip(), "compiler-toctou") + + def test_fixture_quotes_execution_marker_path(self) -> None: + with tempfile.TemporaryDirectory( + prefix="jackal program fixture with spaces " + ) as td: + _, evidence, _, _, marker = make_v3_fixture(Path(td)) + script = (evidence / "artifact").read_text(encoding="utf-8") + self.assertIn(f'> "{marker}"', script) + + def test_check_program_refuses_dangling_out_root_symlink(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-out-root-") as td: + root = Path(td) + source = root / "main.anb" + compiler = root / "anubis" + source.write_text("fn main() {}\n", encoding="utf-8") + compiler.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8") + compiler.chmod(0o755) + out_root = root / "dangling-output" + out_root.symlink_to(root / "missing-target", target_is_directory=True) + compiler_sha = sha(compiler.read_bytes()) + probe = textwrap.dedent( + f""" + import argparse + import importlib.util + + spec = importlib.util.spec_from_file_location( + "anubis_program_verify_out_root_test", {str(VERIFIER)!r} + ) + verifier = importlib.util.module_from_spec(spec) + spec.loader.exec_module(verifier) + verifier.APPROVED_CHECK_COMPILER_SHA256 = {compiler_sha!r} + arguments = argparse.Namespace( + source={str(source)!r}, + anubis_bin={str(compiler)!r}, + expected_source_sha256={sha(source.read_bytes())!r}, + expected_compiler_sha256={compiler_sha!r}, + expected_policy_sha256=verifier.PROGRAM_POLICY_SHA256, + verification_time_unix={VERIFY_TIME!r}, + out_root={str(out_root)!r}, + profile="inventory-safe-v1", + nonce="test-nonce", + emit_receipt=None, + ) + try: + verifier.check_program(arguments) + except verifier.Refusal as refusal: + print(refusal.reason) + else: + raise SystemExit("dangling output symlink was accepted") + """ + ) + completed = subprocess.run( + [sys.executable, "-I", "-S", "-B", "-c", probe], + capture_output=True, + text=True, + cwd=ROOT, + timeout=30, + ) + self.assertEqual(completed.returncode, 0, completed.stdout + completed.stderr) + self.assertEqual(completed.stdout.strip(), "output-exists") + + def test_rup_replay_has_a_wall_clock_budget(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-budget-") as td: + root = Path(td) + probe = root / "probe.py" + probe.write_text( + textwrap.dedent( + """ + import importlib.util + import pathlib + import sys + + verifier = pathlib.Path(sys.argv[1]) + spec = importlib.util.spec_from_file_location("program_verify_probe", verifier) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + cnf = pathlib.Path(sys.argv[2]) + proof = pathlib.Path(sys.argv[3]) + + class Clock: + def __init__(self): + self.calls = 0 + def __call__(self): + self.calls += 1 + return 0.0 if self.calls == 1 else 31.0 + + try: + module.verify_rup( + cnf, + proof, + timeout_seconds=30.0, + clock=Clock(), + ) + except module.Refusal as error: + print(error.reason) + """ + ), + encoding="utf-8", + ) + cnf = root / "probe.cnf" + cnf.write_text("p cnf 1 2\n1 0\n-1 0\n", encoding="ascii") + proof = root / "probe.drat" + proof.write_text("0\n", encoding="ascii") + completed = subprocess.run( + [ + sys.executable, + "-I", + "-S", + "-B", + str(probe), + str(VERIFIER), + str(cnf), + str(proof), + ], + capture_output=True, + text=True, + cwd=ROOT, + timeout=30, + ) + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual(completed.stdout.strip(), "proof-budget") + + def test_manifest_rows_and_digest_come_from_one_read(self) -> None: + source = VERIFIER.read_text(encoding="utf-8") + tree = ast.parse(source) + function = next( + node + for node in tree.body + if isinstance(node, ast.FunctionDef) and node.name == "verify_manifest" + ) + calls = [ + node + for node in ast.walk(function) + if isinstance(node, ast.Call) + and isinstance(node.func, ast.Attribute) + and isinstance(node.func.value, ast.Name) + and node.func.value.id == "manifest" + and node.func.attr in {"read_bytes", "read_text"} + ] + self.assertEqual( + [(call.func.attr) for call in calls], + ["read_bytes"], + "manifest parsing and digesting must share one immutable byte buffer", + ) + + def test_snapshot_cleanup_is_guaranteed_by_finally(self) -> None: + source = VERIFIER.read_text(encoding="utf-8") + tree = ast.parse(source) + function = next( + node + for node in tree.body + if isinstance(node, ast.FunctionDef) and node.name == "build_receipt" + ) + cleanup_in_finally = any( + isinstance(node, ast.Try) + and any( + isinstance(child, ast.Call) + and isinstance(child.func, ast.Attribute) + and child.func.attr == "cleanup" + for statement in node.finalbody + for child in ast.walk(statement) + ) + for node in ast.walk(function) + ) + self.assertTrue(cleanup_in_finally) + + def test_surface_uses_honestly_weaker_inventory_profile(self) -> None: + self.assertTrue(VERIFIER.is_file(), VERIFIER) + self.assertTrue(POLICY_PATH.is_file(), POLICY_PATH) + policy = json.loads(POLICY_PATH.read_text(encoding="utf-8")) + self.assertEqual(policy["profile"], "inventory-safe-v1") + self.assertIs(policy["independent_policy_construct_totality"], False) + self.assertEqual(policy["policy_digest_sha256"], POLICY_SHA256) + self.assertEqual( + {key: value for key, value in policy.items() if key != "policy_digest_sha256"}, + POLICY_BODY, + ) + + def test_pca_v2_refuses_as_insufficient_for_program_evidence_status(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-v2-") as td: + root = Path(td) + source = root / "main.anb" + source.write_text("fn main() { assert(true); }\n", encoding="utf-8") + evidence = root / "evidence" + evidence.mkdir() + (evidence / "source.anubis").write_bytes(source.read_bytes()) + dump(evidence / "pca.json", {"pca_version": 2, "verdict": "PASS"}) + reseal(evidence) + result = run_verify(source, evidence, "0" * 64) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("reason=unsupported-program-evidence-version", result.stdout) + + def test_v3_inventory_profile_verifies_replays_and_never_executes_artifact(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-v3-") as td: + source, evidence, compiler_sha, artifact_sha, marker = make_v3_fixture( + Path(td) + ) + receipt = Path(td) / "receipt.json" + command = [ + sys.executable, + "-I", + "-S", + "-B", + str(VERIFIER), + "verify", + "--source", + str(source), + "--evidence-dir", + str(evidence), + "--expected-source-sha256", + sha(source.read_bytes()), + "--expected-compiler-sha256", + compiler_sha, + "--expected-artifact-sha256", + artifact_sha, + "--expected-policy-sha256", + POLICY_SHA256, + "--verification-time-unix", + VERIFY_TIME, + "--profile", + "inventory-safe-v1", + "--nonce", + "test-nonce", + "--emit-receipt", + str(receipt), + ] + emitted = subprocess.run( + command, capture_output=True, text=True, cwd=ROOT, timeout=30 + ) + self.assertEqual(emitted.returncode, 0, emitted.stdout + emitted.stderr) + self.assertFalse(marker.exists(), "compiled artifact was executed") + document = json.loads(receipt.read_text(encoding="utf-8")) + self.assertEqual(document["status"], "verified-program-evidence") + self.assertEqual(document["profile"], "inventory-safe-v1") + self.assertEqual(document["proof_replay"]["verified"], 1) + self.assertEqual( + document["assurance"]["policy_construct_totality"], + "not-established", + ) + self.assertEqual(document["assurance"]["smt_to_cnf"], "open") + self.assertEqual(document["assurance"]["source_to_vc"], "open") + self.assertEqual(document["assurance"]["runtime"], "not-observed") + self.assertEqual(document["residual_non_claims"], RECEIPT_RESIDUALS) + + replayed_receipt = Path(td) / "replayed-receipt.json" + replay = subprocess.run( + [ + sys.executable, + "-I", + "-S", + "-B", + str(VERIFIER), + "verify-receipt", + "--receipt", + str(receipt), + "--source", + str(source), + "--evidence-dir", + str(evidence), + "--expected-source-sha256", + sha(source.read_bytes()), + "--expected-compiler-sha256", + compiler_sha, + "--expected-artifact-sha256", + artifact_sha, + "--expected-policy-sha256", + POLICY_SHA256, + "--verification-time-unix", + VERIFY_TIME, + "--profile", + "inventory-safe-v1", + "--nonce", + "test-nonce", + "--emit-receipt", + str(replayed_receipt), + ], + capture_output=True, + text=True, + cwd=ROOT, + timeout=30, + ) + self.assertEqual(replay.returncode, 0, replay.stdout + replay.stderr) + self.assertIn("status=verified-program-receipt", replay.stdout) + self.assertEqual( + json.loads(replayed_receipt.read_text(encoding="utf-8")), + document, + "receipt replay must emit the verifier-recomputed canonical receipt", + ) + self.assertFalse(marker.exists(), "receipt replay executed artifact") + + def test_plugin_program_success_statuses_exit_zero(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-plugin-") as td: + source, evidence, compiler_sha, artifact_sha, marker = make_v3_fixture( + Path(td) + ) + arguments = { + "source_path": str(source), + "evidence_dir": str(evidence), + "expected_source_sha256": sha(source.read_bytes()), + "expected_compiler_sha256": compiler_sha, + "expected_artifact_sha256": artifact_sha, + "expected_policy_sha256": POLICY_SHA256, + "verification_time_unix": VERIFY_TIME, + "profile": "inventory-safe-v1", + "nonce": "plugin-exit", + } + launcher = ROOT / "plugin/hermes/jackal_hermes" + verified = subprocess.run( + [ + str(launcher), + "call", + "jackal_anubis_verify_program", + json.dumps(arguments), + ], + capture_output=True, + text=True, + cwd=ROOT, + timeout=30, + ) + self.assertEqual( + verified.returncode, 0, verified.stdout + verified.stderr + ) + document = json.loads(verified.stdout) + self.assertEqual(document["status"], "verified-program-evidence") + replay = subprocess.run( + [ + str(launcher), + "call", + "jackal_anubis_verify_program_receipt", + json.dumps({**arguments, "receipt": document["receipt"]}), + ], + capture_output=True, + text=True, + cwd=ROOT, + timeout=30, + ) + self.assertEqual(replay.returncode, 0, replay.stdout + replay.stderr) + self.assertEqual( + json.loads(replay.stdout)["status"], "verified-program-receipt" + ) + self.assertFalse(marker.exists(), "plugin executed compiled artifact") + + def test_each_caller_identity_pin_mismatch_refuses(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-pins-") as td: + source, evidence, compiler_sha, artifact_sha, _ = make_v3_fixture( + Path(td) + ) + cases = { + "source-pin-mismatch": { + "compiler_sha": compiler_sha, + "artifact_sha": artifact_sha, + "expected_source_sha256": "f" * 64, + }, + "compiler-pin-mismatch": { + "compiler_sha": "f" * 64, + "artifact_sha": artifact_sha, + }, + "artifact-pin-mismatch": { + "compiler_sha": compiler_sha, + "artifact_sha": "f" * 64, + }, + "policy-pin-mismatch": { + "compiler_sha": compiler_sha, + "artifact_sha": artifact_sha, + "expected_policy_sha256": "f" * 64, + }, + } + for reason, values in cases.items(): + with self.subTest(reason=reason): + result = run_verify(source, evidence, **values) + self.assertEqual( + result.returncode, 1, result.stdout + result.stderr + ) + self.assertIn(f"reason={reason}", result.stdout) + + def test_contracted_safe_profile_is_not_silently_inherited(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-profile-") as td: + source, evidence, compiler_sha, artifact_sha, _ = make_v3_fixture(Path(td)) + result = run_verify( + source, + evidence, + compiler_sha, + artifact_sha, + profile="contracted-safe-v1", + ) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("reason=profile-unsupported", result.stdout) + + def test_program_inventory_without_producer_summaries_refuses(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-producerless-") as td: + source, evidence, compiler_sha, artifact_sha, _ = make_v3_fixture(Path(td)) + (evidence / "evidence.json").unlink() + (evidence / "pca.json").unlink() + reseal(evidence) + result = run_verify(source, evidence, compiler_sha, artifact_sha) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("reason=bundle-file-roster", result.stdout) + + def test_summary_hash_laundering_refuses_after_consistent_reseal(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-program-summary-") as td: + source, evidence, compiler_sha, artifact_sha, _ = make_v3_fixture(Path(td)) + summary_path = evidence / "evidence.json" + summary = json.loads(summary_path.read_text(encoding="utf-8")) + summary["build_log_hash"] = "0" * 64 + dump(summary_path, summary) + dump(evidence / "manifest.json", summary) + reseal(evidence) + result = run_verify(source, evidence, compiler_sha, artifact_sha) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("reason=producer-evidence-mismatch", result.stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/capability_drift_gate_test.py b/tests/capability_drift_gate_test.py new file mode 100644 index 0000000..ec1d5f9 --- /dev/null +++ b/tests/capability_drift_gate_test.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +"""Semantic documentation, package-pin, adapter, and skill drift controls.""" + +from __future__ import annotations + +import importlib.util +import json +import shutil +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +DRIFT_PATH = ROOT / "tools/capability_drift_gate.py" +ARTIFACT_PATH = Path("release/capability_inventory_v1.json") +CURRENT_SURFACES = ( + Path("README.md"), + Path("GETTING-STARTED.md"), + Path("PROVENANCE.md"), + Path("docs/superpowers/specs/2026-08-17-jackel-codex-plugin-design.md"), + Path("plugins/jackel/skills/jackel/SKILL.md"), +) + + +def load_module(name: str, path: Path): + spec = importlib.util.spec_from_file_location(name, path) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot load {name}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +DRIFT = load_module("capability_drift_gate", DRIFT_PATH) +INVENTORY = load_module("capability_inventory_for_drift_test", ROOT / "tools/capability_inventory.py") + + +def read_json(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def write_json(path: Path, value: object) -> None: + path.write_text( + json.dumps(value, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + + +class DriftFixture: + def __init__(self) -> None: + self.root = Path(tempfile.mkdtemp(prefix="jackal-capability-drift-")) + paths = { + *INVENTORY.INPUT_PATHS, + INVENTORY.PROGRAM_FLOOR_PATH, + INVENTORY.PROGRAM_POLICY_PATH, + ARTIFACT_PATH, + *CURRENT_SURFACES, + Path("plugins/jackel/scripts/provision_runtime.py"), + Path("plugins/jackel/PLUGIN_IDENTITY.sha256"), + *( + Path("plugins/jackel") / relative + for relative in DRIFT.CODEX_PLUGIN_IDENTITY_FILES + ), + DRIFT.PACKAGE_EVIDENCE_PATH, + } + for relative in sorted(paths): + destination = self.root / relative + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(ROOT / relative, destination) + + def cleanup(self) -> None: + shutil.rmtree(self.root, ignore_errors=True) + + +class CapabilityDriftPositiveTest(unittest.TestCase): + def test_package_pin_uses_dedicated_alignment_receipt(self) -> None: + self.assertEqual( + DRIFT.PACKAGE_EVIDENCE_PATH, + Path("release/evidence/package_alignment_v173_release.json"), + ) + + 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["package_epoch"], "v1.7.3") + + def test_historical_34_tool_fact_outside_current_contract_is_allowed(self) -> None: + fixture = DriftFixture() + try: + provenance = fixture.root / "PROVENANCE.md" + provenance.write_text( + provenance.read_text(encoding="utf-8") + + "\n## Historical migration record\n\n" + + "The v1.7.0 release exposed a 34-tool surface.\n", + encoding="utf-8", + ) + result = DRIFT.verify_surface(fixture.root) + self.assertEqual(result["tool_count"], 41) + finally: + fixture.cleanup() + + 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"]} + skill = (ROOT / "plugins/jackel/skills/jackel/SKILL.md").read_text( + encoding="utf-8" + ) + names = DRIFT.skill_tool_names(skill) + self.assertTrue(names) + self.assertTrue(names <= known) + + def test_codex_plugin_identity_is_generated_from_exact_wrapper_bytes(self) -> None: + self.assertEqual( + (ROOT / "plugins/jackel/PLUGIN_IDENTITY.sha256").read_bytes(), + DRIFT.render_codex_plugin_identity(ROOT), + ) + + def test_cli_reports_bound_counts_and_package_epoch(self) -> None: + completed = subprocess.run( + [sys.executable, "-B", str(DRIFT_PATH), "--root", str(ROOT)], + capture_output=True, + text=True, + check=False, + timeout=120, + ) + 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", + ) + + +class CapabilityDriftRefusalTest(unittest.TestCase): + def replace_once(self, source: str, needle: str, replacement: str) -> str: + self.assertIn(needle, source, f"mutation needle absent: {needle!r}") + mutated = source.replace(needle, replacement, 1) + self.assertNotEqual(mutated, source, "mutation did not change source") + return mutated + + def test_refuses_current_tool_count_drift(self) -> None: + fixture = DriftFixture() + try: + path = fixture.root / "plugins/jackel/.codex-plugin/plugin.json" + document = read_json(path) + document["interface"]["longDescription"] = self.replace_once( + document["interface"]["longDescription"], "41-tool", "34-tool" + ) + write_json(path, document) + with self.assertRaisesRegex(DRIFT.DriftError, "current-tool-count"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_unknown_skill_tool(self) -> None: + fixture = DriftFixture() + try: + skill = fixture.root / "plugins/jackel/skills/jackel/SKILL.md" + skill.write_text( + skill.read_text(encoding="utf-8") + + "\nRoute this through `jackal_nonexistent_probe`.\n", + encoding="utf-8", + ) + with self.assertRaisesRegex(DRIFT.DriftError, "unknown-skill-tool"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_package_pin_mismatch(self) -> None: + fixture = DriftFixture() + try: + provisioner = fixture.root / "plugins/jackel/scripts/provision_runtime.py" + source = provisioner.read_text(encoding="utf-8") + expected = read_json(ROOT / DRIFT.PACKAGE_EVIDENCE_PATH)["package"][ + "sha256" + ] + source = self.replace_once( + source, + f'PACKAGE_SHA256 = "{expected}"', + f'PACKAGE_SHA256 = "{"0" * 64}"', + ) + provisioner.write_text(source, encoding="utf-8") + with self.assertRaisesRegex(DRIFT.DriftError, "package-pin-mismatch"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_unknown_status_vocabulary(self) -> None: + fixture = DriftFixture() + try: + skill = fixture.root / "plugins/jackel/skills/jackel/SKILL.md" + skill.write_text( + skill.read_text(encoding="utf-8") + + "\nAdapter probe result: status=cosmic.\n", + encoding="utf-8", + ) + with self.assertRaisesRegex(DRIFT.DriftError, "status-vocabulary"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_codex_wrapper_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_TOOL_COUNT = 41", + "EXPECTED_TOOL_COUNT = 40", + ) + 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: + readme = fixture.root / "README.md" + text = self.replace_once( + readme.read_text(encoding="utf-8"), + DRIFT.CURRENT_SURFACE_BEGIN, + "", + ) + readme.write_text(text, encoding="utf-8") + with self.assertRaisesRegex(DRIFT.DriftError, "current-surface-marker"): + DRIFT.verify_surface(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_codex_plugin_identity_drift(self) -> None: + fixture = DriftFixture() + try: + identity = fixture.root / "plugins/jackel/PLUGIN_IDENTITY.sha256" + source = identity.read_text(encoding="utf-8") + identity.write_text( + self.replace_once(source, "a", "b"), encoding="utf-8" + ) + with self.assertRaisesRegex(DRIFT.DriftError, "plugin-identity-drift"): + DRIFT.check_codex_plugin_identity(fixture.root) + finally: + fixture.cleanup() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/capability_inventory_test.py b/tests/capability_inventory_test.py new file mode 100644 index 0000000..bbc2332 --- /dev/null +++ b/tests/capability_inventory_test.py @@ -0,0 +1,354 @@ +#!/usr/bin/env python3 +"""Contract and mutation tests for the canonical JACKAL capability inventory.""" + +from __future__ import annotations + +import copy +import hashlib +import importlib.util +import json +import shutil +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +GENERATOR_PATH = ROOT / "tools/capability_inventory.py" +ARTIFACT_PATH = Path("release/capability_inventory_v1.json") +CATALOG_PATH = Path("plugin/hermes/tools.json") +PROFILE_DIR = Path("plugin/hermes/profiles") +MANIFEST_PATH = Path("release/MANIFEST.sha256") +IDENTITY_PATHS = ( + Path("release/evidence/range_proof_identity_v172.json"), + Path("release/evidence/gaussian_proof_identity.json"), + Path("release/evidence/int_cert_proof_identity_v172.json"), +) + + +def load_generator(): + spec = importlib.util.spec_from_file_location("capability_inventory", GENERATOR_PATH) + if spec is None or spec.loader is None: + raise RuntimeError("cannot load capability inventory generator") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +INVENTORY = load_generator() + + +def read_json(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def write_json(path: Path, value: object) -> None: + path.write_text( + json.dumps(value, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + + +def reseal_profile(document: dict) -> None: + payload = { + key: value + for key, value in document.items() + if key != "profile_digest_sha256" + } + document["profile_digest_sha256"] = hashlib.sha256( + INVENTORY.canonical_bytes(payload) + ).hexdigest() + + +class InventoryFixture: + """Minimal throwaway inventory input tree with no live-source mutation.""" + + def __init__(self) -> None: + self.root = Path(tempfile.mkdtemp(prefix="jackal-capability-inventory-")) + paths = [ + Path("tools/capability_inventory.py"), + CATALOG_PATH, + MANIFEST_PATH, + ARTIFACT_PATH, + Path("plugin/hermes/server.py"), + Path("plugins/jackel/.codex-plugin/plugin.json"), + Path("plugins/jackel/mcp/server.py"), + Path("plugins/jackel/scripts/provision_runtime.py"), + Path("release/evidence/anubis_program_dogfood_v1.json"), + INVENTORY.PROGRAM_FLOOR_PATH, + INVENTORY.PROGRAM_POLICY_PATH, + *IDENTITY_PATHS, + *(PROFILE_DIR / f"{profile}.json" for profile in ("core", "formal", "full")), + ] + for relative in paths: + destination = self.root / relative + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(ROOT / relative, destination) + + def cleanup(self) -> None: + shutil.rmtree(self.root, ignore_errors=True) + + +class CapabilityInventoryPositiveTest(unittest.TestCase): + def test_inventory_graph_excludes_self_referential_package_delivery_pins(self) -> None: + """The package contains this inventory, so it cannot hash its own pins.""" + delivery_pins = { + Path("plugins/jackel/scripts/provision_runtime.py"), + Path("release/evidence/anubis_program_dogfood_v1.json"), + } + self.assertTrue(delivery_pins.isdisjoint(INVENTORY.INPUT_PATHS)) + document = INVENTORY.build_inventory(ROOT) + self.assertTrue( + {path.as_posix() for path in delivery_pins}.isdisjoint( + {row["path"] for row in document["inputs"]} + ) + ) + + def test_build_is_exact_ordered_41_tool_surface(self) -> None: + document = INVENTORY.build_inventory(ROOT) + records = document["tools"] + catalog = read_json(ROOT / CATALOG_PATH)["tools"] + names = [row["name"] for row in records] + self.assertEqual(names, [row["name"] for row in catalog]) + self.assertEqual(document["tool_count"], 41) + self.assertEqual(document["unique_tool_count"], 41) + self.assertEqual(len(names), 41) + self.assertEqual(len(set(names)), 41) + + def test_every_schema_identity_is_from_exact_catalog_record_bytes(self) -> None: + document = INVENTORY.build_inventory(ROOT) + catalog = read_json(ROOT / CATALOG_PATH)["tools"] + expected = { + row["name"]: hashlib.sha256(INVENTORY.canonical_bytes(row)).hexdigest() + for row in catalog + } + observed = {row["name"]: row["schema_sha256"] for row in document["tools"]} + self.assertEqual(observed, expected) + + def test_every_tool_has_explicit_exposure_status_boundary_and_dependency(self) -> None: + document = INVENTORY.build_inventory(ROOT) + for row in document["tools"]: + self.assertEqual( + row["exposure"], + {"kernel": True, "hermes": True, "codex": True}, + row["name"], + ) + self.assertTrue(row["status_classes"], row["name"]) + self.assertIn("refused", row["status_classes"], row["name"]) + self.assertTrue(row["assurance_classes"], row["name"]) + self.assertTrue(row["supported_fragment"], row["name"]) + self.assertTrue(row["refusal_boundary"], row["name"]) + self.assertIn("family", row["dependency"]) + self.assertIn("identities", row["dependency"]) + self.assertTrue(row["dependency"]["identities"], row["name"]) + self.assertEqual(row["release_state"], "v1.7.3") + self.assertEqual( + row["containing_ref"], + { + "kind": "surface-origin-commit", + "value": "d25bcd9818e0d106f337798f80527ae611cc3acc", + }, + ) + + self.assertEqual(document["release"]["state"], "v1.7.3") + self.assertEqual( + document["release"]["statement"], + "Published release identity; the annotated v1.7.3 tag and GitHub release must bind these exact bytes.", + ) + + def test_surface_origin_is_an_ancestor_with_the_same_catalog(self) -> None: + document = INVENTORY.build_inventory(ROOT) + INVENTORY.verify_surface_origin( + ROOT, + [row["name"] for row in document["tools"]], + ) + + def test_statuses_are_exact_catalog_tokens_and_allowed(self) -> None: + document = INVENTORY.build_inventory(ROOT) + catalog = read_json(ROOT / CATALOG_PATH)["tools"] + expected = { + row["name"]: row["returns"]["status"].split(" | ") for row in catalog + } + observed = {row["name"]: row["status_classes"] for row in document["tools"]} + self.assertEqual(observed, expected) + for statuses in observed.values(): + self.assertTrue(set(statuses) <= INVENTORY.ALLOWED_STATUSES) + + def test_profile_membership_is_derived_from_profile_bytes(self) -> None: + document = INVENTORY.build_inventory(ROOT) + memberships = { + profile: set(read_json(ROOT / PROFILE_DIR / f"{profile}.json")["tools"]) + for profile in ("core", "formal", "full") + } + for row in document["tools"]: + expected = [ + profile for profile in ("core", "formal", "full") + if row["name"] in memberships[profile] + ] + self.assertEqual(row["profiles"], expected, row["name"]) + + def test_dependency_families_cover_all_required_checker_classes(self) -> None: + document = INVENTORY.build_inventory(ROOT) + families = {row["dependency"]["family"] for row in document["tools"]} + self.assertTrue( + { + "lean-range", + "lean-gaussian", + "lean-int-cert", + "lean-receipt-registry", + "exact-cert-verifier", + "structural-checker", + "decision-checker", + "claim-router", + "claim-verifier", + "program-verifier", + "runtime-only", + } + <= families + ) + + def test_program_tools_bind_the_approved_program_compiler(self) -> None: + document = INVENTORY.build_inventory(ROOT) + approved = read_json( + ROOT / "release/compat/v173_floor.json" + )["approved_check_compiler_sha256"] + program_tools = [ + row for row in document["tools"] + if row["dependency"]["family"] == "program-verifier" + ] + self.assertEqual(len(program_tools), 3) + for row in program_tools: + identities = { + identity["label"]: identity + for identity in row["dependency"]["identities"] + } + self.assertNotIn("compiler_pin", identities) + self.assertEqual( + identities["approved_program_compiler"], + { + "label": "approved_program_compiler", + "locator": ( + "release/compat/v173_floor.json" + "#approved_check_compiler_sha256" + ), + "sha256": approved, + }, + ) + + def test_committed_artifact_is_generated_byte_for_byte(self) -> None: + INVENTORY.check_committed(ROOT) + self.assertEqual( + (ROOT / ARTIFACT_PATH).read_bytes(), + INVENTORY.render_inventory(ROOT), + ) + + def test_cli_check_reports_exact_count(self) -> None: + completed = subprocess.run( + [sys.executable, "-B", str(GENERATOR_PATH), "--check", "--root", str(ROOT)], + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual( + completed.stdout.strip(), + "CAPABILITY_INVENTORY_PASS tools=41 unique=41", + ) + + def test_ci_inventory_jobs_fetch_the_surface_origin_commit(self) -> None: + for relative in ( + ".github/workflows/gaussian-proof-gate.yml", + ".github/workflows/jackal-codex-plugin.yml", + ): + source = (ROOT / relative).read_text(encoding="utf-8") + inventory_step = source.find("tools/capability_inventory.py --check") + self.assertGreaterEqual(inventory_step, 0, relative) + checkout = source.rfind("uses: actions/checkout@", 0, inventory_step) + self.assertGreaterEqual(checkout, 0, relative) + next_step = source.find("\n - name:", checkout) + self.assertGreater(next_step, checkout, relative) + self.assertIn("fetch-depth: 0", source[checkout:next_step], relative) + + +class CapabilityInventoryRefusalTest(unittest.TestCase): + def test_refuses_duplicate_catalog_name(self) -> None: + fixture = InventoryFixture() + try: + catalog = read_json(fixture.root / CATALOG_PATH) + catalog["tools"].append(copy.deepcopy(catalog["tools"][0])) + write_json(fixture.root / CATALOG_PATH, catalog) + with self.assertRaisesRegex(INVENTORY.InventoryError, "duplicate-tool"): + INVENTORY.build_inventory(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_unmapped_tool_even_when_profiles_are_consistent(self) -> None: + fixture = InventoryFixture() + try: + catalog = read_json(fixture.root / CATALOG_PATH) + tool = next(row for row in catalog["tools"] if row["name"] == "jackal_exact") + tool["name"] = "jackal_unmapped_probe" + write_json(fixture.root / CATALOG_PATH, catalog) + full = read_json(fixture.root / PROFILE_DIR / "full.json") + full["tools"] = [ + "jackal_unmapped_probe" if name == "jackal_exact" else name + for name in full["tools"] + ] + reseal_profile(full) + write_json(fixture.root / PROFILE_DIR / "full.json", full) + with self.assertRaisesRegex(INVENTORY.InventoryError, "unmapped-tool"): + INVENTORY.build_inventory(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_unknown_status_vocabulary(self) -> None: + fixture = InventoryFixture() + try: + catalog = read_json(fixture.root / CATALOG_PATH) + catalog["tools"][0]["returns"]["status"] = "cosmic | refused" + write_json(fixture.root / CATALOG_PATH, catalog) + with self.assertRaisesRegex(INVENTORY.InventoryError, "status-vocabulary"): + INVENTORY.build_inventory(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_malformed_optional_consequence_ceiling(self) -> None: + fixture = InventoryFixture() + try: + catalog = read_json(fixture.root / CATALOG_PATH) + catalog["tools"][0]["returns"]["consequence_ceiling"] = 7 + write_json(fixture.root / CATALOG_PATH, catalog) + with self.assertRaisesRegex(INVENTORY.InventoryError, "catalog-shape"): + INVENTORY.build_inventory(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_missing_checker_identity(self) -> None: + fixture = InventoryFixture() + try: + manifest = (fixture.root / MANIFEST_PATH).read_text(encoding="utf-8") + manifest = "\n".join( + line for line in manifest.splitlines() + if not line.startswith("gaussian-checker ") + ) + "\n" + (fixture.root / MANIFEST_PATH).write_text(manifest, encoding="utf-8") + with self.assertRaisesRegex(INVENTORY.InventoryError, "missing-checker-identity"): + INVENTORY.build_inventory(fixture.root) + finally: + fixture.cleanup() + + def test_refuses_committed_artifact_drift(self) -> None: + fixture = InventoryFixture() + try: + artifact = read_json(fixture.root / ARTIFACT_PATH) + artifact["tool_count"] = 40 + write_json(fixture.root / ARTIFACT_PATH, artifact) + with self.assertRaisesRegex(INVENTORY.InventoryError, "artifact-drift"): + INVENTORY.check_committed(fixture.root) + finally: + fixture.cleanup() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/claim_aba_test.py b/tests/claim_aba_test.py index a2b09a8..e60a5a8 100755 --- a/tests/claim_aba_test.py +++ b/tests/claim_aba_test.py @@ -170,7 +170,7 @@ def main() -> int: "sha_restored": sha_r, "matrix_failed_under_poison": not ok_b, "case_failed_under_poison": case_failed, - "observed_under_poison": str(observed)[:160], + "observed_under_poison": str(observed)[:400], "restored_byte_identical": restored, "ok": status, }) diff --git a/tests/claim_hostile_test.py b/tests/claim_hostile_test.py index 95d59b8..163c4b7 100755 --- a/tests/claim_hostile_test.py +++ b/tests/claim_hostile_test.py @@ -510,7 +510,7 @@ def _trusted_producers() -> list[str]: def record(rid: str, ok: bool, expect: str, observed: str) -> None: ROWS.append({"id": rid, "ok": bool(ok), "expect": expect, - "observed": observed[:200]}) + "observed": observed[:400]}) print(f"{'PASS' if ok else 'FAIL'} {rid}" + ("" if ok else f" — expected {expect}, got {observed[:120]}")) @@ -657,7 +657,7 @@ def family_serialization() -> None: if k != "bundle_digest_sha256"})) ok = verdict == "verified" and f"bundle.digest={want}" in out record("S-canonical-roundtrip", ok, f"bundle.digest={want[:16]}", - out[:160].replace("\n", " ")) + out.replace("\n", " ")) def family_graph() -> None: @@ -1504,7 +1504,7 @@ def family_render() -> None: and "VERIFIED\n" not in out and "input provenance supplied" in out.lower()) record("R-pos-conditions-present", ok, - "rendering with conditions", out[:160].replace("\n", " ")) + "rendering with conditions", out.replace("\n", " ")) def main() -> int: diff --git a/tests/claim_package_parity_test.py b/tests/claim_package_parity_test.py index 218a92d..cfa1fb0 100755 --- a/tests/claim_package_parity_test.py +++ b/tests/claim_package_parity_test.py @@ -1,40 +1,49 @@ #!/usr/bin/env python3 -"""v1.7.0 package parity + fresh-extraction gate (mission §13 Phase 7, -dogfood §15.10; re-pointed at the live epoch builder each seal). - -1. Builds the v1.7.0 package TWICE from identical tree/evidence bytes and - requires bit-for-bit tarball equality. -2. Fresh-extracts the tarball into a bounded temp sandbox (deterministic - cleanup; no repository fallback). -3. Exercises every producer-lane tool the PACKAGE carries: 31 plugin tools - through the packaged plugin `call` frontend (30 v1.6.0 + - jackal_integrate_bound_cert), plus the 12 shell wrappers (10 release - wrappers, jackal-claim, jackal-claim-verify). The four domain-pack lanes - (`jackal_test_exists`, `jackal_claim_cites_test`, `jackal_decision_rank`, - `jackal_decision_rank_v2`) are declared on the repo surface but the v1.7.2 - package does not ship `domain_packs/` or their checkers, so inside the - package they refuse `pack-surface-absent` rather than serving a request. - Packaging the pack surface is a seal-time item, not a parity claim here. -4. Proves three-way parity: the same claim request through the repo CLI, - the fresh package CLI, and the plugin returns the SAME canonical root - hash and bundle digest, and the same policy verdict on replay. -5. A tampered bundle refuses through the packaged verifier. +"""v1.7.3 unified package parity and fresh-extraction gate. + +1. Builds the current v1.7.3 package twice into independent destinations from + one frozen repository state and requires byte-identical tarballs. +2. Fresh-extracts one tarball and verifies complete SHA256SUMS. +3. Exercises every self-contained calculator/claim tool plus all four + domain-pack tools and all three Anubis program-evidence plugin routes. +4. Proves three-way claim parity across repository CLI, package CLI, and + packaged plugin, then proves a tampered bundle refuses. +5. Refuses before building when the canonical instrument is pointed at a + superseded builder or catalog. Run: python3 tests/claim_package_parity_test.py """ from __future__ import annotations import hashlib +import filecmp import json import subprocess +import os import sys import tempfile from pathlib import Path +try: + from tests.anubis_program_verifier_test import ( + POLICY_SHA256, + VERIFY_TIME, + make_v3_fixture, + sha, + ) +except ModuleNotFoundError: + from anubis_program_verifier_test import ( + POLICY_SHA256, + VERIFY_TIME, + make_v3_fixture, + sha, + ) + ROOT = Path(__file__).resolve().parents[1] -BUILDER = ROOT / "release/build_package_v170.sh" -TARBALL = ROOT / "release/dist/jackal-v1.7.0-macos-arm64.tar.gz" -PKG_NAME = "jackal-v1.7.0-macos-arm64" +BUILDER = ROOT / "release/build_package_v173.sh" +PKG_NAME = "jackal-v1.7.3-macos-arm64" +TARBALL: Path | None = None +BUILD_HOLDER: tempfile.TemporaryDirectory[str] | None = None ROWS: list[dict] = [] @@ -131,20 +140,77 @@ def canon(obj) -> bytes: ] +def instrument_current() -> tuple[bool, str]: + if BUILDER.name != "build_package_v173.sh": + return False, "superseded-builder" + if not BUILDER.is_file(): + return False, "builder-missing" + catalog = json.loads( + (ROOT / "plugin/hermes/tools.json").read_text(encoding="utf-8") + ) + names = [row.get("name") for row in catalog.get("tools", [])] + if catalog.get("version") != "v1.7.3" or len(names) != 41: + return False, "stale-catalog" + full = json.loads( + (ROOT / "plugin/hermes/profiles/full.json").read_text(encoding="utf-8") + ) + if full.get("tools") != names: + return False, "profile-catalog-divergence" + return True, "current" + + def build_twice() -> bool: - hashes = [] + global BUILD_HOLDER, TARBALL + BUILD_HOLDER = tempfile.TemporaryDirectory(prefix="jackal-parity-builds-") + build_root = Path(BUILD_HOLDER.name) + tarballs: list[Path] = [] + hashes: list[str] = [] for attempt in (1, 2): - proc = subprocess.run(["sh", str(BUILDER)], capture_output=True, - text=True, timeout=600, cwd=ROOT) + destination = build_root / f"attempt-{attempt}" + destination.mkdir() + environment = os.environ.copy() + environment["JACKAL_DIST"] = str(destination) + proc = subprocess.run( + [str(BUILDER), "--build"], + capture_output=True, + text=True, + timeout=1800, + cwd=ROOT, + env=environment, + ) if proc.returncode != 0: - record(f"pkg-build-{attempt}", False, "exit 0", - (proc.stderr or proc.stdout)[-160:]) + record( + f"pkg-build-{attempt}", + False, + "exit 0", + (proc.stderr or proc.stdout)[-160:], + ) + return False + tarball = destination / f"{PKG_NAME}.tar.gz" + ok = tarball.is_file() and "PACKAGE_V173_BUILD_PASS" in proc.stdout + record( + f"pkg-build-{attempt}", + ok, + "current builder emits v1.7.3 tarball", + (proc.stdout or proc.stderr)[-160:].replace("\n", " "), + ) + if not ok: return False - hashes.append(sha_file(TARBALL)) - ok = hashes[0] == hashes[1] - record("pkg-double-build-identical", ok, "bit-identical tarball", - f"{hashes[0][:16]} vs {hashes[1][:16]}") - return ok + tarballs.append(tarball) + hashes.append(sha_file(tarball)) + identical = ( + hashes[0] == hashes[1] + and tarballs[0].stat().st_size == tarballs[1].stat().st_size + and filecmp.cmp(tarballs[0], tarballs[1], shallow=False) + ) + record( + "pkg-double-build-identical", + identical, + "byte-identical tarball", + f"{hashes[0]} vs {hashes[1]}", + ) + TARBALL = tarballs[0] + return identical def plugin_call(pkg: Path, tool: str, arguments: dict) -> dict: @@ -176,8 +242,18 @@ def cli_route(cli: Path, workdir: Path, tag: str) -> dict | None: def main() -> int: + current, reason = instrument_current() + record( + "pkg-instrument-current", + current, + "v1.7.3 builder, 41-tool catalog, matching full profile", + reason, + ) + if not current: + return finish() if not build_twice(): return finish() + assert TARBALL is not None with tempfile.TemporaryDirectory(prefix="jackal-claim-pkg-") as td: work = Path(td) @@ -194,7 +270,7 @@ def main() -> int: and "FAILED" not in (sums.stdout or ""), "all files verify", (sums.stdout or "")[-80:]) - # --- every plugin tool (33 v1.6.0 + 1 new v1.7.0) ------------ + # --- preserved calculator and claim plugin tools ---------------- for tool, arguments, allowed in PLUGIN_CASES: doc = plugin_call(pkg, tool, arguments) record(f"pkg-tool-{tool}", doc.get("status") in allowed, @@ -202,6 +278,204 @@ def main() -> int: f"status={doc.get('status')} " f"reason={doc.get('reason', '')}") + # --- domain-pack tools: two structural and two decision lanes ---- + structural_path = pkg / "tools/anubis_program_verify.py" + structural_relative = structural_path.relative_to(pkg).as_posix() + structural_lines = structural_path.read_text(encoding="utf-8").splitlines() + declaration_lines = [ + index + for index, line in enumerate(structural_lines, 1) + if line.startswith("def build_receipt(") + ] + domain_cases = [] + if not declaration_lines: + record( + "pkg-structural-anchor", + False, + "packaged anubis_program_verify.py declares build_receipt", + structural_relative, + ) + else: + structural_common = { + "file_path": structural_relative, + "file_sha256": sha_file(structural_path), + "symbol": "build_receipt", + "declaration_line": str(declaration_lines[0]), + "declaration_count": str(len(declaration_lines)), + } + domain_cases.extend( + [ + ("jackal_test_exists", structural_common, "structural-exact"), + ( + "jackal_claim_cites_test", + { + "doc_path": "README.txt", + "doc_sha256": sha_file(pkg / "README.txt"), + "claim_text": "First run: shasum -a 256 -c SHA256SUMS", + "test_path": structural_relative, + "test_sha256": sha_file(structural_path), + "symbol": "build_receipt", + }, + "structural-exact", + ), + ] + ) + domain_cases.extend( + [ + ( + "jackal_decision_rank", + { + "decision_id": "package_v173", + "criterion": "latency_ms", + "sense": "min", + "options": "alpha 120 beta 90", + }, + "exact", + ), + ( + "jackal_decision_rank_v2", + { + "decision_id": "package_v173_unit", + "criterion": "latency_ms", + "unit": "ms", + "sense": "min", + "options": "alpha 120 beta 90", + }, + "exact", + ), + ] + ) + for tool, arguments, expected_status in domain_cases: + document = plugin_call(pkg, tool, arguments) + record( + f"pkg-tool-{tool}", + document.get("status") == expected_status, + expected_status, + f"status={document.get('status')} " + f"reason={document.get('reason', '')}", + ) + + # --- all three Anubis program-evidence routes ------------------- + source, evidence, compiler_sha, artifact_sha, marker = make_v3_fixture( + work / "program-fixture" + ) + program_arguments = { + "source_path": str(source), + "evidence_dir": str(evidence), + "expected_source_sha256": sha(source.read_bytes()), + "expected_compiler_sha256": compiler_sha, + "expected_artifact_sha256": artifact_sha, + "expected_policy_sha256": POLICY_SHA256, + "verification_time_unix": VERIFY_TIME, + "profile": "inventory-safe-v1", + "nonce": "package-parity", + } + program_verify = plugin_call( + pkg, "jackal_anubis_verify_program", program_arguments + ) + record( + "pkg-tool-jackal_anubis_verify_program", + program_verify.get("status") == "verified-program-evidence" + and not marker.exists(), + "verified-program-evidence without artifact execution", + f"status={program_verify.get('status')} " + f"reason={program_verify.get('reason', '')}", + ) + program_receipt = program_verify.get("receipt") + if isinstance(program_receipt, dict): + replay_arguments = { + **program_arguments, + "receipt": program_receipt, + } + program_replay = plugin_call( + pkg, + "jackal_anubis_verify_program_receipt", + replay_arguments, + ) + record( + "pkg-tool-jackal_anubis_verify_program_receipt", + program_replay.get("status") == "verified-program-receipt" + and not marker.exists(), + "verified-program-receipt without artifact execution", + f"status={program_replay.get('status')} " + f"reason={program_replay.get('reason', '')}", + ) + else: + record( + "pkg-tool-jackal_anubis_verify_program_receipt", + False, + "program receipt available", + "verify returned no receipt", + ) + + unapproved_compiler = Path(sys.executable).resolve(strict=True) + check_refusal = plugin_call( + pkg, + "jackal_anubis_check_program", + { + "source_path": str(source), + "anubis_bin": str(unapproved_compiler), + "expected_source_sha256": sha(source.read_bytes()), + "expected_compiler_sha256": sha(unapproved_compiler.read_bytes()), + "expected_policy_sha256": POLICY_SHA256, + "verification_time_unix": VERIFY_TIME, + "profile": "inventory-safe-v1", + "nonce": "package-check-refusal", + "out_root": str(work / "program-check-output"), + }, + ) + record( + "pkg-tool-jackal_anubis_check_program", + check_refusal.get("status") == "refused" + and check_refusal.get("reason") == "compiler-not-approved", + "reachable named compiler-not-approved refusal", + f"status={check_refusal.get('status')} " + f"reason={check_refusal.get('reason', '')}", + ) + + direct_receipt = work / "package-program-receipt.json" + direct_program = subprocess.run( + [ + str(pkg / "jackal-anubis-program"), + "verify", + "--source", + str(source), + "--evidence-dir", + str(evidence), + "--expected-source-sha256", + sha(source.read_bytes()), + "--expected-compiler-sha256", + compiler_sha, + "--expected-artifact-sha256", + artifact_sha, + "--expected-policy-sha256", + POLICY_SHA256, + "--verification-time-unix", + VERIFY_TIME, + "--profile", + "inventory-safe-v1", + "--nonce", + "package-parity-direct", + "--emit-receipt", + str(direct_receipt), + ], + capture_output=True, + text=True, + cwd=pkg, + timeout=120, + ) + record( + "pkg-wrapper-jackal-anubis-program", + direct_program.returncode == 0 + and "status=verified-program-evidence" in direct_program.stdout + and direct_receipt.is_file() + and not marker.exists(), + "verified-program-evidence without artifact execution", + (direct_program.stdout or direct_program.stderr)[:120].replace( + "\n", " " + ), + ) + # verify_receipt round trip through the packaged plugin rb = plugin_call(pkg, "jackal_range_bound", {"expression": "cos(x)", "input_lo": "0", @@ -210,7 +484,7 @@ def main() -> int: if receipt: ver = plugin_call(pkg, "jackal_verify_receipt", { "receipt": receipt, - "expected_release_epoch": "v1.5.0", + "expected_release_epoch": "v1.7.2", "expected_command": "range-bound-cert", "expected_expression": "cos(x)", "expected_input_lo": "0", @@ -275,7 +549,7 @@ def main() -> int: "--expected-evaluator", rows["evaluator"], "--expected-checker", rows["checker"], "--expected-source", rows["source"], - "--expected-release-epoch", "v1.5.0", + "--expected-release-epoch", "v1.7.2", "--expected-command", "range-bound-cert", "--expected-expression", "x^2+1", "--expected-input-lo", "1", "--expected-input-hi", "2", diff --git a/tests/codex_plugin/live_acceptance.py b/tests/codex_plugin/live_acceptance.py index e683cee..23dd763 100644 --- a/tests/codex_plugin/live_acceptance.py +++ b/tests/codex_plugin/live_acceptance.py @@ -43,15 +43,10 @@ MARKETPLACE = "anubis-quantum-cipher" PLUGIN = "jackel" MCP_PROTOCOL_VERSION = "2025-11-25" -# Anti-shrink floor, not an exact count. This module is driven with two -# different catalogs: the REPO `plugin/hermes/tools.json` (repo test) and the -# SEALED RELEASE catalog the provisioner downloads (installed-config run). The -# substantive inventory invariant is `discovered == expected` plus uniqueness, -# which is exact and catalog-agnostic; an absolute count here would go stale -# against whichever of the two surfaces moved, and pinning it to one silently -# stops checking the other. 34 is the smallest surface either has ever -# shipped, so a catalog that SHRANK below it still refuses. -MIN_TOOL_COUNT = 34 +# 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 HOST_TRANSCRIPT_LIMIT = 4 * 1024 * 1024 HOST_REGISTRY_LIMIT = 1024 * 1024 HOST_REGISTRY_ENTRY_LIMIT = 256 @@ -65,14 +60,14 @@ HOST_BINARY_BYTE_LIMIT = 512 * 1024 * 1024 HOST_BINARY_PATH_LIMIT = 4096 -HERMES_BUNDLE_SHA256 = "d141c909e8f5f03e268a2112f291e6bd79fafff906522eb7ca9accc247a3274b" +HERMES_BUNDLE_SHA256 = "c6a27483077b89d899d8c73c03bfeb3191f25db2a22f8021254a7dec763ba5fe" INT_CERT_PRODUCER_SHA256 = "b4240fdac3c77b2abd751595303b2b3a0e4bebd492b2ae57fa5ccf052cd50af4" -INT_CERT_CHECKER_SHA256 = "c858e3bfc0ff2809a808170caabbf090077cb54996e76f065dbcd26ffb067d49" +INT_CERT_CHECKER_SHA256 = "f8347cbd18d520852aff56920d41f5e5b496ff192f584e41d84d1a818ff29617" CLAIM_TIME = "1786752000" CLAIM_NONCE = "jackal-codex-task5-v1" CLAIM_RELEASE_EPOCH = "v1.6.0" -FORMAL_RELEASE_EPOCH = "v1.7.0" +FORMAL_RELEASE_EPOCH = "v1.7.2" DEFAULT_POLICY = { "schema": "jackal-claim-policy-v1", diff --git a/tests/codex_plugin/test_live_acceptance.py b/tests/codex_plugin/test_live_acceptance.py index 0bb483c..809d587 100644 --- a/tests/codex_plugin/test_live_acceptance.py +++ b/tests/codex_plugin/test_live_acceptance.py @@ -44,7 +44,7 @@ def formal_payload(emitted_at): receipt = { "schema": "jackal-formal-receipt-v1", "variant": "int_cert", - "release_epoch": "v1.7.0", + "release_epoch": live.FORMAL_RELEASE_EPOCH, "emitted_at_unix": emitted_at, "request": { "command": "integrate-bound-cert", @@ -77,6 +77,20 @@ 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, + ) + self.assertIsNotNone(row, "release manifest has no plugin_hermes row") + self.assertEqual(live.HERMES_BUNDLE_SHA256, row[-1]) + def test_dry_run_lists_each_mcp_tool_once(self): document = live.dry_run_document( codex_binary=Path("/absolute/codex"), @@ -436,13 +450,14 @@ def test_named_formal_refusal_has_no_downgrade_shape(self): live.validate_unsupported_formal(mcp_response("refuse", leaked), leaked) def test_receipt_replay_uses_fixed_int_cert_request(self): + self.assertEqual(live.FORMAL_RELEASE_EPOCH, "v1.7.2") receipt = formal_payload(10)["receipt"] arguments = live.receipt_verification_arguments(receipt) self.assertIs(arguments["receipt"], receipt) self.assertEqual( {key: value for key, value in arguments.items() if key != "receipt"}, { - "expected_release_epoch": "v1.7.0", + "expected_release_epoch": live.FORMAL_RELEASE_EPOCH, "expected_command": "integrate-bound-cert", "expected_expression": "sin(x)", "expected_input_lo": "0", diff --git a/tests/codex_plugin/test_mcp_adapter.py b/tests/codex_plugin/test_mcp_adapter.py index d5b9334..25d443a 100644 --- a/tests/codex_plugin/test_mcp_adapter.py +++ b/tests/codex_plugin/test_mcp_adapter.py @@ -1205,14 +1205,30 @@ async def test_timeout_and_output_limit_kill_backend_and_return_bounded_errors(s self.assertFalse(await self._wait_not_live(pid), f"timed-out process survived: {pid}") output_pid_file = self._new_pid_file("output-tree.pids") + # The output-limit case exercises a different terminal condition after + # the leader has exited. Give it an independent timeout budget so CI + # scheduling cannot make the timeout race win before the orphan emits + # its deliberately oversized stdout. + output_server = adapter.MCPServer( + runtime_root=self.runtime, + launcher=self.launcher, + tool_definitions=self.definitions, + runtime_environment=TEST_RUNTIME_ENVIRONMENT, + tool_timeout=2.0, + stdout_limit=256, + stderr_limit=256, + terminate_grace=0.05, + leader_poll_interval=1.0, + ) + self.addAsyncCleanup(output_server.close) output_task = asyncio.create_task( - timeout_server.handle_message( + output_server.handle_message( self._call(71, mode="orphan-output-exit", pid_file=output_pid_file) ) ) output_pids = await self._wait_for_pids(output_pid_file) await self._wait_for_exited_leader_with_live_descendants(output_pids) - output_response = await asyncio.wait_for(output_task, timeout=2) + output_response = await asyncio.wait_for(output_task, timeout=3) self.assertEqual(output_response["error"]["code"], adapter.BACKEND_ERROR) self.assertLessEqual(len(json.dumps(output_response)), adapter.MAX_ERROR_RESPONSE_BYTES) for pid in output_pids: diff --git a/tests/codex_plugin/test_plugin_metadata.py b/tests/codex_plugin/test_plugin_metadata.py index 597ff84..5a316bd 100644 --- a/tests/codex_plugin/test_plugin_metadata.py +++ b/tests/codex_plugin/test_plugin_metadata.py @@ -15,6 +15,7 @@ MCP_PATH = PLUGIN_ROOT / ".mcp.json" 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" SERVER_PATH = PLUGIN_ROOT / "mcp" / "server.py" WORKFLOW_PATH = REPOSITORY_ROOT / ".github" / "workflows" / "jackal-codex-plugin.yml" @@ -33,8 +34,8 @@ / "2026-08-17-jackel-codex-plugin.md" ) APPROVED_SKILL_DESCRIPTION = ( - "Route claim-aware mathematical evidence work through JACKAL without " - "overstating assurance." + "Route claim-aware computation, domain-pack, and Anubis program evidence " + "through JACKAL without overstating assurance." ) @@ -227,22 +228,27 @@ def test_jackel_plugin_metadata_contract(self): "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.", ], ) expected_long_description = ( - "Use JACKAL's complete mathematical evidence kernel from Codex, with " - "exact, checked, estimated, bounded, formal-bounded, model-based, " - "verified, indeterminate, and refused results preserved at their " - "original assurance level. Formal-bounded applies only to " - "checker-admitted fragments. Requires Apple Silicon macOS, Python " - ">=3.10 at /opt/homebrew/bin/python3 (install with brew install " - "python), and the pinned sealed v1.7.0 runtime." + "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)." ) self.assertEqual(interface["longDescription"], expected_long_description) self.assertIn(f"- `interface.longDescription`: `{expected_long_description}`", DESIGN_PATH.read_text(encoding="utf-8")) self.assertRegex(manifest["version"], r"^0\.1\.0\+codex\.\d{14}$") - self.assertEqual(manifest["description"], "Expose JACKAL's claim-aware mathematical evidence kernel to Codex.") + self.assertEqual(manifest["description"], "Expose JACKAL's claim-aware computation, domain-pack, and program-evidence kernel 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") @@ -295,6 +301,10 @@ def test_jackel_plugin_metadata_contract(self): "jackal_claim", "jackal_verify_bundle", "jackal_verify_receipt", + "jackal_anubis_verify_program", + "inventory-safe-v1", + "policy-construct-totality-not-established", + "None executes the compiled artifact", "direct tools remain available", "preserve every returned status/assumption/non-claim/residual/refusal verbatim", "never promote assurance or silently downgrade", @@ -324,6 +334,7 @@ def test_every_shipped_plugin_path_exists_and_is_identity_governed(self): manifest = self.load_json(MANIFEST_PATH) mcp = self.load_json(MCP_PATH)["mcpServers"]["jackel"] referenced = { + "README.md", ".codex-plugin/plugin.json", manifest["mcpServers"].removeprefix("./"), "mcp/server.py", @@ -340,10 +351,30 @@ def test_every_shipped_plugin_path_exists_and_is_identity_governed(self): for relative in referenced: self.assertTrue((PLUGIN_ROOT / relative).is_file(), relative) self.assertEqual(mcp["args"], ["./scripts/launch_mcp.zsh"]) - self.assertFalse(any( - path.name.endswith(".tar.gz") or path.name == "jackal-v1.7.0-macos-arm64" - for path in PLUGIN_ROOT.iterdir() - )) + self.assertFalse( + any( + path.name.endswith(".tar.gz") or path.name.startswith("jackal-v") + for path in PLUGIN_ROOT.iterdir() + ) + ) + + def test_readme_documents_release_install_discovery_and_boundaries(self): + text = README_PATH.read_text(encoding="utf-8") + for required in ( + "41-tool", + "v1.7.3 release", + "release/capability_inventory_v1.json", + "/bin/zsh scripts/launch_mcp.zsh provision", + "codex mcp list", + "jackal_claim", + "jackal_verify_receipt", + "jackal_anubis_verify_program_receipt", + "policy-construct-totality-not-established", + "refused", + "indeterminate", + "No silent downgrade", + ): + self.assertIn(required, text, required) def test_launcher_uses_only_explicit_absolute_python_candidates_and_exact_flags(self): mcp = self.load_json(MCP_PATH)["mcpServers"]["jackel"] diff --git a/tests/codex_plugin/test_runtime_provisioner.py b/tests/codex_plugin/test_runtime_provisioner.py index 705fd16..c40150d 100644 --- a/tests/codex_plugin/test_runtime_provisioner.py +++ b/tests/codex_plugin/test_runtime_provisioner.py @@ -219,6 +219,34 @@ def test_unsupported_host_refuses_before_opener(self): ) opener.assert_not_called() + def test_published_release_attempts_the_pinned_default_download(self): + opener = mock.Mock(side_effect=RuntimeError("download sentinel")) + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + target = root / "support/runtimes/v1.7.3" + locator = root / "support/codex-plugin/runtime.json" + with self.assertRaisesRegex( + provisioner.ProvisionError, + "download failed", + ): + provisioner.provision( + runtime_target=target, + locator_path=locator, + expected_size=1, + expected_sha256=self.sha(b"x"), + expected_extracted_size=1, + expected_tree_sha256=self.sha(b"tree"), + opener=opener, + system="Darwin", + machine="arm64", + ) + self.assertTrue(target.parent.is_dir()) + self.assertFalse(locator.parent.exists()) + opener.assert_called_once_with( + provisioner.URL, + timeout=provisioner.NETWORK_TIMEOUT, + ) + def test_stream_download_requires_declared_exact_length(self): data = b"abcdef" for declared in (len(data) - 1, len(data) + 1): @@ -1373,8 +1401,8 @@ def test_provision_offline_installs_metadata_and_locator_atomically(self): self.assertTrue((target / "plugin/hermes/jackal_hermes").exists()) expected = { "schema": "jackal-runtime-package-v1", - "epoch": "v1.7.0", - "asset": "jackal-v1.7.0-macos-arm64.tar.gz", + "epoch": "v1.7.3", + "asset": "jackal-v1.7.3-macos-arm64.tar.gz", "package_size": tarball.stat().st_size, "package_sha256": digest, } @@ -1383,7 +1411,7 @@ def test_provision_offline_installs_metadata_and_locator_atomically(self): json.loads(locator.read_text()), { "schema": "jackal-codex-plugin-runtime-v1", - "epoch": "v1.7.0", + "epoch": "v1.7.3", "runtime_path": str(target), "package_size": tarball.stat().st_size, "package_sha256": digest, @@ -1509,22 +1537,26 @@ def test_check_is_read_only_and_never_repairs_missing_locator(self): opener.assert_not_called() def test_pinned_constants_and_default_paths(self): - self.assertEqual(provisioner.EPOCH, "v1.7.0") - self.assertEqual(provisioner.ASSET, "jackal-v1.7.0-macos-arm64.tar.gz") + self.assertEqual(provisioner.EPOCH, "v1.7.3") + self.assertEqual(provisioner.RELEASE_STATE, "published") + self.assertEqual(provisioner.ASSET, "jackal-v1.7.3-macos-arm64.tar.gz") self.assertEqual( provisioner.URL, - "https://github.com/AnubisQuantumCipher/jackal/releases/download/v1.7.0/jackal-v1.7.0-macos-arm64.tar.gz", + "https://github.com/AnubisQuantumCipher/jackal/releases/download/v1.7.3/jackal-v1.7.3-macos-arm64.tar.gz", + ) + self.assertEqual(provisioner.PACKAGE_SIZE, 158363786) + self.assertEqual(provisioner.EXTRACTED_SIZE, 555511970) + self.assertEqual( + provisioner.PACKAGE_SHA256, + "68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae", ) - self.assertEqual(provisioner.PACKAGE_SIZE, 118862060) - self.assertEqual(provisioner.EXTRACTED_SIZE, 416736385) - self.assertEqual(provisioner.PACKAGE_SHA256, "21c7ede586f30a58772f321f7dbb36ab66213e199785489f99133710ac56096e") self.assertEqual( provisioner.SHA256SUMS_SHA256, - "f1f794ccd2ba331e6188840cfc089180cdcd744f23c1880f8364a81b230c1a28", + "a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a", ) self.assertEqual( provisioner.default_runtime_target(Path("/Users/tester")), - Path("/Users/tester/Library/Application Support/JACKAL/runtimes/v1.7.0"), + Path("/Users/tester/Library/Application Support/JACKAL/runtimes/v1.7.3"), ) def test_cli_rejects_relative_tarball_with_one_bounded_line_and_no_traceback(self): diff --git a/tests/jackal_skill_contract_test.py b/tests/jackal_skill_contract_test.py new file mode 100644 index 0000000..813cf84 --- /dev/null +++ b/tests/jackal_skill_contract_test.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +"""Check JACKAL routing skills against the canonical capability inventory.""" + +from __future__ import annotations + +import json +import re +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +INVENTORY = ROOT / "release/capability_inventory_v1.json" +REPO_ROUTER = ROOT / "plugins/jackel/skills/jackel/SKILL.md" +HOME = Path.home() +PERSONAL_CODEX_ORACLE = HOME / ".codex/skills/jackal-assurance-oracle/SKILL.md" +PERSONAL_HERMES_ROUTER = ( + HOME + / ".hermes/skills/software-development/jackal-verified-computation/SKILL.md" +) +PERSONAL_HERMES_RESEAL = ( + HOME + / ".hermes/skills/software-development/jackal-trust-boundary-reseal/SKILL.md" +) +PROFILE_HERMES_ROUTERS = tuple( + HOME + / f".hermes/profiles/{profile}/skills/software-development/" + "jackal-verified-computation/SKILL.md" + for profile in ("alecto", "athena", "hephaestus", "themis") +) + +TOOL_REFERENCE = re.compile(r"`(jackal_[a-z0-9_]+)`") +CURRENT_BEGIN = "" +CURRENT_END = "" +REQUIRED_ROUTING = { + "jackal_claim", + "jackal_verify_bundle", + "jackal_verify_receipt", + "jackal_anubis_verify_program", + "jackal_anubis_verify_program_receipt", +} +STALE_CURRENT = ( + "34-tool surface", + "34 tools", + "v1.7.0 kernel", + "plugin v5.0.0 is enabled", +) + + +def inventory_names() -> set[str]: + document = json.loads(INVENTORY.read_text(encoding="utf-8")) + return {row["name"] for row in document["tools"]} + + +def profile_counts() -> dict[str, int]: + document = json.loads(INVENTORY.read_text(encoding="utf-8")) + counts = {"core": 0, "formal": 0, "full": 0} + for row in document["tools"]: + for profile in row["profiles"]: + counts[profile] += 1 + return counts + + +def current_block(text: str) -> str: + if text.count(CURRENT_BEGIN) != 1 or text.count(CURRENT_END) != 1: + raise AssertionError("skill must contain one canonical current-surface block") + start = text.index(CURRENT_BEGIN) + len(CURRENT_BEGIN) + end = text.index(CURRENT_END) + if end <= start: + raise AssertionError("skill current-surface markers are reversed") + return text[start:end] + + +def assert_router_contract( + case: unittest.TestCase, path: Path, *, require_marker: bool = True +) -> None: + text = path.read_text(encoding="utf-8") + lower = text.lower() + names = inventory_names() + references = set(TOOL_REFERENCE.findall(text)) + case.assertTrue(REQUIRED_ROUTING <= references, (path, references)) + case.assertEqual(references - names, set(), (path, references - names)) + for phrase in ("caller-pinned", "refused", "indeterminate"): + case.assertIn(phrase, lower, (path, phrase)) + case.assertRegex(lower, r"(?:no|never) silent(?:ly)? downgrade") + if require_marker: + block = current_block(text) + case.assertIn("41-tool", block) + case.assertIn("release/capability_inventory_v1.json", 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_personal_codex_oracle_names_current_replay_front_doors(self) -> None: + if not PERSONAL_CODEX_ORACLE.is_file(): + self.skipTest("personal Codex oracle is not installed on this host") + text = PERSONAL_CODEX_ORACLE.read_text(encoding="utf-8") + references = set(TOOL_REFERENCE.findall(text)) + self.assertTrue(REQUIRED_ROUTING <= references, references) + self.assertEqual(references - inventory_names(), set()) + counts = profile_counts() + self.assertIn( + "Current v1.7.3 profiles: " + f"`core={counts['core']}`, `formal={counts['formal']}`, " + f"`full={counts['full']}`", + text, + ) + + def test_personal_hermes_router_uses_only_inventory_tools(self) -> None: + if not PERSONAL_HERMES_ROUTER.is_file(): + self.skipTest("personal Hermes router is not installed on this host") + assert_router_contract(self, PERSONAL_HERMES_ROUTER) + + def test_profile_hermes_routers_equal_the_reviewed_personal_router(self) -> None: + if not PERSONAL_HERMES_ROUTER.is_file(): + self.skipTest("personal Hermes router is not installed on this host") + expected = PERSONAL_HERMES_ROUTER.read_bytes() + checked = 0 + for path in PROFILE_HERMES_ROUTERS: + if not path.is_file(): + continue + checked += 1 + self.assertEqual(path.read_bytes(), expected, path) + if checked == 0: + self.skipTest("no Hermes profile router copies are installed") + + def test_personal_reseal_covers_current_trust_artifacts(self) -> None: + if not PERSONAL_HERMES_RESEAL.is_file(): + self.skipTest("personal Hermes reseal skill is not installed on this host") + text = PERSONAL_HERMES_RESEAL.read_text(encoding="utf-8") + for required in ( + "jackal_cert_check", + "jackal_gaussian_check", + "jackal_int_cert_check", + "release/capability_inventory_v1.json", + "release/build_package_v173.sh", + "inventory-safe-v1", + "policy-construct-totality-not-established", + ): + self.assertIn(required, text, (PERSONAL_HERMES_RESEAL, required)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/lean_admission_audit_test.py b/tests/lean_admission_audit_test.py new file mode 100644 index 0000000..2120e86 --- /dev/null +++ b/tests/lean_admission_audit_test.py @@ -0,0 +1,368 @@ +#!/usr/bin/env python3 +"""Contract and hostile-mutation tests for the repository-wide Lean audit.""" + +from __future__ import annotations + +import importlib.util +import json +import os +import stat +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path +from unittest import mock + + +ROOT = Path(__file__).resolve().parents[1] +AUDITOR_PATH = ROOT / "tools/lean_admission_audit.py" +ARTIFACT_PATH = ROOT / "release/evidence/lean_admission_audit_v173.json" + + +def load_auditor(): + spec = importlib.util.spec_from_file_location("lean_admission_audit", AUDITOR_PATH) + if spec is None or spec.loader is None: + raise RuntimeError("cannot load Lean admission auditor") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +AUDITOR = load_auditor() + + +def lean_is_available() -> bool: + try: + completed = subprocess.run( + ["lake", "env", "lean", "--version"], + cwd=ROOT / "proofs/lean", + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + timeout=30, + check=False, + ) + except (OSError, subprocess.SubprocessError): + return False + return completed.returncode == 0 + + +LEAN_AVAILABLE = lean_is_available() + + +class LeanAuditFixture: + def __init__(self, source: str) -> None: + self.temporary = tempfile.TemporaryDirectory(prefix="jackal-lean-audit-") + self.root = Path(self.temporary.name) + path = self.root / "proofs/lean/Probe.lean" + path.parent.mkdir(parents=True) + path.write_text(source, encoding="utf-8") + + def close(self) -> None: + self.temporary.cleanup() + + +@unittest.skipUnless(LEAN_AVAILABLE, "pinned Lean toolchain is unavailable") +class LeanAdmissionAuditPositiveTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.audit = AUDITOR.build_audit(ROOT) + + def test_inventory_is_exactly_every_tracked_lean_file(self) -> None: + expected = sorted( + os.fsdecode(path) + for path in subprocess.check_output( + ["git", "-C", str(ROOT), "ls-files", "-z", "--", "proofs/lean"], + ).split(b"\0") + if path.endswith(b".lean") + ) + observed = [row["path"] for row in self.audit["source_inventory"]["files"]] + self.assertEqual(observed, expected) + self.assertEqual(self.audit["source_inventory"]["file_count"], 42) + self.assertEqual( + self.audit["source_inventory"]["inventory_source"], "git-ls-files" + ) + self.assertEqual(len(observed), len(set(observed))) + + def test_repository_has_no_logical_admissions_or_axiom_declarations(self) -> None: + policy = self.audit["source_inventory"]["construct_policy"] + self.assertEqual(policy["forbidden_findings"], []) + self.assertEqual(self.audit["trust_surface"]["logical_admissions"], []) + self.assertEqual( + self.audit["trust_surface"]["repository_axiom_declarations"], [] + ) + self.assertEqual( + policy["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]", + }, + ], + ) + + def test_all_release_theorems_have_exact_standard_axiom_surface(self) -> None: + theorem_audit = self.audit["theorem_axiom_audit"] + self.assertEqual(theorem_audit["theorem_count"], 27) + names = [row["theorem"] for row in theorem_audit["theorems"]] + self.assertEqual(len(names), len(set(names))) + for row in theorem_audit["theorems"]: + self.assertEqual( + row["axioms"], ["propext", "Classical.choice", "Quot.sound"] + ) + self.assertEqual( + row["raw_output"], + f"'{row['theorem']}' depends on axioms: " + "[propext, Classical.choice, Quot.sound]", + ) + + def test_toolchain_identity_is_platform_neutral_and_exact(self) -> None: + lean = self.audit["toolchain"]["lean"] + self.assertEqual( + lean, + { + "build_profile": "Release", + "commit": "8c9756b28d64dab099da31a4c09229a9e6a2ef35", + "version": "4.32.0", + }, + ) + + def test_checker_bytes_match_each_source_identity(self) -> None: + for lane in self.audit["release_bindings"]["current_proof_identities"]: + self.assertEqual(lane["identity_checker_sha256"], lane["checker_sha256"]) + self.assertEqual(lane["identity_checker_bytes"], lane["checker_bytes"]) + + def test_lane_namespaces_have_an_explicit_mapping(self) -> None: + mapping = self.audit["release_bindings"]["lane_identifier_mapping"] + self.assertEqual( + mapping["compatibility_floor_to_proof_checker"], + { + "int_cert": "int-cert", + "range": "range", + "rational_variants": "range", + }, + ) + self.assertEqual( + mapping["proof_checker_without_compatibility_floor"], ["gaussian"] + ) + + def test_committed_artifact_is_generated_byte_for_byte(self) -> None: + AUDITOR.check_committed(ROOT) + self.assertEqual(ARTIFACT_PATH.read_bytes(), AUDITOR.render_audit(ROOT)) + + def test_cli_check_reports_exact_counts(self) -> None: + completed = subprocess.run( + [sys.executable, "-B", str(AUDITOR_PATH), "--check", "--root", str(ROOT)], + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual( + completed.stdout.strip(), + "LEAN_ADMISSION_AUDIT_PASS files=42 theorems=27 admissions=0", + ) + + +class LeanAdmissionAuditMutationTest(unittest.TestCase): + def test_evidence_paths_must_remain_below_repository_root(self) -> None: + for supplied in ("../outside.json", "/private/tmp/outside.json"): + with self.subTest(supplied=supplied): + with self.assertRaisesRegex(AUDITOR.AuditError, "escapes audit root"): + AUDITOR.repository_path(ROOT, supplied, "test evidence") + + def test_symlinked_evidence_path_is_refused(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-lean-link-") as td: + temporary = Path(td) + root = temporary / "root" + outside = temporary / "outside" + root.mkdir() + outside.mkdir() + (outside / "evidence.json").write_text("{}\n", encoding="utf-8") + (root / "linked").symlink_to(outside, target_is_directory=True) + with self.assertRaisesRegex(AUDITOR.AuditError, "escapes audit root"): + AUDITOR.repository_path( + root, "linked/evidence.json", "test evidence" + ) + + def test_finding_source_line_uses_only_lf_line_boundaries(self) -> None: + source = "alpha\rbeta\naxiom counterfeit : False\n" + finding = AUDITOR.finding_record( + construct="axiom_declaration", + relative="probe.lean", + source=source, + code=source, + offset=source.index("axiom"), + ) + self.assertEqual(finding["line"], 2) + self.assertEqual(finding["source_line"], "axiom counterfeit : False") + + def test_release_inventory_refuses_when_git_inventory_is_unavailable(self) -> None: + failed = subprocess.CompletedProcess( + args=["git", "ls-files"], returncode=128, stdout=b"", stderr=b"boom" + ) + with mock.patch.object(AUDITOR.subprocess, "run", return_value=failed): + with self.assertRaisesRegex(AUDITOR.AuditError, "git ls-files"): + AUDITOR.tracked_lean_paths(ROOT, require_git=True) + + def test_git_inventory_uses_nul_delimiters_for_newline_paths(self) -> None: + completed = subprocess.CompletedProcess( + args=["git", "ls-files", "-z"], + returncode=0, + stdout=b"proofs/lean/Line\nBreak.lean\0proofs/lean/Probe.lean\0", + stderr=b"", + ) + with mock.patch.object(AUDITOR.subprocess, "run", return_value=completed): + paths, source = AUDITOR.tracked_lean_paths(ROOT, require_git=True) + self.assertEqual( + paths, + ["proofs/lean/Line\nBreak.lean", "proofs/lean/Probe.lean"], + ) + self.assertEqual(source, "git-ls-files") + + def test_axiom_program_forces_nonwrapping_output(self) -> None: + source = Path(AUDITOR_PATH).read_text(encoding="utf-8") + self.assertIn('"set_option format.width 1000"', source) + + def test_string_escape_preserves_newline_accounting(self) -> None: + source = 'def message := "first\\\nsecond"\naxiom counterfeit : False\n' + masked = AUDITOR.code_without_comments_or_strings(source) + self.assertEqual(masked.count("\n"), source.count("\n")) + self.assertEqual(len(masked), len(source)) + + def test_raw_strings_are_explicitly_refused(self) -> None: + for source in ('def value := r"raw\\text"\n', 'def value := r#"raw"#\n'): + with self.subTest(source=source), self.assertRaisesRegex( + AUDITOR.AuditError, "raw strings are unsupported" + ): + AUDITOR.code_without_comments_or_strings(source) + + def test_command_timeout_override_is_bounded(self) -> None: + for value in ("0", "3601", "not-a-number"): + with self.subTest(value=value), mock.patch.dict( + os.environ, {AUDITOR.COMMAND_TIMEOUT_ENV: value} + ), self.assertRaisesRegex(AUDITOR.AuditError, "invalid"): + AUDITOR.command_timeout_seconds() + with mock.patch.dict( + os.environ, {AUDITOR.COMMAND_TIMEOUT_ENV: "600"} + ): + self.assertEqual(AUDITOR.command_timeout_seconds(), 600.0) + + def test_atomic_audit_output_is_world_readable(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-lean-write-") as td: + path = Path(td) / "audit.json" + AUDITOR.write_atomic(path, b"{}\n") + self.assertEqual(stat.S_IMODE(path.stat().st_mode), 0o644) + + def test_platform_neutral_source_cli_checks_all_tracked_files(self) -> None: + completed = subprocess.run( + [ + sys.executable, + "-B", + str(AUDITOR_PATH), + "--source-check", + "--root", + str(ROOT), + ], + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual( + completed.stdout.strip(), + "LEAN_SOURCE_ADMISSION_PASS files=42 admissions=0", + ) + + def assert_refused(self, source: str, reason: str) -> None: + fixture = LeanAuditFixture(source) + try: + with self.assertRaisesRegex(AUDITOR.AuditError, reason): + AUDITOR.scan_sources( + fixture.root, ["proofs/lean/Probe.lean"] + ) + finally: + fixture.close() + + def test_comments_and_strings_do_not_create_findings(self) -> None: + fixture = LeanAuditFixture( + '-- sorry axiom unsafe @[implemented_by fake]\n' + '/- outer admit /- nested native_decide -/ extern partial -/\n' + 'def message := "sorry axiom unsafe @[implemented_by fake]"\n' + "def ok : Nat := 1\n" + ) + try: + inventory = AUDITOR.scan_sources( + fixture.root, ["proofs/lean/Probe.lean"] + ) + self.assertEqual(inventory["construct_policy"]["forbidden_findings"], []) + self.assertEqual(inventory["construct_policy"]["allowed_findings"], []) + finally: + fixture.close() + + def test_character_literals_do_not_hide_following_declarations(self) -> None: + self.assert_refused( + "def doubleQuote : Char := '\"'\n" + "axiom counterfeit : False\n", + "axiom_declaration", + ) + + def test_character_literals_and_prime_identifiers_are_not_findings(self) -> None: + fixture = LeanAuditFixture( + "def apostrophe : Char := '\\''\n" + "def angle : Char := '\\u00ab'\n" + "def ok' : Nat := 1\n" + ) + try: + inventory = AUDITOR.scan_sources( + fixture.root, ["proofs/lean/Probe.lean"] + ) + self.assertEqual(inventory["construct_policy"]["forbidden_findings"], []) + self.assertEqual(inventory["construct_policy"]["allowed_findings"], []) + finally: + fixture.close() + + def test_injected_sorry_is_rejected(self) -> None: + self.assert_refused("theorem bad : True := by sorry\n", "forbidden.*sorry") + + def test_injected_axiom_is_rejected(self) -> None: + self.assert_refused("axiom counterfeit : False\n", "axiom_declaration") + self.assert_refused( + "namespace Probe\nprotected axiom counterfeit : False\nend Probe\n", + "axiom_declaration", + ) + + def test_other_trust_bypasses_are_rejected(self) -> None: + mutations = { + "admit": "theorem bad : True := by admit\n", + "unsafe": "unsafe def bad : Nat := 0\n", + "partial": "partial def bad : Nat -> Nat := fun n => bad n\n", + "extern": '@[extern "bad"] opaque bad : Nat\n', + "native_decide": "example : True := by native_decide\n", + } + for reason, source in mutations.items(): + with self.subTest(reason=reason): + self.assert_refused(source, reason) + + def test_unclassified_implemented_by_is_rejected(self) -> None: + self.assert_refused( + "opaque spec : Nat\n" + "def impl : Nat := 0\n" + "@[implemented_by impl] opaque exposed : Nat\n", + "implemented_by", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/live_receipt_redaction_test.py b/tests/live_receipt_redaction_test.py new file mode 100644 index 0000000..371ed2c --- /dev/null +++ b/tests/live_receipt_redaction_test.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Privacy contract for committed live-session transcript streams.""" + +from __future__ import annotations + +import hashlib +import json +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +TRANSCRIPT = ROOT / "evals/v2/receipts/codex_w3_autonomous_2026-08-20.jsonl" +TRANSCRIPTS = sorted((ROOT / "evals/v2/receipts").glob("codex_*.jsonl")) +SUMMARY = ROOT / "evals/v2/receipts/live_tool_sessions_2026-08-20.json" +FORBIDDEN_PRIVATE_TOKENS = ( + "/Users/", + ".codex/config.toml", + ".codex/plugins/cache", + ".codex/memories", +) +REDACTION_MARKER = "[REDACTED_PRIVATE_LOCAL_CONTEXT]" + + +class LiveReceiptRedactionTest(unittest.TestCase): + def test_all_committed_codex_transcripts_hide_private_local_paths(self) -> None: + self.assertGreaterEqual(len(TRANSCRIPTS), 3) + for transcript in TRANSCRIPTS: + raw = transcript.read_text(encoding="utf-8") + with self.subTest(transcript=transcript.name): + for token in FORBIDDEN_PRIVATE_TOKENS: + self.assertNotIn(token, raw) + + def test_private_context_event_is_minimally_redacted(self) -> None: + records = [ + json.loads(line) + for line in TRANSCRIPT.read_text(encoding="utf-8").splitlines() + ] + redacted = [ + record + for record in records + if record.get("item", {}).get("type") == "command_execution" + and record["item"].get("command") == REDACTION_MARKER + and record["item"].get("aggregated_output") == REDACTION_MARKER + ] + self.assertEqual(len(redacted), 2) + for record in redacted: + item = record["item"] + self.assertEqual(item["type"], "command_execution") + self.assertEqual(item["command"], REDACTION_MARKER) + self.assertEqual(item["aggregated_output"], REDACTION_MARKER) + + def test_summary_binds_exact_post_redaction_bytes(self) -> None: + document = json.loads(SUMMARY.read_text(encoding="utf-8")) + rows = {item["path"]: item for item in document["sessions"]} + expected_paths = { + transcript.relative_to(ROOT).as_posix() for transcript in TRANSCRIPTS + } + self.assertEqual(set(rows), expected_paths) + for transcript in TRANSCRIPTS: + relative = transcript.relative_to(ROOT).as_posix() + row = rows[relative] + raw = transcript.read_bytes() + with self.subTest(transcript=transcript.name): + self.assertEqual(row["bytes"], len(raw)) + self.assertEqual(row["sha256"], hashlib.sha256(raw).hexdigest()) + self.assertIn("redacted", row["content_state"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/package_unified_v173_test.py b/tests/package_unified_v173_test.py new file mode 100755 index 0000000..d1a55ba --- /dev/null +++ b/tests/package_unified_v173_test.py @@ -0,0 +1,558 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import hashlib +import json +import importlib.util +import os +import platform +import subprocess +import tempfile +import shutil +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +BUILDER = ROOT / "release/build_package_v173.sh" +REPIN = ROOT / "release/tools/repin_v173.py" +COMPAT = ROOT / "release/compat/v173_floor.json" +ALIGNMENT_RECEIPT = ROOT / "release/evidence/package_alignment_v173_release.json" +PROGRAM_DOGFOOD = ROOT / "release/evidence/anubis_program_dogfood_v1.json" +PACKAGE_NAME = "jackal-v1.7.3-macos-arm64" +REQUIRED_PACKAGE_INPUTS = { + "release/capability_inventory_v1.json", + "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/domain_pack_verify.py", + "tools/exact_verify.py", + "tools/test_exists_verify.py", + "tools/decision_verify.py", + "release/program/SPEC.md", + "release/evidence/lean_admission_audit_v173.json", + "tools/anubis_program_verify.py", + "release/program/inventory_safe_v1.json", + "plugin/hermes/profiles/core.json", + "plugin/hermes/profiles/formal.json", + "plugin/hermes/profiles/full.json", + "plugin/hermes/schemas/jackal_agent_profile.schema.json", +} +REQUIRED_REPOSITORY_MANIFEST_LABELS = { + "domain_pack_registry", + "domain_pack_verifier", + "domain_pack_test_exists_checker", + "domain_pack_decision_checker", + "anubis_program_verifier", + "anubis_program_policy", + "plugin_hermes", + "source", + "evaluator", + "claim_inference_registry", + "lean-admission-audit", + "lean-admission-audit-digest", +} +REQUIRED_PACKAGE_MANIFEST_LABELS = { + "lean_admission_audit", + "lean_admission_audit_digest", +} + + +def complete_sha256sums(root: Path) -> bool: + sums = root / "SHA256SUMS" + if sums.is_symlink() or not sums.is_file(): + return False + rows: dict[str, str] = {} + for line in sums.read_text(encoding="utf-8").splitlines(): + digest, separator, raw_path = line.partition(" ") + if ( + separator != " " + or len(digest) != 64 + or any(character not in "0123456789abcdef" for character in digest) + or not raw_path.startswith("./") + ): + return False + relative = raw_path[2:] + if not relative or relative in rows: + return False + rows[relative] = digest + actual = { + path.relative_to(root).as_posix() + for path in root.rglob("*") + if path.is_file() and path != sums + } + if set(rows) != actual: + return False + for relative, expected in rows.items(): + path = root / relative + if path.is_symlink() or hashlib.sha256(path.read_bytes()).hexdigest() != expected: + return False + return True + + +class UnifiedPackageV173Test(unittest.TestCase): + def test_program_dogfood_package_is_explicitly_historical(self) -> None: + dogfood = json.loads(PROGRAM_DOGFOOD.read_text(encoding="utf-8")) + alignment = json.loads(ALIGNMENT_RECEIPT.read_text(encoding="utf-8")) + package = dogfood["package"] + self.assertEqual(package["alignment_state"], "historical-pre-alignment-candidate") + self.assertEqual( + package["superseded_by"], + { + "path": "release/evidence/package_alignment_v173_release.json", + "package_sha256": alignment["package"]["sha256"], + }, + ) + self.assertNotEqual(package["sha256"], alignment["package"]["sha256"]) + + def test_alignment_receipt_binds_reproducible_release_and_live_codex(self) -> None: + document = json.loads(ALIGNMENT_RECEIPT.read_text(encoding="utf-8")) + self.assertEqual(document["schema"], "jackal-package-alignment-v1") + self.assertEqual(document["release_state"], "v1.7.3") + self.assertEqual( + document["package"], + { + "basename": "jackal-v1.7.3-macos-arm64.tar.gz", + "bytes": 158363786, + "extracted_file_bytes": 555511970, + "file_count": 106, + "tree_entries": 119, + "roster_aggregate_sha256": "f88ba8a9988afe4b41ab247d5c75cb3da03159defba1bd8985c37190fa595654", + "sha256": "68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae", + "sha256sums_root": "a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a", + }, + ) + source = document["source"] + for key, relative in ( + ("builder_sha256", "release/build_package_v173.sh"), + ("manifest_sha256", "release/MANIFEST.sha256"), + ("capability_inventory_sha256", "release/capability_inventory_v1.json"), + ): + self.assertEqual( + source[key], hashlib.sha256((ROOT / relative).read_bytes()).hexdigest() + ) + self.assertEqual( + document["comparisons"], + {"directory_diff_exit": 0, "tarball_cmp_exit": 0}, + ) + self.assertEqual( + document["gates"]["package_unified_tests"], + {"exit": 0, "passed": 15, "skipped": 0}, + ) + self.assertEqual( + document["gates"]["claim_package_parity"], + {"exit": 0, "failures": 0, "rows": 60}, + ) + self.assertEqual( + document["gates"]["codex_repository_tests"], + {"exit": 0, "passed": 218}, + ) + self.assertEqual( + document["gates"]["codex_live_acceptance"], + { + "status": "accepted", + "discovered_tool_count": 41, + "wrapper_aggregate_sha256": "d4b6cdc32e55335eade1ca6d7cbc385c133c2dbecf4296a894877fe297fe27c3", + "runtime_package_sha256": "68b0e7850fcb60358633908f70ffcf405cbbef103b04d3d93dd1298789e505ae", + "runtime_tree_sha256": "a78fc05e2ebd56f31263d54ccdbf7fcc2ff92d270758720c3e235d5a3121568a", + "gates": { + "claim_bundle": "verified", + "exact": "exact", + "formal": "formal-bounded", + "formal_receipt": "verified", + "unsupported_formal": "producer-refused", + }, + }, + ) + self.assertIn("not-a-cryptographic-signature", document["non_claims"]) + self.assertIn("no-upstream-merge-assertion", document["non_claims"]) + completion = ( + ROOT / "docs/W3_W4_W6_W10_COMPLETION_RECORD.md" + ).read_text(encoding="utf-8") + for value in ( + str(document["package"]["bytes"]), + document["package"]["sha256"], + document["package"]["sha256sums_root"], + ): + self.assertIn(value, completion) + + def test_builder_and_repin_declare_every_unified_trust_input(self) -> None: + self.assertTrue(BUILDER.is_file(), BUILDER) + self.assertTrue(REPIN.is_file(), REPIN) + self.assertTrue(COMPAT.is_file(), COMPAT) + builder = BUILDER.read_text(encoding="utf-8") + repin = REPIN.read_text(encoding="utf-8") + self.assertIn('VER="v1.7.3"', builder) + self.assertIn('JACKAL_DIST', builder) + for relative in sorted(REQUIRED_PACKAGE_INPUTS): + self.assertIn(relative, builder, relative) + for label in sorted(REQUIRED_REPOSITORY_MANIFEST_LABELS): + self.assertIn(label, repin, label) + for label in sorted(REQUIRED_PACKAGE_MANIFEST_LABELS): + self.assertIn(f"\n{label} ", builder, label) + self.assertIn("jackal-anubis-program", builder) + self.assertIn("PACKAGE_V173_BUILD_PASS", builder) + self.assertIn('COMPILER=${JACKAL_ANUBIS_COMPILER_PATH:-}', builder) + self.assertIn("compiler-path-unset", builder) + self.assertNotIn("/Users/sicarii", builder) + self.assertNotIn("/Users/sicarii", repin) + self.assertNotRegex(builder, r"python3(?! -I -S -B)") + self.assertIn("renamex_np", builder) + self.assertIn("RENAME_EXCL", builder) + self.assertIn("dist-cross-filesystem", builder) + self.assertIn("/usr/bin/stat -f '%d'", builder) + self.assertIn("--connect-timeout 20", builder) + self.assertIn("--max-time 900", builder) + self.assertIn("--retry 3", builder) + self.assertIn('! -path ./SHA256SUMS', builder) + self.assertNotIn('! -name SHA256SUMS', builder) + self.assertIn( + '$(awk \'$1=="claim_inference_registry"{print $NF}\' "$M")', + builder, + ) + self.assertIn( + '$(awk \'$1=="claim_unit_registry"{print $NF}\' "$M")', + builder, + ) + self.assertNotIn('sha() { shasum -a 256 "$1"', builder) + self.assertIn('publish_noreplace "$FINAL_PKG" "$PKG"', builder) + self.assertNotIn('/bin/mv "$PKG" "$FINAL_PKG"', builder) + self.assertNotIn('/bin/mv "$STAGED_TARBALL" "$FINAL_TARBALL"', builder) + self.assertNotIn("*'\"status\":\"accepted\"'*", builder) + self.assertRegex( + repin, + r"except \([^)]*ValueError[^)]*\) as error:", + "malformed pinned JSON must refuse without a traceback", + ) + + def test_claim_evidence_never_truncates_root_digests(self) -> None: + for relative in ( + "release/evidence/claim_hostile_matrix_v160.json", + "release/evidence/claim_aba_v160.json", + ): + document = json.loads((ROOT / relative).read_text(encoding="utf-8")) + for row in document.get("rows", document.get("layers", [])): + observed = str( + row.get("observed", row.get("observed_under_poison", "")) + ) + if "root=" not in observed: + continue + root_digest = observed.split("root=", 1)[1].split()[0] + with self.subTest(path=relative, row=row.get("id", row.get("layer"))): + self.assertRegex(root_digest, r"^[0-9a-f]{64}$") + + def test_repin_compiler_override_remains_hash_authorized(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-repin-compiler-") as td: + counterfeit = Path(td) / "anubis-counterfeit" + counterfeit.write_bytes(b"not the pinned compiler") + environment = os.environ.copy() + environment["JACKAL_ANUBIS_COMPILER_PATH"] = os.fspath(counterfeit) + completed = subprocess.run( + [os.fspath(REPIN), "--check"], + capture_output=True, + text=True, + cwd=ROOT, + env=environment, + timeout=120, + ) + self.assertNotEqual(completed.returncode, 0) + self.assertIn("compiler authority drift", completed.stderr) + self.assertIn(os.fspath(counterfeit), completed.stderr) + + def test_catalog_profiles_and_compatibility_floor_agree(self) -> None: + catalog = json.loads((ROOT / "plugin/hermes/tools.json").read_text()) + compatibility = json.loads(COMPAT.read_text()) + full = json.loads((ROOT / "plugin/hermes/profiles/full.json").read_text()) + names = [row["name"] for row in catalog["tools"]] + self.assertEqual(catalog["version"], "v1.7.3") + self.assertEqual(compatibility["release_epoch"], "v1.7.3") + self.assertEqual(compatibility["tool_count"], 41) + self.assertEqual(compatibility["program_profile"], "inventory-safe-v1") + self.assertIs( + compatibility["independent_policy_construct_totality"], False + ) + self.assertEqual(full["tools"], names) + + inventory = json.loads( + (ROOT / "release/capability_inventory_v1.json").read_text() + ) + self.assertEqual(inventory["schema"], "jackal-capability-inventory-v1") + self.assertEqual(inventory["tool_count"], 41) + self.assertEqual(inventory["unique_tool_count"], 41) + self.assertEqual( + [row["name"] for row in inventory["tools"]], + names, + ) + self.assertEqual( + inventory["catalog"]["sha256"], + hashlib.sha256( + (ROOT / "plugin/hermes/tools.json").read_bytes() + ).hexdigest(), + ) + + def test_dry_run_and_repin_check_are_current_source_instruments(self) -> None: + if (platform.system(), platform.machine()) != ("Darwin", "arm64"): + self.skipTest("release/build_package_v173.sh requires Darwin/arm64") + if "JACKAL_ANUBIS_COMPILER_PATH" not in os.environ: + self.skipTest("set JACKAL_ANUBIS_COMPILER_PATH to the pinned compiler") + unset_environment = os.environ.copy() + unset_environment.pop("JACKAL_ANUBIS_COMPILER_PATH", None) + unset = subprocess.run( + [str(BUILDER), "--dry-run"], + capture_output=True, + text=True, + cwd=ROOT, + env=unset_environment, + timeout=120, + ) + self.assertEqual(unset.returncode, 4, unset.stdout + unset.stderr) + self.assertIn("reason=compiler-path-unset", unset.stderr) + dry = subprocess.run( + [str(BUILDER), "--dry-run"], + capture_output=True, + text=True, + cwd=ROOT, + timeout=120, + ) + self.assertEqual(dry.returncode, 0, dry.stdout + dry.stderr) + self.assertIn("PACKAGE_V173_DRY_RUN_PASS", dry.stdout) + extra = subprocess.run( + [str(BUILDER), "--dry-run", "unexpected"], + capture_output=True, + text=True, + cwd=ROOT, + timeout=120, + ) + self.assertEqual(extra.returncode, 2, extra.stdout + extra.stderr) + self.assertIn("usage:", extra.stderr) + repin = subprocess.run( + [os.fspath(REPIN), "--check"], + capture_output=True, + text=True, + cwd=ROOT, + timeout=120, + ) + self.assertEqual(repin.returncode, 0, repin.stdout + repin.stderr) + self.assertIn("REPIN_V173_CHECK_PASS", repin.stdout) + + def test_built_package_has_reachable_complete_surface(self) -> None: + package_root_raw = os.environ.get("JACKAL_TEST_PACKAGE_ROOT") + if not package_root_raw: + self.skipTest("set JACKAL_TEST_PACKAGE_ROOT to a freshly built package") + package = Path(package_root_raw) + self.assertEqual(package.name, PACKAGE_NAME) + self.assertTrue((package / "jackal-anubis-program").is_file()) + manifest_labels = { + line.split()[0] + for line in (package / "MANIFEST.sha256").read_text( + encoding="utf-8" + ).splitlines() + if line.strip() + } + self.assertTrue( + REQUIRED_PACKAGE_MANIFEST_LABELS <= manifest_labels, + sorted(REQUIRED_PACKAGE_MANIFEST_LABELS - manifest_labels), + ) + for relative in REQUIRED_PACKAGE_INPUTS: + destination = relative + if relative.startswith("release/"): + destination = relative.removeprefix("release/") + self.assertTrue((package / destination).is_file(), destination) + listed = subprocess.run( + [str(package / "plugin/hermes/jackal_hermes"), "stdio"], + input=json.dumps( + {"jsonrpc": "2.0", "id": "catalog", "method": "list_tools"} + ) + + "\n", + capture_output=True, + text=True, + cwd=package, + timeout=120, + ) + self.assertEqual(listed.returncode, 0, listed.stdout + listed.stderr) + reply = json.loads(listed.stdout.strip()) + self.assertEqual(len(reply["result"]["tools"]), 41) + + + def test_missing_pack_refuses_pack_only(self) -> None: + package_root_raw = os.environ.get("JACKAL_TEST_PACKAGE_ROOT") + if not package_root_raw: + self.skipTest("set JACKAL_TEST_PACKAGE_ROOT to a freshly built package") + source_package = Path(package_root_raw) + with tempfile.TemporaryDirectory(prefix="jackal-package-no-pack-") as td: + package = Path(td) / PACKAGE_NAME + shutil.copytree(source_package, package) + shutil.rmtree(package / "domain_packs") + nonpack = subprocess.run( + [ + str(package / "plugin/hermes/jackal_hermes"), + "call", + "jackal_exact", + json.dumps({"expression": "1+1"}), + ], + capture_output=True, + text=True, + cwd=package, + timeout=120, + ) + self.assertEqual(nonpack.returncode, 0, nonpack.stdout + nonpack.stderr) + self.assertEqual(json.loads(nonpack.stdout)["status"], "exact") + pack = subprocess.run( + [ + str(package / "plugin/hermes/jackal_hermes"), + "call", + "jackal_test_exists", + json.dumps( + { + "file_path": "README.txt", + "file_sha256": "0" * 64, + "symbol": "missing", + "declaration_line": "1", + "declaration_count": "1", + } + ), + ], + capture_output=True, + text=True, + cwd=package, + timeout=120, + ) + self.assertNotEqual(pack.returncode, 0) + self.assertEqual( + json.loads(pack.stdout)["reason"], "pack-surface-absent" + ) + + def test_declared_program_tool_with_missing_runtime_refuses_startup(self) -> None: + package_root_raw = os.environ.get("JACKAL_TEST_PACKAGE_ROOT") + if not package_root_raw: + self.skipTest("set JACKAL_TEST_PACKAGE_ROOT to a freshly built package") + source_package = Path(package_root_raw) + with tempfile.TemporaryDirectory(prefix="jackal-package-unreachable-") as td: + package = Path(td) / PACKAGE_NAME + shutil.copytree(source_package, package) + (package / "tools/anubis_program_verify.py").unlink() + result = subprocess.run( + [str(package / "plugin/hermes/jackal_hermes"), "selftest"], + capture_output=True, + text=True, + cwd=package, + timeout=120, + ) + self.assertNotEqual(result.returncode, 0) + self.assertIn( + "plugin-layout-missing: anubis_program_verifier", + result.stdout + result.stderr, + ) + + + def test_sha256sums_is_complete_and_mutation_sensitive(self) -> None: + package_root_raw = os.environ.get("JACKAL_TEST_PACKAGE_ROOT") + if not package_root_raw: + self.skipTest("set JACKAL_TEST_PACKAGE_ROOT to a freshly built package") + source_package = Path(package_root_raw) + self.assertTrue(complete_sha256sums(source_package)) + with tempfile.TemporaryDirectory(prefix="jackal-package-sums-") as td: + package = Path(td) / PACKAGE_NAME + shutil.copytree(source_package, package) + (package / "unlisted-extra.txt").write_text("extra\n") + self.assertFalse(complete_sha256sums(package)) + (package / "unlisted-extra.txt").unlink() + + readme = package / "README.txt" + readme.write_bytes(readme.read_bytes() + b"tamper\n") + self.assertFalse(complete_sha256sums(package)) + shutil.copy2(source_package / "README.txt", readme) + + sums = package / "SHA256SUMS" + lines = sums.read_text().splitlines() + sums.write_text("\n".join(lines[1:]) + "\n") + self.assertFalse(complete_sha256sums(package)) + + def test_nested_file_named_sha256sums_is_not_excluded(self) -> None: + with tempfile.TemporaryDirectory(prefix="jackal-nested-sums-") as td: + root = Path(td) + nested = root / "nested/SHA256SUMS" + nested.parent.mkdir() + nested.write_bytes(b"nested payload\n") + digest = hashlib.sha256(nested.read_bytes()).hexdigest() + (root / "SHA256SUMS").write_text( + f"{digest} ./nested/SHA256SUMS\n", + encoding="ascii", + ) + self.assertTrue(complete_sha256sums(root)) + + + def test_stale_binary_source_pair_refuses_formal_tool(self) -> None: + package_root_raw = os.environ.get("JACKAL_TEST_PACKAGE_ROOT") + if not package_root_raw: + self.skipTest("set JACKAL_TEST_PACKAGE_ROOT to a freshly built package") + source_package = Path(package_root_raw) + with tempfile.TemporaryDirectory(prefix="jackal-package-stale-source-") as td: + package = Path(td) / PACKAGE_NAME + shutil.copytree(source_package, package) + source = package / "jackal_calc.anb" + source.write_bytes(source.read_bytes() + b"\n") + result = subprocess.run( + [ + str(package / "plugin/hermes/jackal_hermes"), + "call", + "jackal_range_bound", + json.dumps( + { + "expression": "x", + "input_lo": "0", + "input_hi": "1", + } + ), + ], + capture_output=True, + text=True, + cwd=package, + timeout=120, + ) + self.assertNotEqual(result.returncode, 0) + document = json.loads(result.stdout) + self.assertEqual(document["status"], "refused") + self.assertEqual(document["reason"], "source-identity") + + + def test_every_domain_pack_admits_the_selected_release(self) -> None: + for pack in ("core", "programming", "decision"): + document = json.loads( + (ROOT / f"domain_packs/{pack}/manifest.json").read_text() + ) + compatibility = document["compatibility"] + self.assertEqual( + compatibility["jackal_release_min"], + "v1.7.3", + f"{pack} excludes the selected release", + ) + self.assertEqual( + compatibility["jackal_release_max_exclusive"], "v2.0.0" + ) + + def test_superseded_builder_mutation_turns_instrument_red(self) -> None: + path = ROOT / "tests/claim_package_parity_test.py" + spec = importlib.util.spec_from_file_location("claim_package_parity", path) + self.assertIsNotNone(spec) + self.assertIsNotNone(spec.loader) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + current, current_reason = module.instrument_current() + self.assertTrue(current, current_reason) + module.BUILDER = ROOT / "release/build_package_v170.sh" + accepted, reason = module.instrument_current() + self.assertFalse(accepted) + self.assertEqual(reason, "superseded-builder") + module.BUILDER = ROOT / "missing/build_package_v173.sh" + accepted, reason = module.instrument_current() + self.assertFalse(accepted) + self.assertEqual(reason, "builder-missing") + +if __name__ == "__main__": + unittest.main() diff --git a/tests/plugin_bundle_identity_test.py b/tests/plugin_bundle_identity_test.py index 3d83895..1dc40f4 100644 --- a/tests/plugin_bundle_identity_test.py +++ b/tests/plugin_bundle_identity_test.py @@ -26,8 +26,10 @@ "runtime/coverage_inventory.py", "runtime/formal_coverage_inventory.json", "runtime/range_proof_identity.json", + "runtime/archival_range_proof_identity.json", "runtime/gaussian_proof_identity.json", "runtime/int_cert_proof_identity.json", + "runtime/archival_int_cert_proof_identity.json", "runtime/formal_receipt.py", "runtime/formal_status_gate.py", "runtime/gaussian_certificate.py", @@ -49,6 +51,8 @@ "runtime/claim_bundle_verify.py", "runtime/inference_registry_v1.json", "runtime/unit_registry_v1.json", + "runtime/anubis_program_verify.py", + "runtime/anubis_program_policy.json", } @@ -60,8 +64,10 @@ "runtime/coverage_inventory.py": "coverage_inventory.py", "runtime/formal_coverage_inventory.json": "formal_coverage_inventory.json", "runtime/range_proof_identity.json": "range_proof_identity.json", + "runtime/archival_range_proof_identity.json": "evidence/range_proof_identity_v1.json", "runtime/gaussian_proof_identity.json": "gaussian_proof_identity.json", "runtime/int_cert_proof_identity.json": "int_cert_proof_identity.json", + "runtime/archival_int_cert_proof_identity.json": "evidence/int_cert_proof_identity_v1.json", "runtime/formal_receipt.py": "formal_receipt.py", "runtime/formal_status_gate.py": "formal_status_gate.py", "runtime/gaussian_certificate.py": "gaussian_certificate.py", @@ -83,6 +89,8 @@ "runtime/claim_bundle_verify.py": "claim_bundle_verify.py", "runtime/inference_registry_v1.json": "inference_registry_v1.json", "runtime/unit_registry_v1.json": "unit_registry_v1.json", + "runtime/anubis_program_verify.py": "tools/anubis_program_verify.py", + "runtime/anubis_program_policy.json": "program/inventory_safe_v1.json", } @@ -95,8 +103,8 @@ def main() -> int: repo_files = resolve_runtime_files(PLUGIN_DIR) require(set(repo_files) == EXPECTED_LOGICAL_NAMES, f"runtime logical-name drift: {sorted(repo_files)}") - require(len(EXPECTED_LOGICAL_NAMES) == 30, - f"expected 30 runtime logical names, declared {len(EXPECTED_LOGICAL_NAMES)}") + require(len(EXPECTED_LOGICAL_NAMES) == 34, + f"expected 34 runtime logical names, declared {len(EXPECTED_LOGICAL_NAMES)}") repo_hash = compute_bundle_hash(PLUGIN_DIR) with tempfile.TemporaryDirectory(prefix="jackal-plugin-identity-") as td: diff --git a/tests/plugin_smoke.py b/tests/plugin_smoke.py index dd0afd3..d6ac62e 100755 --- a/tests/plugin_smoke.py +++ b/tests/plugin_smoke.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""JACKAL v1.7.2 Hermes plugin end-to-end smoke. +"""JACKAL v1.7.3 Hermes plugin end-to-end smoke. Fresh-session run against the shipped `plugin/hermes/jackal_hermes` binary and its pinned bundle hash. Verifies: @@ -20,7 +20,7 @@ S7 jackal_verify_receipt refuses a receipt with the outer digest recomputed but the enclosure tampered (cross-check gate). S8 stdio JSON-RPC transport handles list_tools + tool calls with - correct id/jsonrpc/result shape (38 tools listed) and drives the + correct id/jsonrpc/result shape (41 tools listed) and drives the same refusals. S9 jackal_gaussian_integral emits + reverifies a Gaussian receipt. S10 jackal_gaussian_integral refuses unsupported non-canonical Gaussians. @@ -301,6 +301,9 @@ def s8_stdio_transport() -> bool: # domain-pack lanes, additive (34 -> 38); NOT formal, consequence-capped "jackal_test_exists", "jackal_claim_cites_test", "jackal_decision_rank", "jackal_decision_rank_v2", + # inventory-safe Anubis program-evidence tools (38 -> 41); NOT formal + "jackal_anubis_check_program", "jackal_anubis_verify_program", + "jackal_anubis_verify_program_receipt", } ok_list = set(listed) == expected_tools and len(listed) == len(expected_tools) ok_ok = idx.get("OK", {}).get("result", {}).get("status") == "formal-bounded" diff --git a/tests/profile_contract_test.py b/tests/profile_contract_test.py index 5170a44..53700d6 100755 --- a/tests/profile_contract_test.py +++ b/tests/profile_contract_test.py @@ -108,9 +108,9 @@ def test_positive_shipped_profiles_verify(self) -> None: self.assertEqual(result["tools_declared"], EXPECTED_TOOL_COUNT) self.assertEqual(result["profiles"]["core"]["tool_count"], 3) self.assertEqual(result["profiles"]["full"]["tool_count"], EXPECTED_TOOL_COUNT) - # Non-vacuity: the derived count must actually be the shipped surface, - # so a `tools.json` truncated to the old 34 would fail here. - self.assertGreaterEqual(EXPECTED_TOOL_COUNT, 38) + # Non-vacuity: the combined release must carry the merged 38-tool + # surface plus all three program-evidence tools. + self.assertGreaterEqual(EXPECTED_TOOL_COUNT, 41) def test_positive_every_pack_operation_is_reachable_on_full(self) -> None: """The omission this test exists to make impossible. diff --git a/tests/release_authorization_v173_test.py b/tests/release_authorization_v173_test.py new file mode 100644 index 0000000..12ab270 --- /dev/null +++ b/tests/release_authorization_v173_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +AUTHORIZATION = ROOT / "release/evidence/architect_release_authorization_v173.json" + + +class ReleaseAuthorizationV173Test(unittest.TestCase): + def test_architect_approved_both_trust_surfaces_and_release_actions(self) -> None: + document = json.loads(AUTHORIZATION.read_text(encoding="utf-8")) + self.assertEqual(document["schema"], "jackal-release-authorization-v1") + self.assertEqual(document["release"], "v1.7.3") + self.assertEqual(document["authority"], "architect") + self.assertEqual( + document["instructions"], + [ + "Merge and do whatever else you have to do. Don’t leave nothing undone", + "Use your best jusment on all desisions", + ], + ) + self.assertEqual( + document["decisions"], + { + "domain_pack_compatibility_minimum_approved": True, + "inventory_safe_v1_accept_conditions_approved": True, + "jackal_merge_tag_release_approved": True, + "hermes_merge_tag_release_install_approved": True, + "upstream_pr_update_approved": True, + }, + ) + self.assertIn("not-a-cryptographic-signature", document["non_claims"]) + self.assertIn( + "does-not-override-third-party-permissions-or-branch-protection", + document["non_claims"], + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/release_wiring_v172_contract_test.py b/tests/release_wiring_v172_contract_test.py index d33ad72..e8d783b 100644 --- a/tests/release_wiring_v172_contract_test.py +++ b/tests/release_wiring_v172_contract_test.py @@ -127,7 +127,7 @@ def test_plan_preserves_unchanged_v170_lanes(self) -> None: if label not in intentionally_changed: self.assertEqual(new[label], row, label) - def test_evaluator_is_the_fixed_v170_issue4_epoch(self) -> None: + def test_v170_evaluator_stays_historical_while_current_row_tracks_disk(self) -> None: review = json.loads( (ROOT / "release/evidence/release_review_v170.json").read_text( encoding="utf-8" @@ -136,9 +136,10 @@ def test_evaluator_is_the_fixed_v170_issue4_epoch(self) -> None: self.assertEqual( review["identities"]["evaluator_sha256"], V170_EVALUATOR_SHA256 ) - self.assertEqual(sha256(ROOT / "jackal-native"), V170_EVALUATOR_SHA256) + current = sha256(ROOT / "jackal-native") + self.assertNotEqual(current, V170_EVALUATOR_SHA256) mapped = row_map(self.module.build_rows()) - self.assertEqual(mapped["evaluator"][-1], V170_EVALUATOR_SHA256) + self.assertEqual(mapped["evaluator"][-1], current) def test_compiler_authority_is_exact_and_immutable(self) -> None: self.assertEqual(self.module.COMPILER_PATH, COMPILER) @@ -332,7 +333,7 @@ def test_hermes_has_epoch_aware_archival_replay_dispatch(self) -> None: self.assertIn("unsupported int-cert epoch", verify_body) def test_current_formal_tools_emit_the_v172_epoch(self) -> None: - self.assertEqual(self.catalog["version"], "v1.7.2") + self.assertEqual(self.catalog["version"], "v1.7.3") range_body = self.source.split("def tool_range_bound", 1)[1].split( "def tool_gaussian_integral", 1 )[0] diff --git a/tests/unified_surface_contract_test.py b/tests/unified_surface_contract_test.py new file mode 100755 index 0000000..ed2312d --- /dev/null +++ b/tests/unified_surface_contract_test.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import hashlib +import json +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +PLUGIN = ROOT / "plugin/hermes" +PROGRAM_TOOLS = [ + "jackal_anubis_check_program", + "jackal_anubis_verify_program", + "jackal_anubis_verify_program_receipt", +] +BASELINE_TOOLS = [ + "jackal_range_bound", + "jackal_gaussian_integral", + "jackal_integrate_bound_cert", + "jackal_verify_receipt", + "jackal_sqrt_rat_bound", + "jackal_exp_rat_bound", + "jackal_ln_rat_bound", + "jackal_sin_rat_bound", + "jackal_cos_rat_bound", + "jackal_atan_rat_bound", + "jackal_tanh_rat_bound", + "jackal_exact", + "jackal_evaluate", + "jackal_diff", + "jackal_integrate", + "jackal_integrate_adaptive", + "jackal_integrate_bound", + "jackal_solve", + "jackal_canon", + "jackal_poly_canon", + "jackal_poly_eq", + "jackal_poly_gcd", + "jackal_ratfunc_canon", + "jackal_roots_isolate", + "jackal_alg_sign", + "jackal_alg_cmp", + "jackal_xgcd", + "jackal_mod_pow", + "jackal_mod_inv", + "jackal_crt", + "jackal_divides", + "jackal_prime_cert", + "jackal_claim", + "jackal_verify_bundle", + "jackal_test_exists", + "jackal_claim_cites_test", + "jackal_decision_rank", + "jackal_decision_rank_v2", +] +EXPECTED_FULL = [*BASELINE_TOOLS, *PROGRAM_TOOLS] +EXPECTED_CORE = [ + "jackal_verify_receipt", + "jackal_claim", + "jackal_verify_bundle", +] +EXPECTED_FORMAL = [ + "jackal_range_bound", + "jackal_gaussian_integral", + "jackal_integrate_bound_cert", + "jackal_verify_receipt", + "jackal_sqrt_rat_bound", + "jackal_exp_rat_bound", + "jackal_ln_rat_bound", + "jackal_sin_rat_bound", + "jackal_cos_rat_bound", + "jackal_atan_rat_bound", + "jackal_tanh_rat_bound", + "jackal_claim", + "jackal_verify_bundle", +] +PROGRAM_RUNTIME_FILES = { + "runtime/anubis_program_verify.py", + "runtime/anubis_program_policy.json", +} +DOMAIN_MANIFEST_LABELS = { + "domain_pack_registry", + "domain_pack_verifier", + "domain_pack_test_exists_checker", + "domain_pack_decision_checker", +} + + +def digest_profile(document: dict) -> str: + payload = { + key: value + for key, value in document.items() + if key != "profile_digest_sha256" + } + return hashlib.sha256( + json.dumps( + payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + ).hexdigest() + + +class UnifiedSurfaceContractTest(unittest.TestCase): + def test_catalog_is_exact_combined_41_tool_surface(self) -> None: + catalog = json.loads((PLUGIN / "tools.json").read_text(encoding="utf-8")) + names = [row["name"] for row in catalog["tools"]] + self.assertEqual(catalog["version"], "v1.7.3") + self.assertEqual(names, EXPECTED_FULL) + self.assertEqual(len(names), 41) + self.assertEqual(len(set(names)), 41) + + def test_core_and_formal_are_unchanged_program_tools_are_full_only(self) -> None: + profiles = { + name: json.loads( + (PLUGIN / f"profiles/{name}.json").read_text(encoding="utf-8") + ) + for name in ("core", "formal", "full") + } + self.assertEqual(profiles["core"]["tools"], EXPECTED_CORE) + self.assertEqual(profiles["formal"]["tools"], EXPECTED_FORMAL) + self.assertEqual(profiles["full"]["tools"], EXPECTED_FULL) + for name, document in profiles.items(): + self.assertEqual(document["profile_digest_sha256"], digest_profile(document), name) + for tool in PROGRAM_TOOLS: + self.assertNotIn(tool, profiles["core"]["tools"]) + self.assertNotIn(tool, profiles["formal"]["tools"]) + self.assertIn(tool, profiles["full"]["tools"]) + + def test_program_is_bundle_bound_and_domain_surface_is_call_local_pinned(self) -> None: + catalog = json.loads((PLUGIN / "tools.json").read_text(encoding="utf-8")) + runtime_files = set(catalog["runtime_files"]) + self.assertTrue(PROGRAM_RUNTIME_FILES <= runtime_files) + self.assertFalse(any(name.startswith("runtime/domain_pack") for name in runtime_files)) + manifest_labels = { + line.split()[0] + for line in (ROOT / "release/MANIFEST.sha256").read_text().splitlines() + if line and not line.startswith("#") + } + self.assertTrue(DOMAIN_MANIFEST_LABELS <= manifest_labels) + + def test_server_dispatch_declares_all_catalog_tools_once(self) -> None: + source = (PLUGIN / "server.py").read_text(encoding="utf-8") + for tool in PROGRAM_TOOLS: + self.assertEqual(source.count(f'"{tool}"'), 1, tool) + for tool in BASELINE_TOOLS: + self.assertIn(tool, source, tool) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/anubis_program_verify.py b/tools/anubis_program_verify.py new file mode 100755 index 0000000..9e25038 --- /dev/null +++ b/tools/anubis_program_verify.py @@ -0,0 +1,1386 @@ +#!/usr/bin/env python3 +"""Fail-closed verifier for Anubis whole-program evidence packages. + +This verifier is dependency-free and intentionally does not execute program artifacts. +It binds caller-pinned source/compiler/artifact identities, closes the evidence manifest, +reconciles Anubis program-evidence v3 inventories, and independently replays the admitted +RUP proof fragment. Source-to-VC and source-to-native refinement remain explicit residuals. +""" +from __future__ import annotations + +import sys + +if not (sys.flags.isolated and sys.flags.no_site): + print('status=refused reason=python-not-isolated detail="requires python3 -I -S -B"') + raise SystemExit(126) + +import argparse +import contextlib +import hashlib +import json +import os +import re +import subprocess +import stat +import tempfile +import time +from pathlib import Path, PurePosixPath +from typing import Any, Callable + +MAX_FILES = 512 +MAX_TOTAL_BYTES = 64 * 1024 * 1024 +MAX_JSON_BYTES = 8 * 1024 * 1024 +MAX_COMPILER_BYTES = 512 * 1024 * 1024 +MAX_PROOF_STEPS = 200_000 +MAX_CLAUSES = 2_000_000 +MAX_LITERALS = 20_000_000 +RUP_REPLAY_TIMEOUT_SECONDS = 30.0 +HEX64 = re.compile(r"^[0-9a-f]{64}$") +MANIFEST_ROW = re.compile(r"^([0-9a-f]{64}) ([A-Za-z0-9._/-]+)$") +REQUIRED_STAGES = [ + "parse", + "typecheck", + "monomorphization", + "policy-effects", + "policy-capability", + "policy-information-flow", + "policy-declassification", + "symbolic", + "solver", + "source-binding", + "artifact-binding", + "evidence-closure", +] +REQUIRED_CONSUMERS = [ + "effects", + "capability", + "information-flow", + "declassification", + "mode", + "contracts", +] +PRODUCER_RESIDUALS = [ + "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", +] +RECEIPT_RESIDUALS = [ + *PRODUCER_RESIDUALS, + "policy-construct-totality-not-established", +] +PROGRAM_KEYS = { + "schema", + "version", + "mode", + "source", + "compiler", + "artifacts", + "stages", + "solver_inventory", + "policy_inventory", + "residual_non_claims", +} +ALLOWED_PROGRAM_FILES = { + "MANIFEST.sha256", + "analysis/proofs.json", + "analysis/solver.smt2", + "analysis/solver_replay.json", + "artifact", + "bounty-report.md", + "build.log", + "checks.sarif", + "confinement_manifest.json", + "declassify_audit.json", + "dep_closure.json", + "entitlement_profile.json", + "environment.json", + "evidence.json", + "hir.json", + "manifest.json", + "mir.json", + "mono_specializations.json", + "pca.json", + "program-evidence.json", + "program.entitlements", + "solver.json", + "source-merkle-leaves.json", + "source-tree.json", + "source.anubis", + "summaries.json", + "taint-traces.json", + "validate.sh", +} +REQUIRED_PROGRAM_FILES = ALLOWED_PROGRAM_FILES - { + "dep_closure.json", + "source-merkle-leaves.json", +} +PROOF_FILE = re.compile(r"^analysis/proofs/obligation_[0-9]{4}\.(cnf|drat|smt2)$") +Z3_UNSAT_MODEL_ERROR = re.compile( + r'^\(error "line [1-9][0-9]* column [1-9][0-9]*: model is not available"\)$' +) +SUPPORTED_PROFILE = "inventory-safe-v1" +APPROVED_CHECK_COMPILER_SHA256 = ( + "0d6a8f89355eb9ec5971749daf943567c204ed9f2d3001edbd46599f4540d7d6" +) +APPROVED_Z3_PATH = Path("/opt/homebrew/bin/z3") +APPROVED_Z3_SHA256 = "ae6c8df33db9c9ae9a80b6044e77cd66529a141d8b25f0620f1e89b409594f48" +PROGRAM_POLICY_CANDIDATES = ( + Path(__file__).resolve().parents[1] + / "release/program/inventory_safe_v1.json", + Path(__file__).resolve().parents[1] / "program/inventory_safe_v1.json", +) +PROGRAM_POLICY_BODY = { + "schema": "jackal-anubis-program-policy-v1", + "profile": SUPPORTED_PROFILE, + "mode": "safe", + "source_leaves": 1, + "minimum_obligations": 1, + "proof_kinds": ["rup_refutation"], + "required_stages": REQUIRED_STAGES, + "required_consumers": REQUIRED_CONSUMERS, + "approved_check_compiler_sha256": APPROVED_CHECK_COMPILER_SHA256, + "approved_z3_sha256": APPROVED_Z3_SHA256, + "runtime_execution": False, + "policy_inventory_authority": "producer-attested-function-roster", + "independent_policy_construct_totality": False, + "receipt_residual_non_claims": RECEIPT_RESIDUALS, +} +PROGRAM_POLICY_SHA256 = hashlib.sha256( + json.dumps( + PROGRAM_POLICY_BODY, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + ).encode("utf-8") +).hexdigest() + + +class Refusal(Exception): + def __init__(self, reason: str, detail: str = "") -> None: + super().__init__(detail) + self.reason = reason + self.detail = detail + + +def sha(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def sha_file(path: Path) -> str: + return sha(path.read_bytes()) + + +def canonical(value: Any) -> bytes: + return json.dumps( + value, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + + +def reject_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + out: dict[str, Any] = {} + for key, value in pairs: + if key in out: + raise Refusal("duplicate-key", key) + out[key] = value + return out + + +def reject_float(token: str) -> None: + raise Refusal("float-forbidden", token) + + +def load_json(path: Path) -> Any: + try: + data = path.read_bytes() + except OSError as exc: + raise Refusal("input-read", f"{path.name}: {exc}") from None + if len(data) > MAX_JSON_BYTES: + raise Refusal("json-budget", path.name) + try: + return json.loads( + data.decode("utf-8"), + object_pairs_hook=reject_duplicates, + parse_float=reject_float, + parse_constant=reject_float, + ) + except Refusal: + raise + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise Refusal("json-invalid", f"{path.name}: {exc}") from None + + +def require_keys(value: Any, expected: set[str], label: str) -> dict[str, Any]: + if not isinstance(value, dict): + raise Refusal("schema", f"{label} must be an object") + actual = set(value) + if actual != expected: + raise Refusal( + "unknown-field", + f"{label}: missing={sorted(expected - actual)} extra={sorted(actual - expected)}", + ) + return value + + +def require_hex(value: Any, label: str) -> str: + if not isinstance(value, str) or not HEX64.fullmatch(value): + raise Refusal("hash-token", label) + return value + + +@contextlib.contextmanager +def pinned_executable_snapshot(path: Path, expected_sha256: str): + """Copy one stable executable inode, then execute only the private copy. + + The caller-selected path is opened without following a final symlink. Its + identity is checked across the bounded copy and again after execution. A + path replacement during the subprocess therefore cannot change the bytes + that run; any mutation of the original or the private snapshot also makes + the overall check refuse. + """ + + expected = require_hex(expected_sha256, "expected-compiler") + + def identity(info: os.stat_result) -> tuple[int, int, int, int, int, int]: + return ( + info.st_dev, + info.st_ino, + info.st_mode, + info.st_size, + info.st_mtime_ns, + info.st_ctime_ns, + ) + + try: + before = path.lstat() + except OSError as exc: + raise Refusal("input-path", f"anubis-bin: {exc}") from None + if ( + not stat.S_ISREG(before.st_mode) + or stat.S_ISLNK(before.st_mode) + or before.st_size > MAX_COMPILER_BYTES + ): + raise Refusal("input-path", "anubis-bin") + + source_fd = -1 + owner: tempfile.TemporaryDirectory[str] | None = None + try: + source_fd = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)) + opened = os.fstat(source_fd) + if identity(opened) != identity(before): + raise Refusal("compiler-toctou") + + owner = tempfile.TemporaryDirectory(prefix="jackal-anubis-compiler-") + snapshot = Path(owner.name) / path.name + destination_fd = os.open( + snapshot, + os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0), + 0o500, + ) + digest = hashlib.sha256() + total = 0 + try: + while True: + chunk = os.read(source_fd, 1024 * 1024) + if not chunk: + break + total += len(chunk) + if total > MAX_COMPILER_BYTES: + raise Refusal("input-path", "anubis-bin exceeds byte bound") + digest.update(chunk) + remaining = memoryview(chunk) + while remaining: + written = os.write(destination_fd, remaining) + if written <= 0: + raise OSError("short compiler snapshot write") + remaining = remaining[written:] + os.fchmod(destination_fd, 0o500) + os.fsync(destination_fd) + finally: + os.close(destination_fd) + + copied = os.fstat(source_fd) + try: + current = path.lstat() + except OSError: + raise Refusal("compiler-toctou") from None + if identity(opened) != identity(copied) or identity(copied) != identity(current): + raise Refusal("compiler-toctou") + if total != opened.st_size or digest.hexdigest() != expected: + raise Refusal("compiler-pin-mismatch") + if sha_file(snapshot) != expected: + raise Refusal("compiler-snapshot-toctou") + + yield snapshot + + after = os.fstat(source_fd) + try: + current = path.lstat() + except OSError: + raise Refusal("compiler-toctou") from None + if identity(copied) != identity(after) or identity(after) != identity(current): + raise Refusal("compiler-toctou") + if sha_file(snapshot) != expected: + raise Refusal("compiler-snapshot-toctou") + except Refusal: + raise + except OSError as exc: + raise Refusal("compiler-snapshot", str(exc)) from None + finally: + if source_fd >= 0: + os.close(source_fd) + if owner is not None: + owner.cleanup() + + +def load_program_policy() -> tuple[dict[str, Any], str, str]: + present = [ + path + for path in PROGRAM_POLICY_CANDIDATES + if path.exists() or path.is_symlink() + ] + if len(present) != 1: + raise Refusal( + "policy-layout", + f"expected one policy file, found {[str(path) for path in present]}", + ) + path = present[0] + if path.is_symlink() or not path.is_file(): + raise Refusal("policy-layout", "policy must be a regular non-symlink file") + document = require_keys( + load_json(path), + set(PROGRAM_POLICY_BODY) | {"policy_digest_sha256"}, + "program policy", + ) + digest = require_hex(document["policy_digest_sha256"], "policy digest") + body = { + key: value + for key, value in document.items() + if key != "policy_digest_sha256" + } + if digest != sha(canonical(body)): + raise Refusal("policy-digest-mismatch") + if digest != PROGRAM_POLICY_SHA256 or body != PROGRAM_POLICY_BODY: + raise Refusal("policy-unsupported", digest) + return body, digest, sha_file(path) + + +def safe_relative(token: str) -> str: + if "\\" in token or token.startswith("/"): + raise Refusal("path-unsafe", token) + path = PurePosixPath(token) + if not token or any(part in {"", ".", ".."} for part in path.parts): + raise Refusal("path-unsafe", token) + normalized = path.as_posix() + if normalized != token: + raise Refusal("path-noncanonical", token) + return token + + +def regular_tree(root: Path) -> dict[str, Path]: + try: + root = root.resolve(strict=True) + except OSError as exc: + raise Refusal("input-path", str(exc)) from None + if not root.is_dir() or root.is_symlink(): + raise Refusal("input-path", "evidence root must be a real directory") + files: dict[str, Path] = {} + total = 0 + def walk_error(exc: OSError) -> None: + raise Refusal("tree-walk-error", str(exc)) + + for current, dirs, names in os.walk( + root, followlinks=False, onerror=walk_error + ): + current_path = Path(current) + depth = len(current_path.relative_to(root).parts) + if depth > 8: + raise Refusal("path-depth", str(current_path)) + for directory in dirs: + child = current_path / directory + if child.is_symlink(): + raise Refusal("symlink-forbidden", child.relative_to(root).as_posix()) + for name in names: + child = current_path / name + rel = safe_relative(child.relative_to(root).as_posix()) + if child.is_symlink() or not child.is_file(): + raise Refusal("nonregular-file", rel) + if rel in files: + raise Refusal("path-duplicate", rel) + files[rel] = child + total += child.stat().st_size + if len(files) > MAX_FILES or total > MAX_TOTAL_BYTES: + raise Refusal("bundle-budget", f"files={len(files)} bytes={total}") + return files + + +def verify_manifest(root: Path, files: dict[str, Path]) -> tuple[dict[str, str], str]: + manifest = files.get("MANIFEST.sha256") + if manifest is None: + raise Refusal("manifest-missing") + try: + raw = manifest.read_bytes() + text = raw.decode("ascii") + except (OSError, UnicodeDecodeError) as exc: + raise Refusal("manifest-invalid", str(exc)) from None + rows: dict[str, str] = {} + casefold: set[str] = set() + for line in text.splitlines(): + match = MANIFEST_ROW.fullmatch(line) + if match is None: + raise Refusal("manifest-invalid", line[:120]) + digest, raw_path = match.groups() + path = safe_relative(raw_path) + folded = path.casefold() + if path in rows or folded in casefold: + raise Refusal("manifest-duplicate", path) + rows[path] = digest + casefold.add(folded) + actual = set(files) - {"MANIFEST.sha256"} + if set(rows) != actual: + raise Refusal( + "manifest-closure", + f"missing={sorted(actual - set(rows))} extra={sorted(set(rows) - actual)}", + ) + for path, digest in rows.items(): + if sha_file(files[path]) != digest: + raise Refusal("manifest-hash-mismatch", path) + return rows, sha(raw) + + +def verify_file_roster(files: dict[str, Path]) -> None: + unknown = sorted( + path + for path in files + if path not in ALLOWED_PROGRAM_FILES and not PROOF_FILE.fullmatch(path) + ) + if unknown: + raise Refusal("bundle-file-roster", str(unknown)) + missing = sorted(REQUIRED_PROGRAM_FILES - set(files)) + if missing: + raise Refusal("bundle-file-roster", f"missing={missing}") + + +def freeze_evidence_tree( + source_root: Path, files: dict[str, Path], manifest_rows: dict[str, str] +) -> tuple[tempfile.TemporaryDirectory[str], Path, dict[str, Path], str]: + holder = tempfile.TemporaryDirectory(prefix="jackal-program-snapshot-") + snapshot = Path(holder.name) / "evidence" + snapshot.mkdir() + for relative, source in sorted(files.items()): + data = source.read_bytes() + if relative != "MANIFEST.sha256" and sha(data) != manifest_rows[relative]: + holder.cleanup() + raise Refusal("snapshot-drift", relative) + destination = snapshot / relative + destination.parent.mkdir(parents=True, exist_ok=True) + with destination.open("xb") as handle: + handle.write(data) + frozen_files = regular_tree(snapshot) + verify_file_roster(frozen_files) + _, frozen_manifest_sha = verify_manifest(snapshot, frozen_files) + return holder, snapshot, frozen_files, frozen_manifest_sha + + +def _check_proof_deadline( + deadline: float, clock: Callable[[], float] +) -> None: + if clock() > deadline: + raise Refusal("proof-budget", "RUP replay deadline exceeded") + + +def read_dimacs( + path: Path, + *, + deadline: float | None = None, + clock: Callable[[], float] = time.monotonic, +) -> tuple[list[tuple[int, ...]], int]: + clauses: list[tuple[int, ...]] = [] + declared_vars = None + declared_clauses = None + literal_count = 0 + text = path.read_text(encoding="ascii") + if deadline is not None: + _check_proof_deadline(deadline, clock) + for raw in text.splitlines(): + if deadline is not None: + _check_proof_deadline(deadline, clock) + line = raw.strip() + if not line or line.startswith("c"): + continue + if line.startswith("p "): + parts = line.split() + if ( + declared_vars is not None + or clauses + or len(parts) != 4 + or parts[:2] != ["p", "cnf"] + ): + raise Refusal("cnf-invalid", path.name) + try: + declared_vars = int(parts[2]) + declared_clauses = int(parts[3]) + except ValueError: + raise Refusal("cnf-invalid", path.name) from None + continue + try: + values = [int(value) for value in line.split()] + except ValueError: + raise Refusal("cnf-invalid", path.name) from None + if not values or values[-1] != 0 or 0 in values[:-1]: + raise Refusal("cnf-invalid", path.name) + clause = tuple(values[:-1]) + if len(set(clause)) != len(clause) or any(-value in clause for value in clause): + raise Refusal("cnf-invalid", "duplicate/tautological clause") + if declared_vars is not None and any(abs(value) > declared_vars for value in clause): + raise Refusal("cnf-invalid", "literal exceeds declared variable count") + clauses.append(clause) + literal_count += len(clause) + if len(clauses) > MAX_CLAUSES or literal_count > MAX_LITERALS: + raise Refusal("proof-budget", path.name) + if declared_vars is None or declared_clauses != len(clauses): + raise Refusal("cnf-invalid", "header count mismatch") + return clauses, declared_vars + + +def unit_conflict( + clauses: list[tuple[int, ...]], + assumptions: list[int], + *, + deadline: float, + clock: Callable[[], float], +) -> bool: + assignments: dict[int, bool] = {} + for literal in assumptions: + _check_proof_deadline(deadline, clock) + variable = abs(literal) + value = literal > 0 + prior = assignments.get(variable) + if prior is not None: + if prior != value: + return True + else: + assignments[variable] = value + + while True: + changed = False + for clause in clauses: + _check_proof_deadline(deadline, clock) + satisfied = False + unassigned: list[int] = [] + for index, item in enumerate(clause): + if index % 1024 == 0: + _check_proof_deadline(deadline, clock) + assigned = assignments.get(abs(item)) + if assigned is None: + unassigned.append(item) + elif assigned == (item > 0): + satisfied = True + break + if satisfied: + continue + if not unassigned: + return True + if len(unassigned) == 1: + candidate = unassigned[0] + variable = abs(candidate) + desired = candidate > 0 + existing = assignments.get(variable) + if existing is not None: + if existing != desired: + return True + else: + assignments[variable] = desired + changed = True + if not changed: + return False + + +def verify_rup( + cnf_path: Path, + proof_path: Path, + *, + timeout_seconds: float = RUP_REPLAY_TIMEOUT_SECONDS, + clock: Callable[[], float] = time.monotonic, +) -> tuple[int, int, int]: + if timeout_seconds <= 0: + raise Refusal("proof-budget", "RUP replay deadline is not positive") + deadline = clock() + timeout_seconds + clauses, variables = read_dimacs( + cnf_path, deadline=deadline, clock=clock + ) + original_clause_count = len(clauses) + steps = 0 + saw_empty = False + proof_text = proof_path.read_text(encoding="ascii") + _check_proof_deadline(deadline, clock) + for raw in proof_text.splitlines(): + _check_proof_deadline(deadline, clock) + line = raw.strip() + if not line or line.startswith("c"): + continue + if line.startswith("d "): + raise Refusal("proof-feature-unsupported", "deletion/RAT not admitted") + try: + values = [int(value) for value in line.split()] + except ValueError: + raise Refusal("proof-invalid", proof_path.name) from None + if not values or values[-1] != 0 or 0 in values[:-1]: + raise Refusal("proof-invalid", proof_path.name) + clause = tuple(values[:-1]) + if len(set(clause)) != len(clause) or any(-value in clause for value in clause): + raise Refusal("proof-invalid", "duplicate/tautological proof clause") + if not unit_conflict( + clauses, + [-value for value in clause], + deadline=deadline, + clock=clock, + ): + raise Refusal("rup-replay-failed", f"{proof_path.name} step {steps}") + clauses.append(clause) + steps += 1 + if not clause: + saw_empty = True + if steps > MAX_PROOF_STEPS: + raise Refusal("proof-budget", proof_path.name) + if saw_empty: + break + if not saw_empty: + raise Refusal("proof-no-empty-clause", proof_path.name) + return steps, variables, original_clause_count + + +def verify_smt_unsat(path: Path) -> None: + try: + z3_path = APPROVED_Z3_PATH.resolve(strict=True) + except OSError: + raise Refusal("z3-unavailable") from None + if not z3_path.is_file(): + raise Refusal("z3-unavailable") + before = sha_file(z3_path) + if before != APPROVED_Z3_SHA256: + raise Refusal("z3-identity-mismatch") + try: + completed = subprocess.run( + [str(z3_path), "-smt2", str(path)], + capture_output=True, + text=True, + timeout=30, + ) + except (OSError, subprocess.TimeoutExpired) as exc: + raise Refusal("z3-replay-failed", str(exc)) from None + if sha_file(z3_path) != before: + raise Refusal("z3-toctou") + lines = [line.strip() for line in completed.stdout.splitlines() if line.strip()] + clean_unsat = completed.returncode == 0 and lines == ["unsat"] and not completed.stderr + anubis_unsat_with_model_query = ( + completed.returncode == 1 + and len(lines) == 2 + and lines[0] == "unsat" + and Z3_UNSAT_MODEL_ERROR.fullmatch(lines[1]) is not None + and not completed.stderr + ) + if not (clean_unsat or anubis_unsat_with_model_query): + raise Refusal("smt-not-unsat", (completed.stdout + completed.stderr)[:200]) + + +def verify_artifacts(root: Path, program: dict[str, Any]) -> dict[str, Any]: + artifacts = require_keys( + program["artifacts"], + {"hir", "mir", "taint", "solver", "monomorphization", "native"}, + "artifacts", + ) + loaded: dict[str, Any] = {} + for label in ("hir", "mir", "taint", "solver", "monomorphization"): + row = require_keys(artifacts[label], {"path", "sha256", "bytes"}, label) + relative = safe_relative(row["path"]) + path = root / relative + if not path.is_file(): + raise Refusal("artifact-missing", relative) + if sha_file(path) != require_hex(row["sha256"], f"{label}.sha256"): + raise Refusal("artifact-hash-mismatch", label) + if path.stat().st_size != row["bytes"]: + raise Refusal("artifact-size-mismatch", label) + loaded[label] = load_json(path) + native = require_keys(artifacts["native"], {"path", "sha256"}, "native") + if native["path"] != "artifact": + raise Refusal("artifact-path", "inventory-safe-v1 requires artifact") + native_path = root / "artifact" + native_sha256 = require_hex(native["sha256"], "native.sha256") + if not native_path.is_file() or sha_file(native_path) != native_sha256: + raise Refusal("artifact-hash-mismatch", "native") + return loaded + + +def verify_stages(program: dict[str, Any]) -> None: + stages = program["stages"] + if not isinstance(stages, list): + raise Refusal("stage-schema") + seen: list[str] = [] + for row in stages: + item = require_keys(row, {"id", "status", "authority"}, "stage") + if item["id"] in seen: + raise Refusal("stage-duplicate", item["id"]) + seen.append(item["id"]) + if item["status"] != "PASS": + raise Refusal("stage-not-pass", f"{item['id']}={item['status']}") + if seen != REQUIRED_STAGES: + raise Refusal("stage-roster", f"got={seen}") + + +def function_inventory_from_hir(hir: Any) -> tuple[list[dict[str, Any]], list[str]]: + if ( + not isinstance(hir, dict) + or set(hir) != {"functions", "imports", "modules"} + or not isinstance(hir["functions"], list) + or not isinstance(hir["imports"], list) + or not isinstance(hir["modules"], list) + ): + raise Refusal("hir-schema") + rows = [] + ids = [] + for function in hir["functions"]: + if not isinstance(function, dict): + raise Refusal("hir-schema", "function") + identifier = sha(canonical(function)) + row = { + "id": identifier, + "name": function.get("name", ""), + "module": function.get("module"), + "mode": function.get("mode", ""), + "effects": function.get("effects", []), + "param_count": len(function.get("params", [])), + "symbol_count": len(function.get("symbols", [])), + } + rows.append(row) + ids.append(identifier) + if len(ids) != len(set(ids)): + raise Refusal("function-duplicate") + return rows, ids + + +def verify_policy( + root: Path, program: dict[str, Any], loaded: dict[str, Any] +) -> tuple[int, int]: + policy = require_keys( + program["policy_inventory"], + { + "functions", + "consumers", + "capabilities_present_count", + "taint_trace_count", + "monomorphization_count", + "mir_function_count", + }, + "policy_inventory", + ) + expected_functions, function_ids = function_inventory_from_hir(loaded["hir"]) + if policy["functions"] != expected_functions: + raise Refusal("policy-function-mismatch") + if any(row["mode"] != "safe" for row in expected_functions): + raise Refusal("policy-function-mode") + consumers = policy["consumers"] + if not isinstance(consumers, list): + raise Refusal("policy-consumer-schema") + roster = [row.get("id") for row in consumers if isinstance(row, dict)] + if roster != REQUIRED_CONSUMERS: + raise Refusal("policy-consumer-roster", str(roster)) + for row in consumers: + item = require_keys(row, {"id", "status", "authority", "subjects"}, "consumer") + if item["status"] != "PASS": + raise Refusal("policy-consumer-not-pass", item["id"]) + if item["id"] in {"effects", "capability", "information-flow", "mode"}: + if item["subjects"] != function_ids: + raise Refusal("policy-subject-mismatch", item["id"]) + declassifications = 0 + if (root / "declassify_audit.json").is_file(): + audit = load_json(root / "declassify_audit.json") + if not isinstance(audit, dict) or not isinstance(audit.get("declassifications"), list): + raise Refusal("policy-artifact-schema", "declassification") + declassifications = len(audit["declassifications"]) + declass_row = next(row for row in consumers if row["id"] == "declassification") + if declass_row["subjects"] != {"count": declassifications}: + raise Refusal("policy-count-mismatch", "declassification") + capabilities = 0 + if (root / "confinement_manifest.json").is_file(): + confinement = load_json(root / "confinement_manifest.json") + if not isinstance(confinement, dict) or not isinstance( + confinement.get("capabilities_present"), list + ): + raise Refusal("policy-artifact-schema", "capability") + capabilities = len(confinement["capabilities_present"]) + if policy["capabilities_present_count"] != capabilities: + raise Refusal("policy-count-mismatch", "capability") + if policy["taint_trace_count"] != len(loaded["taint"]): + raise Refusal("policy-count-mismatch", "taint") + if policy["monomorphization_count"] != len(loaded["monomorphization"]): + raise Refusal("policy-count-mismatch", "monomorphization") + if policy["mir_function_count"] != len(loaded["mir"]): + raise Refusal("policy-count-mismatch", "mir") + return len(expected_functions), len(consumers) + + +def verify_solver(root: Path, program: dict[str, Any], solver: Any) -> tuple[int, int]: + inventory = require_keys(program["solver_inventory"], {"count", "obligations"}, "solver_inventory") + obligations = inventory["obligations"] + if not isinstance(obligations, list) or not isinstance(solver, list): + raise Refusal("solver-schema") + if inventory["count"] != len(obligations) or len(obligations) != len(solver): + raise Refusal("solver-count-mismatch") + if not obligations: + raise Refusal("zero-obligations", f"{SUPPORTED_PROFILE} requires at least one") + proof_index = load_json(root / "analysis/proofs.json") + if not isinstance(proof_index, dict) or set(proof_index) != {"note", "obligations"}: + raise Refusal("proof-index-schema") + proof_rows = proof_index["obligations"] + if not isinstance(proof_rows, list) or len(proof_rows) != len(obligations): + raise Refusal("proof-count-mismatch") + verified = 0 + total_steps = 0 + ids: set[str] = set() + used_paths: set[str] = set() + used_proof_tuples: set[tuple[str, str, str]] = set() + for index, obligation in enumerate(obligations): + row = require_keys( + obligation, + { + "id", + "name", + "status", + "proof_kind", + "smt_path", + "smt_sha256", + "cnf_path", + "cnf_sha256", + "proof_path", + "proof_sha256", + "num_vars", + "num_clauses", + "steps", + "checker", + "checker_version", + }, + "obligation", + ) + proof = proof_rows[index] + solver_row = solver[index] + require_keys( + proof, + { + "obligation", + "status", + "proof", + "smt", + "cnf_dimacs", + "proof_drat", + "num_vars", + "num_clauses", + "steps", + "checker", + "checker_version", + "replay", + }, + "proof index row", + ) + require_keys(solver_row, {"detail", "model", "name", "smt", "status"}, "solver row") + if row["name"] != proof.get("obligation") or row["name"] != solver_row.get("name"): + raise Refusal("obligation-name-mismatch", str(index)) + if row["status"] != "PASS" or proof.get("status") != "PASS" or solver_row.get("status") != "PASS": + raise Refusal("obligation-not-pass", row["name"]) + if row["proof_kind"] != "rup_refutation" or proof.get("proof") != "rup_refutation": + raise Refusal("proof-feature-unsupported", row["name"]) + for label, program_path, program_hash, proof_key in ( + ("smt", row["smt_path"], row["smt_sha256"], "smt"), + ("cnf", row["cnf_path"], row["cnf_sha256"], "cnf_dimacs"), + ("proof", row["proof_path"], row["proof_sha256"], "proof_drat"), + ): + if program_path != proof.get(proof_key): + raise Refusal("proof-path-mismatch", label) + relative = safe_relative(program_path) + if relative in used_paths: + raise Refusal("proof-path-reuse", relative) + used_paths.add(relative) + actual = sha_file(root / relative) + if actual != require_hex(program_hash, f"{label}.sha256"): + raise Refusal("proof-hash-mismatch", label) + stable = { + "name": row["name"], + "smt_sha256": row["smt_sha256"], + "cnf_sha256": row["cnf_sha256"], + "proof_sha256": row["proof_sha256"], + } + expected_id = sha(canonical(stable)) + if row["id"] != expected_id or row["id"] in ids: + raise Refusal("obligation-id-mismatch", row["name"]) + ids.add(row["id"]) + if (root / row["smt_path"]).read_text(encoding="utf-8") != solver_row["smt"]: + raise Refusal("solver-smt-mismatch", row["name"]) + proof_tuple = (row["smt_sha256"], row["cnf_sha256"], row["proof_sha256"]) + if proof_tuple in used_proof_tuples: + raise Refusal("proof-reuse", row["name"]) + used_proof_tuples.add(proof_tuple) + verify_smt_unsat(root / row["smt_path"]) + steps, variables, clauses = verify_rup( + root / row["cnf_path"], root / row["proof_path"] + ) + if ( + row["num_vars"] != variables + or proof["num_vars"] != variables + or row["num_clauses"] != clauses + or proof["num_clauses"] != clauses + or row["steps"] != steps + or proof["steps"] != steps + or row["checker"] != proof["checker"] + or row["checker_version"] != proof["checker_version"] + ): + raise Refusal("proof-counter-mismatch", row["name"]) + total_steps += steps + verified += 1 + contracts = next(row for row in program["policy_inventory"]["consumers"] if row["id"] == "contracts") + if contracts["subjects"] != {"solver_obligation_count": verified}: + raise Refusal("policy-count-mismatch", "contracts") + return verified, total_steps + + +def verify_producer_evidence( + root: Path, + program: dict[str, Any], + expected_source: str, + expected_artifact: str, + proof_count: int, +) -> None: + evidence_path = root / "evidence.json" + pca_path = root / "pca.json" + if not evidence_path.is_file() or not pca_path.is_file(): + raise Refusal("producer-evidence-missing") + evidence = require_keys( + load_json(evidence_path), + { + "timestamp", + "tool", + "mode", + "source_hash", + "build_log_hash", + "artifact_hash", + "lane", + "environment_hash", + "source_tree_hash", + "sarif_hash", + "bounty_report_hash", + "manifest_sha256", + "checks", + "verdict", + "security", + }, + "evidence.json", + ) + security = evidence["security"] + if ( + evidence["mode"] != "safe" + or evidence["lane"] not in {None, "safe"} + or evidence["verdict"] != "PASS" + or evidence["tool"] != program["compiler"]["tool"] + or evidence["source_hash"] != program["source"]["merkle"] + or evidence["artifact_hash"] != expected_artifact + or not isinstance(security, dict) + or security.get("mode") != "safe" + ): + raise Refusal("producer-evidence-mismatch", "manifest summary") + summary_files = { + "build_log_hash": "build.log", + "environment_hash": "environment.json", + "source_tree_hash": "source-tree.json", + "sarif_hash": "checks.sarif", + "bounty_report_hash": "bounty-report.md", + } + for field, relative in summary_files.items(): + if evidence[field] != sha_file(root / relative): + raise Refusal("producer-evidence-mismatch", field) + expected_manifest_summary = sha( + ( + f"{evidence['source_hash']}:{evidence['build_log_hash']}:" + f"{evidence['source_tree_hash']}:PASS" + ).encode("utf-8") + ) + if evidence["manifest_sha256"] != expected_manifest_summary: + raise Refusal("producer-evidence-mismatch", "manifest_sha256") + if (root / "manifest.json").read_bytes() != evidence_path.read_bytes(): + raise Refusal("producer-evidence-mismatch", "manifest/evidence divergence") + checks = evidence["checks"] + if not isinstance(checks, list): + raise Refusal("producer-evidence-schema", "checks") + seen: set[str] = set() + details: dict[str, Any] = {} + required = { + "parse", + "typecheck", + "monomorphization", + "symbolic", + "solver", + "source_hash", + "build_log_hash", + "artifact", + "artifact_hash", + } + for row in checks: + item = require_keys(row, {"name", "status", "detail"}, "evidence check") + if item["name"] in seen: + raise Refusal("producer-evidence-mismatch", "duplicate check") + seen.add(item["name"]) + if item["status"] != "PASS": + raise Refusal("producer-evidence-mismatch", f"{item['name']} not PASS") + details[item["name"]] = item["detail"] + expected_details = { + "source_hash": evidence["source_hash"], + "build_log_hash": evidence["build_log_hash"], + "artifact_hash": expected_artifact, + } + for name, expected_detail in expected_details.items(): + if details.get(name) != expected_detail: + raise Refusal("producer-evidence-mismatch", f"{name} detail") + if not required <= seen: + raise Refusal("producer-evidence-mismatch", f"missing checks {sorted(required - seen)}") + + pca = require_keys( + load_json(pca_path), + { + "pca_version", + "source_sha256", + "mode", + "tier", + "rejection", + "parse_ok", + "typecheck_ok", + "solver_obligations", + "solver_all_discharged", + "solver_backend", + "zk_present", + "zk_image_id", + "zk_receipt_sha256", + "zk_journal_sha256", + "verdict", + "tool", + }, + "pca.json", + ) + if ( + pca["pca_version"] != 2 + or pca["source_sha256"] != expected_source + or pca["mode"] != "safe" + or pca["tier"] != "checked" + or pca["rejection"] is not None + or pca["parse_ok"] is not True + or pca["typecheck_ok"] is not True + or pca["solver_obligations"] != proof_count + or pca["solver_all_discharged"] is not True + or pca["verdict"] != "PASS" + or pca["tool"] != program["compiler"]["tool"] + ): + raise Refusal("producer-evidence-mismatch", "PCA summary") + + +def build_receipt(args: argparse.Namespace) -> dict[str, Any]: + source_path = Path(args.source) + if source_path.is_symlink() or not source_path.is_file(): + raise Refusal("input-path", "source must be a regular non-symlink file") + source = source_path.read_bytes() + expected_source = require_hex(args.expected_source_sha256, "expected-source") + if sha(source) != expected_source: + raise Refusal("source-pin-mismatch") + expected_compiler = require_hex(args.expected_compiler_sha256, "expected-compiler") + expected_policy = require_hex(args.expected_policy_sha256, "expected-policy") + policy_body, policy_digest, policy_file_sha256 = load_program_policy() + if expected_policy != policy_digest: + raise Refusal("policy-pin-mismatch") + if ( + not isinstance(args.verification_time_unix, str) + or not args.verification_time_unix.isdigit() + ): + raise Refusal("verification-time-invalid") + if args.profile != SUPPORTED_PROFILE: + raise Refusal("profile-unsupported", args.profile) + + provided_root = Path(args.evidence_dir) + if provided_root.is_symlink(): + raise Refusal("input-path", "evidence root symlink") + original_root = provided_root.resolve(strict=True) + original_files = regular_tree(original_root) + if "program-evidence.json" not in original_files: + raise Refusal( + "unsupported-program-evidence-version", + "anubis.program-evidence.v3 is required; PCA v2 is partial", + ) + manifest_rows, original_manifest_sha = verify_manifest(original_root, original_files) + verify_file_roster(original_files) + snapshot_holder, root, files, manifest_sha = freeze_evidence_tree( + original_root, original_files, manifest_rows + ) + try: + if manifest_sha != original_manifest_sha: + raise Refusal("snapshot-drift", "manifest") + program_path = files.get("program-evidence.json") + if program_path is None: + raise Refusal( + "unsupported-program-evidence-version", + "anubis.program-evidence.v3 is required; PCA v2 is partial", + ) + program = require_keys(load_json(program_path), PROGRAM_KEYS, "program-evidence") + if program["schema"] != "anubis.program-evidence.v3" or program["version"] != 3: + raise Refusal("unsupported-program-evidence-version") + if program["mode"] != "safe": + raise Refusal("mode-unsupported", str(program["mode"])) + source_row = require_keys(program["source"], {"path", "sha256", "merkle", "bytes"}, "source") + sealed_source = root / safe_relative(source_row["path"]) + if sealed_source.read_bytes() != source: + raise Refusal("source-byte-mismatch") + if source_row["sha256"] != expected_source or source_row["bytes"] != len(source): + raise Refusal("source-inventory-mismatch") + if source_row["merkle"] != expected_source: + raise Refusal( + "multi-source-unsupported", + f"{SUPPORTED_PROFILE} requires one exact source leaf", + ) + compiler = require_keys(program["compiler"], {"tool", "path_basename", "sha256"}, "compiler") + if compiler["sha256"] != expected_compiler: + raise Refusal("compiler-pin-mismatch") + + verify_stages(program) + loaded = verify_artifacts(root, program) + native = program["artifacts"]["native"] + expected_artifact = args.expected_artifact_sha256 + if expected_artifact is None: + raise Refusal("artifact-pin-required", SUPPORTED_PROFILE) + expected_artifact = require_hex(expected_artifact, "expected-artifact") + if native["sha256"] != expected_artifact: + raise Refusal("artifact-pin-mismatch") + function_count, consumer_count = verify_policy(root, program, loaded) + proof_count, proof_steps = verify_solver(root, program, loaded["solver"]) + verify_producer_evidence(root, program, expected_source, expected_artifact, proof_count) + producer_residuals = program["residual_non_claims"] + if producer_residuals != PRODUCER_RESIDUALS: + raise Refusal("residual-roster", str(producer_residuals)) + + receipt: dict[str, Any] = { + "schema": "jackal-anubis-program-receipt-v1", + "status": "verified-program-evidence", + "profile": args.profile, + "source": {"sha256": expected_source, "bytes": len(source)}, + "compiler": { + "sha256": expected_compiler, + "tool": compiler["tool"], + "path_basename": compiler["path_basename"], + }, + "artifact": {"sha256": expected_artifact}, + "evidence_manifest_sha256": manifest_sha, + "program_evidence_sha256": sha_file(program_path), + "stage_count": len(REQUIRED_STAGES), + "policy": { + "profile": policy_body["profile"], + "policy_digest_sha256": policy_digest, + "policy_file_sha256": policy_file_sha256, + "inventory_authority": policy_body["policy_inventory_authority"], + "independent_construct_totality": policy_body[ + "independent_policy_construct_totality" + ], + "function_count": function_count, + "consumer_count": consumer_count, + }, + "proof_replay": { + "kind": "approved-z3-plus-independent-rup", + "verified": proof_count, + "smt_verified": proof_count, + "steps": proof_steps, + }, + "assurance": { + "source_binding": "verified", + "evidence_closure": "verified", + "proof_replay": "independently-recomputed", + "smt_replay": "approved-z3-unsat", + "smt_to_cnf": "open", + "policy_semantics": "producer-attested-inventory-checked", + "policy_construct_totality": "not-established", + "source_to_vc": "open", + "source_native_refinement": "open", + "runtime": "not-observed", + }, + "residual_non_claims": RECEIPT_RESIDUALS, + "nonce": args.nonce, + "policy_sha256": expected_policy, + "verification_time_unix": args.verification_time_unix, + } + receipt["receipt_digest_sha256"] = sha(canonical(receipt)) + post_files = regular_tree(original_root) + verify_file_roster(post_files) + _, post_manifest_sha = verify_manifest(original_root, post_files) + if post_manifest_sha != original_manifest_sha: + raise Refusal("snapshot-drift", "source evidence changed during verification") + return receipt + finally: + snapshot_holder.cleanup() + + +def write_new(path: Path, document: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + try: + with path.open("x", encoding="utf-8") as handle: + json.dump(document, handle, indent=2, ensure_ascii=False, sort_keys=True) + handle.write("\n") + except FileExistsError: + raise Refusal("output-exists", str(path)) from None + + +def verify(args: argparse.Namespace) -> dict[str, Any]: + receipt = build_receipt(args) + if args.emit_receipt: + write_new(Path(args.emit_receipt), receipt) + return receipt + + +def verify_receipt(args: argparse.Namespace) -> dict[str, Any]: + supplied = load_json(Path(args.receipt)) + if not isinstance(supplied, dict) or "receipt_digest_sha256" not in supplied: + raise Refusal("receipt-schema") + without_digest = { + key: value for key, value in supplied.items() if key != "receipt_digest_sha256" + } + if supplied["receipt_digest_sha256"] != sha(canonical(without_digest)): + raise Refusal("receipt-digest-mismatch") + replay_args = argparse.Namespace(**vars(args)) + replay_args.emit_receipt = None + expected = build_receipt(replay_args) + if supplied != expected: + raise Refusal("receipt-semantic-mismatch") + if args.emit_receipt: + write_new(Path(args.emit_receipt), expected) + return expected + + +def check_program(args: argparse.Namespace) -> dict[str, Any]: + source = Path(args.source) + compiler = Path(args.anubis_bin) + if source.is_symlink() or not source.is_file(): + raise Refusal("input-path", "source") + expected_source = require_hex(args.expected_source_sha256, "expected-source") + expected_compiler = require_hex(args.expected_compiler_sha256, "expected-compiler") + expected_policy = require_hex(args.expected_policy_sha256, "expected-policy") + if expected_policy != PROGRAM_POLICY_SHA256: + raise Refusal("policy-pin-mismatch") + if not isinstance(args.verification_time_unix, str) or not args.verification_time_unix.isdigit(): + raise Refusal("verification-time-invalid") + if sha_file(source) != expected_source: + raise Refusal("source-pin-mismatch") + if expected_compiler != APPROVED_CHECK_COMPILER_SHA256: + raise Refusal("compiler-not-approved", expected_compiler) + out_root = Path(args.out_root) + if out_root.exists() or out_root.is_symlink(): + raise Refusal("output-exists", str(out_root)) + out_root.parent.mkdir(parents=True, exist_ok=True) + try: + with pinned_executable_snapshot(compiler, expected_compiler) as snapshot: + command = [ + str(snapshot), + "build", + str(source), + "--out", + str(out_root), + "--evidence", + ] + completed = subprocess.run(command, capture_output=True, timeout=900) + except (OSError, subprocess.TimeoutExpired) as exc: + raise Refusal("producer-failed", str(exc)) from None + if completed.returncode != 0: + detail = (completed.stdout + completed.stderr).decode("utf-8", "replace")[-2000:] + raise Refusal("producer-rejected", detail) + evidence_dirs = sorted( + path + for path in out_root.iterdir() + if path.is_dir() and path.name.startswith("evidence-") + ) + if len(evidence_dirs) != 1: + raise Refusal("producer-output", f"evidence_dirs={len(evidence_dirs)}") + artifact = evidence_dirs[0] / "artifact" + if not artifact.is_file(): + raise Refusal("producer-output", "missing sealed artifact") + replay_args = argparse.Namespace( + source=str(source), + evidence_dir=str(evidence_dirs[0]), + expected_source_sha256=expected_source, + expected_compiler_sha256=expected_compiler, + expected_artifact_sha256=sha_file(artifact), + expected_policy_sha256=args.expected_policy_sha256, + verification_time_unix=args.verification_time_unix, + profile=args.profile, + nonce=args.nonce, + emit_receipt=args.emit_receipt, + ) + return verify(replay_args) + + +def add_verify_arguments(command: argparse.ArgumentParser) -> None: + command.add_argument("--source", required=True) + command.add_argument("--evidence-dir", required=True) + command.add_argument("--expected-source-sha256", required=True) + command.add_argument("--expected-compiler-sha256", required=True) + command.add_argument("--expected-artifact-sha256") + command.add_argument("--expected-policy-sha256", required=True) + command.add_argument("--verification-time-unix", required=True) + command.add_argument("--profile", required=True) + command.add_argument("--nonce", required=True) + command.add_argument("--emit-receipt") + + +def parser() -> argparse.ArgumentParser: + top = argparse.ArgumentParser() + sub = top.add_subparsers(dest="command", required=True) + verify_parser = sub.add_parser("verify") + add_verify_arguments(verify_parser) + receipt_parser = sub.add_parser("verify-receipt") + receipt_parser.add_argument("--receipt", required=True) + add_verify_arguments(receipt_parser) + check_parser = sub.add_parser("check") + check_parser.add_argument("--source", required=True) + check_parser.add_argument("--anubis-bin", required=True) + check_parser.add_argument("--expected-source-sha256", required=True) + check_parser.add_argument("--expected-compiler-sha256", required=True) + check_parser.add_argument("--expected-policy-sha256", required=True) + check_parser.add_argument("--verification-time-unix", required=True) + check_parser.add_argument("--profile", required=True) + check_parser.add_argument("--nonce", required=True) + check_parser.add_argument("--out-root", required=True) + check_parser.add_argument("--emit-receipt", required=True) + return top + + +def main() -> int: + args = parser().parse_args() + try: + if args.command == "verify": + result = verify(args) + status = "verified-program-evidence" + elif args.command == "verify-receipt": + result = verify_receipt(args) + status = "verified-program-receipt" + else: + result = check_program(args) + status = "verified-program-evidence" + except Refusal as exc: + detail = exc.detail.replace('"', "'") + print(f'status=refused reason={exc.reason} detail="{detail}"') + return 1 + except (OSError, UnicodeError, ValueError, TypeError, KeyError) as exc: + detail = str(exc).replace('"', "'")[:300] + print(f'status=refused reason=verifier-internal detail="{detail}"') + return 1 + print("status=" + status) + print("receipt_digest_sha256=" + result["receipt_digest_sha256"]) + print("proofs_verified=" + str(result["proof_replay"]["verified"])) + print("source_sha256=" + result["source"]["sha256"]) + print("artifact_sha256=" + result["artifact"]["sha256"]) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/capability_drift_gate.py b/tools/capability_drift_gate.py new file mode 100644 index 0000000..70eeb54 --- /dev/null +++ b/tools/capability_drift_gate.py @@ -0,0 +1,441 @@ +#!/usr/bin/env python3 +"""Fail closed on JACKAL capability, package-pin, documentation, or skill drift.""" + +from __future__ import annotations + +import argparse +import ast +import hashlib +import importlib.util +import json +import os +import re +import sys +import tempfile +from pathlib import Path +from typing import Any, NoReturn + +INVENTORY_PATH = Path("release/capability_inventory_v1.json") +PLUGIN_MANIFEST_PATH = Path("plugins/jackel/.codex-plugin/plugin.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") +SKILL_PATH = Path("plugins/jackel/skills/jackel/SKILL.md") +DESIGN_PATH = Path("docs/superpowers/specs/2026-08-17-jackel-codex-plugin-design.md") +CURRENT_SURFACES = ( + Path("README.md"), + Path("GETTING-STARTED.md"), + Path("PROVENANCE.md"), + DESIGN_PATH, + SKILL_PATH, +) +CODEX_PLUGIN_IDENTITY_FILES = ( + ".codex-plugin/plugin.json", + ".mcp.json", + "README.md", + "mcp/server.py", + "scripts/launch_mcp.zsh", + "scripts/provision_runtime.py", + "scripts/verify_plugin.py", + "skills/jackel/SKILL.md", +) +CODEX_PLUGIN_ROOT = Path("plugins/jackel") +CODEX_PLUGIN_IDENTITY_PATH = CODEX_PLUGIN_ROOT / "PLUGIN_IDENTITY.sha256" + +CURRENT_SURFACE_BEGIN = "" +CURRENT_SURFACE_END = "" +TOOL_REFERENCE = re.compile(r"`(jackal_[a-z0-9_]+)`") +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", +) +FORBIDDEN_PROMOTIONAL_CLAIMS = ( + "statuses pass through verbatim", + "statuses pass through unchanged and never inflate", + "status inflation is impossible", +) +STALE_CURRENT_DESIGN_CLAIMS = ( + "revision declares 34 tools", + "runtime's 34-tool `plugin/hermes/tools.json` inventory", + "runtime is the separately sealed JACKAL v1.7.0 macOS release package", + "release epoch: `v1.7.0`", + "asset: `jackal-v1.7.0-macos-arm64.tar.gz`", + "releases/download/v1.7.0/jackal-v1.7.0-macos-arm64.tar.gz", + "Application Support/JACKAL/runtimes/v1.7.0/", + "identify epoch v1.7.0", + "Verify the fixed v1.7.0 URL, epoch, filename", + "Using the pinned v1.7.0 runtime", + "pinned v1.7.0 runtime bytes", +) + + +class DriftError(RuntimeError): + def __init__(self, reason: str, detail: str): + self.reason = reason + self.detail = detail + super().__init__(f"reason={reason} detail={detail}") + + +def refuse(reason: str, detail: str) -> NoReturn: + raise DriftError(reason, detail) + + +def _read_text(path: Path) -> str: + if not path.is_file(): + refuse("missing-surface", f"required regular file is absent: {path}") + try: + return path.read_text(encoding="utf-8") + except UnicodeDecodeError as error: + refuse("surface-encoding", f"{path} is not UTF-8: {error}") + + +def render_codex_plugin_identity(root: Path | str) -> bytes: + root_path = Path(root).resolve() + plugin_root = root_path / CODEX_PLUGIN_ROOT + lines: list[str] = [] + for relative in CODEX_PLUGIN_IDENTITY_FILES: + path = plugin_root / relative + if not path.is_file() or path.is_symlink(): + refuse("plugin-identity-input", f"not a regular non-symlink file: {path}") + digest = hashlib.sha256(path.read_bytes()).hexdigest() + lines.append(f"{digest} {relative}\n") + return "".join(lines).encode("utf-8") + + +def check_codex_plugin_identity(root: Path | str) -> None: + root_path = Path(root).resolve() + path = root_path / CODEX_PLUGIN_IDENTITY_PATH + if not path.is_file(): + refuse("plugin-identity-drift", f"identity manifest is absent: {path}") + expected = render_codex_plugin_identity(root_path) + actual = path.read_bytes() + if actual != expected: + refuse( + "plugin-identity-drift", + f"{CODEX_PLUGIN_IDENTITY_PATH} actual={hashlib.sha256(actual).hexdigest()} " + f"generated={hashlib.sha256(expected).hexdigest()}", + ) + + +def _write_atomic(path: Path, data: bytes) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + descriptor, temporary_name = tempfile.mkstemp( + prefix=f".{path.name}.", dir=path.parent + ) + temporary = Path(temporary_name) + try: + with os.fdopen(descriptor, "wb") as handle: + handle.write(data) + handle.flush() + os.fsync(handle.fileno()) + os.chmod(temporary, 0o644) + os.replace(temporary, path) + finally: + if temporary.exists(): + temporary.unlink() + + +def _load_json(path: Path) -> dict[str, Any]: + def reject_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in pairs: + if key in result: + refuse("duplicate-json-key", f"{path} repeats key {key!r}") + result[key] = value + return result + + try: + value = json.loads(_read_text(path), object_pairs_hook=reject_duplicates) + except json.JSONDecodeError as error: + refuse("invalid-json", f"{path}: {error}") + if not isinstance(value, dict): + refuse("invalid-json", f"{path} top level is not an object") + return value + + +def _load_inventory_module(root: Path): + path = root / "tools/capability_inventory.py" + spec = importlib.util.spec_from_file_location("jackal_capability_inventory", path) + if spec is None or spec.loader is None: + refuse("inventory-generator", f"cannot load {path}") + module = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(module) + except Exception as error: + refuse("inventory-generator", f"cannot execute {path}: {error}") + return module + + +def skill_tool_names(markdown: str) -> set[str]: + return set(TOOL_REFERENCE.findall(markdown)) + + +def _python_constants(path: Path, required: set[str]) -> dict[str, object]: + try: + tree = ast.parse(_read_text(path), filename=str(path)) + except SyntaxError as error: + refuse("python-parse", f"{path}: {error}") + values: dict[str, object] = {} + 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] + value_node = node.value + 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}") + try: + values[target.id] = ast.literal_eval(value_node) + except (ValueError, TypeError) as error: + refuse("python-constant", f"{path} {target.id} is not literal: {error}") + missing = sorted(required - set(values)) + if missing: + refuse("python-constant", f"{path} lacks constants {missing}") + return values + + +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: + refuse("current-surface-marker", f"{path} must contain one current-surface block") + start = text.index(CURRENT_SURFACE_BEGIN) + len(CURRENT_SURFACE_BEGIN) + end = text.index(CURRENT_SURFACE_END) + if end <= start: + refuse("current-surface-marker", f"{path} current-surface markers are reversed") + block = text[start:end].strip() + if not block: + refuse("current-surface-marker", f"{path} current-surface block is empty") + return block + + +def _verify_package_pin(root: Path, version: str) -> dict[str, object]: + names = { + "EPOCH", + "ASSET", + "URL", + "PACKAGE_SIZE", + "PACKAGE_SHA256", + "SHA256SUMS_SHA256", + } + constants = _python_constants(root / PROVISIONER_PATH, names) + evidence = _load_json(root / PACKAGE_EVIDENCE_PATH) + package = evidence.get("package") + if not isinstance(package, dict): + refuse("package-pin-mismatch", "package alignment receipt lacks package object") + expected = { + "EPOCH": version, + "ASSET": package.get("basename"), + "URL": ( + "https://github.com/AnubisQuantumCipher/jackal/releases/download/" + f"{version}/{package.get('basename')}" + ), + "PACKAGE_SIZE": package.get("bytes"), + "PACKAGE_SHA256": package.get("sha256"), + "SHA256SUMS_SHA256": package.get("sha256sums_root"), + } + if evidence.get("release_candidate") != version: + refuse( + "package-pin-mismatch", + f"alignment release_candidate={evidence.get('release_candidate')!r} expected={version!r}", + ) + for name, expected_value in expected.items(): + if constants[name] != expected_value: + refuse( + "package-pin-mismatch", + f"{name} provisioner={constants[name]!r} evidence={expected_value!r}", + ) + for name in ("PACKAGE_SHA256", "SHA256SUMS_SHA256"): + if not isinstance(constants[name], str) or HEX64.fullmatch(constants[name]) is None: + refuse("package-pin-mismatch", f"{name} is not lowercase SHA-256") + return constants + + +def _verify_codex_adapter(root: Path, expected_count: int) -> int: + server_path = root / CODEX_SERVER_PATH + constants = _python_constants(server_path, {"EXPECTED_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}", + ) + 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) + + +def _verify_plugin_metadata( + root: Path, expected_count: int, status_vocabulary: set[str] +) -> str: + manifest = _load_json(root / PLUGIN_MANIFEST_PATH) + interface = manifest.get("interface") + if not isinstance(interface, dict): + refuse("plugin-metadata", "plugin interface is not an object") + 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: + refuse( + "current-tool-count", + f"plugin longDescription does not state {expected_count}-tool", + ) + if "v1.7.3 release runtime" not in description: + refuse("current-release-state", "plugin metadata does not identify release state") + 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() + for forbidden in FORBIDDEN_PROMOTIONAL_CLAIMS: + if forbidden in lowered: + refuse("promotional-metadata", f"plugin metadata contains {forbidden!r}") + missing_statuses = sorted( + status for status in status_vocabulary + if re.search(rf"(? None: + for path, text in texts: + for status in STATUS_ASSIGNMENT.findall(text): + if status not in allowed: + refuse("status-vocabulary", f"{path} uses unknown status {status!r}") + + +def _verify_current_surfaces(root: Path, expected_count: int) -> list[tuple[Path, str]]: + design = _read_text(root / DESIGN_PATH) + for stale in STALE_CURRENT_DESIGN_CLAIMS: + if stale in design: + reason = "current-tool-count" if "34 tool" in stale or "34-tool" in stale else "stale-current-pin" + refuse(reason, f"{DESIGN_PATH} contains stale current claim {stale!r}") + + blocks: list[tuple[Path, str]] = [] + for relative in CURRENT_SURFACES: + block = _current_surface_block(root / relative) + if f"{expected_count}-tool" not in block: + refuse( + "current-tool-count", + f"{relative} current block does not state {expected_count}-tool", + ) + if "v1.7.3 release" not in block: + refuse( + "current-release-state", + f"{relative} current block does not state v1.7.3 release", + ) + if "release/capability_inventory_v1.json" not in block: + refuse( + "current-inventory-link", + f"{relative} current block does not name the canonical inventory", + ) + blocks.append((relative, block)) + return blocks + + +def verify_surface(root: Path | str) -> dict[str, object]: + root_path = Path(root).resolve() + inventory_document = _load_json(root_path / INVENTORY_PATH) + expected_count = inventory_document.get("tool_count") + unique_count = inventory_document.get("unique_tool_count") + records = inventory_document.get("tools") + release = inventory_document.get("release") + vocabulary = inventory_document.get("status_vocabulary") + if ( + not isinstance(expected_count, int) + or isinstance(expected_count, bool) + or expected_count != 41 + or unique_count != expected_count + or not isinstance(records, list) + or len(records) != expected_count + or not isinstance(release, dict) + or release.get("state") != "v1.7.3" + or release.get("version") != "v1.7.3" + or not isinstance(vocabulary, list) + or not all(isinstance(value, str) for value in vocabulary) + ): + refuse("inventory-contract", "committed inventory summary is malformed or stale") + 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) + + package = _verify_package_pin(root_path, str(release["version"])) + codex_count = _verify_codex_adapter(root_path, expected_count) + blocks = _verify_current_surfaces(root_path, expected_count) + description = _verify_plugin_metadata(root_path, expected_count, status_vocabulary) + + skill_text = _read_text(root_path / SKILL_PATH) + unknown_skill_names = sorted(skill_tool_names(skill_text) - known_names) + if unknown_skill_names: + refuse("unknown-skill-tool", f"Codex skill references {unknown_skill_names}") + + _verify_status_assignments( + [*blocks, (PLUGIN_MANIFEST_PATH, description), (SKILL_PATH, skill_text)], + status_vocabulary, + ) + + inventory_module = _load_inventory_module(root_path) + try: + inventory_module.check_committed(root_path) + except Exception as error: + refuse("inventory-artifact-drift", str(error)) + check_codex_plugin_identity(root_path) + + return { + "tool_count": expected_count, + "unique_tool_count": unique_count, + "codex_tool_count": codex_count, + "package_epoch": package["EPOCH"], + "package_sha256": package["PACKAGE_SHA256"], + "skill_tool_references": len(skill_tool_names(skill_text)), + } + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--write-plugin-identity", + action="store_true", + help="regenerate the Codex wrapper identity from its fixed file roster", + ) + parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1]) + args = parser.parse_args(argv) + try: + if args.write_plugin_identity: + rendered = render_codex_plugin_identity(args.root) + _write_atomic(args.root.resolve() / CODEX_PLUGIN_IDENTITY_PATH, rendered) + print( + "CODEX_PLUGIN_IDENTITY_WRITTEN " + f"files={len(CODEX_PLUGIN_IDENTITY_FILES)} " + f"sha256={hashlib.sha256(rendered).hexdigest()}" + ) + return 0 + result = verify_surface(args.root) + except DriftError as error: + print( + f"CAPABILITY_DRIFT_REFUSED reason={error.reason} detail={error.detail}", + file=sys.stderr, + ) + return 1 + print( + "CAPABILITY_DRIFT_PASS " + f"tools={result['tool_count']} unique={result['unique_tool_count']} " + f"codex={result['codex_tool_count']} package={result['package_epoch']}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/capability_inventory.py b/tools/capability_inventory.py new file mode 100644 index 0000000..f2484bb --- /dev/null +++ b/tools/capability_inventory.py @@ -0,0 +1,821 @@ +#!/usr/bin/env python3 +"""Generate and verify JACKAL's canonical exported-capability inventory. + +The executable catalog remains ``plugin/hermes/tools.json``. This tool binds +that ordered roster to profile membership, semantic integration bytes, +release-manifest checker identities, proof-identity bytes, status vocabulary, +and explicit admission/refusal summaries. Package-delivery pins are verified by +``tools/capability_drift_gate.py`` instead: the package contains this inventory, +so binding either the package-pinning provisioner or the package receipt here +would create an unsealable content-hash cycle. It computes no mathematical +result and changes no verifier accept condition. + +Usage: + python3 tools/capability_inventory.py --write [--root PATH] + python3 tools/capability_inventory.py --check [--root PATH] +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import re +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Any, NoReturn + +CATALOG_PATH = Path("plugin/hermes/tools.json") +PROFILE_DIR = Path("plugin/hermes/profiles") +MANIFEST_PATH = Path("release/MANIFEST.sha256") +ARTIFACT_PATH = Path("release/capability_inventory_v1.json") +PROGRAM_FLOOR_PATH = Path("release/compat/v173_floor.json") +PROGRAM_POLICY_PATH = Path("release/program/inventory_safe_v1.json") +PROFILE_IDS = ("core", "formal", "full") +EXPECTED_VERSION = "v1.7.3" +EXPECTED_TOOL_COUNT = 41 +RELEASE_STATE = "v1.7.3" +CONTAINING_REF = { + # This is the immutable surface-origin commit, not the generator's HEAD. + # Embedding HEAD in an artifact committed at HEAD would be self-referential. + "kind": "surface-origin-commit", + "value": "d25bcd9818e0d106f337798f80527ae611cc3acc", +} + +ALLOWED_STATUSES = frozenset( + { + "ok", + "exact", + "structural-exact", + "formal-bounded", + "bounded", + "checked", + "estimated", + "model-based", + "verified", + "verified-program-evidence", + "verified-program-receipt", + "indeterminate", + "refused", + } +) + +INPUT_PATHS = ( + Path("tools/capability_inventory.py"), + CATALOG_PATH, + PROFILE_DIR / "core.json", + PROFILE_DIR / "formal.json", + PROFILE_DIR / "full.json", + Path("plugin/hermes/server.py"), + Path("plugins/jackel/.codex-plugin/plugin.json"), + Path("plugins/jackel/mcp/server.py"), + MANIFEST_PATH, + Path("release/evidence/range_proof_identity_v172.json"), + Path("release/evidence/gaussian_proof_identity.json"), + Path("release/evidence/int_cert_proof_identity_v172.json"), +) + +DEPENDENCY_GROUPS: dict[str, frozenset[str]] = { + "lean-range": frozenset( + { + "jackal_range_bound", + "jackal_sqrt_rat_bound", + "jackal_exp_rat_bound", + "jackal_ln_rat_bound", + "jackal_sin_rat_bound", + "jackal_cos_rat_bound", + "jackal_atan_rat_bound", + "jackal_tanh_rat_bound", + } + ), + "lean-gaussian": frozenset({"jackal_gaussian_integral"}), + "lean-int-cert": frozenset({"jackal_integrate_bound_cert"}), + "lean-receipt-registry": frozenset({"jackal_verify_receipt"}), + "exact-cert-verifier": frozenset( + { + "jackal_poly_canon", + "jackal_poly_eq", + "jackal_poly_gcd", + "jackal_ratfunc_canon", + "jackal_roots_isolate", + "jackal_xgcd", + "jackal_mod_pow", + "jackal_mod_inv", + "jackal_crt", + "jackal_prime_cert", + } + ), + "structural-checker": frozenset( + {"jackal_test_exists", "jackal_claim_cites_test"} + ), + "decision-checker": frozenset( + {"jackal_decision_rank", "jackal_decision_rank_v2"} + ), + "claim-router": frozenset({"jackal_claim"}), + "claim-verifier": frozenset({"jackal_verify_bundle"}), + "program-verifier": frozenset( + { + "jackal_anubis_check_program", + "jackal_anubis_verify_program", + "jackal_anubis_verify_program_receipt", + } + ), + "runtime-only": frozenset( + { + "jackal_exact", + "jackal_evaluate", + "jackal_diff", + "jackal_integrate", + "jackal_integrate_adaptive", + "jackal_integrate_bound", + "jackal_solve", + "jackal_canon", + "jackal_alg_sign", + "jackal_alg_cmp", + "jackal_divides", + } + ), +} + +DEPENDENCY_LABELS: dict[str, tuple[str, ...]] = { + "lean-range": ( + "evaluator", + "checker", + "range-proof-identity", + "range-proof-digest", + "coverage-inventory", + ), + "lean-gaussian": ( + "evaluator", + "gaussian-checker", + "gaussian-proof-identity", + "gaussian-proof-digest", + ), + "lean-int-cert": ( + "evaluator", + "int-cert-checker", + "int-cert-proof-identity", + "int-cert-proof-digest", + ), + "lean-receipt-registry": ( + "checker", + "range-proof-identity", + "range-proof-digest", + "archival-range-checker", + "archival-range-coverage-inventory", + "archival-range-proof-identity", + "archival-range-proof-digest", + "gaussian-checker", + "gaussian-proof-identity", + "gaussian-proof-digest", + "int-cert-checker", + "int-cert-proof-identity", + "int-cert-proof-digest", + ), + "exact-cert-verifier": ("evaluator", "exact_verifier"), + "structural-checker": ( + "domain_pack_registry", + "domain_pack_verifier", + "domain_pack_test_exists_checker", + ), + "decision-checker": ( + "domain_pack_registry", + "domain_pack_verifier", + "domain_pack_decision_checker", + "claim_unit_registry", + ), + "claim-router": ( + "claim_kernel", + "claim_router", + "evaluator", + "checker", + "range-proof-identity", + "range-proof-digest", + "archival-range-checker", + "archival-range-coverage-inventory", + "archival-range-proof-identity", + "archival-range-proof-digest", + "gaussian-checker", + "gaussian-proof-identity", + "gaussian-proof-digest", + "int-cert-checker", + "int-cert-proof-identity", + "int-cert-proof-digest", + "exact_verifier", + "claim_inference_registry", + "claim_unit_registry", + ), + "claim-verifier": ( + "claim_verifier", + "checker", + "range-proof-identity", + "range-proof-digest", + "archival-range-checker", + "archival-range-coverage-inventory", + "archival-range-proof-identity", + "archival-range-proof-digest", + "gaussian-checker", + "gaussian-proof-identity", + "gaussian-proof-digest", + "int-cert-checker", + "int-cert-proof-identity", + "int-cert-proof-digest", + "exact_verifier", + "claim_inference_registry", + "claim_unit_registry", + ), + "program-verifier": ( + "anubis_program_verifier", + "anubis_program_policy", + "program-compatibility-floor", + ), + "runtime-only": ("evaluator",), +} + +REFUSAL_BOUNDARIES = { + "lean-range": ( + "Only the catalog-declared expression and canonical-rational interval " + "fragment is admitted. Unsupported syntax, invalid intervals, missing or " + "mismatched pins, producer/checker rejection, or identity drift refuses; " + "there is no weaker-lane fallback." + ), + "lean-gaussian": ( + "Only the exact catalog-declared Gaussian form and canonical rational " + "bounds/tolerance are admitted. Any other form, failed enclosure, checker " + "rejection, or pin/identity mismatch refuses without downgrade." + ), + "lean-int-cert": ( + "Only the request-bound v1.7.2 composed-integral fragment and canonical " + "bounds/tolerance are admitted. Request-unbound v1.7.0 evidence, unsupported " + "syntax, failed subdivision, checker rejection, or identity mismatch refuses " + "without using the weaker float lane." + ), + "lean-receipt-registry": ( + "Only closed-registry range/rational, Gaussian, and current request-bound " + "int-cert receipts matching independent caller expectations are replayed. " + "Unknown epochs/variants, copied rather than caller-pinned expectations, " + "revoked int-cert evidence, or checker/pin mismatch refuses." + ), + "exact-cert-verifier": ( + "Only the catalog-declared exact fragment and budgets are admitted. Invalid " + "grammar, side conditions, limits, certificate mismatch, or independent " + "verifier rejection refuses; exact is not relabeled formal." + ), + "structural-checker": ( + "Only byte-exact source/citation structure described by the schema is " + "accepted after independent file-byte recomputation. Path traversal, malformed " + "symbols/hashes, missing text/declarations, or checker mismatch refuses; the " + "result never asserts test execution or correctness." + ), + "decision-checker": ( + "Only 2..6 schema-valid options under a caller-declared admissible numeric " + "criterion are ranked. Invalid shape/sense/unit, value-judgment criteria, " + "zero top margin, or checker mismatch refuses; caller values are not treated " + "as measurements or confidence intervals." + ), + "claim-router": ( + "Only jackal-claim-request-v1 and its closed step vocabulary are compiled. " + "Policy, identity, schema, route, or assurance failures refuse; fallback is " + "off by default and any caller-enabled fallback remains explicit in the route " + "trace rather than silently changing assurance." + ), + "claim-verifier": ( + "Only canonical bundles matching separately caller-pinned epoch, policy, " + "root proposition, time, and nonce are replayed. Semantic, graph, freshness, " + "evidence, checker, or pin ambiguity returns refused or indeterminate exactly " + "as declared; it is never converted to success." + ), + "program-verifier": ( + "Only caller-pinned Safe-source anubis.program-evidence.v3 under " + "inventory-safe-v1 is admitted. Any source/compiler/artifact/policy mismatch, " + "roster or proof-path discrepancy, replay failure, symlink/path violation, or " + "unsupported profile refuses. Artifacts are never executed, and success does " + "not establish construct totality, source-to-VC, SMT-to-CNF, source-native " + "refinement, runtime behavior, or universal soundness." + ), + "runtime-only": ( + "Only the catalog-declared engine command, grammar, side conditions, and " + "budgets are admitted. Parse/domain/validation/non-convergence failures and " + "unsupported fragments return refused; no other lane is substituted and the " + "returned status is not promoted." + ), +} + +CLAIM_MATHEMATICAL_ASSURANCE = [ + "estimated", + "model-based", + "checked", + "bounded", + "formal-bounded", + "exact", +] + +HEX64 = re.compile(r"[0-9a-f]{64}\Z") +GIT_OBJECT_ID = re.compile(r"(?:[0-9a-f]{40}|[0-9a-f]{64})\Z") +TOOL_NAME = re.compile(r"jackal_[a-z0-9_]+\Z") +MAX_JSON_BYTES = 8 * 1024 * 1024 + + +class InventoryError(RuntimeError): + """Fail-closed inventory refusal with a stable reason name.""" + + def __init__(self, reason: str, detail: str): + self.reason = reason + self.detail = detail + super().__init__(f"reason={reason} detail={detail}") + + +def refuse(reason: str, detail: str) -> NoReturn: + raise InventoryError(reason, detail) + + +def canonical_bytes(value: object) -> bytes: + return json.dumps( + value, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + + +def _sha256_bytes(value: bytes) -> str: + return hashlib.sha256(value).hexdigest() + + +def _sha256_file(path: Path) -> str: + if not path.is_file(): + refuse("missing-input", f"required regular file is absent: {path}") + return _sha256_bytes(path.read_bytes()) + + +def _load_json(path: Path) -> dict[str, Any]: + if not path.is_file(): + refuse("missing-input", f"required JSON file is absent: {path}") + raw = path.read_bytes() + if len(raw) > MAX_JSON_BYTES: + refuse("oversize-input", f"JSON input exceeds {MAX_JSON_BYTES} bytes: {path}") + try: + value = json.loads(raw.decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError) as error: + refuse("invalid-json", f"{path}: {error}") + if not isinstance(value, dict): + refuse("invalid-json", f"top level is not an object: {path}") + return value + + +def _load_manifest(path: Path) -> dict[str, dict[str, str | None]]: + if not path.is_file(): + refuse("missing-input", f"release manifest is absent: {path}") + entries: dict[str, dict[str, str | None]] = {} + for line_number, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + line = raw.strip() + if not line or line.startswith("#"): + continue + fields = line.split() + if len(fields) not in {2, 3}: + refuse("manifest-shape", f"{path}:{line_number}: expected 2 or 3 fields") + label = fields[0] + if label in entries: + refuse("manifest-duplicate", f"duplicate label {label!r}") + digest = fields[-1] + if HEX64.fullmatch(digest) is None: + refuse("manifest-digest", f"label {label!r} has invalid SHA-256") + entries[label] = { + "label": label, + "locator": fields[1] if len(fields) == 3 else None, + "sha256": digest, + } + return entries + + +def _verify_proof_identity_inputs( + root: Path, manifest: dict[str, dict[str, str | None]] +) -> None: + bindings = { + "range-proof-identity": Path( + "release/evidence/range_proof_identity_v172.json" + ), + "gaussian-proof-identity": Path( + "release/evidence/gaussian_proof_identity.json" + ), + "int-cert-proof-identity": Path( + "release/evidence/int_cert_proof_identity_v172.json" + ), + } + for label, relative in bindings.items(): + entry = manifest.get(label) + if entry is None: + refuse("missing-checker-identity", f"manifest label {label!r} is absent") + if entry["locator"] != relative.as_posix(): + refuse( + "checker-identity-path", + f"{label!r} points to {entry['locator']!r}, expected {relative.as_posix()!r}", + ) + actual = _sha256_file(root / relative) + if entry["sha256"] != actual: + refuse( + "checker-identity-digest", + f"{label!r} manifest={entry['sha256']} actual={actual}", + ) + _load_json(root / relative) + + +def _load_catalog(root: Path) -> tuple[dict[str, Any], list[dict[str, Any]], list[str]]: + document = _load_json(root / CATALOG_PATH) + if document.get("version") != EXPECTED_VERSION: + refuse( + "catalog-version", + f"expected {EXPECTED_VERSION!r}, found {document.get('version')!r}", + ) + tools = document.get("tools") + if not isinstance(tools, list): + refuse("catalog-shape", "tools is not an array") + names: list[str] = [] + for index, row in enumerate(tools): + if not isinstance(row, dict): + refuse("catalog-shape", f"tools[{index}] is not an object") + name = row.get("name") + if not isinstance(name, str) or TOOL_NAME.fullmatch(name) is None: + refuse("catalog-tool-name", f"tools[{index}] has invalid name {name!r}") + names.append(name) + duplicates = sorted({name for name in names if names.count(name) > 1}) + if duplicates: + refuse("duplicate-tool", f"duplicate catalog names: {duplicates}") + if len(names) != EXPECTED_TOOL_COUNT: + refuse("tool-count", f"expected {EXPECTED_TOOL_COUNT}, found {len(names)}") + return document, tools, names + + +def verify_surface_origin(root: Path | str, catalog_names: list[str]) -> None: + """Prove the immutable containing ref is an ancestor with the same roster.""" + + root_path = Path(root).resolve() + value = CONTAINING_REF.get("value") + if ( + CONTAINING_REF.get("kind") != "surface-origin-commit" + or not isinstance(value, str) + or GIT_OBJECT_ID.fullmatch(value) is None + ): + refuse("containing-ref", "surface-origin ref is malformed") + try: + shown = subprocess.run( + [ + "git", + "-C", + str(root_path), + "show", + f"{value}:{CATALOG_PATH.as_posix()}", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=30, + check=False, + ) + ancestor = subprocess.run( + ["git", "-C", str(root_path), "merge-base", "--is-ancestor", value, "HEAD"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=30, + check=False, + ) + except (OSError, subprocess.TimeoutExpired) as error: + refuse("containing-ref", str(error)[:300]) + if shown.returncode != 0 or ancestor.returncode != 0: + detail = (shown.stderr + ancestor.stderr).decode("utf-8", "replace")[:300] + refuse("containing-ref", detail or "surface-origin ref is unavailable") + try: + document = json.loads(shown.stdout.decode("utf-8")) + tools = document["tools"] + origin_names = [row["name"] for row in tools] + except (UnicodeDecodeError, json.JSONDecodeError, KeyError, TypeError) as error: + refuse("containing-ref", f"surface-origin catalog is malformed: {error}") + if origin_names != catalog_names: + refuse("containing-ref", "surface-origin catalog roster drift") + + +def _load_profiles(root: Path, catalog_names: list[str]) -> dict[str, list[str]]: + catalog_set = set(catalog_names) + profiles: dict[str, list[str]] = {} + for profile_id in PROFILE_IDS: + path = root / PROFILE_DIR / f"{profile_id}.json" + document = _load_json(path) + if document.get("profile_id") != profile_id: + refuse("profile-id", f"{path} does not declare {profile_id!r}") + declared = document.get("tools") + if not isinstance(declared, list) or not all( + isinstance(name, str) for name in declared + ): + refuse("profile-shape", f"{profile_id}.tools is not a string array") + if len(declared) != len(set(declared)): + refuse("profile-duplicate", f"{profile_id} contains duplicate tools") + unknown = sorted(set(declared) - catalog_set) + if unknown: + refuse("profile-unknown-tool", f"{profile_id} has unknown tools {unknown}") + expected_order = [name for name in catalog_names if name in set(declared)] + if declared != expected_order: + refuse("profile-order", f"{profile_id} is not in catalog order") + digest = document.get("profile_digest_sha256") + payload = { + key: value + for key, value in document.items() + if key != "profile_digest_sha256" + } + expected_digest = _sha256_bytes(canonical_bytes(payload)) + if digest != expected_digest: + refuse( + "profile-digest", + f"{profile_id} digest={digest!r} expected={expected_digest}", + ) + profiles[profile_id] = declared + if not set(profiles["core"]) <= set(profiles["formal"]): + refuse("profile-nesting", "core is not a subset of formal") + if not set(profiles["formal"]) <= set(profiles["full"]): + refuse("profile-nesting", "formal is not a subset of full") + if profiles["full"] != catalog_names: + refuse("full-profile-mismatch", "full profile is not the exact catalog roster") + return profiles + + +def _dependency_family(name: str) -> str: + matches = [family for family, members in DEPENDENCY_GROUPS.items() if name in members] + if not matches: + refuse("unmapped-tool", f"no capability facts are bound for {name!r}") + if len(matches) != 1: + refuse("dependency-family-conflict", f"{name!r} belongs to {matches}") + return matches[0] + + +def _status_classes(row: dict[str, Any]) -> list[str]: + name = row["name"] + returns = row.get("returns") + if not isinstance(returns, dict): + refuse("catalog-shape", f"{name}.returns is not an object") + declaration = returns.get("status") + if not isinstance(declaration, str): + refuse("catalog-shape", f"{name}.returns.status is not a string") + statuses = declaration.split(" | ") + if not statuses or " | ".join(statuses) != declaration or any(not item for item in statuses): + refuse("status-shape", f"{name} status declaration is not exact ' | ' tokens") + if len(statuses) != len(set(statuses)): + refuse("status-shape", f"{name} repeats a status token") + unknown = sorted(set(statuses) - ALLOWED_STATUSES) + if unknown: + refuse("status-vocabulary", f"{name} uses unknown statuses {unknown}") + if "refused" not in statuses: + refuse("status-refusal-missing", f"{name} does not declare refused") + return statuses + + +def _assurance_classes(name: str, statuses: list[str]) -> list[str]: + if name in {"jackal_claim", "jackal_verify_bundle"}: + return list(CLAIM_MATHEMATICAL_ASSURANCE) + if name == "jackal_verify_receipt": + return ["formal-bounded"] + values = [value for value in statuses if value not in {"refused", "indeterminate", "ok"}] + if not values: + refuse("assurance-mapping", f"{name} has no positive assurance class") + return values + + +def _supported_fragment(row: dict[str, Any]) -> str: + name = row["name"] + description = row.get("description") + arguments = row.get("arguments") + if not isinstance(description, str) or not description.strip(): + refuse("catalog-shape", f"{name}.description is empty") + if not isinstance(arguments, dict) or not arguments: + refuse("catalog-shape", f"{name}.arguments is not a non-empty object") + clauses: list[str] = [] + for argument, definition in arguments.items(): + if not isinstance(argument, str) or not isinstance(definition, dict): + refuse("catalog-shape", f"{name}.arguments has an invalid entry") + help_text = definition.get("help") + if not isinstance(help_text, str) or not help_text.strip(): + refuse("catalog-shape", f"{name}.{argument}.help is empty") + clauses.append(f"{argument}: {help_text.strip()}") + return f"{description.strip()} Inputs: {'; '.join(clauses)}" + + +def _approved_program_compiler_identity( + root: Path, manifest: dict[str, dict[str, str | None]] +) -> dict[str, str]: + bindings = ( + ("program-compatibility-floor", PROGRAM_FLOOR_PATH), + ("anubis_program_policy", PROGRAM_POLICY_PATH), + ) + documents: dict[str, dict[str, Any]] = {} + for label, relative in bindings: + entry = manifest.get(label) + if entry is None: + refuse("missing-checker-identity", f"manifest label {label!r} is absent") + if entry["locator"] != relative.as_posix(): + refuse( + "checker-identity-path", + f"{label!r} points to {entry['locator']!r}, expected {relative.as_posix()!r}", + ) + actual = _sha256_file(root / relative) + if entry["sha256"] != actual: + refuse( + "checker-identity-digest", + f"{label!r} manifest={entry['sha256']} actual={actual}", + ) + documents[label] = _load_json(root / relative) + floor_pin = documents["program-compatibility-floor"].get( + "approved_check_compiler_sha256" + ) + policy_pin = documents["anubis_program_policy"].get( + "approved_check_compiler_sha256" + ) + if not isinstance(floor_pin, str) or HEX64.fullmatch(floor_pin) is None: + refuse("checker-identity-digest", "program compiler approval pin is malformed") + if policy_pin != floor_pin: + refuse( + "checker-identity-digest", + "program compatibility floor and policy compiler pins disagree", + ) + return { + "label": "approved_program_compiler", + "locator": ( + "release/compat/v173_floor.json#approved_check_compiler_sha256" + ), + "sha256": floor_pin, + } + + +def _dependency_record( + family: str, manifest: dict[str, dict[str, str | None]], root: Path +) -> dict[str, Any]: + labels = DEPENDENCY_LABELS[family] + missing = [label for label in labels if label not in manifest] + if missing: + refuse( + "missing-checker-identity", + f"dependency family {family!r} lacks manifest labels {missing}", + ) + identities = [dict(manifest[label]) for label in labels] + if family == "program-verifier": + identities.append(_approved_program_compiler_identity(root, manifest)) + return { + "family": family, + "identities": identities, + } + + +def build_inventory(root: Path | str) -> dict[str, Any]: + root_path = Path(root).resolve() + _catalog, tools, catalog_names = _load_catalog(root_path) + profiles = _load_profiles(root_path, catalog_names) + manifest = _load_manifest(root_path / MANIFEST_PATH) + _verify_proof_identity_inputs(root_path, manifest) + + required_labels = { + label for labels in DEPENDENCY_LABELS.values() for label in labels + } + missing_labels = sorted(required_labels - set(manifest)) + if missing_labels: + refuse( + "missing-checker-identity", + f"required release-manifest labels are absent: {missing_labels}", + ) + + input_digests = [] + for relative in INPUT_PATHS: + input_digests.append( + {"path": relative.as_posix(), "sha256": _sha256_file(root_path / relative)} + ) + + records: list[dict[str, Any]] = [] + for row in tools: + name = row["name"] + family = _dependency_family(name) + statuses = _status_classes(row) + returns = row["returns"] + consequence_ceiling = returns.get("consequence_ceiling") + if consequence_ceiling is not None and ( + not isinstance(consequence_ceiling, str) + or not consequence_ceiling.strip() + ): + refuse( + "catalog-shape", + f"{name}.returns.consequence_ceiling is not a non-empty string", + ) + records.append( + { + "name": name, + "schema_sha256": _sha256_bytes(canonical_bytes(row)), + "exposure": {"kernel": True, "hermes": True, "codex": True}, + "status_classes": statuses, + "assurance_classes": _assurance_classes(name, statuses), + "consequence_ceiling": consequence_ceiling, + "dependency": _dependency_record(family, manifest, root_path), + "supported_fragment": _supported_fragment(row), + "refusal_boundary": REFUSAL_BOUNDARIES[family], + "profiles": [ + profile_id + for profile_id in PROFILE_IDS + if name in profiles[profile_id] + ], + "release_state": RELEASE_STATE, + "containing_ref": dict(CONTAINING_REF), + } + ) + + unique = len({row["name"] for row in records}) + if len(records) != EXPECTED_TOOL_COUNT or unique != EXPECTED_TOOL_COUNT: + refuse( + "tool-count", + f"expected tools={EXPECTED_TOOL_COUNT} unique={EXPECTED_TOOL_COUNT}, " + f"found tools={len(records)} unique={unique}", + ) + return { + "schema": "jackal-capability-inventory-v1", + "catalog": { + "path": CATALOG_PATH.as_posix(), + "version": EXPECTED_VERSION, + "sha256": _sha256_file(root_path / CATALOG_PATH), + }, + "release": { + "state": RELEASE_STATE, + "version": EXPECTED_VERSION, + "containing_ref": dict(CONTAINING_REF), + "statement": ( + "Published release identity; the annotated v1.7.3 tag and " + "GitHub release must bind these exact bytes." + ), + }, + "tool_count": len(records), + "unique_tool_count": unique, + "status_vocabulary": sorted(ALLOWED_STATUSES), + "inputs": input_digests, + "tools": records, + } + + +def render_inventory(root: Path | str) -> bytes: + return canonical_bytes(build_inventory(root)) + b"\n" + + +def check_committed(root: Path | str) -> None: + root_path = Path(root).resolve() + path = root_path / ARTIFACT_PATH + if not path.is_file(): + refuse("artifact-missing", f"committed inventory is absent: {path}") + expected = render_inventory(root_path) + actual = path.read_bytes() + if actual != expected: + refuse( + "artifact-drift", + f"{ARTIFACT_PATH} actual={_sha256_bytes(actual)} " + f"generated={_sha256_bytes(expected)}", + ) + + +def _write_atomic(path: Path, data: bytes) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + descriptor, temporary_name = tempfile.mkstemp( + prefix=f".{path.name}.", dir=path.parent + ) + temporary = Path(temporary_name) + try: + with os.fdopen(descriptor, "wb") as handle: + handle.write(data) + handle.flush() + os.fsync(handle.fileno()) + os.chmod(temporary, 0o644) + os.replace(temporary, path) + finally: + if temporary.exists(): + temporary.unlink() + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + action = parser.add_mutually_exclusive_group() + action.add_argument("--write", action="store_true", help="write generated bytes") + action.add_argument("--check", action="store_true", help="verify committed bytes") + parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1]) + args = parser.parse_args(argv) + try: + document = build_inventory(args.root) + verify_surface_origin( + args.root, [row["name"] for row in document["tools"]] + ) + if args.write: + _write_atomic(args.root.resolve() / ARTIFACT_PATH, render_inventory(args.root)) + else: + check_committed(args.root) + except InventoryError as error: + print( + f"CAPABILITY_INVENTORY_REFUSED reason={error.reason} detail={error.detail}", + file=sys.stderr, + ) + return 1 + print( + "CAPABILITY_INVENTORY_PASS " + f"tools={document['tool_count']} unique={document['unique_tool_count']}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/lean_admission_audit.py b/tools/lean_admission_audit.py new file mode 100644 index 0000000..5384e07 --- /dev/null +++ b/tools/lean_admission_audit.py @@ -0,0 +1,918 @@ +#!/usr/bin/env python3 +"""Generate and verify JACKAL's repository-wide Lean admission audit. + +The audit is deliberately narrow. It inventories every Git-tracked Lean +source, rejects local proof/admission bypasses, replays the exact ``#print +axioms`` surface named by the current release proof identities, and binds the +checker bytes those identities name. It does not authenticate the builder or +prove source-to-native refinement. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import stat +import subprocess +import sys +import tempfile +from typing import Any, Iterable + + +DEFAULT_ROOT = Path(__file__).resolve().parents[1] +ARTIFACT_REL = Path("release/evidence/lean_admission_audit_v173.json") +GENERATOR_REL = Path("tools/lean_admission_audit.py") +LEAN_DIR_REL = Path("proofs/lean") +ALLOWED_AXIOMS = ("propext", "Classical.choice", "Quot.sound") +MAX_SOURCE_BYTES = 16 * 1024 * 1024 +MAX_EVIDENCE_BYTES = 16 * 1024 * 1024 +COMMAND_TIMEOUT_ENV = "JACKAL_LEAN_AUDIT_TIMEOUT_SECONDS" +DEFAULT_COMMAND_TIMEOUT_SECONDS = 180.0 +MAX_COMMAND_TIMEOUT_SECONDS = 3600.0 + +IDENTITY_CONFIGS = ( + ( + "range", + Path("release/evidence/range_proof_identity_v172.json"), + "jackal-range-proof-identity-v2", + ), + ( + "gaussian", + Path("release/evidence/gaussian_proof_identity.json"), + "jackal-gaussian-proof-identity-v1", + ), + ( + "int-cert", + Path("release/evidence/int_cert_proof_identity_v172.json"), + "jackal-int-cert-proof-identity-v2", + ), +) + +ALLOWED_LOCAL_CONSTRUCTS = { + "proofs/lean/JackalIv/Correspondence.lean": { + "implemented_by": ( + "@[implemented_by Dump.parseSexpImpl]", + "@[implemented_by Dump.lowerSexpImpl]", + ) + } +} + +CONSTRUCT_PATTERNS = { + "admit": re.compile(r"\badmit\b"), + "axiom_declaration": re.compile( + r"(?m)^\s*(?:@\[[^\n]*\]\s*)*" + r"(?:(?:private|protected|noncomputable|local)\s+)*axioms?\s+" + ), + "extern": re.compile(r"\bextern\b"), + "implemented_by": re.compile(r"@\[\s*implemented_by\b"), + "native_decide": re.compile(r"\bnative_decide\b"), + "partial": re.compile(r"\bpartial\b"), + "sorry": re.compile(r"\bsorry\b"), + "unsafe": re.compile(r"\bunsafe\b"), +} + +AXIOM_LINE_RE = re.compile(r"^'([^']+)' depends on axioms: \[(.*)\]$") +LEAN_VERSION_RE = re.compile( + r"^Lean \(version ([0-9]+\.[0-9]+\.[0-9]+), [^,]+, " + r"commit ([0-9a-f]{40}), (Release|Debug)\)$" +) + + +class AuditError(RuntimeError): + """A repository-wide Lean trust-surface invariant failed.""" + + +def canonical_bytes(value: Any) -> bytes: + return json.dumps( + value, ensure_ascii=False, separators=(",", ":"), sort_keys=True + ).encode("utf-8") + + +def pretty_bytes(value: Any) -> bytes: + return ( + json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n" + ).encode("utf-8") + + +def sha256_bytes(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def command_timeout_seconds() -> float: + raw = os.environ.get(COMMAND_TIMEOUT_ENV) + if raw is None: + return DEFAULT_COMMAND_TIMEOUT_SECONDS + try: + value = float(raw) + except ValueError as exc: + raise AuditError(f"invalid {COMMAND_TIMEOUT_ENV}: {raw!r}") from exc + if not 0 < value <= MAX_COMMAND_TIMEOUT_SECONDS: + raise AuditError(f"invalid {COMMAND_TIMEOUT_ENV}: {raw!r}") + return value + + +def read_regular(path: Path, maximum: int) -> bytes: + try: + before = path.lstat() + except OSError as exc: + raise AuditError(f"cannot stat required file: {path}") from exc + if not stat.S_ISREG(before.st_mode) or stat.S_ISLNK(before.st_mode): + raise AuditError(f"required path is not a regular non-symlink file: {path}") + if before.st_size > maximum: + raise AuditError(f"required file exceeds byte bound: {path}") + descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)) + try: + opened = os.fstat(descriptor) + if (opened.st_dev, opened.st_ino) != (before.st_dev, before.st_ino): + raise AuditError(f"file identity changed before read: {path}") + chunks: list[bytes] = [] + total = 0 + while True: + chunk = os.read(descriptor, min(1024 * 1024, maximum + 1 - total)) + if not chunk: + break + chunks.append(chunk) + total += len(chunk) + if total > maximum: + raise AuditError(f"required file exceeds byte bound: {path}") + after = os.fstat(descriptor) + finally: + os.close(descriptor) + current = path.lstat() + identity = lambda item: ( # noqa: E731 + item.st_dev, + item.st_ino, + item.st_mode, + item.st_size, + item.st_mtime_ns, + item.st_ctime_ns, + ) + if identity(opened) != identity(after) or identity(after) != identity(current): + raise AuditError(f"file changed while being read: {path}") + return b"".join(chunks) + + +def strict_json(path: Path) -> dict[str, Any]: + def reject_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + document: dict[str, Any] = {} + for key, value in pairs: + if key in document: + raise AuditError(f"duplicate JSON key in {path}: {key}") + document[key] = value + return document + + try: + value = json.loads( + read_regular(path, MAX_EVIDENCE_BYTES).decode("utf-8"), + object_pairs_hook=reject_duplicates, + ) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise AuditError(f"cannot parse JSON evidence {path}: {exc}") from exc + if not isinstance(value, dict): + raise AuditError(f"JSON evidence is not an object: {path}") + return value + + +def repository_path(root: Path, supplied: str, label: str) -> Path: + """Resolve one evidence-supplied relative path without leaving ``root``.""" + + if not supplied or "\x00" in supplied: + raise AuditError(f"{label} escapes audit root: {supplied!r}") + relative = Path(supplied) + if relative.is_absolute() or ".." in relative.parts: + raise AuditError(f"{label} escapes audit root: {supplied!r}") + resolved_root = root.resolve() + candidate = resolved_root / relative + try: + candidate.resolve(strict=False).relative_to(resolved_root) + except (OSError, ValueError) as exc: + raise AuditError(f"{label} escapes audit root: {supplied!r}") from exc + return candidate + + +def _character_literal_end(source: str, start: int) -> int | None: + """Return the end of a Lean character literal, or None for identifier primes.""" + + if source[start] != "'" or start + 2 >= len(source): + return None + cursor = start + 1 + character = source[cursor] + if character in "\r\n'": + return None + if character != "\\": + cursor += 1 + else: + cursor += 1 + if cursor >= len(source): + return None + escape = source[cursor] + if escape == "u" and cursor + 1 < len(source) and source[cursor + 1] == "{": + close = source.find("}", cursor + 2) + if close < 0: + return None + digits = source[cursor + 2 : close] + if not 1 <= len(digits) <= 6 or any( + digit not in "0123456789abcdefABCDEF" for digit in digits + ): + return None + cursor = close + 1 + elif escape in {"u", "U", "x"}: + width = {"u": 4, "U": 8, "x": 2}[escape] + digits = source[cursor + 1 : cursor + 1 + width] + if len(digits) != width or any( + digit not in "0123456789abcdefABCDEF" for digit in digits + ): + return None + cursor += 1 + width + else: + cursor += 1 + if cursor < len(source) and source[cursor] == "'": + return cursor + 1 + return None + + +def code_without_comments_or_strings(source: str) -> str: + """Blank Lean comments, strings, and character literals while preserving lines.""" + + output: list[str] = [] + index = 0 + block_depth = 0 + in_string = False + while index < len(source): + char = source[index] + pair = source[index : index + 2] + if block_depth: + if pair == "/-": + output.extend(" ") + block_depth += 1 + index += 2 + elif pair == "-/": + output.extend(" ") + block_depth -= 1 + index += 2 + else: + output.append("\n" if char == "\n" else " ") + index += 1 + continue + if in_string: + if char == "\\" and index + 1 < len(source): + output.append(" ") + output.append("\n" if source[index + 1] == "\n" else " ") + index += 2 + elif char == '"': + output.append(" ") + in_string = False + index += 1 + else: + output.append("\n" if char == "\n" else " ") + index += 1 + continue + raw_cursor = index + 1 + while raw_cursor < len(source) and source[raw_cursor] == "#": + raw_cursor += 1 + raw_string_prefix = ( + char == "r" + and raw_cursor < len(source) + and source[raw_cursor] == '"' + and ( + index == 0 + or not (source[index - 1].isalnum() or source[index - 1] in "_'") + ) + ) + if raw_string_prefix: + raise AuditError( + "Lean raw strings are unsupported by the admission scanner" + ) + character_literal_end = _character_literal_end(source, index) + if character_literal_end is not None: + output.extend( + "\n" if value == "\n" else " " + for value in source[index:character_literal_end] + ) + index = character_literal_end + elif pair == "/-": + output.extend(" ") + block_depth = 1 + index += 2 + elif pair == "--": + while index < len(source) and source[index] != "\n": + output.append(" ") + index += 1 + elif char == '"': + output.append(" ") + in_string = True + index += 1 + else: + output.append(char) + index += 1 + if block_depth: + raise AuditError("unterminated block comment in Lean source") + if in_string: + raise AuditError("unterminated string in Lean source") + return "".join(output) + + +def tracked_lean_paths( + root: Path, *, require_git: bool = False +) -> tuple[list[str], str]: + completed = subprocess.run( + ["git", "-C", str(root), "ls-files", "-z", "--", LEAN_DIR_REL.as_posix()], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + if completed.returncode == 0: + paths = sorted( + os.fsdecode(item) + for item in completed.stdout.split(b"\0") + if item.endswith(b".lean") + ) + inventory_source = "git-ls-files" + elif require_git: + detail = os.fsdecode(completed.stderr).strip() or f"exit {completed.returncode}" + raise AuditError(f"git ls-files failed: {detail}") + else: + lean_dir = root / LEAN_DIR_REL + paths = sorted( + path.relative_to(root).as_posix() + for path in lean_dir.rglob("*.lean") + if path.is_file() and not path.is_symlink() + ) + inventory_source = "filesystem-fallback" + if not paths: + raise AuditError(f"no Lean sources found below {LEAN_DIR_REL}") + if len(paths) != len(set(paths)): + raise AuditError("duplicate Lean source path in inventory") + return paths, inventory_source + + +def finding_record( + *, construct: str, relative: str, source: str, code: str, offset: int +) -> dict[str, Any]: + line = code.count("\n", 0, offset) + 1 + source_lines = source.split("\n") + return { + "construct": construct, + "line": line, + "path": relative, + "source_line": source_lines[line - 1].strip(), + } + + +def scan_sources( + root: Path, + relative_paths: Iterable[str] | None = None, + *, + require_git_inventory: bool = False, +) -> dict[str, Any]: + root = root.resolve() + if relative_paths is None: + paths, inventory_source = tracked_lean_paths( + root, require_git=require_git_inventory + ) + else: + paths = sorted(relative_paths) + inventory_source = "explicit-paths" + records: list[dict[str, Any]] = [] + findings: list[dict[str, Any]] = [] + noncomputable_occurrences = 0 + for relative in paths: + path = root / relative + try: + path.resolve().relative_to(root) + except ValueError as exc: + raise AuditError(f"Lean source escapes audit root: {relative}") from exc + raw = read_regular(path, MAX_SOURCE_BYTES) + try: + source = raw.decode("utf-8") + except UnicodeDecodeError as exc: + raise AuditError(f"Lean source is not UTF-8: {relative}") from exc + code = code_without_comments_or_strings(source) + noncomputable_occurrences += len(re.findall(r"\bnoncomputable\b", code)) + for construct, pattern in CONSTRUCT_PATTERNS.items(): + for match in pattern.finditer(code): + findings.append( + finding_record( + construct=construct, + relative=relative, + source=source, + code=code, + offset=match.start(), + ) + ) + records.append( + { + "bytes": len(raw), + "path": relative, + "sha256": sha256_bytes(raw), + } + ) + + findings.sort(key=lambda row: (row["path"], row["line"], row["construct"])) + selected_paths = set(paths) + expected_allowed = [ + { + "construct": construct, + "path": relative, + "source_line": source_line, + } + for relative, constructs in sorted(ALLOWED_LOCAL_CONSTRUCTS.items()) + if relative in selected_paths + for construct, source_lines in sorted(constructs.items()) + for source_line in source_lines + ] + observed_allowed_without_lines = [ + { + "construct": row["construct"], + "path": row["path"], + "source_line": row["source_line"], + } + for row in findings + if row["construct"] == "implemented_by" + ] + if observed_allowed_without_lines != expected_allowed: + raise AuditError( + "forbidden or missing implemented_by construct: " + f"expected={expected_allowed!r} actual={observed_allowed_without_lines!r}" + ) + forbidden = [row for row in findings if row["construct"] != "implemented_by"] + if forbidden: + raise AuditError(f"forbidden Lean construct findings: {forbidden!r}") + allowed = [ + {**row, "classification": "dump-only trusted runtime mirror"} + for row in findings + if row["construct"] == "implemented_by" + ] + allowed.sort(key=lambda row: (row["path"], row["line"], row["construct"])) + payload = { + "files": records, + "inventory_source": inventory_source, + "construct_policy": { + "allowed_exact_source_lines": expected_allowed, + "allowed_findings": allowed, + "forbidden_by_default": sorted(CONSTRUCT_PATTERNS), + "forbidden_findings": forbidden, + "noncomputable_classification": ( + "Lean noncomputable declarations are counted but are not logical " + "admissions or executable-code substitutions." + ), + "noncomputable_occurrences": noncomputable_occurrences, + "scan_scope": "comments and string bodies removed; executable Lean tokens scanned", + }, + "file_count": len(records), + } + payload["aggregate_sha256"] = sha256_bytes(canonical_bytes(payload)) + return payload + + +def run_checked( + command: list[str], *, cwd: Path, input_text: str | None = None +) -> subprocess.CompletedProcess[str]: + environment = os.environ.copy() + environment.update({"LANG": "C", "LC_ALL": "C"}) + completed = subprocess.run( + command, + cwd=cwd, + env=environment, + input=input_text, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=command_timeout_seconds(), + check=False, + ) + if completed.returncode != 0: + detail = (completed.stdout + completed.stderr).strip() + raise AuditError( + f"command failed ({completed.returncode}): {' '.join(command)}" + + (f"\n{detail}" if detail else "") + ) + if completed.stderr.strip(): + raise AuditError( + f"command emitted unexpected stderr: {' '.join(command)}\n" + f"{completed.stderr.strip()}" + ) + return completed + + +def collect_identity_bindings( + root: Path, +) -> tuple[list[dict[str, Any]], list[str], list[str]]: + bindings: list[dict[str, Any]] = [] + theorem_axioms: dict[str, list[str]] = {} + root_modules: set[str] = set() + for lane, relative, expected_schema in IDENTITY_CONFIGS: + path = root / relative + raw = read_regular(path, MAX_EVIDENCE_BYTES) + identity = strict_json(path) + if identity.get("schema") != expected_schema: + raise AuditError( + f"proof identity schema mismatch for {lane}: {identity.get('schema')!r}" + ) + proof = identity.get("proof") + closure = identity.get("source_closure") + checker = identity.get("checker") + if not isinstance(proof, dict) or not isinstance(closure, dict) or not isinstance(checker, dict): + raise AuditError(f"proof identity missing proof/source/checker object: {relative}") + theorem_rows = proof.get("theorems") + modules = closure.get("root_modules") + if not isinstance(theorem_rows, list) or not isinstance(modules, list): + raise AuditError(f"proof identity has malformed theorem/module list: {relative}") + lane_theorems: list[str] = [] + for row in theorem_rows: + if not isinstance(row, dict): + raise AuditError(f"malformed theorem row in {relative}") + theorem = row.get("theorem") + axioms = row.get("axioms") + if not isinstance(theorem, str) or not theorem: + raise AuditError(f"malformed theorem name in {relative}") + if axioms != list(ALLOWED_AXIOMS): + raise AuditError( + f"identity theorem axiom policy mismatch for {theorem}: {axioms!r}" + ) + prior = theorem_axioms.setdefault(theorem, list(axioms)) + if prior != axioms: + raise AuditError(f"conflicting theorem axiom declarations for {theorem}") + lane_theorems.append(theorem) + for module in modules: + if not isinstance(module, str) or not module: + raise AuditError(f"malformed root module in {relative}") + root_modules.add(module) + checker_path_value = checker.get("path") + if not isinstance(checker_path_value, str) or not checker_path_value: + raise AuditError(f"proof identity has malformed checker path: {relative}") + checker_path = repository_path( + root, checker_path_value, f"proof identity checker path for {lane}" + ) + checker_raw = read_regular(checker_path, 512 * 1024 * 1024) + checker_digest = sha256_bytes(checker_raw) + if checker.get("sha256") != checker_digest or checker.get("bytes") != len(checker_raw): + raise AuditError(f"checker bytes do not match proof identity for lane {lane}") + identity_digest = identity.get("identity_digest_sha256") + if not isinstance(identity_digest, str) or re.fullmatch(r"[0-9a-f]{64}", identity_digest) is None: + raise AuditError(f"malformed semantic identity digest for lane {lane}") + identity_body = { + key: value for key, value in identity.items() if key != "identity_digest_sha256" + } + calculated_identity_digest = sha256_bytes(canonical_bytes(identity_body)) + if identity_digest != calculated_identity_digest: + raise AuditError( + f"proof identity self-digest mismatch for lane {lane}: " + f"recorded={identity_digest} actual={calculated_identity_digest}" + ) + bindings.append( + { + "checker_bytes": len(checker_raw), + "checker_path": checker_path_value, + "checker_sha256": checker_digest, + "identity_bytes": len(raw), + "identity_checker_bytes": checker["bytes"], + "identity_checker_sha256": checker["sha256"], + "identity_digest_sha256": identity_digest, + "identity_path": relative.as_posix(), + "identity_schema": expected_schema, + "identity_sha256": sha256_bytes(raw), + "lane": lane, + "root_modules": sorted(modules), + "theorems": lane_theorems, + } + ) + return bindings, sorted(theorem_axioms), sorted(root_modules) + + +def run_theorem_axiom_audit( + root: Path, + theorem_names: list[str], + root_modules: list[str], + identity_bindings: list[dict[str, Any]], +) -> dict[str, Any]: + observed: dict[str, tuple[list[str], str]] = {} + invocations: list[dict[str, Any]] = [] + for binding in identity_bindings: + modules = binding["root_modules"] + lane_theorems = binding["theorems"] + program = "\n".join( + [f"import {module}" for module in modules] + + ["set_option format.width 1000"] + + [f"#print axioms {theorem}" for theorem in lane_theorems] + ) + "\n" + completed = run_checked( + ["lake", "env", "lean", "/dev/stdin"], + cwd=root / LEAN_DIR_REL, + input_text=program, + ) + raw_output = completed.stdout + invocation_theorems: list[str] = [] + for line in raw_output.splitlines(): + match = AXIOM_LINE_RE.fullmatch(line) + if match is None: + raise AuditError(f"unexpected #print axioms output: {line!r}") + theorem, raw_axioms = match.groups() + axioms = ( + [] if not raw_axioms else [item.strip() for item in raw_axioms.split(",")] + ) + current = (axioms, line) + if theorem in observed and observed[theorem] != current: + raise AuditError(f"conflicting #print axioms result: {theorem}") + observed[theorem] = current + invocation_theorems.append(theorem) + if invocation_theorems != lane_theorems: + raise AuditError( + f"#print axioms output order/set mismatch for lane {binding['lane']}" + ) + invocations.append( + { + "input_program_sha256": sha256_bytes(program.encode("utf-8")), + "lane": binding["lane"], + "output_sha256": sha256_bytes(raw_output.encode("utf-8")), + "root_modules": modules, + "theorem_count": len(lane_theorems), + } + ) + if set(observed) != set(theorem_names): + raise AuditError( + "#print axioms theorem set mismatch: " + f"missing={sorted(set(theorem_names) - set(observed))} " + f"extra={sorted(set(observed) - set(theorem_names))}" + ) + rows: list[dict[str, Any]] = [] + for theorem in theorem_names: + axioms, line = observed[theorem] + if axioms != list(ALLOWED_AXIOMS): + raise AuditError(f"unexpected axiom surface for {theorem}: {axioms!r}") + rows.append({"axioms": axioms, "raw_output": line, "theorem": theorem}) + return { + "allowed_exactly": list(ALLOWED_AXIOMS), + "command": "lake env lean /dev/stdin", + "invocations": invocations, + "root_modules": root_modules, + "theorem_count": len(rows), + "theorems": rows, + } + + +def collect_compatibility_snapshots(root: Path) -> dict[str, Any]: + relative = Path("release/compat/v172_floor.json") + path = root / relative + raw = read_regular(path, MAX_EVIDENCE_BYTES) + floor = strict_json(path) + if floor.get("schema") != "jackal-proof-compatibility-floor-v1": + raise AuditError("unexpected proof compatibility-floor schema") + lanes = floor.get("lanes") + if not isinstance(lanes, dict): + raise AuditError("proof compatibility floor has no lanes object") + snapshots: list[dict[str, Any]] = [] + for lane, policies in sorted(lanes.items()): + if not isinstance(policies, dict): + raise AuditError(f"malformed compatibility policy for {lane}") + for epoch_class, policy in sorted(policies.items()): + if not isinstance(policy, dict): + raise AuditError(f"malformed compatibility snapshot for {lane}/{epoch_class}") + identity_relative = policy.get("identity_file") + expected_digest = policy.get("identity_file_sha256") + if not isinstance(identity_relative, str) or not isinstance(expected_digest, str): + raise AuditError(f"compatibility snapshot lacks identity binding: {lane}/{epoch_class}") + identity_path = repository_path( + root, + identity_relative, + f"compatibility identity path for {lane}/{epoch_class}", + ) + identity_raw = read_regular(identity_path, MAX_EVIDENCE_BYTES) + actual_digest = sha256_bytes(identity_raw) + if actual_digest != expected_digest: + raise AuditError( + f"compatibility identity drift for {lane}/{epoch_class}: " + f"expected={expected_digest} actual={actual_digest}" + ) + snapshots.append( + { + "allowed_release_epochs": policy.get("allowed_release_epochs"), + "epoch_class": epoch_class, + "identity_path": identity_relative, + "identity_schema": policy.get("schema"), + "identity_sha256": actual_digest, + "lane": lane, + "mode": policy.get("mode", "current"), + "reason": policy.get("reason"), + } + ) + return { + "classification": ( + "Compatibility snapshots constrain replay or refusal policy; they are " + "evidence inputs, not Lean logical admissions." + ), + "current_release_epoch": floor.get("current_release_epoch"), + "floor_bytes": len(raw), + "floor_path": relative.as_posix(), + "floor_sha256": sha256_bytes(raw), + "reversed_interval_policy": floor.get("reversed_interval_policy"), + "snapshots": snapshots, + "unsupported_policy": floor.get("unsupported_policy"), + } + + +def collect_toolchain(root: Path) -> dict[str, Any]: + lean_dir = root / LEAN_DIR_REL + configuration_files: list[dict[str, Any]] = [] + configuration_bytes: dict[str, bytes] = {} + for relative in ( + Path("proofs/lean/lakefile.toml"), + Path("proofs/lean/lake-manifest.json"), + Path("proofs/lean/lean-toolchain"), + ): + raw = read_regular(root / relative, MAX_EVIDENCE_BYTES) + configuration_bytes[relative.as_posix()] = raw + configuration_files.append( + {"bytes": len(raw), "path": relative.as_posix(), "sha256": sha256_bytes(raw)} + ) + manifest = strict_json(root / "proofs/lean/lake-manifest.json") + packages = manifest.get("packages") + if not isinstance(packages, list): + raise AuditError("lake manifest has no package list") + mathlib = [row for row in packages if isinstance(row, dict) and row.get("name") == "mathlib"] + if len(mathlib) != 1 or not isinstance(mathlib[0].get("rev"), str): + raise AuditError("lake manifest does not pin exactly one mathlib revision") + version_output = run_checked( + ["lake", "env", "lean", "--version"], cwd=lean_dir + ).stdout.strip() + version_match = LEAN_VERSION_RE.fullmatch(version_output) + if version_match is None: + raise AuditError(f"unexpected Lean version output: {version_output!r}") + version, commit, build_profile = version_match.groups() + try: + toolchain_token = configuration_bytes[ + "proofs/lean/lean-toolchain" + ].decode("utf-8").strip() + except UnicodeDecodeError as exc: + raise AuditError("lean-toolchain is not UTF-8") from exc + expected_toolchain = f"leanprover/lean4:v{version}" + if toolchain_token != expected_toolchain: + raise AuditError( + "Lean runtime version does not match lean-toolchain: " + f"runtime={version} toolchain={toolchain_token!r}" + ) + return { + "configuration_files": configuration_files, + "lean": { + "build_profile": build_profile, + "commit": commit, + "version": version, + }, + "lean_toolchain": toolchain_token, + "mathlib_revision": mathlib[0]["rev"], + } + + +def build_audit(root: Path) -> dict[str, Any]: + root = root.resolve() + inventory = scan_sources(root, require_git_inventory=True) + bindings, theorem_names, root_modules = collect_identity_bindings(root) + theorem_audit = run_theorem_axiom_audit( + root, theorem_names, root_modules, bindings + ) + compatibility = collect_compatibility_snapshots(root) + generator_raw = read_regular(root / GENERATOR_REL, MAX_EVIDENCE_BYTES) + forbidden_findings = inventory["construct_policy"]["forbidden_findings"] + logical_admissions = [ + row + for row in forbidden_findings + if row["construct"] in {"admit", "sorry"} + ] + repository_axioms = [ + row + for row in forbidden_findings + if row["construct"] == "axiom_declaration" + ] + document: dict[str, Any] = { + "audit_result": { + "logical_admission_count": len(logical_admissions), + "repository_axiom_declaration_count": len(repository_axioms), + "status": "pass" if not forbidden_findings else "fail", + "unexpected_construct_count": len(forbidden_findings), + }, + "generator": { + "bytes": len(generator_raw), + "path": GENERATOR_REL.as_posix(), + "sha256": sha256_bytes(generator_raw), + }, + "release_bindings": { + "compatibility_snapshot_inputs": compatibility, + "current_proof_identities": bindings, + "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": inventory, + "theorem_axiom_audit": theorem_audit, + "toolchain": collect_toolchain(root), + "trust_surface": { + "allowed_local_runtime_substitutions": inventory["construct_policy"]["allowed_findings"], + "lean_standard_axioms": list(ALLOWED_AXIOMS), + "logical_admissions": logical_admissions, + "repository_axiom_declarations": repository_axioms, + "runtime_substitution_boundary": ( + "The two implemented_by attributes are confined to dump-only parser/lowering " + "mirrors; current checker acceptance uses neither definition." + ), + }, + } + document["audit_digest_sha256"] = sha256_bytes(canonical_bytes(document)) + return document + + +def render_audit(root: Path) -> bytes: + return pretty_bytes(build_audit(root)) + + +def write_atomic(path: Path, data: bytes) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + descriptor, temporary = tempfile.mkstemp(prefix=".lean-audit.", dir=path.parent) + try: + with os.fdopen(descriptor, "wb") as handle: + handle.write(data) + handle.flush() + os.fsync(handle.fileno()) + os.chmod(temporary, 0o644) + os.replace(temporary, path) + except BaseException: + try: + os.unlink(temporary) + except FileNotFoundError: + pass + raise + + +def check_committed(root: Path) -> None: + path = root / ARTIFACT_REL + expected = render_audit(root) + actual = read_regular(path, MAX_EVIDENCE_BYTES) + if actual != expected: + raise AuditError(f"generated audit differs from committed artifact: {ARTIFACT_REL}") + + +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument("--check", action="store_true", help="verify the committed artifact") + mode.add_argument("--write", action="store_true", help="atomically replace the artifact") + mode.add_argument( + "--source-check", + action="store_true", + help="platform-neutral tracked-source policy check without release binaries", + ) + parser.add_argument("--root", type=Path, default=DEFAULT_ROOT) + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(sys.argv[1:] if argv is None else argv) + root = args.root.resolve() + if args.source_check: + inventory = scan_sources(root, require_git_inventory=True) + print( + "LEAN_SOURCE_ADMISSION_PASS " + f"files={inventory['file_count']} admissions=0" + ) + return 0 + if args.write: + write_atomic(root / ARTIFACT_REL, render_audit(root)) + else: + check_committed(root) + document = strict_json(root / ARTIFACT_REL) + print( + "LEAN_ADMISSION_AUDIT_PASS " + f"files={document['source_inventory']['file_count']} " + f"theorems={document['theorem_axiom_audit']['theorem_count']} " + f"admissions={document['audit_result']['logical_admission_count']}" + ) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (AuditError, OSError, subprocess.SubprocessError) as error: + print(f"LEAN_ADMISSION_AUDIT_REFUSED detail={str(error)[:1000]}", file=sys.stderr) + raise SystemExit(1) from None