Repository navigation
Commit 7697ddc
fix(service-settings): register the ai.test_embedder and knowledge.test handlers by default (#24830)
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, and
`aiTestEmbedderActionHandler` and `knowledgeTestActionHandler` are
exported beside them. But `SettingsServicePlugin`'s default
`actionHandlers` map registered only `mail.test`, `sms.test`,
`storage.test` and `ai.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-ragflow` and `@objectstack/embedder-openai`
never 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-ai` is `edition: 'cloud'`
and not in this repository, so whether it overrides `ai/test_embedder`
is 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
1. **Registration.** The default map now carries `ai: { test,
test_embedder }` and `knowledge: { test }`. Override semantics are
unchanged (M3, measured): a host's own `actionHandlers` still replaces
the whole default map (`opts.actionHandlers ?? DEFAULTS`), and
`SettingsService.registerAction` (a `Map.set`) still lets a plugin
replace any one handler later. The option's docblock used to say only
"the bundled `mail.test` handler is registered automatically". It now
names the default set and says that passing the option replaces the
whole map.
2. **Answer text (M2's second half).** No answer claimed outright that a
connection or round trip was tested. Two answers did promise something
false, though: "Mount @objectstack/embedder-openai + a knowledge adapter
to exercise live calls" and "Mount @objectstack/knowledge-ragflow to
exercise live calls". In this file that phrase follows the storage
stub's pattern ("Mount @objectstack/service-storage to exercise live
I/O"), and there it is true: service-storage overrides `storage/test`
with 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 `values` object or a bare key map) over the saved values, then:
**AI → "Test embedder"** (`POST /api/settings/ai/test_embedder`)
- `embedder_provider` unset or `none`: `ok: false`, warning, "Embedder
is disabled. Pick a provider to enable knowledge / RAG."
- any provider except `ollama` with no `embedder_api_key`: `ok: false`,
error.
- `custom` / `azure` with no `embedder_base_url`: `ok: false`, error.
- otherwise: `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).
- `turso` with no URL: `ok: true`, info, the
reuse-the-tenant's-connection note, plus "This checks the form only — no
connection was attempted."
- `turso` with a `libsql://` URL and no auth token: `ok: false`, error.
- `turso` with 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-turso`
follows, unchanged.
- `ragflow` with no Base URL or no API key: `ok: false`, error.
- `ragflow` complete: `ok: true`, info, "RAGFlow adapter configured
(URL). This checks the form only — no connection was attempted."
- any other adapter: `ok: false`, error.
The route is unchanged: `ok: true` answers 200 with the result, and `ok:
false` answers 400 `SETTINGS_ACTION_FAILED` with the result under
`error.details`.
## The pin, and the class it closes
`src/settings-default-action-handlers.pin.test.ts` reads every builtin
manifest's `http`-kind `action_button` whose URL is the settings action
route. It constructs the plugin with no options, runs `init()`, and
drives each declared action through `runAction` on 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 when `runAction` answered `ok` without one
(its built-in `reset`, 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 `a66a74166` on the unchanged plugin (base
`a43405d59`): `Tests 3 failed | 2 passed (5)`.
```
AssertionError: expected [ Array(2) ] to deeply equal []
+ "ai/test_embedder",
+ "knowledge/test",
AssertionError: expected 400 to be 200 (POST ai/test_embedder)
AssertionError: expected 400 to be 200 (POST knowledge/test)
```
With the fix, on `62b892d56` (the final head, `origin/main` `307ce4c4b`
merged in): `pnpm --filter @objectstack/service-settings exec vitest run
--maxWorkers=2` reports `Test Files 54 passed (54)`, `Tests 899 passed
(899)`. `pnpm --filter @objectstack/service-settings typecheck` exits 0.
`tsc --listFiles` includes all three touched test files.
### Ablations (one-off, run at `c8dbf1395`; nothing permanent was added)
All three mutations went through `scripts/ablation-replace.mjs` in WRAP
mode. Each anchor hit once, the blob changed, and the restore was
proven: blob equal to HEAD, and `git diff HEAD` empty. The tests resolve
this package's own source through relative imports, and `@objectstack/*`
through the vitest source aliases, so no `dist/` is involved. Every
ablation went red, the expected direction.
- **A: drop `knowledge: { test: knowledgeTestActionHandler }` from the
default map.** `Tests 2 failed | 3 passed (5)`. The failures were
`expected [ 'knowledge/test' ] to deeply equal []` and, at the HTTP
door, `expected 400 to be 200`. Restored blob `716f8a5cda1d` equals
HEAD.
- **B: append the old "Mount @objectstack/embedder-openai + a knowledge
adapter to exercise live calls." to the embedder answer.** `Tests 1
failed | 27 passed (28)`: `expected 'openai embedder settings are
complete…' not to match /to exercise live calls/`. Restored blob
`bb1ad034d9ad` equals HEAD.
- **C: append the old "Mount @objectstack/knowledge-ragflow to exercise
live calls." to the RAGFlow answer.** `Tests 1 failed | 24 passed (25)`:
`ragflow: expected 'RAGFlow adapter configured (http://lo…' not to match
/to exercise live calls/`. Restored blob `396195582b05` equals 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/objectstack` derived 67 commands from the 7 changed
paths, against merge base `307ce4c4b`. Each was run with its exit code
captured before any pipe.
- 66 exited 0. `node scripts/check-plugin-teardown-shape.mjs
--self-test` first 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-loads` exited 3, `PREREQUISITE NOT MET`. It
reads every package's `dist/`, and this worktree has built only the
service-settings closure. NOT MEASURED locally; CI's `Lint & Repo Gates`
runs it.
- `--ran` reconciliation: "67 derived famil(ies) accounted for — 66 run,
1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", 0 UNRUN.
- Dependency closure built with `turbo run build
--filter='@objectstack/service-settings...'` (16 tasks, exit 0).
- `test:repo` was not run locally; it belongs to CI (`Test Core`).
## Acceptance notes
- **Nothing in this repository reads the settings these buttons check.**
That is the `knowledge` namespace and the `ai.embedder_*` keys: the only
readers are service-settings' own manifests, tests and visibility
evaluator. `@objectstack/knowledge-ragflow` and
`@objectstack/embedder-openai` take 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
real `ai/test` lives in `@objectstack/service-ai` and overrides the stub
on `kernel:ready`. That package is cloud-edition, so this could not be
measured here. Left untouched, because `ai.test` was already registered
and is not this card's button.
- **Compatibility edge.** `init()` registers each default handler
through `registerAction`, which throws `UnknownNamespaceError` for an
unregistered namespace. A host that passes its own `manifests` without
`knowledge`, and does not pass `actionHandlers`, would now fail at boot.
The same edge already existed for `mail` / `sms` / `storage` / `ai`.
Every in-repo site that passes `manifests` also passes `actionHandlers:
{}`; the sites that pass no `manifests` get the builtin set, which
includes `knowledge`.
- **Two fixture comments name an incomplete handler list.**
`config-change-audit.test.ts` and
`settings-admission-tenancy-posture.test.ts` describe the bundled
handlers as `mail`/`sms`/`storage`/`ai`. The reason those comments give
still holds; they are outside this card's file surface and were not
edited.
- **Two contrast-control tests were replaced.** The embedder test in
`ai.manifest.test.ts` and the RAGFlow test in
`knowledge.manifest.test.ts` pinned 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-settings` patch.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0c910d5 commit 7697ddc
7 files changed
Lines changed: 309 additions & 44 deletions
File tree
- .changeset
- packages/services/service-settings/src
- manifests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
354 | 354 | | |
355 | 355 | | |
356 | 356 | | |
357 | | - | |
358 | | - | |
359 | | - | |
360 | | - | |
361 | | - | |
362 | | - | |
363 | | - | |
364 | | - | |
365 | | - | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
366 | 381 | | |
367 | | - | |
368 | | - | |
369 | | - | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
370 | 387 | | |
371 | 388 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
427 | 427 | | |
428 | 428 | | |
429 | 429 | | |
430 | | - | |
431 | | - | |
432 | | - | |
| 430 | + | |
| 431 | + | |
433 | 432 | | |
434 | | - | |
435 | | - | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
436 | 442 | | |
437 | 443 | | |
438 | 444 | | |
| |||
469 | 475 | | |
470 | 476 | | |
471 | 477 | | |
472 | | - | |
473 | | - | |
| 478 | + | |
| 479 | + | |
474 | 480 | | |
475 | 481 | | |
476 | 482 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
162 | 170 | | |
163 | 171 | | |
164 | 172 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
137 | 137 | | |
138 | 138 | | |
139 | 139 | | |
140 | | - | |
141 | | - | |
142 | | - | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
143 | 148 | | |
144 | | - | |
145 | | - | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
146 | 158 | | |
147 | 159 | | |
148 | 160 | | |
| |||
163 | 175 | | |
164 | 176 | | |
165 | 177 | | |
166 | | - | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
167 | 181 | | |
168 | 182 | | |
169 | 183 | | |
| |||
178 | 192 | | |
179 | 193 | | |
180 | 194 | | |
181 | | - | |
| 195 | + | |
182 | 196 | | |
183 | 197 | | |
184 | 198 | | |
| |||
195 | 209 | | |
196 | 210 | | |
197 | 211 | | |
198 | | - | |
| 212 | + | |
199 | 213 | | |
200 | 214 | | |
201 | 215 | | |
| |||
0 commit comments