from __future__ import annotations import fnmatch import json from pathlib import Path ROOT = Path(__file__).resolve().parents[1] # Explicit compatibility contract for the connected ITWorx Gitea Workspace app. # New connector tools never become public through a wildcard; a surface change # requires a reviewed catalog + contract update in the same change. CHATGPT_GITEA_SURFACE = { "gitea.branch.create", "gitea.branch.delete", "gitea.branch.list", "gitea.capabilities.get", "gitea.change.review", "gitea.change.status", "gitea.changeset.apply", "gitea.changeset.preview", "gitea.code.search", "gitea.commit.get", "gitea.commit.list", "gitea.commitstatus.list", "gitea.connection.check", "gitea.file.create", "gitea.file.delete", "gitea.file.read", "gitea.file.update", "gitea.issue.comment.create", "gitea.issue.create", "gitea.issue.list", "gitea.issue.search", "gitea.issue.update", "gitea.orchestration.plan", "gitea.orchestration.start", "gitea.portfolio.health", "gitea.pullrequest.create", "gitea.pullrequest.close", "gitea.pullrequest.feedback", "gitea.pullrequest.get", "gitea.pullrequest.list", "gitea.pullrequest.merge", "gitea.pullrequest.sync", "gitea.pullrequest.update", "gitea.ref.compare", "gitea.release.list", "gitea.repository.context", "gitea.repository.create", "gitea.repository.get", "gitea.repository.health", "gitea.repository.list", "gitea.tree.list", "gitea.validation.failure.get", "gitea.validation.get", "gitea.validation.list", "gitea.validation.run", "gitea.workspace.run", "gitea.workspace.status", "search", "fetch", } # Tools advertised by the connected app but missing from the old gitea-all # virtual-server allowlist. They must already be authorized by existing packs. RESTORED_GITEA_SURFACE = { "gitea.capabilities.get", "gitea.change.review", "gitea.change.status", "gitea.commit.get", "gitea.commit.list", "gitea.commitstatus.list", "gitea.connection.check", "gitea.orchestration.plan", "gitea.orchestration.start", "gitea.portfolio.health", "gitea.pullrequest.feedback", "gitea.ref.compare", "gitea.release.list", "gitea.repository.context", "gitea.repository.create", "gitea.repository.health", "gitea.validation.list", "gitea.workspace.run", "gitea.workspace.status", } # These are the minimum primitives needed to finish long-running PR work without # a human rebase/validation/merge bootstrap. LONG_RUNNING_PR_WORKFLOW = { "gitea.pullrequest.sync", "gitea.ref.compare", "gitea.change.review", "gitea.change.status", "gitea.commitstatus.list", "gitea.validation.get", "gitea.validation.list", "gitea.validation.run", "gitea.workspace.run", } ALIASES = { "search": "gitea.research.search", "fetch": "gitea.research.fetch", } def _load(path: Path) -> dict[str, object]: return json.loads(path.read_text(encoding="utf-8")) def _matches(name: str, patterns: list[str]) -> bool: return any(fnmatch.fnmatchcase(name, pattern) for pattern in patterns) def test_connected_chatgpt_surface_is_explicit_published_and_pre_authorized() -> None: profile = _load(ROOT / "catalog/projects/gitea-all.yaml") spec = profile["spec"] assert isinstance(spec, dict) virtual_servers = spec["virtualServers"] assert isinstance(virtual_servers, list) and len(virtual_servers) == 1 server = virtual_servers[0] assert isinstance(server, dict) published = server["toolAllowPatterns"] assert isinstance(published, list) assert all(isinstance(name, str) for name in published) published_names = list(published) assert server.get("defaultDecision") == "deny" assert len(published_names) == len(set(published_names)), "duplicate gitea-all tool publication" assert not any( any(token in name for token in ("*", "?", "[")) for name in published_names if name.startswith("gitea.") ), "gitea-all must not use wildcard Gitea publication" missing = CHATGPT_GITEA_SURFACE - set(published_names) assert not missing, f"connected ChatGPT tools missing from gitea-all: {sorted(missing)}" assert LONG_RUNNING_PR_WORKFLOW <= set(published_names) assert RESTORED_GITEA_SURFACE <= CHATGPT_GITEA_SURFACE manifests: dict[str, dict[str, object]] = {} for path in (ROOT / "catalog/tools").glob("*.yaml"): document = _load(path) manifest_spec = document.get("spec") if not isinstance(manifest_spec, dict) or manifest_spec.get("connectorType") != "gitea": continue canonical_name = manifest_spec.get("canonicalName") assert isinstance(canonical_name, str) manifests[canonical_name] = manifest_spec canonical_surface = {ALIASES.get(name, name) for name in CHATGPT_GITEA_SURFACE} missing_manifests = canonical_surface - manifests.keys() assert not missing_manifests, ( f"connected ChatGPT tools missing canonical manifests: {sorted(missing_manifests)}" ) capability_packs = spec["capabilityPacks"] assert capability_packs == ["project-reader", "gitea-controlled-writer"] packs: dict[str, dict[str, object]] = {} for slug in capability_packs: assert isinstance(slug, str) document = _load(ROOT / f"catalog/capability-packs/{slug}.yaml") pack_spec = document["spec"] assert isinstance(pack_spec, dict) packs[slug] = pack_spec reader_patterns = packs["project-reader"]["toolAllowPatterns"] writer_patterns = packs["gitea-controlled-writer"]["toolAllowPatterns"] assert isinstance(reader_patterns, list) and all( isinstance(pattern, str) for pattern in reader_patterns ) assert isinstance(writer_patterns, list) and all( isinstance(pattern, str) for pattern in writer_patterns ) for name in sorted(RESTORED_GITEA_SURFACE): manifest = manifests[name] read_only = manifest.get("readOnly") assert isinstance(read_only, bool), name if read_only: assert _matches(name, reader_patterns), f"repair would bypass project-reader: {name}" else: assert _matches(name, writer_patterns), ( f"repair would bypass gitea-controlled-writer: {name}" ) assert _matches("gitea.pullrequest.sync", writer_patterns), ( "long-running PR recovery must remain writer-pack governed" ) forbidden = { "gitea.repository.delete", "gitea.repository.admin", "gitea.branch.forcepush", } assert forbidden.isdisjoint(published_names)