feat(capability): add fleet readiness policy
CI / test (push) Failing after 15s

This commit is contained in:
m
2026-08-22 13:43:36 +03:00
parent f73c69960c
commit d38406fbc5
6 changed files with 145 additions and 1 deletions
+25
View File
@@ -0,0 +1,25 @@
package capability
type FleetTier string
const (
FleetTierFast FleetTier = "FAST"
FleetTierMedium FleetTier = "MEDIUM"
FleetTierHeavy FleetTier = "HEAVY"
FleetTierLifeboat FleetTier = "LIFEBOAT"
)
type FleetMember struct {
ProviderInstanceID ProviderInstanceID
ModelDescriptorID ModelDescriptorID
Tier FleetTier
Ready bool
Reason string
}
type FleetReadiness struct {
ObservedAtUnixMillis int64
Members []FleetMember
ReadyTiers []FleetTier
FallbackTier FleetTier
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000134-truth-bundle-compiler.json",
"task_id": "DEV-000135",
"task_id": "DEV-000136",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000136",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "f73c699",
"risk_tier": "E",
"intent": "Make model fleet tiers, readiness, and explicit lifeboat fallback durable as typed capability policy.",
"requirement_ids": ["WP-601", "Model fleet policy", "Fallback readiness"],
"allowed_change_scope": {"modules": ["capability"], "paths": ["contracts/capability/fleet.go", "modules/capability/domain/fleet.go", "modules/capability/domain/fleet_test.go", "tests/cognitive_registry/fleet_contract_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000136-fleet-readiness-policy.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "database/**", "cmd/**", ".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 cognitive registry and health observations"], "may_change": ["Typed fleet readiness policy"]},
"semantic_owner": "capability",
"canonical_state": ["FleetReadiness value object"],
"state_transitions": ["observed member readiness determines selected preferred or fallback tier"],
"side_effects_and_privileges": ["No provider invocation or external mutation"],
"persistence_and_migrations": ["In-memory typed decision; runtime persistence follows fleet registration integration"],
"failure_retry_restart": ["No ready fallback yields explicit error"],
"assumptions": ["Existing Cognitive Gateway remains the only invocation boundary"],
"acceptance_scenarios": ["heavy timeout selects ready lifeboat", "no ready member fails closed"],
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check"],
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
}
+60
View File
@@ -0,0 +1,60 @@
package domain
import (
"errors"
"slices"
"sort"
"git.cin.su/m/Assistant/contracts/capability"
)
var ErrInvalidFleetReadiness = errors.New("invalid fleet readiness")
func BuildFleetReadiness(observedAt int64, members []capability.FleetMember) (capability.FleetReadiness, error) {
if observedAt < 1 || len(members) == 0 {
return capability.FleetReadiness{}, ErrInvalidFleetReadiness
}
result := capability.FleetReadiness{ObservedAtUnixMillis: observedAt, Members: append([]capability.FleetMember(nil), members...), FallbackTier: capability.FleetTierLifeboat}
seen := map[capability.FleetTier]bool{}
for i := range result.Members {
member := &result.Members[i]
if member.ProviderInstanceID == "" || member.ModelDescriptorID == "" || !validFleetTier(member.Tier) || seen[member.Tier] && member.Ready {
return capability.FleetReadiness{}, ErrInvalidFleetReadiness
}
if member.Ready {
seen[member.Tier] = true
}
}
for _, tier := range []capability.FleetTier{capability.FleetTierFast, capability.FleetTierMedium, capability.FleetTierHeavy, capability.FleetTierLifeboat} {
if seen[tier] {
result.ReadyTiers = append(result.ReadyTiers, tier)
}
}
return result, nil
}
func SelectFleetTier(readiness capability.FleetReadiness, preferred capability.FleetTier) (capability.FleetTier, error) {
if readiness.ObservedAtUnixMillis < 1 || len(readiness.Members) == 0 || !validFleetTier(readiness.FallbackTier) {
return "", ErrInvalidFleetReadiness
}
if slices.Contains(readiness.ReadyTiers, preferred) {
return preferred, nil
}
if slices.Contains(readiness.ReadyTiers, readiness.FallbackTier) {
return readiness.FallbackTier, nil
}
return "", ErrInvalidFleetReadiness
}
func validFleetTier(value capability.FleetTier) bool {
return value == capability.FleetTierFast || value == capability.FleetTierMedium || value == capability.FleetTierHeavy || value == capability.FleetTierLifeboat
}
func SortFleetMembers(members []capability.FleetMember) {
sort.SliceStable(members, func(i, j int) bool {
if members[i].Tier != members[j].Tier {
return members[i].Tier < members[j].Tier
}
return members[i].ProviderInstanceID < members[j].ProviderInstanceID
})
}
+18
View File
@@ -0,0 +1,18 @@
package domain
import (
"testing"
"git.cin.su/m/Assistant/contracts/capability"
)
func TestFleetReadinessFallsBackToLifeboatWhenPrimaryTiersUnavailable(t *testing.T) {
readiness, err := BuildFleetReadiness(100, []capability.FleetMember{{ProviderInstanceID: "cpu", ModelDescriptorID: "qwen", Tier: capability.FleetTierLifeboat, Ready: true, Reason: "local"}, {ProviderInstanceID: "gpu", ModelDescriptorID: "heavy", Tier: capability.FleetTierHeavy, Ready: false, Reason: "timeout"}})
if err != nil {
t.Fatal(err)
}
selected, err := SelectFleetTier(readiness, capability.FleetTierHeavy)
if err != nil || selected != capability.FleetTierLifeboat {
t.Fatalf("selected=%q err=%v", selected, err)
}
}
@@ -0,0 +1,18 @@
package cognitive_registry_test
import (
"testing"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/modules/capability/domain"
)
func TestFleetPolicyRequiresAnExplicitReadyFallback(t *testing.T) {
readiness, err := domain.BuildFleetReadiness(1, []capability.FleetMember{{ProviderInstanceID: "cpu", ModelDescriptorID: "model", Tier: capability.FleetTierFast, Ready: false, Reason: "401"}})
if err != nil {
t.Fatal(err)
}
if _, err := domain.SelectFleetTier(readiness, capability.FleetTierFast); err == nil {
t.Fatal("expected no invocable fallback")
}
}