mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-04-28 13:56:21 +02:00
184 lines
4.4 KiB
Go
184 lines
4.4 KiB
Go
package soc
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// ZeroGMode implements §13.4 — manual approval workflow for Strike Force operations.
|
|
// Events in Zero-G mode require explicit analyst approval before auto-response executes.
|
|
type ZeroGMode struct {
|
|
mu sync.RWMutex
|
|
enabled bool
|
|
queue []ZeroGRequest
|
|
resolved []ZeroGRequest
|
|
maxQueue int
|
|
}
|
|
|
|
// ZeroGRequest represents a pending approval request.
|
|
type ZeroGRequest struct {
|
|
ID string `json:"id"`
|
|
EventID string `json:"event_id"`
|
|
IncidentID string `json:"incident_id,omitempty"`
|
|
Action string `json:"action"` // What would auto-execute
|
|
Severity string `json:"severity"`
|
|
Description string `json:"description"`
|
|
Status ZeroGStatus `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ResolvedAt *time.Time `json:"resolved_at,omitempty"`
|
|
ResolvedBy string `json:"resolved_by,omitempty"`
|
|
Verdict ZeroGVerdict `json:"verdict,omitempty"`
|
|
}
|
|
|
|
// ZeroGStatus tracks the request lifecycle.
|
|
type ZeroGStatus string
|
|
|
|
const (
|
|
ZeroGPending ZeroGStatus = "PENDING"
|
|
ZeroGApproved ZeroGStatus = "APPROVED"
|
|
ZeroGDenied ZeroGStatus = "DENIED"
|
|
ZeroGExpired ZeroGStatus = "EXPIRED"
|
|
)
|
|
|
|
// ZeroGVerdict is the analyst's decision.
|
|
type ZeroGVerdict string
|
|
|
|
const (
|
|
ZGVerdictApprove ZeroGVerdict = "APPROVE"
|
|
ZGVerdictDeny ZeroGVerdict = "DENY"
|
|
ZGVerdictEscalate ZeroGVerdict = "ESCALATE"
|
|
)
|
|
|
|
// NewZeroGMode creates the Zero-G approval engine.
|
|
func NewZeroGMode() *ZeroGMode {
|
|
return &ZeroGMode{
|
|
enabled: false,
|
|
maxQueue: 200,
|
|
}
|
|
}
|
|
|
|
// Enable activates Zero-G mode (manual approval required).
|
|
func (z *ZeroGMode) Enable() {
|
|
z.mu.Lock()
|
|
defer z.mu.Unlock()
|
|
z.enabled = true
|
|
}
|
|
|
|
// Disable deactivates Zero-G mode (auto-response resumes).
|
|
func (z *ZeroGMode) Disable() {
|
|
z.mu.Lock()
|
|
defer z.mu.Unlock()
|
|
z.enabled = false
|
|
}
|
|
|
|
// IsEnabled returns whether Zero-G mode is active.
|
|
func (z *ZeroGMode) IsEnabled() bool {
|
|
z.mu.RLock()
|
|
defer z.mu.RUnlock()
|
|
return z.enabled
|
|
}
|
|
|
|
// RequestApproval queues an action for manual approval. Returns the request ID.
|
|
func (z *ZeroGMode) RequestApproval(eventID, incidentID, action, severity, description string) string {
|
|
z.mu.Lock()
|
|
defer z.mu.Unlock()
|
|
|
|
if !z.enabled {
|
|
return "" // Not in Zero-G mode, skip
|
|
}
|
|
|
|
reqID := fmt.Sprintf("zg-%d", time.Now().UnixNano())
|
|
req := ZeroGRequest{
|
|
ID: reqID,
|
|
EventID: eventID,
|
|
IncidentID: incidentID,
|
|
Action: action,
|
|
Severity: severity,
|
|
Description: description,
|
|
Status: ZeroGPending,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
// Enforce max queue size
|
|
if len(z.queue) >= z.maxQueue {
|
|
// Expire oldest
|
|
expired := z.queue[0]
|
|
expired.Status = ZeroGExpired
|
|
now := time.Now()
|
|
expired.ResolvedAt = &now
|
|
z.resolved = append(z.resolved, expired)
|
|
z.queue = z.queue[1:]
|
|
}
|
|
|
|
z.queue = append(z.queue, req)
|
|
return reqID
|
|
}
|
|
|
|
// Resolve processes an analyst's verdict on a pending request.
|
|
func (z *ZeroGMode) Resolve(requestID string, verdict ZeroGVerdict, analyst string) error {
|
|
z.mu.Lock()
|
|
defer z.mu.Unlock()
|
|
|
|
for i, req := range z.queue {
|
|
if req.ID == requestID {
|
|
now := time.Now()
|
|
z.queue[i].ResolvedAt = &now
|
|
z.queue[i].ResolvedBy = analyst
|
|
z.queue[i].Verdict = verdict
|
|
|
|
switch verdict {
|
|
case ZGVerdictApprove:
|
|
z.queue[i].Status = ZeroGApproved
|
|
case ZGVerdictDeny:
|
|
z.queue[i].Status = ZeroGDenied
|
|
case ZGVerdictEscalate:
|
|
z.queue[i].Status = ZeroGPending // Stay pending, but mark escalated
|
|
}
|
|
|
|
// Move to resolved
|
|
z.resolved = append(z.resolved, z.queue[i])
|
|
z.queue = append(z.queue[:i], z.queue[i+1:]...)
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("zero-g request %s not found", requestID)
|
|
}
|
|
|
|
// PendingRequests returns all pending approval requests.
|
|
func (z *ZeroGMode) PendingRequests() []ZeroGRequest {
|
|
z.mu.RLock()
|
|
defer z.mu.RUnlock()
|
|
result := make([]ZeroGRequest, len(z.queue))
|
|
copy(result, z.queue)
|
|
return result
|
|
}
|
|
|
|
// Stats returns Zero-G mode statistics.
|
|
func (z *ZeroGMode) Stats() map[string]any {
|
|
z.mu.RLock()
|
|
defer z.mu.RUnlock()
|
|
|
|
approved := 0
|
|
denied := 0
|
|
expired := 0
|
|
for _, r := range z.resolved {
|
|
switch r.Status {
|
|
case ZeroGApproved:
|
|
approved++
|
|
case ZeroGDenied:
|
|
denied++
|
|
case ZeroGExpired:
|
|
expired++
|
|
}
|
|
}
|
|
|
|
return map[string]any{
|
|
"enabled": z.enabled,
|
|
"pending": len(z.queue),
|
|
"total_resolved": len(z.resolved),
|
|
"approved": approved,
|
|
"denied": denied,
|
|
"expired": expired,
|
|
}
|
|
}
|