using System.Text.Json;
using CareFix.Core;
namespace CareFix.Agent;
///
/// Runs one job locally. The agent does its own checks and does not trust the server blindly:
/// only the seven known job kinds are accepted, every query must pass the same SELECT-only parser,
/// limits come from the hospital's own config, and writes can only go through the guarded procedure.
///
public sealed class JobRunner(AgentConfig cfg, ILogger log)
{
private readonly SqlGuard _guard = new();
private readonly SqlOps _ops = new(
SqlOps.BuildConnectionString(cfg.SqlServer, cfg.SqlPort, cfg.Database, cfg.ReadUser, cfg.ReadPassword, cfg.Encrypt, cfg.TrustServerCertificate, "CareFix-Agent-Read"),
SqlOps.BuildConnectionString(cfg.SqlServer, cfg.SqlPort, cfg.Database, cfg.WriteUser, cfg.WritePassword, cfg.Encrypt, cfg.TrustServerCertificate, "CareFix-Agent-Write"),
new SqlLimits(cfg.MaxRowsPerSelect, cfg.SelectTimeoutSeconds, cfg.LockTimeoutMs));
public async Task RunAsync(AgentJob job, CancellationToken ct)
{
try
{
object? result = job.Kind switch
{
AgentProtocol.Kinds.Select => await SelectAsync(P(job), ct),
AgentProtocol.Kinds.AnyRows => await AnyRowsAsync(P(job), ct),
AgentProtocol.Kinds.GetValue => await Value(P(job), ct),
AgentProtocol.Kinds.GetRow => await Row(P(job), ct),
AgentProtocol.Kinds.ApplySteps => await ApplyAsync(P(job), ct),
AgentProtocol.Kinds.ReadSchema => await _ops.ReadSchemaAsync(ct),
AgentProtocol.Kinds.Test => await _ops.TestAsync(ct),
_ => throw new OpsException(400, $"The agent does not accept job type '{job.Kind}'."),
};
return new AgentResult(true, JsonSerializer.SerializeToElement(result, AgentProtocol.Json), null, null);
}
catch (OpsException ex)
{
return new AgentResult(false, null, ex.Status, ex.Message);
}
catch (Exception ex) when (ex is JsonException or ArgumentException or InvalidOperationException)
{
log.LogWarning(ex, "Job {JobId} ({Kind}) rejected", job.JobId, job.Kind);
return new AgentResult(false, null, 400, "The agent could not read this request: " + ex.Message);
}
}
private static T P(AgentJob job) => job.Payload.Deserialize(AgentProtocol.Json) ?? throw new JsonException("Empty payload.");
private void Check(string sql)
{
var r = _guard.ValidateSelect(sql, null);
if (!r.Ok) throw new OpsException(400, "Refused by the hospital agent: " + r.Reason);
}
private async Task