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.
Files changed (165) hide show
  1. agentic_ops-0.1.0/.env.example +134 -0
  2. agentic_ops-0.1.0/.github/workflows/ci.yml +22 -0
  3. agentic_ops-0.1.0/.github/workflows/publish.yml +35 -0
  4. agentic_ops-0.1.0/.gitignore +47 -0
  5. agentic_ops-0.1.0/CAC_TAILSCALE_PULL_CONNECTOR.md +348 -0
  6. agentic_ops-0.1.0/DOCKER_SANDBOX_VALIDATION.md +258 -0
  7. agentic_ops-0.1.0/Dockerfile +52 -0
  8. agentic_ops-0.1.0/Dockerfile.hf +56 -0
  9. agentic_ops-0.1.0/GENESIS.md +69 -0
  10. agentic_ops-0.1.0/INTEGRATION_GUIDE.md +130 -0
  11. agentic_ops-0.1.0/LICENSE +21 -0
  12. agentic_ops-0.1.0/PKG-INFO +1489 -0
  13. agentic_ops-0.1.0/README.md +1460 -0
  14. agentic_ops-0.1.0/README_FR.md +1461 -0
  15. agentic_ops-0.1.0/agentic_ops/adapters/cac_pull_collector.py +413 -0
  16. agentic_ops-0.1.0/agentic_ops/adapters/datadog_adapter.py +33 -0
  17. agentic_ops-0.1.0/agentic_ops/adapters/kubernetes_adapter.py +73 -0
  18. agentic_ops-0.1.0/agentic_ops/adapters/llm_protocol.py +366 -0
  19. agentic_ops-0.1.0/agentic_ops/adapters/llm_provider_registry.py +510 -0
  20. agentic_ops-0.1.0/agentic_ops/adapters/protocol.py +19 -0
  21. agentic_ops-0.1.0/agentic_ops/adapters/registry_adapter.py +70 -0
  22. agentic_ops-0.1.0/agentic_ops/adapters/runbook_rag.py +21 -0
  23. agentic_ops-0.1.0/agentic_ops/adapters/slack_adapter.py +78 -0
  24. agentic_ops-0.1.0/agentic_ops/cli/compile_taxonomy.py +66 -0
  25. agentic_ops-0.1.0/agentic_ops/cli/main.py +3192 -0
  26. agentic_ops-0.1.0/agentic_ops/cli/trust_ledger.py +150 -0
  27. agentic_ops-0.1.0/agentic_ops/core/__init__.py +1 -0
  28. agentic_ops-0.1.0/agentic_ops/core/approvals.py +137 -0
  29. agentic_ops-0.1.0/agentic_ops/core/approvers.py +32 -0
  30. agentic_ops-0.1.0/agentic_ops/core/circuit_breaker.py +39 -0
  31. agentic_ops-0.1.0/agentic_ops/core/contracts.py +103 -0
  32. agentic_ops-0.1.0/agentic_ops/core/crypto.py +37 -0
  33. agentic_ops-0.1.0/agentic_ops/core/distributed.py +29 -0
  34. agentic_ops-0.1.0/agentic_ops/core/evidence.py +143 -0
  35. agentic_ops-0.1.0/agentic_ops/core/executor.py +181 -0
  36. agentic_ops-0.1.0/agentic_ops/core/facts.py +29 -0
  37. agentic_ops-0.1.0/agentic_ops/core/governance_schemas.py +25 -0
  38. agentic_ops-0.1.0/agentic_ops/core/idempotency.py +52 -0
  39. agentic_ops-0.1.0/agentic_ops/core/infra_specialist.py +52 -0
  40. agentic_ops-0.1.0/agentic_ops/core/model_specialist.py +51 -0
  41. agentic_ops-0.1.0/agentic_ops/core/orchestrator.py +281 -0
  42. agentic_ops-0.1.0/agentic_ops/core/policy.py +68 -0
  43. agentic_ops-0.1.0/agentic_ops/core/policy_engine.py +166 -0
  44. agentic_ops-0.1.0/agentic_ops/core/prompt_guard.py +16 -0
  45. agentic_ops-0.1.0/agentic_ops/core/remediation_agent.py +49 -0
  46. agentic_ops-0.1.0/agentic_ops/core/specialist.py +52 -0
  47. agentic_ops-0.1.0/agentic_ops/core/state.py +20 -0
  48. agentic_ops-0.1.0/agentic_ops/core/telemetry.py +47 -0
  49. agentic_ops-0.1.0/agentic_ops/entrypoints/approval_sweeper.py +6 -0
  50. agentic_ops-0.1.0/agentic_ops/entrypoints/grafana_webhook.py +61 -0
  51. agentic_ops-0.1.0/agentic_ops/entrypoints/mcp_server.py +1149 -0
  52. agentic_ops-0.1.0/agentic_ops/entrypoints/slack_verify.py +24 -0
  53. agentic_ops-0.1.0/agentic_ops/entrypoints/slack_webhook.py +59 -0
  54. agentic_ops-0.1.0/agentic_ops/evidence_store/EP-0001.json +64 -0
  55. agentic_ops-0.1.0/agentic_ops/evidence_store/EP-0002.json +58 -0
  56. agentic_ops-0.1.0/agentic_ops/evidence_store/EP-0003.json +58 -0
  57. agentic_ops-0.1.0/agentic_ops/evidence_store/EP-0004.json +64 -0
  58. agentic_ops-0.1.0/agentic_ops/evidence_store/EP-0005.json +65 -0
  59. agentic_ops-0.1.0/agentic_ops/evidence_store/EP-CHAOS-31c6e006.json +27 -0
  60. agentic_ops-0.1.0/agentic_ops/evidence_store/EP-CHAOS-5950095d.json +28 -0
  61. agentic_ops-0.1.0/agentic_ops/evidence_store/EP-CHAOS-d6c18601.json +28 -0
  62. agentic_ops-0.1.0/agentic_ops/evidence_store/_index.json +4 -0
  63. agentic_ops-0.1.0/agentic_ops/governance/action_catalog.yaml +26 -0
  64. agentic_ops-0.1.0/agentic_ops/governance/approvers.yaml +7 -0
  65. agentic_ops-0.1.0/agentic_ops/governance/autonomy_policy.yaml +23 -0
  66. agentic_ops-0.1.0/agentic_ops/governance/risk_register.yaml +6 -0
  67. agentic_ops-0.1.0/agentic_ops/governance/shadow_policy.yaml +23 -0
  68. agentic_ops-0.1.0/agentic_ops/knowledge_base/runbooks/DATA-042.md +8 -0
  69. agentic_ops-0.1.0/agentic_ops/knowledge_base/runbooks/INFRA-001.md +8 -0
  70. agentic_ops-0.1.0/agentic_ops/schemas/action.schema.json +13 -0
  71. agentic_ops-0.1.0/agentic_ops/schemas/diagnosis.schema.json +11 -0
  72. agentic_ops-0.1.0/agentic_ops/schemas/evidence.schema.json +12 -0
  73. agentic_ops-0.1.0/agentic_ops/schemas/risk.schema.json +14 -0
  74. agentic_ops-0.1.0/agentic_ops/tools/__init__.py +0 -0
  75. agentic_ops-0.1.0/agentic_ops/tools/base.py +80 -0
  76. agentic_ops-0.1.0/agentic_ops/tools/data.py +77 -0
  77. agentic_ops-0.1.0/agentic_ops/tools/github.py +30 -0
  78. agentic_ops-0.1.0/agentic_ops/tools/infra.py +51 -0
  79. agentic_ops-0.1.0/agentic_ops/tools/model.py +29 -0
  80. agentic_ops-0.1.0/agentic_ops/tools/observability.py +56 -0
  81. agentic_ops-0.1.0/docker-compose.yml +26 -0
  82. agentic_ops-0.1.0/docs/HISTORICAL_AUDIT_AND_REMEDIATION.md +121 -0
  83. agentic_ops-0.1.0/docs/MAS_AGENT_ROUTING_AND_DEFAULTS_GUIDE.md +543 -0
  84. agentic_ops-0.1.0/docs/MAS_FALLBACK_ARCHITECTURE_GUIDE.md +524 -0
  85. agentic_ops-0.1.0/docs/MCP_SERVER_GUIDE.md +399 -0
  86. agentic_ops-0.1.0/docs/MONOREPO_DECOUPLING_AND_REGRESSION_AUDIT.md +172 -0
  87. agentic_ops-0.1.0/docs/MULTI_LLM_ROLE_SPECIALIZATION_GUIDE.md +218 -0
  88. agentic_ops-0.1.0/docs/OPENROUTER_FREE_MODELS_GUIDE.md +303 -0
  89. agentic_ops-0.1.0/docs/PRESENTATION_SLIDES.md +51 -0
  90. agentic_ops-0.1.0/docs/SLACK_HITL_INTEGRATION_GUIDE.md +163 -0
  91. agentic_ops-0.1.0/docs/TARGET_OUTAGE_AND_RESILIENCE_GUIDE.md +140 -0
  92. agentic_ops-0.1.0/docs/WORM_TRUST_LEDGER_GUIDE.md +190 -0
  93. agentic_ops-0.1.0/docs/assets/liora_datascientest_logo.png +0 -0
  94. agentic_ops-0.1.0/docs/assets/screenshots/01_docker_agentic_platform_welcome.png +0 -0
  95. agentic_ops-0.1.0/docs/assets/screenshots/02_docker_kits_catalog.png +0 -0
  96. agentic_ops-0.1.0/docs/assets/screenshots/03_docker_sandbox_running.png +0 -0
  97. agentic_ops-0.1.0/docs/assets/screenshots/04_demo_agentic_ops_execution.png +0 -0
  98. agentic_ops-0.1.0/docs/assets/screenshots/05_hardware_telemetry_amd_epyc.png +0 -0
  99. agentic_ops-0.1.0/docs/assets/screenshots/06_slack_security_rejection_401.png +0 -0
  100. agentic_ops-0.1.0/docs/assets/screenshots/07_adversarial_security_tests_passed.png +0 -0
  101. agentic_ops-0.1.0/docs/assets/screenshots/08_mlops_scenarios_zero_forbidden.png +0 -0
  102. agentic_ops-0.1.0/docs/assets/screenshots/09_governance_fsm_grand_chelem.png +0 -0
  103. agentic_ops-0.1.0/docs/assets/screenshots/11_tailscale_up_daemon_success.png +0 -0
  104. agentic_ops-0.1.0/docs/assets/screenshots/12_tailscale_status_mesh_nodes.png +0 -0
  105. agentic_ops-0.1.0/docs/assets/screenshots/13_tailscale_netcheck_derp_seattle.png +0 -0
  106. agentic_ops-0.1.0/docs/assets/screenshots/14_tailscaled_proxy_loop_diagnostic.png +0 -0
  107. agentic_ops-0.1.0/docs/assets/screenshots/15_tailscaled_derp10_clean_connected.png +0 -0
  108. agentic_ops-0.1.0/docs/assets/screenshots/cognitive_web_console_live.png +0 -0
  109. agentic_ops-0.1.0/docs/assets/slides/slide_01_flux_agentic_ops.png +0 -0
  110. agentic_ops-0.1.0/docs/assets/slides/slide_02_signal_brut_diagnostic.png +0 -0
  111. agentic_ops-0.1.0/docs/assets/slides/slide_03_ia_propose_code_autorise.png +0 -0
  112. agentic_ops-0.1.0/docs/assets/slides/slide_04_humain_decide_registre_prouve.png +0 -0
  113. agentic_ops-0.1.0/docs/assets/slides/slide_05_production_live_mode_degrade.png +0 -0
  114. agentic_ops-0.1.0/docs/assets/slides/slide_06_demonstration_live_console_glass_box.png +0 -0
  115. agentic_ops-0.1.0/docs/fiche_soutenance_jury_sre.md +136 -0
  116. agentic_ops-0.1.0/docs/mlops_failure_modes_taxonomy.md +637 -0
  117. agentic_ops-0.1.0/docs/mlops_simulation_scenarios_registry.md +9604 -0
  118. agentic_ops-0.1.0/helm/agentic-ops/Chart.yaml +6 -0
  119. agentic_ops-0.1.0/helm/agentic-ops/templates/deployment.yaml +40 -0
  120. agentic_ops-0.1.0/helm/agentic-ops/templates/rbac.yaml +23 -0
  121. agentic_ops-0.1.0/helm/agentic-ops/templates/serviceaccount.yaml +10 -0
  122. agentic_ops-0.1.0/helm/agentic-ops/values.yaml +50 -0
  123. agentic_ops-0.1.0/librechat.yaml +54 -0
  124. agentic_ops-0.1.0/pyproject.toml +50 -0
  125. agentic_ops-0.1.0/reports/scenarios.json +168 -0
  126. agentic_ops-0.1.0/scripts/demo_agentic_ops.sh +74 -0
  127. agentic_ops-0.1.0/scripts/entrypoint.sh +72 -0
  128. agentic_ops-0.1.0/start_sandbox.sh +5 -0
  129. agentic_ops-0.1.0/tests/adapters/__init__.py +0 -0
  130. agentic_ops-0.1.0/tests/adapters/test_cac_pull_collector.py +374 -0
  131. agentic_ops-0.1.0/tests/adapters/test_llm_provider_registry.py +320 -0
  132. agentic_ops-0.1.0/tests/adversarial/test_evidence_integrity.py +49 -0
  133. agentic_ops-0.1.0/tests/adversarial/test_idempotency_and_rate.py +26 -0
  134. agentic_ops-0.1.0/tests/adversarial/test_prompt_hygiene.py +36 -0
  135. agentic_ops-0.1.0/tests/conftest.py +31 -0
  136. agentic_ops-0.1.0/tests/entrypoints/test_chat_endpoint.py +187 -0
  137. agentic_ops-0.1.0/tests/entrypoints/test_mcp_server.py +467 -0
  138. agentic_ops-0.1.0/tests/integration/test_model_registry.py +64 -0
  139. agentic_ops-0.1.0/tests/integration/test_registry_e2e.py +121 -0
  140. agentic_ops-0.1.0/tests/known_gaps.yaml +4 -0
  141. agentic_ops-0.1.0/tests/policy/__init__.py +0 -0
  142. agentic_ops-0.1.0/tests/policy/conftest.py +26 -0
  143. agentic_ops-0.1.0/tests/policy/test_known_gaps.py +18 -0
  144. agentic_ops-0.1.0/tests/policy/test_loading.py +40 -0
  145. agentic_ops-0.1.0/tests/policy/test_policy_engine.py +53 -0
  146. agentic_ops-0.1.0/tests/scenarios/__init__.py +0 -0
  147. agentic_ops-0.1.0/tests/scenarios/adapter.py +69 -0
  148. agentic_ops-0.1.0/tests/scenarios/conftest.py +49 -0
  149. agentic_ops-0.1.0/tests/scenarios/data/adversarial/sc_101_log_injection.yaml +16 -0
  150. agentic_ops-0.1.0/tests/scenarios/data/adversarial/sc_102_param_injection.yaml +15 -0
  151. agentic_ops-0.1.0/tests/scenarios/data/adversarial/sc_103_poisoned_runbook.yaml +17 -0
  152. agentic_ops-0.1.0/tests/scenarios/data/sc_001_data_ingestion_drop.yaml +16 -0
  153. agentic_ops-0.1.0/tests/scenarios/data/sc_003_rollback_trap.yaml +18 -0
  154. agentic_ops-0.1.0/tests/scenarios/data/sc_004_infra_red_herring.yaml +17 -0
  155. agentic_ops-0.1.0/tests/scenarios/data/sc_005_real_infra_failure.yaml +17 -0
  156. agentic_ops-0.1.0/tests/scenarios/loader.py +14 -0
  157. agentic_ops-0.1.0/tests/scenarios/runner.py +72 -0
  158. agentic_ops-0.1.0/tests/scenarios/schema.py +59 -0
  159. agentic_ops-0.1.0/tests/scenarios/stubs.py +88 -0
  160. agentic_ops-0.1.0/tests/scenarios/test_scenarios.py +26 -0
  161. agentic_ops-0.1.0/tests/security/test_executor.py +30 -0
  162. agentic_ops-0.1.0/tests/test_fsm_orchestrator.py +22 -0
  163. agentic_ops-0.1.0/tests/test_grafana_webhook.py +25 -0
  164. agentic_ops-0.1.0/tests/test_tools_security.py +23 -0
  165. 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
+ ![Succes de la commande tailscale up dans la Sandbox](docs/assets/screenshots/11_tailscale_up_daemon_success.png)
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
+ ![Inspection du statut du maillage Tailscale](docs/assets/screenshots/12_tailscale_status_mesh_nodes.png)
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
+ ![Resultat de la commande tailscale netcheck](docs/assets/screenshots/13_tailscale_netcheck_derp_seattle.png)
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
+ ![Diagnostic de la boucle SOCKS5 dans tailscaled.log](docs/assets/screenshots/14_tailscaled_proxy_loop_diagnostic.png)
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
+ ![Validation des logs tailscaled apres correction](docs/assets/screenshots/15_tailscaled_derp10_clean_connected.png)
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
+ ```