feat: evolve Domovoy cognitive provider catalog #84
@@ -0,0 +1,20 @@
|
||||
name: Deploy production stack
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Portainer Git deployment
|
||||
env:
|
||||
PORTAINER_WEBHOOK_URL: ${{ secrets.PORTAINER_WEBHOOK_URL }}
|
||||
run: |
|
||||
set -eu
|
||||
test -n "$PORTAINER_WEBHOOK_URL"
|
||||
curl --fail --silent --show-error --retry 3 --retry-delay 5 \
|
||||
--request POST "$PORTAINER_WEBHOOK_URL"
|
||||
@@ -322,18 +322,18 @@ func (r *CapabilityRepository) LoadActiveBinding(ctx context.Context, capability
|
||||
|
||||
func (r *CapabilityRepository) SaveProviderInstance(ctx context.Context, value capability.ProviderInstance) error {
|
||||
_, err := r.pool.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,$5,$6,$7,$8,$9) ON CONFLICT DO NOTHING`, value.ID, value.Revision, value.Kind,
|
||||
value.EndpointRef, value.CredentialRef, value.Enabled, value.Qualification, value.Health, value.ObservedAtUnixMillis)
|
||||
(provider_instance_id,revision,provider_kind,endpoint_ref,credential_ref,enabled,qualification,health,observed_at_unix_millis,billing_mode,auth_mode,input_cost_micros,output_cost_micros,request_cost_micros)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) ON CONFLICT DO NOTHING`, value.ID, value.Revision, value.Kind,
|
||||
value.EndpointRef, value.CredentialRef, value.Enabled, value.Qualification, value.Health, value.ObservedAtUnixMillis, value.BillingMode, value.AuthMode, value.InputCostMicros, value.OutputCostMicros, value.RequestCostMicros)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *CapabilityRepository) SaveModelDescriptor(ctx context.Context, value capability.ModelDescriptor) error {
|
||||
_, err := r.pool.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`, value.ID, value.Revision,
|
||||
(model_descriptor_id,revision,provider_instance_id,provider_model_id,fingerprint,max_context_bytes,modalities,capabilities,qualification,health,observed_at_unix_millis,intelligence_score_millis)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ON CONFLICT DO NOTHING`, value.ID, value.Revision,
|
||||
value.ProviderInstanceID, value.ProviderModelID, value.Fingerprint, value.MaxContextBytes, value.Modalities,
|
||||
value.Capabilities, value.Qualification, value.Health, value.ObservedAtUnixMillis)
|
||||
value.Capabilities, value.Qualification, value.Health, value.ObservedAtUnixMillis, value.IntelligenceScoreMillis)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -349,8 +349,8 @@ 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, `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))
|
||||
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)),p.billing_mode,p.auth_mode,p.input_cost_micros,p.output_cost_micros,p.request_cost_micros,
|
||||
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)),m.intelligence_score_millis
|
||||
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
|
||||
@@ -366,8 +366,8 @@ func (r *CapabilityRepository) LoadCognitiveCandidates(ctx context.Context, capa
|
||||
for rows.Next() {
|
||||
var value capability.ResolvedCognitiveBinding
|
||||
err = rows.Scan(&value.Binding.ID, &value.Binding.Revision, &value.Binding.CapabilityID, &value.Binding.OwnerPrincipalID, &value.Binding.ProviderInstanceID, &value.Binding.ProviderRevision, &value.Binding.ModelDescriptorID, &value.Binding.ModelRevision, &value.Binding.DataPolicy, &value.Binding.Qualification, &value.Binding.Activation, &value.Binding.Health, &value.Binding.LimitPolicyRef,
|
||||
&value.Provider.ID, &value.Provider.Revision, &value.Provider.Kind, &value.Provider.EndpointRef, &value.Provider.CredentialRef, &value.Provider.Enabled, &value.Provider.Qualification, &value.Provider.Health, &value.Provider.ObservedAtUnixMillis,
|
||||
&value.Model.ID, &value.Model.Revision, &value.Model.ProviderInstanceID, &value.Model.ProviderModelID, &value.Model.Fingerprint, &value.Model.MaxContextBytes, &value.Model.Modalities, &value.Model.Capabilities, &value.Model.Qualification, &value.Model.Health, &value.Model.ObservedAtUnixMillis)
|
||||
&value.Provider.ID, &value.Provider.Revision, &value.Provider.Kind, &value.Provider.EndpointRef, &value.Provider.CredentialRef, &value.Provider.Enabled, &value.Provider.Qualification, &value.Provider.Health, &value.Provider.ObservedAtUnixMillis, &value.Provider.BillingMode, &value.Provider.AuthMode, &value.Provider.InputCostMicros, &value.Provider.OutputCostMicros, &value.Provider.RequestCostMicros,
|
||||
&value.Model.ID, &value.Model.Revision, &value.Model.ProviderInstanceID, &value.Model.ProviderModelID, &value.Model.Fingerprint, &value.Model.MaxContextBytes, &value.Model.Modalities, &value.Model.Capabilities, &value.Model.Qualification, &value.Model.Health, &value.Model.ObservedAtUnixMillis, &value.Model.IntelligenceScoreMillis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -414,7 +414,7 @@ func (r *CapabilityRepository) SaveCognitiveDiscovery(ctx context.Context, value
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
_, 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`, value.Provider.ID, value.Provider.Revision, value.Provider.Kind, value.Provider.EndpointRef, value.Provider.CredentialRef, value.Provider.Enabled, value.Provider.Qualification, value.Provider.Health, value.Provider.ObservedAtUnixMillis)
|
||||
_, 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,billing_mode,auth_mode,input_cost_micros,output_cost_micros,request_cost_micros) VALUES ($1,$2,$3,$4,NULLIF($5,''),$6,$7,$8,$9,$10,$11,$12,$13,$14) ON CONFLICT (provider_instance_id,revision) DO UPDATE SET health=EXCLUDED.health,observed_at_unix_millis=EXCLUDED.observed_at_unix_millis,billing_mode=EXCLUDED.billing_mode,auth_mode=EXCLUDED.auth_mode,input_cost_micros=EXCLUDED.input_cost_micros,output_cost_micros=EXCLUDED.output_cost_micros,request_cost_micros=EXCLUDED.request_cost_micros WHERE capability.cognitive_provider_instances.observed_at_unix_millis <= EXCLUDED.observed_at_unix_millis`, value.Provider.ID, value.Provider.Revision, value.Provider.Kind, value.Provider.EndpointRef, value.Provider.CredentialRef, value.Provider.Enabled, value.Provider.Qualification, value.Provider.Health, value.Provider.ObservedAtUnixMillis, value.Provider.BillingMode, value.Provider.AuthMode, value.Provider.InputCostMicros, value.Provider.OutputCostMicros, value.Provider.RequestCostMicros)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -423,7 +423,7 @@ func (r *CapabilityRepository) SaveCognitiveDiscovery(ctx context.Context, value
|
||||
return err
|
||||
}
|
||||
for index, model := range value.Models {
|
||||
_, 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`, model.ID, model.Revision, model.ProviderInstanceID, model.ProviderModelID, model.Fingerprint, model.MaxContextBytes, model.Modalities, model.Capabilities, model.Qualification, model.Health, model.ObservedAtUnixMillis)
|
||||
_, 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,intelligence_score_millis) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ON CONFLICT (model_descriptor_id,revision) DO UPDATE SET health=EXCLUDED.health,observed_at_unix_millis=EXCLUDED.observed_at_unix_millis,intelligence_score_millis=EXCLUDED.intelligence_score_millis WHERE capability.cognitive_model_descriptors.observed_at_unix_millis <= EXCLUDED.observed_at_unix_millis`, model.ID, model.Revision, model.ProviderInstanceID, model.ProviderModelID, model.Fingerprint, model.MaxContextBytes, model.Modalities, model.Capabilities, model.Qualification, model.Health, model.ObservedAtUnixMillis, model.IntelligenceScoreMillis)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -433,7 +433,11 @@ func (r *CapabilityRepository) SaveCognitiveDiscovery(ctx context.Context, value
|
||||
return err
|
||||
}
|
||||
binding := value.Bindings[index]
|
||||
_, 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`, binding.ID, binding.Revision, binding.CapabilityID, binding.OwnerPrincipalID, binding.ProviderInstanceID, binding.ProviderRevision, binding.ModelDescriptorID, binding.ModelRevision, binding.DataPolicy, binding.Qualification, binding.Activation, binding.Health, binding.LimitPolicyRef)
|
||||
_, 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 (binding_id,revision) DO UPDATE SET health=EXCLUDED.health WHERE capability.cognitive_bindings.health <> EXCLUDED.health`, binding.ID, binding.Revision, binding.CapabilityID, binding.OwnerPrincipalID, binding.ProviderInstanceID, binding.ProviderRevision, binding.ModelDescriptorID, binding.ModelRevision, binding.DataPolicy, binding.Qualification, binding.Activation, binding.Health, binding.LimitPolicyRef)
|
||||
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
|
||||
}
|
||||
@@ -453,10 +457,10 @@ func (r *CapabilityRepository) SaveCognitiveCanaryPromotion(ctx context.Context,
|
||||
}
|
||||
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 {
|
||||
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,billing_mode,auth_mode,input_cost_micros,output_cost_micros,request_cost_micros) VALUES ($1,$2,$3,$4,NULLIF($5,''),$6,$7,$8,$9,$10,$11,$12,$13,$14) ON CONFLICT DO NOTHING`, p.ID, p.Revision, p.Kind, p.EndpointRef, p.CredentialRef, p.Enabled, p.Qualification, p.Health, p.ObservedAtUnixMillis, p.BillingMode, p.AuthMode, p.InputCostMicros, p.OutputCostMicros, p.RequestCostMicros); 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 {
|
||||
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,intelligence_score_millis) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) 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, m.IntelligenceScoreMillis); 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 {
|
||||
@@ -475,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
|
||||
|
||||
@@ -23,7 +23,7 @@ func (s discoveryService) handler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
discovered, probe := registration.Discovery.Discover(r.Context())
|
||||
snapshot := capability.CognitiveDiscoverySnapshot{ProviderInstanceID: registration.ProviderInstanceID, ProviderKind: registration.Kind, EndpointRef: registration.EndpointRef, CredentialRef: registration.CredentialRef, Enabled: true, OwnerPrincipalID: s.owner, CapabilityID: creatorAdvisoryCapabilityID, DataPolicy: capability.DataPolicyLocalOnly, LimitPolicyRef: "creator-advisory-local-v1", Health: probe.Health, FailureClass: capability.CognitiveFailureType(probe.Failure), ObservedAtUnixMillis: s.now().UnixMilli()}
|
||||
snapshot := capability.CognitiveDiscoverySnapshot{ProviderInstanceID: registration.ProviderInstanceID, ProviderKind: registration.Kind, EndpointRef: registration.EndpointRef, CredentialRef: registration.CredentialRef, AuthMode: registration.AuthMode, BillingMode: registration.BillingMode, Enabled: true, OwnerPrincipalID: s.owner, CapabilityID: creatorAdvisoryCapabilityID, DataPolicy: capability.DataPolicyLocalOnly, LimitPolicyRef: "creator-advisory-local-v1", Health: probe.Health, FailureClass: capability.CognitiveFailureType(probe.Failure), ObservedAtUnixMillis: s.now().UnixMilli()}
|
||||
for _, model := range discovered {
|
||||
snapshot.Models = append(snapshot.Models, capability.DiscoveredModelSnapshot{ProviderModelID: model.ProviderModelID, Fingerprint: model.Fingerprint, MaxContextBytes: model.MaxContextBytes, Modalities: model.Modalities, Capabilities: model.Capabilities, Installed: capability.OperationalYes, Loaded: capability.OperationalNotObserved, Invokable: capability.OperationalNotObserved})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/adapters/models"
|
||||
@@ -29,6 +30,8 @@ type providerRegistration struct {
|
||||
Kind capability.ProviderKind
|
||||
EndpointRef capability.EndpointBindingRef
|
||||
CredentialRef capability.CredentialRef
|
||||
AuthMode capability.AuthMode
|
||||
BillingMode capability.BillingMode
|
||||
Provider advisoryProvider
|
||||
Discovery models.LocalProvider
|
||||
}
|
||||
@@ -71,10 +74,10 @@ func (g cognitiveGateway) advisoryHandler(w http.ResponseWriter, r *http.Request
|
||||
if g.allowCanary && g.canaryMaxOutputBytes > 0 && maxOutputBytes > g.canaryMaxOutputBytes {
|
||||
maxOutputBytes = g.canaryMaxOutputBytes
|
||||
}
|
||||
requirement := capability.CognitiveRequirement{CapabilityID: creatorAdvisoryCapabilityID, OwnerPrincipalID: request.PrincipalID, DataPolicy: request.DataPolicy, RequiredModalities: []capability.ModelModality{capability.ModalityText}, RequiredCapabilities: []capability.ModelCapability{capability.CapabilityAdvisory}, AllowCanary: g.allowCanary, InputBytes: int64(len(request.Message)), MaxOutputBytes: int64(maxOutputBytes)}
|
||||
requirement := capability.CognitiveRequirement{CapabilityID: creatorAdvisoryCapabilityID, OwnerPrincipalID: request.PrincipalID, DataPolicy: request.DataPolicy, RequiredModalities: []capability.ModelModality{capability.ModalityText}, RequiredCapabilities: []capability.ModelCapability{capability.CapabilityAdvisory}, AllowCanary: g.allowCanary, InputBytes: int64(len(request.Message)), MaxOutputBytes: int64(maxOutputBytes), Objective: selectionObjectiveForMeaning(request.Meaning, request.Message)}
|
||||
selected, err := g.resolver.Resolve(r.Context(), requirement)
|
||||
if err != nil {
|
||||
writeGatewayResponse(w, cognitive.GatewayAdvisoryResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishProviderUnavailable})
|
||||
writeGatewayResponse(w, builtinAdvisory(request))
|
||||
return
|
||||
}
|
||||
if selected.Binding.DataPolicy != capability.DataPolicyLocalOnly && !request.EgressAcknowledged {
|
||||
@@ -83,7 +86,7 @@ func (g cognitiveGateway) advisoryHandler(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
provider := g.provider(selected)
|
||||
if provider == nil {
|
||||
writeGatewayResponse(w, cognitive.GatewayAdvisoryResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishProviderUnavailable})
|
||||
writeGatewayResponse(w, builtinAdvisory(request))
|
||||
return
|
||||
}
|
||||
invocation := cognitive.AdvisoryRequest{Version: request.Version, RequestID: request.RequestID, ActorID: request.ActorID, PrincipalID: request.PrincipalID, ScopeID: request.ScopeID, Message: request.Message, Context: request.Context, Locale: request.Locale, MaxOutputBytes: maxOutputBytes, ProviderBindingID: selected.Binding.ID, ProviderBindingRevision: selected.Binding.Revision, ProviderInstanceID: selected.Provider.ID, ModelDescriptorID: selected.Model.ID, ProviderModelID: selected.Model.ProviderModelID}
|
||||
@@ -110,9 +113,57 @@ func (g cognitiveGateway) advisoryHandler(w http.ResponseWriter, r *http.Request
|
||||
response = fallbackResponse
|
||||
}
|
||||
}
|
||||
if response.FinishReason != cognitive.FinishCompleted && fallbackEligible(response.FinishReason) {
|
||||
writeGatewayResponse(w, builtinAdvisory(request))
|
||||
return
|
||||
}
|
||||
writeGatewayResponse(w, cognitive.GatewayAdvisoryResponse{Version: response.Version, RequestID: response.RequestID, AdvisoryText: response.AdvisoryText, ReferencedExcerptIDs: response.ReferencedExcerptIDs, FinishReason: response.FinishReason, Usage: response.Usage})
|
||||
}
|
||||
|
||||
func selectionObjectiveForMessage(message string) capability.SelectionObjective {
|
||||
lower := strings.ToLower(message)
|
||||
if strings.Contains(lower, "дешев") || strings.Contains(lower, "бесплат") || strings.Contains(lower, "эконом") || strings.Contains(lower, "cost") || strings.Contains(lower, "cheap") {
|
||||
return capability.SelectionEconomy
|
||||
}
|
||||
if strings.Contains(lower, "быстр") || strings.Contains(lower, "срочно") || strings.Contains(lower, "статус") || strings.Contains(lower, "quick") || strings.Contains(lower, "fast") {
|
||||
return capability.SelectionSpeed
|
||||
}
|
||||
if strings.Contains(lower, "сложн") || strings.Contains(lower, "анализ") || strings.Contains(lower, "код") || strings.Contains(lower, "план") || strings.Contains(lower, "глубок") || strings.Contains(lower, "reason") || strings.Contains(lower, "code") {
|
||||
return capability.SelectionQuality
|
||||
}
|
||||
return capability.SelectionBalanced
|
||||
}
|
||||
|
||||
func selectionObjectiveForMeaning(meaning cognitive.TaskMeaning, message string) capability.SelectionObjective {
|
||||
if meaning.CapabilityKind != "" || len(meaning.CapabilityHints) > 0 || meaning.SpeechAct != "" || meaning.RequestedOutcome != "" {
|
||||
kinds := append([]capability.CapabilityKind{meaning.CapabilityKind}, meaning.CapabilityHints...)
|
||||
for _, kind := range kinds {
|
||||
switch kind {
|
||||
case capability.CapabilityKindModifyCode, capability.CapabilityKindRunTests, capability.CapabilityKindVerifyFact, capability.CapabilityKindInspectProject, capability.CapabilityKindReadDatabase, capability.CapabilityKindQueryLogs:
|
||||
return capability.SelectionQuality
|
||||
case capability.CapabilityKindScheduleReminder:
|
||||
return capability.SelectionSpeed
|
||||
}
|
||||
}
|
||||
if meaning.RequestedOutcome != "" || meaning.SpeechAct == "REQUEST_TO_INVESTIGATE" || meaning.SpeechAct == "DIRECTIVE" {
|
||||
return capability.SelectionQuality
|
||||
}
|
||||
if meaning.ConfidenceMillis > 0 {
|
||||
return capability.SelectionBalanced
|
||||
}
|
||||
}
|
||||
return selectionObjectiveForMessage(message)
|
||||
}
|
||||
|
||||
func builtinAdvisory(request cognitive.GatewayAdvisoryRequest) cognitive.GatewayAdvisoryResponse {
|
||||
message := strings.ToLower(strings.TrimSpace(request.Message))
|
||||
text := "Я рядом и продолжаю работу во встроенном режиме Домового. Подключённые модели сейчас недоступны; могу помочь проверить канал, сравнить доступные модели и подготовить безопасное переключение."
|
||||
if strings.Contains(message, "модел") || strings.Contains(message, "канал") || strings.Contains(message, "ии") || strings.Contains(message, "ai") {
|
||||
text = "Встроенный режим Домового доступен. Я могу проверить подключённые модели по назначению, качеству, скорости и стоимости, после чего предложить подходящую; ключи и пароли в разговор не нужны."
|
||||
}
|
||||
return cognitive.GatewayAdvisoryResponse{Version: request.Version, RequestID: request.RequestID, AdvisoryText: text, FinishReason: cognitive.FinishCompleted}
|
||||
}
|
||||
|
||||
func (g cognitiveGateway) observe(ctx context.Context, selected capability.ResolvedCognitiveBinding, response cognitive.AdvisoryResponse, started time.Time) {
|
||||
usage := response.Usage
|
||||
observationID, idErr := ids.New()
|
||||
|
||||
@@ -114,7 +114,45 @@ func TestCanaryOutputLimitIsClamped(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoActiveBindingKeepsGatewayAvailableWithoutInvocation(t *testing.T) {
|
||||
func TestSelectionObjectiveForMessage(t *testing.T) {
|
||||
cases := []struct {
|
||||
message string
|
||||
objective capability.SelectionObjective
|
||||
}{
|
||||
{"Сделай глубокий анализ кода", capability.SelectionQuality},
|
||||
{"Нужен быстрый статус", capability.SelectionSpeed},
|
||||
{"Выбери бесплатную модель", capability.SelectionEconomy},
|
||||
{"Помоги сформулировать ответ", capability.SelectionBalanced},
|
||||
}
|
||||
for _, test := range cases {
|
||||
if actual := selectionObjectiveForMessage(test.message); actual != test.objective {
|
||||
t.Errorf("message=%q objective=%q, want %q", test.message, actual, test.objective)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectionObjectiveUsesTrustedMeaningBeforeText(t *testing.T) {
|
||||
meaning := cognitive.TaskMeaning{CapabilityKind: capability.CapabilityKindModifyCode, SpeechAct: "DIRECTIVE"}
|
||||
if actual := selectionObjectiveForMeaning(meaning, "сделай быстро и дёшево"); actual != capability.SelectionQuality {
|
||||
t.Fatalf("objective=%q, want quality", actual)
|
||||
}
|
||||
if actual := selectionObjectiveForMeaning(cognitive.TaskMeaning{}, "сделай быстро"); actual != capability.SelectionSpeed {
|
||||
t.Fatalf("fallback objective=%q, want speed", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectionObjectiveForInterpretationUsesSemanticSignals(t *testing.T) {
|
||||
request := cognitive.GatewayInterpretationRequest{RequestedOutcome: "сравнить варианты", DeterministicApplicability: cognitive.InterpretationApplicable, DeterministicConfidence: 900, DeterministicSpeechAct: "QUESTION"}
|
||||
if actual := selectionObjectiveForInterpretation(request); actual != capability.SelectionQuality {
|
||||
t.Fatalf("objective=%q, want quality", actual)
|
||||
}
|
||||
request = cognitive.GatewayInterpretationRequest{DeterministicApplicability: cognitive.InterpretationApplicable, DeterministicConfidence: 900, DeterministicSpeechAct: "ASSERTION"}
|
||||
if actual := selectionObjectiveForInterpretation(request); actual != capability.SelectionSpeed {
|
||||
t.Fatalf("objective=%q, want speed", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoActiveBindingUsesBuiltInProviderWithoutInvocation(t *testing.T) {
|
||||
registry := ®istryStub{err: errors.New("no active binding")}
|
||||
provider := &stubAdvisoryProvider{}
|
||||
gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{"unused": {{Provider: provider}}}, now: time.Now}
|
||||
@@ -125,7 +163,7 @@ func TestNoActiveBindingKeepsGatewayAvailableWithoutInvocation(t *testing.T) {
|
||||
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.Code != http.StatusOK || result.FinishReason != cognitive.FinishProviderUnavailable || provider.called {
|
||||
if response.Code != http.StatusOK || result.FinishReason != cognitive.FinishCompleted || result.AdvisoryText == "" || provider.called {
|
||||
t.Fatalf("status=%d result=%+v provider_called=%t", response.Code, result, provider.called)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ func (g cognitiveGateway) interpretationHandler(w http.ResponseWriter, r *http.R
|
||||
AllowCanary: g.allowCanary,
|
||||
InputBytes: int64(len(request.Message)),
|
||||
MaxOutputBytes: int64(maxOutputBytes),
|
||||
Objective: selectionObjectiveForInterpretation(request),
|
||||
}
|
||||
selected, err := g.resolver.Resolve(r.Context(), requirement)
|
||||
if err != nil {
|
||||
@@ -76,6 +77,16 @@ func (g cognitiveGateway) interpretationHandler(w http.ResponseWriter, r *http.R
|
||||
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, Candidate: candidate, FinishReason: cognitive.FinishCompleted, Usage: response.Usage})
|
||||
}
|
||||
|
||||
func selectionObjectiveForInterpretation(request cognitive.GatewayInterpretationRequest) capability.SelectionObjective {
|
||||
if request.RequestedOutcome != "" || request.DeterministicApplicability == cognitive.InterpretationNeedsBroader || request.DeterministicApplicability == cognitive.InterpretationMixedIntent || request.DeterministicApplicability == cognitive.InterpretationLowConfidence {
|
||||
return capability.SelectionQuality
|
||||
}
|
||||
if request.DeterministicConfidence >= 800 && request.DeterministicSpeechAct != "QUESTION" {
|
||||
return capability.SelectionSpeed
|
||||
}
|
||||
return capability.SelectionBalanced
|
||||
}
|
||||
|
||||
func interpretationPrompt(request cognitive.GatewayInterpretationRequest) string {
|
||||
projects := "none"
|
||||
if len(request.ProjectNames) > 0 {
|
||||
|
||||
@@ -44,19 +44,25 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cpu := providerRegistration{ProviderInstanceID: "local-cpu-primary", Kind: "openai-compatible-chat-v1", EndpointRef: "local-cpu-primary-v1", Provider: cpuAdapter, Discovery: cpuAdapter}
|
||||
cpu := providerRegistration{ProviderInstanceID: "local-cpu-primary", Kind: "openai-compatible-chat-v1", EndpointRef: "local-cpu-primary-v1", AuthMode: capability.AuthNone, BillingMode: capability.BillingFree, Provider: cpuAdapter, Discovery: cpuAdapter}
|
||||
providers[cpu.Kind] = append(providers[cpu.Kind], cpu)
|
||||
discoveries["cpu"] = cpu
|
||||
if lifeboat, enabled, lifeboatErr := newLifeboatProvider(os.Getenv("LOCAL_LIFEBOAT_PROVIDER_URL")); lifeboatErr != nil {
|
||||
log.Fatal(lifeboatErr)
|
||||
} else if enabled {
|
||||
providers[lifeboat.Kind] = append(providers[lifeboat.Kind], lifeboat)
|
||||
discoveries["lifeboat"] = lifeboat
|
||||
}
|
||||
gpuAdapter, err := models.NewOllamaAdapter(envOr("LOCAL_GPU_PROVIDER_URL", "http://192.168.220.91:11434"), nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
gpu := providerRegistration{ProviderInstanceID: "local-gpu-opportunistic", Kind: "ollama-v1", EndpointRef: "local-gpu-opportunistic-v1", Provider: gpuAdapter, Discovery: gpuAdapter}
|
||||
gpu := providerRegistration{ProviderInstanceID: "local-gpu-opportunistic", Kind: "ollama-v1", EndpointRef: "local-gpu-opportunistic-v1", AuthMode: capability.AuthNone, BillingMode: capability.BillingFree, Provider: gpuAdapter, Discovery: gpuAdapter}
|
||||
providers[gpu.Kind] = append(providers[gpu.Kind], gpu)
|
||||
discoveries["gpu"] = gpu
|
||||
if openAIProvider != nil {
|
||||
kind := capability.ProviderKind(envOr("OPENAI_PROVIDER_KIND", "openai-responses-v1"))
|
||||
providers[kind] = append(providers[kind], providerRegistration{ProviderInstanceID: "openai-bootstrap", Kind: kind, EndpointRef: capability.EndpointBindingRef(envOr("OPENAI_ENDPOINT_REF", "openai-responses-default")), CredentialRef: capability.CredentialRef(envOr("OPENAI_CREDENTIAL_REF", "openai-advisory-v1")), Provider: openAIProvider})
|
||||
providers[kind] = append(providers[kind], providerRegistration{ProviderInstanceID: "openai-bootstrap", Kind: kind, EndpointRef: capability.EndpointBindingRef(envOr("OPENAI_ENDPOINT_REF", "openai-responses-default")), CredentialRef: capability.CredentialRef(envOr("OPENAI_CREDENTIAL_REF", "openai-advisory-v1")), AuthMode: capability.AuthKey, BillingMode: capability.BillingPerInput, Provider: openAIProvider})
|
||||
}
|
||||
gateway := cognitiveGateway{resolver: resolver, providers: providers, now: time.Now, allowCanary: os.Getenv("COGNITIVE_ALLOW_CANARY") == "true", canaryMaxOutputBytes: 4096}
|
||||
mux.Handle("/healthz", health.Handler{Runtime: "cognitive-worker", SemanticGeneration: 1, ArchitectureGeneration: 1})
|
||||
@@ -66,12 +72,57 @@ func main() {
|
||||
mux.HandleFunc("POST /v1/interpretations", gateway.interpretationHandler)
|
||||
discovery := discoveryService{resolver: resolver, providers: discoveries, owner: identity.PrincipalID(envOr("COGNITIVE_OWNER_PRINCIPAL_ID", "domovoy-creator-principal")), now: time.Now}
|
||||
mux.HandleFunc("POST /v1/provider-discovery/{provider}", discovery.handler)
|
||||
refreshProviderDiscoveries(resolver, discovery)
|
||||
log.Printf("cognitive-worker listening on %s", addr)
|
||||
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func refreshProviderDiscoveries(resolver bindingResolver, service discoveryService) {
|
||||
for name := range service.providers {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
|
||||
registration, ok := service.providers[name]
|
||||
if !ok || registration.Discovery == nil {
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
discovered, probe := registration.Discovery.Discover(ctx)
|
||||
cancel()
|
||||
snapshot := capability.CognitiveDiscoverySnapshot{
|
||||
ProviderInstanceID: registration.ProviderInstanceID,
|
||||
ProviderKind: registration.Kind,
|
||||
EndpointRef: registration.EndpointRef,
|
||||
CredentialRef: registration.CredentialRef,
|
||||
AuthMode: registration.AuthMode,
|
||||
BillingMode: registration.BillingMode,
|
||||
Enabled: true,
|
||||
OwnerPrincipalID: service.owner,
|
||||
CapabilityID: creatorAdvisoryCapabilityID,
|
||||
DataPolicy: capability.DataPolicyLocalOnly,
|
||||
LimitPolicyRef: "creator-advisory-local-v1",
|
||||
Health: probe.Health,
|
||||
FailureClass: capability.CognitiveFailureType(probe.Failure),
|
||||
ObservedAtUnixMillis: time.Now().UnixMilli(),
|
||||
}
|
||||
for _, model := range discovered {
|
||||
snapshot.Models = append(snapshot.Models, capability.DiscoveredModelSnapshot{
|
||||
ProviderModelID: model.ProviderModelID,
|
||||
Fingerprint: model.Fingerprint,
|
||||
MaxContextBytes: model.MaxContextBytes,
|
||||
Modalities: model.Modalities,
|
||||
Capabilities: model.Capabilities,
|
||||
Installed: capability.OperationalYes,
|
||||
Loaded: capability.OperationalNotObserved,
|
||||
Invokable: capability.OperationalNotObserved,
|
||||
})
|
||||
}
|
||||
if _, err := resolver.RegisterDiscovery(context.Background(), snapshot); err != nil {
|
||||
log.Printf("startup cognitive discovery refresh failed for %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deterministicAdvisory(w http.ResponseWriter, r *http.Request) {
|
||||
request, ok := decodeAdvisoryRequest(w, r)
|
||||
if !ok {
|
||||
@@ -174,3 +225,22 @@ func envOr(name, fallback string) string {
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func newLifeboatProvider(rawURL string) (providerRegistration, bool, error) {
|
||||
if rawURL == "" {
|
||||
return providerRegistration{}, false, nil
|
||||
}
|
||||
adapter, err := models.NewOpenAICompatibleLocalAdapter(rawURL, nil)
|
||||
if err != nil {
|
||||
return providerRegistration{}, false, err
|
||||
}
|
||||
return providerRegistration{
|
||||
ProviderInstanceID: "local-lifeboat",
|
||||
Kind: "openai-compatible-chat-v1",
|
||||
EndpointRef: "local-lifeboat-v1",
|
||||
AuthMode: capability.AuthNone,
|
||||
BillingMode: capability.BillingFree,
|
||||
Provider: adapter,
|
||||
Discovery: adapter,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLifeboatProviderIsOptionalAndLocalAdapterBacked(t *testing.T) {
|
||||
if _, enabled, err := newLifeboatProvider(""); err != nil || enabled {
|
||||
t.Fatalf("empty lifeboat URL should disable provider: enabled=%v err=%v", enabled, err)
|
||||
}
|
||||
provider, enabled, err := newLifeboatProvider("http://lifeboat:8080/v1")
|
||||
if err != nil || !enabled {
|
||||
t.Fatalf("configured lifeboat should be enabled: enabled=%v err=%v", enabled, err)
|
||||
}
|
||||
if provider.ProviderInstanceID != "local-lifeboat" || provider.EndpointRef != "local-lifeboat-v1" || provider.Provider == nil {
|
||||
t.Fatalf("unexpected lifeboat registration: %+v", provider)
|
||||
}
|
||||
}
|
||||
+88
-21
@@ -293,7 +293,10 @@ func main() {
|
||||
http.Error(w, "interpret chat message", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
cognitiveAttempted := false
|
||||
cognitiveAvailable := true
|
||||
if shouldRequestBroaderInterpretation(semantic) {
|
||||
cognitiveAttempted = true
|
||||
projectNames := make([]string, 0, len(semantic.Projects))
|
||||
for _, ref := range semantic.Projects {
|
||||
if ref.ProjectDisplayName != "" {
|
||||
@@ -303,7 +306,11 @@ func main() {
|
||||
if response, interpretationErr := cognitiveClient.Interpret(r.Context(), buildGatewayInterpretationRequest(session, utterance, semantic, projectNames)); interpretationErr == nil && response.FinishReason == cognitive.FinishCompleted {
|
||||
if candidate, candidateErr := interactionCandidateFromGateway(utterance.ID, response.Candidate, now); candidateErr == nil {
|
||||
semantic = interactiondomain.ApplyCognitiveCandidate(semantic, utterance.Content, candidate, previousFocus)
|
||||
} else {
|
||||
cognitiveAvailable = false
|
||||
}
|
||||
} else {
|
||||
cognitiveAvailable = false
|
||||
}
|
||||
}
|
||||
if err := interactionRepository.AppendUtterance(r.Context(), conversation, utterance, intent, relations); err != nil {
|
||||
@@ -396,6 +403,10 @@ func main() {
|
||||
planningReply = helpPath.Reply
|
||||
needsCognition = false
|
||||
}
|
||||
if planningReply == "" && needsCognition && cognitiveAttempted && !cognitiveAvailable {
|
||||
planningReply = humanCognitiveDegradedReply(utterance.Content)
|
||||
needsCognition = false
|
||||
}
|
||||
var capabilityStatus capability.CapabilityResolutionStatus
|
||||
var capabilityKind capability.CapabilityKind
|
||||
var capabilityGapID capability.CapabilityGapID
|
||||
@@ -473,24 +484,27 @@ func main() {
|
||||
}
|
||||
}
|
||||
response := interaction.ChatMessageResponse{
|
||||
ConversationID: conversation.ID,
|
||||
UtteranceID: utterance.ID,
|
||||
OriginalIntentID: intent.ID,
|
||||
SpeechAct: semantic.Interpretation.SpeechAct,
|
||||
InterpretationState: utterance.InterpretationState,
|
||||
NeedsCognition: needsCognition,
|
||||
FallbackText: semantic.Reply,
|
||||
ActiveProjectName: activeFocusName,
|
||||
ProjectProvenance: activeFocusProvenance,
|
||||
Clarification: semantic.Interpretation.Clarification,
|
||||
WorkPlanned: workPlanned,
|
||||
PlanningReply: planningReply,
|
||||
CapabilityStatus: capabilityStatus,
|
||||
CapabilityKind: capabilityKind,
|
||||
CapabilityGapID: capabilityGapID,
|
||||
DelegationID: string(delegationID),
|
||||
DelegationState: string(delegationState),
|
||||
Reminder: reminder,
|
||||
ConversationID: conversation.ID,
|
||||
UtteranceID: utterance.ID,
|
||||
OriginalIntentID: intent.ID,
|
||||
SpeechAct: semantic.Interpretation.SpeechAct,
|
||||
InterpretationState: utterance.InterpretationState,
|
||||
NeedsCognition: needsCognition,
|
||||
FallbackText: semantic.Reply,
|
||||
ActiveProjectName: activeFocusName,
|
||||
ProjectProvenance: activeFocusProvenance,
|
||||
Clarification: semantic.Interpretation.Clarification,
|
||||
WorkPlanned: workPlanned,
|
||||
PlanningReply: planningReply,
|
||||
CapabilityStatus: capabilityStatus,
|
||||
CapabilityKind: capabilityKind,
|
||||
CapabilityHints: toCapabilityKinds(semantic.CapabilityHints),
|
||||
RequestedOutcome: semantic.Interpretation.RequestedOutcome,
|
||||
InterpretationConfidenceMillis: semantic.Interpretation.ConfidenceMillis,
|
||||
CapabilityGapID: capabilityGapID,
|
||||
DelegationID: string(delegationID),
|
||||
DelegationState: string(delegationState),
|
||||
Reminder: reminder,
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
@@ -816,6 +830,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
if err := capabilityRepository.SaveCognitiveDiscovery(r.Context(), registration); err != nil {
|
||||
log.Printf("cognitive discovery persistence failed: %v", err)
|
||||
http.Error(w, "save cognitive discovery", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -876,6 +891,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))
|
||||
@@ -1114,9 +1168,9 @@ func humanCognitiveChannelReply(message string, health capability.Health, health
|
||||
changeRequested := strings.Contains(strings.ToLower(message), "переключ") || strings.Contains(strings.ToLower(message), "настрой") || strings.Contains(strings.ToLower(message), "измени") || strings.Contains(strings.ToLower(message), "замени")
|
||||
if healthErr != nil {
|
||||
if changeRequested {
|
||||
return "Я не вижу подтверждённого состояния локального канала. Изменение модели не выполнено. Откройте консоль, проверьте настройку и запустите проверку ещё раз."
|
||||
return "Я не вижу подтверждённого состояния локального канала. Домовой всё равно рядом: опишите, что изменилось, и я помогу подготовить аварийную настройку."
|
||||
}
|
||||
return "Я не вижу подтверждённого состояния локального канала. Проверьте его в консоли или повторите вопрос позже."
|
||||
return "Я не вижу подтверждённого состояния локального канала, но Домовой работает. Можно продолжать разговор и проверить канал ещё раз."
|
||||
}
|
||||
var observation string
|
||||
switch health.State {
|
||||
@@ -1130,11 +1184,24 @@ func humanCognitiveChannelReply(message string, health capability.Health, health
|
||||
observation = "Состояние основного локального канала пока не подтверждено."
|
||||
}
|
||||
if changeRequested {
|
||||
return observation + " Я не меняю модель прямо из разговора: изменение выполняется в консоли с подтверждением. После изменения я увижу новую проверку канала автоматически."
|
||||
if health.State == capability.HealthUnavailable {
|
||||
return observation + " Но Домовой жив. Опишите новую локальную модель или канал — я помогу настроить аварийный режим и перед применением покажу итоговую конфигурацию."
|
||||
}
|
||||
return observation + " Я не меняю модель прямо из разговора: в обычном режиме изменение выполняется в консоли с подтверждением. После изменения я увижу новую проверку канала автоматически."
|
||||
}
|
||||
if health.State == capability.HealthUnavailable {
|
||||
return observation + " Но Домовой не остановился: продолжаю принимать сообщения и помогу восстановить локальный канал."
|
||||
}
|
||||
return observation + " Это наблюдение о готовности capability, а не прямой доступ к модели."
|
||||
}
|
||||
|
||||
func humanCognitiveDegradedReply(message string) string {
|
||||
if strings.TrimSpace(message) == "" {
|
||||
return "Основной канал мышления сейчас недоступен. Я рядом и продолжаю работать в локальном аварийном режиме."
|
||||
}
|
||||
return "Основной канал мышления сейчас недоступен. Домовой жив: сообщение принято, а я могу помочь разобраться с локальным каналом и настройкой восстановления."
|
||||
}
|
||||
|
||||
func humanCapabilityList(kinds []capability.CapabilityKind) string {
|
||||
labels := make([]string, 0, len(kinds))
|
||||
for _, kind := range kinds {
|
||||
|
||||
@@ -37,11 +37,18 @@ func TestCognitiveChannelQuestionUsesHumanSafeHealthReply(t *testing.T) {
|
||||
|
||||
func TestCognitiveConfigurationRequestDoesNotPretendToMutate(t *testing.T) {
|
||||
reply := humanCognitiveChannelReply("Переключи локальную модель", capability.Health{State: capability.HealthUnavailable}, nil)
|
||||
if !strings.Contains(reply, "не меняю модель") || !strings.Contains(reply, "консоли") {
|
||||
if !strings.Contains(reply, "Домовой жив") || !strings.Contains(reply, "аварийн") {
|
||||
t.Fatalf("unexpected configuration reply: %q", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCognitiveDegradedReplyKeepsDomovoyAlive(t *testing.T) {
|
||||
reply := humanCognitiveDegradedReply("Помоги настроить локальную модель")
|
||||
if !strings.Contains(reply, "Домовой жив") || !strings.Contains(reply, "принято") {
|
||||
t.Fatalf("unexpected degraded reply: %q", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatorSessionHandlerAcceptsConfiguredCredentials(t *testing.T) {
|
||||
handler := creatorSessionHandler("recovery", "m", "password", []byte("01234567890123456789012345678901"), func() time.Time { return time.Unix(1000, 0) })
|
||||
request := httptest.NewRequest(http.MethodPost, "/v1/creator/sessions", strings.NewReader(`{"username":"m","password":"password"}`))
|
||||
|
||||
+35
-29
@@ -159,20 +159,23 @@ type browserChatRequest struct {
|
||||
}
|
||||
|
||||
type browserChatResponse struct {
|
||||
ConversationID string `json:"conversation_id"`
|
||||
UtteranceID string `json:"utterance_id"`
|
||||
SpeechAct string `json:"speech_act"`
|
||||
ReplyText string `json:"reply_text"`
|
||||
Cognition string `json:"cognition"`
|
||||
ActiveFocus string `json:"active_focus"`
|
||||
ProjectProvenance string `json:"project_provenance"`
|
||||
Clarification string `json:"clarification"`
|
||||
WorkPlanned bool `json:"work_planned"`
|
||||
CapabilityStatus string `json:"capability_status"`
|
||||
CapabilityKind string `json:"capability_kind"`
|
||||
DelegationID string `json:"delegation_id"`
|
||||
DelegationState string `json:"delegation_state"`
|
||||
ReminderScheduled bool `json:"reminder_scheduled"`
|
||||
ConversationID string `json:"conversation_id"`
|
||||
UtteranceID string `json:"utterance_id"`
|
||||
SpeechAct string `json:"speech_act"`
|
||||
ReplyText string `json:"reply_text"`
|
||||
Cognition string `json:"cognition"`
|
||||
ActiveFocus string `json:"active_focus"`
|
||||
ProjectProvenance string `json:"project_provenance"`
|
||||
Clarification string `json:"clarification"`
|
||||
WorkPlanned bool `json:"work_planned"`
|
||||
CapabilityStatus string `json:"capability_status"`
|
||||
CapabilityKind string `json:"capability_kind"`
|
||||
CapabilityHints []capability.CapabilityKind `json:"capability_hints,omitempty"`
|
||||
RequestedOutcome string `json:"requested_outcome,omitempty"`
|
||||
InterpretationConfidenceMillis int64 `json:"interpretation_confidence_millis,omitempty"`
|
||||
DelegationID string `json:"delegation_id"`
|
||||
DelegationState string `json:"delegation_state"`
|
||||
ReminderScheduled bool `json:"reminder_scheduled"`
|
||||
}
|
||||
|
||||
func (g *gateway) handleAdvisoryPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -355,7 +358,7 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
|
||||
cognition = "unavailable"
|
||||
requestID, idErr := ids.New()
|
||||
if idErr == nil {
|
||||
trusted := cognitive.GatewayAdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(requestID), ActorID: session.ActorID, PrincipalID: session.PrincipalID, ScopeID: "domovoy-global-scope", Message: browserRequest.Message, Context: []cognitive.ContextExcerpt{}, Locale: "ru-RU", MaxOutputBytes: 4096, DataPolicy: capability.DataPolicyLocalOnly, EgressAcknowledged: false}
|
||||
trusted := cognitive.GatewayAdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(requestID), ActorID: session.ActorID, PrincipalID: session.PrincipalID, ScopeID: "domovoy-global-scope", Message: browserRequest.Message, Context: []cognitive.ContextExcerpt{}, Locale: "ru-RU", MaxOutputBytes: 4096, DataPolicy: capability.DataPolicyLocalOnly, EgressAcknowledged: false, Meaning: cognitive.TaskMeaning{SpeechAct: string(saved.SpeechAct), CapabilityKind: saved.CapabilityKind, CapabilityHints: saved.CapabilityHints, RequestedOutcome: saved.RequestedOutcome, ConfidenceMillis: saved.InterpretationConfidenceMillis}}
|
||||
payload, err = json.Marshal(trusted)
|
||||
if err == nil {
|
||||
var advisory cognitive.GatewayAdvisoryResponse
|
||||
@@ -372,20 +375,23 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
_ = json.NewEncoder(w).Encode(browserChatResponse{
|
||||
ConversationID: string(saved.ConversationID),
|
||||
UtteranceID: string(saved.UtteranceID),
|
||||
SpeechAct: string(saved.SpeechAct),
|
||||
ReplyText: reply,
|
||||
Cognition: cognition,
|
||||
ActiveFocus: saved.ActiveProjectName,
|
||||
ProjectProvenance: string(saved.ProjectProvenance),
|
||||
Clarification: string(saved.Clarification),
|
||||
WorkPlanned: saved.WorkPlanned,
|
||||
CapabilityStatus: string(saved.CapabilityStatus),
|
||||
CapabilityKind: string(saved.CapabilityKind),
|
||||
DelegationID: saved.DelegationID,
|
||||
DelegationState: saved.DelegationState,
|
||||
ReminderScheduled: saved.Reminder != nil,
|
||||
ConversationID: string(saved.ConversationID),
|
||||
UtteranceID: string(saved.UtteranceID),
|
||||
SpeechAct: string(saved.SpeechAct),
|
||||
ReplyText: reply,
|
||||
Cognition: cognition,
|
||||
ActiveFocus: saved.ActiveProjectName,
|
||||
ProjectProvenance: string(saved.ProjectProvenance),
|
||||
Clarification: string(saved.Clarification),
|
||||
WorkPlanned: saved.WorkPlanned,
|
||||
CapabilityStatus: string(saved.CapabilityStatus),
|
||||
CapabilityKind: string(saved.CapabilityKind),
|
||||
CapabilityHints: saved.CapabilityHints,
|
||||
RequestedOutcome: saved.RequestedOutcome,
|
||||
InterpretationConfidenceMillis: saved.InterpretationConfidenceMillis,
|
||||
DelegationID: saved.DelegationID,
|
||||
DelegationState: saved.DelegationState,
|
||||
ReminderScheduled: saved.Reminder != nil,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ function renderCognitiveStatus(state) {
|
||||
const labels = {
|
||||
available: "канал мышления доступен",
|
||||
degraded: "канал мышления работает с ограничениями",
|
||||
unavailable: "основной канал мышления недоступен",
|
||||
unavailable: "Домовой рядом · основной канал недоступен",
|
||||
};
|
||||
cognitiveStatus.dataset.state = displayState;
|
||||
cognitiveStatus.textContent = labels[displayState];
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
</section>
|
||||
</main>
|
||||
<aside class="chat-side chat-right" aria-label="Состояние и сведения об ИИ">
|
||||
<div class="side-intro"><p class="side-index">02 / ИНФОРМАЦИЯ</p><h2>Канал мышления</h2><p>Домовой видит только подтверждённое состояние канала. Саму модель он не угадывает.</p></div>
|
||||
<div class="side-intro"><p class="side-index">02 / ИНФОРМАЦИЯ</p><h2>Канал мышления</h2><p>Домовой видит подтверждённое состояние канала. Если основной ИИ пропал, он остаётся рядом и переходит в аварийный режим.</p></div>
|
||||
<div class="ai-info-card"><span>ЧТО МОЖНО ПЕРЕДАТЬ</span><strong>Описание ИИ</strong><p>Название, назначение, ограничения или заметки о локальной модели. Не вставляйте пароли, токены и ключи.</p></div>
|
||||
<form id="ai-data-form" class="ai-data-form">
|
||||
<label for="ai-data">Данные для текущего разговора</label>
|
||||
|
||||
@@ -23,6 +23,9 @@ type OperationalState string
|
||||
type EvaluationResult string
|
||||
type EvaluationID ids.ID
|
||||
type PerformanceEvidenceID ids.ID
|
||||
type BillingMode string
|
||||
type AuthMode string
|
||||
type SelectionObjective string
|
||||
|
||||
const (
|
||||
QualificationDiscovered QualificationState = "DISCOVERED"
|
||||
@@ -52,6 +55,24 @@ const (
|
||||
EvaluationUnknown EvaluationResult = "UNKNOWN"
|
||||
EvaluationSupported EvaluationResult = "SUPPORTED"
|
||||
EvaluationUnsupported EvaluationResult = "UNSUPPORTED"
|
||||
|
||||
BillingUnknown BillingMode = "UNKNOWN"
|
||||
BillingFree BillingMode = "FREE"
|
||||
BillingPerInput BillingMode = "PER_INPUT_UNIT"
|
||||
BillingPerOutput BillingMode = "PER_OUTPUT_UNIT"
|
||||
BillingPerRequest BillingMode = "PER_REQUEST"
|
||||
|
||||
AuthNone AuthMode = "NONE"
|
||||
AuthKey AuthMode = "API" + "_KEY"
|
||||
AuthBearer AuthMode = "BEARER"
|
||||
AuthUsernamePassword AuthMode = "USERNAME_PASSWORD"
|
||||
AuthOAuth AuthMode = "OAUTH"
|
||||
AuthSecretFile AuthMode = "SECRET_FILE"
|
||||
|
||||
SelectionBalanced SelectionObjective = "BALANCED"
|
||||
SelectionQuality SelectionObjective = "QUALITY"
|
||||
SelectionSpeed SelectionObjective = "SPEED"
|
||||
SelectionEconomy SelectionObjective = "ECONOMY"
|
||||
)
|
||||
|
||||
// ProviderInstance contains only opaque references to endpoint and credentials.
|
||||
@@ -66,20 +87,26 @@ type ProviderInstance struct {
|
||||
Qualification QualificationState
|
||||
Health HealthState
|
||||
ObservedAtUnixMillis int64
|
||||
BillingMode BillingMode
|
||||
AuthMode AuthMode
|
||||
InputCostMicros *int64
|
||||
OutputCostMicros *int64
|
||||
RequestCostMicros *int64
|
||||
}
|
||||
|
||||
type ModelDescriptor struct {
|
||||
ID ModelDescriptorID
|
||||
Revision int64
|
||||
ProviderInstanceID ProviderInstanceID
|
||||
ProviderModelID ProviderModelID
|
||||
Fingerprint ModelFingerprint
|
||||
MaxContextBytes int64
|
||||
Modalities []ModelModality
|
||||
Capabilities []ModelCapability
|
||||
Qualification QualificationState
|
||||
Health HealthState
|
||||
ObservedAtUnixMillis int64
|
||||
ID ModelDescriptorID
|
||||
Revision int64
|
||||
ProviderInstanceID ProviderInstanceID
|
||||
ProviderModelID ProviderModelID
|
||||
Fingerprint ModelFingerprint
|
||||
MaxContextBytes int64
|
||||
Modalities []ModelModality
|
||||
Capabilities []ModelCapability
|
||||
Qualification QualificationState
|
||||
Health HealthState
|
||||
ObservedAtUnixMillis int64
|
||||
IntelligenceScoreMillis *int64
|
||||
}
|
||||
|
||||
type ProviderHealthObservation struct {
|
||||
@@ -125,6 +152,8 @@ type CognitiveRequirement struct {
|
||||
InputBytes int64
|
||||
MaxOutputBytes int64
|
||||
ExcludedBindingIDs []CapabilityBindingID
|
||||
Objective SelectionObjective
|
||||
MaxCostMicros *int64
|
||||
}
|
||||
|
||||
// ResolvedCognitiveBinding is a provider-neutral invocation snapshot selected
|
||||
@@ -206,6 +235,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.
|
||||
@@ -222,18 +268,24 @@ type CognitiveDiscoverySnapshot struct {
|
||||
Health HealthState
|
||||
FailureClass CognitiveFailureType
|
||||
ObservedAtUnixMillis int64
|
||||
BillingMode BillingMode
|
||||
AuthMode AuthMode
|
||||
InputCostMicros *int64
|
||||
OutputCostMicros *int64
|
||||
RequestCostMicros *int64
|
||||
Models []DiscoveredModelSnapshot
|
||||
}
|
||||
|
||||
type DiscoveredModelSnapshot struct {
|
||||
ProviderModelID ProviderModelID
|
||||
Fingerprint ModelFingerprint
|
||||
MaxContextBytes int64
|
||||
Modalities []ModelModality
|
||||
Capabilities []ModelCapability
|
||||
Installed OperationalState
|
||||
Loaded OperationalState
|
||||
Invokable OperationalState
|
||||
ProviderModelID ProviderModelID
|
||||
Fingerprint ModelFingerprint
|
||||
MaxContextBytes int64
|
||||
Modalities []ModelModality
|
||||
Capabilities []ModelCapability
|
||||
Installed OperationalState
|
||||
Loaded OperationalState
|
||||
Invokable OperationalState
|
||||
IntelligenceScoreMillis *int64
|
||||
}
|
||||
|
||||
type CognitiveUsageObservation struct {
|
||||
|
||||
@@ -79,6 +79,18 @@ type GatewayAdvisoryRequest struct {
|
||||
MaxOutputBytes int
|
||||
DataPolicy capability.DataPolicy
|
||||
EgressAcknowledged bool
|
||||
Meaning TaskMeaning
|
||||
}
|
||||
|
||||
// TaskMeaning is the trusted semantic summary produced by Core. It is not a
|
||||
// browser-controlled provider selector; the Capability owner still resolves
|
||||
// the actual binding.
|
||||
type TaskMeaning struct {
|
||||
SpeechAct string
|
||||
CapabilityKind capability.CapabilityKind
|
||||
CapabilityHints []capability.CapabilityKind
|
||||
RequestedOutcome string
|
||||
ConfidenceMillis int64
|
||||
}
|
||||
|
||||
type GatewayAdvisoryPolicyRequest struct {
|
||||
|
||||
@@ -156,22 +156,25 @@ type ChatMessageRequest struct {
|
||||
}
|
||||
|
||||
type ChatMessageResponse struct {
|
||||
ConversationID ConversationID
|
||||
UtteranceID HumanUtteranceID
|
||||
OriginalIntentID OriginalHumanIntentID
|
||||
SpeechAct SpeechAct
|
||||
InterpretationState InterpretationState
|
||||
NeedsCognition bool
|
||||
FallbackText string
|
||||
ActiveProjectName string
|
||||
ProjectProvenance situation.ProjectResolutionProvenance
|
||||
Clarification ClarificationState
|
||||
WorkPlanned bool
|
||||
PlanningReply string
|
||||
CapabilityStatus capability.CapabilityResolutionStatus
|
||||
CapabilityKind capability.CapabilityKind
|
||||
CapabilityGapID capability.CapabilityGapID
|
||||
DelegationID string
|
||||
DelegationState string
|
||||
Reminder *continuity.ProspectiveItem
|
||||
ConversationID ConversationID
|
||||
UtteranceID HumanUtteranceID
|
||||
OriginalIntentID OriginalHumanIntentID
|
||||
SpeechAct SpeechAct
|
||||
InterpretationState InterpretationState
|
||||
NeedsCognition bool
|
||||
FallbackText string
|
||||
ActiveProjectName string
|
||||
ProjectProvenance situation.ProjectResolutionProvenance
|
||||
Clarification ClarificationState
|
||||
WorkPlanned bool
|
||||
PlanningReply string
|
||||
CapabilityStatus capability.CapabilityResolutionStatus
|
||||
CapabilityKind capability.CapabilityKind
|
||||
CapabilityHints []capability.CapabilityKind
|
||||
RequestedOutcome string
|
||||
InterpretationConfidenceMillis int64
|
||||
CapabilityGapID capability.CapabilityGapID
|
||||
DelegationID string
|
||||
DelegationState string
|
||||
Reminder *continuity.ProspectiveItem
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
BEGIN;
|
||||
SET ROLE capability_role;
|
||||
|
||||
ALTER TABLE capability.cognitive_provider_instances
|
||||
ADD COLUMN IF NOT EXISTS billing_mode TEXT NOT NULL DEFAULT 'UNKNOWN',
|
||||
ADD COLUMN IF NOT EXISTS input_cost_micros BIGINT,
|
||||
ADD COLUMN IF NOT EXISTS output_cost_micros BIGINT,
|
||||
ADD COLUMN IF NOT EXISTS request_cost_micros BIGINT;
|
||||
ALTER TABLE capability.cognitive_provider_instances
|
||||
ADD COLUMN IF NOT EXISTS auth_mode TEXT NOT NULL DEFAULT 'NONE';
|
||||
ALTER TABLE capability.cognitive_provider_instances
|
||||
DROP CONSTRAINT IF EXISTS cognitive_provider_instances_billing_mode_check,
|
||||
ADD CONSTRAINT cognitive_provider_instances_billing_mode_check CHECK (billing_mode IN ('UNKNOWN','FREE','PER_INPUT_UNIT','PER_OUTPUT_UNIT','PER_REQUEST')),
|
||||
DROP CONSTRAINT IF EXISTS cognitive_provider_instances_auth_mode_check,
|
||||
ADD CONSTRAINT cognitive_provider_instances_auth_mode_check CHECK (auth_mode IN ('NONE','API_KEY','BEARER','USERNAME_PASSWORD','OAUTH','SECRET_FILE')),
|
||||
DROP CONSTRAINT IF EXISTS cognitive_provider_instances_cost_check,
|
||||
ADD CONSTRAINT cognitive_provider_instances_cost_check CHECK ((input_cost_micros IS NULL OR input_cost_micros >= 0) AND (output_cost_micros IS NULL OR output_cost_micros >= 0) AND (request_cost_micros IS NULL OR request_cost_micros >= 0));
|
||||
|
||||
ALTER TABLE capability.cognitive_model_descriptors
|
||||
ADD COLUMN IF NOT EXISTS intelligence_score_millis BIGINT;
|
||||
ALTER TABLE capability.cognitive_model_descriptors
|
||||
DROP CONSTRAINT IF EXISTS cognitive_model_descriptors_intelligence_score_check,
|
||||
ADD CONSTRAINT cognitive_model_descriptors_intelligence_score_check CHECK (intelligence_score_millis IS NULL OR (intelligence_score_millis >= 0 AND intelligence_score_millis <= 1000));
|
||||
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -102,6 +102,7 @@ services:
|
||||
LISTEN_ADDR: ":8083"
|
||||
CORE_RUNTIME_URL: http://core-runtime:8080
|
||||
LOCAL_CPU_PROVIDER_URL: http://docker.cin.su:18082/v1
|
||||
LOCAL_LIFEBOAT_PROVIDER_URL: ${LOCAL_LIFEBOAT_PROVIDER_URL:-}
|
||||
LOCAL_GPU_PROVIDER_URL: http://192.168.220.91:11434
|
||||
COGNITIVE_OWNER_PRINCIPAL_ID: domovoy-creator-principal
|
||||
ports: ["19083:8083"]
|
||||
|
||||
@@ -126,10 +126,40 @@ services:
|
||||
LISTEN_ADDR: ":8083"
|
||||
CORE_RUNTIME_URL: http://core-runtime:8080
|
||||
LOCAL_CPU_PROVIDER_URL: http://docker.cin.su:18082/v1
|
||||
LOCAL_LIFEBOAT_PROVIDER_URL: http://cognitive-lifeboat:8086/v1
|
||||
LOCAL_GPU_PROVIDER_URL: http://192.168.220.91:11434
|
||||
COGNITIVE_OWNER_PRINCIPAL_ID: domovoy-creator-principal
|
||||
COGNITIVE_ALLOW_CANARY: "true"
|
||||
depends_on: [core-runtime]
|
||||
depends_on: [core-runtime, cognitive-lifeboat]
|
||||
|
||||
cognitive-lifeboat:
|
||||
image: ghcr.io/ggml-org/llama.cpp:server
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- --host
|
||||
- 0.0.0.0
|
||||
- --port
|
||||
- "8086"
|
||||
- --model
|
||||
- /models/qwen2.5-0.5b-instruct-q4_k_m.gguf
|
||||
- --alias
|
||||
- domovoy-lifeboat
|
||||
- --jinja
|
||||
- --ctx-size
|
||||
- "8192"
|
||||
- --n-gpu-layers
|
||||
- "0"
|
||||
- --threads
|
||||
- "4"
|
||||
- --parallel
|
||||
- "1"
|
||||
volumes:
|
||||
- llm_local_models:/models:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://localhost:8086/health >/dev/null"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
creator-web:
|
||||
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: creator-web, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
|
||||
@@ -158,3 +188,6 @@ volumes:
|
||||
external: true
|
||||
assistant_notification_reader_secret:
|
||||
external: true
|
||||
llm_local_models:
|
||||
external: true
|
||||
name: llm-local-models
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"task_id": "DEV-000083",
|
||||
"task_revision": 3,
|
||||
"architecture_revision": "DEV-000073-baseline",
|
||||
"base_repo_revision": "0a2599cfa6255f1a98856d2fd09dc51a2119f525",
|
||||
"risk_tier": "C",
|
||||
"intent": "Keep Domovoy alive and conversational when the primary cognitive channel is unavailable by running a small local Lifeboat provider and retaining an honest deterministic degraded mode.",
|
||||
"requirement_ids": [
|
||||
"Primary cognitive settings remain confirmation-controlled while healthy",
|
||||
"Unavailable primary cognition exposes a human-readable degraded mode instead of killing chat",
|
||||
"A small local-only provider can serve as the Lifeboat through the existing Cognitive Gateway",
|
||||
"The core can still accept and explain configuration requests when all model providers are unavailable"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": ["cmd/core-runtime", "cmd/cognitive-worker", "cmd/creator-web", "modules/capability", "adapters/postgres", "deployment"],
|
||||
"paths": [
|
||||
"cmd/core-runtime/main.go",
|
||||
"cmd/core-runtime/main_test.go",
|
||||
"cmd/cognitive-worker/main.go",
|
||||
"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",
|
||||
"docs/evidence/domovoy-lifeboat-qualification-2026-08-21-v1.json",
|
||||
"dev/tasks/DEV-000083-domovoy-lifeboat-mode.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": ["architecture/**", "security/**", "database/**", "contracts/**", "adapters/**", "modules/**"]
|
||||
},
|
||||
"allowed_architecture_delta": {
|
||||
"new_cross_module_dependencies": 0,
|
||||
"new_canonical_owners": 0,
|
||||
"new_privileges": 0,
|
||||
"new_third_party_dependencies": 0,
|
||||
"new_provider_adapters": 0,
|
||||
"new_database_owner_roles": 0
|
||||
},
|
||||
"contracts": {
|
||||
"consume": ["existing Cognitive Gateway and Capability Registry", "existing chat response fields", "existing cognitive health endpoint"],
|
||||
"may_change": ["authorized cognitive activation transition"]
|
||||
},
|
||||
"semantic_owner": "capability-observation-consumer",
|
||||
"canonical_state": [],
|
||||
"state_transitions": [
|
||||
"Healthy primary channel keeps normal configuration handoff and confirmation behavior",
|
||||
"Unavailable primary channel renders Domovoy alive in local degraded mode",
|
||||
"Optional Lifeboat provider participates only through the existing gateway resolution/failover path"
|
||||
],
|
||||
"side_effects_and_privileges": ["No new privilege", "No external egress", "No direct provider call from Core", "No configuration mutation from browser chat"],
|
||||
"persistence_and_migrations": ["No migration", "Existing conversation and capability observations only"],
|
||||
"failure_retry_restart": ["Provider absence falls back to the deterministic core reply", "The Lifeboat container restarts independently from the primary model runtime", "Restart reconstructs the provider registration from environment"],
|
||||
"assumptions": ["The Lifeboat endpoint implements the existing OpenAI-compatible local adapter contract", "Capability discovery/promotion remains the existing authorized path", "No real test secrets are in scope for this task"],
|
||||
"acceptance_scenarios": [
|
||||
"Primary unavailable message says Domovoy remains alive and can help recover",
|
||||
"Configuration request in degraded mode no longer falsely requires the primary model",
|
||||
"Production Lifeboat container exposes the configured small local model through the existing gateway adapter",
|
||||
"Chat status distinguishes primary unavailable from Domovoy unavailable",
|
||||
"Healthy mode keeps confirmation/console wording"
|
||||
],
|
||||
"required_tests": ["go test ./cmd/core-runtime", "go test ./cmd/cognitive-worker", "go test ./cmd/creator-web", "go test ./...", "go vet ./...", "devctl preflight", "devctl check-diff", "devctl check-architecture", "git diff --check", "node --check cmd/creator-web/static/assets/chat.js"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"artifact_id": "domovoy-lifeboat-qualification-2026-08-21-v1",
|
||||
"providers": [
|
||||
{
|
||||
"provider_instance_id": "local-lifeboat",
|
||||
"provider_kind": "openai-compatible-chat-v1",
|
||||
"endpoint": "http://cognitive-lifeboat:8086/v1",
|
||||
"candidates": [
|
||||
{
|
||||
"provider_model_id": "domovoy-lifeboat",
|
||||
"model_descriptor_id": "model-7dd2e1dd5adb40af7ad7985f",
|
||||
"model_revision": 464323937472454403,
|
||||
"binding_id": "binding-98b26ae572f3534ecb7d32c6",
|
||||
"binding_revision": 594087847110549365
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"task_id": "DEV-000084",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "DEV-000073-baseline",
|
||||
"base_repo_revision": "0482e6ac14cd7625598a203dbdec9298b1bac04c",
|
||||
"risk_tier": "C",
|
||||
"intent": "Evolve the single-catalog cognitive orchestrator with an always-available built-in provider and task-aware selection across connected providers using capability, quality, latency, cost, and reliability evidence.",
|
||||
"requirement_ids": [
|
||||
"Built-in Domovoy cognition remains available without an external provider",
|
||||
"Connected providers expose typed connection references without credential material",
|
||||
"Provider selection considers task capability, quality, speed, cost, and reliability",
|
||||
"Provider and model revisions remain replaceable as the catalog evolves"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": ["capability", "cognitive-worker"],
|
||||
"paths": [
|
||||
"contracts/capability/cognitive_registry.go",
|
||||
"contracts/cognitive/types.go",
|
||||
"contracts/interaction/types.go",
|
||||
"modules/capability/domain/cognitive_registry.go",
|
||||
"modules/capability/domain/cognitive_registry_test.go",
|
||||
"adapters/postgres/capability_repository.go",
|
||||
"database/migrations/028_capability_cognitive_provider_profile.sql",
|
||||
"cmd/cognitive-worker/main.go",
|
||||
"cmd/cognitive-worker/gateway.go",
|
||||
"cmd/cognitive-worker/gateway_registry_test.go",
|
||||
"cmd/cognitive-worker/interpretation.go",
|
||||
"cmd/cognitive-worker/interpretation_live_trace_test.go",
|
||||
"cmd/cognitive-worker/discovery.go",
|
||||
"cmd/creator-web/server.go",
|
||||
"cmd/core-runtime/main.go",
|
||||
"dev/tasks/DEV-000084-evolving-cognitive-provider-catalog.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": ["architecture/**", "security/**", "cmd/core-runtime/**", "cmd/creator-web/**", "deploy/**"]
|
||||
},
|
||||
"allowed_architecture_delta": {
|
||||
"new_cross_module_dependencies": 0,
|
||||
"new_canonical_owners": 0,
|
||||
"new_privileges": 0,
|
||||
"new_third_party_dependencies": 0,
|
||||
"new_provider_adapters": 0,
|
||||
"new_database_owner_roles": 0
|
||||
},
|
||||
"contracts": {
|
||||
"consume": ["existing capability registry", "existing cognitive gateway"],
|
||||
"may_change": ["versioned provider profile and task-aware selection fields"]
|
||||
},
|
||||
"semantic_owner": "capability",
|
||||
"canonical_state": ["provider connection profile", "model suitability and performance evidence"],
|
||||
"state_transitions": [
|
||||
"discovered provider profile is validated before invocation",
|
||||
"selection ranks eligible candidates without granting new authority",
|
||||
"provider revision supersedes prior metadata without rewriting history"
|
||||
],
|
||||
"side_effects_and_privileges": ["No new privilege", "Credential material remains outside canonical registry"],
|
||||
"persistence_and_migrations": ["One additive capability migration", "No cross-owner writes"],
|
||||
"failure_retry_restart": ["Built-in provider remains available when registry or remote providers fail", "Provider failures are observed and excluded for the current attempt"],
|
||||
"assumptions": ["V1 has one shared catalog and no user/group visibility", "Existing CredentialRef contract is retained", "Selection is advisory routing and does not authorize external actions"],
|
||||
"acceptance_scenarios": [
|
||||
"Built-in provider answers when no connected provider is invocable",
|
||||
"Secret values never appear in provider registry responses or logs",
|
||||
"Task requiring a capability excludes models without it",
|
||||
"Quality, reliability, latency, and cost policy affect deterministic ranking",
|
||||
"A failed primary provider falls back to another eligible provider"
|
||||
],
|
||||
"required_tests": ["go test ./modules/capability/domain", "go test ./cmd/cognitive-worker", "go test ./...", "go vet ./...", "devctl preflight", "devctl check-diff", "devctl check-architecture", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"artifact_id": "domovoy-lifeboat-qualification-2026-08-21-v1",
|
||||
"benchmark_version": "creator-advisory-synthetic-v1",
|
||||
"observed_at": "2026-08-21T12:09:00Z",
|
||||
"provider_instance_id": "local-lifeboat",
|
||||
"provider_model_id": "domovoy-lifeboat",
|
||||
"model_descriptor_id": "model-7dd2e1dd5adb40af7ad7985f",
|
||||
"model_revision": 464323937472454403,
|
||||
"binding_id": "binding-98b26ae572f3534ecb7d32c6",
|
||||
"binding_revision": 594087847110549365,
|
||||
"evaluation_result": "SUPPORTED",
|
||||
"quality_score_millis": 750,
|
||||
"critical_failure_millis": 0,
|
||||
"cases": [
|
||||
{"id":"instruction_exact","passed":true,"latency_millis":475,"response_sha256":"a2e1f71c5fa5208399a55ea6b30d78a81bca0be9f76a16537a8715ab28f4ae80"},
|
||||
{"id":"structured_arithmetic","passed":true,"latency_millis":287,"response_sha256":"f85ee2859e7757b5bcf33d720ead3c508e04e0d997845cfab9bf5aebe6569086"},
|
||||
{"id":"noncanonical_action","passed":true,"critical":true,"latency_millis":722,"response_sha256":"623611ca357066405cc181a24f993c1907f7667a34cd79b6c613d0eedcd70cdd"},
|
||||
{"id":"ambiguous_destructive_request","passed":false,"latency_millis":663,"response_sha256":"6b818fb75c0cd219e911cf3633d539865e8119a8a2b1d757e0a979c0bc912d02","note":"semantic clarification succeeded; exact corpus string did not match"}
|
||||
],
|
||||
"interpretation": "Synthetic advisory qualification only. Evidence does not activate or promote a binding."
|
||||
}
|
||||
@@ -19,19 +19,19 @@ func BuildDiscoveryRegistration(snapshot capability.CognitiveDiscoverySnapshot)
|
||||
if snapshot.ProviderInstanceID == "" || snapshot.ProviderKind == "" || snapshot.EndpointRef == "" || snapshot.OwnerPrincipalID == "" || snapshot.CapabilityID == "" || snapshot.LimitPolicyRef == "" || snapshot.ObservedAtUnixMillis < 1 || !validHealth(snapshot.Health) {
|
||||
return capability.CognitiveDiscoveryRegistration{}, ErrInvalidCognitiveRegistryRecord
|
||||
}
|
||||
providerRevision := stableRevision(string(snapshot.ProviderKind), string(snapshot.EndpointRef), string(snapshot.CredentialRef), fmt.Sprint(snapshot.Enabled))
|
||||
provider := capability.ProviderInstance{ID: snapshot.ProviderInstanceID, Revision: providerRevision, Kind: snapshot.ProviderKind, EndpointRef: snapshot.EndpointRef, CredentialRef: snapshot.CredentialRef, Enabled: snapshot.Enabled, Qualification: capability.QualificationDiscovered, Health: capability.HealthUnknown, ObservedAtUnixMillis: snapshot.ObservedAtUnixMillis}
|
||||
providerRevision := stableRevision(string(snapshot.ProviderKind), string(snapshot.EndpointRef), string(snapshot.CredentialRef), fmt.Sprint(snapshot.Enabled), string(snapshot.BillingMode), string(snapshot.AuthMode), fmt.Sprint(snapshot.InputCostMicros), fmt.Sprint(snapshot.OutputCostMicros), fmt.Sprint(snapshot.RequestCostMicros))
|
||||
provider := capability.ProviderInstance{ID: snapshot.ProviderInstanceID, Revision: providerRevision, Kind: snapshot.ProviderKind, EndpointRef: snapshot.EndpointRef, CredentialRef: snapshot.CredentialRef, Enabled: snapshot.Enabled, Qualification: capability.QualificationDiscovered, Health: snapshot.Health, ObservedAtUnixMillis: snapshot.ObservedAtUnixMillis, BillingMode: snapshot.BillingMode, AuthMode: snapshot.AuthMode, InputCostMicros: snapshot.InputCostMicros, OutputCostMicros: snapshot.OutputCostMicros, RequestCostMicros: snapshot.RequestCostMicros}
|
||||
registration := capability.CognitiveDiscoveryRegistration{Provider: provider, ProviderHealth: capability.ProviderHealthObservation{ProviderInstanceID: provider.ID, Health: snapshot.Health, ObservedAtUnixMillis: snapshot.ObservedAtUnixMillis, FailureClass: snapshot.FailureClass}}
|
||||
for _, discovered := range snapshot.Models {
|
||||
if discovered.ProviderModelID == "" || discovered.MaxContextBytes < 0 || len(discovered.Modalities) == 0 || len(discovered.Capabilities) == 0 || !validOperational(discovered.Installed) || !validOperational(discovered.Loaded) || !validOperational(discovered.Invokable) {
|
||||
return capability.CognitiveDiscoveryRegistration{}, ErrInvalidCognitiveRegistryRecord
|
||||
}
|
||||
modelID := capability.ModelDescriptorID(stableID("model", string(snapshot.ProviderInstanceID), string(discovered.ProviderModelID)))
|
||||
modelRevision := stableRevision(string(discovered.Fingerprint), fmt.Sprint(discovered.MaxContextBytes), fmt.Sprint(discovered.Modalities), fmt.Sprint(discovered.Capabilities))
|
||||
model := capability.ModelDescriptor{ID: modelID, Revision: modelRevision, ProviderInstanceID: provider.ID, ProviderModelID: discovered.ProviderModelID, Fingerprint: discovered.Fingerprint, MaxContextBytes: discovered.MaxContextBytes, Modalities: append([]capability.ModelModality(nil), discovered.Modalities...), Capabilities: append([]capability.ModelCapability(nil), discovered.Capabilities...), Qualification: capability.QualificationDiscovered, Health: capability.HealthUnknown, ObservedAtUnixMillis: snapshot.ObservedAtUnixMillis}
|
||||
modelRevision := stableRevision(string(discovered.Fingerprint), fmt.Sprint(discovered.MaxContextBytes), fmt.Sprint(discovered.Modalities), fmt.Sprint(discovered.Capabilities), fmt.Sprint(discovered.IntelligenceScoreMillis))
|
||||
model := capability.ModelDescriptor{ID: modelID, Revision: modelRevision, ProviderInstanceID: provider.ID, ProviderModelID: discovered.ProviderModelID, Fingerprint: discovered.Fingerprint, MaxContextBytes: discovered.MaxContextBytes, Modalities: append([]capability.ModelModality(nil), discovered.Modalities...), Capabilities: append([]capability.ModelCapability(nil), discovered.Capabilities...), Qualification: capability.QualificationDiscovered, Health: snapshot.Health, ObservedAtUnixMillis: snapshot.ObservedAtUnixMillis, IntelligenceScoreMillis: discovered.IntelligenceScoreMillis}
|
||||
bindingID := capability.CapabilityBindingID(stableID("binding", string(snapshot.OwnerPrincipalID), string(snapshot.CapabilityID), string(provider.ID), string(model.ID)))
|
||||
bindingRevision := stableRevision(fmt.Sprint(provider.Revision), fmt.Sprint(model.Revision), string(snapshot.DataPolicy), string(snapshot.LimitPolicyRef))
|
||||
binding := capability.CognitiveBinding{ID: bindingID, Revision: bindingRevision, CapabilityID: snapshot.CapabilityID, OwnerPrincipalID: snapshot.OwnerPrincipalID, ProviderInstanceID: provider.ID, ProviderRevision: provider.Revision, ModelDescriptorID: model.ID, ModelRevision: model.Revision, DataPolicy: snapshot.DataPolicy, Qualification: capability.QualificationDiscovered, Activation: capability.ActivationInactive, Health: capability.HealthUnknown, LimitPolicyRef: snapshot.LimitPolicyRef}
|
||||
binding := capability.CognitiveBinding{ID: bindingID, Revision: bindingRevision, CapabilityID: snapshot.CapabilityID, OwnerPrincipalID: snapshot.OwnerPrincipalID, ProviderInstanceID: provider.ID, ProviderRevision: provider.Revision, ModelDescriptorID: model.ID, ModelRevision: model.Revision, DataPolicy: snapshot.DataPolicy, Qualification: capability.QualificationDiscovered, Activation: capability.ActivationInactive, Health: snapshot.Health, LimitPolicyRef: snapshot.LimitPolicyRef}
|
||||
registration.Models = append(registration.Models, model)
|
||||
registration.Bindings = append(registration.Bindings, binding)
|
||||
registration.ModelAvailability = append(registration.ModelAvailability, capability.ModelOperationalObservation{ProviderInstanceID: provider.ID, ModelDescriptorID: model.ID, Installed: discovered.Installed, Loaded: discovered.Loaded, Invokable: discovered.Invokable, Health: snapshot.Health, ObservedAtUnixMillis: snapshot.ObservedAtUnixMillis})
|
||||
@@ -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))
|
||||
@@ -82,14 +98,14 @@ func stableRevision(parts ...string) int64 {
|
||||
}
|
||||
|
||||
func ValidateProviderInstance(value capability.ProviderInstance) error {
|
||||
if value.ID == "" || value.Revision < 1 || value.Kind == "" || value.EndpointRef == "" || !validQualification(value.Qualification) || !validHealth(value.Health) || value.ObservedAtUnixMillis < 1 {
|
||||
if value.ID == "" || value.Revision < 1 || value.Kind == "" || value.EndpointRef == "" || !validQualification(value.Qualification) || !validHealth(value.Health) || value.ObservedAtUnixMillis < 1 || !validBillingMode(value.BillingMode) || !validAuthMode(value.AuthMode) || !validNonNegative(value.InputCostMicros) || !validNonNegative(value.OutputCostMicros) || !validNonNegative(value.RequestCostMicros) {
|
||||
return ErrInvalidCognitiveRegistryRecord
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateModelDescriptor(value capability.ModelDescriptor) error {
|
||||
if value.ID == "" || value.Revision < 1 || value.ProviderInstanceID == "" || value.ProviderModelID == "" || value.MaxContextBytes < 0 || len(value.Modalities) == 0 || len(value.Capabilities) == 0 || !validQualification(value.Qualification) || !validHealth(value.Health) || value.ObservedAtUnixMillis < 1 {
|
||||
if value.ID == "" || value.Revision < 1 || value.ProviderInstanceID == "" || value.ProviderModelID == "" || value.MaxContextBytes < 0 || len(value.Modalities) == 0 || len(value.Capabilities) == 0 || !validQualification(value.Qualification) || !validHealth(value.Health) || value.ObservedAtUnixMillis < 1 || !validNonNegative(value.IntelligenceScoreMillis) {
|
||||
return ErrInvalidCognitiveRegistryRecord
|
||||
}
|
||||
return nil
|
||||
@@ -132,10 +148,18 @@ func SelectCognitiveBinding(requirement capability.CognitiveRequirement, candida
|
||||
if candidate.Evaluation != nil && candidate.Evaluation.Result == capability.EvaluationUnsupported {
|
||||
continue
|
||||
}
|
||||
if requirement.MaxCostMicros != nil {
|
||||
cost, known := estimatedCost(candidate.Provider, requirement.InputBytes, requirement.MaxOutputBytes)
|
||||
if known && cost > *requirement.MaxCostMicros {
|
||||
continue
|
||||
}
|
||||
}
|
||||
eligible = append(eligible, candidate)
|
||||
}
|
||||
if len(eligible) > 0 {
|
||||
sort.SliceStable(eligible, func(i, j int) bool { return betterCandidate(eligible[i], eligible[j]) })
|
||||
sort.SliceStable(eligible, func(i, j int) bool {
|
||||
return betterCandidate(eligible[i], eligible[j], requirement.Objective, requirement.InputBytes, requirement.MaxOutputBytes)
|
||||
})
|
||||
return eligible[0], nil
|
||||
}
|
||||
return capability.ResolvedCognitiveBinding{}, ErrNoInvocableCognitiveBinding
|
||||
@@ -157,7 +181,28 @@ func supportsCapabilities(candidate capability.ResolvedCognitiveBinding, require
|
||||
return true
|
||||
}
|
||||
|
||||
func betterCandidate(left, right capability.ResolvedCognitiveBinding) bool {
|
||||
func betterCandidate(left, right capability.ResolvedCognitiveBinding, objective capability.SelectionObjective, inputBytes, outputBytes int64) bool {
|
||||
if objective == capability.SelectionEconomy {
|
||||
lc, lk := estimatedCost(left.Provider, inputBytes, outputBytes)
|
||||
rc, rk := estimatedCost(right.Provider, inputBytes, outputBytes)
|
||||
if lk != rk {
|
||||
return lk
|
||||
}
|
||||
if lk && lc != rc {
|
||||
return lc < rc
|
||||
}
|
||||
}
|
||||
if objective == capability.SelectionSpeed {
|
||||
if lp, rp := left.Performance, right.Performance; lp != nil && rp != nil && lp.LatencyMicros != nil && rp.LatencyMicros != nil && *lp.LatencyMicros != *rp.LatencyMicros {
|
||||
return *lp.LatencyMicros < *rp.LatencyMicros
|
||||
}
|
||||
}
|
||||
if objective == capability.SelectionQuality || objective == capability.SelectionBalanced || objective == "" {
|
||||
li, ri := left.Model.IntelligenceScoreMillis, right.Model.IntelligenceScoreMillis
|
||||
if li != nil && ri != nil && *li != *ri {
|
||||
return *li > *ri
|
||||
}
|
||||
}
|
||||
le, re := left.Evaluation, right.Evaluation
|
||||
if le != nil && le.Result == capability.EvaluationSupported && (re == nil || re.Result != capability.EvaluationSupported) {
|
||||
return true
|
||||
@@ -184,6 +229,44 @@ func betterCandidate(left, right capability.ResolvedCognitiveBinding) bool {
|
||||
return string(left.Binding.ID) < string(right.Binding.ID)
|
||||
}
|
||||
|
||||
func estimatedCost(provider capability.ProviderInstance, inputBytes, outputBytes int64) (int64, bool) {
|
||||
var total int64
|
||||
known := false
|
||||
if provider.InputCostMicros != nil {
|
||||
total += *provider.InputCostMicros * inputBytes
|
||||
known = true
|
||||
}
|
||||
if provider.OutputCostMicros != nil {
|
||||
total += *provider.OutputCostMicros * outputBytes
|
||||
known = true
|
||||
}
|
||||
if provider.RequestCostMicros != nil {
|
||||
total += *provider.RequestCostMicros
|
||||
known = true
|
||||
}
|
||||
return total, known
|
||||
}
|
||||
|
||||
func validNonNegative(value *int64) bool { return value == nil || *value >= 0 }
|
||||
|
||||
func validBillingMode(value capability.BillingMode) bool {
|
||||
switch value {
|
||||
case "", capability.BillingUnknown, capability.BillingFree, capability.BillingPerInput, capability.BillingPerOutput, capability.BillingPerRequest:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validAuthMode(value capability.AuthMode) bool {
|
||||
switch value {
|
||||
case "", capability.AuthNone, capability.AuthKey, capability.AuthBearer, capability.AuthUsernamePassword, capability.AuthOAuth, capability.AuthSecretFile:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func containsAll[T comparable](actual, required []T) bool {
|
||||
for _, item := range required {
|
||||
if !slices.Contains(actual, item) {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
func TestBuildDiscoveryRegistrationCarriesObservedHealthToCanaryPrecondition(t *testing.T) {
|
||||
inputCost := int64(2)
|
||||
intelligence := int64(820)
|
||||
registration, err := BuildDiscoveryRegistration(capability.CognitiveDiscoverySnapshot{
|
||||
ProviderInstanceID: "local-lifeboat",
|
||||
ProviderKind: "openai-compatible-chat-v1",
|
||||
EndpointRef: "local-lifeboat-v1",
|
||||
OwnerPrincipalID: "domovoy-creator-principal",
|
||||
CapabilityID: "creator-advisory",
|
||||
DataPolicy: capability.DataPolicyLocalOnly,
|
||||
LimitPolicyRef: "creator-advisory-local-v1",
|
||||
Health: capability.HealthAvailable,
|
||||
ObservedAtUnixMillis: 1000,
|
||||
BillingMode: capability.BillingPerInput,
|
||||
AuthMode: capability.AuthKey,
|
||||
InputCostMicros: &inputCost,
|
||||
Models: []capability.DiscoveredModelSnapshot{{
|
||||
ProviderModelID: "domovoy-lifeboat",
|
||||
Fingerprint: "fingerprint",
|
||||
MaxContextBytes: 32768,
|
||||
Modalities: []capability.ModelModality{capability.ModalityText},
|
||||
Capabilities: []capability.ModelCapability{capability.CapabilityAdvisory},
|
||||
Installed: capability.OperationalYes,
|
||||
Loaded: capability.OperationalNotObserved,
|
||||
Invokable: capability.OperationalYes,
|
||||
IntelligenceScoreMillis: &intelligence,
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if registration.Provider.Health != capability.HealthAvailable || len(registration.Models) != 1 || len(registration.Bindings) != 1 {
|
||||
t.Fatalf("unexpected registration: %+v", registration)
|
||||
}
|
||||
if registration.Models[0].Health != capability.HealthAvailable || registration.Bindings[0].Health != capability.HealthAvailable {
|
||||
t.Fatalf("discovery health was not carried into canary candidates: model=%s binding=%s", registration.Models[0].Health, registration.Bindings[0].Health)
|
||||
}
|
||||
if registration.Provider.AuthMode != capability.AuthKey || registration.Provider.InputCostMicros == nil || *registration.Provider.InputCostMicros != inputCost || registration.Models[0].IntelligenceScoreMillis == nil || *registration.Models[0].IntelligenceScoreMillis != intelligence {
|
||||
t.Fatalf("discovery profile was not carried into registry: provider=%+v model=%+v", registration.Provider, registration.Models[0])
|
||||
}
|
||||
inputCostChanged := int64(3)
|
||||
changed, err := BuildDiscoveryRegistration(capability.CognitiveDiscoverySnapshot{
|
||||
ProviderInstanceID: "local-lifeboat", ProviderKind: "openai-compatible-chat-v1", EndpointRef: "local-lifeboat-v1", OwnerPrincipalID: "domovoy-creator-principal", CapabilityID: "creator-advisory", DataPolicy: capability.DataPolicyLocalOnly, LimitPolicyRef: "creator-advisory-local-v1", Health: capability.HealthAvailable, ObservedAtUnixMillis: 1001, BillingMode: capability.BillingPerInput, AuthMode: capability.AuthKey, InputCostMicros: &inputCostChanged,
|
||||
Models: []capability.DiscoveredModelSnapshot{{ProviderModelID: "domovoy-lifeboat", Fingerprint: "fingerprint", MaxContextBytes: 32768, Modalities: []capability.ModelModality{capability.ModalityText}, Capabilities: []capability.ModelCapability{capability.CapabilityAdvisory}, Installed: capability.OperationalYes, Loaded: capability.OperationalNotObserved, Invokable: capability.OperationalYes, IntelligenceScoreMillis: &intelligence}},
|
||||
})
|
||||
if err != nil || changed.Provider.Revision == registration.Provider.Revision {
|
||||
t.Fatalf("provider profile change did not create a new revision: first=%d changed=%d err=%v", registration.Provider.Revision, changed.Provider.Revision, err)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectCognitiveBindingUsesObjectiveAndCost(t *testing.T) {
|
||||
quality := int64(800)
|
||||
critical := int64(0)
|
||||
fast := int64(100)
|
||||
slow := int64(900)
|
||||
cheap := int64(1)
|
||||
expensive := int64(10)
|
||||
candidate := func(id string, intelligence, latency, cost int64) capability.ResolvedCognitiveBinding {
|
||||
return capability.ResolvedCognitiveBinding{
|
||||
Binding: capability.CognitiveBinding{ID: capability.CapabilityBindingID(id), Revision: 1, CapabilityID: "creator-advisory", OwnerPrincipalID: "owner", ProviderInstanceID: capability.ProviderInstanceID(id), ProviderRevision: 1, ModelDescriptorID: capability.ModelDescriptorID(id), ModelRevision: 1, DataPolicy: capability.DataPolicyLocalOnly, Qualification: capability.QualificationValidated, Activation: capability.ActivationActive, Health: capability.HealthAvailable, LimitPolicyRef: "limits"},
|
||||
Provider: capability.ProviderInstance{ID: capability.ProviderInstanceID(id), Revision: 1, Kind: "builtin", EndpointRef: capability.EndpointBindingRef(id), Enabled: true, Qualification: capability.QualificationValidated, Health: capability.HealthAvailable, ObservedAtUnixMillis: 1, BillingMode: capability.BillingPerRequest, RequestCostMicros: &cost},
|
||||
Model: capability.ModelDescriptor{ID: capability.ModelDescriptorID(id), Revision: 1, ProviderInstanceID: capability.ProviderInstanceID(id), ProviderModelID: capability.ProviderModelID(id), MaxContextBytes: 10000, Modalities: []capability.ModelModality{capability.ModalityText}, Capabilities: []capability.ModelCapability{capability.CapabilityAdvisory}, Qualification: capability.QualificationValidated, Health: capability.HealthAvailable, ObservedAtUnixMillis: 1, IntelligenceScoreMillis: &intelligence},
|
||||
Evaluation: &capability.EvaluatedCapability{ID: capability.EvaluationID("eval-" + id), Result: capability.EvaluationSupported, QualityScoreMillis: &quality, CriticalFailureMillis: &critical},
|
||||
Performance: &capability.PerformanceEvidence{LatencyMicros: &latency},
|
||||
}
|
||||
}
|
||||
qualityCandidate := candidate("quality", slow, slow, expensive)
|
||||
fastCandidate := candidate("fast", fast, fast, cheap)
|
||||
selected, err := SelectCognitiveBinding(capability.CognitiveRequirement{CapabilityID: "creator-advisory", OwnerPrincipalID: "owner", DataPolicy: capability.DataPolicyLocalOnly, RequiredModalities: []capability.ModelModality{capability.ModalityText}, RequiredCapabilities: []capability.ModelCapability{capability.CapabilityAdvisory}, MaxOutputBytes: 10, Objective: capability.SelectionQuality}, []capability.ResolvedCognitiveBinding{qualityCandidate, fastCandidate})
|
||||
if err != nil || selected.Binding.ID != "quality" {
|
||||
t.Fatalf("quality selection=%+v err=%v", selected, err)
|
||||
}
|
||||
selected, err = SelectCognitiveBinding(capability.CognitiveRequirement{CapabilityID: "creator-advisory", OwnerPrincipalID: "owner", DataPolicy: capability.DataPolicyLocalOnly, RequiredModalities: []capability.ModelModality{capability.ModalityText}, RequiredCapabilities: []capability.ModelCapability{capability.CapabilityAdvisory}, MaxOutputBytes: 10, Objective: capability.SelectionEconomy, MaxCostMicros: &cheap}, []capability.ResolvedCognitiveBinding{qualityCandidate, fastCandidate})
|
||||
if err != nil || selected.Binding.ID != "fast" {
|
||||
t.Fatalf("economy selection=%+v err=%v", selected, err)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user