Load an existing manifest from disk. Raises ``FileNotFoundError`` if the manifest does not exist.
(
cls,
key: str,
project_root: Path,
*,
resolve_project_root: bool = True,
)
| 424 | |
| 425 | @classmethod |
| 426 | def load( |
| 427 | cls, |
| 428 | key: str, |
| 429 | project_root: Path, |
| 430 | *, |
| 431 | resolve_project_root: bool = True, |
| 432 | ) -> IntegrationManifest: |
| 433 | """Load an existing manifest from disk. |
| 434 | |
| 435 | Raises ``FileNotFoundError`` if the manifest does not exist. |
| 436 | """ |
| 437 | inst = cls(key, project_root, resolve_project_root=resolve_project_root) |
| 438 | path = inst.manifest_path |
| 439 | try: |
| 440 | data = json.loads(path.read_text(encoding="utf-8")) |
| 441 | except json.JSONDecodeError as exc: |
| 442 | raise ValueError( |
| 443 | f"Integration manifest at {path} contains invalid JSON" |
| 444 | ) from exc |
| 445 | |
| 446 | if not isinstance(data, dict): |
| 447 | raise ValueError( |
| 448 | f"Integration manifest at {path} must be a JSON object, " |
| 449 | f"got {type(data).__name__}" |
| 450 | ) |
| 451 | |
| 452 | files = data.get("files", {}) |
| 453 | if not isinstance(files, dict) or not all( |
| 454 | isinstance(k, str) and isinstance(v, str) for k, v in files.items() |
| 455 | ): |
| 456 | raise ValueError( |
| 457 | f"Integration manifest 'files' at {path} must be a " |
| 458 | "mapping of string paths to string hashes" |
| 459 | ) |
| 460 | |
| 461 | inst.version = data.get("version", "") |
| 462 | inst._installed_at = data.get("installed_at", "") |
| 463 | inst._files = files |
| 464 | |
| 465 | recovered = data.get("recovered_files", []) |
| 466 | if not isinstance(recovered, list) or not all( |
| 467 | isinstance(p, str) for p in recovered |
| 468 | ): |
| 469 | raise ValueError( |
| 470 | f"Integration manifest 'recovered_files' at {path} must be a " |
| 471 | "list of string paths" |
| 472 | ) |
| 473 | inst._recovered_files = set(recovered) |
| 474 | # Drop any recovered_files entries that don't correspond to tracked |
| 475 | # files — defensive against externally-edited or partially-corrupted |
| 476 | # manifests. Inconsistent state self-corrects on next save(). |
| 477 | inst._recovered_files &= set(inst._files.keys()) |
| 478 | |
| 479 | stored_key = data.get("integration", "") |
| 480 | if stored_key and stored_key != key: |
| 481 | raise ValueError( |
| 482 | f"Manifest at {path} belongs to integration {stored_key!r}, " |
| 483 | f"not {key!r}" |
no test coverage detected