feat: Phase 4 governance/execution slice — PolicyService + ExecutionService
classifyAndGate's decision pipeline moves to core: PolicyService runs the full gate order (classify + transport escalation, plan-first, syntax, host-only/host-lxc, VM QGA preflight, dedup, approval-flood, window routing) over ports.GovernanceStore; ExecutionService records and dispatches (auto-run via ssh.CommandExecutor + TargetResolver, queue via ExecutionRecorder) with one converged path for run/docker_exec. Gating matrix test added (risk x window x declared risk -> outcome); pair coverage 95.6%. Bug fix surfaced by the matrix: the flag-space syntax regex was inverted — it refused valid 'tail -n 3' and missed the actual 'head - n' typo. Fixed to match dash-space-value only. Remaining Phase 4 items tracked in the plan: ApprovalService.Decide convergence, execlog fold, execworker poller. VERSION 0.35.0.
This commit is contained in:
@@ -11,11 +11,14 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/dtoro/oikos/internal/config"
|
||||
"github.com/dtoro/oikos/internal/core/app"
|
||||
"github.com/dtoro/oikos/internal/core/ports"
|
||||
"github.com/dtoro/oikos/internal/adapters/postgres"
|
||||
"github.com/dtoro/oikos/internal/adapters/remote"
|
||||
intremote "github.com/dtoro/oikos/internal/remote"
|
||||
"github.com/dtoro/oikos/internal/adapters/ssh"
|
||||
"github.com/dtoro/oikos/internal/execworker"
|
||||
"github.com/dtoro/oikos/internal/httpapi"
|
||||
@@ -114,7 +117,7 @@ func main() {
|
||||
|
||||
slog.Info("all: starting api with scheduler + execution-worker in background")
|
||||
svc := buildAPIServices(pool)
|
||||
if err := httpapi.ListenAndServe(ctx, pool, cfg, svc.entities, svc.entityRepo, svc.readModels, svc.relService, svc.provisioning, svc.seeds); err != nil {
|
||||
if err := httpapi.ListenAndServe(ctx, pool, cfg, svc.entities, svc.entityRepo, svc.readModels, svc.relService, svc.provisioning, svc.seeds, svc.execSvc); err != nil {
|
||||
slog.Error("api failed", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -207,7 +210,7 @@ func runAPI(ctx context.Context, cfg config.Config) error {
|
||||
}
|
||||
|
||||
svc := buildAPIServices(pool)
|
||||
err = httpapi.ListenAndServe(ctx, pool, cfg, svc.entities, svc.entityRepo, svc.readModels, svc.relService, svc.provisioning, svc.seeds)
|
||||
err = httpapi.ListenAndServe(ctx, pool, cfg, svc.entities, svc.entityRepo, svc.readModels, svc.relService, svc.provisioning, svc.seeds, svc.execSvc)
|
||||
if err == http.ErrServerClosed {
|
||||
return nil
|
||||
}
|
||||
@@ -224,6 +227,7 @@ type apiServices struct {
|
||||
relService *app.RelationshipService
|
||||
provisioning *app.ProvisioningService
|
||||
seeds *app.SeedService
|
||||
execSvc *app.ExecutionService
|
||||
}
|
||||
|
||||
func buildAPIServices(pool *db.Pool) apiServices {
|
||||
@@ -231,15 +235,28 @@ func buildAPIServices(pool *db.Pool) apiServices {
|
||||
onto := db.NewOntologyRepo(pool, time.Minute)
|
||||
readModels := db.NewEntityReader(pool)
|
||||
entities := app.NewEntityService(entityRepo, onto)
|
||||
relService := app.NewRelationshipService(db.NewRelRepo(pool), onto)
|
||||
relRepo := db.NewRelRepo(pool)
|
||||
relService := app.NewRelationshipService(relRepo, onto)
|
||||
|
||||
executor := ssh.NewExecutor(ssh.FileSignerSource(), 5*time.Minute)
|
||||
provisioner := ssh.NewProvisioner(executor)
|
||||
resolver := remote.NewResolver(pool)
|
||||
provisioning := app.NewProvisioningService(provisioner, resolver, entityRepo, db.NewRelRepo(pool))
|
||||
provisioning := app.NewProvisioningService(provisioner, resolver, entityRepo, relRepo)
|
||||
|
||||
seeds := app.NewSeedService(db.NewSeedRepo(pool))
|
||||
|
||||
// Governance/execution slice (Phase 4): the MCP run/docker_exec tools
|
||||
// gate and dispatch through PolicyService + ExecutionService.
|
||||
policySvc := app.NewPolicyService(db.NewGovernanceRepo(pool))
|
||||
policySvc.HostHint = func(ctx context.Context, targetSlug string) string {
|
||||
var id uuid.UUID
|
||||
if err := pool.QueryRow(ctx, "SELECT id FROM entities WHERE slug = $1", targetSlug).Scan(&id); err != nil {
|
||||
id = uuid.Nil
|
||||
}
|
||||
return intremote.ResolveProxmoxHostSlug(ctx, pool, id, "")
|
||||
}
|
||||
execSvc := app.NewExecutionService(policySvc, executor, resolver, db.NewExecRunRepo(pool))
|
||||
|
||||
return apiServices{
|
||||
entities: entities,
|
||||
entityRepo: entityRepo,
|
||||
@@ -247,6 +264,7 @@ func buildAPIServices(pool *db.Pool) apiServices {
|
||||
relService: relService,
|
||||
provisioning: provisioning,
|
||||
seeds: seeds,
|
||||
execSvc: execSvc,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user