Repository navigation
fix(service-settings): register the ai.test_embedder and knowledge.test handlers by default - #24830
Conversation
…n reaches a handler Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y Co-authored-by: Claude <noreply@anthropic.com>
…st handlers by default Both buttons were declared and their handlers exported, but the default actionHandlers map never registered them, so each answered 400 'No handler registered'. Their success messages now say they check the form only and no request was sent: nothing in this repository replaces either with a live probe. Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y Co-authored-by: Claude <noreply@anthropic.com>
…andlers Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y Co-authored-by: Claude <noreply@anthropic.com>
…ttings-test-handlers
📓 Docs Drift Check4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 9 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 befbee0352a3b4decd6c6b65fef5977247098c73 && git checkout befbee0352a3b4decd6c6b65fef5977247098c73
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 87c09ebb7ac768b43d137996296d042a3cee1c97 62b892d56b0bb0b0927a4d999aeec84d7bb21039 && git checkout -B drift-repro 87c09ebb7ac768b43d137996296d042a3cee1c97 && git merge --no-ff 62b892d56b0bb0b0927a4d999aeec84d7bb21039
node scripts/docs-audit/affected-docs.mjs --json 87c09ebb7ac768b43d137996296d042a3cee1c97 |
席位核对ACCEPT · #24769 ·
Generated by Claude Code |
Fixes #24769
Clause-②: no
What was wrong
Setup's AI "Test embedder" and Knowledge "Test connection" buttons both answered
400 SETTINGS_ACTION_FAILED, with "No handler registered for action 'test_embedder' in 'ai'." and "No handler registered for action 'test' in 'knowledge'.". The manifests declare both buttons, andaiTestEmbedderActionHandlerandknowledgeTestActionHandlerare exported beside them. ButSettingsServicePlugin's defaultactionHandlersmap registered onlymail.test,sms.test,storage.testandai.test.Nothing registers either action later. At
a43405d59,git grep "registerAction("finds only three registrations against a settings namespace outside this package: plugin-email (mail), service-sms (sms) and service-storage (storage).@objectstack/service-knowledge,@objectstack/knowledge-memory,@objectstack/knowledge-ragflowand@objectstack/embedder-openainever reference the settings service. So the PM's mechanism assumption M2 holds on the "no override" half: in this repository the stubs are the only possible handlers.@objectstack/service-aiisedition: 'cloud'and not in this repository, so whether it overridesai/test_embedderis NOT MEASURED.Landing:
packages/services/service-settings/src/settings-service-plugin.ts, as the card expected. The defect is in the producer of the default map, not in a consumer.The fix
ai: { test, test_embedder }andknowledge: { test }. Override semantics are unchanged (M3, measured): a host's ownactionHandlersstill replaces the whole default map (opts.actionHandlers ?? DEFAULTS), andSettingsService.registerAction(aMap.set) still lets a plugin replace any one handler later. The option's docblock used to say only "the bundledmail.testhandler is registered automatically". It now names the default set and says that passing the option replaces the whole map.storage/testwith a live probe. Neither embedder-openai nor knowledge-ragflow overrides anything, so mounting them never made either button live. Both phrases are removed. Every passing answer on a remote backend now says what it is: "This checks the form only — no embedding request was sent." for the embedder, and "This checks the form only — no connection was attempted." for Turso and RAGFlow. Two handler docblocks claimed a live-probe override "in@objectstack/service-knowledge". That is false, and they now state what was measured.What each button checks after this change
Neither button touches the network. Each merges the posted form state (a nested
valuesobject or a bare key map) over the saved values, then:AI → "Test embedder" (
POST /api/settings/ai/test_embedder)embedder_providerunset ornone:ok: false, warning, "Embedder is disabled. Pick a provider to enable knowledge / RAG."ollamawith noembedder_api_key:ok: false, error.custom/azurewith noembedder_base_url:ok: false, error.ok: true, info, "PROVIDER embedder settings are complete (model=MODEL). This checks the form only — no embedding request was sent."Knowledge → "Test connection" (
POST /api/settings/knowledge/test)memory:ok: true, warning, "In-memory adapter — no external service to probe. …" (unchanged).tursowith no URL:ok: true, info, the reuse-the-tenant's-connection note, plus "This checks the form only — no connection was attempted."tursowith alibsql://URL and no auth token:ok: false, error.tursowith any other URL:ok: true, info, "Turso adapter configured (URL). This checks the form only — no connection was attempted." The existing sentence about@objectstack/knowledge-tursofollows, unchanged.ragflowwith no Base URL or no API key:ok: false, error.ragflowcomplete:ok: true, info, "RAGFlow adapter configured (URL). This checks the form only — no connection was attempted."ok: false, error.The route is unchanged:
ok: trueanswers 200 with the result, andok: falseanswers 400SETTINGS_ACTION_FAILEDwith the result undererror.details.The pin, and the class it closes
src/settings-default-action-handlers.pin.test.tsreads every builtin manifest'shttp-kindaction_buttonwhose URL is the settings action route. It constructs the plugin with no options, runsinit(), and drives each declared action throughrunActionon the service the plugin registered. Every handler the plugin registers is wrapped to record its own invocation. An action counts as honoured when a registered handler ran, or whenrunActionansweredokwithout one (its built-inreset, served for every namespace). The no-handler answer is neither. No message text is used as the discriminator.The file holds a floor: the population must contain all seven current pairs. A second assertion checks that each button's route names its own namespace and id. Two HTTP-door cases replay the card's own two requests through
registerSettingsRoutes. A new button declared without its handler, or a handler dropped from the default map, now goes red by name.Red before, green after
Run with the test commit
a66a74166on the unchanged plugin (basea43405d59):Tests 3 failed | 2 passed (5).With the fix, on
62b892d56(the final head,origin/main307ce4c4bmerged in):pnpm --filter @objectstack/service-settings exec vitest run --maxWorkers=2reportsTest Files 54 passed (54),Tests 899 passed (899).pnpm --filter @objectstack/service-settings typecheckexits 0.tsc --listFilesincludes all three touched test files.Ablations (one-off, run at
c8dbf1395; nothing permanent was added)All three mutations went through
scripts/ablation-replace.mjsin WRAP mode. Each anchor hit once, the blob changed, and the restore was proven: blob equal to HEAD, andgit diff HEADempty. The tests resolve this package's own source through relative imports, and@objectstack/*through the vitest source aliases, so nodist/is involved. Every ablation went red, the expected direction.knowledge: { test: knowledgeTestActionHandler }from the default map.Tests 2 failed | 3 passed (5). The failures wereexpected [ 'knowledge/test' ] to deeply equal []and, at the HTTP door,expected 400 to be 200. Restored blob716f8a5cda1dequals HEAD.Tests 1 failed | 27 passed (28):expected 'openai embedder settings are complete…' not to match /to exercise live calls/. Restored blobbb1ad034d9adequals HEAD.Tests 1 failed | 24 passed (25):ragflow: expected 'RAGFlow adapter configured (http://lo…' not to match /to exercise live calls/. Restored blob396195582b05equals HEAD.B and C keep the "no … was sent / attempted" sentence and add the old promise, so the negative pin fires on its own.
Gates (local subset, on
62b892d56)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 67 commands from the 7 changed paths, against merge base307ce4c4b. Each was run with its exit code captured before any pipe.node scripts/check-plugin-teardown-shape.mjs --self-testfirst exited 3 because the shallow clone could not reach its pinned fixture commit. Once that commit was present it re-ran: exit 0, "48 cases pass".pnpm check:dual-build-cjs-loadsexited 3,PREREQUISITE NOT MET. It reads every package'sdist/, and this worktree has built only the service-settings closure. NOT MEASURED locally; CI'sLint & Repo Gatesruns it.--ranreconciliation: "67 derived famil(ies) accounted for — 66 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", 0 UNRUN.turbo run build --filter='@objectstack/service-settings...'(16 tasks, exit 0).test:repowas not run locally; it belongs to CI (Test Core).Acceptance notes
knowledgenamespace and theai.embedder_*keys: the only readers are service-settings' own manifests, tests and visibility evaluator.@objectstack/knowledge-ragflowand@objectstack/embedder-openaitake their configuration from plugin constructor options. Whether a cloud runtime reads these values was not measured. Not filed: this is a read-only inference with no public-door measurement. Carrier: none.aiTestActionHandler's docblock is unverified. It still says the realai/testlives in@objectstack/service-aiand overrides the stub onkernel:ready. That package is cloud-edition, so this could not be measured here. Left untouched, becauseai.testwas already registered and is not this card's button.init()registers each default handler throughregisterAction, which throwsUnknownNamespaceErrorfor an unregistered namespace. A host that passes its ownmanifestswithoutknowledge, and does not passactionHandlers, would now fail at boot. The same edge already existed formail/sms/storage/ai. Every in-repo site that passesmanifestsalso passesactionHandlers: {}; the sites that pass nomanifestsget the builtin set, which includesknowledge.config-change-audit.test.tsandsettings-admission-tenancy-posture.test.tsdescribe the bundled handlers asmail/sms/storage/ai. The reason those comments give still holds; they are outside this card's file surface and were not edited.ai.manifest.test.tsand the RAGFlow test inknowledge.manifest.test.tspinned the two removed "Mount … to exercise live calls" sentences, kept because those packages are built here. The packages do exist, but the promise was false for these buttons. The replacements pin the new property, and the AI live-call branches are still each exercised on their own.Risk and rollback
The change is additive registration plus answer text. Rollback is a revert of
c8dbf1395: the two buttons go back to answering 400.Changeset:
.changeset/24769-settings-test-handlers.md,@objectstack/service-settingspatch.Generated by Claude Code