Skip to content

Commit c396797

Browse files
committed
fix(service-settings): move the storage, knowledge and AI warnings into info_banner rows
Each of the four warnings that sat inside a namespace description or a field's help string becomes an info_banner row with bannerSeverity 'warning', an id, a title and a body, placed in the section it concerns directly under the field it warns about. The translations move with them into settings.NAMESPACE.banners.BANNER_ID.{title,text} in every shipped locale, and the feature-flags beta_notice banner gains its copies too. The settings translation coverage test now covers banners in both directions, and a sweep pins that no manifest or bundle description or help string carries the warning glyph. Claude-Session: https://claude.ai/code/session_018VqJPGA1NDNSCZsnecsChP Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2ef4627 commit c396797

12 files changed

Lines changed: 287 additions & 51 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/service-settings': patch
3+
---
4+
5+
Clause-②: no
6+
7+
fix(service-settings): the File Storage, Knowledge and AI settings pages show their warnings as `info_banner` callouts with `bannerSeverity: 'warning'`, not as a sentence inside a subtitle or a field's help text
8+
9+
Four warnings were written into ordinary strings with a hand-typed warning glyph, so the admin read them as one more sentence of the page subtitle or of a toggle's help. Each one is now an `info_banner` row in the section it concerns, directly under the field it warns about:
10+
11+
- `storage` — `adapter_switch_warning`, under the Adapter select: switching adapter does not migrate existing files.
12+
- `knowledge` — `adapter_switch_warning`, under the Adapter select: switching adapter does not migrate existing indices.
13+
- `knowledge` — `enforce_rls_warning`, under the Enforce RLS on search toggle: disabling it skips the vector-store leakage safeguard.
14+
- `ai` — `log_prompts_warning`, under the Log full prompts toggle: full prompts may leak PII.
15+
16+
The namespace description or field help of each keeps its non-warning text. The translations moved with them: in the `en`, `zh-CN`, `ja-JP` and `es-ES` bundles each warning sentence left the `description` / `help` copy and sits under `settings.NAMESPACE.banners.BANNER_ID.{title,text}`, with a translated title per banner. The existing Feature Flags `beta_notice` banner gained the same copies, so no shipped locale reads it in English any more.
17+
18+
No setting key, value, default or save behaviour changes. A host that renders `info_banner` rows from the manifest literals draws the English title and text; translated copy is read through `resolveSettingsBannerTitle` / `resolveSettingsBannerText` from `@objectstack/spec/system`.

‎packages/services/service-settings/src/manifests/ai.manifest.test.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,29 @@ describe('aiSettingsManifest — temperature declares a window, not a grid (#655
237237
});
238238
});
239239

240+
describe('aiSettingsManifest — the log_prompts PII warning is an info_banner', () => {
241+
const specs = aiSettingsManifest.specifiers as any[];
242+
const sectionOf = (i: number) => specs.slice(0, i + 1).reverse().find((s) => s.type === 'group')?.id;
243+
244+
it('declares it as a warning-severity info_banner with an id, a title and a body', () => {
245+
const banners = specs.filter((s) => s.type === 'info_banner');
246+
expect(banners.map((b) => [b.id, b.bannerSeverity])).toEqual([['log_prompts_warning', 'warning']]);
247+
expect(typeof banners[0].label).toBe('string');
248+
expect(banners[0].label.length).toBeGreaterThan(0);
249+
expect(banners[0].bannerText.length).toBeGreaterThan(0);
250+
});
251+
252+
it('places it in the Observability section, directly under the log_prompts toggle', () => {
253+
const i = specs.findIndex((s) => s.id === 'log_prompts_warning');
254+
expect(sectionOf(i)).toBe('observability');
255+
expect(specs[i - 1].key).toBe('log_prompts');
256+
});
257+
258+
it('carries the warning glyph nowhere — the banner severity is the only warning marker', () => {
259+
expect(JSON.stringify(aiSettingsManifest)).not.toContain('⚠');
260+
});
261+
});
262+
240263
describe('aiSettingsManifest — embedder section', () => {
241264
it('exposes embedder_provider select with 10 options incl. none + 5 Chinese providers', () => {
242265
const f = (aiSettingsManifest.specifiers as any[]).find(

‎packages/services/service-settings/src/manifests/ai.manifest.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,13 @@ const manifest = {
229229
description: 'Persist prompt/response traces to sys_ai_trace for debugging and replay.' },
230230
{ type: 'toggle', key: 'log_prompts', label: 'Log full prompts',
231231
required: false, default: false,
232-
description: 'Include rendered prompts (not just metadata) in trace rows. ⚠ May leak PII — disable in regulated environments.' },
232+
description: 'Include rendered prompts (not just metadata) in trace rows.' },
233+
// The PII warning is an `info_banner` row (`bannerSeverity: 'warning'`), not
234+
// a sentence in the toggle's help; its copy lives under
235+
// `settings.ai.banners.log_prompts_warning` in the locale bundles.
236+
{ type: 'info_banner', id: 'log_prompts_warning', label: 'Prompts may contain PII',
237+
required: false, bannerSeverity: 'warning',
238+
bannerText: 'May leak PII — disable in regulated environments.' },
233239

234240
// ── Probe ────────────────────────────────────────────────────
235241
{ type: 'action_button', id: 'test', label: 'Test connection',

‎packages/services/service-settings/src/manifests/knowledge.manifest.test.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,36 @@ describe('knowledgeSettingsManifest', () => {
5757
});
5858
});
5959

60+
describe('knowledgeSettingsManifest — both warnings are info_banner rows', () => {
61+
const specs = knowledgeSettingsManifest.specifiers as any[];
62+
const sectionOf = (i: number) => specs.slice(0, i + 1).reverse().find((s) => s.type === 'group')?.id;
63+
64+
it('declares exactly the two warning-severity banners, each with an id, a title and a body', () => {
65+
const banners = specs.filter((s) => s.type === 'info_banner');
66+
expect(banners.map((b) => [b.id, b.bannerSeverity])).toEqual([
67+
['adapter_switch_warning', 'warning'],
68+
['enforce_rls_warning', 'warning'],
69+
]);
70+
for (const b of banners) {
71+
expect(typeof b.label, `${b.id} label`).toBe('string');
72+
expect(b.label.length, `${b.id} label`).toBeGreaterThan(0);
73+
expect(b.bannerText.length, `${b.id} bannerText`).toBeGreaterThan(0);
74+
}
75+
});
76+
77+
it('places each banner in the section it concerns, directly under the field it warns about', () => {
78+
const at = (id: string) => specs.findIndex((s) => s.id === id);
79+
expect(sectionOf(at('adapter_switch_warning'))).toBe('adapter');
80+
expect(specs[at('adapter_switch_warning') - 1].key).toBe('adapter');
81+
expect(sectionOf(at('enforce_rls_warning'))).toBe('permissions');
82+
expect(specs[at('enforce_rls_warning') - 1].key).toBe('enforce_rls');
83+
});
84+
85+
it('carries the warning glyph nowhere — the banner severity is the only warning marker', () => {
86+
expect(JSON.stringify(knowledgeSettingsManifest)).not.toContain('⚠');
87+
});
88+
});
89+
6090
describe('knowledgeTestActionHandler', () => {
6191
it('warns for memory adapter (no service to probe)', async () => {
6292
const r = await knowledgeTestActionHandler({ values: { adapter: 'memory' } } as any);

‎packages/services/service-settings/src/manifests/knowledge.manifest.ts‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ const manifest = {
4242
icon: 'BookOpen',
4343
description:
4444
'Vector-store backend for RAG / knowledge sources. ' +
45-
'⚠ Switching adapter does NOT migrate existing indices — documents ' +
46-
'indexed under the previous adapter become unreachable until ' +
47-
're-indexed. The embedder is configured separately under AI.',
45+
'The embedder is configured separately under AI.',
4846
scope: 'global',
4947
readPermission: 'manage_platform_settings',
5048
writePermission: 'manage_platform_settings',
@@ -61,6 +59,14 @@ const manifest = {
6159
{ value: 'ragflow', label: 'RAGFlow (external)' },
6260
],
6361
},
62+
// Warnings are `info_banner` rows with `bannerSeverity: 'warning'`, never a
63+
// sentence inside a description; each `id` addresses its copy under
64+
// `settings.knowledge.banners.<id>` in the locale bundles.
65+
{ type: 'info_banner', id: 'adapter_switch_warning', label: 'Existing indices are not migrated',
66+
required: false, bannerSeverity: 'warning',
67+
bannerText:
68+
'Switching adapter does NOT migrate existing indices — documents indexed under the ' +
69+
'previous adapter become unreachable until re-indexed.' },
6470

6571
// ── Turso / libSQL ────────────────────────────────────────────
6672
{ type: 'group', id: 'turso', label: 'Turso / libSQL', required: false,
@@ -113,9 +119,12 @@ const manifest = {
113119
{ type: 'group', id: 'permissions', label: 'Permissions', required: false },
114120
{ type: 'toggle', key: 'enforce_rls', label: 'Enforce RLS on search',
115121
required: false, default: true,
116-
description:
117-
'Re-check every hit against the caller\'s record-level permissions via IDataEngine. ' +
118-
'⚠ Disabling skips the platform\'s unique safeguard against vector-store data leakage — leave on in production.' },
122+
description: 'Re-check every hit against the caller\'s record-level permissions via IDataEngine.' },
123+
{ type: 'info_banner', id: 'enforce_rls_warning', label: 'Leave RLS on in production',
124+
required: false, bannerSeverity: 'warning',
125+
bannerText:
126+
'Disabling skips the platform\'s unique safeguard against vector-store data leakage — ' +
127+
'leave on in production.' },
119128

120129
// ── Probe ─────────────────────────────────────────────────────
121130
{ type: 'action_button', id: 'test', label: 'Test connection',

‎packages/services/service-settings/src/manifests/storage.manifest.test.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,29 @@ describe('storageSettingsManifest', () => {
5050
});
5151
});
5252

53+
describe('storageSettingsManifest — the adapter-switch warning is an info_banner', () => {
54+
const specs = storageSettingsManifest.specifiers as any[];
55+
const sectionOf = (i: number) => specs.slice(0, i + 1).reverse().find((s) => s.type === 'group')?.id;
56+
57+
it('declares it as a warning-severity info_banner with an id, a title and a body', () => {
58+
const banners = specs.filter((s) => s.type === 'info_banner');
59+
expect(banners.map((b) => [b.id, b.bannerSeverity])).toEqual([['adapter_switch_warning', 'warning']]);
60+
expect(typeof banners[0].label).toBe('string');
61+
expect(banners[0].label.length).toBeGreaterThan(0);
62+
expect(banners[0].bannerText.length).toBeGreaterThan(0);
63+
});
64+
65+
it('places it in the Backend section, directly under the adapter select it concerns', () => {
66+
const i = specs.findIndex((s) => s.id === 'adapter_switch_warning');
67+
expect(sectionOf(i)).toBe('adapter');
68+
expect(specs[i - 1].key).toBe('adapter');
69+
});
70+
71+
it('carries the warning glyph nowhere — the banner severity is the only warning marker', () => {
72+
expect(JSON.stringify(storageSettingsManifest)).not.toContain('⚠');
73+
});
74+
});
75+
5376
describe('storageTestActionHandler (fallback)', () => {
5477
it('rejects local adapter without local_root', async () => {
5578
const r = await storageTestActionHandler({

‎packages/services/service-settings/src/manifests/storage.manifest.ts‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@ const manifest = {
1212
version: 1,
1313
label: 'File Storage',
1414
icon: 'HardDrive',
15-
description:
16-
'Backend used for attachments, exports, and user uploads. ' +
17-
'⚠ Switching adapter does not migrate existing files — files ' +
18-
'uploaded under the previous adapter become unreachable through ' +
19-
'the new one.',
15+
description: 'Backend used for attachments, exports, and user uploads.',
2016
scope: 'global',
2117
readPermission: 'manage_platform_settings',
2218
writePermission: 'manage_platform_settings',
@@ -31,6 +27,15 @@ const manifest = {
3127
{ value: 's3', label: 'S3 / S3-compatible' },
3228
],
3329
},
30+
// The adapter-switch warning is a structured callout, not a sentence in
31+
// the namespace description: `bannerSeverity` carries the severity an
32+
// inline warning glyph used to spell by hand, and `id` addresses its copy
33+
// under `settings.storage.banners.adapter_switch_warning` in each bundle.
34+
{ type: 'info_banner', id: 'adapter_switch_warning', label: 'Existing files are not migrated',
35+
required: false, bannerSeverity: 'warning',
36+
bannerText:
37+
'Switching adapter does not migrate existing files — files uploaded under the ' +
38+
'previous adapter become unreachable through the new one.' },
3439

3540
{ type: 'group', id: 'local', label: 'Local', required: false,
3641
visible: "${data.adapter === 'local'}" },

‎packages/services/service-settings/src/translations/en.ts‎

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,15 @@ export const en: PlatformTranslationData = {
216216
realtime_cursors: { label: 'Realtime cursors' },
217217
inline_comments: { label: 'Inline comments' },
218218
},
219+
banners: {
220+
beta_notice: { title: 'Heads up',
221+
text: 'Beta features may change without notice. Pin via env vars (e.g. `OS_FEATURE_FLAGS_AI_ENABLED=true`) to lock for the whole deployment.' },
222+
},
219223
},
220224

221225
storage: {
222226
title: 'File Storage',
223-
description:
224-
'Backend used for attachments, exports, and user uploads. ' +
225-
'⚠ Switching adapter does not migrate existing files — files ' +
226-
'uploaded under the previous adapter become unreachable through ' +
227-
'the new one.',
227+
description: 'Backend used for attachments, exports, and user uploads.',
228228
groups: {
229229
adapter: { title: 'Backend', description: 'Choose where uploaded files are stored.' },
230230
local: { title: 'Local' },
@@ -255,6 +255,11 @@ export const en: PlatformTranslationData = {
255255
actions: {
256256
test: { label: 'Test connection' },
257257
},
258+
banners: {
259+
adapter_switch_warning: { title: 'Existing files are not migrated',
260+
text: 'Switching adapter does not migrate existing files — files uploaded under the '
261+
+ 'previous adapter become unreachable through the new one.' },
262+
},
258263
},
259264

260265
ai: {
@@ -313,7 +318,7 @@ export const en: PlatformTranslationData = {
313318
trace_enabled: { label: 'Record traces',
314319
help: 'Persist prompt/response traces to sys_ai_trace for debugging and replay.' },
315320
log_prompts: { label: 'Log full prompts',
316-
help: 'Include rendered prompts (not just metadata) in trace rows. ⚠ May leak PII — disable in regulated environments.' },
321+
help: 'Include rendered prompts (not just metadata) in trace rows.' },
317322
embedder_provider: {
318323
label: 'Provider',
319324
options: {
@@ -344,13 +349,15 @@ export const en: PlatformTranslationData = {
344349
test: { label: 'Test connection' },
345350
test_embedder: { label: 'Test embedder' },
346351
},
352+
banners: {
353+
log_prompts_warning: { title: 'Prompts may contain PII',
354+
text: 'May leak PII — disable in regulated environments.' },
355+
},
347356
},
348357

349358
knowledge: {
350359
title: 'Knowledge',
351-
description:
352-
'Vector-store backend for RAG / knowledge sources. ' +
353-
'⚠ Switching adapter does NOT migrate existing indices.',
360+
description: 'Vector-store backend for RAG / knowledge sources.',
354361
groups: {
355362
adapter: { title: 'Backend',
356363
description: 'Choose where document chunks and their vectors are stored.' },
@@ -386,11 +393,19 @@ export const en: PlatformTranslationData = {
386393
over_fetch: { label: 'Over-fetch multiplier',
387394
help: 'Internal topK × overFetch candidates fetched so JS-side metadata filtering still has rows.' },
388395
enforce_rls: { label: 'Enforce RLS on search',
389-
help: 'Re-check every hit against the caller\'s record-level permissions. ⚠ Disabling skips the platform\'s unique safeguard.' },
396+
help: 'Re-check every hit against the caller\'s record-level permissions.' },
390397
},
391398
actions: {
392399
test: { label: 'Test connection' },
393400
},
401+
banners: {
402+
adapter_switch_warning: { title: 'Existing indices are not migrated',
403+
text: 'Switching adapter does NOT migrate existing indices — documents indexed under the '
404+
+ 'previous adapter become unreachable until re-indexed.' },
405+
enforce_rls_warning: { title: 'Leave RLS on in production',
406+
text: 'Disabling skips the platform\'s unique safeguard against vector-store data leakage — '
407+
+ 'leave on in production.' },
408+
},
394409
},
395410
},
396411
};

‎packages/services/service-settings/src/translations/es-ES.ts‎

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -340,15 +340,15 @@ export const esES: PlatformTranslationData = {
340340
realtime_cursors: { label: 'Cursores en tiempo real' },
341341
inline_comments: { label: 'Comentarios en línea' },
342342
},
343+
banners: {
344+
beta_notice: { title: 'Atención',
345+
text: 'Las funciones beta pueden cambiar sin previo aviso. Fíjalas con variables de entorno (p. ej., `OS_FEATURE_FLAGS_AI_ENABLED=true`) para bloquearlas en todo el despliegue.' },
346+
},
343347
},
344348

345349
storage: {
346350
title: 'Almacenamiento de archivos',
347-
description:
348-
'Backend usado para adjuntos, exportaciones y subidas de usuarios. ' +
349-
'⚠ Cambiar de adaptador no migra los archivos existentes: los archivos ' +
350-
'subidos con el adaptador anterior dejan de ser accesibles a través ' +
351-
'del nuevo.',
351+
description: 'Backend usado para adjuntos, exportaciones y subidas de usuarios.',
352352
groups: {
353353
adapter: { title: 'Backend', description: 'Elige dónde se almacenan los archivos subidos.' },
354354
local: { title: 'Local' },
@@ -379,6 +379,11 @@ export const esES: PlatformTranslationData = {
379379
actions: {
380380
test: { label: 'Probar conexión' },
381381
},
382+
banners: {
383+
adapter_switch_warning: { title: 'Los archivos existentes no se migran',
384+
text: 'Cambiar de adaptador no migra los archivos existentes: los archivos '
385+
+ 'subidos con el adaptador anterior dejan de ser accesibles a través del nuevo.' },
386+
},
382387
},
383388

384389
ai: {
@@ -462,7 +467,7 @@ export const esES: PlatformTranslationData = {
462467
trace_enabled: { label: 'Registrar trazas',
463468
help: 'Persiste las trazas de prompt/respuesta en sys_ai_trace para depuración y reproducción.' },
464469
log_prompts: { label: 'Registrar prompts completos',
465-
help: 'Incluye los prompts renderizados (no solo metadatos) en las filas de traza. ⚠ Puede filtrar PII: desactívalo en entornos regulados.' },
470+
help: 'Incluye los prompts renderizados (no solo metadatos) en las filas de traza.' },
466471
embedder_provider: {
467472
label: 'Proveedor',
468473
options: {
@@ -493,13 +498,15 @@ export const esES: PlatformTranslationData = {
493498
test: { label: 'Probar conexión' },
494499
test_embedder: { label: 'Probar embedder' },
495500
},
501+
banners: {
502+
log_prompts_warning: { title: 'Los prompts pueden contener PII',
503+
text: 'Puede filtrar PII: desactívalo en entornos regulados.' },
504+
},
496505
},
497506

498507
knowledge: {
499508
title: 'Conocimiento',
500-
description:
501-
'Backend de almacén de vectores para RAG / fuentes de conocimiento. ' +
502-
'⚠ Cambiar de adaptador NO migra los índices existentes.',
509+
description: 'Backend de almacén de vectores para RAG / fuentes de conocimiento.',
503510
groups: {
504511
adapter: { title: 'Backend',
505512
description: 'Elige dónde se almacenan los fragmentos de documento y sus vectores.' },
@@ -535,11 +542,17 @@ export const esES: PlatformTranslationData = {
535542
over_fetch: { label: 'Multiplicador de sobre-obtención',
536543
help: 'Se obtienen topK × overFetch candidatos internos para que el filtrado de metadatos en JS siga teniendo filas.' },
537544
enforce_rls: { label: 'Aplicar RLS en la búsqueda',
538-
help: 'Vuelve a comprobar cada resultado contra los permisos a nivel de registro del solicitante. ⚠ Desactivarlo omite la salvaguarda exclusiva de la plataforma.' },
545+
help: 'Vuelve a comprobar cada resultado contra los permisos a nivel de registro del solicitante.' },
539546
},
540547
actions: {
541548
test: { label: 'Probar conexión' },
542549
},
550+
banners: {
551+
adapter_switch_warning: { title: 'Los índices existentes no se migran',
552+
text: 'Cambiar de adaptador NO migra los índices existentes.' },
553+
enforce_rls_warning: { title: 'Mantenlo activado en producción',
554+
text: 'Desactivarlo omite la salvaguarda exclusiva de la plataforma.' },
555+
},
543556
},
544557
},
545558
};

0 commit comments

Comments
 (0)