fix(authority): gate vector rebuild jobs

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Sisyphus
2026-05-03 19:27:47 +00:00
parent 8b65fcbdb1
commit fecbd1f2a6
4 changed files with 167 additions and 41 deletions

View File

@@ -634,4 +634,55 @@ assert.deepEqual(JSON.parse(String(httpRequests[3].options.body || "{}")), {
},
});
// Regression: onRebuildVectorIndexController skips submitAuthorityVectorRebuildJob when
// shouldUseAuthorityJobs() returns false and still runs syncVectorState with purge: true
// for Authority vector config.
const noJobsRuntime = createVectorControllerRuntime({
shouldUseAuthorityJobs() {
this.calls.push(["shouldUseAuthorityJobs"]);
return false;
},
});
await onRebuildVectorIndexController(noJobsRuntime);
assert.equal(
noJobsRuntime.calls.some(([name]) => name === "submitAuthorityVectorRebuildJob"),
false,
"submitAuthorityVectorRebuildJob should NOT be called when shouldUseAuthorityJobs returns false",
);
const noJobsSync = noJobsRuntime.calls.find(([name]) => name === "syncVectorState");
assert.equal(noJobsSync?.[1]?.purge, true, "syncVectorState should use purge:true for Authority config fallback");
assert.equal(
noJobsRuntime.calls.some(([name]) => name === "saveGraphToChat"),
true,
"saveGraphToChat should still be called in fallback path",
);
// Regression: Fallback warning wording no longer says 本地重建; instead it uses
// direct/synchronous rebuild wording.
const fallbackWarningRuntime = createVectorControllerRuntime({
async submitAuthorityVectorRebuildJob(payload) {
this.calls.push(["submitAuthorityVectorRebuildJob", payload]);
return { submitted: false, error: "job-type-unsupported" };
},
});
await onRebuildVectorIndexController(fallbackWarningRuntime);
const fallbackWarningMessage = fallbackWarningRuntime.calls.find(
([name]) => name === "toastr.warning",
)?.[1];
assert.equal(
typeof fallbackWarningMessage === "string",
true,
"a warning toast should be emitted when job submission fails",
);
assert.equal(
fallbackWarningMessage.includes("本地重建"),
false,
"fallback warning should NOT contain 本地重建",
);
assert.equal(
/直接|同步|direct|synchronous/i.test(fallbackWarningMessage),
true,
"fallback warning should contain direct/synchronous rebuild wording",
);
console.log("authority-jobs tests passed");

View File

@@ -107,6 +107,7 @@ import {
normalizeAuthorityCapabilityState,
probeAuthorityCapabilities,
} from "../runtime/authority-capabilities.js";
import { normalizeAuthorityBlobConfig } from "../maintenance/authority-blob-adapter.js";
import {
createAuthorityBrowserState,
getAuthorityBrowserStateSnapshot,
@@ -138,6 +139,27 @@ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
const indexPath = path.resolve(moduleDir, "../index.js");
const indexSource = await fs.readFile(indexPath, "utf8");
function isAuthorityVectorConfig(config = null) {
return config?.mode === "authority" || config?.source === "authority-trivium";
}
function normalizeAuthorityVectorConfig(settings = {}, overrides = {}) {
return {
...overrides,
mode: "authority",
source: "authority-trivium",
baseUrl: String(settings?.authorityBaseUrl || overrides?.baseUrl || "/api/plugins/authority"),
};
}
function createAuthorityBlobAdapter() {
return {
async writeJson(path = "", payload = null) {
return { path, payload, written: true };
},
};
}
function extractSnippet(startMarker, endMarker) {
const start = indexSource.indexOf(startMarker);
const end = indexSource.indexOf(endMarker);
@@ -629,6 +651,10 @@ async function createGraphPersistenceHarness({
normalizeAuthoritySettings,
normalizeAuthorityCapabilityState,
probeAuthorityCapabilities,
isAuthorityVectorConfig,
normalizeAuthorityVectorConfig,
normalizeAuthorityBlobConfig,
createAuthorityBlobAdapter,
createAuthorityBrowserState,
getAuthorityBrowserStateSnapshot,
normalizeAuthorityBrowserState,
@@ -1581,6 +1607,7 @@ result = {
maybeFlushQueuedGraphPersist,
retryPendingGraphPersist,
persistExtractionBatchResult,
shouldUseAuthorityJobs,
onRebuildLocalCacheFromLukerSidecar,
saveGraphToIndexedDb,
cloneGraphForPersistence,
@@ -3993,6 +4020,12 @@ result = {
sessionReady: true,
permissionReady: true,
features: ["sql.query", "sql.mutation", "trivium.search", "jobs", "blob"],
jobs: {
builtinTypes: ["delay", "sql.backup", "trivium.flush", "fs.import-jsonl"],
registry: {
jobTypes: ["delay", "sql.backup", "trivium.flush", "fs.import-jsonl"],
},
},
reason: "ok",
lastProbeAt: Date.now(),
});
@@ -4037,6 +4070,29 @@ result = {
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-authority-empty-jobs",
globalChatId: "chat-authority-empty-jobs",
});
harness.api.setAuthorityCapabilityState({
installed: true,
healthy: true,
sessionReady: true,
permissionReady: true,
features: ["sql.query", "sql.mutation", "trivium.search", "jobs", "blob"],
supportedJobTypes: [],
supportedJobTypesKnown: true,
reason: "ok",
});
assert.equal(
harness.api.shouldUseAuthorityJobs({ mode: "authority", source: "authority-trivium" }),
false,
"显式空 Authority job 白名单应阻止 vector rebuild job 提交",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-b",