mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
429 lines
12 KiB
JavaScript
429 lines
12 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { randomUUID } from "node:crypto";
|
|
|
|
import { probeAuthorityCapabilities } from "../../runtime/authority-capabilities.js";
|
|
import { AuthorityGraphStore } from "../../sync/authority-graph-store.js";
|
|
import {
|
|
deleteAuthorityTriviumNodes,
|
|
filterAuthorityTriviumNodes,
|
|
normalizeAuthorityVectorConfig,
|
|
purgeAuthorityTriviumNamespace,
|
|
queryAuthorityTriviumNeighbors,
|
|
searchAuthorityTriviumNodes,
|
|
syncAuthorityTriviumLinks,
|
|
upsertAuthorityTriviumEntries,
|
|
} from "../../vector/authority-vector-primary-adapter.js";
|
|
import {
|
|
buildAuthorityJobIdempotencyKey,
|
|
createAuthorityJobAdapter,
|
|
} from "../../maintenance/authority-job-adapter.js";
|
|
import { createAuthorityBlobAdapter } from "../../maintenance/authority-blob-adapter.js";
|
|
|
|
const env = process.env;
|
|
const baseUrl = String(env.AUTHORITY_E2E_BASE_URL || "").trim();
|
|
|
|
if (!baseUrl) {
|
|
console.log("authority-server-primary E2E skipped: set AUTHORITY_E2E_BASE_URL to run against a real Authority server");
|
|
process.exit(0);
|
|
}
|
|
|
|
function parsePositiveInteger(value, fallback, min = 1, max = Number.MAX_SAFE_INTEGER) {
|
|
const parsed = Number(value);
|
|
if (!Number.isFinite(parsed)) return fallback;
|
|
return Math.min(max, Math.max(min, Math.trunc(parsed)));
|
|
}
|
|
|
|
function parseJsonObject(value, fallback = {}) {
|
|
if (!value) return fallback;
|
|
try {
|
|
const parsed = JSON.parse(String(value));
|
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : fallback;
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function resolveBaseUrl(value) {
|
|
const normalized = String(value || "").replace(/\/+$/g, "");
|
|
if (/^https?:\/\//i.test(normalized)) return normalized;
|
|
const origin = String(env.AUTHORITY_E2E_ORIGIN || "").replace(/\/+$/g, "");
|
|
if (origin && normalized.startsWith("/")) return `${origin}${normalized}`;
|
|
throw new Error("AUTHORITY_E2E_BASE_URL must be absolute, or set AUTHORITY_E2E_ORIGIN for relative plugin paths");
|
|
}
|
|
|
|
function createHeaderProvider() {
|
|
const staticHeaders = parseJsonObject(env.AUTHORITY_E2E_HEADER_JSON, {});
|
|
const token = String(env.AUTHORITY_E2E_TOKEN || "").trim();
|
|
const cookie = String(env.AUTHORITY_E2E_COOKIE || "").trim();
|
|
return () => ({
|
|
...staticHeaders,
|
|
...(token ? { Authorization: /^Bearer\s+/i.test(token) ? token : `Bearer ${token}` } : {}),
|
|
...(cookie ? { Cookie: cookie } : {}),
|
|
});
|
|
}
|
|
|
|
function createFetchWithTimeout(timeoutMs) {
|
|
return async (url, options = {}) => {
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(new Error(`Authority E2E request timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
try {
|
|
return await fetch(url, {
|
|
...options,
|
|
signal: controller.signal,
|
|
});
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
};
|
|
}
|
|
|
|
function createContractNode(id, title, nowMs) {
|
|
return {
|
|
id,
|
|
type: "fact",
|
|
fields: {
|
|
title,
|
|
summary: `${title} generated by Authority server-primary E2E contract smoke`,
|
|
},
|
|
seqRange: [1, 1],
|
|
scope: {
|
|
layer: "global",
|
|
ownerType: "system",
|
|
ownerId: "authority-e2e",
|
|
bucket: "contract",
|
|
regionKey: "authority-e2e-region",
|
|
},
|
|
storySegmentId: "authority-e2e-segment",
|
|
importance: 0.8,
|
|
archived: false,
|
|
updatedAt: nowMs,
|
|
};
|
|
}
|
|
|
|
function createContractGraph(chatId, runId) {
|
|
const nowMs = Date.now();
|
|
const nodeA = createContractNode(`${runId}-node-a`, "Authority E2E Alpha", nowMs);
|
|
const nodeB = createContractNode(`${runId}-node-b`, "Authority E2E Beta", nowMs);
|
|
return {
|
|
meta: {
|
|
schemaVersion: 1,
|
|
chatId,
|
|
deviceId: "authority-e2e",
|
|
revision: 1,
|
|
lastModified: nowMs,
|
|
nodeCount: 2,
|
|
edgeCount: 1,
|
|
tombstoneCount: 0,
|
|
},
|
|
nodes: [nodeA, nodeB],
|
|
edges: [
|
|
{
|
|
id: `${runId}-edge-a-b`,
|
|
fromId: nodeA.id,
|
|
toId: nodeB.id,
|
|
relation: "related",
|
|
type: "semantic",
|
|
strength: 0.7,
|
|
updatedAt: nowMs,
|
|
},
|
|
],
|
|
tombstones: [],
|
|
state: {
|
|
lastProcessedFloor: 1,
|
|
extractionCount: 1,
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildVectorEntries(graph) {
|
|
return graph.nodes.map((node, index) => ({
|
|
nodeId: node.id,
|
|
index,
|
|
hash: `${node.id}:hash`,
|
|
text: `${node.fields.title}. ${node.fields.summary}`,
|
|
}));
|
|
}
|
|
|
|
async function runStep(name, fn) {
|
|
const startedAt = Date.now();
|
|
try {
|
|
const result = await fn();
|
|
const durationMs = Date.now() - startedAt;
|
|
console.log(`authority E2E ${name}: ok (${durationMs}ms)`);
|
|
return { name, ok: true, durationMs, result };
|
|
} catch (error) {
|
|
const durationMs = Date.now() - startedAt;
|
|
console.error(`authority E2E ${name}: failed (${durationMs}ms)`);
|
|
console.error(error?.stack || error?.message || String(error));
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
const resolvedBaseUrl = resolveBaseUrl(baseUrl);
|
|
const timeoutMs = parsePositiveInteger(env.AUTHORITY_E2E_TIMEOUT_MS, 15000, 1000, 300000);
|
|
const jobWaitTimeoutMs = parsePositiveInteger(env.AUTHORITY_E2E_JOB_WAIT_TIMEOUT_MS, 15000, 1000, 300000);
|
|
const headerProvider = createHeaderProvider();
|
|
const fetchImpl = createFetchWithTimeout(timeoutMs);
|
|
const runId = String(env.AUTHORITY_E2E_RUN_ID || `authority-e2e-${Date.now()}-${randomUUID().slice(0, 8)}`)
|
|
.replace(/[^A-Za-z0-9._:-]+/g, "-");
|
|
const chatId = String(env.AUTHORITY_E2E_CHAT_ID || `st-bme-${runId}`);
|
|
const namespace = String(env.AUTHORITY_E2E_NAMESPACE || `st-bme-e2e-${runId}`);
|
|
const collectionId = String(env.AUTHORITY_E2E_COLLECTION_ID || `${namespace}::${chatId}`);
|
|
const blobPath = String(env.AUTHORITY_E2E_BLOB_PATH || `st-bme/e2e/${runId}/contract.json`);
|
|
const graph = createContractGraph(chatId, runId);
|
|
|
|
const context = {
|
|
baseUrl: resolvedBaseUrl,
|
|
chatId,
|
|
namespace,
|
|
collectionId,
|
|
blobPath,
|
|
};
|
|
|
|
console.log(`authority-server-primary E2E started: ${JSON.stringify(context)}`);
|
|
|
|
await runStep("probe", async () => {
|
|
const state = await probeAuthorityCapabilities({
|
|
settings: { authorityBaseUrl: resolvedBaseUrl },
|
|
fetchImpl,
|
|
headerProvider,
|
|
allowRelativeUrl: false,
|
|
});
|
|
assert.equal(state.installed, true);
|
|
assert.equal(state.healthy, true);
|
|
return {
|
|
endpoint: state.endpoint,
|
|
features: state.features,
|
|
missingFeatures: state.missingFeatures,
|
|
};
|
|
});
|
|
|
|
await runStep("sql", async () => {
|
|
const store = new AuthorityGraphStore(chatId, {
|
|
baseUrl: resolvedBaseUrl,
|
|
fetchImpl,
|
|
headerProvider,
|
|
});
|
|
try {
|
|
await store.open();
|
|
const importResult = await store.importSnapshot(graph, {
|
|
mode: "replace",
|
|
preserveRevision: true,
|
|
markSyncDirty: false,
|
|
});
|
|
assert.equal(importResult.imported.nodes, graph.nodes.length);
|
|
assert.equal(importResult.imported.edges, graph.edges.length);
|
|
|
|
const commitResult = await store.commitDelta(
|
|
{
|
|
upsertNodes: [createContractNode(`${runId}-node-c`, "Authority E2E Gamma", Date.now())],
|
|
runtimeMetaPatch: { authorityE2eRunId: runId },
|
|
},
|
|
{ reason: "authority-e2e-contract", markSyncDirty: false },
|
|
);
|
|
assert.ok(commitResult.revision >= importResult.revision);
|
|
|
|
const snapshot = await store.exportSnapshot({ includeTombstones: false });
|
|
assert.equal(snapshot.meta.chatId, chatId);
|
|
assert.ok(snapshot.nodes.some((node) => node.id === graph.nodes[0].id));
|
|
assert.ok(snapshot.nodes.some((node) => node.id === `${runId}-node-c`));
|
|
return {
|
|
revision: snapshot.meta.revision,
|
|
nodes: snapshot.nodes.length,
|
|
edges: snapshot.edges.length,
|
|
};
|
|
} finally {
|
|
await store.clearAll().catch(() => null);
|
|
await store.close().catch(() => null);
|
|
}
|
|
});
|
|
|
|
await runStep("trivium", async () => {
|
|
const config = normalizeAuthorityVectorConfig({ authorityBaseUrl: resolvedBaseUrl });
|
|
const entries = buildVectorEntries(graph);
|
|
await purgeAuthorityTriviumNamespace(config, {
|
|
namespace,
|
|
collectionId,
|
|
chatId,
|
|
fetchImpl,
|
|
headerProvider,
|
|
}).catch(() => null);
|
|
|
|
try {
|
|
const upsertResult = await upsertAuthorityTriviumEntries(graph, config, entries, {
|
|
namespace,
|
|
collectionId,
|
|
chatId,
|
|
modelScope: "authority-e2e",
|
|
revision: 1,
|
|
fetchImpl,
|
|
headerProvider,
|
|
});
|
|
assert.equal(upsertResult.upserted, entries.length);
|
|
|
|
const linkResult = await syncAuthorityTriviumLinks(graph, config, {
|
|
namespace,
|
|
collectionId,
|
|
chatId,
|
|
revision: 1,
|
|
fetchImpl,
|
|
headerProvider,
|
|
});
|
|
assert.equal(linkResult.linked, graph.edges.length);
|
|
|
|
const searchResults = await searchAuthorityTriviumNodes(graph, "Authority E2E Alpha", config, {
|
|
namespace,
|
|
collectionId,
|
|
chatId,
|
|
topK: 5,
|
|
fetchImpl,
|
|
headerProvider,
|
|
});
|
|
assert.ok(Array.isArray(searchResults));
|
|
|
|
const filteredIds = await filterAuthorityTriviumNodes(config, {
|
|
namespace,
|
|
collectionId,
|
|
chatId,
|
|
topK: 10,
|
|
where: { chatId, archived: false },
|
|
searchText: "Authority E2E",
|
|
fetchImpl,
|
|
headerProvider,
|
|
});
|
|
assert.ok(Array.isArray(filteredIds));
|
|
|
|
const neighborIds = await queryAuthorityTriviumNeighbors(config, [graph.nodes[0].id], {
|
|
namespace,
|
|
collectionId,
|
|
chatId,
|
|
topK: 5,
|
|
fetchImpl,
|
|
headerProvider,
|
|
});
|
|
assert.ok(Array.isArray(neighborIds));
|
|
|
|
return {
|
|
upserted: upsertResult.upserted,
|
|
linked: linkResult.linked,
|
|
searchResults: searchResults.length,
|
|
filteredIds: filteredIds.length,
|
|
neighborIds: neighborIds.length,
|
|
};
|
|
} finally {
|
|
await deleteAuthorityTriviumNodes(config, graph.nodes.map((node) => node.id), {
|
|
namespace,
|
|
collectionId,
|
|
chatId,
|
|
fetchImpl,
|
|
headerProvider,
|
|
}).catch(() => null);
|
|
await purgeAuthorityTriviumNamespace(config, {
|
|
namespace,
|
|
collectionId,
|
|
chatId,
|
|
fetchImpl,
|
|
headerProvider,
|
|
}).catch(() => null);
|
|
}
|
|
});
|
|
|
|
await runStep("jobs", async () => {
|
|
const adapter = createAuthorityJobAdapter(
|
|
{
|
|
authorityBaseUrl: resolvedBaseUrl,
|
|
pollIntervalMs: 500,
|
|
waitTimeoutMs: jobWaitTimeoutMs,
|
|
},
|
|
{
|
|
fetchImpl,
|
|
headerProvider,
|
|
},
|
|
);
|
|
const listBefore = await adapter.listPage({ limit: 5 });
|
|
assert.ok(Array.isArray(listBefore.jobs));
|
|
|
|
const kind = String(env.AUTHORITY_E2E_JOB_KIND || "authority.vector.rebuild");
|
|
const idempotencyKey = buildAuthorityJobIdempotencyKey({
|
|
kind,
|
|
chatId,
|
|
collectionId,
|
|
revision: 1,
|
|
});
|
|
const submitted = await adapter.submit(
|
|
kind,
|
|
{
|
|
chatId,
|
|
collectionId,
|
|
namespace,
|
|
modelScope: "authority-e2e",
|
|
source: "authority-e2e-contract",
|
|
purge: false,
|
|
dryRun: true,
|
|
contractSmoke: true,
|
|
idempotencyKey,
|
|
},
|
|
{ idempotencyKey },
|
|
);
|
|
assert.ok(submitted.id);
|
|
|
|
const waited = await adapter.waitForCompletion(submitted.id, {
|
|
timeoutMs: jobWaitTimeoutMs,
|
|
pollIntervalMs: 500,
|
|
});
|
|
assert.ok(waited.id || waited.status);
|
|
|
|
const requeued = await adapter.requeue(submitted.id, { safe: true });
|
|
assert.ok(requeued.id || requeued.status);
|
|
|
|
const listAfter = await adapter.listPage({ limit: 5 });
|
|
assert.ok(Array.isArray(listAfter.jobs));
|
|
return {
|
|
listBefore: listBefore.jobs.length,
|
|
submitted: submitted.id,
|
|
waitedStatus: waited.status,
|
|
requeuedStatus: requeued.status,
|
|
listAfter: listAfter.jobs.length,
|
|
};
|
|
});
|
|
|
|
await runStep("blob", async () => {
|
|
const adapter = createAuthorityBlobAdapter(
|
|
{ authorityBaseUrl: resolvedBaseUrl, authorityBlobNamespace: namespace },
|
|
{ fetchImpl, headerProvider },
|
|
);
|
|
const payload = {
|
|
runId,
|
|
chatId,
|
|
collectionId,
|
|
createdAt: new Date().toISOString(),
|
|
graph: {
|
|
nodes: graph.nodes.length,
|
|
edges: graph.edges.length,
|
|
},
|
|
};
|
|
try {
|
|
const writeResult = await adapter.writeJson(blobPath, payload, {
|
|
metadata: { chatId, runId, purpose: "authority-e2e-contract" },
|
|
});
|
|
assert.equal(writeResult.ok, true);
|
|
|
|
const statResult = await adapter.stat(blobPath);
|
|
assert.equal(statResult.exists, true);
|
|
|
|
const readResult = await adapter.readJson(blobPath);
|
|
assert.equal(readResult.exists, true);
|
|
assert.equal(readResult.payload.runId, runId);
|
|
return {
|
|
path: writeResult.path,
|
|
size: writeResult.size,
|
|
etag: writeResult.etag,
|
|
};
|
|
} finally {
|
|
const deleteResult = await adapter.delete(blobPath).catch(() => null);
|
|
if (deleteResult) assert.equal(deleteResult.ok, true);
|
|
}
|
|
});
|
|
|
|
console.log("authority-server-primary E2E passed");
|