@objectstack/objectql 9.2.0 → 9.4.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/dist/index.mjs CHANGED
@@ -310,6 +310,19 @@ var init_seed_loader = __esm({
310
310
  if (records && records.length > 0) {
311
311
  return String(records[0].id || records[0]._id);
312
312
  }
313
+ if (targetField !== "id") {
314
+ const byId = { id: value };
315
+ if (organizationId) byId.organization_id = organizationId;
316
+ const idMatch = await this.engine.find(targetObject, {
317
+ where: byId,
318
+ fields: ["id"],
319
+ limit: 1,
320
+ context: { isSystem: true }
321
+ });
322
+ if (idMatch && idMatch.length > 0) {
323
+ return String(idMatch[0].id || idMatch[0]._id);
324
+ }
325
+ }
313
326
  } catch {
314
327
  }
315
328
  return null;
@@ -886,6 +899,20 @@ function applySystemFields(schema, opts) {
886
899
  fields: { ...additions, ...schema.fields ?? {} }
887
900
  };
888
901
  }
902
+ function isShareableNamespace(ns) {
903
+ return RESERVED_NAMESPACES.has(ns) || ns === "sys";
904
+ }
905
+ var NamespaceConflictError = class extends Error {
906
+ constructor(namespace, existingPackageId, incomingPackageId) {
907
+ super(
908
+ `Namespace conflict: namespace "${namespace}" is already owned by package "${existingPackageId}", so package "${incomingPackageId}" cannot be installed alongside it. A namespace is the mandatory prefix of every object name (e.g. "${namespace}_account") and the container that scopes a package's UI metadata, so it must be unique per installation. Choose a different namespace for "${incomingPackageId}", or uninstall "${existingPackageId}" first. If this is a deliberate migration, set OS_METADATA_COLLISION=warn to downgrade to a warning. See ADR-0048.`
909
+ );
910
+ this.name = "NamespaceConflictError";
911
+ this.namespace = namespace;
912
+ this.existingPackageId = existingPackageId;
913
+ this.incomingPackageId = incomingPackageId;
914
+ }
915
+ };
889
916
  var SchemaRegistry = class {
890
917
  constructor(options = {}) {
891
918
  // ==========================================
@@ -929,6 +956,7 @@ var SchemaRegistry = class {
929
956
  } else {
930
957
  this.multiTenant = String(readEnvWithDeprecation("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
931
958
  }
959
+ this.collisionPolicy = options.collisionPolicy ?? ((process.env.OS_METADATA_COLLISION ?? "").toLowerCase() === "warn" ? "warn" : "error");
932
960
  }
933
961
  get logLevel() {
934
962
  return this._logLevel;
@@ -1289,9 +1317,23 @@ var SchemaRegistry = class {
1289
1317
  console.warn(`[Registry] Attempted to unregister non-existent ${type}: ${name}`);
1290
1318
  }
1291
1319
  /**
1292
- * Universal Get Method
1320
+ * Universal Get Method.
1321
+ *
1322
+ * ADR-0048 §3.3 — *package-scoped* resolution. When `currentPackageId` is
1323
+ * given (the package the caller is resolving within — known from the route /
1324
+ * `activeApp._packageId`), a bare name resolves to *that package's* item
1325
+ * before any cross-package fallback, so two packages shipping e.g.
1326
+ * `page/home` no longer resolve by registration order (first-match-wins).
1327
+ * Because package ids are globally unique this is unambiguous. Omitting
1328
+ * `currentPackageId` preserves the legacy resolution exactly (backward
1329
+ * compatible) and is best-effort: it returns the first match.
1330
+ *
1331
+ * Precedence (highest first):
1332
+ * 1. bare-key runtime/DB overlay (ADR-0005 sanctioned override) — unchanged
1333
+ * 2. the `currentPackageId` composite entry (prefer-local)
1334
+ * 3. first composite match (legacy first-registered-wins fallback)
1293
1335
  */
1294
- getItem(type, name) {
1336
+ getItem(type, name, currentPackageId) {
1295
1337
  if (type === "object" || type === "objects") {
1296
1338
  return this.getObject(name);
1297
1339
  }
@@ -1299,6 +1341,10 @@ var SchemaRegistry = class {
1299
1341
  if (!collection) return void 0;
1300
1342
  const direct = collection.get(name);
1301
1343
  if (direct) return direct;
1344
+ if (currentPackageId) {
1345
+ const local = collection.get(`${currentPackageId}:${name}`);
1346
+ if (local) return local;
1347
+ }
1302
1348
  for (const [key, item] of collection) {
1303
1349
  if (key.endsWith(`:${name}`)) {
1304
1350
  return item;
@@ -1306,6 +1352,74 @@ var SchemaRegistry = class {
1306
1352
  }
1307
1353
  return void 0;
1308
1354
  }
1355
+ /**
1356
+ * Artifact-only lookup (ADR-0010 §3.3). Unlike {@link getItem} — which
1357
+ * returns the plain-key entry first, so a runtime/DB-rehydrated row
1358
+ * registered under the bare name SHADOWS the packaged artifact — this
1359
+ * scans the composite (`<packageId>:<name>`) entries first and only
1360
+ * returns an item whose `_packageId` marks a genuine code package
1361
+ * (truthy and not the `'sys_metadata'` rehydration sentinel).
1362
+ *
1363
+ * This is what the protocol's lock/provenance resolution must use:
1364
+ * the artifact's `_lock` envelope always wins over an overlay, and an
1365
+ * overlay row hydrated into the plain key must never be able to mask
1366
+ * it (that masking is exactly the "registry pollution" bug where a
1367
+ * locked app's `_lock` read back as undefined after a PUT+GET).
1368
+ */
1369
+ getArtifactItem(type, name, currentPackageId) {
1370
+ if (type === "object" || type === "objects") {
1371
+ const obj = this.getObject(name);
1372
+ return obj && obj._packageId && obj._packageId !== "sys_metadata" ? obj : void 0;
1373
+ }
1374
+ const collection = this.metadata.get(type);
1375
+ if (!collection) return void 0;
1376
+ if (currentPackageId) {
1377
+ const local = collection.get(`${currentPackageId}:${name}`);
1378
+ if (local && local._packageId && local._packageId !== "sys_metadata") return local;
1379
+ }
1380
+ for (const [key, item] of collection) {
1381
+ if (key !== name && key.endsWith(`:${name}`)) {
1382
+ const it = item;
1383
+ if (it && it._packageId && it._packageId !== "sys_metadata") return item;
1384
+ }
1385
+ }
1386
+ const direct = collection.get(name);
1387
+ if (direct && direct._packageId && direct._packageId !== "sys_metadata") {
1388
+ return direct;
1389
+ }
1390
+ return void 0;
1391
+ }
1392
+ /**
1393
+ * Remove a plain-key runtime shadow so the packaged artifact registered
1394
+ * under a composite key becomes the visible value again. Used by the
1395
+ * metadata reset path (`deleteMetaItem`): deleting the `sys_metadata`
1396
+ * overlay row must also heal the in-memory registry, otherwise the
1397
+ * stale overlay copy keeps shadowing the artifact until restart.
1398
+ *
1399
+ * Deliberately conservative: the plain-key entry is only deleted when a
1400
+ * packaged artifact still exists under a composite key, so the name
1401
+ * stays resolvable afterwards. A runtime-only item (no artifact
1402
+ * backing) is left untouched. Note the plain entry's own `_packageId`
1403
+ * is NOT consulted — the hydration path grafts the artifact envelope
1404
+ * onto the shadow (ADR-0010 §3.3), so a stamped `_packageId` does not
1405
+ * mean the plain entry IS the artifact registration; artifact loaders
1406
+ * always register under a composite key.
1407
+ */
1408
+ removeRuntimeShadow(type, name) {
1409
+ const collection = this.metadata.get(type);
1410
+ if (!collection || !collection.has(name)) return false;
1411
+ for (const [key, item] of collection) {
1412
+ if (key !== name && key.endsWith(`:${name}`)) {
1413
+ const it = item;
1414
+ if (it && it._packageId && it._packageId !== "sys_metadata") {
1415
+ collection.delete(name);
1416
+ this.log(`[Registry] Removed runtime shadow ${type}: ${name} (artifact ${it._packageId} restored)`);
1417
+ return true;
1418
+ }
1419
+ }
1420
+ }
1421
+ return false;
1422
+ }
1309
1423
  /**
1310
1424
  * Universal List Method
1311
1425
  */
@@ -1346,6 +1460,20 @@ var SchemaRegistry = class {
1346
1460
  // Package Management
1347
1461
  // ==========================================
1348
1462
  installPackage(manifest, settings) {
1463
+ if (manifest.namespace && !isShareableNamespace(manifest.namespace)) {
1464
+ const conflictOwner = this.getNamespaceOwners(manifest.namespace).find(
1465
+ (owner) => owner !== manifest.id
1466
+ );
1467
+ if (conflictOwner) {
1468
+ if (this.collisionPolicy === "warn") {
1469
+ console.warn(
1470
+ `[Registry] Namespace conflict (downgraded to warning via OS_METADATA_COLLISION=warn): namespace "${manifest.namespace}" is already owned by "${conflictOwner}"; installing "${manifest.id}" anyway. See ADR-0048.`
1471
+ );
1472
+ } else {
1473
+ throw new NamespaceConflictError(manifest.namespace, conflictOwner, manifest.id);
1474
+ }
1475
+ }
1476
+ }
1349
1477
  const now = (/* @__PURE__ */ new Date()).toISOString();
1350
1478
  const disabled = this.initialDisabledPackageIds.has(manifest.id);
1351
1479
  const pkg = {
@@ -1423,8 +1551,8 @@ var SchemaRegistry = class {
1423
1551
  registerApp(app, packageId) {
1424
1552
  this.registerItem("app", app, "name", packageId);
1425
1553
  }
1426
- getApp(name) {
1427
- const app = this.getItem("app", name);
1554
+ getApp(name, currentPackageId) {
1555
+ const app = this.getItem("app", name, currentPackageId);
1428
1556
  if (!app) return app;
1429
1557
  return this.applyNavContributions(app);
1430
1558
  }
@@ -1644,7 +1772,7 @@ var SysMetadataRepository = class {
1644
1772
  this.assertOpen();
1645
1773
  const state = opts?.state ?? "active";
1646
1774
  const row = await this.engine.findOne("sys_metadata", {
1647
- where: this.whereFor(ref, state)
1775
+ where: this.whereFor(ref, state, opts && "packageId" in opts ? opts.packageId ?? null : void 0)
1648
1776
  });
1649
1777
  if (!row) return null;
1650
1778
  return this.rowToItem(ref, row);
@@ -1693,7 +1821,7 @@ var SysMetadataRepository = class {
1693
1821
  const hash = hashSpec(body);
1694
1822
  const result = await this.withTxn(async (ctx) => {
1695
1823
  const existing = await this.engine.findOne("sys_metadata", {
1696
- where: this.whereFor(ref, state),
1824
+ where: this.whereFor(ref, state, opts.packageId ?? null),
1697
1825
  context: ctx
1698
1826
  });
1699
1827
  const existingHash = existing?.checksum ?? null;
@@ -2177,13 +2305,15 @@ var SysMetadataRepository = class {
2177
2305
  err.status = 403;
2178
2306
  throw err;
2179
2307
  }
2180
- whereFor(ref, state = "active") {
2181
- return {
2308
+ whereFor(ref, state = "active", packageId) {
2309
+ const where = {
2182
2310
  type: ref.type,
2183
2311
  name: ref.name,
2184
2312
  organization_id: this.organizationId,
2185
2313
  state
2186
2314
  };
2315
+ if (packageId !== void 0) where.package_id = packageId;
2316
+ return where;
2187
2317
  }
2188
2318
  fullRef(ref) {
2189
2319
  return {
@@ -2335,6 +2465,48 @@ function decorateMetadataItems(type, items) {
2335
2465
  if (!Array.isArray(items)) return items;
2336
2466
  return items.map((item) => decorateMetadataItem(type, item));
2337
2467
  }
2468
+ function fieldMap(objectDef) {
2469
+ const map = /* @__PURE__ */ new Map();
2470
+ const fields = objectDef?.fields;
2471
+ if (Array.isArray(fields)) {
2472
+ for (const f of fields) if (f?.name) map.set(f.name, f);
2473
+ } else if (fields && typeof fields === "object") {
2474
+ for (const [name, f] of Object.entries(fields)) map.set(name, f ?? {});
2475
+ }
2476
+ return map;
2477
+ }
2478
+ function computeViewReferenceDiagnostics(view, objectDef) {
2479
+ const fields = fieldMap(objectDef);
2480
+ const errors = [];
2481
+ const requireField = (name, path) => {
2482
+ if (typeof name !== "string" || !name) return;
2483
+ if (!fields.has(name)) {
2484
+ errors.push({
2485
+ path,
2486
+ message: `Field "${name}" does not exist on the source object`,
2487
+ code: "reference_not_found"
2488
+ });
2489
+ }
2490
+ };
2491
+ const userFilters = view?.userFilters;
2492
+ userFilters?.fields?.forEach((f, i) => requireField(f?.field, `userFilters.fields.${i}.field`));
2493
+ userFilters?.tabs?.forEach((t, i) => t?.filter?.forEach((r, j) => requireField(r?.field, `userFilters.tabs.${i}.filter.${j}.field`)));
2494
+ view?.tabs?.forEach((t, i) => t?.filter?.forEach((r, j) => requireField(r?.field, `tabs.${i}.filter.${j}.field`)));
2495
+ view?.filterableFields?.forEach((f, i) => requireField(f, `filterableFields.${i}`));
2496
+ const kanban = view?.kanban;
2497
+ if (kanban?.groupByField) {
2498
+ requireField(kanban.groupByField, "kanban.groupByField");
2499
+ const def = fields.get(kanban.groupByField);
2500
+ if (def && def.type && !["select", "multi-select", "boolean", "lookup", "master_detail"].includes(def.type)) {
2501
+ errors.push({
2502
+ path: "kanban.groupByField",
2503
+ message: `Field "${kanban.groupByField}" (type "${def.type}") cannot group a kanban \u2014 use a select-like field`,
2504
+ code: "invalid_binding"
2505
+ });
2506
+ }
2507
+ }
2508
+ return errors.length ? { valid: false, errors } : { valid: true };
2509
+ }
2338
2510
 
2339
2511
  // src/protocol.ts
2340
2512
  var TYPE_TO_FORM = METADATA_FORM_REGISTRY;
@@ -2833,8 +3005,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
2833
3005
  await exec("DROP INDEX IF EXISTS idx_sys_metadata_overlay_active");
2834
3006
  } catch {
2835
3007
  }
2836
- const partialSql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active ON sys_metadata (type, name, organization_id) WHERE state = 'active'";
2837
- const fallbackSql = "CREATE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active ON sys_metadata (type, name, organization_id)";
3008
+ const partialSql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active ON sys_metadata (type, name, organization_id, COALESCE(package_id, '')) WHERE state = 'active'";
3009
+ const fallbackSql = "CREATE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active ON sys_metadata (type, name, organization_id, package_id)";
2838
3010
  try {
2839
3011
  await exec(partialSql);
2840
3012
  } catch (err) {
@@ -2846,7 +3018,11 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
2846
3018
  }
2847
3019
  }
2848
3020
  }
2849
- const draftPartialSql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_metadata_overlay_draft ON sys_metadata (type, name, organization_id) WHERE state = 'draft'";
3021
+ try {
3022
+ await exec("DROP INDEX IF EXISTS idx_sys_metadata_overlay_draft");
3023
+ } catch {
3024
+ }
3025
+ const draftPartialSql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_metadata_overlay_draft ON sys_metadata (type, name, organization_id, COALESCE(package_id, '')) WHERE state = 'draft'";
2850
3026
  try {
2851
3027
  await exec(draftPartialSql);
2852
3028
  } catch (err) {
@@ -2854,7 +3030,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
2854
3030
  if (/partial|where clause|syntax/i.test(msg)) {
2855
3031
  try {
2856
3032
  await exec(
2857
- "CREATE INDEX IF NOT EXISTS idx_sys_metadata_overlay_draft ON sys_metadata (type, name, organization_id)"
3033
+ "CREATE INDEX IF NOT EXISTS idx_sys_metadata_overlay_draft ON sys_metadata (type, name, organization_id, package_id)"
2858
3034
  );
2859
3035
  } catch {
2860
3036
  }
@@ -3157,8 +3333,13 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3157
3333
  }
3158
3334
  byName.set(data.name, data);
3159
3335
  }
3160
- if (this.environmentId === void 0) {
3161
- this.engine.registry.registerItem(request.type, data, "name");
3336
+ if (this.environmentId === void 0 && data && typeof data === "object") {
3337
+ const artifact = this.lookupArtifactItem(request.type, data.name);
3338
+ this.engine.registry.registerItem(
3339
+ request.type,
3340
+ mergeArtifactProtection(data, artifact),
3341
+ "name"
3342
+ );
3162
3343
  }
3163
3344
  }
3164
3345
  items = Array.from(byName.values());
@@ -3250,7 +3431,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3250
3431
  items.map((it) => {
3251
3432
  const a = this.lookupArtifactItem(
3252
3433
  request.type,
3253
- it?.name
3434
+ it?.name,
3435
+ packageId ?? it?._packageId
3254
3436
  );
3255
3437
  return mergeArtifactProtection(it, a);
3256
3438
  })
@@ -3264,16 +3446,28 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3264
3446
  if (request.previewDrafts && readState !== "draft") {
3265
3447
  try {
3266
3448
  const findDraft = async (oid) => {
3267
- const rec = await this.engine.findOne("sys_metadata", {
3268
- where: { type: request.type, name: request.name, state: "draft", organization_id: oid }
3269
- });
3449
+ const lookup = async (t) => {
3450
+ const base = {
3451
+ type: t,
3452
+ name: request.name,
3453
+ state: "draft",
3454
+ organization_id: oid
3455
+ };
3456
+ if (request.packageId) {
3457
+ const scoped = await this.engine.findOne("sys_metadata", {
3458
+ where: { ...base, package_id: request.packageId }
3459
+ });
3460
+ if (scoped) return scoped;
3461
+ return await this.engine.findOne("sys_metadata", {
3462
+ where: { ...base, package_id: null }
3463
+ });
3464
+ }
3465
+ return await this.engine.findOne("sys_metadata", { where: base });
3466
+ };
3467
+ const rec = await lookup(request.type);
3270
3468
  if (rec) return rec;
3271
3469
  const alt = PLURAL_TO_SINGULAR3[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
3272
- if (alt) {
3273
- return await this.engine.findOne("sys_metadata", {
3274
- where: { type: alt, name: request.name, state: "draft", organization_id: oid }
3275
- });
3276
- }
3470
+ if (alt) return await lookup(alt);
3277
3471
  return void 0;
3278
3472
  };
3279
3473
  const draftRec = (orgId ? await findDraft(orgId) : void 0) ?? await findDraft(null);
@@ -3291,24 +3485,28 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3291
3485
  }
3292
3486
  try {
3293
3487
  const findOverlay = async (oid) => {
3294
- const where = {
3295
- type: request.type,
3296
- name: request.name,
3297
- state: readState,
3298
- organization_id: oid
3299
- };
3300
- const rec = await this.engine.findOne("sys_metadata", { where });
3301
- if (rec) return rec;
3302
- const alt = PLURAL_TO_SINGULAR3[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
3303
- if (alt) {
3304
- const altWhere = {
3305
- type: alt,
3488
+ const lookup = async (t) => {
3489
+ const base = {
3490
+ type: t,
3306
3491
  name: request.name,
3307
3492
  state: readState,
3308
3493
  organization_id: oid
3309
3494
  };
3310
- return await this.engine.findOne("sys_metadata", { where: altWhere });
3311
- }
3495
+ if (request.packageId) {
3496
+ const scoped = await this.engine.findOne("sys_metadata", {
3497
+ where: { ...base, package_id: request.packageId }
3498
+ });
3499
+ if (scoped) return scoped;
3500
+ return await this.engine.findOne("sys_metadata", {
3501
+ where: { ...base, package_id: null }
3502
+ });
3503
+ }
3504
+ return await this.engine.findOne("sys_metadata", { where: base });
3505
+ };
3506
+ const rec = await lookup(request.type);
3507
+ if (rec) return rec;
3508
+ const alt = PLURAL_TO_SINGULAR3[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
3509
+ if (alt) return await lookup(alt);
3312
3510
  return void 0;
3313
3511
  };
3314
3512
  const record = (orgId ? await findOverlay(orgId) : void 0) ?? await findOverlay(null);
@@ -3337,13 +3535,13 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3337
3535
  const services = this.getServicesRegistry?.();
3338
3536
  const metadataService = services?.get("metadata");
3339
3537
  if (metadataService && typeof metadataService.get === "function") {
3340
- const fromService = await metadataService.get(request.type, request.name);
3538
+ const fromService = await metadataService.get(request.type, request.name, request.packageId);
3341
3539
  if (fromService !== void 0 && fromService !== null) {
3342
3540
  item = fromService;
3343
3541
  } else {
3344
3542
  const alt = PLURAL_TO_SINGULAR3[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
3345
3543
  if (alt) {
3346
- const altFromService = await metadataService.get(alt, request.name);
3544
+ const altFromService = await metadataService.get(alt, request.name, request.packageId);
3347
3545
  if (altFromService !== void 0 && altFromService !== null) {
3348
3546
  item = altFromService;
3349
3547
  }
@@ -3354,20 +3552,44 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3354
3552
  }
3355
3553
  }
3356
3554
  if (item === void 0) {
3357
- item = this.engine.registry.getItem(request.type, request.name);
3555
+ item = this.engine.registry.getItem(request.type, request.name, request.packageId);
3358
3556
  if (item === void 0) {
3359
3557
  const alt = PLURAL_TO_SINGULAR3[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
3360
- if (alt) item = this.engine.registry.getItem(alt, request.name);
3558
+ if (alt) item = this.engine.registry.getItem(alt, request.name, request.packageId);
3361
3559
  }
3362
3560
  }
3363
3561
  if ((request.type === "app" || request.type === "apps") && item) {
3364
3562
  item = this.engine.registry.applyNavContributions(item);
3365
3563
  }
3366
- const artifactItem = this.lookupArtifactItem(request.type, request.name);
3367
- const decorated = decorateMetadataItem(
3564
+ const artifactItem = this.lookupArtifactItem(request.type, request.name, request.packageId);
3565
+ let decorated = decorateMetadataItem(
3368
3566
  request.type,
3369
3567
  mergeArtifactProtection(item, artifactItem)
3370
3568
  );
3569
+ if ((request.type === "view" || request.type === "views") && decorated && typeof decorated === "object") {
3570
+ try {
3571
+ const viewDoc = decorated;
3572
+ const sourceObject = viewDoc?.object ?? viewDoc?.data?.object ?? viewDoc?.objectName ?? viewDoc?.list?.data?.object;
3573
+ const objectDef = typeof sourceObject === "string" ? this.engine.registry.getObject(sourceObject) : void 0;
3574
+ if (objectDef) {
3575
+ const refs = computeViewReferenceDiagnostics(viewDoc, objectDef);
3576
+ if (!refs.valid) {
3577
+ const prior = viewDoc._diagnostics;
3578
+ decorated = {
3579
+ ...viewDoc,
3580
+ _diagnostics: {
3581
+ valid: false,
3582
+ errors: [
3583
+ ...prior && prior.valid === false && Array.isArray(prior.errors) ? prior.errors : [],
3584
+ ...refs.errors ?? []
3585
+ ]
3586
+ }
3587
+ };
3588
+ }
3589
+ }
3590
+ } catch {
3591
+ }
3592
+ }
3371
3593
  const artifactBacked = this.isArtifactBacked(request.type, request.name);
3372
3594
  const lockState = resolveLockState(decorated, artifactBacked);
3373
3595
  return {
@@ -3407,20 +3629,20 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3407
3629
  const services = this.getServicesRegistry?.();
3408
3630
  const metadataService = services?.get("metadata");
3409
3631
  if (metadataService && typeof metadataService.get === "function") {
3410
- let fromService = await metadataService.get(request.type, request.name);
3632
+ let fromService = await metadataService.get(request.type, request.name, request.packageId);
3411
3633
  if (fromService === void 0 || fromService === null) {
3412
3634
  const alt = PLURAL_TO_SINGULAR3[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
3413
- if (alt) fromService = await metadataService.get(alt, request.name);
3635
+ if (alt) fromService = await metadataService.get(alt, request.name, request.packageId);
3414
3636
  }
3415
3637
  if (fromService !== void 0 && fromService !== null) code = fromService;
3416
3638
  }
3417
3639
  } catch {
3418
3640
  }
3419
3641
  if (code === null) {
3420
- let regItem = this.engine.registry.getItem(request.type, request.name);
3642
+ let regItem = this.lookupArtifactItem(request.type, request.name, request.packageId) ?? this.engine.registry.getItem(request.type, request.name, request.packageId);
3421
3643
  if (regItem === void 0) {
3422
3644
  const alt = PLURAL_TO_SINGULAR3[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
3423
- if (alt) regItem = this.engine.registry.getItem(alt, request.name);
3645
+ if (alt) regItem = this.engine.registry.getItem(alt, request.name, request.packageId);
3424
3646
  }
3425
3647
  if (regItem !== void 0) code = regItem;
3426
3648
  }
@@ -3428,20 +3650,28 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3428
3650
  let overlayScope = null;
3429
3651
  try {
3430
3652
  const findOverlay = async (oid) => {
3431
- const where = {
3432
- type: request.type,
3433
- name: request.name,
3434
- state: "active",
3435
- organization_id: oid
3653
+ const lookup = async (t) => {
3654
+ const base = {
3655
+ type: t,
3656
+ name: request.name,
3657
+ state: "active",
3658
+ organization_id: oid
3659
+ };
3660
+ if (request.packageId) {
3661
+ const scoped = await this.engine.findOne("sys_metadata", {
3662
+ where: { ...base, package_id: request.packageId }
3663
+ });
3664
+ if (scoped) return scoped;
3665
+ return await this.engine.findOne("sys_metadata", {
3666
+ where: { ...base, package_id: null }
3667
+ });
3668
+ }
3669
+ return await this.engine.findOne("sys_metadata", { where: base });
3436
3670
  };
3437
- let rec = await this.engine.findOne("sys_metadata", { where });
3671
+ let rec = await lookup(request.type);
3438
3672
  if (!rec) {
3439
3673
  const alt = PLURAL_TO_SINGULAR3[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
3440
- if (alt) {
3441
- rec = await this.engine.findOne("sys_metadata", {
3442
- where: { ...where, type: alt }
3443
- });
3444
- }
3674
+ if (alt) rec = await lookup(alt);
3445
3675
  }
3446
3676
  return rec;
3447
3677
  };
@@ -4463,14 +4693,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4463
4693
  * "authoring a DB-only item" (requires only `allowRuntimeCreate`).
4464
4694
  */
4465
4695
  isArtifactBacked(type, name) {
4466
- const registry = this.engine?.registry;
4467
- if (!registry || typeof registry.getItem !== "function") {
4468
- return false;
4469
- }
4470
- const singular = PLURAL_TO_SINGULAR3[type] ?? type;
4471
- const item = registry.getItem(singular, name) ?? registry.getItem(type, name);
4472
- if (!item || !item._packageId) return false;
4473
- return item._packageId !== "sys_metadata";
4696
+ return this.lookupArtifactItem(type, name) !== void 0;
4474
4697
  }
4475
4698
  // ───────────────────────────────────────────────────────────────────
4476
4699
  // ADR-0010 — metadata protection (Phase 1: L3 item-level lock)
@@ -4480,11 +4703,19 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4480
4703
  * type and its singular/plural twin. Returns `undefined` when the
4481
4704
  * registry is unavailable or the item is not artifact-backed.
4482
4705
  */
4483
- lookupArtifactItem(type, name) {
4706
+ lookupArtifactItem(type, name, currentPackageId) {
4484
4707
  const registry = this.engine?.registry;
4485
- if (!registry || typeof registry.getItem !== "function") return void 0;
4708
+ if (!registry) return void 0;
4486
4709
  const singular = PLURAL_TO_SINGULAR3[type] ?? type;
4487
- return registry.getItem(singular, name) ?? registry.getItem(type, name);
4710
+ if (typeof registry.getArtifactItem === "function") {
4711
+ return registry.getArtifactItem(singular, name, currentPackageId) ?? registry.getArtifactItem(type, name, currentPackageId);
4712
+ }
4713
+ if (typeof registry.getItem !== "function") return void 0;
4714
+ const item = registry.getItem(singular, name, currentPackageId) ?? registry.getItem(type, name, currentPackageId);
4715
+ if (!item || !item._packageId || item._packageId === "sys_metadata") {
4716
+ return void 0;
4717
+ }
4718
+ return item;
4488
4719
  }
4489
4720
  /**
4490
4721
  * Resolve the effective `_lock` for an item by consulting the
@@ -4498,13 +4729,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4498
4729
  * scope and the caller is expected to also gate on `environmentId`.
4499
4730
  */
4500
4731
  async getEffectiveLock(type, name, organizationId) {
4501
- const registry = this.engine?.registry;
4502
- const singular = PLURAL_TO_SINGULAR3[type] ?? type;
4503
- let artifactItem;
4504
- if (registry && typeof registry.getItem === "function") {
4505
- artifactItem = registry.getItem(singular, name) ?? registry.getItem(type, name);
4506
- }
4507
- if (artifactItem && artifactItem._packageId && artifactItem._packageId !== "sys_metadata") {
4732
+ const artifactItem = this.lookupArtifactItem(type, name);
4733
+ if (artifactItem) {
4508
4734
  const p = extractProtection(artifactItem);
4509
4735
  if (p.lock !== "none") {
4510
4736
  return { lock: p.lock, lockReason: p.lockReason, lockSource: "artifact" };
@@ -4645,6 +4871,49 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4645
4871
  );
4646
4872
  }
4647
4873
  }
4874
+ /**
4875
+ * Heal the in-memory registry after a metadata reset (overlay-row
4876
+ * delete) on control-plane kernels. Two layers:
4877
+ *
4878
+ * 1. Drop the plain-key runtime shadow so the packaged artifact
4879
+ * (registered under `<packageId>:<name>`) becomes the visible
4880
+ * value again. The shadow is written by the overlay-hydration
4881
+ * paths (`getMetaItems` / `loadMetaFromDb`) and — pre-fix —
4882
+ * survived the reset until restart, leaving stale overlay
4883
+ * content (and a stripped `_lock` envelope) in every
4884
+ * registry-direct read (ADR-0010 §3.3).
4885
+ * 2. When no composite-key artifact exists, fall back to the
4886
+ * MetadataService baseline (FilesystemLoader-sourced types) and
4887
+ * re-register it, preserving the historical refresh behaviour
4888
+ * for items the SchemaRegistry never held as artifacts.
4889
+ *
4890
+ * Best-effort: a failure must never block the delete that already
4891
+ * succeeded; the next full reload fixes the registry anyway.
4892
+ */
4893
+ async restoreArtifactRegistryView(type, name) {
4894
+ try {
4895
+ const registry = this.engine.registry;
4896
+ let healed = false;
4897
+ if (typeof registry.removeRuntimeShadow === "function") {
4898
+ const singular = PLURAL_TO_SINGULAR3[type] ?? type;
4899
+ healed = registry.removeRuntimeShadow(singular, name);
4900
+ if (type !== singular) {
4901
+ healed = registry.removeRuntimeShadow(type, name) || healed;
4902
+ }
4903
+ }
4904
+ if (healed) return;
4905
+ if (this.environmentId !== void 0) return;
4906
+ const services = this.getServicesRegistry?.();
4907
+ const metadataService = services?.get("metadata");
4908
+ if (metadataService && typeof metadataService.get === "function") {
4909
+ const artifactItem = await metadataService.get(type, name);
4910
+ if (artifactItem !== void 0) {
4911
+ this.engine.registry.registerItem(type, artifactItem, "name");
4912
+ }
4913
+ }
4914
+ } catch {
4915
+ }
4916
+ }
4648
4917
  /**
4649
4918
  * Ensure a just-PUBLISHED object's physical table exists so it is usable
4650
4919
  * for data CRUD immediately — without a server restart. Registering the
@@ -4808,7 +5077,10 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4808
5077
  if (request.parentVersion !== void 0) {
4809
5078
  parentVersion = request.parentVersion;
4810
5079
  } else {
4811
- const current = await repo.get(ref, { state: mode === "draft" ? "draft" : "active" });
5080
+ const current = await repo.get(ref, {
5081
+ state: mode === "draft" ? "draft" : "active",
5082
+ packageId: request.packageId ?? null
5083
+ });
4812
5084
  parentVersion = current?.hash ?? null;
4813
5085
  }
4814
5086
  try {
@@ -5466,6 +5738,9 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
5466
5738
  const targetState = request.state === "draft" ? "draft" : "active";
5467
5739
  const current = await repo.get(ref, { state: targetState });
5468
5740
  if (!current) {
5741
+ if (targetState === "active") {
5742
+ await this.restoreArtifactRegistryView(request.type, request.name);
5743
+ }
5469
5744
  return {
5470
5745
  success: true,
5471
5746
  reset: false,
@@ -5480,18 +5755,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
5480
5755
  intent: this.isArtifactBacked(singularTypeForRepo, request.name) ? "override-artifact" : "runtime-only",
5481
5756
  state: targetState
5482
5757
  });
5483
- if (this.environmentId === void 0) {
5484
- try {
5485
- const services = this.getServicesRegistry?.();
5486
- const metadataService = services?.get("metadata");
5487
- if (metadataService && typeof metadataService.get === "function") {
5488
- const artifactItem = await metadataService.get(request.type, request.name);
5489
- if (artifactItem !== void 0) {
5490
- this.engine.registry.registerItem(request.type, artifactItem, "name");
5491
- }
5492
- }
5493
- } catch {
5494
- }
5758
+ if (targetState === "active") {
5759
+ await this.restoreArtifactRegistryView(request.type, request.name);
5495
5760
  }
5496
5761
  if (this.shouldDropStorage(request.type, request.name, request.dropStorage, targetState)) {
5497
5762
  await this.dropObjectStorage(singularTypeForRepo, request.name);
@@ -5550,18 +5815,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
5550
5815
  await this.dropObjectStorage(PLURAL_TO_SINGULAR3[request.type] ?? request.type, request.name);
5551
5816
  }
5552
5817
  }
5553
- if (this.environmentId === void 0) {
5554
- try {
5555
- const services = this.getServicesRegistry?.();
5556
- const metadataService = services?.get("metadata");
5557
- if (metadataService && typeof metadataService.get === "function") {
5558
- const artifactItem = await metadataService.get(request.type, request.name);
5559
- if (artifactItem !== void 0) {
5560
- this.engine.registry.registerItem(request.type, artifactItem, "name");
5561
- }
5562
- }
5563
- } catch {
5564
- }
5818
+ if (request.state !== "draft") {
5819
+ await this.restoreArtifactRegistryView(request.type, request.name);
5565
5820
  }
5566
5821
  return {
5567
5822
  success: true,
@@ -5599,7 +5854,12 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
5599
5854
  if (normalizedType === "object") {
5600
5855
  this.engine.registry.registerObject(data, record.packageId || "sys_metadata");
5601
5856
  } else {
5602
- this.engine.registry.registerItem(normalizedType, data, "name");
5857
+ const artifact = this.lookupArtifactItem(normalizedType, data?.name);
5858
+ this.engine.registry.registerItem(
5859
+ normalizedType,
5860
+ mergeArtifactProtection(data, artifact),
5861
+ "name"
5862
+ );
5603
5863
  }
5604
5864
  loaded++;
5605
5865
  } catch (e) {
@@ -7566,7 +7826,9 @@ var _ObjectQL = class _ObjectQL {
7566
7826
  "mappings",
7567
7827
  "analyticsCubes",
7568
7828
  // Integration Protocol
7569
- "connectors"
7829
+ "connectors",
7830
+ // System Protocol — package documentation (ADR-0046); inert data
7831
+ "docs"
7570
7832
  ];
7571
7833
  for (const key of metadataArrayKeys) {
7572
7834
  const items = manifest[key];
@@ -7702,7 +7964,8 @@ var _ObjectQL = class _ObjectQL {
7702
7964
  "hooks",
7703
7965
  "mappings",
7704
7966
  "analyticsCubes",
7705
- "connectors"
7967
+ "connectors",
7968
+ "docs"
7706
7969
  ];
7707
7970
  for (const key of metadataArrayKeys) {
7708
7971
  const items = plugin[key];
@@ -9231,17 +9494,24 @@ var MetadataFacade = class {
9231
9494
  */
9232
9495
  async register(type, name, data) {
9233
9496
  const definition = typeof data === "object" && data !== null ? { ...data, name: data.name ?? name } : data;
9497
+ const packageId = definition?._packageId;
9234
9498
  if (type === "object") {
9235
- this.registry.registerItem(type, definition, "name");
9499
+ this.registry.registerItem(type, definition, "name", packageId);
9236
9500
  } else {
9237
- this.registry.registerItem(type, definition, definition.id ? "id" : "name");
9501
+ this.registry.registerItem(type, definition, definition.id ? "id" : "name", packageId);
9238
9502
  }
9239
9503
  }
9240
9504
  /**
9241
- * Get a metadata item by type and name
9242
- */
9243
- async get(type, name) {
9244
- const item = this.registry.getItem(type, name);
9505
+ * Get a metadata item by type and name.
9506
+ *
9507
+ * `currentPackageId` (ADR-0048) opts into package-scoped resolution: when two
9508
+ * installed packages ship an item of the same `type`/`name`, the registry
9509
+ * prefers the one owned by `currentPackageId` (composite key
9510
+ * `${packageId}:${name}`) before falling back to first-match. Omit it for the
9511
+ * legacy context-free lookup.
9512
+ */
9513
+ async get(type, name, currentPackageId) {
9514
+ const item = this.registry.getItem(type, name, currentPackageId);
9245
9515
  return item?.content ?? item;
9246
9516
  }
9247
9517
  /**
@@ -9320,6 +9590,7 @@ var ObjectQLPlugin = class {
9320
9590
  */
9321
9591
  this.startupTimeout = 12e4;
9322
9592
  this.skipSchemaSync = false;
9593
+ this.hydrateMetadataFromDb = false;
9323
9594
  /** Unsubscribe handles for metadata-event subscriptions (ADR-0008 PR-7). */
9324
9595
  this.metadataUnsubscribes = [];
9325
9596
  this.init = async (ctx) => {
@@ -9444,7 +9715,7 @@ var ObjectQLPlugin = class {
9444
9715
  } else {
9445
9716
  await this.syncRegisteredSchemas(ctx);
9446
9717
  }
9447
- if (this.environmentId === void 0) {
9718
+ if (this.environmentId === void 0 || this.hydrateMetadataFromDb) {
9448
9719
  await this.restoreMetadataFromDb(ctx);
9449
9720
  } else {
9450
9721
  ctx.logger.info("Project kernel \u2014 skipping sys_metadata hydration (metadata sourced from artifact)");
@@ -9486,6 +9757,7 @@ var ObjectQLPlugin = class {
9486
9757
  this.startupTimeout = opts.startupTimeout;
9487
9758
  }
9488
9759
  this.skipSchemaSync = typeof opts.skipSchemaSync === "boolean" ? opts.skipSchemaSync : process.env.OS_SKIP_SCHEMA_SYNC === "1";
9760
+ this.hydrateMetadataFromDb = opts.hydrateMetadataFromDb === true;
9489
9761
  }
9490
9762
  /**
9491
9763
  * Subscribe to `object` metadata events from the metadata service and
@@ -9908,7 +10180,7 @@ var ObjectQLPlugin = class {
9908
10180
  return;
9909
10181
  }
9910
10182
  if (this.ql?.registry?.registerItem) {
9911
- this.ql.registry.registerItem(type, item, keyField);
10183
+ this.ql.registry.registerItem(type, item, keyField, item._packageId);
9912
10184
  }
9913
10185
  });
9914
10186
  if (type === "hook" && this.ql && typeof this.ql.bindHooks === "function") {