feat(phase2-E): multi-provider routing via secutools delegation
Adds optional delegation of agent-queue tasks to the SecuAAS secutools AI platform (GPU / Gemini / Claude API) instead of dispatching to a local Claude Code tmux session. Per-task opt-in via YAML frontmatter fields preferred_ai, allow_delegation, complexity_hint — absence keeps the Phase 1 behaviour exactly (zero breaking change). Go side: - internal/secutools: HTTP client with exponential-backoff retries (SubmitJob/GetJob/WaitForResult), DecideProvider map adapter for CLI use, table tests. - internal/router: struct-typed Decide() with strict precedence (needs_claude_code > preferred_ai=claude-code > allow_delegation=false > preferred_ai > fail-safe local on unknown). - internal/delegation: Manager submits jobs, writes .md.delegated markers for on-restart recovery, runs a periodic reaper that moves completed jobs into done/ with provider/cost footer and failed jobs into failed/. - internal/dispatcher: WithDelegation() opt-in, routeTask hook before findFreeSession, skips .md.delegated in assignNextTask. - internal/api: /api/delegated/status (active jobs + counters), /watchdog/status extended with delegation counters. - cmd/ccl-delegate: small CLI exposing submit/get/result/decide so the bash dispatcher can call the same contract without duplicating logic. - cmd/claude-failover: delegation wired opt-in via SECUTOOLS_API_KEY. Tests: - 29+ new unit tests across router, secutools, delegation, dispatcher, api packages. go test -race -count=1 clean. - tests/phase2-E-integration.sh: bash end-to-end against a Python stdlib mock HTTP server, exercising the dev-management scripts. Forward-compat with watchdog (Phase 1 B1 already ignores state=delegated_to_secutools) so delegated tasks aren't flagged stale.
This commit is contained in:
parent
47ab86eef9
commit
3e20085204
18 changed files with 2819 additions and 22 deletions
90
VERSION.md
90
VERSION.md
|
|
@ -1,4 +1,92 @@
|
|||
# Version actuelle : 0.3.8
|
||||
# Version actuelle : 0.4.0
|
||||
|
||||
## [0.4.0] - 2026-04-17
|
||||
**Type:** Minor — Phase 2 chantier E : multi-provider routing (delegation to secutools)
|
||||
|
||||
### Ajouté
|
||||
- `internal/secutools` — HTTP client (SubmitJob, GetJob, WaitForResult)
|
||||
pour la plateforme centralisée IA SecuAAS. Interface `Client` mockable
|
||||
pour les tests, implémentation `HTTPClient` avec polling 2s + timeout
|
||||
+ propagation `context.Context`.
|
||||
- `internal/router` — Décide ProviderClaudeCode (Phase 1) vs ProviderGPU /
|
||||
Gemini / ClaudeAPI / Auto (delegation secutools) à partir des nouveaux
|
||||
champs frontmatter `preferred_ai`, `allow_delegation`, `complexity_hint`.
|
||||
Précédence stricte : `needs_claude_code` > `preferred_ai=claude-code` >
|
||||
`allow_delegation=false` (default) > `preferred_ai=...` > fail-safe Claude
|
||||
Code sur valeur inconnue.
|
||||
- `internal/delegation` — Manager qui submit les tâches non-Claude vers
|
||||
secutools, écrit un marker `inbox/<id>.md.delegated` (rebuild-on-restart),
|
||||
et fait tourner un reaper périodique qui finalise les jobs vers `done/`
|
||||
(succès) ou `failed/` (échec). Compteurs atomiques (Active /
|
||||
CompletedTotal / FailedTotal) exposés via Snapshot().
|
||||
- Dispatcher: méthode `WithDelegation()` opt-in, branchement router avant
|
||||
`findFreeSession()`, fallback automatique vers Claude Code si le submit
|
||||
secutools échoue. Skip des `*.md.delegated` dans `assignNextTask`.
|
||||
- Frontmatter: `TaskFrontmatter` étendu avec `PreferredAI`, `AllowDelegation`,
|
||||
`ComplexityHint`. Tous optionnels — un .md sans ces champs garde
|
||||
exactement le comportement Phase 1.
|
||||
- API HTTP : nouveau `GET /api/delegated/status` (jobs en cours +
|
||||
compteurs), `GET /watchdog/status` qui inclut les compteurs delegation.
|
||||
`WithDelegation()` opt-in — endpoints renvoient 404 si désactivés.
|
||||
- main.go : Manager initialisé seulement si `SECUTOOLS_API_KEY` est set
|
||||
(zéro changement pour les déploiements existants). `LoadFromDisk()`
|
||||
réhydrate les markers en attente après un restart.
|
||||
|
||||
### Tests ajoutés (29 nouveaux cas)
|
||||
- `internal/router` (8 tests) : matrice de décision complète, contrats
|
||||
IsDelegated, fail-safe sur provider inconnu.
|
||||
- `internal/secutools` (5 tests) : httptest.Server qui mock le contrat,
|
||||
HappyPath, HTTPError, polling jusqu'à completed, ErrJobFailed, cancel
|
||||
via context.
|
||||
- `internal/delegation` (7 tests) : Submit + marker, rejet provider non
|
||||
délégué, reap success/fail, LoadFromDisk, Active(), end-to-end.
|
||||
- `internal/api` (4 tests) : /health, /api/delegated/status désactivé/
|
||||
activé, /watchdog/status inclut bien les counters.
|
||||
- `internal/dispatcher` (5 nouveaux tests + 8 existants intacts) :
|
||||
routeTask parse les nouveaux champs, dispatchProject délègue le GPU,
|
||||
dispatchProject garde Claude Code en backward-compat, needs_claude_code
|
||||
bypass, end-to-end inbox→submit→reap→done/.
|
||||
|
||||
### Tests effectués
|
||||
- `go build ./...` : ok
|
||||
- `go vet ./...` : clean
|
||||
- `go test -race ./...` : tous les packages passent (~10s)
|
||||
- `go mod tidy` : aucun changement nécessaire
|
||||
|
||||
### Ajouté — complément 2026-04-17 (bash wiring)
|
||||
- `cmd/ccl-delegate` — CLI Go tiny wrapper autour d'`internal/secutools`
|
||||
utilisable depuis bash. Sous-commandes `submit`, `get`, `result`,
|
||||
`decide`. Env `CCL_SECUTOOLS_API_KEY` (preferred) ou `SECUTOOLS_API_KEY`.
|
||||
`CCL_SECUTOOLS_URL` ou `CCL_SECUTOOLS_MOCK_URL` pour tests.
|
||||
- `internal/secutools/routing.go` — `DecideProvider(map[string]any) string`
|
||||
(adapter signature requis par le spec pour usage depuis CLI). Map-input
|
||||
permissif (bool|string|int coerced). Fail-safe retourne `"local"`.
|
||||
Table-driven tests couvrent 14 cas incluant coercions.
|
||||
- `internal/secutools/client.go` — retries exponential backoff (max 3
|
||||
retries, 500ms base, x2 chaque attempt). Retry sur 5xx + transport
|
||||
errors, PAS sur 4xx. `SetRetryPolicy(maxRetries, baseDelay)` exposé
|
||||
pour tests. Tests : `TestSubmitJob_RetriesOn5xx`,
|
||||
`TestSubmitJob_DoesNotRetry4xx`.
|
||||
- `tests/phase2-E-integration.sh` — test bout-en-bout bash côté
|
||||
dev-management avec mock HTTP secutools (Python stdlib).
|
||||
Scénarios : decide, delegate (marker + status.json +
|
||||
state=delegated_to_secutools), drive mock → completed, poll-reaper,
|
||||
assertions done/ body + footer, cleanup inbox, budget tracker,
|
||||
rejet tâche legacy (rc=2).
|
||||
|
||||
### Notes / décisions
|
||||
- Pas d'appel réseau réel à secutools dans les tests — fakeClient +
|
||||
httptest côté Go, serveur mock Python stdlib côté integration.
|
||||
- Le client secutools est branché sur `SECUTOOLS_API_KEY` env var pour
|
||||
garder un path par défaut "Phase 1 only".
|
||||
- Smoke test prod restera nécessaire (test E2E avec `preferred_ai: gpu`
|
||||
réel) — pas effectué dans ce chantier comme demandé (pas de push).
|
||||
- Décision : `DecideProvider(map[string]any)` vit dans `secutools`
|
||||
(adapter CLI, signature du spec) ET `router.Decide(Task)` vit dans
|
||||
`router` (struct-typed, utilisé par le dispatcher Go). Les deux partagent
|
||||
la même matrice de précédence — pas de duplication de logique car le
|
||||
router consomme `TaskFrontmatter` typé via yaml.Unmarshal, alors que
|
||||
la CLI parse manuellement pour rester zero-dep.
|
||||
|
||||
## [0.3.8] - 2026-04-16
|
||||
**Type:** Patch — Bug #1 (A3 flip+ensure inconsistency) + Bug #10 (requiredShared contract test)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue