Complete Entrypoint Detection Re-Engineering Program (Sprints 0410-0415) and Sprint 3500.0002.0003 (Proof Replay + API)

Entrypoint Detection Program (100% complete):
- Sprint 0411: Semantic Entrypoint Engine - all 25 tasks DONE
- Sprint 0412: Temporal & Mesh Entrypoint - all 19 tasks DONE
- Sprint 0413: Speculative Execution Engine - all 19 tasks DONE
- Sprint 0414: Binary Intelligence - all 19 tasks DONE
- Sprint 0415: Predictive Risk Scoring - all tasks DONE

Key deliverables:
- SemanticEntrypoint schema with ApplicationIntent/CapabilityClass
- TemporalEntrypointGraph and MeshEntrypointGraph
- ShellSymbolicExecutor with PathEnumerator and PathConfidenceScorer
- CodeFingerprint index with symbol recovery
- RiskScore with multi-dimensional risk assessment

Sprint 3500.0002.0003 (Proof Replay + API):
- ManifestEndpoints with DSSE content negotiation
- Proof bundle endpoints by root hash
- IdempotencyMiddleware with RFC 9530 Content-Digest
- Rate limiting (100 req/hr per tenant)
- OpenAPI documentation updates

Tests: 357 EntryTrace tests pass, WebService tests blocked by pre-existing infrastructure issue
This commit is contained in:
StellaOps Bot
2025-12-20 17:46:27 +02:00
parent ce8cdcd23d
commit 3698ebf4a8
46 changed files with 4156 additions and 46 deletions

View File

@@ -46,13 +46,15 @@ public interface ISymbolicExecutor
/// <param name="ConstraintEvaluator">Evaluator for path feasibility.</param>
/// <param name="TrackAllCommands">Whether to track all commands or just terminal ones.</param>
/// <param name="PruneInfeasiblePaths">Whether to prune paths with unsatisfiable constraints.</param>
/// <param name="ScriptPath">Path to the script being analyzed (for reporting).</param>
public sealed record SymbolicExecutionOptions(
int MaxDepth = 100,
int MaxPaths = 1000,
IReadOnlyDictionary<string, string>? InitialEnvironment = null,
IConstraintEvaluator? ConstraintEvaluator = null,
bool TrackAllCommands = false,
bool PruneInfeasiblePaths = true)
bool PruneInfeasiblePaths = true,
string? ScriptPath = null)
{
/// <summary>
/// Default options with reasonable limits.

View File

@@ -43,6 +43,20 @@ public sealed class PathConfidenceScorer
{
weights ??= DefaultWeights;
// Short-circuit: Infeasible paths have near-zero confidence
if (!path.IsFeasible)
{
return new PathConfidenceAnalysis(
path.PathId,
0.05f, // Near-zero confidence for infeasible paths
ImmutableArray.Create(new ConfidenceFactor(
"Feasibility",
0.0f,
1.0f,
"path is infeasible")),
ConfidenceLevel.Low);
}
var factors = new List<ConfidenceFactor>();
// Factor 1: Constraint complexity

View File

@@ -47,7 +47,10 @@ public sealed class ShellSymbolicExecutor : ISymbolicExecutor
CancellationToken cancellationToken = default)
{
var script = ShellParser.Parse(source);
return ExecuteAsync(script, options ?? SymbolicExecutionOptions.Default, cancellationToken);
var opts = options ?? SymbolicExecutionOptions.Default;
// Ensure the scriptPath is carried through to the execution tree
var optionsWithPath = opts with { ScriptPath = scriptPath };
return ExecuteAsync(script, optionsWithPath, cancellationToken);
}
/// <inheritdoc/>
@@ -56,7 +59,8 @@ public sealed class ShellSymbolicExecutor : ISymbolicExecutor
SymbolicExecutionOptions options,
CancellationToken cancellationToken = default)
{
var builder = new ExecutionTreeBuilder("script", options.MaxDepth);
var scriptPath = options.ScriptPath ?? "script";
var builder = new ExecutionTreeBuilder(scriptPath, options.MaxDepth);
var constraintEvaluator = options.ConstraintEvaluator ?? PatternConstraintEvaluator.Instance;
var initialState = options.InitialEnvironment is { } env

View File

@@ -0,0 +1,42 @@
// -----------------------------------------------------------------------------
// IdempotencyKeyRow.cs
// Sprint: SPRINT_3500_0002_0003_proof_replay_api
// Task: T3 - Idempotency Middleware
// Description: Entity for idempotency key storage
// -----------------------------------------------------------------------------
namespace StellaOps.Scanner.Storage.Entities;
/// <summary>
/// Entity mapping to scanner.idempotency_keys table.
/// Stores idempotency keys for POST endpoint deduplication.
/// </summary>
public sealed class IdempotencyKeyRow
{
/// <summary>Unique identifier for this key.</summary>
public Guid KeyId { get; set; }
/// <summary>Tenant identifier for multi-tenant isolation.</summary>
public string TenantId { get; set; } = default!;
/// <summary>RFC 9530 Content-Digest header value.</summary>
public string ContentDigest { get; set; } = default!;
/// <summary>Request path for scoping the idempotency key.</summary>
public string EndpointPath { get; set; } = default!;
/// <summary>HTTP status code of the cached response.</summary>
public int ResponseStatus { get; set; }
/// <summary>Cached response body as JSON.</summary>
public string? ResponseBody { get; set; }
/// <summary>Additional response headers to replay.</summary>
public string? ResponseHeaders { get; set; }
/// <summary>When this key was created.</summary>
public DateTimeOffset CreatedAt { get; set; }
/// <summary>When this key expires (24-hour window).</summary>
public DateTimeOffset ExpiresAt { get; set; }
}

View File

@@ -0,0 +1,50 @@
-- Migration: 017_idempotency_keys.sql
-- Sprint: SPRINT_3500_0002_0003_proof_replay_api
-- Task: T3 - Idempotency Middleware
-- Description: Creates table for idempotency key storage with 24-hour window.
-- Idempotency keys for POST endpoint deduplication
CREATE TABLE IF NOT EXISTS scanner.idempotency_keys (
key_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL,
content_digest TEXT NOT NULL, -- RFC 9530 Content-Digest header value
endpoint_path TEXT NOT NULL, -- Request path for scoping
-- Cached response
response_status INTEGER NOT NULL,
response_body JSONB,
response_headers JSONB, -- Additional headers to replay
-- Timing
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL DEFAULT (now() + interval '24 hours'),
-- Unique constraint for idempotency check
CONSTRAINT uk_idempotency_tenant_digest_path UNIQUE (tenant_id, content_digest, endpoint_path)
);
-- Index for efficient lookups by tenant and digest
CREATE INDEX IF NOT EXISTS ix_idempotency_keys_tenant_digest
ON scanner.idempotency_keys (tenant_id, content_digest);
-- Index for expiration cleanup
CREATE INDEX IF NOT EXISTS ix_idempotency_keys_expires_at
ON scanner.idempotency_keys (expires_at);
-- Automatically delete expired keys
CREATE OR REPLACE FUNCTION scanner.cleanup_expired_idempotency_keys()
RETURNS INTEGER AS $$
DECLARE
deleted_count INTEGER;
BEGIN
DELETE FROM scanner.idempotency_keys
WHERE expires_at < now();
GET DIAGNOSTICS deleted_count = ROW_COUNT;
RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;
COMMENT ON TABLE scanner.idempotency_keys IS 'Stores idempotency keys for POST endpoint deduplication with 24-hour TTL';
COMMENT ON COLUMN scanner.idempotency_keys.content_digest IS 'RFC 9530 Content-Digest header value (e.g., sha-256=:base64:)';
COMMENT ON COLUMN scanner.idempotency_keys.expires_at IS '24-hour expiration window for idempotency';

View File

@@ -0,0 +1,144 @@
// -----------------------------------------------------------------------------
// PostgresIdempotencyKeyRepository.cs
// Sprint: SPRINT_3500_0002_0003_proof_replay_api
// Task: T3 - Idempotency Middleware
// Description: PostgreSQL implementation of idempotency key repository
// -----------------------------------------------------------------------------
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Scanner.Storage.Entities;
using StellaOps.Scanner.Storage.Repositories;
namespace StellaOps.Scanner.Storage.Postgres;
/// <summary>
/// PostgreSQL implementation of <see cref="IIdempotencyKeyRepository"/>.
/// </summary>
public sealed class PostgresIdempotencyKeyRepository : IIdempotencyKeyRepository
{
private readonly NpgsqlDataSource _dataSource;
private readonly ILogger<PostgresIdempotencyKeyRepository> _logger;
public PostgresIdempotencyKeyRepository(
NpgsqlDataSource dataSource,
ILogger<PostgresIdempotencyKeyRepository> logger)
{
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
public async Task<IdempotencyKeyRow?> TryGetAsync(
string tenantId,
string contentDigest,
string endpointPath,
CancellationToken cancellationToken = default)
{
const string sql = """
SELECT key_id, tenant_id, content_digest, endpoint_path,
response_status, response_body, response_headers,
created_at, expires_at
FROM scanner.idempotency_keys
WHERE tenant_id = @tenantId
AND content_digest = @contentDigest
AND endpoint_path = @endpointPath
AND expires_at > now()
""";
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
await using var cmd = new NpgsqlCommand(sql, conn);
cmd.Parameters.AddWithValue("tenantId", tenantId);
cmd.Parameters.AddWithValue("contentDigest", contentDigest);
cmd.Parameters.AddWithValue("endpointPath", endpointPath);
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
return null;
}
return new IdempotencyKeyRow
{
KeyId = reader.GetGuid(0),
TenantId = reader.GetString(1),
ContentDigest = reader.GetString(2),
EndpointPath = reader.GetString(3),
ResponseStatus = reader.GetInt32(4),
ResponseBody = reader.IsDBNull(5) ? null : reader.GetString(5),
ResponseHeaders = reader.IsDBNull(6) ? null : reader.GetString(6),
CreatedAt = reader.GetDateTime(7),
ExpiresAt = reader.GetDateTime(8)
};
}
/// <inheritdoc />
public async Task<IdempotencyKeyRow> SaveAsync(
IdempotencyKeyRow key,
CancellationToken cancellationToken = default)
{
const string sql = """
INSERT INTO scanner.idempotency_keys
(key_id, tenant_id, content_digest, endpoint_path,
response_status, response_body, response_headers,
created_at, expires_at)
VALUES
(@keyId, @tenantId, @contentDigest, @endpointPath,
@responseStatus, @responseBody::jsonb, @responseHeaders::jsonb,
@createdAt, @expiresAt)
ON CONFLICT (tenant_id, content_digest, endpoint_path) DO UPDATE
SET response_status = EXCLUDED.response_status,
response_body = EXCLUDED.response_body,
response_headers = EXCLUDED.response_headers,
created_at = EXCLUDED.created_at,
expires_at = EXCLUDED.expires_at
RETURNING key_id
""";
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
await using var cmd = new NpgsqlCommand(sql, conn);
if (key.KeyId == Guid.Empty)
{
key.KeyId = Guid.NewGuid();
}
cmd.Parameters.AddWithValue("keyId", key.KeyId);
cmd.Parameters.AddWithValue("tenantId", key.TenantId);
cmd.Parameters.AddWithValue("contentDigest", key.ContentDigest);
cmd.Parameters.AddWithValue("endpointPath", key.EndpointPath);
cmd.Parameters.AddWithValue("responseStatus", key.ResponseStatus);
cmd.Parameters.AddWithValue("responseBody", (object?)key.ResponseBody ?? DBNull.Value);
cmd.Parameters.AddWithValue("responseHeaders", (object?)key.ResponseHeaders ?? DBNull.Value);
cmd.Parameters.AddWithValue("createdAt", key.CreatedAt);
cmd.Parameters.AddWithValue("expiresAt", key.ExpiresAt);
var keyId = await cmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
key.KeyId = (Guid)keyId!;
_logger.LogDebug(
"Saved idempotency key {KeyId} for tenant {TenantId}, digest {ContentDigest}",
key.KeyId, key.TenantId, key.ContentDigest);
return key;
}
/// <inheritdoc />
public async Task<int> DeleteExpiredAsync(CancellationToken cancellationToken = default)
{
const string sql = "SELECT scanner.cleanup_expired_idempotency_keys()";
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
await using var cmd = new NpgsqlCommand(sql, conn);
var result = await cmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
var deletedCount = Convert.ToInt32(result);
if (deletedCount > 0)
{
_logger.LogInformation("Cleaned up {Count} expired idempotency keys", deletedCount);
}
return deletedCount;
}
}

View File

@@ -0,0 +1,47 @@
// -----------------------------------------------------------------------------
// IIdempotencyKeyRepository.cs
// Sprint: SPRINT_3500_0002_0003_proof_replay_api
// Task: T3 - Idempotency Middleware
// Description: Repository interface for idempotency key operations
// -----------------------------------------------------------------------------
using StellaOps.Scanner.Storage.Entities;
namespace StellaOps.Scanner.Storage.Repositories;
/// <summary>
/// Repository interface for idempotency key operations.
/// </summary>
public interface IIdempotencyKeyRepository
{
/// <summary>
/// Tries to get an existing idempotency key.
/// </summary>
/// <param name="tenantId">Tenant identifier.</param>
/// <param name="contentDigest">RFC 9530 Content-Digest header value.</param>
/// <param name="endpointPath">Request path.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The idempotency key if found and not expired, null otherwise.</returns>
Task<IdempotencyKeyRow?> TryGetAsync(
string tenantId,
string contentDigest,
string endpointPath,
CancellationToken cancellationToken = default);
/// <summary>
/// Saves a new idempotency key.
/// </summary>
/// <param name="key">The idempotency key to save.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The saved idempotency key.</returns>
Task<IdempotencyKeyRow> SaveAsync(
IdempotencyKeyRow key,
CancellationToken cancellationToken = default);
/// <summary>
/// Deletes expired idempotency keys.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Number of deleted keys.</returns>
Task<int> DeleteExpiredAsync(CancellationToken cancellationToken = default);
}