chore: low-severity sweep — import session cap, full compile-error lists, leaf-name-fallback validation warning
#05-T24. Three low-severity cleanups, each TDD'd: - TransportOptions.MaxConcurrentImportSessions (8): BundleSessionStore.Open rejects a new session past the cap (evicting expired entries first), bounding the N×~200 MB decrypted-content footprint. Soft cap (count/add not atomic). - ScriptCompiler.TryCompile now joins ALL forbidden-API violations / compile errors (string.Join instead of [0]) so an operator sees every problem at once; the T15 verdict cache stores the joined string. - SemanticValidator emits an advisory warning when a CallScript target resolves ONLY via the composed leaf-name fallback (dynamic child path, not statically verified) instead of silently accepting it. Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -73,6 +73,26 @@ public sealed class BundleSessionStore : IBundleSessionStore
|
||||
public BundleSession Open(BundleSession session)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(session);
|
||||
// #05-T24: bound the number of concurrently-open sessions — each pins a
|
||||
// decrypted bundle (up to MaxBundleSizeMb of plaintext) in memory until
|
||||
// it expires or is applied/cancelled. Re-opening an existing id (overwrite)
|
||||
// never grows the count, so only a genuinely-new session is gated. A soft
|
||||
// cap: the count/add pair is not atomic, so a race may briefly exceed it
|
||||
// by one — acceptable for a memory-pressure guard, not a security invariant.
|
||||
if (!_sessions.ContainsKey(session.SessionId))
|
||||
{
|
||||
var cap = _options.Value.MaxConcurrentImportSessions;
|
||||
if (_sessions.Count >= cap)
|
||||
{
|
||||
// A full store may be full only of stale entries — reclaim first.
|
||||
EvictExpired();
|
||||
if (_sessions.Count >= cap)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Too many concurrent import sessions ({cap}). Complete or cancel an in-progress import before starting another.");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Overwrite on collision is defensive: GUIDs are random so practical
|
||||
// collisions don't happen, but if a caller reuses an id we always
|
||||
// honor the latest Open call.
|
||||
|
||||
@@ -28,6 +28,13 @@ public sealed class TransportOptions
|
||||
/// generous headroom for unusually compressible bundles.
|
||||
/// </summary>
|
||||
public int MaxBundleEntryCompressionRatio { get; set; } = 50;
|
||||
/// <summary>
|
||||
/// #05-T24: maximum number of concurrently-open import sessions. Each open
|
||||
/// session pins a fully-decrypted bundle (up to <see cref="MaxBundleSizeMb"/>
|
||||
/// of plaintext) in memory until it expires or is applied/cancelled, so this
|
||||
/// bounds the N×~200 MB decrypted-content footprint the central node can hold.
|
||||
/// </summary>
|
||||
public int MaxConcurrentImportSessions { get; set; } = 8;
|
||||
/// <summary>Gets or sets the maximum number of failed passphrase unlock attempts before a session is locked.</summary>
|
||||
public int MaxUnlockAttemptsPerSession { get; set; } = 3;
|
||||
/// <summary>Gets or sets the maximum number of unlock attempts allowed per IP address per hour.</summary>
|
||||
|
||||
Reference in New Issue
Block a user