refactor(authority): complete v0.6-only sql/blob/jobs rollout

This commit is contained in:
Youzini-afk
2026-04-28 21:45:21 +08:00
parent a5b822682a
commit a7e2edac88
23 changed files with 1816 additions and 212 deletions

View File

@@ -1,11 +1,11 @@
const DEFAULT_AUTHORITY_BASE_URL = "/api/plugins/authority";
const DEFAULT_AUTHORITY_PROBE_INTERVAL_MS = 60000;
const SQL_FEATURES = ["sql", "sql.query", "sql.page", "sql.pageall", "querysql"];
const SQL_MUTATION_FEATURES = ["sql", "sql.mutation", "sql.execute", "sql.write", "sql.transaction"];
const TRIVIUM_FEATURES = ["trivium", "trivium.search", "trivium.query", "trivium.filterwhere", "trivium.bulkupsert"];
const JOB_FEATURES = ["jobs", "jobs.list", "jobs.wait", "events", "sse"];
const BLOB_FEATURES = ["blob", "blob.write", "privatefiles", "private.files", "files.private"];
const SQL_FEATURES = ["sql", "sql.query", "sql.querypage", "sql.page", "sql.pageall", "querysql"];
const SQL_MUTATION_FEATURES = ["sql", "sql.mutation", "sql.execute", "sql.exec", "sql.write", "sql.transaction"];
const TRIVIUM_FEATURES = ["trivium", "trivium.search", "trivium.query", "trivium.filterwhere", "trivium.bulkupsert", "trivium.upsert", "trivium.bulkmutations"];
const JOB_FEATURES = ["jobs", "jobs.background", "jobs.list", "jobs.wait", "diagnostics.jobspage", "events", "sse"];
const BLOB_FEATURES = ["blob", "blob.write", "storage.blob", "transfers.blob", "transfers.fs", "fs.private", "privatefiles", "private.files", "files.private"];
function toBoolean(value, fallback = false) {
if (typeof value === "boolean") return value;
@@ -89,6 +89,189 @@ function readNowMs() {
return Date.now();
}
function clonePlain(value, fallbackValue = null) {
if (value == null) return fallbackValue;
if (typeof globalThis.structuredClone === "function") {
try {
return globalThis.structuredClone(value);
} catch {
}
}
try {
return JSON.parse(JSON.stringify(value));
} catch {
return fallbackValue;
}
}
function normalizeHeaderName(name = "") {
return String(name || "").trim().toLowerCase();
}
function buildDefaultSessionInitConfig(source = {}) {
const config = source && typeof source === "object" && !Array.isArray(source) ? source : {};
return {
extensionId: String(config.extensionId || "third-party/st-bme"),
displayName: String(config.displayName || "ST-BME"),
version: String(config.version || "0.0.0"),
installType: String(config.installType || "local"),
declaredPermissions: clonePlain(config.declaredPermissions, null) || {
storage: { kv: true, blob: true },
fs: { private: true },
sql: { private: true },
trivium: { private: true },
jobs: { background: true },
events: { channels: true },
},
...(config.uiLabel ? { uiLabel: String(config.uiLabel) } : {}),
};
}
function withJsonHeaders(headers = {}) {
return {
Accept: "application/json",
"Content-Type": "application/json",
...(headers || {}),
};
}
async function readResponsePayload(response = null) {
if (!response) return {};
if (typeof response.json === "function") {
try {
return await response.json();
} catch {
}
}
if (typeof response.text === "function") {
try {
return { error: await response.text() };
} catch {
return {};
}
}
return {};
}
function readPayloadMessage(payload = {}, fallback = "") {
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return fallback;
return String(payload.error || payload.message || payload.reason || fallback || "");
}
function buildAuthorityPermissionEvaluateRequests(settings = {}, readiness = {}, options = {}) {
const requests = [];
const sqlTarget = String(options.sqlTarget || settings.sqlTarget || "default");
const triviumTarget = String(options.triviumTarget || settings.triviumTarget || "st_bme_vectors");
const jobTarget = String(options.jobTarget || settings.jobTarget || "delay");
if (readiness.sql || readiness.sqlMutation) {
requests.push({ resource: "sql.private", target: sqlTarget, reason: `Probe SQL capability for ${sqlTarget}` });
}
if (readiness.trivium) {
requests.push({ resource: "trivium.private", target: triviumTarget, reason: `Probe Trivium capability for ${triviumTarget}` });
}
if (readiness.blob) {
requests.push({ resource: "fs.private", reason: "Probe private file capability for Authority Blob adapter" });
}
if (readiness.jobs) {
requests.push({ resource: "jobs.background", target: jobTarget, reason: `Probe Jobs capability for ${jobTarget}` });
}
return requests;
}
async function verifyAuthorityDataPlane(baseUrl, fetchImpl, headers, settings = {}, readiness = {}, options = {}) {
const initHeaders = withJsonHeaders(headers);
const initResponse = await fetchImpl(`${baseUrl}/session/init`, {
method: "POST",
headers: initHeaders,
body: JSON.stringify(buildDefaultSessionInitConfig(options.sessionInitConfig || settings)),
});
const initStatus = Number(initResponse?.status || 0);
const initPayload = await readResponsePayload(initResponse);
if (!initResponse?.ok) {
return {
sessionReady: false,
permissionReady: false,
reason: initStatus === 401 || initStatus === 403 ? "session-init-denied" : "session-init-failed",
lastError: readPayloadMessage(initPayload, `HTTP ${initStatus || "unknown"}`),
status: initStatus,
};
}
const sessionToken = String(initPayload?.sessionToken || initPayload?.token || "");
if (!sessionToken) {
return {
sessionReady: false,
permissionReady: false,
reason: "session-token-missing",
lastError: "session token missing",
status: initStatus,
};
}
const sessionHeaders = {
...withJsonHeaders(headers),
...(Object.keys(headers || {}).some((name) => normalizeHeaderName(name) === "x-authority-session-token")
? {}
: { "x-authority-session-token": sessionToken }),
};
const currentResponse = await fetchImpl(`${baseUrl}/session/current`, {
method: "GET",
headers: sessionHeaders,
});
const currentStatus = Number(currentResponse?.status || 0);
const currentPayload = await readResponsePayload(currentResponse);
if (!currentResponse?.ok) {
return {
sessionReady: false,
permissionReady: false,
reason: currentStatus === 401 || currentStatus === 403 ? "session-invalid" : "session-current-failed",
lastError: readPayloadMessage(currentPayload, `HTTP ${currentStatus || "unknown"}`),
status: currentStatus,
};
}
const requests = buildAuthorityPermissionEvaluateRequests(settings, readiness, options);
if (!requests.length) {
return {
sessionReady: true,
permissionReady: true,
reason: "ok",
lastError: "",
status: currentStatus,
};
}
const permissionResponse = await fetchImpl(`${baseUrl}/permissions/evaluate-batch`, {
method: "POST",
headers: sessionHeaders,
body: JSON.stringify({ requests }),
});
const permissionStatus = Number(permissionResponse?.status || 0);
const permissionPayload = await readResponsePayload(permissionResponse);
if (!permissionResponse?.ok) {
return {
sessionReady: true,
permissionReady: false,
reason: permissionStatus === 401 || permissionStatus === 403 ? "permission-denied" : "permission-evaluate-failed",
lastError: readPayloadMessage(permissionPayload, `HTTP ${permissionStatus || "unknown"}`),
status: permissionStatus,
};
}
const results = Array.isArray(permissionPayload?.results) ? permissionPayload.results : [];
const permissionReady = results.length === requests.length && results.every((result) => {
const decision = String(result?.decision || result?.grant?.status || "").trim().toLowerCase();
return decision === "granted";
});
return {
sessionReady: true,
permissionReady,
reason: permissionReady ? "ok" : "permission-not-ready",
lastError: permissionReady ? "" : "required Authority permissions are not granted",
status: permissionStatus || currentStatus,
};
}
export function normalizeAuthorityBaseUrl(baseUrl = DEFAULT_AUTHORITY_BASE_URL) {
const normalized = String(baseUrl || DEFAULT_AUTHORITY_BASE_URL).trim() || DEFAULT_AUTHORITY_BASE_URL;
return normalized.replace(/\/+$/, "");
@@ -116,12 +299,7 @@ export function normalizeAuthoritySettings(settings = {}) {
export function buildAuthorityProbeUrls(baseUrl = DEFAULT_AUTHORITY_BASE_URL) {
const normalizedBaseUrl = normalizeAuthorityBaseUrl(baseUrl);
return [
`${normalizedBaseUrl}/v1/diagnostics/probe`,
`${normalizedBaseUrl}/v1/probe`,
`${normalizedBaseUrl}/probe`,
normalizedBaseUrl,
];
return [`${normalizedBaseUrl}/probe`];
}
export function collectAuthorityFeatures(payload = {}) {
@@ -295,7 +473,7 @@ export async function probeAuthorityCapabilities(options = {}) {
for (const endpoint of buildAuthorityProbeUrls(settings.baseUrl)) {
const startedAt = readNowMs();
try {
const response = await fetchImpl(endpoint, { method: "GET", headers });
const response = await fetchImpl(endpoint, { method: "POST", headers });
const finishedAt = readNowMs();
const status = Number(response?.status || 0);
lastStatus = status;
@@ -340,13 +518,43 @@ export async function probeAuthorityCapabilities(options = {}) {
} catch {
payload = {};
}
return normalizeAuthorityProbeResponse(payload, {
const features = collectAuthorityFeatures(payload);
const readiness = createFeatureReadiness(features);
const missingFeatures = collectMissingFeatures(readiness);
const healthy = payload?.healthy ?? payload?.ok ?? true;
let sessionReady = payload?.sessionReady ?? payload?.session?.ready ?? payload?.session?.active;
let permissionReady = payload?.permissionReady ?? payload?.permissions?.ready ?? payload?.authorized;
let reason = missingFeatures.length ? "missing-required-features" : "ok";
let dataPlaneLastError = "";
let dataPlaneStatus = status;
if (healthy) {
const verified = await verifyAuthorityDataPlane(settings.baseUrl, fetchImpl, headers, settings, readiness, options);
sessionReady = verified.sessionReady;
permissionReady = verified.permissionReady;
dataPlaneStatus = Number(verified.status || status || 0);
dataPlaneLastError = String(verified.lastError || "");
if (verified.reason && verified.reason !== "ok") {
reason = verified.reason;
}
}
return normalizeAuthorityCapabilityState(
{
installed: true,
healthy: Boolean(healthy),
sessionReady: Boolean(sessionReady),
permissionReady: Boolean(permissionReady),
features: Array.from(features),
missingFeatures,
reason,
lastError: dataPlaneLastError,
endpoint,
status: dataPlaneStatus,
latencyMs: normalizeLatencyMs(startedAt, finishedAt),
lastProbeAt: nowMs,
updatedAt: new Date(nowMs).toISOString(),
},
settings,
endpoint,
status,
latencyMs: normalizeLatencyMs(startedAt, finishedAt),
nowMs,
});
);
} catch (error) {
lastError = error?.message || String(error);
}