generated from coulomb/repo-seed
feat(tpsc): Third-Party Services Catalog (CUST-WP-0023)
Introduces TPSC for tracking external service dependencies with GDPR
compliance maturity (CNIL/IAPP CMMI scale), pricing model, ToS, and
data retention information across all repos.
Primary data:
- canon/tpsc/{openai,anthropic,gemini,openrouter}-api.yaml — service definitions
- tpsc.yaml in each repo (llm-connect seeded with 4 services)
State-hub additions:
- Migration j7e8f9a0b1c2: tpsc_catalog + tpsc_snapshots + tpsc_entries
- api/models/tpsc.py, api/schemas/tpsc.py, api/routers/tpsc.py
- /tpsc/catalog/, /tpsc/ingest/, /tpsc/snapshots/, /tpsc/report/gdpr endpoints
- 4 MCP tools: register_service, list_services, ingest_tpsc_tool, get_gdpr_report
- scripts/ingest_tpsc.py + make ingest-tpsc[/-all] targets
- Dashboard: tpsc.md page + docs/tpsc.md
GDPR maturity scale: unknown | non_compliant | initial | developing | defined | managed | certified
Warnings triggered at: unknown, non_compliant, initial
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1911,6 +1911,140 @@ def get_capability_request(request_id: str) -> str:
|
||||
return json.dumps(_get(f"/capability-requests/{request_id}"), indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Third-Party Services Catalog (TPSC)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@mcp.tool()
|
||||
def register_service(
|
||||
slug: str,
|
||||
name: str,
|
||||
provider: str | None = None,
|
||||
category: str | None = None,
|
||||
pricing_model: str = "unknown",
|
||||
gdpr_maturity: str = "unknown",
|
||||
gdpr_notes: str | None = None,
|
||||
dpa_available: bool = False,
|
||||
tos_url: str | None = None,
|
||||
privacy_policy_url: str | None = None,
|
||||
data_processing_regions: list[str] | None = None,
|
||||
data_retention_notes: str | None = None,
|
||||
website_url: str | None = None,
|
||||
) -> str:
|
||||
"""Register or update a service in the Third-Party Services Catalog (TPSC).
|
||||
|
||||
GDPR maturity scale (CNIL/IAPP CMMI-aligned):
|
||||
unknown | non_compliant | initial | developing | defined | managed | certified
|
||||
|
||||
Pricing model: free | paid | freemium | usage_based | unknown
|
||||
|
||||
Args:
|
||||
slug: Unique identifier (e.g. 'openai-api', 'stripe')
|
||||
name: Human-readable service name
|
||||
provider: Company/organisation name
|
||||
category: Category (e.g. 'llm_inference', 'storage', 'payments', 'search')
|
||||
pricing_model: free | paid | freemium | usage_based | unknown
|
||||
gdpr_maturity: GDPR compliance maturity level (see scale above)
|
||||
gdpr_notes: Free-text GDPR notes (DPA details, transfer mechanisms, etc.)
|
||||
dpa_available: Whether a Data Processing Agreement is available
|
||||
tos_url: Terms of Service URL
|
||||
privacy_policy_url: Privacy Policy URL
|
||||
data_processing_regions: List of regions where data is processed (e.g. ['us', 'eu'])
|
||||
data_retention_notes: Data retention policy summary
|
||||
website_url: Service website URL
|
||||
"""
|
||||
return json.dumps(_post("/tpsc/catalog", {
|
||||
"slug": slug,
|
||||
"name": name,
|
||||
"provider": provider,
|
||||
"category": category,
|
||||
"website_url": website_url,
|
||||
"pricing_model": pricing_model,
|
||||
"gdpr_maturity": gdpr_maturity,
|
||||
"gdpr_notes": gdpr_notes,
|
||||
"dpa_available": dpa_available,
|
||||
"tos_url": tos_url,
|
||||
"privacy_policy_url": privacy_policy_url,
|
||||
"data_processing_regions": data_processing_regions or [],
|
||||
"data_retention_notes": data_retention_notes,
|
||||
}), indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def list_services(
|
||||
gdpr_maturity: str | None = None,
|
||||
category: str | None = None,
|
||||
pricing_model: str | None = None,
|
||||
) -> str:
|
||||
"""Browse the Third-Party Services Catalog (TPSC).
|
||||
|
||||
Returns services with their GDPR maturity level and gdpr_warning flag
|
||||
(True when maturity is unknown, non_compliant, or initial — may limit
|
||||
use in corporate/GDPR-regulated environments).
|
||||
|
||||
Args:
|
||||
gdpr_maturity: Filter by maturity level (unknown/non_compliant/initial/developing/defined/managed/certified)
|
||||
category: Filter by category (e.g. 'llm_inference', 'storage')
|
||||
pricing_model: Filter by pricing model (free/paid/freemium/usage_based/unknown)
|
||||
"""
|
||||
return json.dumps(_get("/tpsc/catalog", {
|
||||
"gdpr_maturity": gdpr_maturity,
|
||||
"category": category,
|
||||
"pricing_model": pricing_model,
|
||||
}), indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def ingest_tpsc_tool(repo_slug: str) -> str:
|
||||
"""Ingest tpsc.yaml service dependency declarations for a repo.
|
||||
|
||||
Reads <repo_root>/tpsc.yaml, resolves service slugs against the catalog,
|
||||
and creates a new TPSC snapshot. The repo path is resolved the same way
|
||||
as the SBOM ingest tool (host_paths → local_path with existence check).
|
||||
|
||||
Args:
|
||||
repo_slug: Registered repo slug (e.g. 'llm-connect', 'markitect-project')
|
||||
"""
|
||||
import socket as _socket
|
||||
import subprocess
|
||||
|
||||
repo = _get(f"/repos/{repo_slug}")
|
||||
if isinstance(repo, dict) and repo.get("error"):
|
||||
return f"Repo '{repo_slug}' not found: {repo['error']}"
|
||||
|
||||
repo_root = _resolve_repo_path(repo)
|
||||
if not repo_root:
|
||||
hostname = _socket.gethostname()
|
||||
return (
|
||||
f"⚠ No accessible path found for repo '{repo_slug}' on host '{hostname}'.\n"
|
||||
f"Register with: update_repo_path('{repo_slug}', '/path/to/repo')"
|
||||
)
|
||||
|
||||
script = Path(__file__).parent.parent / "scripts" / "ingest_tpsc.py"
|
||||
result = subprocess.run(
|
||||
["uv", "run", "python", str(script), "--repo", repo_slug],
|
||||
capture_output=True, text=True,
|
||||
cwd=str(Path(__file__).parent.parent),
|
||||
)
|
||||
output = result.stdout + result.stderr
|
||||
if result.returncode != 0:
|
||||
return f"ingest_tpsc failed (exit {result.returncode}):\n{output}"
|
||||
return output.strip()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_gdpr_report() -> str:
|
||||
"""Get an aggregated GDPR compliance report across all repos' latest TPSC snapshots.
|
||||
|
||||
Returns a warning summary for services with gdpr_maturity in:
|
||||
unknown | non_compliant | initial
|
||||
|
||||
These may limit usability in GDPR-regulated / corporate environments.
|
||||
Services at 'developing' or above have at least a DPA available.
|
||||
"""
|
||||
return json.dumps(_get("/tpsc/report/gdpr"), indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user