feat(capability): add release scoped shadow bindings
CI / test (push) Failing after 35s

This commit is contained in:
m
2026-08-31 16:28:57 +03:00
parent d1710907ec
commit dec19e873d
36 changed files with 1078 additions and 50 deletions
@@ -3,21 +3,43 @@ package main
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"os"
"testing"
"git.cin.su/m/Assistant/adapters/notificationexecutor"
"git.cin.su/m/Assistant/contracts/capability"
commitcontract "git.cin.su/m/Assistant/contracts/commit"
"git.cin.su/m/Assistant/contracts/identity"
capabilitydomain "git.cin.su/m/Assistant/modules/capability/domain"
)
func TestCandidateNotificationCredentialsDoNotFallbackToProductionCredential(t *testing.T) {
path := t.TempDir() + "/candidate-token"
if err := os.WriteFile(path, []byte("candidate-only-token"), 0o600); err != nil {
t.Fatal(err)
}
t.Setenv("CANDIDATE_NOTIFICATION_WRITE_TOKEN_FILE", path)
t.Setenv("CANDIDATE_NOTIFICATION_WRITE_SECRET_REF", "credential://domovoy/candidate/notification-write/v1")
ref, secret, err := notificationCredentialsFromRuntime(true)
if err != nil || ref == "" || secret != "candidate-only-token" {
t.Fatalf("candidate credential resolution failed: ref=%q err=%v", ref, err)
}
t.Setenv("CANDIDATE_NOTIFICATION_WRITE_TOKEN_FILE", "")
if _, _, err := notificationCredentialsFromRuntime(true); err == nil {
t.Fatal("candidate path fell back to production notification credential")
}
}
type fakeNotificationBindings struct {
exact capability.Binding
active capability.Binding
exactErr error
activeErr error
exact capability.Binding
active capability.Binding
exactErr error
activeErr error
candidate capability.CandidateBindingResolution
candidateErr error
}
func (f fakeNotificationBindings) CapabilityBinding(context.Context, capability.CapabilityBindingID, int64) (capability.Binding, error) {
@@ -26,6 +48,9 @@ func (f fakeNotificationBindings) CapabilityBinding(context.Context, capability.
func (f fakeNotificationBindings) ActiveCapabilityBinding(context.Context, capability.CapabilityID, identity.PrincipalID) (capability.Binding, error) {
return f.active, f.activeErr
}
func (f fakeNotificationBindings) ResolveCandidateCapabilityBinding(context.Context, capability.CandidateReleaseContext) (capability.CandidateBindingResolution, error) {
return f.candidate, f.candidateErr
}
func dispatcherAction(t *testing.T, binding capability.Binding) commitcontract.AuthorizedAction {
t.Helper()
@@ -81,3 +106,28 @@ func TestOperationDispatcherReconcilesRetiredHistoricalBinding(t *testing.T) {
t.Fatalf("reconcile=%+v", result)
}
}
func TestCandidateDispatcherUsesShadowBindingWithoutActiveFallback(t *testing.T) {
var action commitcontract.AuthorizedAction
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Candidate-Release") != "assistant-slot-b" || r.Header.Get("X-Candidate-Task") != "GEN2-CAPABILITY-001" {
t.Fatalf("candidate headers missing")
}
_ = json.NewEncoder(w).Encode(notificationexecutor.Receipt{ReceiptID: "shadow-1", ActionIntentID: action.Proposal.IntentID, RequestDigest: action.Proposal.NotificationSubject.RequestDigest, AcceptedAt: 1, DeliveryMode: "SHADOW"})
}))
defer server.Close()
stable := dispatcherBinding("https://stable.invalid")
action = dispatcherAction(t, stable)
context := capability.CandidateReleaseContext{ReleaseSetID: "assistant-slot-b", Slot: "b", TaskID: "GEN2-CAPABILITY-001", TaskRevision: 7, PrincipalID: stable.OwnerPrincipalID, CapabilityID: stable.CapabilityID, ResourceScope: "synthetic-shadow-notification-v1", Mode: capability.CandidateBindingModeShadow, TargetKind: "NOTIFICATION_SHADOW_RECEIVER", TargetRef: "notification-receiver-candidate", AuthorityGrantID: "grant"}
candidate := capability.CandidateBinding{ID: "candidate-1", Revision: 1, BaseActiveBindingID: stable.ID, BaseActiveBindingRevision: stable.Revision, Context: context, TargetOrigin: server.URL, DeliveryPath: "/delivery/v1/deliveries", ReceiptPath: "/delivery/v1/receipts", Protocol: capability.ProtocolIdempotentReceiptV1, CreatedAtUnixMillis: 1, ExpiresAtUnixMillis: 100, Status: capability.CandidateBindingActive}
digest, err := capabilitydomain.CandidateTargetDigest(candidate)
if err != nil {
t.Fatal(err)
}
context.TargetDigest, candidate.Context.TargetDigest = digest, digest
dispatcher := operationDispatcher{continuityExecutor: &knownExecutor{}, continuityReconciler: &knownExecutor{}, bindings: fakeNotificationBindings{activeErr: errors.New("stable fallback must not run"), candidate: capability.CandidateBindingResolution{Binding: candidate, Mode: capability.CandidateBindingModeShadow}}, secret: "shadow-secret", client: server.Client(), candidateContext: &context}
result := dispatcher.Execute(action)
if !result.Known || !result.Succeeded || result.Receipt == nil || result.Receipt.DeliveryMode != "SHADOW" {
t.Fatalf("candidate result=%+v", result)
}
}