diff --git a/.changeset/18973-ragflow-reads-declared-adapter-config.md b/.changeset/18973-ragflow-reads-declared-adapter-config.md new file mode 100644 index 00000000000..94d481eb18d --- /dev/null +++ b/.changeset/18973-ragflow-reads-declared-adapter-config.md @@ -0,0 +1,22 @@ +--- +"@objectstack/knowledge-ragflow": patch +--- + +The RAGFlow adapter now reads the declared key: a source's RAGFlow binding comes from `adapterConfig.datasetId`, not `options.datasetId`. + +`KnowledgeSourceSchema` declares `adapterConfig` for adapter-specific configuration and is a plain `z.object` — it carries no `.passthrough()`, so any path that parses a source drops `options` before an adapter ever sees it. The adapter read `options` through a cast, which worked only because no path parses a source today. The cast is gone; there is no fallback that also reads `options` (Prime Directive #12 — one strict contract, no lenient consumer). + +Migration, `FROM` → `TO`, one line per source: + +```ts +// FROM +{ id: 'product_docs', adapter: 'ragflow', options: { datasetId: 'rgf_…' } } +// TO +{ id: 'product_docs', adapter: 'ragflow', adapterConfig: { datasetId: 'rgf_…' } } +``` + +The same move applies to `rerankModel`, `similarityThreshold` and `vectorSimilarityWeight`, which the adapter reads from the same bag. A source left on the old spelling is refused by name — `RAGFlow adapter requires source.adapterConfig.datasetId on source ''` — rather than silently retrieving nothing, so the upgrade is self-describing at the first call. Nothing an author could declare is removed: `options` was never a key `KnowledgeSourceSchema` accepted, which is why this carries no ADR-0087 conversion. + +The package's published `README.md` moves with the adapter and now compiles against it — it was the one block of the 44 that #18915 could not repair, because correcting the spelling alone would have compiled and stopped working. + +Clause-②: no diff --git a/packages/plugins/knowledge-ragflow/README.md b/packages/plugins/knowledge-ragflow/README.md index dd17caa000f..c37eadb4744 100644 --- a/packages/plugins/knowledge-ragflow/README.md +++ b/packages/plugins/knowledge-ragflow/README.md @@ -25,7 +25,7 @@ kernel.use(new KnowledgeServicePlugin({ label: 'Product documentation', adapter: 'ragflow', source: { kind: 'http', urls: ['https://docs.example.com/sitemap.xml'] }, - options: { datasetId: 'rgf_doc_dataset_id' }, // RAGFlow dataset to bind + adapterConfig: { datasetId: 'rgf_doc_dataset_id' }, // RAGFlow dataset to bind }], })); kernel.use(new KnowledgeRagflowPlugin({ @@ -36,7 +36,7 @@ kernel.use(new KnowledgeRagflowPlugin({ ## Source binding -Each `KnowledgeSource` must include `options.datasetId` pointing to a pre-created RAGFlow dataset. The adapter doesn't create datasets — operators do that once in the RAGFlow UI, where they pick the chunking method, embedding model, and rerank policy. +Each `KnowledgeSource` must include `adapterConfig.datasetId` pointing to a pre-created RAGFlow dataset. `adapterConfig` is the key `KnowledgeSourceSchema` declares for adapter-specific configuration; a source that spells it anything else loses it on any parsing path, and the adapter refuses it by name. The adapter doesn't create datasets — operators do that once in the RAGFlow UI, where they pick the chunking method, embedding model, and rerank policy. ## What the adapter does diff --git a/packages/plugins/knowledge-ragflow/src/__tests__/ragflow-adapter.test.ts b/packages/plugins/knowledge-ragflow/src/__tests__/ragflow-adapter.test.ts index d3c77e7c62a..0300ce80bf1 100644 --- a/packages/plugins/knowledge-ragflow/src/__tests__/ragflow-adapter.test.ts +++ b/packages/plugins/knowledge-ragflow/src/__tests__/ragflow-adapter.test.ts @@ -9,7 +9,7 @@ const source: KnowledgeSource = { label: 'Docs', adapter: 'ragflow', source: { kind: 'http', urls: ['https://docs.example.com'] } as KnowledgeSource['source'], - options: { datasetId: 'ds_42' }, + adapterConfig: { datasetId: 'ds_42' }, }; function fakeFetch(handler: (url: string, init?: any) => unknown): { fetch: FetchLike; calls: Array<{ url: string; init: any }> } { @@ -30,11 +30,30 @@ function fakeFetch(handler: (url: string, init?: any) => unknown): { fetch: Fetc } describe('KnowledgeRagflowAdapter', () => { - it('rejects sources without datasetId', async () => { + it('rejects sources without datasetId, naming the declared key', async () => { const { fetch } = fakeFetch(() => ({})); const a = new KnowledgeRagflowAdapter({ endpoint: 'http://x', apiKey: 'k', fetch }); - const bad: KnowledgeSource = { ...source, options: {} as Record }; - await expect(a.search('q', { source: bad, topK: 1 })).rejects.toThrow(/datasetId/); + const bad: KnowledgeSource = { ...source, adapterConfig: {} }; + // The refusal text is the migration notice a host reads, so it is pinned: + // it must name `adapterConfig.datasetId`, the key the schema declares. + await expect(a.search('q', { source: bad, topK: 1 })).rejects.toThrow( + /source\.adapterConfig\.datasetId/, + ); + }); + + it('does not read the undeclared `options` spelling', async () => { + // `KnowledgeSourceSchema` is a plain `z.object`: it declares `adapterConfig` + // and drops `options` on any parsing path. The adapter reads the declared + // key only — no lenient fallback (Prime Directive #12). A host still on the + // old spelling is refused loudly rather than served with silence. + const { fetch, calls } = fakeFetch(() => ({})); + const a = new KnowledgeRagflowAdapter({ endpoint: 'http://x', apiKey: 'k', fetch }); + const { adapterConfig: _dropped, ...rest } = source; + const legacy = { ...rest, options: { datasetId: 'ds_42' } } as unknown as KnowledgeSource; + await expect(a.search('q', { source: legacy, topK: 1 })).rejects.toThrow( + /source\.adapterConfig\.datasetId/, + ); + expect(calls).toHaveLength(0); }); it('upsert deletes-then-creates chunks and stamps objectstack metadata', async () => { @@ -106,7 +125,7 @@ describe('KnowledgeRagflowAdapter', () => { const a = new KnowledgeRagflowAdapter({ endpoint: 'http://r', apiKey: 'k', fetch }); const s: KnowledgeSource = { ...source, - options: { datasetId: 'ds_42', rerankModel: 'bge-reranker', similarityThreshold: 0.6 }, + adapterConfig: { datasetId: 'ds_42', rerankModel: 'bge-reranker', similarityThreshold: 0.6 }, }; await a.search('q', { source: s, topK: 3, filter: { tag: 'a' } }); const body = JSON.parse(calls[0].init.body); diff --git a/packages/plugins/knowledge-ragflow/src/index.ts b/packages/plugins/knowledge-ragflow/src/index.ts index 4a6f2336f76..a78748dfc1f 100644 --- a/packages/plugins/knowledge-ragflow/src/index.ts +++ b/packages/plugins/knowledge-ragflow/src/index.ts @@ -57,23 +57,29 @@ interface RagflowSourceOptions { vectorSimilarityWeight?: number; } +/** + * Reads this source's RAGFlow binding from `adapterConfig` — the key + * `KnowledgeSourceSchema` declares for adapter-specific configuration. + * That schema is a plain `z.object`, so any parsing path drops keys it + * does not declare; the adapter therefore reads the declared key and no + * other spelling (Prime Directive #12 — no lenient consumer). + */ function extractRagflowOptions(source: KnowledgeSource): RagflowSourceOptions { - const opts = ((source as unknown as { options?: Record }).options ?? {}) as - Record; - const datasetId = opts.datasetId; + const cfg = source.adapterConfig ?? {}; + const datasetId = cfg.datasetId; if (typeof datasetId !== 'string' || !datasetId) { throw new Error( - `RAGFlow adapter requires source.options.datasetId on source '${source.id}'`, + `RAGFlow adapter requires source.adapterConfig.datasetId on source '${source.id}'`, ); } return { datasetId, - rerankModel: typeof opts.rerankModel === 'string' ? opts.rerankModel : undefined, + rerankModel: typeof cfg.rerankModel === 'string' ? cfg.rerankModel : undefined, similarityThreshold: - typeof opts.similarityThreshold === 'number' ? opts.similarityThreshold : undefined, + typeof cfg.similarityThreshold === 'number' ? cfg.similarityThreshold : undefined, vectorSimilarityWeight: - typeof opts.vectorSimilarityWeight === 'number' - ? opts.vectorSimilarityWeight + typeof cfg.vectorSimilarityWeight === 'number' + ? cfg.vectorSimilarityWeight : undefined, }; } diff --git a/packages/plugins/knowledge-ragflow/test-typecheck-debt.json b/packages/plugins/knowledge-ragflow/test-typecheck-debt.json index 8d6dbc1fdf0..4880c6397a4 100644 --- a/packages/plugins/knowledge-ragflow/test-typecheck-debt.json +++ b/packages/plugins/knowledge-ragflow/test-typecheck-debt.json @@ -1,9 +1,5 @@ { "_comment": "Per-file tsc error debt of the @objectstack/knowledge-ragflow TEST layer (#5286). `tsconfig.test.json` compiles `src/**/*.test.ts` — which `tsconfig.json` excludes and therefore no gate ever read — and every file below still carries errors from before that gate existed. THIS FIELD IS GENERATED: every regeneration rewrites it from scripts/check-test-typecheck.mts, and the EXACT ratchet below requires a regeneration on every repair — so an edit made here is gone by the next one. Anything true of THIS package goes in the sibling `_note` field, which is authored, is preserved verbatim, and is never written by the generator (#12624). This comment states NO cause for the errors, deliberately: the classes differ per package and per file, they move as the debt is paid down, and a cause written here is rewritten verbatim into every ledger by every regeneration — so it outlives its own repair and cannot be corrected in the file where it is read. Measure instead, before repairing anything: `tsc --noEmit --pretty false -p tsconfig.test.json` in the package prints the real classes with their TS codes. Each entry maps a file to its per-SIGNATURE error counts, never to a bare total (#13470): a signature is the TS code plus the diagnostic message with structural type blobs collapsed, and it carries NO line or column — so the pin survives edits that move code around, and only stops matching when the error itself becomes a different error. EXACT ratchet, judged by re-running tsc: a file that gains errors is red, a file that loses them is red until its number is re-recorded, a file that reaches zero is red until its entry is deleted, a signature that ARRIVES or VANISHES is red even when the file total is unchanged, and a file NOT listed here may have no errors at all. Regenerate with: pnpm --filter @objectstack/knowledge-ragflow gen:test-typecheck-debt", - "_note": "STARTING LEDGER, opened by #14062 under the director ruling of 2026-09-01 (maintainer verbatim: 「同意」), which carries the #5286 maintainer authority for it. 3 errors in 1 file, all PRE-EXISTING — and this package was silent for a DIFFERENT reason than its siblings: its `tsconfig.json` never excluded tests, so a tsc program would have read them, but the package declared NO `typecheck` script at all, and `turbo run typecheck` cannot run a script that does not exist. #14062 added one naming this gate. ⛔ That is not the repo-wide 'packages missing a `typecheck` script' carry-over, which the same ruling holds separate (item 5): this is the one invocation path #14062's own instrument needs in order to run here at all.", - "entries": { - "src/__tests__/ragflow-adapter.test.ts": { - "TS2353: Object literal may only specify known properties, and 'options' does not exist in type '…'.": 3 - } - } + "_note": "STARTING LEDGER, opened by #14062 under the director ruling of 2026-09-01 (maintainer verbatim: 「同意」), which carries the #5286 maintainer authority for it. 3 errors in 1 file, all PRE-EXISTING — and this package was silent for a DIFFERENT reason than its siblings: its `tsconfig.json` never excluded tests, so a tsc program would have read them, but the package declared NO `typecheck` script at all, and `turbo run typecheck` cannot run a script that does not exist. #14062 added one naming this gate. ⛔ That is not the repo-wide 'packages missing a `typecheck` script' carry-over, which the same ruling holds separate (item 5): this is the one invocation path #14062's own instrument needs in order to run here at all. GRADUATED — the ledger is now empty. All 3 errors were one defect: the test wrote `options` on a `KnowledgeSource` literal, a key `KnowledgeSourceSchema` does not declare, because the adapter read that spelling. The adapter now reads the declared `adapterConfig` (ruled: batch #160 item 2, letter A) and the literals moved with it, so tsc reports none. ⛔ This file is kept, not deleted: it is the per-file ledger `check:test-typecheck` reads for this package, and an empty `entries` is the pin that the test layer owes nothing.", + "entries": {} }