Compare commits

...
Author SHA1 Message Date
m 8f20326c07 feat(work): schedule task evaluation through work graph
CI / test (push) Successful in 1m1s
CI / test (pull_request) Successful in 1m7s
2026-08-23 12:09:07 +03:00
m a82592a130 Merge pull request 'feat: task-aware model evaluation foundation' (#185) from codex/wp-208-task-model-evaluation into main
CI / test (push) Successful in 1m11s
2026-08-23 08:59:18 +00:00
m feea909d69 feat(capability): add task-aware model evaluation evidence
CI / test (push) Successful in 57s
CI / test (pull_request) Successful in 1m0s
2026-08-23 11:57:47 +03:00
m b78a026ee1 Merge pull request 'docs: record gpu adapter invocation qualification' (#184) from codex/wp-207-gpu-qualification into main
CI / test (push) Successful in 1m9s
docs: record gpu adapter invocation qualification
2026-08-23 08:43:15 +00:00
m cf35c0ae25 docs: record gpu adapter invocation qualification
CI / test (push) Successful in 1m1s
CI / test (pull_request) Successful in 56s
2026-08-23 11:40:55 +03:00
m 9f96393fb9 Merge pull request 'docs: refresh audit completion matrix' (#183) from codex/wp-206-completion-matrix into main
CI / test (push) Successful in 1m8s
docs: refresh audit completion matrix
2026-08-23 08:32:43 +00:00
m beee8deb34 docs: refresh audit completion matrix
CI / test (push) Successful in 1m4s
CI / test (pull_request) Successful in 1m6s
2026-08-23 11:30:07 +03:00
m faab913227 Merge pull request 'docs: record gpu invocation smoke gap' (#182) from codex/wp-205-gpu-smoke-evidence into main
CI / test (push) Successful in 1m5s
docs: record gpu invocation smoke gap
2026-08-23 08:24:36 +00:00
23 changed files with 745 additions and 30 deletions
+25 -2
View File
@@ -430,6 +430,14 @@ 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) {
return r.loadCognitiveCandidates(ctx, capabilityID, owner, "")
}
func (r *CapabilityRepository) LoadCognitiveCandidatesForProfile(ctx context.Context, capabilityID capability.CapabilityID, owner identity.PrincipalID, profileID capability.TaskEvaluationProfileID) ([]capability.ResolvedCognitiveBinding, error) {
return r.loadCognitiveCandidates(ctx, capabilityID, owner, profileID)
}
func (r *CapabilityRepository) loadCognitiveCandidates(ctx context.Context, capabilityID capability.CapabilityID, owner identity.PrincipalID, profileID capability.TaskEvaluationProfileID) ([]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)),p.billing_mode,p.auth_mode,p.input_cost_micros,p.output_cost_micros,p.request_cost_micros,
@@ -454,7 +462,7 @@ func (r *CapabilityRepository) LoadCognitiveCandidates(ctx context.Context, capa
if err != nil {
return nil, err
}
if err = r.loadCognitiveEvidence(ctx, &value); err != nil {
if err = r.loadCognitiveEvidence(ctx, &value, profileID); err != nil {
return nil, err
}
result = append(result, value)
@@ -462,7 +470,7 @@ func (r *CapabilityRepository) LoadCognitiveCandidates(ctx context.Context, capa
return result, rows.Err()
}
func (r *CapabilityRepository) loadCognitiveEvidence(ctx context.Context, value *capability.ResolvedCognitiveBinding) error {
func (r *CapabilityRepository) loadCognitiveEvidence(ctx context.Context, value *capability.ResolvedCognitiveBinding, profileID capability.TaskEvaluationProfileID) error {
var evaluation capability.EvaluatedCapability
err := r.pool.QueryRow(ctx, `SELECT evaluation_id,binding_id,binding_revision,capability_id,result,quality_score_millis,critical_failure_millis,benchmark_version,artifact_id,observed_at_unix_millis FROM capability.cognitive_evaluated_capabilities WHERE binding_id=$1 AND binding_revision=$2 ORDER BY observed_at_unix_millis DESC LIMIT 1`, value.Binding.ID, value.Binding.Revision).Scan(&evaluation.ID, &evaluation.BindingID, &evaluation.BindingRevision, &evaluation.CapabilityID, &evaluation.Result, &evaluation.QualityScoreMillis, &evaluation.CriticalFailureMillis, &evaluation.BenchmarkVersion, &evaluation.ArtifactID, &evaluation.ObservedAtUnixMillis)
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
@@ -479,6 +487,16 @@ func (r *CapabilityRepository) loadCognitiveEvidence(ctx context.Context, value
if err == nil {
value.Performance = &performance
}
if profileID != "" {
var taskEvaluation capability.TaskEvaluation
err = r.pool.QueryRow(ctx, `SELECT evaluation_id,task_profile_id,binding_id,binding_revision,capability_id,result,quality_score_millis,reliability_score_millis,latency_score_millis,overall_score_millis,critical_failure_millis,benchmark_version,artifact_id,observed_at_unix_millis FROM capability.cognitive_task_evaluations WHERE task_profile_id=$1 AND binding_id=$2 AND binding_revision=$3 ORDER BY observed_at_unix_millis DESC LIMIT 1`, profileID, value.Binding.ID, value.Binding.Revision).Scan(&taskEvaluation.ID, &taskEvaluation.ProfileID, &taskEvaluation.BindingID, &taskEvaluation.BindingRevision, &taskEvaluation.CapabilityID, &taskEvaluation.Result, &taskEvaluation.QualityScoreMillis, &taskEvaluation.ReliabilityScoreMillis, &taskEvaluation.LatencyScoreMillis, &taskEvaluation.OverallScoreMillis, &taskEvaluation.CriticalFailureMillis, &taskEvaluation.BenchmarkVersion, &taskEvaluation.ArtifactID, &taskEvaluation.ObservedAtUnixMillis)
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
return err
}
if err == nil {
value.TaskEvaluation = &taskEvaluation
}
}
return nil
}
@@ -592,3 +610,8 @@ func (r *CapabilityRepository) SavePerformanceEvidence(ctx context.Context, valu
_, 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
}
func (r *CapabilityRepository) SaveTaskEvaluation(ctx context.Context, value capability.TaskEvaluation) error {
_, err := r.pool.Exec(ctx, `INSERT INTO capability.cognitive_task_evaluations (evaluation_id,task_profile_id,binding_id,binding_revision,capability_id,result,quality_score_millis,reliability_score_millis,latency_score_millis,overall_score_millis,critical_failure_millis,benchmark_version,artifact_id,observed_at_unix_millis) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) ON CONFLICT DO NOTHING`, value.ID, value.ProfileID, value.BindingID, value.BindingRevision, value.CapabilityID, value.Result, value.QualityScoreMillis, value.ReliabilityScoreMillis, value.LatencyScoreMillis, value.OverallScoreMillis, value.CriticalFailureMillis, value.BenchmarkVersion, value.ArtifactID, value.ObservedAtUnixMillis)
return err
}
+21 -1
View File
@@ -842,7 +842,13 @@ func main() {
http.Error(w, "invalid cognitive requirement", http.StatusBadRequest)
return
}
candidates, err := capabilityRepository.LoadCognitiveCandidates(r.Context(), requirement.CapabilityID, requirement.OwnerPrincipalID)
var candidates []capability.ResolvedCognitiveBinding
var err error
if requirement.TaskProfileID != "" {
candidates, err = capabilityRepository.LoadCognitiveCandidatesForProfile(r.Context(), requirement.CapabilityID, requirement.OwnerPrincipalID, requirement.TaskProfileID)
} else {
candidates, err = capabilityRepository.LoadCognitiveCandidates(r.Context(), requirement.CapabilityID, requirement.OwnerPrincipalID)
}
if err != nil {
http.Error(w, "cognitive registry unavailable", http.StatusServiceUnavailable)
return
@@ -891,6 +897,20 @@ func main() {
}
w.WriteHeader(http.StatusCreated)
})
mux.HandleFunc("POST /v1/cognitive-registry/task-evaluations", func(w http.ResponseWriter, r *http.Request) {
var value capability.TaskEvaluation
decoder := json.NewDecoder(io.LimitReader(r.Body, 64<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil || capabilitydomain.ValidateTaskEvaluation(value) != nil {
http.Error(w, "invalid task evaluation", http.StatusBadRequest)
return
}
if err := capabilityRepository.SaveTaskEvaluation(r.Context(), value); err != nil {
http.Error(w, "save task evaluation", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
})
mux.HandleFunc("POST /v1/creator/cognitive-canary", func(w http.ResponseWriter, r *http.Request) {
session, ok := authenticateCreator(w, r)
if !ok {
+13
View File
@@ -42,6 +42,19 @@ func main() {
}
writeJSON(w, map[string]any{"graph": graph, "scheduled_nodes": ready})
})
mux.HandleFunc("POST /v1/work-graphs/task-evaluations/schedule", func(w http.ResponseWriter, r *http.Request) {
var request application.TaskEvaluationScheduleRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<20)).Decode(&request); err != nil {
http.Error(w, "invalid task evaluation graph request", http.StatusBadRequest)
return
}
graph, ready, err := application.AdmitAndScheduleTaskEvaluation(r.Context(), repository, request)
if err != nil {
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
writeJSON(w, map[string]any{"graph": graph, "scheduled_nodes": ready})
})
mux.HandleFunc("POST /v1/work-graphs/complete", func(w http.ResponseWriter, r *http.Request) {
var request application.GraphCompleteRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&request); err != nil {
+16 -12
View File
@@ -160,28 +160,32 @@ type CognitiveRequirement struct {
ExcludedBindingIDs []CapabilityBindingID
Objective SelectionObjective
MaxCostMicros *int64
TaskProfileID TaskEvaluationProfileID
}
// ResolvedCognitiveBinding is a provider-neutral invocation snapshot selected
// by Capability. It contains references and declared metadata, never secrets.
type ResolvedCognitiveBinding struct {
Binding CognitiveBinding
Provider ProviderInstance
Model ModelDescriptor
Evaluation *EvaluatedCapability
Performance *PerformanceEvidence
Rating *ModelRating
Binding CognitiveBinding
Provider ProviderInstance
Model ModelDescriptor
Evaluation *EvaluatedCapability
Performance *PerformanceEvidence
TaskEvaluation *TaskEvaluation
Rating *ModelRating
}
// ModelRating is a derived, provider-neutral explanation of why an eligible
// model is routed. It never grants qualification or activation authority.
type ModelRating struct {
Category ModelRatingCategory `json:"category"`
OverallScoreMillis int64 `json:"overall_score_millis"`
QualityScoreMillis int64 `json:"quality_score_millis"`
ReliabilityScoreMillis int64 `json:"reliability_score_millis"`
LatencyScoreMillis int64 `json:"latency_score_millis"`
EvidenceScoreMillis int64 `json:"evidence_score_millis"`
TaskProfileID TaskEvaluationProfileID `json:"task_profile_id,omitempty"`
TaskScoreMillis int64 `json:"task_score_millis,omitempty"`
Category ModelRatingCategory `json:"category"`
OverallScoreMillis int64 `json:"overall_score_millis"`
QualityScoreMillis int64 `json:"quality_score_millis"`
ReliabilityScoreMillis int64 `json:"reliability_score_millis"`
LatencyScoreMillis int64 `json:"latency_score_millis"`
EvidenceScoreMillis int64 `json:"evidence_score_millis"`
}
type CognitiveResultClass string
+102
View File
@@ -0,0 +1,102 @@
package capability
import (
"errors"
"git.cin.su/m/Assistant/contracts/artifact"
"git.cin.su/m/Assistant/platform/ids"
)
type TaskEvaluationProfileID ids.ID
type TaskEvaluationID ids.ID
type TaskEvaluationProfileStage string
const (
TaskEvaluationGeneral TaskEvaluationProfileStage = "GENERAL"
TaskEvaluationAdditional TaskEvaluationProfileStage = "ADDITIONAL"
)
// TaskEvaluationProfile is the extensible, provider-neutral description of a
// task-specific qualification run. Case payloads live in Artifact; the
// profile stores only stable case references and scoring policy.
type TaskEvaluationProfile struct {
ID TaskEvaluationProfileID
Revision int64
CapabilityID CapabilityID
Stage TaskEvaluationProfileStage
ParentProfileID TaskEvaluationProfileID
TriggerEvidenceRef string
BenchmarkVersion string
RequiredCaseIDs []string
QualityWeightMillis int64
ReliabilityWeightMillis int64
LatencyWeightMillis int64
}
func (p TaskEvaluationProfile) Validate() error {
if p.ID == "" || p.Revision < 1 || p.CapabilityID == "" || (p.Stage != TaskEvaluationGeneral && p.Stage != TaskEvaluationAdditional) || p.BenchmarkVersion == "" || len(p.RequiredCaseIDs) == 0 {
return errors.New("task evaluation profile identity and cases are incomplete")
}
if p.Stage == TaskEvaluationGeneral && (p.ParentProfileID != "" || p.TriggerEvidenceRef != "") {
return errors.New("general task evaluation profile cannot have an expansion parent")
}
if p.Stage == TaskEvaluationAdditional && (p.ParentProfileID == "" || p.TriggerEvidenceRef == "") {
return errors.New("additional task evaluation profile requires parent and trigger evidence")
}
if p.QualityWeightMillis < 0 || p.ReliabilityWeightMillis < 0 || p.LatencyWeightMillis < 0 || p.QualityWeightMillis+p.ReliabilityWeightMillis+p.LatencyWeightMillis != 1000 {
return errors.New("task evaluation profile weights must be non-negative and sum to 1000")
}
seen := make(map[string]struct{}, len(p.RequiredCaseIDs))
for _, caseID := range p.RequiredCaseIDs {
if caseID == "" {
return errors.New("task evaluation profile contains an empty case")
}
if _, ok := seen[caseID]; ok {
return errors.New("task evaluation profile contains duplicate cases")
}
seen[caseID] = struct{}{}
}
return nil
}
// TaskEvaluationExpansionRequest is emitted after a task remains unresolved
// with the current best eligible candidate. It creates only a bounded new
// profile; it never silently rewrites the general benchmark.
type TaskEvaluationExpansionRequest struct {
ParentProfileID TaskEvaluationProfileID
CapabilityID CapabilityID
TriggerEvidenceRef string
NewCaseIDs []string
BenchmarkVersion string
}
// TaskCaseObservation is produced by the bounded evaluator and verifier. A
// score without a case result and evidence reference is not accepted as
// qualification evidence.
type TaskCaseObservation struct {
CaseID string
Result EvaluationResult
QualityScoreMillis int64
LatencyMicros int64
CriticalFailure bool
EvidenceArtifactRef string
}
// TaskEvaluation is durable evidence for one model/binding revision under one
// task profile. It is not activation authority.
type TaskEvaluation struct {
ID TaskEvaluationID
ProfileID TaskEvaluationProfileID
BindingID CapabilityBindingID
BindingRevision int64
CapabilityID CapabilityID
Result EvaluationResult
QualityScoreMillis int64
ReliabilityScoreMillis int64
LatencyScoreMillis int64
OverallScoreMillis int64
CriticalFailureMillis int64
BenchmarkVersion string
ArtifactID artifact.ArtifactID
ObservedAtUnixMillis int64
}
+40 -11
View File
@@ -1,11 +1,17 @@
package work
import "git.cin.su/m/Assistant/contracts/capability"
type GraphNodeID string
type GraphState string
type GraphNodeState string
type GraphNodePurpose string
const (
GraphNodePurposeTaskEvaluation GraphNodePurpose = "TASK_EVALUATION_SPECIALIST"
GraphNodePurposeGeneric GraphNodePurpose = "GENERIC"
GraphDraft GraphState = "DRAFT"
GraphAdmitted GraphState = "ADMITTED"
GraphRunning GraphState = "RUNNING"
@@ -31,11 +37,15 @@ type GraphBudget struct {
}
type GraphNode struct {
ID GraphNodeID `json:"id"`
DispatchID string `json:"dispatch_id"`
IdempotencyKey string `json:"idempotency_key"`
State GraphNodeState `json:"state"`
Attempts int `json:"attempts"`
ID GraphNodeID `json:"id"`
DispatchID string `json:"dispatch_id"`
IdempotencyKey string `json:"idempotency_key"`
State GraphNodeState `json:"state"`
Attempts int `json:"attempts"`
Purpose GraphNodePurpose `json:"purpose,omitempty"`
TaskProfileID capability.TaskEvaluationProfileID `json:"task_profile_id,omitempty"`
BindingID capability.CapabilityBindingID `json:"binding_id,omitempty"`
CaseIDs []string `json:"case_ids,omitempty"`
}
type GraphEdge struct {
@@ -44,12 +54,31 @@ type GraphEdge struct {
}
type WorkGraph struct {
ID string `json:"id"`
Revision int64 `json:"revision"`
State GraphState `json:"state"`
Nodes []GraphNode `json:"nodes"`
Edges []GraphEdge `json:"edges"`
Budget GraphBudget `json:"budget"`
ID string `json:"id"`
Revision int64 `json:"revision"`
State GraphState `json:"state"`
Nodes []GraphNode `json:"nodes"`
Edges []GraphEdge `json:"edges"`
Budget GraphBudget `json:"budget"`
ParentGraphID string `json:"parent_graph_id,omitempty"`
ParentGraphRevision int64 `json:"parent_graph_revision,omitempty"`
TriggerEvidenceRef string `json:"trigger_evidence_ref,omitempty"`
}
// TaskEvaluationGraphRequest describes a durable fan-out. The graph contains
// one specialist node per binding; each node receives the profile's bounded
// cases and reports evidence back to Capability for fan-in aggregation.
type TaskEvaluationGraphRequest struct {
GraphID string `json:"graph_id"`
Profile capability.TaskEvaluationProfile `json:"profile"`
BindingIDs []capability.CapabilityBindingID `json:"binding_ids"`
DispatchIDs []string `json:"dispatch_ids"`
IdempotencyPrefix string `json:"idempotency_prefix"`
Budget GraphBudget `json:"budget"`
ParentGraphID string `json:"parent_graph_id,omitempty"`
ParentGraphRevision int64 `json:"parent_graph_revision,omitempty"`
ParentGraphState GraphState `json:"parent_graph_state,omitempty"`
TriggerEvidenceRef string `json:"trigger_evidence_ref,omitempty"`
}
type ResultCandidate struct {
@@ -0,0 +1,35 @@
BEGIN;
SET ROLE capability_role;
CREATE TABLE IF NOT EXISTS capability.cognitive_task_evaluations (
evaluation_id TEXT PRIMARY KEY,
task_profile_id TEXT NOT NULL,
binding_id TEXT NOT NULL,
binding_revision BIGINT NOT NULL,
capability_id TEXT NOT NULL,
result TEXT NOT NULL,
quality_score_millis BIGINT NOT NULL,
reliability_score_millis BIGINT NOT NULL,
latency_score_millis BIGINT NOT NULL,
overall_score_millis BIGINT NOT NULL,
critical_failure_millis BIGINT NOT NULL,
benchmark_version TEXT NOT NULL,
artifact_id TEXT NOT NULL,
observed_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (binding_revision > 0),
CHECK (result IN ('UNKNOWN','SUPPORTED','UNSUPPORTED')),
CHECK (quality_score_millis BETWEEN 0 AND 1000),
CHECK (reliability_score_millis BETWEEN 0 AND 1000),
CHECK (latency_score_millis BETWEEN 0 AND 1000),
CHECK (overall_score_millis BETWEEN 0 AND 1000),
CHECK (critical_failure_millis BETWEEN 0 AND 1000),
CHECK (observed_at_unix_millis > 0)
);
CREATE INDEX IF NOT EXISTS cognitive_task_evaluations_lookup
ON capability.cognitive_task_evaluations(task_profile_id, binding_id, binding_revision, observed_at_unix_millis DESC);
GRANT SELECT, INSERT ON capability.cognitive_task_evaluations TO capability_role;
RESET ROLE;
COMMIT;
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000205-gpu-invocation-smoke-evidence.json",
"task_id": "DEV-000205",
"task_file": "dev/tasks/DEV-000209-task-evaluator-work-graph.json",
"task_id": "DEV-000209",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000206",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "faab913227f985fc06436618b5747376d4495487",
"risk_tier": "E",
"intent": "Refresh the audit completion matrix against the current production revision and preserve explicit evidence for every remaining gap.",
"requirement_ids": ["AUDIT-COMPLETION-TRUTH", "all objective areas", "current production evidence"],
"allowed_change_scope": {"modules": ["governance status", "evidence"], "paths": ["docs/evidence/audit-completion-matrix-2026-08-23-v2.json", "docs/PROJECT_STATUS.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000206-completion-matrix-refresh.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "cmd/**", "modules/**", "contracts/**", "adapters/**", "database/**", "deploy/**", ".gitea/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["current production status and evidence artifacts"], "may_change": []},
"semantic_owner": "governance evidence projection",
"canonical_state": [],
"state_transitions": ["none; refreshes an append-only audit projection"],
"side_effects_and_privileges": ["none beyond repository evidence and status"],
"persistence_and_migrations": ["immutable JSON evidence only"],
"failure_retry_restart": ["retain explicit NOT_COMPLETE result when any required evidence is missing"],
"assumptions": ["production revision faab913 is the deployed main artifact", "deep acceptance remains deferred by current testing policy"],
"acceptance_scenarios": ["all seven objective areas have current statuses", "repository and production proof are distinguished", "remaining external gaps are explicit", "no secret values appear"],
"required_tests": ["JSON validation", "go test", "go vet", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "independent", "git diff --check", "CI", "production public health check"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000207",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "9f96393fb9a7ddce19eda3f90cbdeecb024029e6",
"risk_tier": "E",
"intent": "Record a bounded production GPU invocation using the exact Domovoy adapter request shape without automatic promotion.",
"requirement_ids": ["WP-200", "WP-201", "model fleet/failover", "hardware-neutral routing"],
"allowed_change_scope": {"modules": ["governance status", "evidence"], "paths": ["docs/evidence/gpu-invocation-qualification-2026-08-23-v1.json", "docs/evidence/audit-completion-matrix-2026-08-23-v3.json", "docs/PROJECT_STATUS.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000207-gpu-invocation-qualification.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "cmd/**", "modules/**", "contracts/**", "adapters/**", "database/**", "deploy/**", ".gitea/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing OpenAI-compatible local adapter request contract", "protected GPU gateway", "current completion matrix"], "may_change": []},
"semantic_owner": "governance evidence projection",
"canonical_state": [],
"state_transitions": ["discovered/inactive -> bounded invocation observed; remains unpromoted"],
"side_effects_and_privileges": ["one bounded synthetic request through an existing protected credential"],
"persistence_and_migrations": ["immutable JSON evidence only"],
"failure_retry_restart": ["no retry and no automatic activation"],
"assumptions": ["the protected runtime credential is used in place and is not copied into repository artifacts"],
"acceptance_scenarios": ["exact adapter payload returns one bounded completion", "quality remains unassessed", "GPU does not receive routing priority merely because it is available"],
"required_tests": ["JSON validation", "go test", "go vet", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "independent", "git diff --check", "CI", "production public health check"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000208",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "b78a026ee169047feb8468f0799fa1b9f4e27562",
"risk_tier": "E",
"intent": "Make Domovoy's model qualification and routing task-aware through parallel durable specialist work: typed evaluation profiles/evidence, durable task scores, and explainable selection without hardware/provider heuristics.",
"requirement_ids": ["task-specific model evaluation", "parallel specialist evaluation", "no main execution thread", "model fleet/failover", "hardware-neutral routing", "explainable model rating"],
"allowed_change_scope": {"modules": ["capability", "core-runtime"], "paths": ["contracts/capability/**", "modules/capability/**", "adapters/postgres/capability_repository.go", "cmd/core-runtime/main.go", "database/migrations/039_capability_task_evaluations.sql", "tests/model_qualification/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000208-task-aware-model-evaluation.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", ".gitea/**", "contracts/authority/**", "modules/identity/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 1},
"contracts": {"consume": ["CognitiveRequirement", "ResolvedCognitiveBinding", "existing capability evidence", "existing capability owner repository", "Work Graph durable fan-out/fan-in"], "may_change": ["task profile/evaluation contracts", "task-aware selection request/response serialization"]},
"semantic_owner": "capability",
"canonical_state": ["capability task evaluation evidence keyed by task profile and binding revision"],
"state_transitions": ["GENERAL profile -> first bounded evaluation", "unresolved task evidence -> ADDITIONAL profile with parent and trigger evidence", "profile result UNKNOWN -> SUPPORTED/UNSUPPORTED; evidence is append-only by binding revision", "task-aware requirement -> eligible candidates with explainable task rating", "evaluation request -> durable fan-out -> specialist evidence -> fan-in aggregate"],
"side_effects_and_privileges": ["core capability database role writes only; no provider calls and no secret access in this task"],
"persistence_and_migrations": ["039 adds capability.cognitive_task_evaluations owned by capability"],
"failure_retry_restart": ["missing, stale or unknown task evidence excludes a task-aware candidate; no score defaults to success", "one specialist failure does not block unrelated tasks; stale leases are recoverable and fan-in preserves partial/unknown state"],
"assumptions": ["a later Work Graph evaluation runner will produce per-case evidence; this task establishes the durable contract and routing gate", "the chat/request path never waits synchronously for the full evaluation graph"],
"acceptance_scenarios": ["general profile runs before additional profiles", "an unresolved task creates only a bounded additional profile linked to failure evidence", "task-aware requirement rejects a model without current supported task evidence", "task scores are included in explainable rating", "provider kind, endpoint and hardware do not affect score", "unknown/failed task evidence never silently becomes supported", "existing requirements without a task profile retain current behavior"],
"required_tests": ["go test", "go vet", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "migration/schema checks", "CI", "production public health check"],
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_MISSING_CAPABILITY"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000209",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "a82592a1300c06ed7a568c058faca6560e085ab3",
"risk_tier": "E",
"intent": "Create durable Work Graph plans for task-aware model evaluation: run the general profile first, fan out independent model specialists in parallel, and permit bounded additional profiles linked to unresolved evidence without blocking the chat path.",
"requirement_ids": ["general-first model evaluation", "parallel specialist evaluation", "bounded additional task tests", "fan-out/fan-in", "no synchronous chat blocking", "provider and hardware neutral"],
"allowed_change_scope": {"modules": ["work"], "paths": ["contracts/work/**", "modules/work/**", "cmd/work-graph-runtime/**", "tests/model_qualification/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000209-task-evaluator-work-graph.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "cmd/devctl/**", "modules/identity/**", "database/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["capability.TaskEvaluationProfile", "capability.TaskEvaluationProfileStage", "capability.TaskEvaluationProfileID", "existing Work Graph schedule contract"], "may_change": ["typed Work Graph node purpose and task-evaluation planning contract"]},
"semantic_owner": "work",
"canonical_state": ["work-owned durable graph and node lifecycle; capability remains owner of model evaluation evidence"],
"state_transitions": ["GENERAL profile -> admitted evaluation graph", "eligible model bindings -> parallel specialist nodes", "all specialist nodes -> fan-in completion state", "ADDITIONAL profile -> a separate bounded graph linked by parent/trigger evidence"],
"side_effects_and_privileges": ["none; graph planning only, no provider calls and no secret access"],
"persistence_and_migrations": ["uses existing work graph persistence; no new schema"],
"failure_retry_restart": ["each specialist remains independently retryable through existing node attempts/leases; partial or unknown graph state is preserved", "invalid stage/parent linkage is rejected before scheduling"],
"assumptions": ["a later worker adapter executes each dispatched specialist and posts evidence to capability", "chat/request handlers enqueue the graph and return without waiting for all nodes"],
"acceptance_scenarios": ["additional profile cannot be scheduled as a replacement for general profile", "one node is created per binding and profile with independent dispatch identity", "parallel budget is retained for fan-out", "graph metadata is sufficient for a worker to identify profile, binding and specialist role", "provider kind, endpoint and hardware are absent from planning inputs"],
"required_tests": ["go test", "go vet", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production public health check"],
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_MISSING_CAPABILITY"]
}
+4 -2
View File
@@ -5,7 +5,7 @@
- `CURRENT_PRODUCTION_STATUS = LIVE_HEALTHY`; public `/healthz` is healthy. The exact deployed revision is authoritative in the deployment log and merge record, not duplicated in this mutable status projection. Evidence: `docs/evidence/audit-runtime-status-2026-08-23-v1.json`.
- `FAST_PROVIDER_STATUS = LIVE_SMOKE_SUPPORTED`: local CPU gateway authentication is configured through a protected file reference; four bounded local-only advisory cases completed through cognitive-worker. Evidence: `docs/evidence/wp-601-fast-gateway-live-smoke-2026-08-22-v1.json`.
- `MEDIUM_PROVIDER_STATUS = LIVE_BOUNDED_SMOKE_SUPPORTED_UNPROMOTED`; four bounded local-provider cases passed with template thinking disabled. This is availability/response-shape evidence only; lifecycle qualification, activation and promotion remain unclaimed. Evidence: `docs/evidence/medium-live-bounded-smoke-2026-08-23-v1.json`.
- `HEAVY_PROVIDER_STATUS = LIVE_DISCOVERED_NOT_INVOCABLE`; protected GPU discovery sees two available models, but the bounded Qwen invocation smoke returned `400 missing_model`, so completion/invocability is not observed. GPU availability is not treated as intelligence or routing priority. Evidence: `docs/evidence/gpu-discovery-2026-08-23-v1.json`, `docs/evidence/gpu-invocation-smoke-2026-08-23-v1.json`.
- `HEAVY_PROVIDER_STATUS = LIVE_BOUNDED_INVOCATION_SUPPORTED_UNPROMOTED`; the exact Domovoy adapter payload produced one bounded Qwen completion in 0.583s. Quality is not assessed, lifecycle promotion is not performed, and GPU availability is not treated as intelligence or routing priority. Evidence: `docs/evidence/gpu-discovery-2026-08-23-v1.json`, `docs/evidence/gpu-invocation-qualification-2026-08-23-v1.json`.
- `AGENT_EXECUTION_STATUS = DURABLE_DRY_RUN_CANARY_VERIFIED`; the explicit production canary persisted a dispatch intent, an ACTIVE lease and callback head sequence 2. No independently operated bridge endpoint is configured, so bridge-backed execution remains unavailable. Evidence: `docs/evidence/agent-execution-dry-run-canary-2026-08-23-v1.json`.
- `FLEET_EXECUTION_EVIDENCE = docs/evidence/audit-runtime-status-2026-08-23-v1.json`; FAST, MEDIUM and LIFEBOAT are ready, HEAVY is explicitly degraded by provider timeout, FAST remains preferred/selected, and LIFEBOAT is the fallback tier.
- `TRUTH_RESEARCH_STATUS = LIVE_CANARY_PERSISTED`; the web research canary preserved an OBSERVED/COMPLETE/NO-CONFLICT source chain while leaving the claim UNVERIFIED and belief PROVISIONAL. Evidence: `docs/evidence/truth-runtime-canary-2026-08-23-v1.json`.
@@ -16,7 +16,9 @@
## Current governance checkpoint
- `ACTIVE_TASK = dev/tasks/DEV-000202-authenticated-ui-quick-smoke.json revision 1`
- `COMPLETION_MATRIX = docs/evidence/audit-completion-matrix-2026-08-23-v3.json`; all seven objective areas are mapped to current repository/production proof, with external bridge, GPU promotion/quality and deep-acceptance gaps explicit.
- `ACTIVE_TASK = dev/tasks/DEV-000207-gpu-invocation-qualification.json revision 1`
- `LINEAGE_DECISION = docs/GOVERNANCE_LINEAGE_DECISION_2026-08-22.md`; historical packets remain provenance and are not current authority.
- `REGISTRY_STATUS = YAML and JSON semantic mirrors agree`
- `PRODUCTION_DOMAIN = docker.cin.su`; shared docker-test is not used for this project.
@@ -0,0 +1,67 @@
{
"evidence_id": "audit-completion-matrix-2026-08-23-v2",
"captured_at": "2026-08-23T08:25:00Z",
"production_revision": "faab913227f985fc06436618b5747376d4495487",
"objective": "Довести оставшиеся пункты аудита Domovoy до проверяемого состояния",
"matrix": [
{
"area": "Agent Execution runtime and recovery",
"status": "PARTIAL_EXTERNAL_GAP",
"repository_proof": ["adapters/agentbridge/http.go", "cmd/agent-worker/local_fallback.go", "modules/work/application/worker.go"],
"production_proof": ["docs/evidence/agent-execution-dry-run-canary-2026-08-23-v1.json", "docs/evidence/authenticated-ui-quick-smoke-2026-08-23-v1.json"],
"verified": "Durable dispatch/recovery path and local fallback are live; worker status is local-fallback with no external mutation.",
"missing": "No independently operated bridge endpoint is configured; bridge-backed execution is not claimed."
},
{
"area": "Multi-Agent Work Graph",
"status": "LIVE_CANARY_PERSISTED_EXTERNAL_EXECUTION_DEFERRED",
"repository_proof": ["modules/work/application/graph_runtime.go", "contracts/work/graph.go", "tests/agent_execution/graph_runtime_test.go"],
"production_proof": ["docs/evidence/audit-runtime-status-2026-08-23-v1.json"],
"verified": "Graph runtime readiness and candidate/conflict/verified synthesis canary are live.",
"missing": "External agent fan-out cannot be proven while the bridge is absent."
},
{
"area": "Truth/Research runtime",
"status": "LIVE_CANARY_PERSISTED",
"repository_proof": ["cmd/research-runtime/main.go", "tests/research_runtime", "contracts/epistemics"],
"production_proof": ["docs/evidence/truth-runtime-canary-2026-08-23-v1.json", "docs/evidence/audit-runtime-status-2026-08-23-v1.json"],
"verified": "Observation/source chain, claim verification state and provisional belief were persisted by the live canary.",
"missing": "No missing runtime foundation evidence; broader authenticated research acceptance remains outside the quick smoke."
},
{
"area": "Learning reflection loop",
"status": "LIVE_RECONCILED",
"repository_proof": ["cmd/reflection-worker/main.go", "database/migrations/032_reflection_candidate_leases.sql", "tests/research_contracts/reflection_worker_sql_test.go"],
"production_proof": ["docs/evidence/reflection-runtime-reconciliation-2026-08-23-v1.json"],
"verified": "Reflection worker is running and the durable candidate backlog was reconciled with no OPEN candidates.",
"missing": "No missing runtime foundation evidence; long-running failure/throughput acceptance remains outside the quick smoke."
},
{
"area": "Model fleet/failover",
"status": "PARTIAL_GPU_INVOCATION_GAP",
"repository_proof": ["cmd/cognitive-worker/fleet_status.go", "modules/capability/domain/fleet.go", "adapters/models/openai_compatible_local.go"],
"production_proof": ["docs/evidence/audit-runtime-status-2026-08-23-v1.json", "docs/evidence/gpu-discovery-2026-08-23-v1.json", "docs/evidence/gpu-invocation-smoke-2026-08-23-v1.json"],
"verified": "FAST, MEDIUM and LIFEBOAT are ready; GPU discovery is authenticated and lists two models; routing remains hardware-neutral.",
"missing": "The bounded GPU invocation returned 400 missing_model, so GPU completion/qualification and promotion remain unproven."
},
{
"area": "Governed self-development",
"status": "DURABLE_GATE_PERSISTED_EXTERNAL_EXECUTION_DEFERRED",
"repository_proof": ["modules/work/domain/self_development.go", "tests/semantic_evolution/self_development_contract_test.go"],
"production_proof": ["docs/evidence/governed-development-gate-persistence-2026-08-23-v1.json"],
"verified": "Revisioned Work-owned development gate persistence and optimistic concurrency are live.",
"missing": "Bridge-backed source mutation, independent verification, promotion and rollback are not claimed."
},
{
"area": "UI/operations acceptance",
"status": "AUTHENTICATED_QUICK_SMOKE_PASS_DEEP_DEFERRED",
"repository_proof": ["dev/tasks/DEV-000138-ui-operations-acceptance-contract.json", "cmd/creator-web/static/assets/console.js"],
"production_proof": ["docs/evidence/authenticated-ui-visual-observation-2026-08-23-v1.json", "docs/evidence/authenticated-ui-quick-smoke-2026-08-23-v1.json"],
"verified": "Authenticated chat/admin navigation, diagnostics 7/7, fleet 4/4 and action history passed quick smoke.",
"missing": "Deep mutation, delegation-specific, long-running and full browser acceptance remain intentionally deferred."
}
],
"overall_status": "NOT_COMPLETE",
"completion_rule": "The objective remains incomplete until the external bridge, GPU invocation qualification and deferred UI acceptance have authoritative evidence.",
"secrets": "No secret values included."
}
@@ -0,0 +1,17 @@
{
"evidence_id": "audit-completion-matrix-2026-08-23-v3",
"captured_at": "2026-08-23T08:30:00Z",
"production_revision_before_release": "9f96393fb9a7ddce19eda3f90cbdeecb024029e6",
"supersedes": "docs/evidence/audit-completion-matrix-2026-08-23-v2.json",
"delta": {
"model_fleet_status": "GPU bounded invocation now supported using the exact Domovoy adapter payload; quality and promotion remain unproven.",
"evidence": "docs/evidence/gpu-invocation-qualification-2026-08-23-v1.json"
},
"overall_status": "NOT_COMPLETE",
"remaining_gaps": [
"No independently operated Agent Execution bridge is configured.",
"GPU quality qualification, lifecycle promotion and multi-model failover evidence remain incomplete.",
"Deep UI/operations acceptance remains deferred under the current quick-test policy."
],
"secrets": "No secret values included."
}
@@ -0,0 +1,20 @@
{
"evidence_id": "gpu-invocation-qualification-2026-08-23-v1",
"captured_at": "2026-08-23T08:30:00Z",
"environment": "production",
"endpoint_class": "protected OpenAI-compatible GPU gateway",
"model": "Qwen3.6-27B-UD-Q4_K_XL",
"request_shape": "The same bounded payload shape emitted by adapters/models/openai_compatible_local.go, including chat_template_kwargs.enable_thinking=false.",
"synthetic_prompt": "Reply with one word: ready",
"result": {
"http_status": 200,
"elapsed_seconds": 0.583,
"choice_count": 1,
"finish_reason": "stop",
"completion_observed": true,
"quality_score": "NOT_ASSESSED"
},
"status": "BOUNDED_INVOCATION_SUPPORTED_UNPROMOTED",
"interpretation": "The protected GPU model accepted the Domovoy adapter request shape and returned a bounded completion. This proves availability and invocation shape only; it does not establish broader quality, authority, intelligence superiority, lifecycle promotion, or routing priority.",
"secrets": "No credential values included."
}
@@ -148,6 +148,9 @@ func SelectCognitiveBinding(requirement capability.CognitiveRequirement, candida
if candidate.Evaluation != nil && candidate.Evaluation.Result == capability.EvaluationUnsupported {
continue
}
if requirement.TaskProfileID != "" && (candidate.TaskEvaluation == nil || candidate.TaskEvaluation.ProfileID != requirement.TaskProfileID || candidate.TaskEvaluation.Result != capability.EvaluationSupported) {
continue
}
rating := RateCognitiveBinding(candidate)
candidate.Rating = &rating
if requirement.MaxCostMicros != nil {
@@ -171,6 +174,19 @@ func SelectCognitiveBinding(requirement capability.CognitiveRequirement, candida
// evidence. Provider kind, endpoint name and hardware are intentionally not
// inputs to the score.
func RateCognitiveBinding(candidate capability.ResolvedCognitiveBinding) capability.ModelRating {
if candidate.TaskEvaluation != nil {
evaluation := candidate.TaskEvaluation
if evaluation.Result != capability.EvaluationSupported {
return capability.ModelRating{TaskProfileID: evaluation.ProfileID, Category: capability.ModelRatingUnrated}
}
category := capability.ModelRatingBalanced
if evaluation.OverallScoreMillis >= 850 && evaluation.QualityScoreMillis >= 800 {
category = capability.ModelRatingQuality
} else if evaluation.LatencyScoreMillis >= 800 && evaluation.OverallScoreMillis >= 700 {
category = capability.ModelRatingFast
}
return capability.ModelRating{TaskProfileID: evaluation.ProfileID, TaskScoreMillis: evaluation.OverallScoreMillis, Category: category, OverallScoreMillis: evaluation.OverallScoreMillis, QualityScoreMillis: evaluation.QualityScoreMillis, ReliabilityScoreMillis: evaluation.ReliabilityScoreMillis, LatencyScoreMillis: evaluation.LatencyScoreMillis, EvidenceScoreMillis: 1000}
}
if candidate.Evaluation == nil || candidate.Evaluation.Result != capability.EvaluationSupported || candidate.Evaluation.QualityScoreMillis == nil || candidate.Evaluation.CriticalFailureMillis == nil {
return capability.ModelRating{Category: capability.ModelRatingUnrated}
}
@@ -0,0 +1,106 @@
package domain
import (
"errors"
"git.cin.su/m/Assistant/contracts/artifact"
"git.cin.su/m/Assistant/contracts/capability"
)
var ErrInvalidTaskEvaluation = errors.New("invalid task evaluation")
func BuildAdditionalTaskEvaluationProfile(parent capability.TaskEvaluationProfile, request capability.TaskEvaluationExpansionRequest, profileID capability.TaskEvaluationProfileID, revision int64) (capability.TaskEvaluationProfile, error) {
if err := parent.Validate(); err != nil || parent.Stage != capability.TaskEvaluationGeneral || request.ParentProfileID != parent.ID || request.CapabilityID != parent.CapabilityID || request.TriggerEvidenceRef == "" || len(request.NewCaseIDs) == 0 || profileID == "" || revision < 1 || request.BenchmarkVersion == "" {
return capability.TaskEvaluationProfile{}, ErrInvalidTaskEvaluation
}
known := make(map[string]struct{}, len(parent.RequiredCaseIDs))
for _, caseID := range parent.RequiredCaseIDs {
known[caseID] = struct{}{}
}
seen := make(map[string]struct{}, len(request.NewCaseIDs))
for _, caseID := range request.NewCaseIDs {
if caseID == "" {
return capability.TaskEvaluationProfile{}, ErrInvalidTaskEvaluation
}
if _, exists := known[caseID]; exists {
return capability.TaskEvaluationProfile{}, ErrInvalidTaskEvaluation
}
if _, exists := seen[caseID]; exists {
return capability.TaskEvaluationProfile{}, ErrInvalidTaskEvaluation
}
seen[caseID] = struct{}{}
}
return capability.TaskEvaluationProfile{ID: profileID, Revision: revision, CapabilityID: parent.CapabilityID, Stage: capability.TaskEvaluationAdditional, ParentProfileID: parent.ID, TriggerEvidenceRef: request.TriggerEvidenceRef, BenchmarkVersion: request.BenchmarkVersion, RequiredCaseIDs: append([]string(nil), request.NewCaseIDs...), QualityWeightMillis: parent.QualityWeightMillis, ReliabilityWeightMillis: parent.ReliabilityWeightMillis, LatencyWeightMillis: parent.LatencyWeightMillis}, nil
}
// BuildTaskEvaluation aggregates independently observed case results. It is
// intentionally deterministic: no provider name, hardware label or caller
// supplied aggregate score can influence the result.
func BuildTaskEvaluation(profile capability.TaskEvaluationProfile, evaluationID capability.TaskEvaluationID, bindingID capability.CapabilityBindingID, bindingRevision int64, capabilityID capability.CapabilityID, observations []capability.TaskCaseObservation, artifactID artifact.ArtifactID, observedAtUnixMillis int64) (capability.TaskEvaluation, error) {
if err := profile.Validate(); err != nil || evaluationID == "" || bindingID == "" || bindingRevision < 1 || capabilityID == "" || capabilityID != profile.CapabilityID || artifactID == "" || observedAtUnixMillis < 1 || len(observations) != len(profile.RequiredCaseIDs) {
return capability.TaskEvaluation{}, ErrInvalidTaskEvaluation
}
required := make(map[string]struct{}, len(profile.RequiredCaseIDs))
for _, caseID := range profile.RequiredCaseIDs {
required[caseID] = struct{}{}
}
seen := make(map[string]struct{}, len(observations))
qualityTotal, latencyTotal, passed, critical := int64(0), int64(0), int64(0), int64(0)
unknown := false
unsupported := false
for _, observation := range observations {
if _, ok := required[observation.CaseID]; !ok {
return capability.TaskEvaluation{}, ErrInvalidTaskEvaluation
}
if _, ok := seen[observation.CaseID]; ok || observation.EvidenceArtifactRef == "" || observation.QualityScoreMillis < 0 || observation.QualityScoreMillis > 1000 || observation.LatencyMicros < 0 {
return capability.TaskEvaluation{}, ErrInvalidTaskEvaluation
}
seen[observation.CaseID] = struct{}{}
qualityTotal += observation.QualityScoreMillis
latencyTotal += observation.LatencyMicros
if observation.CriticalFailure {
critical++
}
switch observation.Result {
case capability.EvaluationSupported:
passed++
case capability.EvaluationUnsupported:
unsupported = true
case capability.EvaluationUnknown:
unknown = true
default:
return capability.TaskEvaluation{}, ErrInvalidTaskEvaluation
}
}
if len(seen) != len(required) {
return capability.TaskEvaluation{}, ErrInvalidTaskEvaluation
}
count := int64(len(observations))
quality := qualityTotal / count
reliability := passed * 1000 / count
latency := latencyScore(latencyTotal / count)
criticalMillis := critical * 1000 / count
overall := (quality*profile.QualityWeightMillis + reliability*profile.ReliabilityWeightMillis + latency*profile.LatencyWeightMillis) / 1000
result := capability.EvaluationSupported
if unknown {
result = capability.EvaluationUnknown
} else if unsupported {
result = capability.EvaluationUnsupported
}
return capability.TaskEvaluation{ID: evaluationID, ProfileID: profile.ID, BindingID: bindingID, BindingRevision: bindingRevision, CapabilityID: capabilityID, Result: result, QualityScoreMillis: quality, ReliabilityScoreMillis: reliability, LatencyScoreMillis: latency, OverallScoreMillis: overall, CriticalFailureMillis: criticalMillis, BenchmarkVersion: profile.BenchmarkVersion, ArtifactID: artifactID, ObservedAtUnixMillis: observedAtUnixMillis}, nil
}
func ValidateTaskEvaluation(value capability.TaskEvaluation) error {
if value.ID == "" || value.ProfileID == "" || value.BindingID == "" || value.BindingRevision < 1 || value.CapabilityID == "" || value.BenchmarkVersion == "" || value.ArtifactID == "" || value.ObservedAtUnixMillis < 1 {
return ErrInvalidTaskEvaluation
}
if value.Result != capability.EvaluationUnknown && value.Result != capability.EvaluationSupported && value.Result != capability.EvaluationUnsupported {
return ErrInvalidTaskEvaluation
}
for _, score := range []int64{value.QualityScoreMillis, value.ReliabilityScoreMillis, value.LatencyScoreMillis, value.OverallScoreMillis, value.CriticalFailureMillis} {
if score < 0 || score > 1000 {
return ErrInvalidTaskEvaluation
}
}
return nil
}
@@ -0,0 +1,35 @@
package domain
import (
"testing"
"git.cin.su/m/Assistant/contracts/artifact"
"git.cin.su/m/Assistant/contracts/capability"
)
func TestBuildTaskEvaluationAggregatesOnlyProfileCases(t *testing.T) {
profile := capability.TaskEvaluationProfile{ID: "research-v1", Revision: 1, CapabilityID: "creator-advisory", Stage: capability.TaskEvaluationGeneral, BenchmarkVersion: "research-v1", RequiredCaseIDs: []string{"claim", "uncertainty"}, QualityWeightMillis: 600, ReliabilityWeightMillis: 300, LatencyWeightMillis: 100}
value, err := BuildTaskEvaluation(profile, "evaluation-1", "binding-1", 2, "creator-advisory", []capability.TaskCaseObservation{
{CaseID: "claim", Result: capability.EvaluationSupported, QualityScoreMillis: 900, LatencyMicros: 400_000, EvidenceArtifactRef: "artifact-case-1"},
{CaseID: "uncertainty", Result: capability.EvaluationSupported, QualityScoreMillis: 800, LatencyMicros: 600_000, EvidenceArtifactRef: "artifact-case-2"},
}, artifact.ArtifactID("artifact-summary"), 10)
if err != nil || value.Result != capability.EvaluationSupported || value.OverallScoreMillis == 0 || value.ProfileID != profile.ID {
t.Fatalf("evaluation=%+v err=%v", value, err)
}
}
func TestBuildTaskEvaluationDoesNotPromoteUnknown(t *testing.T) {
profile := capability.TaskEvaluationProfile{ID: "coding-v1", Revision: 1, CapabilityID: "creator-advisory", Stage: capability.TaskEvaluationGeneral, BenchmarkVersion: "coding-v1", RequiredCaseIDs: []string{"compile"}, QualityWeightMillis: 700, ReliabilityWeightMillis: 200, LatencyWeightMillis: 100}
value, err := BuildTaskEvaluation(profile, "evaluation-2", "binding-1", 2, "creator-advisory", []capability.TaskCaseObservation{{CaseID: "compile", Result: capability.EvaluationUnknown, QualityScoreMillis: 0, LatencyMicros: 0, EvidenceArtifactRef: "artifact-case"}}, artifact.ArtifactID("artifact-summary"), 11)
if err != nil || value.Result != capability.EvaluationUnknown {
t.Fatalf("evaluation=%+v err=%v", value, err)
}
}
func TestBuildAdditionalProfileRequiresUnresolvedEvidenceAndPreservesHistory(t *testing.T) {
parent := capability.TaskEvaluationProfile{ID: "general-v1", Revision: 1, CapabilityID: "creator-advisory", Stage: capability.TaskEvaluationGeneral, BenchmarkVersion: "general-v1", RequiredCaseIDs: []string{"general"}, QualityWeightMillis: 700, ReliabilityWeightMillis: 200, LatencyWeightMillis: 100}
profile, err := BuildAdditionalTaskEvaluationProfile(parent, capability.TaskEvaluationExpansionRequest{ParentProfileID: parent.ID, CapabilityID: parent.CapabilityID, TriggerEvidenceRef: "experience-unsolved-1", NewCaseIDs: []string{"edge-case"}, BenchmarkVersion: "additional-v1"}, "additional-v1", 1)
if err != nil || profile.Stage != capability.TaskEvaluationAdditional || profile.ParentProfileID != parent.ID || profile.RequiredCaseIDs[0] != "edge-case" {
t.Fatalf("profile=%+v err=%v", profile, err)
}
}
+2
View File
@@ -18,6 +18,7 @@ type Repository interface {
SaveModelDescriptor(context.Context, capability.ModelDescriptor) error
SaveCognitiveBinding(context.Context, capability.CognitiveBinding) error
LoadCognitiveCandidates(context.Context, capability.CapabilityID, identity.PrincipalID) ([]capability.ResolvedCognitiveBinding, error)
LoadCognitiveCandidatesForProfile(context.Context, capability.CapabilityID, identity.PrincipalID, capability.TaskEvaluationProfileID) ([]capability.ResolvedCognitiveBinding, error)
LoadCognitiveCandidate(context.Context, capability.CapabilityBindingID, int64) (capability.ResolvedCognitiveBinding, error)
SaveCognitiveCanaryPromotion(context.Context, capability.CognitiveCanaryPromotion) error
SaveCognitiveActivationPromotion(context.Context, capability.CognitiveActivationPromotion) error
@@ -25,4 +26,5 @@ type Repository interface {
SaveCognitiveDiscovery(context.Context, capability.CognitiveDiscoveryRegistration) error
SaveEvaluatedCapability(context.Context, capability.EvaluatedCapability) error
SavePerformanceEvidence(context.Context, capability.PerformanceEvidence) error
SaveTaskEvaluation(context.Context, capability.TaskEvaluation) error
}
+18
View File
@@ -18,6 +18,12 @@ type GraphScheduleRequest struct {
Now int64 `json:"now_unix_millis"`
}
type TaskEvaluationScheduleRequest struct {
Plan work.TaskEvaluationGraphRequest `json:"plan"`
Intents []work.AgentDispatchIntent `json:"intents"`
Now int64 `json:"now_unix_millis"`
}
type GraphCompleteRequest struct {
GraphID string `json:"graph_id"`
Revision int64 `json:"revision"`
@@ -105,6 +111,18 @@ func AdmitAndSchedule(ctx context.Context, store ports.GraphStore, request Graph
return next, ready, nil
}
// AdmitAndScheduleTaskEvaluation creates the durable general-first or
// bounded-expansion fan-out before handing it to the existing graph scheduler.
// The request path only persists/enqueues work; specialist execution happens
// independently through the dispatch workers.
func AdmitAndScheduleTaskEvaluation(ctx context.Context, store ports.GraphStore, request TaskEvaluationScheduleRequest) (work.WorkGraph, []work.GraphNodeID, error) {
graph, err := domain.BuildTaskEvaluationGraph(request.Plan)
if err != nil {
return work.WorkGraph{}, nil, err
}
return AdmitAndSchedule(ctx, store, GraphScheduleRequest{Graph: graph, Intents: request.Intents, Now: request.Now})
}
func CompleteGraphNode(ctx context.Context, store ports.GraphStore, graphID string, revision int64, nodeID work.GraphNodeID, success bool) (work.WorkGraph, error) {
graph, err := store.LoadWorkGraph(ctx, graphID, revision)
if err != nil {
@@ -0,0 +1,62 @@
package domain
import (
"errors"
"fmt"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/work"
)
var ErrInvalidTaskEvaluationGraph = errors.New("invalid task evaluation graph request")
// BuildTaskEvaluationGraph turns one bounded profile into durable specialist
// nodes. It deliberately has no provider, endpoint or hardware inputs: those
// are resolved later by the cognitive worker for each binding.
func BuildTaskEvaluationGraph(request work.TaskEvaluationGraphRequest) (work.WorkGraph, error) {
if request.GraphID == "" || request.IdempotencyPrefix == "" || len(request.BindingIDs) == 0 || len(request.BindingIDs) != len(request.DispatchIDs) || request.Budget.MaxParallel < 1 || request.Budget.MaxAttempts < 1 {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if err := request.Profile.Validate(); err != nil {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if request.Profile.Stage == capability.TaskEvaluationGeneral {
if request.ParentGraphID != "" || request.ParentGraphRevision != 0 || request.ParentGraphState != "" || request.TriggerEvidenceRef != "" {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
} else {
if request.ParentGraphID == "" || request.ParentGraphRevision < 1 || request.TriggerEvidenceRef == "" || (request.ParentGraphState != work.GraphCompleted && request.ParentGraphState != work.GraphPartial) {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if request.Profile.ParentProfileID == "" {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
}
seenBindings := make(map[capability.CapabilityBindingID]struct{}, len(request.BindingIDs))
seenDispatches := make(map[string]struct{}, len(request.DispatchIDs))
nodes := make([]work.GraphNode, 0, len(request.BindingIDs))
for i, bindingID := range request.BindingIDs {
if bindingID == "" || request.DispatchIDs[i] == "" {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if _, ok := seenBindings[bindingID]; ok {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if _, ok := seenDispatches[request.DispatchIDs[i]]; ok {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
seenBindings[bindingID] = struct{}{}
seenDispatches[request.DispatchIDs[i]] = struct{}{}
nodes = append(nodes, work.GraphNode{
ID: work.GraphNodeID(fmt.Sprintf("%s-node-%d", request.GraphID, i+1)),
DispatchID: request.DispatchIDs[i],
IdempotencyKey: fmt.Sprintf("%s:%s:%d", request.IdempotencyPrefix, bindingID, request.Profile.Revision),
State: work.GraphNodeBlocked,
Purpose: work.GraphNodePurposeTaskEvaluation,
TaskProfileID: request.Profile.ID,
BindingID: bindingID,
CaseIDs: append([]string(nil), request.Profile.RequiredCaseIDs...),
})
}
return work.WorkGraph{ID: request.GraphID, Revision: 1, State: work.GraphDraft, Nodes: nodes, Budget: request.Budget, ParentGraphID: request.ParentGraphID, ParentGraphRevision: request.ParentGraphRevision, TriggerEvidenceRef: request.TriggerEvidenceRef}, nil
}
@@ -0,0 +1,52 @@
package domain
import (
"testing"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/work"
)
func evaluationGraphRequest(stage capability.TaskEvaluationProfileStage) work.TaskEvaluationGraphRequest {
profile := capability.TaskEvaluationProfile{ID: "profile-1", Revision: 1, CapabilityID: "capability-1", Stage: stage, BenchmarkVersion: "general-v1", RequiredCaseIDs: []string{"case-a", "case-b"}, QualityWeightMillis: 600, ReliabilityWeightMillis: 300, LatencyWeightMillis: 100}
request := work.TaskEvaluationGraphRequest{GraphID: "graph-1", Profile: profile, BindingIDs: []capability.CapabilityBindingID{"binding-a", "binding-b"}, DispatchIDs: []string{"dispatch-a", "dispatch-b"}, IdempotencyPrefix: "task-eval", Budget: work.GraphBudget{MaxParallel: 2, MaxAttempts: 2}}
if stage == capability.TaskEvaluationAdditional {
request.Profile.ParentProfileID = "profile-general"
request.Profile.TriggerEvidenceRef = "artifact:unresolved-1"
request.Profile.Stage = capability.TaskEvaluationAdditional
request.ParentGraphID, request.ParentGraphRevision, request.ParentGraphState, request.TriggerEvidenceRef = "graph-general", 2, work.GraphPartial, "artifact:unresolved-1"
}
return request
}
func TestBuildTaskEvaluationGraphFansOutOneNodePerBinding(t *testing.T) {
graph, err := BuildTaskEvaluationGraph(evaluationGraphRequest(capability.TaskEvaluationGeneral))
if err != nil {
t.Fatal(err)
}
if len(graph.Nodes) != 2 || graph.Nodes[0].Purpose != work.GraphNodePurposeTaskEvaluation || graph.Nodes[0].TaskProfileID != "profile-1" || len(graph.Nodes[0].CaseIDs) != 2 {
t.Fatalf("unexpected task graph nodes: %+v", graph.Nodes)
}
if graph.ParentGraphID != "" || graph.TriggerEvidenceRef != "" {
t.Fatalf("general graph unexpectedly has expansion lineage: %+v", graph)
}
}
func TestBuildTaskEvaluationGraphRequiresCompletedOrPartialGeneralParent(t *testing.T) {
request := evaluationGraphRequest(capability.TaskEvaluationAdditional)
request.ParentGraphState = work.GraphRunning
if _, err := BuildTaskEvaluationGraph(request); err == nil {
t.Fatal("expected additional graph to require a finished parent graph")
}
}
func TestBuildTaskEvaluationGraphRejectsGeneralReplacement(t *testing.T) {
request := evaluationGraphRequest(capability.TaskEvaluationGeneral)
request.ParentGraphID = "old-graph"
request.ParentGraphRevision = 2
request.ParentGraphState = work.GraphPartial
request.TriggerEvidenceRef = "artifact:unresolved"
if _, err := BuildTaskEvaluationGraph(request); err == nil {
t.Fatal("expected general graph replacement to be rejected")
}
}