diff --git a/.changeset/30204-feature-flags-namespace-removed.md b/.changeset/30204-feature-flags-namespace-removed.md new file mode 100644 index 00000000000..b2e94e5be60 --- /dev/null +++ b/.changeset/30204-feature-flags-namespace-removed.md @@ -0,0 +1,22 @@ +--- +'@objectstack/service-settings': patch +'@objectstack/platform-objects': patch +--- + +fix(service-settings)!: Setup no longer offers a Feature Flags page whose four toggles changed nothing (#30204) + +Clause-②: no (narrowing) — removes the four settable keys of the `feature_flags` settings namespace (`ai_enabled`, `kanban_swimlanes`, `realtime_cursors`, `inline_comments`), the namespace itself (left empty by their removal), its Setup entry and the `featureFlagsSettingsManifest` export. Nothing is widened. +Metadata-protocol: none + + + +**BREAKING** for an administrator who saved one of the four Feature Flags toggles, and for a host that imports `featureFlagsSettingsManifest`. + +- **Why.** No code read any of the four keys, so turning one on changed nothing (ADR-0049 enforce-or-remove). A flag comes back with the feature that reads it. +- **Removed keys.** `feature_flags.ai_enabled`, `feature_flags.kanban_swimlanes`, `feature_flags.realtime_cursors` and `feature_flags.inline_comments`: removed; each had no effect. The in-app AI panel is decided elsewhere and is unchanged. +- **The namespace.** `feature_flags` is no longer registered, so `GET` and `PUT /api/settings/feature_flags` answer `404 UNKNOWN_NAMESPACE`, and `SettingsService.getManifest('feature_flags')` throws `UnknownNamespaceError` (`SETTINGS_UNKNOWN_NAMESPACE`). Setup's Configuration group no longer lists Feature Flags (`@objectstack/platform-objects`, entry `nav_settings_feature_flags` and its four locale labels). +- **Stored values.** A `sys_setting` row saved under `feature_flags` and an `OS_FEATURE_FLAGS_*` environment variable are left in place and become inert: nothing registers the namespace, so nothing resolves them. They are not cleaned up; delete them at will. + +The one-line fix for a host: + +- FROM `featureFlagsSettingsManifest` TO nothing: drop it from the host's `SettingsServicePlugin({ manifests })` list; `builtinSettingsManifests` no longer carries it. diff --git a/content/docs/protocol/kernel/config-resolution.mdx b/content/docs/protocol/kernel/config-resolution.mdx index 2444b04abe1..dab1795277d 100644 --- a/content/docs/protocol/kernel/config-resolution.mdx +++ b/content/docs/protocol/kernel/config-resolution.mdx @@ -247,8 +247,8 @@ derived mechanically from `(namespace, key)` by `envKeyOf`: uppercase, `.` and # namespace 'crm', key 'api_key' OS_CRM_API_KEY=sk_live_... -# namespace 'feature_flags', key 'ai-enabled' -OS_FEATURE_FLAGS_AI_ENABLED=true +# namespace 'ai', key 'log-prompts' +OS_AI_LOG_PROMPTS=true ``` The raw string is coerced by the **type of the specifier's default** diff --git a/content/docs/ui/apps.mdx b/content/docs/ui/apps.mdx index c77c167cecc..ce160fefcee 100644 --- a/content/docs/ui/apps.mdx +++ b/content/docs/ui/apps.mdx @@ -870,7 +870,7 @@ The Setup app's anchors: | `group_people_org` | Users, organizations, business units, teams, invitations | | `group_access_control` | Positions, permission sets, sharing rules, API keys | | `group_approvals` | Approvals inbox, requests, history, delegations | -| `group_configuration` | Settings, localization, exchange rates, authentication, email, storage, AI, feature flags, branding | +| `group_configuration` | Settings, localization, exchange rates, authentication, email, storage, AI, branding | | `group_diagnostics` | Sessions, notification events, audit log | | `group_integrations` | Webhooks, cloud connection | | `group_advanced` | OAuth applications | diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index 6ee51c2fd49..14569d9cb78 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -632,7 +632,7 @@ "as admin, GET /api/v1/meta/app?id=setup and extract every merged nav destination — Setup is a SHELL of empty group anchors (packages/platform-objects/src/apps/setup.app.ts: group_overview/apps/people_org/access_control/approvals/configuration/diagnostics/integrations/advanced) filled by SETUP_NAV_CONTRIBUTIONS (setup-nav.contributions.ts) plus capability-plugin contributions (plugin-security Positions/Permission Sets, plugin-sharing Sharing Rules/Record Shares, plugin-approvals, plugin-webhooks)", "GET /api/v1/meta/app with NO query first and record the names actually served — a stock boot answers exactly showcase_app, setup, account. Then GET /api/v1/meta/app?id=account (account.app.ts — Profile + Inbox/Security/Developer groups). ⚠️ GET /api/v1/meta/app?id=studio answers 200 {\"items\":[]} on every stock boot and that is the expected result (knownGap): record the absence as an assertion and move on — do NOT install @objectstack/studio to make the step runnable", "as admin, hand-walk each destination in each app. At a window narrower than 1416 px (1280 px included) the console folds the sidebar to its icon rail, and Setup and Account open that way (ui/packages/app-shell/src/layout/consoleSidebarRail.ts CONSOLE_SIDEBAR_RAIL_BELOW_WIDTH, objectstack#27593; QA run #29877 met it at 1280 px): press the sidebar toggle at the app bar's left edge (Ctrl/⌘+B) to unfold it before expanding any group. EXPAND every nav group first: some render collapsed by default — Setup's Advanced group and Account's Developer group declare `expanded: false` (packages/platform-objects/src/apps/setup.app.ts, account.app.ts), and QA run #24557 found Setup's Configuration group collapsed as well — so a walk that never expands them never reaches their entries. Then navigate, wait for render, SCREENSHOT FIRST, then read the DOM — assert no pageerror, no 'failed to load', no blank
, no 'no actions configured' placeholder", - "Setup detail: confirm each settings URL entry (nav_settings_* → /apps/setup/system/settings/) opens the settings namespace form — the 11 namespaces of a stock single-posture boot, localization/company/branding/auth/mail/sms/storage/ai/knowledge/lifecycle/feature_flags: the nav_settings_* entries of packages/platform-objects/src/apps/setup-nav.contributions.ts, plus nav_settings_branding, which packages/services/service-settings/src/settings-service-plugin.ts contributes only where no organization wall stands, and Users / Organization / Business Units / Teams / Sessions / OAuth Applications render. User preferences and identity links are not Setup destinations: open a user from Users and confirm the user page's Preferences and Linked Accounts tabs render that user's rows", + "Setup detail: confirm each settings URL entry (nav_settings_* → /apps/setup/system/settings/) opens the settings namespace form — the 10 namespaces of a stock single-posture boot, localization/company/branding/auth/mail/sms/storage/ai/knowledge/lifecycle: the nav_settings_* entries of packages/platform-objects/src/apps/setup-nav.contributions.ts, plus nav_settings_branding, which packages/services/service-settings/src/settings-service-plugin.ts contributes only where no organization wall stands, and Users / Organization / Business Units / Teams / Sessions / OAuth Applications render. User preferences and identity links are not Setup destinations: open a user from Users and confirm the user page's Preferences and Linked Accounts tabs render that user's rows", "Studio detail is NOT walked from here — the app is not registered on a stock boot (knownGap), so there is no merged nav to render. Its metadata:resource lists and component surfaces (App Builder, All Metadata Types, Packages, API Console, Flow Runs, Public Forms) are reached through the console surface at /_console/studio// and are covered by area studio-authoring. What THIS item asserts about Studio is the absence itself, per clause 6", "Account detail: confirm Profile (account:profile_card), Notifications (sys_inbox_message/mine), Approvals (nav_account_approvals, the approvals:inbox component, served as …/component/approvals/inbox — the Approvals Inbox, not the sys_approval_request grid; account.app.ts), Linked Accounts (sys_account), Active Sessions (sys_session/mine), API Keys (sys_api_key/mine), OAuth Applications (sys_oauth_application/mine) and Connect an Agent (nav_connect_agent) each render — 8 Account destinations and 46 Setup destinations for the admin on a stock single-org showcase boot, as QA run #29877 read them at 589049b0 and a fresh showcase boot re-read them at 232d3bb5ef (GET /api/v1/meta/app leaves, which match the nav source minus its gates). Since QA run #28545's reading Setup's Packaged automation page retired under ADR-0146 D11, leaving Packaged Actions (nav_packaged_actions, automation:packaged-actions; #28785), and its unfiltered Packages entry moved to Studio (#29547) (47 for #28927, 50 at eeec88d1 for QA run #28545, 48 re-measured at 3fd80337 for #28338, QA run #27481 read 44 and 8, QA run #24557 read 42 at 5b14b7f3, 45 on 2026-10-01, and #9453 recorded 43 and 7). Recount from the payload each run", "confirm the gated entries resolve to ABSENT-not-erroring for the admin: nav_organizations (requiresService org-scoping) is absent in single-org mode; nav_jwks Signing Keys is ABSENT for EVERY persona, admin included — the entry was removed in #7544 because sys_jwks declares apiEnabled:false, so its list answers OBJECT_API_DISABLED (404) for all callers and no permission gate could prune it; SSO Providers is absent unless OS_SSO_ENABLED (knownGap)", @@ -785,7 +785,7 @@ "fixtures": { "app": "showcase", "requires": [ - "the 11 open-framework settings namespaces a stock boot serves: the 10 in builtinSettingsManifests (packages/services/service-settings/src/manifests: localization, company, branding, auth, mail, sms, storage, ai, knowledge, feature_flags) PLUS lifecycle (5 specifiers), which is NOT in that aggregate — ObjectQLPlugin registers it at kernel:ready whenever a SettingsService is present (packages/objectql/src/lifecycle/lifecycle-settings.ts). Enumerating only the manifests directory undercounts by one", + "the 10 open-framework settings namespaces a stock boot serves: the 9 in builtinSettingsManifests (packages/services/service-settings/src/manifests: localization, company, branding, auth, mail, sms, storage, ai, knowledge) PLUS lifecycle (5 specifiers), which is NOT in that aggregate — ObjectQLPlugin registers it at kernel:ready whenever a SettingsService is present (packages/objectql/src/lifecycle/lifecycle-settings.ts). Enumerating only the manifests directory undercounts by one", "the settings service wired with a data engine so it swaps from in-memory to the sys_setting table, and with the sys_secret store + auditWriter (SettingsServicePlugin binds these on kernel:ready; the showcase stack mounts the engine)" ], "knownGaps": [ @@ -793,7 +793,7 @@ ] }, "steps": [ - "as admin, GET /api/settings — capture the visible manifests (the 11 open-framework namespaces above; the route is the SettingsService REST surface in settings-routes.ts)", + "as admin, GET /api/settings — capture the visible manifests (the 10 open-framework namespaces above; the route is the SettingsService REST surface in settings-routes.ts)", "pick localization (an OBSERVABLE-CONSUMER namespace — packages/core/src/security/resolve-authz-context.ts reads localization.timezone/locale/currency onto ExecutionContext). GET /api/settings/localization — capture {manifest, values}; note the timezone value and its source. On an `os dev --seed-admin` boot the dev seed has ALREADY written the host's zone (UTC in these containers) at tenant scope (packages/plugins/plugin-auth/src/auth-plugin.ts, #23175), so source reads 'tenant' before any PUT; only a boot without the dev seed starts at 'default'", "change it: PUT /api/settings/localization { timezone: 'Asia/Tokyo' }; re-read GET /api/settings/localization and confirm the value persists and source is the scope, 'tenant' (localization scope is 'tenant'): on a dev-seeded boot the source already read 'tenant', so the value is what round-trips; on an unseeded boot the source flips from 'default'. For the console badge (steps[5]) pick a localization key the dev seed did NOT write (e.g. default_country), so its source starts at 'default'", "prove EFFECT (not just the saved form): re-derive an ExecutionContext-dependent output and confirm it now resolves against the saved zone, not UTC. Two stock consumers serve: GET /api/v1/auth/me/localization (the resolved timezone the session carries), and a datetime the console renders — e.g. showcase_field_zoo's f_datetime on its record page, which shifts by exactly the zone offset. ⛔ The analytics date bucket has no stock observable (the only date-typed time dimension is due_date, and the seeded admin holds no export grant), so do not reach for it. If no localization effect is observable at all, fall back to branding.workspace_name visible in the console shell — but never tick effect off the form alone", @@ -882,7 +882,7 @@ "evidence": "the two 401 bodies" }, { - "clause": "each of the 11 open-framework namespaces resolves GET /api/settings/:ns with its manifest + values — no namespace 500s or serves an empty manifest", + "clause": "each of the 10 open-framework namespaces resolves GET /api/settings/:ns with its manifest + values — no namespace 500s or serves an empty manifest", "oracle": "api", "verify": "one GET per variant in variants[]; each returns {manifest, values} with the manifest's specifiers", "evidence": "the per-namespace responses" @@ -907,8 +907,7 @@ "sms", "storage", "ai", - "knowledge", - "feature_flags" + "knowledge" ], "traps": ["stale-console-bundle", "dispatcher-vs-hono-route"], "source": [ diff --git a/packages/cli/README.md b/packages/cli/README.md index 0be368fe949..ee8d2587945 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -428,7 +428,7 @@ service plugins for every preset **except** `--preset minimal`: | `queue` | `QueueServicePlugin` | Background retries (mail, audit batching) need a queue. | | `job` | `JobServicePlugin` | Scheduled jobs (cleanup, reports, cron). | | `cache` | `CacheServicePlugin` | In-memory adapter; performance baseline. | -| `settings` | `SettingsServicePlugin` | Mail / storage / branding / feature-flag UI + persistence. | +| `settings` | `SettingsServicePlugin` | Mail / storage / branding settings UI + persistence. | | `email` | `EmailServicePlugin` | Auth callbacks (verify, reset, magic-link) hard-depend. | | `storage` | `StorageServicePlugin` | Avatars, attachments, exports. Local fallback in dev. | @@ -437,8 +437,8 @@ Apps no longer need to list these in `requires: [...]`. **Opt-out:** run `objectstack serve --preset minimal` to skip the slate and only load core + your explicit `requires`. -**Built-in Settings manifests:** `mail`, `storage`, `branding`, -`feature_flags` are pre-registered. Both `mail` and `storage` expose a +**Built-in Settings manifests:** `mail`, `storage` and `branding` +are among those pre-registered. Both `mail` and `storage` expose a "Test connection" action that exercises the live transport / adapter end-to-end. Switching the storage adapter does **not** migrate previously uploaded files — the plugin logs a warning on every swap. diff --git a/packages/cli/test/connect-agent-both-halves-wire.pin.test.ts b/packages/cli/test/connect-agent-both-halves-wire.pin.test.ts index 751241141a2..32093693bab 100644 --- a/packages/cli/test/connect-agent-both-halves-wire.pin.test.ts +++ b/packages/cli/test/connect-agent-both-halves-wire.pin.test.ts @@ -254,15 +254,16 @@ describe('#17647 — Connect-an-Agent visibility: BOTH halves, over the wire', ( .toContain('nav_connect_agent'); // The Setup tree the platform admin is served BY THIS COMPOSITION: 9 // authored groups less `group_approvals`, which nothing contributes - // into and the filter collapses, plus the 24 entries + // into and the filter collapses, plus the 23 entries // `SETUP_NAV_CONTRIBUTIONS` carries, plus the MCP one. (25 until - // #25098 moved the unfiltered Packages entry to Studio.) + // #25098 moved the unfiltered Packages entry to Studio; 24 until + // #30204 removed the Feature Flags entry with its namespace.) // `nav_settings_branding` is not among them, and not because of a // tenancy wall (this harness boots none): since #24766 it is // contributed by `@objectstack/service-settings` together with the // `branding` namespace, offered only where no organization wall stands, // and this harness composes platform-objects and the MCP bundle alone. - expect(allowedIds).toHaveLength(33); + expect(allowedIds).toHaveLength(32); expect(allowedIds).not.toContain('nav_settings_branding'); // The two per-user stores are reached from the User record page, not // from Setup's menu (#23803). @@ -285,8 +286,8 @@ describe('#17647 — Connect-an-Agent visibility: BOTH halves, over the wire', ( expect(withAccountHalf.statusCode).toBe(200); expect(withoutAccountHalf.statusCode).toBe(200); expect(navIds(withAccountHalf.body?.item)).toEqual(navIds(withoutAccountHalf.body?.item)); - // 33: the composition the count in the test above states. - expect(navIds(withAccountHalf.body?.item)).toHaveLength(33); + // 32: the composition the count in the test above states. + expect(navIds(withAccountHalf.body?.item)).toHaveLength(32); // The mechanism behind the invariance, asserted rather than assumed: // the bundle aims exactly one contribution at `setup` and one at diff --git a/packages/mcp/src/connect-ui.ts b/packages/mcp/src/connect-ui.ts index 03c67480234..39e21d873ea 100644 --- a/packages/mcp/src/connect-ui.ts +++ b/packages/mcp/src/connect-ui.ts @@ -31,7 +31,7 @@ * entry stays for admins. ⛔ Ungating Setup is NOT the fix and was measured: * the app-level `setup.access` gate fires BEFORE the group gate, so dropping * the group gate alone changes nothing, and dropping both serves 14+ unrelated - * Setup surfaces (Users, Organization, Branding, Feature Flags, …) to every + * Setup surfaces (Users, Organization, Branding, Company, …) to every * signed-in user — far past what this card asks for. ⛔ Nor is a * `requiresService: 'mcp'` gate on an `account.app.ts` entry: the `mcp` service * registers unconditionally in `init()` while this bundle registers behind diff --git a/packages/platform-objects/src/apps/setup-nav.contributions.ts b/packages/platform-objects/src/apps/setup-nav.contributions.ts index e8992d3f9d5..626016aa6c3 100644 --- a/packages/platform-objects/src/apps/setup-nav.contributions.ts +++ b/packages/platform-objects/src/apps/setup-nav.contributions.ts @@ -154,7 +154,6 @@ export const SETUP_NAV_CONTRIBUTIONS: NavigationContribution[] = [ // `lifecycle` is `@objectstack/objectql`'s manifest (ADR-0057), offered // wherever a settings service is mounted, like the entries above. { id: 'nav_settings_lifecycle', type: 'url', label: 'Data Lifecycle', url: '/apps/setup/system/settings/lifecycle', icon: 'timer', requiredPermissions: ['manage_platform_settings'] }, - { id: 'nav_settings_feature_flags', type: 'url', label: 'Feature Flags', url: '/apps/setup/system/settings/feature_flags', icon: 'flag' }, ], }, { diff --git a/packages/platform-objects/src/apps/translations/en.objects.generated.ts b/packages/platform-objects/src/apps/translations/en.objects.generated.ts index b4e6affed8a..9aac201e62c 100644 --- a/packages/platform-objects/src/apps/translations/en.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/en.objects.generated.ts @@ -3388,7 +3388,7 @@ export const enObjects: NonNullable = { }, namespace: { label: "Namespace", - help: "Manifest namespace (e.g. mail, branding, feature_flags)." + help: "Manifest namespace (e.g. mail, branding, storage)." }, key: { label: "Key", diff --git a/packages/platform-objects/src/apps/translations/en.ts b/packages/platform-objects/src/apps/translations/en.ts index 9f7486d75a7..33819f11ee2 100644 --- a/packages/platform-objects/src/apps/translations/en.ts +++ b/packages/platform-objects/src/apps/translations/en.ts @@ -129,7 +129,6 @@ export const en: TranslationData = { nav_settings_ai: { label: 'AI & Embedder' }, nav_settings_knowledge: { label: 'Knowledge' }, nav_settings_lifecycle: { label: 'Data Lifecycle' }, - nav_settings_feature_flags: { label: 'Feature Flags' }, // Notifications (contributed by @objectstack/service-messaging) nav_notification_preferences: { label: 'Notification Preferences' }, nav_notification_templates: { label: 'Notification Templates' }, diff --git a/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts b/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts index bb35af5d669..429a91c5ed0 100644 --- a/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts @@ -3388,7 +3388,7 @@ export const esESObjects: NonNullable = { }, namespace: { label: "Espacio de nombres", - help: "Espacio de nombres del manifiesto (p. ej. mail, branding, feature_flags)." + help: "Espacio de nombres del manifiesto (p. ej. mail, branding, storage)." }, key: { label: "Clave", diff --git a/packages/platform-objects/src/apps/translations/es-ES.source-hashes.ts b/packages/platform-objects/src/apps/translations/es-ES.source-hashes.ts index e1673e5ad08..ac68631f53d 100644 --- a/packages/platform-objects/src/apps/translations/es-ES.source-hashes.ts +++ b/packages/platform-objects/src/apps/translations/es-ES.source-hashes.ts @@ -111,7 +111,6 @@ export const esESSourceHashes: Readonly> = { 'apps.setup.navigation.nav_settings_ai.label': 'a6b41a554173f7ab', 'apps.setup.navigation.nav_settings_knowledge.label': '140348539a50d9d5', 'apps.setup.navigation.nav_settings_lifecycle.label': 'cd9fe9c930afed06', - 'apps.setup.navigation.nav_settings_feature_flags.label': '4e7e9e879ea1bcc3', 'apps.setup.navigation.nav_notification_preferences.label': '8869f324c1bd545a', 'apps.setup.navigation.nav_notification_templates.label': '9e270c87ef2578df', 'apps.setup.navigation.nav_sessions.label': '673bde6e0c8a3ef9', diff --git a/packages/platform-objects/src/apps/translations/es-ES.ts b/packages/platform-objects/src/apps/translations/es-ES.ts index c3829fc56d1..9ff92dded13 100644 --- a/packages/platform-objects/src/apps/translations/es-ES.ts +++ b/packages/platform-objects/src/apps/translations/es-ES.ts @@ -88,7 +88,6 @@ export const esES: TranslationData = { nav_settings_ai: { label: 'IA y Embedder' }, nav_settings_knowledge: { label: 'Conocimiento' }, nav_settings_lifecycle: { label: 'Ciclo de vida de los datos' }, - nav_settings_feature_flags: { label: 'Indicadores de función' }, nav_notification_preferences: { label: 'Preferencias de Notificación' }, nav_notification_templates: { label: 'Plantillas de Notificación' }, diff --git a/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts b/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts index 69fa38b1c08..d63dbecd380 100644 --- a/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts @@ -3388,7 +3388,7 @@ export const jaJPObjects: NonNullable = { }, namespace: { label: "名前空間", - help: "マニフェスト名前空間(例: mail、branding、feature_flags)。" + help: "マニフェスト名前空間(例: mail、branding、storage)。" }, key: { label: "キー", diff --git a/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.ts b/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.ts index 56853d088ff..2b644449261 100644 --- a/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.ts +++ b/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.ts @@ -111,7 +111,6 @@ export const jaJPSourceHashes: Readonly> = { 'apps.setup.navigation.nav_settings_ai.label': 'a6b41a554173f7ab', 'apps.setup.navigation.nav_settings_knowledge.label': '140348539a50d9d5', 'apps.setup.navigation.nav_settings_lifecycle.label': 'cd9fe9c930afed06', - 'apps.setup.navigation.nav_settings_feature_flags.label': '4e7e9e879ea1bcc3', 'apps.setup.navigation.nav_notification_preferences.label': '8869f324c1bd545a', 'apps.setup.navigation.nav_notification_templates.label': '9e270c87ef2578df', 'apps.setup.navigation.nav_sessions.label': '673bde6e0c8a3ef9', diff --git a/packages/platform-objects/src/apps/translations/ja-JP.ts b/packages/platform-objects/src/apps/translations/ja-JP.ts index c774f18a705..e3c31ffd562 100644 --- a/packages/platform-objects/src/apps/translations/ja-JP.ts +++ b/packages/platform-objects/src/apps/translations/ja-JP.ts @@ -88,7 +88,6 @@ export const jaJP: TranslationData = { nav_settings_ai: { label: 'AI と Embedder' }, nav_settings_knowledge: { label: 'ナレッジ' }, nav_settings_lifecycle: { label: 'データライフサイクル' }, - nav_settings_feature_flags: { label: '機能フラグ' }, nav_notification_preferences: { label: '通知設定' }, nav_notification_templates: { label: '通知テンプレート' }, diff --git a/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts b/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts index c17d9fee8c8..ff6344f3a60 100644 --- a/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts @@ -3388,7 +3388,7 @@ export const zhCNObjects: NonNullable = { }, namespace: { label: "命名空间", - help: "Manifest 命名空间(例如 mail、branding、feature_flags)。" + help: "Manifest 命名空间(例如 mail、branding、storage)。" }, key: { label: "键", diff --git a/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.ts b/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.ts index f6686899170..c1a715c144c 100644 --- a/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.ts +++ b/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.ts @@ -111,7 +111,6 @@ export const zhCNSourceHashes: Readonly> = { 'apps.setup.navigation.nav_settings_ai.label': 'a6b41a554173f7ab', 'apps.setup.navigation.nav_settings_knowledge.label': '140348539a50d9d5', 'apps.setup.navigation.nav_settings_lifecycle.label': 'cd9fe9c930afed06', - 'apps.setup.navigation.nav_settings_feature_flags.label': '4e7e9e879ea1bcc3', 'apps.setup.navigation.nav_notification_preferences.label': '8869f324c1bd545a', 'apps.setup.navigation.nav_notification_templates.label': '9e270c87ef2578df', 'apps.setup.navigation.nav_sessions.label': '673bde6e0c8a3ef9', diff --git a/packages/platform-objects/src/apps/translations/zh-CN.ts b/packages/platform-objects/src/apps/translations/zh-CN.ts index d748ede03be..379d7320242 100644 --- a/packages/platform-objects/src/apps/translations/zh-CN.ts +++ b/packages/platform-objects/src/apps/translations/zh-CN.ts @@ -92,7 +92,6 @@ export const zhCN: TranslationData = { nav_settings_ai: { label: 'AI 与 Embedder' }, nav_settings_knowledge: { label: '知识库' }, nav_settings_lifecycle: { label: '数据生命周期' }, - nav_settings_feature_flags: { label: '功能开关' }, nav_notification_preferences: { label: '通知偏好' }, nav_notification_templates: { label: '通知模板' }, diff --git a/packages/platform-objects/src/system/sys-setting.object.ts b/packages/platform-objects/src/system/sys-setting.object.ts index e47f751b466..6da48e35bd5 100644 --- a/packages/platform-objects/src/system/sys-setting.object.ts +++ b/packages/platform-objects/src/system/sys-setting.object.ts @@ -116,7 +116,7 @@ export const SysSetting = ObjectSchema.create({ label: 'Namespace', required: true, maxLength: 64, - description: 'Manifest namespace (e.g. mail, branding, feature_flags).', + description: 'Manifest namespace (e.g. mail, branding, storage).', }), key: Field.text({ diff --git a/packages/plugins/plugin-security/src/objects/default-permission-sets.ts b/packages/plugins/plugin-security/src/objects/default-permission-sets.ts index af3bc8d250f..c22b03cb810 100644 --- a/packages/plugins/plugin-security/src/objects/default-permission-sets.ts +++ b/packages/plugins/plugin-security/src/objects/default-permission-sets.ts @@ -361,7 +361,7 @@ const baseDefaultPermissionSets: PermissionSet[] = [ // (mail / storage / AI / knowledge) and platform-only Setup pages // (sharing rules, audit logs, OAuth apps, JWKS, …) require this // and are hidden / 403'd for org admins. Tenant-scoped manifests - // (`branding`, `feature_flags`) keep using `setup.access` so org + // (`branding`, `company`, `localization`) keep using `setup.access` so org // admins CAN configure their own org's branding. // // **Anti-escalation — the organization boundary** (ADR-0090 D12, amended diff --git a/packages/services/service-datasource/src/admin-routes.ts b/packages/services/service-datasource/src/admin-routes.ts index b77801e2edd..fe621db2670 100644 --- a/packages/services/service-datasource/src/admin-routes.ts +++ b/packages/services/service-datasource/src/admin-routes.ts @@ -242,7 +242,7 @@ function buildGetSession(ctx: PluginContext): ((headers: Headers) => Promise Tenant > User > Default`. `process.env[OS_NAMESPACE_KEY]` (uppercased, with dots / hyphens converted to underscores) takes precedence over any stored value. For example, `mail.smtp_host` resolves from `OS_MAIL_SMTP_HOST`, and -`feature_flags.ai_enabled` resolves from `OS_FEATURE_FLAGS_AI_ENABLED`. +`ai.log_prompts` resolves from `OS_AI_LOG_PROMPTS`. Such fields are returned with `source: 'env', locked: true` and writes (service or REST) fail with HTTP 409. @@ -117,7 +117,6 @@ the following built-in manifests are pre-registered out of the box: | `mail` | `EmailServicePlugin` | SMTP / Resend / Postmark + `mail/test` | | `storage` | `StorageServicePlugin` | Local FS / S3 + encrypted secret + `storage/test` | | `branding` | (built-in fallback) | Name, logo and colour served to the console by `RuntimeConfigPlugin` (name also read by `plugin-auth`); offered under the `single` tenancy posture only | -| `feature_flags`| (built-in fallback) | Opt-in experimental features | ## License diff --git a/packages/services/service-settings/src/index.ts b/packages/services/service-settings/src/index.ts index ba9b6356c56..a5a696fa369 100644 --- a/packages/services/service-settings/src/index.ts +++ b/packages/services/service-settings/src/index.ts @@ -155,13 +155,12 @@ export { SETTINGS_PLUGIN_VERSION, } from './manifest.js'; -// Reference manifests (mail / branding / feature flags) and the +// Reference manifests (mail / branding / storage) and the // convenience aggregate. Hosts can pass `builtinSettingsManifests` // directly to `new SettingsServicePlugin({ manifests })`. export { builtinSettingsManifests, brandingSettingsManifest, - featureFlagsSettingsManifest, mailSettingsManifest, mailTestActionHandler, smsSettingsManifest, diff --git a/packages/services/service-settings/src/manifests/feature-flags.manifest.ts b/packages/services/service-settings/src/manifests/feature-flags.manifest.ts deleted file mode 100644 index 8f2846d810a..00000000000 --- a/packages/services/service-settings/src/manifests/feature-flags.manifest.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import type { SettingsManifest } from '@objectstack/spec/system'; - -/** Feature Flags — opt into experimental capabilities. */ -export const featureFlagsSettingsManifest: SettingsManifest = { - namespace: 'feature_flags', - version: 1, - label: 'Feature Flags', - icon: 'FlaskConical', - description: 'Toggle experimental and beta features for this organization.', - scope: 'tenant', - readPermission: 'setup.access', - writePermission: 'setup.write', - category: 'Beta', - order: 100, - beta: true, - specifiers: [ - { type: 'info_banner', id: 'beta_notice', label: 'Heads up', required: false, - bannerText: - 'Beta features may change without notice. A feature set in the server configuration is locked on or off for the whole deployment and cannot be changed here.', - bannerSeverity: 'warning' }, - - { type: 'group', id: 'productivity', label: 'Productivity', required: false }, - { type: 'toggle', key: 'ai_enabled', label: 'AI Assistant', required: false, default: false, - description: 'Enables the in-app AI assistant panel.' }, - { type: 'toggle', key: 'kanban_swimlanes', label: 'Kanban swimlanes', required: false, default: false }, - - { type: 'group', id: 'collaboration', label: 'Collaboration', required: false }, - { type: 'toggle', key: 'realtime_cursors', label: 'Realtime cursors', required: false, default: false }, - { type: 'toggle', key: 'inline_comments', label: 'Inline comments', required: false, default: true }, - ], -}; diff --git a/packages/services/service-settings/src/manifests/index.ts b/packages/services/service-settings/src/manifests/index.ts index 780c7999039..44fb9522b60 100644 --- a/packages/services/service-settings/src/manifests/index.ts +++ b/packages/services/service-settings/src/manifests/index.ts @@ -5,7 +5,6 @@ export { authSettingsManifest } from './auth.manifest.js'; export { mailSettingsManifest, mailTestActionHandler } from './mail.manifest.js'; export { smsSettingsManifest, smsTestActionHandler } from './sms.manifest.js'; export { brandingSettingsManifest } from './branding.manifest.js'; -export { featureFlagsSettingsManifest } from './feature-flags.manifest.js'; export { storageSettingsManifest, storageTestActionHandler } from './storage.manifest.js'; export { aiSettingsManifest, @@ -20,7 +19,6 @@ import { authSettingsManifest } from './auth.manifest.js'; import { mailSettingsManifest } from './mail.manifest.js'; import { smsSettingsManifest } from './sms.manifest.js'; import { brandingSettingsManifest } from './branding.manifest.js'; -import { featureFlagsSettingsManifest } from './feature-flags.manifest.js'; import { storageSettingsManifest } from './storage.manifest.js'; import { aiSettingsManifest } from './ai.manifest.js'; import { knowledgeSettingsManifest } from './knowledge.manifest.js'; @@ -38,5 +36,4 @@ export const builtinSettingsManifests = [ storageSettingsManifest, aiSettingsManifest, knowledgeSettingsManifest, - featureFlagsSettingsManifest, ]; diff --git a/packages/services/service-settings/src/settings-branding-posture.pin.test.ts b/packages/services/service-settings/src/settings-branding-posture.pin.test.ts index 4229b577598..0d71613953e 100644 --- a/packages/services/service-settings/src/settings-branding-posture.pin.test.ts +++ b/packages/services/service-settings/src/settings-branding-posture.pin.test.ts @@ -154,7 +154,7 @@ describe('the Branding namespace under a walled posture (#24766)', () => { const namespaces = await b.listed(); expect(namespaces).not.toContain('branding'); // Control: the listing is real and the rest of the slate is offered. - expect(namespaces).toEqual(expect.arrayContaining(['company', 'localization', 'feature_flags'])); + expect(namespaces).toEqual(expect.arrayContaining(['company', 'localization'])); expect(b.service.listManifests().map((m) => m.namespace)).not.toContain('branding'); }); @@ -205,7 +205,7 @@ describe('the Branding namespace where no wall stands (#24766)', () => { expect(ids.filter((id) => id === 'nav_settings_branding')).toHaveLength(1); // In the Configuration group, after the platform-objects entries. expect(configuration).toContain('nav_settings_branding'); - expect(configuration.indexOf('nav_settings_branding')).toBeGreaterThan(configuration.indexOf('nav_settings_feature_flags')); + expect(configuration.indexOf('nav_settings_branding')).toBeGreaterThan(configuration.indexOf('nav_settings_lifecycle')); }); } }); diff --git a/packages/services/service-settings/src/settings-help-business-words.pin.test.ts b/packages/services/service-settings/src/settings-help-business-words.pin.test.ts index 2e7c4b4cceb..02b59b46bd4 100644 --- a/packages/services/service-settings/src/settings-help-business-words.pin.test.ts +++ b/packages/services/service-settings/src/settings-help-business-words.pin.test.ts @@ -18,7 +18,9 @@ * source's config path. They now say what the setting does, and a saved key * is described as used instead of the one in the server configuration. * - * The sweep reads every string an administrator is shown for those five + * The Feature Flags namespace has since been removed: its toggles had no reader. + * + * The sweep reads every string an administrator is shown for those four * namespaces — the manifest literals (the English fallback) and each shipped * locale's copy — and refuses the four shapes that only a developer can read. * It pins SHAPES, never wording: a rewrite that stays in business words keeps @@ -28,7 +30,6 @@ import { describe, it, expect } from 'vitest'; import type { SettingsManifest } from '@objectstack/spec/system'; import { authSettingsManifest } from './manifests/auth.manifest.js'; -import { featureFlagsSettingsManifest } from './manifests/feature-flags.manifest.js'; import { mailSettingsManifest } from './manifests/mail.manifest.js'; import { aiSettingsManifest } from './manifests/ai.manifest.js'; import { knowledgeSettingsManifest } from './manifests/knowledge.manifest.js'; @@ -36,7 +37,6 @@ import { en, zhCN, jaJP, esES } from './translations/index.js'; const MANIFESTS: SettingsManifest[] = [ authSettingsManifest, - featureFlagsSettingsManifest, mailSettingsManifest, // #28069 aiSettingsManifest, diff --git a/packages/services/service-settings/src/settings-retired-workspace-noun.pin.test.ts b/packages/services/service-settings/src/settings-retired-workspace-noun.pin.test.ts index 6066e04adde..8228e7bf59b 100644 --- a/packages/services/service-settings/src/settings-retired-workspace-noun.pin.test.ts +++ b/packages/services/service-settings/src/settings-retired-workspace-noun.pin.test.ts @@ -112,7 +112,7 @@ describe('the retired noun: no settings manifest or bundle says workspace / 工 }); it('walks every built-in manifest', () => { - expect(manifests.length).toBeGreaterThanOrEqual(10); + expect(manifests.length).toBeGreaterThanOrEqual(9); for (const m of builtinSettingsManifests) expect(manifests).toContain(m); }); diff --git a/packages/services/service-settings/src/settings-service-plugin.ts b/packages/services/service-settings/src/settings-service-plugin.ts index 629b996deee..b389b0dbf7c 100644 --- a/packages/services/service-settings/src/settings-service-plugin.ts +++ b/packages/services/service-settings/src/settings-service-plugin.ts @@ -120,7 +120,7 @@ const UNWALLED_ONLY_SETUP_NAV_BUNDLE_ID = `${SETTINGS_PLUGIN_ID}.setup-nav`; export interface SettingsServicePluginOptions { /** * Pre-register these manifests at boot. When omitted, the bundled - * builtin manifests (mail / branding / feature_flags) are loaded so + * builtin manifests (`builtinSettingsManifests`) are loaded so * a host gets a working Settings hub out of the box. Pass an empty * array to opt out entirely. A `branding` manifest, bundled or passed * here, is offered only where no organization wall stands (see diff --git a/packages/services/service-settings/src/settings-service.test.ts b/packages/services/service-settings/src/settings-service.test.ts index c76ee0849af..b3e1b7349bb 100644 --- a/packages/services/service-settings/src/settings-service.test.ts +++ b/packages/services/service-settings/src/settings-service.test.ts @@ -16,23 +16,21 @@ import { authSettingsManifest } from './manifests/auth.manifest.js'; import { localizationSettingsManifest } from './manifests/localization.manifest.js'; import { companySettingsManifest } from './manifests/company.manifest.js'; import { brandingSettingsManifest } from './manifests/branding.manifest.js'; -import { featureFlagsSettingsManifest } from './manifests/feature-flags.manifest.js'; import { builtinSettingsManifests } from './manifests/index.js'; import { evaluateVisibility, visibilitySource } from './visibility-eval.js'; import { SettingsManifestSchema } from '@objectstack/spec/system'; describe('reference manifests are spec-valid', () => { - it('mail / branding / feature_flags pass schema', () => { + it('mail / branding pass schema', () => { expect(() => SettingsManifestSchema.parse(mailSettingsManifest)).not.toThrow(); expect(() => SettingsManifestSchema.parse(brandingSettingsManifest)).not.toThrow(); - expect(() => SettingsManifestSchema.parse(featureFlagsSettingsManifest)).not.toThrow(); }); }); describe('envKeyOf', () => { it('uppercases and underscores', () => { expect(envKeyOf('mail', 'smtp_host')).toBe('OS_MAIL_SMTP_HOST'); - expect(envKeyOf('feature_flags', 'ai-enabled')).toBe('OS_FEATURE_FLAGS_AI_ENABLED'); + expect(envKeyOf('ai', 'log-prompts')).toBe('OS_AI_LOG_PROMPTS'); }); }); @@ -43,6 +41,27 @@ describe('SettingsService — registry', () => { expect(() => svc.getManifest('nope')).toThrow(UnknownNamespaceError); }); + it('no longer serves the retired feature_flags namespace', async () => { + // Its four toggles had no reader, so the namespace was removed rather than + // left as a page of switches that change nothing (ADR-0049 enforce-or-remove). + // A stored `sys_setting` row or an `OS_FEATURE_FLAGS_*` variable is inert: + // nothing registers the namespace, so nothing resolves it. + const svc = new SettingsService({ env: { OS_FEATURE_FLAGS_AI_ENABLED: 'true' } }); + for (const m of builtinSettingsManifests) svc.registerManifest(m); + expect(builtinSettingsManifests.map((m) => m.namespace)).not.toContain('feature_flags'); + let caught: unknown; + try { + svc.getManifest('feature_flags'); + } catch (e) { + caught = e; + } + expect(caught).toBeInstanceOf(UnknownNamespaceError); + expect(caught).toMatchObject({ code: 'SETTINGS_UNKNOWN_NAMESPACE', namespace: 'feature_flags' }); + await expect(svc.get('feature_flags', 'ai_enabled')).rejects.toMatchObject({ + code: 'SETTINGS_UNKNOWN_NAMESPACE', + }); + }); + it('rejects unknown key', async () => { const svc = new SettingsService(); svc.registerManifest(brandingSettingsManifest); @@ -93,9 +112,9 @@ describe('SettingsService — resolver precedence', () => { }); it('coerces env strings via default-type hint', async () => { - const svc = new SettingsService({ env: { OS_FEATURE_FLAGS_AI_ENABLED: 'true' } }); - svc.registerManifest(featureFlagsSettingsManifest); - const r = await svc.get('feature_flags', 'ai_enabled'); + const svc = new SettingsService({ env: { OS_AI_LOG_PROMPTS: 'true' } }); + svc.registerManifest(aiSettingsManifest); + const r = await svc.get('ai', 'log_prompts'); expect(r.value).toBe(true); }); @@ -223,11 +242,11 @@ describe('SettingsService — audit sink', () => { describe('SettingsService — getNamespace', () => { it('returns manifest + values for every key', async () => { const svc = new SettingsService({ env: {} }); - svc.registerManifest(featureFlagsSettingsManifest); - const payload = await svc.getNamespace('feature_flags'); - expect(payload.manifest.namespace).toBe('feature_flags'); - expect(payload.values.ai_enabled.value).toBe(false); - expect(payload.values.inline_comments.value).toBe(true); + svc.registerManifest(aiSettingsManifest); + const payload = await svc.getNamespace('ai'); + expect(payload.manifest.namespace).toBe('ai'); + expect(payload.values.log_prompts.value).toBe(false); + expect(payload.values.trace_enabled.value).toBe(true); }); }); @@ -815,25 +834,25 @@ describe('SettingsService — env overrides are checked against declared options // a pin for the option check specifically: `branding.workspace_name` is // free text to the enumeration but does declare `minLength: 1, // maxLength: 60`, and since #5932 that window IS enforced on this same path - // — `EnvCorp` (7) simply sits inside it. `feature_flags.ai_enabled` is a + // — `EnvCorp` (7) simply sits inside it. `ai.log_prompts` is a // toggle and declares no window at all. const { errors, logger } = spyLogger(); const svc = new SettingsService({ env: { OS_BRANDING_WORKSPACE_NAME: 'EnvCorp', // no option table; length 7 of [1, 60] - OS_FEATURE_FLAGS_AI_ENABLED: 'true', // boolean — coerced, not enumerated + OS_AI_LOG_PROMPTS: 'true', // boolean — coerced, not enumerated }, logger, }); svc.registerManifest(brandingSettingsManifest); - svc.registerManifest(featureFlagsSettingsManifest); + svc.registerManifest(aiSettingsManifest); const name = await svc.get('branding', 'workspace_name'); expect(name.value).toBe('EnvCorp'); expect(name.source).toBe('env'); expect(name.locked).toBe(true); - const flag = await svc.get('feature_flags', 'ai_enabled'); + const flag = await svc.get('ai', 'log_prompts'); expect(flag.value).toBe(true); expect(flag.source).toBe('env'); diff --git a/packages/services/service-settings/src/settings-visibility-declaration.pin.test.ts b/packages/services/service-settings/src/settings-visibility-declaration.pin.test.ts index f7b35d09212..1d4c8bd10fe 100644 --- a/packages/services/service-settings/src/settings-visibility-declaration.pin.test.ts +++ b/packages/services/service-settings/src/settings-visibility-declaration.pin.test.ts @@ -63,7 +63,7 @@ describe('settings `visible` — declaration ⇄ evaluator', () => { expect(refusals).toEqual([]); }); - it('the corpus is still the size the ruling measured — 10 manifests, 96 predicates', () => { + it('the corpus is still the size the ruling measured — 9 manifests, 96 predicates', () => { // If a bundled manifest gains or loses a `visible`, this number moves and // the reader is sent back to the measurement rather than trusting a stale // one. It is a tripwire on the premise, not a rule about how many @@ -72,7 +72,9 @@ describe('settings `visible` — declaration ⇄ evaluator', () => { ...(typeof m.visible === 'undefined' ? [] : [m.visible]), ...(m.specifiers ?? []).filter((s: any) => typeof s.visible !== 'undefined').map((s: any) => s.visible), ]); - expect(builtinSettingsManifests).toHaveLength(10); + // 10 -> 9 at #30204: the `feature_flags` manifest was removed; it declared + // no `visible`, so the predicate count does not move. + expect(builtinSettingsManifests).toHaveLength(9); // 94 -> 96 at #11768: the `audience` group added two gated fields // (`audience_allowed_email_domains`, `audience_self_registration_permission_set`), // each with one `visible` predicate over `data.audience_posture`. diff --git a/packages/services/service-settings/src/translations/en.ts b/packages/services/service-settings/src/translations/en.ts index 3f60f8597b9..af5e2d0e6d7 100644 --- a/packages/services/service-settings/src/translations/en.ts +++ b/packages/services/service-settings/src/translations/en.ts @@ -6,7 +6,7 @@ import { countryOptionLabels } from '../manifests/company.manifest.js'; /** * English (en) — built-in settings manifest translations. * - * Mirrors literals in `manifests/{mail,branding,feature-flags,storage}.manifest.ts`. + * Mirrors literals in `manifests/{mail,branding,storage}.manifest.ts`. * Keeping them explicit here lets the resolver chain (locale → fallback → literal) * always have at least an English entry to fall back to. */ @@ -222,28 +222,6 @@ export const en: PlatformTranslationData = { }, }, - feature_flags: { - title: 'Feature Flags', - description: 'Toggle experimental and beta features for this organization.', - groups: { - productivity: { title: 'Productivity' }, - collaboration: { title: 'Collaboration' }, - }, - keys: { - ai_enabled: { - label: 'AI Assistant', - help: 'Enables the in-app AI assistant panel.', - }, - kanban_swimlanes: { label: 'Kanban swimlanes' }, - realtime_cursors: { label: 'Realtime cursors' }, - inline_comments: { label: 'Inline comments' }, - }, - banners: { - beta_notice: { title: 'Heads up', - text: 'Beta features may change without notice. A feature set in the server configuration is locked on or off for the whole deployment and cannot be changed here.' }, - }, - }, - storage: { title: 'File Storage', description: 'Backend used for attachments, exports, and user uploads.', diff --git a/packages/services/service-settings/src/translations/es-ES.ts b/packages/services/service-settings/src/translations/es-ES.ts index a79c6112f66..0fbb4b0f4cb 100644 --- a/packages/services/service-settings/src/translations/es-ES.ts +++ b/packages/services/service-settings/src/translations/es-ES.ts @@ -346,28 +346,6 @@ export const esES: PlatformTranslationData = { }, }, - feature_flags: { - title: 'Indicadores de función', - description: 'Activa funciones experimentales y en beta para esta organización.', - groups: { - productivity: { title: 'Productividad' }, - collaboration: { title: 'Colaboración' }, - }, - keys: { - ai_enabled: { - label: 'Asistente de IA', - help: 'Habilita el panel del asistente de IA dentro de la aplicación.', - }, - kanban_swimlanes: { label: 'Carriles de Kanban' }, - realtime_cursors: { label: 'Cursores en tiempo real' }, - inline_comments: { label: 'Comentarios en línea' }, - }, - banners: { - beta_notice: { title: 'Atención', - text: 'Las funciones beta pueden cambiar sin previo aviso. Una función definida en la configuración del servidor queda fijada, activada o desactivada, en todo el despliegue y no se puede cambiar aquí.' }, - }, - }, - storage: { title: 'Almacenamiento de archivos', description: 'Backend usado para adjuntos, exportaciones y subidas de usuarios.', diff --git a/packages/services/service-settings/src/translations/ja-JP.ts b/packages/services/service-settings/src/translations/ja-JP.ts index e4dbaec8cba..3d2d758a149 100644 --- a/packages/services/service-settings/src/translations/ja-JP.ts +++ b/packages/services/service-settings/src/translations/ja-JP.ts @@ -346,28 +346,6 @@ export const jaJP: PlatformTranslationData = { }, }, - feature_flags: { - title: '機能フラグ', - description: 'この組織で実験的・ベータ機能を切替えます。', - groups: { - productivity: { title: '生産性' }, - collaboration: { title: 'コラボレーション' }, - }, - keys: { - ai_enabled: { - label: 'AI アシスタント', - help: 'アプリ内 AI アシスタントパネルを有効化します。', - }, - kanban_swimlanes: { label: 'カンバンのスイムレーン' }, - realtime_cursors: { label: 'リアルタイムカーソル' }, - inline_comments: { label: 'インラインコメント' }, - }, - banners: { - beta_notice: { title: 'ご注意ください', - text: 'ベータ機能は予告なく変更される場合があります。サーバー設定で指定した機能はデプロイ全体でオンまたはオフに固定され、ここでは変更できません。' }, - }, - }, - storage: { title: 'ファイルストレージ', description: '添付ファイル・エクスポート・ユーザーアップロードに使用するバックエンド。', diff --git a/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts b/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts index 79c87c8f49e..b6ba0a0d147 100644 --- a/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts +++ b/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts @@ -126,7 +126,7 @@ function undeclaredIn(data: { settings?: Record }): string[] { describe('settings translation coverage', () => { it('covers every built-in settings manifest', () => { - expect(bundledManifests.length).toBeGreaterThanOrEqual(10); + expect(bundledManifests.length).toBeGreaterThanOrEqual(9); }); it('covers the hosted platform manifests by namespace', () => { diff --git a/packages/services/service-settings/src/translations/zh-CN.ts b/packages/services/service-settings/src/translations/zh-CN.ts index 795b99e37d4..2f375fd245f 100644 --- a/packages/services/service-settings/src/translations/zh-CN.ts +++ b/packages/services/service-settings/src/translations/zh-CN.ts @@ -408,28 +408,6 @@ export const zhCN: PlatformTranslationData = { }, }, - feature_flags: { - title: '功能开关', - description: '为当前组织开启实验性与测试功能。', - groups: { - productivity: { title: '生产力' }, - collaboration: { title: '协作' }, - }, - keys: { - ai_enabled: { - label: 'AI 助手', - help: '启用应用内 AI 助手面板。', - }, - kanban_swimlanes: { label: '看板泳道' }, - realtime_cursors: { label: '实时光标' }, - inline_comments: { label: '行内评论' }, - }, - banners: { - beta_notice: { title: '注意', - text: 'Beta 功能可能随时变更,恕不另行通知。在服务器配置中设置的功能会对整个部署锁定开启或关闭,无法在此更改。' }, - }, - }, - storage: { title: '文件存储', description: '附件、导出文件与用户上传所使用的存储后端。',