2026-07-03 08:51:03 +00:00
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from typing import Any
2026-07-03 09:05:06 +00:00
from . agent import component_artifact_dir , run_agent_action , write_decision
2026-07-03 09:24:13 +00:00
from . attestation import load_attestation , validate_attestation
2026-07-03 08:51:03 +00:00
from . audit import scan_hygiene
from . claims import build_claim_card
from . config import RepoConfig , load_config
2026-07-03 09:24:13 +00:00
from . lean import (
LeanCheckResult ,
build_lean_env ,
detect_tools ,
env_script_available ,
lean_check_files ,
resolve_lean_project_dir ,
run_axiom_audit ,
)
2026-07-03 08:51:03 +00:00
from . manifest import discover_layout
from . profiles import get_profile
from . repo import clone_or_fetch , status_for
from . report import render_markdown
from . risk import score_claim_card
from . yamlio import dump_data , load_data
def main ( argv : list [ str ] | None = None ) - > int :
parser = build_parser ( )
args = parser . parse_args ( argv )
try :
return args . func ( args )
except ( KeyError , ValueError , RuntimeError ) as exc :
print ( f " error: { exc } " , file = sys . stderr )
return 2
def build_parser ( ) - > argparse . ArgumentParser :
parser = argparse . ArgumentParser ( prog = " pacta " , description = " Proof-aware crypto tooling evidence interpreter. " )
sub = parser . add_subparsers ( dest = " command " , required = True )
scan = sub . add_parser ( " scan " , help = " Show configured repositories and local verification status. " )
scan . add_argument ( " --config " , required = True )
scan . add_argument ( " --base-dir " , default = " repos " )
scan . add_argument ( " --clone " , action = " store_true " , help = " Clone missing repositories. " )
scan . add_argument ( " --fetch " , action = " store_true " , help = " Fetch existing repositories when cloning/scanning. " )
scan . set_defaults ( func = cmd_scan )
2026-07-03 09:24:13 +00:00
doctor = sub . add_parser ( " doctor " , help = " Diagnose local verifier capabilities for a configured repository. " )
doctor . add_argument ( " --config " , required = True )
doctor . add_argument ( " --repo-name " , required = True )
doctor . add_argument ( " --env-script " )
doctor . add_argument ( " --lean-project-dir " )
doctor . set_defaults ( func = cmd_doctor )
2026-07-03 08:51:03 +00:00
audit = sub . add_parser ( " audit " , help = " Run static proof hygiene checks. " )
audit . add_argument ( " --repo " , required = True )
audit . add_argument ( " --verification-dir " , default = " verification " )
audit . add_argument ( " --config " )
audit . add_argument ( " --repo-name " )
audit . set_defaults ( func = cmd_audit )
lean_check = sub . add_parser ( " lean-check " , help = " Replay portable Lean checks without Linux-only scripts. " )
lean_check . add_argument ( " --repo " , required = True )
lean_check . add_argument ( " --verification-dir " , default = " verification " )
lean_check . add_argument ( " --timeout " , type = int , default = 120 )
lean_check . add_argument ( " --log-dir " , default = " .pacta " )
2026-07-03 09:24:13 +00:00
lean_check . add_argument ( " --env-script " )
lean_check . add_argument ( " --lean-project-dir " )
2026-07-03 08:51:03 +00:00
lean_check . set_defaults ( func = cmd_lean_check )
check = sub . add_parser ( " check " , help = " Alias for lean-check. " )
check . add_argument ( " --repo " , required = True )
check . add_argument ( " --verification-dir " , default = " verification " )
check . add_argument ( " --timeout " , type = int , default = 120 )
check . add_argument ( " --log-dir " , default = " .pacta " )
2026-07-03 09:24:13 +00:00
check . add_argument ( " --env-script " )
check . add_argument ( " --lean-project-dir " )
2026-07-03 08:51:03 +00:00
check . set_defaults ( func = cmd_lean_check )
axiom = sub . add_parser ( " axioms " , help = " Run configured Lean axiom audit. " )
axiom . add_argument ( " --repo " , required = True )
axiom . add_argument ( " --config " , required = True )
axiom . add_argument ( " --repo-name " , required = True )
axiom . add_argument ( " --timeout " , type = int , default = 120 )
axiom . add_argument ( " --log-dir " , default = " .pacta " )
2026-07-03 09:24:13 +00:00
axiom . add_argument ( " --env-script " )
axiom . add_argument ( " --lean-project-dir " )
2026-07-03 08:51:03 +00:00
axiom . set_defaults ( func = cmd_axioms )
claims = sub . add_parser ( " claims " , help = " Generate a machine-readable claim card. " )
claims . add_argument ( " --config " , required = True )
claims . add_argument ( " --repo-name " , required = True )
claims . add_argument ( " --repo " )
claims . add_argument ( " --base-dir " , default = " repos " )
claims . add_argument ( " --out " )
claims . add_argument ( " --offline-fixture " , action = " store_true " )
claims . add_argument ( " --run-axioms " , action = " store_true " )
claims . add_argument ( " --timeout " , type = int , default = 120 )
claims . add_argument ( " --log-dir " , default = " .pacta " )
2026-07-03 09:24:13 +00:00
claims . add_argument ( " --env-script " )
claims . add_argument ( " --lean-project-dir " )
claims . add_argument ( " --attestation " )
claims . add_argument ( " --trust-attestation-provider " )
2026-07-03 08:51:03 +00:00
claims . set_defaults ( func = cmd_claims )
report = sub . add_parser ( " report " , help = " Generate a human-readable Markdown risk report. " )
report . add_argument ( " --claims " )
report . add_argument ( " --config " )
report . add_argument ( " --repo-name " )
report . add_argument ( " --repo " )
report . add_argument ( " --base-dir " , default = " repos " )
report . add_argument ( " --offline-fixture " , action = " store_true " )
report . add_argument ( " --out " )
report . set_defaults ( func = cmd_report )
score = sub . add_parser ( " score " , help = " Score an existing claim card. " )
score . add_argument ( " --claims " , required = True )
score . set_defaults ( func = cmd_score )
2026-07-03 09:05:06 +00:00
agent = sub . add_parser ( " agent " , help = " Apply a policy-gated consequence to verification evidence. " )
agent . add_argument ( " --claims " , help = " Existing claim card to act on. " )
agent . add_argument ( " --config " , help = " Repository config used to generate a claim card. " )
agent . add_argument ( " --repo-name " , help = " Configured repository name. " )
agent . add_argument ( " --repo " , help = " Local repository path. " )
agent . add_argument ( " --base-dir " , default = " repos " )
agent . add_argument ( " --artifact-dir " , default = " artifacts " )
agent . add_argument ( " --action " , choices = [ " build-library " , " build-wallet-demo " ] , default = " build-library " )
agent . add_argument ( " --min-risk " , default = " R3 " )
agent . add_argument ( " --clone " , action = " store_true " , help = " Clone/fetch the configured repository before acting. " )
agent . add_argument ( " --offline-fixture " , action = " store_true " , help = " Use synthetic clean evidence for a showcase run. " )
agent . add_argument ( " --run-axioms " , action = " store_true " , help = " Replay Lean and run axiom audit before acting. " )
agent . add_argument ( " --dry-run " , action = " store_true " )
agent . add_argument ( " --timeout " , type = int , default = 120 )
agent . add_argument ( " --log-dir " , default = " .pacta " )
2026-07-03 09:24:13 +00:00
agent . add_argument ( " --env-script " )
agent . add_argument ( " --lean-project-dir " )
agent . add_argument ( " --attestation " )
agent . add_argument ( " --trust-attestation-provider " )
2026-07-03 09:05:06 +00:00
agent . set_defaults ( func = cmd_agent )
2026-07-03 08:51:03 +00:00
return parser
def cmd_scan ( args : argparse . Namespace ) - > int :
config = load_config ( args . config )
for repo in config . repos :
status = clone_or_fetch ( repo , args . base_dir , fetch = args . fetch ) if args . clone else status_for ( repo , args . base_dir )
marker = " ok " if status . verification_exists else " missing "
print ( f " { repo . name } : { marker } " )
print ( f " url: { repo . url } " )
print ( f " local: { status . local_path } " )
print ( f " verification: { status . verification_dir } " )
print ( f " commit: { status . commit or ' unknown ' } " )
return 0
2026-07-03 09:24:13 +00:00
def cmd_doctor ( args : argparse . Namespace ) - > int :
config = load_config ( args . config )
repo = config . repo_named ( args . repo_name )
env_script = args . env_script or repo . env_script
lean_project_dir = args . lean_project_dir or repo . lean_project_dir
ok_env , env_error = env_script_available ( env_script )
env = build_lean_env ( repo . verification_dir , env_script = env_script ) if ok_env else { }
tools = detect_tools ( env if env else None )
project_dir = resolve_lean_project_dir ( lean_project_dir , env if env else None )
print ( f " repo: { repo . name } " )
print ( f " env_script: { env_script or ' not configured ' } " )
print ( f " env_script_status: { ' ok ' if ok_env else ' missing ' } " )
if env_error :
print ( f " env_script_error: { env_error } " )
print ( f " lean_project_dir: { lean_project_dir or ' not configured ' } " )
print ( f " lean_project_dir_status: { ' ok ' if project_dir else ' missing ' } " )
if project_dir :
print ( f " lean_project_dir_resolved: { project_dir } " )
print ( f " lean: { tools . lean or ' missing ' } " )
print ( f " lake: { tools . lake or ' missing ' } " )
print ( f " lean_version: { tools . lean_version or ' unknown ' } " )
print ( f " lake_version: { tools . lake_version or ' unknown ' } " )
if not ok_env or not project_dir :
print ( " remediation: install or point to the pinned verifier environment, for example --env-script ~/aeneas-toolchain/env.sh --lean-project-dir ' $AENEAS_HOME/backends/lean ' " )
return 1
if not tools . lean and not tools . lake :
print ( " remediation: ensure lean/lake are on PATH after sourcing the verifier environment. " )
return 1
return 0
2026-07-03 08:51:03 +00:00
def cmd_audit ( args : argparse . Namespace ) - > int :
repo_config = _repo_from_optional_config ( args . config , args . repo_name , args . verification_dir )
layout = discover_layout ( args . repo , repo_config . verification_dir )
issues = scan_hygiene ( layout , repo_config . certificates )
for warning in layout . warnings :
print ( f " warning: { warning } " )
if not issues :
print ( " No proof hygiene issues found. " )
return 0
for issue in issues :
print ( f " { issue . severity } : { issue . path } : { issue . line } : { issue . code } : { issue . message } " )
return 1 if any ( issue . severity == " error " for issue in issues ) else 0
def cmd_lean_check ( args : argparse . Namespace ) - > int :
layout = discover_layout ( args . repo , args . verification_dir )
for warning in layout . warnings :
print ( f " warning: { warning } " )
2026-07-03 09:24:13 +00:00
result = lean_check_files (
layout . compile_order ,
layout . verification_dir ,
timeout = args . timeout ,
log_dir = args . log_dir ,
env_script = args . env_script ,
lean_project_dir = args . lean_project_dir ,
)
2026-07-03 08:51:03 +00:00
if not result . attempted :
print ( " ; " . join ( result . diagnostics ) )
return 2
print ( f " checked: { len ( result . checked_files ) } " )
print ( f " failed: { len ( result . failed_files ) } " )
print ( f " log: { result . log_path } " )
return 0 if result . ok else 1
def cmd_axioms ( args : argparse . Namespace ) - > int :
config = load_config ( args . config )
repo = config . repo_named ( args . repo_name )
profile = get_profile ( repo . kind , repo )
layout = discover_layout ( args . repo , repo . verification_dir )
2026-07-03 09:24:13 +00:00
env_script = args . env_script or repo . env_script
lean_project_dir = args . lean_project_dir or repo . lean_project_dir
check_result = lean_check_files (
layout . compile_order ,
layout . verification_dir ,
timeout = args . timeout ,
log_dir = args . log_dir ,
env_script = env_script ,
lean_project_dir = lean_project_dir ,
)
2026-07-03 08:51:03 +00:00
if check_result . log_path :
print ( f " check log: { check_result . log_path } " )
if not check_result . attempted :
print ( " ; " . join ( check_result . diagnostics ) )
return 2
if not check_result . ok :
print ( f " portable Lean replay failed for { len ( check_result . failed_files ) } file(s) " )
result = run_axiom_audit (
Path ( args . repo ) / repo . verification_dir ,
profile . axiom_imports ,
repo . certificates or profile . default_certificates ,
profile . expected_axioms ,
timeout = args . timeout ,
log_dir = args . log_dir ,
2026-07-03 09:24:13 +00:00
env_script = env_script ,
lean_project_dir = lean_project_dir ,
2026-07-03 08:51:03 +00:00
)
for cert in result . certificates :
print ( f " { cert . name } : { cert . status } , axioms= { cert . axiom_status } , observed= { cert . observed_axioms } " )
if result . log_path :
print ( f " log: { result . log_path } " )
return 0 if result . ok and check_result . ok else 1
def cmd_claims ( args : argparse . Namespace ) - > int :
config = load_config ( args . config )
repo = config . repo_named ( args . repo_name )
local_path = Path ( args . repo ) if args . repo else status_for ( repo , args . base_dir ) . local_path
layout = None
axiom_result = None
check_result : LeanCheckResult | None = None
if local_path . exists ( ) :
layout = discover_layout ( local_path , repo . verification_dir )
elif not args . offline_fixture :
raise ValueError ( f " Local repo does not exist: { local_path } " )
if args . run_axioms :
if layout is None :
raise ValueError ( " --run-axioms requires a local repository " )
profile = get_profile ( repo . kind , repo )
2026-07-03 09:24:13 +00:00
env_script = args . env_script or repo . env_script
lean_project_dir = args . lean_project_dir or repo . lean_project_dir
check_result = lean_check_files (
layout . compile_order ,
layout . verification_dir ,
timeout = args . timeout ,
log_dir = args . log_dir ,
env_script = env_script ,
lean_project_dir = lean_project_dir ,
)
2026-07-03 08:51:03 +00:00
axiom_result = run_axiom_audit (
local_path / repo . verification_dir ,
profile . axiom_imports ,
repo . certificates or profile . default_certificates ,
profile . expected_axioms ,
timeout = args . timeout ,
log_dir = args . log_dir ,
2026-07-03 09:24:13 +00:00
env_script = env_script ,
lean_project_dir = lean_project_dir ,
2026-07-03 08:51:03 +00:00
)
2026-07-03 09:24:13 +00:00
attestation = _attestation_for_args ( args , repo )
2026-07-03 08:51:03 +00:00
card = build_claim_card (
repo ,
local_path ,
layout = layout ,
lean_check = check_result ,
axiom_audit = axiom_result ,
2026-07-03 09:24:13 +00:00
attestation = attestation ,
2026-07-03 08:51:03 +00:00
offline_fixture = args . offline_fixture ,
)
if args . out :
dump_data ( card , args . out )
else :
print_yaml ( card )
return 0
def cmd_report ( args : argparse . Namespace ) - > int :
if args . claims :
card = load_data ( args . claims )
else :
if not args . config or not args . repo_name :
raise ValueError ( " report requires --claims or both --config and --repo-name " )
config = load_config ( args . config )
repo = config . repo_named ( args . repo_name )
local_path = Path ( args . repo ) if args . repo else status_for ( repo , args . base_dir ) . local_path
layout = discover_layout ( local_path , repo . verification_dir ) if local_path . exists ( ) else None
card = build_claim_card ( repo , local_path , layout = layout , offline_fixture = args . offline_fixture )
markdown = render_markdown ( card )
if args . out :
Path ( args . out ) . write_text ( markdown , encoding = " utf-8 " )
else :
print ( markdown )
return 0
def cmd_score ( args : argparse . Namespace ) - > int :
card = load_data ( args . claims )
assessment = score_claim_card ( card )
print ( f " { assessment . level } : { assessment . rationale } " )
if assessment . blockers :
print ( " blockers: " )
for blocker in assessment . blockers :
print ( f " - { blocker } " )
return 0
2026-07-03 09:05:06 +00:00
def cmd_agent ( args : argparse . Namespace ) - > int :
card = _card_for_agent ( args )
decision = run_agent_action (
card ,
action = args . action ,
artifact_root = args . artifact_dir ,
minimum_build_risk = args . min_risk ,
timeout = args . timeout ,
dry_run = args . dry_run ,
)
component_dir = component_artifact_dir ( args . artifact_dir , str ( card . get ( " component " ) or " component " ) )
component_dir . mkdir ( parents = True , exist_ok = True )
dump_data ( card , component_dir / " claims.yaml " )
( component_dir / " report.md " ) . write_text ( render_markdown ( card ) , encoding = " utf-8 " )
decision_path = write_decision ( decision , args . artifact_dir , str ( card . get ( " component " ) or " component " ) )
print ( f " action: { decision . requested_action } " )
print ( f " allowed: { str ( decision . allowed ) . lower ( ) } " )
print ( f " risk: { decision . risk_level } " )
print ( f " decision: { decision_path } " )
if decision . artifact :
print ( f " artifact: { decision . artifact . artifact_dir } " )
if decision . artifact . crate_dir :
print ( f " crate: { decision . artifact . crate_dir } " )
if decision . artifact . log_path :
print ( f " log: { decision . artifact . log_path } " )
print ( decision . rationale )
return 0 if decision . allowed else 1
def _card_for_agent ( args : argparse . Namespace ) - > dict [ str , Any ] :
if args . claims :
return load_data ( args . claims )
if not args . config or not args . repo_name :
raise ValueError ( " agent requires --claims or both --config and --repo-name " )
config = load_config ( args . config )
repo = config . repo_named ( args . repo_name )
if args . clone :
status = clone_or_fetch ( repo , args . base_dir , fetch = True )
local_path = status . local_path
else :
local_path = Path ( args . repo ) if args . repo else status_for ( repo , args . base_dir ) . local_path
layout = discover_layout ( local_path , repo . verification_dir ) if local_path . exists ( ) else None
if layout is None and not args . offline_fixture :
raise ValueError ( f " Local repo does not exist: { local_path } " )
check_result = None
axiom_result = None
if args . run_axioms :
if layout is None :
raise ValueError ( " --run-axioms requires a local repository " )
profile = get_profile ( repo . kind , repo )
2026-07-03 09:24:13 +00:00
env_script = args . env_script or repo . env_script
lean_project_dir = args . lean_project_dir or repo . lean_project_dir
check_result = lean_check_files (
layout . compile_order ,
layout . verification_dir ,
timeout = args . timeout ,
log_dir = args . log_dir ,
env_script = env_script ,
lean_project_dir = lean_project_dir ,
)
2026-07-03 09:05:06 +00:00
axiom_result = run_axiom_audit (
local_path / repo . verification_dir ,
profile . axiom_imports ,
repo . certificates or profile . default_certificates ,
profile . expected_axioms ,
timeout = args . timeout ,
log_dir = args . log_dir ,
2026-07-03 09:24:13 +00:00
env_script = env_script ,
lean_project_dir = lean_project_dir ,
2026-07-03 09:05:06 +00:00
)
2026-07-03 09:24:13 +00:00
attestation = _attestation_for_args ( args , repo )
2026-07-03 09:05:06 +00:00
return build_claim_card (
repo ,
local_path ,
layout = layout ,
lean_check = check_result ,
axiom_audit = axiom_result ,
2026-07-03 09:24:13 +00:00
attestation = attestation ,
2026-07-03 09:05:06 +00:00
offline_fixture = args . offline_fixture ,
)
2026-07-03 08:51:03 +00:00
def _repo_from_optional_config ( config_path : str | None , repo_name : str | None , verification_dir : str ) - > RepoConfig :
if config_path :
config = load_config ( config_path )
if not repo_name :
raise ValueError ( " --repo-name is required with --config " )
return config . repo_named ( repo_name )
return RepoConfig ( name = Path ( " . " ) . resolve ( ) . name , verification_dir = verification_dir )
def print_yaml ( data : dict [ str , Any ] ) - > None :
from . yamlio import dumps
print ( dumps ( data ) , end = " " )
2026-07-03 09:24:13 +00:00
def _attestation_for_args ( args : argparse . Namespace , repo : RepoConfig ) :
attestation_path = getattr ( args , " attestation " , None )
if not attestation_path :
return None
raw = load_attestation ( attestation_path )
return validate_attestation ( raw , repo , path = attestation_path , trusted_provider = getattr ( args , " trust_attestation_provider " , None ) )