sqlinsight-pro/src/SQLInsight.Agent/Hub/SubmissionBuilder.cs
Claude bfb27778d2
Some checks are pending
CI / Build og unit tests (push) Waiting to run
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
Fase 3: findings sync from agent to hub (queue, retry, data minimization)
- Evidence can be marked sensitive (query text, logins, hosts, programs,
  job error messages); rules mark it. Sensitive values and the server
  name are sent as [udeladt] unless the customer allows them.
- Full-state submission contract (all active findings and rule outcomes
  per instance), idempotent by submission id
- Local outbound queue in the repository (migration 0002): newest state
  per instance only, exponential backoff up to an hour, permanent
  rejections (bad license, bad request) are given up, sent items purged
- Hub status (license state, last sent, last error) for the web UI
- Sync runs only with a valid license (or in grace); monitoring never
  depends on it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
2026-09-29 08:35:22 +00:00

56 lines
2.3 KiB
C#

using SQLInsight.Contracts;
using SQLInsight.Core.Instances;
using SQLInsight.Repository;
using SQLInsight.Rules.Engine;
namespace SQLInsight.Agent.Hub;
/// <summary>
/// Turns an instance's current state into a hub submission, leaving out what the customer has not allowed.
/// </summary>
public static class SubmissionBuilder
{
public static readonly string AgentVersion =
typeof(SubmissionBuilder).Assembly.GetName().Version?.ToString() ?? "0.0.0";
public static FindingsSubmission Build(
string instanceName, InstanceInfo info, IReadOnlyList<RuleResult> results, IReadOnlyList<StoredFinding> activeFindings,
HubOptions options, DateTimeOffset now)
{
ArgumentNullException.ThrowIfNull(info);
ArgumentNullException.ThrowIfNull(results);
ArgumentNullException.ThrowIfNull(activeFindings);
ArgumentNullException.ThrowIfNull(options);
return new FindingsSubmission
{
SubmissionId = Guid.NewGuid(),
AgentVersion = AgentVersion,
CollectedAt = now,
Instance = new InstanceSnapshot
{
InstanceKey = instanceName,
Release = info.Release.DisplayName(),
ProductVersion = info.Version.ToString(),
ProductLevel = info.ProductLevel,
ProductUpdateLevel = info.ProductUpdateLevel,
Edition = info.Edition,
ServerName = options.SendServerName ? info.ServerName : null,
},
Rules = results.Select(r => new RuleOutcomeDto { RuleId = r.RuleId, Outcome = r.Outcome.ToString(), Reason = r.Reason }).ToList(),
Findings = activeFindings.Select(f => new FindingDto
{
RuleId = f.RuleId,
Subject = f.Subject,
Severity = f.Severity.ToString(),
Title = f.Title,
Explanation = f.Explanation,
Recommendation = f.Recommendation,
FirstSeenAt = new DateTimeOffset(DateTime.SpecifyKind(f.FirstSeenAt, DateTimeKind.Utc)),
Evidence = f.Evidence
.Select(e => new EvidenceDto(e.Name, e.Sensitive && !options.SendSensitiveEvidence ? FindingsSubmission.Redacted : e.Value))
.ToList(),
}).ToList(),
};
}
}