From 3777e26dc067b2383761c1281d16a95bfc1971d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 22:24:54 +0000 Subject: [PATCH] fix(services): in-system copy says "this system" or drops the noun, not "this environment" Refusals, two guard warnings and Setup help text in plugin-auth, plugin-security and service-settings, read inside the running system, follow the per-string rule: "this system", or drop the noun. The OIDC authorize-gate refusal keeps its noun: only the cloud control plane installs that gate, and there it names the environment being signed in to. Copy only: no status, error code, settings key or response shape moves. Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH Co-authored-by: Claude --- .changeset/24368-services-this-system-copy.md | 29 +++++++++++++++++++ .../plugin-auth/src/audience-posture.ts | 4 +-- .../plugins/plugin-auth/src/auth-plugin.ts | 2 +- .../plugin-auth/src/last-admin-guard.ts | 20 ++++++------- .../plugin-auth/src/register-sso-provider.ts | 2 +- .../src/packaged-permission-set-lock.ts | 6 ++-- .../src/manifests/auth.manifest.ts | 2 +- .../src/manifests/mail.manifest.ts | 2 +- .../src/manifests/sms.manifest.ts | 2 +- .../service-settings/src/translations/en.ts | 4 +-- .../src/translations/es-ES.ts | 6 ++-- .../src/translations/ja-JP.ts | 6 ++-- .../src/translations/zh-CN.ts | 6 ++-- 13 files changed, 60 insertions(+), 31 deletions(-) create mode 100644 .changeset/24368-services-this-system-copy.md diff --git a/.changeset/24368-services-this-system-copy.md b/.changeset/24368-services-this-system-copy.md new file mode 100644 index 00000000000..7bacbf6216d --- /dev/null +++ b/.changeset/24368-services-this-system-copy.md @@ -0,0 +1,29 @@ +--- +'@objectstack/plugin-auth': patch +'@objectstack/plugin-security': patch +'@objectstack/service-settings': patch +--- + +Clause-②: no + +Refusals and Setup help text that a user reads inside the running system no longer call that system +"this environment". They say "this system", or they drop the noun. + +- `@objectstack/plugin-auth`: the two self-registration refusals drop the noun ("Self-registration is + closed (audience posture invite_only).", "Self-registration is limited to approved email domains, …"). + So do the OAuth self-service `503` ("OIDC provider is not enabled") and the SSO `400` + `DOMAIN_VERIFICATION_DISABLED` ("Domain verification is not enabled (set + OS_SSO_DOMAIN_VERIFICATION)."). The last-administrator guard's refusals and its two warning lines say + "this system", including "nobody able to administer the system". The OIDC authorize-gate refusal + (`403 ENV_ACCESS_DENIED`) keeps "this environment". Only the cloud control plane installs that gate, + and there the noun names the environment a cloud user is signing in to. +- `@objectstack/plugin-security`: the diagnostic `message` of the packaged-permission-set lock now reads + "… is declared by package '…' and is locked — editing it here would …". The provenance-unknown + refusal says "this system could not determine …". Both `userMessage` texts are unchanged. +- `@objectstack/service-settings`: the Mail and SMS provider groups read "Choose how this system sends + outbound email." and "… outbound SMS.". The Authentication audience group reads "Who may become a user + of this system's apps.". The zh-CN, ja-JP and es-ES bundles change only the noun, to 本系统, + このシステム and este sistema. + +Only the human-readable text moves. Every HTTP status, error code, settings key and response shape +stays as it was. diff --git a/packages/plugins/plugin-auth/src/audience-posture.ts b/packages/plugins/plugin-auth/src/audience-posture.ts index f48b6f6b966..2f4dd79f570 100644 --- a/packages/plugins/plugin-auth/src/audience-posture.ts +++ b/packages/plugins/plugin-auth/src/audience-posture.ts @@ -473,7 +473,7 @@ export function decideAudienceAdmission(input: AudienceAdmissionInput): Audience admit: false, code: SELF_REGISTRATION_CLOSED, message: - 'Self-registration is closed on this environment (audience posture invite_only). ' + + 'Self-registration is closed (audience posture invite_only). ' + 'Ask an administrator for an invitation.', }; case 'email_domain': @@ -484,7 +484,7 @@ export function decideAudienceAdmission(input: AudienceAdmissionInput): Audience admit: false, code: EMAIL_DOMAIN_NOT_ALLOWED, message: - 'Self-registration on this environment is limited to approved email domains, and this address ' + + 'Self-registration is limited to approved email domains, and this address ' + 'is not on the list. Use your organization email, or ask an administrator for an invitation.', }; case 'open': diff --git a/packages/plugins/plugin-auth/src/auth-plugin.ts b/packages/plugins/plugin-auth/src/auth-plugin.ts index 854996ddba1..4de01344a9d 100644 --- a/packages/plugins/plugin-auth/src/auth-plugin.ts +++ b/packages/plugins/plugin-auth/src/auth-plugin.ts @@ -3502,7 +3502,7 @@ export class AuthPlugin implements Plugin { const authApi: any = await this.authManager!.getApi(); if (!authApi?.createOAuthClient) { - return c.json({ success: false, error: { code: 'SERVICE_UNAVAILABLE', message: 'OIDC provider is not enabled on this environment' } }, 503); + return c.json({ success: false, error: { code: 'SERVICE_UNAVAILABLE', message: 'OIDC provider is not enabled' } }, 503); } // Forward request headers so better-auth can resolve the caller's diff --git a/packages/plugins/plugin-auth/src/last-admin-guard.ts b/packages/plugins/plugin-auth/src/last-admin-guard.ts index fdf2e3d1468..f8ba9876f6b 100644 --- a/packages/plugins/plugin-auth/src/last-admin-guard.ts +++ b/packages/plugins/plugin-auth/src/last-admin-guard.ts @@ -890,7 +890,7 @@ export function registerLastAdminGuard( `Refusing this ${words.noun}: '${object}' returned more than ${maxScan} rows, so the ` + `remaining administrators could not be verified (${BREAK_GLASS_CITATION}). ` + `${words.Verb} a narrower set of ${words.subject}, or raise the guard's maxScan if ` + - 'this environment really is that large.', + 'this system really is that large.', words.table, ); } @@ -1275,7 +1275,7 @@ export function registerLastAdminGuard( const walledRemedy = postureEnforcesWall(resolveTenancyPosture()) ? ` ⚠️ This deployment runs a WALLED tenancy posture, where an unscoped '${ADMIN_FULL_ACCESS}' ` + 'grant row is no longer an anchor for platform admin standing at all — restoring or ' - + 're-activating that row will NOT give this environment an administrator. Declare one in ' + + 're-activating that row will NOT give this system an administrator. Declare one in ' + `${PLATFORM_OWNER_EMAIL_ENV} (a verified account's address, comma-separated for several) ` + 'instead; that is the only channel to platform admin standing under a wall.' : ''; @@ -1301,7 +1301,7 @@ export function registerLastAdminGuard( `permission set is switched OFF — ${stranded.length} unscoped grant(s) confer nothing`, ); throw refuse( - `Refusing this ${words.noun}: this environment recognises NO administrator because its ` + + `Refusing this ${words.noun}: this system recognises NO administrator because its ` + `'${ADMIN_FULL_ACCESS}' permission set is switched OFF in '${METADATA_ACTIVATION}' — ` + `${stranded.length} unscoped, in-window '${USER_PERMISSION_SET}' grant(s) still point ` + 'at it and confer nothing while it is off. That is not the bootstrap window, and ' + @@ -1351,7 +1351,7 @@ export function registerLastAdminGuard( `NOT a fresh install — ${dangling.length} dangling unscoped grant(s): ${dangling.join(', ')}`, ); throw refuse( - `Refusing this ${words.noun}: this environment recognises NO administrator, and this is not ` + + `Refusing this ${words.noun}: this system recognises NO administrator, and this is not ` + `the bootstrap window — ${dangling.length} unscoped, in-window '${USER_PERMISSION_SET}' ` + 'grant(s) still name a permission set the security catalog does not hold, ' + `or name none yet (${dangling.join(', ')}). That is the state a REMOVED '${ADMIN_FULL_ACCESS}' ` + @@ -1507,16 +1507,16 @@ export function registerLastAdminGuard( if (remaining.length > 0) return; logger?.warn( - `[LastAdminGuard] refused a ${words.noun} that would have left this environment with no ` + + `[LastAdminGuard] refused a ${words.noun} that would have left this system with no ` + `unbanned administrator (target: ${losing.join(', ')})`, ); const many = losing.length > 1; throw refuse( `Refusing to ${words.verb} ${losing.map((id) => `'${id}'`).join(', ')}: ` + `${many ? 'those are the last administrators' : 'that is the last administrator'} this ` + - `environment has that ${many ? 'are' : 'is'} not already banned, and ${words.gerund} ` + + `system has that ${many ? 'are' : 'is'} not already banned, and ${words.gerund} ` + `${many ? 'them' : 'that account'} would leave nobody able to administer the ` + - `environment or restore anyone's access (${BREAK_GLASS_CITATION}). ${REMEDY} ` + + `system or restore anyone's access (${BREAK_GLASS_CITATION}). ${REMEDY} ` + `If the ${words.noun} came from an identity provider, the SCIM deprovision is too ` + 'broad — fix the IdP group, not this guard.', words.table, @@ -1610,18 +1610,18 @@ export function registerLastAdminGuard( logger?.warn( `[LastAdminGuard] refused a ${words.noun} on '${table}' that would have left this ` + - `environment with no unbanned administrator (losing: ${losing.join(', ')})`, + `system with no unbanned administrator (losing: ${losing.join(', ')})`, ); const many = losing.length > 1; throw refuse( `Refusing this ${words.noun}: it would revoke the administrator standing of ` + `${losing.map((id) => `'${id}'`).join(', ')}, ` + `${many ? 'who are the last administrators' : 'who is the last administrator'} this ` + - `environment has that ${many ? 'are' : 'is'} not already banned. The ` + + `system has that ${many ? 'are' : 'is'} not already banned. The ` + `'${table}' row is what MAKES ${many ? 'those accounts' : 'that account'} an ` + `administrator, so ${words.gerund} it has the same end state as ${words.gerund} ` + `${many ? 'the users themselves' : 'the user themselves'}: nobody would be able to ` + - `administer the environment or restore anyone's access (${BREAK_GLASS_CITATION}). ` + + `administer the system or restore anyone's access (${BREAK_GLASS_CITATION}). ` + `${REMEDY} ${standingOrigin(table, words.noun)}`, words.table, ); diff --git a/packages/plugins/plugin-auth/src/register-sso-provider.ts b/packages/plugins/plugin-auth/src/register-sso-provider.ts index 4255bb4a92d..0868cf27d36 100644 --- a/packages/plugins/plugin-auth/src/register-sso-provider.ts +++ b/packages/plugins/plugin-auth/src/register-sso-provider.ts @@ -382,7 +382,7 @@ type DomainVerificationBridgeResult = RegisterSsoFormResult & { body: RegisterSs /** The feature is off on this environment — one answer for both routes (#10859). */ function domainVerificationDisabled(): DomainVerificationBridgeResult { - return { status: 400, body: { success: false, error: { code: 'DOMAIN_VERIFICATION_DISABLED', message: 'Domain verification is not enabled for this environment (set OS_SSO_DOMAIN_VERIFICATION).' } } }; + return { status: 400, body: { success: false, error: { code: 'DOMAIN_VERIFICATION_DISABLED', message: 'Domain verification is not enabled (set OS_SSO_DOMAIN_VERIFICATION).' } } }; } /** diff --git a/packages/plugins/plugin-security/src/packaged-permission-set-lock.ts b/packages/plugins/plugin-security/src/packaged-permission-set-lock.ts index 059dd1c5d3d..0a8d2538ffe 100644 --- a/packages/plugins/plugin-security/src/packaged-permission-set-lock.ts +++ b/packages/plugins/plugin-security/src/packaged-permission-set-lock.ts @@ -334,8 +334,8 @@ export class PackagedPermissionSetLockedError extends Error { readonly userMessage: string; constructor(name: string, packageId: string, _operation: 'insert' | 'update') { super( - `[Security] Permission set '${name}' is declared by package '${packageId}' and is locked in this ` - + `environment — editing it here would silently fork it from the package, and the fork would win over ` + `[Security] Permission set '${name}' is declared by package '${packageId}' and is locked — ` + + `editing it here would silently fork it from the package, and the fork would win over ` + `every future upgrade with no signal. ` + `Clone it instead (the "Clone" action on the permission set, or POST ` + `/api/v1/security/permission-sets/${name}/clone with a new name) and edit the clone — the clone is your organization's own set, and upgrades keep ` @@ -360,7 +360,7 @@ export class PackagedPermissionSetProvenanceUnknownError extends Error { readonly userMessage: string; constructor(name: string, reason: string) { super( - `[Security] Permission set '${name}' cannot be saved right now: this environment could not determine ` + `[Security] Permission set '${name}' cannot be saved right now: this system could not determine ` + `whether the set is declared by an installed package (${reason}). The save is refused rather than ` + `accepted, because accepting it would silently fork a packaged set if it turns out to be one. Retry ` + `once the metadata layer is readable; if you meant to customize a packaged set, clone it instead.`, diff --git a/packages/services/service-settings/src/manifests/auth.manifest.ts b/packages/services/service-settings/src/manifests/auth.manifest.ts index e658e52658c..c6991edd25a 100644 --- a/packages/services/service-settings/src/manifests/auth.manifest.ts +++ b/packages/services/service-settings/src/manifests/auth.manifest.ts @@ -89,7 +89,7 @@ const manifest = { label: 'Audience', required: false, description: - 'Who may become a user of this environment\'s apps. Allowlisted email domains always force email verification on. Open forces it on unless the deployment turns it off; a value saved in this console cannot. Invitations, admin-created users, SCIM provisioning, and enterprise SSO are admitted under every posture.', + 'Who may become a user of this system\'s apps. Allowlisted email domains always force email verification on. Open forces it on unless the deployment turns it off; a value saved in this console cannot. Invitations, admin-created users, SCIM provisioning, and enterprise SSO are admitted under every posture.', }, { type: 'select', diff --git a/packages/services/service-settings/src/manifests/mail.manifest.ts b/packages/services/service-settings/src/manifests/mail.manifest.ts index db39fb74ba9..9583f23be25 100644 --- a/packages/services/service-settings/src/manifests/mail.manifest.ts +++ b/packages/services/service-settings/src/manifests/mail.manifest.ts @@ -55,7 +55,7 @@ const manifest = { order: 10, specifiers: [ { type: 'group', id: 'provider', label: 'Provider', required: false, - description: 'Choose how this environment sends outbound email.' }, + description: 'Choose how this system sends outbound email.' }, // See PROVIDER_OPTIONS above — the option list is a contract with // @objectstack/plugin-email, not a wish list. diff --git a/packages/services/service-settings/src/manifests/sms.manifest.ts b/packages/services/service-settings/src/manifests/sms.manifest.ts index 83913daa46f..5852444d961 100644 --- a/packages/services/service-settings/src/manifests/sms.manifest.ts +++ b/packages/services/service-settings/src/manifests/sms.manifest.ts @@ -19,7 +19,7 @@ const manifest = { order: 11, specifiers: [ { type: 'group', id: 'provider', label: 'Provider', required: false, - description: 'Choose how this environment sends outbound SMS.' }, + description: 'Choose how this system sends outbound SMS.' }, // ⚠️ This options table is a CONTRACT, not a menu of aspirations: every // value here must be one `@objectstack/service-sms` can actually build a diff --git a/packages/services/service-settings/src/translations/en.ts b/packages/services/service-settings/src/translations/en.ts index e87b63c0b81..28f1340d21d 100644 --- a/packages/services/service-settings/src/translations/en.ts +++ b/packages/services/service-settings/src/translations/en.ts @@ -24,7 +24,7 @@ export const en: PlatformTranslationData = { title: 'Mail Delivery', description: 'SMTP and transactional email provider configuration.', groups: { - provider: { title: 'Provider', description: 'Choose how this environment sends outbound email.' }, + provider: { title: 'Provider', description: 'Choose how this system sends outbound email.' }, smtp: { title: 'SMTP' }, api_key: { title: 'API key' }, from_address: { title: 'From address' }, @@ -136,7 +136,7 @@ export const en: PlatformTranslationData = { }, audience: { title: 'Audience', - description: 'Who may become a user of this environment\'s apps. Allowlisted email domains always force email verification on. Open forces it on unless the deployment turns it off; a value saved in this console cannot. Invitations, admin-created users, SCIM provisioning, and enterprise SSO are admitted under every posture.', + description: 'Who may become a user of this system\'s apps. Allowlisted email domains always force email verification on. Open forces it on unless the deployment turns it off; a value saved in this console cannot. Invitations, admin-created users, SCIM provisioning, and enterprise SSO are admitted under every posture.', }, password_policy: { title: 'Password policy', diff --git a/packages/services/service-settings/src/translations/es-ES.ts b/packages/services/service-settings/src/translations/es-ES.ts index ba308027f67..0be06a67aa4 100644 --- a/packages/services/service-settings/src/translations/es-ES.ts +++ b/packages/services/service-settings/src/translations/es-ES.ts @@ -20,7 +20,7 @@ export const esES: PlatformTranslationData = { title: 'Envío de correo', description: 'Configuración de SMTP y del proveedor de correo transaccional.', groups: { - provider: { title: 'Proveedor', description: 'Elige cómo envía correo saliente este entorno.' }, + provider: { title: 'Proveedor', description: 'Elige cómo envía correo saliente este sistema.' }, smtp: { title: 'SMTP' }, api_key: { title: 'Clave de API' }, from_address: { title: 'Dirección de remitente' }, @@ -91,7 +91,7 @@ export const esES: PlatformTranslationData = { }, audience: { title: 'Audiencia', - description: 'Quién puede convertirse en usuario de las aplicaciones de este entorno. «Dominios de correo» siempre fuerza la verificación de correo. «Abierto» también la fuerza, salvo que el despliegue la desactive; un valor guardado en esta consola no puede hacerlo. Las invitaciones, los usuarios creados por administradores, el aprovisionamiento SCIM y el SSO empresarial se admiten bajo cualquier postura.', + description: 'Quién puede convertirse en usuario de las aplicaciones de este sistema. «Dominios de correo» siempre fuerza la verificación de correo. «Abierto» también la fuerza, salvo que el despliegue la desactive; un valor guardado en esta consola no puede hacerlo. Las invitaciones, los usuarios creados por administradores, el aprovisionamiento SCIM y el SSO empresarial se admiten bajo cualquier postura.', }, password_policy: { title: 'Política de contraseñas', @@ -251,7 +251,7 @@ export const esES: PlatformTranslationData = { title: 'Envío de SMS', description: 'Configuración del proveedor de SMS para inicio de sesión con OTP, invitaciones y notificaciones.', groups: { - provider: { title: 'Proveedor', description: 'Elige cómo envía este entorno los SMS salientes.' }, + provider: { title: 'Proveedor', description: 'Elige cómo envía este sistema los SMS salientes.' }, aliyun: { title: 'Aliyun SMS' }, twilio: { title: 'Twilio' }, limits: { title: 'Límites de gasto', description: 'Limita el volumen de SMS salientes del despliegue. El SMS es un canal de pago y cada envío cuesta dinero real.' }, diff --git a/packages/services/service-settings/src/translations/ja-JP.ts b/packages/services/service-settings/src/translations/ja-JP.ts index 06a7e0f91f3..c9d77abfe86 100644 --- a/packages/services/service-settings/src/translations/ja-JP.ts +++ b/packages/services/service-settings/src/translations/ja-JP.ts @@ -20,7 +20,7 @@ export const jaJP: PlatformTranslationData = { title: 'メール配信', description: 'SMTP およびトランザクションメールプロバイダー設定。', groups: { - provider: { title: 'プロバイダー', description: 'この環境の送信方法を選択します。' }, + provider: { title: 'プロバイダー', description: 'このシステムの送信方法を選択します。' }, smtp: { title: 'SMTP' }, api_key: { title: 'API キー' }, from_address: { title: '差出人アドレス' }, @@ -91,7 +91,7 @@ export const jaJP: PlatformTranslationData = { }, audience: { title: '登録対象', - description: 'この環境のアプリのユーザーになれる対象。「メールドメイン」ではメール確認が常に強制されます。「オープン」でも強制されますが、デプロイ側で無効にできます。このコンソールで保存した値では無効にできません。招待、管理者による作成、SCIM プロビジョニング、エンタープライズ SSO はどのポスチャでも許可されます。', + description: 'このシステムのアプリのユーザーになれる対象。「メールドメイン」ではメール確認が常に強制されます。「オープン」でも強制されますが、デプロイ側で無効にできます。このコンソールで保存した値では無効にできません。招待、管理者による作成、SCIM プロビジョニング、エンタープライズ SSO はどのポスチャでも許可されます。', }, password_policy: { title: 'パスワードポリシー', @@ -251,7 +251,7 @@ export const jaJP: PlatformTranslationData = { title: 'SMS 配信', description: 'OTP サインイン、招待、通知に使う SMS プロバイダー設定。', groups: { - provider: { title: 'プロバイダー', description: 'この環境が送信 SMS をどう送るかを選択します。' }, + provider: { title: 'プロバイダー', description: 'このシステムが送信 SMS をどう送るかを選択します。' }, aliyun: { title: 'Aliyun SMS' }, twilio: { title: 'Twilio' }, limits: { title: '送信上限', description: 'この配備の送信 SMS 量を制限します。SMS は有料チャネルで、1 通ごとに実費が発生します。' }, diff --git a/packages/services/service-settings/src/translations/zh-CN.ts b/packages/services/service-settings/src/translations/zh-CN.ts index 568263dfa14..8d947000c84 100644 --- a/packages/services/service-settings/src/translations/zh-CN.ts +++ b/packages/services/service-settings/src/translations/zh-CN.ts @@ -20,7 +20,7 @@ export const zhCN: PlatformTranslationData = { title: '邮件投递', description: 'SMTP 与事务性邮件服务商配置。', groups: { - provider: { title: '服务商', description: '选择此环境如何发送邮件。' }, + provider: { title: '服务商', description: '选择本系统如何发送邮件。' }, smtp: { title: 'SMTP' }, api_key: { title: 'API 密钥' }, from_address: { title: '发件地址' }, @@ -60,7 +60,7 @@ export const zhCN: PlatformTranslationData = { title: '短信投递', description: '用于 OTP 登录、邀请与通知的短信服务商配置。', groups: { - provider: { title: '服务商', description: '选择此环境如何发送外发短信。' }, + provider: { title: '服务商', description: '选择本系统如何发送外发短信。' }, aliyun: { title: '阿里云短信' }, twilio: { title: 'Twilio' }, limits: { title: '发送额度', description: '限制本部署的外发短信量。短信是付费通道,每一条都产生真实费用。' }, @@ -234,7 +234,7 @@ export const zhCN: PlatformTranslationData = { }, audience: { title: '注册受众', - description: '谁可以成为本环境应用的用户。「邮箱域名」口径始终强制开启邮箱验证。「开放」口径也会强制开启,除非部署方关闭它;在此控制台保存的值无法关闭。邀请、管理员创建、SCIM 开通和企业 SSO 在任何口径下都可进入。', + description: '谁可以成为本系统应用的用户。「邮箱域名」口径始终强制开启邮箱验证。「开放」口径也会强制开启,除非部署方关闭它;在此控制台保存的值无法关闭。邀请、管理员创建、SCIM 开通和企业 SSO 在任何口径下都可进入。', }, password_policy: { title: '密码策略',