feat(POSTV1-02): 增加AI真实生成探测
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run

This commit is contained in:
suyx
2026-08-05 13:14:40 +08:00
parent 99fd3b1802
commit b1f143c238
6 changed files with 163 additions and 3 deletions
@@ -170,6 +170,8 @@ internal static class Program
True(leakProbe.SensitiveOutputDetected, "credential echo must be detected");
Equal(string.Empty, leakProbe.StandardOutput, "credential echo output discarded");
Equal(string.Empty, leakProbe.StandardError, "credential echo error discarded");
True(AiGatewayProbe.TryValidateOutput("{\"code\":\"ai_probe_passed\",\"mime_type\":\"image/png\",\"pixel_height\":1080,\"pixel_width\":1080,\"real_calls\":1,\"success\":true}", out _), "AI probe success output accepted");
False(AiGatewayProbe.TryValidateOutput("{\"code\":\"ai_probe_passed\",\"raw_body\":\"private\",\"real_calls\":1,\"success\":true}", out _), "AI probe private output rejected");
var externalArguments = new[]
{
@@ -0,0 +1,74 @@
using System.Diagnostics;
using System.Text.Json;
namespace Dada.Supervisor;
internal static class AiGatewayProbe
{
internal static async Task<int> RunAsync(ICredentialStore credentials, CancellationToken cancellationToken = default)
{
var node = Path.Combine(AppContext.BaseDirectory, "runtime", "node.exe");
var worker = Path.Combine(AppContext.BaseDirectory, "server", "worker.mjs");
if (!File.Exists(node) || !File.Exists(worker)) return WriteFailure("ai_probe_runtime_missing", 0);
var startInfo = new ProcessStartInfo(node) { WorkingDirectory = AppContext.BaseDirectory };
startInfo.Environment["DADA_SQLITE_NATIVE_BINDING"] = Path.Combine(AppContext.BaseDirectory, "server", "native", "better_sqlite3.node");
startInfo.ArgumentList.Add(worker);
startInfo.ArgumentList.Add("--dada-ai-probe");
startInfo.ArgumentList.Add("--dada-credential-stdin");
var result = await CredentialProcessLauncher.RunToCompletionAsync(startInfo, ChildRole.Worker, credentials, cancellationToken);
if (result.SensitiveOutputDetected || result.StandardError.Length > 0 || !TryValidateOutput(result.StandardOutput, out var sanitized))
{
return WriteFailure("ai_probe_runtime_failed", 0);
}
Console.WriteLine(sanitized);
return result.ExitCode;
}
internal static bool TryValidateOutput(string output, out string sanitized)
{
sanitized = string.Empty;
try
{
using var document = JsonDocument.Parse(output);
var root = document.RootElement;
if (root.ValueKind != JsonValueKind.Object) return false;
var allowed = new HashSet<string>(StringComparer.Ordinal)
{
"code", "error_category", "mime_type", "pixel_height", "pixel_width", "real_calls", "success",
};
if (root.EnumerateObject().Any(property => !allowed.Contains(property.Name))) return false;
if (!root.TryGetProperty("success", out var success) || success.ValueKind is not (JsonValueKind.True or JsonValueKind.False)) return false;
if (!root.TryGetProperty("real_calls", out var realCalls) || realCalls.ValueKind != JsonValueKind.Number || !realCalls.TryGetInt32(out var count) || count is < 0 or > 1) return false;
var passed = success.GetBoolean();
var code = root.GetProperty("code").GetString();
if (passed)
{
if (code != "ai_probe_passed" || count != 1) return false;
var mime = root.GetProperty("mime_type").GetString();
if (mime is not ("image/jpeg" or "image/png" or "image/webp")) return false;
if (!PositiveDimension(root, "pixel_width") || !PositiveDimension(root, "pixel_height")) return false;
}
else
{
if (code != "ai_probe_failed" || !root.TryGetProperty("error_category", out var category)
|| category.ValueKind != JsonValueKind.String || (category.GetString()?.Length ?? 0) is < 1 or > 64) return false;
}
sanitized = JsonSerializer.Serialize(root);
return true;
}
catch (Exception exception) when (exception is JsonException or InvalidOperationException or KeyNotFoundException)
{
return false;
}
}
private static bool PositiveDimension(JsonElement root, string name) =>
root.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.Number
&& value.TryGetInt32(out var dimension) && dimension is > 0 and <= 4096;
private static int WriteFailure(string code, int realCalls)
{
Console.WriteLine(JsonSerializer.Serialize(new { code, real_calls = realCalls, success = false }));
return 2;
}
}
@@ -26,7 +26,7 @@ internal static class OfflineCommandRouter
return args[0] switch
{
"configure" => RunConfigure(args.Skip(1).ToArray()),
"secrets" => RunSecrets(args.Skip(1).ToArray(), credentials),
"secrets" => await RunSecretsAsync(args.Skip(1).ToArray(), credentials),
"admin-allowlist" => RunAdminAllowlist(args.Skip(1).ToArray(), credentials),
"doctor" when args.Length == 1 => RunDoctor(credentials),
"validate-external" => await ControlledExternalValidationLauncher.RunAsync(args.Skip(1).ToArray(), credentials),
@@ -66,7 +66,7 @@ internal static class OfflineCommandRouter
return 0;
}
private static int RunSecrets(string[] args, ICredentialStore store)
private static async Task<int> RunSecretsAsync(string[] args, ICredentialStore store)
{
if (args.Length != 2 || !TryResolveCredential(args[1], out var target)) return Usage();
switch (args[0])
@@ -86,6 +86,8 @@ internal static class OfflineCommandRouter
return 0;
case "probe" when target == CredentialCatalog.ApiAmap:
return AmapProbe.Run(store.Read(target));
case "probe" when target == CredentialCatalog.WorkerAiGateway:
return await AiGatewayProbe.RunAsync(store);
default:
return Usage();
}