mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
fix: support Dify-managed KnowledgeFS deployment
This commit is contained in:
parent
85c8d026a2
commit
0300a4d881
@ -43,6 +43,7 @@ ENV NODE_ENV=production
|
||||
WORKDIR /workspace
|
||||
|
||||
COPY --from=builder /workspace/apps/api/dist/server.mjs ./server.mjs
|
||||
COPY --from=builder /workspace/apps/api/dist/migrate.mjs ./migrate.mjs
|
||||
|
||||
EXPOSE 8787
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc --noEmit",
|
||||
"build:prod": "esbuild src/server.ts --bundle --platform=node --format=esm --target=node22 --banner:js=\"import { createRequire as __knowledgeCreateRequire } from 'node:module';const require = __knowledgeCreateRequire(import.meta.url);\" --outfile=dist/server.mjs",
|
||||
"build:prod": "esbuild src/server.ts src/migrate.ts --bundle --platform=node --format=esm --target=node22 --banner:js=\"import { createRequire as __knowledgeCreateRequire } from 'node:module';const require = __knowledgeCreateRequire(import.meta.url);\" --outdir=dist --out-extension:.js=.mjs",
|
||||
"dev": "NODE_ENV=development node --env-file-if-exists=../../infra/local/.env --import tsx --watch src/server.ts",
|
||||
"start": "node dist/server.mjs",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
|
||||
@ -116,10 +116,10 @@ describe("API durable deletion assembly", () => {
|
||||
const repository = repositoryStub();
|
||||
const assembly = createApiDurableDeletionAssembly({
|
||||
adapter: createNodePlatformAdapter({ env: {} }),
|
||||
credentialMode: "dify-managed",
|
||||
enabled: true,
|
||||
production: true,
|
||||
repository,
|
||||
secretStore: secretStoreStub(),
|
||||
usesDatabaseRepositories: true,
|
||||
});
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ export interface ApiDurableDeletionAssembly {
|
||||
|
||||
export interface CreateApiDurableDeletionAssemblyOptions {
|
||||
readonly adapter: KnowledgeGatewayOptions["adapter"];
|
||||
readonly credentialMode?: "dify-managed" | "local" | undefined;
|
||||
readonly enabled: boolean;
|
||||
readonly production: boolean;
|
||||
readonly repository?: DurableDeletionRepository | undefined;
|
||||
@ -44,6 +45,7 @@ export async function assertApiDurableDeletionDataReadiness({
|
||||
/** Production is fail-closed: HTTP request persistence and both background loops are one unit. */
|
||||
export function createApiDurableDeletionAssembly({
|
||||
adapter,
|
||||
credentialMode = "local",
|
||||
enabled,
|
||||
production,
|
||||
repository,
|
||||
@ -62,15 +64,16 @@ export function createApiDurableDeletionAssembly({
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (!secretStore) {
|
||||
if (credentialMode === "local" && !secretStore) {
|
||||
throw new Error("Durable deletion requires SourceSecretStore cleanup capability");
|
||||
}
|
||||
|
||||
const capabilities = createDatabaseDurableDeletionTargetCapabilities({
|
||||
cache: adapter.cache,
|
||||
credentialMode,
|
||||
database: adapter.database,
|
||||
objectStorage: adapter.objectStorage,
|
||||
secretStore,
|
||||
...(secretStore ? { secretStore } : {}),
|
||||
});
|
||||
const processor = createDurableDeletionTargetProcessors({
|
||||
documentAsset: capabilities,
|
||||
|
||||
@ -228,6 +228,7 @@ await assertApiDurableDeletionDataReadiness({
|
||||
});
|
||||
const durableDeletion = createApiDurableDeletionAssembly({
|
||||
adapter,
|
||||
credentialMode: "dify-managed",
|
||||
enabled: databaseRepositories.durableDeletionEnabled,
|
||||
production: process.env.NODE_ENV === "production",
|
||||
repository: databaseRepositories.durableDeletionRepository,
|
||||
|
||||
@ -23,6 +23,7 @@ describe("API Dockerfile production runtime", () => {
|
||||
const dockerfile = readFileSync(resolve(import.meta.dirname, "../Dockerfile"), "utf8");
|
||||
|
||||
expect(dockerfile).toContain('CMD ["node", "server.mjs"]');
|
||||
expect(dockerfile).toContain("dist/migrate.mjs ./migrate.mjs");
|
||||
expect(dockerfile).toContain("USER node");
|
||||
expect(dockerfile).not.toContain('"tsx"');
|
||||
});
|
||||
@ -61,5 +62,6 @@ describe("API source dev script", () => {
|
||||
|
||||
expect(packageJson.scripts?.["build:prod"]).toContain("--format=esm");
|
||||
expect(packageJson.scripts?.["build:prod"]).toContain("createRequire");
|
||||
expect(packageJson.scripts?.["build:prod"]).toContain("src/migrate.ts");
|
||||
});
|
||||
});
|
||||
|
||||
@ -118,6 +118,32 @@ describe("database durable deletion target capabilities", () => {
|
||||
).rejects.toThrow("has no cache key");
|
||||
});
|
||||
|
||||
it(`rejects legacy local credential residue in Dify-managed mode (${dialect})`, async () => {
|
||||
const capabilities = createDatabaseDurableDeletionTargetCapabilities({
|
||||
cache: createMemoryCacheAdapter({ maxEntries: 10 }),
|
||||
credentialMode: "dify-managed",
|
||||
database: createSchemaDatabaseAdapter({
|
||||
executor: async () => result([]),
|
||||
kind: dialect,
|
||||
}),
|
||||
objectStorage: createMemoryObjectStorageAdapter({
|
||||
kind: "memory",
|
||||
maxObjectBytes: 1_024,
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(
|
||||
capabilities.executeExternalItem({
|
||||
item: deletionItem("secret_ref", {
|
||||
credentialRef: "source-secret:v1:legacy",
|
||||
resourceId: targetDocumentId,
|
||||
}),
|
||||
job: job(),
|
||||
signal: new AbortController().signal,
|
||||
}),
|
||||
).rejects.toThrow("legacy KnowledgeFS-managed source credential cleanup");
|
||||
});
|
||||
|
||||
it(`resumes bounded document object inventory across database, manifest, and storage phases (${dialect})`, async () => {
|
||||
const prefix = `tenant-a/spaces/${spaceId}`;
|
||||
const artifactKey = `${prefix}/documents/${targetDocumentId}/artifact.json`;
|
||||
|
||||
@ -44,10 +44,11 @@ import type { SourceSecretStore } from "./source-secret-store";
|
||||
|
||||
export interface DatabaseDurableDeletionTargetCapabilitiesOptions {
|
||||
readonly cache: CacheAdapter;
|
||||
readonly credentialMode?: "dify-managed" | "local" | undefined;
|
||||
readonly database: DatabaseAdapter;
|
||||
readonly generatePublicationId?: (() => string) | undefined;
|
||||
readonly objectStorage: ObjectStorageAdapter;
|
||||
readonly secretStore: Pick<SourceSecretStore, "delete">;
|
||||
readonly secretStore?: Pick<SourceSecretStore, "delete"> | undefined;
|
||||
}
|
||||
|
||||
interface InventoryCursor {
|
||||
@ -72,6 +73,7 @@ interface InventoryCursor {
|
||||
*/
|
||||
export function createDatabaseDurableDeletionTargetCapabilities({
|
||||
cache,
|
||||
credentialMode = "local",
|
||||
database,
|
||||
generatePublicationId = randomUUID,
|
||||
objectStorage,
|
||||
@ -80,6 +82,9 @@ export function createDatabaseDurableDeletionTargetCapabilities({
|
||||
if (!cache.deletePrefix) {
|
||||
throw new DeletionCleanupCapabilityUnavailableError("cache.deletePrefix");
|
||||
}
|
||||
if (credentialMode === "local" && !secretStore) {
|
||||
throw new DeletionCleanupCapabilityUnavailableError("SourceSecretStore.delete");
|
||||
}
|
||||
const deleteCachePrefix = cache.deletePrefix.bind(cache);
|
||||
const retrievalExecutionLeases = createDatabaseRetrievalExecutionLeaseRepository({ database });
|
||||
|
||||
@ -325,6 +330,11 @@ export function createDatabaseDurableDeletionTargetCapabilities({
|
||||
if (!item.credentialRef || !item.resourceId) {
|
||||
throw new Error("Durable deletion secret item is incomplete");
|
||||
}
|
||||
if (!secretStore) {
|
||||
throw new DeletionCleanupCapabilityUnavailableError(
|
||||
"legacy KnowledgeFS-managed source credential cleanup",
|
||||
);
|
||||
}
|
||||
await secretStore.delete({
|
||||
knowledgeSpaceId: job.knowledgeSpaceId,
|
||||
ref: item.credentialRef,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user