mirror of
https://github.com/velocitatem/cvfs.git
synced 2026-05-31 16:53:38 +00:00
45 lines
1.5 KiB
Python
45 lines
1.5 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Iterable
|
|
|
|
from .schema import PatchPayload, PatchOperation, StructuredDocument
|
|
|
|
|
|
class PatchValidationError(ValueError):
|
|
pass
|
|
|
|
|
|
def validate_patchset(
|
|
document: StructuredDocument,
|
|
patches: Iterable[PatchPayload],
|
|
*,
|
|
max_changes: int = 12,
|
|
max_growth_ratio: float = 1.45,
|
|
) -> None:
|
|
patch_list = list(patches)
|
|
if len(patch_list) > max_changes:
|
|
raise PatchValidationError(
|
|
f"Patchset exceeds max changes ({len(patch_list)} > {max_changes})"
|
|
)
|
|
block_map = {block.path: block for block in document.blocks}
|
|
for patch in patch_list:
|
|
block = block_map.get(patch.target_path)
|
|
if not block:
|
|
raise PatchValidationError(
|
|
f"Target path {patch.target_path} does not exist in base document"
|
|
)
|
|
if patch.operation == PatchOperation.REPLACE_TEXT:
|
|
if not patch.new_value:
|
|
raise PatchValidationError("replace_text requires new_value")
|
|
baseline = len(block.text.strip()) or 1
|
|
if len(patch.new_value.strip()) / baseline > max_growth_ratio:
|
|
raise PatchValidationError("Patch grows text beyond ATS safe threshold")
|
|
if (
|
|
patch.operation
|
|
in {PatchOperation.REMOVE_BLOCK, PatchOperation.SUPPRESS_BLOCK}
|
|
and block.block_type == "heading"
|
|
):
|
|
raise PatchValidationError(
|
|
"Headings cannot be removed without manual confirmation"
|
|
)
|