| """ |
| enforcement.py — Commitment Conservation Gate |
| |
| The gate is an architectural component, not a post-hoc patch. |
| It sits between the compressor output and the pipeline output. |
| |
| Protocol: |
| 1. Extract commitments from ORIGINAL signal (once, at entry) |
| 2. Compress the signal |
| 3. Extract commitments from compressed output |
| 4. Score fidelity |
| 5. IF fidelity >= threshold: PASS (output compressed) |
| 6. IF fidelity < threshold AND retries remain: |
| - Re-inject missing commitments into input |
| - Re-compress (retry) |
| 7. IF retries exhausted: FALLBACK |
| - Return best attempt seen so far |
| - Log the failure |
| |
| This is NOT "append missing text to the end." |
| That was the v1 bug. Appended text gets stripped on the next |
| compression cycle because the summarizer treats it as low-salience. |
| |
| Instead: re-inject commitments into the INPUT before re-compression, |
| structured as high-salience prefix. The compressor sees them as |
| the most important content on retry. |
| """ |
|
|
| from typing import Set, Optional, Tuple |
| from dataclasses import dataclass, field |
| from .extraction import extract_commitment_texts |
| from .fidelity import fidelity_score, fidelity_breakdown |
| from .compression import CompressionBackend |
|
|
|
|
| @dataclass |
| class GateResult: |
| """Result of passing a signal through the commitment gate.""" |
| output: str |
| passed: bool |
| fidelity: float |
| fidelity_detail: dict |
| attempts: int |
| original_commitments: Set[str] |
| output_commitments: Set[str] |
| missing_commitments: Set[str] |
|
|
|
|
| class CommitmentGate: |
| """ |
| Commitment conservation gate. |
| |
| Wraps a compression backend and enforces commitment preservation |
| through a reject-and-retry loop with structured re-injection. |
| """ |
| |
| def __init__( |
| self, |
| backend: CompressionBackend, |
| threshold: float = 0.6, |
| max_retries: int = 3, |
| ): |
| """ |
| Args: |
| backend: The compression backend to wrap |
| threshold: Minimum fidelity score to pass (0.0 to 1.0) |
| max_retries: Maximum re-injection attempts before fallback |
| """ |
| self.backend = backend |
| self.threshold = threshold |
| self.max_retries = max_retries |
| |
| def compress( |
| self, |
| text: str, |
| original_commitments: Set[str], |
| target_ratio: float = 0.5, |
| ) -> GateResult: |
| """ |
| Compress text through the commitment gate. |
| |
| Args: |
| text: Text to compress (may be original or already-processed) |
| original_commitments: The commitments that MUST be preserved |
| (extracted once from the original signal) |
| target_ratio: Compression target |
| |
| Returns: |
| GateResult with output text, pass/fail, fidelity scores |
| """ |
| best_output = text |
| best_fidelity = 0.0 |
| best_detail = {} |
| |
| current_input = text |
| |
| for attempt in range(1, self.max_retries + 1): |
| |
| compressed = self.backend.compress(current_input, target_ratio) |
| |
| |
| output_commitments = extract_commitment_texts(compressed) |
| detail = fidelity_breakdown(original_commitments, output_commitments) |
| score = detail['min_aggregated'] |
| |
| |
| if score > best_fidelity: |
| best_output = compressed |
| best_fidelity = score |
| best_detail = detail |
| |
| |
| if score >= self.threshold: |
| return GateResult( |
| output=compressed, |
| passed=True, |
| fidelity=score, |
| fidelity_detail=detail, |
| attempts=attempt, |
| original_commitments=original_commitments, |
| output_commitments=output_commitments, |
| missing_commitments=original_commitments - output_commitments, |
| ) |
| |
| |
| missing = original_commitments - output_commitments |
| if missing and attempt < self.max_retries: |
| |
| |
| constraint_block = '. '.join(sorted(missing)) + '. ' |
| current_input = constraint_block + compressed |
| else: |
| |
| break |
| |
| |
| output_commitments = extract_commitment_texts(best_output) |
| return GateResult( |
| output=best_output, |
| passed=False, |
| fidelity=best_fidelity, |
| fidelity_detail=best_detail, |
| attempts=min(attempt, self.max_retries), |
| original_commitments=original_commitments, |
| output_commitments=output_commitments, |
| missing_commitments=original_commitments - output_commitments, |
| ) |
|
|
|
|
| def baseline_compress( |
| backend: CompressionBackend, |
| text: str, |
| target_ratio: float = 0.5, |
| ) -> str: |
| """ |
| Baseline compression — no gate, no enforcement. |
| Just compress and return whatever comes out. |
| """ |
| return backend.compress(text, target_ratio) |
|
|