fix(cognitive): resolve providers by stable instance identity #216
@@ -298,12 +298,21 @@ func fallbackEligible(reason cognitive.FinishReason) bool {
|
||||
}
|
||||
|
||||
func (g cognitiveGateway) provider(selected capability.ResolvedCognitiveBinding) advisoryProvider {
|
||||
var byStableID advisoryProvider
|
||||
for _, registration := range g.providers[selected.Provider.Kind] {
|
||||
if registration.EndpointRef == selected.Provider.EndpointRef && registration.CredentialRef == selected.Provider.CredentialRef {
|
||||
return registration.Provider
|
||||
}
|
||||
// Capability revisions can legitimately retain an older credential
|
||||
// reference while the protected runtime has rotated or reloaded the
|
||||
// current credential. ProviderInstanceID is the stable owner identity;
|
||||
// use it after exact metadata matching so stale non-secret metadata does
|
||||
// not force an unnecessary builtin fallback.
|
||||
if byStableID == nil && registration.ProviderInstanceID == selected.Provider.ID && registration.Kind == selected.Provider.Kind {
|
||||
byStableID = registration.Provider
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return byStableID
|
||||
}
|
||||
|
||||
func writeGatewayResponse(w http.ResponseWriter, response cognitive.GatewayAdvisoryResponse) {
|
||||
|
||||
@@ -88,6 +88,19 @@ func TestActiveRegistryBindingReachesProviderAdapter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderUsesStableInstanceWhenCredentialMetadataIsStale(t *testing.T) {
|
||||
provider := &stubAdvisoryProvider{}
|
||||
gateway := cognitiveGateway{providers: map[capability.ProviderKind][]providerRegistration{
|
||||
"provider-kind-1": {{ProviderInstanceID: "provider-1", Kind: "provider-kind-1", EndpointRef: "current-endpoint", CredentialRef: "current-credential", Provider: provider}},
|
||||
}}
|
||||
selected := gatewaySelection()
|
||||
selected.Provider.EndpointRef = "old-endpoint"
|
||||
selected.Provider.CredentialRef = ""
|
||||
if got := gateway.provider(selected); got != provider {
|
||||
t.Fatalf("stable provider identity was not used for stale metadata")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanaryFlagIsForwardedToCapabilityResolver(t *testing.T) {
|
||||
registry := ®istryStub{err: errors.New("stop after resolve")}
|
||||
gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{}, now: time.Now, allowCanary: true}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000238-local-fallback-evidence.json",
|
||||
"task_id": "DEV-000238",
|
||||
"task_file": "dev/tasks/DEV-000239-stable-provider-resolution.json",
|
||||
"task_id": "DEV-000239",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000239",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "6061eb5c13e46cec1e60fc104b022394f0d94dee",
|
||||
"risk_tier": "E",
|
||||
"intent": "Resolve a registered provider by its stable instance identity when a persisted Capability revision contains stale non-secret endpoint credential metadata, while preserving exact metadata matching first.",
|
||||
"requirement_ids": ["model fleet/failover", "credential rotation compatibility", "local fallback attribution"],
|
||||
"allowed_change_scope": {"modules": ["cognitive-worker"], "paths": ["cmd/cognitive-worker/gateway.go", "cmd/cognitive-worker/gateway_registry_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000239-stable-provider-resolution.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**", "contracts/**"]},
|
||||
"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 ResolvedCognitiveBinding", "protected provider registration"], "may_change": ["internal provider lookup fallback order"]},
|
||||
"semantic_owner": "Cognitive worker provider adapter boundary",
|
||||
"canonical_state": ["no new durable state"],
|
||||
"state_transitions": ["exact provider metadata match -> stable provider identity match -> no provider"],
|
||||
"side_effects_and_privileges": ["provider lookup only; secret values remain in protected runtime"],
|
||||
"persistence_and_migrations": ["none"],
|
||||
"failure_retry_restart": ["missing provider remains explicit and uses existing builtin fallback"],
|
||||
"assumptions": ["ProviderInstanceID is the stable Capability-owned provider identity"],
|
||||
"acceptance_scenarios": ["stale credential metadata still reaches current registered adapter", "exact metadata match retains priority", "no provider remains nil"],
|
||||
"required_tests": ["go test ./cmd/cognitive-worker/...", "go vet ./cmd/cognitive-worker/...", "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", "git diff --check", "independent", "CI", "production health smoke"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
|
||||
}
|
||||
Reference in New Issue
Block a user