@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.js CHANGED
@@ -332,6 +332,19 @@ var init_seed_loader = __esm({
332
332
  if (records && records.length > 0) {
333
333
  return String(records[0].id || records[0]._id);
334
334
  }
335
+ if (targetField !== "id") {
336
+ const byId = { id: value };
337
+ if (organizationId) byId.organization_id = organizationId;
338
+ const idMatch = await this.engine.find(targetObject, {
339
+ where: byId,
340
+ fields: ["id"],
341
+ limit: 1,
342
+ context: { isSystem: true }
343
+ });
344
+ if (idMatch && idMatch.length > 0) {
345
+ return String(idMatch[0].id || idMatch[0]._id);
346
+ }
347
+ }
335
348
  } catch {
336
349
  }
337
350
  return null;
@@ -950,6 +963,20 @@ function applySystemFields(schema, opts) {
950
963
  fields: { ...additions, ...schema.fields ?? {} }
951
964
  };
952
965
  }
966
+ function isShareableNamespace(ns) {
967
+ return RESERVED_NAMESPACES.has(ns) || ns === "sys";
968
+ }
969
+ var NamespaceConflictError = class extends Error {
970
+ constructor(namespace, existingPackageId, incomingPackageId) {
971
+ super(
972
+ `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.`
973
+ );
974
+ this.name = "NamespaceConflictError";
975
+ this.namespace = namespace;
976
+ this.existingPackageId = existingPackageId;
977
+ this.incomingPackageId = incomingPackageId;
978
+ }
979
+ };
953
980
  var SchemaRegistry = class {
954
981
  constructor(options = {}) {
955
982
  // ==========================================
@@ -993,6 +1020,7 @@ var SchemaRegistry = class {
993
1020
  } else {
994
1021
  this.multiTenant = String((0, import_types.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
995
1022
  }
1023
+ this.collisionPolicy = options.collisionPolicy ?? ((process.env.OS_METADATA_COLLISION ?? "").toLowerCase() === "warn" ? "warn" : "error");
996
1024
  }
997
1025
  get logLevel() {
998
1026
  return this._logLevel;
@@ -1353,9 +1381,23 @@ var SchemaRegistry = class {
1353
1381
  console.warn(`[Registry] Attempted to unregister non-existent ${type}: ${name}`);
1354
1382
  }
1355
1383
  /**
1356
- * Universal Get Method
1384
+ * Universal Get Method.
1385
+ *
1386
+ * ADR-0048 §3.3 — *package-scoped* resolution. When `currentPackageId` is
1387
+ * given (the package the caller is resolving within — known from the route /
1388
+ * `activeApp._packageId`), a bare name resolves to *that package's* item
1389
+ * before any cross-package fallback, so two packages shipping e.g.
1390
+ * `page/home` no longer resolve by registration order (first-match-wins).
1391
+ * Because package ids are globally unique this is unambiguous. Omitting
1392
+ * `currentPackageId` preserves the legacy resolution exactly (backward
1393
+ * compatible) and is best-effort: it returns the first match.
1394
+ *
1395
+ * Precedence (highest first):
1396
+ * 1. bare-key runtime/DB overlay (ADR-0005 sanctioned override) — unchanged
1397
+ * 2. the `currentPackageId` composite entry (prefer-local)
1398
+ * 3. first composite match (legacy first-registered-wins fallback)
1357
1399
  */
1358
- getItem(type, name) {
1400
+ getItem(type, name, currentPackageId) {
1359
1401
  if (type === "object" || type === "objects") {
1360
1402
  return this.getObject(name);
1361
1403
  }
@@ -1363,6 +1405,10 @@ var SchemaRegistry = class {
1363
1405
  if (!collection) return void 0;
1364
1406
  const direct = collection.get(name);
1365
1407
  if (direct) return direct;
1408
+ if (currentPackageId) {
1409
+ const local = collection.get(`${currentPackageId}:${name}`);
1410
+ if (local) return local;
1411
+ }
1366
1412
  for (const [key, item] of collection) {
1367
1413
  if (key.endsWith(`:${name}`)) {
1368
1414
  return item;
@@ -1370,6 +1416,74 @@ var SchemaRegistry = class {
1370
1416
  }
1371
1417
  return void 0;
1372
1418
  }
1419
+ /**
1420
+ * Artifact-only lookup (ADR-0010 §3.3). Unlike {@link getItem} — which
1421
+ * returns the plain-key entry first, so a runtime/DB-rehydrated row
1422
+ * registered under the bare name SHADOWS the packaged artifact — this
1423
+ * scans the composite (`<packageId>:<name>`) entries first and only
1424
+ * returns an item whose `_packageId` marks a genuine code package
1425
+ * (truthy and not the `'sys_metadata'` rehydration sentinel).
1426
+ *
1427
+ * This is what the protocol's lock/provenance resolution must use:
1428
+ * the artifact's `_lock` envelope always wins over an overlay, and an
1429
+ * overlay row hydrated into the plain key must never be able to mask
1430
+ * it (that masking is exactly the "registry pollution" bug where a
1431
+ * locked app's `_lock` read back as undefined after a PUT+GET).
1432
+ */
1433
+ getArtifactItem(type, name, currentPackageId) {
1434
+ if (type === "object" || type === "objects") {
1435
+ const obj = this.getObject(name);
1436
+ return obj && obj._packageId && obj._packageId !== "sys_metadata" ? obj : void 0;
1437
+ }
1438
+ const collection = this.metadata.get(type);
1439
+ if (!collection) return void 0;
1440
+ if (currentPackageId) {
1441
+ const local = collection.get(`${currentPackageId}:${name}`);
1442
+ if (local && local._packageId && local._packageId !== "sys_metadata") return local;
1443
+ }
1444
+ for (const [key, item] of collection) {
1445
+ if (key !== name && key.endsWith(`:${name}`)) {
1446
+ const it = item;
1447
+ if (it && it._packageId && it._packageId !== "sys_metadata") return item;
1448
+ }
1449
+ }
1450
+ const direct = collection.get(name);
1451
+ if (direct && direct._packageId && direct._packageId !== "sys_metadata") {
1452
+ return direct;
1453
+ }
1454
+ return void 0;
1455
+ }
1456
+ /**
1457
+ * Remove a plain-key runtime shadow so the packaged artifact registered
1458
+ * under a composite key becomes the visible value again. Used by the
1459
+ * metadata reset path (`deleteMetaItem`): deleting the `sys_metadata`
1460
+ * overlay row must also heal the in-memory registry, otherwise the
1461
+ * stale overlay copy keeps shadowing the artifact until restart.
1462
+ *
1463
+ * Deliberately conservative: the plain-key entry is only deleted when a
1464
+ * packaged artifact still exists under a composite key, so the name
1465
+ * stays resolvable afterwards. A runtime-only item (no artifact
1466
+ * backing) is left untouched. Note the plain entry's own `_packageId`
1467
+ * is NOT consulted — the hydration path grafts the artifact envelope
1468
+ * onto the shadow (ADR-0010 §3.3), so a stamped `_packageId` does not
1469
+ * mean the plain entry IS the artifact registration; artifact loaders
1470
+ * always register under a composite key.
1471
+ */
1472
+ removeRuntimeShadow(type, name) {
1473
+ const collection = this.metadata.get(type);
1474
+ if (!collection || !collection.has(name)) return false;
1475
+ for (const [key, item] of collection) {
1476
+ if (key !== name && key.endsWith(`:${name}`)) {
1477
+ const it = item;
1478
+ if (it && it._packageId && it._packageId !== "sys_metadata") {
1479
+ collection.delete(name);
1480
+ this.log(`[Registry] Removed runtime shadow ${type}: ${name} (artifact ${it._packageId} restored)`);
1481
+ return true;
1482
+ }
1483
+ }
1484
+ }
1485
+ return false;
1486
+ }
1373
1487
  /**
1374
1488
  * Universal List Method
1375
1489
  */
@@ -1410,6 +1524,20 @@ var SchemaRegistry = class {
1410
1524
  // Package Management
1411
1525
  // ==========================================
1412
1526
  installPackage(manifest, settings) {
1527
+ if (manifest.namespace && !isShareableNamespace(manifest.namespace)) {
1528
+ const conflictOwner = this.getNamespaceOwners(manifest.namespace).find(
1529
+ (owner) => owner !== manifest.id
1530
+ );
1531
+ if (conflictOwner) {
1532
+ if (this.collisionPolicy === "warn") {
1533
+ console.warn(
1534
+ `[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.`
1535
+ );
1536
+ } else {
1537
+ throw new NamespaceConflictError(manifest.namespace, conflictOwner, manifest.id);
1538
+ }
1539
+ }
1540
+ }
1413
1541
  const now = (/* @__PURE__ */ new Date()).toISOString();
1414
1542
  const disabled = this.initialDisabledPackageIds.has(manifest.id);
1415
1543
  const pkg = {
@@ -1487,8 +1615,8 @@ var SchemaRegistry = class {
1487
1615
  registerApp(app, packageId) {
1488
1616
  this.registerItem("app", app, "name", packageId);
1489
1617
  }
1490
- getApp(name) {
1491
- const app = this.getItem("app", name);
1618
+ getApp(name, currentPackageId) {
1619
+ const app = this.getItem("app", name, currentPackageId);
1492
1620
  if (!app) return app;
1493
1621
  return this.applyNavContributions(app);
1494
1622
  }
@@ -1708,7 +1836,7 @@ var SysMetadataRepository = class {
1708
1836
  this.assertOpen();
1709
1837
  const state = opts?.state ?? "active";
1710
1838
  const row = await this.engine.findOne("sys_metadata", {
1711
- where: this.whereFor(ref, state)
1839
+ where: this.whereFor(ref, state, opts && "packageId" in opts ? opts.packageId ?? null : void 0)
1712
1840
  });
1713
1841
  if (!row) return null;
1714
1842
  return this.rowToItem(ref, row);
@@ -1757,7 +1885,7 @@ var SysMetadataRepository = class {
1757
1885
  const hash = (0, import_metadata_core.hashSpec)(body);
1758
1886
  const result = await this.withTxn(async (ctx) => {
1759
1887
  const existing = await this.engine.findOne("sys_metadata", {
1760
- where: this.whereFor(ref, state),
1888
+ where: this.whereFor(ref, state, opts.packageId ?? null),
1761
1889
  context: ctx
1762
1890
  });
1763
1891
  const existingHash = existing?.checksum ?? null;
@@ -2241,13 +2369,15 @@ var SysMetadataRepository = class {
2241
2369
  err.status = 403;
2242
2370
  throw err;
2243
2371
  }
2244
- whereFor(ref, state = "active") {
2245
- return {
2372
+ whereFor(ref, state = "active", packageId) {
2373
+ const where = {
2246
2374
  type: ref.type,
2247
2375
  name: ref.name,
2248
2376
  organization_id: this.organizationId,
2249
2377
  state
2250
2378
  };
2379
+ if (packageId !== void 0) where.package_id = packageId;
2380
+ return where;
2251
2381
  }
2252
2382
  fullRef(ref) {
2253
2383
  return {
@@ -2394,6 +2524,48 @@ function decorateMetadataItems(type, items) {
2394
2524
  if (!Array.isArray(items)) return items;
2395
2525
  return items.map((item) => decorateMetadataItem(type, item));
2396
2526
  }
2527
+ function fieldMap(objectDef) {
2528
+ const map = /* @__PURE__ */ new Map();
2529
+ const fields = objectDef?.fields;
2530
+ if (Array.isArray(fields)) {
2531
+ for (const f of fields) if (f?.name) map.set(f.name, f);
2532
+ } else if (fields && typeof fields === "object") {
2533
+ for (const [name, f] of Object.entries(fields)) map.set(name, f ?? {});
2534
+ }
2535
+ return map;
2536
+ }
2537
+ function computeViewReferenceDiagnostics(view, objectDef) {
2538
+ const fields = fieldMap(objectDef);
2539
+ const errors = [];
2540
+ const requireField = (name, path) => {
2541
+ if (typeof name !== "string" || !name) return;
2542
+ if (!fields.has(name)) {
2543
+ errors.push({
2544
+ path,
2545
+ message: `Field "${name}" does not exist on the source object`,
2546
+ code: "reference_not_found"
2547
+ });
2548
+ }
2549
+ };
2550
+ const userFilters = view?.userFilters;
2551
+ userFilters?.fields?.forEach((f, i) => requireField(f?.field, `userFilters.fields.${i}.field`));
2552
+ userFilters?.tabs?.forEach((t, i) => t?.filter?.forEach((r, j) => requireField(r?.field, `userFilters.tabs.${i}.filter.${j}.field`)));
2553
+ view?.tabs?.forEach((t, i) => t?.filter?.forEach((r, j) => requireField(r?.field, `tabs.${i}.filter.${j}.field`)));
2554
+ view?.filterableFields?.forEach((f, i) => requireField(f, `filterableFields.${i}`));
2555
+ const kanban = view?.kanban;
2556
+ if (kanban?.groupByField) {
2557
+ requireField(kanban.groupByField, "kanban.groupByField");
2558
+ const def = fields.get(kanban.groupByField);
2559
+ if (def && def.type && !["select", "multi-select", "boolean", "lookup", "master_detail"].includes(def.type)) {
2560
+ errors.push({
2561
+ path: "kanban.groupByField",
2562
+ message: `Field "${kanban.groupByField}" (type "${def.type}") cannot group a kanban \u2014 use a select-like field`,
2563
+ code: "invalid_binding"
2564
+ });
2565
+ }
2566
+ }
2567
+ return errors.length ? { valid: false, errors } : { valid: true };
2568
+ }
2397
2569
 
2398
2570
  // src/protocol.ts
2399
2571
  var TYPE_TO_FORM = import_system.METADATA_FORM_REGISTRY;
@@ -2892,8 +3064,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
2892
3064
  await exec("DROP INDEX IF EXISTS idx_sys_metadata_overlay_active");
2893
3065
  } catch {
2894
3066
  }
2895
- const partialSql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active ON sys_metadata (type, name, organization_id) WHERE state = 'active'";
2896
- const fallbackSql = "CREATE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active ON sys_metadata (type, name, organization_id)";
3067
+ 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'";
3068
+ const fallbackSql = "CREATE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active ON sys_metadata (type, name, organization_id, package_id)";
2897
3069
  try {
2898
3070
  await exec(partialSql);
2899
3071
  } catch (err) {
@@ -2905,7 +3077,11 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
2905
3077
  }
2906
3078
  }
2907
3079
  }
2908
- const draftPartialSql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_metadata_overlay_draft ON sys_metadata (type, name, organization_id) WHERE state = 'draft'";
3080
+ try {
3081
+ await exec("DROP INDEX IF EXISTS idx_sys_metadata_overlay_draft");
3082
+ } catch {
3083
+ }
3084
+ 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'";
2909
3085
  try {
2910
3086
  await exec(draftPartialSql);
2911
3087
  } catch (err) {
@@ -2913,7 +3089,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
2913
3089
  if (/partial|where clause|syntax/i.test(msg)) {
2914
3090
  try {
2915
3091
  await exec(
2916
- "CREATE INDEX IF NOT EXISTS idx_sys_metadata_overlay_draft ON sys_metadata (type, name, organization_id)"
3092
+ "CREATE INDEX IF NOT EXISTS idx_sys_metadata_overlay_draft ON sys_metadata (type, name, organization_id, package_id)"
2917
3093
  );
2918
3094
  } catch {
2919
3095
  }
@@ -3216,8 +3392,13 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3216
3392
  }
3217
3393
  byName.set(data.name, data);
3218
3394
  }
3219
- if (this.environmentId === void 0) {
3220
- this.engine.registry.registerItem(request.type, data, "name");
3395
+ if (this.environmentId === void 0 && data && typeof data === "object") {
3396
+ const artifact = this.lookupArtifactItem(request.type, data.name);
3397
+ this.engine.registry.registerItem(
3398
+ request.type,
3399
+ mergeArtifactProtection(data, artifact),
3400
+ "name"
3401
+ );
3221
3402
  }
3222
3403
  }
3223
3404
  items = Array.from(byName.values());
@@ -3309,7 +3490,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3309
3490
  items.map((it) => {
3310
3491
  const a = this.lookupArtifactItem(
3311
3492
  request.type,
3312
- it?.name
3493
+ it?.name,
3494
+ packageId ?? it?._packageId
3313
3495
  );
3314
3496
  return mergeArtifactProtection(it, a);
3315
3497
  })
@@ -3323,16 +3505,28 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3323
3505
  if (request.previewDrafts && readState !== "draft") {
3324
3506
  try {
3325
3507
  const findDraft = async (oid) => {
3326
- const rec = await this.engine.findOne("sys_metadata", {
3327
- where: { type: request.type, name: request.name, state: "draft", organization_id: oid }
3328
- });
3508
+ const lookup = async (t) => {
3509
+ const base = {
3510
+ type: t,
3511
+ name: request.name,
3512
+ state: "draft",
3513
+ organization_id: oid
3514
+ };
3515
+ if (request.packageId) {
3516
+ const scoped = await this.engine.findOne("sys_metadata", {
3517
+ where: { ...base, package_id: request.packageId }
3518
+ });
3519
+ if (scoped) return scoped;
3520
+ return await this.engine.findOne("sys_metadata", {
3521
+ where: { ...base, package_id: null }
3522
+ });
3523
+ }
3524
+ return await this.engine.findOne("sys_metadata", { where: base });
3525
+ };
3526
+ const rec = await lookup(request.type);
3329
3527
  if (rec) return rec;
3330
3528
  const alt = import_shared4.PLURAL_TO_SINGULAR[request.type] ?? import_shared4.SINGULAR_TO_PLURAL[request.type];
3331
- if (alt) {
3332
- return await this.engine.findOne("sys_metadata", {
3333
- where: { type: alt, name: request.name, state: "draft", organization_id: oid }
3334
- });
3335
- }
3529
+ if (alt) return await lookup(alt);
3336
3530
  return void 0;
3337
3531
  };
3338
3532
  const draftRec = (orgId ? await findDraft(orgId) : void 0) ?? await findDraft(null);
@@ -3350,24 +3544,28 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3350
3544
  }
3351
3545
  try {
3352
3546
  const findOverlay = async (oid) => {
3353
- const where = {
3354
- type: request.type,
3355
- name: request.name,
3356
- state: readState,
3357
- organization_id: oid
3358
- };
3359
- const rec = await this.engine.findOne("sys_metadata", { where });
3360
- if (rec) return rec;
3361
- const alt = import_shared4.PLURAL_TO_SINGULAR[request.type] ?? import_shared4.SINGULAR_TO_PLURAL[request.type];
3362
- if (alt) {
3363
- const altWhere = {
3364
- type: alt,
3547
+ const lookup = async (t) => {
3548
+ const base = {
3549
+ type: t,
3365
3550
  name: request.name,
3366
3551
  state: readState,
3367
3552
  organization_id: oid
3368
3553
  };
3369
- return await this.engine.findOne("sys_metadata", { where: altWhere });
3370
- }
3554
+ if (request.packageId) {
3555
+ const scoped = await this.engine.findOne("sys_metadata", {
3556
+ where: { ...base, package_id: request.packageId }
3557
+ });
3558
+ if (scoped) return scoped;
3559
+ return await this.engine.findOne("sys_metadata", {
3560
+ where: { ...base, package_id: null }
3561
+ });
3562
+ }
3563
+ return await this.engine.findOne("sys_metadata", { where: base });
3564
+ };
3565
+ const rec = await lookup(request.type);
3566
+ if (rec) return rec;
3567
+ const alt = import_shared4.PLURAL_TO_SINGULAR[request.type] ?? import_shared4.SINGULAR_TO_PLURAL[request.type];
3568
+ if (alt) return await lookup(alt);
3371
3569
  return void 0;
3372
3570
  };
3373
3571
  const record = (orgId ? await findOverlay(orgId) : void 0) ?? await findOverlay(null);
@@ -3396,13 +3594,13 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3396
3594
  const services = this.getServicesRegistry?.();
3397
3595
  const metadataService = services?.get("metadata");
3398
3596
  if (metadataService && typeof metadataService.get === "function") {
3399
- const fromService = await metadataService.get(request.type, request.name);
3597
+ const fromService = await metadataService.get(request.type, request.name, request.packageId);
3400
3598
  if (fromService !== void 0 && fromService !== null) {
3401
3599
  item = fromService;
3402
3600
  } else {
3403
3601
  const alt = import_shared4.PLURAL_TO_SINGULAR[request.type] ?? import_shared4.SINGULAR_TO_PLURAL[request.type];
3404
3602
  if (alt) {
3405
- const altFromService = await metadataService.get(alt, request.name);
3603
+ const altFromService = await metadataService.get(alt, request.name, request.packageId);
3406
3604
  if (altFromService !== void 0 && altFromService !== null) {
3407
3605
  item = altFromService;
3408
3606
  }
@@ -3413,20 +3611,44 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3413
3611
  }
3414
3612
  }
3415
3613
  if (item === void 0) {
3416
- item = this.engine.registry.getItem(request.type, request.name);
3614
+ item = this.engine.registry.getItem(request.type, request.name, request.packageId);
3417
3615
  if (item === void 0) {
3418
3616
  const alt = import_shared4.PLURAL_TO_SINGULAR[request.type] ?? import_shared4.SINGULAR_TO_PLURAL[request.type];
3419
- if (alt) item = this.engine.registry.getItem(alt, request.name);
3617
+ if (alt) item = this.engine.registry.getItem(alt, request.name, request.packageId);
3420
3618
  }
3421
3619
  }
3422
3620
  if ((request.type === "app" || request.type === "apps") && item) {
3423
3621
  item = this.engine.registry.applyNavContributions(item);
3424
3622
  }
3425
- const artifactItem = this.lookupArtifactItem(request.type, request.name);
3426
- const decorated = decorateMetadataItem(
3623
+ const artifactItem = this.lookupArtifactItem(request.type, request.name, request.packageId);
3624
+ let decorated = decorateMetadataItem(
3427
3625
  request.type,
3428
3626
  mergeArtifactProtection(item, artifactItem)
3429
3627
  );
3628
+ if ((request.type === "view" || request.type === "views") && decorated && typeof decorated === "object") {
3629
+ try {
3630
+ const viewDoc = decorated;
3631
+ const sourceObject = viewDoc?.object ?? viewDoc?.data?.object ?? viewDoc?.objectName ?? viewDoc?.list?.data?.object;
3632
+ const objectDef = typeof sourceObject === "string" ? this.engine.registry.getObject(sourceObject) : void 0;
3633
+ if (objectDef) {
3634
+ const refs = computeViewReferenceDiagnostics(viewDoc, objectDef);
3635
+ if (!refs.valid) {
3636
+ const prior = viewDoc._diagnostics;
3637
+ decorated = {
3638
+ ...viewDoc,
3639
+ _diagnostics: {
3640
+ valid: false,
3641
+ errors: [
3642
+ ...prior && prior.valid === false && Array.isArray(prior.errors) ? prior.errors : [],
3643
+ ...refs.errors ?? []
3644
+ ]
3645
+ }
3646
+ };
3647
+ }
3648
+ }
3649
+ } catch {
3650
+ }
3651
+ }
3430
3652
  const artifactBacked = this.isArtifactBacked(request.type, request.name);
3431
3653
  const lockState = (0, import_kernel5.resolveLockState)(decorated, artifactBacked);
3432
3654
  return {
@@ -3466,20 +3688,20 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3466
3688
  const services = this.getServicesRegistry?.();
3467
3689
  const metadataService = services?.get("metadata");
3468
3690
  if (metadataService && typeof metadataService.get === "function") {
3469
- let fromService = await metadataService.get(request.type, request.name);
3691
+ let fromService = await metadataService.get(request.type, request.name, request.packageId);
3470
3692
  if (fromService === void 0 || fromService === null) {
3471
3693
  const alt = import_shared4.PLURAL_TO_SINGULAR[request.type] ?? import_shared4.SINGULAR_TO_PLURAL[request.type];
3472
- if (alt) fromService = await metadataService.get(alt, request.name);
3694
+ if (alt) fromService = await metadataService.get(alt, request.name, request.packageId);
3473
3695
  }
3474
3696
  if (fromService !== void 0 && fromService !== null) code = fromService;
3475
3697
  }
3476
3698
  } catch {
3477
3699
  }
3478
3700
  if (code === null) {
3479
- let regItem = this.engine.registry.getItem(request.type, request.name);
3701
+ let regItem = this.lookupArtifactItem(request.type, request.name, request.packageId) ?? this.engine.registry.getItem(request.type, request.name, request.packageId);
3480
3702
  if (regItem === void 0) {
3481
3703
  const alt = import_shared4.PLURAL_TO_SINGULAR[request.type] ?? import_shared4.SINGULAR_TO_PLURAL[request.type];
3482
- if (alt) regItem = this.engine.registry.getItem(alt, request.name);
3704
+ if (alt) regItem = this.engine.registry.getItem(alt, request.name, request.packageId);
3483
3705
  }
3484
3706
  if (regItem !== void 0) code = regItem;
3485
3707
  }
@@ -3487,20 +3709,28 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3487
3709
  let overlayScope = null;
3488
3710
  try {
3489
3711
  const findOverlay = async (oid) => {
3490
- const where = {
3491
- type: request.type,
3492
- name: request.name,
3493
- state: "active",
3494
- organization_id: oid
3712
+ const lookup = async (t) => {
3713
+ const base = {
3714
+ type: t,
3715
+ name: request.name,
3716
+ state: "active",
3717
+ organization_id: oid
3718
+ };
3719
+ if (request.packageId) {
3720
+ const scoped = await this.engine.findOne("sys_metadata", {
3721
+ where: { ...base, package_id: request.packageId }
3722
+ });
3723
+ if (scoped) return scoped;
3724
+ return await this.engine.findOne("sys_metadata", {
3725
+ where: { ...base, package_id: null }
3726
+ });
3727
+ }
3728
+ return await this.engine.findOne("sys_metadata", { where: base });
3495
3729
  };
3496
- let rec = await this.engine.findOne("sys_metadata", { where });
3730
+ let rec = await lookup(request.type);
3497
3731
  if (!rec) {
3498
3732
  const alt = import_shared4.PLURAL_TO_SINGULAR[request.type] ?? import_shared4.SINGULAR_TO_PLURAL[request.type];
3499
- if (alt) {
3500
- rec = await this.engine.findOne("sys_metadata", {
3501
- where: { ...where, type: alt }
3502
- });
3503
- }
3733
+ if (alt) rec = await lookup(alt);
3504
3734
  }
3505
3735
  return rec;
3506
3736
  };
@@ -4522,14 +4752,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4522
4752
  * "authoring a DB-only item" (requires only `allowRuntimeCreate`).
4523
4753
  */
4524
4754
  isArtifactBacked(type, name) {
4525
- const registry = this.engine?.registry;
4526
- if (!registry || typeof registry.getItem !== "function") {
4527
- return false;
4528
- }
4529
- const singular = import_shared4.PLURAL_TO_SINGULAR[type] ?? type;
4530
- const item = registry.getItem(singular, name) ?? registry.getItem(type, name);
4531
- if (!item || !item._packageId) return false;
4532
- return item._packageId !== "sys_metadata";
4755
+ return this.lookupArtifactItem(type, name) !== void 0;
4533
4756
  }
4534
4757
  // ───────────────────────────────────────────────────────────────────
4535
4758
  // ADR-0010 — metadata protection (Phase 1: L3 item-level lock)
@@ -4539,11 +4762,19 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4539
4762
  * type and its singular/plural twin. Returns `undefined` when the
4540
4763
  * registry is unavailable or the item is not artifact-backed.
4541
4764
  */
4542
- lookupArtifactItem(type, name) {
4765
+ lookupArtifactItem(type, name, currentPackageId) {
4543
4766
  const registry = this.engine?.registry;
4544
- if (!registry || typeof registry.getItem !== "function") return void 0;
4767
+ if (!registry) return void 0;
4545
4768
  const singular = import_shared4.PLURAL_TO_SINGULAR[type] ?? type;
4546
- return registry.getItem(singular, name) ?? registry.getItem(type, name);
4769
+ if (typeof registry.getArtifactItem === "function") {
4770
+ return registry.getArtifactItem(singular, name, currentPackageId) ?? registry.getArtifactItem(type, name, currentPackageId);
4771
+ }
4772
+ if (typeof registry.getItem !== "function") return void 0;
4773
+ const item = registry.getItem(singular, name, currentPackageId) ?? registry.getItem(type, name, currentPackageId);
4774
+ if (!item || !item._packageId || item._packageId === "sys_metadata") {
4775
+ return void 0;
4776
+ }
4777
+ return item;
4547
4778
  }
4548
4779
  /**
4549
4780
  * Resolve the effective `_lock` for an item by consulting the
@@ -4557,13 +4788,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4557
4788
  * scope and the caller is expected to also gate on `environmentId`.
4558
4789
  */
4559
4790
  async getEffectiveLock(type, name, organizationId) {
4560
- const registry = this.engine?.registry;
4561
- const singular = import_shared4.PLURAL_TO_SINGULAR[type] ?? type;
4562
- let artifactItem;
4563
- if (registry && typeof registry.getItem === "function") {
4564
- artifactItem = registry.getItem(singular, name) ?? registry.getItem(type, name);
4565
- }
4566
- if (artifactItem && artifactItem._packageId && artifactItem._packageId !== "sys_metadata") {
4791
+ const artifactItem = this.lookupArtifactItem(type, name);
4792
+ if (artifactItem) {
4567
4793
  const p = (0, import_kernel5.extractProtection)(artifactItem);
4568
4794
  if (p.lock !== "none") {
4569
4795
  return { lock: p.lock, lockReason: p.lockReason, lockSource: "artifact" };
@@ -4704,6 +4930,49 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4704
4930
  );
4705
4931
  }
4706
4932
  }
4933
+ /**
4934
+ * Heal the in-memory registry after a metadata reset (overlay-row
4935
+ * delete) on control-plane kernels. Two layers:
4936
+ *
4937
+ * 1. Drop the plain-key runtime shadow so the packaged artifact
4938
+ * (registered under `<packageId>:<name>`) becomes the visible
4939
+ * value again. The shadow is written by the overlay-hydration
4940
+ * paths (`getMetaItems` / `loadMetaFromDb`) and — pre-fix —
4941
+ * survived the reset until restart, leaving stale overlay
4942
+ * content (and a stripped `_lock` envelope) in every
4943
+ * registry-direct read (ADR-0010 §3.3).
4944
+ * 2. When no composite-key artifact exists, fall back to the
4945
+ * MetadataService baseline (FilesystemLoader-sourced types) and
4946
+ * re-register it, preserving the historical refresh behaviour
4947
+ * for items the SchemaRegistry never held as artifacts.
4948
+ *
4949
+ * Best-effort: a failure must never block the delete that already
4950
+ * succeeded; the next full reload fixes the registry anyway.
4951
+ */
4952
+ async restoreArtifactRegistryView(type, name) {
4953
+ try {
4954
+ const registry = this.engine.registry;
4955
+ let healed = false;
4956
+ if (typeof registry.removeRuntimeShadow === "function") {
4957
+ const singular = import_shared4.PLURAL_TO_SINGULAR[type] ?? type;
4958
+ healed = registry.removeRuntimeShadow(singular, name);
4959
+ if (type !== singular) {
4960
+ healed = registry.removeRuntimeShadow(type, name) || healed;
4961
+ }
4962
+ }
4963
+ if (healed) return;
4964
+ if (this.environmentId !== void 0) return;
4965
+ const services = this.getServicesRegistry?.();
4966
+ const metadataService = services?.get("metadata");
4967
+ if (metadataService && typeof metadataService.get === "function") {
4968
+ const artifactItem = await metadataService.get(type, name);
4969
+ if (artifactItem !== void 0) {
4970
+ this.engine.registry.registerItem(type, artifactItem, "name");
4971
+ }
4972
+ }
4973
+ } catch {
4974
+ }
4975
+ }
4707
4976
  /**
4708
4977
  * Ensure a just-PUBLISHED object's physical table exists so it is usable
4709
4978
  * for data CRUD immediately — without a server restart. Registering the
@@ -4867,7 +5136,10 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4867
5136
  if (request.parentVersion !== void 0) {
4868
5137
  parentVersion = request.parentVersion;
4869
5138
  } else {
4870
- const current = await repo.get(ref, { state: mode === "draft" ? "draft" : "active" });
5139
+ const current = await repo.get(ref, {
5140
+ state: mode === "draft" ? "draft" : "active",
5141
+ packageId: request.packageId ?? null
5142
+ });
4871
5143
  parentVersion = current?.hash ?? null;
4872
5144
  }
4873
5145
  try {
@@ -5525,6 +5797,9 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
5525
5797
  const targetState = request.state === "draft" ? "draft" : "active";
5526
5798
  const current = await repo.get(ref, { state: targetState });
5527
5799
  if (!current) {
5800
+ if (targetState === "active") {
5801
+ await this.restoreArtifactRegistryView(request.type, request.name);
5802
+ }
5528
5803
  return {
5529
5804
  success: true,
5530
5805
  reset: false,
@@ -5539,18 +5814,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
5539
5814
  intent: this.isArtifactBacked(singularTypeForRepo, request.name) ? "override-artifact" : "runtime-only",
5540
5815
  state: targetState
5541
5816
  });
5542
- if (this.environmentId === void 0) {
5543
- try {
5544
- const services = this.getServicesRegistry?.();
5545
- const metadataService = services?.get("metadata");
5546
- if (metadataService && typeof metadataService.get === "function") {
5547
- const artifactItem = await metadataService.get(request.type, request.name);
5548
- if (artifactItem !== void 0) {
5549
- this.engine.registry.registerItem(request.type, artifactItem, "name");
5550
- }
5551
- }
5552
- } catch {
5553
- }
5817
+ if (targetState === "active") {
5818
+ await this.restoreArtifactRegistryView(request.type, request.name);
5554
5819
  }
5555
5820
  if (this.shouldDropStorage(request.type, request.name, request.dropStorage, targetState)) {
5556
5821
  await this.dropObjectStorage(singularTypeForRepo, request.name);
@@ -5609,18 +5874,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
5609
5874
  await this.dropObjectStorage(import_shared4.PLURAL_TO_SINGULAR[request.type] ?? request.type, request.name);
5610
5875
  }
5611
5876
  }
5612
- if (this.environmentId === void 0) {
5613
- try {
5614
- const services = this.getServicesRegistry?.();
5615
- const metadataService = services?.get("metadata");
5616
- if (metadataService && typeof metadataService.get === "function") {
5617
- const artifactItem = await metadataService.get(request.type, request.name);
5618
- if (artifactItem !== void 0) {
5619
- this.engine.registry.registerItem(request.type, artifactItem, "name");
5620
- }
5621
- }
5622
- } catch {
5623
- }
5877
+ if (request.state !== "draft") {
5878
+ await this.restoreArtifactRegistryView(request.type, request.name);
5624
5879
  }
5625
5880
  return {
5626
5881
  success: true,
@@ -5658,7 +5913,12 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
5658
5913
  if (normalizedType === "object") {
5659
5914
  this.engine.registry.registerObject(data, record.packageId || "sys_metadata");
5660
5915
  } else {
5661
- this.engine.registry.registerItem(normalizedType, data, "name");
5916
+ const artifact = this.lookupArtifactItem(normalizedType, data?.name);
5917
+ this.engine.registry.registerItem(
5918
+ normalizedType,
5919
+ mergeArtifactProtection(data, artifact),
5920
+ "name"
5921
+ );
5662
5922
  }
5663
5923
  loaded++;
5664
5924
  } catch (e) {
@@ -7625,7 +7885,9 @@ var _ObjectQL = class _ObjectQL {
7625
7885
  "mappings",
7626
7886
  "analyticsCubes",
7627
7887
  // Integration Protocol
7628
- "connectors"
7888
+ "connectors",
7889
+ // System Protocol — package documentation (ADR-0046); inert data
7890
+ "docs"
7629
7891
  ];
7630
7892
  for (const key of metadataArrayKeys) {
7631
7893
  const items = manifest[key];
@@ -7761,7 +8023,8 @@ var _ObjectQL = class _ObjectQL {
7761
8023
  "hooks",
7762
8024
  "mappings",
7763
8025
  "analyticsCubes",
7764
- "connectors"
8026
+ "connectors",
8027
+ "docs"
7765
8028
  ];
7766
8029
  for (const key of metadataArrayKeys) {
7767
8030
  const items = plugin[key];
@@ -9290,17 +9553,24 @@ var MetadataFacade = class {
9290
9553
  */
9291
9554
  async register(type, name, data) {
9292
9555
  const definition = typeof data === "object" && data !== null ? { ...data, name: data.name ?? name } : data;
9556
+ const packageId = definition?._packageId;
9293
9557
  if (type === "object") {
9294
- this.registry.registerItem(type, definition, "name");
9558
+ this.registry.registerItem(type, definition, "name", packageId);
9295
9559
  } else {
9296
- this.registry.registerItem(type, definition, definition.id ? "id" : "name");
9560
+ this.registry.registerItem(type, definition, definition.id ? "id" : "name", packageId);
9297
9561
  }
9298
9562
  }
9299
9563
  /**
9300
- * Get a metadata item by type and name
9301
- */
9302
- async get(type, name) {
9303
- const item = this.registry.getItem(type, name);
9564
+ * Get a metadata item by type and name.
9565
+ *
9566
+ * `currentPackageId` (ADR-0048) opts into package-scoped resolution: when two
9567
+ * installed packages ship an item of the same `type`/`name`, the registry
9568
+ * prefers the one owned by `currentPackageId` (composite key
9569
+ * `${packageId}:${name}`) before falling back to first-match. Omit it for the
9570
+ * legacy context-free lookup.
9571
+ */
9572
+ async get(type, name, currentPackageId) {
9573
+ const item = this.registry.getItem(type, name, currentPackageId);
9304
9574
  return item?.content ?? item;
9305
9575
  }
9306
9576
  /**
@@ -9374,6 +9644,7 @@ var ObjectQLPlugin = class {
9374
9644
  */
9375
9645
  this.startupTimeout = 12e4;
9376
9646
  this.skipSchemaSync = false;
9647
+ this.hydrateMetadataFromDb = false;
9377
9648
  /** Unsubscribe handles for metadata-event subscriptions (ADR-0008 PR-7). */
9378
9649
  this.metadataUnsubscribes = [];
9379
9650
  this.init = async (ctx) => {
@@ -9498,7 +9769,7 @@ var ObjectQLPlugin = class {
9498
9769
  } else {
9499
9770
  await this.syncRegisteredSchemas(ctx);
9500
9771
  }
9501
- if (this.environmentId === void 0) {
9772
+ if (this.environmentId === void 0 || this.hydrateMetadataFromDb) {
9502
9773
  await this.restoreMetadataFromDb(ctx);
9503
9774
  } else {
9504
9775
  ctx.logger.info("Project kernel \u2014 skipping sys_metadata hydration (metadata sourced from artifact)");
@@ -9540,6 +9811,7 @@ var ObjectQLPlugin = class {
9540
9811
  this.startupTimeout = opts.startupTimeout;
9541
9812
  }
9542
9813
  this.skipSchemaSync = typeof opts.skipSchemaSync === "boolean" ? opts.skipSchemaSync : process.env.OS_SKIP_SCHEMA_SYNC === "1";
9814
+ this.hydrateMetadataFromDb = opts.hydrateMetadataFromDb === true;
9543
9815
  }
9544
9816
  /**
9545
9817
  * Subscribe to `object` metadata events from the metadata service and
@@ -9962,7 +10234,7 @@ var ObjectQLPlugin = class {
9962
10234
  return;
9963
10235
  }
9964
10236
  if (this.ql?.registry?.registerItem) {
9965
- this.ql.registry.registerItem(type, item, keyField);
10237
+ this.ql.registry.registerItem(type, item, keyField, item._packageId);
9966
10238
  }
9967
10239
  });
9968
10240
  if (type === "hook" && this.ql && typeof this.ql.bindHooks === "function") {