Publish curated ITWorx_MCP_Hub source from 6c7434368ce2a594005983c5a6601a31a639e7f1

This commit is contained in:
Public Source Publisher committed 2026-10-01 18:03:00 +00:00
1 parent 77946d62c4
commit 6d40ab975c
86 files changed
+6784 -1478

No files matched your search

+29 -4
View File
@@ -11,8 +11,10 @@ authorization, and the real policy engine -- not a mocked frontend:
-> fake ContextForge (in-process MockTransport; nothing the
browser talks to directly, so no real HTTP needed here)
Each subcommand below blocks running one of these processes; Playwright's
`webServer` config (apps/web/playwright.config.ts) starts all three and
The worker readiness dependency is an explicit offline fixture; worker reconciliation
is validated by separate integration tests. Each subcommand below blocks running
one of these processes; Playwright's
`webServer` config (apps/web/playwright.config.ts) starts all four and
waits for each to answer its readiness probe before running any test.
Persona switching: `OIDCSettings`/the real `/auth/login` route were left
@@ -37,6 +39,7 @@ if str(ROOT) not in sys.path:
import httpx # noqa: E402
import uvicorn # noqa: E402
from fastapi import FastAPI # noqa: E402
from services.control_api.auth.config import OIDCSettings # noqa: E402
from services.control_api.auth.fake_provider import FakeOIDCProvider # noqa: E402
@@ -124,13 +127,29 @@ def run_control_api(port: int, oidc_port: int, web_port: int) -> None:
uvicorn.run(app, host="127.0.0.1", port=port, log_level="warning")
def run_web(port: int, control_port: int, dist_root: Path) -> None:
def worker_readiness_fixture() -> FastAPI:
"""Offline readiness dependency; worker reconciliation has separate integration tests."""
app = FastAPI()
@app.get("/health/ready")
def ready() -> dict[str, str]:
return {"status": "ready", "component": "e2e-worker-fixture"}
return app
def run_worker(port: int) -> None:
uvicorn.run(worker_readiness_fixture(), host="127.0.0.1", port=port, log_level="warning")
def run_web(port: int, control_port: int, worker_port: int, dist_root: Path) -> None:
if not (dist_root / "index.html").is_file():
raise SystemExit(
f"{dist_root} has no built frontend; "
"run `pnpm --filter @itworx-mcp-hub/web build` first"
)
os.environ["HUB_CONTROL_URL"] = f"http://127.0.0.1:{control_port}"
os.environ["HUB_WORKER_URL"] = f"http://127.0.0.1:{worker_port}"
os.environ["HUB_WEB_ROOT"] = str(dist_root)
tls = generate_local_tls_material(STATE_DIR / "tls-web")
uvicorn.run(
@@ -158,15 +177,21 @@ def main() -> None:
web_parser = sub.add_parser("web")
web_parser.add_argument("--port", type=int, required=True)
web_parser.add_argument("--control-port", type=int, required=True)
web_parser.add_argument("--worker-port", type=int, required=True)
web_parser.add_argument("--dist", type=Path, required=True)
worker_parser = sub.add_parser("worker")
worker_parser.add_argument("--port", type=int, required=True)
args = parser.parse_args()
if args.command == "oidc":
run_oidc(args.port)
elif args.command == "control-api":
run_control_api(args.port, args.oidc_port, args.web_port)
elif args.command == "web":
run_web(args.port, args.control_port, args.dist)
run_web(args.port, args.control_port, args.worker_port, args.dist)
elif args.command == "worker":
run_worker(args.port)
if __name__ == "__main__":
+7 -14
View File
@@ -33,14 +33,8 @@ def test_appops_tool_catalog_is_bijective_and_server_namespaced() -> None:
PUBLIC_TO_UPSTREAM["server.appops.self_upgrade.preflight"]
== "appops.self_upgrade.preflight"
)
assert (
PUBLIC_TO_UPSTREAM["server.appops.self_upgrade.apply"]
== "appops.self_upgrade.apply"
)
assert (
PUBLIC_TO_UPSTREAM["server.appops.self_upgrade.status"]
== "appops.self_upgrade.status"
)
assert PUBLIC_TO_UPSTREAM["server.appops.self_upgrade.apply"] == "appops.self_upgrade.apply"
assert PUBLIC_TO_UPSTREAM["server.appops.self_upgrade.status"] == "appops.self_upgrade.status"
def test_every_public_appops_tool_has_governance_metadata() -> None:
@@ -60,9 +54,7 @@ def test_appops_self_upgrade_manifests_are_fixed_target_and_fingerprint_bound()
for path in (ROOT / "catalog" / "tools").glob("*.yaml"):
document = yaml.safe_load(path.read_text(encoding="utf-8"))
spec = document.get("spec") if isinstance(document, dict) else None
if isinstance(spec, dict) and spec.get("canonicalName", "").startswith(
"server.appops."
):
if isinstance(spec, dict) and spec.get("canonicalName", "").startswith("server.appops."):
specs[spec["canonicalName"]] = spec
assert set(specs) == {
@@ -84,9 +76,10 @@ def test_appops_self_upgrade_manifests_are_fixed_target_and_fingerprint_bound()
apply = specs["server.appops.self_upgrade.apply"]
assert apply["riskClass"] == "R3"
assert apply["readOnly"] is False
assert apply["inputSchema"]["properties"]["expected_preflight_fingerprint"][
"pattern"
] == "^[0-9a-f]{64}$"
assert (
apply["inputSchema"]["properties"]["expected_preflight_fingerprint"]["pattern"]
== "^[0-9a-f]{64}$"
)
assert {
"expected_preflight_fingerprint",
"idempotency_key",
+17
View File
@@ -76,6 +76,11 @@ class BootstrapTests(unittest.TestCase):
"apps/web/src/rbac-context.tsx",
"apps/web/src/navigation.ts",
"apps/web/src/routing.ts",
"apps/web/src/operator-data.ts",
"apps/web/src/preferences.ts",
"apps/web/src/schema-input.ts",
"apps/web/src/status-refresh.ts",
"apps/web/src/tool-history.ts",
"apps/web/src/components/Brand.tsx",
"apps/web/src/components/Breadcrumbs.tsx",
"apps/web/src/components/CommandMenu.tsx",
@@ -88,9 +93,13 @@ class BootstrapTests(unittest.TestCase):
"apps/web/src/components/Modal.tsx",
"apps/web/src/components/SessionMenu.tsx",
"apps/web/src/components/StatusPill.tsx",
"apps/web/src/components/RecentToolRuns.tsx",
"apps/web/src/components/SchemaInputFields.tsx",
"apps/web/src/components/modal-focus.ts",
"apps/web/src/pages/AuthGateway.tsx",
"apps/web/src/pages/CatalogWorkflow.tsx",
"apps/web/src/pages/ClientOnboarding.tsx",
"apps/web/src/pages/ConnectorDetail.tsx",
"apps/web/src/pages/Dashboard.tsx",
"apps/web/src/pages/ForbiddenPanel.tsx",
"apps/web/src/pages/Operations.tsx",
@@ -102,6 +111,10 @@ class BootstrapTests(unittest.TestCase):
"apps/web/tests/contracts.test.ts",
"apps/web/tests/api.test.ts",
"apps/web/tests/rbac.test.ts",
"apps/web/tests/operator-data.test.ts",
"apps/web/tests/preferences.test.ts",
"apps/web/tests/schema-input.test.ts",
"apps/web/tests/tool-history.test.ts",
"apps/web/tests/e2e/fixtures.ts",
"apps/web/tests/e2e/auth.spec.ts",
"apps/web/tests/e2e/rbac.spec.ts",
@@ -110,6 +123,10 @@ class BootstrapTests(unittest.TestCase):
"apps/web/tests/e2e/navigation.spec.ts",
"apps/web/tests/e2e/accessibility.spec.ts",
"apps/web/tests/e2e/visual-quality.spec.ts",
"apps/web/tests/e2e/operator-matrix.spec.ts",
"apps/web/tests/e2e/operator-shell.spec.ts",
"apps/web/tests/e2e/operator-review.spec.ts",
"apps/web/tests/e2e/operator-workflows.spec.ts",
"apps/web/src/main.ts",
"apps/web/tests/bootstrap.test.ts",
"apps/web/vite.config.ts",
+54
View File
@@ -5,6 +5,7 @@ import json
import tempfile
import unittest
from pathlib import Path
from typing import Any
from uuid import uuid4
from fastapi import FastAPI
@@ -30,6 +31,59 @@ def adapter_for(fake: FakeContextForge) -> ContextForgeClient:
class CatalogLoaderTests(unittest.TestCase):
def test_schema_store_loads_once_per_catalog_load_without_changing_validation(self) -> None:
class CountingCatalogLoader(CatalogLoader):
store_loads = 0
def _schema_store(self) -> dict[str, Any]:
self.store_loads += 1
return super()._schema_store()
loader = CountingCatalogLoader(ROOT / "catalog")
report = loader.load()
self.assertTrue(report.valid, report.issues)
self.assertEqual(len(report.documents), 281)
self.assertEqual(report.issues, ())
self.assertEqual(loader.store_loads, 1)
with tempfile.TemporaryDirectory() as directory:
root = Path(directory) / "catalog"
project_dir = root / "projects"
project_dir.mkdir(parents=True)
payload = json.loads(
(ROOT / "catalog/projects/general-infrastructure.yaml").read_text(encoding="utf-8")
)
payload["spec"]["virtualServers"][0]["endpointAlias"] = "not-an-alias"
payload["spec"]["capabilityPacks"] = []
payload["spec"]["connectorBindings"] = []
payload["spec"]["credentialToken"] = "plain-secret-value"
(project_dir / "bad.yaml").write_text(json.dumps(payload), encoding="utf-8")
invalid_loader = CountingCatalogLoader(root, schema_root=ROOT / "contracts/schemas")
invalid_report = invalid_loader.load()
self.assertFalse(invalid_report.valid)
self.assertEqual(
{
(issue.code, issue.source.file, issue.source.pointer)
for issue in invalid_report.issues
},
{
(
"schema_error",
"projects/bad.yaml",
"/spec/virtualServers/0/endpointAlias",
),
("schema_error", "projects/bad.yaml", "/spec/capabilityPacks"),
("schema_error", "projects/bad.yaml", "/spec"),
("secret_value", "projects/bad.yaml", "/spec/credentialToken"),
},
)
self.assertEqual(invalid_loader.store_loads, 1)
self.assertNotIn(
"plain-secret-value",
json.dumps([document.payload for document in invalid_report.documents]),
)
def test_committed_catalog_validates_without_network_or_secret_values(self) -> None:
report = CatalogLoader(ROOT / "catalog").load()
self.assertTrue(report.valid, report.issues)
+42
View File
@@ -0,0 +1,42 @@
from __future__ import annotations
from pathlib import Path
from typing import Any
import pytest
from scripts import validate_pack
ROOT = Path(__file__).resolve().parents[1]
@pytest.mark.parametrize(
("canonical_name", "risk", "accepted"),
[
("server.appops.self_upgrade.apply", "R3", True),
("server.appops.self_upgrade.apply", "R2", False),
("server.appops.arbitrary.apply", "R3", False),
],
)
def test_catalog_accepts_only_the_approved_exact_risk_write(
monkeypatch: pytest.MonkeyPatch, canonical_name: str, risk: str, accepted: bool
) -> None:
tool_path = ROOT / "catalog/tools/server-appops-self-upgrade-apply.yaml"
policy_path = next((ROOT / "catalog/policies").glob("*global-deny*"))
tool = validate_pack.load_json(tool_path)
tool["spec"]["canonicalName"] = canonical_name
tool["spec"]["riskClass"] = risk
policy = validate_pack.load_json(policy_path)
def read_documents(_errors: list[str]) -> list[tuple[Path, Any]]:
return [(tool_path, tool), (policy_path, policy)]
monkeypatch.setattr(validate_pack, "read_catalog_documents", read_documents)
errors: list[str] = []
validate_pack.validate_catalog(errors, [])
if accepted:
assert errors == []
else:
assert any("invalid risk class" in error for error in errors)
if canonical_name.endswith("arbitrary.apply"):
assert any("not an approved controlled write tool" in error for error in errors)
+108
View File
@@ -0,0 +1,108 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from typing import Any
import pytest
from sqlalchemy import event, select
from packages.hub_clients import ClientKind, ClientStatus
from services.control_api.auth.models import AuthenticatedPrincipal, PrincipalKind
from services.control_api.persistence.models import AuditEventRow, ClientBindingRow, ClientRow
from services.runtime.client_service import PersistentClientService
pytest_plugins = ("tests.test_machine_client_provisioning",)
def _actor() -> AuthenticatedPrincipal:
now = datetime.now(UTC)
return AuthenticatedPrincipal(
subject="operator-audit-fixture",
issuer="https://identity.fixture.invalid",
audience=("hub-fixture",),
kind=PrincipalKind.MANAGEMENT_USER,
issued_at=now,
expires_at=now + timedelta(minutes=5),
)
def _active_client(database: Any) -> tuple[PersistentClientService, Any]:
service = PersistentClientService(
database, issuer="https://identity.fixture.invalid", public_host="mcp.fixture.invalid"
)
record, _, _ = service.create(
name="Audit fixture", project="ludarium", environment="prod", kind=ClientKind.STDIO_BRIDGE
)
with database() as session, session.begin():
row = session.get(ClientRow, record.id)
assert row is not None
row.status = "active"
binding = session.scalar(
select(ClientBindingRow).where(ClientBindingRow.client_id == record.id)
)
assert binding is not None
binding.enabled = True
return service, record.id
def test_client_disable_and_revoke_are_scoped_and_append_actor_audits(database: Any) -> None:
service, client_id = _active_client(database)
actor = _actor()
disabled = service.transition(client_id, ClientStatus.DISABLED, actor)
assert disabled.status is ClientStatus.DISABLED
revoked = service.transition(client_id, ClientStatus.REVOKED, actor)
assert revoked.status is ClientStatus.REVOKED
with database() as session:
binding = session.scalar(
select(ClientBindingRow).where(ClientBindingRow.client_id == client_id)
)
assert binding is not None and binding.enabled is False
audits = session.scalars(
select(AuditEventRow)
.where(AuditEventRow.client_id == client_id)
.order_by(AuditEventRow.occurred_at)
).all()
assert [row.event_type for row in audits] == ["client.disable", "client.revoke"]
for row in audits:
assert row.principal_subject == actor.subject
assert row.principal_issuer == actor.issuer
assert row.project_id == binding.project_id
assert row.virtual_server_id == binding.virtual_server_id
assert row.environment_id is not None
assert row.policy_decision == "allow" and row.outcome == "success"
assert row.sanitized_arguments == {"clientId": str(client_id), "action": row.event_type}
assert row.arguments_hash is not None and len(row.arguments_hash) == 64
assert audits[0].trace_id != audits[1].trace_id
with pytest.raises(ValueError, match="revoked"):
service.transition(client_id, ClientStatus.DISABLED, actor)
with database() as session:
row = session.get(ClientRow, client_id)
assert row is not None and row.status == "revoked"
assert len(session.scalars(select(AuditEventRow)).all()) == 2
@pytest.mark.parametrize("status", [ClientStatus.DISABLED, ClientStatus.REVOKED])
def test_audit_failure_rolls_back_client_and_binding_transition(
database: Any, status: ClientStatus
) -> None:
service, client_id = _active_client(database)
def reject_audit(*_: Any) -> None:
raise RuntimeError("fixture audit insert failed")
event.listen(AuditEventRow, "before_insert", reject_audit)
try:
with pytest.raises(RuntimeError, match="fixture audit insert failed"):
service.transition(client_id, status, _actor())
finally:
event.remove(AuditEventRow, "before_insert", reject_audit)
with database() as session:
row = session.get(ClientRow, client_id)
binding = session.scalar(
select(ClientBindingRow).where(ClientBindingRow.client_id == client_id)
)
assert row is not None and row.status == "active"
assert row.revoked_at is None
assert row.record_metadata["operatorDisabled"] is False
assert binding is not None and binding.enabled is True
assert session.scalars(select(AuditEventRow)).all() == []
+37
View File
@@ -0,0 +1,37 @@
"""The browser harness supplies every real web readiness dependency offline."""
from unittest.mock import patch
from fastapi.testclient import TestClient
from tests.e2e.harness import run_web, worker_readiness_fixture
def test_worker_fixture_is_explicit_and_read_only() -> None:
with TestClient(worker_readiness_fixture()) as client:
response = client.get("/health/ready")
assert response.status_code == 200
assert response.json() == {"status": "ready", "component": "e2e-worker-fixture"}
assert client.post("/health/ready").status_code == 405
def test_web_harness_configures_both_upstream_dependencies(tmp_path) -> None:
(tmp_path / "index.html").write_text("<html></html>", encoding="utf-8")
with (
patch("tests.e2e.harness.os.environ", {}),
patch("tests.e2e.harness.web_runtime.build_app") as build,
patch("tests.e2e.harness.generate_local_tls_material") as tls,
patch("tests.e2e.harness.uvicorn.run") as serve,
):
import os
def inspect_dependencies():
assert os.environ["HUB_CONTROL_URL"] == "http://127.0.0.1:8781"
assert os.environ["HUB_WORKER_URL"] == "http://127.0.0.1:8783"
return "configured-app"
build.side_effect = inspect_dependencies
tls.return_value.cert_path = tmp_path / "cert.pem"
tls.return_value.key_path = tmp_path / "key.pem"
run_web(8780, 8781, 8783, tmp_path)
assert serve.call_args.args == ("configured-app",)
+11 -19
View File
@@ -267,8 +267,7 @@ class GiteaConnectorTests(unittest.TestCase):
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" - name: Validate repository with a bounded profile\n",
" - name: Validate repository with a bounded profile\n"
" if: false\n",
" - name: Validate repository with a bounded profile\n if: false\n",
),
)
)
@@ -293,14 +292,11 @@ class GiteaConnectorTests(unittest.TestCase):
bypass_mutations = (
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pnpm test:e2e:real\n",
" cat <<'EOF'\n"
" pnpm test:e2e:real\n"
" EOF\n",
" cat <<'EOF'\n pnpm test:e2e:real\n EOF\n",
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pnpm --dir backend test:database\n",
" exit 0\n"
" pnpm --dir backend test:database\n",
" exit 0\n pnpm --dir backend test:database\n",
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pnpm test:e2e:real\n",
@@ -357,9 +353,7 @@ class GiteaConnectorTests(unittest.TestCase):
"Jens/projectbrain", PROJECTBRAIN_VALIDATION_WORKFLOW + "\njobs: {}\n"
)
)
self.assertIsNone(
_managed_validation_profile("Acme/hub", PROJECTBRAIN_VALIDATION_WORKFLOW)
)
self.assertIsNone(_managed_validation_profile("Acme/hub", PROJECTBRAIN_VALIDATION_WORKFLOW))
self.assertEqual(
_managed_validation_profile("Acme/hub", MANAGED_VALIDATION_WORKFLOW),
"canonical",
@@ -1400,9 +1394,7 @@ class GiteaConnectorTests(unittest.TestCase):
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"] = [
"Pull request / quality (pull_request)"
]
original_protection = copy.deepcopy(
fixture._branch_protections["Jens/projectbrain"][0]
)
original_protection = copy.deepcopy(fixture._branch_protections["Jens/projectbrain"][0])
try:
current = context()
preview = connector_instance.branch_protection_preview(
@@ -1467,9 +1459,9 @@ class GiteaConnectorTests(unittest.TestCase):
nonlocal read_count
read_count += 1
if read_count == 3:
fixture._branch_protections["Jens/projectbrain"][0][
"status_check_contexts"
].remove("Additional required check")
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"].remove(
"Additional required check"
)
return original_read(repository)
try:
@@ -1704,9 +1696,9 @@ class GiteaConnectorTests(unittest.TestCase):
nonlocal read_count
read_count += 1
if read_count == 3:
fixture._branch_protections["Jens/projectbrain"][0][
"status_check_contexts"
].remove("Additional required check")
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"].remove(
"Additional required check"
)
return original_read(repository)
try:
+67 -6
View File
@@ -4,6 +4,7 @@ from uuid import uuid4
import pytest
from packages.hub_clients import ClientKind, generate_config
from services.control_api.persistence.db import Base, create_engine_from_url, session_factory
from services.control_api.persistence.machine_client import provision_machine_client
from services.control_api.persistence.models import (
@@ -14,6 +15,7 @@ from services.control_api.persistence.models import (
VirtualServerRow,
WorkspaceRow,
)
from services.runtime.client_service import PersistentClientService
@pytest.fixture
@@ -114,8 +116,19 @@ def test_revoked_machine_client_cannot_be_silently_reactivated(database) -> None
_provision(database)
def test_pending_ui_client_is_activated_in_place(database) -> None: # type: ignore[no-untyped-def]
@pytest.mark.parametrize("profile", [*ClientKind, None])
def test_pending_ui_client_is_activated_in_place_with_selected_profile(
database, profile: ClientKind | None
) -> None: # type: ignore[no-untyped-def]
pending_id = uuid4()
expected_profile = profile or ClientKind.STDIO_BRIDGE
metadata = {
"managedBy": "runtime-onboarding",
"project": "ludarium",
"environment": "prod",
}
if profile is not None:
metadata["kind"] = profile.value
with database() as session, session.begin():
workspace = session.query(WorkspaceRow).filter_by(slug="itworx").one()
project = session.query(ProjectRow).filter_by(slug="ludarium").one()
@@ -127,11 +140,7 @@ def test_pending_ui_client_is_activated_in_place(database) -> None: # type: ign
subject=f"pending-{pending_id}",
issuer="https://auth.itworx.test/application/o/itworx-mcp-hub/",
status="pending",
record_metadata={
"managedBy": "runtime-onboarding",
"project": "ludarium",
"environment": "prod",
},
record_metadata=metadata,
)
)
session.flush()
@@ -162,3 +171,55 @@ def test_pending_ui_client_is_activated_in_place(database) -> None: # type: ign
assert client.status == "active"
assert client.subject == "b7f5d1aa-f199-4334-b077-f6be0870115f"
assert binding.enabled is True
assert client.record_metadata["kind"] == expected_profile.value
assert generate_config(PersistentClientService._record(client)).kind is expected_profile
def test_pending_ui_client_rejects_unknown_profile(database) -> None: # type: ignore[no-untyped-def]
pending_id = uuid4()
with database() as session, session.begin():
workspace = session.query(WorkspaceRow).filter_by(slug="itworx").one()
project = session.query(ProjectRow).filter_by(slug="ludarium").one()
server = session.query(VirtualServerRow).filter_by(slug="ludarium-prod-readonly").one()
session.add(
ClientRow(
id=pending_id,
workspace_id=workspace.id,
subject=f"pending-{pending_id}",
issuer="https://auth.itworx.test/application/o/itworx-mcp-hub/",
status="pending",
record_metadata={
"managedBy": "runtime-onboarding",
"project": "ludarium",
"environment": "prod",
"kind": "arbitrary-profile",
},
)
)
session.flush()
session.add(
ClientBindingRow(
workspace_id=workspace.id,
project_id=project.id,
client_id=pending_id,
virtual_server_id=server.id,
enabled=False,
)
)
with database() as session:
with pytest.raises(ValueError, match="invalid client kind"):
with session.begin():
provision_machine_client(
session,
issuer="https://auth.itworx.test/application/o/itworx-mcp-hub/",
subject="b7f5d1aa-f199-4334-b077-f6be0870115f",
oauth_username="svc-mcp-codex-ludarium",
name="Codex workstation",
project_slug="ludarium",
client_id=pending_id,
)
with database() as session:
client = session.get(ClientRow, pending_id)
assert client is not None
assert client.status == "pending"
assert client.record_metadata["kind"] == "arbitrary-profile"
+194
View File
@@ -0,0 +1,194 @@
from __future__ import annotations
from pathlib import Path
from uuid import uuid4
from services.control_api.persistence import create_engine_from_url, session_factory
from services.control_api.persistence.models import (
AuditEventRow,
EnvironmentRow,
ProjectRow,
WorkspaceRow,
)
from tests.test_wp230_rbac import ManagementRole, RoleSession
pytest_plugins = ("tests.test_wp230_rbac",)
def test_resource_search_matches_connector_slug(
role_sessions: dict[ManagementRole, RoleSession],
) -> None:
response = role_sessions[ManagementRole.VIEWER].get(
"/api/connectors", params={"q": "knowledge-default"}
)
assert response.status_code == 200
body = response.json()
assert [row["id"] for row in body["rows"]] == ["knowledge-default"]
def test_project_scoped_connector_search_uses_bindings_and_environment(
role_sessions: dict[ManagementRole, RoleSession],
) -> None:
viewer = role_sessions[ManagementRole.VIEWER]
response = viewer.get(
"/api/connectors",
params={
"project": "ludarium",
"environment": "prod",
"q": "knowledge-default",
},
)
assert response.status_code == 200
rows = response.json()["rows"]
assert len(rows) == 1
assert rows[0]["id"] == "knowledge-default"
assert rows[0]["project"] == "ludarium"
assert rows[0]["environment"] == "prod"
assert not any("url" in key.casefold() for key in rows[0])
unbound_project = viewer.get(
"/api/connectors",
params={
"project": "mobilityops",
"environment": "prod",
"q": "knowledge-default",
},
)
assert unbound_project.status_code == 200
assert unbound_project.json()["rows"] == []
def test_resource_search_matches_tool_canonical_name(
role_sessions: dict[ManagementRole, RoleSession],
) -> None:
response = role_sessions[ManagementRole.VIEWER].get(
"/api/tools", params={"q": "deployment.project.status"}
)
assert response.status_code == 200
body = response.json()
assert [row["canonicalName"] for row in body["rows"]] == ["deployment.project.status"]
def test_project_scoped_tool_search_uses_bindings_and_capability_packs(
role_sessions: dict[ManagementRole, RoleSession],
) -> None:
viewer = role_sessions[ManagementRole.VIEWER]
ludarium_tools = viewer.get(
"/api/tools",
params={"project": "ludarium", "limit": 100},
)
assert ludarium_tools.status_code == 200
ludarium_body = ludarium_tools.json()
ludarium_rows = ludarium_body["rows"]
assert ludarium_body["page"]["total"] == len(ludarium_rows)
ludarium_names = {row["canonicalName"] for row in ludarium_rows}
assert "deployment.project.status" in ludarium_names
assert "gitea.branch.delete" not in ludarium_names
assert all(row["project"] == "ludarium" for row in ludarium_rows)
assert all(row["environment"] == "prod" for row in ludarium_rows)
unbound_connector = viewer.get(
"/api/tools",
params={
"project": "mobilityops",
"environment": "prod",
"q": "deployment.project.status",
},
)
assert unbound_connector.status_code == 200
assert unbound_connector.json()["rows"] == []
environment_without_server = viewer.get(
"/api/tools",
params={
"project": "forgeflow",
"environment": "dev",
"q": "deployment.project.status",
},
)
assert environment_without_server.status_code == 200
assert environment_without_server.json()["rows"] == []
def test_audit_search_matches_trace_id_without_exposing_search_to_viewers(
role_sessions: dict[ManagementRole, RoleSession], db_path: Path
) -> None:
trace_id = f"operator-search-{uuid4()}"
engine = create_engine_from_url(f"sqlite:///{db_path}")
sessions = session_factory(engine)
with sessions() as session, session.begin():
workspace = session.query(WorkspaceRow).filter_by(slug="itworx").one()
project = session.query(ProjectRow).filter_by(slug="ludarium").one()
environment = (
session.query(EnvironmentRow).filter_by(project_id=project.id, name="prod").one()
)
session.add(
AuditEventRow(
trace_id=trace_id,
workspace_id=workspace.id,
project_id=project.id,
environment_id=environment.id,
event_type="operator.search.fixture",
tool_name="operator.search.fixture",
policy_decision="must-not-be-a-search-field",
outcome="success",
)
)
engine.dispose()
auditor = role_sessions[ManagementRole.AUDITOR]
matching = auditor.get("/api/audit", params={"q": trace_id})
assert matching.status_code == 200
assert [row["traceId"] for row in matching.json()["rows"]] == [trace_id]
excluded_field = auditor.get("/api/audit", params={"q": "must-not-be-a-search-field"})
assert excluded_field.status_code == 200
assert excluded_field.json()["rows"] == []
denied = role_sessions[ManagementRole.VIEWER].get("/api/audit", params={"q": trace_id})
assert denied.status_code == 403
def test_servers_list_virtual_server_identities_and_apply_project_environment_scope(
role_sessions: dict[ManagementRole, RoleSession],
) -> None:
viewer = role_sessions[ManagementRole.VIEWER]
scoped = viewer.get(
"/api/servers",
params={"project": "forgeflow", "environment": "prod", "limit": 1},
)
assert scoped.status_code == 200
scoped_body = scoped.json()
assert scoped_body["page"]["total"] == 1
assert scoped_body["rows"] == [
{
"id": "forgeflow-prod-readonly",
"name": "forgeflow-prod-readonly",
"status": "unknown",
"summary": "Catalog registered; live runtime health has not been observed.",
"project": "forgeflow",
"environment": "prod",
"risk": None,
"canonicalName": None,
}
]
wrong_environment = viewer.get(
"/api/servers", params={"project": "forgeflow", "environment": "dev"}
)
assert wrong_environment.status_code == 200
assert wrong_environment.json()["page"]["total"] == 0
assert wrong_environment.json()["rows"] == []
wrong_project = viewer.get(
"/api/servers",
params={"project": "mobilityops", "environment": "prod", "q": "forgeflow-prod-readonly"},
)
assert wrong_project.status_code == 200
assert wrong_project.json()["page"]["total"] == 0
assert wrong_project.json()["rows"] == []
+519
View File
@@ -0,0 +1,519 @@
from __future__ import annotations
import os
from collections.abc import Iterator
from contextlib import ExitStack
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from http.cookies import SimpleCookie
from pathlib import Path
from typing import Any
from urllib.parse import parse_qs, urlparse
from uuid import UUID, uuid4
import pytest
from fastapi.routing import APIRoute
from fastapi.testclient import TestClient
from services.control_api.auth.config import OIDCSettings
from services.control_api.auth.fake_provider import FakeOIDCProvider
from services.control_api.auth.models import (
AuthenticatedPrincipal,
ManagementRole,
PrincipalKind,
)
from services.control_api.contextforge.fake import FakeContextForge
from services.control_api.contextforge.models import ContextForgeSettings
from services.control_api.persistence import Base, create_engine_from_url, session_factory
from services.control_api.persistence.machine_client import provision_machine_client
from services.control_api.persistence.models import (
AuditEventRow,
ClientBindingRow,
ClientRow,
ConnectorRow,
EnvironmentRow,
ProjectRow,
VirtualServerRow,
WorkspaceRow,
)
from services.runtime import control_api
from services.runtime.control_api import build_app
from services.runtime.state_sync import sync_runtime_state
ROOT = Path(__file__).resolve().parents[1]
ISSUER = "https://fake.operator-workspaces.test"
ROLE_GROUPS = {
ManagementRole.VIEWER: "hub-viewer",
ManagementRole.OPERATOR: "hub-operator",
ManagementRole.ADMIN: "hub-admin",
}
@dataclass
class RoleSession:
app: Any
client: TestClient
csrf: str
def get(self, path: str) -> Any:
return self.client.get(path)
def post(self, path: str, **kwargs: Any) -> Any:
headers = dict(kwargs.pop("headers", {}) or {})
headers["X-CSRF-Token"] = self.csrf
return self.client.post(path, headers=headers, **kwargs)
@pytest.fixture(scope="module", autouse=True)
def client_bindings_env() -> Iterator[None]:
previous_bindings = os.environ.get("MCP_CLIENT_BINDINGS_JSON")
previous_token = os.environ.get("OPERATOR_WORKSPACES_FAKE_CONTEXTFORGE_TOKEN")
os.environ["MCP_CLIENT_BINDINGS_JSON"] = "[]"
os.environ["OPERATOR_WORKSPACES_FAKE_CONTEXTFORGE_TOKEN"] = "operator-workspaces-fake-token" # noqa: S105
yield
if previous_bindings is None:
os.environ.pop("MCP_CLIENT_BINDINGS_JSON", None)
else:
os.environ["MCP_CLIENT_BINDINGS_JSON"] = previous_bindings
if previous_token is None:
os.environ.pop("OPERATOR_WORKSPACES_FAKE_CONTEXTFORGE_TOKEN", None)
else:
os.environ["OPERATOR_WORKSPACES_FAKE_CONTEXTFORGE_TOKEN"] = previous_token
@pytest.fixture(scope="module")
def db_path(tmp_path_factory: pytest.TempPathFactory) -> Path:
path = tmp_path_factory.mktemp("operator-workspaces") / "hub.db"
engine = create_engine_from_url(f"sqlite:///{path}")
Base.metadata.create_all(engine)
sessions = session_factory(engine)
with sessions() as session, session.begin():
sync_runtime_state(session, ROOT / "catalog", ISSUER)
engine.dispose()
return path
def _login(client: TestClient, provider: FakeOIDCProvider) -> str:
login = client.get("/auth/login", follow_redirects=False)
query = parse_qs(urlparse(login.headers["location"]).query)
code = provider.authorize(login.headers["location"])
callback = client.get(
"/auth/callback",
params={"code": code, "state": query["state"][0]},
follow_redirects=False,
)
cookie = SimpleCookie()
cookie.load(callback.headers["set-cookie"])
cookie_name = next(iter(cookie))
client.cookies.set(cookie_name, cookie[cookie_name].value)
return str(client.get("/auth/session").json()["csrf_token"])
@pytest.fixture(scope="module")
def role_sessions(db_path: Path) -> Iterator[dict[ManagementRole, RoleSession]]:
with ExitStack() as stack:
result: dict[ManagementRole, RoleSession] = {}
for role, group in ROLE_GROUPS.items():
provider = FakeOIDCProvider(issuer=ISSUER, user_groups=(group,))
oidc = OIDCSettings(
issuer=ISSUER,
audience=provider.audience,
client_id="operator-workspaces-hub",
redirect_uri="https://hub.test/auth/callback",
secure_cookie=True,
role_group_map={value: key for key, value in ROLE_GROUPS.items()},
)
engine = create_engine_from_url(f"sqlite:///{db_path}")
app = build_app(
oidc=oidc,
provider_transport=provider.transport(),
contextforge_settings=ContextForgeSettings(
base_url="https://contextforge.test",
auth_token_env="OPERATOR_WORKSPACES_FAKE_CONTEXTFORGE_TOKEN", # noqa: S106
),
contextforge_transport=FakeContextForge().transport(),
engine=engine,
catalog_root=ROOT / "catalog",
)
client = stack.enter_context(TestClient(app))
csrf = _login(client, provider)
result[role] = RoleSession(app=app, client=client, csrf=csrf)
yield result
def _ready_probe(_: str) -> dict[str, Any]:
return {"reachable": True, "status": "ready", "detail": "Direct connector readiness verified."}
def test_project_workspace_returns_registered_server_and_source_metadata(
role_sessions: dict[ManagementRole, RoleSession],
) -> None:
response = role_sessions[ManagementRole.VIEWER].get("/api/projects/ludarium")
assert response.status_code == 200, response.text
body = response.json()
assert body["mcpServers"]
assert body["mcpServers"][0]["endpointAlias"] == "/mcp/ludarium/prod"
assert body["mcpServers"][0]["stateSource"] in {"persisted", "catalog-only"}
assert body["knowledgeSources"]
source = body["knowledgeSources"][0]
assert source["uri"] == "discovery://repository/ludarium"
assert source["indexesSecrets"] is False
assert "scoped Knowledge connector" in source["accessBoundary"]
assert "README*" in source["include"]
assert "credentialReferences" not in response.text
def test_connector_workspace_is_scoped_and_secret_free(
role_sessions: dict[ManagementRole, RoleSession], monkeypatch: pytest.MonkeyPatch
) -> None:
probe_calls: list[str] = []
def record_probe(slug: str) -> dict[str, Any]:
probe_calls.append(slug)
return _ready_probe(slug)
monkeypatch.setattr(control_api, "_direct_connector_readiness", record_probe)
viewer = role_sessions[ManagementRole.VIEWER]
response = viewer.get("/api/connectors/knowledge-default?project=ludarium&environment=prod")
assert response.status_code == 200, response.text
body = response.json()
assert body["id"] == "knowledge-default"
assert body["connectorType"] == "knowledge"
assert body["registration"]["state"] == "registered"
assert body["readiness"]["source"] in {"persisted", "unavailable"}
if body["readiness"]["source"] == "unavailable":
assert body["readiness"]["observedAt"] is None
assert probe_calls == []
assert body["bindings"] == [
{"project": "ludarium", "environment": "prod", "verification": "verified"}
]
assert body["relatedTools"]
for sensitive_key in ("credentialReferences", "credentialReference", "endpoint", "token"):
assert sensitive_key not in body
assert "credentialReferences" not in response.text
assert "service_token" not in response.text
assert (
viewer.get("/api/connectors/knowledge-default?project=missing&environment=prod").status_code
== 404
)
assert (
viewer.get(
"/api/connectors/knowledge-default?project=ludarium&environment=staging"
).status_code
== 404
)
assert viewer.get("/api/connectors/knowledge-default?project=ludarium").status_code == 422
def test_connector_workspace_marks_old_persisted_health_unknown_without_probing(
role_sessions: dict[ManagementRole, RoleSession],
db_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
probe_calls: list[str] = []
def record_probe(slug: str) -> dict[str, Any]:
probe_calls.append(slug)
return _ready_probe(slug)
monkeypatch.setattr(control_api, "_direct_connector_readiness", record_probe)
engine = create_engine_from_url(f"sqlite:///{db_path}")
sessions = session_factory(engine)
observed_at = datetime.now(UTC) - timedelta(minutes=5)
with sessions() as session, session.begin():
workspace = session.query(WorkspaceRow).filter_by(slug="itworx").one()
project = (
session.query(ProjectRow).filter_by(workspace_id=workspace.id, slug="ludarium").one()
)
environment = (
session.query(EnvironmentRow)
.filter_by(workspace_id=workspace.id, project_id=project.id, name="prod")
.one()
)
connector = (
session.query(ConnectorRow)
.filter_by(
workspace_id=workspace.id,
project_id=project.id,
environment_scope="prod",
type="knowledge",
slug="knowledge-default",
)
.one_or_none()
)
if connector is None:
connector = ConnectorRow(
id=uuid4(),
workspace_id=workspace.id,
project_id=project.id,
environment_id=environment.id,
environment_scope="prod",
type="knowledge",
slug="knowledge-default",
)
session.add(connector)
connector.health_state = "healthy"
connector.last_check_at = observed_at
connector.endpoint = "https://knowledge.example.invalid/health"
connector.credential_reference = "vault://private-reference"
engine.dispose()
response = role_sessions[ManagementRole.VIEWER].get(
"/api/connectors/knowledge-default?project=ludarium&environment=prod"
)
assert response.status_code == 200, response.text
readiness = response.json()["readiness"]
assert readiness["source"] == "persisted"
assert readiness["stale"] is True
assert readiness["reachable"] is None
assert readiness["status"] == "unknown"
assert readiness["observedAt"]
assert "Stale observation" in readiness["detail"]
assert "knowledge.example.invalid" not in response.text
assert "vault://private-reference" not in response.text
assert probe_calls == []
def test_connector_and_onboarding_routes_deny_machine_principals_even_with_admin_role(
role_sessions: dict[ManagementRole, RoleSession],
) -> None:
app = role_sessions[ManagementRole.VIEWER].app
route = next(
route
for route in app.routes
if isinstance(route, APIRoute) and route.path == "/api/connectors/{slug}"
)
dependency_calls: list[Any] = []
def visit(dependant: Any) -> None:
if dependant.call is not None:
dependency_calls.append(dependant.call)
for child in dependant.dependencies:
visit(child)
visit(route.dependant)
authenticate = next(
call for call in dependency_calls if getattr(call, "__name__", "") == "authenticate"
)
now = datetime.now(UTC)
machine = AuthenticatedPrincipal(
subject="machine-connector-reader",
issuer=ISSUER,
audience=("operator-workspaces-hub",),
kind=PrincipalKind.MACHINE_CLIENT,
client_id=uuid4(),
roles=frozenset({ManagementRole.ADMIN}),
expires_at=now + timedelta(minutes=5),
issued_at=now,
)
app.dependency_overrides[authenticate] = lambda: machine
try:
client = role_sessions[ManagementRole.VIEWER].client
connector = client.get("/api/connectors/knowledge-default")
onboarding = client.get(f"/api/client-management/{uuid4()}/onboarding-status")
finally:
app.dependency_overrides.pop(authenticate, None)
assert connector.status_code == 403
assert onboarding.status_code == 403
def test_onboarding_verification_records_server_checks_without_enabling_client_or_binding(
role_sessions: dict[ManagementRole, RoleSession], db_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
probe_calls: list[str] = []
def record_probe(slug: str) -> dict[str, Any]:
probe_calls.append(slug)
return _ready_probe(slug)
monkeypatch.setattr(control_api, "_direct_connector_readiness", record_probe)
admin = role_sessions[ManagementRole.ADMIN]
operator = role_sessions[ManagementRole.OPERATOR]
viewer = role_sessions[ManagementRole.VIEWER]
created = admin.post(
"/api/client-management",
json={
"name": "resume-me",
"project": "gitea-loop",
"environment": "prod",
"kind": "native-2026",
},
)
assert created.status_code == 201, created.text
client_id = created.json()["client"]["id"]
operator_status = operator.get(f"/api/client-management/{client_id}/onboarding-status")
assert operator_status.status_code == 200, operator_status.text
assert operator_status.json()["checks"]["connector"]["state"] == "unknown"
assert probe_calls == []
status = viewer.get(f"/api/client-management/{client_id}/onboarding-status")
assert status.status_code == 200, status.text
before = status.json()
assert before["clientStatus"] == "pending"
assert before["checks"]["externalSubject"]["state"] == "pending"
assert before["checks"]["externalScope"]["state"] == "unavailable"
assert before["checks"]["catalog"]["state"] == "ready"
assert before["checks"]["hubBinding"]["state"] == "pending"
assert before["checks"]["connector"]["state"] == "unknown"
assert before["checks"]["connector"]["observedAt"] is None
assert before["checks"]["mcpProof"]["state"] == "unavailable"
handoff = before["provisioning"]
assert handoff == {
"status": "pending-authentik-service-account",
"subject": None,
"issuer": ISSUER,
"oauthUsername": f"svc-mcp-codex-gitea-loop-{client_id.replace('-', '')[:8]}",
"tokenEndpoint": f"{ISSUER}/application/o/token/",
"oauthClientId": "operator-workspaces-hub",
"steps": [
"Provision the named Authentik service account and app password.",
"Activate this pending Hub client with the exact Authentik user UUID.",
"Install the generated Codex stdio profile using a user-only credential file.",
"Run MCP discovery, one read-only invocation and one cross-project denial.",
],
}
assert not {"credentialReference", "bootstrapReference", "credential", "secret"} & set(handoff)
assert "credentialReference" not in status.text
assert "bootstrapReference" not in status.text
assert "authentik-bootstrap://" not in status.text
assert "authentik://service-account/" not in status.text
assert probe_calls == []
denied_verify = viewer.post(f"/api/client-management/{client_id}/verify")
assert denied_verify.status_code == 403
assert probe_calls == []
checked_pending = admin.post(f"/api/client-management/{client_id}/verify")
assert checked_pending.status_code == 200, checked_pending.text
assert probe_calls
pending_verification_probe_count = len(probe_calls)
assert checked_pending.json()["verification"]["checkedByManagement"] is True
assert checked_pending.json()["verification"]["verifiedByManagement"] is False
assert checked_pending.json()["clientStatus"] == "pending"
engine = create_engine_from_url(f"sqlite:///{db_path}")
sessions = session_factory(engine)
with sessions() as session, session.begin():
server = session.query(VirtualServerRow).filter_by(slug="gitea-loop-prod").one()
server.gateway_external_id = "fixture-contextforge-server"
with sessions() as session, session.begin():
provision_machine_client(
session,
issuer=ISSUER,
subject="authentik-user-uuid-1234",
oauth_username="svc-mcp-codex-gitea-loop-test1234",
name="resume-me",
project_slug="gitea-loop",
environment_name="prod",
public_host="hub.test",
client_id=UUID(client_id),
)
resumed = viewer.get(f"/api/client-management/{client_id}/onboarding-status")
assert resumed.status_code == 200, resumed.text
resumed_body = resumed.json()
assert resumed_body["clientStatus"] == "enabled"
assert resumed_body["checks"]["externalSubject"]["state"] == "ready"
assert resumed_body["checks"]["hubBinding"]["state"] == "ready"
assert resumed_body["provisioning"]["status"] == "authentik-service-account-provisioned"
assert resumed_body["provisioning"]["subject"] == "authentik-user-uuid-1234"
assert resumed_body["provisioning"]["oauthUsername"] == "svc-mcp-codex-gitea-loop-test1234"
assert resumed_body["provisioning"]["issuer"] == ISSUER
assert resumed_body["provisioning"]["tokenEndpoint"] == f"{ISSUER}/application/o/token/"
assert resumed_body["provisioning"]["oauthClientId"] == "operator-workspaces-hub"
assert len(probe_calls) == pending_verification_probe_count
assert resumed_body["checks"]["externalScope"]["state"] == "unavailable"
assert resumed_body["checks"]["mcpProof"]["state"] == "unavailable"
with sessions() as session, session.begin():
client_row = session.get(ClientRow, UUID(client_id))
assert client_row is not None
client_row.issuer = "https://foreign-identity.example.invalid"
probes_before_foreign_issuer_read = len(probe_calls)
foreign_issuer_status = viewer.get(f"/api/client-management/{client_id}/onboarding-status")
assert foreign_issuer_status.status_code == 200, foreign_issuer_status.text
foreign_issuer_body = foreign_issuer_status.json()
assert foreign_issuer_body["checks"]["externalSubject"]["state"] == "pending"
assert foreign_issuer_body["provisioning"]["subject"] is None
assert foreign_issuer_body["verification"]["verifiedByManagement"] is False
assert len(probe_calls) == probes_before_foreign_issuer_read
foreign_issuer_verification = admin.post(f"/api/client-management/{client_id}/verify")
assert foreign_issuer_verification.status_code == 200, foreign_issuer_verification.text
assert foreign_issuer_verification.json()["checks"]["externalSubject"]["state"] == "pending"
assert foreign_issuer_verification.json()["verification"]["verifiedByManagement"] is False
assert len(probe_calls) > probes_before_foreign_issuer_read
with sessions() as session, session.begin():
client_row = session.get(ClientRow, UUID(client_id))
assert client_row is not None
client_row.issuer = ISSUER
verified = admin.post(f"/api/client-management/{client_id}/verify")
assert verified.status_code == 200, verified.text
assert len(probe_calls) > pending_verification_probe_count
after = verified.json()
assert after["verification"]["checkedByManagement"] is True
assert after["verification"]["verifiedByManagement"] is True
assert after["verification"]["hubChecksPassed"] is True
assert after["checks"]["externalScope"]["state"] == "unavailable"
assert after["checks"]["mcpProof"]["state"] == "unavailable"
verified_probe_count = len(probe_calls)
persisted_status = viewer.get(f"/api/client-management/{client_id}/onboarding-status")
assert persisted_status.status_code == 200, persisted_status.text
assert persisted_status.json()["checks"]["connector"]["state"] == "healthy"
assert persisted_status.json()["verification"]["verifiedByManagement"] is True
assert len(probe_calls) == verified_probe_count
for stale_checked_at in (
datetime.now(UTC) - timedelta(minutes=5),
datetime.now(UTC) + timedelta(minutes=5),
):
stale_timestamp = stale_checked_at.isoformat()
with sessions() as session, session.begin():
client_row = session.get(ClientRow, UUID(client_id))
assert client_row is not None
metadata = dict(client_row.record_metadata or {})
verification = dict(metadata["onboardingVerification"])
verification["checkedAt"] = stale_timestamp
metadata["onboardingVerification"] = verification
client_row.record_metadata = metadata
stale_status = viewer.get(f"/api/client-management/{client_id}/onboarding-status")
assert stale_status.status_code == 200, stale_status.text
stale_body = stale_status.json()
assert stale_body["checks"]["connector"]["state"] == "unknown"
assert stale_body["checks"]["connector"]["observedAt"] is None
assert stale_body["verification"]["checkedAt"] == stale_timestamp
assert stale_body["verification"]["checkedByManagement"] is True
assert stale_body["verification"]["verifiedByManagement"] is False
assert stale_body["verification"]["hubChecksPassed"] is False
assert len(probe_calls) == verified_probe_count
with sessions() as session:
client_row = session.get(ClientRow, UUID(client_id))
workspace = session.query(WorkspaceRow).filter_by(slug="itworx").one()
binding = session.query(ClientBindingRow).filter_by(client_id=UUID(client_id)).one()
audit_event = (
session.query(AuditEventRow)
.filter_by(client_id=UUID(client_id), event_type="client.onboarding.verify")
.order_by(AuditEventRow.occurred_at.desc())
.first()
)
engine.dispose()
assert client_row is not None and client_row.status == "active"
assert binding.workspace_id == workspace.id
assert binding.enabled is True
assert client_row.record_metadata["onboardingVerification"]["verifiedByManagement"] is True
assert "credential" not in str(client_row.record_metadata["onboardingVerification"]).lower()
assert audit_event is not None
assert audit_event.principal_subject == "user-management-1"
assert audit_event.sanitized_arguments["hubChecksPassed"] is True
def test_unauthed_client_is_not_exposed_by_connector_management_route(
role_sessions: dict[ManagementRole, RoleSession],
) -> None:
unauthenticated = TestClient(role_sessions[ManagementRole.VIEWER].app)
unauthenticated.cookies.clear()
response = unauthenticated.get("/api/connectors/knowledge-default")
assert response.status_code == 401
+21 -11
View File
@@ -47,19 +47,29 @@ def invocation(
class WP110GovernanceTests(unittest.TestCase):
def test_merge_result_is_retained_in_append_only_audit(self) -> None:
audit = InMemoryAuditLog()
engine = PolicyEngine([
PolicyRule(
slug="allow-merge", project="ludarium", priority=10,
description="allow merge", match={"tool": "gitea.pullrequest.merge"},
decision=Decision.ALLOW,
)
])
engine = PolicyEngine(
[
PolicyRule(
slug="allow-merge",
project="ludarium",
priority=10,
description="allow merge",
match={"tool": "gitea.pullrequest.merge"},
decision=Decision.ALLOW,
)
]
)
governance = Governance(engine, audit)
evidence = {
"repositoryRef": "test-repo", "pr": 7, "headSha": "e" * 40,
"baseSha": "a" * 40, "requiredContext": "Managed validation / full (pull_request)",
"validationRunId": 42, "validationEvent": "pull_request",
"validationConclusion": "success", "mergeMethod": "squash",
"repositoryRef": "test-repo",
"pr": 7,
"headSha": "e" * 40,
"baseSha": "a" * 40,
"requiredContext": "Managed validation / full (pull_request)",
"validationRunId": 42,
"validationEvent": "pull_request",
"validationConclusion": "success",
"mergeMethod": "squash",
"resultingMainSha": "6" * 40,
}
result = governance.invoke(
+2
View File
@@ -93,6 +93,7 @@ def test_audit_read_denied_for_viewer_alone() -> None:
"tool.invoke",
"operations.check",
"client.bootstrap",
"client.verify",
"client.disable",
"client.revoke",
],
@@ -111,6 +112,7 @@ def test_client_lifecycle_actions_allowed_for_operator_and_admin(
"tool.invoke",
"operations.check",
"client.bootstrap",
"client.verify",
"client.disable",
"client.revoke",
],
+19 -14
View File
@@ -449,26 +449,31 @@ def test_only_reviewed_gitea_r3_lifecycle_set_is_allowed_in_production() -> None
for tool in ("gitea.pullrequest.sync", "gitea.pullrequest.merge"):
loop_trace = engine.evaluate(
InvocationContext(
workspace="itworx",
project="gitea-loop",
environment="prod",
principal="chatgpt-gitea",
tool=tool,
connector="gitea",
risk_class="R3",
environment_classification="production",
binding_present=True,
client_enabled=True,
workspace="itworx",
project="gitea-loop",
environment="prod",
principal="chatgpt-gitea",
tool=tool,
connector="gitea",
risk_class="R3",
environment_classification="production",
binding_present=True,
client_enabled=True,
)
)
assert loop_trace.decision == Decision.ALLOW_IF_CONDITIONS
assert loop_trace.matched_rule_ids == ("036-gitea-loop-reviewed-lifecycle-v1",)
denied = engine.evaluate(
InvocationContext(
workspace="itworx", project="gitea-loop", environment="prod",
principal="chatgpt-gitea", tool="gitea.pullrequest.merge",
connector="gitea", risk_class="R3",
environment_classification="production", binding_present=False,
workspace="itworx",
project="gitea-loop",
environment="prod",
principal="chatgpt-gitea",
tool="gitea.pullrequest.merge",
connector="gitea",
risk_class="R3",
environment_classification="production",
binding_present=False,
client_enabled=True,
)
)
+9 -3
View File
@@ -204,8 +204,11 @@ def test_policy_simulate_available_to_every_role(
def test_catalog_preview_available_to_every_role(
role_sessions: dict[ManagementRole, RoleSession], role: ManagementRole
) -> None:
response = role_sessions[role].post("/api/catalog/preview", json={"project": "nonexistent"})
response = role_sessions[role].post("/api/catalog/preview", json={"project": "ludarium"})
assert response.status_code == 200, (role, response.text) # type: ignore[union-attr]
preview = response.json() # type: ignore[union-attr]
assert preview["valid"] is True
assert preview["changes"] == ["reconcile all registered project routes, including ludarium"]
# --- Tool readiness: bounded live probe, operator+ only ------------------
@@ -400,6 +403,9 @@ def test_live_operations_check_available_to_operator_and_admin(
response = role_sessions[role].post("/api/operations/check")
assert response.status_code == 200, (role, response.text) # type: ignore[union-attr]
assert response.json()["connectors"] # type: ignore[union-attr]
assert {"id": "knowledge-default", "name": "Knowledge Default"} in response.json()[
"connectors"
]["itworx-knowledge"]["connectorRefs"] # type: ignore[union-attr]
# --- Audit read: auditor+ only, viewer denied ---------------------------
@@ -457,7 +463,7 @@ def test_client_create_allowed_for_admin(role_sessions: dict[ManagementRole, Rol
assert response.status_code == 201, response.text # type: ignore[union-attr]
@pytest.mark.parametrize("action", ["bootstrap", "disable", "revoke"])
@pytest.mark.parametrize("action", ["bootstrap", "verify", "disable", "revoke"])
@pytest.mark.parametrize("role", [ManagementRole.VIEWER, ManagementRole.AUDITOR])
def test_client_lifecycle_actions_denied_below_operator(
role_sessions: dict[ManagementRole, RoleSession], role: ManagementRole, action: str
@@ -470,7 +476,7 @@ def test_client_lifecycle_actions_denied_below_operator(
assert response.status_code == 403, (role, action, response.text) # type: ignore[union-attr]
@pytest.mark.parametrize("action", ["bootstrap", "disable"])
@pytest.mark.parametrize("action", ["bootstrap", "verify", "disable"])
def test_client_lifecycle_actions_allowed_for_operator(
role_sessions: dict[ManagementRole, RoleSession], action: str
) -> None:
+6 -10
View File
@@ -237,24 +237,20 @@ def test_dashboard_drift_is_measured_against_managed_connector_gateways(
assert "connector gateway drift" in drift["detail"].lower()
def test_dashboard_project_and_connector_health_matches_resource_list_not_hardcoded_unknown(
def test_dashboard_distinguishes_registration_from_connector_readiness(
viewer_client: TestClient,
) -> None:
"""The dashboard overview used to hardcode "unknown" for every project
and connector regardless of catalog state, while the /api/resource
list endpoint (GET /api/projects, /api/connectors) already computed a
real "healthy"/"unknown" signal from catalog registration validity --
the two surfaces visually disagreed. Both now share
_catalog_registration_health(); this proves the dashboard reflects it
too, not just the resource list.
"""
"""A valid registration is not evidence that a connector route is live."""
dashboard = viewer_client.get("/api/dashboard")
assert dashboard.status_code == 200
body = dashboard.json()
assert body["projects"], "expected at least one registered project in the test catalog"
assert body["connectors"], "expected at least one registered connector in the test catalog"
assert all(item["health"] == "healthy" for item in body["projects"])
assert all(item["state"] == "healthy" for item in body["connectors"])
assert all(item["state"] == "unknown" for item in body["connectors"])
assert all(item["registrationState"] == "registered" for item in body["connectors"])
assert all(item["readiness"]["source"] == "not-probed" for item in body["connectors"])
assert all(item["readiness"]["observedAt"] is None for item in body["connectors"])
projects_list = viewer_client.get("/api/projects")
assert projects_list.status_code == 200