feat: initialize existing development playout PCs

This commit is contained in:
2026-07-26 23:11:29 +09:00
parent 8b628908f0
commit 483b2785a0
18 changed files with 4029 additions and 74 deletions

View File

@@ -98,6 +98,113 @@ public sealed class LegacyDevelopmentLiveStartupContractTests
Assert.Contains("args.Arguments", app, StringComparison.Ordinal);
Assert.Contains("Environment.GetCommandLineArgs()", app, StringComparison.Ordinal);
Assert.Contains("ResolveLaunchArguments(", app, StringComparison.Ordinal);
Assert.Contains(
"IsDevelopmentLiveLaunchRequested = string.Equals(",
app,
StringComparison.Ordinal);
Assert.Contains(
"IsDevelopmentLiveLaunch = developmentLive.IsApplied;",
app,
StringComparison.Ordinal);
}
[Fact]
public void AppliedDevelopmentLiveUsesOnlyProtectedStartupInputs()
{
var window = File.ReadAllText(Path.Combine(ParityRoot, "MainWindow.xaml.cs"));
var playout = File.ReadAllText(Path.Combine(
ParityRoot,
"MainWindow.Playout.cs"));
Assert.Contains(
"_isDevelopmentLiveLaunch = App.IsDevelopmentLiveLaunch;",
window,
StringComparison.Ordinal);
Assert.Contains(
"_isDevelopmentLiveLaunchRequested = App.IsDevelopmentLiveLaunchRequested;",
window,
StringComparison.Ordinal);
Assert.Contains(
"? LegacyRuntimeStockCutMenuLoader.ResolveFromBaseDirectory(",
window,
StringComparison.Ordinal);
Assert.Contains(
"? null\n : _appliedOperatorSettings.ResourceDirectory;",
window.ReplaceLineEndings("\n"),
StringComparison.Ordinal);
Assert.Contains(
"DatabaseRuntime.CreateDevelopmentLiveLocalOverride()",
window,
StringComparison.Ordinal);
Assert.Contains(
"? PlayoutOptionsLoader.LoadDevelopmentLive(",
playout,
StringComparison.Ordinal);
Assert.Contains(
"IsSourceOnlyBuild ||\n _isDevelopmentLiveLaunchRequested",
playout.ReplaceLineEndings("\n"),
StringComparison.Ordinal);
}
[Fact]
public void DevelopmentLiveDatabaseFailureForcesDryRunAndSkipsAutomaticConnect()
{
var window = File.ReadAllText(Path.Combine(ParityRoot, "MainWindow.xaml.cs"))
.ReplaceLineEndings("\n");
var playout = File.ReadAllText(Path.Combine(
ParityRoot,
"MainWindow.Playout.cs"))
.ReplaceLineEndings("\n");
var blockAssignment = window.IndexOf(
"_developmentLiveStartupBlocked = true;",
StringComparison.Ordinal);
var successAssignment = window.IndexOf(
"databaseInitializationSucceeded = true;",
StringComparison.Ordinal);
var runtimeReset = window.IndexOf(
"_databaseRuntime = null;",
successAssignment,
StringComparison.Ordinal);
var pagePlanReset = window.IndexOf(
"fixedPagePlanProvider = null;",
successAssignment,
StringComparison.Ordinal);
var playoutInitialization = window.IndexOf(
"InitializePlayoutRuntime();",
StringComparison.Ordinal);
Assert.True(successAssignment >= 0);
Assert.True(runtimeReset > successAssignment);
Assert.True(pagePlanReset > successAssignment);
Assert.True(blockAssignment > runtimeReset);
Assert.True(playoutInitialization > blockAssignment);
Assert.Contains(
"var databaseInitializationSucceeded = false;",
window,
StringComparison.Ordinal);
Assert.Contains(
"if (_isDevelopmentLiveLaunch && !databaseInitializationSucceeded)\n" +
" {\n" +
" // Development Live is one fail-closed startup transaction.",
window,
StringComparison.Ordinal);
Assert.Contains(
"_isDevelopmentLiveLaunch &&\n" +
" !_developmentLiveStartupBlocked\n" +
" ? PlayoutOptionsLoader.LoadDevelopmentLive(",
playout,
StringComparison.Ordinal);
Assert.Contains(
"_isDevelopmentLiveLaunchRequested ||\n" +
" _developmentLiveStartupBlocked",
playout,
StringComparison.Ordinal);
Assert.Contains(
"if (!_developmentLiveStartupBlocked)\n" +
" {\n" +
" await ConnectPlayoutAsync(_lifetimeCancellation.Token);",
window,
StringComparison.Ordinal);
}
[Fact]

View File

@@ -333,7 +333,10 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
element.Attribute("Name")?.Value,
"ValidateLegacyRuntimeAssets",
StringComparison.Ordinal));
var sourceOnlyMessage = Assert.Single(validationTarget.Elements("Message"));
var sourceOnlyMessage = Assert.Single(
validationTarget.Elements("Message"),
message => (message.Attribute("Condition")?.Value ?? string.Empty)
.Contains("$(LegacyRuntimeAssetsMode)", StringComparison.Ordinal));
Assert.Contains(
"$(LegacyRuntimeAssetsMode)",
sourceOnlyMessage.Attribute("Condition")?.Value,
@@ -342,6 +345,21 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
"source-only DryRun",
sourceOnlyMessage.Attribute("Text")?.Value,
StringComparison.Ordinal);
Assert.Contains(
"Initialize-ExistingDevelopmentPc.ps1",
sourceOnlyMessage.Attribute("Text")?.Value,
StringComparison.Ordinal);
var fullRuntimeMessage = Assert.Single(
validationTarget.Elements("Message"),
message => (message.Attribute("Text")?.Value ?? string.Empty)
.Contains(
"Verified full legacy runtime assets are enabled",
StringComparison.Ordinal));
Assert.Contains(
"Verified full legacy runtime assets are enabled",
fullRuntimeMessage.Attribute("Text")?.Value,
StringComparison.Ordinal);
var runtimeValidationErrors = validationTarget.Elements("Error")
.Where(error => (error.Attribute("Condition")?.Value ?? string.Empty)
@@ -353,6 +371,11 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
"$(LegacyRuntimeAssetsValidationEnabled)",
error.Attribute("Condition")?.Value,
StringComparison.Ordinal));
Assert.Contains(
runtimeValidationErrors,
error => (error.Attribute("Text")?.Value ?? string.Empty).Contains(
"Initialize-ExistingDevelopmentPc.ps1",
StringComparison.Ordinal));
string[] externalContentIncludes =
[
@@ -602,6 +625,377 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
StringComparison.Ordinal);
}
[Fact]
public void ExistingDevelopmentPcInitializerUsesLocalAssetsWithoutPerformingLiveActions()
{
var initializer = File.ReadAllText(Path.Combine(
RepositoryRoot,
"scripts",
"Initialize-ExistingDevelopmentPc.ps1"));
Assert.Contains("[string] $LegacyRuntimeSourceRoot", initializer,
StringComparison.Ordinal);
Assert.Contains("[switch] $NoFolderPicker", initializer,
StringComparison.Ordinal);
Assert.Contains(
@"..\MBN_STOCK_N\MBN_STOCK_N\bin\Debug",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Test-DirectCutsAndResRootCandidate",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"-Path $siblingCandidate",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"System.Windows.Forms.FolderBrowserDialog",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"$selectedLeaf.Equals('Cuts'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"$selectedLeaf.Equals('Res'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"single common parent of Cuts and Res",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"must be on a ready local fixed drive",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"must be outside and must not overlap this repository",
initializer,
StringComparison.Ordinal);
Assert.Contains("Assert-NoReparsePointInTree", initializer,
StringComparison.Ordinal);
Assert.Contains("'New-LegacyRuntimeBundle.ps1'", initializer,
StringComparison.Ordinal);
Assert.Contains("'Initialize-LegacyRuntimeBundle.ps1'", initializer,
StringComparison.Ordinal);
Assert.Contains(
"$existingRuntime.ManifestSha256.Equals(",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Assert-InstalledRuntimeMatchesManifest",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Installed runtime bundle contains an unexpected file",
initializer,
StringComparison.Ordinal);
Assert.Contains("[switch] $ReplaceRuntimeBinding", initializer,
StringComparison.Ordinal);
Assert.Contains("Set-RuntimeBindingAtomically", initializer,
StringComparison.Ordinal);
Assert.Contains(
"[IO.File]::Replace($temporary, $destination, $backup)",
initializer,
StringComparison.Ordinal);
var shadowInstall = initializer.IndexOf(
"-RepositoryRoot $shadowRepositoryRoot",
StringComparison.Ordinal);
var bindingReplace = initializer.IndexOf(
"Set-RuntimeBindingAtomically",
shadowInstall,
StringComparison.Ordinal);
Assert.True(shadowInstall >= 0);
Assert.True(bindingReplace > shadowInstall);
Assert.Contains("[string] $DatabaseIniPath", initializer,
StringComparison.Ordinal);
Assert.Contains("[IO.Path]::IsPathRooted($DatabaseIniPath)", initializer,
StringComparison.Ordinal);
Assert.Contains("[switch] $SkipDatabaseProfile", initializer,
StringComparison.Ordinal);
Assert.Contains("[switch] $ReplaceDatabaseProfile", initializer,
StringComparison.Ordinal);
Assert.Contains(@"Res\MmoneyCoder.ini", initializer,
StringComparison.Ordinal);
Assert.Contains("$maximumDatabaseIniLength = 1MB", initializer,
StringComparison.Ordinal);
Assert.Contains("Copy-DatabaseProfileAtomically", initializer,
StringComparison.Ordinal);
Assert.Contains(
"[IO.File]::Replace($temporaryPath, $Destination, $backupPath)",
initializer,
StringComparison.Ordinal);
Assert.Contains("SetAccessRuleProtection($true, $false)", initializer,
StringComparison.Ordinal);
Assert.Contains("'Inspect-K3DRegistration.ps1'", initializer,
StringComparison.Ordinal);
Assert.Contains("Get-VerifiedK3DRegistration -Reports $registrationReports",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"[string] $registration.RegistryHive -cne 'HKLM'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"[string] $registration.RegistryView -cne 'Registry64'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"[string] $registration.CurrentUserOverrides -cne 'None'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"[string] $classReport.ThreadingModel -cne 'Apartment'",
initializer,
StringComparison.Ordinal);
Assert.DoesNotContain("Activator.CreateInstance", initializer,
StringComparison.Ordinal);
Assert.DoesNotContain("Connect(", initializer, StringComparison.Ordinal);
Assert.DoesNotContain("TAKE IN", initializer, StringComparison.Ordinal);
Assert.DoesNotContain("TAKE OUT", initializer, StringComparison.Ordinal);
Assert.Contains("[switch] $ConfigureDevelopmentLive", initializer,
StringComparison.Ordinal);
Assert.Contains("[switch] $ReplaceLiveConfig", initializer,
StringComparison.Ordinal);
Assert.Contains(
"-ConfigureDevelopmentLive requires the default Debug x64 build verification.",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Independent native and interop SHA-256 approvals are both required.",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Get-FileHash -LiteralPath $nativePath -Algorithm SHA256",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Get-FileHash -LiteralPath $interopPath -Algorithm SHA256",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"$PlayoutHost -cne '127.0.0.1'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Existing Development Live files differ from the exact requested",
initializer,
StringComparison.Ordinal);
Assert.Contains("& dotnet build $ProjectPath -c Debug '-p:Platform=x64'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"'-getProperty:LegacyRuntimeAssetsEnabled'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"unexpectedly resolved as SourceOnly",
initializer,
StringComparison.Ordinal);
Assert.Contains("Debug x64 payload count mismatch", initializer,
StringComparison.Ordinal);
Assert.Contains(
"Credential-bearing MmoneyCoder.ini entered the Debug build output.",
initializer,
StringComparison.Ordinal);
var runtimeWrite = initializer.LastIndexOf(
"Set-RuntimeBindingAtomically",
StringComparison.Ordinal);
var databaseWrite = initializer.LastIndexOf(
"Copy-DatabaseProfileAtomically",
StringComparison.Ordinal);
var build = initializer.LastIndexOf(
"Invoke-DebugBuildAndVerify",
StringComparison.Ordinal);
var cleanup = initializer.LastIndexOf(
"-Path $rollbackRoot",
StringComparison.Ordinal);
var liveWrite = initializer.LastIndexOf(
"& $liveInitializerPath @liveArguments",
StringComparison.Ordinal);
Assert.True(runtimeWrite >= 0);
Assert.True(databaseWrite > runtimeWrite);
Assert.True(build > databaseWrite);
Assert.True(cleanup > build);
Assert.True(liveWrite > cleanup);
}
[Fact]
public void ExistingDevelopmentPcInitializerRollsBackAndReissuesLiveFailClosed()
{
var initializer = File.ReadAllText(Path.Combine(
RepositoryRoot,
"scripts",
"Initialize-ExistingDevelopmentPc.ps1"));
Assert.Contains("function New-FileRollbackSnapshot", initializer,
StringComparison.Ordinal);
Assert.Contains("function Restore-FileRollbackSnapshot", initializer,
StringComparison.Ordinal);
Assert.Contains("OriginalBytes = [IO.File]::ReadAllBytes($backupPath)",
initializer,
StringComparison.Ordinal);
Assert.Contains("AccessSddl = $accessSddl", initializer,
StringComparison.Ordinal);
Assert.Contains(
"$Description rollback backup failed its immediate ACL/hash verification.",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Previous database profile backup failed immediate ACL/hash verification.",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"$Description rollback did not restore the exact bytes and ACL.",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Set-UserOnlyDirectoryAcl -Path $rollbackRoot",
initializer,
StringComparison.Ordinal);
var databaseMutation = initializer.LastIndexOf(
"$databaseMutationStarted = $true",
StringComparison.Ordinal);
var databaseWrite = initializer.LastIndexOf(
"Copy-DatabaseProfileAtomically",
StringComparison.Ordinal);
var databaseRollback = initializer.LastIndexOf(
"-Snapshot $databaseSnapshot",
StringComparison.Ordinal);
var runtimeMutation = initializer.LastIndexOf(
"$runtimeBindingMutationStarted = $true",
StringComparison.Ordinal);
var runtimeWrite = initializer.LastIndexOf(
"Set-RuntimeBindingAtomically",
StringComparison.Ordinal);
var runtimeRollback = initializer.LastIndexOf(
"-Snapshot $propsSnapshot",
StringComparison.Ordinal);
Assert.True(databaseMutation >= 0 && databaseMutation < databaseWrite);
Assert.True(runtimeMutation >= 0 && runtimeMutation < runtimeWrite);
Assert.True(databaseRollback > databaseWrite);
Assert.True(runtimeRollback > runtimeWrite);
Assert.Contains("Original failure:", initializer, StringComparison.Ordinal);
Assert.Contains("Secondary recovery failure(s):", initializer,
StringComparison.Ordinal);
Assert.Contains("function Assert-ExactObjectProperties", initializer,
StringComparison.Ordinal);
Assert.Contains(
"-Description 'Runtime bundle creation result'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"-Description 'Runtime bundle initialization result'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"-Description 'K3D registration inspection result'",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Runtime bundle creation result paths escaped or changed the exact output contract.",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Runtime bundle initialization result paths or hashes violate containment.",
initializer,
StringComparison.Ordinal);
Assert.Contains("function Get-VerifiedRuntimeManifest", initializer,
StringComparison.Ordinal);
Assert.Contains(
"duplicate/extra/case/type/format changes are rejected.",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"function Assert-DebugRuntimePayloadMatchesManifest",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Debug x64 runtime payload differs from the manifest:",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Debug x64 runtime output contains an unexpected file:",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"Debug x64 runtime output does not match the exact manifest file set.",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"-Root $targetRoot",
initializer,
StringComparison.Ordinal);
Assert.Contains("function Test-ExactUtf8Text", initializer,
StringComparison.Ordinal);
Assert.Contains(
"case changes, type coercion, reordered properties",
initializer,
StringComparison.Ordinal);
Assert.DoesNotContain("Get-JsonContractText", initializer,
StringComparison.Ordinal);
var livePreflightStart = initializer.IndexOf(
"function Get-DevelopmentLivePreflight",
StringComparison.Ordinal);
var livePreflightEnd = initializer.IndexOf(
"function Copy-DatabaseProfileAtomically",
livePreflightStart,
StringComparison.Ordinal);
Assert.True(livePreflightStart >= 0 && livePreflightEnd > livePreflightStart);
var livePreflightFunction = initializer[livePreflightStart..livePreflightEnd];
Assert.DoesNotContain("NeedsWrite = $false", livePreflightFunction,
StringComparison.Ordinal);
Assert.Contains("DevelopmentLiveReused = $false", initializer,
StringComparison.Ordinal);
var firstPersistentLiveInvalidation = initializer.IndexOf(
"# Invalidate the previous one-launch authorization before database/runtime",
StringComparison.Ordinal);
var setupDirectoryCreation = initializer.IndexOf(
"[IO.Directory]::CreateDirectory($setupParent)",
StringComparison.Ordinal);
var buildVerification = initializer.LastIndexOf(
"Invoke-DebugBuildAndVerify",
StringComparison.Ordinal);
var setupCleanup = initializer.LastIndexOf(
"-Path $rollbackRoot",
StringComparison.Ordinal);
var liveIssue = initializer.LastIndexOf(
"$liveResults = @(& $liveInitializerPath @liveArguments)",
StringComparison.Ordinal);
var liveJsonVerification = initializer.LastIndexOf(
"-ExpectedJson ([string] $livePreflight.AuthorizationJson)",
StringComparison.Ordinal);
var failClosedLiveRemoval = initializer.LastIndexOf(
"Remove-DevelopmentLiveAuthorizationFailClosed",
StringComparison.Ordinal);
Assert.True(firstPersistentLiveInvalidation >= 0);
Assert.True(firstPersistentLiveInvalidation < setupDirectoryCreation);
Assert.True(buildVerification < setupCleanup);
Assert.True(setupCleanup < liveIssue);
Assert.True(liveIssue < liveJsonVerification);
Assert.True(liveJsonVerification < failClosedLiveRemoval);
Assert.Contains(
"Development Live initializer returned unexpected success-stream output.",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"$Description failed its exact JSON/ACL verification.",
initializer,
StringComparison.Ordinal);
}
[Fact]
public void RepositoryDoesNotContainCopiedLegacyRuntimeTrees()
{