Implement Phase 6R-AF device permission workflow

This commit is contained in:
axiomlogicnexus 2026-05-26 00:17:54 +02:00
parent a7e69c7f4d
commit 8ad3dfbc37
15 changed files with 1370 additions and 36 deletions

View file

@ -903,6 +903,61 @@ namespace HyperTwistContractLibraryInternal
return Profile;
}
FHyperTwistSpeechDevicePermissionWorkflowProfile
MakeDefaultSpeechDevicePermissionWorkflowProfile(const FString& ProfileId)
{
FHyperTwistSpeechDevicePermissionWorkflowProfile Profile;
Profile.DevicePermissionWorkflowProfileId = ProfileId;
Profile.WorkflowKind = TEXT("operator-device-permission-workflow");
Profile.PermissionContractId = TEXT("device-permission-workflow-v1");
Profile.SettingsHandoffContractId = TEXT("device-permission-settings-handoff-v1");
Profile.PermissionRecheckContractId = TEXT("device-permission-recheck-v1");
Profile.PermissionRequestActionId = TEXT("request-microphone-permission");
Profile.OpenSettingsActionId = TEXT("open-microphone-privacy-settings");
Profile.PermissionRecheckActionId = TEXT("recheck-microphone-permission");
Profile.bSupportsPermissionRequest = true;
Profile.bSupportsSettingsHandoff = true;
Profile.bSupportsPermissionRecheck = 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("permission-workflow-summary-card"), TEXT("permission-workflow-summary-card"), TEXT("top-left"), true);
AddPanel(TEXT("permission-settings-handoff-card"), TEXT("permission-settings-handoff-card"), TEXT("left-stack-below-summary"), true);
AddPanel(TEXT("permission-recheck-card"), TEXT("permission-recheck-card"), TEXT("right-stack-aligned-summary"), true);
AddAction(TEXT("request-microphone-permission"), TEXT("P"), TEXT("permission-workflow-summary-card"));
AddAction(TEXT("open-microphone-privacy-settings"), TEXT("S"), TEXT("permission-settings-handoff-card"));
AddAction(TEXT("recheck-microphone-permission"), TEXT("R"), TEXT("permission-recheck-card"));
AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root"));
return Profile;
}
FHyperTwistSpeechUsageCostDashboardShellProfile MakeDefaultSpeechUsageCostDashboardShellProfile(
const FString& ProfileId
)
@ -2467,6 +2522,11 @@ FHyperTwistSpeechSessionConfig UHyperTwistContractLibrary::MakeSampleSpeechSessi
HyperTwistContractLibraryInternal::MakeDefaultCoachCommandMicrophoneShellProfile(
Config.MicrophoneShellProfileId
);
Config.DevicePermissionWorkflowProfileId = TEXT("speech-device-permission-workflow-v1");
Config.DevicePermissionWorkflowProfileDefinition =
HyperTwistContractLibraryInternal::MakeDefaultSpeechDevicePermissionWorkflowProfile(
Config.DevicePermissionWorkflowProfileId
);
Config.ProviderProfileId = TEXT("speech-provider/local-http-sidecar-profile-v1");
Config.ProviderProfileDefinition =
HyperTwistContractLibraryInternal::MakeLocalHttpSpeechProviderProfile(
@ -2644,6 +2704,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistContractLibrary::MakeMockSpeechService
TEXT("devicePermissionShell"),
TEXT("captureRouteReadinessShell"),
TEXT("captureRouteRetry"),
TEXT("devicePermissionWorkflow"),
TEXT("devicePermissionSettingsHandoff"),
TEXT("devicePermissionRecheck"),
TEXT("streamingPreview"),
TEXT("manualTranscriptCommit"),
TEXT("batchedTranscribe"),

View file

@ -324,6 +324,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal
TEXT("devicePermissionShell"),
TEXT("captureRouteReadinessShell"),
TEXT("captureRouteRetry"),
TEXT("devicePermissionWorkflow"),
TEXT("devicePermissionSettingsHandoff"),
TEXT("devicePermissionRecheck"),
TEXT("streamingPreview"),
TEXT("manualTranscriptCommit"),
TEXT("batchedTranscribe"),
@ -413,6 +416,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal
TEXT("devicePermissionShell"),
TEXT("captureRouteReadinessShell"),
TEXT("captureRouteRetry"),
TEXT("devicePermissionWorkflow"),
TEXT("devicePermissionSettingsHandoff"),
TEXT("devicePermissionRecheck"),
TEXT("streamingPreview"),
TEXT("manualTranscriptCommit"),
TEXT("batchedTranscribe"),
@ -478,6 +484,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal
Health.Capabilities.AddUnique(TEXT("devicePermissionShell"));
Health.Capabilities.AddUnique(TEXT("captureRouteReadinessShell"));
Health.Capabilities.AddUnique(TEXT("captureRouteRetry"));
Health.Capabilities.AddUnique(TEXT("devicePermissionWorkflow"));
Health.Capabilities.AddUnique(TEXT("devicePermissionSettingsHandoff"));
Health.Capabilities.AddUnique(TEXT("devicePermissionRecheck"));
Health.Capabilities.AddUnique(TEXT("streamingPreview"));
Health.Capabilities.AddUnique(TEXT("manualTranscriptCommit"));
Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1"));

View file

@ -132,6 +132,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistMockSpeechClient::GetSpeechServiceHeal
Health.Capabilities.AddUnique(TEXT("devicePermissionShell"));
Health.Capabilities.AddUnique(TEXT("captureRouteReadinessShell"));
Health.Capabilities.AddUnique(TEXT("captureRouteRetry"));
Health.Capabilities.AddUnique(TEXT("devicePermissionWorkflow"));
Health.Capabilities.AddUnique(TEXT("devicePermissionSettingsHandoff"));
Health.Capabilities.AddUnique(TEXT("devicePermissionRecheck"));
Health.Capabilities.AddUnique(TEXT("streamingPreview"));
Health.Capabilities.AddUnique(TEXT("manualTranscriptCommit"));
Health.Capabilities.AddUnique(TEXT("batchedTranscribe"));

View file

@ -2916,6 +2916,11 @@ namespace HyperTwistTrainingSubsystemInternal
const bool bSessionOpen
);
FHyperTwistSpeechDevicePermissionWorkflowState BuildSpeechDevicePermissionWorkflowState(
const FHyperTwistSpeechSessionConfig& SessionConfig,
const FHyperTwistTrainingCompanionSpeechSessionState* PriorState
);
FHyperTwistSpeechUsageCostDashboardShellState BuildSpeechUsageCostDashboardShellState(
const FHyperTwistSpeechSessionConfig& SessionConfig,
const FHyperTwistTrainingCompanionSpeechSessionState* PriorState
@ -3004,6 +3009,61 @@ namespace HyperTwistTrainingSubsystemInternal
return Profile;
}
FHyperTwistSpeechDevicePermissionWorkflowProfile
BuildDefaultSpeechDevicePermissionWorkflowProfile(const FString& ProfileId)
{
FHyperTwistSpeechDevicePermissionWorkflowProfile Profile;
Profile.DevicePermissionWorkflowProfileId = ProfileId;
Profile.WorkflowKind = TEXT("operator-device-permission-workflow");
Profile.PermissionContractId = TEXT("device-permission-workflow-v1");
Profile.SettingsHandoffContractId = TEXT("device-permission-settings-handoff-v1");
Profile.PermissionRecheckContractId = TEXT("device-permission-recheck-v1");
Profile.PermissionRequestActionId = TEXT("request-microphone-permission");
Profile.OpenSettingsActionId = TEXT("open-microphone-privacy-settings");
Profile.PermissionRecheckActionId = TEXT("recheck-microphone-permission");
Profile.bSupportsPermissionRequest = true;
Profile.bSupportsSettingsHandoff = true;
Profile.bSupportsPermissionRecheck = 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("permission-workflow-summary-card"), TEXT("permission-workflow-summary-card"), TEXT("top-left"), true);
AddPanel(TEXT("permission-settings-handoff-card"), TEXT("permission-settings-handoff-card"), TEXT("left-stack-below-summary"), true);
AddPanel(TEXT("permission-recheck-card"), TEXT("permission-recheck-card"), TEXT("right-stack-aligned-summary"), true);
AddAction(TEXT("request-microphone-permission"), TEXT("P"), TEXT("permission-workflow-summary-card"));
AddAction(TEXT("open-microphone-privacy-settings"), TEXT("S"), TEXT("permission-settings-handoff-card"));
AddAction(TEXT("recheck-microphone-permission"), TEXT("R"), TEXT("permission-recheck-card"));
AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root"));
return Profile;
}
FHyperTwistSpeechUsageCostDashboardShellProfile BuildDefaultSpeechUsageCostDashboardShellProfile(
const FString& ProfileId
)
@ -3382,6 +3442,9 @@ namespace HyperTwistTrainingSubsystemInternal
TEXT("devicePermissionShell"),
TEXT("captureRouteReadinessShell"),
TEXT("captureRouteRetry"),
TEXT("devicePermissionWorkflow"),
TEXT("devicePermissionSettingsHandoff"),
TEXT("devicePermissionRecheck"),
TEXT("streamingPreview"),
TEXT("manualTranscriptCommit"),
TEXT("batchedTranscribe"),
@ -4486,15 +4549,38 @@ namespace HyperTwistTrainingSubsystemInternal
Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionShell"));
Health.Capabilities.AddUnique(TEXT("providerExternalPortalHandoff"));
Health.Capabilities.AddUnique(TEXT("providerSettlementExceptionReview"));
Health.Capabilities.AddUnique(TEXT("devicePermissionWorkflow"));
Health.Capabilities.AddUnique(TEXT("devicePermissionSettingsHandoff"));
Health.Capabilities.AddUnique(TEXT("devicePermissionRecheck"));
}
bool IsSpeechPermissionError(const FString& ErrorText)
{
const FString NormalizedError = ErrorText.ToLower();
return NormalizedError.Contains(TEXT("permission"))
|| NormalizedError.Contains(TEXT("not-allowed"))
|| NormalizedError.Contains(TEXT("access-denied"));
}
bool IsSpeechPermissionError(const FString& ErrorText)
{
const FString NormalizedError = ErrorText.ToLower();
return NormalizedError.Contains(TEXT("permission"))
|| NormalizedError.Contains(TEXT("not-allowed"))
|| NormalizedError.Contains(TEXT("access-denied"));
}
bool IsSpeechPermissionSettingsError(const FString& ErrorText)
{
const FString NormalizedError = ErrorText.ToLower();
return IsSpeechPermissionError(ErrorText)
&& (NormalizedError.Contains(TEXT("denied"))
|| NormalizedError.Contains(TEXT("blocked"))
|| NormalizedError.Contains(TEXT("settings"))
|| NormalizedError.Contains(TEXT("privacy")));
}
bool IsSpeechPermissionRecheckError(const FString& ErrorText)
{
const FString NormalizedError = ErrorText.ToLower();
return IsSpeechPermissionError(ErrorText)
&& (NormalizedError.Contains(TEXT("recheck"))
|| NormalizedError.Contains(TEXT("retry-permission"))
|| NormalizedError.Contains(TEXT("verify-permission"))
|| NormalizedError.Contains(TEXT("pending")));
}
bool IsSpeechRouteError(const FString& ErrorText)
{
@ -4574,7 +4660,7 @@ namespace HyperTwistTrainingSubsystemInternal
Issue.IssueKind = TEXT("permission-required");
Issue.StatusLine = TEXT("Microphone permission is required before capture can start.");
Issue.DetailLine =
TEXT("Grant microphone access and reopen the shell. This packet only exposes shell posture; it does not own the OS permission workflow.");
TEXT("Use the first-party permission workflow to request access, review system settings, and recheck before reopening capture. Native OS grant execution remains outside this packet.");
Issue.RecommendedActionId = Profile.PermissionRequestActionId;
AddSpeechMicrophoneShellIssue(ShellState, Issue);
@ -4707,6 +4793,197 @@ namespace HyperTwistTrainingSubsystemInternal
);
}
void AddSpeechDevicePermissionWorkflowIssue(
FHyperTwistSpeechDevicePermissionWorkflowState& WorkflowState,
const FHyperTwistSpeechDevicePermissionWorkflowIssue& Issue
)
{
if (!Issue.IsStructurallyValid())
{
return;
}
WorkflowState.Issues.Add(Issue);
if (!WorkflowState.ActiveIssue.IsStructurallyValid())
{
WorkflowState.ActiveIssue = Issue;
}
}
void ApplySpeechDevicePermissionWorkflowPosture(
const FHyperTwistSpeechDevicePermissionWorkflowProfile& Profile,
const FHyperTwistTrainingCompanionSpeechSessionState& SessionState,
FHyperTwistSpeechDevicePermissionWorkflowState& WorkflowState
)
{
const FString PermissionError = !SessionState.LastError.IsEmpty()
? SessionState.LastError
: SessionState.ServiceHealth.LastError;
WorkflowState.PermissionStateId = SessionState.MicrophoneShellState.IsStructurallyValid()
? SessionState.MicrophoneShellState.PermissionStateId
: TEXT("permission-granted");
WorkflowState.WorkflowStateId = TEXT("permission-workflow-cleared");
WorkflowState.LatestSourceKind = TEXT("permission-state-granted");
WorkflowState.StatusLine = SessionState.bSessionOpen
? TEXT("Microphone permission already granted.")
: TEXT("Permission workflow ready when the speech shell opens.");
WorkflowState.DetailLine = SessionState.bSessionOpen
? TEXT("The first-party permission workflow is clear for the bounded speech shell.")
: TEXT("Open the companion speech session to evaluate live permission posture.");
WorkflowState.LastPermissionError = PermissionError;
WorkflowState.WorkflowEntryCount = 0;
WorkflowState.PermissionRequestEntryCount = 0;
WorkflowState.SettingsHandoffEntryCount = 0;
WorkflowState.PermissionRecheckEntryCount = 0;
WorkflowState.bPermissionGranted = true;
WorkflowState.bWorkflowSummaryReady = true;
WorkflowState.bPermissionWorkflowReady = true;
WorkflowState.bSettingsReviewRecommended = false;
WorkflowState.bPermissionRequestVisible = false;
WorkflowState.bSettingsHandoffVisible = false;
WorkflowState.bPermissionRecheckVisible = false;
WorkflowState.Entries.Reset();
WorkflowState.Issues.Reset();
WorkflowState.ActiveIssue = FHyperTwistSpeechDevicePermissionWorkflowIssue();
WorkflowState.ActiveIssueCount = 0;
if (!PermissionError.IsEmpty() && IsSpeechPermissionError(PermissionError))
{
FHyperTwistSpeechDevicePermissionWorkflowEntry Entry;
FHyperTwistSpeechDevicePermissionWorkflowIssue Issue;
if (IsSpeechPermissionSettingsError(PermissionError))
{
Entry.EntryId = TEXT("device-permission-settings-review");
Entry.SourceKind = TEXT("permission-settings-review-required");
Entry.WorkflowStateId = TEXT("permission-settings-review-required");
Entry.StatusLine =
TEXT("Microphone permission must be reviewed in system settings.");
Entry.DetailLine = FString::Printf(
TEXT("The speech lane reported '%s'. Review the system privacy or permissions screen, then recheck before reopening capture."),
*PermissionError
);
Entry.RecommendedActionId = Profile.OpenSettingsActionId;
Entry.bRequiresSettingsHandoff = true;
Entry.bRequiresPermissionRecheck = true;
Entry.bPermissionGranted = false;
Issue.IssueId = TEXT("device-permission-settings-review");
Issue.IssueKind = TEXT("permission-settings-review-required");
Issue.StatusLine = Entry.StatusLine;
Issue.DetailLine = Entry.DetailLine;
Issue.RecommendedActionId = Profile.OpenSettingsActionId;
WorkflowState.SettingsHandoffEntryCount = 1;
WorkflowState.PermissionRecheckEntryCount = 1;
WorkflowState.bSettingsReviewRecommended = true;
WorkflowState.bSettingsHandoffVisible = Profile.bSupportsSettingsHandoff;
WorkflowState.bPermissionRecheckVisible = Profile.bSupportsPermissionRecheck;
}
else if (IsSpeechPermissionRecheckError(PermissionError))
{
Entry.EntryId = TEXT("device-permission-recheck");
Entry.SourceKind = TEXT("permission-recheck-required");
Entry.WorkflowStateId = TEXT("permission-recheck-required");
Entry.StatusLine =
TEXT("Microphone permission must be rechecked before capture resumes.");
Entry.DetailLine = FString::Printf(
TEXT("The speech lane reported '%s'. Recheck permission posture before reopening the bounded shell."),
*PermissionError
);
Entry.RecommendedActionId = Profile.PermissionRecheckActionId;
Entry.bRequiresPermissionRecheck = true;
Entry.bPermissionGranted = false;
Issue.IssueId = TEXT("device-permission-recheck");
Issue.IssueKind = TEXT("permission-recheck-required");
Issue.StatusLine = Entry.StatusLine;
Issue.DetailLine = Entry.DetailLine;
Issue.RecommendedActionId = Profile.PermissionRecheckActionId;
WorkflowState.PermissionRecheckEntryCount = 1;
WorkflowState.bPermissionRecheckVisible = Profile.bSupportsPermissionRecheck;
}
else
{
Entry.EntryId = TEXT("device-permission-request");
Entry.SourceKind = TEXT("permission-request-required");
Entry.WorkflowStateId = TEXT("permission-request-required");
Entry.StatusLine =
TEXT("Microphone permission must be requested before capture starts.");
Entry.DetailLine = PermissionError.IsEmpty()
? TEXT("Use the first-party permission workflow to request microphone access before reopening capture.")
: FString::Printf(
TEXT("The speech lane reported '%s'. Request microphone access before reopening capture."),
*PermissionError
);
Entry.RecommendedActionId = Profile.PermissionRequestActionId;
Entry.bPermissionGranted = false;
Issue.IssueId = TEXT("device-permission-request");
Issue.IssueKind = TEXT("permission-request-required");
Issue.StatusLine = Entry.StatusLine;
Issue.DetailLine = Entry.DetailLine;
Issue.RecommendedActionId = Profile.PermissionRequestActionId;
WorkflowState.PermissionRequestEntryCount = 1;
WorkflowState.bPermissionRequestVisible = Profile.bSupportsPermissionRequest;
}
WorkflowState.Entries.Add(Entry);
WorkflowState.WorkflowEntryCount = WorkflowState.Entries.Num();
WorkflowState.WorkflowStateId = Entry.WorkflowStateId;
WorkflowState.PermissionStateId = TEXT("permission-required");
WorkflowState.LatestSourceKind = Entry.SourceKind;
WorkflowState.StatusLine = Entry.StatusLine;
WorkflowState.DetailLine = Entry.DetailLine;
WorkflowState.bPermissionGranted = false;
WorkflowState.bPermissionWorkflowReady = false;
AddSpeechDevicePermissionWorkflowIssue(WorkflowState, Issue);
}
WorkflowState.ActiveIssueCount = WorkflowState.Issues.Num();
}
void SynchronizeSpeechDevicePermissionWorkflowState(
FHyperTwistTrainingCompanionSpeechSessionState& SessionState
)
{
FHyperTwistSpeechSessionConfig EffectiveConfig = SessionState.SessionConfig;
if (EffectiveConfig.DevicePermissionWorkflowProfileId.IsEmpty())
{
EffectiveConfig.DevicePermissionWorkflowProfileId =
TEXT("speech-device-permission-workflow-v1");
}
const FHyperTwistSpeechDevicePermissionWorkflowProfile Profile =
EffectiveConfig.DevicePermissionWorkflowProfileDefinition.IsStructurallyValid()
? EffectiveConfig.DevicePermissionWorkflowProfileDefinition
: BuildDefaultSpeechDevicePermissionWorkflowProfile(
!EffectiveConfig.DevicePermissionWorkflowProfileId.IsEmpty()
? EffectiveConfig.DevicePermissionWorkflowProfileId
: TEXT("speech-device-permission-workflow-v1")
);
if (!SessionState.DevicePermissionWorkflowState.IsStructurallyValid())
{
SessionState.DevicePermissionWorkflowState =
BuildSpeechDevicePermissionWorkflowState(EffectiveConfig, &SessionState);
}
FHyperTwistSpeechDevicePermissionWorkflowState& WorkflowState =
SessionState.DevicePermissionWorkflowState;
WorkflowState.DevicePermissionWorkflowProfileId =
Profile.DevicePermissionWorkflowProfileId;
WorkflowState.AvailableActionIds.Reset();
for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings)
{
WorkflowState.AvailableActionIds.AddUnique(ActionBinding.ActionId);
}
ApplySpeechDevicePermissionWorkflowPosture(Profile, SessionState, WorkflowState);
}
void AddSpeechUsageCostDashboardIssue(
FHyperTwistSpeechUsageCostDashboardShellState& ShellState,
const FHyperTwistSpeechUsageCostDashboardIssue& Issue
@ -6513,6 +6790,46 @@ namespace HyperTwistTrainingSubsystemInternal
return ShellState;
}
FHyperTwistSpeechDevicePermissionWorkflowState BuildSpeechDevicePermissionWorkflowState(
const FHyperTwistSpeechSessionConfig& SessionConfig,
const FHyperTwistTrainingCompanionSpeechSessionState* PriorState
)
{
const FHyperTwistSpeechDevicePermissionWorkflowProfile Profile =
SessionConfig.DevicePermissionWorkflowProfileDefinition.IsStructurallyValid()
? SessionConfig.DevicePermissionWorkflowProfileDefinition
: BuildDefaultSpeechDevicePermissionWorkflowProfile(
!SessionConfig.DevicePermissionWorkflowProfileId.IsEmpty()
? SessionConfig.DevicePermissionWorkflowProfileId
: TEXT("speech-device-permission-workflow-v1")
);
FHyperTwistSpeechDevicePermissionWorkflowState WorkflowState;
WorkflowState.DevicePermissionWorkflowProfileId =
Profile.DevicePermissionWorkflowProfileId;
for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings)
{
WorkflowState.AvailableActionIds.AddUnique(ActionBinding.ActionId);
}
if (PriorState != nullptr
&& PriorState->DevicePermissionWorkflowState.IsStructurallyValid()
&& PriorState->DevicePermissionWorkflowState.DevicePermissionWorkflowProfileId
== Profile.DevicePermissionWorkflowProfileId)
{
WorkflowState = PriorState->DevicePermissionWorkflowState;
WorkflowState.DevicePermissionWorkflowProfileId =
Profile.DevicePermissionWorkflowProfileId;
WorkflowState.AvailableActionIds.Reset();
for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings)
{
WorkflowState.AvailableActionIds.AddUnique(ActionBinding.ActionId);
}
}
return WorkflowState;
}
FHyperTwistSpeechUsageCostDashboardShellState BuildSpeechUsageCostDashboardShellState(
const FHyperTwistSpeechSessionConfig& SessionConfig,
const FHyperTwistTrainingCompanionSpeechSessionState* PriorState
@ -8379,6 +8696,13 @@ FHyperTwistTrainingCompanionSpeechSessionState UHyperTwistTrainingSubsystem::Get
return ActiveCompanionSpeechSessionState;
}
#if WITH_AUTOMATION_TESTS
void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealthForAutomation()
{
RefreshCompanionSpeechServiceHealth();
}
#endif
TArray<FHyperTwistCoachSignal> UHyperTwistTrainingSubsystem::GetActiveCoachSignals() const
{
return ActiveCoachSignals;
@ -12140,6 +12464,11 @@ bool UHyperTwistTrainingSubsystem::OpenActiveCompanionSpeechSession(FString& Out
ActiveCompanionSpeechSessionState.FinalTranscriptCount = 0;
ActiveCompanionSpeechSessionState.bHasTranscriptResult = false;
ActiveCompanionSpeechSessionState.LastTranscriptResult = FHyperTwistSpeechTranscriptResult();
ActiveCompanionSpeechSessionState.DevicePermissionWorkflowState =
HyperTwistTrainingSubsystemInternal::BuildSpeechDevicePermissionWorkflowState(
SessionConfig,
nullptr
);
ActiveCompanionSpeechSessionState.UsageCostHistoryExportShellState =
HyperTwistTrainingSubsystemInternal::BuildSpeechUsageCostHistoryExportShellState(
SessionConfig,
@ -12231,6 +12560,9 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechMicrophoneShellState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechDevicePermissionWorkflowState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState(
ActiveCompanionSpeechSessionState
);
@ -12417,6 +12749,9 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechMicrophoneShellState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechDevicePermissionWorkflowState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState(
ActiveCompanionSpeechSessionState
);
@ -13015,6 +13350,31 @@ FHyperTwistSpeechSessionConfig UHyperTwistTrainingSubsystem::BuildActiveCompanio
SessionConfig.MicrophoneShellProfileDefinition.MicrophoneShellProfileId =
SessionConfig.MicrophoneShellProfileId;
}
if (SessionConfig.DevicePermissionWorkflowProfileId.IsEmpty()
&& SessionConfig.DevicePermissionWorkflowProfileDefinition.IsStructurallyValid())
{
SessionConfig.DevicePermissionWorkflowProfileId =
SessionConfig.DevicePermissionWorkflowProfileDefinition
.DevicePermissionWorkflowProfileId;
}
if (SessionConfig.DevicePermissionWorkflowProfileId.IsEmpty())
{
SessionConfig.DevicePermissionWorkflowProfileId =
TEXT("speech-device-permission-workflow-v1");
}
if (!SessionConfig.DevicePermissionWorkflowProfileDefinition.IsStructurallyValid())
{
SessionConfig.DevicePermissionWorkflowProfileDefinition =
HyperTwistTrainingSubsystemInternal::BuildDefaultSpeechDevicePermissionWorkflowProfile(
SessionConfig.DevicePermissionWorkflowProfileId
);
}
if (SessionConfig.DevicePermissionWorkflowProfileDefinition.DevicePermissionWorkflowProfileId
.IsEmpty())
{
SessionConfig.DevicePermissionWorkflowProfileDefinition.DevicePermissionWorkflowProfileId =
SessionConfig.DevicePermissionWorkflowProfileId;
}
SessionConfig.bEnableVad = true;
if (!SessionConfig.VadPolicy.IsStructurallyValid())
{
@ -13175,6 +13535,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth()
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechMicrophoneShellState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechDevicePermissionWorkflowState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState(
ActiveCompanionSpeechSessionState
);
@ -13215,6 +13578,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth()
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechMicrophoneShellState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechDevicePermissionWorkflowState(
ActiveCompanionSpeechSessionState
);
HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState(
ActiveCompanionSpeechSessionState
);

View file

@ -1806,6 +1806,297 @@ struct FHyperTwistSpeechMicrophoneShellState
}
};
USTRUCT(BlueprintType)
struct FHyperTwistSpeechDevicePermissionWorkflowProfile
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DevicePermissionWorkflowProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString WorkflowKind = TEXT("operator-device-permission-workflow");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PermissionContractId = TEXT("device-permission-workflow-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SettingsHandoffContractId = TEXT("device-permission-settings-handoff-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PermissionRecheckContractId = TEXT("device-permission-recheck-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PermissionRequestActionId = TEXT("request-microphone-permission");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString OpenSettingsActionId = TEXT("open-microphone-privacy-settings");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PermissionRecheckActionId = TEXT("recheck-microphone-permission");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsPermissionRequest = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsSettingsHandoff = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsPermissionRecheck = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistSpeechShellPanelLayout> Panels;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistSpeechShellActionBinding> ActionBindings;
bool IsStructurallyValid() const
{
if (DevicePermissionWorkflowProfileId.IsEmpty()
|| WorkflowKind.IsEmpty()
|| PermissionContractId.IsEmpty()
|| SettingsHandoffContractId.IsEmpty()
|| PermissionRecheckContractId.IsEmpty()
|| PermissionRequestActionId.IsEmpty()
|| OpenSettingsActionId.IsEmpty()
|| PermissionRecheckActionId.IsEmpty()
|| 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 FHyperTwistSpeechDevicePermissionWorkflowEntry
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString EntryId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceKind;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString WorkflowStateId;
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 bRequiresSettingsHandoff = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRequiresPermissionRecheck = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPermissionGranted = true;
bool IsStructurallyValid() const
{
return !EntryId.IsEmpty()
&& !SourceKind.IsEmpty()
&& !WorkflowStateId.IsEmpty()
&& !StatusLine.IsEmpty()
&& !DetailLine.IsEmpty()
&& !RecommendedActionId.IsEmpty();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistSpeechDevicePermissionWorkflowIssue
{
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 bBlocksCaptureStart = true;
bool IsStructurallyValid() const
{
return !IssueId.IsEmpty()
&& !IssueKind.IsEmpty()
&& !StatusLine.IsEmpty()
&& !DetailLine.IsEmpty()
&& !RecommendedActionId.IsEmpty();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistSpeechDevicePermissionWorkflowState
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DevicePermissionWorkflowProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString WorkflowStateId = TEXT("permission-workflow-cleared");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PermissionStateId = TEXT("permission-granted");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LatestSourceKind = TEXT("permission-state-granted");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine = TEXT("Microphone permission already granted.");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine =
TEXT("The first-party permission workflow is clear for the bounded speech shell.");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LastPermissionError;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 WorkflowEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 PermissionRequestEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SettingsHandoffEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 PermissionRecheckEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPermissionGranted = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bWorkflowSummaryReady = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPermissionWorkflowReady = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSettingsReviewRecommended = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPermissionRequestVisible = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSettingsHandoffVisible = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPermissionRecheckVisible = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ActiveIssueCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechDevicePermissionWorkflowIssue ActiveIssue;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistSpeechDevicePermissionWorkflowEntry> Entries;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistSpeechDevicePermissionWorkflowIssue> Issues;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> AvailableActionIds;
bool IsStructurallyValid() const
{
if (DevicePermissionWorkflowProfileId.IsEmpty()
|| WorkflowStateId.IsEmpty()
|| PermissionStateId.IsEmpty()
|| LatestSourceKind.IsEmpty()
|| StatusLine.IsEmpty()
|| DetailLine.IsEmpty()
|| WorkflowEntryCount < 0
|| PermissionRequestEntryCount < 0
|| SettingsHandoffEntryCount < 0
|| PermissionRecheckEntryCount < 0
|| ActiveIssueCount < 0
|| AvailableActionIds.Num() <= 0)
{
return false;
}
if (WorkflowEntryCount != Entries.Num()
|| ActiveIssueCount != Issues.Num()
|| PermissionRequestEntryCount > WorkflowEntryCount
|| SettingsHandoffEntryCount > WorkflowEntryCount
|| PermissionRecheckEntryCount > WorkflowEntryCount)
{
return false;
}
if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid())
{
return false;
}
for (const FHyperTwistSpeechDevicePermissionWorkflowEntry& Entry : Entries)
{
if (!Entry.IsStructurallyValid())
{
return false;
}
}
for (const FHyperTwistSpeechDevicePermissionWorkflowIssue& Issue : Issues)
{
if (!Issue.IsStructurallyValid())
{
return false;
}
}
for (const FString& ActionId : AvailableActionIds)
{
if (ActionId.IsEmpty())
{
return false;
}
}
return true;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistSpeechModelPayloadDescriptor
{
@ -4471,6 +4762,14 @@ struct FHyperTwistSpeechSessionConfig
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechMicrophoneShellProfile MicrophoneShellProfileDefinition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DevicePermissionWorkflowProfileId =
TEXT("speech-device-permission-workflow-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechDevicePermissionWorkflowProfile
DevicePermissionWorkflowProfileDefinition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ProviderProfileId = TEXT("speech-provider/local-http-sidecar-profile-v1");
@ -4577,6 +4876,8 @@ struct FHyperTwistSpeechSessionConfig
|| TaskKind.IsEmpty()
|| MicrophoneShellProfileId.IsEmpty()
|| !MicrophoneShellProfileDefinition.IsStructurallyValid()
|| DevicePermissionWorkflowProfileId.IsEmpty()
|| !DevicePermissionWorkflowProfileDefinition.IsStructurallyValid()
|| ProviderProfileId.IsEmpty()
|| !ProviderProfileDefinition.IsStructurallyValid()
|| ByokCustodyProfileId.IsEmpty()

View file

@ -179,6 +179,10 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Speech")
FHyperTwistTrainingCompanionSpeechSessionState GetActiveCompanionSpeechSessionState() const;
#if WITH_AUTOMATION_TESTS
void RefreshCompanionSpeechServiceHealthForAutomation();
#endif
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
TArray<FHyperTwistCoachSignal> GetActiveCoachSignals() const;

View file

@ -1940,6 +1940,9 @@ struct FHyperTwistTrainingCompanionSpeechSessionState
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechMicrophoneShellState MicrophoneShellState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechDevicePermissionWorkflowState DevicePermissionWorkflowState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistSpeechUsageCostDashboardShellState UsageCostDashboardShellState;

View file

@ -0,0 +1,267 @@
// Copyright HyperTwist, Inc. All Rights Reserved.
#include "Misc/AutomationTest.h"
#include "Engine/GameInstance.h"
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
#include "UObject/UnrealType.h"
#if WITH_AUTOMATION_TESTS
namespace HyperTwistWhisperCppPhase6RAFTestInternal
{
FHyperTwistTrainingDeck MakeSpeechDeck()
{
FHyperTwistTrainingDeck Deck;
Deck.DeckId = TEXT("phase6r-af/whispercpp-device-permission-workflow");
Deck.Title = TEXT("Phase 6R-AF Whisper Device Permission Workflow");
Deck.DeliveryModes = {
EHyperTwistTrainingDeliveryMode::CoachReviewed
};
FHyperTwistTrainingCase TrainingCase;
TrainingCase.CaseId = TEXT("phase6r-af-case");
TrainingCase.PuzzleId = TEXT("cube/3x3x3");
TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence;
TrainingCase.PromptLabel = TEXT("Phase 6R-AF 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<FStrProperty>(
UHyperTwistTrainingSubsystem::StaticClass(),
TEXT("CompanionSpeechClientKind")
))
{
SpeechClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, ClientKind);
}
}
UHyperTwistTrainingSubsystem* MakeSpeechSubsystem(const FString& SessionId, const FString& ClientKind)
{
UGameInstance* GameInstance = NewObject<UGameInstance>(GetTransientPackage());
if (GameInstance == nullptr)
{
return nullptr;
}
UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
if (TrainingSubsystem == nullptr)
{
return nullptr;
}
ForceSpeechClientKind(TrainingSubsystem, ClientKind);
const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
MakeSpeechDeck(),
TEXT("phase6r-af-user"),
SessionId,
EHyperTwistTrainingDeliveryMode::CoachReviewed
);
return RunState.IsStructurallyValid() ? TrainingSubsystem : nullptr;
}
FHyperTwistTrainingCompanionSpeechSessionState* ResolveActiveSpeechSessionState(
UHyperTwistTrainingSubsystem* TrainingSubsystem
)
{
if (TrainingSubsystem == nullptr)
{
return nullptr;
}
if (FStructProperty* SessionStateProperty = FindFProperty<FStructProperty>(
UHyperTwistTrainingSubsystem::StaticClass(),
TEXT("ActiveCompanionSpeechSessionState")
))
{
return SessionStateProperty->ContainerPtrToValuePtr<FHyperTwistTrainingCompanionSpeechSessionState>(
TrainingSubsystem
);
}
return nullptr;
}
void ForcePermissionError(UHyperTwistTrainingSubsystem* TrainingSubsystem, const FString& ErrorText)
{
if (FHyperTwistTrainingCompanionSpeechSessionState* SessionState =
ResolveActiveSpeechSessionState(TrainingSubsystem))
{
SessionState->LastError = ErrorText;
SessionState->ServiceHealth.LastError = ErrorText;
}
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistWhisperCppPhase6RAFDevicePermissionWorkflowSessionConfigTest,
"HyperTwist.Permissive.WhisperCpp.Phase6R.AF.DevicePermissionWorkflowSessionConfig",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistWhisperCppPhase6RAFDevicePermissionWorkflowSessionConfigTest::RunTest(
const FString& Parameters
)
{
UHyperTwistTrainingSubsystem* TrainingSubsystem =
HyperTwistWhisperCppPhase6RAFTestInternal::MakeSpeechSubsystem(
TEXT("phase6r-af-config-session"),
TEXT("mock")
);
TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AF."), TrainingSubsystem);
if (TrainingSubsystem == nullptr)
{
return false;
}
FString OpenError;
TestTrue(TEXT("The device-permission workflow route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
TestTrue(TEXT("Opening the speech session must not report an error."), OpenError.IsEmpty());
const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
TrainingSubsystem->GetActiveCompanionSpeechSessionState();
const FHyperTwistSpeechDevicePermissionWorkflowProfile& Profile =
SessionState.SessionConfig.DevicePermissionWorkflowProfileDefinition;
TestTrue(TEXT("The device-permission workflow route must expose a structurally valid profile."), Profile.IsStructurallyValid());
TestEqual(TEXT("The workflow profile must preserve the first-party profile id."), Profile.DevicePermissionWorkflowProfileId, TEXT("speech-device-permission-workflow-v1"));
TestEqual(TEXT("The workflow profile must preserve the workflow kind."), Profile.WorkflowKind, TEXT("operator-device-permission-workflow"));
TestEqual(TEXT("The workflow profile must preserve the permission contract id."), Profile.PermissionContractId, TEXT("device-permission-workflow-v1"));
TestEqual(TEXT("The workflow profile must preserve the settings handoff contract id."), Profile.SettingsHandoffContractId, TEXT("device-permission-settings-handoff-v1"));
TestEqual(TEXT("The workflow profile must preserve the recheck contract id."), Profile.PermissionRecheckContractId, TEXT("device-permission-recheck-v1"));
TestEqual(TEXT("The workflow profile must preserve the request action id."), Profile.PermissionRequestActionId, TEXT("request-microphone-permission"));
TestEqual(TEXT("The workflow profile must preserve the settings handoff action id."), Profile.OpenSettingsActionId, TEXT("open-microphone-privacy-settings"));
TestEqual(TEXT("The workflow profile must preserve the recheck action id."), Profile.PermissionRecheckActionId, TEXT("recheck-microphone-permission"));
TestTrue(TEXT("The speech service health must expose device-permission workflow capability."), SessionState.ServiceHealth.Capabilities.Contains(TEXT("devicePermissionWorkflow")));
TestTrue(TEXT("The speech service health must expose settings handoff capability."), SessionState.ServiceHealth.Capabilities.Contains(TEXT("devicePermissionSettingsHandoff")));
TestTrue(TEXT("The speech service health must expose permission recheck capability."), SessionState.ServiceHealth.Capabilities.Contains(TEXT("devicePermissionRecheck")));
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistWhisperCppPhase6RAFDevicePermissionWorkflowTransitionTest,
"HyperTwist.Permissive.WhisperCpp.Phase6R.AF.DevicePermissionWorkflowTransitions",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistWhisperCppPhase6RAFDevicePermissionWorkflowTransitionTest::RunTest(
const FString& Parameters
)
{
UHyperTwistTrainingSubsystem* TrainingSubsystem =
HyperTwistWhisperCppPhase6RAFTestInternal::MakeSpeechSubsystem(
TEXT("phase6r-af-transition-session"),
TEXT("mock")
);
TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AF."), TrainingSubsystem);
if (TrainingSubsystem == nullptr)
{
return false;
}
FString OpenError;
TestTrue(TEXT("The device-permission workflow route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
TestTrue(TEXT("Opening the speech session must not report an error."), OpenError.IsEmpty());
FHyperTwistTrainingCompanionSpeechSessionState SessionState =
TrainingSubsystem->GetActiveCompanionSpeechSessionState();
FHyperTwistSpeechDevicePermissionWorkflowState WorkflowState =
SessionState.DevicePermissionWorkflowState;
TestTrue(TEXT("The granted workflow state must be structurally valid."), WorkflowState.IsStructurallyValid());
TestEqual(TEXT("The granted workflow state must start cleared."), WorkflowState.WorkflowStateId, TEXT("permission-workflow-cleared"));
TestEqual(TEXT("The granted workflow state must preserve permission-granted posture."), WorkflowState.PermissionStateId, TEXT("permission-granted"));
TestTrue(TEXT("The granted workflow state must report permission granted."), WorkflowState.bPermissionGranted);
TestTrue(TEXT("The granted workflow state must report workflow ready."), WorkflowState.bPermissionWorkflowReady);
TestFalse(TEXT("The granted workflow state must keep permission request hidden."), WorkflowState.bPermissionRequestVisible);
HyperTwistWhisperCppPhase6RAFTestInternal::ForcePermissionError(
TrainingSubsystem,
TEXT("microphone-permission-required")
);
TrainingSubsystem->RefreshCompanionSpeechServiceHealthForAutomation();
SessionState = TrainingSubsystem->GetActiveCompanionSpeechSessionState();
WorkflowState = SessionState.DevicePermissionWorkflowState;
TestTrue(TEXT("The request-required workflow state must remain structurally valid."), WorkflowState.IsStructurallyValid());
TestEqual(TEXT("The request-required workflow state must preserve the request posture."), WorkflowState.WorkflowStateId, TEXT("permission-request-required"));
TestEqual(TEXT("The request-required workflow state must preserve the permission-required posture."), WorkflowState.PermissionStateId, TEXT("permission-required"));
TestFalse(TEXT("The request-required workflow state must report permission withheld."), WorkflowState.bPermissionGranted);
TestFalse(TEXT("The request-required workflow state must block workflow ready."), WorkflowState.bPermissionWorkflowReady);
TestTrue(TEXT("The request-required workflow state must surface the permission request action."), WorkflowState.bPermissionRequestVisible);
TestFalse(TEXT("The request-required workflow state must keep settings handoff hidden."), WorkflowState.bSettingsHandoffVisible);
TestFalse(TEXT("The request-required workflow state must keep permission recheck hidden."), WorkflowState.bPermissionRecheckVisible);
TestEqual(TEXT("The request-required workflow state must retain one workflow entry."), WorkflowState.WorkflowEntryCount, 1);
TestEqual(TEXT("The request-required workflow state must retain one request entry."), WorkflowState.PermissionRequestEntryCount, 1);
TestEqual(TEXT("The request-required workflow state must classify the active issue correctly."), WorkflowState.ActiveIssue.IssueKind, TEXT("permission-request-required"));
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistWhisperCppPhase6RAFDevicePermissionSettingsWorkflowTest,
"HyperTwist.Permissive.WhisperCpp.Phase6R.AF.DevicePermissionSettingsWorkflow",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistWhisperCppPhase6RAFDevicePermissionSettingsWorkflowTest::RunTest(
const FString& Parameters
)
{
UHyperTwistTrainingSubsystem* TrainingSubsystem =
HyperTwistWhisperCppPhase6RAFTestInternal::MakeSpeechSubsystem(
TEXT("phase6r-af-settings-session"),
TEXT("mock")
);
TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AF."), TrainingSubsystem);
if (TrainingSubsystem == nullptr)
{
return false;
}
FString OpenError;
TestTrue(TEXT("The settings-review workflow route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
TestTrue(TEXT("Opening the settings-review speech session must not report an error."), OpenError.IsEmpty());
HyperTwistWhisperCppPhase6RAFTestInternal::ForcePermissionError(
TrainingSubsystem,
TEXT("microphone-permission-denied-settings")
);
TrainingSubsystem->RefreshCompanionSpeechServiceHealthForAutomation();
const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
TrainingSubsystem->GetActiveCompanionSpeechSessionState();
const FHyperTwistSpeechDevicePermissionWorkflowState& WorkflowState =
SessionState.DevicePermissionWorkflowState;
TestTrue(TEXT("The settings-review workflow state must remain structurally valid."), WorkflowState.IsStructurallyValid());
TestEqual(TEXT("The settings-review workflow must preserve the settings-review posture."), WorkflowState.WorkflowStateId, TEXT("permission-settings-review-required"));
TestEqual(TEXT("The settings-review workflow must preserve permission-required posture."), WorkflowState.PermissionStateId, TEXT("permission-required"));
TestEqual(TEXT("The settings-review workflow must preserve the settings-review source kind."), WorkflowState.LatestSourceKind, TEXT("permission-settings-review-required"));
TestFalse(TEXT("The settings-review workflow must report permission withheld."), WorkflowState.bPermissionGranted);
TestFalse(TEXT("The settings-review workflow must block workflow-ready posture."), WorkflowState.bPermissionWorkflowReady);
TestTrue(TEXT("The settings-review workflow must recommend settings review."), WorkflowState.bSettingsReviewRecommended);
TestTrue(TEXT("The settings-review workflow must surface settings handoff."), WorkflowState.bSettingsHandoffVisible);
TestTrue(TEXT("The settings-review workflow must surface permission recheck."), WorkflowState.bPermissionRecheckVisible);
TestEqual(TEXT("The settings-review workflow must retain one workflow entry."), WorkflowState.WorkflowEntryCount, 1);
TestEqual(TEXT("The settings-review workflow must retain one settings handoff entry."), WorkflowState.SettingsHandoffEntryCount, 1);
TestEqual(TEXT("The settings-review workflow must retain one permission recheck entry."), WorkflowState.PermissionRecheckEntryCount, 1);
TestEqual(TEXT("The settings-review workflow must classify the active issue correctly."), WorkflowState.ActiveIssue.IssueKind, TEXT("permission-settings-review-required"));
TestTrue(TEXT("The health surface must still advertise device-permission workflow capability."), SessionState.ServiceHealth.Capabilities.Contains(TEXT("devicePermissionWorkflow")));
TestTrue(TEXT("The health surface must still advertise settings handoff capability."), SessionState.ServiceHealth.Capabilities.Contains(TEXT("devicePermissionSettingsHandoff")));
TestTrue(TEXT("The health surface must still advertise permission recheck capability."), SessionState.ServiceHealth.Capabilities.Contains(TEXT("devicePermissionRecheck")));
return true;
}
#endif

View file

@ -211,8 +211,13 @@ Status update on `2026-05-21`:
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
- the generic source-backed `Phase 6R-AF` first-party real device-permission workflow control
pass is now consumed
- the bounded first-party `Phase 6R-AF` real device-permission workflow packet is now landed in
current code
- the current next bounded move is a source-backed `Phase 6R-AG` first-party native
capture-route ownership and 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
@ -361,8 +366,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-AE` first-party provider settlement
exception and external-portal handoff preparation/control pass
- the next bounded move is a source-backed `Phase 6R-AG` first-party native capture-route
ownership and workflow preparation/control pass
Companion docs:
@ -1618,6 +1623,8 @@ Approved working posture:
profile and shell-state boundary above the existing transcript-session seam
- the next bounded permissive implementation slice is now landed as device-permission and
capture-route readiness shell posture above the existing microphone shell seam
- the next bounded first-party implementation slice is now landed as real device-permission
workflow posture above the existing permission/readiness shell seam
- the next bounded permissive implementation slice is now landed as downloadable model and
payload custody metadata above the existing transcript-session and shell seams
- the next bounded first-party implementation slice is now landed as provider-profile and BYOK
@ -1636,11 +1643,14 @@ Approved working posture:
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 next bounded first-party implementation slice is now landed as real device-permission
workflow metadata above the existing transcript-session, shell, permission/readiness,
payload-custody, provider-custody, provider-routing, usage/cost, history/export,
receipt-review, settlement, and exception/handoff seams
- the top-level provider/session contract remains first-party HyperTwist-owned and
provider-neutral; this row does not own that lane
- keep real device-permission workflow, native capture-route ownership, actual payment
execution, provider-portal ownership, actual payload shipping, and future voice-asset review
separate from the code-license judgment
- keep 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

View file

@ -0,0 +1,168 @@
# HyperTwist Phase 6R-AF first-party real device-permission workflow implementation packet
Created on `2026-05-25`
## Status
- first-party HyperTwist packet
- bounded `Phase 6R-AF` implementation slice
## Purpose
This packet lands the next bounded first-party slice above the landed speech
session, microphone-shell, permission/readiness shell, payload-custody,
provider-custody, provider-routing, normalized usage/cost event,
operator-facing usage/cost dashboard, provider usage/cost history/export,
provider receipt-review, provider billing-settlement, and provider settlement
exception seams.
The landed slice is:
- first-party real device-permission workflow
It is not:
- a native audio-device route ownership packet
- an actual OS permission-grant execution packet
- an actual payment execution packet
- a provider-portal ownership packet
- an actual payload-shipping 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_AF_FIRST_PARTY_REAL_DEVICE_PERMISSION_WORKFLOW_PREPARATION_PACKET_2026-05-25.md`
- `docs/arch/HYPERTWIST_PHASE6R_V_WHISPER_CPP_DEVICE_PERMISSION_AND_CAPTURE_ROUTE_READINESS_SHELL_IMPLEMENTATION_PACKET_2026-05-24.md`
The preserved owners do not change:
- first-party HyperTwist remains the top-level provider/session owner
- first-party HyperTwist remains the usage/cost governance owner
- first-party HyperTwist remains the real device-permission workflow 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 permission-workflow seam through:
- retained recognition contract types for:
- `FHyperTwistSpeechDevicePermissionWorkflowProfile`
- `FHyperTwistSpeechDevicePermissionWorkflowEntry`
- `FHyperTwistSpeechDevicePermissionWorkflowIssue`
- `FHyperTwistSpeechDevicePermissionWorkflowState`
- expanded `FHyperTwistSpeechSessionConfig`
- expanded `FHyperTwistTrainingCompanionSpeechSessionState`
- contract-library defaults for:
- permission-workflow profile ids and definitions
- permission panel ids and action bindings
- service-health capability exposure
- active companion session-config, service-health, and derived
permission-workflow composition in:
- `UHyperTwistTrainingSubsystem`
- direct speech-client health capability exposure in:
- `UHyperTwistHttpSpeechClient`
- `UHyperTwistMockSpeechClient`
- focused automation coverage in:
- `HyperTwistWhisperCppPhase6RAFDevicePermissionWorkflowContractTest.cpp`
## Why this is still intentionally bounded
This packet lands the real device-permission workflow family only.
Still deferred:
- native audio-device route ownership beyond bounded readiness/workflow posture
- actual OS permission-grant execution ownership
- actual payment execution or provider-portal ownership
- actual downloadable model/payload shipping
- broad assistant-platform scope
## Validation
Build validation:
- `UnrealHyperTwistEditor Win64 Development`
Focused automation validation:
- `HyperTwist.Permissive.WhisperCpp.Phase6R.AF` `3/3`
Regression automation validation:
- `HyperTwist.Permissive.WhisperCpp.Phase6R.AE` `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:/vision/health` hostname-resolution warnings in provider-backed
automation.
## Queue effect
This packet consumes the current `Phase 6R-AF` 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
- first-party real device-permission workflow boundary for:
- permission-workflow profile ids and definitions
- request / settings handoff / recheck issue posture
- bounded capability exposure for:
- `devicePermissionWorkflow`
- `devicePermissionSettingsHandoff`
- `devicePermissionRecheck`
- 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
- still deferred:
- native audio-device route ownership beyond bounded readiness/workflow
posture
- actual OS permission-grant execution ownership
- actual payment execution or provider-portal ownership
- actual downloadable model/payload shipping
- broad assistant-platform scope
The next clean move is:
- a source-backed `Phase 6R-AG` first-party native capture-route ownership and
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,
exception/handoff shell lane, and permission-workflow 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, exception/handoff, and permission-workflow seams rather than
widening it into native route ownership, payment execution, or
provider-portal ownership

View file

@ -0,0 +1,129 @@
# HyperTwist Phase 6R-AF first-party real device-permission workflow preparation packet
Created on `2026-05-25`
## Status
- first-party HyperTwist packet
- source-backed `Phase 6R-AF` preparation/control slice
## Purpose
This packet scopes the next bounded first-party slice above the landed speech
session, microphone-shell, permission/readiness shell, payload-custody,
provider-custody, provider-routing, normalized usage/cost event,
operator-facing usage/cost dashboard, provider usage/cost history/export,
provider receipt-review, provider billing-settlement, and provider settlement
exception seams.
The granted slice is:
- first-party real device-permission workflow only
It is not:
- a native audio-device route ownership packet
- an actual OS permission-grant execution packet
- an actual payment execution packet
- a provider-portal ownership packet
- an actual payload-shipping 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_V_WHISPER_CPP_DEVICE_PERMISSION_AND_CAPTURE_ROUTE_READINESS_SHELL_IMPLEMENTATION_PACKET_2026-05-24.md`
- `docs/arch/HYPERTWIST_PHASE6R_AE_FIRST_PARTY_PROVIDER_SETTLEMENT_EXCEPTION_AND_EXTERNAL_PORTAL_HANDOFF_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 real device-permission workflow 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-AF` slice may land:
1. first-party permission-workflow profile ids and definitions
2. derived permission-workflow entry, issue, and shell-state record shapes
3. session-config and companion-session-state expansion for those shapes
4. bounded capability exposure for:
- `devicePermissionWorkflow`
- `devicePermissionSettingsHandoff`
- `devicePermissionRecheck`
5. focused automation for:
- permission-request-required posture
- settings-review-required posture
- permission-recheck posture
The packet must stay out of:
- native audio-device route ownership
- actual OS permission-grant execution ownership
- actual payment execution
- provider-portal ownership
- actual payload download or redistribution ownership
## Proposed implementation shape
Land the narrower first-party boundary through:
- retained recognition contract types for:
- permission-workflow profile
- permission-workflow entry
- permission-workflow issue
- permission-workflow state
- expanded speech session config
- expanded companion speech session state
- contract-library defaults for:
- permission-workflow profile ids and definitions
- action bindings and panel ids
- service-health capability exposure
- training-subsystem composition for:
- bounded request / settings handoff / recheck posture
- explicit separation between workflow posture and native route ownership
- explicit separation between workflow posture and actual OS grant execution
- direct speech-client health capability exposure for:
- `devicePermissionWorkflow`
- `devicePermissionSettingsHandoff`
- `devicePermissionRecheck`
## Validation target
Validate with:
- Unreal build for `UnrealHyperTwistEditor Win64 Development`
- focused automation:
- `HyperTwist.Permissive.WhisperCpp.Phase6R.AF`
- regressions:
- `HyperTwist.Permissive.WhisperCpp.Phase6R.AE`
- `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-AG` first-party native capture-route ownership and
workflow preparation/control pass
Keep the narrow sequencing guards visible:
- keep native capture-route ownership separate from the landed
permission-workflow posture
- 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

View file

@ -188,11 +188,15 @@ The next bounded move is now:
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:
37. the generic source-backed `Phase 6R-AF` first-party real device-permission workflow
control pass is now consumed
38. the bounded first-party `Phase 6R-AF` real device-permission workflow packet is now
landed in current code
39. the next bounded move is a source-backed `Phase 6R-AG` first-party native
capture-route ownership and workflow preparation/control pass
40. keep the speech-lane guard visible:
- keep code-license judgments separate from model, voice, and payload-license review
39. keep the provider-neutral speech-lane guard visible:
41. 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

View file

@ -224,8 +224,10 @@ Current landed ordering status:
bounded first-party implementation anchor
- 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
- real device-permission workflow now also has a bounded first-party
implementation anchor above the landed permission/readiness shell posture
- native capture-route ownership and workflow is now the next bounded first-party
gap above the landed readiness and permission-workflow seams
- actual payment execution or provider-portal ownership remains later
first-party work
- provider-specific adapters remain later work

View file

@ -131,6 +131,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice.
| 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. |
| First-party real device-permission workflow | Implemented now | landed first-party `Phase 6R-AF` | First-party permission-workflow profiles, request/settings/recheck posture, and bounded workflow capability exposure are now live above the landed permission/readiness shell 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. |
@ -210,6 +211,7 @@ repo.
| Speech transcript session boundary | Implemented now | landed `whisper.cpp` packet | Current bounded STT truth. |
| Live microphone capture shell | Implemented now | landed `whisper.cpp` `Phase 6R-U` packet | Current bounded microphone shell truth above the landed transcript-session and orchestration seams. |
| Device-permission and capture-route readiness shell | Implemented now | landed `whisper.cpp` `Phase 6R-V` packet | Current bounded permission/readiness shell posture, route fallback issue objects, and explicit retry/request semantics above the landed microphone shell seam. |
| First-party real device-permission workflow | Implemented now | landed first-party `Phase 6R-AF` packet | Current bounded permission-workflow profile ids/definitions, request/settings/recheck posture, and workflow capability exposure are live above the landed permission/readiness shell seam. Native capture-route ownership remains deferred. |
| Downloadable model and payload custody boundary | Implemented now | landed `whisper.cpp` `Phase 6R-W` packet | Current bounded custody profile, required/supported payload descriptors, and health-surface integrity metadata above the landed transcript-session and shell seams. |
| Provider-backed speech session health and transcript envelopes | Implemented now | first-party speech client surfaces | Current first-party speech seam. |
| Python transcription-service orchestration profile | Implemented now | landed `faster-whisper` packet | Current bounded Python STT orchestration truth above the existing speech session boundary. |

View file

@ -187,10 +187,13 @@ Canonical discovery surfaces for roadmap interpretation:
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 generic source-backed `Phase 6R-AF` first-party real device-permission workflow control
pass is now consumed
- the bounded first-party `Phase 6R-AF` real device-permission workflow packet is now landed in
current code
- the current next bounded move is a source-backed `Phase 6R-AG` first-party native
capture-route ownership and workflow preparation/control pass, while keeping 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
@ -277,14 +280,14 @@ 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-AF` first-party real
device-permission workflow preparation/control pass.
The next bounded move is a source-backed `Phase 6R-AG` first-party native
capture-route ownership and 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 real device-permission workflow remainder above the
- the first-party native capture-route ownership remainder above the
partially landed `whisper.cpp` and `faster-whisper` speech seams
- `HactarCE/Hyperspeedcube` is now closed for the currently justified retained row:
- landed:
@ -455,15 +458,15 @@ Queue interpretation after that control pass:
- viseme / gesture runtime integration
- broad assistant-platform scope
- the next queue shape is now:
- 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
- first-party native capture-route ownership and workflow assessment above the
landed speech session, shell, permission/readiness, permission-workflow,
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-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
- a source-backed `Phase 6R-AG` control pass before any widening into 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
- keep the provider-neutral speech-lane guard visible: