"""Conversion Event detection and Conversion Attestation generation. TSD §3.5 / PRD FR-7 / G6 / working default Q13: the Conversion Event is a pure fact about the Manifest + Ledger, never a discretionary declaration. `is_converted` and `conversion_status` below never require an attestation document to answer "has this Phase converted?" — the attestation, when generated, is evidence of an already-true fact, not its cause. """ from __future__ import annotations from dataclasses import dataclass from typing import Any from . import fold as fold_module from . import validation @dataclass(frozen=True) class ConversionStatus: """Pure, offline-computable conversion status for a Phase.""" is_converted: bool development_credit: float remission_credit: float outstanding_target: float future_license: str def conversion_status( manifest: dict[str, Any], entries: list[dict[str, Any]] ) -> ConversionStatus: """Compute conversion status directly from Manifest + Ledger. No attestation document is consulted or required. This function is the canonical "has this Phase converted" answer for any conformant tool. """ result = fold_module.fold_phase(manifest, entries) return ConversionStatus( is_converted=result.is_converted, development_credit=result.development_credit, remission_credit=result.remission_credit, outstanding_target=result.outstanding_target, future_license=manifest["phase"]["future_license"], ) def generate_attestation( manifest: dict[str, Any], entries: list[dict[str, Any]], conversion_timestamp: str, ) -> dict[str, Any]: """Build a Conversion Attestation document for an already-converted Phase. Raises ValueError if the Phase has not actually converted (Outstanding Target > 0) — an attestation must never assert a conversion that hasn't happened, and must never be required to make one happen. """ status = conversion_status(manifest, entries) if not status.is_converted: raise ValueError( "cannot generate a Conversion Attestation: Outstanding Target " f"is {status.outstanding_target}, not 0" ) last_entry = entries[-1] if entries else None checkpoint = last_entry["id"] if last_entry else "GENESIS" attestation = { "phase": manifest["phase"]["id"], "milestone_release": manifest["phase"]["milestone_release"]["name"], "conversion_timestamp": conversion_timestamp, "future_license": status.future_license, "final_development_credit": status.development_credit, "final_remission_credit": status.remission_credit, "final_outstanding_target": status.outstanding_target, "ledger_checkpoint": checkpoint, } validation.validate_conversion_attestation(attestation) return attestation