diff --git a/.changeset/24406-spec-this-system-copy.md b/.changeset/24406-spec-this-system-copy.md new file mode 100644 index 00000000000..43d1d77ec0e --- /dev/null +++ b/.changeset/24406-spec-this-system-copy.md @@ -0,0 +1,17 @@ +--- +'@objectstack/spec': patch +'@objectstack/platform-objects': patch +--- + +Clause-②: no + +fix(spec): published copy that names the running system says "this system", or drops the noun, not "this environment" + +- **What changed.** Inside the running system, user-facing copy names the system the reader is in as "this system", or drops the noun. A permission set made here, not shipped by a package, is "authored in this system". + - The permission-set `packageId` refusal on the save, restore and publish doors now reads "a set written into no package is authored in this system, which an absent `packageId` states". + - The permission set's `packageId` help text in Setup ends "Leave empty for a set authored in this system." The English Setup forms bundle of `@objectstack/platform-objects` is regenerated from it. + - The `permission.managedBy` tombstone prescription, and the `permission-set-managed-by-retired` protocol-18 upgrade-guide entry, say "a set authored in this system carries neither". + - The `AudienceConfig.posture` and `AuthConfig.audience` describes say "who may self-register", with no noun. Their TSDoc says "who may become a user of this system's apps", the sentence the Setup Authentication group already reads. + - `AiModelsResponse.models` describes "Models this system offers". The `IMetadataService.listDiagnosed` TSDoc example reads "this system contains N items". +- **Kept "environment", where the word names a deployment or configuration scope.** `SeedLoaderConfig.env` ("Only load datasets matching this environment", the `prod | dev | test` tier), the `package-uninstall-environment-wide` upgrade-guide entry (the ADR-0131 environment-wide scope, as against an organization), and the environment artifact's consent-record TSDoc. +- **Unchanged.** Every error code, status, issue path, accepted value and export. Only the human-readable text moves. diff --git a/content/docs/references/api/protocol.mdx b/content/docs/references/api/protocol.mdx index 8b0e3e5bded..131324efa30 100644 --- a/content/docs/references/api/protocol.mdx +++ b/content/docs/references/api/protocol.mdx @@ -207,7 +207,7 @@ const result = AiAgentCapabilitiesSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **models** | `(string \| { id: string; label: string; default: boolean })[]` | ✅ | Models this environment offers | +| **models** | `(string \| { id: string; label: string; default: boolean })[]` | ✅ | Models this system offers | | **defaultModel** | `string` | optional | Default model id, when the service reports one | ### Nested Shape: `AiModelsResponse.models[number]` diff --git a/content/docs/references/security/permission.mdx b/content/docs/references/security/permission.mdx index 6bc580aa265..07566f59695 100644 --- a/content/docs/references/security/permission.mdx +++ b/content/docs/references/security/permission.mdx @@ -118,7 +118,7 @@ const result = AdminScopeSchema.parse(data); | **label** | `string` | optional | Display label | | **description** | `string` | optional | Human-readable description shown in Setup | | **packageId** | `string` | optional | [ADR-0086 D3] Owning package id for a package-shipped set (absent = env-authored); a value that disagrees with the package that ships the set, or that it is saved into, is refused by the stack schema (defineStack, os validate, os build), the package registration, and the metadata save, restore and publish doors | -| **managedBy** | `never` | optional | [REMOVED] `permission.managedBy` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0086 D3) — nothing ever read the authored key: no seeder, package upgrade or lock consulted it, so `package` never made a set upgrade-owned and `platform` or `user` never shielded one from an upgrade. Delete the key; a set's provenance is the package that ships it, decided from that package's artifact and recorded as `packageId`, and a set created in this environment carries neither. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. | +| **managedBy** | `never` | optional | [REMOVED] `permission.managedBy` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0086 D3) — nothing ever read the authored key: no seeder, package upgrade or lock consulted it, so `package` never made a set upgrade-owned and `platform` or `user` never shielded one from an upgrade. Delete the key; a set's provenance is the package that ships it, decided from that package's artifact and recorded as `packageId`, and a set authored in this system carries neither. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. | | **isDefault** | `boolean` | optional (default: `false`) | [ADR-0090 D5] App baseline for the everyone position: app-level sets are auto-bound at boot (guarded, idempotent); package-level sets become install-time suggestions an admin confirms | | **objects** | `Record` | ✅ | Entity permissions | | **fields** | `Record` | optional | Field level security | diff --git a/content/docs/references/system/auth-config.mdx b/content/docs/references/system/auth-config.mdx index 9052efb607e..fb7fd0a8541 100644 --- a/content/docs/references/system/auth-config.mdx +++ b/content/docs/references/system/auth-config.mdx @@ -52,7 +52,7 @@ Advanced / low-level Better-Auth options | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **posture** | `Enum<'invite_only' \| 'email_domain' \| 'open'>` | optional (default: `"invite_only"`) | Who may self-register into this environment: invite_only (default — operator acts only), email_domain (allowlisted email domains), or open (anyone). email_domain forces email verification on; open forces it on unless the deployment explicitly sets emailAndPassword.requireEmailVerification to false. | +| **posture** | `Enum<'invite_only' \| 'email_domain' \| 'open'>` | optional (default: `"invite_only"`) | Who may self-register: invite_only (default — operator acts only), email_domain (allowlisted email domains), or open (anyone). email_domain forces email verification on; open forces it on unless the deployment explicitly sets emailAndPassword.requireEmailVerification to false. | | **allowedEmailDomains** | `string[]` | optional | Email domains admitted to self-register under posture email_domain (exact, case-insensitive match; subdomains need their own entries). Required non-empty for email_domain; refused under other postures. | | **selfRegistrationPermissionSet** | `string` | optional | Name of the permission set granted to each self-registrant. Required when posture is email_domain or open; refused for invite_only. admin_full_access is refused. | @@ -77,7 +77,7 @@ Advanced / low-level Better-Auth options | **oidcProviders** | `{ providerId: string; name?: string; discoveryUrl?: string; issuer?: string; … }[]` | optional | List of OIDC/OAuth2 providers for enterprise SSO. Product or enterprise packages can pass this directly or contribute it through auth:configure. | | **emailAndPassword** | `{ enabled?: boolean; disableSignUp?: boolean; requireEmailVerification?: boolean; minPasswordLength?: number; … }` | optional | Email and password authentication options forwarded to better-auth | | **emailVerification** | `{ sendOnSignUp?: boolean; sendOnSignIn?: boolean; autoSignInAfterVerification?: boolean; expiresIn?: number }` | optional | Email verification options forwarded to better-auth | -| **audience** | `{ posture?: Enum<'invite_only' \| 'email_domain' \| 'open'>; allowedEmailDomains?: string[]; selfRegistrationPermissionSet?: string }` | optional | Audience posture: who may self-register into this environment (invite_only — the default — \| email_domain \| open). See AudienceConfigSchema. | +| **audience** | `{ posture?: Enum<'invite_only' \| 'email_domain' \| 'open'>; allowedEmailDomains?: string[]; selfRegistrationPermissionSet?: string }` | optional | Audience posture: who may self-register (invite_only — the default — \| email_domain \| open). See AudienceConfigSchema. | | **advanced** | `{ crossSubDomainCookies?: object; useSecureCookies?: boolean; disableCSRFCheck?: boolean; cookiePrefix?: string }` | optional | Advanced / low-level Better-Auth options | | **ssoOnlyMode** | `boolean` | optional | SSO-only login: hide the local password form + self-registration (the break-glass password endpoint stays enabled) | | **mutualTls** | `{ enabled?: boolean; clientCertRequired?: boolean; trustedCAs: string[]; crlUrl?: string; … }` | optional | Mutual TLS (mTLS) configuration | @@ -169,7 +169,7 @@ OIDC / Generic OAuth2 provider configuration for enterprise SSO | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **posture** | `Enum<'invite_only' \| 'email_domain' \| 'open'>` | optional (default: `"invite_only"`) | Who may self-register into this environment: invite_only (default — operator acts only), email_domain (allowlisted email domains), or open (anyone). email_domain forces email verification on; open forces it on unless the deployment explicitly sets emailAndPassword.requireEmailVerification to false. | +| **posture** | `Enum<'invite_only' \| 'email_domain' \| 'open'>` | optional (default: `"invite_only"`) | Who may self-register: invite_only (default — operator acts only), email_domain (allowlisted email domains), or open (anyone). email_domain forces email verification on; open forces it on unless the deployment explicitly sets emailAndPassword.requireEmailVerification to false. | | **allowedEmailDomains** | `string[]` | optional | Email domains admitted to self-register under posture email_domain (exact, case-insensitive match; subdomains need their own entries). Required non-empty for email_domain; refused under other postures. | | **selfRegistrationPermissionSet** | `string` | optional | Name of the permission set granted to each self-registrant. Required when posture is email_domain or open; refused for invite_only. admin_full_access is refused. | diff --git a/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts index bd476bb387f..9883700cd95 100644 --- a/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts @@ -2334,7 +2334,7 @@ export const enMetadataForms: NonNullable = { }, packageId: { label: "Package Id", - helpText: "The package that ships this set; a value naming any other package is refused. Leave empty for a set created in this environment." + helpText: "The package that ships this set; a value naming any other package is refused. Leave empty for a set authored in this system." }, systemPermissions: { label: "System Permissions", diff --git a/packages/spec/src/api/protocol.zod.ts b/packages/spec/src/api/protocol.zod.ts index 2d64babedb4..ff6e8f21781 100644 --- a/packages/spec/src/api/protocol.zod.ts +++ b/packages/spec/src/api/protocol.zod.ts @@ -3370,7 +3370,7 @@ export const AiModelsResponseSchema = lazySchema(() => z.object({ label: z.string().describe('Display label for the picker'), default: z.boolean().describe('Whether this is the environment default'), }), - ])).describe('Models this environment offers'), + ])).describe('Models this system offers'), defaultModel: z.string().optional().describe('Default model id, when the service reports one'), })); diff --git a/packages/spec/src/contracts/metadata-service.ts b/packages/spec/src/contracts/metadata-service.ts index 6da7ee244fe..caa60ea5423 100644 --- a/packages/spec/src/contracts/metadata-service.ts +++ b/packages/spec/src/contracts/metadata-service.ts @@ -390,7 +390,7 @@ export interface IMetadataService { * read a MISS and an OUTAGE arrive as the same `undefined`; on the plural * read they arrive as the same *array*, and an array carries a **count**. * A count is the strongest positive claim a read can make — a consumer that - * renders `list(t).length` as "this environment contains N items" states, + * renders `list(t).length` as "this system contains N items" states, * positively and numerically, something no read established. `list` cannot * be fixed in place (every caller would have to change at once, and most * are right to keep ignoring the verdict), so the verdict is offered diff --git a/packages/spec/src/migrations/entries/semantic/18.permission-set-managed-by-retired.ts b/packages/spec/src/migrations/entries/semantic/18.permission-set-managed-by-retired.ts index e43f5d1d510..fe6fb276a7b 100644 --- a/packages/spec/src/migrations/entries/semantic/18.permission-set-managed-by-retired.ts +++ b/packages/spec/src/migrations/entries/semantic/18.permission-set-managed-by-retired.ts @@ -15,7 +15,7 @@ export const entry: SemanticMigration = { surface: 'permissions[].managedBy — the authored per-record provenance (package / platform / user) on a ' + 'permission set', replacement: 'Nothing: delete the key. A set\'s provenance is the package that ships it, decided from that ' - + 'package\'s artifact and recorded as packageId; a set created in this environment carries neither. The ' + + 'package\'s artifact and recorded as packageId; a set authored in this system carries neither. The ' + 'sys_permission_set.managed_by column the platform keeps is a different surface and is unchanged.', reason: 'The D2 conversion `permission-set-managed-by-removed` deletes `managedBy` from every permission set ' + 'in author sources, stored permission rows and built artifacts, and the delete is lossless: no seeder, ' diff --git a/packages/spec/src/security/permission-set-owner-binding.ts b/packages/spec/src/security/permission-set-owner-binding.ts index 59b4d78e55e..a530638d587 100644 --- a/packages/spec/src/security/permission-set-owner-binding.ts +++ b/packages/spec/src/security/permission-set-owner-binding.ts @@ -124,7 +124,7 @@ export function judgePermissionSetOwnerBinding( : `, or ${door === 'save' ? 'save the set' : 'save it'} into package '${declared}' ` + `(?package=${declared} on the REST door, packageId in process)`; const owner = bound === null - ? 'a set written into no package is authored in this environment, which an absent `packageId` states' + ? 'a set written into no package is authored in this system, which an absent `packageId` states' : 'the package a set is written into is its owner'; const tail = `and ${owner} (ADR-0086 D3): a \`packageId\` that disagrees is refused, never silently overridden.`; const message = door === 'save' diff --git a/packages/spec/src/security/permission.form.ts b/packages/spec/src/security/permission.form.ts index a48ef1a3bb9..e4d656de222 100644 --- a/packages/spec/src/security/permission.form.ts +++ b/packages/spec/src/security/permission.form.ts @@ -49,7 +49,7 @@ export const permissionForm = defineForm({ // (`judgePermissionSetOwnerBinding`). { field: 'description', type: 'textarea', colSpan: 2, helpText: 'Human-readable description shown in Setup.' }, { field: 'isDefault', type: 'boolean', colSpan: 1, helpText: 'Gives this set to everyone who signs in, through the built-in everyone position. A set the app itself declares is granted at startup; a set from an installed package becomes a suggestion an admin confirms in Setup. A set with high-privilege permissions is never granted this way. Default false.' }, - { field: 'packageId', type: 'text', colSpan: 2, helpText: 'The package that ships this set; a value naming any other package is refused. Leave empty for a set created in this environment.' }, + { field: 'packageId', type: 'text', colSpan: 2, helpText: 'The package that ships this set; a value naming any other package is refused. Leave empty for a set authored in this system.' }, ], }, { diff --git a/packages/spec/src/security/permission.zod.ts b/packages/spec/src/security/permission.zod.ts index e74ddbdc2f7..bdbca304488 100644 --- a/packages/spec/src/security/permission.zod.ts +++ b/packages/spec/src/security/permission.zod.ts @@ -677,7 +677,7 @@ const PERMISSION_SET_MANAGED_BY_REMOVED = + 'nothing ever read the authored key: no seeder, package upgrade or lock consulted it, so `package` never made ' + 'a set upgrade-owned and `platform` or `user` never shielded one from an upgrade. Delete the key; a set\'s ' + 'provenance is the package that ships it, decided from that package\'s artifact and recorded as `packageId`, ' - + 'and a set created in this environment carries neither. ' + + 'and a set authored in this system carries neither. ' + 'Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand.'; /** diff --git a/packages/spec/src/system/auth-config.zod.ts b/packages/spec/src/system/auth-config.zod.ts index a92454d61b5..1acecf8a93d 100644 --- a/packages/spec/src/system/auth-config.zod.ts +++ b/packages/spec/src/system/auth-config.zod.ts @@ -342,7 +342,7 @@ export const EmailVerificationConfigSchema = lazySchema(() => z.object({ /** * Audience posture — the ONE declaration answering "who may become a user of - * an app built on this environment" (#11739 / epic #11723). + * an app built on this system" (#11739 / epic #11723). * * Before this existed, the answer was an emergent property of * `emailAndPassword.disableSignUp` + `emailVerification` + `ssoOnlyMode` + @@ -398,7 +398,7 @@ const AUDIENCE_EMAIL_DOMAIN_SHAPE = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[ */ export const AudienceConfigSchema = lazySchema(() => z.object({ /** - * Who may become a user of this environment's apps: + * Who may become a user of this system's apps: * * - `invite_only` (default): self-registration is CLOSED. A user comes into * existence only through an operator-side act — a pending invitation @@ -414,7 +414,7 @@ export const AudienceConfigSchema = lazySchema(() => z.object({ * console cannot turn it off. */ posture: z.enum(AUDIENCE_POSTURES).default('invite_only').describe( - 'Who may self-register into this environment: invite_only (default — operator acts only), ' + + 'Who may self-register: invite_only (default — operator acts only), ' + 'email_domain (allowlisted email domains), or open (anyone). ' + 'email_domain forces email verification on; open forces it on unless the deployment explicitly ' + 'sets emailAndPassword.requireEmailVerification to false.', @@ -578,7 +578,7 @@ export const AuthConfigSchema = lazySchema(() => z.object({ emailAndPassword: EmailAndPasswordConfigSchema, emailVerification: EmailVerificationConfigSchema, /** - * Audience posture (#11739) — who may become a user of this environment's + * Audience posture (#11739) — who may become a user of this system's * apps. Undeclared ⇒ `invite_only` (the safe default; maintainer ruling * 2026-08-24 on epic #11723 — no legacy/undeclared limbo). * @@ -598,7 +598,7 @@ export const AuthConfigSchema = lazySchema(() => z.object({ * A `false` stored only through the settings console is still refused. */ audience: AudienceConfigSchema.optional().describe( - 'Audience posture: who may self-register into this environment ' + + 'Audience posture: who may self-register ' + '(invite_only — the default — | email_domain | open). See AudienceConfigSchema.', ), advanced: AdvancedAuthConfigSchema,