diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp index 354d887..99ea68d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -982,6 +982,46 @@ namespace HyperTwistContractLibraryInternal }; } + TArray MakeDefaultSpeechRoutingEligibleProviderClasses() + { + return { + TEXT("official-direct-provider"), + TEXT("openai-compatible-custom-endpoint"), + TEXT("byok-endpoint"), + TEXT("managed-gateway-provider"), + TEXT("local-self-hosted-provider"), + TEXT("bounded-speech-sidecar-service"), + TEXT("first-party-mock-provider") + }; + } + + TArray MakeDefaultSpeechRoutingEligibleEndpointClasses() + { + return { + TEXT("official-http-api"), + TEXT("openai-compatible-http-api"), + TEXT("managed-gateway-http"), + TEXT("local-self-hosted-http"), + TEXT("in-process-mock") + }; + } + + TArray MakeDefaultSpeechRoutingRequiredCapabilities() + { + return { + TEXT("transcribe"), + TEXT("grammarHints"), + TEXT("manualTranscriptCommit") + }; + } + + TArray MakeDefaultSpeechRoutingSupportedTaskKinds() + { + return { + TEXT("transcribe") + }; + } + FHyperTwistSpeechProviderProfile MakeMockSpeechProviderProfile() { FHyperTwistSpeechProviderProfile Profile; @@ -1029,6 +1069,110 @@ namespace HyperTwistContractLibraryInternal return Profile; } + FHyperTwistSpeechProviderRoutingPolicy MakeMockSpeechProviderRoutingPolicy() + { + FHyperTwistSpeechProviderRoutingPolicy Policy; + Policy.ProviderRoutingPolicyId = TEXT("speech-provider-routing/local-offline-mock-first-v1"); + Policy.PolicyKind = TEXT("speech-provider-routing"); + Policy.RouteSelectionMode = TEXT("local-offline-mock-first"); + Policy.FallbackMode = TEXT("fail-closed"); + Policy.WorkflowPolicyState = TEXT("first-party-provider-policy-governed"); + Policy.FailureEscalationMode = TEXT("preserve-local-verification-route"); + Policy.UserOverridePosture = TEXT("client-kind-and-profile-override-supported"); + Policy.EligibleProviderClasses = MakeDefaultSpeechRoutingEligibleProviderClasses(); + Policy.EligibleEndpointClasses = MakeDefaultSpeechRoutingEligibleEndpointClasses(); + Policy.RequiredCapabilityFlags = MakeDefaultSpeechRoutingRequiredCapabilities(); + Policy.SupportedTaskKinds = MakeDefaultSpeechRoutingSupportedTaskKinds(); + Policy.bAllowProviderBackedRoutes = false; + Policy.bAllowMockVerificationRoutes = true; + Policy.bAllowOpenAiCompatibleCustomEndpointRoutes = true; + Policy.bAllowUserLabeledByokRoutes = true; + Policy.bAllowManagedGatewayRoutes = true; + Policy.bAllowLocalSelfHostedRoutes = true; + Policy.bRespectsProfileEnableDisable = true; + Policy.bAllowFailClosedWhenNoEligibleRoute = true; + Policy.bAllowHealthPostureFallback = false; + Policy.bPreservesFirstPartyRoutingOwnership = true; + Policy.bAllowsDonorOwnedRoutingLanguage = false; + return Policy; + } + + FHyperTwistSpeechProviderRoutingPolicy MakeProviderBackedSpeechProviderRoutingPolicy() + { + FHyperTwistSpeechProviderRoutingPolicy Policy; + Policy.ProviderRoutingPolicyId = TEXT("speech-provider-routing/provider-backed-sidecar-first-v1"); + Policy.PolicyKind = TEXT("speech-provider-routing"); + Policy.RouteSelectionMode = TEXT("provider-backed-sidecar-first"); + Policy.FallbackMode = TEXT("health-posture-only"); + Policy.WorkflowPolicyState = TEXT("first-party-provider-policy-governed"); + Policy.FailureEscalationMode = TEXT("fail-closed-and-report-health"); + Policy.UserOverridePosture = TEXT("profile-or-client-kind-override-supported"); + Policy.EligibleProviderClasses = MakeDefaultSpeechRoutingEligibleProviderClasses(); + Policy.EligibleEndpointClasses = MakeDefaultSpeechRoutingEligibleEndpointClasses(); + Policy.RequiredCapabilityFlags = MakeDefaultSpeechRoutingRequiredCapabilities(); + Policy.SupportedTaskKinds = MakeDefaultSpeechRoutingSupportedTaskKinds(); + Policy.bAllowProviderBackedRoutes = true; + Policy.bAllowMockVerificationRoutes = true; + Policy.bAllowOpenAiCompatibleCustomEndpointRoutes = true; + Policy.bAllowUserLabeledByokRoutes = true; + Policy.bAllowManagedGatewayRoutes = true; + Policy.bAllowLocalSelfHostedRoutes = true; + Policy.bRespectsProfileEnableDisable = true; + Policy.bAllowFailClosedWhenNoEligibleRoute = true; + Policy.bAllowHealthPostureFallback = true; + Policy.bPreservesFirstPartyRoutingOwnership = true; + Policy.bAllowsDonorOwnedRoutingLanguage = false; + return Policy; + } + + FHyperTwistSpeechProviderRouteDecision MakeSpeechProviderRouteDecision( + const FHyperTwistSpeechProviderRoutingPolicy& Policy, + const FHyperTwistSpeechProviderProfile& Profile, + const FString& SelectedServiceLaneId, + const FString& DecisionSource, + const FString& RouteStateId, + const FString& DecisionReason, + const FString& FailureReason, + const bool bProviderBackedRouteRequested, + const bool bProviderBackedRouteSelected, + const bool bUsedFallbackHealthPosture, + const bool bRouteReady + ) + { + FHyperTwistSpeechProviderRouteDecision Decision; + Decision.RouteDecisionId = FString::Printf( + TEXT("speech-route-decision/%s"), + *Profile.ProviderProfileId + ); + Decision.ProviderRoutingPolicyId = Policy.ProviderRoutingPolicyId; + Decision.DecisionSource = DecisionSource; + Decision.RequestedProviderProfileId = Profile.ProviderProfileId; + Decision.SelectedProviderProfileId = Profile.ProviderProfileId; + Decision.SelectedProviderClass = Profile.ProviderClass; + Decision.SelectedEndpointClass = Profile.EndpointClass; + Decision.SelectedServiceLaneId = SelectedServiceLaneId.IsEmpty() + ? TEXT("speech/python-service") + : SelectedServiceLaneId; + Decision.SelectedTransportKind = Profile.EndpointClass.Equals(TEXT("in-process-mock"), ESearchCase::IgnoreCase) + ? TEXT("in-process") + : TEXT("http"); + Decision.RouteStateId = RouteStateId; + Decision.DecisionReason = DecisionReason; + Decision.FailureReason = FailureReason; + Decision.FallbackMode = Policy.FallbackMode; + Decision.bProviderBackedRouteRequested = bProviderBackedRouteRequested; + Decision.bProviderBackedRouteSelected = bProviderBackedRouteSelected; + Decision.bUsedFallbackHealthPosture = bUsedFallbackHealthPosture; + Decision.bSelectedProfileEnabled = Profile.bEnabled; + Decision.bSelectedCustomEndpointRoute = + Profile.bUserLabeled + && Profile.bSupportsCustomBaseUrl + && !Profile.EndpointBaseUrl.IsEmpty() + && !Profile.EndpointBaseUrl.Contains(TEXT("127.0.0.1"), ESearchCase::IgnoreCase); + Decision.bRouteReady = bRouteReady; + return Decision; + } + FHyperTwistSpeechByokCustodyProfile MakeDefaultSpeechByokCustodyProfile(const FString& ProfileId) { FHyperTwistSpeechByokCustodyProfile Profile; @@ -1852,6 +1996,23 @@ FHyperTwistSpeechSessionConfig UHyperTwistContractLibrary::MakeSampleSpeechSessi HyperTwistContractLibraryInternal::MakeDefaultSpeechByokCustodyProfile( Config.ByokCustodyProfileId ); + Config.ProviderRoutingPolicyId = TEXT("speech-provider-routing/provider-backed-sidecar-first-v1"); + Config.ProviderRoutingPolicyDefinition = + HyperTwistContractLibraryInternal::MakeProviderBackedSpeechProviderRoutingPolicy(); + Config.ProviderRouteDecision = + HyperTwistContractLibraryInternal::MakeSpeechProviderRouteDecision( + Config.ProviderRoutingPolicyDefinition, + Config.ProviderProfileDefinition, + TEXT("speech/python-service"), + TEXT("sample-session-config"), + TEXT("route-ready"), + TEXT("provider-backed-sidecar-route-selected"), + FString(), + true, + true, + false, + true + ); Config.PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); Config.PayloadCustodyProfileDefinition = HyperTwistContractLibraryInternal::MakeDefaultSpeechPayloadCustodyProfile( @@ -1950,6 +2111,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistContractLibrary::MakeMockSpeechService TEXT("promptHints"), TEXT("providerProfileCustody"), TEXT("byokCustody"), + TEXT("providerRoutingPolicy"), + TEXT("providerRouteSelection"), + TEXT("workflowPolicyState"), TEXT("openAiCompatibleCustomEndpointProfile"), TEXT("userLabeledByokProfiles"), TEXT("downloadableModelPayloadCustody"), @@ -1965,6 +2129,23 @@ FHyperTwistSpeechServiceHealth UHyperTwistContractLibrary::MakeMockSpeechService HyperTwistContractLibraryInternal::MakeDefaultSpeechByokCustodyProfile( Health.ByokCustodyProfileId ); + Health.ProviderRoutingPolicyId = TEXT("speech-provider-routing/local-offline-mock-first-v1"); + Health.ProviderRoutingPolicyDefinition = + HyperTwistContractLibraryInternal::MakeMockSpeechProviderRoutingPolicy(); + Health.ProviderRouteDecision = + HyperTwistContractLibraryInternal::MakeSpeechProviderRouteDecision( + Health.ProviderRoutingPolicyDefinition, + Health.ProviderProfileDefinition, + TEXT("speech/python-service"), + TEXT("mock-health-default"), + TEXT("route-ready"), + TEXT("mock-verification-route-selected"), + FString(), + false, + false, + false, + true + ); Health.PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); Health.PayloadCustodyProfileDefinition = HyperTwistContractLibraryInternal::MakeDefaultSpeechPayloadCustodyProfile( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp index 339effb..aafd1ed 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp @@ -332,6 +332,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal TEXT("promptHints"), TEXT("providerProfileCustody"), TEXT("byokCustody"), + TEXT("providerRoutingPolicy"), + TEXT("providerRouteSelection"), + TEXT("workflowPolicyState"), TEXT("openAiCompatibleCustomEndpointProfile"), TEXT("userLabeledByokProfiles"), TEXT("downloadableModelPayloadCustody"), @@ -358,6 +361,18 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal Health.ProviderProfileDefinition.EndpointBaseUrl = ServiceBaseUrl; Health.ByokCustodyProfileId = ProviderDefaults.ByokCustodyProfileId; Health.ByokCustodyProfileDefinition = ProviderDefaults.ByokCustodyProfileDefinition; + Health.ProviderRoutingPolicyId = ProviderDefaults.ProviderRoutingPolicyId; + Health.ProviderRoutingPolicyDefinition = ProviderDefaults.ProviderRoutingPolicyDefinition; + Health.ProviderRouteDecision = ProviderDefaults.ProviderRouteDecision; + Health.ProviderRouteDecision.RouteStateId = ServiceBaseUrl.IsEmpty() + ? TEXT("route-awaiting-endpoint") + : TEXT("route-health-degraded"); + Health.ProviderRouteDecision.DecisionReason = ServiceBaseUrl.IsEmpty() + ? TEXT("provider-backed-sidecar-route-unavailable") + : TEXT("provider-backed-sidecar-health-degraded"); + Health.ProviderRouteDecision.FailureReason = LastTransportError; + Health.ProviderRouteDecision.bUsedFallbackHealthPosture = true; + Health.ProviderRouteDecision.bRouteReady = false; Health.LastError = LastTransportError; return Health; } @@ -388,6 +403,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal TEXT("promptHints"), TEXT("providerProfileCustody"), TEXT("byokCustody"), + TEXT("providerRoutingPolicy"), + TEXT("providerRouteSelection"), + TEXT("workflowPolicyState"), TEXT("openAiCompatibleCustomEndpointProfile"), TEXT("userLabeledByokProfiles"), TEXT("downloadableModelPayloadCustody"), @@ -404,6 +422,14 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal Health.ProviderProfileDefinition.EndpointBaseUrl = ServiceBaseUrl; Health.ByokCustodyProfileId = ProviderDefaults.ByokCustodyProfileId; Health.ByokCustodyProfileDefinition = ProviderDefaults.ByokCustodyProfileDefinition; + Health.ProviderRoutingPolicyId = ProviderDefaults.ProviderRoutingPolicyId; + Health.ProviderRoutingPolicyDefinition = ProviderDefaults.ProviderRoutingPolicyDefinition; + Health.ProviderRouteDecision = ProviderDefaults.ProviderRouteDecision; + Health.ProviderRouteDecision.RouteStateId = TEXT("route-health-degraded"); + Health.ProviderRouteDecision.DecisionReason = TEXT("provider-backed-sidecar-health-degraded"); + Health.ProviderRouteDecision.FailureReason = LastTransportError; + Health.ProviderRouteDecision.bUsedFallbackHealthPosture = true; + Health.ProviderRouteDecision.bRouteReady = false; Health.LastError = LastTransportError; return Health; } @@ -432,6 +458,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal Health.Capabilities.AddUnique(TEXT("promptHints")); Health.Capabilities.AddUnique(TEXT("providerProfileCustody")); Health.Capabilities.AddUnique(TEXT("byokCustody")); + Health.Capabilities.AddUnique(TEXT("providerRoutingPolicy")); + Health.Capabilities.AddUnique(TEXT("providerRouteSelection")); + Health.Capabilities.AddUnique(TEXT("workflowPolicyState")); Health.Capabilities.AddUnique(TEXT("openAiCompatibleCustomEndpointProfile")); Health.Capabilities.AddUnique(TEXT("userLabeledByokProfiles")); Health.Capabilities.AddUnique(TEXT("downloadableModelPayloadCustody")); @@ -457,6 +486,18 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal { Health.ByokCustodyProfileDefinition = ProviderDefaults.ByokCustodyProfileDefinition; } + if (Health.ProviderRoutingPolicyId.IsEmpty()) + { + Health.ProviderRoutingPolicyId = ProviderDefaults.ProviderRoutingPolicyId; + } + if (!Health.ProviderRoutingPolicyDefinition.IsStructurallyValid()) + { + Health.ProviderRoutingPolicyDefinition = ProviderDefaults.ProviderRoutingPolicyDefinition; + } + if (!Health.ProviderRouteDecision.IsStructurallyValid()) + { + Health.ProviderRouteDecision = ProviderDefaults.ProviderRouteDecision; + } if (Health.PayloadCustodyProfileId.IsEmpty()) { Health.PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp index f1f3ba4..35623cd 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp @@ -138,6 +138,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistMockSpeechClient::GetSpeechServiceHeal Health.Capabilities.AddUnique(TEXT("wordTimestamps")); Health.Capabilities.AddUnique(TEXT("languageDetection")); Health.Capabilities.AddUnique(TEXT("promptHints")); + Health.Capabilities.AddUnique(TEXT("providerRoutingPolicy")); + Health.Capabilities.AddUnique(TEXT("providerRouteSelection")); + Health.Capabilities.AddUnique(TEXT("workflowPolicyState")); Health.Capabilities.AddUnique(TEXT("downloadableModelPayloadCustody")); Health.Capabilities.AddUnique(TEXT("payloadIntegrityReview")); Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1")); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index fcd8b42..46227e7 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -3057,6 +3057,46 @@ namespace HyperTwistTrainingSubsystemInternal }; } + TArray BuildDefaultSpeechRoutingEligibleProviderClasses() + { + return { + TEXT("official-direct-provider"), + TEXT("openai-compatible-custom-endpoint"), + TEXT("byok-endpoint"), + TEXT("managed-gateway-provider"), + TEXT("local-self-hosted-provider"), + TEXT("bounded-speech-sidecar-service"), + TEXT("first-party-mock-provider") + }; + } + + TArray BuildDefaultSpeechRoutingEligibleEndpointClasses() + { + return { + TEXT("official-http-api"), + TEXT("openai-compatible-http-api"), + TEXT("managed-gateway-http"), + TEXT("local-self-hosted-http"), + TEXT("in-process-mock") + }; + } + + TArray BuildDefaultSpeechRoutingRequiredCapabilities() + { + return { + TEXT("transcribe"), + TEXT("grammarHints"), + TEXT("manualTranscriptCommit") + }; + } + + TArray BuildDefaultSpeechRoutingSupportedTaskKinds() + { + return { + TEXT("transcribe") + }; + } + FHyperTwistSpeechProviderProfile BuildDefaultMockSpeechProviderProfile() { FHyperTwistSpeechProviderProfile Profile; @@ -3104,6 +3144,110 @@ namespace HyperTwistTrainingSubsystemInternal return Profile; } + FHyperTwistSpeechProviderRoutingPolicy BuildDefaultMockSpeechProviderRoutingPolicy() + { + FHyperTwistSpeechProviderRoutingPolicy Policy; + Policy.ProviderRoutingPolicyId = TEXT("speech-provider-routing/local-offline-mock-first-v1"); + Policy.PolicyKind = TEXT("speech-provider-routing"); + Policy.RouteSelectionMode = TEXT("local-offline-mock-first"); + Policy.FallbackMode = TEXT("fail-closed"); + Policy.WorkflowPolicyState = TEXT("first-party-provider-policy-governed"); + Policy.FailureEscalationMode = TEXT("preserve-local-verification-route"); + Policy.UserOverridePosture = TEXT("client-kind-and-profile-override-supported"); + Policy.EligibleProviderClasses = BuildDefaultSpeechRoutingEligibleProviderClasses(); + Policy.EligibleEndpointClasses = BuildDefaultSpeechRoutingEligibleEndpointClasses(); + Policy.RequiredCapabilityFlags = BuildDefaultSpeechRoutingRequiredCapabilities(); + Policy.SupportedTaskKinds = BuildDefaultSpeechRoutingSupportedTaskKinds(); + Policy.bAllowProviderBackedRoutes = false; + Policy.bAllowMockVerificationRoutes = true; + Policy.bAllowOpenAiCompatibleCustomEndpointRoutes = true; + Policy.bAllowUserLabeledByokRoutes = true; + Policy.bAllowManagedGatewayRoutes = true; + Policy.bAllowLocalSelfHostedRoutes = true; + Policy.bRespectsProfileEnableDisable = true; + Policy.bAllowFailClosedWhenNoEligibleRoute = true; + Policy.bAllowHealthPostureFallback = false; + Policy.bPreservesFirstPartyRoutingOwnership = true; + Policy.bAllowsDonorOwnedRoutingLanguage = false; + return Policy; + } + + FHyperTwistSpeechProviderRoutingPolicy BuildDefaultProviderBackedSpeechProviderRoutingPolicy() + { + FHyperTwistSpeechProviderRoutingPolicy Policy; + Policy.ProviderRoutingPolicyId = TEXT("speech-provider-routing/provider-backed-sidecar-first-v1"); + Policy.PolicyKind = TEXT("speech-provider-routing"); + Policy.RouteSelectionMode = TEXT("provider-backed-sidecar-first"); + Policy.FallbackMode = TEXT("health-posture-only"); + Policy.WorkflowPolicyState = TEXT("first-party-provider-policy-governed"); + Policy.FailureEscalationMode = TEXT("fail-closed-and-report-health"); + Policy.UserOverridePosture = TEXT("profile-or-client-kind-override-supported"); + Policy.EligibleProviderClasses = BuildDefaultSpeechRoutingEligibleProviderClasses(); + Policy.EligibleEndpointClasses = BuildDefaultSpeechRoutingEligibleEndpointClasses(); + Policy.RequiredCapabilityFlags = BuildDefaultSpeechRoutingRequiredCapabilities(); + Policy.SupportedTaskKinds = BuildDefaultSpeechRoutingSupportedTaskKinds(); + Policy.bAllowProviderBackedRoutes = true; + Policy.bAllowMockVerificationRoutes = true; + Policy.bAllowOpenAiCompatibleCustomEndpointRoutes = true; + Policy.bAllowUserLabeledByokRoutes = true; + Policy.bAllowManagedGatewayRoutes = true; + Policy.bAllowLocalSelfHostedRoutes = true; + Policy.bRespectsProfileEnableDisable = true; + Policy.bAllowFailClosedWhenNoEligibleRoute = true; + Policy.bAllowHealthPostureFallback = true; + Policy.bPreservesFirstPartyRoutingOwnership = true; + Policy.bAllowsDonorOwnedRoutingLanguage = false; + return Policy; + } + + FHyperTwistSpeechProviderRouteDecision BuildSpeechProviderRouteDecision( + const FHyperTwistSpeechProviderRoutingPolicy& Policy, + const FHyperTwistSpeechProviderProfile& Profile, + const FString& SelectedServiceLaneId, + const FString& DecisionSource, + const FString& RouteStateId, + const FString& DecisionReason, + const FString& FailureReason, + const bool bProviderBackedRouteRequested, + const bool bProviderBackedRouteSelected, + const bool bUsedFallbackHealthPosture, + const bool bRouteReady + ) + { + FHyperTwistSpeechProviderRouteDecision Decision; + Decision.RouteDecisionId = FString::Printf( + TEXT("speech-route-decision/%s"), + *Profile.ProviderProfileId + ); + Decision.ProviderRoutingPolicyId = Policy.ProviderRoutingPolicyId; + Decision.DecisionSource = DecisionSource; + Decision.RequestedProviderProfileId = Profile.ProviderProfileId; + Decision.SelectedProviderProfileId = Profile.ProviderProfileId; + Decision.SelectedProviderClass = Profile.ProviderClass; + Decision.SelectedEndpointClass = Profile.EndpointClass; + Decision.SelectedServiceLaneId = SelectedServiceLaneId.IsEmpty() + ? TEXT("speech/python-service") + : SelectedServiceLaneId; + Decision.SelectedTransportKind = Profile.EndpointClass.Equals(TEXT("in-process-mock"), ESearchCase::IgnoreCase) + ? TEXT("in-process") + : TEXT("http"); + Decision.RouteStateId = RouteStateId; + Decision.DecisionReason = DecisionReason; + Decision.FailureReason = FailureReason; + Decision.FallbackMode = Policy.FallbackMode; + Decision.bProviderBackedRouteRequested = bProviderBackedRouteRequested; + Decision.bProviderBackedRouteSelected = bProviderBackedRouteSelected; + Decision.bUsedFallbackHealthPosture = bUsedFallbackHealthPosture; + Decision.bSelectedProfileEnabled = Profile.bEnabled; + Decision.bSelectedCustomEndpointRoute = + Profile.bUserLabeled + && Profile.bSupportsCustomBaseUrl + && !Profile.EndpointBaseUrl.IsEmpty() + && !Profile.EndpointBaseUrl.Contains(TEXT("127.0.0.1"), ESearchCase::IgnoreCase); + Decision.bRouteReady = bRouteReady; + return Decision; + } + FHyperTwistSpeechByokCustodyProfile BuildDefaultSpeechByokCustodyProfile(const FString& ProfileId) { FHyperTwistSpeechByokCustodyProfile Profile; @@ -3213,6 +3357,88 @@ namespace HyperTwistTrainingSubsystemInternal SessionConfig.ByokCustodyProfileId = SessionConfig.ByokCustodyProfileDefinition.ByokCustodyProfileId; } + void ApplySpeechProviderRoutingDefaults( + FHyperTwistSpeechSessionConfig& SessionConfig, + const bool bUseMockClient + ) + { + if (SessionConfig.ProviderRoutingPolicyId.IsEmpty() + && SessionConfig.ProviderRoutingPolicyDefinition.IsStructurallyValid()) + { + SessionConfig.ProviderRoutingPolicyId = + SessionConfig.ProviderRoutingPolicyDefinition.ProviderRoutingPolicyId; + } + if (SessionConfig.ProviderRoutingPolicyId.IsEmpty()) + { + SessionConfig.ProviderRoutingPolicyId = bUseMockClient + ? TEXT("speech-provider-routing/local-offline-mock-first-v1") + : TEXT("speech-provider-routing/provider-backed-sidecar-first-v1"); + } + if (!SessionConfig.ProviderRoutingPolicyDefinition.IsStructurallyValid()) + { + SessionConfig.ProviderRoutingPolicyDefinition = bUseMockClient + ? BuildDefaultMockSpeechProviderRoutingPolicy() + : BuildDefaultProviderBackedSpeechProviderRoutingPolicy(); + } + SessionConfig.ProviderRoutingPolicyId = + SessionConfig.ProviderRoutingPolicyDefinition.ProviderRoutingPolicyId; + + const FHyperTwistSpeechProviderProfile EffectiveProfile = + SessionConfig.ProviderProfileDefinition.IsStructurallyValid() + ? SessionConfig.ProviderProfileDefinition + : (bUseMockClient + ? BuildDefaultMockSpeechProviderProfile() + : BuildDefaultHttpSpeechProviderProfile( + TEXT("Local HTTP Speech Sidecar"), + TEXT("http://127.0.0.1:8766") + )); + const FString SelectedServiceLaneId = SessionConfig.OrchestrationProfile.ServiceLaneId; + + FString RouteStateId; + FString DecisionReason; + FString FailureReason; + bool bRouteReady = true; + + if (!EffectiveProfile.bEnabled) + { + RouteStateId = TEXT("route-disabled"); + DecisionReason = TEXT("selected-provider-profile-disabled"); + FailureReason = TEXT("selected-provider-profile-disabled"); + bRouteReady = false; + } + else if (bUseMockClient) + { + RouteStateId = TEXT("route-ready"); + DecisionReason = TEXT("mock-verification-route-selected"); + } + else if (EffectiveProfile.EndpointBaseUrl.IsEmpty()) + { + RouteStateId = TEXT("route-awaiting-endpoint"); + DecisionReason = TEXT("provider-backed-sidecar-route-unavailable"); + FailureReason = TEXT("service-base-url-missing"); + bRouteReady = false; + } + else + { + RouteStateId = TEXT("route-ready"); + DecisionReason = TEXT("provider-backed-sidecar-route-selected"); + } + + SessionConfig.ProviderRouteDecision = BuildSpeechProviderRouteDecision( + SessionConfig.ProviderRoutingPolicyDefinition, + EffectiveProfile, + SelectedServiceLaneId, + TEXT("session-config-default"), + RouteStateId, + DecisionReason, + FailureReason, + !bUseMockClient, + !bUseMockClient, + false, + bRouteReady + ); + } + void ApplySpeechPayloadCustodyDefaults(FHyperTwistSpeechSessionConfig& SessionConfig) { if (SessionConfig.PayloadCustodyProfileId.IsEmpty()) @@ -3277,6 +3503,130 @@ namespace HyperTwistTrainingSubsystemInternal Health.Capabilities.AddUnique(TEXT("userLabeledByokProfiles")); } + void ApplySpeechProviderRoutingHealthDefaults( + FHyperTwistSpeechServiceHealth& Health, + const FHyperTwistSpeechSessionConfig* SessionConfig, + const bool bUseProviderBackedClient, + const FString& LastError + ) + { + if (Health.ProviderRoutingPolicyId.IsEmpty() + && Health.ProviderRoutingPolicyDefinition.IsStructurallyValid()) + { + Health.ProviderRoutingPolicyId = + Health.ProviderRoutingPolicyDefinition.ProviderRoutingPolicyId; + } + if (Health.ProviderRoutingPolicyId.IsEmpty()) + { + Health.ProviderRoutingPolicyId = bUseProviderBackedClient + ? TEXT("speech-provider-routing/provider-backed-sidecar-first-v1") + : TEXT("speech-provider-routing/local-offline-mock-first-v1"); + } + if (!Health.ProviderRoutingPolicyDefinition.IsStructurallyValid()) + { + Health.ProviderRoutingPolicyDefinition = bUseProviderBackedClient + ? BuildDefaultProviderBackedSpeechProviderRoutingPolicy() + : BuildDefaultMockSpeechProviderRoutingPolicy(); + } + Health.ProviderRoutingPolicyId = Health.ProviderRoutingPolicyDefinition.ProviderRoutingPolicyId; + + const FHyperTwistSpeechProviderProfile EffectiveProfile = + Health.ProviderProfileDefinition.IsStructurallyValid() + ? Health.ProviderProfileDefinition + : (SessionConfig != nullptr && SessionConfig->ProviderProfileDefinition.IsStructurallyValid() + ? SessionConfig->ProviderProfileDefinition + : (bUseProviderBackedClient + ? BuildDefaultHttpSpeechProviderProfile( + Health.ProviderLabel, + Health.ServiceEndpoint + ) + : BuildDefaultMockSpeechProviderProfile())); + const FString EffectiveRouteError = !LastError.IsEmpty() ? LastError : Health.LastError; + const FString SelectedServiceLaneId = + SessionConfig != nullptr + && !SessionConfig->OrchestrationProfile.ServiceLaneId.IsEmpty() + ? SessionConfig->OrchestrationProfile.ServiceLaneId + : TEXT("speech/python-service"); + + FString RouteStateId; + FString DecisionReason; + FString FailureReason; + bool bRouteReady = true; + bool bUsedFallbackHealthPosture = false; + + if (!EffectiveProfile.bEnabled) + { + RouteStateId = TEXT("route-disabled"); + DecisionReason = TEXT("selected-provider-profile-disabled"); + FailureReason = TEXT("selected-provider-profile-disabled"); + bRouteReady = false; + } + else if (!bUseProviderBackedClient) + { + if (!Health.bReady && !EffectiveRouteError.IsEmpty()) + { + RouteStateId = TEXT("route-health-degraded"); + DecisionReason = TEXT("mock-verification-route-health-degraded"); + FailureReason = EffectiveRouteError; + bRouteReady = false; + } + else + { + RouteStateId = TEXT("route-ready"); + DecisionReason = TEXT("mock-verification-route-selected"); + } + } + else if (EffectiveRouteError == TEXT("no-active-training-run")) + { + RouteStateId = TEXT("route-idle"); + DecisionReason = TEXT("provider-backed-route-idle"); + FailureReason = EffectiveRouteError; + bRouteReady = false; + bUsedFallbackHealthPosture = true; + } + else if (EffectiveProfile.EndpointBaseUrl.IsEmpty() + || EffectiveRouteError == TEXT("service-base-url-missing")) + { + RouteStateId = TEXT("route-awaiting-endpoint"); + DecisionReason = TEXT("provider-backed-sidecar-route-unavailable"); + FailureReason = TEXT("service-base-url-missing"); + bRouteReady = false; + bUsedFallbackHealthPosture = true; + } + else if (!Health.bReady || !EffectiveRouteError.IsEmpty()) + { + RouteStateId = TEXT("route-health-degraded"); + DecisionReason = TEXT("provider-backed-sidecar-health-degraded"); + FailureReason = !EffectiveRouteError.IsEmpty() + ? EffectiveRouteError + : TEXT("provider-route-unready"); + bRouteReady = false; + bUsedFallbackHealthPosture = true; + } + else + { + RouteStateId = TEXT("route-ready"); + DecisionReason = TEXT("provider-backed-sidecar-route-selected"); + } + + Health.ProviderRouteDecision = BuildSpeechProviderRouteDecision( + Health.ProviderRoutingPolicyDefinition, + EffectiveProfile, + SelectedServiceLaneId, + TEXT("service-health-refresh"), + RouteStateId, + DecisionReason, + FailureReason, + bUseProviderBackedClient, + bUseProviderBackedClient, + bUsedFallbackHealthPosture, + bRouteReady + ); + Health.Capabilities.AddUnique(TEXT("providerRoutingPolicy")); + Health.Capabilities.AddUnique(TEXT("providerRouteSelection")); + Health.Capabilities.AddUnique(TEXT("workflowPolicyState")); + } + void ApplySpeechPayloadCustodyHealthDefaults(FHyperTwistSpeechServiceHealth& Health) { if (Health.PayloadCustodyProfileId.IsEmpty()) @@ -9747,6 +10097,10 @@ FHyperTwistSpeechSessionConfig UHyperTwistTrainingSubsystem::BuildActiveCompanio SessionConfig, bUseMockClient ); + HyperTwistTrainingSubsystemInternal::ApplySpeechProviderRoutingDefaults( + SessionConfig, + bUseMockClient + ); return SessionConfig; } @@ -9868,6 +10222,10 @@ FHyperTwistSpeechSessionConfig UHyperTwistTrainingSubsystem::BuildActiveCompanio SessionConfig, bUseMockClient ); + HyperTwistTrainingSubsystemInternal::ApplySpeechProviderRoutingDefaults( + SessionConfig, + bUseMockClient + ); return SessionConfig; } @@ -9947,6 +10305,12 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth() ActiveCompanionSpeechSessionState.ServiceHealth, ActiveCompanionSpeechSessionState.bUsingProviderBackedClient ); + HyperTwistTrainingSubsystemInternal::ApplySpeechProviderRoutingHealthDefaults( + ActiveCompanionSpeechSessionState.ServiceHealth, + &ActiveCompanionSpeechSessionState.SessionConfig, + ActiveCompanionSpeechSessionState.bUsingProviderBackedClient, + ActiveCompanionSpeechSessionState.LastError + ); HyperTwistTrainingSubsystemInternal::ApplySpeechPayloadCustodyHealthDefaults( ActiveCompanionSpeechSessionState.ServiceHealth ); @@ -9961,6 +10325,12 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth() ActiveCompanionSpeechSessionState.ServiceHealth, ActiveCompanionSpeechSessionState.bUsingProviderBackedClient ); + HyperTwistTrainingSubsystemInternal::ApplySpeechProviderRoutingHealthDefaults( + ActiveCompanionSpeechSessionState.ServiceHealth, + &ActiveCompanionSpeechSessionState.SessionConfig, + ActiveCompanionSpeechSessionState.bUsingProviderBackedClient, + ActiveCompanionSpeechSessionState.LastError + ); HyperTwistTrainingSubsystemInternal::ApplySpeechPayloadCustodyHealthDefaults( ActiveCompanionSpeechSessionState.ServiceHealth ); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h index dbe7af3..2452061 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h @@ -2104,6 +2104,209 @@ struct FHyperTwistSpeechByokCustodyProfile } }; +USTRUCT(BlueprintType) +struct FHyperTwistSpeechProviderRoutingPolicy +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ProviderRoutingPolicyId = TEXT("speech-provider-routing/provider-backed-sidecar-first-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PolicyKind = TEXT("speech-provider-routing"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RouteSelectionMode = TEXT("provider-backed-sidecar-first"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FallbackMode = TEXT("health-posture-only"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString WorkflowPolicyState = TEXT("first-party-provider-policy-governed"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FailureEscalationMode = TEXT("fail-closed-and-report-health"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UserOverridePosture = TEXT("profile-or-client-kind-override-supported"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray EligibleProviderClasses; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray EligibleEndpointClasses; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray RequiredCapabilityFlags; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray SupportedTaskKinds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bAllowProviderBackedRoutes = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bAllowMockVerificationRoutes = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bAllowOpenAiCompatibleCustomEndpointRoutes = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bAllowUserLabeledByokRoutes = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bAllowManagedGatewayRoutes = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bAllowLocalSelfHostedRoutes = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRespectsProfileEnableDisable = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bAllowFailClosedWhenNoEligibleRoute = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bAllowHealthPostureFallback = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bPreservesFirstPartyRoutingOwnership = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bAllowsDonorOwnedRoutingLanguage = false; + + bool IsStructurallyValid() const + { + if (ProviderRoutingPolicyId.IsEmpty() + || PolicyKind.IsEmpty() + || RouteSelectionMode.IsEmpty() + || FallbackMode.IsEmpty() + || WorkflowPolicyState.IsEmpty() + || FailureEscalationMode.IsEmpty() + || UserOverridePosture.IsEmpty() + || EligibleProviderClasses.Num() <= 0 + || EligibleEndpointClasses.Num() <= 0 + || RequiredCapabilityFlags.Num() <= 0 + || SupportedTaskKinds.Num() <= 0) + { + return false; + } + + for (const FString& ProviderClassId : EligibleProviderClasses) + { + if (ProviderClassId.IsEmpty()) + { + return false; + } + } + + for (const FString& EndpointClassId : EligibleEndpointClasses) + { + if (EndpointClassId.IsEmpty()) + { + return false; + } + } + + for (const FString& CapabilityFlag : RequiredCapabilityFlags) + { + if (CapabilityFlag.IsEmpty()) + { + return false; + } + } + + for (const FString& TaskKindId : SupportedTaskKinds) + { + if (TaskKindId.IsEmpty()) + { + return false; + } + } + + return true; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistSpeechProviderRouteDecision +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RouteDecisionId = TEXT("speech-route-decision/provider-backed-sidecar"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ProviderRoutingPolicyId = TEXT("speech-provider-routing/provider-backed-sidecar-first-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DecisionSource = TEXT("session-config-default"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RequestedProviderProfileId = TEXT("speech-provider/local-http-sidecar-profile-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectedProviderProfileId = TEXT("speech-provider/local-http-sidecar-profile-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectedProviderClass = TEXT("bounded-speech-sidecar-service"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectedEndpointClass = TEXT("local-self-hosted-http"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectedServiceLaneId = TEXT("speech/python-service"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectedTransportKind = TEXT("http"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RouteStateId = TEXT("route-ready"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DecisionReason = TEXT("provider-backed-sidecar-route-selected"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FailureReason; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FallbackMode = TEXT("health-posture-only"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bProviderBackedRouteRequested = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bProviderBackedRouteSelected = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bUsedFallbackHealthPosture = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSelectedProfileEnabled = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSelectedCustomEndpointRoute = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRouteReady = true; + + bool IsStructurallyValid() const + { + return !RouteDecisionId.IsEmpty() + && !ProviderRoutingPolicyId.IsEmpty() + && !DecisionSource.IsEmpty() + && !RequestedProviderProfileId.IsEmpty() + && !SelectedProviderProfileId.IsEmpty() + && !SelectedProviderClass.IsEmpty() + && !SelectedEndpointClass.IsEmpty() + && !SelectedServiceLaneId.IsEmpty() + && !SelectedTransportKind.IsEmpty() + && !RouteStateId.IsEmpty() + && !DecisionReason.IsEmpty() + && !FallbackMode.IsEmpty(); + } +}; + USTRUCT(BlueprintType) struct FHyperTwistSpeechSessionConfig { @@ -2154,6 +2357,15 @@ struct FHyperTwistSpeechSessionConfig UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechByokCustodyProfile ByokCustodyProfileDefinition; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ProviderRoutingPolicyId = TEXT("speech-provider-routing/provider-backed-sidecar-first-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechProviderRoutingPolicy ProviderRoutingPolicyDefinition; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechProviderRouteDecision ProviderRouteDecision; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); @@ -2193,6 +2405,9 @@ struct FHyperTwistSpeechSessionConfig || !ProviderProfileDefinition.IsStructurallyValid() || ByokCustodyProfileId.IsEmpty() || !ByokCustodyProfileDefinition.IsStructurallyValid() + || ProviderRoutingPolicyId.IsEmpty() + || !ProviderRoutingPolicyDefinition.IsStructurallyValid() + || !ProviderRouteDecision.IsStructurallyValid() || PayloadCustodyProfileId.IsEmpty() || !PayloadCustodyProfileDefinition.IsStructurallyValid() || RequiredModelPayloads.Num() <= 0 @@ -2433,6 +2648,15 @@ struct FHyperTwistSpeechServiceHealth UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechByokCustodyProfile ByokCustodyProfileDefinition; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ProviderRoutingPolicyId = TEXT("speech-provider-routing/provider-backed-sidecar-first-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechProviderRoutingPolicy ProviderRoutingPolicyDefinition; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechProviderRouteDecision ProviderRouteDecision; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); @@ -2459,6 +2683,9 @@ struct FHyperTwistSpeechServiceHealth || !ProviderProfileDefinition.IsStructurallyValid() || ByokCustodyProfileId.IsEmpty() || !ByokCustodyProfileDefinition.IsStructurallyValid() + || ProviderRoutingPolicyId.IsEmpty() + || !ProviderRoutingPolicyDefinition.IsStructurallyValid() + || !ProviderRouteDecision.IsStructurallyValid() || PayloadCustodyProfileId.IsEmpty() || !PayloadCustodyProfileDefinition.IsStructurallyValid()) { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RYProviderRoutingPolicyContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RYProviderRoutingPolicyContractTest.cpp new file mode 100644 index 0000000..867ed84 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RYProviderRoutingPolicyContractTest.cpp @@ -0,0 +1,251 @@ +// Copyright HyperTwist, Inc. All Rights Reserved. + +#include "Misc/AutomationTest.h" + +#include "Engine/GameInstance.h" +#include "HyperTwistRecognition/HyperTwistSpeechClient.h" +#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h" +#include "UObject/UnrealType.h" + +#if WITH_AUTOMATION_TESTS + +namespace HyperTwistWhisperCppPhase6RYTestInternal +{ + FHyperTwistTrainingDeck MakeSpeechDeck() + { + FHyperTwistTrainingDeck Deck; + Deck.DeckId = TEXT("phase6r-y/provider-routing-policy"); + Deck.Title = TEXT("Phase 6R-Y Provider Routing And Policy"); + Deck.DeliveryModes = { + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + + FHyperTwistTrainingCase TrainingCase; + TrainingCase.CaseId = TEXT("phase6r-y-case"); + TrainingCase.PuzzleId = TEXT("cube/3x3x3"); + TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence; + TrainingCase.PromptLabel = TEXT("Phase 6R-Y Coach Speech"); + TrainingCase.AllowedDeliveryModes = { + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + Deck.Cases = {TrainingCase}; + return Deck; + } + + void ForceSpeechClientKind(UHyperTwistTrainingSubsystem* TrainingSubsystem, const FString& ClientKind) + { + if (TrainingSubsystem == nullptr) + { + return; + } + + if (FStrProperty* SpeechClientKindProperty = FindFProperty( + UHyperTwistTrainingSubsystem::StaticClass(), + TEXT("CompanionSpeechClientKind") + )) + { + SpeechClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, ClientKind); + } + } + + UHyperTwistTrainingSubsystem* MakeSpeechSubsystem(const FString& SessionId, const FString& ClientKind) + { + UGameInstance* GameInstance = NewObject(GetTransientPackage()); + if (GameInstance == nullptr) + { + return nullptr; + } + + UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject(GameInstance); + if (TrainingSubsystem == nullptr) + { + return nullptr; + } + + ForceSpeechClientKind(TrainingSubsystem, ClientKind); + const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck( + MakeSpeechDeck(), + TEXT("phase6r-y-user"), + SessionId, + EHyperTwistTrainingDeliveryMode::CoachReviewed + ); + return RunState.IsStructurallyValid() ? TrainingSubsystem : nullptr; + } + + UHyperTwistHttpSpeechClient* ResolveHttpSpeechClient(UHyperTwistTrainingSubsystem* TrainingSubsystem) + { + if (TrainingSubsystem == nullptr) + { + return nullptr; + } + + FString CloseError; + TrainingSubsystem->CloseActiveCompanionSpeechSession(CloseError); + + if (FObjectPropertyBase* ClientProperty = FindFProperty( + UHyperTwistTrainingSubsystem::StaticClass(), + TEXT("ActiveCompanionSpeechClientObject") + )) + { + return Cast( + ClientProperty->GetObjectPropertyValue_InContainer(TrainingSubsystem) + ); + } + + return nullptr; + } +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RYProviderRoutingSessionConfigTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.Y.ProviderRoutingPolicySessionConfig", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RYProviderRoutingSessionConfigTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RYTestInternal::MakeSpeechSubsystem( + TEXT("phase6r-y-config-session"), + TEXT("mock") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-Y."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + FString OpenError; + TestTrue(TEXT("The mock provider-routing route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestTrue(TEXT("Opening the mock provider-routing speech session must not report an error."), OpenError.IsEmpty()); + + const FHyperTwistSpeechSessionConfig& Config = + TrainingSubsystem->GetActiveCompanionSpeechSessionState().SessionConfig; + + TestTrue(TEXT("The provider-routing session config must remain structurally valid."), Config.IsStructurallyValid()); + TestEqual(TEXT("The mock route must preserve the mock routing policy id."), Config.ProviderRoutingPolicyId, TEXT("speech-provider-routing/local-offline-mock-first-v1")); + TestTrue(TEXT("The routing policy definition must remain structurally valid."), Config.ProviderRoutingPolicyDefinition.IsStructurallyValid()); + TestEqual(TEXT("The mock route must keep the local-offline selection mode."), Config.ProviderRoutingPolicyDefinition.RouteSelectionMode, TEXT("local-offline-mock-first")); + TestFalse(TEXT("The mock route must not request provider-backed routing as its primary posture."), Config.ProviderRoutingPolicyDefinition.bAllowProviderBackedRoutes); + TestTrue(TEXT("The mock route must preserve mock verification routing."), Config.ProviderRoutingPolicyDefinition.bAllowMockVerificationRoutes); + TestTrue(TEXT("The routing policy must preserve first-party ownership."), Config.ProviderRoutingPolicyDefinition.bPreservesFirstPartyRoutingOwnership); + TestFalse(TEXT("The routing policy must not allow donor-owned routing language."), Config.ProviderRoutingPolicyDefinition.bAllowsDonorOwnedRoutingLanguage); + + TestTrue(TEXT("The mock route decision must remain structurally valid."), Config.ProviderRouteDecision.IsStructurallyValid()); + TestEqual(TEXT("The mock route decision must target the mock provider profile."), Config.ProviderRouteDecision.SelectedProviderProfileId, TEXT("speech-provider/mock-offline-profile-v1")); + TestEqual(TEXT("The mock route decision must preserve the in-process transport."), Config.ProviderRouteDecision.SelectedTransportKind, TEXT("in-process")); + TestEqual(TEXT("The mock route decision must keep the orchestration service lane."), Config.ProviderRouteDecision.SelectedServiceLaneId, TEXT("speech/python-service")); + TestEqual(TEXT("The mock route decision must report a ready route."), Config.ProviderRouteDecision.RouteStateId, TEXT("route-ready")); + TestEqual(TEXT("The mock route decision must expose the expected reason."), Config.ProviderRouteDecision.DecisionReason, TEXT("mock-verification-route-selected")); + TestFalse(TEXT("The mock route decision must not request a provider-backed route."), Config.ProviderRouteDecision.bProviderBackedRouteRequested); + TestFalse(TEXT("The mock route decision must not mark a provider-backed route as selected."), Config.ProviderRouteDecision.bProviderBackedRouteSelected); + TestTrue(TEXT("The mock route decision must remain route-ready."), Config.ProviderRouteDecision.bRouteReady); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RYProviderRoutingServiceHealthTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.Y.ProviderRoutingPolicyServiceHealth", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RYProviderRoutingServiceHealthTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RYTestInternal::MakeSpeechSubsystem( + TEXT("phase6r-y-health-session"), + TEXT("mock") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-Y."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + FString OpenError; + TestTrue(TEXT("The mock provider-routing route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestTrue(TEXT("Opening the mock provider-routing speech session must not report an error."), OpenError.IsEmpty()); + + const FHyperTwistSpeechServiceHealth& Health = + TrainingSubsystem->GetActiveCompanionSpeechSessionState().ServiceHealth; + + TestTrue(TEXT("The provider-routing service health must remain structurally valid."), Health.IsStructurallyValid()); + TestTrue(TEXT("The service health must preserve provider routing policy capability."), Health.Capabilities.Contains(TEXT("providerRoutingPolicy"))); + TestTrue(TEXT("The service health must preserve provider route selection capability."), Health.Capabilities.Contains(TEXT("providerRouteSelection"))); + TestTrue(TEXT("The service health must preserve workflow-policy state capability."), Health.Capabilities.Contains(TEXT("workflowPolicyState"))); + TestEqual(TEXT("The service health must preserve the mock routing policy id."), Health.ProviderRoutingPolicyId, TEXT("speech-provider-routing/local-offline-mock-first-v1")); + TestTrue(TEXT("The service health routing policy definition must remain structurally valid."), Health.ProviderRoutingPolicyDefinition.IsStructurallyValid()); + TestEqual(TEXT("The mock routing health must keep fail-closed fallback wording."), Health.ProviderRoutingPolicyDefinition.FallbackMode, TEXT("fail-closed")); + TestTrue(TEXT("The mock routing policy must require manual transcript capability."), Health.ProviderRoutingPolicyDefinition.RequiredCapabilityFlags.Contains(TEXT("manualTranscriptCommit"))); + TestTrue(TEXT("The mock routing policy must support transcribe tasks."), Health.ProviderRoutingPolicyDefinition.SupportedTaskKinds.Contains(TEXT("transcribe"))); + + TestTrue(TEXT("The mock routing health decision must remain structurally valid."), Health.ProviderRouteDecision.IsStructurallyValid()); + TestEqual(TEXT("The mock routing health decision must preserve the in-process endpoint class."), Health.ProviderRouteDecision.SelectedEndpointClass, TEXT("in-process-mock")); + TestEqual(TEXT("The mock routing health decision must keep the ready route state."), Health.ProviderRouteDecision.RouteStateId, TEXT("route-ready")); + TestFalse(TEXT("The mock routing health decision must not mark a provider-backed route as selected."), Health.ProviderRouteDecision.bProviderBackedRouteSelected); + TestFalse(TEXT("The mock routing health decision must not fall back into health-posture mode."), Health.ProviderRouteDecision.bUsedFallbackHealthPosture); + TestTrue(TEXT("The mock routing health decision must keep the route ready."), Health.ProviderRouteDecision.bRouteReady); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RYProviderBackedRoutingFallbackTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.Y.ProviderBackedRoutingFallback", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RYProviderBackedRoutingFallbackTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RYTestInternal::MakeSpeechSubsystem( + TEXT("phase6r-y-provider-session"), + TEXT("http-sidecar") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-Y."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + UHyperTwistHttpSpeechClient* HttpSpeechClient = + HyperTwistWhisperCppPhase6RYTestInternal::ResolveHttpSpeechClient(TrainingSubsystem); + TestNotNull(TEXT("The provider-backed speech client must be available for the routing fallback test."), HttpSpeechClient); + if (HttpSpeechClient == nullptr) + { + return false; + } + + HttpSpeechClient->ServiceBaseUrl.Reset(); + + FString OpenError; + TestFalse(TEXT("The provider-backed routing path must fail cleanly when no provider endpoint is configured."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestEqual(TEXT("The provider-backed routing path must report the missing service base URL."), OpenError, TEXT("service-base-url-missing")); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechServiceHealth& Health = SessionState.ServiceHealth; + + TestTrue(TEXT("The provider-backed routing fallback health must remain structurally valid."), Health.IsStructurallyValid()); + TestEqual(TEXT("The provider-backed routing fallback must keep the provider-backed routing policy id."), Health.ProviderRoutingPolicyId, TEXT("speech-provider-routing/provider-backed-sidecar-first-v1")); + TestTrue(TEXT("The provider-backed routing policy definition must remain structurally valid."), Health.ProviderRoutingPolicyDefinition.IsStructurallyValid()); + TestEqual(TEXT("The provider-backed routing policy must preserve sidecar-first selection mode."), Health.ProviderRoutingPolicyDefinition.RouteSelectionMode, TEXT("provider-backed-sidecar-first")); + TestEqual(TEXT("The provider-backed routing policy must preserve health-posture fallback wording."), Health.ProviderRoutingPolicyDefinition.FallbackMode, TEXT("health-posture-only")); + TestTrue(TEXT("The provider-backed routing policy must allow provider-backed routes."), Health.ProviderRoutingPolicyDefinition.bAllowProviderBackedRoutes); + TestTrue(TEXT("The provider-backed routing policy must preserve health-posture fallback."), Health.ProviderRoutingPolicyDefinition.bAllowHealthPostureFallback); + + TestTrue(TEXT("The provider-backed route decision must remain structurally valid."), Health.ProviderRouteDecision.IsStructurallyValid()); + TestEqual(TEXT("The provider-backed route decision must target the local HTTP provider profile."), Health.ProviderRouteDecision.SelectedProviderProfileId, TEXT("speech-provider/local-http-sidecar-profile-v1")); + TestEqual(TEXT("The provider-backed route decision must preserve the local self-hosted endpoint class."), Health.ProviderRouteDecision.SelectedEndpointClass, TEXT("local-self-hosted-http")); + TestEqual(TEXT("The provider-backed route decision must preserve HTTP transport."), Health.ProviderRouteDecision.SelectedTransportKind, TEXT("http")); + TestEqual(TEXT("The provider-backed route decision must report the awaiting-endpoint state."), Health.ProviderRouteDecision.RouteStateId, TEXT("route-awaiting-endpoint")); + TestEqual(TEXT("The provider-backed route decision must expose the unavailability reason."), Health.ProviderRouteDecision.DecisionReason, TEXT("provider-backed-sidecar-route-unavailable")); + TestEqual(TEXT("The provider-backed route decision must expose the missing-endpoint failure."), Health.ProviderRouteDecision.FailureReason, TEXT("service-base-url-missing")); + TestTrue(TEXT("The provider-backed route decision must mark a provider-backed route as requested."), Health.ProviderRouteDecision.bProviderBackedRouteRequested); + TestTrue(TEXT("The provider-backed route decision must mark a provider-backed route as selected."), Health.ProviderRouteDecision.bProviderBackedRouteSelected); + TestTrue(TEXT("The provider-backed route decision must preserve fallback health posture."), Health.ProviderRouteDecision.bUsedFallbackHealthPosture); + TestFalse(TEXT("The provider-backed route decision must not report a ready route."), Health.ProviderRouteDecision.bRouteReady); + TestEqual(TEXT("The shell state must preserve the route error."), SessionState.MicrophoneShellState.LastRouteError, TEXT("service-base-url-missing")); + return true; +} + +#endif diff --git a/docs/REPO_LICENSE_TRACKING.md b/docs/REPO_LICENSE_TRACKING.md index b93f2be..4712a1a 100644 --- a/docs/REPO_LICENSE_TRACKING.md +++ b/docs/REPO_LICENSE_TRACKING.md @@ -183,8 +183,12 @@ Status update on `2026-05-21`: pass is now consumed - the bounded first-party `Phase 6R-X` provider-profile and BYOK custody packet is now landed in current code -- the current next bounded move is a source-backed `Phase 6R-Y` first-party provider routing and - policy preparation/control pass, not a new restrictive packet by default +- the generic source-backed `Phase 6R-Y` first-party provider routing and policy control pass is + now consumed +- the bounded first-party `Phase 6R-Y` provider routing and policy packet is now landed in + current code +- the current next bounded move is a source-backed `Phase 6R-Z` first-party normalized + usage/cost event-model preparation/control pass, not a new restrictive packet by default - use the repo-row README census, the portfolio standing refresh backfill, and the `2R-A` ownership contract for the current queue after that correction @@ -288,8 +292,8 @@ Current practical interpretation: - the landed `PostHog/posthog` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_D_POSTHOG_CONTROL_PLANE_IMPLEMENTATION_2026-05-13.md` - the landed `screenpipe/screenpipe` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_E_SCREENPIPE_CAPTURE_HISTORY_IMPLEMENTATION_2026-05-13.md` - the landed `remotion-dev/remotion` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_F_REMOTION_MEDIA_EXPORT_IMPLEMENTATION_2026-05-13.md` -- the next bounded move is a source-backed `Phase 6R-Y` first-party provider routing and policy - preparation/control pass +- the next bounded move is a source-backed `Phase 6R-Z` first-party normalized usage/cost + event-model preparation/control pass Companion docs: @@ -1545,11 +1549,14 @@ Approved working posture: payload custody metadata above the existing transcript-session and shell seams - the next bounded first-party implementation slice is now landed as provider-profile and BYOK custody metadata above the existing transcript-session, shell, and payload-custody seams +- the next bounded first-party implementation slice is now landed as provider routing and policy + metadata above the existing transcript-session, shell, payload-custody, and provider-custody + seams - the top-level provider/session contract remains first-party HyperTwist-owned and provider-neutral; this row does not own that lane - keep real device-permission workflow, native capture-route ownership, actual payload shipping, - first-party provider routing and policy, normalized usage/cost events, and future voice-asset - review separate from the code-license judgment + normalized usage/cost events, and future voice-asset review separate from the code-license + judgment - treat `SYSTRAN/faster-whisper` as the landed complementary Python-orchestration donor rather than widening `whisper.cpp` into the runtime core diff --git a/docs/arch/HYPERTWIST_PHASE6R_Y_FIRST_PARTY_PROVIDER_ROUTING_AND_POLICY_IMPLEMENTATION_PACKET_2026-05-24.md b/docs/arch/HYPERTWIST_PHASE6R_Y_FIRST_PARTY_PROVIDER_ROUTING_AND_POLICY_IMPLEMENTATION_PACKET_2026-05-24.md new file mode 100644 index 0000000..f273f57 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_Y_FIRST_PARTY_PROVIDER_ROUTING_AND_POLICY_IMPLEMENTATION_PACKET_2026-05-24.md @@ -0,0 +1,153 @@ +# HyperTwist Phase 6R-Y first-party provider routing and policy implementation packet + +Created on `2026-05-24` + +## Status + +- first-party HyperTwist packet +- bounded `Phase 6R-Y` implementation slice + +## Purpose + +This packet lands the next bounded first-party slice above the landed speech +session, microphone-shell, payload-custody, and provider-custody seams. + +The landed slice is: + +- first-party provider routing and workflow-policy metadata + +It is not: + +- a normalized usage/cost event-model packet +- an actual payload-shipping packet +- a real device-permission workflow packet +- a native audio-device route ownership packet +- a provider-specific overlay packet +- a voice-output packet +- a broad assistant-platform packet + +## Current authority basis + +This implementation packet stands on: + +- `docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md` +- `docs/arch/HYPERTWIST_PHASE6R_Y_FIRST_PARTY_PROVIDER_ROUTING_AND_POLICY_PREPARATION_PACKET_2026-05-24.md` +- `docs/arch/HYPERTWIST_PHASE6R_X_FIRST_PARTY_PROVIDER_PROFILE_AND_BYOK_CUSTODY_IMPLEMENTATION_PACKET_2026-05-24.md` + +The preserved owners do not change: + +- first-party HyperTwist remains the top-level provider/session owner +- first-party HyperTwist remains the provider routing/policy owner +- `ggml-org/whisper.cpp` remains bounded to offline STT donor seams +- `SYSTRAN/faster-whisper` remains bounded to complementary Python + orchestration/service-lane seams + +## Landed scope + +The current code now owns a bounded provider-routing / workflow-policy seam +through: + +- retained recognition contract types for: + - `FHyperTwistSpeechProviderRoutingPolicy` + - `FHyperTwistSpeechProviderRouteDecision` + - expanded `FHyperTwistSpeechSessionConfig` + - expanded `FHyperTwistSpeechServiceHealth` +- contract-library defaults for: + - provider-backed route policy + - mock verification route policy + - sample route-decision posture +- active companion session-config and service-health normalization in: + - `UHyperTwistTrainingSubsystem` +- direct speech-client health capability exposure in: + - `UHyperTwistHttpSpeechClient` + - `UHyperTwistMockSpeechClient` +- focused automation coverage in: + - `HyperTwistWhisperCppPhase6RYProviderRoutingPolicyContractTest.cpp` + +## Why this is still intentionally bounded + +This packet lands the routing/policy metadata family only. + +Still deferred: + +- normalized usage/cost events +- actual downloadable payload shipping +- real device-permission workflow +- native audio-device route ownership beyond bounded shell posture +- broad voice-output ownership +- broad assistant-platform scope + +## Validation + +Build validation: + +- `C:\Program Files\Epic Games\UE_5.7\Engine\Build\BatchFiles\Build.bat UnrealHyperTwistEditor Win64 Development -Project='C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.uproject' -WaitMutex -NoHotReloadFromIDE -NoUba` + +Focused automation validation: + +- `Automation RunTests HyperTwist.Permissive.WhisperCpp.Phase6R.Y` + +Regression automation validation: + +- `HyperTwist.Permissive.WhisperCpp.Phase6R.X` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.W` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.V` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.U` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.E` +- `HyperTwist.Permissive.FasterWhisper.Phase6R.F` +- `HyperTwist.Permissive.Piper.Phase6R.G` +- `HyperTwist.Permissive.Coqui.Phase6R.H` +- `HyperTwist.CleanRoom.CubeDesk` + +Expected covered tests: + +- `ProviderRoutingPolicySessionConfig` +- `ProviderRoutingPolicyServiceHealth` +- `ProviderBackedRoutingFallback` + +## Queue effect + +This packet consumes the current `Phase 6R-Y` implementation slice. + +`ggml-org/whisper.cpp` remains only partially incorporated: + +- landed now: + - speech transcript session boundary + - live microphone shell boundary + - device-permission and capture-route readiness shell posture + - downloadable model and payload custody boundary + - first-party provider-profile and BYOK custody boundary + - first-party provider routing and workflow-policy boundary for: + - routing-policy ids and definitions + - route-decision summaries + - fallback-health posture + - capability exposure for: + - `providerRoutingPolicy` + - `providerRouteSelection` + - `workflowPolicyState` +- still deferred: + - normalized usage/cost event model + - actual downloadable payload shipping + - real device-permission workflow + - native audio-device route ownership beyond bounded shell posture + - broad assistant-platform scope + +The next clean move is: + +- a source-backed `Phase 6R-Z` first-party normalized usage/cost event-model + preparation/control pass + +Keep the future sequencing guards visible: + +- first-party HyperTwist + - keep the top-level provider/session contract and provider routing/policy + lane first-party + - keep normalized usage/cost events separate from the landed provider-routing + seam +- `ggml-org/whisper.cpp` + - keep code-license judgments separate from actual model or payload shipping + review in any follow-on packet +- `SYSTRAN/faster-whisper` + - keep the row complementary to the landed provider-routing seam and bounded + to Python orchestration/service-lane ownership rather than usage/cost + governance ownership diff --git a/docs/arch/HYPERTWIST_PHASE6R_Y_FIRST_PARTY_PROVIDER_ROUTING_AND_POLICY_PREPARATION_PACKET_2026-05-24.md b/docs/arch/HYPERTWIST_PHASE6R_Y_FIRST_PARTY_PROVIDER_ROUTING_AND_POLICY_PREPARATION_PACKET_2026-05-24.md new file mode 100644 index 0000000..89f9f65 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_Y_FIRST_PARTY_PROVIDER_ROUTING_AND_POLICY_PREPARATION_PACKET_2026-05-24.md @@ -0,0 +1,116 @@ +# HyperTwist Phase 6R-Y first-party provider routing and policy preparation packet + +Created on `2026-05-24` + +## Status + +- first-party HyperTwist packet +- source-backed `Phase 6R-Y` preparation/control slice + +## Purpose + +This packet scopes the next bounded first-party slice above the landed +provider/session, microphone-shell, payload-custody, and provider-custody +seams. + +The granted slice is: + +- first-party provider routing and workflow-policy metadata only + +It is not: + +- a normalized usage/cost event-model packet +- an actual payload-shipping packet +- a real device-permission workflow packet +- a native audio-device route ownership packet +- a provider-specific overlay packet +- a voice-output packet +- a broad assistant-platform packet + +## Current authority basis + +This control pass stands on: + +- `docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md` +- `docs/arch/HYPERTWIST_PHASE6R_X_FIRST_PARTY_PROVIDER_PROFILE_AND_BYOK_CUSTODY_IMPLEMENTATION_PACKET_2026-05-24.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md` + +The preserved owners do not change here: + +- first-party HyperTwist remains the top-level provider/session owner +- first-party HyperTwist remains the routing/policy owner +- `ggml-org/whisper.cpp` remains bounded to offline STT donor seams +- `SYSTRAN/faster-whisper` remains bounded to complementary Python + orchestration/service-lane seams + +## Granted family + +The bounded `Phase 6R-Y` slice may land: + +1. first-party provider-routing policy ids and definitions +2. first-party route-decision summaries for session config and service health +3. explicit fallback-health posture wording above provider-backed route failure +4. workflow-policy capability exposure in the bounded speech-health seam +5. focused automation for mock-route and provider-backed-route posture + +The packet must stay out of: + +- normalized usage/cost event ownership +- actual provider-gateway fan-out ownership +- real secret-storage workflow ownership +- payload download or redistribution ownership +- real device-permission workflow ownership + +## Proposed implementation shape + +Land the narrower first-party boundary through: + +- retained recognition contract types for: + - routing-policy definition + - route-decision summary + - expanded speech session config + - expanded speech service health +- contract-library defaults for: + - provider-backed route policy + - local mock route policy + - explicit route-decision defaults +- training-subsystem normalization for: + - requested session-route posture + - health-surface route posture + - fallback-health route posture on provider failure +- direct speech-client health capability exposure for: + - `providerRoutingPolicy` + - `providerRouteSelection` + - `workflowPolicyState` + +## Validation target + +Validate with: + +- Unreal build for `UnrealHyperTwistEditor Win64 Development` +- focused automation: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.Y` +- regressions: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.X` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.W` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.V` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.U` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.E` + - `HyperTwist.Permissive.FasterWhisper.Phase6R.F` + - `HyperTwist.Permissive.Piper.Phase6R.G` + - `HyperTwist.Permissive.Coqui.Phase6R.H` + - `HyperTwist.CleanRoom.CubeDesk` + +## Queue effect + +If this packet lands cleanly, the next bounded move becomes: + +- a source-backed `Phase 6R-Z` first-party normalized usage/cost event-model + preparation/control pass + +Keep the narrow sequencing guards visible: + +- keep actual payload shipping separate from routing/policy ownership +- keep real device-permission workflow and native capture-route ownership + outside this packet +- keep normalized usage/cost events as the next separate first-party family diff --git a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md index 5156db2..372ab90 100644 --- a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md +++ b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md @@ -132,15 +132,19 @@ The next bounded move is now: control pass is now consumed 22. the bounded first-party `Phase 6R-X` provider-profile and BYOK custody packet is now landed in current code -23. the next bounded move is a source-backed `Phase 6R-Y` first-party provider routing and - policy preparation/control pass -24. keep the speech-lane guard visible: +23. the generic source-backed `Phase 6R-Y` first-party provider routing and policy control + pass is now consumed +24. the bounded first-party `Phase 6R-Y` provider routing and policy packet is now landed in + current code +25. the next bounded move is a source-backed `Phase 6R-Z` first-party normalized usage/cost + event-model preparation/control pass +26. keep the speech-lane guard visible: - keep code-license judgments separate from model, voice, and payload-license review -25. keep the provider-neutral speech-lane guard visible: +27. keep the provider-neutral speech-lane guard visible: - first-party HyperTwist owns the top-level provider/session contract - `whisper.cpp` and `faster-whisper` may win narrower donor slices without inheriting that lane or the provider routing/policy lane -26. keep the `MagicTile` guard visible: +28. keep the `MagicTile` guard visible: - keep broad non-Euclidean interaction or WinForms/OpenTK host-shell ownership closed by default unless a narrower first-party gap is proven above the landed macro-remapping seam diff --git a/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md b/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md index 19513f5..709481d 100644 --- a/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md +++ b/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md @@ -56,9 +56,11 @@ Those seams prove that HyperTwist already owns bounded provider contracts for: - session open/close - transcript/result payloads - health/status envelopes +- provider-routing policy ids and definitions +- route-decision summaries and fallback-health posture -Treat those as the current provisional first-party `A1` anchor for recognition -and speech-provider contracts. +Treat those as the current first-party `A1` anchor for recognition, +speech-provider contracts, and bounded provider routing/policy ownership. Do not let future provider donors overtake that top-level contract by accident. @@ -132,10 +134,11 @@ Keep these lanes distinct: ### Routing/gateway lane -- current strongest owner unresolved in HyperTwist packet authority -- provisional posture: - - first-party target - - later donor evaluation may refine `A/R/F` +- `A1 / R1 / F1` - first-party only for the bounded routing/policy layer +- current evidence anchor: + - `FHyperTwistSpeechProviderRoutingPolicy` + - `FHyperTwistSpeechProviderRouteDecision` + - `UHyperTwistTrainingSubsystem` ### Provider-profile and BYOK custody lane @@ -162,6 +165,10 @@ Keep these lanes distinct: ### Admin/policy lane - `A1 / R1 / F1` - first-party only +- current evidence anchor: + - `FHyperTwistSpeechProviderRoutingPolicy` + - `FHyperTwistSpeechProviderRouteDecision` + - `UHyperTwistTrainingSubsystem` ## BYOK rules @@ -202,6 +209,12 @@ The correct implementation order is: 5. operator-facing overlay/dashboard surfaces 6. bounded provider-specific adapters where still useful +Current landed ordering status: + +- steps `1` through `3` now have bounded first-party implementation anchors +- normalized usage/cost events remain the next separate first-party gap +- operator-facing overlays and provider-specific adapters remain later work + Do not invert that order. If HyperTwist starts from a provider-specific overlay and only later tries to diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index 5793777..328f706 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -124,6 +124,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice. | Device-permission and capture-route readiness shell | Implemented now | landed `whisper.cpp` `Phase 6R-V` | First-party permission/readiness shell posture, issue objects, retry/request semantics, and bounded capability exposure are now live above the microphone shell seam. | | Downloadable model and payload custody boundary | Implemented now | landed `whisper.cpp` `Phase 6R-W` | First-party payload-custody profile, required/supported model-payload descriptors, and bounded integrity metadata are now live above the landed speech session and shell seams. | | First-party provider profile and BYOK custody boundary | Implemented now | landed first-party `Phase 6R-X` | First-party provider-profile definitions, BYOK custody definitions, per-profile model/capability maps, and out-of-band auth-reference wording are now live above the landed speech session, shell, and payload-custody seams. | +| First-party provider routing and workflow-policy boundary | Implemented now | landed first-party `Phase 6R-Y` | First-party routing-policy ids/definitions, route-decision summaries, fallback-health posture, and workflow-policy capability exposure are now live above the landed provider-custody seam. | | Analytics/reporting surfaces | Implemented now | landed analytics/reporting packets | Reporting is real, but bounded to accepted retained slices. | | Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, not proof of unlimited browser-shell parity. | @@ -217,8 +218,9 @@ repo. |---|---|---|---| | First-party provider-neutral speech/vision contract | Implemented now | first-party recognition/speech client seams | Current bounded provider/session truth. | | Provider-neutral BYOK/profile custody boundary | Implemented now | landed first-party `Phase 6R-X` packet | Current bounded provider-profile ids/definitions, BYOK custody ids/definitions, per-profile model/capability maps, and out-of-band auth-reference wording. | +| First-party provider routing and workflow-policy boundary | Implemented now | landed first-party `Phase 6R-Y` packet | Current bounded routing-policy ids/definitions, route-decision summaries, explicit fallback-health posture, and workflow-policy capability exposure above the landed provider/session and provider-custody seams. | | OpenAI-compatible custom endpoint support | Deep-source grounded retained | doctrine-defined first-party target | First-class target is now represented in the landed custody model, but full routing/runtime support is still deferred. | -| Normalized usage/cost/routing surface | Deep-source grounded retained | doctrine-defined first-party target | Retained governance target, not live product truth yet. | +| Normalized usage/cost event surface | Deep-source grounded retained | doctrine-defined first-party target | Usage/cost normalization remains the next retained governance target after the landed routing/policy boundary. | | Provider-specific overlays | Shallow placeholder | future provider-family work only | Keep internal until source-grounded and normalized. | ### 9. Memory, continuity, notes, and provenance diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 60080c9..f6a333f 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -159,10 +159,13 @@ Canonical discovery surfaces for roadmap interpretation: pass is now consumed - the bounded first-party `Phase 6R-X` provider-profile and BYOK custody packet is now landed in current code -- the current next bounded move is a source-backed `Phase 6R-Y` first-party provider routing and - policy preparation/control pass, while keeping normalized usage/cost events, real - device-permission workflow, native capture-route ownership, and actual payload shipping - separately deferred +- the generic source-backed `Phase 6R-Y` first-party provider routing and policy control pass is + now consumed +- the bounded first-party `Phase 6R-Y` provider routing and policy packet is now landed in + current code +- the current next bounded move is a source-backed `Phase 6R-Z` first-party normalized + usage/cost event-model preparation/control pass, while keeping real device-permission + workflow, native capture-route ownership, and actual payload shipping separately deferred - the repo-row implementation queue is now live from that `Phase 6R-A` entry point rather than waiting on another first-party packet @@ -224,14 +227,14 @@ Current routing truth: - active non-live implementation-board rows: `34` - retained benchmark, oracle, or clean-room-later rows outside the active implementation board: `9` -The next bounded move is a source-backed `Phase 6R-Y` first-party -provider routing and policy preparation/control pass. +The next bounded move is a source-backed `Phase 6R-Z` first-party +normalized usage/cost event-model preparation/control pass. Queue interpretation after that control pass: - then continue with the retained repo-row queue - highest current repo-row queue pressure sits in: - - the first-party provider routing and policy remainder above the + - the first-party normalized usage/cost event-model remainder above the partially landed `whisper.cpp` and `faster-whisper` speech seams - `HactarCE/Hyperspeedcube` is now closed for the currently justified retained row: - landed: @@ -320,6 +323,7 @@ Queue interpretation after that control pass: - device-permission and capture-route readiness shell posture - downloadable model and payload custody boundary - first-party provider-profile and BYOK custody boundary + - first-party provider routing and policy boundary - bounded shell capability exposure for: - microphone capture shell - device-permission shell @@ -327,8 +331,10 @@ Queue interpretation after that control pass: - capture-route retry - streaming preview - manual transcript commit + - provider routing policy + - provider route selection + - workflow-policy state - still deferred: - - first-party provider routing and policy - normalized usage/cost event model - real device-permission workflow - native capture-route ownership beyond bounded shell posture @@ -377,12 +383,12 @@ Queue interpretation after that control pass: - viseme / gesture runtime integration - broad assistant-platform scope - the next queue shape is now: - - first-party provider routing and policy assessment above the landed speech session, shell, - payload-custody, and provider-custody seams + - first-party normalized usage/cost event-model assessment above the landed speech session, + shell, payload-custody, provider-custody, and provider-routing seams - the next bounded move should stay narrow: - - a source-backed `Phase 6R-Y` control pass before any widening into normalized usage/cost - events, actual payload shipping, real device-permission workflow, broad voice-output - ownership, or broad assistant-platform scope + - a source-backed `Phase 6R-Z` control pass before any widening into actual payload shipping, + real device-permission workflow, broad voice-output ownership, or broad assistant-platform + scope - keep the speech-input / voice sidecar legal sequencing guard visible: - keep code-license judgments separate from model, voice, and payload-license review - keep the provider-neutral speech-lane guard visible: