Remove pre-1.0 production governance surfaces
This commit is contained in:
@@ -114,9 +114,9 @@ func (svc *CoreService) InvokeAIForSession(sessionID string, request domain.AIIn
|
||||
if request.ServerInstanceID == "" {
|
||||
return domain.AIInvocationResponse{}, validationError("serverInstanceId is required for AI config recommendations")
|
||||
}
|
||||
svc.productionMu.Lock()
|
||||
svc.pluginOperationsMu.Lock()
|
||||
preview, persistErr := svc.persistAIConfigDiff(user.ID, provider, request, result)
|
||||
svc.productionMu.Unlock()
|
||||
svc.pluginOperationsMu.Unlock()
|
||||
if persistErr != nil {
|
||||
return domain.AIInvocationResponse{}, persistErr
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ func TestCoreServiceDoesNotPrebindLegacyRunBeforeDistributionBuild(t *testing.T)
|
||||
|
||||
func TestCoreServiceDistributionBuildIgnoresStaleRunEndpoint(t *testing.T) {
|
||||
svc, session, instance := newDistributionTestFixture(t)
|
||||
svc.now = func() time.Time { return fixedTime.Add(capacityHeartbeatStaleAfter + time.Second) }
|
||||
svc.now = func() time.Time { return fixedTime.Add(runHeartbeatStaleAfter + time.Second) }
|
||||
|
||||
distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
|
||||
@@ -284,7 +284,7 @@ func (svc *CoreService) CompleteRunJob(result domain.RunJobResult) (domain.RunJo
|
||||
if err := svc.projectClientManagerLifecycleResult(job, stamp); err != nil {
|
||||
return domain.RunJobResultResult{}, err
|
||||
}
|
||||
if err := svc.projectProductionOpsJobResult(job, stamp); err != nil {
|
||||
if err := svc.projectPluginOperationsJobResult(job, stamp); err != nil {
|
||||
return domain.RunJobResultResult{}, err
|
||||
}
|
||||
return domain.RunJobResultResult{Accepted: true, Job: assignmentFromJob(job, result.LeaseToken), ServerTime: stamp}, nil
|
||||
|
||||
@@ -70,8 +70,8 @@ func (svc *CoreService) RunPluginLifecycleForSession(sessionID string, request d
|
||||
return svc.pluginLifecycleDenied(user.ID, instance, plugin, request, "plugin is not compatible with the assigned endpoint platform")
|
||||
}
|
||||
|
||||
svc.productionMu.Lock()
|
||||
defer svc.productionMu.Unlock()
|
||||
svc.pluginOperationsMu.Lock()
|
||||
defer svc.pluginOperationsMu.Unlock()
|
||||
installationID := pluginLifecycleInstallationID(request.PluginID, request.ServerInstanceID)
|
||||
installation, getErr := svc.store.PluginLifecycles().Get(installationID)
|
||||
if errors.Is(getErr, repo.ErrNotFound) {
|
||||
@@ -156,8 +156,8 @@ func (svc *CoreService) ApproveAIConfigDiffForSession(sessionID string, request
|
||||
if err != nil {
|
||||
return domain.AIConfigDiffApprovalResult{}, err
|
||||
}
|
||||
svc.productionMu.Lock()
|
||||
defer svc.productionMu.Unlock()
|
||||
svc.pluginOperationsMu.Lock()
|
||||
defer svc.pluginOperationsMu.Unlock()
|
||||
preview, err := svc.store.AIConfigDiffs().Get(request.DiffID)
|
||||
if err != nil {
|
||||
return domain.AIConfigDiffApprovalResult{}, err
|
||||
@@ -206,12 +206,12 @@ func (svc *CoreService) ApproveAIConfigDiffForSession(sessionID string, request
|
||||
return domain.CopyAIConfigDiffApprovalResult(domain.AIConfigDiffApprovalResult{Preview: preview, Dispatch: dispatch}), nil
|
||||
}
|
||||
|
||||
func (svc *CoreService) projectProductionOpsJobResult(job domain.Job, stamp time.Time) error {
|
||||
func (svc *CoreService) projectPluginOperationsJobResult(job domain.Job, stamp time.Time) error {
|
||||
if !strings.HasPrefix(job.ID, "job-plugin-lifecycle-") || job.ExecutionInput.LifecycleOperation == "" || job.ExecutionInput.PluginID == "" {
|
||||
return nil
|
||||
}
|
||||
svc.productionMu.Lock()
|
||||
defer svc.productionMu.Unlock()
|
||||
svc.pluginOperationsMu.Lock()
|
||||
defer svc.pluginOperationsMu.Unlock()
|
||||
installation, err := svc.store.PluginLifecycles().Get(pluginLifecycleInstallationID(job.ExecutionInput.PluginID, job.ServerInstanceID))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -288,8 +288,8 @@ func (svc *CoreService) getServerConfigForUser(userID, serverInstanceID string)
|
||||
}
|
||||
|
||||
func (svc *CoreService) pluginLifecycleDenied(actorID string, instance domain.ServerInstance, plugin domain.GamePlugin, request domain.PluginLifecycleRequest, reason string) (domain.PluginLifecycleResult, error) {
|
||||
svc.productionMu.Lock()
|
||||
defer svc.productionMu.Unlock()
|
||||
svc.pluginOperationsMu.Lock()
|
||||
defer svc.pluginOperationsMu.Unlock()
|
||||
stamp := svc.now()
|
||||
installation := domain.PluginLifecycleInstallation{ID: pluginLifecycleInstallationID(plugin.ID, instance.ID), PluginID: plugin.ID, ServerInstanceID: instance.ID, TargetVersion: request.TargetVersion, DesiredState: domain.PluginLifecycleStatePending, CurrentState: domain.PluginLifecycleStateFailed, LastOperation: request.Operation, Compatibility: "incompatible", DependencyState: domain.DependencyStateUnknown, FailureReason: safeBridgeReason(reason), IdempotencyKey: request.IdempotencyKey, CreatedAt: stamp, UpdatedAt: stamp}
|
||||
if existing, err := svc.store.PluginLifecycles().Get(installation.ID); err == nil {
|
||||
@@ -308,13 +308,15 @@ func (svc *CoreService) pluginLifecycleDeniedLocked(actorID string, installation
|
||||
if err := validator.ValidatePluginLifecycleInstallation(installation); err != nil {
|
||||
return domain.PluginLifecycleResult{}, err
|
||||
}
|
||||
if _, err := svc.store.PluginLifecycles().Get(installation.ID); errors.Is(err, repo.ErrNotFound) {
|
||||
err = svc.store.PluginLifecycles().Create(installation)
|
||||
} else if err == nil {
|
||||
err = svc.store.PluginLifecycles().Update(installation)
|
||||
_, getErr := svc.store.PluginLifecycles().Get(installation.ID)
|
||||
switch {
|
||||
case errors.Is(getErr, repo.ErrNotFound):
|
||||
getErr = svc.store.PluginLifecycles().Create(installation)
|
||||
case getErr == nil:
|
||||
getErr = svc.store.PluginLifecycles().Update(installation)
|
||||
}
|
||||
if err != nil {
|
||||
return domain.PluginLifecycleResult{}, err
|
||||
if getErr != nil {
|
||||
return domain.PluginLifecycleResult{}, getErr
|
||||
}
|
||||
return domain.CopyPluginLifecycleResult(domain.PluginLifecycleResult{Installation: installation, Status: "denied"}), nil
|
||||
}
|
||||
+4
-4
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPluginLifecycleDispatchIsIdempotentAndRejectsInputDrift(t *testing.T) {
|
||||
svc, session, instance := newProductionOpsFixture(t)
|
||||
svc, session, instance := newPluginOperationsFixture(t)
|
||||
request := domain.PluginLifecycleRequest{PluginID: instance.PluginID, ServerInstanceID: instance.ID, Operation: domain.PluginLifecycleOperationInstall, TargetVersion: "1.0.0", IdempotencyKey: "plugin-install-v1"}
|
||||
first, err := svc.RunPluginLifecycleForSession(session, request)
|
||||
if err != nil {
|
||||
@@ -35,7 +35,7 @@ func TestPluginLifecycleDispatchIsIdempotentAndRejectsInputDrift(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPluginLifecycleBridgeDispatchesBoundedJob(t *testing.T) {
|
||||
svc, session, instance := newProductionOpsFixture(t)
|
||||
svc, session, instance := newPluginOperationsFixture(t)
|
||||
plugin, err := svc.store.GamePlugins().Get(instance.PluginID)
|
||||
if err != nil {
|
||||
t.Fatalf("get plugin: %v", err)
|
||||
@@ -73,7 +73,7 @@ func TestPluginLifecycleBridgeDispatchesBoundedJob(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAIConfigRecommendationRequiresApprovalAndRejectsStaleRevision(t *testing.T) {
|
||||
svc, session, instance := newProductionOpsFixture(t)
|
||||
svc, session, instance := newPluginOperationsFixture(t)
|
||||
provider, err := svc.CreateAIProvider(domain.AIProvider{ID: "ai-local", Name: "Local AI", Kind: domain.AIProviderKindOllama, BaseURL: "http://127.0.0.1:11434/v1", Models: []string{"test-model"}, DefaultModel: "test-model", RelayMode: domain.AIRelayModeLocal, TimeoutMS: 1000, Status: domain.AIProviderStatusActive, RedactionPolicy: "strict"})
|
||||
if err != nil {
|
||||
t.Fatalf("create provider: %v", err)
|
||||
@@ -122,7 +122,7 @@ func TestAIConfigRecommendationRequiresApprovalAndRejectsStaleRevision(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func newProductionOpsFixture(t *testing.T) (*CoreService, string, domain.ServerInstance) {
|
||||
func newPluginOperationsFixture(t *testing.T) (*CoreService, string, domain.ServerInstance) {
|
||||
t.Helper()
|
||||
svc := newTestCoreService()
|
||||
plugin, endpoint := createPluginAndRunEndpoint(t, svc)
|
||||
@@ -23,7 +23,10 @@ var (
|
||||
ErrForbidden = errors.New("forbidden")
|
||||
)
|
||||
|
||||
const ServerDeletionForceConfirmation = "FORCE DELETE"
|
||||
const (
|
||||
ServerDeletionForceConfirmation = "FORCE DELETE"
|
||||
runHeartbeatStaleAfter = 2 * time.Minute
|
||||
)
|
||||
|
||||
type ForbiddenError struct {
|
||||
Reason string
|
||||
@@ -234,7 +237,7 @@ type CoreService struct {
|
||||
artifactTransfers map[string]domain.ArtifactTransferSession
|
||||
artifactPayloads map[string][]byte
|
||||
artifactTransferSeq uint64
|
||||
productionMu sync.Mutex
|
||||
pluginOperationsMu sync.Mutex
|
||||
sourceRCONCommands *sourceRCONCommandBroker
|
||||
aiProviderClient AIProviderClient
|
||||
secretEnvelope SecretEnvelope
|
||||
@@ -2654,7 +2657,14 @@ func (svc *CoreService) runEndpointHeartbeatCurrent(endpoint domain.RunEndpoint)
|
||||
if endpoint.LastHeartbeatAt.IsZero() {
|
||||
return false
|
||||
}
|
||||
return !svc.now().After(endpoint.LastHeartbeatAt.Add(capacityHeartbeatStaleAfter))
|
||||
return !svc.now().After(endpoint.LastHeartbeatAt.Add(runHeartbeatStaleAfter))
|
||||
}
|
||||
|
||||
func maxInt(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func validateJobServerTarget(job domain.Job, instance domain.ServerInstance, plugin domain.GamePlugin) error {
|
||||
|
||||
Reference in New Issue
Block a user