fix(knowledge-ragflow): read source.adapterConfig.datasetId, the declared key - #19251
Conversation
…ared key `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 <noreply@anthropic.com>
…duated 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 <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a0af214409ba7dd2192eb022aa6a2b7b64c40c14 && git checkout a0af214409ba7dd2192eb022aa6a2b7b64c40c14
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1b82c519df9db93891514b814b4d8e16e42d5d5c 6f17a4f13276ab6798b94eb3064d1f06df6987dc && git checkout -B drift-repro 1b82c519df9db93891514b814b4d8e16e42d5d5c && git merge --no-ff 6f17a4f13276ab6798b94eb3064d1f06df6987dc
node scripts/docs-audit/affected-docs.mjs --json 1b82c519df9db93891514b814b4d8e16e42d5d5c |
…lared options key
`content/docs/ai/knowledge-rag.mdx` configured its `ragflow` knowledge source with
`options: { datasetId }`. `KnowledgeSourceSchema` declares `adapterConfig` and is a plain
`z.object` with no `.passthrough()`, so the key is dropped before any adapter sees it.
Since #19251 (card #18973, ruling batch #160 item 2, letter A) `extractRagflowOptions`
reads `source.adapterConfig` with no fallback, so a source copied from this page is
refused by name at the first upsert / search / delete:
RAGFlow adapter requires source.adapterConfig.datasetId on source '<id>'
One line, one page. No spec change and no ADR-0087 conversion: `options` was never a key
`KnowledgeSourceSchema` accepted, so nothing an author could declare is removed.
Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Co-authored-by: Claude <noreply@anthropic.com>
Fixes #18973
Clause-②: no
Ruled on-card: batch #160 item 2, letter A, maintainer 「同意」 2026-09-18 (
issuecomment-5729652660) — "the spec wins:@objectstack/knowledge-ragflowreadssource.adapterConfig.datasetId, its README says the same, andKnowledgeSourceSchemais untouched".packages/specis untouched here, as ruled.What was wrong
extractRagflowOptionscast the source to a shape it does not have and readoptions.datasetId:KnowledgeSourceSchemadeclaresadapterConfigfor adapter-specific configuration and is a plainz.objectwith no.passthrough(), so any path that parses a source dropsoptionsbefore an adapter sees it. The adapter worked only because nothing parses a source today. The published README documented the undeclared spelling, which made it the one block of #18915's 44 that could not be repaired: correcting the word alone would have compiled and stopped working.What changed
src/index.ts— the cast is gone.extractRagflowOptionsreadssource.adapterConfig, a declared, already-typed property, so no cast is needed at all. There is no fallback that also readsoptions(Prime Directive Add comprehensive test suite for Zod schema validation #12 — no lenient consumer). The refusal now names the declared key:RAGFlow adapter requires source.adapterConfig.datasetId on source 'SOURCE_ID'(the source's own id interpolated), so a host on the old spelling is told what to write instead of retrieving nothing.README.md— the example and the "Source binding" sentence move toadapterConfig, and the block now compiles against the package (evidence below).src/__tests__/ragflow-adapter.test.ts— the threeKnowledgeSourceliterals move with it; the refusal test now pins the message text (the ruling relies on it naming the key, so the wording is contractual here), and a new test pins that a source carrying only the legacyoptionsspelling is refused and reaches no transport at all.test-typecheck-debt.json— emptied. See below; this was not an incidental repair.@objectstack/knowledge-ragflowpatch, behaviour: reads the declared key, with theFROM→TOmapping in the body.Four call sites, one repair — and a correction to the dispatch order
The card body names one call site (
:108). There are four sites onorigin/main— the definition at:60and calls at:108,:136,:144— and the dispatch order carried that as "a repair that fixes one call site is not the repair". Measured on this branch's base: no call site needed editing. All three calls pass the wholesourceand destructure the result; the undeclared key was read in exactly one place, the definition's cast. The four-site count is correct and the inference drawn from it is not: the repair is one function body, and it covers all three callers.grep -n "options" src/index.tsafter the change returns one line —pass options.fetchin the adapter constructor's own error, which is aboutKnowledgeRagflowAdapterOptionsand not about aKnowledgeSource.The type-debt ledger was this same defect, frozen
packages/plugins/knowledge-ragflow/test-typecheck-debt.jsonpinned 3 errors in one file, allTS2353: Object literal may only specify known properties, and 'options' does not exist in type '…'. Those three errors were this card: the test wrote the undeclared key because the adapter read it. With the adapter onadapterConfigthe file graduates to zero, and the ledger is shrink-only — a graduated file is red until its entry is deleted.check:test-typechecksaid so in as many words, and the entry is now gone. The file is kept with an emptyentriesmap (it is the per-file ledger the gate reads for this package), and its authored_noterecords the graduation; the note survives regeneration, verified by running the generator twice.Two shipped surfaces already said
adapterConfigThe adapter was the outlier, not the schema. Both of these ship today:
packages/services/service-settings/src/manifests/knowledge.manifest.ts:97— "Per-source values on KnowledgeSource.adapterConfig take precedence", in all four translated locales.skills/objectstack-ai/SKILL.md— "they belong to the adapter (adapterConfig) or application code", above an example that callsKnowledgeSourceSchema.parse({ … adapter: 'ragflow' … }).Neither is touched by this PR; they are cited because they make the ruled direction the one that leaves the repo self-consistent.
Evidence
The README block compiles, and the measurement can fail.
measure-markdown-ts-blocksis a census, not a gate, so a bare green from it is worth little — it was run in both directions, at6f17a4f13:options(viascripts/ablation-replace.mjs, anchor hit 1→0, blobc37eadb4744e→55123d3e9226)TS2353 x1The instrument's own
FIRING_CONTROLreported 5 diagnostics on both runs, so the zero is a reading and not a dead search. Restore was proven byte-identical bygit hash-object(c37eadb4744e…before and after) withgit diff HEADclean, not by the wrapper's exit code.Gates.
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 60 families from this change set; all 60 were run and all recorded exit 0, reconciled with--ran:60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN— a derived zero, since every family recorded its code. Two answered exit 3 (PREREQUISITE NOT MET — not a pass) on the first pass,check:dual-build-cjs-loadsandcheck:type-check-debt; both state the same prerequisite, a built workspace. It was cleared (turbo run build --filter='./packages/*' --filter='./packages/*/*', 72/72 successful) and both re-run green.Per-package.
pnpm --filter @objectstack/knowledge-ragflow test→ 10 passed (was 9).pnpm --filter @objectstack/knowledge-ragflow typecheck→ exit 0 across both of its legs (tsc --noEmit, thencheck:test-typecheck, which reports 0 files / 0 errors / 0 pinned signatures).pnpm lintover the whole repo → exit 0.Acceptance notes
Noted here, not fixed, not filed by this PR:
content/docs/ai/knowledge-rag.mdx:37still writesoptions: { datasetId: 'rgf_doc_dataset' }on aragflowsource. It is the same defect in a second, hand-written document, and after this PR it is a live one: an author copying it now gets a source the adapter refuses by name. It is outside this card's file surface (packages/plugins/knowledge-ragflow/), andcontent/docs/**brings its own gate family, so it is reported to the dispatching seat to file rather than ridden in here..changeset/18915-published-readme-examples-compile.mdcloses with "One block is deliberately left", describing this README. That changeset belongs to PR docs(readme): make the published README TypeScript examples compile #18968 and covers 20 other packages; its text becomes stale when both land. Left alone deliberately — it accurately records what that PR did — and the closure is stated in this PR's own changeset instead.Errors, with no ADR-0112 envelope (code/status) on any refusal, so the new refusal test pins the message text rather than an envelope. Introducing an envelope on this seam is a contract decision well outside a patch to one adapter's key spelling.KnowledgeSourceSchema⇒ p1) is still unmet in runtime code:git grep KnowledgeSourceSchemaoutsidepackages/specreturns zero importers inpackages/**/*.ts(positive control: 8,741*Schema.parse|safeParsecall sites repo-wide). The only parse call sites arepackages/spec's own tests and a published-skill example.Generated by Claude Code