partly or unimplemented features - now implemented
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
using StellaOps.AdvisoryAI.Explanation;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.WebService.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// API request for Codex/Zastava companion explanation generation.
|
||||
/// </summary>
|
||||
public sealed record CompanionExplainRequest
|
||||
{
|
||||
[Required]
|
||||
public required string FindingId { get; init; }
|
||||
|
||||
[Required]
|
||||
public required string ArtifactDigest { get; init; }
|
||||
|
||||
[Required]
|
||||
public required string Scope { get; init; }
|
||||
|
||||
[Required]
|
||||
public required string ScopeId { get; init; }
|
||||
|
||||
public string ExplanationType { get; init; } = "full";
|
||||
|
||||
[Required]
|
||||
public required string VulnerabilityId { get; init; }
|
||||
|
||||
public string? ComponentPurl { get; init; }
|
||||
|
||||
public bool PlainLanguage { get; init; }
|
||||
|
||||
public int MaxLength { get; init; }
|
||||
|
||||
public string? CorrelationId { get; init; }
|
||||
|
||||
public IReadOnlyList<CompanionRuntimeSignalRequest> RuntimeSignals { get; init; } = Array.Empty<CompanionRuntimeSignalRequest>();
|
||||
|
||||
public CodexCompanionRequest ToDomain()
|
||||
{
|
||||
if (!Enum.TryParse<ExplanationType>(ExplanationType, ignoreCase: true, out var parsedType))
|
||||
{
|
||||
parsedType = StellaOps.AdvisoryAI.Explanation.ExplanationType.Full;
|
||||
}
|
||||
|
||||
return new CodexCompanionRequest
|
||||
{
|
||||
ExplanationRequest = new ExplanationRequest
|
||||
{
|
||||
FindingId = FindingId,
|
||||
ArtifactDigest = ArtifactDigest,
|
||||
Scope = Scope,
|
||||
ScopeId = ScopeId,
|
||||
ExplanationType = parsedType,
|
||||
VulnerabilityId = VulnerabilityId,
|
||||
ComponentPurl = ComponentPurl,
|
||||
PlainLanguage = PlainLanguage,
|
||||
MaxLength = MaxLength,
|
||||
CorrelationId = CorrelationId,
|
||||
},
|
||||
RuntimeSignals = RuntimeSignals.Select(static signal => new CompanionRuntimeSignal
|
||||
{
|
||||
Source = signal.Source,
|
||||
Signal = signal.Signal,
|
||||
Value = signal.Value,
|
||||
Path = signal.Path,
|
||||
Confidence = signal.Confidence,
|
||||
}).ToArray(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runtime signal request payload.
|
||||
/// </summary>
|
||||
public sealed record CompanionRuntimeSignalRequest
|
||||
{
|
||||
[Required]
|
||||
public required string Source { get; init; }
|
||||
|
||||
[Required]
|
||||
public required string Signal { get; init; }
|
||||
|
||||
[Required]
|
||||
public required string Value { get; init; }
|
||||
|
||||
public string? Path { get; init; }
|
||||
|
||||
public double Confidence { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API response for Codex/Zastava companion explanation generation.
|
||||
/// </summary>
|
||||
public sealed record CompanionExplainResponse
|
||||
{
|
||||
public required string CompanionId { get; init; }
|
||||
public required string CompanionHash { get; init; }
|
||||
public required ExplainResponse Explanation { get; init; }
|
||||
public required ExplainSummaryResponse CompanionSummary { get; init; }
|
||||
public required IReadOnlyList<CompanionRuntimeSignalResponse> RuntimeHighlights { get; init; }
|
||||
|
||||
public static CompanionExplainResponse FromDomain(CodexCompanionResponse response)
|
||||
{
|
||||
return new CompanionExplainResponse
|
||||
{
|
||||
CompanionId = response.CompanionId,
|
||||
CompanionHash = response.CompanionHash,
|
||||
Explanation = ExplainResponse.FromDomain(response.Explanation),
|
||||
CompanionSummary = new ExplainSummaryResponse
|
||||
{
|
||||
Line1 = response.CompanionSummary.Line1,
|
||||
Line2 = response.CompanionSummary.Line2,
|
||||
Line3 = response.CompanionSummary.Line3,
|
||||
},
|
||||
RuntimeHighlights = response.RuntimeHighlights.Select(static signal => new CompanionRuntimeSignalResponse
|
||||
{
|
||||
Source = signal.Source,
|
||||
Signal = signal.Signal,
|
||||
Value = signal.Value,
|
||||
Path = signal.Path,
|
||||
Confidence = signal.Confidence,
|
||||
}).ToArray(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runtime signal response payload.
|
||||
/// </summary>
|
||||
public sealed record CompanionRuntimeSignalResponse
|
||||
{
|
||||
public required string Source { get; init; }
|
||||
public required string Signal { get; init; }
|
||||
public required string Value { get; init; }
|
||||
public string? Path { get; init; }
|
||||
public double Confidence { get; init; }
|
||||
}
|
||||
@@ -42,6 +42,7 @@ builder.Configuration
|
||||
|
||||
builder.Services.AddAdvisoryAiCore(builder.Configuration);
|
||||
builder.Services.AddAdvisoryChat(builder.Configuration);
|
||||
builder.Services.TryAddSingleton<ICodexCompanionService, CodexZastavaCompanionService>();
|
||||
|
||||
// Authorization service
|
||||
builder.Services.AddSingleton<StellaOps.AdvisoryAI.WebService.Services.IAuthorizationService, StellaOps.AdvisoryAI.WebService.Services.HeaderBasedAuthorizationService>();
|
||||
@@ -140,6 +141,9 @@ app.MapPost("/v1/advisory-ai/explain", HandleExplain)
|
||||
app.MapGet("/v1/advisory-ai/explain/{explanationId}/replay", HandleExplanationReplay)
|
||||
.RequireRateLimiting("advisory-ai");
|
||||
|
||||
app.MapPost("/v1/advisory-ai/companion/explain", HandleCompanionExplain)
|
||||
.RequireRateLimiting("advisory-ai");
|
||||
|
||||
// Remediation endpoints (SPRINT_20251226_016_AI_remedy_autopilot)
|
||||
app.MapPost("/v1/advisory-ai/remediation/plan", HandleRemediationPlan)
|
||||
.RequireRateLimiting("advisory-ai");
|
||||
@@ -383,7 +387,9 @@ static bool EnsureExplainAuthorized(HttpContext context)
|
||||
.SelectMany(value => value?.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? [])
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return allowed.Contains("advisory:run") || allowed.Contains("advisory:explain");
|
||||
return allowed.Contains("advisory:run")
|
||||
|| allowed.Contains("advisory:explain")
|
||||
|| allowed.Contains("advisory:companion");
|
||||
}
|
||||
|
||||
// ZASTAVA-13: POST /v1/advisory-ai/explain
|
||||
@@ -450,6 +456,40 @@ static async Task<IResult> HandleExplanationReplay(
|
||||
}
|
||||
}
|
||||
|
||||
// SPRINT_20260208_003: POST /v1/advisory-ai/companion/explain
|
||||
static async Task<IResult> HandleCompanionExplain(
|
||||
HttpContext httpContext,
|
||||
CompanionExplainRequest request,
|
||||
ICodexCompanionService companionService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using var activity = AdvisoryAiActivitySource.Instance.StartActivity("advisory_ai.companion_explain", ActivityKind.Server);
|
||||
activity?.SetTag("advisory.finding_id", request.FindingId);
|
||||
activity?.SetTag("advisory.vulnerability_id", request.VulnerabilityId);
|
||||
activity?.SetTag("advisory.runtime_signal_count", request.RuntimeSignals.Count);
|
||||
|
||||
if (!EnsureExplainAuthorized(httpContext))
|
||||
{
|
||||
return Results.StatusCode(StatusCodes.Status403Forbidden);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var domainRequest = request.ToDomain();
|
||||
var result = await companionService.GenerateAsync(domainRequest, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
activity?.SetTag("advisory.companion_id", result.CompanionId);
|
||||
activity?.SetTag("advisory.companion_hash", result.CompanionHash);
|
||||
activity?.SetTag("advisory.explanation_id", result.Explanation.ExplanationId);
|
||||
|
||||
return Results.Ok(CompanionExplainResponse.FromDomain(result));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
static bool EnsureRemediationAuthorized(HttpContext context)
|
||||
{
|
||||
if (!context.Request.Headers.TryGetValue("X-StellaOps-Scopes", out var scopes))
|
||||
|
||||
@@ -6,3 +6,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
| --- | --- | --- |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/AdvisoryAI/StellaOps.AdvisoryAI.WebService/StellaOps.AdvisoryAI.WebService.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
|
||||
| SPRINT_20260208_003-WEB | DONE | Companion explain endpoint/contracts for Codex/Zastava flow. |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user