From a7e69c7f4d97624e280b729b8e7969892108b841 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 25 May 2026 22:53:38 +0200 Subject: [PATCH] Implement Phase 6R-AE provider settlement exception shell --- .../HyperTwistContractLibrary.cpp | 79 ++- .../HyperTwistHttpSpeechClient.cpp | 13 +- .../HyperTwistMockSpeechClient.cpp | 3 + .../HyperTwistTrainingSubsystem.cpp | 569 +++++++++++++++++- .../HyperTwistRecognitionTypes.h | 407 +++++++++++++ .../HyperTwistTrainingTypes.h | 3 + ...erSettlementExceptionShellContractTest.cpp | 295 +++++++++ docs/REPO_LICENSE_TRACKING.md | 19 +- ...ANDOFF_IMPLEMENTATION_PACKET_2026-05-25.md | 175 ++++++ ...L_HANDOFF_PREPARATION_PACKET_2026-05-25.md | 138 +++++ ...PERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md | 14 +- ...NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md | 6 +- .../HyperTwist/FEATURE_REGISTRY.md | 2 + .../HyperTwist/ROADMAP.md | 39 +- 14 files changed, 1727 insertions(+), 35 deletions(-) create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAEProviderSettlementExceptionShellContractTest.cpp create mode 100644 docs/arch/HYPERTWIST_PHASE6R_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_IMPLEMENTATION_PACKET_2026-05-25.md create mode 100644 docs/arch/HYPERTWIST_PHASE6R_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_PREPARATION_PACKET_2026-05-25.md diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp index 6b06cca..133a65d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -1148,6 +1148,69 @@ namespace HyperTwistContractLibraryInternal return Profile; } + FHyperTwistSpeechProviderSettlementExceptionShellProfile + MakeDefaultSpeechProviderSettlementExceptionShellProfile(const FString& ProfileId) + { + FHyperTwistSpeechProviderSettlementExceptionShellProfile Profile; + Profile.SettlementExceptionShellProfileId = ProfileId; + Profile.ShellKind = TEXT("operator-provider-settlement-exception"); + Profile.ExceptionSurfaceMode = TEXT("settlement-exception-triage"); + Profile.ExternalPortalHandoffMode = TEXT("reference-only-provider-portal-handoff"); + Profile.RefreshActionId = TEXT("refresh-provider-settlement-exceptions"); + Profile.ExceptionInspectActionId = TEXT("inspect-provider-settlement-exception"); + Profile.ExternalPortalHandoffActionId = + TEXT("review-provider-external-portal-handoff"); + Profile.RouteInspectActionId = TEXT("inspect-provider-route"); + Profile.BudgetInspectActionId = TEXT("review-provider-budget"); + Profile.RetainedExceptionEntryLimit = 12; + Profile.bSupportsExceptionTimeline = true; + Profile.bSupportsExternalPortalHandoff = true; + Profile.bSupportsSettlementExceptionReview = true; + Profile.bSupportsPendingChargeExceptions = true; + Profile.bPreservesFirstPartyHandoffOwnership = true; + + auto AddPanel = [&Profile]( + const TCHAR* PanelId, + const TCHAR* PanelKind, + const TCHAR* AnchorId, + const bool bVisibleByDefault + ) + { + FHyperTwistSpeechShellPanelLayout Panel; + Panel.PanelId = PanelId; + Panel.PanelKind = PanelKind; + Panel.AnchorId = AnchorId; + Panel.bVisibleByDefault = bVisibleByDefault; + Profile.Panels.Add(Panel); + }; + + auto AddAction = [&Profile]( + const TCHAR* ActionId, + const TCHAR* InputBinding, + const TCHAR* SurfaceId + ) + { + FHyperTwistSpeechShellActionBinding ActionBinding; + ActionBinding.ActionId = ActionId; + ActionBinding.InputBinding = InputBinding; + ActionBinding.SurfaceId = SurfaceId; + Profile.ActionBindings.Add(ActionBinding); + }; + + AddPanel(TEXT("settlement-exception-timeline-card"), TEXT("settlement-exception-timeline-card"), TEXT("top-left"), true); + AddPanel(TEXT("external-portal-handoff-card"), TEXT("external-portal-handoff-card"), TEXT("top-right"), true); + AddPanel(TEXT("exception-summary-card"), TEXT("exception-summary-card"), TEXT("left-stack-below-settlement"), true); + AddPanel(TEXT("exception-readiness-card"), TEXT("exception-readiness-card"), TEXT("right-stack-below-handoff"), true); + + AddAction(TEXT("refresh-provider-settlement-exceptions"), TEXT("F9"), TEXT("settlement-exception-timeline-card")); + AddAction(TEXT("inspect-provider-settlement-exception"), TEXT("E"), TEXT("settlement-exception-timeline-card")); + AddAction(TEXT("review-provider-external-portal-handoff"), TEXT("H"), TEXT("external-portal-handoff-card")); + AddAction(TEXT("inspect-provider-route"), TEXT("P"), TEXT("exception-summary-card")); + AddAction(TEXT("review-provider-budget"), TEXT("B"), TEXT("exception-readiness-card")); + AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root")); + return Profile; + } + FHyperTwistSpeechModelPayloadDescriptor MakeSpeechModelPayloadDescriptor( const TCHAR* PayloadId, const TCHAR* PayloadKind, @@ -1238,7 +1301,10 @@ namespace HyperTwistContractLibraryInternal TEXT("providerChargeVarianceReview"), TEXT("providerBillingSettlementShell"), TEXT("providerInvoiceReconciliation"), - TEXT("providerSettlementReadinessReview") + TEXT("providerSettlementReadinessReview"), + TEXT("providerSettlementExceptionShell"), + TEXT("providerExternalPortalHandoff"), + TEXT("providerSettlementExceptionReview") }; } @@ -2482,6 +2548,12 @@ FHyperTwistSpeechSessionConfig UHyperTwistContractLibrary::MakeSampleSpeechSessi HyperTwistContractLibraryInternal::MakeDefaultSpeechProviderBillingSettlementShellProfile( Config.ProviderBillingSettlementShellProfileId ); + Config.ProviderSettlementExceptionShellProfileId = + TEXT("speech-provider-settlement-exception-shell-v1"); + Config.ProviderSettlementExceptionShellProfileDefinition = + HyperTwistContractLibraryInternal::MakeDefaultSpeechProviderSettlementExceptionShellProfile( + Config.ProviderSettlementExceptionShellProfileId + ); Config.PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); Config.PayloadCustodyProfileDefinition = HyperTwistContractLibraryInternal::MakeDefaultSpeechPayloadCustodyProfile( @@ -2601,7 +2673,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistContractLibrary::MakeMockSpeechService TEXT("providerChargeVarianceReview"), TEXT("providerBillingSettlementShell"), TEXT("providerInvoiceReconciliation"), - TEXT("providerSettlementReadinessReview") + TEXT("providerSettlementReadinessReview"), + TEXT("providerSettlementExceptionShell"), + TEXT("providerExternalPortalHandoff"), + TEXT("providerSettlementExceptionReview") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp index 942b9a0..30d9c8e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp @@ -353,7 +353,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal TEXT("providerChargeVarianceReview"), TEXT("providerBillingSettlementShell"), TEXT("providerInvoiceReconciliation"), - TEXT("providerSettlementReadinessReview") + TEXT("providerSettlementReadinessReview"), + TEXT("providerSettlementExceptionShell"), + TEXT("providerExternalPortalHandoff"), + TEXT("providerSettlementExceptionReview") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") @@ -439,7 +442,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal TEXT("providerChargeVarianceReview"), TEXT("providerBillingSettlementShell"), TEXT("providerInvoiceReconciliation"), - TEXT("providerSettlementReadinessReview") + TEXT("providerSettlementReadinessReview"), + TEXT("providerSettlementExceptionShell"), + TEXT("providerExternalPortalHandoff"), + TEXT("providerSettlementExceptionReview") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") @@ -510,6 +516,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal Health.Capabilities.AddUnique(TEXT("providerBillingSettlementShell")); Health.Capabilities.AddUnique(TEXT("providerInvoiceReconciliation")); Health.Capabilities.AddUnique(TEXT("providerSettlementReadinessReview")); + Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionShell")); + Health.Capabilities.AddUnique(TEXT("providerExternalPortalHandoff")); + Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionReview")); Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1")); if (Health.ProviderProfileId.IsEmpty()) { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp index 2ba46a9..8731992 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp @@ -158,6 +158,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistMockSpeechClient::GetSpeechServiceHeal Health.Capabilities.AddUnique(TEXT("providerBillingSettlementShell")); Health.Capabilities.AddUnique(TEXT("providerInvoiceReconciliation")); Health.Capabilities.AddUnique(TEXT("providerSettlementReadinessReview")); + Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionShell")); + Health.Capabilities.AddUnique(TEXT("providerExternalPortalHandoff")); + Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionReview")); Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1")); return Health; } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index f0ad989..646a501 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -2936,6 +2936,12 @@ namespace HyperTwistTrainingSubsystemInternal const FHyperTwistTrainingCompanionSpeechSessionState* PriorState ); + FHyperTwistSpeechProviderSettlementExceptionShellState + BuildSpeechProviderSettlementExceptionShellState( + const FHyperTwistSpeechSessionConfig& SessionConfig, + const FHyperTwistTrainingCompanionSpeechSessionState* PriorState + ); + FHyperTwistSpeechMicrophoneShellProfile BuildDefaultCoachCommandMicrophoneShellProfile(const FString& ProfileId) { FHyperTwistSpeechMicrophoneShellProfile Profile; @@ -3243,6 +3249,69 @@ namespace HyperTwistTrainingSubsystemInternal return Profile; } + FHyperTwistSpeechProviderSettlementExceptionShellProfile + BuildDefaultSpeechProviderSettlementExceptionShellProfile(const FString& ProfileId) + { + FHyperTwistSpeechProviderSettlementExceptionShellProfile Profile; + Profile.SettlementExceptionShellProfileId = ProfileId; + Profile.ShellKind = TEXT("operator-provider-settlement-exception"); + Profile.ExceptionSurfaceMode = TEXT("settlement-exception-triage"); + Profile.ExternalPortalHandoffMode = TEXT("reference-only-provider-portal-handoff"); + Profile.RefreshActionId = TEXT("refresh-provider-settlement-exceptions"); + Profile.ExceptionInspectActionId = TEXT("inspect-provider-settlement-exception"); + Profile.ExternalPortalHandoffActionId = + TEXT("review-provider-external-portal-handoff"); + Profile.RouteInspectActionId = TEXT("inspect-provider-route"); + Profile.BudgetInspectActionId = TEXT("review-provider-budget"); + Profile.RetainedExceptionEntryLimit = 12; + Profile.bSupportsExceptionTimeline = true; + Profile.bSupportsExternalPortalHandoff = true; + Profile.bSupportsSettlementExceptionReview = true; + Profile.bSupportsPendingChargeExceptions = true; + Profile.bPreservesFirstPartyHandoffOwnership = true; + + auto AddPanel = [&Profile]( + const TCHAR* PanelId, + const TCHAR* PanelKind, + const TCHAR* AnchorId, + const bool bVisibleByDefault + ) + { + FHyperTwistSpeechShellPanelLayout Panel; + Panel.PanelId = PanelId; + Panel.PanelKind = PanelKind; + Panel.AnchorId = AnchorId; + Panel.bVisibleByDefault = bVisibleByDefault; + Profile.Panels.Add(Panel); + }; + + auto AddAction = [&Profile]( + const TCHAR* ActionId, + const TCHAR* InputBinding, + const TCHAR* SurfaceId + ) + { + FHyperTwistSpeechShellActionBinding ActionBinding; + ActionBinding.ActionId = ActionId; + ActionBinding.InputBinding = InputBinding; + ActionBinding.SurfaceId = SurfaceId; + Profile.ActionBindings.Add(ActionBinding); + }; + + AddPanel(TEXT("settlement-exception-timeline-card"), TEXT("settlement-exception-timeline-card"), TEXT("top-left"), true); + AddPanel(TEXT("external-portal-handoff-card"), TEXT("external-portal-handoff-card"), TEXT("top-right"), true); + AddPanel(TEXT("exception-summary-card"), TEXT("exception-summary-card"), TEXT("left-stack-below-settlement"), true); + AddPanel(TEXT("exception-readiness-card"), TEXT("exception-readiness-card"), TEXT("right-stack-below-handoff"), true); + + AddAction(TEXT("refresh-provider-settlement-exceptions"), TEXT("F9"), TEXT("settlement-exception-timeline-card")); + AddAction(TEXT("inspect-provider-settlement-exception"), TEXT("E"), TEXT("settlement-exception-timeline-card")); + AddAction(TEXT("review-provider-external-portal-handoff"), TEXT("H"), TEXT("external-portal-handoff-card")); + AddAction(TEXT("inspect-provider-route"), TEXT("P"), TEXT("exception-summary-card")); + AddAction(TEXT("review-provider-budget"), TEXT("B"), TEXT("exception-readiness-card")); + AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root")); + return Profile; + } + FHyperTwistSpeechModelPayloadDescriptor MakeSpeechModelPayloadDescriptor( const TCHAR* PayloadId, const TCHAR* PayloadKind, @@ -3333,7 +3402,10 @@ namespace HyperTwistTrainingSubsystemInternal TEXT("providerChargeVarianceReview"), TEXT("providerBillingSettlementShell"), TEXT("providerInvoiceReconciliation"), - TEXT("providerSettlementReadinessReview") + TEXT("providerSettlementReadinessReview"), + TEXT("providerSettlementExceptionShell"), + TEXT("providerExternalPortalHandoff"), + TEXT("providerSettlementExceptionReview") }; } @@ -4048,6 +4120,28 @@ namespace HyperTwistTrainingSubsystemInternal } SessionConfig.ProviderBillingSettlementShellProfileId = SessionConfig.ProviderBillingSettlementShellProfileDefinition.BillingSettlementShellProfileId; + if (SessionConfig.ProviderSettlementExceptionShellProfileId.IsEmpty() + && SessionConfig.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid()) + { + SessionConfig.ProviderSettlementExceptionShellProfileId = + SessionConfig.ProviderSettlementExceptionShellProfileDefinition + .SettlementExceptionShellProfileId; + } + if (SessionConfig.ProviderSettlementExceptionShellProfileId.IsEmpty()) + { + SessionConfig.ProviderSettlementExceptionShellProfileId = + TEXT("speech-provider-settlement-exception-shell-v1"); + } + if (!SessionConfig.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid()) + { + SessionConfig.ProviderSettlementExceptionShellProfileDefinition = + BuildDefaultSpeechProviderSettlementExceptionShellProfile( + SessionConfig.ProviderSettlementExceptionShellProfileId + ); + } + SessionConfig.ProviderSettlementExceptionShellProfileId = + SessionConfig.ProviderSettlementExceptionShellProfileDefinition + .SettlementExceptionShellProfileId; } void ApplySpeechProviderProfileHealthDefaults( @@ -4389,6 +4483,9 @@ namespace HyperTwistTrainingSubsystemInternal Health.Capabilities.AddUnique(TEXT("providerBillingSettlementShell")); Health.Capabilities.AddUnique(TEXT("providerInvoiceReconciliation")); Health.Capabilities.AddUnique(TEXT("providerSettlementReadinessReview")); + Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionShell")); + Health.Capabilities.AddUnique(TEXT("providerExternalPortalHandoff")); + Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionReview")); } bool IsSpeechPermissionError(const FString& ErrorText) @@ -5917,6 +6014,402 @@ namespace HyperTwistTrainingSubsystemInternal } } + FString BuildSpeechProviderSettlementExceptionSourceKind( + const FHyperTwistSpeechProviderBillingSettlementEntry& SettlementEntry + ) + { + if (SettlementEntry.SourceKind.Contains(TEXT("fallback"), ESearchCase::IgnoreCase)) + { + return TEXT("settlement-fallback-health"); + } + + if (!SettlementEntry.bHasProviderPostedCharge) + { + return TEXT("settlement-derived-pending-charge"); + } + + if (SettlementEntry.bRequiresManualReview) + { + return TEXT("settlement-derived-variance-review"); + } + + if (SettlementEntry.bSettlementBlocked) + { + return TEXT("settlement-derived-blocked"); + } + + return TEXT("settlement-derived-ready"); + } + + FHyperTwistSpeechProviderSettlementExceptionEntry + BuildSpeechProviderSettlementExceptionEntry( + const FHyperTwistSpeechProviderBillingSettlementEntry& SettlementEntry, + const FString& SelectedSettlementWindowId + ) + { + FHyperTwistSpeechProviderSettlementExceptionEntry Entry; + Entry.ExceptionEntryId = FString::Printf( + TEXT("speech-provider-settlement-exception/%s"), + *SettlementEntry.SettlementEntryId + ); + Entry.SourceKind = BuildSpeechProviderSettlementExceptionSourceKind(SettlementEntry); + Entry.SettlementEntryId = SettlementEntry.SettlementEntryId; + Entry.ReceiptEntryId = SettlementEntry.ReceiptEntryId; + Entry.HistoryEntryId = SettlementEntry.HistoryEntryId; + Entry.ProviderProfileId = SettlementEntry.ProviderProfileId; + Entry.ServiceLaneId = SettlementEntry.ServiceLaneId; + Entry.CapturedAtUtc = SettlementEntry.CapturedAtUtc; + Entry.EstimatedCostUsd = SettlementEntry.EstimatedCostUsd; + Entry.ReportedCostUsd = SettlementEntry.ReportedCostUsd; + Entry.SettlementDeltaUsd = SettlementEntry.SettlementDeltaUsd; + Entry.bRequiresManualReview = SettlementEntry.bRequiresManualReview; + Entry.bSettlementBlocked = SettlementEntry.bSettlementBlocked; + Entry.HandoffStateId = TEXT("handoff-not-required"); + + if (Entry.SourceKind.Equals(TEXT("settlement-fallback-health"), ESearchCase::IgnoreCase)) + { + Entry.ExceptionKindId = TEXT("route-degraded"); + Entry.HandoffStateId = TEXT("handoff-ready-for-route-escalation"); + Entry.bExternalPortalHandoffReady = true; + Entry.bRequiresOperatorAttention = true; + } + else if (!SettlementEntry.bHasProviderPostedCharge) + { + Entry.ExceptionKindId = TEXT("provider-charge-pending"); + Entry.HandoffStateId = TEXT("handoff-deferred-until-provider-posted-charge"); + Entry.bExternalPortalHandoffReady = false; + Entry.bRequiresOperatorAttention = false; + } + else if (SettlementEntry.bRequiresManualReview) + { + Entry.ExceptionKindId = TEXT("settlement-variance-review-required"); + Entry.HandoffStateId = TEXT("handoff-ready-for-provider-reference"); + Entry.bExternalPortalHandoffReady = true; + Entry.bRequiresOperatorAttention = true; + } + else + { + Entry.ExceptionKindId = TEXT("settlement-blocked"); + Entry.HandoffStateId = TEXT("handoff-ready-for-first-party-review"); + Entry.bExternalPortalHandoffReady = true; + Entry.bRequiresOperatorAttention = SettlementEntry.bSettlementBlocked; + } + + if (!SelectedSettlementWindowId.IsEmpty()) + { + Entry.HandoffTargetId = TEXT("external-provider-portal"); + } + + return Entry; + } + + void AddSpeechProviderSettlementExceptionIssue( + FHyperTwistSpeechProviderSettlementExceptionShellState& ShellState, + const FHyperTwistSpeechProviderSettlementExceptionIssue& Issue + ) + { + if (!Issue.IsStructurallyValid()) + { + return; + } + + ShellState.Issues.Add(Issue); + if (!ShellState.ActiveIssue.IsStructurallyValid()) + { + ShellState.ActiveIssue = Issue; + } + } + + void ApplySpeechProviderSettlementExceptionPosture( + const FHyperTwistSpeechProviderSettlementExceptionShellProfile& Profile, + const FHyperTwistTrainingCompanionSpeechSessionState& SessionState, + FHyperTwistSpeechProviderSettlementExceptionShellState& ShellState + ) + { + ShellState.ActiveProviderProfileId = + !SessionState.ProviderBillingSettlementShellState.ActiveProviderProfileId.IsEmpty() + ? SessionState.ProviderBillingSettlementShellState.ActiveProviderProfileId + : (!SessionState.SessionConfig.ProviderProfileDefinition.ProviderProfileId.IsEmpty() + ? SessionState.SessionConfig.ProviderProfileDefinition.ProviderProfileId + : TEXT("speech-provider/local-http-sidecar-profile-v1")); + ShellState.ActiveProviderDisplayLabel = + !SessionState.ProviderBillingSettlementShellState.ActiveProviderDisplayLabel.IsEmpty() + ? SessionState.ProviderBillingSettlementShellState.ActiveProviderDisplayLabel + : (!SessionState.SessionConfig.ProviderProfileDefinition.DisplayLabel.IsEmpty() + ? SessionState.SessionConfig.ProviderProfileDefinition.DisplayLabel + : TEXT("Local HTTP Speech Sidecar")); + ShellState.ActiveServiceLaneId = + !SessionState.ProviderBillingSettlementShellState.ActiveServiceLaneId.IsEmpty() + ? SessionState.ProviderBillingSettlementShellState.ActiveServiceLaneId + : (!SessionState.SessionConfig.OrchestrationProfile.ServiceLaneId.IsEmpty() + ? SessionState.SessionConfig.OrchestrationProfile.ServiceLaneId + : TEXT("speech/python-service")); + ShellState.SelectedSettlementWindowId = + !SessionState.ProviderBillingSettlementShellState.SelectedSettlementWindowId.IsEmpty() + ? SessionState.ProviderBillingSettlementShellState.SelectedSettlementWindowId + : TEXT("current-session"); + ShellState.ExceptionSurfaceModeId = Profile.ExceptionSurfaceMode; + ShellState.ExternalPortalHandoffModeId = Profile.ExternalPortalHandoffMode; + ShellState.bExceptionTimelineVisible = Profile.bSupportsExceptionTimeline; + ShellState.bExternalPortalHandoffVisible = Profile.bSupportsExternalPortalHandoff; + ShellState.bExceptionReviewVisible = Profile.bSupportsSettlementExceptionReview; + ShellState.ExceptionEntries.Reset(); + ShellState.Issues.Reset(); + ShellState.ActiveIssue = FHyperTwistSpeechProviderSettlementExceptionIssue(); + ShellState.ExceptionEntryCount = 0; + ShellState.ExternalPortalHandoffReadyEntryCount = 0; + ShellState.PendingChargeEntryCount = 0; + ShellState.ManualReviewEntryCount = 0; + ShellState.RouteDegradedEntryCount = 0; + ShellState.BlockedSettlementEntryCount = 0; + ShellState.TotalEstimatedCostUsd = 0.0f; + ShellState.TotalReportedCostUsd = 0.0f; + ShellState.TotalSettlementDeltaUsd = 0.0f; + ShellState.HighestSettlementDeltaUsd = 0.0f; + ShellState.LatestExceptionEntryId.Reset(); + ShellState.bExceptionSummaryReady = false; + ShellState.bExternalPortalHandoffReady = false; + ShellState.bSettlementExceptionReviewReady = false; + + const FHyperTwistSpeechProviderBillingSettlementShellState& SettlementState = + SessionState.ProviderBillingSettlementShellState; + if (!SettlementState.IsStructurallyValid() || SettlementState.SettlementEntries.Num() <= 0) + { + FHyperTwistSpeechProviderSettlementExceptionIssue Issue; + Issue.IssueId = TEXT("provider-settlement-exception-settlement-missing"); + Issue.IssueKind = TEXT("no-settlement-review"); + Issue.StatusLine = TEXT("Provider settlement exceptions are waiting for settlement review."); + Issue.DetailLine = TEXT("Refresh the retained provider billing-settlement shell before settlement-exception posture begins."); + Issue.RecommendedActionId = Profile.RefreshActionId; + Issue.bRequiresOperatorAttention = false; + AddSpeechProviderSettlementExceptionIssue(ShellState, Issue); + ShellState.StatusLine = Issue.StatusLine; + ShellState.DetailLine = Issue.DetailLine; + ShellState.ActiveIssueCount = ShellState.Issues.Num(); + return; + } + + const int32 ExceptionLimit = FMath::Max(1, Profile.RetainedExceptionEntryLimit); + const int32 StartIndex = + FMath::Max(0, SettlementState.SettlementEntries.Num() - ExceptionLimit); + for (int32 EntryIndex = StartIndex; + EntryIndex < SettlementState.SettlementEntries.Num(); + ++EntryIndex) + { + const FHyperTwistSpeechProviderBillingSettlementEntry& SettlementEntry = + SettlementState.SettlementEntries[EntryIndex]; + if (!SettlementEntry.IsStructurallyValid()) + { + continue; + } + + const bool bHasException = + SettlementEntry.SourceKind.Contains(TEXT("fallback"), ESearchCase::IgnoreCase) + || !SettlementEntry.bHasProviderPostedCharge + || SettlementEntry.bRequiresManualReview + || SettlementEntry.bSettlementBlocked; + if (!bHasException) + { + continue; + } + + const FHyperTwistSpeechProviderSettlementExceptionEntry Entry = + BuildSpeechProviderSettlementExceptionEntry( + SettlementEntry, + ShellState.SelectedSettlementWindowId + ); + ShellState.ExceptionEntries.Add(Entry); + ShellState.LatestExceptionEntryId = Entry.ExceptionEntryId; + ShellState.LatestSourceKind = Entry.SourceKind; + ShellState.TotalEstimatedCostUsd += Entry.EstimatedCostUsd; + ShellState.TotalReportedCostUsd += Entry.ReportedCostUsd; + ShellState.TotalSettlementDeltaUsd += Entry.SettlementDeltaUsd; + ShellState.HighestSettlementDeltaUsd = + FMath::Max(ShellState.HighestSettlementDeltaUsd, Entry.SettlementDeltaUsd); + if (Entry.bExternalPortalHandoffReady) + { + ++ShellState.ExternalPortalHandoffReadyEntryCount; + } + if (Entry.ExceptionKindId.Equals(TEXT("provider-charge-pending"), ESearchCase::IgnoreCase)) + { + ++ShellState.PendingChargeEntryCount; + } + if (Entry.bRequiresManualReview) + { + ++ShellState.ManualReviewEntryCount; + } + if (Entry.ExceptionKindId.Equals(TEXT("route-degraded"), ESearchCase::IgnoreCase)) + { + ++ShellState.RouteDegradedEntryCount; + } + if (Entry.bSettlementBlocked) + { + ++ShellState.BlockedSettlementEntryCount; + } + } + + ShellState.ExceptionEntryCount = ShellState.ExceptionEntries.Num(); + ShellState.bExceptionSummaryReady = ShellState.ExceptionEntryCount > 0; + ShellState.bExternalPortalHandoffReady = + Profile.bSupportsExternalPortalHandoff + && ShellState.ExternalPortalHandoffReadyEntryCount > 0; + ShellState.bSettlementExceptionReviewReady = + Profile.bSupportsSettlementExceptionReview + && ShellState.ExceptionEntryCount > 0; + + if (SettlementState.ActiveIssue.IsStructurallyValid() + && (SettlementState.ActiveIssue.IssueKind.Equals(TEXT("route-degraded"), ESearchCase::IgnoreCase) + || SettlementState.ActiveIssue.IssueKind.Equals(TEXT("hard-blocked"), ESearchCase::IgnoreCase) + || SettlementState.ActiveIssue.IssueKind.Equals(TEXT("rate-limited"), ESearchCase::IgnoreCase) + || SettlementState.ActiveIssue.IssueKind.Equals(TEXT("no-history"), ESearchCase::IgnoreCase) + || SettlementState.ActiveIssue.IssueKind.Equals(TEXT("no-receipt-review"), ESearchCase::IgnoreCase))) + { + FHyperTwistSpeechProviderSettlementExceptionIssue Issue; + Issue.IssueId = FString::Printf( + TEXT("provider-settlement-exception-%s"), + *SettlementState.ActiveIssue.IssueKind + ); + Issue.IssueKind = SettlementState.ActiveIssue.IssueKind; + Issue.StatusLine = SettlementState.ActiveIssue.StatusLine; + Issue.DetailLine = SettlementState.ActiveIssue.DetailLine; + Issue.RecommendedActionId = + SettlementState.ActiveIssue.IssueKind.Equals(TEXT("route-degraded"), ESearchCase::IgnoreCase) + || SettlementState.ActiveIssue.IssueKind.Equals(TEXT("hard-blocked"), ESearchCase::IgnoreCase) + || SettlementState.ActiveIssue.IssueKind.Equals(TEXT("rate-limited"), ESearchCase::IgnoreCase) + ? Profile.ExternalPortalHandoffActionId + : Profile.RefreshActionId; + Issue.bRequiresOperatorAttention = + SettlementState.ActiveIssue.bRequiresOperatorAttention; + AddSpeechProviderSettlementExceptionIssue(ShellState, Issue); + } + else if (ShellState.ManualReviewEntryCount > 0) + { + FHyperTwistSpeechProviderSettlementExceptionIssue Issue; + Issue.IssueId = TEXT("provider-settlement-exception-external-handoff-review"); + Issue.IssueKind = TEXT("external-handoff-review-recommended"); + Issue.StatusLine = + TEXT("Settlement exception review is ready for bounded external handoff."); + Issue.DetailLine = TEXT("At least one retained provider charge still diverges from the first-party settlement expectation, so the next bounded step is reference-only external portal review rather than payment execution."); + Issue.RecommendedActionId = Profile.ExternalPortalHandoffActionId; + Issue.bRequiresOperatorAttention = true; + AddSpeechProviderSettlementExceptionIssue(ShellState, Issue); + } + else if (ShellState.PendingChargeEntryCount > 0) + { + FHyperTwistSpeechProviderSettlementExceptionIssue Issue; + Issue.IssueId = TEXT("provider-settlement-exception-charge-pending"); + Issue.IssueKind = TEXT("provider-charge-pending"); + Issue.StatusLine = + TEXT("Provider settlement exceptions are waiting for posted charges."); + Issue.DetailLine = TEXT("Settlement-exception posture is bounded and ready, but the retained settlement entries still show estimate-only provider cost posture rather than a posted charge."); + Issue.RecommendedActionId = Profile.ExceptionInspectActionId; + Issue.bRequiresOperatorAttention = false; + AddSpeechProviderSettlementExceptionIssue(ShellState, Issue); + } + + if (ShellState.ActiveIssue.IsStructurallyValid()) + { + ShellState.StatusLine = ShellState.ActiveIssue.StatusLine; + ShellState.DetailLine = ShellState.ActiveIssue.DetailLine; + } + else if (ShellState.ExternalPortalHandoffReadyEntryCount > 0) + { + ShellState.StatusLine = + TEXT("Provider settlement exceptions are ready for bounded external handoff review."); + ShellState.DetailLine = FString::Printf( + TEXT("%d retained settlement exceptions for %s are ready for reference-only provider portal review without widening into payment execution."), + ShellState.ExternalPortalHandoffReadyEntryCount, + *ShellState.ActiveProviderDisplayLabel + ); + } + else + { + ShellState.StatusLine = + TEXT("Provider settlement exceptions remain bounded above reconciliation."); + ShellState.DetailLine = FString::Printf( + TEXT("%d retained settlement exceptions are available for %s, but external handoff is not ready yet."), + ShellState.ExceptionEntryCount, + *ShellState.ActiveProviderDisplayLabel + ); + } + + ShellState.ExceptionSummaryText = FString::Printf( + TEXT("providerProfileId=%s\nserviceLaneId=%s\nexceptionEntryCount=%d\n") + TEXT("externalPortalHandoffReadyEntryCount=%d\npendingChargeEntryCount=%d\n") + TEXT("manualReviewEntryCount=%d\nrouteDegradedEntryCount=%d\n") + TEXT("blockedSettlementEntryCount=%d\ntotalEstimatedCostUsd=%.2f\n") + TEXT("totalReportedCostUsd=%.2f\ntotalSettlementDeltaUsd=%.2f\n") + TEXT("highestSettlementDeltaUsd=%.2f\nlatestExceptionEntryId=%s\n") + TEXT("latestSourceKind=%s\nlastError=%s"), + *ShellState.ActiveProviderProfileId, + *ShellState.ActiveServiceLaneId, + ShellState.ExceptionEntryCount, + ShellState.ExternalPortalHandoffReadyEntryCount, + ShellState.PendingChargeEntryCount, + ShellState.ManualReviewEntryCount, + ShellState.RouteDegradedEntryCount, + ShellState.BlockedSettlementEntryCount, + ShellState.TotalEstimatedCostUsd, + ShellState.TotalReportedCostUsd, + ShellState.TotalSettlementDeltaUsd, + ShellState.HighestSettlementDeltaUsd, + *ShellState.LatestExceptionEntryId, + *ShellState.LatestSourceKind, + *SessionState.LastError + ); + ShellState.ActiveIssueCount = ShellState.Issues.Num(); + } + + void SynchronizeSpeechProviderSettlementExceptionState( + FHyperTwistTrainingCompanionSpeechSessionState& SessionState + ) + { + FHyperTwistSpeechSessionConfig EffectiveConfig = SessionState.SessionConfig; + ApplySpeechUsageCostDefaults(EffectiveConfig); + if (!SessionState.ProviderBillingSettlementShellState.IsStructurallyValid()) + { + SessionState.ProviderBillingSettlementShellState = + BuildSpeechProviderBillingSettlementShellState(EffectiveConfig, &SessionState); + } + + const FHyperTwistSpeechProviderSettlementExceptionShellProfile Profile = + EffectiveConfig.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid() + ? EffectiveConfig.ProviderSettlementExceptionShellProfileDefinition + : BuildDefaultSpeechProviderSettlementExceptionShellProfile( + !EffectiveConfig.ProviderSettlementExceptionShellProfileId.IsEmpty() + ? EffectiveConfig.ProviderSettlementExceptionShellProfileId + : TEXT("speech-provider-settlement-exception-shell-v1") + ); + + if (!SessionState.ProviderSettlementExceptionShellState.IsStructurallyValid()) + { + SessionState.ProviderSettlementExceptionShellState = + BuildSpeechProviderSettlementExceptionShellState(EffectiveConfig, &SessionState); + } + + FHyperTwistSpeechProviderSettlementExceptionShellState& ShellState = + SessionState.ProviderSettlementExceptionShellState; + ShellState.SettlementExceptionShellProfileId = + Profile.SettlementExceptionShellProfileId; + ShellState.AvailableActionIds.Reset(); + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings) + { + ShellState.AvailableActionIds.AddUnique(ActionBinding.ActionId); + } + + ApplySpeechProviderSettlementExceptionPosture(Profile, SessionState, ShellState); + + ShellState.AvailableActionIds.AddUnique(Profile.RefreshActionId); + ShellState.AvailableActionIds.AddUnique(Profile.ExceptionInspectActionId); + ShellState.AvailableActionIds.AddUnique(Profile.ExternalPortalHandoffActionId); + ShellState.AvailableActionIds.AddUnique(Profile.RouteInspectActionId); + ShellState.AvailableActionIds.AddUnique(Profile.BudgetInspectActionId); + if (ShellState.ActiveIssue.IsStructurallyValid()) + { + ShellState.AvailableActionIds.AddUnique(ShellState.ActiveIssue.RecommendedActionId); + } + } + FHyperTwistSpeechMicrophoneShellState BuildCoachCommandMicrophoneShellState( const FHyperTwistSpeechSessionConfig& SessionConfig, const FHyperTwistTrainingCompanionSpeechSessionState* PriorState, @@ -6221,6 +6714,63 @@ namespace HyperTwistTrainingSubsystemInternal return ShellState; } + FHyperTwistSpeechProviderSettlementExceptionShellState + BuildSpeechProviderSettlementExceptionShellState( + const FHyperTwistSpeechSessionConfig& SessionConfig, + const FHyperTwistTrainingCompanionSpeechSessionState* PriorState + ) + { + const FHyperTwistSpeechProviderSettlementExceptionShellProfile Profile = + SessionConfig.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid() + ? SessionConfig.ProviderSettlementExceptionShellProfileDefinition + : BuildDefaultSpeechProviderSettlementExceptionShellProfile( + !SessionConfig.ProviderSettlementExceptionShellProfileId.IsEmpty() + ? SessionConfig.ProviderSettlementExceptionShellProfileId + : TEXT("speech-provider-settlement-exception-shell-v1") + ); + + FHyperTwistSpeechProviderSettlementExceptionShellState ShellState; + ShellState.SettlementExceptionShellProfileId = + Profile.SettlementExceptionShellProfileId; + ShellState.ActiveProviderProfileId = + SessionConfig.ProviderProfileDefinition.IsStructurallyValid() + ? SessionConfig.ProviderProfileDefinition.ProviderProfileId + : TEXT("speech-provider/local-http-sidecar-profile-v1"); + ShellState.ActiveProviderDisplayLabel = + SessionConfig.ProviderProfileDefinition.IsStructurallyValid() + ? SessionConfig.ProviderProfileDefinition.DisplayLabel + : TEXT("Local HTTP Speech Sidecar"); + ShellState.ActiveServiceLaneId = !SessionConfig.OrchestrationProfile.ServiceLaneId.IsEmpty() + ? SessionConfig.OrchestrationProfile.ServiceLaneId + : TEXT("speech/python-service"); + ShellState.SelectedSettlementWindowId = !SessionConfig.CostEventTemplate.AggregationWindowId.IsEmpty() + ? SessionConfig.CostEventTemplate.AggregationWindowId + : TEXT("current-session"); + ShellState.ExceptionSurfaceModeId = Profile.ExceptionSurfaceMode; + ShellState.ExternalPortalHandoffModeId = Profile.ExternalPortalHandoffMode; + ShellState.bExceptionTimelineVisible = Profile.bSupportsExceptionTimeline; + ShellState.bExternalPortalHandoffVisible = Profile.bSupportsExternalPortalHandoff; + ShellState.bExceptionReviewVisible = Profile.bSupportsSettlementExceptionReview; + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings) + { + ShellState.AvailableActionIds.AddUnique(ActionBinding.ActionId); + } + + if (PriorState != nullptr + && PriorState->ProviderSettlementExceptionShellState + .SettlementExceptionShellProfileId + == Profile.SettlementExceptionShellProfileId) + { + ShellState = PriorState->ProviderSettlementExceptionShellState; + ShellState.SettlementExceptionShellProfileId = + Profile.SettlementExceptionShellProfileId; + ShellState.ExceptionSurfaceModeId = Profile.ExceptionSurfaceMode; + ShellState.ExternalPortalHandoffModeId = Profile.ExternalPortalHandoffMode; + } + + return ShellState; + } + FHyperTwistVisionCorrectionState BuildClassicCubeCorrectionState( const FHyperTwistVisionSessionConfig& SessionConfig, const FHyperTwistVisionReconstructionSession& ReconstructionSession, @@ -11605,6 +12155,11 @@ bool UHyperTwistTrainingSubsystem::OpenActiveCompanionSpeechSession(FString& Out SessionConfig, nullptr ); + ActiveCompanionSpeechSessionState.ProviderSettlementExceptionShellState = + HyperTwistTrainingSubsystemInternal::BuildSpeechProviderSettlementExceptionShellState( + SessionConfig, + nullptr + ); if (ActiveCompanionSpeechSessionState.bSessionOpen && ActiveCompanionSpeechSessionState.ActiveSessionId != SessionConfig.SessionId) @@ -11688,6 +12243,9 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderSettlementExceptionState( + ActiveCompanionSpeechSessionState + ); return Result; } @@ -11871,6 +12429,9 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderSettlementExceptionState( + ActiveCompanionSpeechSessionState + ); return Result; } @@ -12626,6 +13187,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth() HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderSettlementExceptionState( + ActiveCompanionSpeechSessionState + ); return; } @@ -12663,6 +13227,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth() HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderBillingSettlementState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechProviderSettlementExceptionState( + ActiveCompanionSpeechSessionState + ); } void UHyperTwistTrainingSubsystem::AppendRecognitionReplayEvent( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h index d697668..72b4daa 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h @@ -4036,6 +4036,403 @@ struct FHyperTwistSpeechProviderBillingSettlementShellState } }; +USTRUCT(BlueprintType) +struct FHyperTwistSpeechProviderSettlementExceptionShellProfile +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SettlementExceptionShellProfileId = + TEXT("speech-provider-settlement-exception-shell-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ShellKind = TEXT("operator-provider-settlement-exception"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExceptionSurfaceMode = TEXT("settlement-exception-triage"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExternalPortalHandoffMode = TEXT("reference-only-provider-portal-handoff"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RefreshActionId = TEXT("refresh-provider-settlement-exceptions"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExceptionInspectActionId = TEXT("inspect-provider-settlement-exception"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExternalPortalHandoffActionId = + TEXT("review-provider-external-portal-handoff"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RouteInspectActionId = TEXT("inspect-provider-route"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString BudgetInspectActionId = TEXT("review-provider-budget"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RetainedExceptionEntryLimit = 12; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsExceptionTimeline = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsExternalPortalHandoff = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsSettlementExceptionReview = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsPendingChargeExceptions = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bPreservesFirstPartyHandoffOwnership = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray Panels; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ActionBindings; + + bool IsStructurallyValid() const + { + if (SettlementExceptionShellProfileId.IsEmpty() + || ShellKind.IsEmpty() + || ExceptionSurfaceMode.IsEmpty() + || ExternalPortalHandoffMode.IsEmpty() + || RefreshActionId.IsEmpty() + || ExceptionInspectActionId.IsEmpty() + || ExternalPortalHandoffActionId.IsEmpty() + || RouteInspectActionId.IsEmpty() + || BudgetInspectActionId.IsEmpty() + || RetainedExceptionEntryLimit <= 0 + || Panels.Num() <= 0 + || ActionBindings.Num() <= 0) + { + return false; + } + + for (const FHyperTwistSpeechShellPanelLayout& Panel : Panels) + { + if (!Panel.IsStructurallyValid()) + { + return false; + } + } + + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : ActionBindings) + { + if (!ActionBinding.IsStructurallyValid()) + { + return false; + } + } + + return true; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistSpeechProviderSettlementExceptionEntry +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExceptionEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SourceKind = TEXT("settlement-derived-pending-charge"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SettlementEntryId = TEXT("speech-provider-settlement-entry/current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ReceiptEntryId = TEXT("speech-provider-receipt-entry/current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HistoryEntryId = TEXT("speech-history-entry/current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ProviderProfileId = TEXT("speech-provider/local-http-sidecar-profile-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ServiceLaneId = TEXT("speech/python-service"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExceptionKindId = TEXT("provider-charge-pending"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HandoffTargetId = TEXT("external-provider-portal"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HandoffStateId = TEXT("handoff-deferred-until-provider-posted-charge"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CapturedAtUtc = TEXT("2026-05-25T00:00:00Z"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float EstimatedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float ReportedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float SettlementDeltaUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRequiresManualReview = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bExternalPortalHandoffReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSettlementBlocked = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRequiresOperatorAttention = false; + + bool IsStructurallyValid() const + { + return !ExceptionEntryId.IsEmpty() + && !SourceKind.IsEmpty() + && !SettlementEntryId.IsEmpty() + && !ReceiptEntryId.IsEmpty() + && !HistoryEntryId.IsEmpty() + && !ProviderProfileId.IsEmpty() + && !ServiceLaneId.IsEmpty() + && !ExceptionKindId.IsEmpty() + && !HandoffTargetId.IsEmpty() + && !HandoffStateId.IsEmpty() + && !CapturedAtUtc.IsEmpty() + && EstimatedCostUsd >= 0.0f + && ReportedCostUsd >= 0.0f + && SettlementDeltaUsd >= 0.0f; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistSpeechProviderSettlementExceptionIssue +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString IssueId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString IssueKind; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RecommendedActionId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRequiresOperatorAttention = true; + + bool IsStructurallyValid() const + { + return !IssueId.IsEmpty() + && !IssueKind.IsEmpty() + && !StatusLine.IsEmpty() + && !DetailLine.IsEmpty() + && !RecommendedActionId.IsEmpty(); + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistSpeechProviderSettlementExceptionShellState +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SettlementExceptionShellProfileId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveProviderProfileId = TEXT("speech-provider/local-http-sidecar-profile-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveProviderDisplayLabel = TEXT("Local HTTP Speech Sidecar"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveServiceLaneId = TEXT("speech/python-service"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectedSettlementWindowId = TEXT("current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExceptionSurfaceModeId = TEXT("settlement-exception-triage"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExternalPortalHandoffModeId = TEXT("reference-only-provider-portal-handoff"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestExceptionEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestSourceKind = TEXT("settlement-derived-pending-charge"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine = TEXT("Provider settlement exceptions are ready for bounded inspection."); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine = TEXT("First-party settlement exception posture remains bounded above retained settlement review and only exposes reference handoff surfaces."); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExceptionSummaryText; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ExceptionEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ExternalPortalHandoffReadyEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 PendingChargeEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ManualReviewEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RouteDegradedEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 BlockedSettlementEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float TotalEstimatedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float TotalReportedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float TotalSettlementDeltaUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float HighestSettlementDeltaUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bExceptionTimelineVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bExternalPortalHandoffVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bExceptionReviewVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bExceptionSummaryReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bExternalPortalHandoffReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSettlementExceptionReviewReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ActiveIssueCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechProviderSettlementExceptionIssue ActiveIssue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray Issues; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ExceptionEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray AvailableActionIds; + + bool IsStructurallyValid() const + { + if (SettlementExceptionShellProfileId.IsEmpty() + || ActiveProviderProfileId.IsEmpty() + || ActiveProviderDisplayLabel.IsEmpty() + || ActiveServiceLaneId.IsEmpty() + || SelectedSettlementWindowId.IsEmpty() + || ExceptionSurfaceModeId.IsEmpty() + || ExternalPortalHandoffModeId.IsEmpty() + || LatestSourceKind.IsEmpty() + || StatusLine.IsEmpty() + || DetailLine.IsEmpty() + || ExceptionEntryCount < 0 + || ExternalPortalHandoffReadyEntryCount < 0 + || PendingChargeEntryCount < 0 + || ManualReviewEntryCount < 0 + || RouteDegradedEntryCount < 0 + || BlockedSettlementEntryCount < 0 + || TotalEstimatedCostUsd < 0.0f + || TotalReportedCostUsd < 0.0f + || TotalSettlementDeltaUsd < 0.0f + || HighestSettlementDeltaUsd < 0.0f + || ActiveIssueCount < 0 + || AvailableActionIds.Num() <= 0) + { + return false; + } + + if (ExceptionEntryCount != ExceptionEntries.Num() + || ExternalPortalHandoffReadyEntryCount > ExceptionEntryCount + || PendingChargeEntryCount > ExceptionEntryCount + || ManualReviewEntryCount > ExceptionEntryCount + || RouteDegradedEntryCount > ExceptionEntryCount + || BlockedSettlementEntryCount > ExceptionEntryCount) + { + return false; + } + + if (ExceptionEntryCount > 0 && LatestExceptionEntryId.IsEmpty()) + { + return false; + } + + if (bExceptionSummaryReady && ExceptionSummaryText.IsEmpty()) + { + return false; + } + + if (ActiveIssueCount != Issues.Num()) + { + return false; + } + + if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid()) + { + return false; + } + + for (const FHyperTwistSpeechProviderSettlementExceptionIssue& Issue : Issues) + { + if (!Issue.IsStructurallyValid()) + { + return false; + } + } + + for (const FHyperTwistSpeechProviderSettlementExceptionEntry& Entry : ExceptionEntries) + { + if (!Entry.IsStructurallyValid()) + { + return false; + } + } + + for (const FString& ActionId : AvailableActionIds) + { + if (ActionId.IsEmpty()) + { + return false; + } + } + + return true; + } +}; + USTRUCT(BlueprintType) struct FHyperTwistSpeechSessionConfig { @@ -4137,6 +4534,14 @@ struct FHyperTwistSpeechSessionConfig FHyperTwistSpeechProviderBillingSettlementShellProfile ProviderBillingSettlementShellProfileDefinition; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ProviderSettlementExceptionShellProfileId = + TEXT("speech-provider-settlement-exception-shell-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechProviderSettlementExceptionShellProfile + ProviderSettlementExceptionShellProfileDefinition; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); @@ -4191,6 +4596,8 @@ struct FHyperTwistSpeechSessionConfig || !ProviderReceiptReviewShellProfileDefinition.IsStructurallyValid() || ProviderBillingSettlementShellProfileId.IsEmpty() || !ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid() + || ProviderSettlementExceptionShellProfileId.IsEmpty() + || !ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid() || PayloadCustodyProfileId.IsEmpty() || !PayloadCustodyProfileDefinition.IsStructurallyValid() || RequiredModelPayloads.Num() <= 0 diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 25e440f..2dfaa20 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -1952,6 +1952,9 @@ struct FHyperTwistTrainingCompanionSpeechSessionState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechProviderBillingSettlementShellState ProviderBillingSettlementShellState; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechProviderSettlementExceptionShellState ProviderSettlementExceptionShellState; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechTranscriptResult LastTranscriptResult; }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAEProviderSettlementExceptionShellContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAEProviderSettlementExceptionShellContractTest.cpp new file mode 100644 index 0000000..add1e9e --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAEProviderSettlementExceptionShellContractTest.cpp @@ -0,0 +1,295 @@ +// 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 HyperTwistWhisperCppPhase6RAETestInternal +{ + FHyperTwistTrainingDeck MakeSpeechDeck() + { + FHyperTwistTrainingDeck Deck; + Deck.DeckId = TEXT("phase6r-ae/provider-settlement-exception"); + Deck.Title = TEXT("Phase 6R-AE Provider Settlement Exception"); + Deck.DeliveryModes = { + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + + FHyperTwistTrainingCase TrainingCase; + TrainingCase.CaseId = TEXT("phase6r-ae-case"); + TrainingCase.PuzzleId = TEXT("cube/3x3x3"); + TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence; + TrainingCase.PromptLabel = TEXT("Phase 6R-AE 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-ae-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; + } + + FHyperTwistSpeechUtteranceEnvelope MakeUtterance() + { + FHyperTwistSpeechUtteranceEnvelope Utterance; + Utterance.AudioRef = TEXT("exception-coach"); + Utterance.SpeechStartMs = 120; + Utterance.SpeechEndMs = 840; + Utterance.SilenceGapMs = 240; + return Utterance; + } + + bool ContainsPanelId( + const TArray& Panels, + const FString& ExpectedPanelId + ) + { + for (const FHyperTwistSpeechShellPanelLayout& Panel : Panels) + { + if (Panel.PanelId == ExpectedPanelId) + { + return true; + } + } + + return false; + } + + bool ContainsActionId( + const TArray& ActionBindings, + const FString& ExpectedActionId + ) + { + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : ActionBindings) + { + if (ActionBinding.ActionId == ExpectedActionId) + { + return true; + } + } + + return false; + } +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RAEProviderSettlementExceptionSessionConfigTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AE.ProviderSettlementExceptionSessionConfig", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RAEProviderSettlementExceptionSessionConfigTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RAETestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ae-config-session"), + TEXT("mock") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AE."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + FString OpenError; + TestTrue(TEXT("The mock settlement-exception route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestTrue(TEXT("Opening the mock settlement-exception speech session must not report an error."), OpenError.IsEmpty()); + + const FHyperTwistSpeechSessionConfig& Config = + TrainingSubsystem->GetActiveCompanionSpeechSessionState().SessionConfig; + + TestTrue(TEXT("The settlement-exception session config must remain structurally valid."), Config.IsStructurallyValid()); + TestEqual(TEXT("The settlement-exception shell profile id must be present."), Config.ProviderSettlementExceptionShellProfileId, TEXT("speech-provider-settlement-exception-shell-v1")); + TestTrue(TEXT("The settlement-exception shell profile must remain structurally valid."), Config.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid()); + TestEqual(TEXT("The settlement-exception shell must preserve the operator-facing shell kind."), Config.ProviderSettlementExceptionShellProfileDefinition.ShellKind, TEXT("operator-provider-settlement-exception")); + TestEqual(TEXT("The settlement-exception shell must preserve the exception inspect action id."), Config.ProviderSettlementExceptionShellProfileDefinition.ExceptionInspectActionId, TEXT("inspect-provider-settlement-exception")); + TestEqual(TEXT("The settlement-exception shell must preserve the external handoff action id."), Config.ProviderSettlementExceptionShellProfileDefinition.ExternalPortalHandoffActionId, TEXT("review-provider-external-portal-handoff")); + TestTrue(TEXT("The settlement-exception shell must include the external handoff panel."), HyperTwistWhisperCppPhase6RAETestInternal::ContainsPanelId(Config.ProviderSettlementExceptionShellProfileDefinition.Panels, TEXT("external-portal-handoff-card"))); + TestTrue(TEXT("The settlement-exception shell must include the exception inspect action."), HyperTwistWhisperCppPhase6RAETestInternal::ContainsActionId(Config.ProviderSettlementExceptionShellProfileDefinition.ActionBindings, TEXT("inspect-provider-settlement-exception"))); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RAEProviderSettlementExceptionShellStateTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AE.ProviderSettlementExceptionShellState", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RAEProviderSettlementExceptionShellStateTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RAETestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ae-shell-session"), + TEXT("mock") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AE."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + FString OpenError; + TestTrue(TEXT("The mock settlement-exception route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestTrue(TEXT("Opening the mock settlement-exception speech session must not report an error."), OpenError.IsEmpty()); + + const FHyperTwistSpeechTranscriptResult Result = + TrainingSubsystem->SubmitActiveCompanionSpeechUtterance( + HyperTwistWhisperCppPhase6RAETestInternal::MakeUtterance() + ); + TestTrue(TEXT("The mock settlement-exception utterance must produce a final transcript."), Result.bIsFinal); + TestTrue(TEXT("The mock settlement-exception utterance must remain structurally valid."), Result.IsStructurallyValid()); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechServiceHealth& Health = SessionState.ServiceHealth; + const FHyperTwistSpeechProviderSettlementExceptionShellState& ExceptionState = + SessionState.ProviderSettlementExceptionShellState; + + TestTrue(TEXT("The service health must expose the settlement-exception shell."), Health.Capabilities.Contains(TEXT("providerSettlementExceptionShell"))); + TestTrue(TEXT("The service health must expose external portal handoff."), Health.Capabilities.Contains(TEXT("providerExternalPortalHandoff"))); + TestTrue(TEXT("The service health must expose settlement-exception review."), Health.Capabilities.Contains(TEXT("providerSettlementExceptionReview"))); + + TestTrue(TEXT("The settlement-exception shell state must remain structurally valid."), ExceptionState.IsStructurallyValid()); + TestEqual(TEXT("The settlement-exception shell must preserve the mock provider profile."), ExceptionState.ActiveProviderProfileId, TEXT("speech-provider/mock-offline-profile-v1")); + TestEqual(TEXT("The settlement-exception shell must retain exactly two bounded exception entries after open and submit."), ExceptionState.ExceptionEntryCount, 2); + TestEqual(TEXT("The settlement-exception shell must preserve the latest source kind."), ExceptionState.LatestSourceKind, TEXT("settlement-derived-pending-charge")); + TestTrue(TEXT("The settlement-exception shell must preserve summary readiness."), ExceptionState.bExceptionSummaryReady); + TestFalse(TEXT("The settlement-exception shell must not mark external handoff ready before provider-posted charges or route exceptions appear."), ExceptionState.bExternalPortalHandoffReady); + TestTrue(TEXT("The settlement-exception shell must preserve exception-review visibility."), ExceptionState.bSettlementExceptionReviewReady); + TestTrue(TEXT("The settlement-exception shell must preserve the external handoff action."), ExceptionState.AvailableActionIds.Contains(TEXT("review-provider-external-portal-handoff"))); + TestTrue(TEXT("The settlement-exception shell must preserve the exception inspect action."), ExceptionState.AvailableActionIds.Contains(TEXT("inspect-provider-settlement-exception"))); + TestEqual(TEXT("The settlement-exception shell must preserve zero external-handoff-ready entries in the mock route."), ExceptionState.ExternalPortalHandoffReadyEntryCount, 0); + TestEqual(TEXT("The settlement-exception shell must preserve the pending-charge entry count."), ExceptionState.PendingChargeEntryCount, 2); + TestEqual(TEXT("The settlement-exception shell must preserve the manual-review entry count."), ExceptionState.ManualReviewEntryCount, 0); + TestEqual(TEXT("The settlement-exception shell must preserve the blocked-settlement entry count."), ExceptionState.BlockedSettlementEntryCount, 2); + TestTrue(TEXT("The settlement-exception shell must accumulate the mock estimated cost."), FMath::IsNearlyEqual(ExceptionState.TotalEstimatedCostUsd, 0.02f)); + TestTrue(TEXT("The settlement-exception shell must preserve zero settlement delta while provider charges are still pending."), FMath::IsNearlyEqual(ExceptionState.TotalSettlementDeltaUsd, 0.0f)); + TestEqual(TEXT("The settlement-exception shell must surface the pending provider-charge issue."), ExceptionState.ActiveIssue.IssueKind, TEXT("provider-charge-pending")); + TestTrue(TEXT("The settlement-exception summary text must preserve the bounded pending-charge count."), ExceptionState.ExceptionSummaryText.Contains(TEXT("pendingChargeEntryCount=2"))); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RAEProviderBackedSettlementExceptionFallbackTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AE.ProviderBackedSettlementExceptionFallback", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RAEProviderBackedSettlementExceptionFallbackTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RAETestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ae-provider-session"), + TEXT("http-sidecar") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AE."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + UHyperTwistHttpSpeechClient* HttpSpeechClient = + HyperTwistWhisperCppPhase6RAETestInternal::ResolveHttpSpeechClient(TrainingSubsystem); + TestNotNull(TEXT("The provider-backed speech client must be available for the settlement-exception fallback test."), HttpSpeechClient); + if (HttpSpeechClient == nullptr) + { + return false; + } + + HttpSpeechClient->ServiceBaseUrl.Reset(); + + FString OpenError; + TestFalse(TEXT("The provider-backed settlement-exception path must fail cleanly when no provider endpoint is configured."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestEqual(TEXT("The provider-backed settlement-exception path must report the missing service base URL."), OpenError, TEXT("service-base-url-missing")); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechProviderSettlementExceptionShellState& ExceptionState = + SessionState.ProviderSettlementExceptionShellState; + + TestTrue(TEXT("The provider-backed settlement-exception fallback must remain structurally valid."), ExceptionState.IsStructurallyValid()); + TestEqual(TEXT("The fallback settlement-exception shell must preserve the sidecar profile id."), ExceptionState.ActiveProviderProfileId, TEXT("speech-provider/local-http-sidecar-profile-v1")); + TestEqual(TEXT("The fallback settlement-exception shell must preserve the fallback source kind."), ExceptionState.LatestSourceKind, TEXT("settlement-fallback-health")); + TestEqual(TEXT("The fallback settlement-exception shell must preserve one exception entry."), ExceptionState.ExceptionEntryCount, 1); + TestEqual(TEXT("The fallback settlement-exception shell must surface one active issue."), ExceptionState.ActiveIssueCount, 1); + TestEqual(TEXT("The fallback settlement-exception shell must surface the route-degraded issue kind."), ExceptionState.ActiveIssue.IssueKind, TEXT("route-degraded")); + TestTrue(TEXT("The fallback settlement-exception shell must still expose summary readiness for bounded inspection output."), ExceptionState.bExceptionSummaryReady); + TestTrue(TEXT("The fallback settlement-exception shell must mark external handoff ready for route escalation posture."), ExceptionState.bExternalPortalHandoffReady); + TestEqual(TEXT("The fallback settlement-exception shell must preserve one external-handoff-ready entry."), ExceptionState.ExternalPortalHandoffReadyEntryCount, 1); + TestTrue(TEXT("The fallback settlement-exception summary text must preserve the missing-endpoint reason."), ExceptionState.ExceptionSummaryText.Contains(TEXT("service-base-url-missing"))); + return true; +} + +#endif diff --git a/docs/REPO_LICENSE_TRACKING.md b/docs/REPO_LICENSE_TRACKING.md index 23de480..d59c57f 100644 --- a/docs/REPO_LICENSE_TRACKING.md +++ b/docs/REPO_LICENSE_TRACKING.md @@ -207,9 +207,12 @@ Status update on `2026-05-21`: reconciliation control pass is now consumed - the bounded first-party `Phase 6R-AD` provider billing settlement and invoice reconciliation packet is now landed in current code -- the current next bounded move is a source-backed `Phase 6R-AE` first-party provider settlement - exception and external-portal handoff preparation/control pass, not a new restrictive packet by - default +- the generic source-backed `Phase 6R-AE` first-party provider settlement exception and + external-portal handoff control pass is now consumed +- the bounded first-party `Phase 6R-AE` provider settlement exception and external-portal handoff + packet is now landed in current code +- the current next bounded move is a source-backed `Phase 6R-AF` first-party real + device-permission workflow 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 @@ -1629,11 +1632,15 @@ Approved working posture: and invoice reconciliation shell metadata above the existing transcript-session, shell, payload-custody, provider-custody, provider-routing, usage/cost, history/export, and receipt-review seams +- the next bounded first-party implementation slice is now landed as provider settlement + exception and external-portal handoff shell metadata above the existing transcript-session, + shell, payload-custody, provider-custody, provider-routing, usage/cost, history/export, + receipt-review, and settlement seams - the top-level provider/session contract remains first-party HyperTwist-owned and provider-neutral; this row does not own that lane -- keep provider settlement exception/handoff, real device-permission workflow, native - capture-route ownership, actual payment execution, provider-portal ownership, actual payload - shipping, and future voice-asset review separate from the code-license judgment +- keep real device-permission workflow, native capture-route ownership, actual payment + execution, provider-portal ownership, actual payload shipping, 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_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_IMPLEMENTATION_PACKET_2026-05-25.md b/docs/arch/HYPERTWIST_PHASE6R_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_IMPLEMENTATION_PACKET_2026-05-25.md new file mode 100644 index 0000000..74ed376 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_IMPLEMENTATION_PACKET_2026-05-25.md @@ -0,0 +1,175 @@ +# HyperTwist Phase 6R-AE first-party provider settlement exception and external-portal handoff implementation packet + +Created on `2026-05-25` + +## Status + +- first-party HyperTwist packet +- bounded `Phase 6R-AE` implementation slice + +## Purpose + +This packet lands the next bounded first-party slice above the landed speech +session, microphone-shell, payload-custody, provider-custody, +provider-routing, normalized usage/cost event, operator-facing +usage/cost dashboard, provider usage/cost history/export, provider +receipt-review, and provider billing-settlement seams. + +The landed slice is: + +- first-party provider settlement exception and external-portal handoff shell + +It is not: + +- an actual payment execution packet +- a provider-portal ownership packet +- an external billing settlement packet +- an external invoice remittance packet +- an actual payload-shipping packet +- a real device-permission workflow packet +- a native audio-device route ownership 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_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_PREPARATION_PACKET_2026-05-25.md` +- `docs/arch/HYPERTWIST_PHASE6R_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_IMPLEMENTATION_PACKET_2026-05-25.md` + +The preserved owners do not change: + +- first-party HyperTwist remains the top-level provider/session owner +- first-party HyperTwist remains the usage/cost governance owner +- first-party HyperTwist remains the settlement exception and handoff shell 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 settlement-exception seam through: + +- retained recognition contract types for: + - `FHyperTwistSpeechProviderSettlementExceptionShellProfile` + - `FHyperTwistSpeechProviderSettlementExceptionEntry` + - `FHyperTwistSpeechProviderSettlementExceptionIssue` + - `FHyperTwistSpeechProviderSettlementExceptionShellState` + - expanded `FHyperTwistSpeechSessionConfig` + - expanded `FHyperTwistTrainingCompanionSpeechSessionState` +- contract-library defaults for: + - exception shell profile ids and definitions + - exception panel ids and action bindings + - service-health capability exposure +- active companion session-config, service-health, and derived settlement-exception + shell composition in: + - `UHyperTwistTrainingSubsystem` +- direct speech-client health capability exposure in: + - `UHyperTwistHttpSpeechClient` + - `UHyperTwistMockSpeechClient` +- focused automation coverage in: + - `HyperTwistWhisperCppPhase6RAEProviderSettlementExceptionShellContractTest.cpp` + +## Why this is still intentionally bounded + +This packet lands the settlement-exception and external-portal handoff shell +family only. + +Still deferred: + +- actual payment execution or provider-portal ownership +- real device-permission workflow +- native audio-device route ownership beyond bounded shell posture +- actual downloadable model/payload shipping +- broad assistant-platform scope + +## Validation + +Build validation: + +- `UnrealHyperTwistEditor Win64 Development` + +Focused automation validation: + +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AE` `3/3` + +Regression automation validation: + +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AD` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AC` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AB` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AA` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.Z` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.Y` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.X` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.W` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.V` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.U` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.E` `3/3` +- `HyperTwist.Permissive.FasterWhisper.Phase6R.F` `3/3` +- `HyperTwist.Permissive.Piper.Phase6R.G` `3/3` +- `HyperTwist.Permissive.Coqui.Phase6R.H` `3/3` +- `HyperTwist.CleanRoom.CubeDesk` `14/14` + +Non-blocking warnings stayed limited to the existing Unreal headless/editor +noise, the pre-existing plugin dependency warning on `UnrealMCP`, and the +known `http:/speech/health` and `http:/vision/health` hostname-resolution +warnings in provider-backed automation. + +## Queue effect + +This packet consumes the current `Phase 6R-AE` 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 + - first-party normalized usage/cost event-model boundary + - first-party operator-facing provider usage/cost dashboard shell boundary + - first-party provider usage/cost history/export shell boundary + - first-party provider receipt review and posted-charge inspection shell + boundary + - first-party provider billing settlement and invoice reconciliation shell + boundary + - first-party provider settlement exception and external-portal handoff shell + boundary for: + - exception shell profile ids and definitions + - derived exception entry and issue/state posture + - reference-only external-portal handoff readiness + - capability exposure for: + - `providerSettlementExceptionShell` + - `providerExternalPortalHandoff` + - `providerSettlementExceptionReview` +- still deferred: + - actual payment execution or provider-portal ownership + - real device-permission workflow + - native audio-device route ownership beyond bounded shell posture + - actual downloadable model/payload shipping + - broad assistant-platform scope + +The next clean move is: + +- a source-backed `Phase 6R-AF` first-party real device-permission workflow + preparation/control pass + +Keep the future sequencing guards visible: + +- first-party HyperTwist + - keep the top-level provider/session contract, routing/policy lane, + usage/cost governance lane, dashboard shell lane, history/export shell + lane, receipt-review shell lane, settlement shell lane, and + exception/handoff shell lane first-party +- `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/session, custody, + routing, usage/cost, dashboard, history/export, receipt-review, + settlement, and exception/handoff seams rather than widening it into + payment execution or provider-portal ownership diff --git a/docs/arch/HYPERTWIST_PHASE6R_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_PREPARATION_PACKET_2026-05-25.md b/docs/arch/HYPERTWIST_PHASE6R_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_PREPARATION_PACKET_2026-05-25.md new file mode 100644 index 0000000..e0d26d0 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_PREPARATION_PACKET_2026-05-25.md @@ -0,0 +1,138 @@ +# HyperTwist Phase 6R-AE first-party provider settlement exception and external-portal handoff preparation packet + +Created on `2026-05-25` + +## Status + +- first-party HyperTwist packet +- source-backed `Phase 6R-AE` preparation/control slice + +## Purpose + +This packet scopes the next bounded first-party slice above the landed speech +session, microphone-shell, payload-custody, provider-custody, +provider-routing, normalized usage/cost event, operator-facing +usage/cost dashboard, provider usage/cost history/export, provider +receipt-review, and provider billing-settlement seams. + +The granted slice is: + +- first-party provider settlement exception and external-portal handoff shell only + +It is not: + +- an actual payment execution packet +- a provider-portal ownership packet +- an external billing settlement packet +- an external invoice remittance packet +- an actual payload-shipping packet +- a real device-permission workflow packet +- a native audio-device route ownership 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_AD_FIRST_PARTY_PROVIDER_BILLING_SETTLEMENT_AND_INVOICE_RECONCILIATION_IMPLEMENTATION_PACKET_2026-05-25.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 usage/cost governance owner +- first-party HyperTwist remains the settlement exception and handoff shell 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-AE` slice may land: + +1. first-party settlement-exception shell profile ids and definitions +2. derived exception entry, issue, and shell-state record shapes +3. session-config and companion-session-state expansion for those shapes +4. bounded capability exposure for: + - `providerSettlementExceptionShell` + - `providerExternalPortalHandoff` + - `providerSettlementExceptionReview` +5. focused automation for pending-charge exception posture and provider-backed + route-degraded handoff posture + +The packet must stay out of: + +- actual payment execution +- provider-portal ownership +- external settlement ownership +- external invoice-remittance ownership +- actual payload download or redistribution ownership +- real device-permission workflow ownership +- native audio-device route ownership + +## Proposed implementation shape + +Land the narrower first-party boundary through: + +- retained recognition contract types for: + - provider settlement-exception shell profile + - provider settlement-exception entry + - provider settlement-exception issue + - provider settlement-exception shell state + - expanded speech session config + - expanded companion speech session state +- contract-library defaults for: + - exception shell profile ids and definitions + - exception action bindings and panel ids + - service-health capability exposure +- training-subsystem composition for: + - bounded exception posture derived from retained settlement snapshots + - reference-only external-portal handoff readiness posture + - pending-charge and route-degraded exception posture + - explicit separation between first-party handoff surfaces and actual + payment execution +- direct speech-client health capability exposure for: + - `providerSettlementExceptionShell` + - `providerExternalPortalHandoff` + - `providerSettlementExceptionReview` + +## Validation target + +Validate with: + +- Unreal build for `UnrealHyperTwistEditor Win64 Development` +- focused automation: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AE` +- regressions: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AD` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AC` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AB` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AA` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.Z` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.Y` + - `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-AF` first-party real device-permission workflow + preparation/control pass + +Keep the narrow sequencing guards visible: + +- keep real device-permission workflow separate from native capture-route + ownership +- keep actual payment execution and provider-portal ownership separate from + this packet +- keep actual payload shipping separate from usage/cost governance +- keep broad assistant-platform widening outside this packet diff --git a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md index 9bc33ff..f1fa101 100644 --- a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md +++ b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md @@ -184,15 +184,19 @@ The next bounded move is now: invoice reconciliation control pass is now consumed 34. the bounded first-party `Phase 6R-AD` provider billing settlement and invoice reconciliation packet is now landed in current code -35. the next bounded move is a source-backed `Phase 6R-AE` first-party provider settlement - exception and external-portal handoff preparation/control pass -36. keep the speech-lane guard visible: +35. the generic source-backed `Phase 6R-AE` first-party provider settlement exception and + external-portal handoff control pass is now consumed +36. the bounded first-party `Phase 6R-AE` provider settlement exception and external-portal + handoff packet is now landed in current code +37. the next bounded move is a source-backed `Phase 6R-AF` first-party real + device-permission workflow preparation/control pass +38. keep the speech-lane guard visible: - keep code-license judgments separate from model, voice, and payload-license review -37. keep the provider-neutral speech-lane guard visible: +39. 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, the provider routing/policy lane, or the usage/cost governance lane -38. keep the `MagicTile` guard visible: +40. 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 50fe686..7e6d1d9 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 @@ -222,8 +222,10 @@ Current landed ordering status: first-party implementation anchor - provider billing settlement and invoice reconciliation now also have a bounded first-party implementation anchor -- provider settlement exception and external-portal handoff remain the next - bounded first-party gap above the landed settlement shell +- provider settlement exception and external-portal handoff now also have a + bounded first-party implementation anchor +- real device-permission workflow is now the next bounded first-party gap + above the landed permission/readiness shell posture - actual payment execution or provider-portal ownership remains later first-party work - provider-specific adapters remain later work 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 6b4918c..c69c892 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -130,6 +130,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice. | First-party provider usage/cost history/export shell | Implemented now | landed first-party `Phase 6R-AB` | First-party rolling history entries, export issue/state objects, summary export preview/copy posture, and operator-facing panel/action contracts are now live above the landed provider usage/cost dashboard seam. | | First-party provider receipt review and posted-charge inspection shell | Implemented now | landed first-party `Phase 6R-AC` | First-party receipt-review shell profiles, derived receipt-review entries, charge-pending and variance issue posture, and bounded inspection capability exposure are now live above the landed provider usage/cost history/export seam. | | First-party provider billing settlement and invoice reconciliation shell | Implemented now | landed first-party `Phase 6R-AD` | First-party settlement shell profiles, derived settlement entries, invoice-reconciliation posture, settlement-readiness issue state, and bounded settlement capability exposure are now live above the landed provider receipt-review seam. | +| First-party provider settlement exception and external-portal handoff shell | Implemented now | landed first-party `Phase 6R-AE` | First-party settlement-exception shell profiles, derived exception entries, reference-only external-portal handoff posture, and bounded exception capability exposure are now live above the landed provider settlement 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. | @@ -229,6 +230,7 @@ repo. | First-party provider usage/cost history/export shell | Implemented now | landed first-party `Phase 6R-AB` packet | Current bounded history/export shell profile ids/definitions, rolling history entry and export issue/state posture, summary export preview/copy behavior, and capability exposure above the landed provider usage/cost dashboard seam. | | First-party provider receipt review and posted-charge inspection shell | Implemented now | landed first-party `Phase 6R-AC` packet | Current bounded receipt-review shell profile ids/definitions, derived receipt-review entry and issue/state posture, and capability exposure above the landed provider usage/cost history/export seam. | | First-party provider billing settlement and invoice reconciliation shell | Implemented now | landed first-party `Phase 6R-AD` packet | Current bounded settlement shell profile ids/definitions, derived settlement entry and issue/state posture, invoice-reconciliation readiness, and capability exposure above the landed provider receipt-review seam. | +| First-party provider settlement exception and external-portal handoff shell | Implemented now | landed first-party `Phase 6R-AE` packet | Current bounded settlement-exception shell profile ids/definitions, derived exception entry and issue/state posture, reference-only external-portal handoff readiness, and capability exposure above the landed provider settlement seam. | | 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. | | Provider-specific overlays | Shallow placeholder | future provider-family work only | Keep internal until source-grounded and normalized. | diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 5fafd6a..d215caf 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -183,10 +183,14 @@ Canonical discovery surfaces for roadmap interpretation: reconciliation control pass is now consumed - the bounded first-party `Phase 6R-AD` provider billing settlement and invoice reconciliation packet is now landed in current code -- the current next bounded move is a source-backed `Phase 6R-AE` first-party provider settlement - exception and external-portal handoff preparation/control pass, while keeping actual payment - execution, provider-portal ownership, real device-permission workflow, native capture-route - ownership, and actual payload shipping separately deferred +- the generic source-backed `Phase 6R-AE` first-party provider settlement exception and + external-portal handoff control pass is now consumed +- the bounded first-party `Phase 6R-AE` provider settlement exception and external-portal + handoff packet is now landed in current code +- the current next bounded move is a source-backed `Phase 6R-AF` first-party real + device-permission workflow preparation/control pass, while keeping native capture-route + ownership, actual payment execution, provider-portal 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 @@ -273,16 +277,15 @@ Current routing truth: - `brianpeiris/RiftSketch` - the v6.3 CSV label `not_live_reference_or_discard_candidate` is not the final state by itself; read the closed `0R-E` packet for the retained-versus-discarded split -The next bounded move is a source-backed `Phase 6R-AE` first-party provider -settlement exception and external-portal handoff preparation/control pass. +The next bounded move is a source-backed `Phase 6R-AF` first-party real +device-permission workflow 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 settlement exception and external-portal handoff - remainder above the partially landed `whisper.cpp` and `faster-whisper` - speech seams + - the first-party real device-permission workflow remainder above the + partially landed `whisper.cpp` and `faster-whisper` speech seams - `HactarCE/Hyperspeedcube` is now closed for the currently justified retained row: - landed: - puzzle catalog contract @@ -400,8 +403,10 @@ Queue interpretation after that control pass: - provider billing settlement shell - provider invoice reconciliation - provider settlement-readiness review + - provider settlement exception shell + - provider external-portal handoff + - provider settlement-exception review - still deferred: - - provider settlement exception and external-portal handoff shell surfaces - actual payment execution or provider-portal ownership - real device-permission workflow - native capture-route ownership beyond bounded shell posture @@ -450,14 +455,14 @@ Queue interpretation after that control pass: - viseme / gesture runtime integration - broad assistant-platform scope - the next queue shape is now: - - first-party provider settlement exception and external-portal handoff - assessment above the landed speech session, shell, payload-custody, - provider-custody, provider-routing, normalized usage/cost, operator-facing - dashboard, history/export, receipt-review, and settlement seams + - first-party real device-permission workflow assessment above the landed + speech session, shell, payload-custody, provider-custody, provider-routing, + normalized usage/cost, operator-facing dashboard, history/export, + receipt-review, settlement, and exception/handoff seams - the next bounded move should stay narrow: - - a source-backed `Phase 6R-AE` control pass before any widening into actual - payment execution, provider-portal ownership, actual payload shipping, - real device-permission workflow, broad voice-output ownership, or broad + - a source-backed `Phase 6R-AF` control pass before any widening into native + capture-route ownership, actual payment execution, provider-portal + ownership, actual payload shipping, 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