Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c41634a065 | ||
|
|
2609f31e59 |
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.cin.su/m/Assistant/contracts/capability"
|
"git.cin.su/m/Assistant/contracts/capability"
|
||||||
@@ -17,6 +18,10 @@ type discoveryService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s discoveryService) handler(w http.ResponseWriter, r *http.Request) {
|
func (s discoveryService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet && r.URL.Path == "/v1/provider-discovery/status" {
|
||||||
|
s.statusHandler(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
registration, ok := s.providers[r.PathValue("provider")]
|
registration, ok := s.providers[r.PathValue("provider")]
|
||||||
if !ok || registration.Discovery == nil {
|
if !ok || registration.Discovery == nil {
|
||||||
http.Error(w, "provider discovery not found", http.StatusNotFound)
|
http.Error(w, "provider discovery not found", http.StatusNotFound)
|
||||||
@@ -35,3 +40,42 @@ func (s discoveryService) handler(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_ = json.NewEncoder(w).Encode(result)
|
_ = json.NewEncoder(w).Encode(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type discoveryStatus struct {
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
ProviderKind capability.ProviderKind `json:"provider_kind"`
|
||||||
|
Health capability.HealthState `json:"health"`
|
||||||
|
FailureClass capability.CognitiveFailureType `json:"failure_class,omitempty"`
|
||||||
|
ObservedAtUnixMillis int64 `json:"observed_at_unix_millis"`
|
||||||
|
Models []discoveredModelStatus `json:"models"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type discoveredModelStatus struct {
|
||||||
|
ProviderModelID capability.ProviderModelID `json:"provider_model_id"`
|
||||||
|
Fingerprint capability.ModelFingerprint `json:"fingerprint,omitempty"`
|
||||||
|
Capabilities []capability.ModelCapability `json:"capabilities,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s discoveryService) statusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
providers := make([]string, 0, len(s.providers))
|
||||||
|
for name := range s.providers {
|
||||||
|
providers = append(providers, name)
|
||||||
|
}
|
||||||
|
sort.Strings(providers)
|
||||||
|
result := make([]discoveryStatus, 0, len(providers))
|
||||||
|
for _, name := range providers {
|
||||||
|
registration := s.providers[name]
|
||||||
|
status := discoveryStatus{Provider: name, ProviderKind: registration.Kind, Health: capability.HealthUnknown, ObservedAtUnixMillis: s.now().UnixMilli(), Models: []discoveredModelStatus{}}
|
||||||
|
if registration.Discovery != nil {
|
||||||
|
models, probe := registration.Discovery.Discover(r.Context())
|
||||||
|
status.Health = probe.Health
|
||||||
|
status.FailureClass = capability.CognitiveFailureType(probe.Failure)
|
||||||
|
for _, model := range models {
|
||||||
|
status.Models = append(status.Models, discoveredModelStatus{ProviderModelID: model.ProviderModelID, Fingerprint: model.Fingerprint, Capabilities: model.Capabilities})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = append(result, status)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_ = json.NewEncoder(w).Encode(result)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.cin.su/m/Assistant/adapters/models"
|
||||||
|
"git.cin.su/m/Assistant/contracts/capability"
|
||||||
|
"git.cin.su/m/Assistant/contracts/cognitive"
|
||||||
|
"git.cin.su/m/Assistant/contracts/identity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type discoveryTestProvider struct{}
|
||||||
|
|
||||||
|
func (discoveryTestProvider) Probe(context.Context) models.ProviderProbe {
|
||||||
|
return models.ProviderProbe{Health: capability.HealthAvailable}
|
||||||
|
}
|
||||||
|
func (discoveryTestProvider) Discover(context.Context) ([]models.DiscoveredModel, models.ProviderProbe) {
|
||||||
|
return []models.DiscoveredModel{{ProviderModelID: "local-model", Fingerprint: "fingerprint-1", Capabilities: []capability.ModelCapability{capability.CapabilityAdvisory}}}, models.ProviderProbe{Health: capability.HealthAvailable}
|
||||||
|
}
|
||||||
|
func (discoveryTestProvider) Advise(context.Context, cognitive.AdvisoryRequest) (cognitive.AdvisoryResponse, error) {
|
||||||
|
return cognitive.AdvisoryResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscoveryStatusIsReadOnlyAndDoesNotExposeCredentialMetadata(t *testing.T) {
|
||||||
|
service := discoveryService{
|
||||||
|
providers: map[string]providerRegistration{"cpu": {ProviderInstanceID: "provider-1", Kind: "openai-compatible-chat-v1", CredentialRef: "credential://must-not-be-returned", Discovery: discoveryTestProvider{}}},
|
||||||
|
owner: identity.PrincipalID("owner-1"),
|
||||||
|
now: func() time.Time { return time.UnixMilli(1234) },
|
||||||
|
}
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
service.handler(response, httptest.NewRequest(http.MethodGet, "/v1/provider-discovery/status", nil))
|
||||||
|
if response.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status=%d", response.Code)
|
||||||
|
}
|
||||||
|
body := response.Body.String()
|
||||||
|
var result []discoveryStatus
|
||||||
|
if err := json.NewDecoder(strings.NewReader(body)).Decode(&result); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(result) != 1 || result[0].Provider != "cpu" || result[0].Health != capability.HealthAvailable || len(result[0].Models) != 1 || result[0].ObservedAtUnixMillis != 1234 {
|
||||||
|
t.Fatalf("unexpected discovery status: %+v", result)
|
||||||
|
}
|
||||||
|
if strings.Contains(body, "must-not-be-returned") || strings.Contains(body, "credential://") {
|
||||||
|
t.Fatalf("credential metadata leaked in discovery status: %s", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -169,6 +169,7 @@ func main() {
|
|||||||
mux.HandleFunc("POST /v1/task-evaluations/execute", gateway.taskEvaluationHandler)
|
mux.HandleFunc("POST /v1/task-evaluations/execute", gateway.taskEvaluationHandler)
|
||||||
discovery := discoveryService{resolver: resolver, providers: discoveries, owner: identity.PrincipalID(envOr("COGNITIVE_OWNER_PRINCIPAL_ID", "domovoy-creator-principal")), now: time.Now}
|
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)
|
mux.HandleFunc("POST /v1/provider-discovery/{provider}", discovery.handler)
|
||||||
|
mux.HandleFunc("GET /v1/provider-discovery/status", discovery.statusHandler)
|
||||||
refreshProviderDiscoveries(resolver, discovery)
|
refreshProviderDiscoveries(resolver, discovery)
|
||||||
log.Printf("cognitive-worker listening on %s", addr)
|
log.Printf("cognitive-worker listening on %s", addr)
|
||||||
if err := http.ListenAndServe(addr, mux); err != nil {
|
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"task_file": "dev/tasks/DEV-000227-local-executor.json",
|
"task_file": "dev/tasks/DEV-000228-controlled-discovery.json",
|
||||||
"task_id": "DEV-000227",
|
"task_id": "DEV-000228",
|
||||||
"task_revision": 1
|
"task_revision": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"task_id": "DEV-000228",
|
||||||
|
"task_revision": 1,
|
||||||
|
"architecture_revision": "ADR-0018",
|
||||||
|
"base_repo_revision": "2609f31e59d3914cdf4adcf23ec618705277d12a",
|
||||||
|
"risk_tier": "E",
|
||||||
|
"intent": "Expose a read-only, provider-neutral discovery status view so Domovoy can inspect available optional providers and models before requesting qualification, without exposing credential metadata or activating anything.",
|
||||||
|
"requirement_ids": ["controlled discovery", "provider qualification visibility", "optional external tools"],
|
||||||
|
"allowed_change_scope": {"modules": ["cognitive-worker", "operations-status"], "paths": ["cmd/cognitive-worker/discovery.go", "cmd/cognitive-worker/discovery_test.go", "cmd/cognitive-worker/main.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000228-controlled-discovery.json", "docs/PROJECT_STATUS.md"]},
|
||||||
|
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "contracts/**", "database/**", "deploy/**", "LLM-local/**"]},
|
||||||
|
"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 LocalProvider discovery probes", "existing capability model metadata"], "may_change": ["read-only cognitive-worker discovery status projection"]},
|
||||||
|
"semantic_owner": "cognitive-worker observation projection",
|
||||||
|
"canonical_state": ["existing Capability discovery registration remains the canonical persisted state"],
|
||||||
|
"state_transitions": ["provider probe -> observed discovery status", "discovery registration remains DISCOVERED/INACTIVE until qualification and promotion"],
|
||||||
|
"side_effects_and_privileges": ["read-only provider probe on explicit status request; no activation, mutation or credential disclosure"],
|
||||||
|
"persistence_and_migrations": ["none"],
|
||||||
|
"failure_retry_restart": ["unavailable provider is reported with health/failure state; persisted discovery remains available through existing owner"],
|
||||||
|
"assumptions": ["provider discovery adapters already enforce bounded requests", "qualification remains a separate task-evaluation and promotion step"],
|
||||||
|
"acceptance_scenarios": ["status lists registered providers deterministically", "model inventory and probe health are visible", "credential references are not returned", "status does not activate or persist a provider", "unknown provider remains absent or unavailable"],
|
||||||
|
"required_tests": ["go test ./cmd/cognitive-worker/...", "go vet ./cmd/cognitive-worker/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production status smoke"],
|
||||||
|
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
- `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.
|
- `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-000227-local-executor.json revision 1`; the existing task-evaluation path is now being exposed as a bounded local executor while unsupported work remains durably waiting.
|
- `ACTIVE_TASK = dev/tasks/DEV-000228-controlled-discovery.json revision 1`; the cognitive worker now exposes a read-only provider/model discovery status view. Discovery does not qualify, activate or authorize a provider; those remain separate evidence-gated steps.
|
||||||
- `LINEAGE_DECISION = docs/GOVERNANCE_LINEAGE_DECISION_2026-08-22.md`; historical packets remain provenance and are not current authority.
|
- `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`
|
- `REGISTRY_STATUS = YAML and JSON semantic mirrors agree`
|
||||||
- `PRODUCTION_DOMAIN = docker.cin.su`; shared docker-test is not used for this project.
|
- `PRODUCTION_DOMAIN = docker.cin.su`; shared docker-test is not used for this project.
|
||||||
|
|||||||
Reference in New Issue
Block a user