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
|
|
@ -0,0 +1,167 @@
|
|||
using AcDream.App.Configuration;
|
||||
using AcDream.App.Credentials;
|
||||
|
||||
namespace AcDream.App.Tests.Credentials;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign LA slice LA1: <see cref="AppCredentialResolver"/> is a minimal
|
||||
/// port of <c>AcDream.Headless.Credentials.HeadlessCredentialResolver</c>
|
||||
/// scoped to the App session-config credential shape — see that file's own
|
||||
/// doc for why it is an independent port rather than a shared reference.
|
||||
/// Mirrors <c>HeadlessCredentialResolverTests</c>'s coverage.
|
||||
/// </summary>
|
||||
public sealed class AppCredentialResolverTests
|
||||
{
|
||||
[Fact]
|
||||
public void EnvironmentSecretIsRedactedAndErasable()
|
||||
{
|
||||
const string variable = "ACDREAM_LA1_TEST_ENV_SECRET";
|
||||
const string secretValue = "test-secret-value";
|
||||
Environment.SetEnvironmentVariable(variable, secretValue);
|
||||
try
|
||||
{
|
||||
var resolver = new AppCredentialResolver(
|
||||
TextReader.Null,
|
||||
Environment.CurrentDirectory,
|
||||
isLinux: false);
|
||||
|
||||
AppCredentialSecret secret = resolver.Resolve(
|
||||
"session",
|
||||
new SessionCredentialDescriptor
|
||||
{
|
||||
Provider = SessionCredentialProviderKind.Environment,
|
||||
Reference = variable,
|
||||
});
|
||||
|
||||
Assert.Equal(secretValue, secret.Reveal());
|
||||
Assert.DoesNotContain(secretValue, secret.ToString());
|
||||
secret.Dispose();
|
||||
Assert.True(secret.IsDisposed);
|
||||
Assert.Throws<ObjectDisposedException>(secret.Reveal);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable(variable, null);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StandardInputConsumesOneSecretWithoutEchoingIt()
|
||||
{
|
||||
const string secretValue = "stdin-secret";
|
||||
var resolver = new AppCredentialResolver(
|
||||
new StringReader(secretValue + Environment.NewLine),
|
||||
Environment.CurrentDirectory,
|
||||
isLinux: false);
|
||||
|
||||
using AppCredentialSecret secret = resolver.Resolve(
|
||||
"session",
|
||||
new SessionCredentialDescriptor
|
||||
{
|
||||
Provider = SessionCredentialProviderKind.StandardInput,
|
||||
Reference = "session-stdin",
|
||||
});
|
||||
|
||||
Assert.Equal(secretValue, secret.Reveal());
|
||||
Assert.DoesNotContain(secretValue, secret.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialFileIsResolvedRelativeToConfiguredDirectory()
|
||||
{
|
||||
string directory = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
$"acdream-app-credentials-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(directory);
|
||||
string path = Path.Combine(directory, "session.pass");
|
||||
File.WriteAllText(path, "file-secret" + Environment.NewLine);
|
||||
try
|
||||
{
|
||||
var resolver = new AppCredentialResolver(
|
||||
TextReader.Null,
|
||||
directory,
|
||||
isLinux: false);
|
||||
|
||||
using AppCredentialSecret secret = resolver.Resolve(
|
||||
"session",
|
||||
new SessionCredentialDescriptor
|
||||
{
|
||||
Provider = SessionCredentialProviderKind.File,
|
||||
Reference = "session.pass",
|
||||
});
|
||||
|
||||
Assert.Equal("file-secret", secret.Reveal());
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
Directory.Delete(directory);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingSecretErrorNeverContainsAnotherSecret()
|
||||
{
|
||||
const string variable = "ACDREAM_LA1_TEST_OTHER_SECRET";
|
||||
const string unrelatedSecret = "must-not-leak";
|
||||
Environment.SetEnvironmentVariable(variable, unrelatedSecret);
|
||||
try
|
||||
{
|
||||
var resolver = new AppCredentialResolver(
|
||||
new StringReader(string.Empty),
|
||||
Environment.CurrentDirectory,
|
||||
isLinux: false);
|
||||
|
||||
AppCredentialException error =
|
||||
Assert.Throws<AppCredentialException>(() =>
|
||||
resolver.Resolve(
|
||||
"session",
|
||||
new SessionCredentialDescriptor
|
||||
{
|
||||
Provider = SessionCredentialProviderKind.Environment,
|
||||
Reference = "ACDREAM_LA1_TEST_DOES_NOT_EXIST",
|
||||
}));
|
||||
|
||||
Assert.DoesNotContain(unrelatedSecret, error.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable(variable, null);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LinuxRejectsGroupOrOtherCredentialPermissions()
|
||||
{
|
||||
if (!OperatingSystem.IsLinux())
|
||||
return;
|
||||
|
||||
string path = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
$"acdream-app-credential-{Guid.NewGuid():N}");
|
||||
File.WriteAllText(path, "linux-secret");
|
||||
File.SetUnixFileMode(
|
||||
path,
|
||||
UnixFileMode.UserRead | UnixFileMode.GroupRead);
|
||||
try
|
||||
{
|
||||
var resolver = new AppCredentialResolver(
|
||||
TextReader.Null,
|
||||
Path.GetDirectoryName(path)!,
|
||||
isLinux: true);
|
||||
|
||||
Assert.Throws<AppCredentialException>(() =>
|
||||
resolver.Resolve(
|
||||
"session",
|
||||
new SessionCredentialDescriptor
|
||||
{
|
||||
Provider = SessionCredentialProviderKind.File,
|
||||
Reference = Path.GetFileName(path),
|
||||
}));
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue