Skip to content
44 changes: 40 additions & 4 deletions src/vouch/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
import yaml

from . import audit
from .storage import sha256_hex
from .models import Claim, Entity, Evidence, Proposal, Relation, Session, Source
from .storage import _deserialize_page, sha256_hex

MANIFEST_NAME = "manifest.json"
SPEC_VERSION = "vouch-bundle-0.1"
Expand All @@ -38,6 +39,17 @@
"evidence", "sessions", "decided",
)

VALIDATORS: dict[str, Any] = {
"claims": lambda data: Claim.model_validate(yaml.safe_load(data)),
"pages": lambda data: _deserialize_page(data.decode()),
"sources": lambda data: Source.model_validate(yaml.safe_load(data)),
"entities": lambda data: Entity.model_validate(yaml.safe_load(data)),
"relations": lambda data: Relation.model_validate(yaml.safe_load(data)),
"evidence": lambda data: Evidence.model_validate(yaml.safe_load(data)),
"sessions": lambda data: Session.model_validate(yaml.safe_load(data)),
"decided": lambda data: Proposal.model_validate(yaml.safe_load(data)),
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.


# --- export ---------------------------------------------------------------

Expand Down Expand Up @@ -163,6 +175,17 @@ class ImportCheckResult:
issues: list[str]


def _validate_content(path: str, data: bytes, issues: list[str]) -> None:
subdir = path.split("/")[0]
validator = VALIDATORS.get(subdir)
if validator is None:
return
try:
validator(data)
except Exception as e:
issues.append(f"schema validation failed: {path}: {e}")


def import_check(kb_dir: Path, bundle_path: Path) -> ImportCheckResult:
"""Diff a bundle against the destination KB without writing anything."""
new_files: list[str] = []
Expand All @@ -180,15 +203,22 @@ def import_check(kb_dir: Path, bundle_path: Path) -> ImportCheckResult:
)
manifest = json.loads(tar.extractfile(mf_member).read().decode()) # type: ignore[union-attr]
bundle_id = manifest.get("bundle_id", "")
manifest_paths = {f["path"] for f in manifest["files"]}
for f in manifest["files"]:
dest = kb_dir / f["path"]
if not dest.exists():
new_files.append(f["path"])
continue
if sha256_hex(dest.read_bytes()) == f["sha256"]:
elif sha256_hex(dest.read_bytes()) == f["sha256"]:
identical.append(f["path"])
else:
conflicts.append(f["path"])
for member in tar.getmembers():
if member.name == MANIFEST_NAME or not member.isfile():
continue
if member.name not in manifest_paths:
continue
body = tar.extractfile(member).read() # type: ignore[union-attr]
_validate_content(member.name, body, issues)

return ImportCheckResult(
ok=True, bundle_id=bundle_id,
Expand Down Expand Up @@ -236,7 +266,13 @@ def import_apply(
skipped.append(member.name)
continue
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_bytes(tar.extractfile(member).read()) # type: ignore[union-attr]
body = tar.extractfile(member).read() # type: ignore[union-attr]
val_issues: list[str] = []
_validate_content(member.name, body, val_issues)
if val_issues:
skipped.append(member.name)
continue
dest.write_bytes(body)
written.append(member.name)
result = {
"bundle_id": check.bundle_id,
Expand Down
4 changes: 2 additions & 2 deletions src/vouch/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from . import audit
from .models import Source
from .storage import KBStore, sha256_hex
from .storage import ArtifactNotFoundError, KBStore, sha256_hex


@dataclass
Expand All @@ -27,7 +27,7 @@ class VerificationResult:
def verify_source(store: KBStore, source: Source) -> VerificationResult:
try:
body = store.read_source_content(source.id)
except FileNotFoundError:
except ArtifactNotFoundError:
return VerificationResult(source=source, stored_ok=False,
external_status="n/a", note="stored content missing")
stored_ok = sha256_hex(body) == source.id
Expand Down
Loading