198 lines
6.6 KiB
Python
198 lines
6.6 KiB
Python
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)
|