feat(cognitive): add evidence-gated canary transition
CI / test (push) Successful in 14s

This commit is contained in:
m
2026-08-17 10:21:30 +03:00
parent ac5c6cbd30
commit 2a0aee0073
12 changed files with 226 additions and 8 deletions
+36 -4
View File
@@ -98,6 +98,10 @@ func (r *CapabilityRepository) SaveBinding(ctx context.Context, binding capabili
if err != nil {
return err
}
_, err = tx.Exec(ctx, `INSERT INTO capability.cognitive_binding_revision_heads (binding_id,revision) VALUES ($1,$2) ON CONFLICT DO NOTHING`, binding.ID, binding.Revision)
if err != nil {
return err
}
}
return tx.Commit(ctx)
}
@@ -157,13 +161,12 @@ func (r *CapabilityRepository) SaveCognitiveBinding(ctx context.Context, value c
}
func (r *CapabilityRepository) LoadCognitiveCandidates(ctx context.Context, capabilityID capability.CapabilityID, owner identity.PrincipalID) ([]capability.ResolvedCognitiveBinding, error) {
rows, err := r.pool.Query(ctx, `WITH latest_bindings AS (
SELECT DISTINCT ON (binding_id) * FROM capability.cognitive_bindings ORDER BY binding_id,revision DESC
) SELECT
rows, err := r.pool.Query(ctx, `SELECT
b.binding_id,b.revision,b.capability_id,b.owner_principal_id,b.provider_instance_id,b.provider_revision,b.model_descriptor_id,b.model_revision,b.data_policy,b.qualification,b.activation,b.health,b.limit_policy_ref,
p.provider_instance_id,p.revision,p.provider_kind,p.endpoint_ref,COALESCE(p.credential_ref,''),p.enabled,p.qualification,COALESCE(ph.health,p.health),GREATEST(p.observed_at_unix_millis,COALESCE(ph.observed_at_unix_millis,0)),
m.model_descriptor_id,m.revision,m.provider_instance_id,m.provider_model_id,m.fingerprint,m.max_context_bytes,m.modalities,m.capabilities,m.qualification,COALESCE(ma.health,m.health),GREATEST(m.observed_at_unix_millis,COALESCE(ma.observed_at_unix_millis,0))
FROM latest_bindings b
FROM capability.cognitive_binding_revision_heads bh
JOIN capability.cognitive_bindings b ON b.binding_id=bh.binding_id AND b.revision=bh.revision
JOIN capability.cognitive_provider_instances p ON p.provider_instance_id=b.provider_instance_id AND p.revision=b.provider_revision
JOIN capability.cognitive_model_descriptors m ON m.model_descriptor_id=b.model_descriptor_id AND m.revision=b.model_revision
LEFT JOIN capability.cognitive_provider_health ph ON ph.provider_instance_id=p.provider_instance_id
@@ -257,6 +260,35 @@ func (r *CapabilityRepository) SaveEvaluatedCapability(ctx context.Context, valu
return err
}
func (r *CapabilityRepository) SaveCognitiveCanaryPromotion(ctx context.Context, value capability.CognitiveCanaryPromotion) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
return err
}
defer func() { _ = tx.Rollback(ctx) }()
p, m, b, e := value.Provider, value.Model, value.Binding, value.Evaluation
if _, err = tx.Exec(ctx, `INSERT INTO capability.cognitive_provider_instances (provider_instance_id,revision,provider_kind,endpoint_ref,credential_ref,enabled,qualification,health,observed_at_unix_millis) VALUES ($1,$2,$3,$4,NULLIF($5,''),$6,$7,$8,$9) ON CONFLICT DO NOTHING`, p.ID, p.Revision, p.Kind, p.EndpointRef, p.CredentialRef, p.Enabled, p.Qualification, p.Health, p.ObservedAtUnixMillis); err != nil {
return err
}
if _, err = tx.Exec(ctx, `INSERT INTO capability.cognitive_model_descriptors (model_descriptor_id,revision,provider_instance_id,provider_model_id,fingerprint,max_context_bytes,modalities,capabilities,qualification,health,observed_at_unix_millis) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) ON CONFLICT DO NOTHING`, m.ID, m.Revision, m.ProviderInstanceID, m.ProviderModelID, m.Fingerprint, m.MaxContextBytes, m.Modalities, m.Capabilities, m.Qualification, m.Health, m.ObservedAtUnixMillis); err != nil {
return err
}
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
}
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
+39
View File
@@ -277,6 +277,45 @@ func main() {
}
w.WriteHeader(http.StatusCreated)
})
mux.HandleFunc("POST /v1/creator/cognitive-canary", func(w http.ResponseWriter, r *http.Request) {
session, ok := authenticateCreator(w, r)
if !ok {
return
}
var request capability.CognitiveCanaryPromotionRequest
decoder := json.NewDecoder(io.LimitReader(r.Body, 16<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil {
http.Error(w, "invalid canary promotion", 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.BuildCanaryPromotion(request, current)
if err != nil {
http.Error(w, "canary promotion precondition failed", http.StatusConflict)
return
}
if err = capabilityRepository.SaveCognitiveCanaryPromotion(r.Context(), promotion); err != nil {
http.Error(w, "canary promotion 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))
@@ -183,6 +183,29 @@ type CognitiveDiscoveryRegistration struct {
ModelAvailability []ModelOperationalObservation
}
type CognitiveCanaryPromotionRequest struct {
PromotionID ids.ID
RequestedByPrincipalID identity.PrincipalID
CapabilityID CapabilityID
BindingID CapabilityBindingID
ExpectedBindingRevision int64
ProviderInstanceID ProviderInstanceID
ExpectedProviderRevision int64
ModelDescriptorID ModelDescriptorID
ExpectedModelRevision int64
EvaluationID EvaluationID
ObservedAtUnixMillis int64
}
type CognitiveCanaryPromotion struct {
PromotionID ids.ID
ExpectedBindingRevision int64
Provider ProviderInstance
Model ModelDescriptor
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.
@@ -221,6 +221,11 @@ CREATE TABLE IF NOT EXISTS capability.cognitive_binding_heads (
capability_id TEXT NOT NULL, owner_principal_id TEXT NOT NULL, binding_id TEXT NOT NULL,
revision BIGINT NOT NULL, PRIMARY KEY (capability_id, owner_principal_id), CHECK (revision > 0)
);
CREATE TABLE IF NOT EXISTS capability.cognitive_binding_revision_heads (
binding_id TEXT PRIMARY KEY,
revision BIGINT NOT NULL CHECK (revision > 0),
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS capability.cognitive_usage_observations (
observation_id TEXT PRIMARY KEY, request_id TEXT NOT NULL, binding_id TEXT NOT NULL,
binding_revision BIGINT NOT NULL, provider_instance_id TEXT NOT NULL, model_descriptor_id TEXT NOT NULL,
@@ -0,0 +1,14 @@
BEGIN;
SET ROLE capability_role;
CREATE TABLE IF NOT EXISTS capability.cognitive_binding_revision_heads (
binding_id TEXT PRIMARY KEY,
revision BIGINT NOT NULL CHECK (revision > 0),
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO capability.cognitive_binding_revision_heads (binding_id,revision)
SELECT DISTINCT ON (binding_id) binding_id,revision
FROM capability.cognitive_bindings
ORDER BY binding_id,recorded_at DESC
ON CONFLICT DO NOTHING;
RESET ROLE;
COMMIT;
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000057-local-cognitive-qualification.json",
"task_id": "DEV-000057",
"task_file": "dev/tasks/DEV-000058-local-cognitive-canary.json",
"task_id": "DEV-000058",
"task_revision": 1
}
@@ -0,0 +1,28 @@
{
"task_id": "DEV-000058",
"task_revision": 1,
"architecture_revision": "ADR-0008",
"base_repo_revision": "ac5c6cbd30d6cdd588d392e62180df105cff3829",
"risk_tier": "E",
"intent": "Add an authenticated, append-only and evidence-gated Capability lifecycle transition for one exact local CPU binding revision, promote it to CANARY with CAS semantics, and prove bounded advisory invocation without granting model output any authority.",
"requirement_ids": ["INV-OWN-001", "INV-EPI-001", "ADR-0006", "ADR-0008", "LOCAL-COGNITIVE-CANARY-STAGE-1"],
"allowed_change_scope": {
"modules": ["capability"],
"paths": ["contracts/capability/**", "modules/capability/**", "adapters/postgres/**", "database/migrations/**", "database/bootstrap/**", "cmd/core-runtime/**", "tests/cognitive_registry/**", "tests/cognitive_router/**", "tests/cognitive_canary/**", "docs/evidence/**", "docs/PROJECT_STATUS.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000058-local-cognitive-canary.json"]
},
"forbidden_change_scope": {
"paths": ["architecture/**", "contracts/identity/**", "contracts/authority/**", "modules/identity/**", "modules/authority/**", "modules/situation/**", "modules/work/**", "modules/continuity/**", "modules/commit/**", "deploy/**", "security/**", "ci/**", ".gitea/**", "cmd/devctl/**", "cmd/cognitive-worker/**", "cmd/creator-web/**"]
},
"allowed_architecture_delta": {"new_cross_module_dependencies":0,"new_canonical_owners":0,"new_privileges":0,"new_third_party_dependencies":0,"new_provider_adapters":0},
"contracts": {"consume":["CreatorSession","Capability Cognitive Registry","EvaluatedCapability","ADR-0008"],"may_change":["contracts.capability cognitive lifecycle transition"]},
"semantic_owner": "capability",
"canonical_state": ["append-only validated ProviderInstance revision", "append-only validated ModelDescriptor revision", "append-only canary CognitiveBinding revision", "per-binding current revision head", "evidence reference copied to exact promoted binding revision"],
"state_transitions": ["DISCOVERED/INACTIVE to VALIDATED/CANARY for one exact binding lineage only", "no ACTIVE transition"],
"side_effects_and_privileges": ["authenticated Creator-directed owner-local Capability mutation", "bounded local CPU advisory smoke after successful canary", "no external mutation", "no OpenAI request", "no Commit or Executor privilege"],
"persistence_and_migrations": ["Capability-owned binding revision head table and deterministic backfill", "append-only existing registry/evaluation tables", "no cross-owner relation", "no event"],
"failure_retry_restart": ["expected-revision CAS", "stale promotion fails conflict", "same PromotionID is idempotent", "no automatic inference retry", "rollback appends an INACTIVE revision in a later explicit transition rather than deleting history"],
"assumptions": ["the Creator's explicit continuation authorizes canary for the recommended exact CPU Qwen2.5-3B binding", "GPU qwen3:8b remains inactive fallback candidate", "docker-test remains prohibited"],
"acceptance_scenarios": ["unauthenticated promotion denied", "unsupported evidence denied", "stale revision denied", "exact supported CPU revision becomes CANARY", "all other bindings remain INACTIVE", "router selects canary only when AllowCanary=true", "LLM output remains advisory and non-canonical"],
"required_tests": ["domain transition", "repository CAS", "authenticated endpoint", "router head selection", "migration and DB ownership", "production canary smoke", "go test ./...", "go vet ./...", "architecture sentinel", "exact diff scope"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"]
}
+3 -2
View File
@@ -1,6 +1,6 @@
# Assistant project status
Audit date: 2026-08-17. Active implementation: DEV-000057 revision 1.
Audit date: 2026-08-17. Active implementation: DEV-000058 revision 1.
## Current milestone
@@ -11,6 +11,7 @@ Audit date: 2026-08-17. Active implementation: DEV-000057 revision 1.
- The current milestone integrates one normally available local CPU provider and one opportunistic local Ollama/GPU provider through typed discovery, health and routing boundaries.
- Discovery never activates a provider, model or binding. Until a later benchmark and explicit promotion task, all discovered bindings remain `INACTIVE` and production remains fully LLM-free.
- DEV-000057 executed a bounded synthetic qualification corpus against all 11 current local model revisions. Four exact binding revisions demonstrated the minimum advisory threshold; seven remain unsupported. Qualification evidence does not change lifecycle or activation.
- DEV-000058 promoted the exact CPU `Qwen2.5-3B-Instruct-Q4_K_M` lineage to `VALIDATED/CANARY` through an authenticated, evidence-gated, append-only CAS transition. Ordinary routing still excludes it; only an explicit `AllowCanary` request can select it. All other current bindings remain `INACTIVE`.
## Delivered
@@ -30,7 +31,7 @@ Audit date: 2026-08-17. Active implementation: DEV-000057 revision 1.
## Remaining work requiring a new task decision
1. **Cognitive promotion:** DEV-000057 records revision-bound qualification evidence. A separate explicit promotion task must decide whether any supported exact revision may move through `EVALUATING → VALIDATED → CANARY`; no binding is currently active.
1. **Cognitive canary observation:** collect bounded usage/reliability evidence for the exact CPU canary revision before any separate `ACTIVE` decision. GPU candidates remain inactive.
2. **Product breadth:** notification delivery is the only accepted real external operation. Any additional operation needs its own proposal, authority model, idempotency/reconciliation semantics and Capability binding.
3. **Artifact and Epistemics product behavior:** their ownership boundaries exist, but no product task has yet specified durable user-facing workflows for them.
@@ -0,0 +1,18 @@
{
"artifact_id": "local-cpu-canary-promotion-2026-08-17-v1",
"promotion_id": "promotion-cpu-qwen25-3b-canary-v1",
"provider_instance_id": "local-cpu-primary",
"provider_revision": 2478733237506203521,
"provider_model_id": "Qwen2.5-3B-Instruct-Q4_K_M",
"model_descriptor_id": "model-d44e5f49f22f866e182060a8",
"model_revision": 4433750841304261136,
"binding_id": "binding-9e5e9779cd10b907c3695374",
"binding_revision": 2865230063392044815,
"qualification": "VALIDATED",
"activation": "CANARY",
"evaluation_artifact_id": "local-cognitive-qualification-2026-08-17-v1",
"runtime_revision": "9b11d17d58810e7d01b22c95ee3d73298c7e2bc1",
"ordinary_resolve": "NOT_FOUND",
"allow_canary_resolve": "SELECTED",
"other_current_bindings": {"INACTIVE": 11, "CANARY": 0, "ACTIVE": 0}
}
@@ -39,6 +39,29 @@ func BuildDiscoveryRegistration(snapshot capability.CognitiveDiscoverySnapshot)
return registration, nil
}
func BuildCanaryPromotion(request capability.CognitiveCanaryPromotionRequest, current capability.ResolvedCognitiveBinding) (capability.CognitiveCanaryPromotion, error) {
if request.PromotionID == "" || request.RequestedByPrincipalID == "" || request.CapabilityID == "" || request.BindingID == "" || request.ExpectedBindingRevision < 1 || request.ProviderInstanceID == "" || request.ExpectedProviderRevision < 1 || request.ModelDescriptorID == "" || request.ExpectedModelRevision < 1 || request.EvaluationID == "" || request.ObservedAtUnixMillis < 1 || current.Evaluation == nil {
return capability.CognitiveCanaryPromotion{}, ErrInvalidCognitiveRegistryRecord
}
if current.Binding.ID != request.BindingID || current.Binding.Revision != request.ExpectedBindingRevision || current.Binding.OwnerPrincipalID != request.RequestedByPrincipalID || current.Binding.CapabilityID != request.CapabilityID || current.Provider.ID != request.ProviderInstanceID || current.Provider.Revision != request.ExpectedProviderRevision || current.Model.ID != request.ModelDescriptorID || current.Model.Revision != request.ExpectedModelRevision || 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.Activation != capability.ActivationInactive {
return capability.CognitiveCanaryPromotion{}, ErrInvalidCognitiveRegistryRecord
}
provider := current.Provider
provider.Revision = stableRevision(fmt.Sprint(provider.Revision), string(request.PromotionID), string(capability.QualificationValidated))
provider.Qualification, provider.Health, provider.ObservedAtUnixMillis = capability.QualificationValidated, capability.HealthAvailable, request.ObservedAtUnixMillis
model := current.Model
model.Revision = stableRevision(fmt.Sprint(model.Revision), string(request.PromotionID), string(capability.QualificationValidated))
model.Qualification, model.Health, model.ObservedAtUnixMillis = capability.QualificationValidated, capability.HealthAvailable, request.ObservedAtUnixMillis
binding := current.Binding
binding.Revision = stableRevision(fmt.Sprint(binding.Revision), fmt.Sprint(provider.Revision), fmt.Sprint(model.Revision), string(request.PromotionID), string(capability.ActivationCanary))
binding.ProviderRevision, binding.ModelRevision = provider.Revision, model.Revision
binding.Qualification, binding.Activation, binding.Health = capability.QualificationValidated, capability.ActivationCanary, 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.CognitiveCanaryPromotion{PromotionID: request.PromotionID, ExpectedBindingRevision: request.ExpectedBindingRevision, Provider: provider, Model: model, Binding: binding, Evaluation: evaluation}, nil
}
func stableID(kind string, parts ...string) string {
hash := sha256.New()
_, _ = hash.Write([]byte(kind))
+2
View File
@@ -18,6 +18,8 @@ type Repository interface {
SaveModelDescriptor(context.Context, capability.ModelDescriptor) error
SaveCognitiveBinding(context.Context, capability.CognitiveBinding) error
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
SaveCognitiveUsageObservation(context.Context, capability.CognitiveUsageObservation) error
SaveCognitiveDiscovery(context.Context, capability.CognitiveDiscoveryRegistration) error
SaveEvaluatedCapability(context.Context, capability.EvaluatedCapability) error
+33
View File
@@ -0,0 +1,33 @@
package cognitive_canary_test
import (
"testing"
"git.cin.su/m/Assistant/contracts/capability"
capabilitydomain "git.cin.su/m/Assistant/modules/capability/domain"
)
func TestSupportedExactRevisionPromotesToCanary(t *testing.T) {
quality, critical := int64(1000), int64(0)
current := capability.ResolvedCognitiveBinding{
Binding: capability.CognitiveBinding{ID: "binding-1", Revision: 11, CapabilityID: "creator-advisory", OwnerPrincipalID: "creator", ProviderInstanceID: "provider-1", ProviderRevision: 12, ModelDescriptorID: "model-1", ModelRevision: 13, DataPolicy: capability.DataPolicyLocalOnly, Qualification: capability.QualificationDiscovered, Activation: capability.ActivationInactive, Health: capability.HealthUnknown, LimitPolicyRef: "limits"},
Provider: capability.ProviderInstance{ID: "provider-1", Revision: 12, Kind: "local", EndpointRef: "endpoint", Enabled: true, Qualification: capability.QualificationDiscovered, Health: capability.HealthAvailable, ObservedAtUnixMillis: 1},
Model: capability.ModelDescriptor{ID: "model-1", Revision: 13, ProviderInstanceID: "provider-1", ProviderModelID: "snapshot", Fingerprint: "digest", MaxContextBytes: 4096, Modalities: []capability.ModelModality{capability.ModalityText}, Capabilities: []capability.ModelCapability{capability.CapabilityAdvisory}, Qualification: capability.QualificationDiscovered, Health: capability.HealthAvailable, ObservedAtUnixMillis: 1},
Evaluation: &capability.EvaluatedCapability{ID: "eval-1", BindingID: "binding-1", BindingRevision: 11, CapabilityID: "creator-advisory", Result: capability.EvaluationSupported, QualityScoreMillis: &quality, CriticalFailureMillis: &critical, BenchmarkVersion: "v1", ArtifactID: "artifact-1", ObservedAtUnixMillis: 1},
}
request := capability.CognitiveCanaryPromotionRequest{PromotionID: "promotion-1", RequestedByPrincipalID: "creator", CapabilityID: "creator-advisory", BindingID: "binding-1", ExpectedBindingRevision: 11, ProviderInstanceID: "provider-1", ExpectedProviderRevision: 12, ModelDescriptorID: "model-1", ExpectedModelRevision: 13, EvaluationID: "eval-1", ObservedAtUnixMillis: 2}
promotion, err := capabilitydomain.BuildCanaryPromotion(request, current)
if err != nil {
t.Fatal(err)
}
if promotion.Binding.Activation != capability.ActivationCanary || promotion.Binding.Qualification != capability.QualificationValidated || promotion.Provider.Qualification != capability.QualificationValidated || promotion.Model.Qualification != capability.QualificationValidated {
t.Fatalf("invalid promotion: %+v", promotion)
}
if promotion.Binding.Revision == current.Binding.Revision || promotion.Evaluation.BindingRevision != promotion.Binding.Revision {
t.Fatal("promotion is not append-only and evidence-bound")
}
current.Evaluation.Result = capability.EvaluationUnsupported
if _, err = capabilitydomain.BuildCanaryPromotion(request, current); err == nil {
t.Fatal("unsupported evaluation promoted")
}
}