Skip to content

Commit 14dc786

Browse files
os-zhuangclaude
andcommitted
feat(managedBy): add explicit engine-owned lifecycle bucket (tracks framework ADR-0103 addendum, #3343)
Mirrors the framework's engine-owned enum split in the UI type + runtime + badge. - @object-ui/types: `ManagedByBucket` union + `MANAGED_BY_BUCKETS` gain `'engine-owned'` (closed union → missed consumers are compile errors). - @object-ui/core: `resolveCrudAffordances` DEFAULTS gains the engine-owned all-locked row (same matrix as system/append-only), so isObjectInlineEditable and the grid/form gates treat it read-only automatically. - @object-ui/app-shell: ManagedByBadge renders engine-owned with the same read-only "System-managed" copy as a locked `system` object — reuses the existing `managedByBadge.system` i18n key (zero translation churn; the distinction is at the schema level). resolveManagedByEmptyState reuses the `system` engine-owned empty state. Behaviour-preserving — engine-owned resolves to the same locked affordances a locked `system` object did, so rendering is unchanged; the value just makes the schema self-documenting. New unit coverage across resolveCrudAffordances / isObjectInlineEditable / MANAGED_BY_BUCKETS / the empty-state helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0a3710b commit 14dc786

7 files changed

Lines changed: 59 additions & 14 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@object-ui/types": minor
3+
"@object-ui/core": minor
4+
"@object-ui/app-shell": patch
5+
---
6+
7+
**Add the explicit `engine-owned` lifecycle bucket (tracks framework ADR-0103 addendum / #3343).** The framework split the overloaded `managedBy: 'system'` bucket by promoting the engine-owned case to its own enum value; this mirrors it in the UI type + runtime + badge.
8+
9+
- **`@object-ui/types`**`ManagedByBucket` union and `MANAGED_BY_BUCKETS` gain `'engine-owned'` (canonical order: `platform, config, system, engine-owned, append-only, better-auth`). The union stays closed, so every consumer that missed the new value is a compile error.
10+
- **`@object-ui/core`**`resolveCrudAffordances` gains the `engine-owned` default row (identical all-locked matrix as `system`/`append-only`), so `isObjectInlineEditable` / the grid + form gates treat it as read-only automatically.
11+
- **`@object-ui/app-shell`** — the `ManagedByBadge` renders `engine-owned` with the same read-only "System-managed" copy as a locked `system` object (reuses the existing `managedByBadge.system` i18n key — zero translation churn; the distinction is at the schema level, not the user-facing string), and `resolveManagedByEmptyState` reuses the `system` engine-owned empty state.
12+
13+
Behaviour-preserving: `engine-owned` resolves to the same locked affordances `system` did by default, so nothing about how a locked object renders changes — the value just makes the schema self-documenting. New unit coverage for the bucket in `resolveCrudAffordances` / `isObjectInlineEditable` / `MANAGED_BY_BUCKETS` / the empty-state helper.

packages/app-shell/src/components/ManagedByBadge.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ import { isSystemWritable, type ManagedByBucket } from '../utils/crudAffordances
2828
* `@objectstack/spec/data/object.zod.ts` → `ObjectSchemaBase.managedBy`:
2929
* - `platform` — User-owned business data. **Renders nothing.**
3030
* - `config` — Admin-authored configuration.
31-
* - `system` — Engine-managed runtime rows (default) — but a `system`
32-
* object that opens writes via `userActions` (ADR-0103) is
33-
* platform-defined, admin/user-writable DATA and gets the
34-
* distinct writable-system copy instead.
31+
* - `system` — Engine-managed schema. A `system` object that opens writes
32+
* via `userActions` (ADR-0103) is platform-defined,
33+
* admin/user-writable DATA and gets the distinct
34+
* writable-system copy; a locked one reads engine-owned.
35+
* - `engine-owned` — Runtime rows a platform service owns end to end (ADR-0103);
36+
* the explicit read-only monitoring surface.
3537
* - `append-only` — Immutable audit log.
3638
* - `better-auth` — Identity tables owned by better-auth driver.
3739
*
@@ -106,6 +108,22 @@ const VARIANTS: Record<VariantKey, Variant> = {
106108
'Rows here are created automatically when actions run on the source record. The list below is a read-only monitoring surface — row-level actions (Approve, Recall, Resend, …) live on each row.',
107109
tone: 'border-slate-300/60 bg-slate-50 text-slate-900 hover:bg-slate-100 dark:border-slate-500/40 dark:bg-slate-950/40 dark:text-slate-100',
108110
},
111+
// ADR-0103 — the explicit engine-owned bucket: rows a platform service owns end
112+
// to end (jobs, automation runs, approval runtime rows, the metadata store, …).
113+
// To a user this reads identically to a locked `system` object ("the platform
114+
// manages this, read-only"), so it deliberately REUSES the `system` copy/i18n key
115+
// — zero translation churn, consistent UX; the self-documentation is at the
116+
// schema level. (Same object shape as `system`; the distinct bucket value is the
117+
// point, not distinct user-facing copy.)
118+
'engine-owned': {
119+
icon: Lock,
120+
i18nKey: 'system',
121+
short: 'System-managed',
122+
title: 'Managed by the platform',
123+
body: () =>
124+
'Rows here are created automatically when actions run on the source record. The list below is a read-only monitoring surface — row-level actions (Approve, Recall, Resend, …) live on each row.',
125+
tone: 'border-slate-300/60 bg-slate-50 text-slate-900 hover:bg-slate-100 dark:border-slate-500/40 dark:bg-slate-950/40 dark:text-slate-100',
126+
},
109127
// ADR-0103 — a `system`-bucket object that opened writes via `userActions`:
110128
// platform-defined schema, but admin/user-writable DATA (e.g. Notification
111129
// Preferences, delegated RBAC assignments). Resolved in the component when the

packages/app-shell/src/utils/__tests__/managedByEmptyState.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ describe('resolveManagedByEmptyState', () => {
2020
expect(resolveManagedByEmptyState('append-only', t)?.title).toBe('No events recorded');
2121
});
2222

23+
// ADR-0103 — the explicit engine-owned bucket reuses the `system` engine-owned
24+
// empty state; it never opens creation, so it always renders that copy.
25+
it('gives the engine-owned bucket the same engine-owned empty state as locked system', () => {
26+
expect(resolveManagedByEmptyState('engine-owned', t)?.title).toBe('Nothing here yet');
27+
expect(resolveManagedByEmptyState('engine-owned', t, 'sys_automation_run', undefined)?.title).toBe('Nothing here yet');
28+
});
29+
2330
// ADR-0103 — a `system` object that opened creation (writable set: Notification
2431
// Preferences, delegated RBAC assignments, …) is admin/user-writable data. The
2532
// "entries appear automatically" copy would be wrong, so the helper returns

packages/app-shell/src/utils/managedByEmptyState.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export function resolveManagedByEmptyState(
4343
userActions?: UserActionsOverride | null,
4444
): ManagedByEmptyState | undefined {
4545
switch (managedBy) {
46+
// ADR-0103 — the explicit engine-owned bucket reuses the `system` engine-owned
47+
// empty state ("entries appear automatically"); it never opens creation, so the
48+
// resolveCrudAffordances guard below is a no-op for it.
49+
case 'engine-owned':
4650
case 'system':
4751
// ADR-0103 — a `system` object that opened creation is admin/user-writable
4852
// data (e.g. Notification Preferences). The "entries appear automatically"

packages/core/src/utils/managedBy.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ describe('resolveCrudAffordances (shared source of truth)', () => {
2323
});
2424
});
2525

26-
it('system / append-only / better-auth: export-only by default', () => {
27-
for (const managedBy of ['system', 'append-only', 'better-auth']) {
26+
it('system / engine-owned / append-only / better-auth: export-only by default', () => {
27+
for (const managedBy of ['system', 'engine-owned', 'append-only', 'better-auth']) {
2828
expect(resolveCrudAffordances({ managedBy })).toEqual({
2929
create: false, import: false, edit: false, delete: false, exportCsv: true,
3030
});
@@ -86,7 +86,7 @@ describe('isSystemWritable (ADR-0103)', () => {
8686
describe('isObjectInlineEditable', () => {
8787
it('mirrors the resolved edit affordance (replaces the old NON_EDITABLE_BUCKETS set)', () => {
8888
// Non-editable buckets by default...
89-
for (const managedBy of ['system', 'append-only', 'better-auth']) {
89+
for (const managedBy of ['system', 'engine-owned', 'append-only', 'better-auth']) {
9090
expect(isObjectInlineEditable({ managedBy })).toBe(false);
9191
}
9292
// ...editable buckets and opened-up system objects.
@@ -139,7 +139,7 @@ describe('userActionPredicates', () => {
139139
});
140140

141141
describe('MANAGED_BY_BUCKETS', () => {
142-
it('is the closed 5-bucket union in canonical order', () => {
143-
expect(MANAGED_BY_BUCKETS).toEqual(['platform', 'config', 'system', 'append-only', 'better-auth']);
142+
it('is the closed 6-bucket union in canonical order (ADR-0103 engine-owned split)', () => {
143+
expect(MANAGED_BY_BUCKETS).toEqual(['platform', 'config', 'system', 'engine-owned', 'append-only', 'better-auth']);
144144
});
145145
});

packages/core/src/utils/managedBy.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,12 @@ export interface SchemaLike {
6666
}
6767

6868
const DEFAULTS: Record<ManagedByBucket, CrudAffordances> = {
69-
platform: { create: true, import: true, edit: true, delete: true, exportCsv: true },
70-
config: { create: true, import: false, edit: true, delete: true, exportCsv: true },
71-
system: { create: false, import: false, edit: false, delete: false, exportCsv: true },
72-
'append-only': { create: false, import: false, edit: false, delete: false, exportCsv: true },
73-
'better-auth': { create: false, import: false, edit: false, delete: false, exportCsv: true },
69+
platform: { create: true, import: true, edit: true, delete: true, exportCsv: true },
70+
config: { create: true, import: false, edit: true, delete: true, exportCsv: true },
71+
system: { create: false, import: false, edit: false, delete: false, exportCsv: true },
72+
'engine-owned': { create: false, import: false, edit: false, delete: false, exportCsv: true },
73+
'append-only': { create: false, import: false, edit: false, delete: false, exportCsv: true },
74+
'better-auth': { create: false, import: false, edit: false, delete: false, exportCsv: true },
7475
};
7576

7677
/**

packages/types/src/managed-by.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type ManagedByBucket =
1919
| 'platform'
2020
| 'config'
2121
| 'system'
22+
| 'engine-owned'
2223
| 'append-only'
2324
| 'better-auth';
2425

@@ -27,6 +28,7 @@ export const MANAGED_BY_BUCKETS: readonly ManagedByBucket[] = [
2728
'platform',
2829
'config',
2930
'system',
31+
'engine-owned',
3032
'append-only',
3133
'better-auth',
3234
];

0 commit comments

Comments
 (0)