Watch
1
0
Fork
You've already forked RedFlag
0
RedFlag/server/internal/orchestrator/interfaces.go
Fimeg 62f2764260 v0.2.5.1: lifecycle, live operations, unified history
Lifecycle:
- ReopenUpdate + ResolveUpdate replace RetryUpdate; routes for
  GET /updates/:id/lifecycle and POST reopen/resolve
- confirmUpdateCommand marks update_agent completed on version attestation
- migration 048: started/running added to update_logs.result
- helper atomic_replace_binary: copy-to-sibling then rename() (ETXTBSY)

Live operations:
- event_stream service + /events endpoints, LiveOperations page
- capability-token queries for the live view

History + CVE:
- History page reads /events/recent: filterable lifecycle/command/
  system/orchestrator timeline with agent crosslinks
- CVE drill-down: OSV parse carries CVSS vector, fixed version, published
  date, severity; issuer-linked aliases (CVE->NVD, GHSA->GitHub, ALSA->errata)
- DependencyClosureTree: one shared closure component in update detail
- STARTED (blue spinner) and PARTIAL (amber) result badges
2026-06-05 09:13:42 -04:00

82 lines
3.6 KiB
Go

// Package orchestrator advances the package-update lifecycle server-side. It
// auto-approves packages by operator policy and recovers packages stuck in
// active states, holding no state of its own — every decision is read from the
// database on each sweep, so a server restart loses nothing and the next tick
// reconciles. The DB-level guarded UPDATE from LIFECYCLE-001 makes every
// transition idempotent, so a redundant advance is a no-op.
//
// The dependencies below are declared as consumer interfaces so the package is
// unit-testable with fakes and imports neither the queries nor the handlers
// layer. The concrete wiring lives in cmd/server/main.go.
package orchestrator
import (
"time"
"github.com/Fimeg/RedFlag/server/internal/models"
"github.com/gofrs/uuid/v5"
)
// Store is the persistence surface the orchestrator drives. Satisfied by
// *queries.UpdateQueries.
type Store interface {
GetUpdateByID(id uuid.UUID) (*models.UpdateState, error)
GetPackagesInStatus(status models.PackageStatus) ([]models.UpdateState, error)
TransitionByID(id uuid.UUID, to models.PackageStatus, historyMeta models.JSONB) error
BumpRetryCounter(id uuid.UUID, key string) (int, error)
}
// DryRunEnqueuer creates and signs the dry_run_update command that moves an
// approved package into checking_dependencies. Satisfied by *handlers.UpdateHandler.
type DryRunEnqueuer interface {
EnqueueDryRun(update *models.UpdateState) error
}
// DependencyConfirmer performs the capability-path confirm step headlessly: it
// transitions a package waiting in pending_dependencies into installing and
// mints its capability token — the equivalent of an operator's
// ConfirmDependencies dashboard click. Satisfied by *handlers.UpdateHandler.
//
// It returns handled=false when the package is not capability-gated (legacy
// command path: docker/winget/Windows). Those are left for the operator, since
// the server cannot verify a legacy dependency was satisfied. An error means a
// genuine failure (the confirmer has already marked the package failed); the
// orchestrator only logs it. The caller (orchestrator) has already checked the
// maintenance window and auto-approval policy before invoking.
type DependencyConfirmer interface {
ConfirmDependenciesAuto(update *models.UpdateState) (handled bool, err error)
}
// MaintenanceWindow reports whether installs are currently permitted. The
// orchestrator consults it before auto-confirming a dependency closure: dry-run
// is read-only and safe anytime, but advancing to installing must respect the
// operator's window (design Q1). Satisfied by *queries.MaintenanceWindowQueries.
type MaintenanceWindow interface {
IsWithinMaintenanceWindow(now time.Time) (bool, error)
}
// Settings supplies live, operator-tunable policy and timing values. Satisfied
// by *services.SecuritySettingsService. A nil/missing value falls back to the
// supplied default, so a misconfigured deployment degrades to the safe default
// (auto-approval off) rather than failing.
type Settings interface {
GetPolicyString(key, fallback string) string
GetPolicyBool(key string, fallback bool) bool
GetOperationalInt(key string, fallback int) int
}
// EventLogger persists server-side system events (orchestrator timeouts,
// workflow state changes) to the system_events table. Satisfied by
// *services.SystemEventLogger.
type EventLogger interface {
LogEvent(agentID *uuid.UUID, eventType, eventSubtype, severity, component, message string, metadata map[string]interface{})
}
// Clock is injected so timeout logic is deterministic under test.
type Clock interface {
Now() time.Time
}
type systemClock struct{}
func (systemClock) Now() time.Time { return time.Now().UTC() }