Publish curated Unraid-AppOps-Gateway source from ad0a390e431166c3198a2ac3bec010c2b44f2dea

This commit is contained in:
Public Source Publisher committed 2026-10-02 09:56:13 +00:00
1 parent 29c1e3d5a1
commit 98d31477f9
35 files changed
+7264 -185

No files matched your search

+180 -2
View File
@@ -21,6 +21,7 @@ from .setup_service import apply_stored_connector_settings
GENERIC_EXECUTOR_ACTIONS = {
"container.restart",
"controller.artifact.promote",
"controller.managed-storage.initialize",
"deployment.targets.sync",
"deployment.runnerless.provision",
"deployment.target.deploy_workmesh",
@@ -69,6 +70,10 @@ APPROVAL_EXECUTOR_ACTIONS = {
"unraid.vm.pause",
}
MCP_TYPED_EXECUTOR_ACTIONS = APPROVAL_EXECUTOR_ACTIONS | {
"controller.managed-storage.initialize",
}
HUMAN_APPROVAL_ONLY_ACTIONS = {"deployment.target.promote_workmesh", "privatemuse.comfyui.install_artifact"}
_APPROVAL_STEPS = {
@@ -148,7 +153,7 @@ def approval_execution_supported(action: str) -> bool:
def mcp_execution_supported(action: str) -> bool:
"""MCP is a no-human-approval surface; never admit human-only executors."""
return approval_execution_supported(action) and action not in HUMAN_APPROVAL_ONLY_ACTIONS
return action in MCP_TYPED_EXECUTOR_ACTIONS and action not in HUMAN_APPROVAL_ONLY_ACTIONS
def _execute_deployment_target_sync(
@@ -1758,7 +1763,7 @@ def execute_governed_action(
small allowlist of typed rollback/post-check capable executors below is reachable.
"""
if not approval_execution_supported(action):
if not mcp_execution_supported(action):
return {
"success": False,
"status": "failed",
@@ -1786,6 +1791,24 @@ def execute_governed_action(
original_policy_decision=original_policy_decision,
)
if action == "controller.managed-storage.initialize":
if target != "tower/deployment-controller":
return {
"success": False,
"status": "failed",
"result": "blocked",
"message": "Managed storage initialization accepts only tower/deployment-controller.",
"policyDecision": "blocked",
"originalPolicyDecision": original_policy_decision,
}
return _execute_managed_appdata_initialize(
session,
source=source,
risk=risk,
reason=reason,
original_policy_decision=original_policy_decision,
)
if action == "runner.fleet.upgrade_v2":
if target.strip().lower() != "tower/runner-fleet-v2":
return {
@@ -2058,6 +2081,161 @@ def execute_governed_action(
}
def _execute_managed_appdata_initialize(
session: Session,
*,
source: str,
risk: str,
reason: str,
original_policy_decision: str,
) -> dict[str, Any]:
"""Initialize only the fixed storage marker after trust and policy opt-in."""
from .deployment_broker import DeploymentBrokerClient
from .enrollment_plane import EnrollmentPlane
from .secret_store import get_secret
action = "controller.managed-storage.initialize"
target = "tower/deployment-controller"
trust = EnrollmentPlane.get_trust_config(session)
if (
trust is None
or trust.trusted_appdata_root != "/var/lib/docker/volumes/itworx-managed-appdata/_data"
or trust.managed_appdata_backup_acknowledged is not True
):
audit = log_event(
session,
source="action",
identity=source,
action_id=action,
target=target,
target_type="controller",
risk=risk,
policy_decision="allowed",
reason="Managed Docker-root storage requires an explicit trusted selector and backup acknowledgement.",
result="blocked",
redacted_metadata={"executor": action, "trustOptIn": False},
)
return {
"success": False,
"status": "failed",
"result": "blocked",
"message": "Managed storage is not explicitly selected and acknowledged in enrollment trust.",
"auditId": audit.id,
"policyDecision": "allowed",
"originalPolicyDecision": original_policy_decision,
}
bootstrap_secret = get_secret(session, trust.bootstrap_secret_ref, touch=False)
if not bootstrap_secret:
audit = log_event(
session,
source="action",
identity=source,
action_id=action,
target=target,
target_type="controller",
risk=risk,
policy_decision="allowed",
reason="Controller bootstrap capability is unavailable by its configured reference.",
result="failed",
redacted_metadata={"executor": action, "secretMaterialReturned": False},
)
return {
"success": False,
"status": "failed",
"result": "failed",
"message": "Controller bootstrap capability is unavailable.",
"auditId": audit.id,
"policyDecision": "allowed",
"originalPolicyDecision": original_policy_decision,
}
steps = [
"Verify the exact target, trust selector and backup acknowledgement",
"Authenticate the fixed controller request with bootstrap HMAC and replay protection",
"Validate the protected controller, target registry, repository root and fixed Docker volume",
"Initialize the selector only if the managed Docker volume is empty and unchanged",
"Re-read the fixed selector and record sanitized controller readiness",
]
job = create_job(
session,
action_id=action,
target=target,
source=source,
risk=risk,
policy_decision="allowed",
steps=steps,
)
start_job(session, job)
set_job_progress(session, job, 20, "Validating fixed managed-storage initialization prerequisites.")
try:
result = DeploymentBrokerClient().initialize_managed_appdata(secret=bootstrap_secret)
reported_status = result.get("status")
status = (
reported_status
if isinstance(reported_status, str)
and reported_status in {"initialized", "already_initialized"}
else "refused"
)
success = (
status in {"initialized", "already_initialized"}
and result.get("trustedAppdataRoot") == trust.trusted_appdata_root
and result.get("backupLocation") == "dockerRootDir"
)
message = (
"Managed storage selector initialized and verified."
if success
else "Managed storage initialization was refused or unavailable."
)
except Exception:
result = {"status": "error"}
status = "error"
success = False
message = "Managed storage initialization was refused or unavailable."
completed = complete_job(
session,
job,
status="succeeded" if success else "failed",
result_summary=message,
)
audit = log_event(
session,
source="action",
identity=source,
action_id=action,
target=target,
target_type="controller",
risk=risk,
policy_decision="allowed",
reason=reason,
result="success" if success else "failed",
execution_plan={"steps": steps, "jobId": completed.id},
after_state={
"status": status,
"trustedAppdataRoot": trust.trusted_appdata_root if success else None,
"backupLocation": "dockerRootDir" if success else None,
},
redacted_metadata={
"executor": action,
"target": target,
"integration": "mcp" if source == "chatgpt-mcp" else "operator",
"originalPolicyDecision": original_policy_decision,
"secretMaterialReturned": False,
},
)
return {
"success": success,
"status": "success" if success else "failed",
"result": "success" if success else "failed",
"message": message,
"jobId": completed.id,
"auditId": audit.id,
"policyDecision": "allowed",
"originalPolicyDecision": original_policy_decision,
}
def execute_approved_action(session: Session, approval: Approval) -> dict[str, Any]:
if not approval_execution_supported(approval.action):
return {
+4
View File
@@ -8,6 +8,7 @@ ACTIONS = [
'container.stop',
'runner.fleet.upgrade_v2',
'controller.artifact.promote',
'controller.managed-storage.initialize',
'gateway.clear_cache',
'stack.status',
'stack.redeploy',
@@ -214,6 +215,7 @@ ACTION_RISK = {
'container.stop': 'high',
'runner.fleet.upgrade_v2': 'high',
'controller.artifact.promote': 'high',
'controller.managed-storage.initialize': 'high',
'gateway.clear_cache': 'high',
'stack.status': 'low',
'stack.redeploy': 'high',
@@ -525,6 +527,8 @@ def action_category(action: str) -> str:
def action_default_target(action: str) -> str:
if action == 'controller.artifact.promote':
return 'tower/deployment-controller'
if action == 'controller.managed-storage.initialize':
return 'tower/deployment-controller'
if action == 'runner.fleet.upgrade_v2':
return 'tower/runner-fleet-v2'
if action == 'deployment.targets.sync':
@@ -39,12 +39,12 @@ def helper_container_spec(*, revision: str, image_id: str) -> dict[str, Any]:
raise ValueError("controller_promoter_image_invalid")
command = f"""set -Eeuo pipefail
umask 077
revision='{revision}'
version='{CONTROLLER_VERSION}'
repo='/gitea-repositories/jens/unraid-appops-gateway.git'
work='/mnt/cache/appdata/gitea-deploy-runners/.controller-promoter-{revision[:12]}'
rm -rf -- "$work"
install -d -m 0700 "$work"
work="$(mktemp -d -p /tmp 'itworx-controller-artifact-promoter-{revision[:12]}.XXXXXX')"
[[ -d "$work" && ! -L "$work" && "$(stat -c '%u:%a' "$work")" == '0:700' ]]
cleanup() {{ rm -rf -- "$work"; }}
trap cleanup EXIT
command -v git >/dev/null
@@ -59,7 +59,10 @@ git --git-dir="$repo" archive --format=tar "$revision" \
ops/gitea-deploy-broker \
scripts/prepare_controller_artifact.py \
scripts/reconcile_controller_state.py \
scripts/run_repopulse_adoption.py > "$work/controller.tar"
scripts/run_repopulse_adoption.py \
scripts/prepare_controller_hub_release_volume.py \
scripts/upgrade_deployment_controller.sh > "$work/controller.tar"
chmod 0600 "$work/controller.tar"
archive_sha="$(sha256sum "$work/controller.tar" | cut -d ' ' -f 1)"
git --git-dir="$repo" show "$revision:scripts/upgrade_deployment_controller.sh" > "$work/upgrade.sh"
chmod 0700 "$work/upgrade.sh"
@@ -70,6 +73,7 @@ bash "$work/upgrade.sh" \
--source-revision "$revision" \
--source-tree "$tree" \
--controller-version "$version" \
--image-id '{image_id}' \
--confirm UPGRADE_DEPLOYMENT_CONTROLLER_V2
"""
@@ -88,6 +92,7 @@ bash "$work/upgrade.sh" \
"/mnt/cache:/mnt/cache",
"/mnt/user:/mnt/user",
"/srv/example/appdata/gitea/git/repositories:/gitea-repositories:ro",
"/var/lib/docker:/var/lib/docker:ro",
"/var/run/docker.sock:/var/run/docker.sock",
],
"NetworkMode": "host",
+12
View File
@@ -311,6 +311,18 @@ class DeploymentBrokerClient:
payload={},
)
def initialize_managed_appdata(self, *, secret: str) -> dict[str, Any]:
"""Initialize only the fixed managed storage root through bootstrap HMAC."""
return self._request(
"POST",
"/v1/appops/managed-storage/initialize",
secret=secret,
payload={
"apiVersion": "itworx.managed-storage-initialize/v1",
"target": "tower/deployment-controller",
},
)
def repopulse_adoption_capabilities(self, *, secret: str) -> dict[str, Any]:
"""Read fixed RepoPulse adoption readiness through bootstrap HMAC v2."""
return self._request(
+123 -4
View File
@@ -22,7 +22,12 @@ from ..connectors.nginx_proxy_manager import NginxProxyManagerConnector
from ..connectors.portainer import PortainerConnector
from ..core.time import utc_now
from ..db.models import AppSetting, Enrollment, EnrollmentJob, Policy
from ..schemas.enrollment import EnrollmentPlanV1, EnrollmentTrustConfigV1
from ..schemas.enrollment import (
LEGACY_APPDATA_ROOT,
MANAGED_DOCKER_APPDATA_ROOT,
EnrollmentPlanV1,
EnrollmentTrustConfigV1,
)
from .audit_logger import log_event
from .deployment_broker import DeploymentBrokerClient
from .enrollment_profiles import PROFILE_VERSION, profile_contract, profile_summary
@@ -40,6 +45,47 @@ _TERMINAL = {"ready", "failed", "compensation_required", "preflight_failed"}
_CONTROLLER_EVIDENCE = {"status", "targetId", "repository", "applicationId", "appdataPath"}
_PROXY_EVIDENCE = {"success", "status", "hostId", "hostname"}
_OIDC_EVIDENCE = {"success", "status", "applicationId", "providerId", "clientSecretCreated"}
_APPDATA_ROOT_STATUSES = {"ready", "needs_initialization", "unavailable"}
def _trusted_appdata_roots_readiness(
controller_capability: dict[str, Any],
) -> list[dict[str, str]]:
roots = (LEGACY_APPDATA_ROOT, MANAGED_DOCKER_APPDATA_ROOT)
unavailable = {root: "unavailable" for root in roots}
reported = controller_capability.get("availableTrustedAppdataRoots")
if isinstance(reported, list):
if len(reported) != len(roots):
return [{"root": root, "status": unavailable[root]} for root in roots]
statuses: dict[str, str] = {}
for item in reported:
if not isinstance(item, dict):
return [{"root": root, "status": unavailable[root]} for root in roots]
root = item.get("root")
status = item.get("status")
if (
not isinstance(root, str)
or root not in unavailable
or root in statuses
or not isinstance(status, str)
or status not in _APPDATA_ROOT_STATUSES
):
return [{"root": expected, "status": unavailable[expected]} for expected in roots]
statuses[root] = status
if set(statuses) != set(roots):
return [{"root": root, "status": unavailable[root]} for root in roots]
return [{"root": root, "status": statuses[root]} for root in roots]
# A legacy V1 controller reports only its configured root. It cannot attest
# independent managed-volume readiness, so only its exact legacy root can
# be marked ready through this compatibility path.
if "availableTrustedAppdataRoots" not in controller_capability:
if (
controller_capability.get("status") == "ready"
and controller_capability.get("trustedAppdataRoot") == LEGACY_APPDATA_ROOT
):
unavailable[LEGACY_APPDATA_ROOT] = "ready"
return [{"root": root, "status": unavailable[root]} for root in roots]
class EnrollmentError(ValueError):
@@ -135,6 +181,8 @@ class EnrollmentPlane:
"status": "needs_setup",
"blockers": ["enrollment_trust_not_configured"],
"configured": False,
"trustedAppdataRoot": LEGACY_APPDATA_ROOT,
"managedAppdataBackupAcknowledged": False,
"secretStore": storage,
}
return {
@@ -143,6 +191,7 @@ class EnrollmentPlane:
"configured": True,
"controllerIdentity": trust.controller_identity,
"trustedAppdataRoot": trust.trusted_appdata_root,
"managedAppdataBackupAcknowledged": trust.managed_appdata_backup_acknowledged,
"approvedNetworks": list(trust.approved_networks),
"allowedDomainSuffixes": list(trust.allowed_domain_suffixes),
"autonomousProfiles": list(trust.autonomous_profiles),
@@ -162,11 +211,43 @@ class EnrollmentPlane:
bootstrap_secret = get_secret(session, trust.bootstrap_secret_ref, touch=False)
if bootstrap_secret:
controller_capability = self.broker.enrollment_capabilities(secret=bootstrap_secret)
available_roots = _trusted_appdata_roots_readiness(controller_capability)
if trust is not None:
selected_root = next(
(
item
for item in available_roots
if item["root"] == trust.trusted_appdata_root
),
None,
)
controller_root_matches = bool(selected_root and selected_root.get("status") == "ready")
else:
controller_root_matches = False
broker_ready = bool(
bootstrap
and self.broker.is_configured()
and controller_capability.get("status") == "ready"
and controller_root_matches
)
controller_managed_storage = controller_capability.get("managedStorage")
if not isinstance(controller_managed_storage, dict):
controller_managed_storage = {}
if trust is None or trust.trusted_appdata_root != MANAGED_DOCKER_APPDATA_ROOT:
managed_storage_status = "not_selected"
managed_storage_blocker = ""
elif not trust.managed_appdata_backup_acknowledged:
managed_storage_status = "blocked"
managed_storage_blocker = "managed_appdata_backup_acknowledgement_required"
elif controller_root_matches and controller_managed_storage.get("status") == "ready":
managed_storage_status = "ready"
managed_storage_blocker = ""
elif controller_managed_storage.get("initializationAvailable") is True:
managed_storage_status = "initialization_required"
managed_storage_blocker = "managed_appdata_initialization_required"
else:
managed_storage_status = "unavailable"
managed_storage_blocker = "managed_appdata_volume_unavailable"
npm_ready = self.npm.is_configured()
oidc_configured = bool(
trust
@@ -193,9 +274,27 @@ class EnrollmentPlane:
"policyProvisioning": item(configured, "policy_template_unavailable"),
"rollback": item(broker_ready, "deployment_executor_unavailable"),
"verification": item(broker_ready, "deployment_executor_unavailable"),
"availableTrustedAppdataRoots": available_roots,
"managedStorage": {
"status": managed_storage_status,
"blockerCode": managed_storage_blocker,
"root": MANAGED_DOCKER_APPDATA_ROOT,
"backupScope": "dockerRootDir",
"backupAcknowledged": bool(
trust and trust.managed_appdata_backup_acknowledged
),
},
"controller": {
"status": str(controller_capability.get("status") or "unavailable"),
"reason": str(controller_capability.get("reason") or "deployment_executor_unavailable"),
"status": "ready" if broker_ready else "unavailable",
"reason": (
""
if broker_ready
else str(controller_capability.get("reason") or (
"trusted_appdata_root_mismatch"
if controller_capability.get("status") == "ready" and not controller_root_matches
else "deployment_executor_unavailable"
))
),
},
"profiles": [profile_summary(name) for name in ("internal-webapp", "internal-webapp-oidc", "internal-service", "repopulse-authenticated")],
}
@@ -233,6 +332,7 @@ class EnrollmentPlane:
"container_name": plan.target.container_name,
"compose_file": plan.target.compose_file,
"appdata_path": plan.appdata_path,
"trusted_appdata_root": plan.trusted_appdata_root,
"data_path": plan.data_path,
"runtime_uid": plan.storage.runtime_uid,
"runtime_gid": plan.storage.runtime_gid,
@@ -264,6 +364,17 @@ class EnrollmentPlane:
checks.append(_check("network", "passed", "Requested Docker network is within the trusted profile boundary."))
else:
checks.append(_check("network_unavailable", "blocked", "Requested Docker network is not approved for enrollment."))
# RepoPulse is an established, fixed legacy-root exception; the
# operator's selected root applies to other new targets only.
expected_appdata_root = (
LEGACY_APPDATA_ROOT
if plan.profile == "repopulse-authenticated"
else trust.trusted_appdata_root
)
if plan.trusted_appdata_root == expected_appdata_root:
checks.append(_check("trusted_appdata_root", "passed", "Requested appdata root matches its fixed profile trust boundary."))
else:
checks.append(_check("trusted_appdata_root_mismatch", "blocked", "Requested appdata root does not match the explicit operator trust boundary."))
if plan.profile in trust.autonomous_profiles:
checks.append(_check("autonomous_profile", "passed", "Deployment profile is approved for autonomous enrollment."))
else:
@@ -397,7 +508,15 @@ class EnrollmentPlane:
checks.append(_check("secret_store", "passed" if storage_ready else "blocked", "Production-grade write-only secret storage is ready." if storage_ready else "Enrollment requires APPOPS_SECRET_KEY from a protected environment variable or file; the development fallback is not permitted."))
if trust:
checks.append(_check("controller_bootstrap_secret", "passed" if has_secret(session, trust.bootstrap_secret_ref) else "blocked", "Controller bootstrap secret is available by reference." if has_secret(session, trust.bootstrap_secret_ref) else "Controller bootstrap secret reference is not configured."))
if has_secret(session, trust.bootstrap_secret_ref):
expected_appdata_root = (
LEGACY_APPDATA_ROOT
if plan.profile == "repopulse-authenticated"
else trust.trusted_appdata_root
)
root_matches_trust = plan.trusted_appdata_root == expected_appdata_root
if not root_matches_trust:
checks.append(_check("controller_target", "blocked", "Controller preflight was skipped because the requested appdata root is outside the profile trust boundary."))
elif has_secret(session, trust.bootstrap_secret_ref):
controller = self.broker.enrollment_preflight(
self._controller_payload(plan, enrollment_id="enr-00000000000000000000"),
secret=get_secret(session, trust.bootstrap_secret_ref, touch=False) or "",
@@ -10,6 +10,7 @@ from ..core.redaction import redact_message
_CONTROLLER = "gitea-deploy-control"
_REPOSITORY = "jens/itworx_mcp_hub"
_COMPOSE_PROJECT = "itworx-mcp-hub"
_TOPOLOGY_CONTRACT = "itworx.mcp-hub-topology/17-v1"
# One-time fail-closed re-attestation for the existing MCP Hub runtime. The
# script never builds, restarts, recreates, stops or deploys a container. It
@@ -28,11 +29,12 @@ import sys
import time
repository, expected_compose_project = sys.argv[1:3]
registry_path = Path("/opt/gitea-deploy/projects.json")
host_registry_path = Path("/mnt/cache/appdata/gitea-deploy-runners/controller/projects.json")
registry_path = Path("/controller-config/projects.json")
host_registry_path = Path("/controller-hub-releases/controller-config/projects.json")
state_root = Path("/mnt/cache/appdata/gitea-deploy-runners/state")
global_lock_path = Path("/mnt/cache/appdata/gitea-deploy-runners/deploy.lock")
owned_image_source = "https://gitea.example.com/jens/itworx-mcp-hub"
topology_contract = "itworx.mcp-hub-topology/17-v1"
if repository != "jens/itworx_mcp_hub" or expected_compose_project != "itworx-mcp-hub":
raise SystemExit(40)
@@ -69,6 +71,10 @@ expected_fixed = {
"itworx-mcp-hub-web-1",
"itworx-mcp-hub-worker-1",
}
expected_runtime = expected_fixed | {
"itworx-mcp-hub-connector-ingestion-1",
"itworx-mcp-hub-ingestion-clamd-1",
}
if (
project.get("branch") != "main"
or project.get("slug") != expected_compose_project
@@ -77,6 +83,7 @@ if (
or set(expected_containers) != expected_fixed
):
raise SystemExit(23)
expected_containers = sorted(expected_runtime)
state_path = state_root / f"{expected_compose_project}.json"
project_lock_path = state_root / "locks" / f"{expected_compose_project}.lock"
@@ -310,12 +317,33 @@ with project_lock:
receipt = state.get("live_receipt")
recorded_artifacts = state.get("live_artifacts")
registered_live_containers = (
recorded_artifacts.get("containers")
if isinstance(recorded_artifacts, dict)
else None
)
if isinstance(registered_live_containers, dict) and set(registered_live_containers) == expected_fixed:
print("mcp-hub-topology-migration-required", flush=True)
raise SystemExit(56)
already_reconciled = (
state.get("live_legacy_provenance") is True
and recorded_status == "success"
recorded_status == "success"
and recorded_live_status == "healthy"
and re.fullmatch(r"[0-9a-f]{40}", recorded_live) is not None
and isinstance(recorded_artifacts, dict)
and state.get("compose_project") == expected_compose_project
and isinstance(registered_live_containers, dict)
and set(registered_live_containers) == expected_runtime
and recorded_artifacts.get("topology_contract") == topology_contract
and (
state.get("live_legacy_provenance") is True
or (
isinstance(receipt, dict)
and receipt.get("schema") == "gitea-deploy-receipt.v1"
and receipt.get("repository") == repository
and receipt.get("revision") == recorded_live
)
)
)
if not already_reconciled and (
recorded_status != "failed"
@@ -513,7 +541,11 @@ with project_lock:
if not drift_found:
raise SystemExit(52)
artifacts = {"containers": container_artifacts, "compose_sha256": ""}
artifacts = {
"containers": container_artifacts,
"compose_sha256": "",
"topology_contract": topology_contract,
}
now = int(time.time())
payload = {
"schema_version": 3,
@@ -678,8 +710,16 @@ class McpHubDeploymentReconciliation:
53: "reconciled MCP Hub deployment state could not be written durably",
54: "reconciled MCP Hub deployment state did not verify after write",
55: "unknown MCP Hub live status was not produced by a proven recoverable snapshot-stage failure",
56: "MCP Hub 15-to-17 topology migration requires its guarded protected-main deployment",
}
if exit_code != 0:
if exit_code == 56:
return {
"status": "topology_migration_required",
"repository": _REPOSITORY,
"composeProject": _COMPOSE_PROJECT,
"reason": reasons[exit_code],
}
return {
"status": "validation_failed",
"reason": reasons.get(exit_code, "MCP Hub deployment reconciliation failed closed"),
+22
View File
@@ -142,6 +142,28 @@ def evaluate_policy(
'scope_decision': None,
}
if action == "controller.managed-storage.initialize":
exact_policy = session.exec(
select(Policy).where(
(Policy.action == action) & (Policy.target == "tower/deployment-controller")
)
).first()
allowed = bool(
target == "tower/deployment-controller"
and source != "voice"
and exact_policy is not None
and exact_policy.manual == "allowed"
)
return {
"decision": "allowed" if allowed else "blocked",
"reason": (
"Managed storage initialization requires an exact-target explicit operator policy."
),
"risk": ACTION_RISK.get(action, "high"),
"policy_id": exact_policy.id if allowed and exact_policy is not None else None,
"scope_decision": "allowed" if allowed else None,
}
if action in _DEFAULT_DECISIONS:
reason = _DEFAULT_REASONS.get(action, 'Action uses default governance policy.')
return {
+6 -1
View File
@@ -170,7 +170,12 @@ def required_permission(path: str, method: str) -> str:
return "settings.manage"
if path.startswith(("/api/settings/connectors", "/api/connectors", "/api/setup/")):
return "connectors.manage"
if path.startswith(("/api/settings/", "/api/system/")) or path in {"/api/ai/rag/reindex", "/api/knowledge/reindex", "/api/enrollments/trust"}:
if path.startswith(("/api/settings/", "/api/system/")) or path in {
"/api/ai/rag/reindex",
"/api/knowledge/reindex",
"/api/enrollments/trust",
"/api/enrollments/trust/storage-root",
}:
return "settings.manage"
if path.startswith("/api/apps/") and path.endswith(("/metadata", "/container-mapping")):
return "inventory.manage"