From bb15b91682a90627cf93b34ad57d32dc30b333b4 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 25 May 2026 17:37:23 +0200 Subject: [PATCH] Implement Phase 6R-AB provider usage cost history export shell --- .../HyperTwistContractLibrary.cpp | 78 ++- .../HyperTwistHttpSpeechClient.cpp | 13 +- .../HyperTwistMockSpeechClient.cpp | 3 + .../HyperTwistTrainingSubsystem.cpp | 515 +++++++++++++++++- .../HyperTwistRecognitionTypes.h | 397 ++++++++++++++ .../HyperTwistTrainingTypes.h | 3 + ...sageCostHistoryExportShellContractTest.cpp | 282 ++++++++++ docs/REPO_LICENSE_TRACKING.md | 12 +- ..._SHELL_IMPLEMENTATION_PACKET_2026-05-25.md | 163 ++++++ ...ORT_SHELL_PREPARATION_PACKET_2026-05-25.md | 130 +++++ ...PERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md | 14 +- ...NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md | 7 +- .../HyperTwist/FEATURE_REGISTRY.md | 2 + .../HyperTwist/ROADMAP.md | 36 +- 14 files changed, 1627 insertions(+), 28 deletions(-) create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RABUsageCostHistoryExportShellContractTest.cpp create mode 100644 docs/arch/HYPERTWIST_PHASE6R_AB_FIRST_PARTY_PROVIDER_USAGE_COST_HISTORY_EXPORT_SHELL_IMPLEMENTATION_PACKET_2026-05-25.md create mode 100644 docs/arch/HYPERTWIST_PHASE6R_AB_FIRST_PARTY_PROVIDER_USAGE_COST_HISTORY_EXPORT_SHELL_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 e0192ba..be799d4 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -962,6 +962,68 @@ namespace HyperTwistContractLibraryInternal return Profile; } + FHyperTwistSpeechUsageCostHistoryExportShellProfile + MakeDefaultSpeechUsageCostHistoryExportShellProfile(const FString& ProfileId) + { + FHyperTwistSpeechUsageCostHistoryExportShellProfile Profile; + Profile.HistoryExportShellProfileId = ProfileId; + Profile.ShellKind = TEXT("operator-usage-cost-history-export"); + Profile.HistorySurfaceMode = TEXT("rolling-usage-cost-snapshot-history"); + Profile.ExportSurfaceMode = TEXT("first-party-summary-preview"); + Profile.RefreshActionId = TEXT("refresh-provider-usage-cost-history"); + Profile.ExportPreviewActionId = TEXT("preview-provider-usage-cost-export"); + Profile.CopySummaryActionId = TEXT("copy-provider-usage-cost-summary"); + Profile.RouteInspectActionId = TEXT("inspect-provider-route"); + Profile.BudgetInspectActionId = TEXT("review-provider-budget"); + Profile.RetainedHistoryEntryLimit = 12; + Profile.bSupportsHistoryTimeline = true; + Profile.bSupportsExportPreview = true; + Profile.bSupportsCopySummary = true; + Profile.bSupportsEstimateWatermark = true; + Profile.bPreservesFirstPartySummaryOwnership = 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("history-timeline-card"), TEXT("history-timeline-card"), TEXT("top-left"), true); + AddPanel(TEXT("history-summary-card"), TEXT("history-summary-card"), TEXT("left-stack-below-history"), true); + AddPanel(TEXT("export-preview-card"), TEXT("export-preview-card"), TEXT("top-right"), true); + AddPanel(TEXT("export-readiness-card"), TEXT("export-readiness-card"), TEXT("right-stack-below-preview"), true); + + AddAction(TEXT("refresh-provider-usage-cost-history"), TEXT("F6"), TEXT("history-timeline-card")); + AddAction(TEXT("preview-provider-usage-cost-export"), TEXT("E"), TEXT("export-preview-card")); + AddAction(TEXT("copy-provider-usage-cost-summary"), TEXT("C"), TEXT("export-readiness-card")); + AddAction(TEXT("inspect-provider-route"), TEXT("P"), TEXT("history-summary-card")); + AddAction(TEXT("review-provider-budget"), TEXT("B"), TEXT("export-readiness-card")); + AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root")); + return Profile; + } + FHyperTwistSpeechModelPayloadDescriptor MakeSpeechModelPayloadDescriptor( const TCHAR* PayloadId, const TCHAR* PayloadKind, @@ -1043,7 +1105,10 @@ namespace HyperTwistContractLibraryInternal TEXT("quotaRateLimitState"), TEXT("providerUsageCostDashboardShell"), TEXT("providerQuotaEscalationBanner"), - TEXT("providerRouteCostDiagnostics") + TEXT("providerRouteCostDiagnostics"), + TEXT("providerUsageCostHistoryShell"), + TEXT("providerUsageCostExportPreview"), + TEXT("providerUsageCostSummaryCopy") }; } @@ -2269,6 +2334,12 @@ FHyperTwistSpeechSessionConfig UHyperTwistContractLibrary::MakeSampleSpeechSessi HyperTwistContractLibraryInternal::MakeDefaultSpeechUsageCostDashboardShellProfile( Config.UsageCostDashboardShellProfileId ); + Config.UsageCostHistoryExportShellProfileId = + TEXT("speech-provider-usage-cost-history-export-shell-v1"); + Config.UsageCostHistoryExportShellProfileDefinition = + HyperTwistContractLibraryInternal::MakeDefaultSpeechUsageCostHistoryExportShellProfile( + Config.UsageCostHistoryExportShellProfileId + ); Config.PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); Config.PayloadCustodyProfileDefinition = HyperTwistContractLibraryInternal::MakeDefaultSpeechPayloadCustodyProfile( @@ -2379,7 +2450,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistContractLibrary::MakeMockSpeechService TEXT("quotaRateLimitState"), TEXT("providerUsageCostDashboardShell"), TEXT("providerQuotaEscalationBanner"), - TEXT("providerRouteCostDiagnostics") + TEXT("providerRouteCostDiagnostics"), + TEXT("providerUsageCostHistoryShell"), + TEXT("providerUsageCostExportPreview"), + TEXT("providerUsageCostSummaryCopy") }; 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 ac8e972..f0a6870 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp @@ -344,7 +344,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal TEXT("quotaRateLimitState"), TEXT("providerUsageCostDashboardShell"), TEXT("providerQuotaEscalationBanner"), - TEXT("providerRouteCostDiagnostics") + TEXT("providerRouteCostDiagnostics"), + TEXT("providerUsageCostHistoryShell"), + TEXT("providerUsageCostExportPreview"), + TEXT("providerUsageCostSummaryCopy") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") @@ -421,7 +424,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal TEXT("quotaRateLimitState"), TEXT("providerUsageCostDashboardShell"), TEXT("providerQuotaEscalationBanner"), - TEXT("providerRouteCostDiagnostics") + TEXT("providerRouteCostDiagnostics"), + TEXT("providerUsageCostHistoryShell"), + TEXT("providerUsageCostExportPreview"), + TEXT("providerUsageCostSummaryCopy") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") @@ -483,6 +489,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal Health.Capabilities.AddUnique(TEXT("providerUsageCostDashboardShell")); Health.Capabilities.AddUnique(TEXT("providerQuotaEscalationBanner")); Health.Capabilities.AddUnique(TEXT("providerRouteCostDiagnostics")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostHistoryShell")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostExportPreview")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostSummaryCopy")); 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 2683f8f..5363014 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp @@ -149,6 +149,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistMockSpeechClient::GetSpeechServiceHeal Health.Capabilities.AddUnique(TEXT("providerUsageCostDashboardShell")); Health.Capabilities.AddUnique(TEXT("providerQuotaEscalationBanner")); Health.Capabilities.AddUnique(TEXT("providerRouteCostDiagnostics")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostHistoryShell")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostExportPreview")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostSummaryCopy")); 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 3b4f13d..f89243a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -2921,6 +2921,11 @@ namespace HyperTwistTrainingSubsystemInternal const FHyperTwistTrainingCompanionSpeechSessionState* PriorState ); + FHyperTwistSpeechUsageCostHistoryExportShellState BuildSpeechUsageCostHistoryExportShellState( + const FHyperTwistSpeechSessionConfig& SessionConfig, + const FHyperTwistTrainingCompanionSpeechSessionState* PriorState + ); + FHyperTwistSpeechMicrophoneShellProfile BuildDefaultCoachCommandMicrophoneShellProfile(const FString& ProfileId) { FHyperTwistSpeechMicrophoneShellProfile Profile; @@ -3042,6 +3047,68 @@ namespace HyperTwistTrainingSubsystemInternal return Profile; } + FHyperTwistSpeechUsageCostHistoryExportShellProfile + BuildDefaultSpeechUsageCostHistoryExportShellProfile(const FString& ProfileId) + { + FHyperTwistSpeechUsageCostHistoryExportShellProfile Profile; + Profile.HistoryExportShellProfileId = ProfileId; + Profile.ShellKind = TEXT("operator-usage-cost-history-export"); + Profile.HistorySurfaceMode = TEXT("rolling-usage-cost-snapshot-history"); + Profile.ExportSurfaceMode = TEXT("first-party-summary-preview"); + Profile.RefreshActionId = TEXT("refresh-provider-usage-cost-history"); + Profile.ExportPreviewActionId = TEXT("preview-provider-usage-cost-export"); + Profile.CopySummaryActionId = TEXT("copy-provider-usage-cost-summary"); + Profile.RouteInspectActionId = TEXT("inspect-provider-route"); + Profile.BudgetInspectActionId = TEXT("review-provider-budget"); + Profile.RetainedHistoryEntryLimit = 12; + Profile.bSupportsHistoryTimeline = true; + Profile.bSupportsExportPreview = true; + Profile.bSupportsCopySummary = true; + Profile.bSupportsEstimateWatermark = true; + Profile.bPreservesFirstPartySummaryOwnership = 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("history-timeline-card"), TEXT("history-timeline-card"), TEXT("top-left"), true); + AddPanel(TEXT("history-summary-card"), TEXT("history-summary-card"), TEXT("left-stack-below-history"), true); + AddPanel(TEXT("export-preview-card"), TEXT("export-preview-card"), TEXT("top-right"), true); + AddPanel(TEXT("export-readiness-card"), TEXT("export-readiness-card"), TEXT("right-stack-below-preview"), true); + + AddAction(TEXT("refresh-provider-usage-cost-history"), TEXT("F6"), TEXT("history-timeline-card")); + AddAction(TEXT("preview-provider-usage-cost-export"), TEXT("E"), TEXT("export-preview-card")); + AddAction(TEXT("copy-provider-usage-cost-summary"), TEXT("C"), TEXT("export-readiness-card")); + AddAction(TEXT("inspect-provider-route"), TEXT("P"), TEXT("history-summary-card")); + AddAction(TEXT("review-provider-budget"), TEXT("B"), TEXT("export-readiness-card")); + AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root")); + return Profile; + } + FHyperTwistSpeechModelPayloadDescriptor MakeSpeechModelPayloadDescriptor( const TCHAR* PayloadId, const TCHAR* PayloadKind, @@ -3123,7 +3190,10 @@ namespace HyperTwistTrainingSubsystemInternal TEXT("quotaRateLimitState"), TEXT("providerUsageCostDashboardShell"), TEXT("providerQuotaEscalationBanner"), - TEXT("providerRouteCostDiagnostics") + TEXT("providerRouteCostDiagnostics"), + TEXT("providerUsageCostHistoryShell"), + TEXT("providerUsageCostExportPreview"), + TEXT("providerUsageCostSummaryCopy") }; } @@ -3778,6 +3848,26 @@ namespace HyperTwistTrainingSubsystemInternal } SessionConfig.UsageCostDashboardShellProfileId = SessionConfig.UsageCostDashboardShellProfileDefinition.DashboardShellProfileId; + if (SessionConfig.UsageCostHistoryExportShellProfileId.IsEmpty() + && SessionConfig.UsageCostHistoryExportShellProfileDefinition.IsStructurallyValid()) + { + SessionConfig.UsageCostHistoryExportShellProfileId = + SessionConfig.UsageCostHistoryExportShellProfileDefinition.HistoryExportShellProfileId; + } + if (SessionConfig.UsageCostHistoryExportShellProfileId.IsEmpty()) + { + SessionConfig.UsageCostHistoryExportShellProfileId = + TEXT("speech-provider-usage-cost-history-export-shell-v1"); + } + if (!SessionConfig.UsageCostHistoryExportShellProfileDefinition.IsStructurallyValid()) + { + SessionConfig.UsageCostHistoryExportShellProfileDefinition = + BuildDefaultSpeechUsageCostHistoryExportShellProfile( + SessionConfig.UsageCostHistoryExportShellProfileId + ); + } + SessionConfig.UsageCostHistoryExportShellProfileId = + SessionConfig.UsageCostHistoryExportShellProfileDefinition.HistoryExportShellProfileId; } void ApplySpeechProviderProfileHealthDefaults( @@ -4110,6 +4200,9 @@ namespace HyperTwistTrainingSubsystemInternal Health.Capabilities.AddUnique(TEXT("providerUsageCostDashboardShell")); Health.Capabilities.AddUnique(TEXT("providerQuotaEscalationBanner")); Health.Capabilities.AddUnique(TEXT("providerRouteCostDiagnostics")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostHistoryShell")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostExportPreview")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostSummaryCopy")); } bool IsSpeechPermissionError(const FString& ErrorText) @@ -4604,6 +4697,352 @@ namespace HyperTwistTrainingSubsystemInternal } } + FString BuildSpeechUsageCostHistorySourceKind( + const FHyperTwistTrainingCompanionSpeechSessionState& SessionState + ) + { + if (!SessionState.LastError.IsEmpty() && !SessionState.ServiceHealth.bReady) + { + return TEXT("provider-fallback-health"); + } + if (!SessionState.bSessionOpen && !SessionState.OpenedAtUtc.IsEmpty()) + { + return TEXT("session-closed-health"); + } + if (SessionState.bHasTranscriptResult) + { + return TEXT("post-transcript-health"); + } + if (SessionState.bSessionOpen) + { + return TEXT("session-open-health"); + } + + return TEXT("service-health-snapshot"); + } + + FString BuildSpeechUsageCostHistorySignature( + const FHyperTwistTrainingCompanionSpeechSessionState& SessionState, + const FHyperTwistSpeechUsageCostDashboardShellState& DashboardState, + const FString& SourceKind + ) + { + return FString::Printf( + TEXT("%s|%s|%s|%s|%s|%s|%s|%d|%d|%d|%d|%d"), + *SourceKind, + *DashboardState.LatestUsageEventId, + *DashboardState.LatestCostEventId, + *DashboardState.LatestQuotaStateId, + *DashboardState.LatestRouteDecisionId, + *DashboardState.ActiveRouteStateId, + *SessionState.LastError, + SessionState.SubmittedUtteranceCount, + SessionState.FinalTranscriptCount, + DashboardState.bRouteReady ? 1 : 0, + DashboardState.bQuotaKnown ? 1 : 0, + SessionState.bSessionOpen ? 1 : 0 + ); + } + + FHyperTwistSpeechUsageCostHistoryEntry BuildSpeechUsageCostHistoryEntry( + const FHyperTwistTrainingCompanionSpeechSessionState& SessionState, + const FHyperTwistSpeechUsageCostDashboardShellState& DashboardState, + const FString& SourceKind, + const int32 EntryOrdinal + ) + { + FHyperTwistSpeechUsageCostHistoryEntry Entry; + const FString EffectiveSessionId = + !SessionState.ActiveSessionId.IsEmpty() + ? SessionState.ActiveSessionId + : (!SessionState.SessionConfig.SessionId.IsEmpty() + ? SessionState.SessionConfig.SessionId + : TEXT("speech-session-default")); + Entry.HistoryEntryId = FString::Printf( + TEXT("speech-usage-cost-history/%s/%02d"), + *EffectiveSessionId, + FMath::Max(EntryOrdinal, 1) + ); + Entry.SourceKind = SourceKind; + Entry.SessionId = EffectiveSessionId; + Entry.ProviderProfileId = DashboardState.ActiveProviderProfileId; + Entry.ServiceLaneId = DashboardState.ActiveServiceLaneId; + Entry.UsageEventId = DashboardState.LatestUsageEventId; + Entry.CostEventId = DashboardState.LatestCostEventId; + Entry.QuotaStateId = DashboardState.LatestQuotaStateId; + Entry.RouteDecisionId = DashboardState.LatestRouteDecisionId; + Entry.CapturedAtUtc = !SessionState.LastUpdatedAtUtc.IsEmpty() + ? SessionState.LastUpdatedAtUtc + : FDateTime::UtcNow().ToIso8601(); + Entry.UsageQuantity = DashboardState.DisplayedUsageQuantity; + Entry.EstimatedCostUsd = DashboardState.DisplayedEstimatedCostUsd; + Entry.ReportedCostUsd = DashboardState.DisplayedReportedCostUsd; + Entry.RemainingEstimatedSpendUsd = DashboardState.RemainingEstimatedSpendUsd; + Entry.RemainingRequestCount = DashboardState.RemainingRequestCount; + Entry.RemainingAudioSeconds = DashboardState.RemainingAudioSeconds; + Entry.bEstimateOnly = DashboardState.bEstimateBadgeVisible; + Entry.bProviderReceiptPosted = DashboardState.bProviderReceiptPosted; + Entry.bQuotaKnown = DashboardState.bQuotaKnown; + Entry.bRateLimited = DashboardState.bRateLimited; + Entry.bHardBlocked = DashboardState.bHardBlocked; + Entry.bRouteReady = DashboardState.bRouteReady; + return Entry; + } + + void AddSpeechUsageCostHistoryExportIssue( + FHyperTwistSpeechUsageCostHistoryExportShellState& ShellState, + const FHyperTwistSpeechUsageCostHistoryExportIssue& Issue + ) + { + if (!Issue.IsStructurallyValid()) + { + return; + } + + ShellState.Issues.Add(Issue); + if (!ShellState.ActiveIssue.IsStructurallyValid()) + { + ShellState.ActiveIssue = Issue; + } + } + + void AppendSpeechUsageCostHistoryEntry( + const FHyperTwistSpeechUsageCostHistoryExportShellProfile& Profile, + const FString& Signature, + const FHyperTwistSpeechUsageCostHistoryEntry& Entry, + FHyperTwistSpeechUsageCostHistoryExportShellState& ShellState + ) + { + if (!Entry.IsStructurallyValid() || ShellState.LastRecordedSignature == Signature) + { + return; + } + + ShellState.HistoryEntries.Add(Entry); + while (ShellState.HistoryEntries.Num() > Profile.RetainedHistoryEntryLimit) + { + ShellState.HistoryEntries.RemoveAt(0); + } + ShellState.LastRecordedSignature = Signature; + } + + void ApplySpeechUsageCostHistoryExportPosture( + const FHyperTwistSpeechUsageCostHistoryExportShellProfile& Profile, + const FHyperTwistTrainingCompanionSpeechSessionState& SessionState, + FHyperTwistSpeechUsageCostHistoryExportShellState& ShellState + ) + { + const FHyperTwistSpeechUsageCostDashboardShellState& DashboardState = + SessionState.UsageCostDashboardShellState; + const FString SourceKind = BuildSpeechUsageCostHistorySourceKind(SessionState); + ShellState.ActiveProviderProfileId = DashboardState.ActiveProviderProfileId; + ShellState.ActiveProviderDisplayLabel = DashboardState.ActiveProviderDisplayLabel; + ShellState.ActiveServiceLaneId = DashboardState.ActiveServiceLaneId; + ShellState.SelectedHistoryWindowId = !DashboardState.UsageAggregationWindowId.IsEmpty() + ? DashboardState.UsageAggregationWindowId + : TEXT("current-session"); + ShellState.ExportPreviewFormatId = Profile.ExportSurfaceMode; + ShellState.bHistoryVisible = Profile.bSupportsHistoryTimeline; + ShellState.bExportPreviewVisible = Profile.bSupportsExportPreview; + + const FString Signature = BuildSpeechUsageCostHistorySignature( + SessionState, + DashboardState, + SourceKind + ); + AppendSpeechUsageCostHistoryEntry( + Profile, + Signature, + BuildSpeechUsageCostHistoryEntry( + SessionState, + DashboardState, + SourceKind, + ShellState.HistoryEntries.Num() + 1 + ), + ShellState + ); + + ShellState.TotalUsageQuantity = 0.0f; + ShellState.TotalEstimatedCostUsd = 0.0f; + ShellState.TotalReportedCostUsd = 0.0f; + ShellState.LowestRemainingEstimatedSpendUsd = 0.0f; + ShellState.EstimateOnlyEntryCount = 0; + ShellState.ProviderReceiptEntryCount = 0; + ShellState.Issues.Reset(); + ShellState.ActiveIssue = FHyperTwistSpeechUsageCostHistoryExportIssue(); + + for (int32 EntryIndex = 0; EntryIndex < ShellState.HistoryEntries.Num(); ++EntryIndex) + { + const FHyperTwistSpeechUsageCostHistoryEntry& HistoryEntry = ShellState.HistoryEntries[EntryIndex]; + ShellState.TotalUsageQuantity += HistoryEntry.UsageQuantity; + ShellState.TotalEstimatedCostUsd += HistoryEntry.EstimatedCostUsd; + ShellState.TotalReportedCostUsd += HistoryEntry.ReportedCostUsd; + ShellState.LowestRemainingEstimatedSpendUsd = + EntryIndex == 0 + ? HistoryEntry.RemainingEstimatedSpendUsd + : FMath::Min( + ShellState.LowestRemainingEstimatedSpendUsd, + HistoryEntry.RemainingEstimatedSpendUsd + ); + if (HistoryEntry.bEstimateOnly) + { + ++ShellState.EstimateOnlyEntryCount; + } + if (HistoryEntry.bProviderReceiptPosted) + { + ++ShellState.ProviderReceiptEntryCount; + } + } + + ShellState.HistoryEntryCount = ShellState.HistoryEntries.Num(); + ShellState.bExportPreviewReady = ShellState.HistoryEntries.Num() > 0; + ShellState.bCopySummaryReady = + Profile.bSupportsCopySummary && ShellState.bExportPreviewReady; + ShellState.bEstimateWatermarkVisible = + Profile.bSupportsEstimateWatermark && ShellState.EstimateOnlyEntryCount > 0; + ShellState.LatestSourceKind = SourceKind; + ShellState.LatestHistoryEntryId = ShellState.HistoryEntries.Num() > 0 + ? ShellState.HistoryEntries.Last().HistoryEntryId + : FString(); + + if (ShellState.HistoryEntries.Num() <= 0) + { + FHyperTwistSpeechUsageCostHistoryExportIssue Issue; + Issue.IssueId = TEXT("provider-usage-cost-history-empty"); + Issue.IssueKind = TEXT("no-history"); + Issue.StatusLine = TEXT("Provider usage/cost history not captured yet."); + Issue.DetailLine = + TEXT("Refresh the provider usage/cost surfaces before relying on export preview output."); + Issue.RecommendedActionId = Profile.RefreshActionId; + AddSpeechUsageCostHistoryExportIssue(ShellState, Issue); + ShellState.StatusLine = Issue.StatusLine; + ShellState.DetailLine = Issue.DetailLine; + } + else + { + const FHyperTwistSpeechUsageCostHistoryEntry& LatestEntry = ShellState.HistoryEntries.Last(); + ShellState.LatestSourceKind = LatestEntry.SourceKind; + + if (!LatestEntry.bRouteReady || (!SessionState.LastError.IsEmpty() && !SessionState.ServiceHealth.bReady)) + { + FHyperTwistSpeechUsageCostHistoryExportIssue Issue; + Issue.IssueId = TEXT("provider-usage-cost-history-route-degraded"); + Issue.IssueKind = TEXT("route-degraded"); + Issue.StatusLine = TEXT("Provider history captured with route degradation."); + Issue.DetailLine = SessionState.LastError.IsEmpty() + ? TEXT("The latest history snapshot came from a degraded provider route. Review the route before treating export preview output as current operating posture.") + : FString::Printf( + TEXT("The latest history snapshot captured '%s'. Review the route before treating export preview output as current operating posture."), + *SessionState.LastError + ); + Issue.RecommendedActionId = Profile.RouteInspectActionId; + AddSpeechUsageCostHistoryExportIssue(ShellState, Issue); + } + else if (LatestEntry.bHardBlocked) + { + FHyperTwistSpeechUsageCostHistoryExportIssue Issue; + Issue.IssueId = TEXT("provider-usage-cost-history-hard-blocked"); + Issue.IssueKind = TEXT("hard-blocked"); + Issue.StatusLine = TEXT("Provider history shows hard-blocked budget posture."); + Issue.DetailLine = + TEXT("The latest history snapshot reflects a hard-blocked provider budget posture. Review budget state before relying on more provider-backed capture."); + Issue.RecommendedActionId = Profile.BudgetInspectActionId; + AddSpeechUsageCostHistoryExportIssue(ShellState, Issue); + } + else if (LatestEntry.bRateLimited) + { + FHyperTwistSpeechUsageCostHistoryExportIssue Issue; + Issue.IssueId = TEXT("provider-usage-cost-history-rate-limited"); + Issue.IssueKind = TEXT("rate-limited"); + Issue.StatusLine = TEXT("Provider history shows rate-limited posture."); + Issue.DetailLine = + TEXT("The latest history snapshot reflects a rate-limited provider route. Review budget state before relying on more provider-backed capture."); + Issue.RecommendedActionId = Profile.BudgetInspectActionId; + AddSpeechUsageCostHistoryExportIssue(ShellState, Issue); + } + + if (ShellState.ActiveIssue.IsStructurallyValid()) + { + ShellState.StatusLine = ShellState.ActiveIssue.StatusLine; + ShellState.DetailLine = ShellState.ActiveIssue.DetailLine; + } + else + { + ShellState.StatusLine = TEXT("Provider usage/cost history ready for operator preview."); + ShellState.DetailLine = FString::Printf( + TEXT("%d snapshots retain %.2f %s and %.2f USD estimated cost for %s."), + ShellState.HistoryEntryCount, + ShellState.TotalUsageQuantity, + *DashboardState.UsageMeterKind, + ShellState.TotalEstimatedCostUsd, + *ShellState.ActiveProviderDisplayLabel + ); + } + } + + ShellState.ExportPreviewText = FString::Printf( + TEXT("providerProfileId=%s\nserviceLaneId=%s\nhistoryEntryCount=%d\nlatestSourceKind=%s\ntotalUsageQuantity=%.2f\n") + TEXT("totalEstimatedCostUsd=%.2f\ntotalReportedCostUsd=%.2f\nestimateOnlyEntryCount=%d\nproviderReceiptEntryCount=%d\n") + TEXT("lowestRemainingEstimatedSpendUsd=%.2f\nlatestHistoryEntryId=%s\nlastError=%s"), + *ShellState.ActiveProviderProfileId, + *ShellState.ActiveServiceLaneId, + ShellState.HistoryEntryCount, + *ShellState.LatestSourceKind, + ShellState.TotalUsageQuantity, + ShellState.TotalEstimatedCostUsd, + ShellState.TotalReportedCostUsd, + ShellState.EstimateOnlyEntryCount, + ShellState.ProviderReceiptEntryCount, + ShellState.LowestRemainingEstimatedSpendUsd, + *ShellState.LatestHistoryEntryId, + *SessionState.LastError + ); + ShellState.ActiveIssueCount = ShellState.Issues.Num(); + } + + void SynchronizeSpeechUsageCostHistoryExportState( + FHyperTwistTrainingCompanionSpeechSessionState& SessionState + ) + { + FHyperTwistSpeechSessionConfig EffectiveConfig = SessionState.SessionConfig; + ApplySpeechUsageCostDefaults(EffectiveConfig); + if (!SessionState.UsageCostHistoryExportShellState.IsStructurallyValid()) + { + SessionState.UsageCostHistoryExportShellState = + BuildSpeechUsageCostHistoryExportShellState(EffectiveConfig, &SessionState); + } + + const FHyperTwistSpeechUsageCostHistoryExportShellProfile Profile = + EffectiveConfig.UsageCostHistoryExportShellProfileDefinition.IsStructurallyValid() + ? EffectiveConfig.UsageCostHistoryExportShellProfileDefinition + : BuildDefaultSpeechUsageCostHistoryExportShellProfile( + !EffectiveConfig.UsageCostHistoryExportShellProfileId.IsEmpty() + ? EffectiveConfig.UsageCostHistoryExportShellProfileId + : TEXT("speech-provider-usage-cost-history-export-shell-v1") + ); + + FHyperTwistSpeechUsageCostHistoryExportShellState& ShellState = + SessionState.UsageCostHistoryExportShellState; + ShellState.HistoryExportShellProfileId = Profile.HistoryExportShellProfileId; + ShellState.AvailableActionIds.Reset(); + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings) + { + ShellState.AvailableActionIds.AddUnique(ActionBinding.ActionId); + } + + ApplySpeechUsageCostHistoryExportPosture(Profile, SessionState, ShellState); + + ShellState.AvailableActionIds.AddUnique(Profile.RefreshActionId); + ShellState.AvailableActionIds.AddUnique(Profile.ExportPreviewActionId); + ShellState.AvailableActionIds.AddUnique(Profile.CopySummaryActionId); + 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, @@ -4759,6 +5198,55 @@ namespace HyperTwistTrainingSubsystemInternal return ShellState; } + FHyperTwistSpeechUsageCostHistoryExportShellState BuildSpeechUsageCostHistoryExportShellState( + const FHyperTwistSpeechSessionConfig& SessionConfig, + const FHyperTwistTrainingCompanionSpeechSessionState* PriorState + ) + { + const FHyperTwistSpeechUsageCostHistoryExportShellProfile Profile = + SessionConfig.UsageCostHistoryExportShellProfileDefinition.IsStructurallyValid() + ? SessionConfig.UsageCostHistoryExportShellProfileDefinition + : BuildDefaultSpeechUsageCostHistoryExportShellProfile( + !SessionConfig.UsageCostHistoryExportShellProfileId.IsEmpty() + ? SessionConfig.UsageCostHistoryExportShellProfileId + : TEXT("speech-provider-usage-cost-history-export-shell-v1") + ); + + FHyperTwistSpeechUsageCostHistoryExportShellState ShellState; + ShellState.HistoryExportShellProfileId = Profile.HistoryExportShellProfileId; + 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.SelectedHistoryWindowId = !SessionConfig.UsageEventTemplate.AggregationWindowId.IsEmpty() + ? SessionConfig.UsageEventTemplate.AggregationWindowId + : TEXT("current-session"); + ShellState.ExportPreviewFormatId = Profile.ExportSurfaceMode; + ShellState.bHistoryVisible = Profile.bSupportsHistoryTimeline; + ShellState.bExportPreviewVisible = Profile.bSupportsExportPreview; + ShellState.bEstimateWatermarkVisible = Profile.bSupportsEstimateWatermark; + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings) + { + ShellState.AvailableActionIds.AddUnique(ActionBinding.ActionId); + } + + if (PriorState != nullptr + && PriorState->UsageCostHistoryExportShellState.HistoryExportShellProfileId + == Profile.HistoryExportShellProfileId) + { + ShellState = PriorState->UsageCostHistoryExportShellState; + ShellState.HistoryExportShellProfileId = Profile.HistoryExportShellProfileId; + ShellState.ExportPreviewFormatId = Profile.ExportSurfaceMode; + } + + return ShellState; + } + FHyperTwistVisionCorrectionState BuildClassicCubeCorrectionState( const FHyperTwistVisionSessionConfig& SessionConfig, const FHyperTwistVisionReconstructionSession& ReconstructionSession, @@ -10124,6 +10612,16 @@ bool UHyperTwistTrainingSubsystem::OpenActiveCompanionSpeechSession(FString& Out return true; } + ActiveCompanionSpeechSessionState.SubmittedUtteranceCount = 0; + ActiveCompanionSpeechSessionState.FinalTranscriptCount = 0; + ActiveCompanionSpeechSessionState.bHasTranscriptResult = false; + ActiveCompanionSpeechSessionState.LastTranscriptResult = FHyperTwistSpeechTranscriptResult(); + ActiveCompanionSpeechSessionState.UsageCostHistoryExportShellState = + HyperTwistTrainingSubsystemInternal::BuildSpeechUsageCostHistoryExportShellState( + SessionConfig, + nullptr + ); + if (ActiveCompanionSpeechSessionState.bSessionOpen && ActiveCompanionSpeechSessionState.ActiveSessionId != SessionConfig.SessionId) { @@ -10197,6 +10695,9 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostHistoryExportState( + ActiveCompanionSpeechSessionState + ); return Result; } @@ -10368,6 +10869,12 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp HyperTwistTrainingSubsystemInternal::SynchronizeSpeechMicrophoneShellState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState( + ActiveCompanionSpeechSessionState + ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostHistoryExportState( + ActiveCompanionSpeechSessionState + ); return Result; } @@ -11114,6 +11621,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth() HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostHistoryExportState( + ActiveCompanionSpeechSessionState + ); return; } @@ -11142,6 +11652,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth() HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostHistoryExportState( + ActiveCompanionSpeechSessionState + ); } void UHyperTwistTrainingSubsystem::AppendRecognitionReplayEvent( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h index 391dc5b..701d5ea 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h @@ -2869,6 +2869,393 @@ struct FHyperTwistSpeechUsageCostDashboardShellState } }; +USTRUCT(BlueprintType) +struct FHyperTwistSpeechUsageCostHistoryExportShellProfile +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HistoryExportShellProfileId = TEXT("speech-provider-usage-cost-history-export-shell-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ShellKind = TEXT("operator-usage-cost-history-export"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HistorySurfaceMode = TEXT("rolling-usage-cost-snapshot-history"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExportSurfaceMode = TEXT("first-party-summary-preview"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RefreshActionId = TEXT("refresh-provider-usage-cost-history"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExportPreviewActionId = TEXT("preview-provider-usage-cost-export"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CopySummaryActionId = TEXT("copy-provider-usage-cost-summary"); + + 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 RetainedHistoryEntryLimit = 12; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsHistoryTimeline = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsExportPreview = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsCopySummary = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsEstimateWatermark = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bPreservesFirstPartySummaryOwnership = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray Panels; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ActionBindings; + + bool IsStructurallyValid() const + { + if (HistoryExportShellProfileId.IsEmpty() + || ShellKind.IsEmpty() + || HistorySurfaceMode.IsEmpty() + || ExportSurfaceMode.IsEmpty() + || RefreshActionId.IsEmpty() + || ExportPreviewActionId.IsEmpty() + || CopySummaryActionId.IsEmpty() + || RouteInspectActionId.IsEmpty() + || BudgetInspectActionId.IsEmpty() + || RetainedHistoryEntryLimit <= 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 FHyperTwistSpeechUsageCostHistoryEntry +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HistoryEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SourceKind = TEXT("service-health-snapshot"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SessionId = TEXT("speech-session-default"); + + 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 UsageEventId = TEXT("speech-usage-event/current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CostEventId = TEXT("speech-cost-event/current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString QuotaStateId = TEXT("speech-quota-state/current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RouteDecisionId = TEXT("speech-route-decision/current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CapturedAtUtc = TEXT("2026-05-25T00:00:00Z"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float UsageQuantity = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float EstimatedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float ReportedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float RemainingEstimatedSpendUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RemainingRequestCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RemainingAudioSeconds = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bEstimateOnly = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bProviderReceiptPosted = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bQuotaKnown = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRateLimited = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHardBlocked = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRouteReady = true; + + bool IsStructurallyValid() const + { + return !HistoryEntryId.IsEmpty() + && !SourceKind.IsEmpty() + && !SessionId.IsEmpty() + && !ProviderProfileId.IsEmpty() + && !ServiceLaneId.IsEmpty() + && !UsageEventId.IsEmpty() + && !CostEventId.IsEmpty() + && !QuotaStateId.IsEmpty() + && !RouteDecisionId.IsEmpty() + && !CapturedAtUtc.IsEmpty() + && UsageQuantity >= 0.0f + && EstimatedCostUsd >= 0.0f + && ReportedCostUsd >= 0.0f + && RemainingEstimatedSpendUsd >= 0.0f + && RemainingRequestCount >= 0 + && RemainingAudioSeconds >= 0; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistSpeechUsageCostHistoryExportIssue +{ + 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 FHyperTwistSpeechUsageCostHistoryExportShellState +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HistoryExportShellProfileId; + + 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 SelectedHistoryWindowId = TEXT("current-session"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExportPreviewFormatId = TEXT("first-party-summary-preview"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestHistoryEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestSourceKind = TEXT("service-health-snapshot"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine = TEXT("Provider usage/cost history ready for operator preview."); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine = TEXT("First-party usage/cost history is ready for bounded preview and copy surfaces."); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExportPreviewText; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 HistoryEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 EstimateOnlyEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ProviderReceiptEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float TotalUsageQuantity = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float TotalEstimatedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float TotalReportedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float LowestRemainingEstimatedSpendUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHistoryVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bExportPreviewVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bEstimateWatermarkVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bExportPreviewReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCopySummaryReady = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ActiveIssueCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechUsageCostHistoryExportIssue ActiveIssue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray Issues; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray HistoryEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray AvailableActionIds; + + FString LastRecordedSignature; + + bool IsStructurallyValid() const + { + if (HistoryExportShellProfileId.IsEmpty() + || ActiveProviderProfileId.IsEmpty() + || ActiveProviderDisplayLabel.IsEmpty() + || ActiveServiceLaneId.IsEmpty() + || SelectedHistoryWindowId.IsEmpty() + || ExportPreviewFormatId.IsEmpty() + || LatestSourceKind.IsEmpty() + || StatusLine.IsEmpty() + || DetailLine.IsEmpty() + || HistoryEntryCount < 0 + || EstimateOnlyEntryCount < 0 + || ProviderReceiptEntryCount < 0 + || TotalUsageQuantity < 0.0f + || TotalEstimatedCostUsd < 0.0f + || TotalReportedCostUsd < 0.0f + || LowestRemainingEstimatedSpendUsd < 0.0f + || ActiveIssueCount < 0 + || AvailableActionIds.Num() <= 0) + { + return false; + } + + if (HistoryEntryCount != HistoryEntries.Num()) + { + return false; + } + + if (HistoryEntryCount > 0 && LatestHistoryEntryId.IsEmpty()) + { + return false; + } + + if (bExportPreviewReady && ExportPreviewText.IsEmpty()) + { + return false; + } + + if (ActiveIssueCount != Issues.Num()) + { + return false; + } + + if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid()) + { + return false; + } + + for (const FHyperTwistSpeechUsageCostHistoryExportIssue& Issue : Issues) + { + if (!Issue.IsStructurallyValid()) + { + return false; + } + } + + for (const FHyperTwistSpeechUsageCostHistoryEntry& HistoryEntry : HistoryEntries) + { + if (!HistoryEntry.IsStructurallyValid()) + { + return false; + } + } + + for (const FString& ActionId : AvailableActionIds) + { + if (ActionId.IsEmpty()) + { + return false; + } + } + + return true; + } +}; + USTRUCT(BlueprintType) struct FHyperTwistSpeechSessionConfig { @@ -2946,6 +3333,14 @@ struct FHyperTwistSpeechSessionConfig UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechUsageCostDashboardShellProfile UsageCostDashboardShellProfileDefinition; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UsageCostHistoryExportShellProfileId = + TEXT("speech-provider-usage-cost-history-export-shell-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechUsageCostHistoryExportShellProfile + UsageCostHistoryExportShellProfileDefinition; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); @@ -2994,6 +3389,8 @@ struct FHyperTwistSpeechSessionConfig || !CostEventTemplate.IsStructurallyValid() || UsageCostDashboardShellProfileId.IsEmpty() || !UsageCostDashboardShellProfileDefinition.IsStructurallyValid() + || UsageCostHistoryExportShellProfileId.IsEmpty() + || !UsageCostHistoryExportShellProfileDefinition.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 9fdac99..7ef66f1 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -1943,6 +1943,9 @@ struct FHyperTwistTrainingCompanionSpeechSessionState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechUsageCostDashboardShellState UsageCostDashboardShellState; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechUsageCostHistoryExportShellState UsageCostHistoryExportShellState; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechTranscriptResult LastTranscriptResult; }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RABUsageCostHistoryExportShellContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RABUsageCostHistoryExportShellContractTest.cpp new file mode 100644 index 0000000..c4f22b4 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RABUsageCostHistoryExportShellContractTest.cpp @@ -0,0 +1,282 @@ +// 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 HyperTwistWhisperCppPhase6RABTestInternal +{ + FHyperTwistTrainingDeck MakeSpeechDeck() + { + FHyperTwistTrainingDeck Deck; + Deck.DeckId = TEXT("phase6r-ab/provider-usage-cost-history-export"); + Deck.Title = TEXT("Phase 6R-AB Provider Usage/Cost History Export"); + Deck.DeliveryModes = { + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + + FHyperTwistTrainingCase TrainingCase; + TrainingCase.CaseId = TEXT("phase6r-ab-case"); + TrainingCase.PuzzleId = TEXT("cube/3x3x3"); + TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence; + TrainingCase.PromptLabel = TEXT("Phase 6R-AB 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-ab-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("ready-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( + FHyperTwistWhisperCppPhase6RABUsageCostHistoryExportSessionConfigTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AB.UsageCostHistoryExportSessionConfig", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RABUsageCostHistoryExportSessionConfigTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RABTestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ab-config-session"), + TEXT("mock") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AB."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + FString OpenError; + TestTrue(TEXT("The mock history/export route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestTrue(TEXT("Opening the mock history/export speech session must not report an error."), OpenError.IsEmpty()); + + const FHyperTwistSpeechSessionConfig& Config = + TrainingSubsystem->GetActiveCompanionSpeechSessionState().SessionConfig; + + TestTrue(TEXT("The history/export session config must remain structurally valid."), Config.IsStructurallyValid()); + TestEqual(TEXT("The history/export shell profile id must be present."), Config.UsageCostHistoryExportShellProfileId, TEXT("speech-provider-usage-cost-history-export-shell-v1")); + TestTrue(TEXT("The history/export shell profile must remain structurally valid."), Config.UsageCostHistoryExportShellProfileDefinition.IsStructurallyValid()); + TestEqual(TEXT("The history/export shell must preserve the operator-facing shell kind."), Config.UsageCostHistoryExportShellProfileDefinition.ShellKind, TEXT("operator-usage-cost-history-export")); + TestEqual(TEXT("The history/export shell must preserve the export preview action id."), Config.UsageCostHistoryExportShellProfileDefinition.ExportPreviewActionId, TEXT("preview-provider-usage-cost-export")); + TestEqual(TEXT("The history/export shell must preserve the copy summary action id."), Config.UsageCostHistoryExportShellProfileDefinition.CopySummaryActionId, TEXT("copy-provider-usage-cost-summary")); + TestTrue(TEXT("The history/export shell must include the history timeline panel."), HyperTwistWhisperCppPhase6RABTestInternal::ContainsPanelId(Config.UsageCostHistoryExportShellProfileDefinition.Panels, TEXT("history-timeline-card"))); + TestTrue(TEXT("The history/export shell must include the export preview action."), HyperTwistWhisperCppPhase6RABTestInternal::ContainsActionId(Config.UsageCostHistoryExportShellProfileDefinition.ActionBindings, TEXT("preview-provider-usage-cost-export"))); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RABUsageCostHistoryExportShellStateTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AB.UsageCostHistoryExportShellState", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RABUsageCostHistoryExportShellStateTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RABTestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ab-shell-session"), + TEXT("mock") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AB."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + FString OpenError; + TestTrue(TEXT("The mock history/export route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestTrue(TEXT("Opening the mock history/export speech session must not report an error."), OpenError.IsEmpty()); + + const FHyperTwistSpeechTranscriptResult Result = + TrainingSubsystem->SubmitActiveCompanionSpeechUtterance( + HyperTwistWhisperCppPhase6RABTestInternal::MakeUtterance() + ); + TestTrue(TEXT("The mock history/export utterance must produce a final transcript."), Result.bIsFinal); + TestTrue(TEXT("The mock history/export utterance must remain structurally valid."), Result.IsStructurallyValid()); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechServiceHealth& Health = SessionState.ServiceHealth; + const FHyperTwistSpeechUsageCostHistoryExportShellState& HistoryExport = + SessionState.UsageCostHistoryExportShellState; + + TestTrue(TEXT("The service health must expose the usage/cost history shell."), Health.Capabilities.Contains(TEXT("providerUsageCostHistoryShell"))); + TestTrue(TEXT("The service health must expose the usage/cost export preview capability."), Health.Capabilities.Contains(TEXT("providerUsageCostExportPreview"))); + TestTrue(TEXT("The service health must expose the usage/cost summary copy capability."), Health.Capabilities.Contains(TEXT("providerUsageCostSummaryCopy"))); + + TestTrue(TEXT("The history/export shell state must remain structurally valid."), HistoryExport.IsStructurallyValid()); + TestEqual(TEXT("The history/export shell must preserve the mock provider profile."), HistoryExport.ActiveProviderProfileId, TEXT("speech-provider/mock-offline-profile-v1")); + TestEqual(TEXT("The history/export shell must retain exactly two bounded snapshots after open and submit."), HistoryExport.HistoryEntryCount, 2); + TestEqual(TEXT("The history/export shell must preserve the latest source kind."), HistoryExport.LatestSourceKind, TEXT("post-transcript-health")); + TestTrue(TEXT("The history/export shell must preserve export preview readiness."), HistoryExport.bExportPreviewReady); + TestTrue(TEXT("The history/export shell must preserve copy-summary readiness."), HistoryExport.bCopySummaryReady); + TestTrue(TEXT("The history/export shell must preserve the export preview action."), HistoryExport.AvailableActionIds.Contains(TEXT("preview-provider-usage-cost-export"))); + TestTrue(TEXT("The history/export shell must preserve the copy summary action."), HistoryExport.AvailableActionIds.Contains(TEXT("copy-provider-usage-cost-summary"))); + TestTrue(TEXT("The history/export shell must accumulate the mock usage quantity."), FMath::IsNearlyEqual(HistoryExport.TotalUsageQuantity, 1.50f)); + TestTrue(TEXT("The history/export shell must accumulate the mock estimated cost."), FMath::IsNearlyEqual(HistoryExport.TotalEstimatedCostUsd, 0.02f)); + TestEqual(TEXT("The history/export shell must preserve estimate-only entry count."), HistoryExport.EstimateOnlyEntryCount, 2); + TestTrue(TEXT("The history/export preview text must preserve the bounded history count."), HistoryExport.ExportPreviewText.Contains(TEXT("historyEntryCount=2"))); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RABProviderBackedHistoryExportFallbackTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AB.ProviderBackedHistoryExportFallback", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RABProviderBackedHistoryExportFallbackTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RABTestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ab-provider-session"), + TEXT("http-sidecar") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AB."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + UHyperTwistHttpSpeechClient* HttpSpeechClient = + HyperTwistWhisperCppPhase6RABTestInternal::ResolveHttpSpeechClient(TrainingSubsystem); + TestNotNull(TEXT("The provider-backed speech client must be available for the history/export fallback test."), HttpSpeechClient); + if (HttpSpeechClient == nullptr) + { + return false; + } + + HttpSpeechClient->ServiceBaseUrl.Reset(); + + FString OpenError; + TestFalse(TEXT("The provider-backed history/export path must fail cleanly when no provider endpoint is configured."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestEqual(TEXT("The provider-backed history/export path must report the missing service base URL."), OpenError, TEXT("service-base-url-missing")); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechUsageCostHistoryExportShellState& HistoryExport = + SessionState.UsageCostHistoryExportShellState; + + TestTrue(TEXT("The provider-backed history/export fallback must remain structurally valid."), HistoryExport.IsStructurallyValid()); + TestEqual(TEXT("The fallback history/export shell must preserve the sidecar profile id."), HistoryExport.ActiveProviderProfileId, TEXT("speech-provider/local-http-sidecar-profile-v1")); + TestEqual(TEXT("The fallback history/export shell must preserve the fallback source kind."), HistoryExport.LatestSourceKind, TEXT("provider-fallback-health")); + TestEqual(TEXT("The fallback history/export shell must preserve one history entry."), HistoryExport.HistoryEntryCount, 1); + TestEqual(TEXT("The fallback history/export shell must surface one active issue."), HistoryExport.ActiveIssueCount, 1); + TestEqual(TEXT("The fallback history/export shell must surface the route-degraded issue kind."), HistoryExport.ActiveIssue.IssueKind, TEXT("route-degraded")); + TestTrue(TEXT("The fallback history/export shell must still expose export preview readiness for first-party summary output."), HistoryExport.bExportPreviewReady); + TestTrue(TEXT("The fallback history/export preview text must preserve the missing-endpoint reason."), HistoryExport.ExportPreviewText.Contains(TEXT("service-base-url-missing"))); + return true; +} + +#endif diff --git a/docs/REPO_LICENSE_TRACKING.md b/docs/REPO_LICENSE_TRACKING.md index f905cbc..0e85f9b 100644 --- a/docs/REPO_LICENSE_TRACKING.md +++ b/docs/REPO_LICENSE_TRACKING.md @@ -195,8 +195,12 @@ Status update on `2026-05-21`: dashboard shell control pass is now consumed - the bounded first-party `Phase 6R-AA` operator-facing provider usage/cost dashboard shell packet is now landed in current code -- the current next bounded move is a source-backed `Phase 6R-AB` first-party provider - usage/cost history/export shell preparation/control pass, not a new restrictive packet by +- the generic source-backed `Phase 6R-AB` first-party provider usage/cost history/export shell + control pass is now consumed +- the bounded first-party `Phase 6R-AB` provider usage/cost history/export shell packet is now + landed in current code +- the current next bounded move is a source-backed `Phase 6R-AC` first-party provider receipt + review and posted-charge inspection 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 @@ -346,8 +350,8 @@ Current practical interpretation: - the landed `PostHog/posthog` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_D_POSTHOG_CONTROL_PLANE_IMPLEMENTATION_2026-05-13.md` - the landed `screenpipe/screenpipe` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_E_SCREENPIPE_CAPTURE_HISTORY_IMPLEMENTATION_2026-05-13.md` - the landed `remotion-dev/remotion` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_F_REMOTION_MEDIA_EXPORT_IMPLEMENTATION_2026-05-13.md` -- the next bounded move is a source-backed `Phase 6R-AB` first-party provider usage/cost - history/export shell preparation/control pass +- the next bounded move is a source-backed `Phase 6R-AC` first-party provider receipt review and + posted-charge inspection preparation/control pass Companion docs: diff --git a/docs/arch/HYPERTWIST_PHASE6R_AB_FIRST_PARTY_PROVIDER_USAGE_COST_HISTORY_EXPORT_SHELL_IMPLEMENTATION_PACKET_2026-05-25.md b/docs/arch/HYPERTWIST_PHASE6R_AB_FIRST_PARTY_PROVIDER_USAGE_COST_HISTORY_EXPORT_SHELL_IMPLEMENTATION_PACKET_2026-05-25.md new file mode 100644 index 0000000..cb3a380 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_AB_FIRST_PARTY_PROVIDER_USAGE_COST_HISTORY_EXPORT_SHELL_IMPLEMENTATION_PACKET_2026-05-25.md @@ -0,0 +1,163 @@ +# HyperTwist Phase 6R-AB first-party provider usage/cost history/export shell implementation packet + +Created on `2026-05-25` + +## Status + +- first-party HyperTwist packet +- bounded `Phase 6R-AB` 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, and operator-facing +usage/cost dashboard seams. + +The landed slice is: + +- first-party provider usage/cost history/export shell + +It is not: + +- a provider receipt review packet +- an actual billing settlement packet +- an invoice reconciliation packet +- an actual payload-shipping packet +- a real device-permission workflow packet +- a native audio-device route ownership packet +- a provider-specific overlay packet +- a 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_AB_FIRST_PARTY_PROVIDER_USAGE_COST_HISTORY_EXPORT_SHELL_PREPARATION_PACKET_2026-05-25.md` +- `docs/arch/HYPERTWIST_PHASE6R_AA_FIRST_PARTY_OPERATOR_FACING_PROVIDER_USAGE_COST_DASHBOARD_SHELL_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 +- `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 history/export seam through: + +- retained recognition contract types for: + - `FHyperTwistSpeechUsageCostHistoryExportShellProfile` + - `FHyperTwistSpeechUsageCostHistoryEntry` + - `FHyperTwistSpeechUsageCostHistoryExportIssue` + - `FHyperTwistSpeechUsageCostHistoryExportShellState` + - expanded `FHyperTwistSpeechSessionConfig` + - expanded `FHyperTwistTrainingCompanionSpeechSessionState` +- contract-library defaults for: + - history/export shell profile ids and definitions + - history/export panel ids and action bindings + - service-health capability exposure +- active companion session-config, service-health, and rolling history/export + shell composition in: + - `UHyperTwistTrainingSubsystem` +- direct speech-client health capability exposure in: + - `UHyperTwistHttpSpeechClient` + - `UHyperTwistMockSpeechClient` +- focused automation coverage in: + - `HyperTwistWhisperCppPhase6RABUsageCostHistoryExportShellContractTest.cpp` + +## Why this is still intentionally bounded + +This packet lands the operator-facing history/export shell family only. + +Still deferred: + +- provider receipt review and posted-charge inspection shell surfaces +- actual billing settlement or invoice reconciliation +- actual downloadable model/payload shipping +- real device-permission workflow +- native audio-device route ownership beyond bounded shell posture +- broad assistant-platform scope + +## Validation + +Build validation: + +- `UnrealHyperTwistEditor Win64 Development` + +Focused automation validation: + +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AB` `3/3` + +Regression automation validation: + +- `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-AB` 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 for: + - history/export shell profile ids and definitions + - rolling history entry and export issue/state posture + - companion-session history/export composition + - capability exposure for: + - `providerUsageCostHistoryShell` + - `providerUsageCostExportPreview` + - `providerUsageCostSummaryCopy` +- still deferred: + - provider receipt review and posted-charge inspection shell surfaces + - actual billing settlement or invoice reconciliation + - actual downloadable model/payload shipping + - real device-permission workflow + - native audio-device route ownership beyond bounded shell posture + - broad assistant-platform scope + +The next clean move is: + +- a source-backed `Phase 6R-AC` first-party provider receipt review and + posted-charge inspection 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, and history/export 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, and history/export seams rather than + widening it into receipt review or billing ownership diff --git a/docs/arch/HYPERTWIST_PHASE6R_AB_FIRST_PARTY_PROVIDER_USAGE_COST_HISTORY_EXPORT_SHELL_PREPARATION_PACKET_2026-05-25.md b/docs/arch/HYPERTWIST_PHASE6R_AB_FIRST_PARTY_PROVIDER_USAGE_COST_HISTORY_EXPORT_SHELL_PREPARATION_PACKET_2026-05-25.md new file mode 100644 index 0000000..742e03d --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_AB_FIRST_PARTY_PROVIDER_USAGE_COST_HISTORY_EXPORT_SHELL_PREPARATION_PACKET_2026-05-25.md @@ -0,0 +1,130 @@ +# HyperTwist Phase 6R-AB first-party provider usage/cost history/export shell preparation packet + +Created on `2026-05-25` + +## Status + +- first-party HyperTwist packet +- source-backed `Phase 6R-AB` 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, and operator-facing +usage/cost dashboard seams. + +The granted slice is: + +- first-party provider usage/cost history/export shell only + +It is not: + +- an actual billing export packet +- an actual settlement or invoice reconciliation packet +- a provider receipt review packet +- an actual payload-shipping packet +- a real device-permission workflow packet +- a native audio-device route ownership packet +- a provider-specific overlay packet +- a 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_AA_FIRST_PARTY_OPERATOR_FACING_PROVIDER_USAGE_COST_DASHBOARD_SHELL_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 +- `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-AB` slice may land: + +1. first-party usage/cost history/export shell profile ids and definitions +2. rolling history entry, export issue, and shell-state record shapes +3. session-config and companion-session-state expansion for those shapes +4. bounded capability exposure for: + - `providerUsageCostHistoryShell` + - `providerUsageCostExportPreview` + - `providerUsageCostSummaryCopy` +5. focused automation for mock history accumulation and provider-backed + fallback preview posture + +The packet must stay out of: + +- provider receipt review or posted-charge inspection +- actual billing settlement or invoice reconciliation +- actual payload download or redistribution ownership +- real device-permission workflow ownership +- native audio-device route ownership +- provider-specific export or billing skinning + +## Proposed implementation shape + +Land the narrower first-party boundary through: + +- retained recognition contract types for: + - usage/cost history/export shell profile + - usage/cost history entry + - usage/cost history/export issue + - usage/cost history/export shell state + - expanded speech session config + - expanded companion speech session state +- contract-library defaults for: + - history/export shell profile ids and definitions + - history/export action bindings and panel ids + - service-health capability exposure +- training-subsystem composition for: + - rolling history accumulation + - export preview text posture + - provider-backed fallback issue posture + - estimate-versus-provider-receipt summary posture +- direct speech-client health capability exposure for: + - `providerUsageCostHistoryShell` + - `providerUsageCostExportPreview` + - `providerUsageCostSummaryCopy` + +## Validation target + +Validate with: + +- Unreal build for `UnrealHyperTwistEditor Win64 Development` +- focused automation: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AB` +- regressions: + - `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-AC` first-party provider receipt review and + posted-charge inspection preparation/control pass + +Keep the narrow sequencing guards visible: + +- keep actual billing settlement and invoice reconciliation separate from + history/export shell ownership +- keep actual payload shipping separate from usage/cost governance +- keep real device-permission workflow and native capture-route ownership + outside this packet diff --git a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md index daa5ec0..abe7364 100644 --- a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md +++ b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md @@ -172,15 +172,19 @@ The next bounded move is now: dashboard shell control pass is now consumed 28. the bounded first-party `Phase 6R-AA` operator-facing provider usage/cost dashboard shell packet is now landed in current code -29. the next bounded move is a source-backed `Phase 6R-AB` first-party provider usage/cost - history/export shell preparation/control pass -30. keep the speech-lane guard visible: +29. the generic source-backed `Phase 6R-AB` first-party provider usage/cost history/export + shell control pass is now consumed +30. the bounded first-party `Phase 6R-AB` provider usage/cost history/export shell packet is + now landed in current code +31. the next bounded move is a source-backed `Phase 6R-AC` first-party provider receipt review + and posted-charge inspection preparation/control pass +32. keep the speech-lane guard visible: - keep code-license judgments separate from model, voice, and payload-license review -31. keep the provider-neutral speech-lane guard visible: +33. 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 -32. keep the `MagicTile` guard visible: +34. 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 1f3473d..d5d2099 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 @@ -216,8 +216,11 @@ The correct implementation order is: Current landed ordering status: - steps `1` through `5` now have bounded first-party implementation anchors -- usage/cost history/export shells and actual billing/settlement remain later - first-party work +- operator-facing usage/cost history/export shells now also have a bounded + first-party implementation anchor +- provider receipt review and posted-charge inspection remain the next bounded + first-party gap above the landed history/export shell +- actual billing/settlement remains later first-party work - provider-specific adapters remain later work Do not invert that order. 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 e4f4097..3fc4971 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -127,6 +127,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice. | First-party provider routing and workflow-policy boundary | Implemented now | landed first-party `Phase 6R-Y` | First-party routing-policy ids/definitions, route-decision summaries, fallback-health posture, and workflow-policy capability exposure are now live above the landed provider-custody seam. | | First-party normalized usage/cost event surface | Implemented now | landed first-party `Phase 6R-Z` | First-party accounting-profile definitions, normalized usage/cost event templates and health summaries, quota/rate-limit state, and bounded capability exposure are now live above the landed provider-routing seam. | | First-party operator-facing provider usage/cost dashboard shell | Implemented now | landed first-party `Phase 6R-AA` | First-party dashboard shell profiles, dashboard issue/state objects, quota escalation banners, route diagnostics, and operator-facing panel/action contracts are now live above the landed normalized usage/cost seam. | +| 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. | | 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. | @@ -223,6 +224,7 @@ repo. | First-party provider routing and workflow-policy boundary | Implemented now | landed first-party `Phase 6R-Y` packet | Current bounded routing-policy ids/definitions, route-decision summaries, explicit fallback-health posture, and workflow-policy capability exposure above the landed provider/session and provider-custody seams. | | First-party normalized usage/cost event surface | Implemented now | landed first-party `Phase 6R-Z` packet | Current bounded accounting-profile ids/definitions, normalized usage/cost event summaries, quota/rate-limit state, and bounded capability exposure above the landed provider-routing seam. | | First-party operator-facing provider usage/cost dashboard shell | Implemented now | landed first-party `Phase 6R-AA` packet | Current bounded dashboard shell profile ids/definitions, dashboard issue/state posture, and capability exposure above the landed normalized usage/cost seam. | +| 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. | | 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 358acf5..e9aabe0 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -171,8 +171,12 @@ Canonical discovery surfaces for roadmap interpretation: dashboard shell control pass is now consumed - the bounded first-party `Phase 6R-AA` operator-facing provider usage/cost dashboard shell packet is now landed in current code -- the current next bounded move is a source-backed `Phase 6R-AB` first-party provider - usage/cost history/export shell preparation/control pass, while keeping actual billing, +- the generic source-backed `Phase 6R-AB` first-party provider usage/cost history/export shell + control pass is now consumed +- the bounded first-party `Phase 6R-AB` provider usage/cost history/export shell packet is now + landed in current code +- the current next bounded move is a source-backed `Phase 6R-AC` first-party provider receipt + review and posted-charge inspection preparation/control pass, while keeping actual billing, settlement, real device-permission workflow, native capture-route ownership, and actual payload shipping separately deferred - the repo-row implementation queue is now live from that `Phase 6R-A` entry point rather than @@ -261,14 +265,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-AB` first-party provider -usage/cost history/export shell preparation/control pass. +The next bounded move is a source-backed `Phase 6R-AC` first-party provider +receipt review and posted-charge inspection 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 usage/cost history/export shell remainder above + - the first-party provider receipt review and posted-charge inspection shell + remainder above the partially landed `whisper.cpp` and `faster-whisper` speech seams - `HactarCE/Hyperspeedcube` is now closed for the currently justified retained row: - landed: @@ -359,6 +364,7 @@ Queue interpretation after that control pass: - first-party provider-profile and BYOK custody boundary - first-party provider routing and policy boundary - first-party normalized usage/cost event-model boundary + - first-party provider usage/cost history/export shell boundary - bounded shell capability exposure for: - microphone capture shell - device-permission shell @@ -373,8 +379,12 @@ Queue interpretation after that control pass: - normalized cost events - quota/rate-limit state - operator-facing provider usage/cost dashboard shell + - provider usage/cost history shell + - provider usage/cost export preview + - provider usage/cost summary copy - still deferred: - - provider usage/cost history/export shell surfaces + - provider receipt review and posted-charge inspection shell surfaces + - actual billing settlement or invoice reconciliation - real device-permission workflow - native capture-route ownership beyond bounded shell posture - actual downloadable model or payload shipping @@ -422,13 +432,15 @@ Queue interpretation after that control pass: - viseme / gesture runtime integration - broad assistant-platform scope - the next queue shape is now: - - first-party provider usage/cost history/export shell assessment above the landed speech - session, shell, payload-custody, provider-custody, provider-routing, normalized usage/cost, - and operator-facing dashboard seams + - first-party provider receipt review and posted-charge inspection shell + assessment above the landed speech session, shell, payload-custody, + provider-custody, provider-routing, normalized usage/cost, operator-facing + dashboard, and history/export seams - the next bounded move should stay narrow: - - a source-backed `Phase 6R-AB` control pass before any widening into actual billing - settlement, actual payload shipping, real device-permission workflow, broad voice-output - ownership, or broad assistant-platform scope + - a source-backed `Phase 6R-AC` control pass before any widening into actual + billing settlement, invoice reconciliation, actual payload shipping, real + device-permission workflow, broad voice-output ownership, or broad + assistant-platform scope - keep the speech-input / voice sidecar legal sequencing guard visible: - keep code-license judgments separate from model, voice, and payload-license review - keep the provider-neutral speech-lane guard visible: