@objectstack/metadata-protocol 11.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +7 -0
- package/dist/build-probes-I3227FYL.cjs +7 -0
- package/dist/build-probes-I3227FYL.cjs.map +1 -0
- package/dist/build-probes-TLWDII7Z.js +7 -0
- package/dist/build-probes-TLWDII7Z.js.map +1 -0
- package/dist/chunk-7LOFAEHA.cjs +161 -0
- package/dist/chunk-7LOFAEHA.cjs.map +1 -0
- package/dist/chunk-HJVPAKGD.js +639 -0
- package/dist/chunk-HJVPAKGD.js.map +1 -0
- package/dist/chunk-JRNTUZG6.cjs +639 -0
- package/dist/chunk-JRNTUZG6.cjs.map +1 -0
- package/dist/chunk-KJGVCNUC.js +161 -0
- package/dist/chunk-KJGVCNUC.js.map +1 -0
- package/dist/index.cjs +4855 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1893 -0
- package/dist/index.d.ts +1893 -0
- package/dist/index.js +4855 -0
- package/dist/index.js.map +1 -0
- package/dist/seed-loader-IFRY33L4.cjs +7 -0
- package/dist/seed-loader-IFRY33L4.cjs.map +1 -0
- package/dist/seed-loader-KNXGLL2V.js +7 -0
- package/dist/seed-loader-KNXGLL2V.js.map +1 -0
- package/package.json +61 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1893 @@
|
|
|
1
|
+
import * as _objectstack_metadata_core from '@objectstack/metadata-core';
|
|
2
|
+
import { MetadataRepository, MetaRef, MetadataItem, PutOptions, PutResult, DeleteOptions, DeleteResult, MetadataWriteIntent, ListFilter, MetadataItemHeader, HistoryOptions, MetadataEvent, WatchFilter } from '@objectstack/metadata-core';
|
|
3
|
+
import { ObjectStackProtocol, MetadataCacheRequest, MetadataCacheResponse, BatchUpdateRequest, BatchUpdateResponse, UpdateManyDataRequest, DeleteManyDataRequest, InstallPackageRequest, InstallPackageResponse } from '@objectstack/spec/api';
|
|
4
|
+
import { IDataEngine } from '@objectstack/core';
|
|
5
|
+
import { IFeedService, ISeedLoaderService, IDataEngine as IDataEngine$1, IMetadataService } from '@objectstack/spec/contracts';
|
|
6
|
+
import { MetadataValidationResult, MetadataLock, MetadataProvenance } from '@objectstack/spec/kernel';
|
|
7
|
+
import { SeedLoaderRequest, SeedLoaderResult, ObjectDependencyGraph, Seed, SeedLoaderConfigInput } from '@objectstack/spec/data';
|
|
8
|
+
|
|
9
|
+
/** One runtime-verification finding (ADR-0038 BuildIssue, layer 'runtime'). */
|
|
10
|
+
interface RuntimeBuildIssue {
|
|
11
|
+
layer: 'runtime';
|
|
12
|
+
severity: 'error' | 'warning';
|
|
13
|
+
/** The artifact whose runtime behaviour is broken. */
|
|
14
|
+
artifact: {
|
|
15
|
+
type: string;
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
/** What it exercised, when narrower than the artifact (e.g. a widget). */
|
|
19
|
+
ref?: {
|
|
20
|
+
type: string;
|
|
21
|
+
name: string;
|
|
22
|
+
member?: string;
|
|
23
|
+
};
|
|
24
|
+
/** 'seed_not_applied' | 'view_read_failed' | 'empty_query' | 'widget_query_failed' | 'probes_unavailable' */
|
|
25
|
+
code: string;
|
|
26
|
+
message: string;
|
|
27
|
+
fix?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Aggregate result of one post-publish probe pass. */
|
|
30
|
+
interface BuildProbeReport {
|
|
31
|
+
/** Findings, empty when every probe passed. */
|
|
32
|
+
issues: RuntimeBuildIssue[];
|
|
33
|
+
/** How many probes actually ran, per plane (0s mean nothing to probe). */
|
|
34
|
+
checked: {
|
|
35
|
+
seeds: number;
|
|
36
|
+
views: number;
|
|
37
|
+
widgets: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** The single read the probes need from the data engine. */
|
|
41
|
+
interface ProbeEngine {
|
|
42
|
+
find(objectName: string, query: unknown): Promise<Array<Record<string, unknown>>>;
|
|
43
|
+
}
|
|
44
|
+
/** Optional analytics surface — when absent, widget probes degrade to a warning. */
|
|
45
|
+
interface ProbeAnalytics {
|
|
46
|
+
queryDataset(dataset: unknown, selection: unknown, context?: unknown): Promise<unknown>;
|
|
47
|
+
}
|
|
48
|
+
interface RunBuildProbesOptions {
|
|
49
|
+
engine: ProbeEngine;
|
|
50
|
+
/** Read an ACTIVE (published) item body by type+name; undefined when absent. */
|
|
51
|
+
getItem: (type: string, name: string) => Promise<unknown | undefined>;
|
|
52
|
+
/** The just-published artifact set (publishPackageDrafts' `published`). */
|
|
53
|
+
published: Array<{
|
|
54
|
+
type: string;
|
|
55
|
+
name: string;
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* The kernel's analytics service, when one is mounted. Widget probes run
|
|
59
|
+
* the SAME `queryDataset` path the dashboard renderer hits — absent
|
|
60
|
+
* service means widgets can't be probed (one aggregate warning, not
|
|
61
|
+
* per-widget noise).
|
|
62
|
+
*/
|
|
63
|
+
analytics?: ProbeAnalytics;
|
|
64
|
+
/** Threaded into engine/analytics reads (tenant scoping). */
|
|
65
|
+
organizationId?: string | null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Run the L3 runtime probes over a just-published artifact set:
|
|
69
|
+
*
|
|
70
|
+
* • per published `seed` — its target object must have rows now
|
|
71
|
+
* (`seed_not_applied`: the rows were promised but never materialized);
|
|
72
|
+
* • per published `view` — a limit-1 read through the same engine the
|
|
73
|
+
* renderer uses must not throw (`view_read_failed`);
|
|
74
|
+
* • per published `dashboard` widget — its real dataset selection must
|
|
75
|
+
* execute (`widget_query_failed`) and must not return empty on an object
|
|
76
|
+
* that HAS rows (`empty_query` — the four-layer staging incident class).
|
|
77
|
+
*
|
|
78
|
+
* All probes are reads (limit-1 / single aggregate); a probe crash is
|
|
79
|
+
* reported, never thrown — verification must not break the publish it
|
|
80
|
+
* verifies.
|
|
81
|
+
*/
|
|
82
|
+
declare function runBuildProbes(opts: RunBuildProbesOptions): Promise<BuildProbeReport>;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Re-export the canonical validation-result type so callers in this
|
|
86
|
+
* package don't need to dual-import from `@objectstack/spec/kernel`.
|
|
87
|
+
*/
|
|
88
|
+
type MetadataDiagnostics = MetadataValidationResult;
|
|
89
|
+
/**
|
|
90
|
+
* Compute spec diagnostics for a single metadata document.
|
|
91
|
+
*
|
|
92
|
+
* Returns `undefined` when the type has no registered Zod schema
|
|
93
|
+
* (`function` / `service` / `router`, or any plugin type that has not
|
|
94
|
+
* called `registerMetadataTypeSchema()`). Callers MUST treat that as
|
|
95
|
+
* "no opinion" — not as "valid" — and either skip decoration entirely
|
|
96
|
+
* or surface a `validatable: false` flag if their UI cares.
|
|
97
|
+
*/
|
|
98
|
+
declare function computeMetadataDiagnostics(type: string, item: unknown): MetadataDiagnostics | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Attach `_diagnostics` to a single metadata item. Returns the item
|
|
101
|
+
* unchanged when no diagnostics could be computed (unknown type) or
|
|
102
|
+
* when the input is not an object.
|
|
103
|
+
*
|
|
104
|
+
* The returned reference is always a shallow copy when decoration
|
|
105
|
+
* occurs — callers must not assume identity equality with the input.
|
|
106
|
+
*/
|
|
107
|
+
declare function decorateMetadataItem<T>(type: string, item: T): T;
|
|
108
|
+
/**
|
|
109
|
+
* Decorate an array of metadata items. Non-array inputs and non-object
|
|
110
|
+
* elements are returned unchanged, preserving the upstream defensive
|
|
111
|
+
* "items may be a wrapped or naked array" contract documented in
|
|
112
|
+
* `rest-server.ts`.
|
|
113
|
+
*/
|
|
114
|
+
declare function decorateMetadataItems<T>(type: string, items: T[]): T[];
|
|
115
|
+
/** Minimal object-definition shape the reference checker needs. */
|
|
116
|
+
interface ObjectDefLike {
|
|
117
|
+
fields?: Record<string, {
|
|
118
|
+
type?: string;
|
|
119
|
+
}> | Array<{
|
|
120
|
+
name: string;
|
|
121
|
+
type?: string;
|
|
122
|
+
}>;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Cross-document reference checks Zod cannot express: every field a list
|
|
126
|
+
* view's user-facing filter surface points at must exist on the source
|
|
127
|
+
* object, and binding-dependent visualizations must have resolvable
|
|
128
|
+
* bindings (kanban → select-like `groupByField`).
|
|
129
|
+
*
|
|
130
|
+
* Pure function — callers (read decoration, the ADR-0033 AI apply loop)
|
|
131
|
+
* supply the already-resolved object definition. Returns `{ valid: true }`
|
|
132
|
+
* when every reference resolves; errors use the same wire shape as
|
|
133
|
+
* {@link computeMetadataDiagnostics} so consumers can merge the two.
|
|
134
|
+
*
|
|
135
|
+
* Spec-shape validation stays in `computeMetadataDiagnostics`; this only
|
|
136
|
+
* covers what a schema alone cannot see.
|
|
137
|
+
*/
|
|
138
|
+
declare function computeViewReferenceDiagnostics(view: Record<string, unknown>, objectDef: ObjectDefLike): MetadataDiagnostics;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Guarantee a `view` body carries a top-level `name`.
|
|
142
|
+
*
|
|
143
|
+
* {@link ObjectStackProtocolImplementation.getMetaItems} only surfaces a
|
|
144
|
+
* sys_metadata overlay row when its parsed body has a top-level `name` (objects
|
|
145
|
+
* and dashboards include one; some view producers — notably loose `{ list }`
|
|
146
|
+
* fragments — do not, so the view is silently dropped from the object's view
|
|
147
|
+
* list and never appears as a tab). We stamp the save name here, at the single
|
|
148
|
+
* write chokepoint, without otherwise reshaping the document.
|
|
149
|
+
*
|
|
150
|
+
* Deliberately does NOT convert shape: both the `defineView` container form
|
|
151
|
+
* (`{ list, listViews, … }`) and the `{ name, object, viewKind, config }`
|
|
152
|
+
* record form are valid and the console consumes both — reshaping a container
|
|
153
|
+
* into a record risks producing an invalid record (e.g. a non-`<object>.<key>`
|
|
154
|
+
* name). Structural validity is enforced separately by the view metadata schema
|
|
155
|
+
* during the spec-validation step. No-op for non-view types and bodies that
|
|
156
|
+
* already carry a `name`.
|
|
157
|
+
*/
|
|
158
|
+
declare function normalizeViewMetadata(type: string, item: unknown, saveName: string): unknown;
|
|
159
|
+
/**
|
|
160
|
+
* Thrown by `updateData` / `deleteData` when the caller supplies an
|
|
161
|
+
* `expectedVersion` that does not match the current record's `updated_at`.
|
|
162
|
+
*
|
|
163
|
+
* The HTTP layer maps this to `409 Conflict` with code `CONCURRENT_UPDATE`,
|
|
164
|
+
* and includes both the current server-side version and the current record
|
|
165
|
+
* payload so the client can render an informed conflict-resolution UI
|
|
166
|
+
* ("Reload latest" vs. "Overwrite anyway").
|
|
167
|
+
*
|
|
168
|
+
* NOTE: This is an *application-level* compare-and-set — not an atomic
|
|
169
|
+
* storage-layer CAS. There is a small TOCTOU window between the version
|
|
170
|
+
* check and the subsequent write. For the conflict frequency this targets
|
|
171
|
+
* (different users seconds-to-minutes apart in B2B record editing) this
|
|
172
|
+
* is more than adequate; a future revision can push the check into the
|
|
173
|
+
* driver's UPDATE statement (`WHERE id=? AND updated_at=?`) for true
|
|
174
|
+
* atomicity.
|
|
175
|
+
*/
|
|
176
|
+
declare class ConcurrentUpdateError extends Error {
|
|
177
|
+
readonly code = "CONCURRENT_UPDATE";
|
|
178
|
+
readonly status = 409;
|
|
179
|
+
readonly currentVersion: string | null;
|
|
180
|
+
readonly currentRecord: unknown;
|
|
181
|
+
constructor(opts: {
|
|
182
|
+
currentVersion: string | null;
|
|
183
|
+
currentRecord: unknown;
|
|
184
|
+
message?: string;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
188
|
+
private engine;
|
|
189
|
+
private getServicesRegistry?;
|
|
190
|
+
private getFeedService?;
|
|
191
|
+
/**
|
|
192
|
+
* Project scope applied to sys_metadata reads/writes. When undefined
|
|
193
|
+
* (single-kernel deployments), rows land in / come from the
|
|
194
|
+
* platform-global bucket (`environment_id IS NULL`). When set, every
|
|
195
|
+
* saveMetaItem insert/update and loadMetaFromDb query is filtered by
|
|
196
|
+
* `environment_id = environmentId`, so per-project kernels see only their own
|
|
197
|
+
* metadata even if several projects share the same physical database.
|
|
198
|
+
*/
|
|
199
|
+
private environmentId?;
|
|
200
|
+
/**
|
|
201
|
+
* Lazily-instantiated SysMetadataRepository per organization. Keyed by
|
|
202
|
+
* `${organizationId ?? '__env__'}`. Repositories are stateful — they
|
|
203
|
+
* carry the per-org `seqCounter` and watch subscribers — so we cache
|
|
204
|
+
* them rather than constructing one per call.
|
|
205
|
+
*/
|
|
206
|
+
private overlayRepos;
|
|
207
|
+
constructor(engine: IDataEngine, getServicesRegistry?: () => Map<string, any>, getFeedService?: () => IFeedService | undefined, environmentId?: string);
|
|
208
|
+
/**
|
|
209
|
+
* Lazily obtain a SysMetadataRepository for the given organization.
|
|
210
|
+
* Env-wide overlays (organizationId == null) share a singleton under
|
|
211
|
+
* the `__env__` key.
|
|
212
|
+
*/
|
|
213
|
+
private getOverlayRepo;
|
|
214
|
+
/**
|
|
215
|
+
* One-time guard for ensuring the overlay-uniqueness UNIQUE INDEX exists
|
|
216
|
+
* on `sys_metadata`. ADR-0005: scopes overlays by
|
|
217
|
+
* `(type, name, organization_id, environment_id, scope)` for active rows only.
|
|
218
|
+
* Idempotent SQL — safe to attempt on every protocol instance.
|
|
219
|
+
*
|
|
220
|
+
* Inlined here (rather than importing from @objectstack/metadata/migrations)
|
|
221
|
+
* to avoid a circular dependency: metadata already depends on objectql.
|
|
222
|
+
*/
|
|
223
|
+
private overlayIndexEnsured;
|
|
224
|
+
private ensureOverlayIndex;
|
|
225
|
+
/**
|
|
226
|
+
* Exposes the project scope the protocol is bound to. Consumers like
|
|
227
|
+
* the HTTP dispatcher use this to decide whether to trust the process-
|
|
228
|
+
* wide SchemaRegistry or whether they must route a read through the
|
|
229
|
+
* protocol's environment_id-filtered lookup.
|
|
230
|
+
*/
|
|
231
|
+
getProjectId(): string | undefined;
|
|
232
|
+
private requireFeedService;
|
|
233
|
+
getDiscovery(): Promise<{
|
|
234
|
+
version: string;
|
|
235
|
+
apiName: string;
|
|
236
|
+
routes: {
|
|
237
|
+
data: string;
|
|
238
|
+
metadata: string;
|
|
239
|
+
discovery?: string | undefined;
|
|
240
|
+
ui?: string | undefined;
|
|
241
|
+
auth?: string | undefined;
|
|
242
|
+
automation?: string | undefined;
|
|
243
|
+
storage?: string | undefined;
|
|
244
|
+
analytics?: string | undefined;
|
|
245
|
+
graphql?: string | undefined;
|
|
246
|
+
packages?: string | undefined;
|
|
247
|
+
workflow?: string | undefined;
|
|
248
|
+
approvals?: string | undefined;
|
|
249
|
+
realtime?: string | undefined;
|
|
250
|
+
notifications?: string | undefined;
|
|
251
|
+
ai?: string | undefined;
|
|
252
|
+
i18n?: string | undefined;
|
|
253
|
+
feed?: string | undefined;
|
|
254
|
+
};
|
|
255
|
+
services: Record<string, {
|
|
256
|
+
enabled: boolean;
|
|
257
|
+
status: "available" | "registered" | "unavailable" | "degraded" | "stub";
|
|
258
|
+
handlerReady?: boolean | undefined;
|
|
259
|
+
route?: string | undefined;
|
|
260
|
+
provider?: string | undefined;
|
|
261
|
+
version?: string | undefined;
|
|
262
|
+
message?: string | undefined;
|
|
263
|
+
rateLimit?: {
|
|
264
|
+
requestsPerMinute?: number | undefined;
|
|
265
|
+
requestsPerHour?: number | undefined;
|
|
266
|
+
burstLimit?: number | undefined;
|
|
267
|
+
retryAfterMs?: number | undefined;
|
|
268
|
+
} | undefined;
|
|
269
|
+
}>;
|
|
270
|
+
capabilities: Record<string, {
|
|
271
|
+
enabled: boolean;
|
|
272
|
+
description?: string;
|
|
273
|
+
}>;
|
|
274
|
+
}>;
|
|
275
|
+
getMetaTypes(): Promise<{
|
|
276
|
+
types: any[];
|
|
277
|
+
entries: {
|
|
278
|
+
actions?: {
|
|
279
|
+
name: string;
|
|
280
|
+
label: string;
|
|
281
|
+
type: "url" | "flow" | "script" | "api" | "form" | "modal";
|
|
282
|
+
refreshAfter: boolean;
|
|
283
|
+
objectName?: string | undefined;
|
|
284
|
+
icon?: string | undefined;
|
|
285
|
+
locations?: ("list_toolbar" | "list_item" | "record_header" | "record_more" | "record_related" | "record_section" | "global_nav")[] | undefined;
|
|
286
|
+
component?: "action:button" | "action:icon" | "action:menu" | "action:group" | undefined;
|
|
287
|
+
target?: string | undefined;
|
|
288
|
+
openIn?: "self" | "new-tab" | undefined;
|
|
289
|
+
body?: {
|
|
290
|
+
language: "expression";
|
|
291
|
+
source: string;
|
|
292
|
+
} | {
|
|
293
|
+
language: "js";
|
|
294
|
+
source: string;
|
|
295
|
+
capabilities: ("log" | "api.read" | "api.write" | "api.transaction" | "crypto.uuid" | "crypto.hash")[];
|
|
296
|
+
timeoutMs?: number | undefined;
|
|
297
|
+
memoryMb?: number | undefined;
|
|
298
|
+
} | undefined;
|
|
299
|
+
execute?: string | undefined;
|
|
300
|
+
params?: {
|
|
301
|
+
required: boolean;
|
|
302
|
+
name?: string | undefined;
|
|
303
|
+
field?: string | undefined;
|
|
304
|
+
objectOverride?: string | undefined;
|
|
305
|
+
label?: string | undefined;
|
|
306
|
+
type?: "number" | "boolean" | "date" | "record" | "file" | "code" | "json" | "url" | "summary" | "datetime" | "color" | "time" | "text" | "textarea" | "email" | "phone" | "password" | "secret" | "markdown" | "html" | "richtext" | "currency" | "percent" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "user" | "image" | "avatar" | "video" | "audio" | "formula" | "autonumber" | "composite" | "repeater" | "location" | "address" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "tags" | "vector" | undefined;
|
|
307
|
+
options?: {
|
|
308
|
+
label: string;
|
|
309
|
+
value: string;
|
|
310
|
+
}[] | undefined;
|
|
311
|
+
placeholder?: string | undefined;
|
|
312
|
+
helpText?: string | undefined;
|
|
313
|
+
defaultValue?: unknown;
|
|
314
|
+
defaultFromRow?: boolean | undefined;
|
|
315
|
+
}[] | undefined;
|
|
316
|
+
variant?: "link" | "primary" | "secondary" | "danger" | "ghost" | undefined;
|
|
317
|
+
confirmText?: string | undefined;
|
|
318
|
+
successMessage?: string | undefined;
|
|
319
|
+
errorMessage?: string | undefined;
|
|
320
|
+
undoable?: boolean | undefined;
|
|
321
|
+
resultDialog?: {
|
|
322
|
+
title?: string | undefined;
|
|
323
|
+
description?: string | undefined;
|
|
324
|
+
acknowledge?: string | undefined;
|
|
325
|
+
format?: "json" | "text" | "secret" | "qrcode" | "code-list" | undefined;
|
|
326
|
+
fields?: {
|
|
327
|
+
path: string;
|
|
328
|
+
label?: string | undefined;
|
|
329
|
+
format?: "json" | "text" | "secret" | "qrcode" | "code-list" | undefined;
|
|
330
|
+
}[] | undefined;
|
|
331
|
+
} | undefined;
|
|
332
|
+
visible?: {
|
|
333
|
+
dialect: "cron" | "cel" | "js" | "template";
|
|
334
|
+
source?: string | undefined;
|
|
335
|
+
ast?: unknown;
|
|
336
|
+
meta?: {
|
|
337
|
+
rationale?: string | undefined;
|
|
338
|
+
generatedBy?: string | undefined;
|
|
339
|
+
} | undefined;
|
|
340
|
+
} | undefined;
|
|
341
|
+
disabled?: boolean | {
|
|
342
|
+
dialect: "cron" | "cel" | "js" | "template";
|
|
343
|
+
source?: string | undefined;
|
|
344
|
+
ast?: unknown;
|
|
345
|
+
meta?: {
|
|
346
|
+
rationale?: string | undefined;
|
|
347
|
+
generatedBy?: string | undefined;
|
|
348
|
+
} | undefined;
|
|
349
|
+
} | undefined;
|
|
350
|
+
requiredPermissions?: string[] | undefined;
|
|
351
|
+
shortcut?: string | undefined;
|
|
352
|
+
bulkEnabled?: boolean | undefined;
|
|
353
|
+
ai?: {
|
|
354
|
+
exposed: boolean;
|
|
355
|
+
description
|
|
356
|
+
/**
|
|
357
|
+
* Simple hash function for ETag generation (browser-compatible)
|
|
358
|
+
* Uses a basic hash algorithm instead of crypto.createHash
|
|
359
|
+
*/
|
|
360
|
+
?: string | undefined;
|
|
361
|
+
category?: "data" | "flow" | "analytics" | "action" | "integration" | "vector_search" | "utility" | undefined;
|
|
362
|
+
paramHints?: Record<string, {
|
|
363
|
+
description?: string | undefined;
|
|
364
|
+
enum?: (string | number)[] | undefined;
|
|
365
|
+
examples?: unknown[] | undefined;
|
|
366
|
+
}> | undefined;
|
|
367
|
+
outputSchema?: Record<string, unknown> | undefined;
|
|
368
|
+
requiresConfirmation?: boolean | undefined;
|
|
369
|
+
} | undefined;
|
|
370
|
+
recordIdParam?: string | undefined;
|
|
371
|
+
recordIdField?: string | undefined;
|
|
372
|
+
bodyShape?: "flat" | {
|
|
373
|
+
wrap: string;
|
|
374
|
+
} | undefined;
|
|
375
|
+
method?: "POST" | "PUT" | "DELETE" | "PATCH" | undefined;
|
|
376
|
+
bodyExtra?: Record<string, unknown> | undefined;
|
|
377
|
+
mode?: "custom" | "create" | "delete" | "edit" | undefined;
|
|
378
|
+
opensInNewTab?: boolean | undefined;
|
|
379
|
+
newTabUrl?: string | undefined;
|
|
380
|
+
timeout?: number | undefined;
|
|
381
|
+
aria?: {
|
|
382
|
+
ariaLabel?: string | undefined;
|
|
383
|
+
ariaDescribedBy?: string | undefined;
|
|
384
|
+
role?: string | undefined;
|
|
385
|
+
} | undefined;
|
|
386
|
+
}[] | undefined;
|
|
387
|
+
createSeed?: {} | null | undefined;
|
|
388
|
+
type: string;
|
|
389
|
+
schemaId: string;
|
|
390
|
+
allowOrgOverride: boolean;
|
|
391
|
+
overrideSource: "registry" | "env";
|
|
392
|
+
schema: Record<string, unknown>;
|
|
393
|
+
form: {
|
|
394
|
+
type: "modal" | "split" | "drawer" | "simple" | "tabbed" | "wizard";
|
|
395
|
+
data?: {
|
|
396
|
+
provider: "object";
|
|
397
|
+
object: string;
|
|
398
|
+
} | {
|
|
399
|
+
provider: "api";
|
|
400
|
+
read?: {
|
|
401
|
+
url: string;
|
|
402
|
+
method: "POST" | "PUT" | "DELETE" | "PATCH" | "GET";
|
|
403
|
+
headers?: Record<string, string> | undefined;
|
|
404
|
+
params?: Record<string, unknown> | undefined;
|
|
405
|
+
body?: unknown;
|
|
406
|
+
} | undefined;
|
|
407
|
+
write?: {
|
|
408
|
+
url: string;
|
|
409
|
+
method: "POST" | "PUT" | "DELETE" | "PATCH" | "GET";
|
|
410
|
+
headers?: Record<string, string> | undefined;
|
|
411
|
+
params?: Record<string, unknown> | undefined;
|
|
412
|
+
body?: unknown;
|
|
413
|
+
} | undefined;
|
|
414
|
+
} | {
|
|
415
|
+
provider: "value";
|
|
416
|
+
items: unknown[];
|
|
417
|
+
} | {
|
|
418
|
+
provider: "schema";
|
|
419
|
+
schemaId: string;
|
|
420
|
+
schema?: Record<string, unknown> | undefined;
|
|
421
|
+
} | undefined;
|
|
422
|
+
sections?: {
|
|
423
|
+
collapsible: boolean;
|
|
424
|
+
collapsed: boolean;
|
|
425
|
+
columns: 2 | 1 | 4 | 3;
|
|
426
|
+
fields: any[];
|
|
427
|
+
name?: string | undefined;
|
|
428
|
+
label?: string | undefined;
|
|
429
|
+
description?: string | undefined;
|
|
430
|
+
visibleOn?: {
|
|
431
|
+
dialect: "cel" | "js" | "cron" | "template";
|
|
432
|
+
source?: string | undefined;
|
|
433
|
+
ast?: unknown;
|
|
434
|
+
meta?: {
|
|
435
|
+
rationale?: string | undefined;
|
|
436
|
+
generatedBy?: string | undefined;
|
|
437
|
+
} | undefined;
|
|
438
|
+
} | {
|
|
439
|
+
dialect: "js" | "cron" | "cel" | "template";
|
|
440
|
+
source?: string | undefined;
|
|
441
|
+
ast?: unknown;
|
|
442
|
+
meta?: {
|
|
443
|
+
rationale?: string | undefined;
|
|
444
|
+
generatedBy?: string | undefined;
|
|
445
|
+
} | undefined;
|
|
446
|
+
} | undefined;
|
|
447
|
+
}[] | undefined;
|
|
448
|
+
groups?: {
|
|
449
|
+
collapsible: boolean;
|
|
450
|
+
collapsed: boolean;
|
|
451
|
+
columns: 2 | 1 | 4 | 3;
|
|
452
|
+
fields: any[];
|
|
453
|
+
name?: string | undefined;
|
|
454
|
+
label?: string | undefined;
|
|
455
|
+
description?: string | undefined;
|
|
456
|
+
visibleOn?: {
|
|
457
|
+
dialect: "cel" | "js" | "cron" | "template";
|
|
458
|
+
source?: string | undefined;
|
|
459
|
+
ast?: unknown;
|
|
460
|
+
meta?: {
|
|
461
|
+
rationale?: string | undefined;
|
|
462
|
+
generatedBy?: string | undefined;
|
|
463
|
+
} | undefined;
|
|
464
|
+
} | {
|
|
465
|
+
dialect: "js" | "cron" | "cel" | "template";
|
|
466
|
+
source?: string | undefined;
|
|
467
|
+
ast?: unknown;
|
|
468
|
+
meta?: {
|
|
469
|
+
rationale?: string | undefined;
|
|
470
|
+
generatedBy?: string | undefined;
|
|
471
|
+
} | undefined;
|
|
472
|
+
} | undefined;
|
|
473
|
+
}[] | undefined;
|
|
474
|
+
subforms?: {
|
|
475
|
+
childObject: string;
|
|
476
|
+
relationshipField?: string | undefined;
|
|
477
|
+
columns?: any[] | undefined;
|
|
478
|
+
amountField?: string | undefined;
|
|
479
|
+
totalField?: string | undefined;
|
|
480
|
+
title?: string | undefined;
|
|
481
|
+
addLabel?: string | undefined;
|
|
482
|
+
minRows?: number | undefined;
|
|
483
|
+
maxRows?: number | undefined;
|
|
484
|
+
}[] | undefined;
|
|
485
|
+
defaultSort?: {
|
|
486
|
+
field: string;
|
|
487
|
+
order: "asc" | "desc";
|
|
488
|
+
}[] | undefined;
|
|
489
|
+
sharing?: {
|
|
490
|
+
enabled: boolean;
|
|
491
|
+
allowAnonymous: boolean;
|
|
492
|
+
publicLink?: string | undefined;
|
|
493
|
+
password?: string | undefined;
|
|
494
|
+
allowedDomains?: string[] | undefined;
|
|
495
|
+
expiresAt?: string | undefined;
|
|
496
|
+
} | undefined;
|
|
497
|
+
submitBehavior?: {
|
|
498
|
+
kind: "thank-you";
|
|
499
|
+
title?: string | undefined;
|
|
500
|
+
message?: string | undefined;
|
|
501
|
+
} | {
|
|
502
|
+
kind: "redirect";
|
|
503
|
+
url: string;
|
|
504
|
+
delayMs?: number | undefined;
|
|
505
|
+
} | {
|
|
506
|
+
kind: "continue";
|
|
507
|
+
} | {
|
|
508
|
+
kind: "next-record";
|
|
509
|
+
} | undefined;
|
|
510
|
+
aria?: {
|
|
511
|
+
ariaLabel?: string | undefined;
|
|
512
|
+
ariaDescribedBy?: string | undefined;
|
|
513
|
+
role?: string | undefined;
|
|
514
|
+
} | undefined;
|
|
515
|
+
};
|
|
516
|
+
label: string;
|
|
517
|
+
filePatterns: string[];
|
|
518
|
+
supportsOverlay: boolean;
|
|
519
|
+
allowRuntimeCreate: boolean;
|
|
520
|
+
supportsVersioning: boolean;
|
|
521
|
+
executionPinned: boolean;
|
|
522
|
+
loadOrder: number;
|
|
523
|
+
domain: "data" | "ui" | "automation" | "ai" | "system" | "security";
|
|
524
|
+
description?: string | undefined;
|
|
525
|
+
}[];
|
|
526
|
+
}>;
|
|
527
|
+
/**
|
|
528
|
+
* Sweep all (or filtered) metadata types and report entries that
|
|
529
|
+
* fail spec validation. Powers the Studio governance view
|
|
530
|
+
* (`GET /api/v1/meta/diagnostics`) and `os doctor`-style CLI
|
|
531
|
+
* checks.
|
|
532
|
+
*
|
|
533
|
+
* `severity` defaults to `'error'` — only entries with at least
|
|
534
|
+
* one Zod error issue are returned. `'warning'` includes
|
|
535
|
+
* everything we surface (warnings are reserved for a future lint
|
|
536
|
+
* layer on top of spec validation).
|
|
537
|
+
*
|
|
538
|
+
* `type` may be either a singular (`'view'`) or plural (`'views'`)
|
|
539
|
+
* identifier; the underlying `getMetaItems` already normalises.
|
|
540
|
+
*
|
|
541
|
+
* Implementation note: leverages the `_diagnostics` already
|
|
542
|
+
* decorated onto items by `getMetaItems()` to avoid running
|
|
543
|
+
* `safeParse()` twice. For types whose schema is unregistered we
|
|
544
|
+
* skip silently (they cannot be validated and should not appear
|
|
545
|
+
* as "valid" either — they are simply opaque to this report).
|
|
546
|
+
*/
|
|
547
|
+
getMetaDiagnostics(request?: {
|
|
548
|
+
type?: string;
|
|
549
|
+
severity?: 'error' | 'warning';
|
|
550
|
+
organizationId?: string;
|
|
551
|
+
packageId?: string;
|
|
552
|
+
}): Promise<{
|
|
553
|
+
entries: Array<{
|
|
554
|
+
type: string;
|
|
555
|
+
name: string;
|
|
556
|
+
diagnostics: MetadataDiagnostics;
|
|
557
|
+
}>;
|
|
558
|
+
total: number;
|
|
559
|
+
scannedTypes: number;
|
|
560
|
+
scannedItems: number;
|
|
561
|
+
/**
|
|
562
|
+
* Per-type aggregate stats — count of items and the list of
|
|
563
|
+
* packages contributing to each type. Computed in the same
|
|
564
|
+
* sweep so the Studio directory page can render tile counts
|
|
565
|
+
* and a package filter in one round-trip.
|
|
566
|
+
*/
|
|
567
|
+
stats: Record<string, {
|
|
568
|
+
count: number;
|
|
569
|
+
locked: number;
|
|
570
|
+
packages: string[];
|
|
571
|
+
}>;
|
|
572
|
+
}>;
|
|
573
|
+
getMetaItems(request: {
|
|
574
|
+
type: string;
|
|
575
|
+
packageId?: string;
|
|
576
|
+
organizationId?: string;
|
|
577
|
+
previewDrafts?: boolean;
|
|
578
|
+
}): Promise<{
|
|
579
|
+
type: string;
|
|
580
|
+
items: any[];
|
|
581
|
+
}>;
|
|
582
|
+
getMetaItem(request: {
|
|
583
|
+
type: string;
|
|
584
|
+
name: string;
|
|
585
|
+
packageId?: string;
|
|
586
|
+
organizationId?: string;
|
|
587
|
+
state?: 'active' | 'draft';
|
|
588
|
+
previewDrafts?: boolean;
|
|
589
|
+
}): Promise<{
|
|
590
|
+
type: string;
|
|
591
|
+
name: string;
|
|
592
|
+
item: any;
|
|
593
|
+
} | {
|
|
594
|
+
editable: boolean;
|
|
595
|
+
deletable: boolean;
|
|
596
|
+
resettable: boolean;
|
|
597
|
+
packageVersion?: string | undefined;
|
|
598
|
+
packageId?: string | undefined;
|
|
599
|
+
provenance?: "org" | "package" | "env-forced" | undefined;
|
|
600
|
+
lockDocsUrl?: string | undefined;
|
|
601
|
+
lockSource?: "package" | "artifact" | "env-forced" | undefined;
|
|
602
|
+
lockReason?: string | undefined;
|
|
603
|
+
type: string;
|
|
604
|
+
name: string;
|
|
605
|
+
item: unknown;
|
|
606
|
+
lock: "full" | "none" | "no-overlay" | "no-delete";
|
|
607
|
+
}>;
|
|
608
|
+
/**
|
|
609
|
+
* Phase 3a-layered-get: return the 3 layers of a metadata item
|
|
610
|
+
* separately — `code` (artifact-loaded baseline), `overlay` (per-org
|
|
611
|
+
* customisation row, if any), and `effective` (what `getMetaItem`
|
|
612
|
+
* would return, i.e. overlay-wins merge).
|
|
613
|
+
*
|
|
614
|
+
* Drives the "Code default vs Overlay vs Effective" diff tab in the
|
|
615
|
+
* generic Metadata Resource Edit page. Admins can see exactly what
|
|
616
|
+
* was customised and reset selectively.
|
|
617
|
+
*
|
|
618
|
+
* `code` is null if no artifact baseline exists; `overlay` is null if
|
|
619
|
+
* no sys_metadata row exists for the requested scope; `effective` is
|
|
620
|
+
* never null when either layer exists.
|
|
621
|
+
*/
|
|
622
|
+
getMetaItemLayered(request: {
|
|
623
|
+
type: string;
|
|
624
|
+
name: string;
|
|
625
|
+
packageId?: string;
|
|
626
|
+
organizationId?: string;
|
|
627
|
+
}): Promise<{
|
|
628
|
+
type: string;
|
|
629
|
+
name: string;
|
|
630
|
+
code: unknown | null;
|
|
631
|
+
overlay: unknown | null;
|
|
632
|
+
overlayScope: 'org' | 'env' | null;
|
|
633
|
+
effective: unknown | null;
|
|
634
|
+
/**
|
|
635
|
+
* Load-time validation result for the effective payload — same
|
|
636
|
+
* shape attached to getMetaItems/getMetaItem by
|
|
637
|
+
* decorateMetadataItem. Undefined for types without a registered
|
|
638
|
+
* Zod schema (function/service/router). Lets the Studio edit
|
|
639
|
+
* page surface invalid-metadata banners + inline field errors
|
|
640
|
+
* without a second round-trip.
|
|
641
|
+
*/
|
|
642
|
+
_diagnostics?: MetadataDiagnostics;
|
|
643
|
+
lock: MetadataLock;
|
|
644
|
+
lockReason?: string;
|
|
645
|
+
lockSource?: 'artifact' | 'package' | 'env-forced' | 'overlay';
|
|
646
|
+
lockDocsUrl?: string;
|
|
647
|
+
provenance?: MetadataProvenance;
|
|
648
|
+
packageId?: string;
|
|
649
|
+
packageVersion?: string;
|
|
650
|
+
editable: boolean;
|
|
651
|
+
deletable: boolean;
|
|
652
|
+
resettable: boolean;
|
|
653
|
+
}>;
|
|
654
|
+
/**
|
|
655
|
+
* ADR-0010 §3.6 / Phase 4.1 — read the metadata-protection audit log
|
|
656
|
+
* for a single item. Returns the most-recent rows of
|
|
657
|
+
* `sys_metadata_audit` for this (type, name) tuple, sorted newest
|
|
658
|
+
* first. Refused (`denied`) and forced (`forced`) writes both appear
|
|
659
|
+
* here — they never reach the `history` endpoint, which only tracks
|
|
660
|
+
* successful body snapshots.
|
|
661
|
+
*
|
|
662
|
+
* The table is provisioned by `platform-objects` and is the
|
|
663
|
+
* compliance surface for the lock-enforcement story. When the
|
|
664
|
+
* environment has not yet provisioned the table (legacy install
|
|
665
|
+
* prior to ADR-0010) the call returns `{ events: [] }` instead of
|
|
666
|
+
* raising, keeping the Studio tab harmless.
|
|
667
|
+
*/
|
|
668
|
+
auditMetaItem(request: {
|
|
669
|
+
type: string;
|
|
670
|
+
name: string;
|
|
671
|
+
organizationId?: string | null;
|
|
672
|
+
limit?: number;
|
|
673
|
+
}): Promise<{
|
|
674
|
+
events: Array<{
|
|
675
|
+
id: unknown;
|
|
676
|
+
occurredAt: string;
|
|
677
|
+
actor: string;
|
|
678
|
+
source: string | null;
|
|
679
|
+
operation: 'save' | 'publish' | 'rollback' | 'delete' | 'reset';
|
|
680
|
+
outcome: 'allowed' | 'denied' | 'forced';
|
|
681
|
+
code: string;
|
|
682
|
+
lockState: MetadataLock | null;
|
|
683
|
+
lockOverridden: boolean;
|
|
684
|
+
requestId: string | null;
|
|
685
|
+
note: string | null;
|
|
686
|
+
}>;
|
|
687
|
+
}>;
|
|
688
|
+
getUiView(request: {
|
|
689
|
+
object: string;
|
|
690
|
+
type: 'list' | 'form';
|
|
691
|
+
}): Promise<{
|
|
692
|
+
list: {
|
|
693
|
+
type: "grid";
|
|
694
|
+
object: string;
|
|
695
|
+
label: any;
|
|
696
|
+
columns: {
|
|
697
|
+
field: string;
|
|
698
|
+
label: any;
|
|
699
|
+
sortable: boolean;
|
|
700
|
+
}[];
|
|
701
|
+
sort: any;
|
|
702
|
+
searchableFields: string[];
|
|
703
|
+
};
|
|
704
|
+
form?: undefined;
|
|
705
|
+
} | {
|
|
706
|
+
form: {
|
|
707
|
+
type: "simple";
|
|
708
|
+
object: string;
|
|
709
|
+
label: string;
|
|
710
|
+
sections: {
|
|
711
|
+
label: string;
|
|
712
|
+
columns: 2;
|
|
713
|
+
collapsible: boolean;
|
|
714
|
+
collapsed: boolean;
|
|
715
|
+
fields: {
|
|
716
|
+
field: string;
|
|
717
|
+
label: any;
|
|
718
|
+
required: any;
|
|
719
|
+
readonly: any;
|
|
720
|
+
type: any;
|
|
721
|
+
colSpan: number;
|
|
722
|
+
}[];
|
|
723
|
+
}[];
|
|
724
|
+
};
|
|
725
|
+
list?: undefined;
|
|
726
|
+
}>;
|
|
727
|
+
findData(request: {
|
|
728
|
+
object: string;
|
|
729
|
+
query?: any;
|
|
730
|
+
context?: any;
|
|
731
|
+
}): Promise<{
|
|
732
|
+
object: string;
|
|
733
|
+
records: any[];
|
|
734
|
+
total: number;
|
|
735
|
+
hasMore: boolean;
|
|
736
|
+
}>;
|
|
737
|
+
getData(request: {
|
|
738
|
+
object: string;
|
|
739
|
+
id: string;
|
|
740
|
+
expand?: string | string[];
|
|
741
|
+
select?: string | string[];
|
|
742
|
+
context?: any;
|
|
743
|
+
}): Promise<{
|
|
744
|
+
object: string;
|
|
745
|
+
id: string;
|
|
746
|
+
record: any;
|
|
747
|
+
}>;
|
|
748
|
+
createData(request: {
|
|
749
|
+
object: string;
|
|
750
|
+
data: any;
|
|
751
|
+
context?: any;
|
|
752
|
+
}): Promise<{
|
|
753
|
+
object: string;
|
|
754
|
+
id: any;
|
|
755
|
+
record: any;
|
|
756
|
+
}>;
|
|
757
|
+
/**
|
|
758
|
+
* Clone a record — read the source, drop engine-owned columns, and
|
|
759
|
+
* insert a fresh copy. Gated by the object's `enable.clone` capability
|
|
760
|
+
* (default `true`; only an explicit `enable.clone === false` disables it).
|
|
761
|
+
*
|
|
762
|
+
* Shallow by design: it duplicates the record's own scalar/business field
|
|
763
|
+
* values, not its related child records. The insert path re-stamps audit
|
|
764
|
+
* columns, regenerates `autonumber` fields, and recomputes derived
|
|
765
|
+
* (`formula`/`summary`) fields, so the copy is a valid new row rather than
|
|
766
|
+
* a byte-identical twin. Caller-supplied `overrides` are applied last and
|
|
767
|
+
* win over the copied values — the natural place to set a new `name`,
|
|
768
|
+
* clear a unique field, or reset status before insert.
|
|
769
|
+
*/
|
|
770
|
+
cloneData(request: {
|
|
771
|
+
object: string;
|
|
772
|
+
id: string;
|
|
773
|
+
overrides?: Record<string, any>;
|
|
774
|
+
context?: any;
|
|
775
|
+
}): Promise<{
|
|
776
|
+
object: string;
|
|
777
|
+
id: any;
|
|
778
|
+
sourceId: string;
|
|
779
|
+
record: any;
|
|
780
|
+
}>;
|
|
781
|
+
updateData(request: {
|
|
782
|
+
object: string;
|
|
783
|
+
id: string;
|
|
784
|
+
data: any;
|
|
785
|
+
expectedVersion?: string;
|
|
786
|
+
context?: any;
|
|
787
|
+
}): Promise<{
|
|
788
|
+
object: string;
|
|
789
|
+
id: string;
|
|
790
|
+
record: any;
|
|
791
|
+
}>;
|
|
792
|
+
deleteData(request: {
|
|
793
|
+
object: string;
|
|
794
|
+
id: string;
|
|
795
|
+
expectedVersion?: string;
|
|
796
|
+
context?: any;
|
|
797
|
+
}): Promise<{
|
|
798
|
+
object: string;
|
|
799
|
+
id: string;
|
|
800
|
+
success: boolean;
|
|
801
|
+
}>;
|
|
802
|
+
/**
|
|
803
|
+
* Optimistic Concurrency Control gate shared by updateData/deleteData.
|
|
804
|
+
*
|
|
805
|
+
* When the caller passes a non-empty `expectedVersion` token (typically
|
|
806
|
+
* the `updated_at` value they read), this fetches the current record
|
|
807
|
+
* and compares its `updated_at` against the token. Mismatch → throw
|
|
808
|
+
* `ConcurrentUpdateError` which the REST layer maps to 409.
|
|
809
|
+
*
|
|
810
|
+
* Behaviour:
|
|
811
|
+
* - Empty/missing token → no check (opt-in semantics; existing callers
|
|
812
|
+
* that haven't yet adopted OCC are unaffected).
|
|
813
|
+
* - Record not found → no check; downstream `engine.update` will
|
|
814
|
+
* surface the usual `RECORD_NOT_FOUND` 404. We intentionally do not
|
|
815
|
+
* treat "missing record" as a concurrency conflict.
|
|
816
|
+
* - Record has no `updated_at` field (timestamps disabled) → no check.
|
|
817
|
+
* Logging would be noisy here; OCC is opt-in and the absence of a
|
|
818
|
+
* version column is an explicit "this object doesn't support OCC"
|
|
819
|
+
* signal.
|
|
820
|
+
*/
|
|
821
|
+
private assertVersionMatch;
|
|
822
|
+
/**
|
|
823
|
+
* Cross-object substring search across all registered objects that opt in
|
|
824
|
+
* via `enable.searchable !== false` and `enable.apiEnabled !== false`.
|
|
825
|
+
* Searches text-like fields (text/textarea/email/url/phone/markdown/html/string)
|
|
826
|
+
* whose `searchable: true` flag is set, falling back to the object's
|
|
827
|
+
* `displayNameField` (or `name`) when no fields are explicitly searchable.
|
|
828
|
+
*
|
|
829
|
+
* The query is split into whitespace-separated terms; each term must match
|
|
830
|
+
* (case-insensitive LIKE) at least one searchable field. RBAC/RLS is
|
|
831
|
+
* enforced by forwarding the caller's `context` to `engine.find` so users
|
|
832
|
+
* only see records they are entitled to read.
|
|
833
|
+
*/
|
|
834
|
+
searchAll(request: {
|
|
835
|
+
q: string;
|
|
836
|
+
objects?: string[];
|
|
837
|
+
limit?: number;
|
|
838
|
+
perObject?: number;
|
|
839
|
+
context?: any;
|
|
840
|
+
}): Promise<{
|
|
841
|
+
query: string;
|
|
842
|
+
hits: Array<{
|
|
843
|
+
object: string;
|
|
844
|
+
id: string;
|
|
845
|
+
title: string;
|
|
846
|
+
snippet?: string;
|
|
847
|
+
record: any;
|
|
848
|
+
}>;
|
|
849
|
+
totalObjects: number;
|
|
850
|
+
totalHits: number;
|
|
851
|
+
truncated: boolean;
|
|
852
|
+
}>;
|
|
853
|
+
getMetaItemCached(request: {
|
|
854
|
+
type: string;
|
|
855
|
+
name: string;
|
|
856
|
+
cacheRequest?: MetadataCacheRequest;
|
|
857
|
+
locale?: string;
|
|
858
|
+
}): Promise<MetadataCacheResponse>;
|
|
859
|
+
batchData(request: {
|
|
860
|
+
object: string;
|
|
861
|
+
request: BatchUpdateRequest;
|
|
862
|
+
}): Promise<BatchUpdateResponse>;
|
|
863
|
+
createManyData(request: {
|
|
864
|
+
object: string;
|
|
865
|
+
records: any[];
|
|
866
|
+
}): Promise<any>;
|
|
867
|
+
updateManyData(request: UpdateManyDataRequest): Promise<BatchUpdateResponse>;
|
|
868
|
+
analyticsQuery(request: any): Promise<any>;
|
|
869
|
+
getAnalyticsMeta(request: any): Promise<any>;
|
|
870
|
+
private mapAnalyticsOperator;
|
|
871
|
+
triggerAutomation(_request: any): Promise<any>;
|
|
872
|
+
deleteManyData(request: DeleteManyDataRequest): Promise<any>;
|
|
873
|
+
/**
|
|
874
|
+
* Metadata types that are customer-overridable via {@link saveMetaItem}/
|
|
875
|
+
* {@link deleteMetaItem} in project-kernel mode. Derived from the canonical
|
|
876
|
+
* registry in {@link DEFAULT_METADATA_TYPE_REGISTRY}: a type opts in by
|
|
877
|
+
* setting `allowOrgOverride: true` on its registry entry. The set is
|
|
878
|
+
* augmented with the plural form of every singular so callers using REST
|
|
879
|
+
* conventions (`/api/v1/meta/views/...`) get the same gate. See ADR-0005
|
|
880
|
+
* §"Whitelist enforcement" for the rationale and the per-type rollout
|
|
881
|
+
* checklist.
|
|
882
|
+
*/
|
|
883
|
+
private static readonly OVERLAY_ALLOWED_TYPES;
|
|
884
|
+
/**
|
|
885
|
+
* Phase 3a-env-writable: parse `OS_METADATA_WRITABLE` once.
|
|
886
|
+
* Comma-separated singular type names. When the env var is set, the
|
|
887
|
+
* listed types get treated as `allowOrgOverride: true` regardless of
|
|
888
|
+
* their static registry entry. This is the runtime escape hatch admins
|
|
889
|
+
* use to enable Studio-side editing of types whose protocol-level flag
|
|
890
|
+
* is still false (object, field, permission, …).
|
|
891
|
+
*
|
|
892
|
+
* Memoised at first call. Tests can override by clearing the cache via
|
|
893
|
+
* {@link ObjectStackProtocolImplementation.resetEnvWritableCache}.
|
|
894
|
+
*/
|
|
895
|
+
private static _envWritableTypes;
|
|
896
|
+
private static envWritableTypes;
|
|
897
|
+
/** Test hook — clear the memoised env-writable cache. */
|
|
898
|
+
static resetEnvWritableCache(): void;
|
|
899
|
+
/**
|
|
900
|
+
* Types that opt into runtime creation of brand-new items (ADR-0005
|
|
901
|
+
* extension — two-tier model). A type may have
|
|
902
|
+
* `allowOrgOverride: false` (cannot overlay artifact-shipped items)
|
|
903
|
+
* yet still set `allowRuntimeCreate: true` (users can author new
|
|
904
|
+
* items in `sys_metadata`). The two flags are orthogonal; see
|
|
905
|
+
* {@link isArtifactBacked} for how the protocol decides which gate
|
|
906
|
+
* applies to a given save/delete.
|
|
907
|
+
*/
|
|
908
|
+
/**
|
|
909
|
+
* Set of type names that have a static entry in
|
|
910
|
+
* `DEFAULT_METADATA_TYPE_REGISTRY`. Anything outside this set is
|
|
911
|
+
* runtime-registered (plugin-provided types like `theme`, `api`,
|
|
912
|
+
* `connector`) — the listing endpoint at `getMetaTypes()` synthesises
|
|
913
|
+
* those with `allowRuntimeCreate: true`, so this gate must agree.
|
|
914
|
+
*/
|
|
915
|
+
private static readonly STATIC_REGISTRY_TYPES;
|
|
916
|
+
private static readonly RUNTIME_CREATE_ALLOWED_TYPES;
|
|
917
|
+
/** Normalize plural→singular before consulting the allow-list. */
|
|
918
|
+
private static isOverlayAllowed;
|
|
919
|
+
/** Does this type permit creating brand-new (artifact-free) items? */
|
|
920
|
+
private static isRuntimeCreateAllowed;
|
|
921
|
+
/**
|
|
922
|
+
* Does an artifact (npm-package-loaded) item exist at `(type, name)`?
|
|
923
|
+
*
|
|
924
|
+
* The schema registry's `_packageId` tag is set only when
|
|
925
|
+
* `registerItem(..., packageId)` is called with a truthy packageId
|
|
926
|
+
* — and only artifact loaders do that. DB-rehydrated items
|
|
927
|
+
* (sys_metadata rows registered back into the registry by
|
|
928
|
+
* `getMetaItems` / `loadMetaFromDb`) call `registerItem` without a
|
|
929
|
+
* packageId, so they carry no `_packageId` and are correctly
|
|
930
|
+
* excluded here.
|
|
931
|
+
*
|
|
932
|
+
* Used by the two-tier authorization model to distinguish
|
|
933
|
+
* "overlaying a packaged item" (requires `allowOrgOverride`) from
|
|
934
|
+
* "authoring a DB-only item" (requires only `allowRuntimeCreate`).
|
|
935
|
+
*/
|
|
936
|
+
private isArtifactBacked;
|
|
937
|
+
/**
|
|
938
|
+
* Look up an item from the artifact registry across both the requested
|
|
939
|
+
* type and its singular/plural twin. Returns `undefined` when the
|
|
940
|
+
* registry is unavailable or the item is not artifact-backed.
|
|
941
|
+
*/
|
|
942
|
+
private lookupArtifactItem;
|
|
943
|
+
/**
|
|
944
|
+
* True when `packageId` is a **writable base** — a DB-backed package an
|
|
945
|
+
* org or the AI may author *new* metadata into (ADR-0070 D2). The two
|
|
946
|
+
* read-only kinds return `false`:
|
|
947
|
+
*
|
|
948
|
+
* • **Booted code packages** — they register a manifest into the engine
|
|
949
|
+
* at startup (`registerApp` → `engine.manifests`); their items are
|
|
950
|
+
* code-shipped artifacts. Only `allowOrgOverride` overlays are allowed
|
|
951
|
+
* (ADR-0005), never fresh authored items.
|
|
952
|
+
* • **Installed / platform packages** — manifest `scope` is `system` or
|
|
953
|
+
* `cloud` (marketplace / platform-delivered).
|
|
954
|
+
*
|
|
955
|
+
* A project-scoped DB package, or a bare ADR-0048 *authoring-workspace* id
|
|
956
|
+
* with no registered manifest, is writable.
|
|
957
|
+
*
|
|
958
|
+
* NOTE: the code-package signal is the engine manifest map ONLY — we
|
|
959
|
+
* deliberately do NOT fall back to "owns ≥1 registered object" (the old
|
|
960
|
+
* `isLoadedPackage` heuristic). A writable base accrues registered objects
|
|
961
|
+
* once its drafts publish, and that must never flip the base to read-only
|
|
962
|
+
* — that is the exact #2252 read-only-after-publish trap this ADR removes.
|
|
963
|
+
*/
|
|
964
|
+
private isWritablePackage;
|
|
965
|
+
/**
|
|
966
|
+
* Resolve the effective `_lock` for an item by consulting the
|
|
967
|
+
* artifact registry first, then the persisted overlay row. Artifact
|
|
968
|
+
* always wins — by design, an overlay cannot loosen a packaged
|
|
969
|
+
* lock (ADR-0010 §3.3).
|
|
970
|
+
*
|
|
971
|
+
* Returns `'none'` when nothing is locked, which is the common
|
|
972
|
+
* case. Safe to call when `environmentId` is undefined (control-
|
|
973
|
+
* plane bootstrap) — the lock check is only meaningful in tenant
|
|
974
|
+
* scope and the caller is expected to also gate on `environmentId`.
|
|
975
|
+
*/
|
|
976
|
+
private getEffectiveLock;
|
|
977
|
+
/**
|
|
978
|
+
* Best-effort audit-row writer (ADR-0010 §3.6). Failures here are
|
|
979
|
+
* logged but never block the underlying decision: an environment
|
|
980
|
+
* without the audit table provisioned (legacy installs before this
|
|
981
|
+
* ADR landed) still answers normal API calls, just without the
|
|
982
|
+
* compliance trail. Phase 2 will make the audit table a hard
|
|
983
|
+
* dependency.
|
|
984
|
+
*/
|
|
985
|
+
private recordMetadataAudit;
|
|
986
|
+
/**
|
|
987
|
+
* Phase 1 L3 enforcement for write operations (save / publish /
|
|
988
|
+
* rollback). Returns null on allow. Returns the structured `Error`
|
|
989
|
+
* the caller should `throw` on deny — also records the denial in
|
|
990
|
+
* the audit log so refused attempts are visible in compliance
|
|
991
|
+
* reports (refused writes never reach sys_metadata_history).
|
|
992
|
+
*/
|
|
993
|
+
private assertLockAllowsWrite;
|
|
994
|
+
/** Counterpart of {@link assertLockAllowsWrite} for delete. */
|
|
995
|
+
private assertLockAllowsDelete;
|
|
996
|
+
/**
|
|
997
|
+
* Mirror an object-type overlay write into the in-memory engine
|
|
998
|
+
* registry so subsequent CRUD finds the new schema. Idempotent and
|
|
999
|
+
* safe to call after a successful persistence call. For the legacy
|
|
1000
|
+
* write path this is invoked BEFORE persistence (historical behavior
|
|
1001
|
+
* preserved); for the PR-10d.3 repository path it is invoked only
|
|
1002
|
+
* AFTER `put()` resolves successfully, so a failed write — DB error,
|
|
1003
|
+
* optimistic-lock conflict, validation failure — never leaks a
|
|
1004
|
+
* stale schema into the registry.
|
|
1005
|
+
*/
|
|
1006
|
+
private applyObjectRegistryMutation;
|
|
1007
|
+
/**
|
|
1008
|
+
* Heal the in-memory registry after a metadata reset (overlay-row
|
|
1009
|
+
* delete) on control-plane kernels. Two layers:
|
|
1010
|
+
*
|
|
1011
|
+
* 1. Drop the plain-key runtime shadow so the packaged artifact
|
|
1012
|
+
* (registered under `<packageId>:<name>`) becomes the visible
|
|
1013
|
+
* value again. The shadow is written by the overlay-hydration
|
|
1014
|
+
* paths (`getMetaItems` / `loadMetaFromDb`) and — pre-fix —
|
|
1015
|
+
* survived the reset until restart, leaving stale overlay
|
|
1016
|
+
* content (and a stripped `_lock` envelope) in every
|
|
1017
|
+
* registry-direct read (ADR-0010 §3.3).
|
|
1018
|
+
* 2. When no composite-key artifact exists, fall back to the
|
|
1019
|
+
* MetadataService baseline (FilesystemLoader-sourced types) and
|
|
1020
|
+
* re-register it, preserving the historical refresh behaviour
|
|
1021
|
+
* for items the SchemaRegistry never held as artifacts.
|
|
1022
|
+
*
|
|
1023
|
+
* Best-effort: a failure must never block the delete that already
|
|
1024
|
+
* succeeded; the next full reload fixes the registry anyway.
|
|
1025
|
+
*/
|
|
1026
|
+
private restoreArtifactRegistryView;
|
|
1027
|
+
/**
|
|
1028
|
+
* Ensure a just-PUBLISHED object's physical table exists so it is usable
|
|
1029
|
+
* for data CRUD immediately — without a server restart. Registering the
|
|
1030
|
+
* object (above) only updates the in-memory registry; the table is created
|
|
1031
|
+
* by the driver's schema sync, which otherwise only runs at boot. Without
|
|
1032
|
+
* this, inserting into a freshly-published object fails with "no such
|
|
1033
|
+
* table" (surfaced as `object_not_found`) until the next restart.
|
|
1034
|
+
* Best-effort + non-fatal: drivers without DDL (or read-only datasources)
|
|
1035
|
+
* simply no-op, and a sync failure must not abort the publish.
|
|
1036
|
+
*/
|
|
1037
|
+
private ensureObjectStorage;
|
|
1038
|
+
/**
|
|
1039
|
+
* Inverse of {@link ensureObjectStorage}: drop an object's physical table.
|
|
1040
|
+
* DESTRUCTIVE — deletes the table and all its rows. Only invoked when a
|
|
1041
|
+
* delete explicitly opts into storage teardown (see {@link deleteMetaItem}'s
|
|
1042
|
+
* `dropStorage`), so publishing an object solely to preview it can be undone
|
|
1043
|
+
* without leaving an orphan table. Best-effort: a failure is logged, not
|
|
1044
|
+
* thrown — the metadata delete already succeeded, and a stray table is
|
|
1045
|
+
* reclaimed by the next sync/drop rather than blocking the delete.
|
|
1046
|
+
*/
|
|
1047
|
+
private dropObjectStorage;
|
|
1048
|
+
/**
|
|
1049
|
+
* Guard for storage teardown on delete. Drops a physical table only when
|
|
1050
|
+
* the caller opted in AND it is safe: object types only (others have no
|
|
1051
|
+
* table), active state only (drafts were never materialised), and never a
|
|
1052
|
+
* `sys_`-prefixed platform table.
|
|
1053
|
+
*/
|
|
1054
|
+
private shouldDropStorage;
|
|
1055
|
+
saveMetaItem(request: {
|
|
1056
|
+
type: string;
|
|
1057
|
+
name: string;
|
|
1058
|
+
item?: any;
|
|
1059
|
+
organizationId?: string;
|
|
1060
|
+
parentVersion?: string | null;
|
|
1061
|
+
actor?: string;
|
|
1062
|
+
force?: boolean;
|
|
1063
|
+
mode?: 'draft' | 'publish';
|
|
1064
|
+
packageId?: string | null;
|
|
1065
|
+
}): Promise<{
|
|
1066
|
+
success: boolean;
|
|
1067
|
+
version: string;
|
|
1068
|
+
seq: number;
|
|
1069
|
+
state: string;
|
|
1070
|
+
message: string;
|
|
1071
|
+
} | {
|
|
1072
|
+
success: boolean;
|
|
1073
|
+
message: string;
|
|
1074
|
+
version?: undefined;
|
|
1075
|
+
seq?: undefined;
|
|
1076
|
+
state?: undefined;
|
|
1077
|
+
}>;
|
|
1078
|
+
/**
|
|
1079
|
+
* Yield the durable change-log for a single metadata item — every
|
|
1080
|
+
* put/delete recorded in `sys_metadata_history` for `(org, type, name)`,
|
|
1081
|
+
* in event_seq order. Powers the Studio "History" tab and any
|
|
1082
|
+
* client-side audit timeline.
|
|
1083
|
+
*
|
|
1084
|
+
* Returns `[]` for non-overlay-allowed types (the legacy raw-engine
|
|
1085
|
+
* path doesn't record history) instead of throwing — callers can treat
|
|
1086
|
+
* "no history" uniformly.
|
|
1087
|
+
*/
|
|
1088
|
+
historyMetaItem(request: {
|
|
1089
|
+
type: string;
|
|
1090
|
+
name: string;
|
|
1091
|
+
organizationId?: string;
|
|
1092
|
+
sinceSeq?: number;
|
|
1093
|
+
limit?: number;
|
|
1094
|
+
}): Promise<{
|
|
1095
|
+
events: _objectstack_metadata_core.MetadataEvent[];
|
|
1096
|
+
}>;
|
|
1097
|
+
/**
|
|
1098
|
+
* Promote the pending draft overlay to the live (`active`) row.
|
|
1099
|
+
* Records a history event with `op='publish'`. 404 (`[no_draft]`)
|
|
1100
|
+
* when there is nothing to publish.
|
|
1101
|
+
*/
|
|
1102
|
+
publishMetaItem(request: {
|
|
1103
|
+
type: string;
|
|
1104
|
+
name: string;
|
|
1105
|
+
organizationId?: string;
|
|
1106
|
+
actor?: string;
|
|
1107
|
+
message?: string;
|
|
1108
|
+
/**
|
|
1109
|
+
* INTERNAL — `publishPackageDrafts` publishes many drafts and batch-applies
|
|
1110
|
+
* every seed body in ONE loader pass afterwards (cross-seed references need
|
|
1111
|
+
* multi-pass over the whole set), so it suppresses the per-item apply here.
|
|
1112
|
+
*/
|
|
1113
|
+
_skipSeedApply?: boolean;
|
|
1114
|
+
}): Promise<{
|
|
1115
|
+
success: boolean;
|
|
1116
|
+
version: string;
|
|
1117
|
+
seq: number;
|
|
1118
|
+
message?: string;
|
|
1119
|
+
/**
|
|
1120
|
+
* Present when a `seed` draft was published: the result of materializing
|
|
1121
|
+
* its rows. Publishing the metadata ALWAYS succeeds independently — a
|
|
1122
|
+
* seed-load problem is surfaced here, never thrown, so callers (and UIs)
|
|
1123
|
+
* must check `seedApplied.success` instead of assuming data went live.
|
|
1124
|
+
*/
|
|
1125
|
+
seedApplied?: {
|
|
1126
|
+
success: boolean;
|
|
1127
|
+
inserted: number;
|
|
1128
|
+
updated: number;
|
|
1129
|
+
error?: string;
|
|
1130
|
+
errors?: unknown[];
|
|
1131
|
+
};
|
|
1132
|
+
}>;
|
|
1133
|
+
/**
|
|
1134
|
+
* Materialize published `seed` bodies into data rows via the SeedLoaderService
|
|
1135
|
+
* (externalId-keyed upsert, multi-pass for cross-seed references). Passing ALL
|
|
1136
|
+
* of a publish's seed bodies in ONE call lets a child seed reference a parent
|
|
1137
|
+
* seed's rows regardless of publish order. Best-effort: any failure is
|
|
1138
|
+
* returned, never thrown — publishing metadata must not be blocked by a data
|
|
1139
|
+
* problem, but the caller surfaces `seedApplied` so the failure is LOUD.
|
|
1140
|
+
*/
|
|
1141
|
+
private applySeedBodies;
|
|
1142
|
+
/**
|
|
1143
|
+
* List pending DRAFT metadata (ADR-0033) for the org, optionally narrowed
|
|
1144
|
+
* by `packageId` and/or `type`. The list reads of `getMetaItems` only see
|
|
1145
|
+
* the ACTIVE registry; this exposes what an AI authored but a human hasn't
|
|
1146
|
+
* published yet, so the console can show a "pending changes" surface and a
|
|
1147
|
+
* just-built app package isn't displayed as empty. No body is returned.
|
|
1148
|
+
*/
|
|
1149
|
+
listDrafts(request?: {
|
|
1150
|
+
packageId?: string;
|
|
1151
|
+
type?: string;
|
|
1152
|
+
organizationId?: string;
|
|
1153
|
+
}): Promise<{
|
|
1154
|
+
drafts: Array<{
|
|
1155
|
+
type: string;
|
|
1156
|
+
name: string;
|
|
1157
|
+
packageId: string | null;
|
|
1158
|
+
updatedAt: string | null;
|
|
1159
|
+
updatedBy: string | null;
|
|
1160
|
+
}>;
|
|
1161
|
+
}>;
|
|
1162
|
+
/**
|
|
1163
|
+
* Publish every pending DRAFT bound to a package in one shot (ADR-0033) —
|
|
1164
|
+
* the "publish whole app" action. Promotes each draft→active by reusing the
|
|
1165
|
+
* per-item {@link publishMetaItem} primitive (which runs the overridable /
|
|
1166
|
+
* lock guards and refreshes the runtime registry), so this needs NO
|
|
1167
|
+
* `metadata` service (unlike `MetadataService.publishPackage`, which reads
|
|
1168
|
+
* the in-memory registry and 503s when that service is absent). Per-item
|
|
1169
|
+
* failures are collected and do NOT abort the rest.
|
|
1170
|
+
*/
|
|
1171
|
+
publishPackageDrafts(request: {
|
|
1172
|
+
packageId: string;
|
|
1173
|
+
organizationId?: string;
|
|
1174
|
+
actor?: string;
|
|
1175
|
+
/** ADR-0067 — commit message (for AI turns: the user's instruction). */
|
|
1176
|
+
message?: string;
|
|
1177
|
+
/** ADR-0067 — AI model that authored the turn (absent for human/CLI). */
|
|
1178
|
+
aiModel?: string;
|
|
1179
|
+
}): Promise<{
|
|
1180
|
+
success: boolean;
|
|
1181
|
+
publishedCount: number;
|
|
1182
|
+
failedCount: number;
|
|
1183
|
+
published: Array<{
|
|
1184
|
+
type: string;
|
|
1185
|
+
name: string;
|
|
1186
|
+
version: string;
|
|
1187
|
+
}>;
|
|
1188
|
+
failed: Array<{
|
|
1189
|
+
type: string;
|
|
1190
|
+
name: string;
|
|
1191
|
+
error: string;
|
|
1192
|
+
code?: string;
|
|
1193
|
+
}>;
|
|
1194
|
+
/** Aggregate result of materializing every published `seed` (absent when no seeds). */
|
|
1195
|
+
seedApplied?: {
|
|
1196
|
+
success: boolean;
|
|
1197
|
+
inserted: number;
|
|
1198
|
+
updated: number;
|
|
1199
|
+
error?: string;
|
|
1200
|
+
errors?: unknown[];
|
|
1201
|
+
};
|
|
1202
|
+
/**
|
|
1203
|
+
* ADR-0038 L3 — post-publish runtime probe report (absent when nothing
|
|
1204
|
+
* was publishable). One real read per published artifact: seeded
|
|
1205
|
+
* objects must have rows, views must be readable, dashboard widgets'
|
|
1206
|
+
* dataset selections must execute and return data. `issues` carries
|
|
1207
|
+
* BuildIssue-shaped findings (layer 'runtime') for the agent / chat
|
|
1208
|
+
* health surfaces; probes never fail the publish itself.
|
|
1209
|
+
*/
|
|
1210
|
+
probes?: BuildProbeReport;
|
|
1211
|
+
/** ADR-0067 — id of the commit this publish recorded (absent if nothing published). */
|
|
1212
|
+
commitId?: string;
|
|
1213
|
+
}>;
|
|
1214
|
+
/**
|
|
1215
|
+
* Discard every pending DRAFT bound to a package — the NON-destructive
|
|
1216
|
+
* inverse of {@link publishPackageDrafts}. Drops only `state='draft'` rows
|
|
1217
|
+
* (via the per-item delete primitive), reverting the package to its last
|
|
1218
|
+
* published baseline; active/published metadata and physical tables are
|
|
1219
|
+
* left untouched.
|
|
1220
|
+
*
|
|
1221
|
+
* Use case: "I edited this app for a while and it turned out worse than
|
|
1222
|
+
* before — abandon all my changes." Routes through the sys_metadata path
|
|
1223
|
+
* (no metadata-service dependency, unlike `POST /packages/:id/revert`).
|
|
1224
|
+
*/
|
|
1225
|
+
discardPackageDrafts(request: {
|
|
1226
|
+
packageId: string;
|
|
1227
|
+
organizationId?: string;
|
|
1228
|
+
actor?: string;
|
|
1229
|
+
}): Promise<{
|
|
1230
|
+
success: boolean;
|
|
1231
|
+
discardedCount: number;
|
|
1232
|
+
failedCount: number;
|
|
1233
|
+
discarded: Array<{
|
|
1234
|
+
type: string;
|
|
1235
|
+
name: string;
|
|
1236
|
+
}>;
|
|
1237
|
+
failed: Array<{
|
|
1238
|
+
type: string;
|
|
1239
|
+
name: string;
|
|
1240
|
+
error: string;
|
|
1241
|
+
code?: string;
|
|
1242
|
+
}>;
|
|
1243
|
+
}>;
|
|
1244
|
+
/**
|
|
1245
|
+
* Delete an ENTIRE package: every `sys_metadata` row bound to it (active
|
|
1246
|
+
* AND draft) and — by default — the physical table of each object it
|
|
1247
|
+
* defined. DESTRUCTIVE: removes the app and its data. Use case: "I don't
|
|
1248
|
+
* want this package anymore."
|
|
1249
|
+
*
|
|
1250
|
+
* Set `keepData: true` to remove the metadata but preserve object tables.
|
|
1251
|
+
* The `sys_`-table guard in {@link deleteMetaItem} still applies, so
|
|
1252
|
+
* platform storage is never dropped. Drafts are removed before active rows
|
|
1253
|
+
* so each object's table is torn down once. Per-item failures are collected
|
|
1254
|
+
* without aborting the rest.
|
|
1255
|
+
*/
|
|
1256
|
+
deletePackage(request: {
|
|
1257
|
+
packageId: string;
|
|
1258
|
+
organizationId?: string;
|
|
1259
|
+
actor?: string;
|
|
1260
|
+
keepData?: boolean;
|
|
1261
|
+
}): Promise<{
|
|
1262
|
+
success: boolean;
|
|
1263
|
+
deletedCount: number;
|
|
1264
|
+
failedCount: number;
|
|
1265
|
+
deleted: Array<{
|
|
1266
|
+
type: string;
|
|
1267
|
+
name: string;
|
|
1268
|
+
state: string;
|
|
1269
|
+
}>;
|
|
1270
|
+
failed: Array<{
|
|
1271
|
+
type: string;
|
|
1272
|
+
name: string;
|
|
1273
|
+
error: string;
|
|
1274
|
+
code?: string;
|
|
1275
|
+
}>;
|
|
1276
|
+
}>;
|
|
1277
|
+
/**
|
|
1278
|
+
* ADR-0070 D4 — duplicate a writable base into a NEW package (the Airtable
|
|
1279
|
+
* "duplicate base" gesture). Clones every ACTIVE item the source owns into
|
|
1280
|
+
* `targetPackageId`, RE-NAMESPACING object names — the blueprint prefixes a
|
|
1281
|
+
* base's object names with its namespace (e.g. `iojn_repair_ticket`), and
|
|
1282
|
+
* `sys_metadata` keys on (type,name,org), so a same-name copy would collide
|
|
1283
|
+
* with the source — and rewriting every intra-package reference (lookup
|
|
1284
|
+
* `reference`, view `object`, expressions, etc.) to the new names. Per-item
|
|
1285
|
+
* best-effort; one failure never aborts the whole clone.
|
|
1286
|
+
*/
|
|
1287
|
+
duplicatePackage(request: {
|
|
1288
|
+
sourcePackageId: string;
|
|
1289
|
+
targetPackageId: string;
|
|
1290
|
+
targetName?: string;
|
|
1291
|
+
targetNamespace?: string;
|
|
1292
|
+
organizationId?: string;
|
|
1293
|
+
actor?: string;
|
|
1294
|
+
}): Promise<{
|
|
1295
|
+
success: boolean;
|
|
1296
|
+
copiedCount: number;
|
|
1297
|
+
failedCount: number;
|
|
1298
|
+
targetPackageId: string;
|
|
1299
|
+
copied: Array<{
|
|
1300
|
+
type: string;
|
|
1301
|
+
name: string;
|
|
1302
|
+
}>;
|
|
1303
|
+
failed: Array<{
|
|
1304
|
+
type: string;
|
|
1305
|
+
name: string;
|
|
1306
|
+
error: string;
|
|
1307
|
+
}>;
|
|
1308
|
+
}>;
|
|
1309
|
+
/**
|
|
1310
|
+
* ADR-0070 D5 — adopt orphaned (package-less) metadata into a base. The
|
|
1311
|
+
* pre-package-first stopgaps left runtime-authored items with
|
|
1312
|
+
* `package_id = null` (or the `sys_metadata` sentinel). This bulk-rebinds
|
|
1313
|
+
* every such orphan to `targetPackageId` so the env converges on the
|
|
1314
|
+
* package-first model and the "Local / Custom" migration scope can be
|
|
1315
|
+
* retired. Owned rows (already bound to a real package) are left untouched.
|
|
1316
|
+
* Updates the durable column; the in-memory registry picks the new binding
|
|
1317
|
+
* up on the next metadata reload.
|
|
1318
|
+
*/
|
|
1319
|
+
reassignOrphanedMetadata(request: {
|
|
1320
|
+
targetPackageId: string;
|
|
1321
|
+
organizationId?: string;
|
|
1322
|
+
actor?: string;
|
|
1323
|
+
}): Promise<{
|
|
1324
|
+
success: boolean;
|
|
1325
|
+
reassignedCount: number;
|
|
1326
|
+
reassigned: Array<{
|
|
1327
|
+
type: string;
|
|
1328
|
+
name: string;
|
|
1329
|
+
}>;
|
|
1330
|
+
targetPackageId: string;
|
|
1331
|
+
}>;
|
|
1332
|
+
/**
|
|
1333
|
+
* Record one commit row (best-effort) grouping a turn's published
|
|
1334
|
+
* artifacts. Returns the commit id, or null if the commit store is
|
|
1335
|
+
* unavailable (e.g. unit-test stubs) — recording never blocks a publish.
|
|
1336
|
+
*/
|
|
1337
|
+
private recordPackageCommit;
|
|
1338
|
+
private parseCommitItems;
|
|
1339
|
+
/**
|
|
1340
|
+
* List the commit timeline for a package, newest-first (ADR-0067). Returns
|
|
1341
|
+
* [] if the commit store is unavailable.
|
|
1342
|
+
*/
|
|
1343
|
+
listCommits(request: {
|
|
1344
|
+
packageId: string;
|
|
1345
|
+
organizationId?: string;
|
|
1346
|
+
limit?: number;
|
|
1347
|
+
}): Promise<Array<{
|
|
1348
|
+
id: string;
|
|
1349
|
+
operation: 'apply' | 'revert';
|
|
1350
|
+
message?: string;
|
|
1351
|
+
actor?: string;
|
|
1352
|
+
aiModel?: string;
|
|
1353
|
+
parentCommitId?: string;
|
|
1354
|
+
itemCount: number;
|
|
1355
|
+
items: Array<{
|
|
1356
|
+
type: string;
|
|
1357
|
+
name: string;
|
|
1358
|
+
existedBefore: boolean;
|
|
1359
|
+
prevVersion: number | null;
|
|
1360
|
+
}>;
|
|
1361
|
+
createdAt?: string;
|
|
1362
|
+
}>>;
|
|
1363
|
+
/**
|
|
1364
|
+
* Revert a single commit (ADR-0067): undo exactly the artifacts it touched.
|
|
1365
|
+
* A created-by-this-commit artifact is soft-removed (metadata row deleted;
|
|
1366
|
+
* the data table is NOT dropped — recoverable, per ADR-0067 §5); a modified
|
|
1367
|
+
* artifact is restored to its pre-commit `prevVersion`. The revert is itself
|
|
1368
|
+
* recorded as a NEW commit (operation='revert'), so history stays
|
|
1369
|
+
* append-only and the revert is itself revertible.
|
|
1370
|
+
*/
|
|
1371
|
+
revertCommit(request: {
|
|
1372
|
+
commitId: string;
|
|
1373
|
+
organizationId?: string;
|
|
1374
|
+
actor?: string;
|
|
1375
|
+
}): Promise<{
|
|
1376
|
+
success: boolean;
|
|
1377
|
+
revertedCount: number;
|
|
1378
|
+
failedCount: number;
|
|
1379
|
+
reverted: Array<{
|
|
1380
|
+
type: string;
|
|
1381
|
+
name: string;
|
|
1382
|
+
action: 'removed' | 'restored';
|
|
1383
|
+
}>;
|
|
1384
|
+
failed: Array<{
|
|
1385
|
+
type: string;
|
|
1386
|
+
name: string;
|
|
1387
|
+
error: string;
|
|
1388
|
+
code?: string;
|
|
1389
|
+
}>;
|
|
1390
|
+
revertCommitId?: string;
|
|
1391
|
+
}>;
|
|
1392
|
+
/**
|
|
1393
|
+
* Roll a package back THROUGH every `apply` commit newer than `commitId`
|
|
1394
|
+
* (newest first), leaving the package as it was at that commit. Each step is
|
|
1395
|
+
* an individual `revertCommit`, so the whole rollback is itself audited.
|
|
1396
|
+
*/
|
|
1397
|
+
rollbackToPackageCommit(request: {
|
|
1398
|
+
commitId: string;
|
|
1399
|
+
organizationId?: string;
|
|
1400
|
+
actor?: string;
|
|
1401
|
+
}): Promise<{
|
|
1402
|
+
success: boolean;
|
|
1403
|
+
revertedCommits: string[];
|
|
1404
|
+
failed: Array<{
|
|
1405
|
+
commitId: string;
|
|
1406
|
+
error: string;
|
|
1407
|
+
}>;
|
|
1408
|
+
}>;
|
|
1409
|
+
/**
|
|
1410
|
+
* Restore the body recorded at history `toVersion` as the new
|
|
1411
|
+
* live row. Writes a history event with `op='revert'`. 404
|
|
1412
|
+
* (`[version_not_found]`) when the target version doesn't exist;
|
|
1413
|
+
* 409 (`[version_not_restorable]`) when the target is a delete
|
|
1414
|
+
* tombstone (no body to bring back).
|
|
1415
|
+
*/
|
|
1416
|
+
rollbackMetaItem(request: {
|
|
1417
|
+
type: string;
|
|
1418
|
+
name: string;
|
|
1419
|
+
toVersion: number;
|
|
1420
|
+
organizationId?: string;
|
|
1421
|
+
actor?: string;
|
|
1422
|
+
message?: string;
|
|
1423
|
+
}): Promise<{
|
|
1424
|
+
success: boolean;
|
|
1425
|
+
version: string;
|
|
1426
|
+
seq: number;
|
|
1427
|
+
restoredFromVersion: number;
|
|
1428
|
+
message?: string;
|
|
1429
|
+
}>;
|
|
1430
|
+
/**
|
|
1431
|
+
* Compute a shallow structural diff between two historical
|
|
1432
|
+
* versions of a metadata item. Either side may be omitted: when
|
|
1433
|
+
* `toVersion` is undefined the current active body is used; when
|
|
1434
|
+
* `fromVersion` is undefined the immediately previous history row
|
|
1435
|
+
* is used. Returns `{ added, removed, changed }` keyed by JSON
|
|
1436
|
+
* pointer-style paths for primitive leaves; nested objects/arrays
|
|
1437
|
+
* are reported as a single change record.
|
|
1438
|
+
*/
|
|
1439
|
+
diffMetaItem(request: {
|
|
1440
|
+
type: string;
|
|
1441
|
+
name: string;
|
|
1442
|
+
fromVersion?: number;
|
|
1443
|
+
toVersion?: number;
|
|
1444
|
+
organizationId?: string;
|
|
1445
|
+
}): Promise<{
|
|
1446
|
+
type: string;
|
|
1447
|
+
name: string;
|
|
1448
|
+
fromVersion: number | null;
|
|
1449
|
+
toVersion: number | null;
|
|
1450
|
+
added: Array<{
|
|
1451
|
+
path: string;
|
|
1452
|
+
value: unknown;
|
|
1453
|
+
}>;
|
|
1454
|
+
removed: Array<{
|
|
1455
|
+
path: string;
|
|
1456
|
+
value: unknown;
|
|
1457
|
+
}>;
|
|
1458
|
+
changed: Array<{
|
|
1459
|
+
path: string;
|
|
1460
|
+
from: unknown;
|
|
1461
|
+
to: unknown;
|
|
1462
|
+
}>;
|
|
1463
|
+
}>;
|
|
1464
|
+
/**
|
|
1465
|
+
* Remove a customization overlay row for the given metadata item, so the
|
|
1466
|
+
* next read falls through to the artifact-loaded default. Implements the
|
|
1467
|
+
* "Reset to factory default" semantic from ADR-0005. Whitelist is shared
|
|
1468
|
+
* with {@link saveMetaItem}.
|
|
1469
|
+
*/
|
|
1470
|
+
deleteMetaItem(request: {
|
|
1471
|
+
type: string;
|
|
1472
|
+
name: string;
|
|
1473
|
+
organizationId?: string;
|
|
1474
|
+
parentVersion?: string | null;
|
|
1475
|
+
actor?: string;
|
|
1476
|
+
state?: 'active' | 'draft';
|
|
1477
|
+
/**
|
|
1478
|
+
* When true, also drop the object's physical table after the metadata
|
|
1479
|
+
* is removed (object + active only; never `sys_`). Default false keeps
|
|
1480
|
+
* delete non-destructive to data. Used by the "discard a previewed
|
|
1481
|
+
* object" flow so a publish-to-preview leaves no orphan table.
|
|
1482
|
+
*/
|
|
1483
|
+
dropStorage?: boolean;
|
|
1484
|
+
}): Promise<{
|
|
1485
|
+
success: boolean;
|
|
1486
|
+
message?: string;
|
|
1487
|
+
reset?: boolean;
|
|
1488
|
+
seq?: number;
|
|
1489
|
+
}>;
|
|
1490
|
+
/**
|
|
1491
|
+
* Hydrate SchemaRegistry from the database on startup.
|
|
1492
|
+
* Loads all active metadata records and registers them in the in-memory registry.
|
|
1493
|
+
* Safe to call repeatedly — idempotent (latest DB record wins).
|
|
1494
|
+
*
|
|
1495
|
+
* Per ADR-0005, project-kernel mode ALSO hydrates from sys_metadata —
|
|
1496
|
+
* customization overlay rows must survive restart. Scope filter
|
|
1497
|
+
* (`environment_id = this.environmentId ?? null`) keeps tenants isolated.
|
|
1498
|
+
*/
|
|
1499
|
+
loadMetaFromDb(): Promise<{
|
|
1500
|
+
loaded: number;
|
|
1501
|
+
errors: number;
|
|
1502
|
+
}>;
|
|
1503
|
+
/**
|
|
1504
|
+
* Scan all loaded metadata for references pointing at the given
|
|
1505
|
+
* `{type, name}` target. Returns one row per referring artifact with
|
|
1506
|
+
* the path that produced the hit, so the admin UI can render an
|
|
1507
|
+
* "Used by" panel before destructive actions (rename / delete /
|
|
1508
|
+
* type-narrowing).
|
|
1509
|
+
*
|
|
1510
|
+
* Coverage is driven by the hand-curated {@link REFERENCE_PATHS}
|
|
1511
|
+
* registry. Types not present in the registry simply return no hits
|
|
1512
|
+
* — the engine never throws.
|
|
1513
|
+
*/
|
|
1514
|
+
findReferencesToMeta(request: {
|
|
1515
|
+
type: string;
|
|
1516
|
+
name: string;
|
|
1517
|
+
organizationId?: string;
|
|
1518
|
+
}): Promise<{
|
|
1519
|
+
references: Array<{
|
|
1520
|
+
type: string;
|
|
1521
|
+
name: string;
|
|
1522
|
+
label?: string;
|
|
1523
|
+
path: string;
|
|
1524
|
+
kind: string;
|
|
1525
|
+
}>;
|
|
1526
|
+
}>;
|
|
1527
|
+
listFeed(request: any): Promise<any>;
|
|
1528
|
+
createFeedItem(request: any): Promise<any>;
|
|
1529
|
+
updateFeedItem(request: any): Promise<any>;
|
|
1530
|
+
deleteFeedItem(request: any): Promise<any>;
|
|
1531
|
+
addReaction(request: any): Promise<any>;
|
|
1532
|
+
removeReaction(request: any): Promise<any>;
|
|
1533
|
+
pinFeedItem(request: any): Promise<any>;
|
|
1534
|
+
unpinFeedItem(request: any): Promise<any>;
|
|
1535
|
+
starFeedItem(request: any): Promise<any>;
|
|
1536
|
+
unstarFeedItem(request: any): Promise<any>;
|
|
1537
|
+
searchFeed(request: any): Promise<any>;
|
|
1538
|
+
getChangelog(request: any): Promise<any>;
|
|
1539
|
+
feedSubscribe(request: any): Promise<any>;
|
|
1540
|
+
feedUnsubscribe(request: any): Promise<any>;
|
|
1541
|
+
/**
|
|
1542
|
+
* Install a package from a manifest — the single canonical write primitive
|
|
1543
|
+
* for the package subsystem (ADR-0033 consolidation).
|
|
1544
|
+
*
|
|
1545
|
+
* It writes BOTH stores that the runtime keeps for packages, so a package
|
|
1546
|
+
* surfaces consistently no matter which read path is used:
|
|
1547
|
+
* 1. the in-memory `SchemaRegistry` (what the dispatcher's
|
|
1548
|
+
* `/api/v1/packages` list/detail and `getMetaItems({type:'package'})`
|
|
1549
|
+
* read — i.e. what Studio's package selector shows), and
|
|
1550
|
+
* 2. the durable `sys_packages` table via the optional `package` service
|
|
1551
|
+
* (so the package survives a restart; that service re-hydrates these
|
|
1552
|
+
* rows back into the registry on boot).
|
|
1553
|
+
*
|
|
1554
|
+
* The DB write is best-effort and non-fatal: when the `package` service is
|
|
1555
|
+
* absent (e.g. the `marketplace` capability is off) the package is still
|
|
1556
|
+
* registered in-memory and visible for the lifetime of the process.
|
|
1557
|
+
*/
|
|
1558
|
+
installPackage(request: InstallPackageRequest): Promise<InstallPackageResponse>;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/**
|
|
1562
|
+
* Overlay-row lifecycle state.
|
|
1563
|
+
*
|
|
1564
|
+
* - `'active'` → the published, live overlay. `getMetaItem` (the
|
|
1565
|
+
* default read path) and runtime loaders observe this row.
|
|
1566
|
+
* - `'draft'` → an unpublished pending change. Lives alongside the
|
|
1567
|
+
* active row (one of each per `(org,type,name)`). Promoted to
|
|
1568
|
+
* `active` via {@link SysMetadataRepository.promoteDraft}.
|
|
1569
|
+
*
|
|
1570
|
+
* Other lifecycle values defined on `sys_metadata.state` (`'archived'`,
|
|
1571
|
+
* `'deprecated'`) are not yet plumbed through the overlay write path;
|
|
1572
|
+
* they remain reserved for future flows (item retirement, freeze).
|
|
1573
|
+
*/
|
|
1574
|
+
type OverlayState = 'active' | 'draft';
|
|
1575
|
+
/**
|
|
1576
|
+
* Extended history operation tag. The base `'create' | 'update' |
|
|
1577
|
+
* 'delete'` operations are emitted by the canonical put/delete paths.
|
|
1578
|
+
* `'publish'` is recorded when a draft is promoted, `'revert'` when a
|
|
1579
|
+
* historical version is restored. Both are surfaced as MetadataEvent
|
|
1580
|
+
* `.op` values via `history()`.
|
|
1581
|
+
*/
|
|
1582
|
+
type ExtendedOperation = 'create' | 'update' | 'publish' | 'revert' | 'delete';
|
|
1583
|
+
/**
|
|
1584
|
+
* Sub-set of the ObjectQL engine shape we depend on. Kept narrow so
|
|
1585
|
+
* tests can stub it with a plain mock. Mirrors the real engine's
|
|
1586
|
+
* `options.context` pattern so transactions can thread through.
|
|
1587
|
+
*/
|
|
1588
|
+
interface SysMetadataEngine {
|
|
1589
|
+
find(table: string, options: {
|
|
1590
|
+
where: Record<string, unknown>;
|
|
1591
|
+
limit?: number;
|
|
1592
|
+
orderBy?: any;
|
|
1593
|
+
context?: any;
|
|
1594
|
+
}): Promise<any[]>;
|
|
1595
|
+
findOne(table: string, options: {
|
|
1596
|
+
where: Record<string, unknown>;
|
|
1597
|
+
context?: any;
|
|
1598
|
+
}): Promise<any | null>;
|
|
1599
|
+
insert(table: string, data: Record<string, unknown>, options?: {
|
|
1600
|
+
context?: any;
|
|
1601
|
+
}): Promise<{
|
|
1602
|
+
id: string;
|
|
1603
|
+
}>;
|
|
1604
|
+
update(table: string, data: Record<string, unknown>, options: {
|
|
1605
|
+
where: Record<string, unknown>;
|
|
1606
|
+
context?: any;
|
|
1607
|
+
}): Promise<{
|
|
1608
|
+
id: string;
|
|
1609
|
+
}>;
|
|
1610
|
+
delete(table: string, options: {
|
|
1611
|
+
where: Record<string, unknown>;
|
|
1612
|
+
context?: any;
|
|
1613
|
+
}): Promise<{
|
|
1614
|
+
deleted: number;
|
|
1615
|
+
}>;
|
|
1616
|
+
/**
|
|
1617
|
+
* Optional. Falls through to direct callback invocation if the
|
|
1618
|
+
* underlying driver lacks ACID support (matches the real
|
|
1619
|
+
* `ObjectQL.transaction` semantics). Repository code must not rely on
|
|
1620
|
+
* rollback for correctness against in-memory drivers.
|
|
1621
|
+
*/
|
|
1622
|
+
transaction?<T>(callback: (trxCtx: any) => Promise<T>, baseContext?: any): Promise<T>;
|
|
1623
|
+
}
|
|
1624
|
+
interface SysMetadataRepositoryOptions {
|
|
1625
|
+
engine: SysMetadataEngine;
|
|
1626
|
+
/**
|
|
1627
|
+
* Tenancy scope. `null` writes to env-wide overlay rows; a string
|
|
1628
|
+
* scopes to one organization (the supported shared-DB tenant model
|
|
1629
|
+
* — see ADR-0005 amendment).
|
|
1630
|
+
*/
|
|
1631
|
+
organizationId?: string | null;
|
|
1632
|
+
/** Org label embedded in returned MetaRefs. Defaults to organizationId or `"system"`. */
|
|
1633
|
+
orgLabel?: string;
|
|
1634
|
+
}
|
|
1635
|
+
/** Test hook — clear the memoised env-writable cache. */
|
|
1636
|
+
declare function resetEnvWritableMetadataTypes(): void;
|
|
1637
|
+
declare class SysMetadataRepository implements MetadataRepository {
|
|
1638
|
+
private readonly engine;
|
|
1639
|
+
private readonly organizationId;
|
|
1640
|
+
private readonly orgLabel;
|
|
1641
|
+
/**
|
|
1642
|
+
* Local seq counter for in-memory watch() event broadcasts. Mirrors
|
|
1643
|
+
* the durable `event_seq` we write into `sys_metadata_history` on
|
|
1644
|
+
* each successful put/delete — assigned AFTER the transaction commits
|
|
1645
|
+
* so we never broadcast events that got rolled back.
|
|
1646
|
+
*/
|
|
1647
|
+
private seqCounter;
|
|
1648
|
+
private readonly watchers;
|
|
1649
|
+
private closed;
|
|
1650
|
+
/** Table name for the durable event log. */
|
|
1651
|
+
private readonly historyTable;
|
|
1652
|
+
constructor(opts: SysMetadataRepositoryOptions);
|
|
1653
|
+
/**
|
|
1654
|
+
* Run `cb` inside `engine.transaction(...)` if the engine supports it,
|
|
1655
|
+
* otherwise fall through to a direct call. Matches the real
|
|
1656
|
+
* `ObjectQL.transaction` semantics — in-memory drivers (and our test
|
|
1657
|
+
* fakes) get no rollback, which is acceptable because production
|
|
1658
|
+
* always runs on a SQL driver with real ACID.
|
|
1659
|
+
*/
|
|
1660
|
+
private withTxn;
|
|
1661
|
+
/**
|
|
1662
|
+
* Read the current overlay row. Returns null if no row exists —
|
|
1663
|
+
* callers (e.g. LayeredRepository) fall through to lower layers.
|
|
1664
|
+
*
|
|
1665
|
+
* `opts.state` selects which lifecycle row to read: defaults to the
|
|
1666
|
+
* live published row (`'active'`). Pass `'draft'` to read the pending
|
|
1667
|
+
* unpublished revision (if any).
|
|
1668
|
+
*/
|
|
1669
|
+
get(ref: MetaRef, opts?: {
|
|
1670
|
+
state?: OverlayState;
|
|
1671
|
+
packageId?: string | null;
|
|
1672
|
+
}): Promise<MetadataItem | null>;
|
|
1673
|
+
/**
|
|
1674
|
+
* Resolve a historical version by content hash (ADR-0009).
|
|
1675
|
+
*
|
|
1676
|
+
* Looks up `sys_metadata_history` by `(organization_id, type, name,
|
|
1677
|
+
* checksum)`. Returns null if no row matches. `executionPinned` types
|
|
1678
|
+
* are guaranteed to find their body here because history GC skips
|
|
1679
|
+
* them.
|
|
1680
|
+
*/
|
|
1681
|
+
getByHash(ref: MetaRef, hash: string): Promise<MetadataItem | null>;
|
|
1682
|
+
put(ref: MetaRef, spec: unknown, opts: PutOptions & {
|
|
1683
|
+
state?: OverlayState;
|
|
1684
|
+
opType?: ExtendedOperation;
|
|
1685
|
+
}): Promise<PutResult>;
|
|
1686
|
+
delete(ref: MetaRef, opts: DeleteOptions & {
|
|
1687
|
+
state?: OverlayState;
|
|
1688
|
+
}): Promise<DeleteResult>;
|
|
1689
|
+
/**
|
|
1690
|
+
* Promote the pending draft row for `ref` into the live (`active`)
|
|
1691
|
+
* overlay. Atomic: reads the draft inside the same transaction, runs
|
|
1692
|
+
* the canonical `put` to upsert the active row (which appends a
|
|
1693
|
+
* history event with `operation_type='publish'`), then deletes the
|
|
1694
|
+
* draft row.
|
|
1695
|
+
*
|
|
1696
|
+
* Errors if no draft exists (callers should 404). The active row's
|
|
1697
|
+
* `parentVersion` is computed from the current active hash so this
|
|
1698
|
+
* also surfaces optimistic-lock conflicts when something else has
|
|
1699
|
+
* published in between (e.g. another admin reverted to an older
|
|
1700
|
+
* version since the draft was authored).
|
|
1701
|
+
*/
|
|
1702
|
+
promoteDraft(ref: MetaRef, opts: {
|
|
1703
|
+
actor: string;
|
|
1704
|
+
source?: string;
|
|
1705
|
+
message?: string;
|
|
1706
|
+
intent?: MetadataWriteIntent;
|
|
1707
|
+
}): Promise<{
|
|
1708
|
+
version: string;
|
|
1709
|
+
seq: number;
|
|
1710
|
+
item: MetadataItem;
|
|
1711
|
+
}>;
|
|
1712
|
+
/**
|
|
1713
|
+
* Restore the body recorded in history at `targetVersion` (per-org
|
|
1714
|
+
* lineage counter) as the new active row. Writes a history event
|
|
1715
|
+
* with `operation_type='revert'` so the audit trail captures the
|
|
1716
|
+
* intent. Does NOT touch any draft row.
|
|
1717
|
+
*
|
|
1718
|
+
* Throws `[version_not_found]` (404) if the target version row is
|
|
1719
|
+
* missing or is a delete tombstone (no body to restore).
|
|
1720
|
+
*/
|
|
1721
|
+
restoreVersion(ref: MetaRef, targetVersion: number, opts: {
|
|
1722
|
+
actor: string;
|
|
1723
|
+
source?: string;
|
|
1724
|
+
message?: string;
|
|
1725
|
+
intent?: MetadataWriteIntent;
|
|
1726
|
+
}): Promise<{
|
|
1727
|
+
version: string;
|
|
1728
|
+
seq: number;
|
|
1729
|
+
item: MetadataItem;
|
|
1730
|
+
}>;
|
|
1731
|
+
list(filter: ListFilter): AsyncIterable<MetadataItemHeader>;
|
|
1732
|
+
/**
|
|
1733
|
+
* List pending DRAFT rows (ADR-0033) for this org, optionally narrowed by
|
|
1734
|
+
* `type` and/or `packageId`. Unlike {@link list} (which is hard-scoped to
|
|
1735
|
+
* `state='active'`), this reads `state='draft'` so the console can surface
|
|
1736
|
+
* what an AI authored but a human hasn't published yet. Returns a light
|
|
1737
|
+
* header projection (no body) suitable for a "pending changes" list.
|
|
1738
|
+
*/
|
|
1739
|
+
listDrafts(filter?: {
|
|
1740
|
+
type?: string;
|
|
1741
|
+
packageId?: string;
|
|
1742
|
+
}): Promise<Array<{
|
|
1743
|
+
type: string;
|
|
1744
|
+
name: string;
|
|
1745
|
+
packageId: string | null;
|
|
1746
|
+
updatedAt: string | null;
|
|
1747
|
+
updatedBy: string | null;
|
|
1748
|
+
}>>;
|
|
1749
|
+
/**
|
|
1750
|
+
* Yield every history event for `(org, type?, name?)` from the
|
|
1751
|
+
* durable log, ordered by per-(type,name) `version` ascending. When
|
|
1752
|
+
* `filter.type`/`filter.name` are unset the consumer gets the full
|
|
1753
|
+
* org-scoped event stream — still ordered by version within each
|
|
1754
|
+
* (type,name) bucket, then by `recorded_at` across buckets (we sort
|
|
1755
|
+
* client-side because the test engine doesn't honor `orderBy`).
|
|
1756
|
+
*/
|
|
1757
|
+
history(ref: MetaRef, opts?: HistoryOptions): AsyncIterable<MetadataEvent>;
|
|
1758
|
+
/**
|
|
1759
|
+
* Live event stream. Fires for every successful put/delete on THIS
|
|
1760
|
+
* instance — cross-replica fan-out is M1. Manual AsyncIterator (not
|
|
1761
|
+
* an async generator) so we can deterministically tear down via
|
|
1762
|
+
* `iter.return()`, matching the pattern used by InMemoryRepository.
|
|
1763
|
+
*/
|
|
1764
|
+
watch(filter: WatchFilter, since?: number): AsyncIterable<MetadataEvent>;
|
|
1765
|
+
/** Shut down all watch iterators. */
|
|
1766
|
+
close(): void;
|
|
1767
|
+
private assertOpen;
|
|
1768
|
+
/**
|
|
1769
|
+
* Defense-in-depth authorization gate.
|
|
1770
|
+
*
|
|
1771
|
+
* `intent` defaults to `'override-artifact'` (the historical strict
|
|
1772
|
+
* behavior). The protocol layer passes `'runtime-only'` after it has
|
|
1773
|
+
* verified — via the schema registry — that no artifact item exists
|
|
1774
|
+
* at `(type, name)`. In that case we accept types with
|
|
1775
|
+
* `allowRuntimeCreate: true`, even when `allowOrgOverride` is false.
|
|
1776
|
+
*
|
|
1777
|
+
* The env-var escape hatch (`OS_METADATA_WRITABLE`) still
|
|
1778
|
+
* applies to BOTH intents, so operators can opt into artifact
|
|
1779
|
+
* overrides at runtime for emergency fixes.
|
|
1780
|
+
*/
|
|
1781
|
+
private assertAllowed;
|
|
1782
|
+
private whereFor;
|
|
1783
|
+
private fullRef;
|
|
1784
|
+
private rowToItem;
|
|
1785
|
+
private broadcast;
|
|
1786
|
+
private matchesFilter;
|
|
1787
|
+
/**
|
|
1788
|
+
* Per-org monotonic event sequence. Reads `MAX(event_seq) + 1` from
|
|
1789
|
+
* `sys_metadata_history` scoped by `organization_id`. MUST be called
|
|
1790
|
+
* inside a transaction (the only caller is the put/delete txn body) —
|
|
1791
|
+
* concurrent writers in the same org race otherwise.
|
|
1792
|
+
*/
|
|
1793
|
+
private nextEventSeq;
|
|
1794
|
+
/**
|
|
1795
|
+
* Per-(org,type,name) lineage counter. Reads from history (not from
|
|
1796
|
+
* `sys_metadata.version`) so delete + recreate continues incrementing
|
|
1797
|
+
* instead of restarting at 1.
|
|
1798
|
+
*/
|
|
1799
|
+
private nextItemVersion;
|
|
1800
|
+
/** Lightweight UUID-ish id for history rows; sufficient for an audit log. */
|
|
1801
|
+
private uuid;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* The engine surface the metadata protocol needs from its host (ADR-0076).
|
|
1806
|
+
*
|
|
1807
|
+
* The protocol uses the data engine purely as storage + a schema registry; it
|
|
1808
|
+
* is injected a concrete engine at runtime (today `@objectstack/objectql`'s
|
|
1809
|
+
* `ObjectQL`). Typing against this interface — instead of the concrete class —
|
|
1810
|
+
* is what lets `@objectstack/metadata-protocol` avoid a dependency on
|
|
1811
|
+
* `@objectstack/objectql` (which would otherwise form a cycle, since the
|
|
1812
|
+
* ObjectQL plugin constructs the protocol).
|
|
1813
|
+
*/
|
|
1814
|
+
interface MetadataHostEngine extends IDataEngine {
|
|
1815
|
+
/** Schema registry (listItems/getItem/registerItem/getObject/registerObject/installPackage/...). */
|
|
1816
|
+
registry: any;
|
|
1817
|
+
/** DDL: create/sync the physical table for an object schema. */
|
|
1818
|
+
syncObjectSchema(...args: any[]): Promise<any>;
|
|
1819
|
+
/** DDL: drop the physical table for an object schema. */
|
|
1820
|
+
dropObjectSchema(...args: any[]): Promise<any>;
|
|
1821
|
+
[key: string]: any;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
interface Logger {
|
|
1825
|
+
info(message: string, meta?: Record<string, any>): void;
|
|
1826
|
+
warn(message: string, meta?: Record<string, any>): void;
|
|
1827
|
+
error(message: string, error?: Error, meta?: Record<string, any>): void;
|
|
1828
|
+
debug(message: string, meta?: Record<string, any>): void;
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* SeedLoaderService — Runtime implementation of ISeedLoaderService
|
|
1832
|
+
*
|
|
1833
|
+
* Provides metadata-driven seed data loading with:
|
|
1834
|
+
* - Automatic lookup/master_detail reference resolution via externalId
|
|
1835
|
+
* - Topological dependency ordering (parents before children)
|
|
1836
|
+
* - Multi-pass loading for circular references
|
|
1837
|
+
* - Dry-run validation mode
|
|
1838
|
+
* - Upsert support honoring SeedSchema mode
|
|
1839
|
+
* - Actionable error reporting
|
|
1840
|
+
*/
|
|
1841
|
+
declare class SeedLoaderService implements ISeedLoaderService {
|
|
1842
|
+
private engine;
|
|
1843
|
+
private metadata;
|
|
1844
|
+
private logger;
|
|
1845
|
+
/**
|
|
1846
|
+
* Tenant org to stamp BUSINESS seed rows with when the caller pinned no
|
|
1847
|
+
* explicit `config.organizationId` (resolved per {@link resolveSoleOrganizationId}).
|
|
1848
|
+
* Set once per {@link load}; never applied to `sys_`/`cloud_`/`ai_` platform
|
|
1849
|
+
* seeds (those stay intentionally global/cross-tenant).
|
|
1850
|
+
*/
|
|
1851
|
+
private fallbackOrgId?;
|
|
1852
|
+
constructor(engine: IDataEngine$1, metadata: IMetadataService, logger: Logger);
|
|
1853
|
+
load(request: SeedLoaderRequest): Promise<SeedLoaderResult>;
|
|
1854
|
+
buildDependencyGraph(objectNames: string[]): Promise<ObjectDependencyGraph>;
|
|
1855
|
+
validate(datasets: Seed[], config?: SeedLoaderConfigInput): Promise<SeedLoaderResult>;
|
|
1856
|
+
private loadDataset;
|
|
1857
|
+
/**
|
|
1858
|
+
* Best-effort resolve the tenant's SOLE organization id — used to stamp
|
|
1859
|
+
* business seed rows when the caller pinned no `config.organizationId` (an
|
|
1860
|
+
* in-process publish has no active user session). A fresh env has exactly one
|
|
1861
|
+
* org, so its seeds should carry it like a normal write instead of landing
|
|
1862
|
+
* org-less (→ invisible under strict org-scoping). Returns undefined when
|
|
1863
|
+
* there are zero or several orgs (genuinely ambiguous — keep the historical
|
|
1864
|
+
* global/cross-tenant NULL) or when `sys_organization` is absent.
|
|
1865
|
+
*/
|
|
1866
|
+
private resolveSoleOrganizationId;
|
|
1867
|
+
private resolveFromDatabase;
|
|
1868
|
+
private resolveDeferredUpdates;
|
|
1869
|
+
/**
|
|
1870
|
+
* Seed writes always run as a privileged system context. This bypasses
|
|
1871
|
+
* RBAC checks (so seeds can target system tables like `sys_*`) and
|
|
1872
|
+
* disables the SecurityPlugin's auto-injection of `organization_id` /
|
|
1873
|
+
* `owner_id` — seeds either declare those fields explicitly per
|
|
1874
|
+
* record, or are intentionally cross-tenant / global.
|
|
1875
|
+
*/
|
|
1876
|
+
private static readonly SEED_OPTIONS;
|
|
1877
|
+
private writeRecord;
|
|
1878
|
+
/**
|
|
1879
|
+
* Kahn's algorithm for topological sort with cycle detection.
|
|
1880
|
+
*/
|
|
1881
|
+
private topologicalSort;
|
|
1882
|
+
private findCycles;
|
|
1883
|
+
private filterByEnv;
|
|
1884
|
+
private orderDatasets;
|
|
1885
|
+
private buildReferenceMap;
|
|
1886
|
+
private loadExistingRecords;
|
|
1887
|
+
private looksLikeInternalId;
|
|
1888
|
+
private extractId;
|
|
1889
|
+
private buildEmptyResult;
|
|
1890
|
+
private buildResult;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
export { type BuildProbeReport, ConcurrentUpdateError, type ExtendedOperation, type MetadataDiagnostics, type MetadataHostEngine, ObjectStackProtocolImplementation, type OverlayState, type ProbeAnalytics, type ProbeEngine, type RunBuildProbesOptions, type RuntimeBuildIssue, SeedLoaderService, type SysMetadataEngine, SysMetadataRepository, type SysMetadataRepositoryOptions, computeMetadataDiagnostics, computeViewReferenceDiagnostics, decorateMetadataItem, decorateMetadataItems, normalizeViewMetadata, resetEnvWritableMetadataTypes, runBuildProbes };
|