Documentation

CatCryptCore.Tactics.ProofTermAudit

ProofTermAudit — machine-checked shell / vacuity detection over proof terms #

This module ports the deterministic (zero-LLM-token) core of facebookresearch/autoform-bot's autoform/eval/ subsystem to CatCrypt. autoform-bot grades autoformalizations by introspecting the compiled proof term rather than the surface syntax; we reuse the same idea to machine-check the project's shell-vs-genuine-reduction distinction.

CatCrypt's existing audit (scripts/audit_security.lean) detects "shell" theorems via a docstring-tag convention ((composition plumbing), (concurrent UC via ITreeN), …) and via text scanning of theorem signatures. Those are convention-dependent: a theorem mislabelled in its docstring, or whose body silently became trivial, slips through. This module inspects the elaborated Expr of a declaration's proof term and reports structural tags that no comment can fake.

Tags #

Each tag is computed purely from the proof term e := value? of a declaration, after stripping leading fun-binders (stripLambdas). Let nl be the number of stripped binders (the declaration's parameters and hypotheses) and body the head-beta-reduced lambda body.

TagDetection (implemented)
vacuous_bodybody's head constant is True.intro / trivial / And.intro of True.intros, or body is a rfl-proof (Eq.refl / rfl) at a propositionally trivial head — i.e. the proof carries no hypothesis-dependent content.
returns_assumptionbody is exactly a bvar referring to one of the nl binders — the proof fun … hᵢ … => hᵢ directly returns a hypothesis.
trivial_constructorbody's head is a structure/inductive constructor (ConstructorVal) and the constructor has no explicit (non-instance, non-Prop-erased) data arguments derived from the binders — a content-free constructor application (e.g. ⟨⟩ : True, PUnit.unit, Unit.unit).
field_projection_bodybody is a .proj, or its head is a projection function (Environment.getProjectionFnInfo?) — the proof is "read a field", not a derivation.
proof_by_subsingletonbody's head constant is Subsingleton.elim / Subsingleton.allEq / proof_irrel.
proof_by_exfalsobody's head constant is False.elim / absurd / False.rec / Empty.elim — goal derived from a contradiction on an assumption.
ignores_paramsAt least one of the nl stripped binders does not occur in body. (Reported with the used/total count.) This is the structural analogue of autoform-bot's "the proof does not use its premises" check. Note: a binder may legitimately appear only in the type of a later binder; this tag is advisory, surfaced together with the stronger returns_assumption / vacuous_body signals rather than failing on its own.

Stubbed / partial #

TagStatus
custom_hypothesis_in_typePartial. Full autoform-bot semantics ("the statement carries a suspicious custom hypothesis that trivializes the goal") requires modelling which hypotheses are load-bearing for the conclusion — undecidable in general. We implement a conservative proxy: flag declarations whose type contains a hypothesis of the form (h : P) → P (an identity implication a returns_assumption body would discharge), or whose conclusion is defeq to True. Anything subtler is deferred.

Shell classification #

shellTags is the subset that indicates a content-free proof: vacuous_body, returns_assumption, trivial_constructor. The audit driver treats a declaration as a potential undocumented shell when it carries a shell tag but its docstring lacks an honest (composition plumbing) / sdist_self marker, and as a docstring/body mismatch when the docstring claims a genuine result yet the body is vacuous_body / returns_assumption.

A genuine reduction (e.g. one that applys a composition lemma and then discharges its premise from the hypotheses, like UCEmulates_of_sdist followed by exact hkdf_sdist …) elaborates to a term whose head is the composition lemma applied to the binders, so it is not tagged — exactly the discrimination the feedback_no_genuine_reduction memory and the UC docstring convention are guarding.

Usage #

Future work (NOT implemented this increment) #

A structural tag computed from a declaration's proof term.

  • vacuousBody : Tag
  • returnsAssumption : Tag
  • trivialConstructor : Tag
  • fieldProjectionBody : Tag
  • proofBySubsingleton : Tag
  • proofByExfalso : Tag
  • ignoresParams (used total : Nat) : Tag

    carries (used, total) binder counts

  • customHypothesisInType : Tag

    partial detector (see module doc)

Instances For
    Equations
    • One or more equations did not get rendered due to their size.
    Instances For

      Term-inspection helpers #

      Strip leading fun-binders, returning their count and the body.

      Collect the de-Bruijn indices (relative to the body, i.e. counting the stripped lambdas as the outermost binders) of every free bvar occurrence. extra tracks binders introduced inside body.

      Count how many of the nl outermost binders occur in body.

      Equations
      Instances For

        Is n one of the recognised "vacuous proof" head constants?

        Equations
        Instances For

          Is n a subsingleton-elimination head?

          Equations
          Instances For

            Is n an ex-falso head?

            Equations
            Instances For

              Is n a recognised content-free rfl-proof head? Eq.refl / rfl / HEq.refl / Iff.rfl — these carry no hypothesis-dependent content.

              Equations
              Instances For

                Does body's head resolve to a structure/inductive constructor with no explicit data argument that comes from a binder? Such an application (e.g. PUnit.unit, ⟨⟩ : True) is a trivial constructor.

                Equations
                • One or more equations did not get rendered due to their size.
                Instances For

                  Is body (or its head) a structure-field projection?

                  Equations
                  • One or more equations did not get rendered due to their size.
                  Instances For

                    Type-side (partial) detector #

                    Walk a forall-telescope syntactically (no whnf/isDefEq), returning the list of binder domains and the final conclusion. Cheap and total — deliberately avoids the reduction cost that makes a MetaM telescope blow heartbeats on heavy crypto types.

                    Conservative, syntactic custom_hypothesis_in_type proxy: scan the type's pi-telescope for a hypothesis binder whose domain is structurally identical to the conclusion (an (h : P) → … → P identity implication a returns_assumption body would discharge), or a conclusion that is the constant True. Purely syntactic so it never reduces (and never blows heartbeats); subtler defeq cases are deferred (see module doc).

                    Equations
                    • One or more equations did not get rendered due to their size.
                    Instances For

                      The tagger #

                      Compute the structural tags for declaration n. Returns #[] for declarations with no inspectable value (axioms, opaques) — they are not the subject of this audit.

                      Equations
                      • One or more equations did not get rendered due to their size.
                      Instances For

                        Interactive command #

                        #proof_term_tags foo logs the structural shell/vacuity tags of foo.

                        Equations
                        • One or more equations did not get rendered due to their size.
                        Instances For