diff --git a/index.js b/index.js index 24295cd..de2973a 100644 --- a/index.js +++ b/index.js @@ -2840,6 +2840,96 @@ async function restoreAuthorityCheckpointFromBlob(options = {}) { } } +async function writeAuthorityCheckpointFromCurrentGraph(options = {}) { + const settings = getSettings(); + const { capability } = getAuthorityRuntimeSnapshot(settings); + const updatedAt = new Date().toISOString(); + const chatId = normalizeChatIdCandidate( + options.chatId || getCurrentChatId() || graphPersistenceState.chatId || currentGraph?.chatId, + ); + if (!chatId) { + return { + success: false, + error: "missing-chat-id", + }; + } + if (!capability.blobReady || !shouldUseAuthorityBlobCheckpoint()) { + return { + success: false, + error: "Authority Blob unavailable", + }; + } + + ensureCurrentGraphRuntimeState(); + if (!currentGraph) { + return { + success: false, + error: "Authority runtime graph unavailable", + }; + } + + const revision = Math.max( + 1, + Number(options.revision || 0), + Number(currentGraph?.meta?.revision || 0), + Number(getGraphPersistedRevision(currentGraph) || 0), + Number(graphPersistenceState.revision || 0), + ); + const integrity = + normalizeChatIdCandidate(options.integrity) || + normalizeChatIdCandidate(getGraphPersistenceMeta(currentGraph)?.integrity) || + getChatMetadataIntegrity(getContext()) || + graphPersistenceState.metadataIntegrity; + const reason = String(options.reason || "manual-authority-checkpoint"); + const checkpoint = buildLukerGraphCheckpointV2(currentGraph, { + revision, + chatId, + integrity, + reason, + storageTier: "authority-sql-primary", + persistedAt: updatedAt, + }); + if (!checkpoint) { + return { + success: false, + error: "Authority checkpoint payload unavailable", + }; + } + + const writeResult = await writeAuthorityLukerCheckpointBlob(checkpoint, { + chatId, + reason, + signal: options.signal, + }); + if (!writeResult?.ok) { + return { + success: false, + error: + writeResult?.error?.message || + writeResult?.reason || + "authority-blob-checkpoint-write-failed", + }; + } + + const auditResult = await runAuthorityConsistencyAudit({ + chatId, + collectionId: + normalizeChatIdCandidate(options.collectionId) || + normalizeChatIdCandidate(currentGraph?.vectorIndexState?.collectionId) || + buildVectorCollectionId(chatId), + }).catch(() => null); + return { + success: true, + result: { + path: writeResult.path, + revision, + checkpointRevision: Number(checkpoint.revision || revision || 0), + auditSummary: auditResult?.audit?.summary || null, + auditActions: auditResult?.audit?.actions || [], + }, + }; +} + async function submitAuthorityVectorRebuildJob({ config = null, range = null, @@ -21404,6 +21494,12 @@ async function onRunAuthorityConsistencyAudit() { }); } +async function onWriteAuthorityCheckpoint() { + return await writeAuthorityCheckpointFromCurrentGraph({ + reason: "panel-authority-checkpoint-write", + }); +} + async function onRestoreAuthorityCheckpoint() { return await restoreAuthorityCheckpointFromBlob({ reason: "panel-authority-checkpoint-restore", @@ -21996,6 +22092,7 @@ async function onCompactLukerSidecar() { requeueAuthorityJob: async (jobId) => await requeueAuthorityJob(jobId), refreshAuthorityJobs: onRefreshAuthorityJobs, runAuthorityConsistencyAudit: onRunAuthorityConsistencyAudit, + writeAuthorityCheckpoint: onWriteAuthorityCheckpoint, restoreAuthorityCheckpoint: onRestoreAuthorityCheckpoint, captureAuthorityPerformanceBaseline: onCaptureAuthorityPerformanceBaseline, reembedDirect: onReembedDirect, diff --git a/tests/authority-consistency.mjs b/tests/authority-consistency.mjs index 64f3569..982d1e9 100644 --- a/tests/authority-consistency.mjs +++ b/tests/authority-consistency.mjs @@ -145,5 +145,6 @@ assert.equal(auditDrift.summary.level, "warning"); assert.ok(auditDrift.issues.some((issue) => issue.code === "sql-runtime-revision-drift")); assert.ok(auditDrift.issues.some((issue) => issue.code === "vector-dirty")); assert.ok(auditDrift.actions.includes("rebuild-authority-trivium")); +assert.ok(auditDrift.actions.includes("write-authority-checkpoint")); console.log("authority-consistency tests passed"); diff --git a/ui/panel.js b/ui/panel.js index b688bb2..8278ff6 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -3248,13 +3248,27 @@ function _refreshTaskPersistence() { ["诊断包时间", authorityBundleUpdatedLabel], ["诊断包原因", ps.authorityDiagnosticsBundleReason || "—"], ]; + const authorityAuditActions = Array.isArray(ps.authorityConsistencyAudit?.actions) + ? ps.authorityConsistencyAudit.actions.map((value) => String(value || "").trim()).filter(Boolean) + : []; + const showAuthorityCheckpointWriteAction = + authorityAuditActions.includes("write-authority-checkpoint") || + (!ps.authorityBlobCheckpointPath && ps.authorityBlobReady); + const showAuthorityTriviumRebuildAction = + authorityAuditActions.includes("rebuild-authority-trivium"); const authorityActionButtons = [ typeof _actionHandlers.runAuthorityConsistencyAudit === "function" ? `` : "", + showAuthorityCheckpointWriteAction && typeof _actionHandlers.writeAuthorityCheckpoint === "function" + ? `` + : "", typeof _actionHandlers.restoreAuthorityCheckpoint === "function" ? `` : "", + showAuthorityTriviumRebuildAction && typeof _actionHandlers.rebuildVectorIndex === "function" + ? `` + : "", typeof _actionHandlers.captureAuthorityPerformanceBaseline === "function" ? `` : "", @@ -3321,6 +3335,15 @@ function _refreshTaskPersistence() { } else { toastr.warning(`Authority 审计失败:${result?.error || "unknown"}`, "ST-BME"); } + } else if (action === "checkpoint") { + if (typeof _actionHandlers.writeAuthorityCheckpoint !== "function") return; + toastr.info("Authority Checkpoint 写入中…", "ST-BME", { timeOut: 2000 }); + const result = await _actionHandlers.writeAuthorityCheckpoint(); + if (result?.success) { + toastr.success(`Authority Checkpoint 已写入:rev ${Number(result?.result?.checkpointRevision || result?.result?.revision || 0) || "?"}`, "ST-BME"); + } else { + toastr.warning(`Authority Checkpoint 写入失败:${result?.error || "unknown"}`, "ST-BME"); + } } else if (action === "restore") { if (typeof _actionHandlers.restoreAuthorityCheckpoint !== "function") return; toastr.info("Authority Checkpoint 恢复中…", "ST-BME", { timeOut: 2000 }); @@ -3330,6 +3353,10 @@ function _refreshTaskPersistence() { } else { toastr.warning(`Authority Checkpoint 恢复失败:${result?.error || "unknown"}`, "ST-BME"); } + } else if (action === "rebuild-trivium") { + if (typeof _actionHandlers.rebuildVectorIndex !== "function") return; + await _actionHandlers.rebuildVectorIndex(); + return; } else if (action === "baseline") { if (typeof _actionHandlers.captureAuthorityPerformanceBaseline !== "function") return; const result = await _actionHandlers.captureAuthorityPerformanceBaseline(); @@ -3349,6 +3376,10 @@ function _refreshTaskPersistence() { toastr.error( action === "restore" ? `Authority Checkpoint 恢复失败: ${error?.message || error}` + : action === "checkpoint" + ? `Authority Checkpoint 写入失败: ${error?.message || error}` + : action === "rebuild-trivium" + ? `Authority Trivium 重建失败: ${error?.message || error}` : action === "baseline" ? `Authority Perf Baseline 捕获失败: ${error?.message || error}` : `Authority 审计失败: ${error?.message || error}`,