Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/24368-services-this-system-copy.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions packages/plugins/plugin-auth/src/audience-posture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand All @@ -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':
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/auth-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 10 additions & 10 deletions packages/plugins/plugin-auth/src/last-admin-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}
Expand Down Expand Up @@ -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.'
: '';
Expand All @@ -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 ' +
Expand Down Expand Up @@ -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}' ` +
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/register-sso-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).' } } };
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 `
Expand All @@ -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.`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/services/service-settings/src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down Expand Up @@ -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',
Expand Down
6 changes: 3 additions & 3 deletions packages/services/service-settings/src/translations/es-ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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.' },
Expand Down
6 changes: 3 additions & 3 deletions packages/services/service-settings/src/translations/ja-JP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '差出人アドレス' },
Expand Down Expand Up @@ -91,7 +91,7 @@ export const jaJP: PlatformTranslationData = {
},
audience: {
title: '登録対象',
description: 'この環境のアプリのユーザーになれる対象。「メールドメイン」ではメール確認が常に強制されます。「オープン」でも強制されますが、デプロイ側で無効にできます。このコンソールで保存した値では無効にできません。招待、管理者による作成、SCIM プロビジョニング、エンタープライズ SSO はどのポスチャでも許可されます。',
description: 'このシステムのアプリのユーザーになれる対象。「メールドメイン」ではメール確認が常に強制されます。「オープン」でも強制されますが、デプロイ側で無効にできます。このコンソールで保存した値では無効にできません。招待、管理者による作成、SCIM プロビジョニング、エンタープライズ SSO はどのポスチャでも許可されます。',
},
password_policy: {
title: 'パスワードポリシー',
Expand Down Expand Up @@ -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 通ごとに実費が発生します。' },
Expand Down
Loading
Loading