agentic-ops 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentic_ops-0.1.0/.env.example +134 -0
- agentic_ops-0.1.0/.github/workflows/ci.yml +22 -0
- agentic_ops-0.1.0/.github/workflows/publish.yml +35 -0
- agentic_ops-0.1.0/.gitignore +47 -0
- agentic_ops-0.1.0/CAC_TAILSCALE_PULL_CONNECTOR.md +348 -0
- agentic_ops-0.1.0/DOCKER_SANDBOX_VALIDATION.md +258 -0
- agentic_ops-0.1.0/Dockerfile +52 -0
- agentic_ops-0.1.0/Dockerfile.hf +56 -0
- agentic_ops-0.1.0/GENESIS.md +69 -0
- agentic_ops-0.1.0/INTEGRATION_GUIDE.md +130 -0
- agentic_ops-0.1.0/LICENSE +21 -0
- agentic_ops-0.1.0/PKG-INFO +1489 -0
- agentic_ops-0.1.0/README.md +1460 -0
- agentic_ops-0.1.0/README_FR.md +1461 -0
- agentic_ops-0.1.0/agentic_ops/adapters/cac_pull_collector.py +413 -0
- agentic_ops-0.1.0/agentic_ops/adapters/datadog_adapter.py +33 -0
- agentic_ops-0.1.0/agentic_ops/adapters/kubernetes_adapter.py +73 -0
- agentic_ops-0.1.0/agentic_ops/adapters/llm_protocol.py +366 -0
- agentic_ops-0.1.0/agentic_ops/adapters/llm_provider_registry.py +510 -0
- agentic_ops-0.1.0/agentic_ops/adapters/protocol.py +19 -0
- agentic_ops-0.1.0/agentic_ops/adapters/registry_adapter.py +70 -0
- agentic_ops-0.1.0/agentic_ops/adapters/runbook_rag.py +21 -0
- agentic_ops-0.1.0/agentic_ops/adapters/slack_adapter.py +78 -0
- agentic_ops-0.1.0/agentic_ops/cli/compile_taxonomy.py +66 -0
- agentic_ops-0.1.0/agentic_ops/cli/main.py +3192 -0
- agentic_ops-0.1.0/agentic_ops/cli/trust_ledger.py +150 -0
- agentic_ops-0.1.0/agentic_ops/core/__init__.py +1 -0
- agentic_ops-0.1.0/agentic_ops/core/approvals.py +137 -0
- agentic_ops-0.1.0/agentic_ops/core/approvers.py +32 -0
- agentic_ops-0.1.0/agentic_ops/core/circuit_breaker.py +39 -0
- agentic_ops-0.1.0/agentic_ops/core/contracts.py +103 -0
- agentic_ops-0.1.0/agentic_ops/core/crypto.py +37 -0
- agentic_ops-0.1.0/agentic_ops/core/distributed.py +29 -0
- agentic_ops-0.1.0/agentic_ops/core/evidence.py +143 -0
- agentic_ops-0.1.0/agentic_ops/core/executor.py +181 -0
- agentic_ops-0.1.0/agentic_ops/core/facts.py +29 -0
- agentic_ops-0.1.0/agentic_ops/core/governance_schemas.py +25 -0
- agentic_ops-0.1.0/agentic_ops/core/idempotency.py +52 -0
- agentic_ops-0.1.0/agentic_ops/core/infra_specialist.py +52 -0
- agentic_ops-0.1.0/agentic_ops/core/model_specialist.py +51 -0
- agentic_ops-0.1.0/agentic_ops/core/orchestrator.py +281 -0
- agentic_ops-0.1.0/agentic_ops/core/policy.py +68 -0
- agentic_ops-0.1.0/agentic_ops/core/policy_engine.py +166 -0
- agentic_ops-0.1.0/agentic_ops/core/prompt_guard.py +16 -0
- agentic_ops-0.1.0/agentic_ops/core/remediation_agent.py +49 -0
- agentic_ops-0.1.0/agentic_ops/core/specialist.py +52 -0
- agentic_ops-0.1.0/agentic_ops/core/state.py +20 -0
- agentic_ops-0.1.0/agentic_ops/core/telemetry.py +47 -0
- agentic_ops-0.1.0/agentic_ops/entrypoints/approval_sweeper.py +6 -0
- agentic_ops-0.1.0/agentic_ops/entrypoints/grafana_webhook.py +61 -0
- agentic_ops-0.1.0/agentic_ops/entrypoints/mcp_server.py +1149 -0
- agentic_ops-0.1.0/agentic_ops/entrypoints/slack_verify.py +24 -0
- agentic_ops-0.1.0/agentic_ops/entrypoints/slack_webhook.py +59 -0
- agentic_ops-0.1.0/agentic_ops/evidence_store/EP-0001.json +64 -0
- agentic_ops-0.1.0/agentic_ops/evidence_store/EP-0002.json +58 -0
- agentic_ops-0.1.0/agentic_ops/evidence_store/EP-0003.json +58 -0
- agentic_ops-0.1.0/agentic_ops/evidence_store/EP-0004.json +64 -0
- agentic_ops-0.1.0/agentic_ops/evidence_store/EP-0005.json +65 -0
- agentic_ops-0.1.0/agentic_ops/evidence_store/EP-CHAOS-31c6e006.json +27 -0
- agentic_ops-0.1.0/agentic_ops/evidence_store/EP-CHAOS-5950095d.json +28 -0
- agentic_ops-0.1.0/agentic_ops/evidence_store/EP-CHAOS-d6c18601.json +28 -0
- agentic_ops-0.1.0/agentic_ops/evidence_store/_index.json +4 -0
- agentic_ops-0.1.0/agentic_ops/governance/action_catalog.yaml +26 -0
- agentic_ops-0.1.0/agentic_ops/governance/approvers.yaml +7 -0
- agentic_ops-0.1.0/agentic_ops/governance/autonomy_policy.yaml +23 -0
- agentic_ops-0.1.0/agentic_ops/governance/risk_register.yaml +6 -0
- agentic_ops-0.1.0/agentic_ops/governance/shadow_policy.yaml +23 -0
- agentic_ops-0.1.0/agentic_ops/knowledge_base/runbooks/DATA-042.md +8 -0
- agentic_ops-0.1.0/agentic_ops/knowledge_base/runbooks/INFRA-001.md +8 -0
- agentic_ops-0.1.0/agentic_ops/schemas/action.schema.json +13 -0
- agentic_ops-0.1.0/agentic_ops/schemas/diagnosis.schema.json +11 -0
- agentic_ops-0.1.0/agentic_ops/schemas/evidence.schema.json +12 -0
- agentic_ops-0.1.0/agentic_ops/schemas/risk.schema.json +14 -0
- agentic_ops-0.1.0/agentic_ops/tools/__init__.py +0 -0
- agentic_ops-0.1.0/agentic_ops/tools/base.py +80 -0
- agentic_ops-0.1.0/agentic_ops/tools/data.py +77 -0
- agentic_ops-0.1.0/agentic_ops/tools/github.py +30 -0
- agentic_ops-0.1.0/agentic_ops/tools/infra.py +51 -0
- agentic_ops-0.1.0/agentic_ops/tools/model.py +29 -0
- agentic_ops-0.1.0/agentic_ops/tools/observability.py +56 -0
- agentic_ops-0.1.0/docker-compose.yml +26 -0
- agentic_ops-0.1.0/docs/HISTORICAL_AUDIT_AND_REMEDIATION.md +121 -0
- agentic_ops-0.1.0/docs/MAS_AGENT_ROUTING_AND_DEFAULTS_GUIDE.md +543 -0
- agentic_ops-0.1.0/docs/MAS_FALLBACK_ARCHITECTURE_GUIDE.md +524 -0
- agentic_ops-0.1.0/docs/MCP_SERVER_GUIDE.md +399 -0
- agentic_ops-0.1.0/docs/MONOREPO_DECOUPLING_AND_REGRESSION_AUDIT.md +172 -0
- agentic_ops-0.1.0/docs/MULTI_LLM_ROLE_SPECIALIZATION_GUIDE.md +218 -0
- agentic_ops-0.1.0/docs/OPENROUTER_FREE_MODELS_GUIDE.md +303 -0
- agentic_ops-0.1.0/docs/PRESENTATION_SLIDES.md +51 -0
- agentic_ops-0.1.0/docs/SLACK_HITL_INTEGRATION_GUIDE.md +163 -0
- agentic_ops-0.1.0/docs/TARGET_OUTAGE_AND_RESILIENCE_GUIDE.md +140 -0
- agentic_ops-0.1.0/docs/WORM_TRUST_LEDGER_GUIDE.md +190 -0
- agentic_ops-0.1.0/docs/assets/liora_datascientest_logo.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/01_docker_agentic_platform_welcome.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/02_docker_kits_catalog.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/03_docker_sandbox_running.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/04_demo_agentic_ops_execution.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/05_hardware_telemetry_amd_epyc.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/06_slack_security_rejection_401.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/07_adversarial_security_tests_passed.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/08_mlops_scenarios_zero_forbidden.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/09_governance_fsm_grand_chelem.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/11_tailscale_up_daemon_success.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/12_tailscale_status_mesh_nodes.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/13_tailscale_netcheck_derp_seattle.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/14_tailscaled_proxy_loop_diagnostic.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/15_tailscaled_derp10_clean_connected.png +0 -0
- agentic_ops-0.1.0/docs/assets/screenshots/cognitive_web_console_live.png +0 -0
- agentic_ops-0.1.0/docs/assets/slides/slide_01_flux_agentic_ops.png +0 -0
- agentic_ops-0.1.0/docs/assets/slides/slide_02_signal_brut_diagnostic.png +0 -0
- agentic_ops-0.1.0/docs/assets/slides/slide_03_ia_propose_code_autorise.png +0 -0
- agentic_ops-0.1.0/docs/assets/slides/slide_04_humain_decide_registre_prouve.png +0 -0
- agentic_ops-0.1.0/docs/assets/slides/slide_05_production_live_mode_degrade.png +0 -0
- agentic_ops-0.1.0/docs/assets/slides/slide_06_demonstration_live_console_glass_box.png +0 -0
- agentic_ops-0.1.0/docs/fiche_soutenance_jury_sre.md +136 -0
- agentic_ops-0.1.0/docs/mlops_failure_modes_taxonomy.md +637 -0
- agentic_ops-0.1.0/docs/mlops_simulation_scenarios_registry.md +9604 -0
- agentic_ops-0.1.0/helm/agentic-ops/Chart.yaml +6 -0
- agentic_ops-0.1.0/helm/agentic-ops/templates/deployment.yaml +40 -0
- agentic_ops-0.1.0/helm/agentic-ops/templates/rbac.yaml +23 -0
- agentic_ops-0.1.0/helm/agentic-ops/templates/serviceaccount.yaml +10 -0
- agentic_ops-0.1.0/helm/agentic-ops/values.yaml +50 -0
- agentic_ops-0.1.0/librechat.yaml +54 -0
- agentic_ops-0.1.0/pyproject.toml +50 -0
- agentic_ops-0.1.0/reports/scenarios.json +168 -0
- agentic_ops-0.1.0/scripts/demo_agentic_ops.sh +74 -0
- agentic_ops-0.1.0/scripts/entrypoint.sh +72 -0
- agentic_ops-0.1.0/start_sandbox.sh +5 -0
- agentic_ops-0.1.0/tests/adapters/__init__.py +0 -0
- agentic_ops-0.1.0/tests/adapters/test_cac_pull_collector.py +374 -0
- agentic_ops-0.1.0/tests/adapters/test_llm_provider_registry.py +320 -0
- agentic_ops-0.1.0/tests/adversarial/test_evidence_integrity.py +49 -0
- agentic_ops-0.1.0/tests/adversarial/test_idempotency_and_rate.py +26 -0
- agentic_ops-0.1.0/tests/adversarial/test_prompt_hygiene.py +36 -0
- agentic_ops-0.1.0/tests/conftest.py +31 -0
- agentic_ops-0.1.0/tests/entrypoints/test_chat_endpoint.py +187 -0
- agentic_ops-0.1.0/tests/entrypoints/test_mcp_server.py +467 -0
- agentic_ops-0.1.0/tests/integration/test_model_registry.py +64 -0
- agentic_ops-0.1.0/tests/integration/test_registry_e2e.py +121 -0
- agentic_ops-0.1.0/tests/known_gaps.yaml +4 -0
- agentic_ops-0.1.0/tests/policy/__init__.py +0 -0
- agentic_ops-0.1.0/tests/policy/conftest.py +26 -0
- agentic_ops-0.1.0/tests/policy/test_known_gaps.py +18 -0
- agentic_ops-0.1.0/tests/policy/test_loading.py +40 -0
- agentic_ops-0.1.0/tests/policy/test_policy_engine.py +53 -0
- agentic_ops-0.1.0/tests/scenarios/__init__.py +0 -0
- agentic_ops-0.1.0/tests/scenarios/adapter.py +69 -0
- agentic_ops-0.1.0/tests/scenarios/conftest.py +49 -0
- agentic_ops-0.1.0/tests/scenarios/data/adversarial/sc_101_log_injection.yaml +16 -0
- agentic_ops-0.1.0/tests/scenarios/data/adversarial/sc_102_param_injection.yaml +15 -0
- agentic_ops-0.1.0/tests/scenarios/data/adversarial/sc_103_poisoned_runbook.yaml +17 -0
- agentic_ops-0.1.0/tests/scenarios/data/sc_001_data_ingestion_drop.yaml +16 -0
- agentic_ops-0.1.0/tests/scenarios/data/sc_003_rollback_trap.yaml +18 -0
- agentic_ops-0.1.0/tests/scenarios/data/sc_004_infra_red_herring.yaml +17 -0
- agentic_ops-0.1.0/tests/scenarios/data/sc_005_real_infra_failure.yaml +17 -0
- agentic_ops-0.1.0/tests/scenarios/loader.py +14 -0
- agentic_ops-0.1.0/tests/scenarios/runner.py +72 -0
- agentic_ops-0.1.0/tests/scenarios/schema.py +59 -0
- agentic_ops-0.1.0/tests/scenarios/stubs.py +88 -0
- agentic_ops-0.1.0/tests/scenarios/test_scenarios.py +26 -0
- agentic_ops-0.1.0/tests/security/test_executor.py +30 -0
- agentic_ops-0.1.0/tests/test_fsm_orchestrator.py +22 -0
- agentic_ops-0.1.0/tests/test_grafana_webhook.py +25 -0
- agentic_ops-0.1.0/tests/test_tools_security.py +23 -0
- agentic_ops-0.1.0/uv.lock +4925 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# Agentic-Ops Control Plane — Universal Environment Configuration Template
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
# This template documents all configurable parameters across the Control Plane:
|
|
5
|
+
# 1. Universal LLM Provider Hub (OpenRouter, Gemini, OpenAI, Anthropic, Azure, Mistral, HuggingFace, Ollama)
|
|
6
|
+
# 2. Tailscale Mesh Network & Persistence (TS_STATE_B64, TS_AUTHKEY)
|
|
7
|
+
# 3. Security, Slack HITL Cryptography & Approvals Storage
|
|
8
|
+
# 4. Redis In-Memory Isolation & Rate-Limiting
|
|
9
|
+
# ==============================================================================
|
|
10
|
+
|
|
11
|
+
# ------------------------------------------------------------------------------
|
|
12
|
+
# 1. UNIVERSAL LLM PROVIDER HUB CONFIGURATION
|
|
13
|
+
# ------------------------------------------------------------------------------
|
|
14
|
+
# Active provider selector:
|
|
15
|
+
# Supported options: openrouter | gemini | openai | anthropic | azure | mistral | huggingface | ollama
|
|
16
|
+
LLM_PROVIDER=openrouter
|
|
17
|
+
|
|
18
|
+
# Target model identifier (LiteLLM format):
|
|
19
|
+
# Examples:
|
|
20
|
+
# OpenRouter (0 EUR) : openrouter/deepseek/deepseek-r1:free
|
|
21
|
+
# OpenRouter (0 EUR) : openrouter/deepseek/deepseek-chat:free
|
|
22
|
+
# OpenRouter (0 EUR) : openrouter/meta-llama/llama-3.3-70b-instruct:free
|
|
23
|
+
# OpenRouter (0 EUR) : openrouter/google/gemini-2.0-flash-exp:free
|
|
24
|
+
# Google Gemini : gemini/gemini-1.5-pro-latest
|
|
25
|
+
# OpenAI : gpt-4o-mini
|
|
26
|
+
# Anthropic : claude-3-5-haiku-20241022
|
|
27
|
+
# Azure OpenAI : azure/gpt-4o
|
|
28
|
+
# Mistral AI : mistral/mistral-large-2411
|
|
29
|
+
# HuggingFace : huggingface/mistralai/Mistral-7B-Instruct-v0.2
|
|
30
|
+
# Ollama (Local/VPN) : ollama/llama3.2:3b
|
|
31
|
+
LLM_MODEL=openrouter/deepseek/deepseek-r1:free
|
|
32
|
+
|
|
33
|
+
# Comma-separated fallback models chain (tried sequentially if primary fails or rate-limits):
|
|
34
|
+
LLM_FALLBACK_MODELS=openrouter/deepseek/deepseek-chat:free,openrouter/meta-llama/llama-3.3-70b-instruct:free
|
|
35
|
+
|
|
36
|
+
# Execution hyperparameters
|
|
37
|
+
LLM_TEMPERATURE=0.0
|
|
38
|
+
LLM_MAX_TOKENS=1024
|
|
39
|
+
LLM_TIMEOUT_SECONDS=30
|
|
40
|
+
LLM_MAX_RETRIES=3
|
|
41
|
+
LLM_STRUCTURED_MODE=instructor
|
|
42
|
+
# LLM_SEED=42
|
|
43
|
+
|
|
44
|
+
# --- Cognitive Role Specialization (Optional — defaults to LLM_MODEL & LLM_FALLBACK_MODELS) ---
|
|
45
|
+
# If omitted, every agent role automatically inherits the global LLM_MODEL and LLM_FALLBACK_MODELS.
|
|
46
|
+
# Override per-role to match specific cognitive requirements:
|
|
47
|
+
# - Remediation Coordinator : Deliberate CoT reasoning & blast radius calculation
|
|
48
|
+
# LLM_MODEL_REMEDIATION=openrouter/deepseek/deepseek-r1:free
|
|
49
|
+
# LLM_FALLBACK_MODELS_REMEDIATION=openrouter/meta-llama/llama-3.3-70b-instruct:free
|
|
50
|
+
#
|
|
51
|
+
# - Infrastructure Specialist: 256k context window for large Loki/system log dumps
|
|
52
|
+
# LLM_MODEL_INFRA=openrouter/google/gemma-4-31b-it:free
|
|
53
|
+
# LLM_FALLBACK_MODELS_INFRA=openrouter/google/gemma-4-26b-a4b-it:free
|
|
54
|
+
#
|
|
55
|
+
# - Data Specialist : Low-latency covariance, drift matrices & JSON parsing
|
|
56
|
+
# LLM_MODEL_DATA=openrouter/google/gemma-4-26b-a4b-it:free
|
|
57
|
+
#
|
|
58
|
+
# - Model Specialist : 70B parameter rigor for MLflow F1-score & metric drift
|
|
59
|
+
# LLM_MODEL_MODEL=openrouter/meta-llama/llama-3.3-70b-instruct:free
|
|
60
|
+
#
|
|
61
|
+
# - Standalone Web Chat SRE : Responsive conversational model without <think> tags
|
|
62
|
+
# LLM_MODEL_CHAT=openrouter/deepseek/deepseek-chat:free
|
|
63
|
+
#
|
|
64
|
+
# Per-role providers can also be isolated if needed:
|
|
65
|
+
# LLM_PROVIDER_INFRA=openrouter
|
|
66
|
+
# LLM_PROVIDER_MODEL=ollama
|
|
67
|
+
|
|
68
|
+
# --- Provider Credentials & Connection Endpoints ---
|
|
69
|
+
# OpenRouter (Get free key at https://openrouter.ai):
|
|
70
|
+
OPENROUTER_API_KEY=sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
71
|
+
|
|
72
|
+
# Google Gemini:
|
|
73
|
+
GOOGLE_API_KEY=AIzaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
74
|
+
|
|
75
|
+
# OpenAI:
|
|
76
|
+
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
77
|
+
|
|
78
|
+
# Anthropic:
|
|
79
|
+
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
80
|
+
|
|
81
|
+
# Azure OpenAI:
|
|
82
|
+
AZURE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
83
|
+
AZURE_API_BASE=https://your-resource-name.openai.azure.com/
|
|
84
|
+
AZURE_API_VERSION=2024-02-01
|
|
85
|
+
|
|
86
|
+
# Mistral AI:
|
|
87
|
+
MISTRAL_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
88
|
+
|
|
89
|
+
# HuggingFace Inference API:
|
|
90
|
+
HUGGINGFACE_API_KEY=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
91
|
+
|
|
92
|
+
# Ollama Endpoint Configuration:
|
|
93
|
+
# - Local Workstation : http://localhost:11434
|
|
94
|
+
# - Tailscale Mesh (Your Mac/PC): http://100.x.y.z:11434
|
|
95
|
+
# - Cloudflare Named Tunnel : https://ollama.yourdomain.com
|
|
96
|
+
# - Vast.ai / External Cloud VPS: http://public-ip:11434
|
|
97
|
+
OLLAMA_BASE_URL=http://localhost:11434
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ------------------------------------------------------------------------------
|
|
101
|
+
# 2. TAILSCALE USERSERVICE MESH & PERSISTENCE
|
|
102
|
+
# ------------------------------------------------------------------------------
|
|
103
|
+
# Ephemeral Node State Persistence (Base64 encoded /tmp/tailscaled.state):
|
|
104
|
+
# Generated via GET /api/tailscale/status on first login
|
|
105
|
+
TS_STATE_B64=
|
|
106
|
+
|
|
107
|
+
# Headless Auth Key (Only if you are admin of the Tailnet):
|
|
108
|
+
# TS_AUTHKEY=tskey-auth-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
109
|
+
|
|
110
|
+
# SOCKS5 Proxy Host and Port (Default: 127.0.0.1:1055):
|
|
111
|
+
TS_SOCKS5_PROXY=127.0.0.1:1055
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ------------------------------------------------------------------------------
|
|
115
|
+
# 3. CONTROL PLANE SECURITY & HITL CRYPTOGRAPHY
|
|
116
|
+
# ------------------------------------------------------------------------------
|
|
117
|
+
# Environment mode (production enforces strict fail-closed HMAC and WORM policies)
|
|
118
|
+
AGENTIC_OPS_ENV=production
|
|
119
|
+
|
|
120
|
+
# Slack HITL Signing Secret (Used to verify X-Slack-Signature HMAC-SHA256):
|
|
121
|
+
SLACK_SIGNING_SECRET=your_slack_signing_secret_here
|
|
122
|
+
|
|
123
|
+
# Approval Database Storage (SQLite WAL path):
|
|
124
|
+
APPROVAL_DB_PATH=state/approvals.db
|
|
125
|
+
|
|
126
|
+
# Evidence WORM Cryptographic KMS Secret (Used for HMAC-SHA512 tamper-proof signing):
|
|
127
|
+
EVIDENCE_STORE_HMAC_KEY=super_secret_kms_tamper_proof_signing_key_512
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ------------------------------------------------------------------------------
|
|
131
|
+
# 4. REDIS IN-MEMORY ISOLATION & RATE-LIMITING
|
|
132
|
+
# ------------------------------------------------------------------------------
|
|
133
|
+
# Local in-container daemon or managed Redis endpoint:
|
|
134
|
+
REDIS_URL=redis://127.0.0.1:6379/0
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
|
|
4
|
+
jobs:
|
|
5
|
+
adversarial:
|
|
6
|
+
runs-on: ubuntu-latest
|
|
7
|
+
timeout-minutes: 15
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: ${{ matrix.python-version }}
|
|
16
|
+
- name: Install dependencies
|
|
17
|
+
run: pip install -e ".[dev]"
|
|
18
|
+
- name: Run tests
|
|
19
|
+
run: pytest tests/ -q -p no:randomly
|
|
20
|
+
- name: Dettes ouvertes
|
|
21
|
+
if: always()
|
|
22
|
+
run: python -c "import yaml;g=yaml.safe_load(open('tests/known_gaps.yaml'));print(f'{len(g)} dettes ouvertes:',sorted({v['p0'] for v in g.values()}))"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pypi-publish:
|
|
10
|
+
name: Upload release to PyPI
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment:
|
|
13
|
+
name: pypi
|
|
14
|
+
url: https://pypi.org/p/agentic-ops
|
|
15
|
+
permissions:
|
|
16
|
+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Install uv
|
|
23
|
+
uses: astral-sh/setup-uv@v5
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.11"
|
|
29
|
+
|
|
30
|
+
- name: Build package
|
|
31
|
+
run: |
|
|
32
|
+
uv build
|
|
33
|
+
|
|
34
|
+
- name: Publish package distributions to PyPI
|
|
35
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
share/python-wheels/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
|
|
24
|
+
# Virtual environments
|
|
25
|
+
.venv/
|
|
26
|
+
env/
|
|
27
|
+
venv/
|
|
28
|
+
ENV/
|
|
29
|
+
|
|
30
|
+
# Testing & Cache
|
|
31
|
+
.pytest_cache/
|
|
32
|
+
.coverage
|
|
33
|
+
htmlcov/
|
|
34
|
+
.hypothesis/
|
|
35
|
+
|
|
36
|
+
# Runtime state & local databases
|
|
37
|
+
state/
|
|
38
|
+
*.db
|
|
39
|
+
*.sqlite
|
|
40
|
+
*.sqlite3
|
|
41
|
+
*.log
|
|
42
|
+
|
|
43
|
+
# OS & IDE
|
|
44
|
+
.DS_Store
|
|
45
|
+
.idea/
|
|
46
|
+
.vscode/
|
|
47
|
+
*.swp
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Architecture et Validation du Connecteur Tailscale Pull (`CacPullCollector`)
|
|
2
|
+
|
|
3
|
+
> **Document de Reference d'Interconnexion MLOps et SecOps**
|
|
4
|
+
> Ce document detaille l'architecture reseau, le protocole de tunnelisation `Tailscale` en espace utilisateur (`userspace-networking`), l'analyse de diagnostic bas niveau et l'implementation du connecteur d'ingestion en lecture seule `CacPullCollector` reliant le Control Plane `Agentic-Ops` a la plateforme distante `CAC MLOps` ([`https://github.com/jakatt/cac_mlops`](https://github.com/jakatt/cac_mlops) — VPS Scaleway `scw-jovial-dubinsky`, IP Tailscale `100.117.99.62`).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Philosophie d'Interconnexion et Regle d'Or (Read-Only Pull Mode)
|
|
9
|
+
|
|
10
|
+
Dans une architecture MLOps distribuee multi-equipes, le branchement d'un plan de controle autonome (`Agentic-Ops`) sur une infrastructure de production tierce ([`jakatt/cac_mlops`](https://github.com/jakatt/cac_mlops) hebergee sur `Scaleway`) exige une frontiere de securite physique et logique infranchissable.
|
|
11
|
+
|
|
12
|
+
### A. Pourquoi le mode "Pull Lecture Seule" est obligatoire
|
|
13
|
+
|
|
14
|
+
Les services exposes à l'interieur du reseau prive `Tailscale` (`Loki`, `Prometheus`, `MLflow`, `Prefect`) fonctionnent sans authentification applicative additionnelle car la confiance est deleguee a la couche cryptographique `WireGuard` du maillage. Par consequent, toute machine membre du `Tailnet` pourrait theoriquement emettre des requetes mutantes destructrices :
|
|
15
|
+
* Declencher un flow `Prefect` de reinitialisation (`reset`) effacant les tables de donnees.
|
|
16
|
+
* Modifier ou supprimer l'alias `Production` dans le registre `MLflow` (`POST / DELETE registered-models/alias`).
|
|
17
|
+
* Injecter de faux evenements d'alerte dans `Loki` (`POST /loki/api/v1/push`).
|
|
18
|
+
* Appeler la route `/predict` de l'API d'inference, ce qui polluerait la base de donnees de production avec des predictions synthetiques et fausserait mathematiquement le calcul de derive (`Data Drift`) du trafic reel.
|
|
19
|
+
|
|
20
|
+
Pour eliminer tout risque d'effet de bord sur la production distante, `Agentic-Ops` applique le **Principe d'Isolation Cognitive et d'Ingestion Passive** :
|
|
21
|
+
1. Le collecteur `CacPullCollector` n'emet **exclusivement que des requetes HTTP `GET`**.
|
|
22
|
+
2. Aucune methode d'ecriture (`POST`, `PUT`, `PATCH`, `DELETE`) n'est implementee dans l'adaptateur.
|
|
23
|
+
3. Les decisions de gouvernance (diagnostics, propositions de remediation, validations `HITL` et scellement `WORM` dans le `Trust Ledger`) s'executent integralement dans l'espace isole d'`Agentic-Ops`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Topologie Reseau et Tunnelisation Userspace (`Docker Sandbox` vers `Scaleway`)
|
|
28
|
+
|
|
29
|
+
L'execution d'`Agentic-Ops` au sein d'un conteneur Cloud isole (`Docker Agentic Platform`) impose deux contraintes d'infrastructure majeures :
|
|
30
|
+
1. **Absence de peripherique noyau `/dev/net/tun`** : Le conteneur s'execute sans privileges `CAP_NET_ADMIN` sur l'hote physique, interdisant la creation d'une interface reseau virtuelle `tun0` dans le noyau Linux.
|
|
31
|
+
2. **Filtrage strict du protocole `UDP` sortant** : Le pare-feu de l'hebergeur Cloud bloque les paquets `UDP` directs (port `41641` utilise nativement par `WireGuard`).
|
|
32
|
+
|
|
33
|
+
### Schema Architectural du Tunnel et du Routage `SOCKS5` / `DERP`
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
+---------------------------------------------------------------------------------------------------+
|
|
37
|
+
| CLOUD DOCKER SANDBOX (Isolat Linux x86_64 - IP Tailscale : 100.102.123.121) |
|
|
38
|
+
| |
|
|
39
|
+
| +----------------------------------+ +------------------------------------------------+ |
|
|
40
|
+
| | Agentic-Ops Control Plane | | Demon tailscaled (--tun=userspace-networking) | |
|
|
41
|
+
| | | | | |
|
|
42
|
+
| | CacPullCollector (httpx) | TCP | Serveur Proxy SOCKS5 : 127.0.0.1:1055 | |
|
|
43
|
+
| | - _vps_client() ----------------+-------->+ Socket d'admin Unix : /var/run/.../sock | |
|
|
44
|
+
| | - _direct_transport ------------+----+ | Pile WireGuard en memoire vive (RAM) | |
|
|
45
|
+
| +----------------------------------+ | +-----------------------+------------------------+ |
|
|
46
|
+
+------------------------------------------|----------------------------|---------------------------+
|
|
47
|
+
| |
|
|
48
|
+
Flux HTTPS Direct (Port 443) | | Flux TLS/TCP Chiffre (Port 443)
|
|
49
|
+
(Sans Proxy Tailscale) | | Encapsulation WireGuard sur HTTP
|
|
50
|
+
| v
|
|
51
|
+
| +------------------------------------------------+
|
|
52
|
+
| | RELAIS TAILSCALE DERP #10 (Seattle - 9.5 ms) |
|
|
53
|
+
| +-----------------------+------------------------+
|
|
54
|
+
| |
|
|
55
|
+
| | Maillage Inter-Relais
|
|
56
|
+
| v
|
|
57
|
+
| +------------------------------------------------+
|
|
58
|
+
| | RELAIS TAILSCALE DERP (Paris - "par") |
|
|
59
|
+
| +-----------------------+------------------------+
|
|
60
|
+
| |
|
|
61
|
+
v v
|
|
62
|
+
+------------------------------------------+----------------------------+---------------------------+
|
|
63
|
+
| INFRASTRUCTURE DISTANTE CAC MLOPS (VPS Scaleway : scw-jovial-dubinsky - IP : 100.117.99.62) |
|
|
64
|
+
| |
|
|
65
|
+
| +---------------------------+ +-------------------------------------------------------------+ |
|
|
66
|
+
| | Reverse Proxy Public | | Services Internes Tailnet (Lecture Seule - GET Uniquement) | |
|
|
67
|
+
| | mlops.jakat-inc.fr | | | |
|
|
68
|
+
| | | | - Loki (:3100) : /loki/api/v1/query_range | |
|
|
69
|
+
| | /reports/drift/ | | - Prometheus (:9090) : /api/v1/query | |
|
|
70
|
+
| | latest_summary.json | | - MLflow (:5001) : /api/2.0/mlflow/... | |
|
|
71
|
+
| | | | - Prefect (:4200) : /api/flow_runs | |
|
|
72
|
+
| +---------------------------+ +-------------------------------------------------------------+ |
|
|
73
|
+
+---------------------------------------------------------------------------------------------------+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 3. Chronologie d'Initialisation et Diagnostic Reseau (Preuves par Captures)
|
|
79
|
+
|
|
80
|
+
Cette section documente pas a pas l'etablissement du tunnel entre la Sandbox Docker et le reseau de production, ainsi que la resolution des problemes systeme rencontres.
|
|
81
|
+
|
|
82
|
+
### Etape 1 : Demarrage du Demon en Mode Userspace et Authentification OAuth
|
|
83
|
+
|
|
84
|
+
Pour contourner l'absence de peripherique `/dev/net/tun`, le demon `tailscaled` est demarre avec le drapeau `--tun=userspace-networking` et expose un serveur proxy `SOCKS5` local sur `127.0.0.1:1055` :
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
sudo mkdir -p /var/run/tailscale
|
|
88
|
+
sudo nohup tailscaled \
|
|
89
|
+
--tun=userspace-networking \
|
|
90
|
+
--socks5-server=127.0.0.1:1055 \
|
|
91
|
+
--state=/var/run/tailscale/tailscaled.state > /tmp/tailscaled.log 2>&1 &
|
|
92
|
+
|
|
93
|
+
sudo tailscale up --hostname=agentic-ops-sandbox --accept-routes
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
L'execution de `tailscale up` genere une URL d'authentification unique permettant de rattacher le conteneur au `Tailnet` distant. Des la validation OAuth effectuee, le demon local recoit les cles cryptographiques du plan de controle `Tailscale` et affiche `Success.` dans le terminal :
|
|
97
|
+
|
|
98
|
+

|
|
99
|
+
*Figure 1 : Confirmation `Success.` dans le terminal `Shell 5` de la Sandbox Docker suite a l'enregistrement du noeud `agentic-ops-sandbox`.*
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### Etape 2 : Gestion des Permissions Unix Socket et Inspection de la Table de Routage
|
|
104
|
+
|
|
105
|
+
#### Diagnostic de l'erreur `permission denied` sur `tailscaled.sock`
|
|
106
|
+
Lorsque `tailscaled` est lance via `sudo`, le fichier socket de communication inter-processus `/var/run/tailscale/tailscaled.sock` est cree avec les droits exclusifs `root:root`. Toute invocation de la commande cliente `tailscale status` par l'utilisateur standard `agent` echoue alors avec le message :
|
|
107
|
+
`dial unix /var/run/tailscale/tailscaled.sock: connect: permission denied`.
|
|
108
|
+
|
|
109
|
+
**Resolution appliquee** :
|
|
110
|
+
Attribution des droits de lecture/ecriture sur la socket Unix via `sudo chmod 666 /var/run/tailscale/tailscaled.sock` ou invocation explicite avec `sudo tailscale status`.
|
|
111
|
+
|
|
112
|
+

|
|
113
|
+
*Figure 2 : Sortie de `sudo tailscale status` confirmant l'attribution de l'IP `100.102.123.121` au conteneur et revelant l'etat des pairs du reseau (identifiants comptes masques).*
|
|
114
|
+
|
|
115
|
+
#### Interpretation ligne par ligne de `tailscale status` :
|
|
116
|
+
* `100.102.123.121 agentic-ops-sandbox <redacted>@ linux -` : Notre conteneur Docker Cloud Sandbox est officiellement enregistre et actif sur le maillage.
|
|
117
|
+
* `100.113.161.112 <peer-node-1> <redacted>@ macOS -` : Poste de travail pair present sur le Tailnet.
|
|
118
|
+
* `100.76.198.37 <peer-node-2> <redacted>@ macOS offline, last seen 8m ago` : Second poste macOS hors ligne depuis 8 minutes.
|
|
119
|
+
* `100.92.220.73 <operator-node> <redacted>@ macOS -` : Poste de developpement local de l'operateur.
|
|
120
|
+
* `100.117.99.62 scw-jovial-dubinsky <redacted>@ linux active; relay "par"; offline, last seen 2d ago` : Le serveur VPS Scaleway de production est identifie sur le relais de Paris (`relay "par"`), mais son instance Cloud est physiquement arretee (`offline, last seen 2d ago`).
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Etape 3 : Audit de Connectivite Physique (`tailscale netcheck`)
|
|
125
|
+
|
|
126
|
+
Pour comprendre comment les paquets traversent le pare-feu du fournisseur Cloud Docker, un diagnostic `netcheck` complet a ete execute :
|
|
127
|
+
|
|
128
|
+

|
|
129
|
+
*Figure 3 : Diagnostic `sudo tailscale netcheck` mesurant la disponibilite des protocoles et la latence vers les serveurs relais mondiaux (`DERP`).*
|
|
130
|
+
|
|
131
|
+
#### Interpretation technique de `netcheck` :
|
|
132
|
+
* `UDP: false` : Confirme que le pare-feu de l'infrastructure `Docker Agentic Platform` bloque les flux `UDP` sortants.
|
|
133
|
+
* `IPv4: (no addr found)` : Pas d'adresse IPv4 publique directe en mode `STUN` (`UDP` etant bloque).
|
|
134
|
+
* `Nearest DERP: Seattle` (`sea: 9.5ms`, `sfo: 20.7ms`, `den: 22.3ms`) : Le conteneur Cloud est heberge sur la cote Ouest des Etats-Unis (region de Seattle). `Tailscale` bascule automatiquement en mode **`DERP` (Designated Encrypted Relay for Packets)**, encapsulant les trames `WireGuard` dans des flux `HTTPS/TLS` standards sur le port `TCP 443` avec seulement `9.5 ms` de latence jusqu'au premier relais.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### Etape 4 : Diagnostic et Elimination de la Boucle de Proxy (`Proxy Loop \x05\xff`)
|
|
139
|
+
|
|
140
|
+
Lors de la premiere tentative de configuration, les variables d'environnement `HTTP_PROXY=socks5://localhost:1055`, `HTTPS_PROXY=socks5://localhost:1055` et `ALL_PROXY=socks5://localhost:1055` avaient ete exportees globalement dans le shell.
|
|
141
|
+
|
|
142
|
+
L'inspection du fichier `/tmp/tailscaled.log` a permis d'identifier une anomalie critique :
|
|
143
|
+
|
|
144
|
+

|
|
145
|
+
*Figure 4 : Journal `/tmp/tailscaled.log` exposant l'erreur `malformed HTTP response "\x05\xff"` et `incompatible SOCKS version`.*
|
|
146
|
+
|
|
147
|
+
#### Explication chirurgicale du bug `\x05\xff` :
|
|
148
|
+
1. Le demon `tailscaled` doit se connecter en `HTTPS` (port `443`) aux serveurs relais `derp2e.tailscale.com` pour maintenir le tunnel `WireGuard`.
|
|
149
|
+
2. Lorsque `HTTPS_PROXY` est defini globalement dans l'environnement, le client HTTP interne de `tailscaled` (`derphttp`) tente d'etablir un tunnel HTTP `CONNECT` en passant par `localhost:1055` (c'est-a-dire par lui-meme).
|
|
150
|
+
3. Le serveur `SOCKS5` sur `1055` recoit une requete texte HTTP `CONNECT` au lieu d'un handshake binaire `SOCKS5`. Il repond alors avec les deux octets hexadecimaux `\x05\xff` (`0x05` = version du protocole `SOCKS5`, `0xFF` = aucune methode d'authentification valide) et journalise `socks5: client connection failed: incompatible SOCKS version`.
|
|
151
|
+
|
|
152
|
+
#### Remediation definitive :
|
|
153
|
+
1. Suppression des variables globales dans le shell (`unset HTTP_PROXY HTTPS_PROXY ALL_PROXY`).
|
|
154
|
+
2. Relance du demon `tailscaled` dans un environnement vierge via `env -i PATH=$PATH` afin qu'aucune variable de proxy ne puisse jamais polluer ses connexions sortantes vers les relais `DERP`.
|
|
155
|
+
3. Configuration du proxy `SOCKS5` (`socks5://127.0.0.1:1055`) **exclusivement a l'interieur du transport `httpx.HTTPTransport`** du collecteur Python, uniquement pour les requetes ciblant l'IP privee `100.117.99.62`.
|
|
156
|
+
|
|
157
|
+

|
|
158
|
+
*Figure 5 : Journal `/tmp/tailscaled.log` apres redemarrage propre : connexion `derp-10 connected; connGen=1` stable et disparition totale des erreurs `\x05\xff`.*
|
|
159
|
+
|
|
160
|
+
#### Validation finale par Ping Bidirectionnel Inter-Continents :
|
|
161
|
+
Depuis la machine locale (`100.92.220.73`), un ping `Tailscale` vers le conteneur Cloud (`100.102.123.121`) a confirme la continuite de bout en bout du tunnel :
|
|
162
|
+
```text
|
|
163
|
+
$ tailscale ping --c 2 100.102.123.121
|
|
164
|
+
pong from agentic-ops-sandbox (100.102.123.121) via DERP(sea) in 559ms
|
|
165
|
+
pong from agentic-ops-sandbox (100.102.123.121) via DERP(sea) in 213ms
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 4. Anatomie Detaillee du Module `cac_pull_collector.py`
|
|
171
|
+
|
|
172
|
+
Le fichier [`agentic_ops/adapters/cac_pull_collector.py`](agentic_ops/adapters/cac_pull_collector.py) implemente l'adaptateur d'ingestion. Voici le detail exhaustif de chaque composant.
|
|
173
|
+
|
|
174
|
+
### A. Constantes de Configuration et Endpoints Cibles
|
|
175
|
+
|
|
176
|
+
| Constante | Valeur | Role Architectural |
|
|
177
|
+
| :--- | :--- | :--- |
|
|
178
|
+
| `CAC_VPS_IP` | `"100.117.99.62"` | Adresse IP statique `Tailscale` du serveur `scw-jovial-dubinsky`. |
|
|
179
|
+
| `CAC_LOKI_BASE` | `"http://100.117.99.62:3100"` | Point d'entree du concentrateur de logs `Grafana Loki`. |
|
|
180
|
+
| `CAC_PROMETHEUS_BASE` | `"http://100.117.99.62:9090"` | Point d'entree de la base de series temporelles `Prometheus`. |
|
|
181
|
+
| `CAC_MLFLOW_BASE` | `"http://100.117.99.62:5001"` | Point d'entree du Model Registry et Tracking Server `MLflow`. |
|
|
182
|
+
| `CAC_PREFECT_BASE` | `"http://100.117.99.62:4200"` | Point d'entree de l'orchestrateur de pipelines `Prefect`. |
|
|
183
|
+
| `CAC_DRIFT_REPORT_URL` | `"https://mlops.jakat-inc.fr/reports/drift/latest_summary.json"` | Endpoint HTTPS public exposant le dernier rapport `Evidently`. |
|
|
184
|
+
| `TAILSCALE_SOCKS5_PROXY` | `"socks5://127.0.0.1:1055"` | Adresse locale du proxy `SOCKS5` expose par `tailscaled`. |
|
|
185
|
+
| `DEFAULT_TIMEOUT_S` | `20.0` | Timeout par requete conforme aux pre-requis du guide (15 a 30s). |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### B. Modeles de Contrat de Donnees (`Pydantic` Schemas)
|
|
190
|
+
|
|
191
|
+
Chaque source distante est validee et typee par un modele `Pydantic` strict avant d'etre transmise au reste du systeme :
|
|
192
|
+
|
|
193
|
+
1. **`RemoteServiceStatus` (`Enum`)** :
|
|
194
|
+
Definit l'etat de sante global de la collecte :
|
|
195
|
+
* `ONLINE` : Les 5 sources ont repondu avec succes (`0` erreur).
|
|
196
|
+
* `PARTIAL` : Au moins une source a repondu, mais une ou plusieurs autres ont echoue (ex: `Prefect` en maintenance mais `Prometheus` actif).
|
|
197
|
+
* `REMOTE_TARGET_OFFLINE` : Les 5 sources sont injoignables simultanement (cas typique ou le VPS Scaleway `scw-jovial-dubinsky` est eteint).
|
|
198
|
+
* `ERROR` : Erreur interne non reseau.
|
|
199
|
+
|
|
200
|
+
2. **`LokiAlert`** :
|
|
201
|
+
Encapsule une ligne de log d'alerte extraite de `Loki` :
|
|
202
|
+
* `timestamp` (`datetime` UTC converti depuis l'epoch en nanosecondes de `Loki`).
|
|
203
|
+
* `labels` (`Dict[str, str]`, ex: `{"service": "prefect-worker", "level": "warning"}`).
|
|
204
|
+
* `line` (`str`, contenu JSON brut de l'evenement structuré).
|
|
205
|
+
|
|
206
|
+
3. **`PrometheusSnapshot`** :
|
|
207
|
+
Synthetise l'etat metrique temps reel de la plateforme :
|
|
208
|
+
* `drift_share` (`Optional[float]`) : Valeur de la jauge `cac_mlops_drift_share` (proportion de colonnes en derive).
|
|
209
|
+
* `model_in_production` (`Optional[str]`) : Valeur issue de `mlops_model_info{alias="Production"}`.
|
|
210
|
+
* `api_request_total` (`Optional[float]`) : Compteur `http_requests_total` mesurant l'activite de l'API.
|
|
211
|
+
|
|
212
|
+
4. **`MlflowModelInfo`** :
|
|
213
|
+
Trace l'identite exacte et les performances du modele en production :
|
|
214
|
+
* `name` (`"lgbm_accidents"`), `alias` (`"Production"`), `version` (ex: `"7"`), `run_id` (ex: `"abc123def456"`).
|
|
215
|
+
* `metrics` (`Dict[str, Any]`) : Metriques d'entrainement recuperees sur le `run_id` associe (ex: `{"f1_score": 0.883, "accuracy": 0.891}`).
|
|
216
|
+
* `tags` (`Dict[str, str]`) : Metadonnees de gouvernance attachees a la version du modele.
|
|
217
|
+
|
|
218
|
+
5. **`PrefectFlowRunSummary`** :
|
|
219
|
+
Represente l'historique d'execution d'un pipeline MLOps :
|
|
220
|
+
* `id`, `name`, `state_name` (`"Completed"`, `"Failed"`), `state_type` (`"COMPLETED"`, `"FAILED"`), `start_time`, `end_time`.
|
|
221
|
+
|
|
222
|
+
6. **`DriftReportSnapshot`** :
|
|
223
|
+
Structure le rapport d'analyse statistique `Evidently` :
|
|
224
|
+
* `report_date` (`str`), `drift_share` (`float`), `drifted_columns` (`List[str]`, ex: `["age_conducteur", "hrmn", "surf"]`), et `raw` (`Dict` complet pour audit).
|
|
225
|
+
|
|
226
|
+
7. **`CacCollectedSnapshot`** :
|
|
227
|
+
L'enveloppe racine renvoyee par `collect_all()`. Contient le `status`, l'horodatage UTC `collected_at`, le dictionnaire `errors` repertoriant les pannes par source, et les 5 sous-objets collectes. Entierement serialisable via `.model_dump()` pour archivage cryptographique dans l'`EvidenceStore`.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### C. Fonctionnement Interne des 5 Methodes de Collecte
|
|
232
|
+
|
|
233
|
+
1. **`_fetch_loki_alerts()`** :
|
|
234
|
+
* Calcule une fenetre glissante de 6 heures en nanosecondes (`end_ns = now * 1e9`, `start_ns = end_ns - 6h`).
|
|
235
|
+
* Execute un `GET` sur `/loki/api/v1/query_range` avec la requete `LogQL` `{service="prefect-worker"} |= "event=alert"`, limitee a `100` entrees en ordre antéchronologique (`backward`).
|
|
236
|
+
* Parcourt les flux (`streams`) et convertit chaque tuple `[timestamp_ns, log_line]` en instance `LokiAlert`.
|
|
237
|
+
|
|
238
|
+
2. **`_fetch_prometheus()`** :
|
|
239
|
+
* Utilise une fonction interne `instant_query(metric_expr)` effectuant un `GET` sur `/api/v1/query`.
|
|
240
|
+
* Extrait la valeur scalaire situee dans `data.result[0].value[1]` pour les trois expressions `cac_mlops_drift_share`, `mlops_model_info{alias="Production"}` et `http_requests_total`.
|
|
241
|
+
* Convertit proprement les chaines numeriques renvoyees par `Prometheus` en `float` Python.
|
|
242
|
+
|
|
243
|
+
3. **`_fetch_mlflow_model()`** :
|
|
244
|
+
* Execute un premier `GET` sur `/api/2.0/mlflow/registered-models/alias?name=lgbm_accidents&alias=Production` pour identifier la version active et son `run_id`.
|
|
245
|
+
* Si `run_id` est present, execute un second `GET` sur `/api/2.0/mlflow/runs/get?run_id=<run_id>` afin d'extraire le tableau `run.data.metrics` et reconstruire le dictionnaire des scores de validation (`f1_score`, `accuracy`, etc.).
|
|
246
|
+
|
|
247
|
+
4. **`_fetch_prefect_flow_runs()`** :
|
|
248
|
+
* Execute un `GET` sur `/api/flow_runs` avec `limit=10` et `sort=START_TIME_DESC`.
|
|
249
|
+
* Convertit les horodatages ISO 8601 (`2026-10-10T08:00:00Z`) en objets `datetime` UTC via l'utilitaire `_parse_dt()`.
|
|
250
|
+
|
|
251
|
+
5. **`_fetch_drift_report()`** :
|
|
252
|
+
* Utilise `self._direct_transport` (sans passer par le proxy `SOCKS5`) pour effectuer un `GET` HTTPS sur `https://mlops.jakat-inc.fr/reports/drift/latest_summary.json`.
|
|
253
|
+
* Gere les variantes de nommage de cles (`drift_share` ou `share_of_drifted_columns`, `report_date` ou `date`).
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 5. Catalogue des 13 Tests Unitaires et de Securite (`test_cac_pull_collector.py`)
|
|
258
|
+
|
|
259
|
+
La suite de tests [`tests/adapters/test_cac_pull_collector.py`](tests/adapters/test_cac_pull_collector.py) valide le connecteur de maniere totalement deterministe grace au mocking des transports `httpx`. Elle s'execute en `0.80s` sans necessiter que le VPS distant soit allume.
|
|
260
|
+
|
|
261
|
+
```text
|
|
262
|
+
============================= test session starts ==============================
|
|
263
|
+
collected 13 items
|
|
264
|
+
|
|
265
|
+
tests/adapters/test_cac_pull_collector.py::TestFetchLokiAlerts::test_parse_alerts_correctement PASSED [ 7%]
|
|
266
|
+
tests/adapters/test_cac_pull_collector.py::TestFetchLokiAlerts::test_retourne_liste_vide_si_no_result PASSED [ 15%]
|
|
267
|
+
tests/adapters/test_cac_pull_collector.py::TestFetchPrometheus::test_parse_drift_share PASSED [ 23%]
|
|
268
|
+
tests/adapters/test_cac_pull_collector.py::TestFetchMlflowModel::test_parse_modele_production PASSED [ 30%]
|
|
269
|
+
tests/adapters/test_cac_pull_collector.py::TestFetchPrefect::test_parse_flow_runs PASSED [ 38%]
|
|
270
|
+
tests/adapters/test_cac_pull_collector.py::TestFetchPrefect::test_n_appelle_que_get PASSED [ 46%]
|
|
271
|
+
tests/adapters/test_cac_pull_collector.py::TestFetchDriftReport::test_parse_rapport_public PASSED [ 53%]
|
|
272
|
+
tests/adapters/test_cac_pull_collector.py::TestCollectAll::test_status_online_si_tout_repond PASSED [ 61%]
|
|
273
|
+
tests/adapters/test_cac_pull_collector.py::TestCollectAll::test_status_offline_si_vps_eteint PASSED [ 69%]
|
|
274
|
+
tests/adapters/test_cac_pull_collector.py::TestCollectAll::test_status_partial_si_une_source_echoue PASSED [ 76%]
|
|
275
|
+
tests/adapters/test_cac_pull_collector.py::TestCollectAll::test_snapshot_est_serialisable PASSED [ 84%]
|
|
276
|
+
tests/adapters/test_cac_pull_collector.py::TestReadOnlyEnforcement::test_aucun_post_dans_fetch_mlflow PASSED [ 92%]
|
|
277
|
+
tests/adapters/test_cac_pull_collector.py::TestReadOnlyEnforcement::test_aucun_post_dans_fetch_loki PASSED [100%]
|
|
278
|
+
======================== 13 passed in 0.80s =========================
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Explication detaillee de chaque test :
|
|
282
|
+
|
|
283
|
+
| Classe de Test | Nom du Test | Ce qu'il verifie et prouve |
|
|
284
|
+
| :--- | :--- | :--- |
|
|
285
|
+
| `TestFetchLokiAlerts` | `test_parse_alerts_correctement` | Verifie que deux evenements JSON (`DATA-042` et `INFRA-001`) renvoyes par `Loki` en nanosecondes sont correctement transformes en objets `LokiAlert` avec leurs labels et timestamps UTC. |
|
|
286
|
+
| `TestFetchLokiAlerts` | `test_retourne_liste_vide_si_no_result` | Garantit que si `Loki` renvoie `{"data": {"result": []}}`, la methode retourne une liste vide `[]` sans erreur d'indexation (`IndexError`). |
|
|
287
|
+
| `TestFetchPrometheus` | `test_parse_drift_share` | Simule 3 appels successifs a `Prometheus` (`drift_share=0.34`, `model_in_production="lgbm_v3"`, et une metrique absente `[]`) et verifie la conversion exacte en `float`, `str` et `None`. |
|
|
288
|
+
| `TestFetchMlflowModel` | `test_parse_modele_production` | Valide l'enchainement des 2 requetes `GET` vers `MLflow` (resolution de l'alias `Production` vers la version `7`, puis extraction du `f1_score=0.883` depuis le run `abc123def456`). |
|
|
289
|
+
| `TestFetchPrefect` | `test_parse_flow_runs` | Verifie l'extraction de deux flow runs (`COMPLETED` et `FAILED`) et le parsing ISO 8601 des dates de debut et de fin. |
|
|
290
|
+
| `TestFetchPrefect` | `test_n_appelle_que_get` | Preuve formelle par assertion sur le mock (`assert_not_called()`) qu'aucune methode `post`, `put` ou `delete` n'est invoquee sur `Prefect`. |
|
|
291
|
+
| `TestFetchDriftReport` | `test_parse_rapport_public` | Verifie le parsing du rapport JSON `Evidently` (`drift_share=0.34` et liste des 3 colonnes en derive `["age_conducteur", "hrmn", "surf"]`). |
|
|
292
|
+
| `TestCollectAll` | `test_status_online_si_tout_repond` | Prouve que lorsque les 5 collecteurs repondent sans exception, l'agregat affiche `status == RemoteServiceStatus.ONLINE` et `errors == {}`. |
|
|
293
|
+
| `TestCollectAll` | `test_status_offline_si_vps_eteint` | **Test de Resilience Critique** : Simule une panne totale du VPS (`httpx.ConnectError("Connection refused")` sur les 5 sources). Prouve que `collect_all()` n'eleve aucune exception fatale et retourne proprement `REMOTE_TARGET_OFFLINE` avec les 5 erreurs journalisees. |
|
|
294
|
+
| `TestCollectAll` | `test_status_partial_si_une_source_echoue` | Simule un `TimeoutException` sur `Loki` uniquement, pendant que les 4 autres sources fonctionnent. Prouve que le statut passe a `PARTIAL` et que les donnees de `Prometheus` et `MLflow` sont bien preservees. |
|
|
295
|
+
| `TestCollectAll` | `test_snapshot_est_serialisable` | Verifie que `snapshot.model_dump()` produit un dictionnaire Python pur integrable sans erreur dans le `Trust Ledger` (`EvidenceStore`). |
|
|
296
|
+
| `TestReadOnlyEnforcement` | `test_aucun_post_dans_fetch_mlflow` | Garantit que la collecte `MLflow` n'appelle jamais `POST`, `PUT` ou `DELETE` (protegeant l'alias `Production`). |
|
|
297
|
+
| `TestReadOnlyEnforcement` | `test_aucun_post_dans_fetch_loki` | Garantit que la collecte `Loki` n'appelle jamais `POST /loki/api/v1/push` (empechant toute injection de faux logs). |
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 6. Guide d'Utilisation Pratique (En Local ou dans la Sandbox Docker)
|
|
302
|
+
|
|
303
|
+
### A. Verification Manuelle Rapide via `curl` (Quand le VPS Cible est allume)
|
|
304
|
+
|
|
305
|
+
Dans le terminal de la Sandbox Docker (avec `tailscaled` actif sur `127.0.0.1:1055`) :
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
# 1. Verifier que le VPS scw-jovial-dubinsky est passe de "offline" a "active"
|
|
309
|
+
sudo tailscale status
|
|
310
|
+
|
|
311
|
+
# 2. Tester les 4 sondes de sante internes via le proxy SOCKS5
|
|
312
|
+
curl -s --socks5 127.0.0.1:1055 http://100.117.99.62:3100/ready # Attendu : ready
|
|
313
|
+
curl -s --socks5 127.0.0.1:1055 http://100.117.99.62:9090/-/ready # Attendu : Prometheus Server is Ready.
|
|
314
|
+
curl -s --socks5 127.0.0.1:1055 http://100.117.99.62:5001/health # Attendu : OK
|
|
315
|
+
curl -s --socks5 127.0.0.1:1055 http://100.117.99.62:4200/api/health # Attendu : true
|
|
316
|
+
|
|
317
|
+
# 3. Lire le rapport de derive public en direct (sans proxy)
|
|
318
|
+
curl -s https://mlops.jakat-inc.fr/reports/drift/latest_summary.json
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### B. Utilisation Programmatique dans `Agentic-Ops`
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
from agentic_ops.adapters.cac_pull_collector import CacPullCollector, RemoteServiceStatus
|
|
325
|
+
|
|
326
|
+
# Mode Sandbox Docker (passe par le proxy SOCKS5 local 127.0.0.1:1055)
|
|
327
|
+
collector = CacPullCollector(
|
|
328
|
+
socks5_proxy="socks5://127.0.0.1:1055",
|
|
329
|
+
timeout=20.0,
|
|
330
|
+
use_proxy_for_vps=True,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Mode Machine Locale (si Tailscale tourne nativement sur macOS/Linux avec interface tun)
|
|
334
|
+
# collector = CacPullCollector(use_proxy_for_vps=False)
|
|
335
|
+
|
|
336
|
+
snapshot = collector.collect_all()
|
|
337
|
+
|
|
338
|
+
if snapshot.status == RemoteServiceStatus.REMOTE_TARGET_OFFLINE:
|
|
339
|
+
print("Le VPS scw-jovial-dubinsky est actuellement eteint. Orchestrateur en attente passive.")
|
|
340
|
+
elif snapshot.status in (RemoteServiceStatus.ONLINE, RemoteServiceStatus.PARTIAL):
|
|
341
|
+
if snapshot.prometheus:
|
|
342
|
+
print(f"Drift Share (Prometheus) : {snapshot.prometheus.drift_share}")
|
|
343
|
+
if snapshot.mlflow_model:
|
|
344
|
+
print(f"Modele actif : {snapshot.mlflow_model.name} v{snapshot.mlflow_model.version}")
|
|
345
|
+
print(f"Metriques du run : {snapshot.mlflow_model.metrics}")
|
|
346
|
+
if snapshot.loki_alerts:
|
|
347
|
+
print(f"Alertes Loki detectees : {len(snapshot.loki_alerts)}")
|
|
348
|
+
```
|