@camstack/system 1.2.313 → 1.2.315

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.
Files changed (111) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/autotrack/index.js +1 -1
  10. package/dist/builtins/autotrack/index.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  13. package/dist/builtins/camera-grid/index.js +1 -1
  14. package/dist/builtins/camera-grid/index.mjs +1 -1
  15. package/dist/builtins/composer/claim-gate.d.ts +32 -0
  16. package/dist/builtins/composer/composed-device-set.d.ts +38 -0
  17. package/dist/builtins/composer/composed-device.d.ts +4 -3
  18. package/dist/builtins/composer/composer-grafts.d.ts +54 -0
  19. package/dist/builtins/composer/composer-plan.d.ts +96 -0
  20. package/dist/builtins/composer/composer-verdict.d.ts +19 -0
  21. package/dist/builtins/composer/composer.addon.js +2717 -477
  22. package/dist/builtins/composer/composer.addon.mjs +2722 -482
  23. package/dist/builtins/composer/composer.d.ts +71 -52
  24. package/dist/builtins/composer/composition-runtime.d.ts +93 -9
  25. package/dist/builtins/composer/confirmed-seed.d.ts +32 -0
  26. package/dist/builtins/composer/existing-target.d.ts +96 -0
  27. package/dist/builtins/composer/field-record.d.ts +50 -0
  28. package/dist/builtins/composer/graft-host.d.ts +47 -0
  29. package/dist/builtins/composer/held-claims.d.ts +12 -0
  30. package/dist/builtins/composer/owned-fields-target.d.ts +55 -0
  31. package/dist/builtins/composer/slice-assembler.d.ts +10 -0
  32. package/dist/builtins/composer/source-readings.d.ts +122 -0
  33. package/dist/builtins/composer/source-tracker.d.ts +35 -42
  34. package/dist/builtins/console-logging/index.js +1 -1
  35. package/dist/builtins/console-logging/index.mjs +1 -1
  36. package/dist/builtins/core-blocks/composition-api.d.ts +7 -4
  37. package/dist/builtins/core-blocks/composition-peers.d.ts +9 -0
  38. package/dist/builtins/core-blocks/composition-sources.d.ts +16 -3
  39. package/dist/builtins/core-blocks/core-block-store.d.ts +8 -1
  40. package/dist/builtins/core-blocks/core-blocks.addon.d.ts +4 -3
  41. package/dist/builtins/core-blocks/core-blocks.addon.js +255 -79
  42. package/dist/builtins/core-blocks/core-blocks.addon.mjs +255 -79
  43. package/dist/builtins/device-manager/claimed-status-overlay.d.ts +12 -0
  44. package/dist/builtins/device-manager/device-manager.addon.d.ts +16 -0
  45. package/dist/builtins/device-manager/device-manager.addon.js +1825 -317
  46. package/dist/builtins/device-manager/device-manager.addon.mjs +1825 -317
  47. package/dist/builtins/device-manager/device-provider-context.d.ts +24 -2
  48. package/dist/builtins/device-manager/device-row-store.d.ts +2 -0
  49. package/dist/builtins/device-manager/device-state-claim-views.d.ts +77 -0
  50. package/dist/builtins/device-manager/device-state-claims.d.ts +43 -0
  51. package/dist/builtins/device-manager/device-state-mirror.d.ts +145 -61
  52. package/dist/builtins/device-manager/field-claims-index.d.ts +69 -0
  53. package/dist/builtins/device-manager/field-ownership.d.ts +53 -0
  54. package/dist/builtins/device-manager/migrate-device.d.ts +27 -0
  55. package/dist/builtins/device-manager/migrate-guard.d.ts +7 -0
  56. package/dist/builtins/device-manager/migrate-hardware-state.d.ts +55 -0
  57. package/dist/builtins/device-manager/migration-refused.d.ts +23 -0
  58. package/dist/builtins/device-manager/mirror-row-writer.d.ts +138 -0
  59. package/dist/builtins/device-manager/runtime-state-persist-gate.d.ts +4 -0
  60. package/dist/builtins/doorbell/binding-mirror.d.ts +1 -0
  61. package/dist/builtins/doorbell/doorbell-composition-migration.d.ts +59 -0
  62. package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +3 -0
  63. package/dist/builtins/doorbell/virtual-doorbell.addon.js +315 -10
  64. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +315 -10
  65. package/dist/builtins/hub-forwarder/index.js +1 -1
  66. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  67. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  68. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  69. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  70. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  71. package/dist/builtins/local-network/local-network.addon.js +1 -1
  72. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  73. package/dist/builtins/loki-logging/index.js +1 -1
  74. package/dist/builtins/loki-logging/index.mjs +1 -1
  75. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  76. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  77. package/dist/builtins/platform-probe/index.js +1 -1
  78. package/dist/builtins/platform-probe/index.mjs +1 -1
  79. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  80. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  81. package/dist/builtins/snapshot/index.js +1 -1
  82. package/dist/builtins/snapshot/index.mjs +1 -1
  83. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  84. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  85. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  86. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  87. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  88. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  89. package/dist/builtins/system-config/system-config.addon.js +1 -1
  90. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  91. package/dist/builtins/winston-logging/index.js +1 -1
  92. package/dist/builtins/winston-logging/index.mjs +1 -1
  93. package/dist/{child-cap-dispatch-CHTiHbsv.mjs → child-cap-dispatch-AAltfBUH.mjs} +1484 -1417
  94. package/dist/{child-cap-dispatch-Dyn_7ezH.js → child-cap-dispatch-DzcUd_ct.js} +1503 -1424
  95. package/dist/composition-sources-DYqQLRzF.js +122 -0
  96. package/dist/composition-sources-yonsRneP.mjs +111 -0
  97. package/dist/{dist-CkRwnhbo.js → dist-DVKv5i-e.js} +5396 -4331
  98. package/dist/{dist-Cj7pJmXq.mjs → dist-MzYJVCLE.mjs} +5330 -4331
  99. package/dist/index.js +137 -8
  100. package/dist/index.mjs +133 -8
  101. package/dist/kernel/capability-registry.d.ts +43 -1
  102. package/dist/kernel/index.d.ts +3 -1
  103. package/dist/kernel/moleculer/device-cap-proxy.d.ts +0 -10
  104. package/dist/kernel/status-overlay.d.ts +18 -0
  105. package/dist/kernel/transport/claimed-get-status.d.ts +15 -0
  106. package/dist/kernel/transport/index.d.ts +2 -0
  107. package/dist/kernel/transport/local-child-registry.d.ts +23 -0
  108. package/dist/kernel/transport/parent-unowned-call.d.ts +25 -0
  109. package/dist/{retired-settings-keys-C1duPikd.mjs → retired-settings-keys-8yY2sTc-.mjs} +1 -1
  110. package/dist/{retired-settings-keys-CGSuXhP8.js → retired-settings-keys-iY5I0nKM.js} +1 -1
  111. package/package.json +1 -1
@@ -1,7 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-CkRwnhbo.js");
3
+ const require_dist = require("../../dist-DVKv5i-e.js");
4
+ const require_composition_sources = require("../../composition-sources-DYqQLRzF.js");
4
5
  let node_crypto = require("node:crypto");
6
+ let _camstack_types_node = require("@camstack/types/node");
5
7
  let node_fs_promises = require("node:fs/promises");
6
8
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
7
9
  let node_path = require("node:path");
@@ -340,7 +342,7 @@ var BlockSupervisor = class {
340
342
  for (const { block } of compositions) await this.report(block.id, "failed", "no composer is available on this node — a composition cannot run here");
341
343
  return;
342
344
  }
343
- if (runners.find((r) => r.name === "composer")?.state === "failed") {
345
+ if (runners.find((r) => r.name === require_dist.COMPOSER_RUNNER_ID)?.state === "failed") {
344
346
  for (const { block } of compositions) await this.report(block.id, "failed", "the composer runner's crash circuit-breaker tripped — no composition runs until it is restarted");
345
347
  return;
346
348
  }
@@ -1013,15 +1015,103 @@ function armBlocksIntegration(options) {
1013
1015
  })() };
1014
1016
  }
1015
1017
  //#endregion
1018
+ //#region src/builtins/core-blocks/composition-peers.ts
1019
+ /**
1020
+ * The stored compositions a save is judged against, as graph peers (D663,
1021
+ * ruling S2). Each is identified by its BLOCK ID, targets the device it writes
1022
+ * — `composedDeviceRef(block.id)` for a new device, the customized device for an
1023
+ * existing one — and, on an existing device, names the fields it REPLACES, so
1024
+ * its self-read of a replaced field is the native value and never an edge.
1025
+ *
1026
+ * `replaced` is not stored anywhere: it is recomputed at save time from the
1027
+ * peer's target description (`planComposition(..., target)`), read in the same
1028
+ * batch as the candidate's. A peer whose device could not be read has unknown
1029
+ * replaced fields: it is OVER-approximated — nothing replaced, every self-read
1030
+ * an edge (edges only added, never a cycle hidden) — and its device is listed
1031
+ * in `unread`, so the validator refuses a cyclic component only when it holds
1032
+ * a candidate node, and names the device (`findCandidateCycles`). An unreadable
1033
+ * device never blocks an unrelated save. A peer whose device is GONE is treated
1034
+ * the same way (M-1): a stored peer validated earlier may still read it, and
1035
+ * assuming every field replaced would remove edges.
1036
+ *
1037
+ * The candidate's own old version is kept in the list: the graph drops it BY ID
1038
+ * (`compositionEdges`), never by target — that is how a second block on
1039
+ * the same device reaches `duplicate-target`.
1040
+ */
1041
+ function storedCompositions(blocks) {
1042
+ return blocks.flatMap((block) => {
1043
+ const composition = require_dist.coreBlockComposition(block);
1044
+ return composition === null ? [] : [{
1045
+ blockId: block.id,
1046
+ composition
1047
+ }];
1048
+ });
1049
+ }
1050
+ /** The existing devices the stored customizations write — each read once with the candidate's. */
1051
+ function storedCustomizationTargets(blocks) {
1052
+ return storedCompositions(blocks).flatMap(({ composition }) => composition.target.kind === "existing" ? [composition.target.device] : []);
1053
+ }
1054
+ /** The fields a stored customization replaces, or why they are unknown. */
1055
+ function replacedOf(stored, description, lookupCap) {
1056
+ if (description === void 0) return { error: "it was not read" };
1057
+ if (description.kind === "unreadable") return { error: description.error };
1058
+ if (description.kind === "absent") return { error: "the device does not exist" };
1059
+ return require_dist.replacedFieldKeys(require_dist.planComposition(stored.composition, lookupCap, description));
1060
+ }
1061
+ function peerOf(stored, targets, lookupCap, candidateBlockId) {
1062
+ const { blockId, composition } = stored;
1063
+ if (composition.target.kind === "new") return {
1064
+ peer: {
1065
+ blockId,
1066
+ target: require_dist.composedDeviceRef(blockId),
1067
+ composition
1068
+ },
1069
+ unread: null
1070
+ };
1071
+ const device = composition.target.device;
1072
+ const key = require_dist.compositionSourceKey(device);
1073
+ const replaced = replacedOf(stored, targets.get(key), lookupCap);
1074
+ if (!("error" in replaced)) return {
1075
+ peer: {
1076
+ blockId,
1077
+ target: device,
1078
+ composition,
1079
+ replaced
1080
+ },
1081
+ unread: null
1082
+ };
1083
+ return {
1084
+ peer: {
1085
+ blockId,
1086
+ target: device,
1087
+ composition,
1088
+ replaced: /* @__PURE__ */ new Set()
1089
+ },
1090
+ unread: blockId === candidateBlockId ? null : [key, replaced.error]
1091
+ };
1092
+ }
1093
+ function storedPeers(blocks, targets, lookupCap, candidateBlockId) {
1094
+ const read = storedCompositions(blocks).map((stored) => peerOf(stored, targets, lookupCap, candidateBlockId));
1095
+ return {
1096
+ peers: read.map((r) => r.peer),
1097
+ unread: new Map(read.flatMap((r) => r.unread === null ? [] : [r.unread]))
1098
+ };
1099
+ }
1100
+ //#endregion
1016
1101
  //#region src/builtins/core-blocks/composition-api.ts
1017
1102
  /**
1018
1103
  * The composition half of the `core-blocks` provider (D659). Flat methods,
1019
1104
  * spread into the provider: no nested sub-object.
1020
1105
  *
1021
- * Every save is VALIDATED against the live sources and every other stored
1022
- * composition first; a refusal names each problem by path and reason (D391)
1023
- * and stores nothing. The store's own refusals (not loaded, unknown block, a
1024
- * code block) propagate unchanged — this layer never swallows them.
1106
+ * Every save is VALIDATED against the live sources, its existing target (read
1107
+ * once: slim row + `getBindings`) and every stored composition first; a refusal
1108
+ * names each problem by path and reason (D391) and stores nothing. The store's
1109
+ * own refusals (not loaded, unknown block, a code block) propagate unchanged —
1110
+ * this layer never swallows them.
1111
+ *
1112
+ * A customization of an existing device (D663) has no name of its own: the
1113
+ * server derives it (`customizationBlockName`), refuses one the client sends,
1114
+ * and never lets it move to another device. One per device, by block id (S2).
1025
1115
  */
1026
1116
  var NOT_LOADED_REASON = "core_blocks has not loaded yet";
1027
1117
  function writeQueue() {
@@ -1048,24 +1138,30 @@ function refuseIfChanged(deps, blockId, expectedUpdatedAt) {
1048
1138
  } });
1049
1139
  throw new Error(`core-blocks: the composition "${stored.name}" changed since you read it: stored updatedAt ${stored.updatedAt}, yours ${expectedUpdatedAt} — reload and re-apply your edit`);
1050
1140
  }
1141
+ /** The device a composition writes, when it is an EXISTING one; null for a new device. */
1142
+ function existingTarget(composition) {
1143
+ return composition.target.kind === "existing" ? composition.target.device : null;
1144
+ }
1051
1145
  async function validateAgainstStore(deps, composition, blockId) {
1052
1146
  if (!deps.store.isLoaded()) throw new Error(`${NOT_LOADED_REASON} — a composition cannot be judged against the stored ones until it has; retry shortly`);
1053
1147
  const self = blockId === null ? null : require_dist.composedDeviceRef(blockId);
1054
- const peers = deps.store.list().flatMap((block) => {
1055
- const peer = require_dist.coreBlockComposition(block);
1056
- return peer === null || block.id === blockId ? [] : [{
1057
- target: require_dist.composedDeviceRef(block.id),
1058
- composition: peer
1059
- }];
1060
- });
1061
- const selfKey = self === null ? null : require_dist.compositionSourceKey(self);
1062
- const refs = require_dist.compositionSourceRefs(composition).filter((r) => require_dist.compositionSourceKey(r) !== selfKey);
1063
- const sources = await deps.describeSources(refs);
1148
+ const target = existingTarget(composition);
1149
+ const ownKeys = new Set([self, target].flatMap((ref) => ref === null ? [] : [require_dist.compositionSourceKey(ref)]));
1150
+ const refs = require_dist.compositionSourceRefs(composition).filter((r) => !ownKeys.has(require_dist.compositionSourceKey(r)));
1151
+ const blocks = deps.store.list();
1152
+ const read = await deps.describe(refs, [...target === null ? [] : [target], ...storedCustomizationTargets(blocks)]);
1153
+ const stored = storedPeers(blocks, read.targets, deps.lookupCap, blockId);
1064
1154
  return require_dist.validateComposition(composition, {
1065
1155
  lookupCap: deps.lookupCap,
1066
- sources,
1156
+ sources: read.sources,
1067
1157
  self,
1068
- peers
1158
+ blockId,
1159
+ target: target === null ? null : read.targets.get(require_dist.compositionSourceKey(target)) ?? {
1160
+ kind: "unreadable",
1161
+ error: "it was not read"
1162
+ },
1163
+ peers: stored.peers,
1164
+ unreadTargets: stored.unread
1069
1165
  });
1070
1166
  }
1071
1167
  function describeProblems(validation) {
@@ -1100,6 +1196,16 @@ async function readStates(deps, blockIds) {
1100
1196
  }]));
1101
1197
  }
1102
1198
  }
1199
+ /**
1200
+ * One block's composer state. A block the answer leaves out is UNREACHABLE,
1201
+ * never folded into "no composition" (D315, M-4).
1202
+ */
1203
+ async function readState(deps, blockId) {
1204
+ return (await readStates(deps, [blockId])).get(blockId) ?? {
1205
+ kind: "unreachable",
1206
+ error: "the composer state read returned no entry for the block"
1207
+ };
1208
+ }
1103
1209
  function toViews(blocks, states) {
1104
1210
  return blocks.flatMap((block) => {
1105
1211
  const state = states.get(block.id);
@@ -1109,6 +1215,98 @@ function toViews(blocks, states) {
1109
1215
  }];
1110
1216
  });
1111
1217
  }
1218
+ var NO_NAME_OF_ITS_OWN = "a customization has no name of its own — the device's name is shown; omit `name`";
1219
+ var NEW_DEVICE_NEEDS_NAME = "a new device needs a name";
1220
+ var DEVICE_IS_FIXED = "a customization's device cannot be changed — delete it and customize the other device";
1221
+ var NEVER_BECOMES_CUSTOMIZATION = "a composed device cannot become the customization of another device — create the customization instead";
1222
+ /**
1223
+ * The numeric id of a save's existing target, for the refusal line's tag
1224
+ * (D391: a line about a device carries `tags.deviceId`). Read only on a
1225
+ * refusal — one listing — and a failed read names itself on the line instead.
1226
+ */
1227
+ async function resolveTarget(deps, target) {
1228
+ try {
1229
+ const found = (await deps.describe([], [target])).targets.get(require_dist.compositionSourceKey(target));
1230
+ if (found?.kind === "found") return {
1231
+ kind: "device",
1232
+ deviceId: found.deviceId
1233
+ };
1234
+ return {
1235
+ kind: "unresolved",
1236
+ why: found?.kind ?? "not described"
1237
+ };
1238
+ } catch (err) {
1239
+ return {
1240
+ kind: "unresolved",
1241
+ why: require_dist.errMsg(err)
1242
+ };
1243
+ }
1244
+ }
1245
+ /** A save refused before validation, by name (D391): logged, then thrown. */
1246
+ async function refuseSave(deps, subject, target, reason) {
1247
+ const resolved = target === null ? null : await resolveTarget(deps, target);
1248
+ deps.logger.warn("composition save refused", {
1249
+ ...resolved?.kind === "device" ? { tags: { deviceId: resolved.deviceId } } : {},
1250
+ meta: {
1251
+ name: subject.name,
1252
+ blockId: subject.blockId,
1253
+ target: target === null ? null : require_dist.compositionSourceKey(target),
1254
+ ...resolved?.kind === "unresolved" ? { targetUnresolved: resolved.why } : {},
1255
+ reason
1256
+ }
1257
+ });
1258
+ throw new Error(`core-blocks: the composition "${subject.name}" was refused — ${reason}`);
1259
+ }
1260
+ /**
1261
+ * The name a new block is stored under (rule 1): a new device's own, required;
1262
+ * a customization's DERIVED from its device (ruling S2), never the client's.
1263
+ */
1264
+ async function nameOnCreate(deps, block) {
1265
+ const target = existingTarget(block.composition);
1266
+ if (target === null) {
1267
+ if (block.name === void 0) return refuseSave(deps, {
1268
+ name: "(unnamed)",
1269
+ blockId: null
1270
+ }, null, NEW_DEVICE_NEEDS_NAME);
1271
+ return block.name;
1272
+ }
1273
+ if (block.name !== void 0) return refuseSave(deps, {
1274
+ name: block.name,
1275
+ blockId: null
1276
+ }, target, NO_NAME_OF_ITS_OWN);
1277
+ return (0, _camstack_types_node.customizationBlockName)(target);
1278
+ }
1279
+ /**
1280
+ * A customization is pinned to its device and carries no client name (rule 2);
1281
+ * a composed device never turns into one — its name would be the client's. An
1282
+ * unknown or code block falls through to the store, whose refusal names it.
1283
+ */
1284
+ async function refuseRetarget(deps, blockId, patch) {
1285
+ const stored = deps.store.get(blockId);
1286
+ const current = stored === null ? null : require_dist.coreBlockComposition(stored);
1287
+ if (stored === null || current === null) return;
1288
+ const subject = {
1289
+ name: stored.name,
1290
+ blockId
1291
+ };
1292
+ const pinned = existingTarget(current);
1293
+ const next = patch.composition === void 0 ? pinned : existingTarget(patch.composition);
1294
+ if (pinned === null) {
1295
+ if (next !== null) return refuseSave(deps, subject, next, NEVER_BECOMES_CUSTOMIZATION);
1296
+ return;
1297
+ }
1298
+ if (patch.name !== void 0) return refuseSave(deps, subject, pinned, NO_NAME_OF_ITS_OWN);
1299
+ if (next === null || require_dist.compositionSourceKey(next) !== require_dist.compositionSourceKey(pinned)) return refuseSave(deps, subject, pinned, DEVICE_IS_FIXED);
1300
+ }
1301
+ /** The block customizing `device`, found by TARGET — never by its derived name (rule 4). */
1302
+ function customizationOf(blocks, device) {
1303
+ const key = require_dist.compositionSourceKey(device);
1304
+ return blocks.find((block) => {
1305
+ const composition = require_dist.coreBlockComposition(block);
1306
+ const target = composition === null ? null : existingTarget(composition);
1307
+ return target !== null && require_dist.compositionSourceKey(target) === key;
1308
+ }) ?? null;
1309
+ }
1112
1310
  /**
1113
1311
  * Every write runs validate-then-persist on ONE chain. Validation awaits a
1114
1312
  * source read before the save lands; two unserialized saves (A reads B, B
@@ -1119,12 +1317,16 @@ function buildCompositionApi(deps) {
1119
1317
  const serialized = writeQueue();
1120
1318
  return {
1121
1319
  createComposition: ({ block, caller }) => serialized(async () => {
1320
+ const name = await nameOnCreate(deps, block);
1122
1321
  const validation = await validateAgainstStore(deps, block.composition, null);
1123
1322
  if (!validation.ok) refuse(deps, {
1124
- name: block.name,
1323
+ name,
1125
1324
  blockId: null
1126
1325
  }, validation);
1127
- const created = await deps.store.createComposition(block, caller.userId);
1326
+ const created = await deps.store.createComposition({
1327
+ ...block,
1328
+ name
1329
+ }, caller.userId);
1128
1330
  deps.logger.info("composition created", { meta: {
1129
1331
  blockId: created.id,
1130
1332
  name: created.name,
@@ -1135,6 +1337,7 @@ function buildCompositionApi(deps) {
1135
1337
  }),
1136
1338
  updateComposition: ({ blockId, expectedUpdatedAt, block, caller }) => serialized(async () => {
1137
1339
  refuseIfChanged(deps, blockId, expectedUpdatedAt);
1340
+ await refuseRetarget(deps, blockId, block);
1138
1341
  if (block.composition !== void 0) {
1139
1342
  const validation = await validateAgainstStore(deps, block.composition, blockId);
1140
1343
  if (!validation.ok) refuse(deps, {
@@ -1160,10 +1363,12 @@ function buildCompositionApi(deps) {
1160
1363
  state: "loaded",
1161
1364
  view: null
1162
1365
  };
1163
- const [view] = toViews([block], await readStates(deps, [blockId]));
1164
1366
  return {
1165
1367
  state: "loaded",
1166
- view: view ?? null
1368
+ view: {
1369
+ block,
1370
+ state: await readState(deps, blockId)
1371
+ }
1167
1372
  };
1168
1373
  },
1169
1374
  listCompositions: async () => {
@@ -1181,61 +1386,31 @@ function buildCompositionApi(deps) {
1181
1386
  views: toViews(blocks, await readStates(deps, blocks.map((b) => b.id)))
1182
1387
  };
1183
1388
  },
1389
+ getCustomization: async ({ addonId, stableId }) => {
1390
+ if (!deps.store.isLoaded()) return {
1391
+ state: "not-loaded",
1392
+ reason: NOT_LOADED_REASON
1393
+ };
1394
+ const block = customizationOf(deps.store.list(), {
1395
+ addonId,
1396
+ stableId
1397
+ });
1398
+ if (block === null) return {
1399
+ state: "loaded",
1400
+ view: null
1401
+ };
1402
+ return {
1403
+ state: "loaded",
1404
+ view: {
1405
+ block,
1406
+ state: await readState(deps, block.id)
1407
+ }
1408
+ };
1409
+ },
1184
1410
  validateComposition: async ({ composition, blockId }) => validateAgainstStore(deps, composition, blockId ?? null)
1185
1411
  };
1186
1412
  }
1187
1413
  //#endregion
1188
- //#region src/builtins/core-blocks/composition-sources.ts
1189
- /**
1190
- * Describe a composition's sources for SAVE-time validation (D659): one
1191
- * `listAll` per owning addon, one `getBindings` per found device. A read that
1192
- * fails makes the source `unreadable`, and the save is REFUSED rather than
1193
- * stored unchecked. Refusing a save destroys nothing (D49).
1194
- *
1195
- * Sources are keyed by `{addonId, stableId}` — never a numeric device id,
1196
- * which is this node's allocation and not the composition's identity.
1197
- */
1198
- async function listOwners(ports, refs) {
1199
- const addons = [...new Set(refs.map((r) => r.addonId))];
1200
- const listed = await Promise.all(addons.map(async (addonId) => {
1201
- try {
1202
- return [addonId, await ports.listByAddon(addonId)];
1203
- } catch (err) {
1204
- return [addonId, { error: require_dist.errMsg(err) }];
1205
- }
1206
- }));
1207
- return new Map(listed);
1208
- }
1209
- async function describeOne(ports, ref, listing) {
1210
- if (listing === void 0) return {
1211
- kind: "unreadable",
1212
- error: `\`${ref.addonId}\` was not listed`
1213
- };
1214
- if ("error" in listing) return {
1215
- kind: "unreadable",
1216
- error: listing.error
1217
- };
1218
- const row = listing.find((r) => r.stableId === ref.stableId);
1219
- if (row === void 0) return { kind: "absent" };
1220
- try {
1221
- return {
1222
- kind: "found",
1223
- deviceId: row.id,
1224
- caps: new Set(await ports.boundCaps(row.id))
1225
- };
1226
- } catch (err) {
1227
- return {
1228
- kind: "unreadable",
1229
- error: require_dist.errMsg(err)
1230
- };
1231
- }
1232
- }
1233
- async function describeCompositionSources(ports, refs) {
1234
- const owners = await listOwners(ports, refs);
1235
- const described = await Promise.all(refs.map(async (ref) => [require_dist.compositionSourceKey(ref), await describeOne(ports, ref, owners.get(ref.addonId))]));
1236
- return new Map(described);
1237
- }
1238
- //#endregion
1239
1414
  //#region src/builtins/core-blocks/core-blocks.addon.ts
1240
1415
  /**
1241
1416
  * The `core-blocks` provider — CRUD over user-authored TypeScript, with the
@@ -1507,9 +1682,10 @@ var CoreBlocksAddon = class extends require_dist.BaseAddon {
1507
1682
  };
1508
1683
  }
1509
1684
  /**
1510
- * The composition methods (D659), flat. Sources are read over `ctx.api` at
1511
- * SAVE time — one listing per owning addon, one binding read per found
1512
- * device — and a read that fails refuses the save rather than skipping it.
1685
+ * The composition methods (D659), flat. Sources and existing targets are read
1686
+ * over `ctx.api` at SAVE time — one slim listing per owning addon, one binding
1687
+ * read per found device — and a read that fails refuses the save rather than
1688
+ * skipping it.
1513
1689
  */
1514
1690
  buildCompositionApi() {
1515
1691
  const requireApi = () => {
@@ -1519,13 +1695,13 @@ var CoreBlocksAddon = class extends require_dist.BaseAddon {
1519
1695
  };
1520
1696
  return buildCompositionApi({
1521
1697
  store: this.requireStore(),
1522
- describeSources: (refs) => describeCompositionSources({
1698
+ describe: (sources, targets) => require_composition_sources.describeCompositionSources({
1523
1699
  listByAddon: async (addonId) => requireApi().deviceManager.listAll.query({
1524
1700
  addonId,
1525
1701
  projection: "slim"
1526
1702
  }),
1527
- boundCaps: async (deviceId) => (await requireApi().deviceManager.getBindings.query({ deviceId })).entries.map((e) => e.capName)
1528
- }, refs),
1703
+ bindings: async (deviceId) => (await requireApi().deviceManager.getBindings.query({ deviceId })).entries
1704
+ }, sources, targets),
1529
1705
  lookupCap: require_dist.buildCapabilityLookup(require_dist.ALL_CAPABILITY_DEFINITIONS),
1530
1706
  composerStates: async (blockIds) => (await requireApi().composer.getStates.query({ blockIds: [...blockIds] })).states,
1531
1707
  convergeSoon: () => this.convergeSoon(),