Skip to content

Commit 6e5bb29

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-11525-objectql-double-limit-gate
2 parents 2bd0e5c + 0b04839 commit 6e5bb29

72 files changed

Lines changed: 4075 additions & 300 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/plugin-auth': minor
4+
'@objectstack/verify': patch
5+
---
6+
7+
feat(spec,plugin-auth)!: one declared audience posture — `invite_only | email_domain | open`, default `invite_only`
8+
9+
**BREAKING CHANGE (ships as `minor` under the launch-window rule; every publishable package rides the fixed group).** "Who may become a user of an environment's apps" is now ONE declaration instead of an emergent property of five switches — and its default flips to the safe end.
10+
11+
- New authorable surface `auth.audience` on `AuthConfig` (`@objectstack/spec/system`): `posture` (`invite_only` | `email_domain` | `open`), `allowedEmailDomains` (required non-empty for `email_domain`), `selfRegistrationPermissionSet` (required whenever the posture permits self-registration; `admin_full_access` refused). Off-vocabulary postures and inert declarations (domains outside `email_domain`, a permission set under `invite_only`) are refused at parse AND at plugin-auth's config entry — never coerced.
12+
- **FROM:** an undeclared audience meant open email/password self-registration with no email verification, and self-registrants implicitly fell back to the `member_default` permission set. **TO:** an undeclared audience IS `invite_only` — self-serve sign-up (email/password, social-provider OAuth JIT, magic-link/OTP/phone/anonymous, and any unclassified creation method) is refused `403 SELF_REGISTRATION_CLOSED` unless the address holds a pending `sys_invitation` (the first account on a fresh install is exempt — the bootstrap bypass). One-line fix for deployments that mean to stay open: declare `auth: { audience: { posture: 'open', selfRegistrationPermissionSet: 'member_default' } }`.
13+
- `email_domain` admits only allowlisted domains (`403 EMAIL_DOMAIN_NOT_ALLOWED` otherwise; exact case-insensitive match, subdomains not implied, `+tag` local parts irrelevant). Any self-registration-permitting posture FORCES `requireEmailVerification` on (an explicit `false` beside it is refused at boot) and grants each self-registrant the DECLARED permission set (`sys_user_permission_set`); a declaration that cannot be resolved refuses admission (`403 AUTH_CONFIG_ERROR`) rather than admitting ungranted.
14+
- Operator-driven creation is never posture-gated: admin create-user / bulk import, SCIM provisioning, and JIT through operator-registered identity providers (`oidcProviders`, `@better-auth/sso`) keep working under every posture.
15+
- `/api/v1/auth/config` now serves `features.audiencePosture` and mirrors the forced verification flag; `SELF_REGISTRATION_CLOSED` and `EMAIL_DOMAIN_NOT_ALLOWED` are registered in the ADR-0112 ledger.
16+
- The BOOTSTRAP bypass counts non-system HUMANS, not `sys_user` rows, so a database still carrying the legacy `usr_system` service row is still a fresh install; the same predicate now backs the dev-admin seed's own precondition. The `emailAndPassword.disableSignUp` bootstrap bypass reads it too.
17+
- `@objectstack/verify`: `stack.signUp(...)` seeds a pending `sys_invitation` for the address before signing up, so harness fixtures that mint a second/third identity enter through the invitation carve-out under the new default. Fixtures asserting on their environment's pending invitations should filter by their own `organization_id` (the harness rows carry `org_verify_audience_gate`).
18+
19+
<!-- adr-0087: registered audience-posture-default-invite-only -->
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): retire the nine dead members of the plugin-manifest `contributes` block — `events` / `menus` / `themes` / `translations` / `actions` / `drivers` / `fieldTypes` / `functions` / `commands` (#10724, ADR-0049 enforce-or-remove)
6+
7+
<!-- adr-0087: registered plugin-manifest-contributes-dead-members-retired -->
8+
9+
**BREAKING** accept-set narrowing, landing after the v17.0.0 cut (the lockstep
10+
launch-window convention ships it as `minor`; the migration prescription is
11+
registered under protocol major 18, where `os migrate meta` users will look).
12+
13+
The census behind it (#10627, controlled and re-verified at claim time across
14+
objectstack, objectui and cloud) measured that the ENTIRE monorepo contains
15+
exactly one non-test read of `manifest.contributes`, and it reads `kinds`
16+
(`packages/objectql/src/engine.ts``registry.registerKind`). The other nine
17+
members parsed, entered the manifest, and changed nothing — while published
18+
material kept teaching them: `commands` documented Commander.js runtime
19+
resolution the CLI dropped for oclif auto-discovery, `fieldTypes` advertised a
20+
registration seam that has never existed, and `events` was decorative even for
21+
its only in-repo author, which already subscribes imperatively.
22+
23+
**What is refused:** authoring any of the nine keys. Each is a `retiredKey()`
24+
tombstone (the `manifest.loading` precedent — neither `ManifestSchema` nor the
25+
`contributes` object is `.strict()`, so a plain deletion would have silently
26+
stripped the keys), so authoring one is a `tsc` error and a parse error
27+
carrying the per-key prescription.
28+
29+
**FROM → TO, per member** (each tombstone carries its own one-line fix):
30+
31+
- `contributes.events` → subscribe in plugin code (`ctx.hook('kernel:ready', …)`
32+
from `init`/`start`); delete the key.
33+
- `contributes.menus` → app `navigation` / `manifest.navigationContributions`
34+
(ADR-0029 D7); delete the key.
35+
- `contributes.themes` → the stack-level `themes` metadata collection (an
36+
unrelated `ThemeSchema` surface); delete the key.
37+
- `contributes.translations` → the `translation` metadata type:
38+
`defineTranslationBundle` in `defineStack({ translations })`; delete the key.
39+
- `contributes.actions` → the stack `actions` collection or
40+
`engine.registerAction`; delete the key.
41+
- `contributes.drivers` → register a kernel service named `driver.*`; delete
42+
the key.
43+
- `contributes.fieldTypes` → nothing (no registration seam exists; the
44+
vocabulary is the spec `FieldType` enum); delete the key.
45+
- `contributes.functions``defineStack({ functions })`; delete the key.
46+
- `contributes.commands` → oclif native plugin auto-discovery (an `oclif`
47+
section in the plugin's own `package.json`; see `cli-extension.zod.ts`);
48+
delete the key.
49+
50+
**What stays:** `contributes.kinds` (the block's one live member) and
51+
`contributes.routes` (an open enforce-or-remove fork, #10726 — deliberately
52+
untouched here). Runtime behaviour is unchanged: nothing ever read the nine
53+
members, so removing them removes no behaviour; a stored manifest still
54+
carrying one degrades to a single `[metadata_spec_invalid]` log line at
55+
registration rather than a boot failure.
56+
57+
D3 semantic entry `plugin-manifest-contributes-dead-members-retired`; no D2
58+
conversion, because a package manifest is not a stack collection member
59+
(`PLURAL_TO_SINGULAR` has no `packages`/`plugins` entry) and a conversion
60+
would be a transform with no seam that ever runs.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): retire `contributes.kinds[].globs` — the declared file-type watch patterns nothing ever read (#11169, ADR-0049 enforce-or-remove; maintainer-ruled 2026-08-24)
6+
7+
<!-- adr-0087: registered plugin-manifest-kind-globs-retired -->
8+
9+
**BREAKING** accept-set narrowing, landing after the v17.0.0 cut (the lockstep
10+
launch-window convention ships it as `minor`; the migration prescription is
11+
registered under protocol major 18, where `os migrate meta` users will look).
12+
13+
**Removed key:** `manifest.contributes.kinds[].globs`.
14+
15+
The schema promised that declaring `globs` "enables the system to parse and
16+
validate new file types" (its own example: a BI plugin handling
17+
`*.report.ts`). The promise was never kept: real glob-driven artifact
18+
discovery reads `filePatterns` off the metadata type registry — which
19+
`contributes.kinds` does not extend, as `metadata-plugin.zod.ts` records
20+
outright — so an authored `globs` was accepted, stored, served back through
21+
`GET /metadata/kind`, and never consulted. Measured (PR #11168, re-verified
22+
with positive control at claim): zero value reads anywhere; the only non-test
23+
occurrences of the path were the schema declaration and two type positions.
24+
25+
**FROM → TO:** `kinds: [{ id, globs: […], description? }]`
26+
`kinds: [{ id, description? }]` — delete the `globs` key; the kind's `id` and
27+
`description` are unchanged and still register. The key is a `retiredKey()`
28+
tombstone, so authoring it is a `tsc` error and a parse error carrying this
29+
prescription.
30+
31+
**What stays:** the `contributes.kinds` bucket itself and its `id` field
32+
(live: engine → `registry.registerKind`, served via `GET /metadata/kind`).
33+
File-type discovery remains single-channel on the metadata type registry's
34+
`filePatterns`; if plugin-extensible discovery is ever wanted, it gets
35+
designed against that registry, not revived here. The `registerKind` /
36+
`getAllKinds` type positions drop `globs` (type-only; the parameter widens).
37+
38+
D3 semantic entry `plugin-manifest-kind-globs-retired`; no D2 conversion (a
39+
manifest is not a stack collection member — no seam would ever run it).
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"@objectstack/plugin-auth": patch
3+
---
4+
5+
feat(plugin-auth): warn at boot when a walled deployment declares an owner it can never verify (#11640)
6+
7+
A walled deployment (`OS_TENANCY_POSTURE=group|isolated`) that declares
8+
`OS_PLATFORM_OWNER_EMAIL` but wires **no verification path** — no email
9+
transport and no trusted federated sign-in — now emits a loud, named boot
10+
warning (`walled_owner_no_verification_path`) on `kernel:ready`.
11+
12+
Since #11343, walled platform-admin elevation requires the declared owner's
13+
address to be **verified**, and verification can only arrive by an emailed
14+
link or by a federated sign-in that inserts the account already verified. With
15+
neither wired, the declared owner registers, is refused
16+
(`walled_owner_not_verified`), and has no in-product way to satisfy the
17+
condition — a dead end that previously surfaced only weeks later, at the
18+
owner's rejected registration. The warning names both missing inputs and the
19+
concrete wiring for either remedy (an email service, or SSO / a social
20+
provider), since either one alone clears it.
21+
22+
**Boot proceeds — this is not a refusal**, and no accept/reject behaviour
23+
changes anywhere: the walled + undeclared-owner boot refusal (#11184) and the
24+
fail-closed elevation refusal (#11343) are untouched. Deployments already
25+
wiring either verification path see no new output, and neither does a
26+
dev/harness boot whose declared owner is the dev-admin the seed provisions and
27+
stamps verified.

content/docs/protocol/kernel/i18n-standard.mdx

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -590,44 +590,63 @@ context.i18n.formatNumber(smallNumber, {
590590

591591
## Plugin Integration
592592

593-
Plugins register translation bundles in their manifest:
593+
Translations are **metadata**, declared on the stack — not manifest entries.
594+
(The former `contributes.translations` manifest key was removed in v17 (#10724,
595+
ADR-0049): no loader ever read its `{ locale, path }` entries, so a manifest
596+
that still carries it now fails the parse with the upgrade prescription.)
597+
598+
A package declares a translation bundle and registers it in its stack's
599+
`translations` collection:
594600

595601
{/* os:check */}
596602
```typescript
597-
// plugin.manifest.ts — a plugin manifest validated by `ManifestSchema`
598-
// from `@objectstack/spec/kernel` (there is no `definePlugin()` helper).
599-
// Translation files are registered under `contributes.translations` as
600-
// { locale, path } entries (see packages/spec/src/kernel/manifest.zod.ts).
601-
const manifest = {
602-
id: 'com.mycompany.crm',
603-
name: '@mycompany/crm',
604-
version: '1.0.0',
605-
606-
contributes: {
607-
translations: [
608-
{ locale: 'en', path: 'i18n/en/account.json' },
609-
{ locale: 'de', path: 'i18n/de/account.json' },
610-
],
603+
// src/translations/crm.translation.ts
604+
import { defineTranslationBundle } from '@objectstack/spec/system';
605+
606+
export const CrmTranslationBundle = defineTranslationBundle({
607+
en: {
608+
objects: {
609+
crm_account: {
610+
label: 'Account',
611+
pluralLabel: 'Accounts',
612+
fields: {
613+
name: { label: 'Account Name' },
614+
},
615+
},
616+
},
611617
},
612-
};
613-
614-
export default manifest;
618+
de: {
619+
objects: {
620+
crm_account: {
621+
label: 'Konto',
622+
pluralLabel: 'Konten',
623+
fields: {
624+
name: { label: 'Kontoname' },
625+
},
626+
},
627+
},
628+
},
629+
});
615630
```
616631

617-
### Translation File Registration
632+
```typescript
633+
// objectstack.config.ts
634+
import { defineStack } from '@objectstack/spec';
635+
import { CrmTranslationBundle } from './src/translations/crm.translation.js';
618636

619-
```
620-
@mycompany/crm/
621-
i18n/
622-
en/
623-
account.json → Namespace: crm.account
624-
contact.json → Namespace: crm.contact
625-
de/
626-
account.json
627-
contact.json
637+
export default defineStack({
638+
manifest: {
639+
id: 'com.mycompany.crm',
640+
version: '1.0.0',
641+
type: 'app',
642+
name: 'CRM',
643+
},
644+
translations: [CrmTranslationBundle],
645+
});
628646
```
629647

630-
**Namespace Convention:** `{pluginName}.{filename}`
648+
The engine registers the collection as `translation` metadata (the governed
649+
`translation` type), and the i18n pipeline serves it.
631650

632651
### Using Plugin Translations
633652

content/docs/references/api/contract.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const result = ApiErrorSchema.parse(data);
2727

2828
| Property | Type | Required | Description |
2929
| :--- | :--- | :--- | :--- |
30-
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +285 more>` || Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
30+
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +287 more>` || Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
3131
| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112, #9106) |
3232
| **message** | `string` || Readable error message |
3333
| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim (#9934). Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution (#3821) for anything unmarked. Status-agnostic; never replaces `message`. |
@@ -140,6 +140,7 @@ const result = ApiErrorSchema.parse(data);
140140
* `DRIVER_UNAVAILABLE`
141141
* `DUPLICATE_REQUEST`
142142
* `ELIGIBILITY_UNEVALUABLE`
143+
* `EMAIL_DOMAIN_NOT_ALLOWED`
143144
* `EMAIL_SEND_FAILED`
144145
* `EMAIL_SERVICE_REQUIRED`
145146
* `ENQUEUE_FAILED`
@@ -288,6 +289,7 @@ const result = ApiErrorSchema.parse(data);
288289
* `SAML_REGISTER_FAILED`
289290
* `SCHEDULES_LIST_FAILED`
290291
* `SCHEDULE_DELETE_FAILED`
292+
* `SELF_REGISTRATION_CLOSED`
291293
* `SETTINGS_ACTION_FAILED`
292294
* `SETTINGS_CRYPTO_UNAVAILABLE`
293295
* `SETTINGS_ENGINE_NOT_BOUND`

content/docs/references/api/error-code-ledger.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ const result = ErrorCode.parse(data);
244244
* `DRIVER_UNAVAILABLE`
245245
* `DUPLICATE_REQUEST`
246246
* `ELIGIBILITY_UNEVALUABLE`
247+
* `EMAIL_DOMAIN_NOT_ALLOWED`
247248
* `EMAIL_SEND_FAILED`
248249
* `EMAIL_SERVICE_REQUIRED`
249250
* `ENQUEUE_FAILED`
@@ -392,6 +393,7 @@ const result = ErrorCode.parse(data);
392393
* `SAML_REGISTER_FAILED`
393394
* `SCHEDULES_LIST_FAILED`
394395
* `SCHEDULE_DELETE_FAILED`
396+
* `SELF_REGISTRATION_CLOSED`
395397
* `SETTINGS_ACTION_FAILED`
396398
* `SETTINGS_CRYPTO_UNAVAILABLE`
397399
* `SETTINGS_ENGINE_NOT_BOUND`

content/docs/references/index.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Protocol Reference
3-
description: Every schema published by @objectstack/spec — 1582 schemas across 14 protocol modules
3+
description: Every schema published by @objectstack/spec — 1583 schemas across 14 protocol modules
44
---
55

66
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -31,9 +31,9 @@ counts are sums of the rows they head. Regenerate with
3131
| [Security Protocol](/docs/references/security) | 5 | 27 | Permission sets, row-level security, sharing rules, tenancy posture. |
3232
| [Shared Protocol](/docs/references/shared) | 8 | 31 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. |
3333
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
34-
| [System Protocol](/docs/references/system) | 36 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
34+
| [System Protocol](/docs/references/system) | 36 | 288 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
3535
| [UI Protocol](/docs/references/ui) | 16 | 152 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
36-
| **Total** | **199** | **1582** | 14 protocol modules |
36+
| **Total** | **199** | **1583** | 14 protocol modules |
3737

3838
---
3939

@@ -318,14 +318,14 @@ Studio designer metadata — the authoring surfaces for the protocols above.
318318

319319
## System Protocol
320320

321-
**Source:** `packages/spec/src/system/` · **Import:** `@objectstack/spec/system` · **36 pages, 287 schemas**
321+
**Source:** `packages/spec/src/system/` · **Import:** `@objectstack/spec/system` · **36 pages, 288 schemas**
322322

323323
The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance.
324324

325325
| File | Schemas |
326326
| :--- | :--- |
327327
| [`app-install.zod.ts`](/docs/references/system/app-install) | `AppCompatibilityCheck`, `AppInstallRequest`, `AppInstallResult`, `AppManifest` |
328-
| [`auth-config.zod.ts`](/docs/references/system/auth-config) | `AdvancedAuthConfig`, `AuthConfig`, `AuthPluginConfig`, `AuthProviderConfig`, `EmailAndPasswordConfig`, `EmailVerificationConfig`, `MutualTLSConfig`, `OidcProviderConfig`, `OidcProvidersConfig`, `SocialProviderConfig` |
328+
| [`auth-config.zod.ts`](/docs/references/system/auth-config) | `AdvancedAuthConfig`, `AudienceConfig`, `AuthConfig`, `AuthPluginConfig`, `AuthProviderConfig`, `EmailAndPasswordConfig`, `EmailVerificationConfig`, `MutualTLSConfig`, `OidcProviderConfig`, `OidcProvidersConfig`, `SocialProviderConfig` |
329329
| [`book.zod.ts`](/docs/references/system/book) | `Book`, `BookAudience`, `BookGroup`, `BookInclude`, `BookNode` |
330330
| [`cache.zod.ts`](/docs/references/system/cache) | `CacheAvalanchePrevention`, `CacheConfig`, `CacheConsistency`, `CacheInvalidation`, `CacheStrategy`, `CacheTier`, `CacheWarmup`, `DistributedCacheConfig` |
331331
| [`change-management.zod.ts`](/docs/references/system/change-management) | `ChangeImpact`, `ChangePriority`, `ChangeRequest`, `ChangeStatus`, `ChangeType`, `RollbackPlan` |

content/docs/references/kernel/manifest.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const result = ManifestSchema.parse(data);
4040
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
4141
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
4242
| **configuration** | `{ title?: string; properties: Record<string, object> }` | optional | Plugin configuration settings |
43-
| **contributes** | `{ kinds?: object[]; events?: string[]; menus?: Record<string, object[]>; themes?: object[]; … }` | optional | Platform contributions |
43+
| **contributes** | `{ kinds?: object[]; routes?: object[] }` | optional | Platform contributions |
4444
| **data** | `{ object: string; externalId?: string \| string[]; mode?: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; env?: Enum<'prod' \| 'dev' \| 'test'>[]; … }[]` | optional | Initial seed data (prefer top-level data field) |
4545
| **capabilities** | `{ implements?: object[]; provides?: object[]; requires?: object[]; extensionPoints?: object[]; … }` | optional | Plugin capability declarations for interoperability |
4646
| **extensions** | `Record<string, any>` | optional | Extension points and contributions |

0 commit comments

Comments
 (0)