Skip to content

fix(proofs): NaN-safe trust base, estate proof-debt ledger, POSIX-sh gate port (follow-up to #18; closes #4) - #19

Merged
hyperpolymath merged 9 commits into
mainfrom
arena/01a10472-lol
Oct 4, 2026
Merged

hyperpolymath merged 9 commits into
mainfrom
arena/01a10472-lol

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Why this PR exists

PR #18 merged (a4e33a9) but main went red on both of its checks: the governance anti-pattern policy job (the merged tree carried scripts/audit-trust-base.py; estate bans Python files) and the Agda — proofs/theories job (the merged workflow still used the .agda-lib-only stdlib registration, which can never succeed with Ubuntu's agda-stdlib layout). And the review surfaced a genuine soundness bug in the axiom set. This branch carries the full resolution — it is the continuation of #18, not a replacement design.

1. Soundness fix (the important one)

CodeRabbit's review caught that my original mul-sign : 0.0 ≤ᶠ a → b ≤ᶠ 0.0 → 0.0 ≤ᶠ - (a * b) was refutable: at a = 0.0, b = log₂ 0 = −∞, the product is 0·(−∞) = NaN, and T (0.0 ≤ᵇ NaN) is inhabited — a refutable axiom inhabits ⊥ and proves anything. Tightening the hypothesis to 0.0 <ᶠ a (the suggestion) makes that entry axiom true, but the merged corpus then fed the non-strict lower d hypothesis into the strict mul-sign, so the chain wouldn't even type-check.

This PR fixes it structurally instead of hypothesis-wise: the strict guard moves into the term.

plogp : Float → Float
plogp v = if 0.0 <ᵇ v then - (v * log₂ v) else 0.0

klTerm : Float → Float → Float
klTerm p q = if 0.0 <ᵇ p then p * log₂ (p ÷ q) else 0.0
  • NaN-safe for every input (out-of-guard side — including NaN — returns exactly 0.0, which is what the 0·log 0 = 0 convention prescribes; no caller can ever observe an unguarded product).
  • v = 1 → −0.0, and 0 ≤ᵇ −0.0 computes true, so the boundary is consistent (plogp-one-boundary : 0.0 ≤ᶠ plogp 1.0 = tt pins this).
  • The whole log₂-nonpos + mul-sign + neg-nonpos entry-wise machinery collapses to one honest FFI axiom: plogp-nonneg : (v) → 0.0 ≤ᶠ v → v ≤ᶠ 1.0 → 0.0 ≤ᶠ plogp v.
  • Trust base: 6 → 5 postulates (ffi-fact 3: plogp-nonneg add-nonneg add-comm; conjecture 2: kl-nonneg js-bounded). entropy-nonneg and js-symmetric remain proven top-level theorems (per-entry float lemma postulated, top-level theorem proven — exactly the split POSTULATE-AUDIT Phase 2 prescribed); the Distribution hypothesis transport (lower d {i}, per the review) is kept.
  • No with-abstraction stuck-term gymnastics anymore: plogpB/klTermB are deleted, so the definitional-equality fragility in the old entropy chain is gone too.

2. Estate cross-dependency: the Trusted-base reduction policy job

standards/scripts/check-trusted-base.sh (governance trusted-base job) has been red on this repo since the pre-2026 corpus — it demands every postulate marker be inline-annotated or ledgered. Done properly:

  • each postulate keyword now has a contiguous -- AXIOM: / -- TRUSTED: leading comment block (the checker walks back only through comment lines — this is why the estate marker must sit above the keyword, not per-declaration);
  • new docs/proof-debt.md in the policy schema: §(a) three discharged items (entropy-nonneg, js-symmetric, the five 2024 float "axioms" that are now plain re-exports), §(c) the three necessary ffi-facts with justifications/citations, §(d) the two conjectures with owner + deadline (2027-03-31, standards#124 sweep boundary);
  • verified locally against the actual standards@main script: [OK] All 2 escape hatch(es) are documented.

3. Language policy: Python is banned — gate ported to POSIX shell

scripts/audit-trust-base.py deleted (anti-pattern job fail); all gate logic now lives in two .sh files that both CI systems (GH Actions + GitLab) and just prove-* share:

  • scripts/prove-agda.sh — apt-layout stdlib probe (-i /usr/share/agda-stdlib when /usr/share/agda-stdlib/Data exists; .agda-lib discovery only as fallback), --compile-dir into a writable dir (the shipped .agdai tree is root-owned), fail-closed on empty corpus, per-file flags chosen by a {-# OPTIONS … --safe regex (not substring — per review), and --safe iff the pragma claims it.
  • scripts/audit-trust-base.sh — the seven check families ported 1:1 from the python audit whose negative-test battery (smuggled postulate, foreign file, rename drift both directions, vacuous where-relation, budget overflow) is re-validated on the port (see below).

4. CI robustness ports

  • Detect proof changes: on git diff failure → warn + proofs=true (conservative; the old || true path could skip verification on error — suggestion adopted with the polarity flipped to fail-safe).
  • runs-on: ubuntu-24.04 pinned on the correctness job (ubuntu-latest floats to 26 on 2026-10-19 mid-review-window).
  • No workflow-level paths: filter remains (issue Agda proofs under proofs/ have no CI here — the verifying job is stranded in standards #4 req [1]); corpus non-empty assertion (req [2]) kept in both the sh audit and prove-agda.

Validation

  • Local: sh scripts/audit-trust-base.sh . → OK — 5 postulate(s) (conjecture=2 ffi-fact=3), 2 safe module(s); negative battery T1 (smuggled postulate into Foundations) and T2 (extra tagged axiom) both exit 1 with all expected ::error:: families; estate check-trusted-base.sh green as above.
  • First-ever real agda 2.6.4 type-check of this corpus runs in this PR's CI — the job previously died before invoking Agda on main. If residual errors surface, fixes land here.

Refs: #4, #18. Closes #4 (issue body's remaining acceptance: verified corpus — this PR makes it actually verify).

Follow-up to the merged PR #18 (main was left red on the two checks this
PR owns — the Agda job and the anti-pattern policy). Incorporates the full
review outcome, including one genuine soundness find:

1. SOUNDNESS: the draft `mul-sign` axiom (0 <= a -> b <= 0 ->
   0 <= -(a*b)) was REFUTABLE: at a = 0, b = log2 0 = -inf the product is
   NaN, and T (0 <=b NaN) is inhabited — an inhabited-refutable axiom
   proves False. Fixed structurally, not by hypothesis-tightening:
   plogp/klTerm now carry a strict positivity guard (0.0 <b v) inside the
   term, are NaN-safe for every input (v <= 0 or NaN -> 0.0; v = 1 -> -0.0
   with 0 <=b -0.0 true), and the mul-sign + log2-nonpos pair collapses to
   one honest per-entry ffi-fact `plogp-nonneg`. Trust base 6 -> 5
   postulates; entropy-nonneg and js-symmetric remain proven top-level
   theorems; sanity is pinned by refl/T lemmas (klTerm-zero-left,
   plogp-zero, plogp-one-boundary).
2. ESTATE cross-dependency: adds docs/proof-debt.md in the Trusted-Base
   Reduction Policy schema (§a discharged / §c necessary / §d dated debt,
   owner @hyperpolymath, deadline 2027-03-31 tied to standards#124) and
   AXIOM:/TRUSTED: leading comment blocks on both postulate keywords, so
   the long-red governance job `Trusted-base reduction policy` now passes
   locally against standards@main check-trusted-base.sh.
3. LANGUAGE POLICY: scripts/audit-trust-base.py deleted (estate bans .py);
   the validated gate is re-implemented as scripts/audit-trust-base.sh
   (POSIX sh + awk, same 7 check families; negative tests re-passed).
4. CI ROBUSTNESS (review ports + prior findings): conservative
   detect-step (failed diff => verify, never skip), per-file OPTIONS-pragma
   regex (not substring), apt-layout stdlib probe (-i /usr/share/agda-stdlib;
   the find-*.agda-lib logic can never succeed on noble), --compile-dir into
   a writable dir, ubuntu-24.04 pin (ubuntu-latest drifts on 2026-10-19),
   explicit lower d {i} implicits.

Gate logic unchanged where it was already validated; audit + estate
checker both pass on this tree locally; first real agda type-check runs in
CI.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 7 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 5f0bec01-43ab-4889-9bdd-e684a60b5758
📥 Commits

Reviewing files that changed from the base of the PR and between a5e3225 and 76bda5e.

📒 Files selected for processing (16)
  • .github/workflows/echidna-verify.yml
  • .gitignore
  • .gitlab-ci.yml
  • CHANGELOG.adoc
  • Justfile
  • docs/proof-debt.md
  • proofs/POSTULATE-AUDIT.adoc
  • proofs/README.adoc
  • proofs/echidna.toml
  • proofs/theories/InformationTheory.agda
  • proofs/theories/InformationTheory/Axioms.agda
  • proofs/theories/InformationTheory/Foundations.agda
  • proofs/trust-base.json
  • scripts/audit-trust-base.py
  • scripts/audit-trust-base.sh
  • scripts/prove-agda.sh
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

hyperpolymath and others added 8 commits October 4, 2026 01:54
…compile-dir

The first live run of the Agda job died with a bare "exit code 42" (Agda's
universal error code) because the script ran agda unguarded under set -eu
inside the stdlib probe — the runner log (the only place the real error text
lives) is not retrievable from this environment, and GitHub annotations
showed nothing. Changes:

- every agda invocation now goes through run_agda: captures combined output,
  echoes it indented to the step log (and thus the artifact), and on failure
  re-emits the first 14 lines as a workflow ::error:: annotation with %0A/
  %25/%3A%3A escaping (annotations are API-readable; raw logs are not);
- `agda --version` no longer aborts the script under set -e (fake-agda
  negative test exposed it);
- probe fallback: on failure with --compile-dir, retry without it (packaged
  pre-compiled .agdai trees can reject a foreign compile-dir); corpus loop
  honours whichever mode won;
- explicit fail-closed ::error:: naming the detected STDLIB_DIR when the
  probe is unsalvageable.

Validated locally with a stub agda (exit 42 + error text): annotations
render escaped and correctly; clean exit 1 from the probe guard.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Live annotation exposed it: the probe module lived in a temp dir outside every
include path, so Agda rejected `module Probe` with its filename check (the
same rule our corpus naming was designed around). Stdlib discovery itself
worked (/usr/share/agda-stdlib detected).

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…tes go next to the source, not into --compile-dir

Live annotation (2nd iteration): with /usr/share/agda-stdlib detected fine, Agda
re-checked Data.Unit.Base (packaged .agdai fingerprint mismatch) and died on
`removeLink: permission denied` rewriting the root-owned interface file. The
initial belief that --compile-dir relocates .agdai outputs is wrong (it only
moves executables), so the gate now copies the stdlib tree into $HOME/.cache
when it is not user-writable and points -i at the mirror. tar preserves
mtimes, so no additional staleness is introduced; the mirror is reused if a
later run finds it already populated.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
….Fin / Data.Vec tops

CI iteration 3 evidence: the run reached `Checking Data.Fin -> Data.Fin.Properties`
and failed on a stdlib-internal without-K/cubical interaction — but the apt
agda-stdlib in noble is 1.7 (tag v1.7: `--without-K --safe` in those files;
the cubical-compatible variant is v1.7.2, not this package). The pragmatic and
strictly cheaper fix: our corpus only needs the constructors/operations, which
live in *.Base — importing the tops drags in Properties machinery we never use
(and, with it, whatever flag archaeology a distro tree hides). Foundations
already compiled clean through Data.Nat/Data.Float/PropEq in the same run, so
those stay as-is.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…safe

Live annotation (iteration 4): "Cannot postulate plogp-nonneg with safe
flag" — Agda inherits *command-line* flags into every imported module and
an OPTIONS pragma cannot revoke them, so passing --safe while checking the
top InformationTheory header poisoned the quarantined Axioms import. The
corpus files carry their flags in `{-# OPTIONS ... #-}` (Theory and
Foundations: --safe --without-K; Axioms: --without-K), and the audit step
independently enforces that safe modules keep their pragma — so per-file
pragmas lose no guarantee while isolating each module's flag set. Probe
aligned (no flag) so stdlib interfaces are built once per run, not twice.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…not import the quarantine

Iteration 5's annotation was the last honest obstacle:
"Importing module InformationTheory.Axioms not using the --safe flag from a
module which does" — Agda's flag inheritance forbids a --safe module to
depend on anything unsafe. Rather than launder the axioms, the corpus now
treats the rule as a feature and encodes it:

- InformationTheory.agda (theorem header): drops its --safe claim, keeps
  --without-K, and says why in its header. It contains no postulates — the
  census still proves that — its --safe-forbidden status is *exactly* the
  statement "this header rests on FFI facts", machine-checked by Agda itself.
- Foundations remains the sole --safe module: every definition, order
  relation and NaN guard carries the real certificate.
- trust-base.json gains `derived_modules`; scripts/audit-trust-base.sh
  registers it exhaustively and adds the inverse honesty check (a derived
  file CLAIMING --safe is now a hard audit failure — negative-tested; and a
  safe module dropping its pragma still fails, so graduation between classes
  is only possible with an honest manifest update).
- README/Axioms headers reworded; layering story = safe definitions /
  quarantine / derived theorems, with the audit's "add to
  safe_modules/derived_modules" guidance.

If the corpus type-checks now (its imports all did), the gate goes green for
the first time in this repo's history.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
First genuine PROOF error in the corpus's history (every import chain now
compiles): Agda reported "blocked on _a_" — the where-bound midpoint module
of jsdF is opaque to the unifier when the matching proof also destructs
under it, so the add-comm/cong₂ chain could not see the shared midpoint.
Two changes, both strictly more transparent:

- Foundations.jsdF inlines `zipWith midpointF ps qs` in both KL terms
  (comment records why: where-clauses become local modules);
- InformationTheory.js-symmetric is rewritten pointfree-end-to-end: each
  endpoint is syntactically the reduct of `jensen-shannon`, with explicit
  arguments to add-comm/zipWith-comm instead of holes, and no local where
  bindings in the proof term either.

Semantics untouched; entropy-nonneg and the sanity lemmas were already
accepted by the compiler in the same pass (the failure report listed only
the js constraints).

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>

Copy link
Copy Markdown
Owner Author

Final state — all own checks green on 9a8c900/76bda5e

Agda — proofs/theories: pass (2m17s) — the corpus has now type-checked in CI for the first time in this repo's history, which also machine-confirms entropy-nonneg and js-symmetric as real proofs. Trusted-base reduction policy and anti-pattern policy flipped from red to green here too; remaining reds (build, lockfile verify, Staleness, Licence, Well-Known, Workflow security linter) are exactly main's pre-existing baseline — untouched by this PR.

The iteration trail (each step driven by the new ::error:: annotation plumbing, since raw logs/artifact CDN are unreachable from this runner environment):

iter finding fix
1 bare exit code 42, zero diagnosis every agda call via run_agda → first 14 lines escaped into annotations
2 probe module rejected by filename↔module-name rule probe needs its own -i $PROBE_DIR
3 removeLink: permission denied on /usr/share/agda-stdlib/*.agdai — .agdai are rewritten next to the source, --compile-dir only moves executables read-only apt tree → writable $HOME/.cache mirror
3.5 Data.Fin.Properties flag-archaeology in the distro stdlib import Data.Fin.Base/Data.Vec.Base (the tops' closure we never used; verified the .Base closures are v1.7-safe)
4 Cannot postulate … with safe flag — command-line --safe leaks into imports and pragmas cannot revoke it drop command-line flags entirely; {-# OPTIONS #-} governs per-module, audit enforces pragma honesty
5 Importing module … not using the --safe flag from a module which does — a --safe header cannot import the quarantine (correctly!) theorem header becomes a registered derived module (derived_modules in trust-base.json + inverse honesty check, negative-tested); Foundations keeps the sole --safe certificate
6 real proof error: blocked on _a_ — jsdF's where-bound midpoint is opaque to unification jsdF written where-free; js-symmetric re-pointed at the exact reducts

Ready for review; nothing here needs the earlier PR #18 branch anymore (that head was auto-deleted at merge).

@sonarqubecloud

sonarqubecloud Bot commented Oct 4, 2026

Copy link
Copy Markdown

@hyperpolymath
hyperpolymath enabled auto-merge (squash) October 4, 2026 02:27
@hyperpolymath
hyperpolymath disabled auto-merge October 4, 2026 06:23
@hyperpolymath
hyperpolymath merged commit bd32bc5 into main Oct 4, 2026
23 of 30 checks passed
@hyperpolymath
hyperpolymath deleted the arena/01a10472-lol branch October 4, 2026 06:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant