feat: finalize operations model workspace
This commit is contained in:
@@ -334,11 +334,10 @@ func (s *Service) resolveActor(ctx context.Context, principal identity.Principal
|
||||
var result actor
|
||||
err := s.registry.pool.QueryRow(ctx, `
|
||||
SELECT t.id::text,t.public_id::text,ua.id::text,ua.public_id::text,
|
||||
COALESCE(bool_or(hm.role IN ('owner','admin') AND hm.status='active'),false)
|
||||
ua.account_kind='operator' AND ua.platform_role IN ('ops_operator','ops_admin')
|
||||
FROM tenant t JOIN user_account ua ON ua.tenant_id=t.id AND ua.status='active'
|
||||
LEFT JOIN household_member hm ON hm.user_id=ua.id AND hm.tenant_id=t.id
|
||||
WHERE t.public_id=$1::uuid AND t.status='active' AND ua.oidc_issuer=$2 AND ua.oidc_subject=$3
|
||||
GROUP BY t.id,t.public_id,ua.id,ua.public_id`, tenantPublicID, principal.Issuer, principal.Subject).Scan(&result.tenantID, &result.tenantPublicID, &result.userID, &result.userPublicID, &result.canManage)
|
||||
`, tenantPublicID, principal.Issuer, principal.Subject).Scan(&result.tenantID, &result.tenantPublicID, &result.userID, &result.userPublicID, &result.canManage)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return actor{}, ErrAccessDenied
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package contentmodel
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -38,7 +39,14 @@ func TestServiceTestsAndActivatesCompatibleModel(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
principal := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000101"}
|
||||
principal := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000104"}
|
||||
customerPrincipal := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000101"}
|
||||
if _, err := service.Create(ctx, customerPrincipal, CreateInput{
|
||||
TenantID: "10000000-0000-0000-0000-000000000010", DisplayName: "家庭端不应创建", Provider: "openai_compatible",
|
||||
BaseURL: server.URL + "/v1", ModelID: "customer-model", APIKey: "integration-key", Temperature: 0.1, MaxTokens: 512,
|
||||
}, "content-model-customer-create"); !errors.Is(err, ErrAccessDenied) {
|
||||
t.Fatalf("customer content model create error=%v want access denied", err)
|
||||
}
|
||||
created, err := service.Create(ctx, principal, CreateInput{
|
||||
TenantID: "10000000-0000-0000-0000-000000000010", DisplayName: "集成测试模型", Provider: "openai_compatible",
|
||||
BaseURL: server.URL + "/v1", ModelID: "test-model", APIKey: "integration-key", Temperature: 0.1, MaxTokens: 512,
|
||||
|
||||
@@ -91,7 +91,10 @@ func (p *Processor) Handle(ctx context.Context, event outbox.Event) error {
|
||||
}
|
||||
sourceTurns := subjectTurns(snapshot.turns)
|
||||
if len(sourceTurns) == 0 {
|
||||
return outbox.Retryable(ErrConversationContentNotReady)
|
||||
// Terminal conversations cannot receive more subject turns. Treat the
|
||||
// extraction request as complete so permanently empty conversations do
|
||||
// not cycle through retry and dead-letter queues forever.
|
||||
return nil
|
||||
}
|
||||
content := joinedConversationContent(snapshot.turns)
|
||||
generated, err := p.generateSafely(ctx, content, snapshot.tenantID)
|
||||
|
||||
@@ -39,6 +39,7 @@ func TestProcessorExtractionIdempotencyAndSensitiveContent(t *testing.T) {
|
||||
consentSnapshotID, consentSnapshotPublicID := uuid.NewString(), uuid.NewString()
|
||||
contextID, contextPublicID := uuid.NewString(), uuid.NewString()
|
||||
conversationID, conversationPublicID := uuid.NewString(), uuid.NewString()
|
||||
emptyConversationID, emptyConversationPublicID := uuid.NewString(), uuid.NewString()
|
||||
defer func() {
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM conversation_content_assessment WHERE conversation_id=$1::uuid`, conversationID)
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM fact_candidate WHERE story_id IN (SELECT id FROM story_draft WHERE conversation_id=$1::uuid)`, conversationID)
|
||||
@@ -49,6 +50,7 @@ func TestProcessorExtractionIdempotencyAndSensitiveContent(t *testing.T) {
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM conversation_summary WHERE conversation_id=$1::uuid`, conversationID)
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM conversation_turn WHERE conversation_id=$1::uuid`, conversationID)
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM conversation WHERE id=$1::uuid`, conversationID)
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM conversation WHERE id=$1::uuid`, emptyConversationID)
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM context_snapshot WHERE id=$1::uuid`, contextID)
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM consent_snapshot WHERE id=$1::uuid`, consentSnapshotID)
|
||||
}()
|
||||
@@ -74,6 +76,18 @@ func TestProcessorExtractionIdempotencyAndSensitiveContent(t *testing.T) {
|
||||
conversationID, conversationPublicID, consentSnapshotID, contextID, "extract-task-"+conversationID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, `
|
||||
INSERT INTO conversation(id,public_id,tenant_id,household_id,subject_id,device_id,consent_snapshot_id,context_snapshot_id,
|
||||
provider_task_ref,engine_mode,retention_mode,status,allocation_expires_at,started_at,ended_at,stop_reason)
|
||||
VALUES($1::uuid,$2::uuid,'10000000-0000-0000-0000-000000000001','10000000-0000-0000-0000-000000000201',
|
||||
'10000000-0000-0000-0000-000000000401','10000000-0000-0000-0000-000000000501',
|
||||
$3::uuid,$4::uuid,$5,'s2s_hybrid','transcript_only','COMPLETED',now()+interval '5 minutes',now()-interval '1 minute',now(),'user_hangup')`,
|
||||
emptyConversationID, emptyConversationPublicID, consentSnapshotID, contextID, "extract-empty-task-"+emptyConversationID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := processor.Handle(ctx, outbox.Event{AggregateID: emptyConversationID, Payload: []byte(`{"generation_version":1}`)}); err != nil {
|
||||
t.Fatalf("terminal conversation without subject turns should not retry: %v", err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, `
|
||||
INSERT INTO conversation_turn(tenant_id,subject_id,conversation_id,turn_key,sequence_no,speaker,start_at,end_at,final_text,source_type)
|
||||
VALUES('10000000-0000-0000-0000-000000000001','10000000-0000-0000-0000-000000000401',$1::uuid,$2,1,'subject',now()-interval '2 minutes',now()-interval '110 seconds',$3,'provider'),
|
||||
|
||||
@@ -27,7 +27,6 @@ const (
|
||||
CapabilityRecordingDownload Capability = "recording.download"
|
||||
CapabilityPrivacyRead Capability = "privacy.read"
|
||||
CapabilityPrivacyManage Capability = "privacy.manage"
|
||||
CapabilityModelManage Capability = "model.manage"
|
||||
)
|
||||
|
||||
var roleCapabilities = map[Role]map[Capability]struct{}{
|
||||
@@ -36,14 +35,12 @@ var roleCapabilities = map[Role]map[Capability]struct{}{
|
||||
CapabilityOwnershipTransfer, CapabilitySubjectRead, CapabilitySubjectWrite,
|
||||
CapabilityConversationRead, CapabilityStoryReview, CapabilityDeviceManage,
|
||||
CapabilityFirmwareManage, CapabilityPrivacyRead, CapabilityPrivacyManage,
|
||||
CapabilityModelManage,
|
||||
),
|
||||
RoleAdmin: capabilitySet(
|
||||
CapabilityHouseholdRead, CapabilityHouseholdManage, CapabilityMemberManage,
|
||||
CapabilitySubjectRead, CapabilitySubjectWrite, CapabilityConversationRead,
|
||||
CapabilityStoryReview, CapabilityDeviceManage,
|
||||
CapabilityFirmwareManage, CapabilityPrivacyRead,
|
||||
CapabilityModelManage,
|
||||
),
|
||||
RoleEditor: capabilitySet(
|
||||
CapabilityHouseholdRead, CapabilitySubjectRead, CapabilitySubjectWrite,
|
||||
|
||||
Reference in New Issue
Block a user