@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.
@@ -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 };