From 0482e6ac14cd7625598a203dbdec9298b1bac04c Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 21 Aug 2026 15:49:57 +0300 Subject: [PATCH] feat: authorize cognitive canary activation --- adapters/postgres/capability_repository.go | 26 +++++++++++++ cmd/core-runtime/main.go | 39 +++++++++++++++++++ contracts/capability/cognitive_registry.go | 17 ++++++++ .../DEV-000083-domovoy-lifeboat-mode.json | 7 +++- .../capability/domain/cognitive_registry.go | 16 ++++++++ .../domain/cognitive_registry_test.go | 19 +++++++++ modules/capability/ports/repository.go | 1 + 7 files changed, 123 insertions(+), 2 deletions(-) diff --git a/adapters/postgres/capability_repository.go b/adapters/postgres/capability_repository.go index 99d72e8..7143cd7 100644 --- a/adapters/postgres/capability_repository.go +++ b/adapters/postgres/capability_repository.go @@ -479,6 +479,32 @@ func (r *CapabilityRepository) SaveCognitiveCanaryPromotion(ctx context.Context, return tx.Commit(ctx) } +func (r *CapabilityRepository) SaveCognitiveActivationPromotion(ctx context.Context, value capability.CognitiveActivationPromotion) error { + tx, err := r.pool.Begin(ctx) + if err != nil { + return err + } + defer func() { _ = tx.Rollback(ctx) }() + b, e := value.Binding, value.Evaluation + if _, err = tx.Exec(ctx, `INSERT INTO capability.cognitive_bindings (binding_id,revision,capability_id,owner_principal_id,provider_instance_id,provider_revision,model_descriptor_id,model_revision,data_policy,qualification,activation,health,limit_policy_ref) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) ON CONFLICT DO NOTHING`, b.ID, b.Revision, b.CapabilityID, b.OwnerPrincipalID, b.ProviderInstanceID, b.ProviderRevision, b.ModelDescriptorID, b.ModelRevision, b.DataPolicy, b.Qualification, b.Activation, b.Health, b.LimitPolicyRef); err != nil { + return err + } + if _, err = tx.Exec(ctx, `INSERT INTO capability.cognitive_evaluated_capabilities (evaluation_id,binding_id,binding_revision,capability_id,result,quality_score_millis,critical_failure_millis,benchmark_version,artifact_id,observed_at_unix_millis) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) ON CONFLICT DO NOTHING`, e.ID, e.BindingID, e.BindingRevision, e.CapabilityID, e.Result, e.QualityScoreMillis, e.CriticalFailureMillis, e.BenchmarkVersion, e.ArtifactID, e.ObservedAtUnixMillis); err != nil { + return err + } + if _, err = tx.Exec(ctx, `UPDATE capability.cognitive_bindings SET activation='DEPRECATED' WHERE capability_id=$1 AND owner_principal_id=$2 AND activation='ACTIVE' AND NOT (binding_id=$3 AND revision=$4)`, b.CapabilityID, b.OwnerPrincipalID, b.ID, b.Revision); err != nil { + return err + } + result, err := tx.Exec(ctx, `UPDATE capability.cognitive_binding_revision_heads SET revision=$1,updated_at=CURRENT_TIMESTAMP WHERE binding_id=$2 AND revision=$3`, b.Revision, b.ID, value.ExpectedBindingRevision) + if err != nil { + return err + } + if result.RowsAffected() != 1 { + return ErrCapabilityBindingConflict + } + return tx.Commit(ctx) +} + func (r *CapabilityRepository) SavePerformanceEvidence(ctx context.Context, value capability.PerformanceEvidence) error { _, err := r.pool.Exec(ctx, `INSERT INTO capability.cognitive_performance_evidence (evidence_id,binding_id,binding_revision,model_descriptor_id,model_revision,hardware_profile,runtime,quantization,context_units,thinking_enabled,generation_tps_millis,prompt_tps_millis,ttft_micros,latency_micros,memory_bytes,reliability_millis,benchmark_version,artifact_id,observed_at_unix_millis) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19) ON CONFLICT DO NOTHING`, value.ID, value.BindingID, value.BindingRevision, value.ModelDescriptorID, value.ModelRevision, value.HardwareProfile, value.Runtime, value.Quantization, value.ContextUnits, value.ThinkingEnabled, value.GenerationTPSMillis, value.PromptTPSMillis, value.TTFTMicros, value.LatencyMicros, value.MemoryBytes, value.ReliabilityMillis, value.BenchmarkVersion, value.ArtifactID, value.ObservedAtUnixMillis) return err diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index 4e12960..726681a 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -888,6 +888,45 @@ func main() { w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(promotion) }) + mux.HandleFunc("POST /v1/creator/cognitive-activate", func(w http.ResponseWriter, r *http.Request) { + session, ok := authenticateCreator(w, r) + if !ok { + return + } + var request capability.CognitiveActivationPromotionRequest + decoder := json.NewDecoder(io.LimitReader(r.Body, 16<<10)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&request); err != nil { + http.Error(w, "invalid cognitive activation", http.StatusBadRequest) + return + } + request.RequestedByPrincipalID = session.PrincipalID + request.CapabilityID = "creator-advisory" + candidates, err := capabilityRepository.LoadCognitiveCandidates(r.Context(), request.CapabilityID, session.PrincipalID) + if err != nil { + http.Error(w, "cognitive registry unavailable", http.StatusServiceUnavailable) + return + } + var current capability.ResolvedCognitiveBinding + for _, candidate := range candidates { + if candidate.Binding.ID == request.BindingID && candidate.Binding.Revision == request.ExpectedBindingRevision { + current = candidate + break + } + } + promotion, err := capabilitydomain.BuildActivationPromotion(request, current) + if err != nil { + http.Error(w, "cognitive activation precondition failed", http.StatusConflict) + return + } + if err = capabilityRepository.SaveCognitiveActivationPromotion(r.Context(), promotion); err != nil { + http.Error(w, "cognitive activation conflict", http.StatusConflict) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + _ = json.NewEncoder(w).Encode(promotion) + }) mux.HandleFunc("POST /v1/cognitive-registry/performance-evidence", func(w http.ResponseWriter, r *http.Request) { var value capability.PerformanceEvidence decoder := json.NewDecoder(io.LimitReader(r.Body, 64<<10)) diff --git a/contracts/capability/cognitive_registry.go b/contracts/capability/cognitive_registry.go index 64e34a4..4495a73 100644 --- a/contracts/capability/cognitive_registry.go +++ b/contracts/capability/cognitive_registry.go @@ -206,6 +206,23 @@ type CognitiveCanaryPromotion struct { Evaluation EvaluatedCapability } +type CognitiveActivationPromotionRequest struct { + PromotionID ids.ID + RequestedByPrincipalID identity.PrincipalID + CapabilityID CapabilityID + BindingID CapabilityBindingID + ExpectedBindingRevision int64 + EvaluationID EvaluationID + ObservedAtUnixMillis int64 +} + +type CognitiveActivationPromotion struct { + PromotionID ids.ID + ExpectedBindingRevision int64 + Binding CognitiveBinding + Evaluation EvaluatedCapability +} + // CognitiveDiscoverySnapshot is provider-neutral observed inventory submitted // to the Capability owner. The owner assigns immutable revisions and always // registers discovered bindings as DISCOVERED/INACTIVE. diff --git a/dev/tasks/DEV-000083-domovoy-lifeboat-mode.json b/dev/tasks/DEV-000083-domovoy-lifeboat-mode.json index 3fd4b2c..e3b1f01 100644 --- a/dev/tasks/DEV-000083-domovoy-lifeboat-mode.json +++ b/dev/tasks/DEV-000083-domovoy-lifeboat-mode.json @@ -1,6 +1,6 @@ { "task_id": "DEV-000083", - "task_revision": 2, + "task_revision": 3, "architecture_revision": "DEV-000073-baseline", "base_repo_revision": "0a2599cfa6255f1a98856d2fd09dc51a2119f525", "risk_tier": "C", @@ -20,10 +20,13 @@ "cmd/cognitive-worker/main_test.go", "modules/capability/domain/cognitive_registry.go", "modules/capability/domain/cognitive_registry_test.go", + "modules/capability/ports/repository.go", + "contracts/capability/cognitive_registry.go", "cmd/creator-web/static/assets/chat.js", "cmd/creator-web/static/chat.html", "cmd/creator-web/chat_test.go", "adapters/postgres/capability_repository.go", + "tests/cognitive_canary/promotion_test.go", "deploy/docker-compose.prod.yml", "deploy/docker-compose.dev.yml", "dev/tasks/DEV-000083-lifeboat-benchmark-manifest.json", @@ -44,7 +47,7 @@ }, "contracts": { "consume": ["existing Cognitive Gateway and Capability Registry", "existing chat response fields", "existing cognitive health endpoint"], - "may_change": [] + "may_change": ["authorized cognitive activation transition"] }, "semantic_owner": "capability-observation-consumer", "canonical_state": [], diff --git a/modules/capability/domain/cognitive_registry.go b/modules/capability/domain/cognitive_registry.go index d8e6d66..3fbdf1a 100644 --- a/modules/capability/domain/cognitive_registry.go +++ b/modules/capability/domain/cognitive_registry.go @@ -62,6 +62,22 @@ func BuildCanaryPromotion(request capability.CognitiveCanaryPromotionRequest, cu return capability.CognitiveCanaryPromotion{PromotionID: request.PromotionID, ExpectedBindingRevision: request.ExpectedBindingRevision, Provider: provider, Model: model, Binding: binding, Evaluation: evaluation}, nil } +func BuildActivationPromotion(request capability.CognitiveActivationPromotionRequest, current capability.ResolvedCognitiveBinding) (capability.CognitiveActivationPromotion, error) { + if request.PromotionID == "" || request.RequestedByPrincipalID == "" || request.CapabilityID == "" || request.BindingID == "" || request.ExpectedBindingRevision < 1 || request.EvaluationID == "" || request.ObservedAtUnixMillis < 1 || current.Evaluation == nil { + return capability.CognitiveActivationPromotion{}, ErrInvalidCognitiveRegistryRecord + } + if current.Binding.ID != request.BindingID || current.Binding.Revision != request.ExpectedBindingRevision || current.Binding.OwnerPrincipalID != request.RequestedByPrincipalID || current.Binding.CapabilityID != request.CapabilityID || current.Evaluation.ID != request.EvaluationID || current.Evaluation.Result != capability.EvaluationSupported || current.Evaluation.QualityScoreMillis == nil || *current.Evaluation.QualityScoreMillis < 750 || current.Evaluation.CriticalFailureMillis == nil || *current.Evaluation.CriticalFailureMillis != 0 || current.Provider.Health != capability.HealthAvailable || current.Model.Health != capability.HealthAvailable || current.Binding.Qualification != capability.QualificationValidated || current.Binding.Activation != capability.ActivationCanary || current.Binding.Health != capability.HealthAvailable { + return capability.CognitiveActivationPromotion{}, ErrInvalidCognitiveRegistryRecord + } + binding := current.Binding + binding.Revision = stableRevision(fmt.Sprint(binding.Revision), string(request.PromotionID), string(capability.ActivationActive)) + binding.Qualification, binding.Activation, binding.Health = capability.QualificationValidated, capability.ActivationActive, capability.HealthAvailable + evaluation := *current.Evaluation + evaluation.ID = capability.EvaluationID(stableID("evaluation", string(request.PromotionID), string(binding.ID), fmt.Sprint(binding.Revision))) + evaluation.BindingRevision = binding.Revision + return capability.CognitiveActivationPromotion{PromotionID: request.PromotionID, ExpectedBindingRevision: request.ExpectedBindingRevision, Binding: binding, Evaluation: evaluation}, nil +} + func stableID(kind string, parts ...string) string { hash := sha256.New() _, _ = hash.Write([]byte(kind)) diff --git a/modules/capability/domain/cognitive_registry_test.go b/modules/capability/domain/cognitive_registry_test.go index 89d3a90..624c727 100644 --- a/modules/capability/domain/cognitive_registry_test.go +++ b/modules/capability/domain/cognitive_registry_test.go @@ -38,3 +38,22 @@ func TestBuildDiscoveryRegistrationCarriesObservedHealthToCanaryPrecondition(t * t.Fatalf("discovery health was not carried into canary candidates: model=%s binding=%s", registration.Models[0].Health, registration.Bindings[0].Health) } } + +func TestBuildActivationPromotionRequiresHealthyCanary(t *testing.T) { + quality := int64(750) + critical := int64(0) + current := capability.ResolvedCognitiveBinding{ + Binding: capability.CognitiveBinding{ID: "binding-1", Revision: 2, CapabilityID: "creator-advisory", OwnerPrincipalID: "owner", ProviderInstanceID: "provider-1", ProviderRevision: 3, ModelDescriptorID: "model-1", ModelRevision: 4, DataPolicy: capability.DataPolicyLocalOnly, Qualification: capability.QualificationValidated, Activation: capability.ActivationCanary, Health: capability.HealthAvailable, LimitPolicyRef: "limits"}, + Provider: capability.ProviderInstance{ID: "provider-1", Revision: 3, Health: capability.HealthAvailable}, + Model: capability.ModelDescriptor{ID: "model-1", Revision: 4, ProviderInstanceID: "provider-1", Health: capability.HealthAvailable}, + Evaluation: &capability.EvaluatedCapability{ID: "eval-1", Result: capability.EvaluationSupported, QualityScoreMillis: &quality, CriticalFailureMillis: &critical}, + } + promotion, err := BuildActivationPromotion(capability.CognitiveActivationPromotionRequest{PromotionID: "promotion-1", RequestedByPrincipalID: "owner", CapabilityID: "creator-advisory", BindingID: "binding-1", ExpectedBindingRevision: 2, EvaluationID: "eval-1", ObservedAtUnixMillis: 3}, current) + if err != nil || promotion.Binding.Activation != capability.ActivationActive || promotion.Binding.Revision == current.Binding.Revision { + t.Fatalf("promotion=%+v err=%v", promotion, err) + } + current.Binding.Activation = capability.ActivationInactive + if _, err = BuildActivationPromotion(capability.CognitiveActivationPromotionRequest{PromotionID: "promotion-2", RequestedByPrincipalID: "owner", CapabilityID: "creator-advisory", BindingID: "binding-1", ExpectedBindingRevision: 2, EvaluationID: "eval-1", ObservedAtUnixMillis: 3}, current); err == nil { + t.Fatal("expected inactive binding to be rejected") + } +} diff --git a/modules/capability/ports/repository.go b/modules/capability/ports/repository.go index 7f2cca2..02f0e24 100644 --- a/modules/capability/ports/repository.go +++ b/modules/capability/ports/repository.go @@ -20,6 +20,7 @@ type Repository interface { LoadCognitiveCandidates(context.Context, capability.CapabilityID, identity.PrincipalID) ([]capability.ResolvedCognitiveBinding, error) LoadCognitiveCandidate(context.Context, capability.CapabilityBindingID, int64) (capability.ResolvedCognitiveBinding, error) SaveCognitiveCanaryPromotion(context.Context, capability.CognitiveCanaryPromotion) error + SaveCognitiveActivationPromotion(context.Context, capability.CognitiveActivationPromotion) error SaveCognitiveUsageObservation(context.Context, capability.CognitiveUsageObservation) error SaveCognitiveDiscovery(context.Context, capability.CognitiveDiscoveryRegistration) error SaveEvaluatedCapability(context.Context, capability.EvaluatedCapability) error