From fbe91d01722b0731bf67a719cf752a356e3ae265 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 20 Sep 2026 04:24:35 +0000 Subject: [PATCH 1/2] fix(knowledge-ragflow): read source.adapterConfig.datasetId, the declared key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `extractRagflowOptions` cast the source to a shape carrying `options` and read `options.datasetId`. `KnowledgeSourceSchema` declares `adapterConfig` and is a plain `z.object`, so any path that parses a source drops `options` entirely — the adapter worked only because no path parses. The cast is removed, the adapter reads the declared key, and the refusal names `adapterConfig.datasetId` so a host on the old spelling is told what to write. The published README example moves with it. Ruled: batch #160 item 2, letter A (maintainer 2026-09-18). Claude-Session: https://claude.ai/code/session_01AhQASwqJr2Z7XfGWUdvnbF Co-authored-by: Claude --- packages/plugins/knowledge-ragflow/README.md | 4 +-- .../src/__tests__/ragflow-adapter.test.ts | 29 +++++++++++++++---- .../plugins/knowledge-ragflow/src/index.ts | 22 +++++++++----- 3 files changed, 40 insertions(+), 15 deletions(-) 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, }; } From 6f17a4f13276ab6798b94eb3064d1f06df6987dc Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 20 Sep 2026 04:45:34 +0000 Subject: [PATCH 2/2] chore(knowledge-ragflow): retire the test-layer type debt the fix graduated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three TS2353 errors this package's test-typecheck ledger froze were one defect: the test wrote `options` on a `KnowledgeSource` literal, a key `KnowledgeSourceSchema` does not declare, because the adapter read that spelling. With the adapter on `adapterConfig` the literals moved with it and tsc reports none, so the entry is deleted — the ledger is shrink-only and a graduated file is red until its entry goes. Adds the changeset for the behaviour change. Claude-Session: https://claude.ai/code/session_01AhQASwqJr2Z7XfGWUdvnbF Co-authored-by: Claude --- ...3-ragflow-reads-declared-adapter-config.md | 22 +++++++++++++++++++ .../test-typecheck-debt.json | 8 ++----- 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 .changeset/18973-ragflow-reads-declared-adapter-config.md 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/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": {} }