|
17 | 17 |
|
18 | 18 | ROOT = Path(__file__).resolve().parents[1] |
19 | 19 | FIRST_PARTY_CATALOG = ROOT / "catalog" / "first-party" / "catalog.v1.json" |
20 | | -EXTERNAL_CATALOG = ROOT / "catalog" / "external" / "approved-skills.v1.json" |
| 20 | +EXTERNAL_CATALOGS = ( |
| 21 | + ROOT / "catalog" / "external" / "approved-skills.v1.json", |
| 22 | + ROOT / "catalog" / "external" / "source-candidates.v1.json", |
| 23 | +) |
21 | 24 | AGENT_CATALOG = ROOT / "library" / "organization" / "agents" / "catalog.json" |
22 | 25 | SCHEMA_PATH = ROOT / "catalog" / "schemas" / "aether.provenance-catalog.v1.schema.json" |
23 | 26 | REPOSITORY_LICENSE = "MIT" |
@@ -97,7 +100,7 @@ def _publishability(record: dict[str, Any]) -> tuple[bool, list[str]]: |
97 | 100 |
|
98 | 101 | if not revision or revision == "unknown" or REVISION_RE.fullmatch(revision) is None: |
99 | 102 | reasons.append("source revision is not an immutable commit or semantic version") |
100 | | - if digest.get("algorithm") not in {"sha256-utf8-lf", "sha256-tree"}: |
| 103 | + if digest.get("algorithm") not in {"sha256-utf8-lf", "sha256-tree", "sha256-bytes"}: |
101 | 104 | reasons.append("source digest algorithm is unsupported") |
102 | 105 | if not isinstance(digest.get("value"), str) or HEX_64_RE.fullmatch(digest["value"]) is None: |
103 | 106 | reasons.append("source digest is missing or invalid") |
@@ -272,15 +275,19 @@ def _external_lifecycle(entry: dict[str, Any]) -> dict[str, str]: |
272 | 275 |
|
273 | 276 | def build_external() -> dict[str, Any]: |
274 | 277 | """Build the normalized external provenance catalog.""" |
275 | | - source_catalog = load_json(EXTERNAL_CATALOG) |
276 | | - entries = source_catalog.get("entries") |
277 | | - if not isinstance(entries, list): |
278 | | - raise ValueError(f"{EXTERNAL_CATALOG} entries must be an array") |
| 278 | + entries: list[dict[str, Any]] = [] |
| 279 | + for catalog_path in EXTERNAL_CATALOGS: |
| 280 | + source_catalog = load_json(catalog_path) |
| 281 | + candidate_entries = source_catalog.get("entries") |
| 282 | + if not isinstance(candidate_entries, list): |
| 283 | + raise ValueError(f"{catalog_path} entries must be an array") |
| 284 | + for entry in candidate_entries: |
| 285 | + if not isinstance(entry, dict): |
| 286 | + raise ValueError(f"{catalog_path} entries must be objects") |
| 287 | + entries.append(entry) |
279 | 288 |
|
280 | 289 | records: list[dict[str, Any]] = [] |
281 | 290 | for entry in entries: |
282 | | - if not isinstance(entry, dict): |
283 | | - raise ValueError("external catalog entries must be objects") |
284 | 291 | source_type = str(entry.get("source_type", "other")) |
285 | 292 | if source_type not in {"skill", "specification", "agent", "prompt", "instruction"}: |
286 | 293 | raise ValueError(f"unsupported external source_type: {source_type}") |
@@ -402,12 +409,14 @@ def check(scope: str) -> list[str]: |
402 | 409 | errors.append(f"missing canonical agent records: {missing}") |
403 | 410 |
|
404 | 411 | if scope in {"external", "all"}: |
405 | | - external_catalog = load_json(EXTERNAL_CATALOG) |
406 | | - expected_external = { |
407 | | - str(entry.get("id")) |
408 | | - for entry in external_catalog.get("entries", []) |
409 | | - if isinstance(entry, dict) |
410 | | - } |
| 412 | + expected_external = set() |
| 413 | + for catalog_path in EXTERNAL_CATALOGS: |
| 414 | + external_catalog = load_json(catalog_path) |
| 415 | + expected_external.update( |
| 416 | + str(entry.get("id")) |
| 417 | + for entry in external_catalog.get("entries", []) |
| 418 | + if isinstance(entry, dict) |
| 419 | + ) |
411 | 420 | present = { |
412 | 421 | record["id"] for record in first["records"] if record.get("party") == "external" |
413 | 422 | } |
|
0 commit comments