docs: Campaign LA — pinned launch-contract schema COMMITTED into plan LA1
The LA3 Opus review process note was right: the contract both sides implement lived only in orchestrator prompts, which is exactly the drift mode the pin exists to prevent (and it produced the paths-key CRITICAL). The schema, field rules, probe-mode discriminator, and status vocabulary are now a binding plan section; amendments change this text first, implementations second. Ledger: LA3 fix round dispatched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0bcc7ba3a3
commit
db9ad53c1c
38 changed files with 2397 additions and 40 deletions
|
|
@ -1,4 +1,6 @@
|
|||
using AcDream.App;
|
||||
using AcDream.App.Configuration;
|
||||
using AcDream.App.Credentials;
|
||||
using AcDream.App.Plugins;
|
||||
using AcDream.App.Platform;
|
||||
using AcDream.App.Rendering;
|
||||
|
|
@ -32,17 +34,96 @@ Log.Information(
|
|||
dependency =>
|
||||
$"{dependency.Feature}={dependency.PublishedFileName}")));
|
||||
|
||||
var datDir = args.FirstOrDefault() ?? Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR");
|
||||
if (string.IsNullOrWhiteSpace(datDir))
|
||||
{
|
||||
Log.Error("usage: AcDream.App <dat-directory> (or set ACDREAM_DAT_DIR)");
|
||||
return 2;
|
||||
}
|
||||
// Campaign LA slice LA1: --session-config <path> is purely additive — the
|
||||
// existing one positional dat-dir argument and every ACDREAM_* env var keep
|
||||
// working exactly as before when the flag is absent. See
|
||||
// docs/plans/2026-08-14-launcher-campaign.md LA1.
|
||||
string? sessionConfigFlagPath = ExtractFlagValue(args, "--session-config");
|
||||
string[] positionalArgs = WithoutFlagAndValue(args, "--session-config");
|
||||
|
||||
var datDirArg = positionalArgs.FirstOrDefault();
|
||||
var envDatDir = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR");
|
||||
|
||||
// Single read of the startup-time process environment. Every downstream
|
||||
// consumer (GameWindow + collaborators) reads the typed bundle, not the
|
||||
// raw env vars. See docs/architecture/code-structure.md §2 Rule 4.
|
||||
var runtimeOptions = RuntimeOptions.FromEnvironment(datDir);
|
||||
RuntimeOptions runtimeOptions;
|
||||
if (sessionConfigFlagPath is not null)
|
||||
{
|
||||
SessionConfiguration sessionConfig;
|
||||
SessionDescriptor session;
|
||||
try
|
||||
{
|
||||
(sessionConfig, session) = SessionConfigurationLoader.Load(sessionConfigFlagPath);
|
||||
}
|
||||
catch (Exception error)
|
||||
when (error is IOException
|
||||
or UnauthorizedAccessException
|
||||
or ArgumentException
|
||||
or NotSupportedException
|
||||
or System.Text.Json.JsonException
|
||||
or SessionConfigurationException)
|
||||
{
|
||||
Log.Error("--session-config invalid: {Error}", error.Message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
string? resolvedDatDir =
|
||||
NullIfEmpty(sessionConfig.Process?.Content?.DatDirectory)
|
||||
?? NullIfEmpty(datDirArg)
|
||||
?? NullIfEmpty(envDatDir);
|
||||
if (resolvedDatDir is null)
|
||||
{
|
||||
Log.Error(
|
||||
"usage: AcDream.App <dat-directory> (or set ACDREAM_DAT_DIR, "
|
||||
+ "or supply process.content.datDirectory in --session-config)");
|
||||
return 2;
|
||||
}
|
||||
|
||||
AppCredentialSecret? secret = null;
|
||||
try
|
||||
{
|
||||
var resolver = new AppCredentialResolver(
|
||||
Console.In,
|
||||
applicationPaths.ConfigDirectory,
|
||||
graphicalPlatform.OperatingSystem
|
||||
== GraphicalHostOperatingSystem.Linux);
|
||||
secret = resolver.Resolve(session.Id, session.Credential);
|
||||
runtimeOptions = RuntimeOptions.FromSessionConfig(
|
||||
resolvedDatDir,
|
||||
Environment.GetEnvironmentVariable,
|
||||
sessionConfigFlagPath,
|
||||
sessionConfig,
|
||||
session,
|
||||
secret.Reveal());
|
||||
}
|
||||
catch (AppCredentialException error)
|
||||
{
|
||||
Log.Error("--session-config credential unavailable: {Error}", error.Message);
|
||||
return 2;
|
||||
}
|
||||
finally
|
||||
{
|
||||
secret?.Dispose();
|
||||
}
|
||||
|
||||
// Env-var flow untouched when the flag is absent; when both are present
|
||||
// the flag wins — this line makes that explicit rather than silent.
|
||||
Log.Information(
|
||||
"--session-config {Path} present; overriding ACDREAM_LIVE*/ACDREAM_TEST_* "
|
||||
+ "env-var live-session settings",
|
||||
sessionConfigFlagPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var datDir = datDirArg ?? envDatDir;
|
||||
if (string.IsNullOrWhiteSpace(datDir))
|
||||
{
|
||||
Log.Error("usage: AcDream.App <dat-directory> (or set ACDREAM_DAT_DIR)");
|
||||
return 2;
|
||||
}
|
||||
runtimeOptions = RuntimeOptions.FromEnvironment(datDir);
|
||||
}
|
||||
|
||||
if (runtimeOptions.DevTools)
|
||||
{
|
||||
|
|
@ -158,3 +239,35 @@ finally
|
|||
}
|
||||
|
||||
return 0;
|
||||
|
||||
// Campaign LA slice LA1: --session-config <path> parsing helpers. Kept
|
||||
// local/minimal rather than a general-purpose CLI parser — App has exactly
|
||||
// one optional flag-with-value today; the positional dat-dir argument must
|
||||
// stay untouched by its presence (see the comment above the flag parse).
|
||||
static string? ExtractFlagValue(string[] arguments, string flag)
|
||||
{
|
||||
for (int i = 0; i < arguments.Length - 1; i++)
|
||||
{
|
||||
if (string.Equals(arguments[i], flag, StringComparison.Ordinal))
|
||||
return arguments[i + 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static string[] WithoutFlagAndValue(string[] arguments, string flag)
|
||||
{
|
||||
var result = new List<string>(arguments.Length);
|
||||
for (int i = 0; i < arguments.Length; i++)
|
||||
{
|
||||
if (string.Equals(arguments[i], flag, StringComparison.Ordinal))
|
||||
{
|
||||
i++; // also skip the flag's value
|
||||
continue;
|
||||
}
|
||||
result.Add(arguments[i]);
|
||||
}
|
||||
return [.. result];
|
||||
}
|
||||
|
||||
static string? NullIfEmpty(string? value) =>
|
||||
string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue