acdream/src/AcDream.App/Credentials/AppCredentialSecret.cs
Erik db9ad53c1c 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>
2026-08-14 16:04:32 +02:00

65 lines
1.9 KiB
C#

using System.Security.Cryptography;
namespace AcDream.App.Credentials;
/// <summary>
/// Campaign LA slice LA1: retains a resolved <c>--session-config</c>
/// credential in erasable memory — the App-side mirror of
/// <c>AcDream.Headless.Credentials.HeadlessCredentialSecret</c> (that type is
/// internal to the Headless project, so this is a minimal, independent port
/// rather than a shared reference). The network boundary still requires one
/// short-lived immutable string; callers must not retain that value beyond
/// constructing the connect request.
/// </summary>
internal sealed class AppCredentialSecret : IDisposable
{
private char[]? _buffer;
internal AppCredentialSecret(string referenceId, ReadOnlySpan<char> value)
{
ArgumentException.ThrowIfNullOrWhiteSpace(referenceId);
if (value.IsEmpty)
{
throw new AppCredentialException(
$"Credential '{referenceId}' resolved to an empty secret.");
}
ReferenceId = referenceId;
_buffer = value.ToArray();
}
internal string ReferenceId { get; }
internal bool IsDisposed => _buffer is null;
internal string Reveal()
{
ObjectDisposedException.ThrowIf(_buffer is null, this);
return new string(_buffer);
}
public void Dispose()
{
char[]? buffer = Interlocked.Exchange(ref _buffer, null);
if (buffer is null)
return;
CryptographicOperations.ZeroMemory(
System.Runtime.InteropServices.MemoryMarshal.AsBytes(
buffer.AsSpan()));
}
public override string ToString() =>
$"[redacted:{ReferenceId}]";
}
internal sealed class AppCredentialException : Exception
{
internal AppCredentialException(string message)
: base(message)
{
}
internal AppCredentialException(string message, Exception innerException)
: base(message, innerException)
{
}
}