#!/usr/bin/env python3 """Channel registry, canonical hashing, and internal-preview contracts.""" from __future__ import annotations import io import json import os import re import shutil import stat import subprocess import tempfile import unittest from contextlib import redirect_stderr, redirect_stdout from pathlib import Path from unittest import mock from _test_support import load_module SCRIPTS = Path(__file__).resolve().parent.parent PLUGIN_ROOT = SCRIPTS.parent REPO_ROOT = PLUGIN_ROOT.parents[2] REGISTRY_PATH = SCRIPTS / "capability_registry.py" CATALOG_PATH = SCRIPTS / "discovery_catalog.py" MANIFEST_PATH = PLUGIN_ROOT / "catalog/public-release-manifest.json" NOTICE = "INTERNAL PREVIEW — not publicly supported" class CapabilityRegistryTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.registry = load_module(REGISTRY_PATH, "capability_registry_under_test") cls.catalog = load_module(CATALOG_PATH, "channel_catalog_under_test") def test_canonical_tree_hash_is_order_independent_and_tracks_bytes_type_and_execute_bit(self): with tempfile.TemporaryDirectory() as td: root = Path(td) / "skill" root.mkdir() (root / "z.txt").write_bytes(b"z\x00bytes") (root / "a.txt").write_bytes(b"alpha") first = self.registry.canonical_tree_sha256(root) self.assertEqual(first, self.registry.canonical_tree_sha256(root)) (root / "a.txt").chmod((root / "a.txt").stat().st_mode | stat.S_IXUSR) executable = self.registry.canonical_tree_sha256(root) self.assertNotEqual(first, executable) (root / "a.txt").chmod((root / "a.txt").stat().st_mode & ~0o111) self.assertEqual(first, self.registry.canonical_tree_sha256(root)) (root / "z.txt").write_bytes(b"changed") self.assertNotEqual(first, self.registry.canonical_tree_sha256(root)) def test_hash_rejects_special_files_and_unsafe_symlinks(self): with tempfile.TemporaryDirectory() as td: root = Path(td) / "skill" root.mkdir() (root / "SKILL.md").write_text("safe", encoding="utf-8") (root / "outside").symlink_to(Path(td).parent) with self.assertRaisesRegex(self.registry.RegistryError, "symlink"): self.registry.canonical_tree_sha256(root) (root / "outside").unlink() fifo = root / "pipe" os.mkfifo(fifo) with self.assertRaisesRegex(self.registry.RegistryError, "special"): self.registry.canonical_tree_sha256(root) def _public_checkout_fixture(self, root: Path, origin: str) -> Path: checkout = root / "checkout" checkout.mkdir() subprocess.run(["git", "init", "-q", str(checkout)], check=True) subprocess.run(["git", "-C", str(checkout), "config", "user.email", "fixture@example.invalid"], check=True) subprocess.run(["git", "-C", str(checkout), "config", "user.name", "Fixture"], check=True) skill = checkout / "skills/platform-widget-search" skill.mkdir(parents=True) skill.joinpath("SKILL.md").write_text( '---\nname: platform-widget-search\ndescription: "Use this public fixture to search for platform widgets safely and deterministically."\n---\nbody\n', encoding="utf-8", ) subprocess.run(["git", "-C", str(checkout), "add", "."], check=True) subprocess.run(["git", "-C", str(checkout), "commit", "-qm", "fixture"], check=True) subprocess.run( ["git", "-C", str(checkout), "tag", "--no-sign", "-m", "fixture", "1.32.0"], check=True, ) subprocess.run(["git", "-C", str(checkout), "remote", "add", "origin", origin], check=True) return checkout def test_public_snapshot_rejects_ignored_entries_under_skills(self): with tempfile.TemporaryDirectory() as td: checkout = self._public_checkout_fixture( Path(td), "https://github.com/forcedotcom/sf-skills.git" ) checkout.joinpath(".git/info/exclude").write_text("skills/**/ignored.bin\n", encoding="utf-8") checkout.joinpath("skills/platform-widget-search/ignored.bin").write_bytes(b"absent from commit") with self.assertRaisesRegex(self.registry.RegistryError, "tracked git tree"): self.registry.build_public_manifest(checkout, "1.32.0") def test_public_origin_normalizes_supported_github_forms_without_echoing_tokens(self): accepted = ( "https://github.com/forcedotcom/sf-skills.git", "https://github.com/forcedotcom/sf-skills", "git@github.com:forcedotcom/sf-skills.git", "ssh://git@github.com/forcedotcom/sf-skills.git", "https://x-access-token:do-not-echo@github.com/forcedotcom/sf-skills.git", ) for origin in accepted: with self.subTest(origin=origin): self.assertEqual(self.registry.normalize_public_repository(origin), self.registry.PUBLIC_REPOSITORY) for origin in ( "https://github.com/other/sf-skills.git", "https://gitlab.com/forcedotcom/sf-skills.git", "http://github.com/forcedotcom/sf-skills.git", ): with self.subTest(origin=origin): with self.assertRaises(self.registry.RegistryError) as caught: self.registry.normalize_public_repository(origin) self.assertNotIn(origin, str(caught.exception)) self.assertNotIn("do-not-echo", str(caught.exception)) def test_public_release_ref_is_strict_and_resolves_to_recorded_commit(self): with tempfile.TemporaryDirectory() as td: checkout = self._public_checkout_fixture(Path(td), "git@github.com:forcedotcom/sf-skills.git") manifest = self.registry.build_public_manifest(checkout, "1.32.0") self.assertEqual(manifest["releaseRef"], "1.32.0") self.assertEqual(manifest["repository"], self.registry.PUBLIC_REPOSITORY) for release_ref in ("v1.32.0", "main", "1.32", "1.32.0^{commit}"): with self.subTest(release_ref=release_ref): with self.assertRaises(self.registry.RegistryError): self.registry.build_public_manifest(checkout, release_ref) def test_public_check_detects_missing_snapshot_and_drift(self): # check_public is the public-manifest digest-drift gate (the analog of # discovery_catalog.check). Missing destination → surfaced; a fresh snapshot # → current; any byte change → stale. All fail LOUD (RegistryError), never a # silent "current". with tempfile.TemporaryDirectory() as td: checkout = self._public_checkout_fixture( Path(td), "git@github.com:forcedotcom/sf-skills.git" ) dest = Path(td) / "public-release-manifest.json" with self.assertRaisesRegex(self.registry.RegistryError, "missing"): self.registry.check_public(checkout, dest, "1.32.0") self.registry.snapshot_public(checkout, dest, "1.32.0") self.assertTrue(self.registry.check_public(checkout, dest, "1.32.0")) dest.write_text(dest.read_text(encoding="utf-8") + "\n", encoding="utf-8") with self.assertRaisesRegex(self.registry.RegistryError, "stale"): self.registry.check_public(checkout, dest, "1.32.0") def test_checked_public_manifest_and_v2_catalog_counts_and_sets(self): manifest = self.registry.load_public_manifest(MANIFEST_PATH) self.assertEqual(manifest["repository"], "https://github.com/forcedotcom/sf-skills.git") self.assertEqual(manifest["commit"], "7baeb07b36799eada4dce06d85664c0c16a269a8") self.assertEqual(manifest["releaseRef"], "1.32.0") self.assertEqual(manifest["counts"], {"public": 102}) self.assertEqual(len(manifest["skills"]), 102) data = self.catalog.load_catalog(PLUGIN_ROOT) self.assertEqual(data["schemaVersion"], "2.0") self.assertEqual(data["channel"], "public") self.assertEqual(data["counts"], { "public": 102, "foundation": 40, "overlap": 29, "publicStandaloneAddable": 73, "foundationOnly": 11, "visibleUnion": 113, }) public = {row["name"] for row in manifest["skills"]} foundation = {entry.name for entry in (PLUGIN_ROOT / "skills").iterdir() if entry.is_dir()} rows = {row["name"]: row for row in data["skills"]} self.assertEqual(set(rows), public | foundation) self.assertEqual({name for name, row in rows.items() if row["publicAvailable"]}, public) self.assertEqual({name for name, row in rows.items() if row["foundationInstalled"]}, foundation) for name, row in rows.items(): self.assertEqual(set(row["variants"]), ({"public"} if name in public else set()) | ({"foundation"} if name in foundation else set())) for variant in row["variants"].values(): self.assertRegex(variant["skillMdSha256"], r"^[0-9a-f]{64}$") self.assertRegex(variant["treeSha256"], r"^[0-9a-f]{64}$") overlap = next(rows[name] for name in sorted(public & foundation)) public_record = next(row for row in manifest["skills"] if row["name"] == overlap["name"]) self.assertEqual(overlap["variants"]["public"]["description"], public_record["description"]) def test_public_manifest_loader_rejects_schema_count_order_and_hash_damage(self): baseline = self.registry.load_public_manifest(MANIFEST_PATH) cases = [] damaged = json.loads(json.dumps(baseline)) damaged["extra"] = True cases.append(damaged) damaged = json.loads(json.dumps(baseline)) damaged["counts"]["public"] -= 1 cases.append(damaged) damaged = json.loads(json.dumps(baseline)) damaged["releaseRef"] = "main" cases.append(damaged) damaged = json.loads(json.dumps(baseline)) damaged["skills"][0]["treeSha256"] = "bad" cases.append(damaged) damaged = json.loads(json.dumps(baseline)) damaged["skills"][0], damaged["skills"][1] = damaged["skills"][1], damaged["skills"][0] cases.append(damaged) with tempfile.TemporaryDirectory() as td: path = Path(td) / "manifest.json" for data in cases: path.write_text(json.dumps(data), encoding="utf-8") with self.assertRaises(self.registry.RegistryError): self.registry.load_public_manifest(path) def test_public_artifacts_do_not_leak_internal_only_names_or_descriptions(self): manifest = self.registry.load_public_manifest(MANIFEST_PATH) self.catalog.load_catalog(PLUGIN_ROOT) public = {row["name"] for row in manifest["skills"]} foundation = {entry.name for entry in (PLUGIN_ROOT / "skills").iterdir() if entry.is_dir()} authoring = {entry.name for entry in (REPO_ROOT / "skills").iterdir() if entry.is_dir()} internal_only = authoring - (public | foundation) evidence_root = REPO_ROOT / "evidence/channel-registry" checked_files = [MANIFEST_PATH, PLUGIN_ROOT / "catalog/discovery.json"] + [ path for path in evidence_root.rglob("*") if path.is_file() ] blob = "\n".join(path.read_text(encoding="utf-8") for path in checked_files) for name in internal_only: self.assertNotIn(f'"{name}"', blob) self.assertIsNone(re.search(rf"(?