from __future__ import annotations ACTIONS = [ 'container.status', 'container.logs', 'container.clear_logs', 'container.restart', 'container.stop', 'runner.fleet.upgrade_v2', 'runner.fleet.specialize', 'runner.statistics.configure', 'controller.artifact.promote', 'controller.managed-storage.initialize', 'hub.migration.prepare_permissions', 'gateway.clear_cache', 'stack.status', 'stack.redeploy', 'deployment.preflight', 'deployment.targets.sync', 'deployment.runnerless.provision', 'deployment.target.deploy_workmesh', 'deployment.target.promote_workmesh', 'deployment.target.register_portfolio', 'deployment.target.register_privatemuse', 'deployment.target.reconcile_media_codec_dashboard', 'deployment.webhook.repair_privatemuse', 'privatemuse.comfyui.install_artifact', 'deployment.apply', 'deployment.rollback', 'volume.delete', 'unraid.health_summary', 'unraid.vms', 'unraid.vm.start', 'unraid.vm.resume', 'unraid.vm.pause', 'unraid.vm.stop', 'unraid.vm.reboot', 'unraid.vm.force_stop', 'unraid.vm.reset', 'unraid.mover.start', 'setup.status', 'safety.summary', 'media_pipeline.check', 'media_pipeline.diagnose', 'notifications.summary', 'notifications.evaluate_safety', 'sonarr.search_series', 'sonarr.add_series', 'sonarr.health', 'sonarr.queue_status', 'sonarr.wanted_missing', 'sonarr.delete_series', 'sonarr.mass_edit', 'radarr.search_movie', 'radarr.add_movie', 'radarr.health', 'radarr.queue_status', 'radarr.wanted_missing', 'radarr.delete_movie', 'radarr.delete_files', 'radarr.mass_edit', 'jellyfin.scan_library', 'widefrog.health', 'widefrog.queue_status', 'widefrog.failed_jobs', 'widefrog.activity', 'widefrog.retry_failed', 'widefrog.scan_library', 'widefrog.download_status', 'prowlarr.health', 'prowlarr.indexer_status', 'prowlarr.application_status', 'prowlarr.indexer_health', 'prowlarr.search_test', 'prowlarr.sync_status', 'prowlarr.delete_indexer', 'prowlarr.delete_application', 'prowlarr.update_indexer', 'prowlarr.update_application', 'prowlarr.grab_release', 'prowlarr.push_release', 'prowlarr.mass_edit', 'prowlarr.test_all_indexers', 'deluge.health', 'deluge.session_status', 'deluge.torrent_status', 'deluge.active_downloads', 'deluge.stalled_downloads', 'deluge.paused_downloads', 'deluge.speed_status', 'deluge.pause_torrent', 'deluge.resume_torrent', 'deluge.pause_all', 'deluge.resume_all', 'deluge.remove_torrent', 'deluge.remove_torrent_data', 'deluge.delete_data', 'deluge.force_recheck', 'deluge.move_storage', 'deluge.add_torrent', 'deluge.add_magnet', 'deluge.mass_edit', 'deluge.set_options', 'deluge.unknown_mutation', 'plex.health', 'plex.server_status', 'plex.library_status', 'plex.active_sessions', 'plex.recent_media', 'plex.scan_library', 'plex.scan_all_libraries', 'plex.stream_summary', 'tautulli.health', 'tautulli.status', 'tautulli.activity', 'tautulli.history', 'tautulli.users', 'tautulli.libraries', 'tautulli.recently_added', 'tautulli.watch_stats', 'tautulli.stream_summary', 'tautulli.plex_info', 'npm.health', 'npm.proxy_hosts', 'npm.certificates', 'npm.expiring_certificates', 'npm.access_lists', 'npm.exposure_summary', 'npm.host_reachability', 'npm.security_warnings', 'npm.create_proxy_host', 'npm.update_proxy_host', 'npm.delete_proxy_host', 'npm.enable_proxy_host', 'npm.disable_proxy_host', 'npm.renew_certificate', 'npm.delete_certificate', 'npm.modify_access_list', 'npm.create_user', 'npm.update_user', 'npm.delete_user', 'npm.mass_edit', 'npm.unknown_mutation', 'authentik.health', 'authentik.system_status', 'authentik.applications', 'authentik.providers', 'authentik.proxy_providers', 'authentik.oauth2_providers', 'authentik.outposts', 'authentik.outpost_health', 'authentik.flows_summary', 'authentik.policies_summary', 'authentik.events', 'authentik.auth_coverage', 'authentik.security_warnings', 'authentik.create_application', 'authentik.update_application', 'authentik.delete_application', 'authentik.create_provider', 'authentik.update_provider', 'authentik.delete_provider', 'authentik.create_outpost', 'authentik.update_outpost', 'authentik.delete_outpost', 'authentik.create_policy', 'authentik.update_policy', 'authentik.delete_policy', 'authentik.create_user', 'authentik.update_user', 'authentik.delete_user', 'authentik.create_group', 'authentik.update_group', 'authentik.delete_group', 'authentik.rotate_token', 'authentik.force_sync', 'authentik.mass_edit', 'authentik.unknown_mutation', 'forge.oidc.provision', 'forge.secrets.prepare', 'forge.route.stage', 'tautulli.terminate_session', 'tautulli.delete_newsletter', 'tautulli.delete_notification_log', 'tautulli.delete_notifier', 'tautulli.delete_recently_added', 'tautulli.download_config', 'tautulli.download_database', 'tautulli.download_export', 'tautulli.download_log', 'tautulli.download_plex_log', 'tautulli.notify', 'tautulli.user_management', 'tautulli.mass_edit', 'tautulli.unknown_mutation', 'plex.delete_media', 'plex.delete_library', 'plex.empty_trash', 'plex.clean_bundles', 'plex.optimize_database', 'plex.kill_session', 'plex.user_management', 'plex.force_refresh_all_metadata', 'plex.mass_edit', 'plex.unknown_mutation', 'widefrog.delete_job', 'widefrog.delete_media', 'widefrog.clear_library', 'widefrog.clear_queue', 'widefrog.reset_database', 'widefrog.delete_downloads', 'widefrog.prune', 'playbook.run', ] ACTION_RISK = { 'container.status': 'low', 'container.logs': 'low', 'container.clear_logs': 'high', 'container.restart': 'medium', 'container.stop': 'high', 'runner.fleet.upgrade_v2': 'high', 'runner.fleet.specialize': 'high', 'runner.statistics.configure': 'high', 'controller.artifact.promote': 'high', 'controller.managed-storage.initialize': 'high', 'hub.migration.prepare_permissions': 'high', 'gateway.clear_cache': 'high', 'stack.status': 'low', 'stack.redeploy': 'high', 'deployment.preflight': 'low', 'deployment.targets.sync': 'high', 'deployment.runnerless.provision': 'high', 'deployment.target.deploy_workmesh': 'high', 'deployment.target.promote_workmesh': 'high', 'deployment.target.register_portfolio': 'high', 'deployment.target.register_privatemuse': 'high', 'deployment.target.reconcile_media_codec_dashboard': 'high', 'deployment.webhook.repair_privatemuse': 'high', 'privatemuse.comfyui.install_artifact': 'high', 'deployment.apply': 'high', 'deployment.rollback': 'high', 'volume.delete': 'critical', 'unraid.health_summary': 'low', 'unraid.vms': 'low', 'unraid.vm.start': 'medium', 'unraid.vm.resume': 'medium', 'unraid.vm.pause': 'high', 'unraid.vm.stop': 'critical', 'unraid.vm.reboot': 'critical', 'unraid.vm.force_stop': 'critical', 'unraid.vm.reset': 'critical', 'unraid.mover.start': 'high', 'setup.status': 'low', 'safety.summary': 'low', 'media_pipeline.check': 'low', 'media_pipeline.diagnose': 'low', 'notifications.summary': 'low', 'notifications.evaluate_safety': 'medium', 'sonarr.search_series': 'low', 'sonarr.add_series': 'low', 'sonarr.health': 'low', 'sonarr.queue_status': 'low', 'sonarr.wanted_missing': 'low', 'sonarr.delete_series': 'critical', 'sonarr.mass_edit': 'critical', 'radarr.search_movie': 'low', 'radarr.add_movie': 'low', 'radarr.health': 'low', 'radarr.queue_status': 'low', 'radarr.wanted_missing': 'low', 'radarr.delete_movie': 'critical', 'radarr.delete_files': 'critical', 'radarr.mass_edit': 'critical', 'jellyfin.scan_library': 'medium', 'widefrog.health': 'low', 'widefrog.queue_status': 'low', 'widefrog.failed_jobs': 'low', 'widefrog.activity': 'low', 'widefrog.retry_failed': 'medium', 'widefrog.scan_library': 'medium', 'widefrog.download_status': 'low', 'prowlarr.health': 'low', 'prowlarr.indexer_status': 'low', 'prowlarr.application_status': 'low', 'prowlarr.indexer_health': 'low', 'prowlarr.search_test': 'medium', 'prowlarr.sync_status': 'low', 'prowlarr.delete_indexer': 'critical', 'prowlarr.delete_application': 'critical', 'prowlarr.update_indexer': 'high', 'prowlarr.update_application': 'high', 'prowlarr.grab_release': 'critical', 'prowlarr.push_release': 'critical', 'prowlarr.mass_edit': 'critical', 'prowlarr.test_all_indexers': 'medium', 'deluge.health': 'low', 'deluge.session_status': 'low', 'deluge.torrent_status': 'low', 'deluge.active_downloads': 'low', 'deluge.stalled_downloads': 'low', 'deluge.paused_downloads': 'low', 'deluge.speed_status': 'low', 'deluge.pause_torrent': 'medium', 'deluge.resume_torrent': 'medium', 'deluge.pause_all': 'high', 'deluge.resume_all': 'high', 'deluge.remove_torrent': 'critical', 'deluge.remove_torrent_data': 'critical', 'deluge.delete_data': 'critical', 'deluge.force_recheck': 'high', 'deluge.move_storage': 'high', 'deluge.add_torrent': 'high', 'deluge.add_magnet': 'high', 'deluge.mass_edit': 'critical', 'deluge.set_options': 'critical', 'deluge.unknown_mutation': 'critical', 'plex.health': 'low', 'plex.server_status': 'low', 'plex.library_status': 'low', 'plex.active_sessions': 'low', 'plex.recent_media': 'low', 'plex.scan_library': 'medium', 'plex.scan_all_libraries': 'high', 'plex.stream_summary': 'low', 'tautulli.health': 'low', 'tautulli.status': 'low', 'tautulli.activity': 'low', 'tautulli.history': 'medium', 'tautulli.users': 'medium', 'tautulli.libraries': 'low', 'tautulli.recently_added': 'low', 'tautulli.watch_stats': 'medium', 'tautulli.stream_summary': 'low', 'tautulli.plex_info': 'low', 'npm.health': 'low', 'npm.proxy_hosts': 'medium', 'npm.certificates': 'low', 'npm.expiring_certificates': 'low', 'npm.access_lists': 'medium', 'npm.exposure_summary': 'medium', 'npm.host_reachability': 'low', 'npm.security_warnings': 'low', 'npm.create_proxy_host': 'critical', 'npm.update_proxy_host': 'critical', 'npm.delete_proxy_host': 'critical', 'npm.enable_proxy_host': 'high', 'npm.disable_proxy_host': 'high', 'npm.renew_certificate': 'high', 'npm.delete_certificate': 'critical', 'npm.modify_access_list': 'critical', 'npm.create_user': 'critical', 'npm.update_user': 'critical', 'npm.delete_user': 'critical', 'npm.mass_edit': 'critical', 'npm.unknown_mutation': 'critical', 'authentik.health': 'low', 'authentik.system_status': 'low', 'authentik.applications': 'medium', 'authentik.providers': 'medium', 'authentik.proxy_providers': 'medium', 'authentik.oauth2_providers': 'medium', 'authentik.outposts': 'medium', 'authentik.outpost_health': 'low', 'authentik.flows_summary': 'medium', 'authentik.policies_summary': 'medium', 'authentik.events': 'medium', 'authentik.auth_coverage': 'medium', 'authentik.security_warnings': 'low', 'authentik.create_application': 'critical', 'authentik.update_application': 'critical', 'authentik.delete_application': 'critical', 'authentik.create_provider': 'critical', 'authentik.update_provider': 'critical', 'authentik.delete_provider': 'critical', 'authentik.create_outpost': 'critical', 'authentik.update_outpost': 'critical', 'authentik.delete_outpost': 'critical', 'authentik.create_policy': 'critical', 'authentik.update_policy': 'critical', 'authentik.delete_policy': 'critical', 'authentik.create_user': 'critical', 'authentik.update_user': 'critical', 'authentik.delete_user': 'critical', 'authentik.create_group': 'critical', 'authentik.update_group': 'critical', 'authentik.delete_group': 'critical', 'authentik.rotate_token': 'critical', 'authentik.force_sync': 'high', 'authentik.mass_edit': 'critical', 'authentik.unknown_mutation': 'critical', 'forge.oidc.provision': 'critical', 'forge.secrets.prepare': 'critical', 'forge.route.stage': 'critical', 'tautulli.terminate_session': 'high', 'tautulli.delete_newsletter': 'critical', 'tautulli.delete_notification_log': 'critical', 'tautulli.delete_notifier': 'critical', 'tautulli.delete_recently_added': 'critical', 'tautulli.download_config': 'critical', 'tautulli.download_database': 'critical', 'tautulli.download_export': 'critical', 'tautulli.download_log': 'critical', 'tautulli.download_plex_log': 'critical', 'tautulli.notify': 'high', 'tautulli.user_management': 'critical', 'tautulli.mass_edit': 'critical', 'tautulli.unknown_mutation': 'critical', 'plex.delete_media': 'critical', 'plex.delete_library': 'critical', 'plex.empty_trash': 'critical', 'plex.clean_bundles': 'critical', 'plex.optimize_database': 'critical', 'plex.kill_session': 'high', 'plex.user_management': 'critical', 'plex.force_refresh_all_metadata': 'critical', 'plex.mass_edit': 'critical', 'plex.unknown_mutation': 'critical', 'widefrog.delete_job': 'critical', 'widefrog.delete_media': 'critical', 'widefrog.clear_library': 'critical', 'widefrog.clear_queue': 'critical', 'widefrog.reset_database': 'critical', 'widefrog.delete_downloads': 'critical', 'widefrog.prune': 'critical', 'playbook.run': 'medium', } READ_ONLY_VERBS = { 'activity', 'active_downloads', 'active_sessions', 'application_status', 'applications', 'auth_coverage', 'certificates', 'check', 'download_status', 'diagnose', 'events', 'expiring_certificates', 'exposure_summary', 'failed_jobs', 'flows_summary', 'health', 'health_summary', 'history', 'host_reachability', 'indexer_health', 'indexer_status', 'libraries', 'library_status', 'logs', 'oauth2_providers', 'outpost_health', 'outposts', 'paused_downloads', 'plex_info', 'preflight', 'policies_summary', 'providers', 'proxy_hosts', 'proxy_providers', 'queue_status', 'recent_media', 'recently_added', 'search_movie', 'search_series', 'security_warnings', 'server_status', 'session_status', 'speed_status', 'stalled_downloads', 'status', 'stream_summary', 'summary', 'sync_status', 'system_status', 'torrent_status', 'users', 'wanted_missing', 'watch_stats', 'vms', } DESTRUCTIVE_VERBS = { 'clear_cache', 'clear_library', 'clear_logs', 'clear_queue', 'clean_bundles', 'delete_application', 'delete_certificate', 'delete_data', 'delete_downloads', 'delete_files', 'delete_group', 'delete_indexer', 'delete_job', 'delete_library', 'delete_media', 'delete_movie', 'delete_newsletter', 'delete_notification_log', 'delete_notifier', 'delete_outpost', 'delete_policy', 'delete_provider', 'delete_proxy_host', 'delete_recently_added', 'delete_series', 'delete_user', 'download_config', 'download_database', 'download_export', 'download_log', 'download_plex_log', 'empty_trash', 'force_refresh_all_metadata', 'grab_release', 'mass_edit', 'modify_access_list', 'prune', 'push_release', 'remove_torrent', 'remove_torrent_data', 'reset_database', 'rotate_token', 'set_options', 'stop', 'unknown_mutation', } def action_category(action: str) -> str: return action.split('.', 1)[0] if '.' in action else 'general' def action_default_target(action: str) -> str: if action == 'forge.oidc.provision': return 'itworx-forge' if action == 'forge.secrets.prepare': return 'itworx-forge' if action == 'forge.route.stage': return 'itworx-forge' if action == 'controller.artifact.promote': return 'tower/deployment-controller' if action == 'controller.managed-storage.initialize': return 'tower/deployment-controller' if action == 'hub.migration.prepare_permissions': return 'tower/itworx-mcp-hub-permissions' if action == 'runner.fleet.upgrade_v2': return 'tower/runner-fleet-v2' if action == 'runner.fleet.specialize': return 'tower/runner-fleet-specialized' if action == 'runner.statistics.configure': return 'tower/projectbrain-runner-statistics' if action == 'deployment.targets.sync': return 'jens/*' if action == 'deployment.target.deploy_workmesh': return 'jens/workmesh' if action == 'deployment.target.promote_workmesh': return 'jens/workmesh' if action == 'deployment.target.register_portfolio': return 'jens/portfolio' if action in { 'deployment.target.register_privatemuse', 'deployment.webhook.repair_privatemuse', }: return 'jens/privatemuse' if action == 'deployment.target.reconcile_media_codec_dashboard': return 'jens/media-codec-dashboard' category = action_category(action) if category == 'container': return 'container' if category == 'volume': return 'volume' if category == 'media_pipeline': return 'media-pipeline' return category def action_label(action: str) -> str: return action.replace('_', ' ').replace('.', ' / ').title() def action_capability(action: str) -> str: verb = action.split('.', 1)[1] if '.' in action else action if action in {'unraid.vm.stop', 'unraid.vm.reboot', 'unraid.vm.force_stop', 'unraid.vm.reset'}: return 'destructive' if verb in DESTRUCTIVE_VERBS or ACTION_RISK.get(action) == 'critical': return 'destructive' if verb in READ_ONLY_VERBS: return 'read_only' return 'operator' def action_blast_radius(action: str, capability: str) -> list[str]: category = action_category(action) if capability == 'read_only': return [f'Read-only visibility for {category}; no state changes are performed.'] if category == 'volume': return ['Storage volume data and dependent apps could be affected if execution were ever enabled.'] if category == 'container': return ['Target container availability and dependent routes may be affected.'] if category == 'stack': return ['Multiple containers in the stack may restart or change state.'] if category == 'gateway': return ['AppOps runtime visibility, cached connector state or operator context may be affected if execution were ever enabled.'] if category in {'authentik', 'npm'}: return ['Authentication, exposure, routing or access-control behavior may be affected.'] if category in {'sonarr', 'radarr', 'prowlarr', 'deluge', 'plex', 'tautulli', 'widefrog'}: return ['Media pipeline state, queues or user-facing media availability may be affected.'] if category == 'unraid': return ['Unraid host or array operations may be affected.'] return [f'{category} operational state may be affected.'] def action_safeguards(action: str, decision: str, capability: str) -> list[str]: safeguards = ['Registry detail is read-only and cannot execute this action.'] if capability == 'destructive': safeguards.append('Destructive or critical actions stay blocked unless an explicit future policy path is designed.') if decision == 'blocked': safeguards.append('AppOps Gateway V1 exposes this only as a blocked boundary; no executor is registered.') safeguards.append('Current policy decision is blocked.') elif decision == 'approval_required': safeguards.append('Current policy decision requires operator approval before execution.') else: safeguards.append('Allowed policy preview does not bypass preflight, audit or runtime checks.') if 'token' in action or 'secret' in action: safeguards.append('Secret-like values are never returned by the registry.') return safeguards def action_operator_guidance(action: str, decision: str, capability: str) -> str: if decision == 'blocked': return 'AppOps Gateway V1 blocks this action; treat it as governance visibility only and use external/manual tooling until a separate safe execution design exists.' if decision == 'approval_required': return 'Use approved operational flows only after reviewing impact and maintenance context.' if capability == 'read_only': return 'Safe to inspect for operational awareness.' return 'Use the existing preflight and audited action flow before any live operation.'