You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(data-modeling): document the managedBy lifecycle bucket and ADR-0103 write policy (#3333)
The Object reference never documented `managedBy` at all. Add a `managedBy` +
`userActions` row to the object property table and a "Lifecycle bucket"
subsection covering the five buckets, the engine-owned vs admin/user-writable
`system` split (ADR-0103), the resolved-affordance write policy, the
`assertEngineOwnedWriteAllowed` guard, and the apiMethods reconciliation.
- fields.mdx: `readonly`'s "governed by their own guards" now names the
resolved-affordance write policy and links to the bucket section.
- authentication.mdx: note the better-auth identity objects are an
identity-owned bucket — user-context generic CRUD is suppressed, better-auth's
own writes run as system context.
Docs only. Refs #3220 / ADR-0092 / ADR-0103.
Claude-Session: https://claude.ai/code/session_01Fp9yZxRQ3mb7p4vVwqFXKE
Co-authored-by: Claude <noreply@anthropic.com>
|`inlineHelpText`|`string`| — | Help text shown in UI |
286
286
|`hidden`|`boolean`|`false`| Hide from default views |
287
-
|`readonly`|`boolean`|`false`| Prevent editing — hidden from create/edit forms AND server-enforced on both write paths: a non-system write to the field is silently dropped on `UPDATE` (in the engine) and on `INSERT` through the data API (REST/GraphQL/MCP/import, at the DataProtocol ingress). A stripped field still falls back to its `defaultValue`; **seeding a `readonly` column at create requires a system context** (import/migration/programmatic seed). Platform (`sys_`/`managedBy`) objects are governed by their own guards instead. |
287
+
|`readonly`|`boolean`|`false`| Prevent editing — hidden from create/edit forms AND server-enforced on both write paths: a non-system write to the field is silently dropped on `UPDATE` (in the engine) and on `INSERT` through the data API (REST/GraphQL/MCP/import, at the DataProtocol ingress). A stripped field still falls back to its `defaultValue`; **seeding a `readonly` column at create requires a system context** (import/migration/programmatic seed). Platform (`sys_`/`managedBy`) objects are governed by their own write policy instead — the resolved-affordance write guard keyed off the object's [lifecycle bucket](/docs/data-modeling/objects#lifecycle-bucket-managedby) (ADR-0103), not this field-level flag. |
288
288
|`sortable`|`boolean`|`true`| Allow sorting by this field |
289
289
|`group`|`string`| — | Group name for organizing in forms (e.g. `'billing'`) |
Copy file name to clipboardExpand all lines: content/docs/data-modeling/objects.mdx
+51Lines changed: 51 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -217,10 +217,61 @@ indexes: [
217
217
| Property | Type | Description |
218
218
| :--- | :--- | :--- |
219
219
|`isSystem`|`boolean`| System object, protected from deletion (default: `false`) |
220
+
|`managedBy`|`enum`| Lifecycle bucket that sets the default CRUD affordances and write policy — `'platform'` (default), `'config'`, `'system'`, `'append-only'`, `'better-auth'`. See [Lifecycle bucket](#lifecycle-bucket-managedby) below. |
221
+
|`userActions`|`object`| Per-object override of the CRUD affordances the `managedBy` default implies — `{ create?, edit?, delete?, import?, exportCsv? }`. This is what makes a `system`/`append-only` object admin/user-writable. See [Lifecycle bucket](#lifecycle-bucket-managedby). |
220
222
|`sharingModel`|`enum`| Org-Wide Default record visibility (ADR-0055/0056/0090). Canonical four only: `'private'`, `'public_read'`, `'public_read_write'`, `'controlled_by_parent'` (detail visibility derived from its master). The legacy aliases (`'read'`, `'read_write'`, `'full'`) were removed from the enum (ADR-0090 D4) — authoring rejects them. Unset on a custom object resolves to `'private'` (ADR-0090 D1) |
221
223
|`ownership`|`enum`| Record-ownership model: `'user'` (default — injects the reassignable `owner_id` lookup, engaging owner-scoped RLS, "My" views and owner reports), `'org'`, or `'none'` (no per-record owner — Dataverse-style catalog / junction tables, skips `owner_id`). Distinct from the package `own`/`extend` contribution kind. |
222
224
|`validations`|`ValidationRule[]`| Object-level validation rules (see [Validation](/docs/data-modeling/validation)) |
223
225
226
+
### Lifecycle bucket (`managedBy`)
227
+
228
+
`managedBy` declares which lifecycle bucket an object belongs to. It sets the
229
+
**default** CRUD affordances the UI renders and the write policy the platform
230
+
enforces. The enforced policy is the *resolved affordance* — the bucket default
231
+
with any `userActions` override applied (`resolveCrudAffordances`) — **not** the
232
+
bare bucket string.
233
+
234
+
| Bucket | Default write policy |
235
+
| :--- | :--- |
236
+
|`platform`|**Default.** User-owned business data — full New / Import / Edit / Delete. |
237
+
|`config`| Admin-authored configuration — New / Edit / Delete, no CSV import. |
238
+
|`system`| Platform-defined schema. **Engine-owned by default**: a platform service owns the row lifecycle, generic CRUD is hidden, and the object is exposed `['get', 'list']` only. |
|`better-auth`| Identity tables owned by the better-auth driver — generic user-context CRUD is suppressed; mutations flow through the auth API (sign-in, invite, reset). |
241
+
242
+
**Engine-owned vs. admin-writable `system` objects (ADR-0103).** The `system`
243
+
bucket covers two kinds of object. Most are *engine-owned* — jobs,
Copy file name to clipboardExpand all lines: content/docs/permissions/authentication.mdx
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -170,6 +170,8 @@ The plugin creates the following auth objects (using ObjectStack `sys_` protocol
170
170
> automatically maps these to `sys_`-prefixed protocol names via `AUTH_MODEL_TO_PROTOCOL`.
171
171
> Client-side API routes (`/api/v1/auth/*`) are **not affected** — they do not expose object names.
172
172
173
+
These objects are `managedBy: 'better-auth'` (an identity-owned [lifecycle bucket](/docs/data-modeling/objects#lifecycle-bucket-managedby)): generic user-context CRUD through the `/data` API is suppressed — a fail-closed identity write guard (ADR-0092/0103) rejects direct writes so password hashing, session validation, and verification flows can't be bypassed. better-auth's own writes run under a **system context** and pass through. Mutate these records through the sign-in, invitation, and security flows above, never by writing the objects directly.
0 commit comments