Skip to content

Commit 498dc69

Browse files
committed
feat(spec,objectql,metadata-core)!: retire placeholder metadata kinds per ADR-0088
- spec kernel: MetadataTypeSchema + DEFAULT_METADATA_TYPE_REGISTRY drop trigger/router/function/service (30 → 26 kinds) with tombstone comments pointing at the delivered forms (hook / record_change flows; contributes.routes + apis:; defineStack({functions}); the plugin registry). external_catalog annotated RUNTIME-CREATED (ADR-0062/0088). - plugin-structure: OPS_FILE_SUFFIX_REGEX drops the four suffixes. - objectql: remove the dormant 'function'-kind load path (nothing ever produced such items; defineStack({functions}) is the one way in) and drop 'function' from the metadata-service sync list. - metadata-core: lockstep enum follows. - Tests that leaned on the retired kinds as deny-gate fixtures now use the remaining allowRuntimeCreate:false kind (agent, ADR-0063) and allowOrgOverride:false kinds (hook/validation/external_catalog); the registry-invariant asserts keep the retired names as reintroduction guards. - Reference docs regenerated (gen:docs); ADR-0005/0010 prose updated. - showcase KIND_COVERAGE shrinks in lockstep; external_catalog waiver is now a permanent, documented exclusion. - changeset: minor for spec/objectql/metadata-core. Verified: spec 6690 tests, objectql 773, metadata-core 80, metadata 260, metadata-protocol 11, full turbo build (70 tasks), showcase verify, crm 19 — all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G6pPpRszk9cD3SxcKNFMWs
1 parent 9f80418 commit 498dc69

19 files changed

Lines changed: 110 additions & 137 deletions
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/objectql': minor
4+
'@objectstack/metadata-core': minor
5+
---
6+
7+
feat(spec)!: retire the placeholder metadata kinds `trigger`, `router`, `function`, `service` (ADR-0088).
8+
9+
The registry is the contract authors — human and AI — read to learn what can be authored, and these four kinds had no authoring surface, no loader, no schema, and no (or a dead) consumer. `MetadataTypeSchema` + `DEFAULT_METADATA_TYPE_REGISTRY` shrink 30 → 26; `OPS_FILE_SUFFIX_REGEX` drops the four suffixes; the dormant objectql load path that registered QL functions from `type: 'function'` metadata items is removed (`defineStack({ functions })` / plugin `contributes.functions` remain the delivered forms); the metadata-core lockstep enum follows. `external_catalog` stays and is now annotated RUNTIME-CREATED (ADR-0062): its lack of an authoring surface is correct design. The delivered replacements: `hook` / `record_change` flows (trigger), plugin `contributes.routes` + declarative `apis:` (router), `defineStack({ functions })` (function), the plugin/service registry (service). Persisted `sys_metadata` rows are unaffected — no production read path re-parses stored `type` values through the enum.

content/docs/references/api/metadata.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ Metadata query with filtering, sorting, and pagination
314314

315315
| Property | Type | Required | Description |
316316
| :--- | :--- | :--- | :--- |
317-
| **types** | `Enum<'object' \| 'field' \| 'trigger' \| 'validation' \| 'hook' \| 'seed' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'router' \| 'function' \| 'service' \| 'email_template' \| 'doc' \| 'book' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>[]` | optional | Filter by metadata types |
317+
| **types** | `Enum<'object' \| 'field' \| 'validation' \| 'hook' \| 'seed' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'email_template' \| 'doc' \| 'book' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>[]` | optional | Filter by metadata types |
318318
| **namespaces** | `string[]` | optional | Filter by namespaces |
319319
| **packageId** | `string` | optional | Filter by owning package |
320320
| **search** | `string` | optional | Full-text search query |
@@ -349,7 +349,7 @@ Metadata query with filtering, sorting, and pagination
349349

350350
| Property | Type | Required | Description |
351351
| :--- | :--- | :--- | :--- |
352-
| **type** | `Enum<'object' \| 'field' \| 'trigger' \| 'validation' \| 'hook' \| 'seed' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'router' \| 'function' \| 'service' \| 'email_template' \| 'doc' \| 'book' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>` || Metadata type |
352+
| **type** | `Enum<'object' \| 'field' \| 'validation' \| 'hook' \| 'seed' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'email_template' \| 'doc' \| 'book' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>` || Metadata type |
353353
| **name** | `string` || Item name (snake_case) |
354354
| **data** | `Record<string, any>` || Metadata payload |
355355
| **namespace** | `string` | optional | Optional namespace |

content/docs/references/kernel/metadata-plugin.mdx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ const result = MetadataBulkRegisterRequest.parse(data);
130130
| Property | Type | Required | Description |
131131
| :--- | :--- | :--- | :--- |
132132
| **event** | `Enum<'metadata.registered' \| 'metadata.updated' \| 'metadata.unregistered' \| 'metadata.validated' \| 'metadata.deployed' \| 'metadata.overlay.applied' \| 'metadata.overlay.removed' \| 'metadata.imported' \| 'metadata.exported'>` || Event type |
133-
| **metadataType** | `Enum<'object' \| 'field' \| 'trigger' \| 'validation' \| 'hook' \| 'seed' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'router' \| 'function' \| 'service' \| 'email_template' \| 'doc' \| 'book' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>` || Metadata type |
133+
| **metadataType** | `Enum<'object' \| 'field' \| 'validation' \| 'hook' \| 'seed' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'email_template' \| 'doc' \| 'book' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>` || Metadata type |
134134
| **name** | `string` || Metadata item name |
135135
| **namespace** | `string` | optional | Namespace |
136136
| **packageId** | `string` | optional | Owning package ID |
@@ -147,7 +147,7 @@ const result = MetadataBulkRegisterRequest.parse(data);
147147

148148
| Property | Type | Required | Description |
149149
| :--- | :--- | :--- | :--- |
150-
| **types** | `Enum<'object' \| 'field' \| 'trigger' \| 'validation' \| 'hook' \| 'seed' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'router' \| 'function' \| 'service' \| 'email_template' \| 'doc' \| 'book' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>[]` | optional | Filter by metadata types |
150+
| **types** | `Enum<'object' \| 'field' \| 'validation' \| 'hook' \| 'seed' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'email_template' \| 'doc' \| 'book' \| 'permission' \| 'profile' \| 'role' \| 'agent' \| 'tool' \| 'skill'>[]` | optional | Filter by metadata types |
151151
| **namespaces** | `string[]` | optional | Filter by namespaces |
152152
| **packageId** | `string` | optional | Filter by owning package |
153153
| **search** | `string` | optional | Full-text search query |
@@ -182,7 +182,6 @@ const result = MetadataBulkRegisterRequest.parse(data);
182182

183183
* `object`
184184
* `field`
185-
* `trigger`
186185
* `validation`
187186
* `hook`
188187
* `seed`
@@ -198,9 +197,6 @@ const result = MetadataBulkRegisterRequest.parse(data);
198197
* `datasource`
199198
* `external_catalog`
200199
* `translation`
201-
* `router`
202-
* `function`
203-
* `service`
204200
* `email_template`
205201
* `doc`
206202
* `book`

content/docs/references/ui/component.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ Type: `string`
346346
| :--- | :--- | :--- | :--- |
347347
| **objectName** | `string` || Related object name (e.g., "task", "opportunity") |
348348
| **relationshipField** | `string` || Field on related object that points to this record (e.g., "account_id") |
349-
| **columns** | `string[]` | | Fields to display in the related list |
349+
| **columns** | `string[]` | optional | Fields to display in the related list. Optional: when omitted, columns derive from the related object's highlightFields / default list columns (a related list is just another surface that lists that object). Override chain: child highlightFields → field-level relatedListColumns → this inline list. |
350350
| **sort** | `string \| Object[]` | optional | Sort order for related records |
351351
| **limit** | `integer` || Number of records to display initially |
352352
| **filter** | `Object[]` | optional | Additional filter criteria for related records |

content/docs/references/ui/view.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,8 @@ List chart view configuration
236236
| **view** | `string` | optional | Name of the form view to use for details (e.g. "summary_view", "edit_form") |
237237
| **preventNavigation** | `boolean` || Disable standard navigation entirely |
238238
| **openNewTab** | `boolean` || Force open in new tab (applies to page mode) |
239-
| **width** | `string \| number` | optional | Width of the drawer/modal (e.g. "600px", "50%") |
239+
| **size** | `Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>` || [#2578] Overlay size bucket for drawer/modal detail: 'auto' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it. |
240+
| **width** | `string \| number` | optional | [DEPRECATED → size] Pixel/percent width of the drawer/modal (e.g. "600px"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket. |
240241

241242

242243
---

docs/adr/0005-metadata-customization-overlay.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ derives its set from that flag — there is no parallel allowlist
5353
| Domain | Type | Per-org override? | Why |
5454
|:---|:---|:---:|:---|
5555
| data | `object`, `field` || A per-org overlay would diverge the table schema; shared DB cannot honour that. |
56-
| data | `trigger`, `validation`, `hook` || DB-side contracts. Per-org variants must ship as a separate package, not an overlay. |
56+
| data | `validation`, `hook` || DB-side contracts. Per-org variants must ship as a separate package, not an overlay. (`trigger` retired as a kind — ADR-0088.) |
5757
| automation | `flow`, `workflow`, `approval` || Carry execution side-effects (events, jobs, audit). Per-org variants are a deployment, not an overlay. |
5858
| security | `permission`, `profile`, `role` || Authorization correctness; overlays would create silent privilege drift. |
59-
| system | `datasource`, `router`, `function`, `service` || Wiring level; changes require code paths, not metadata. |
59+
| system | `datasource` || Wiring level; changes require code paths, not metadata. (`router`/`function`/`service` retired as kinds — ADR-0088; they are code contributions.) |
6060
| ai | `agent`, `tool`, `skill` || Behavioural contracts with model providers; treat like flows. |
6161
| **ui** | **`view`, `dashboard`, `report`** || **Pure presentation. Safe per-org override.** |
6262
| ui | `page`, `app`, `action` || Conservative default — these bind to routes and side-effects. Promote individually if a concrete need appears. |
@@ -534,7 +534,7 @@ entries derived from the prior baseline.
534534

535535
The original ADR gates writes purely on `allowOrgOverride` (type-level).
536536
This proved too coarse for one category of types: those that ship
537-
executable code (`hook`, `trigger`, `validation`) declare
537+
executable code (`hook`, `validation`) declare
538538
`allowOrgOverride: false` AND `allowRuntimeCreate: true`. The intent —
539539
documented in `metadata-plugin.zod.ts` — is "users may author brand-new
540540
items of this type, but artifact-shipped items remain immutable".

docs/adr/0010-metadata-protection-model.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ The existing `allowOrgOverride` / `allowRuntimeCreate` flags on
120120
`MetadataTypeRegistryEntry` continue to express **type-wide** policy:
121121

122122
- `allowOrgOverride: false` → no overlay write to any item of this type
123-
is ever accepted from a tenant. (`datasource`, `router`, `function`,
124-
`service` keep this stance because they encode deployment topology.)
123+
is ever accepted from a tenant. (`datasource` keeps this stance because
124+
it encodes deployment topology; the former `router`/`function`/`service`
125+
kinds were retired outright — ADR-0088.)
125126
- `allowRuntimeCreate: false` → tenants cannot author brand-new items
126127
of this type at runtime.
127128

examples/app-showcase/src/coverage.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,6 @@ export const KIND_COVERAGE: Record<MetadataType, KindCoverage> = {
6161
notes:
6262
'FieldSchema is authored inline on objects (the stack DSL has no standalone `fields` collection); field-zoo exhausts every field type — see the variant-level test.',
6363
},
64-
trigger: {
65-
status: 'waived',
66-
reason:
67-
'No declarative authoring surface: no stack collection or defineTrigger helper. Record-change triggering is demonstrated behaviorally by the record-change flows (requires: ["triggers"]).',
68-
issue: ISSUE.noAuthoringSurface,
69-
},
7064
validation: {
7165
status: 'demonstrated',
7266
files: [
@@ -102,26 +96,10 @@ export const KIND_COVERAGE: Record<MetadataType, KindCoverage> = {
10296
external_catalog: {
10397
status: 'waived',
10498
reason:
105-
'Runtime-created via Setup → Datasources → Sync (ADR-0062); no declarative artifact an app package can ship. The showcase demos the federation flow that produces one.',
99+
'PERMANENT by design (ADR-0088): a runtime-created snapshot produced by Setup → Datasources → Sync (ADR-0062). A package shipping one would be stale on arrival; the showcase demos the federation flow that produces it.',
106100
issue: ISSUE.noAuthoringSurface,
107101
},
108102
translation: { status: 'demonstrated', files: ['src/system/translations/index.ts'] },
109-
router: {
110-
status: 'waived',
111-
reason:
112-
'Code-only (allowRuntimeCreate: false). The code-level equivalent is the imperative HTTP mount in src/system/server/recalc-endpoint.ts.',
113-
issue: ISSUE.noAuthoringSurface,
114-
},
115-
function: {
116-
status: 'waived',
117-
reason: 'Code-only (allowRuntimeCreate: false); no declarative authoring surface.',
118-
issue: ISSUE.noAuthoringSurface,
119-
},
120-
service: {
121-
status: 'waived',
122-
reason: 'Code-only (allowRuntimeCreate: false); no declarative authoring surface.',
123-
issue: ISSUE.noAuthoringSurface,
124-
},
125103
email_template: { status: 'demonstrated', files: ['src/system/emails/index.ts'] },
126104
doc: {
127105
status: 'demonstrated',

packages/metadata-core/src/types.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { z } from 'zod';
1919
export const MetadataTypeSchema = z.enum([
2020
'object',
2121
'field',
22-
'trigger',
2322
'validation',
2423
'hook',
2524
'view',
@@ -29,6 +28,8 @@ export const MetadataTypeSchema = z.enum([
2928
'action',
3029
'flow',
3130
'workflow',
31+
// ADR-0088: trigger/router/function/service retired (kept in lockstep
32+
// with the spec kernel enum; see metadata-plugin.zod.ts).
3233
// ADR-0019: `approval` is a flow node, not a metadata type.
3334
'job',
3435
'agent',
@@ -45,9 +46,6 @@ export const MetadataTypeSchema = z.enum([
4546
'datasource',
4647
'cube',
4748
'settings',
48-
'router',
49-
'function',
50-
'service',
5149
'email_template',
5250
]).describe('Canonical metadata type name');
5351

packages/objectql/src/overlay-precedence.test.ts

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,14 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => {
163163
// After PR-10d.7 introduced `allowRuntimeCreate`, "denied" now splits
164164
// into two cohorts:
165165
//
166-
// 1. Types with `allowRuntimeCreate: true` (hook/trigger/validation) —
166+
// 1. Types with `allowRuntimeCreate: true` (hook/validation) —
167167
// blocked only when overlaying an artifact-backed item. Brand-new
168168
// (artifact-free) names succeed. Tested separately below.
169169
//
170-
// 2. Types with `allowRuntimeCreate: false` (router/function/service) —
171-
// blocked for ANY write in project-kernel mode.
170+
// 2. Types with `allowRuntimeCreate: false` — after ADR-0088 retired the
171+
// router/function/service placeholder kinds, `agent` (platform-owned,
172+
// ADR-0063) is the remaining member — blocked for ANY write in
173+
// project-kernel mode.
172174
//
173175
// NOTE: `datasource` moved to cohort #1 with the ADR-0015 Addendum
174176
// (runtime-UI-creatable datasources). Brand-new runtime datasources
@@ -181,19 +183,9 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => {
181183
describe('denied — must throw 403 (not_overridable or not_creatable)', () => {
182184
const deniedTypeWide: Array<{ type: string; reason: string; item: any }> = [
183185
{
184-
type: 'router',
185-
reason: 'API routing must be deterministic; per-org divergence creates invisible conflicts',
186-
item: { name: 'case_api', path: '/api/cases' },
187-
},
188-
{
189-
type: 'function',
190-
reason: 'serverless function definitions must be deployment-only, not per-org',
191-
item: { name: 'process_payment', handler: 'index.ts' },
192-
},
193-
{
194-
type: 'service',
195-
reason: 'service definitions must be deployment-only, not per-org',
196-
item: { name: 'notification_service' },
186+
type: 'agent',
187+
reason: 'agents are platform-owned (ADR-0063); per-org agent forks are withdrawn',
188+
item: { name: 'my_agent', label: 'My Agent' },
197189
},
198190
];
199191

@@ -275,16 +267,17 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => {
275267

276268
// ── single-kernel deployments: gate disengaged ──
277269
describe('single-kernel mode (no environmentId) — gate bypassed', () => {
278-
it('allows function overlay when environmentId is undefined (gate bypassed)', async () => {
270+
it('allows agent overlay when environmentId is undefined (gate bypassed)', async () => {
279271
// No environmentId => not project-kernel mode => legacy "anything goes"
280272
// path used by control-plane bootstrap. ADR-0005 §"Whitelist".
281-
// `function` is a definitively-denied type in project-kernel mode,
282-
// so this case best demonstrates the bypass semantics.
273+
// `agent` is a definitively-denied type in project-kernel mode
274+
// (allowRuntimeCreate: false, ADR-0063), so this case best
275+
// demonstrates the bypass semantics.
283276
const { protocol: localProto } = makeProtocol({ environmentId: undefined });
284277
const result = await localProto.saveMetaItem({
285-
type: 'function',
286-
name: 'my_fn',
287-
item: { name: 'my_fn', handler: 'index.ts' },
278+
type: 'agent',
279+
name: 'my_agent',
280+
item: { name: 'my_agent', label: 'My Agent', role: 'assistant', instructions: 'Answer questions about test data.' },
288281
});
289282
expect(result.success).toBe(true);
290283
});
@@ -323,6 +316,8 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => {
323316
expect(allowedFromRegistry.has('profile')).toBe(true);
324317
// Execution/wiring-layer types must NOT be in the set.
325318
// Accepting them as overlays would corrupt runtime semantics.
319+
// (trigger/router/function/service were retired outright by
320+
// ADR-0088 — the asserts double as reintroduction guards.)
326321
expect(allowedFromRegistry.has('trigger')).toBe(false);
327322
expect(allowedFromRegistry.has('validation')).toBe(false);
328323
expect(allowedFromRegistry.has('hook')).toBe(false);

0 commit comments

Comments
 (0)