2026-07-03 11:03:58 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
import shutil
|
|
|
|
|
import subprocess
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
from pacta.attestation import validate_attestation
|
|
|
|
|
from pacta.config import RepoConfig
|
|
|
|
|
from pacta.lean import LeanCheckResult, build_lean_env, detect_tools, lean_check_files, resolve_lean_project_dir, run_axiom_audit
|
|
|
|
|
from pacta.manifest import discover_layout
|
|
|
|
|
from pacta.profiles import get_profile
|
|
|
|
|
from pacta.repo import git_commit
|
|
|
|
|
from pacta.signing import sign_attestation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_attestation(
|
|
|
|
|
repo: RepoConfig,
|
|
|
|
|
repo_path: str | Path,
|
|
|
|
|
provider: str,
|
|
|
|
|
private_key: str | Path,
|
|
|
|
|
public_key: str | Path,
|
|
|
|
|
env_script: str | Path | None = None,
|
|
|
|
|
lean_project_dir: str | Path | None = None,
|
|
|
|
|
timeout: int = 120,
|
|
|
|
|
log_dir: str | Path = "provider/out/logs",
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
path = Path(repo_path)
|
|
|
|
|
profile = get_profile(repo.kind, repo)
|
|
|
|
|
layout = discover_layout(path, repo.verification_dir)
|
|
|
|
|
check = lean_check_files(
|
|
|
|
|
layout.compile_order,
|
|
|
|
|
layout.verification_dir,
|
|
|
|
|
timeout=timeout,
|
|
|
|
|
log_dir=log_dir,
|
|
|
|
|
env_script=env_script or repo.env_script,
|
|
|
|
|
lean_project_dir=lean_project_dir or repo.lean_project_dir,
|
|
|
|
|
)
|
|
|
|
|
axiom = None
|
|
|
|
|
if check.attempted and check.ok:
|
|
|
|
|
axiom = run_axiom_audit(
|
|
|
|
|
path / repo.verification_dir,
|
|
|
|
|
profile.axiom_imports,
|
|
|
|
|
repo.certificates or profile.default_certificates,
|
|
|
|
|
profile.expected_axioms,
|
|
|
|
|
timeout=timeout,
|
|
|
|
|
log_dir=log_dir,
|
|
|
|
|
env_script=env_script or repo.env_script,
|
|
|
|
|
lean_project_dir=lean_project_dir or repo.lean_project_dir,
|
Estate sync: boundary-axiom vocabulary + the four-tier apex reality (R4)
The verified corpus completed its phase 2 on 2026-07-06: every ed25519
fork now carries FOUR button-enforced apex tiers up to the full lift
(accept <=> decompress(R) = [k](-A)+[s]B as points), the complete scalar
layer, and the constructive encoding/decoding chain. pacta was calibrated
to the pre-apex corpus and - worse - had no vocabulary for
boundary-audited certificates: its axiom audit knew only "clean = exactly
the three standard axioms", so the apex tiers would have scored dirty.
New vocabulary:
- Profile.certificate_axioms: per-certificate ALLOWED axiom sets;
expected_axioms_for(cert) resolves each certificate's own boundary.
- RepoConfig.apex_boundary: a simple per-fork key (dalek-wrappers /
hash3 / anza) expanded by the ed25519 profile into the exact
per-tier allowed sets. AUTHORITY NOTE in profiles/ed25519.py: each
repo's check.sh Phase 3b is the enforcement point; if the button and
this table disagree, the button wins.
- run_axiom_audit compares each certificate against ITS allowed set;
deviation in EITHER direction (extra axiom or missing boundary
axiom) is dirty.
New risk reality:
- R4 is now reachable: full four-tier apex + constructive chain +
scalar arithmetic, all proven with cones pinned to their documented
boundaries. R4 always carries explicit residual blockers (SHA-512
oracle, hypothesis-parametric wire parses, translation faithfulness,
no side-channel/build assurance - those gate R5).
- R3 unchanged (arithmetic pair) and now explains exactly which apex
certificates are missing for R4.
Attestation trust model hardened:
- The provider is trusted for its OBSERVATION, never its VERDICT:
axiom_status is re-derived locally from observed_axioms against the
agent's own boundary policy. A provider that labels a dirty cone
"clean" gains nothing; "proven" with no observed axioms is
"unverifiable".
- Partial attestations degrade instead of being rejected: uncovered
certificates stay unproven and the score caps accordingly (an
arithmetic-only attestation still authorizes an R3 library capsule,
never a wallet).
Also: scripts/mini_pytest.py - a dependency-free test runner (tmp_path,
raises, monkeypatch, capsys) for hosts without pytest; examples
regenerated FROM the tool (dalek/anza fixtures now R4, 16 certs; new
full four-tier attestation example); tests updated + new
tests/test_boundaries.py (lying-provider, missing-boundary-axiom,
partial-coverage cases). 40/40 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:04:43 +00:00
|
|
|
certificate_axioms=profile.certificate_axioms,
|
2026-07-03 11:03:58 +00:00
|
|
|
)
|
|
|
|
|
certs = [
|
|
|
|
|
{
|
|
|
|
|
"name": cert.name,
|
|
|
|
|
"status": cert.status,
|
|
|
|
|
"axiom_status": cert.axiom_status,
|
|
|
|
|
"observed_axioms": cert.observed_axioms,
|
|
|
|
|
"expected_axioms": cert.expected_axioms,
|
|
|
|
|
"diagnostics": cert.diagnostics,
|
|
|
|
|
}
|
|
|
|
|
for cert in axiom.certificates
|
|
|
|
|
]
|
|
|
|
|
else:
|
|
|
|
|
certs = _failed_certificates(repo, profile.expected_axioms, check)
|
|
|
|
|
provider_env = build_lean_env(path / repo.verification_dir, env_script=env_script or repo.env_script)
|
|
|
|
|
project_dir = resolve_lean_project_dir(lean_project_dir or repo.lean_project_dir, provider_env)
|
|
|
|
|
tools = detect_tools(provider_env)
|
|
|
|
|
lean_version = _project_version(["lake", "env", "lean", "--version"], project_dir, provider_env) or tools.lean_version
|
|
|
|
|
lake_version = _project_version(["lake", "--version"], project_dir, provider_env) or tools.lake_version
|
|
|
|
|
unsigned = {
|
|
|
|
|
"schema_version": 1,
|
|
|
|
|
"provider": provider,
|
|
|
|
|
"issued_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
|
|
|
|
|
"subject": {
|
|
|
|
|
"component": repo.name,
|
|
|
|
|
"repo_url": repo.url,
|
|
|
|
|
"repo_commit": git_commit(path),
|
|
|
|
|
"verification_dir": repo.verification_dir,
|
|
|
|
|
"kind": repo.kind,
|
|
|
|
|
"verified_backend": repo.verified_backend,
|
|
|
|
|
},
|
|
|
|
|
"environment": {
|
|
|
|
|
"lean_version": lean_version,
|
|
|
|
|
"lake_version": lake_version,
|
|
|
|
|
"env_script": str(env_script or repo.env_script or ""),
|
|
|
|
|
"lean_project_dir": str(project_dir or lean_project_dir or repo.lean_project_dir or ""),
|
|
|
|
|
},
|
|
|
|
|
"replay": {
|
|
|
|
|
"check_attempted": check.attempted,
|
|
|
|
|
"check_ok": check.ok,
|
|
|
|
|
"check_log_path": check.log_path,
|
|
|
|
|
"checked_files": len(check.checked_files),
|
|
|
|
|
"failed_files": check.failed_files,
|
|
|
|
|
"diagnostics": check.diagnostics,
|
|
|
|
|
"axiom_attempted": axiom.attempted if axiom else False,
|
|
|
|
|
"axiom_ok": axiom.ok if axiom else False,
|
|
|
|
|
"axiom_log_path": axiom.log_path if axiom else None,
|
|
|
|
|
"axiom_diagnostics": axiom.diagnostics if axiom else [],
|
|
|
|
|
},
|
|
|
|
|
"certificates": certs,
|
|
|
|
|
}
|
|
|
|
|
signed = sign_attestation(unsigned, private_key, public_key)
|
|
|
|
|
# Self-check before emitting a certificate.
|
|
|
|
|
validation = validate_attestation(signed, repo, trusted_provider=provider, public_key_path=public_key)
|
|
|
|
|
if not validation.accepted:
|
|
|
|
|
signed.setdefault("provider_warnings", []).extend(validation.diagnostics)
|
|
|
|
|
return signed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _failed_certificates(repo: RepoConfig, expected_axioms: list[str], check: LeanCheckResult) -> list[dict[str, Any]]:
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
"name": name,
|
|
|
|
|
"status": "failed" if check.attempted else "unknown",
|
|
|
|
|
"axiom_status": "not_checked",
|
|
|
|
|
"observed_axioms": [],
|
|
|
|
|
"expected_axioms": expected_axioms,
|
|
|
|
|
}
|
|
|
|
|
for name in repo.certificates
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _project_version(cmd: list[str], cwd: Path | None, env: dict[str, str]) -> str | None:
|
|
|
|
|
executable = shutil.which(cmd[0], path=env.get("PATH"))
|
|
|
|
|
if not executable:
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
completed = subprocess.run(
|
|
|
|
|
[executable, *cmd[1:]],
|
|
|
|
|
check=False,
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
timeout=20,
|
|
|
|
|
cwd=str(cwd) if cwd else None,
|
|
|
|
|
env=env,
|
|
|
|
|
)
|
|
|
|
|
except (OSError, subprocess.TimeoutExpired):
|
|
|
|
|
return None
|
|
|
|
|
if completed.returncode != 0:
|
|
|
|
|
return None
|
|
|
|
|
return (completed.stdout or completed.stderr).strip() or None
|