fix(cognitive): resolve providers by stable instance identity #216

Merged
m merged 1 commits from codex/dev-000239-stable-provider-resolution into main 2026-08-23 16:03:08 +00:00
4 changed files with 48 additions and 3 deletions
+10 -1
View File
@@ -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 := &registryStub{err: errors.New("stop after resolve")}
gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{}, now: time.Now, allowCanary: true}
+2 -2
View File
@@ -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"]
}