@camstack/types 1.2.263 → 1.2.265

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 (33) hide show
  1. package/dist/automation/recipe.d.ts +1 -1
  2. package/dist/capabilities/composer.cap.d.ts +328 -0
  3. package/dist/capabilities/control.cap.d.ts +4 -4
  4. package/dist/capabilities/core-blocks.cap.d.ts +2339 -0
  5. package/dist/capabilities/custom-model-registry.cap.d.ts +44 -2
  6. package/dist/capabilities/day-night.cap.d.ts +8 -8
  7. package/dist/capabilities/device-extension.cap.d.ts +8 -8
  8. package/dist/capabilities/index.d.ts +4 -2
  9. package/dist/capabilities/model-convert.cap.d.ts +22 -1
  10. package/dist/capabilities/model-distributor.cap.d.ts +44 -2
  11. package/dist/capabilities/notification-rules.cap.d.ts +74 -74
  12. package/dist/capabilities/osd-manager.cap.d.ts +13 -13
  13. package/dist/composition/composition-graph.d.ts +36 -0
  14. package/dist/composition/composition-report.d.ts +264 -0
  15. package/dist/composition/composition.d.ts +362 -0
  16. package/dist/composition/evaluate-field.d.ts +63 -0
  17. package/dist/composition/examples.d.ts +10 -0
  18. package/dist/composition/expression-kinds.d.ts +36 -0
  19. package/dist/composition/index.d.ts +12 -0
  20. package/dist/composition/state-fields.d.ts +19 -0
  21. package/dist/composition/validate-composition.d.ts +46 -0
  22. package/dist/device/schema-fields.d.ts +3 -0
  23. package/dist/expression/builtins.d.ts +30 -2
  24. package/dist/generated/addon-api.d.ts +57 -0
  25. package/dist/generated/capability-router-map.d.ts +5 -2
  26. package/dist/generated/method-access-map.d.ts +1 -1
  27. package/dist/generated/system-proxy.d.ts +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +1388 -83
  30. package/dist/index.mjs +1314 -84
  31. package/dist/notification/rule-kinds.d.ts +3 -2
  32. package/dist/types/models.d.ts +80 -2
  33. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5215,6 +5215,50 @@ function inferModelProvider(entry) {
5215
5215
  if (/frigate/i.test(haystack)) return "frigate";
5216
5216
  return "custom";
5217
5217
  }
5218
+ var MODEL_HOSTING = [
5219
+ "camstack-hf",
5220
+ "third-party",
5221
+ "built-in"
5222
+ ];
5223
+ var ModelLicenseRecordSchema = zod.z.object({
5224
+ /** SPDX id of the WEIGHTS' terms, a `LicenseRef-*` for non-SPDX terms, or `UNKNOWN`. */
5225
+ weights: zod.z.string().min(1),
5226
+ /** SPDX id of the upstream code that defines / trained the network. */
5227
+ code: zod.z.string().min(1).optional(),
5228
+ /** Training data whose terms add obligations (attribution, non-commercial). */
5229
+ data: zod.z.object({
5230
+ name: zod.z.string().min(1),
5231
+ terms: zod.z.string().min(1),
5232
+ url: zod.z.string().url().optional()
5233
+ }).optional(),
5234
+ /** The project the weights come from. Required on built-in entries (guard). */
5235
+ upstream: zod.z.object({
5236
+ name: zod.z.string().min(1),
5237
+ url: zod.z.string().url()
5238
+ }).optional(),
5239
+ /** Where the licence text governing the weights is published. */
5240
+ url: zod.z.string().url().optional(),
5241
+ /** Attribution the licence REQUIRES, verbatim. */
5242
+ attribution: zod.z.string().min(1).optional(),
5243
+ /** What CamStack changed. Required when `hosting` is `camstack-hf` (guard). */
5244
+ modifications: zod.z.string().min(1).optional(),
5245
+ /** Who serves the file a node downloads. */
5246
+ hosting: zod.z.enum(MODEL_HOSTING).optional(),
5247
+ /** True when the weights or their data forbid commercial use. */
5248
+ nonCommercial: zod.z.boolean().optional()
5249
+ });
5250
+ /**
5251
+ * The licence of a model, as a record rather than a bare SPDX string: a
5252
+ * permissive CODE licence routinely sits on top of restrictive WEIGHTS or
5253
+ * DATA, and the obligations (attribution, modification notices, source offers)
5254
+ * are per upstream. A persisted custom-model row written when this field was a
5255
+ * string still parses — the string is lifted into `{ weights }`.
5256
+ *
5257
+ * Every licence surface (the admin "Models and licenses" page,
5258
+ * `THIRD_PARTY_MODELS.md`, the Hugging Face model table) is GENERATED from
5259
+ * these records by `scripts/gen-model-licenses.ts` (D661).
5260
+ */
5261
+ var ModelLicenseSchema = zod.z.preprocess((value) => typeof value === "string" ? { weights: value } : value, ModelLicenseRecordSchema);
5218
5262
  var ModelCatalogEntrySchema = zod.z.object({
5219
5263
  id: zod.z.string(),
5220
5264
  name: zod.z.string(),
@@ -5303,11 +5347,11 @@ var ModelCatalogEntrySchema = zod.z.object({
5303
5347
  p95LatencyMs: zod.z.record(zod.z.string(), zod.z.number()).optional()
5304
5348
  }).optional(),
5305
5349
  /**
5306
- * SPDX-ish license id of the model weights (e.g. `AGPL-3.0` for Ultralytics
5307
- * YOLO26, `GPL-3.0` for YOLOv9, `Apache-2.0` for D-FINE/RF-DETR). Matters for
5308
- * the retraining addon and any future commercial distribution.
5350
+ * The model's licence record — see {@link ModelLicenseSchema}. Optional in
5351
+ * the schema (a custom model's author may state none); REQUIRED and complete
5352
+ * on every built-in catalog entry (`scripts/check-catalog-licence.ts`).
5309
5353
  */
5310
- license: zod.z.string().optional(),
5354
+ license: ModelLicenseSchema.optional(),
5311
5355
  /**
5312
5356
  * Variant-selector grouping. The UI groups models by `family` + `tier` and
5313
5357
  * offers `precision` / `optimization` as variant axes WITHIN a tier — so all
@@ -5503,23 +5547,6 @@ var ExpressionEvalError = class extends Error {
5503
5547
  };
5504
5548
  //#endregion
5505
5549
  //#region src/expression/builtins.ts
5506
- /**
5507
- * Frozen, null-prototype builtin function table for the expression engine
5508
- * (spec §4 rule 4). The table is the SOLE surface of callable functions: the
5509
- * parser rejects any callee not in it, and the evaluator gates each call on an
5510
- * own-property check against it.
5511
- *
5512
- * Because the object has a NULL prototype AND is `Object.freeze`d:
5513
- * - it cannot be polluted (no `__proto__` / `constructor` write reaches it);
5514
- * - a lookup for `toString` / `hasOwnProperty` / `constructor` finds NOTHING
5515
- * (there is no `Object.prototype` in the chain), so those names are not
5516
- * callable — they are simply "unknown function" at parse time.
5517
- *
5518
- * Every numeric argument is validated as a finite number and every numeric
5519
- * RESULT is re-checked finite, so `/0`, `sqrt(-1)` (→ NaN) and overflow
5520
- * (`pow(10,400)` → Infinity) all raise `ExpressionEvalError` and fail the link
5521
- * closed rather than emitting a garbage value.
5522
- */
5523
5550
  function asFiniteNumber(value, name, index) {
5524
5551
  if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
5525
5552
  return value;
@@ -5535,6 +5562,10 @@ function finiteResult(value, name) {
5535
5562
  function allFiniteNumbers(args, name) {
5536
5563
  return args.map((a, idx) => asFiniteNumber(a, name, idx));
5537
5564
  }
5565
+ function asBoolean$1(value, name, index) {
5566
+ if (typeof value !== "boolean") throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a boolean`);
5567
+ return value;
5568
+ }
5538
5569
  var INF = Number.POSITIVE_INFINITY;
5539
5570
  var table = {
5540
5571
  min: {
@@ -5635,6 +5666,40 @@ var table = {
5635
5666
  if (from === to) return x;
5636
5667
  throw new ExpressionEvalError("convert: unit conversion table not installed");
5637
5668
  }
5669
+ },
5670
+ any: {
5671
+ minArgs: 1,
5672
+ maxArgs: INF,
5673
+ apply: (args) => args.map((a, i) => asBoolean$1(a, "any", i)).some((b) => b)
5674
+ },
5675
+ all: {
5676
+ minArgs: 1,
5677
+ maxArgs: INF,
5678
+ apply: (args) => args.map((a, i) => asBoolean$1(a, "all", i)).every((b) => b)
5679
+ },
5680
+ within: {
5681
+ minArgs: 2,
5682
+ maxArgs: 2,
5683
+ apply: (args, hooks) => {
5684
+ const windowMs = asFiniteNumber(args[1], "within", 1);
5685
+ if (windowMs < 0) throw new ExpressionEvalError("within: the window must not be negative");
5686
+ const at = args[0];
5687
+ if (at === null) return false;
5688
+ const ts = asFiniteNumber(at, "within", 0);
5689
+ const now = hooks.now;
5690
+ if (now === void 0 || !Number.isFinite(now)) throw new ExpressionEvalError("within: no clock was supplied to this evaluation");
5691
+ const inside = now - ts <= windowMs;
5692
+ if (inside) hooks.noteDeadline?.(ts + windowMs + 1);
5693
+ return inside;
5694
+ }
5695
+ },
5696
+ latest: {
5697
+ minArgs: 1,
5698
+ maxArgs: INF,
5699
+ apply: (args) => {
5700
+ const present = args.flatMap((a, i) => a === null ? [] : [asFiniteNumber(a, "latest", i)]);
5701
+ return present.length === 0 ? null : finiteResult(Math.max(...present), "latest");
5702
+ }
5638
5703
  }
5639
5704
  };
5640
5705
  /** Frozen, null-prototype builtin table. */
@@ -6239,10 +6304,17 @@ function evaluateExpressionSource(expr, bindingValues, now, opts) {
6239
6304
  ...bindingValues,
6240
6305
  ["now"]: now
6241
6306
  });
6307
+ const hooks = {
6308
+ ...opts?.hooks,
6309
+ now
6310
+ };
6242
6311
  try {
6243
6312
  return {
6244
6313
  ok: true,
6245
- value: evaluateAst(compiled.parsed.ast, scope, opts)
6314
+ value: evaluateAst(compiled.parsed.ast, scope, {
6315
+ ...opts,
6316
+ hooks
6317
+ })
6246
6318
  };
6247
6319
  } catch (err) {
6248
6320
  return {
@@ -10054,6 +10126,277 @@ function kebabToCamel(s) {
10054
10126
  return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
10055
10127
  }
10056
10128
  //#endregion
10129
+ //#region src/composition/composition.ts
10130
+ /**
10131
+ * A COMPOSITION: a device assembled from other devices' state (D659).
10132
+ *
10133
+ * The data half of the composed-devices spec §1. A composition is stored inside
10134
+ * a core block (`CoreBlock.source.kind === 'composition'`) and run by the shared
10135
+ * `composer` runner. It names a TARGET and, for each capability of the target, a
10136
+ * FIELD MAP: every state field has exactly one source.
10137
+ *
10138
+ * Sources are keyed by `(addonId, stableId)`, NEVER by numeric device id. A
10139
+ * resync reissues numeric ids, and a composition that stored them would be
10140
+ * orphaned by the first re-adoption (the linked-devices lesson). A `stableId` is
10141
+ * unique only within its owning addon, so the owner travels with it.
10142
+ *
10143
+ * Later slices EXTEND these unions and do not reshape them. The variants a later
10144
+ * slice implements are already in the wire shape, and `validateComposition`
10145
+ * refuses them BY NAME until then:
10146
+ * target `existing` → slice 2 · feature `passthrough` → slice 3 ·
10147
+ * `commands` (`forward`) → slice 3 · source `code` → slice 4.
10148
+ */
10149
+ /** The owner of every composed device: the `composer` builtin. */
10150
+ var COMPOSER_ADDON_ID = "composer";
10151
+ /** A composed device's stableId is this prefix plus the block id. */
10152
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
10153
+ /** A device with more capabilities than this is two devices. */
10154
+ var MAX_COMPOSITION_FEATURES = 16;
10155
+ var MAX_COMPOSITION_FIELDS_PER_FEATURE = 32;
10156
+ /** Longest snippet a `code` source may carry (slice 4). */
10157
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
10158
+ var CompositionSourceRefSchema = zod.z.object({
10159
+ /** The owning addon; `stableId` is unique only within it. */
10160
+ addonId: zod.z.string().min(1),
10161
+ stableId: zod.z.string().min(1)
10162
+ });
10163
+ /** One field of one capability of one source device. */
10164
+ var CompositionFieldReadSchema = zod.z.object({
10165
+ source: CompositionSourceRefSchema,
10166
+ cap: zod.z.string().min(1),
10167
+ /** Dotted path into the source cap's runtime-state slice. */
10168
+ fieldPath: zod.z.string().min(1)
10169
+ });
10170
+ /** `from`: copy one source field verbatim. It is also an expression's `from` binding. */
10171
+ var CompositionFromSourceSchema = CompositionFieldReadSchema.extend({ kind: zod.z.literal("from") });
10172
+ /** A constant binding. No device is read. */
10173
+ var CompositionLiteralBindingSchema = zod.z.object({
10174
+ kind: zod.z.literal("literal"),
10175
+ value: zod.z.union([
10176
+ zod.z.string(),
10177
+ zod.z.number(),
10178
+ zod.z.boolean(),
10179
+ zod.z.null()
10180
+ ])
10181
+ });
10182
+ var CompositionBindingSchema = zod.z.discriminatedUnion("kind", [CompositionFromSourceSchema, CompositionLiteralBindingSchema]);
10183
+ /**
10184
+ * `expression`: a formula over named bindings, in the salvaged expression
10185
+ * engine. Validated at parse by the SAME function every other consumer runs,
10186
+ * so the editor and the store cannot disagree.
10187
+ */
10188
+ var CompositionExpressionSourceSchema = zod.z.object({
10189
+ kind: zod.z.literal("expression"),
10190
+ expr: zod.z.string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
10191
+ bindings: zod.z.record(zod.z.string().regex(EXPRESSION_IDENTIFIER_RE), CompositionBindingSchema)
10192
+ }).superRefine((src, ctx) => {
10193
+ const err = validateExpressionSource(src);
10194
+ if (err !== null) ctx.addIssue({
10195
+ code: "custom",
10196
+ message: err,
10197
+ path: ["expr"]
10198
+ });
10199
+ });
10200
+ /** `code`: RESERVED for slice 4 (own runner, one-way eject). */
10201
+ var CompositionCodeSourceSchema = zod.z.object({
10202
+ kind: zod.z.literal("code"),
10203
+ code: zod.z.string().min(1).max(MAX_COMPOSITION_CODE_LENGTH)
10204
+ });
10205
+ var CompositionFieldSourceSchema = zod.z.discriminatedUnion("kind", [
10206
+ CompositionFromSourceSchema,
10207
+ CompositionExpressionSourceSchema,
10208
+ CompositionCodeSourceSchema
10209
+ ]);
10210
+ /** `forward`: RESERVED for slice 3. Argument mapping arrives there as an additive optional `args`. */
10211
+ var CompositionForwardCommandSchema = zod.z.object({
10212
+ kind: zod.z.literal("forward"),
10213
+ source: CompositionSourceRefSchema,
10214
+ cap: zod.z.string().min(1),
10215
+ method: zod.z.string().min(1)
10216
+ });
10217
+ var CompositionCommandTargetSchema = zod.z.discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
10218
+ var CompositionFieldsFeatureSchema = zod.z.object({
10219
+ kind: zod.z.literal("fields"),
10220
+ cap: zod.z.string().min(1),
10221
+ fields: zod.z.record(zod.z.string().min(1), CompositionFieldSourceSchema).refine((fields) => Object.keys(fields).length <= 32, { message: `at most 32 fields per capability` }),
10222
+ /** RESERVED for slice 3; refused by the validator when non-empty. */
10223
+ commands: zod.z.record(zod.z.string().min(1), CompositionCommandTargetSchema).optional()
10224
+ });
10225
+ /** RESERVED for slice 3: a media / data-plane cap, whole, from one source. */
10226
+ var CompositionPassthroughFeatureSchema = zod.z.object({
10227
+ kind: zod.z.literal("passthrough"),
10228
+ cap: zod.z.string().min(1),
10229
+ source: CompositionSourceRefSchema
10230
+ });
10231
+ var CompositionFeatureSchema = zod.z.discriminatedUnion("kind", [CompositionFieldsFeatureSchema, CompositionPassthroughFeatureSchema]);
10232
+ /**
10233
+ * A NEW device. It has no `name` of its own: the block's name IS the device's
10234
+ * name (2026-08-08). A second copy here would be a second writer of one string (D62).
10235
+ */
10236
+ var CompositionNewTargetSchema = zod.z.object({
10237
+ kind: zod.z.literal("new"),
10238
+ type: zod.z.enum(require_sleep.DeviceType),
10239
+ role: zod.z.enum(require_sleep.DeviceRole).optional()
10240
+ });
10241
+ /** RESERVED for slice 2: customize an existing device. */
10242
+ var CompositionExistingTargetSchema = zod.z.object({
10243
+ kind: zod.z.literal("existing"),
10244
+ device: CompositionSourceRefSchema
10245
+ });
10246
+ var CompositionTargetSchema = zod.z.discriminatedUnion("kind", [CompositionNewTargetSchema, CompositionExistingTargetSchema]);
10247
+ var CompositionSchema = zod.z.object({
10248
+ target: CompositionTargetSchema,
10249
+ features: zod.z.array(CompositionFeatureSchema).min(1).max(16)
10250
+ }).superRefine((composition, ctx) => {
10251
+ const seen = /* @__PURE__ */ new Set();
10252
+ composition.features.forEach((feature, index) => {
10253
+ if (seen.has(feature.cap)) ctx.addIssue({
10254
+ code: "custom",
10255
+ message: `capability \`${feature.cap}\` is composed twice — one feature per capability`,
10256
+ path: [
10257
+ "features",
10258
+ index,
10259
+ "cap"
10260
+ ]
10261
+ });
10262
+ seen.add(feature.cap);
10263
+ });
10264
+ });
10265
+ function compositionSourceKey(ref) {
10266
+ return `${ref.addonId}/${ref.stableId}`;
10267
+ }
10268
+ function compositionSliceKey(ref, cap) {
10269
+ return `${compositionSourceKey(ref)}#${cap}`;
10270
+ }
10271
+ function compositionReadKey(read) {
10272
+ return `${compositionSliceKey(read.source, read.cap)}.${read.fieldPath}`;
10273
+ }
10274
+ function composedDeviceStableId(blockId) {
10275
+ return `${COMPOSED_DEVICE_STABLE_ID_PREFIX}${blockId}`;
10276
+ }
10277
+ function composedDeviceRef(blockId) {
10278
+ return {
10279
+ addonId: COMPOSER_ADDON_ID,
10280
+ stableId: composedDeviceStableId(blockId)
10281
+ };
10282
+ }
10283
+ /** Every device field a source reads: `from` is one read, an expression is its `from` bindings. */
10284
+ function fieldReads(source) {
10285
+ switch (source.kind) {
10286
+ case "from": return [{
10287
+ source: source.source,
10288
+ cap: source.cap,
10289
+ fieldPath: source.fieldPath
10290
+ }];
10291
+ case "expression": return Object.values(source.bindings).flatMap((binding) => binding.kind === "from" ? [{
10292
+ source: binding.source,
10293
+ cap: binding.cap,
10294
+ fieldPath: binding.fieldPath
10295
+ }] : []);
10296
+ case "code": return [];
10297
+ }
10298
+ }
10299
+ //#endregion
10300
+ //#region src/composition/composition-report.ts
10301
+ /**
10302
+ * What the validator says about a composition and what the composer says about
10303
+ * a running one: the wire shapes the API returns (D659).
10304
+ */
10305
+ var CompositionProblemCodeSchema = zod.z.enum([
10306
+ "reserved-variant",
10307
+ "unknown-capability",
10308
+ "not-composable",
10309
+ "wrong-device-type",
10310
+ "has-commands",
10311
+ "unknown-field",
10312
+ "nested-field",
10313
+ "missing-field",
10314
+ "kind-mismatch",
10315
+ "may-be-null",
10316
+ "source-missing",
10317
+ "source-unreadable",
10318
+ "source-capability-missing",
10319
+ "source-field-unknown",
10320
+ "expression-invalid",
10321
+ "cycle",
10322
+ "duplicate-target"
10323
+ ]);
10324
+ /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
10325
+ var CompositionProblemSchema = zod.z.object({
10326
+ code: CompositionProblemCodeSchema,
10327
+ path: zod.z.string(),
10328
+ message: zod.z.string()
10329
+ });
10330
+ var CompositionFieldKindSchema = zod.z.enum([
10331
+ "string",
10332
+ "number",
10333
+ "boolean",
10334
+ "enum"
10335
+ ]);
10336
+ /** How a field says "unavailable": `null` when its schema allows it, else the device goes offline (D393). */
10337
+ var CompositionUnavailableModeSchema = zod.z.enum(["null", "offline"]);
10338
+ /**
10339
+ * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
10340
+ * field of its cap changes value · `constant` reads nothing, computed once.
10341
+ */
10342
+ var CompositionFieldRoleSchema = zod.z.enum([
10343
+ "derived",
10344
+ "clock",
10345
+ "constant"
10346
+ ]);
10347
+ var CompositionFieldPlanSchema = zod.z.object({
10348
+ cap: zod.z.string(),
10349
+ path: zod.z.string(),
10350
+ /** Index into `composition.features` this field's capability came from —
10351
+ * lets a source/cycle problem name its path as `features[N].fields.<path>`
10352
+ * (the same convention `planComposition`'s own problems already use). */
10353
+ featureIndex: zod.z.number().int().nonnegative(),
10354
+ kind: CompositionFieldKindSchema,
10355
+ nullable: zod.z.boolean(),
10356
+ required: zod.z.boolean(),
10357
+ unavailable: CompositionUnavailableModeSchema,
10358
+ role: CompositionFieldRoleSchema,
10359
+ source: CompositionFieldSourceSchema,
10360
+ reads: zod.z.array(CompositionFieldReadSchema)
10361
+ });
10362
+ var CompositionValidationSchema = zod.z.object({
10363
+ ok: zod.z.boolean(),
10364
+ problems: zod.z.array(CompositionProblemSchema),
10365
+ fields: zod.z.array(CompositionFieldPlanSchema)
10366
+ });
10367
+ /** Mirrors `CoreBlockStatus`, pinned by `core-blocks-source.spec.ts` (Task 11). */
10368
+ var CompositionRunStatusSchema = zod.z.enum([
10369
+ "stopped",
10370
+ "starting",
10371
+ "running",
10372
+ "failed"
10373
+ ]);
10374
+ var CompositionFieldStateKindSchema = zod.z.enum([
10375
+ "fresh",
10376
+ "stale",
10377
+ "unavailable"
10378
+ ]);
10379
+ var CompositionFieldStateSchema = zod.z.object({
10380
+ cap: zod.z.string(),
10381
+ path: zod.z.string(),
10382
+ state: CompositionFieldStateKindSchema,
10383
+ /** `null` whenever `state === 'unavailable'`; never a plausible stand-in (D393). */
10384
+ value: zod.z.union([
10385
+ zod.z.string(),
10386
+ zod.z.number(),
10387
+ zod.z.boolean(),
10388
+ zod.z.null()
10389
+ ]),
10390
+ reason: zod.z.string().optional()
10391
+ });
10392
+ var CompositionBlockStateSchema = zod.z.object({
10393
+ blockId: zod.z.string(),
10394
+ deviceId: zod.z.number().nullable(),
10395
+ status: CompositionRunStatusSchema,
10396
+ reason: zod.z.string().optional(),
10397
+ fields: zod.z.array(CompositionFieldStateSchema)
10398
+ });
10399
+ //#endregion
10057
10400
  //#region src/capabilities/core-blocks.cap.ts
10058
10401
  /**
10059
10402
  * core-blocks — user-authored TypeScript, stored in the kernel and executed in
@@ -10139,6 +10482,30 @@ var CoreBlockInputSchema = zod.z.object({
10139
10482
  */
10140
10483
  integrationId: zod.z.string().optional()
10141
10484
  });
10485
+ /**
10486
+ * What a block IS (D659). Absent means `code` — every row stored before this
10487
+ * union keeps working unchanged.
10488
+ * - `code`: the TypeScript in `code`, compiled and run in its own runner;
10489
+ * - `recipe`: RESERVED (automations), stored shape only; refused at run by name;
10490
+ * - `composition`: declarative, run by the shared `composer`; `code` is `''`.
10491
+ */
10492
+ var CoreBlockSourceSchema = zod.z.discriminatedUnion("kind", [
10493
+ zod.z.object({ kind: zod.z.literal("code") }),
10494
+ zod.z.object({
10495
+ kind: zod.z.literal("recipe"),
10496
+ recipe: AutomationRecipeSchema
10497
+ }),
10498
+ zod.z.object({
10499
+ kind: zod.z.literal("composition"),
10500
+ composition: CompositionSchema
10501
+ })
10502
+ ]);
10503
+ /** The client-authored half of a composition block. No `code`, no placement (hub-only; Phase 7). */
10504
+ var CompositionBlockInputSchema = zod.z.object({
10505
+ name: zod.z.string().min(1).max(120),
10506
+ enabled: zod.z.boolean(),
10507
+ composition: CompositionSchema
10508
+ });
10142
10509
  /** A stored block. */
10143
10510
  var CoreBlockSchema = CoreBlockInputSchema.extend({
10144
10511
  id: zod.z.string(),
@@ -10154,8 +10521,46 @@ var CoreBlockSchema = CoreBlockInputSchema.extend({
10154
10521
  */
10155
10522
  lastError: zod.z.string().optional(),
10156
10523
  /** Ms epoch of the last state change. */
10157
- lastChangedAt: zod.z.number()
10524
+ lastChangedAt: zod.z.number(),
10525
+ /** What the block is. Absent means `code` (D659). */
10526
+ source: CoreBlockSourceSchema.optional()
10527
+ });
10528
+ function coreBlockSourceKind(block) {
10529
+ return block.source?.kind ?? "code";
10530
+ }
10531
+ function coreBlockComposition(block) {
10532
+ return block.source?.kind === "composition" ? block.source.composition : null;
10533
+ }
10534
+ /** What the composer said, or that it could not be asked. Unknown never looks like absent (D315). */
10535
+ var CompositionStateReadSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
10536
+ kind: zod.z.literal("answered"),
10537
+ state: CompositionBlockStateSchema.nullable()
10538
+ }), zod.z.object({
10539
+ kind: zod.z.literal("unreachable"),
10540
+ error: zod.z.string()
10541
+ })]);
10542
+ /** A stored composition block and what the composer says about it. */
10543
+ var CompositionViewSchema = zod.z.object({
10544
+ block: CoreBlockSchema,
10545
+ state: CompositionStateReadSchema
10158
10546
  });
10547
+ /**
10548
+ * A read of the stored compositions. `not-loaded` while the `core_blocks`
10549
+ * table has not been READ: an empty answer then would make "not yet known"
10550
+ * look like "none", and invite the operator to recreate what exists (D315).
10551
+ */
10552
+ var CompositionsNotLoadedSchema = zod.z.object({
10553
+ state: zod.z.literal("not-loaded"),
10554
+ reason: zod.z.string()
10555
+ });
10556
+ var CompositionListReadSchema = zod.z.discriminatedUnion("state", [zod.z.object({
10557
+ state: zod.z.literal("loaded"),
10558
+ views: zod.z.array(CompositionViewSchema)
10559
+ }), CompositionsNotLoadedSchema]);
10560
+ var CompositionGetReadSchema = zod.z.discriminatedUnion("state", [zod.z.object({
10561
+ state: zod.z.literal("loaded"),
10562
+ view: CompositionViewSchema.nullable()
10563
+ }), CompositionsNotLoadedSchema]);
10159
10564
  /** What a compile attempt produced. */
10160
10565
  var CoreBlockCompileResultSchema = zod.z.object({
10161
10566
  ok: zod.z.boolean(),
@@ -10237,7 +10642,119 @@ var coreBlocksCapability = {
10237
10642
  getTypeDefs: require_sleep.method(zod.z.object({}), zod.z.object({ libs: zod.z.array(zod.z.object({
10238
10643
  filePath: zod.z.string(),
10239
10644
  content: zod.z.string()
10240
- })) }), { auth: "admin" })
10645
+ })) }), { auth: "admin" }),
10646
+ /**
10647
+ * Create a composition block (D659). VALIDATED first against the live
10648
+ * sources and every stored composition; a refusal names every problem by
10649
+ * path and reason (D391) and stores nothing.
10650
+ */
10651
+ createComposition: require_sleep.method(zod.z.object({ block: CompositionBlockInputSchema }), zod.z.object({ block: CoreBlockSchema }), {
10652
+ kind: "mutation",
10653
+ auth: "admin",
10654
+ caller: "required"
10655
+ }),
10656
+ /**
10657
+ * Patch a composition block. A changed `composition` is validated exactly
10658
+ * as on create. `expectedUpdatedAt` is the `updatedAt` the caller READ: a
10659
+ * block changed since is refused by name, never silently overwritten.
10660
+ */
10661
+ updateComposition: require_sleep.method(zod.z.object({
10662
+ blockId: zod.z.string(),
10663
+ expectedUpdatedAt: zod.z.number(),
10664
+ block: CompositionBlockInputSchema.partial()
10665
+ }), zod.z.object({ block: CoreBlockSchema }), {
10666
+ kind: "mutation",
10667
+ auth: "admin",
10668
+ caller: "required"
10669
+ }),
10670
+ /** One composition and the composer's state for it, or `not-loaded` (D315). */
10671
+ getComposition: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), CompositionGetReadSchema, { auth: "admin" }),
10672
+ listCompositions: require_sleep.method(zod.z.object({}), CompositionListReadSchema, { auth: "admin" }),
10673
+ /**
10674
+ * Validate without saving — the same verdict the save reaches. A mutation
10675
+ * for its body size; it writes nothing, so its access is `view`.
10676
+ */
10677
+ validateComposition: require_sleep.method(zod.z.object({
10678
+ composition: CompositionSchema,
10679
+ blockId: zod.z.string().optional()
10680
+ }), CompositionValidationSchema, {
10681
+ kind: "mutation",
10682
+ auth: "admin",
10683
+ access: "view"
10684
+ })
10685
+ }
10686
+ };
10687
+ //#endregion
10688
+ //#region src/capabilities/composer.cap.ts
10689
+ /**
10690
+ * composer — runs every DECLARATIVE composition (D659).
10691
+ *
10692
+ * Internal and admin-only: its one caller is `core-blocks`' supervisor, which
10693
+ * sends the WHOLE set of composition blocks on every converge (idempotent),
10694
+ * and reads their state for the API. Nothing else talks to it.
10695
+ */
10696
+ /** The composer's runner id, `resolveRunnerId(decl, 'composer')`; pinned by `composer-manifest.spec.ts`. */
10697
+ var COMPOSER_RUNNER_ID = COMPOSER_ADDON_ID;
10698
+ var ComposerBlockSchema = zod.z.object({
10699
+ id: zod.z.string().min(1),
10700
+ name: zod.z.string().min(1).max(120),
10701
+ enabled: zod.z.boolean(),
10702
+ updatedAt: zod.z.number(),
10703
+ composition: CompositionSchema
10704
+ });
10705
+ /**
10706
+ * A stored composition row that no longer PARSES — a schema a release
10707
+ * tightened, a row written by a newer build. Sent by id so the composer keeps
10708
+ * its device as a placeholder and reports the block `failed` naming the parse
10709
+ * error. Leaving it out would read, twice, exactly like a delete (D49).
10710
+ */
10711
+ var ComposerUnreadableBlockSchema = zod.z.object({
10712
+ id: zod.z.string().min(1),
10713
+ /** The row's stored name column; null when even that is unreadable (the device keeps the name it has). */
10714
+ name: zod.z.string().nullable(),
10715
+ /** The parse error, in the operator's words. */
10716
+ error: zod.z.string()
10717
+ });
10718
+ var ComposerApplyInputSchema = zod.z.object({
10719
+ /** The shared Blocks integration; null until `core-blocks` has reconciled it. */
10720
+ integrationId: zod.z.string().nullable(),
10721
+ /** False when the `core_blocks` read failed: then nothing may be withdrawn (D49). */
10722
+ storeLoaded: zod.z.boolean(),
10723
+ blocks: zod.z.array(ComposerBlockSchema),
10724
+ /** Operator restarts: rebuild these runtimes and re-resolve their sources. Stores nothing (D62). */
10725
+ restartBlockIds: zod.z.array(zod.z.string()),
10726
+ /** Composition rows that exist but do not parse: held, never withdrawn, reported failed. */
10727
+ unreadableBlocks: zod.z.array(ComposerUnreadableBlockSchema)
10728
+ });
10729
+ var ComposerBlockVerdictSchema = zod.z.object({
10730
+ blockId: zod.z.string(),
10731
+ status: CompositionRunStatusSchema,
10732
+ reason: zod.z.string().optional()
10733
+ });
10734
+ var ComposerApplyResultSchema = zod.z.object({
10735
+ blocks: zod.z.array(ComposerBlockVerdictSchema),
10736
+ /**
10737
+ * False when the composer declared and withdrew NOTHING (store not loaded,
10738
+ * integration unknown, its own rows unread or read empty once). An empty
10739
+ * apply the caller owes is paid off only by one the composer acted on (D49).
10740
+ */
10741
+ acted: zod.z.boolean()
10742
+ });
10743
+ var composerCapability = {
10744
+ name: "composer",
10745
+ scope: "system",
10746
+ mode: "singleton",
10747
+ internal: true,
10748
+ methods: {
10749
+ /**
10750
+ * Not `apply`: tRPC reserves `apply`, `call` and `then` as router keys, and
10751
+ * `router({})` throws on one — every cap router is built at boot.
10752
+ */
10753
+ applyBlocks: require_sleep.method(ComposerApplyInputSchema, ComposerApplyResultSchema, {
10754
+ kind: "mutation",
10755
+ auth: "admin"
10756
+ }),
10757
+ getStates: require_sleep.method(zod.z.object({ blockIds: zod.z.array(zod.z.string()) }), zod.z.object({ states: zod.z.array(CompositionBlockStateSchema) }), { auth: "admin" })
10241
10758
  }
10242
10759
  };
10243
10760
  //#endregion
@@ -18807,8 +19324,8 @@ var NcTestResultSchema = zod.z.object({
18807
19324
  className: zod.z.string().optional(),
18808
19325
  label: zod.z.string().optional()
18809
19326
  });
18810
- /** The rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts` is the
18811
- * same union; the template-vars spec pins the two together. `mixed` is DERIVED
19327
+ /** The rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts` is
19328
+ * `z.infer` of this schema — one shape, no twin. `mixed` is DERIVED
18812
19329
  * (a stored system rule whose kinds span families, D637) and never authored. */
18813
19330
  var NcRuleKindSchema = zod.z.enum([
18814
19331
  "detection",
@@ -44659,6 +45176,720 @@ function createDeviceExtensionProvider(options) {
44659
45176
  };
44660
45177
  }
44661
45178
  //#endregion
45179
+ //#region src/composition/composition-graph.ts
45180
+ /**
45181
+ * The field graph across every composition, and the cycles in it (D659).
45182
+ *
45183
+ * A node is ONE field of ONE device (`<addon>/<stableId>#<cap>.<field>`); an
45184
+ * edge points from a composed field to each field it reads. A cycle, direct or
45185
+ * through another composition, is refused at save by name (spec §2).
45186
+ *
45187
+ * The graph is built for ONE save: the CANDIDATE composition (the one being
45188
+ * validated) plus every STORED composition. The candidate ALWAYS replaces any
45189
+ * stored peer that targets the same device, regardless of where that stored
45190
+ * peer sits in `stored` — whether a save introduces or removes a cycle must
45191
+ * never depend on array order. Two DIFFERENT stored peers that (wrongly)
45192
+ * target the same device are corrupted state, not a cycle to resolve by
45193
+ * picking one silently; that is refused by name (D391).
45194
+ *
45195
+ * `findCompositionCycles` reports at least one cycle per cycle-SET (the DFS
45196
+ * marks a node `done` after its first expansion) — enough to refuse a save,
45197
+ * not to enumerate every elementary cycle through a shared node. A dotted
45198
+ * `fieldPath` is matched only as an exact string: a write to `state.value` and
45199
+ * a read of `state` would not connect (no nested field writes exist yet).
45200
+ */
45201
+ /** Two stored compositions both target the same device — refused by name, never silently resolved. */
45202
+ var CompositionGraphError = class extends Error {};
45203
+ function peerEdges(peer) {
45204
+ return peer.composition.features.flatMap((feature) => feature.kind !== "fields" ? [] : Object.entries(feature.fields).map(([path, source]) => [compositionReadKey({
45205
+ source: peer.target,
45206
+ cap: feature.cap,
45207
+ fieldPath: path
45208
+ }), fieldReads(source).map(compositionReadKey)]));
45209
+ }
45210
+ /**
45211
+ * Every peer that actually takes part in the graph: each stored peer minus
45212
+ * whichever one targets the same device as the candidate (the candidate
45213
+ * replaces it — this drop happens before any edge is built, so the
45214
+ * candidate's old edges never enter the map to begin with), plus the
45215
+ * candidate itself, exactly once. Throws {@link CompositionGraphError} if two
45216
+ * DIFFERENT stored peers (neither of them the candidate's own old version)
45217
+ * target the same device.
45218
+ */
45219
+ function resolvedPeers(candidate, stored) {
45220
+ const candidateKey = compositionSourceKey(candidate.target);
45221
+ const survivors = /* @__PURE__ */ new Map();
45222
+ for (const peer of stored) {
45223
+ const key = compositionSourceKey(peer.target);
45224
+ if (key === candidateKey) continue;
45225
+ if (survivors.has(key)) throw new CompositionGraphError(`two stored compositions both target device \`${key}\` — refusing to build the field graph`);
45226
+ survivors.set(key, peer);
45227
+ }
45228
+ return [...survivors.values(), candidate];
45229
+ }
45230
+ function compositionEdges(candidate, stored) {
45231
+ const edges = resolvedPeers(candidate, stored).flatMap(peerEdges);
45232
+ return new Map(edges);
45233
+ }
45234
+ function findCompositionCycles(candidate, stored) {
45235
+ const edges = compositionEdges(candidate, stored);
45236
+ const marks = /* @__PURE__ */ new Map();
45237
+ const found = /* @__PURE__ */ new Map();
45238
+ const visit = (node, stack) => {
45239
+ const mark = marks.get(node);
45240
+ if (mark === "done") return;
45241
+ if (mark === "visiting") {
45242
+ const path = [...stack.slice(stack.indexOf(node)), node];
45243
+ const id = [...new Set(path)].toSorted().join("|");
45244
+ if (!found.has(id)) found.set(id, { path });
45245
+ return;
45246
+ }
45247
+ marks.set(node, "visiting");
45248
+ for (const next of edges.get(node) ?? []) visit(next, [...stack, node]);
45249
+ marks.set(node, "done");
45250
+ };
45251
+ for (const node of edges.keys()) visit(node, []);
45252
+ return [...found.values()];
45253
+ }
45254
+ //#endregion
45255
+ //#region src/composition/evaluate-field.ts
45256
+ function describeRead(read) {
45257
+ return `${compositionSourceKey(read.source)} ${read.cap}.${read.fieldPath}`;
45258
+ }
45259
+ function fromReading(reading, read) {
45260
+ if (reading.state === "unavailable") return {
45261
+ state: "unavailable",
45262
+ value: null,
45263
+ reason: `${describeRead(read)}: ${reading.reason}`,
45264
+ fatal: reading.fatal
45265
+ };
45266
+ return {
45267
+ state: reading.state,
45268
+ value: reading.value,
45269
+ reason: reading.state === "stale" ? `${describeRead(read)}: ${reading.reason}` : null,
45270
+ deadline: null
45271
+ };
45272
+ }
45273
+ function expressionOutcome(source, read, now) {
45274
+ const values = {};
45275
+ const staleReasons = [];
45276
+ let fatalUnavailable = null;
45277
+ let nonFatalUnavailable = null;
45278
+ for (const [name, binding] of Object.entries(source.bindings)) {
45279
+ if (binding.kind === "literal") {
45280
+ values[name] = binding.value;
45281
+ continue;
45282
+ }
45283
+ const reading = read(binding);
45284
+ if (reading.state === "unavailable") {
45285
+ const outcome = {
45286
+ state: "unavailable",
45287
+ value: null,
45288
+ reason: `binding \`${name}\` (${describeRead(binding)}): ${reading.reason}`,
45289
+ fatal: reading.fatal
45290
+ };
45291
+ if (reading.fatal) {
45292
+ fatalUnavailable = outcome;
45293
+ break;
45294
+ }
45295
+ if (nonFatalUnavailable === null) nonFatalUnavailable = outcome;
45296
+ continue;
45297
+ }
45298
+ values[name] = reading.value;
45299
+ if (reading.state === "stale") staleReasons.push(`\`${name}\`: ${reading.reason}`);
45300
+ }
45301
+ if (fatalUnavailable !== null) return fatalUnavailable;
45302
+ if (nonFatalUnavailable !== null) return nonFatalUnavailable;
45303
+ const deadlines = [];
45304
+ const result = evaluateExpressionSource(source.expr, values, now, { hooks: { noteDeadline: (at) => deadlines.push(at) } });
45305
+ if (!result.ok) return {
45306
+ state: "unavailable",
45307
+ value: null,
45308
+ reason: `the expression failed: ${result.error}`,
45309
+ fatal: false
45310
+ };
45311
+ return {
45312
+ state: staleReasons.length > 0 ? "stale" : "fresh",
45313
+ value: result.value,
45314
+ reason: staleReasons.length > 0 ? staleReasons.join("; ") : null,
45315
+ deadline: deadlines.length > 0 ? Math.min(...deadlines) : null
45316
+ };
45317
+ }
45318
+ function evaluateCompositionField(input) {
45319
+ const { source } = input;
45320
+ switch (source.kind) {
45321
+ case "from": return fromReading(input.read(source), source);
45322
+ case "expression": return expressionOutcome(source, input.read, input.now);
45323
+ case "code": return {
45324
+ state: "unavailable",
45325
+ value: null,
45326
+ reason: "code fields are not runnable in the composer — they arrive in slice 4",
45327
+ fatal: true
45328
+ };
45329
+ }
45330
+ }
45331
+ //#endregion
45332
+ //#region src/composition/examples.ts
45333
+ /**
45334
+ * The worked composed-device example: a ROOM. Temperature is the average of N
45335
+ * sensors, and occupancy is any motion within the last five minutes. It is the
45336
+ * slice-1 acceptance case and the template an author starts from (Phase 4
45337
+ * §4 item 4 asked for one).
45338
+ */
45339
+ var ROOM_OCCUPANCY_WINDOW_MS = 3e5;
45340
+ var ROOM_EXAMPLE_TEMPERATURE_SOURCES = [
45341
+ {
45342
+ addonId: "provider-homeassistant",
45343
+ stableId: "sensor.living_room_temperature"
45344
+ },
45345
+ {
45346
+ addonId: "provider-homeassistant",
45347
+ stableId: "sensor.sofa_temperature"
45348
+ },
45349
+ {
45350
+ addonId: "provider-homeassistant",
45351
+ stableId: "sensor.window_temperature"
45352
+ }
45353
+ ];
45354
+ var ROOM_EXAMPLE_MOTION_SOURCES = [{
45355
+ addonId: "provider-homeassistant",
45356
+ stableId: "binary_sensor.living_room_motion"
45357
+ }, {
45358
+ addonId: "provider-homeassistant",
45359
+ stableId: "binary_sensor.living_room_presence"
45360
+ }];
45361
+ function from(source, cap, fieldPath) {
45362
+ return {
45363
+ kind: "from",
45364
+ source,
45365
+ cap,
45366
+ fieldPath
45367
+ };
45368
+ }
45369
+ function named(prefix, sources, cap, fieldPath) {
45370
+ return Object.fromEntries(sources.map((s, i) => [`${prefix}${i + 1}`, from(s, cap, fieldPath)]));
45371
+ }
45372
+ function roomCompositionExample(input) {
45373
+ const t = named("t", input.temperatureSources, "temperature-sensor", "celsius");
45374
+ const f = named("f", input.temperatureSources, "temperature-sensor", "lastFetchedAt");
45375
+ const d = named("d", input.motionSources, "motion", "detected");
45376
+ const m = named("m", input.motionSources, "motion", "lastDetectedAt");
45377
+ const windows = Object.keys(m).map((name) => `within(${name}, ${input.occupancyWindowMs})`);
45378
+ return CompositionSchema.parse({
45379
+ target: {
45380
+ kind: "new",
45381
+ type: require_sleep.DeviceType.Sensor
45382
+ },
45383
+ features: [{
45384
+ kind: "fields",
45385
+ cap: "temperature-sensor",
45386
+ fields: {
45387
+ celsius: {
45388
+ kind: "expression",
45389
+ expr: `round(avg(${Object.keys(t).join(", ")}), 1)`,
45390
+ bindings: t
45391
+ },
45392
+ lastFetchedAt: {
45393
+ kind: "expression",
45394
+ expr: `max(${Object.keys(f).join(", ")})`,
45395
+ bindings: f
45396
+ }
45397
+ }
45398
+ }, {
45399
+ kind: "fields",
45400
+ cap: "binary",
45401
+ fields: {
45402
+ on: {
45403
+ kind: "expression",
45404
+ expr: `any(${[...Object.keys(d), ...windows].join(", ")})`,
45405
+ bindings: {
45406
+ ...d,
45407
+ ...m
45408
+ }
45409
+ },
45410
+ lastChangedAt: {
45411
+ kind: "expression",
45412
+ expr: "now",
45413
+ bindings: {}
45414
+ }
45415
+ }
45416
+ }]
45417
+ });
45418
+ }
45419
+ //#endregion
45420
+ //#region src/composition/expression-kinds.ts
45421
+ var NUMBER = {
45422
+ kind: "fixed",
45423
+ result: ["number"]
45424
+ };
45425
+ var BOOLEAN = {
45426
+ kind: "fixed",
45427
+ result: ["boolean"]
45428
+ };
45429
+ /** One rule per builtin. `expression-kinds.spec.ts` fails for a builtin missing here. */
45430
+ var BUILTIN_RESULT_KINDS = Object.freeze({
45431
+ min: NUMBER,
45432
+ max: NUMBER,
45433
+ abs: NUMBER,
45434
+ floor: NUMBER,
45435
+ ceil: NUMBER,
45436
+ sqrt: NUMBER,
45437
+ round: NUMBER,
45438
+ pow: NUMBER,
45439
+ clamp: NUMBER,
45440
+ avg: NUMBER,
45441
+ sum: NUMBER,
45442
+ age: NUMBER,
45443
+ convert: NUMBER,
45444
+ coalesce: { kind: "coalesce" },
45445
+ any: BOOLEAN,
45446
+ all: BOOLEAN,
45447
+ within: BOOLEAN,
45448
+ latest: {
45449
+ kind: "fixed",
45450
+ result: ["number", "null"]
45451
+ }
45452
+ });
45453
+ var KindInferenceError = class extends Error {};
45454
+ function kindOfValue(value) {
45455
+ if (value === null) return "null";
45456
+ if (typeof value === "number") return "number";
45457
+ if (typeof value === "string") return "string";
45458
+ return "boolean";
45459
+ }
45460
+ function only(kind) {
45461
+ return new Set([kind]);
45462
+ }
45463
+ function union(...sets) {
45464
+ return new Set(sets.flatMap((s) => [...s]));
45465
+ }
45466
+ function withoutNull(set) {
45467
+ return new Set([...set].filter((k) => k !== "null"));
45468
+ }
45469
+ function binaryKinds(op, left, right) {
45470
+ if (op === "+") {
45471
+ const all = [...left, ...right];
45472
+ if (all.every((k) => k === "string")) return only("string");
45473
+ if (all.every((k) => k === "number")) return only("number");
45474
+ return new Set(["number", "string"]);
45475
+ }
45476
+ if (op === "-" || op === "*" || op === "/" || op === "%") return only("number");
45477
+ return only("boolean");
45478
+ }
45479
+ /** "exactly N" / "at least N" / "N–M", for an arity refusal that says what IS allowed. */
45480
+ function arityRange(builtin) {
45481
+ if (builtin.minArgs === builtin.maxArgs) return `exactly ${builtin.minArgs}`;
45482
+ if (builtin.maxArgs === Number.POSITIVE_INFINITY) return `at least ${builtin.minArgs}`;
45483
+ return `${builtin.minArgs}–${builtin.maxArgs}`;
45484
+ }
45485
+ function callKinds(callee, args) {
45486
+ const builtin = EXPRESSION_BUILTINS[callee];
45487
+ if (builtin !== void 0 && (args.length < builtin.minArgs || args.length > builtin.maxArgs)) throw new KindInferenceError(`${callee}: wrong number of arguments (${args.length}); expected ${arityRange(builtin)}`);
45488
+ const rule = BUILTIN_RESULT_KINDS[callee];
45489
+ if (rule === void 0) throw new KindInferenceError(`no result-kind rule for builtin '${callee}'`);
45490
+ if (rule.kind === "fixed") return new Set(rule.result);
45491
+ const nonNull = union(...args.map(withoutNull));
45492
+ return args.every((a) => a.has("null")) ? union(nonNull, only("null")) : nonNull;
45493
+ }
45494
+ function kindsOf(node, bindings) {
45495
+ switch (node.kind) {
45496
+ case "literal": return only(kindOfValue(node.value));
45497
+ case "identifier": {
45498
+ if (node.name === "now") return only("number");
45499
+ const bound = bindings.get(node.name);
45500
+ if (bound === void 0) throw new KindInferenceError(`unbound identifier '${node.name}'`);
45501
+ return bound;
45502
+ }
45503
+ case "unary": return only(node.op === "!" ? "boolean" : "number");
45504
+ case "binary": return binaryKinds(node.op, kindsOf(node.left, bindings), kindsOf(node.right, bindings));
45505
+ case "logical": return union(kindsOf(node.left, bindings), kindsOf(node.right, bindings));
45506
+ case "conditional": return union(kindsOf(node.consequent, bindings), kindsOf(node.alternate, bindings));
45507
+ case "call": return callKinds(node.callee, node.args.map((a) => kindsOf(a, bindings)));
45508
+ }
45509
+ }
45510
+ function inferExpressionKinds(input) {
45511
+ const compiled = compileExpressionSafe(input.expr);
45512
+ if (!compiled.ok) return {
45513
+ ok: false,
45514
+ error: compiled.error
45515
+ };
45516
+ try {
45517
+ return {
45518
+ ok: true,
45519
+ kinds: kindsOf(compiled.parsed.ast, input.bindingKinds)
45520
+ };
45521
+ } catch (err) {
45522
+ if (err instanceof KindInferenceError) return {
45523
+ ok: false,
45524
+ error: err.message
45525
+ };
45526
+ throw err;
45527
+ }
45528
+ }
45529
+ //#endregion
45530
+ //#region src/device/schema-fields.ts
45531
+ /** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
45532
+ * Zod v4 exposes `.unwrap()` on all three wrapper classes. */
45533
+ function unwrapSchema(schema) {
45534
+ let s = schema;
45535
+ while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
45536
+ return s;
45537
+ }
45538
+ function leafKind(schema) {
45539
+ const s = unwrapSchema(schema);
45540
+ if (s instanceof zod.z.ZodEnum) return {
45541
+ kind: "enum",
45542
+ enumValues: s.options
45543
+ };
45544
+ if (s instanceof zod.z.ZodNumber) return { kind: "number" };
45545
+ if (s instanceof zod.z.ZodString) return { kind: "string" };
45546
+ if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
45547
+ return null;
45548
+ }
45549
+ /** Walk a cap status schema into flat wireable leaf fields (dotted paths).
45550
+ * Recurses into nested ZodObject (unwrapping nullable/optional/default first),
45551
+ * so fields with null live values are still offered. Arrays / unknown shapes
45552
+ * are skipped — never throws. */
45553
+ function enumerateSchemaFields(schema, prefix = "") {
45554
+ const root = unwrapSchema(schema);
45555
+ if (!(root instanceof zod.z.ZodObject)) return [];
45556
+ const out = [];
45557
+ const shape = root.shape;
45558
+ for (const [key, field] of Object.entries(shape)) {
45559
+ const path = prefix ? `${prefix}.${key}` : key;
45560
+ const inner = unwrapSchema(field);
45561
+ if (inner instanceof zod.z.ZodObject) {
45562
+ out.push(...enumerateSchemaFields(inner, path));
45563
+ continue;
45564
+ }
45565
+ const leaf = leafKind(field);
45566
+ if (leaf) out.push({
45567
+ path,
45568
+ kind: leaf.kind,
45569
+ ...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
45570
+ });
45571
+ }
45572
+ return out;
45573
+ }
45574
+ /** Enumerate the per-item wireable fields of an item-array cap (see
45575
+ * `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
45576
+ * `keyField` (the key comes from the link's `itemKey`, never from a wired
45577
+ * source), each tagged `item: true` so the authoring UI collects an
45578
+ * `itemKey` alongside the field. Never throws. */
45579
+ function enumerateItemArrayFields(itemArray) {
45580
+ return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
45581
+ ...f,
45582
+ item: true
45583
+ }));
45584
+ }
45585
+ //#endregion
45586
+ //#region src/composition/state-fields.ts
45587
+ /**
45588
+ * What a capability's state field IS: kind, nullability, whether required. The
45589
+ * validator needs nullability, which `enumerateSchemaFields` does not report,
45590
+ * and that decides a field's unavailable mode (D393).
45591
+ */
45592
+ function fieldAt(schema, path) {
45593
+ let current = schema;
45594
+ for (const segment of path.split(".")) {
45595
+ const container = unwrapSchema(current);
45596
+ if (!(container instanceof zod.z.ZodObject)) return null;
45597
+ const next = container.shape[segment];
45598
+ if (!(next instanceof zod.z.ZodType)) return null;
45599
+ current = next;
45600
+ }
45601
+ return current;
45602
+ }
45603
+ function describeStateField(schema, path) {
45604
+ const leaf = enumerateSchemaFields(schema).find((f) => f.path === path);
45605
+ if (leaf === void 0) return null;
45606
+ const field = fieldAt(schema, path);
45607
+ if (field === null) return null;
45608
+ return {
45609
+ path,
45610
+ kind: leaf.kind,
45611
+ nullable: field.safeParse(null).success,
45612
+ required: !field.safeParse(void 0).success,
45613
+ ...leaf.enumValues === void 0 ? {} : { enumValues: leaf.enumValues }
45614
+ };
45615
+ }
45616
+ function describeTopLevelStateFields(schema) {
45617
+ return new Map(enumerateSchemaFields(schema).filter((f) => !f.path.includes(".")).flatMap((f) => {
45618
+ const described = describeStateField(schema, f.path);
45619
+ return described === null ? [] : [[f.path, described]];
45620
+ }));
45621
+ }
45622
+ /** Top-level keys a value MUST carry: those whose schema refuses `undefined`. */
45623
+ function requiredTopLevelKeys(schema) {
45624
+ const root = unwrapSchema(schema);
45625
+ if (!(root instanceof zod.z.ZodObject)) return [];
45626
+ return Object.entries(root.shape).flatMap(([key, field]) => field instanceof zod.z.ZodType && !field.safeParse(void 0).success ? [key] : []);
45627
+ }
45628
+ //#endregion
45629
+ //#region src/composition/validate-composition.ts
45630
+ function buildCapabilityLookup(defs) {
45631
+ const byName = new Map(defs.map((d) => [d.name, d]));
45632
+ return (capName) => byName.get(capName) ?? null;
45633
+ }
45634
+ /** Caps every `BaseDevice` registers on itself; a second registration throws. */
45635
+ var BASE_DEVICE_CAPS = new Set(["device-status", "feature-probe"]);
45636
+ var PENDING_SELF = {
45637
+ addonId: COMPOSER_ADDON_ID,
45638
+ stableId: `${COMPOSED_DEVICE_STABLE_ID_PREFIX}(unsaved)`
45639
+ };
45640
+ function problem(code, path, message) {
45641
+ return {
45642
+ code,
45643
+ path,
45644
+ message
45645
+ };
45646
+ }
45647
+ function refused(...problems) {
45648
+ return {
45649
+ problems,
45650
+ fields: []
45651
+ };
45652
+ }
45653
+ function readKinds(read, lookupCap) {
45654
+ const def = lookupCap(read.cap);
45655
+ const schema = def?.runtimeState ?? def?.status?.schema;
45656
+ if (def === null || schema === void 0) return {
45657
+ ok: false,
45658
+ code: "source-field-unknown",
45659
+ message: `no capability \`${read.cap}\` with state to read`
45660
+ };
45661
+ const field = describeStateField(schema, read.fieldPath);
45662
+ if (field === null) return {
45663
+ ok: false,
45664
+ code: "source-field-unknown",
45665
+ message: `\`${read.cap}\` has no field \`${read.fieldPath}\``
45666
+ };
45667
+ const base = field.kind === "enum" ? "string" : field.kind;
45668
+ return {
45669
+ ok: true,
45670
+ kinds: new Set(field.nullable ? [base, "null"] : [base])
45671
+ };
45672
+ }
45673
+ function sourceKinds(source, lookupCap) {
45674
+ if (source.kind === "from") return readKinds(source, lookupCap);
45675
+ if (source.kind === "code") return {
45676
+ ok: false,
45677
+ code: "reserved-variant",
45678
+ message: "code fields arrive in slice 4"
45679
+ };
45680
+ const bindingKinds = /* @__PURE__ */ new Map();
45681
+ for (const [name, binding] of Object.entries(source.bindings)) {
45682
+ if (binding.kind === "literal") {
45683
+ bindingKinds.set(name, new Set([kindOfValue(binding.value)]));
45684
+ continue;
45685
+ }
45686
+ const kinds = readKinds(binding, lookupCap);
45687
+ if (!kinds.ok) return {
45688
+ ...kinds,
45689
+ message: `binding \`${name}\`: ${kinds.message}`
45690
+ };
45691
+ bindingKinds.set(name, kinds.kinds);
45692
+ }
45693
+ const inferred = inferExpressionKinds({
45694
+ expr: source.expr,
45695
+ bindingKinds
45696
+ });
45697
+ return inferred.ok ? {
45698
+ ok: true,
45699
+ kinds: inferred.kinds
45700
+ } : {
45701
+ ok: false,
45702
+ code: "expression-invalid",
45703
+ message: inferred.error
45704
+ };
45705
+ }
45706
+ function compatibility(cap, target, kinds, at) {
45707
+ const allowed = target.kind === "enum" ? "string" : target.kind;
45708
+ const offending = [...kinds].filter((k) => k !== "null" && k !== allowed);
45709
+ const problems = [];
45710
+ if (offending.length > 0) problems.push(problem("kind-mismatch", at, `\`${cap}.${target.path}\` is a ${target.kind}; this source can produce ${offending.join(" or ")}`));
45711
+ if (kinds.has("null") && !target.nullable) problems.push(problem("may-be-null", at, `\`${cap}.${target.path}\` cannot be null and this source can produce null — wrap it in coalesce(…) or pick a non-null source`));
45712
+ return problems;
45713
+ }
45714
+ /**
45715
+ * A string a source produces WITHOUT reading any device — a field-level
45716
+ * `expression` source whose compiled AST is a bare string literal (`"red"`,
45717
+ * never `a` or `a + "x"`). Anything else that can only produce `'string'` at
45718
+ * the kind level (a `from` read of an enum-typed source field, a computed
45719
+ * expression) is NOT checked against the target's `enumValues` here: the
45720
+ * kind inference already collapsed it to the coarse `'string'` kind, losing
45721
+ * whichever enum it actually came from, so membership can only be judged at
45722
+ * write time against the runtime-state schema. Known limitation — see the
45723
+ * fix report for Task 6 round 1.
45724
+ */
45725
+ function literalStringValue(source) {
45726
+ if (source.kind !== "expression") return null;
45727
+ const compiled = compileExpressionSafe(source.expr);
45728
+ if (!compiled.ok) return null;
45729
+ const ast = compiled.parsed.ast;
45730
+ return ast.kind === "literal" && typeof ast.value === "string" ? ast.value : null;
45731
+ }
45732
+ /**
45733
+ * An enum target whose source is a save-time-KNOWN string literal outside its
45734
+ * `enumValues` is refused now rather than left to fail the write later. A
45735
+ * dynamic source (a `from` read, a non-literal expression) is left to
45736
+ * runtime — see {@link literalStringValue}.
45737
+ */
45738
+ function enumLiteralProblem(cap, target, source, at) {
45739
+ if (target.kind !== "enum" || target.enumValues === void 0) return null;
45740
+ const value = literalStringValue(source);
45741
+ if (value === null || target.enumValues.includes(value)) return null;
45742
+ const allowed = target.enumValues.map((v) => `\`${v}\``).join(", ");
45743
+ return problem("kind-mismatch", at, `\`${cap}.${target.path}\` accepts only ${allowed}; this source is the literal \`${value}\``);
45744
+ }
45745
+ function roleOf(source) {
45746
+ if (source.kind !== "expression") return "derived";
45747
+ if (fieldReads(source).length > 0) return "derived";
45748
+ const compiled = compileExpressionSafe(source.expr);
45749
+ return compiled.ok && compiled.parsed.identifiers.has("now") ? "clock" : "constant";
45750
+ }
45751
+ function planFeature(feature, featureIndex, target, lookupCap) {
45752
+ const at = `features[${featureIndex}]`;
45753
+ const def = lookupCap(feature.cap);
45754
+ if (def === null) return refused(problem("unknown-capability", `${at}.cap`, `no capability is named \`${feature.cap}\``));
45755
+ if (BASE_DEVICE_CAPS.has(def.name)) return refused(problem("not-composable", `${at}.cap`, `\`${def.name}\` is registered by every device itself — it cannot be composed`));
45756
+ const state = def.runtimeState;
45757
+ if (def.scope !== "device" || state === void 0) return refused(problem("not-composable", `${at}.cap`, `\`${def.name}\` has no per-device state to compose`));
45758
+ const capProblems = [];
45759
+ if (def.deviceTypes !== void 0 && !def.deviceTypes.includes(target.type)) capProblems.push(problem("wrong-device-type", `${at}.cap`, `\`${def.name}\` is not offered on a \`${target.type}\` device (it allows ${def.deviceTypes.join(", ")})`));
45760
+ const served = Object.entries(def.methods).filter(([, m]) => m.systemOnly !== true).map(([name]) => `\`${name}\``);
45761
+ if (served.length > 0) capProblems.push(problem("has-commands", `${at}.cap`, `\`${def.name}\` has ${served.join(", ")}, which a composed device cannot serve until commands arrive (slice 3)`));
45762
+ if (feature.commands !== void 0 && Object.keys(feature.commands).length > 0) capProblems.push(problem("reserved-variant", `${at}.commands`, "commands (`forward`) arrive in slice 3"));
45763
+ const targetFields = describeTopLevelStateFields(state);
45764
+ for (const key of requiredTopLevelKeys(state)) if (!targetFields.has(key)) capProblems.push(problem("not-composable", `${at}.fields`, `\`${def.name}.${key}\` is not a scalar — nested fields arrive in a later slice`));
45765
+ else if (!(key in feature.fields)) capProblems.push(problem("missing-field", `${at}.fields.${key}`, `\`${def.name}.${key}\` is required and has no source`));
45766
+ const perField = Object.entries(feature.fields).map(([path, source]) => {
45767
+ const fieldAt = `${at}.fields.${path}`;
45768
+ if (path.includes(".")) return refused(problem("nested-field", fieldAt, `\`${path}\` is nested — nested fields arrive in a later slice`));
45769
+ const tf = targetFields.get(path);
45770
+ if (tf === void 0) return refused(problem("unknown-field", fieldAt, `\`${def.name}\` has no field \`${path}\``));
45771
+ const kinds = sourceKinds(source, lookupCap);
45772
+ if (!kinds.ok) return refused(problem(kinds.code, fieldAt, kinds.message));
45773
+ const plan = {
45774
+ cap: def.name,
45775
+ path,
45776
+ featureIndex,
45777
+ kind: tf.kind,
45778
+ nullable: tf.nullable,
45779
+ required: tf.required,
45780
+ unavailable: tf.nullable ? "null" : "offline",
45781
+ role: roleOf(source),
45782
+ source,
45783
+ reads: [...fieldReads(source)]
45784
+ };
45785
+ const enumProblem = enumLiteralProblem(def.name, tf, source, fieldAt);
45786
+ return {
45787
+ problems: [...compatibility(def.name, tf, kinds.kinds, fieldAt), ...enumProblem === null ? [] : [enumProblem]],
45788
+ fields: [plan]
45789
+ };
45790
+ });
45791
+ return {
45792
+ problems: [...capProblems, ...perField.flatMap((r) => r.problems)],
45793
+ fields: perField.flatMap((r) => r.fields)
45794
+ };
45795
+ }
45796
+ function planComposition(composition, lookupCap) {
45797
+ const target = composition.target;
45798
+ if (target.kind === "existing") return refused(problem("reserved-variant", "target", "an EXISTING device as the target arrives in slice 2 — this release composes new devices only"));
45799
+ const results = composition.features.map((feature, index) => {
45800
+ const at = `features[${index}]`;
45801
+ if (feature.kind === "passthrough") return refused(problem("reserved-variant", at, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`));
45802
+ return planFeature(feature, index, target, lookupCap);
45803
+ });
45804
+ return {
45805
+ problems: results.flatMap((r) => r.problems),
45806
+ fields: results.flatMap((r) => r.fields)
45807
+ };
45808
+ }
45809
+ function compositionSourceRefs(composition) {
45810
+ const refs = composition.features.flatMap((f) => f.kind === "fields" ? Object.values(f.fields).flatMap(fieldReads).map((r) => r.source) : []);
45811
+ return [...new Map(refs.map((r) => [compositionSourceKey(r), r])).values()];
45812
+ }
45813
+ function sourceProblems(fields, ctx) {
45814
+ const selfKey = ctx.self === null ? null : compositionSourceKey(ctx.self);
45815
+ return fields.flatMap((field) => field.reads.flatMap((read) => {
45816
+ const at = `features[${field.featureIndex}].fields.${field.path}`;
45817
+ const key = compositionSourceKey(read.source);
45818
+ if (key === selfKey) return fields.some((f) => f.cap === read.cap && f.path === read.fieldPath) ? [] : [problem("source-field-unknown", at, `this device has no composed field \`${read.cap}.${read.fieldPath}\``)];
45819
+ const desc = ctx.sources.get(key);
45820
+ if (desc === void 0 || desc.kind === "unreadable") return [problem("source-unreadable", at, `could not read source \`${key}\` (${desc === void 0 ? "it was not read" : desc.error}) — the save is refused rather than stored unchecked`)];
45821
+ if (desc.kind === "absent") return [problem("source-missing", at, `source \`${key}\` does not exist`)];
45822
+ if (!desc.caps.has(read.cap)) return [problem("source-capability-missing", at, `source \`${key}\` (device ${desc.deviceId}) has no \`${read.cap}\``)];
45823
+ return [];
45824
+ }));
45825
+ }
45826
+ /**
45827
+ * A cycle's node keys are `compositionReadKey` strings:
45828
+ * `<addonId>/<stableId>#<cap>.<fieldPath>`. When a node belongs to the
45829
+ * CANDIDATE itself (this composition being saved), pull its `cap`/`fieldPath`
45830
+ * back out so the refusal can point at that exact field, matching the
45831
+ * documented `features[N].fields.<path>` convention — slice 1 has no nested
45832
+ * field writes, so splitting `rest` on the FIRST `.` is exact.
45833
+ */
45834
+ function candidateFieldOfNode(nodeKey, candidateKey) {
45835
+ const prefix = `${candidateKey}#`;
45836
+ if (!nodeKey.startsWith(prefix)) return null;
45837
+ const rest = nodeKey.slice(prefix.length);
45838
+ const dot = rest.indexOf(".");
45839
+ if (dot === -1) return null;
45840
+ return {
45841
+ cap: rest.slice(0, dot),
45842
+ fieldPath: rest.slice(dot + 1)
45843
+ };
45844
+ }
45845
+ /** The first node of a cycle's path that is one of THIS composition's own
45846
+ * planned fields, as a documented-convention path; `'features'` when the
45847
+ * cycle runs entirely through other stored compositions. */
45848
+ function cyclePath(nodePath, candidateKey, fields) {
45849
+ for (const nodeKey of nodePath) {
45850
+ const hit = candidateFieldOfNode(nodeKey, candidateKey);
45851
+ if (hit === null) continue;
45852
+ const field = fields.find((f) => f.cap === hit.cap && f.path === hit.fieldPath);
45853
+ if (field !== void 0) return `features[${field.featureIndex}].fields.${field.path}`;
45854
+ }
45855
+ return "features";
45856
+ }
45857
+ /**
45858
+ * Cycle check runs against the CANDIDATE (this composition, targeting `self`
45859
+ * when it already exists, else a placeholder that cannot collide with a
45860
+ * stored target) plus every OTHER stored composition. Two stored peers that
45861
+ * (wrongly) share a target are corrupted state — `findCompositionCycles`
45862
+ * throws `CompositionGraphError` rather than silently picking one, and this
45863
+ * is refused BY NAME (D391) instead of escaping as an uncaught throw.
45864
+ */
45865
+ function cycleProblems(composition, ctx, fields) {
45866
+ const candidate = {
45867
+ target: ctx.self ?? PENDING_SELF,
45868
+ composition
45869
+ };
45870
+ const candidateKey = compositionSourceKey(candidate.target);
45871
+ try {
45872
+ return findCompositionCycles(candidate, ctx.peers).map((c) => problem("cycle", cyclePath(c.path, candidateKey, fields), `a field reads itself: ${c.path.join(" → ")}`));
45873
+ } catch (err) {
45874
+ if (err instanceof CompositionGraphError) return [problem("duplicate-target", "features", err.message)];
45875
+ throw err;
45876
+ }
45877
+ }
45878
+ function validateComposition(composition, ctx) {
45879
+ const plan = planComposition(composition, ctx.lookupCap);
45880
+ const cycles = cycleProblems(composition, ctx, plan.fields);
45881
+ const problems = [
45882
+ ...plan.problems,
45883
+ ...sourceProblems(plan.fields, ctx),
45884
+ ...cycles
45885
+ ];
45886
+ return {
45887
+ ok: problems.length === 0,
45888
+ problems,
45889
+ fields: [...plan.fields]
45890
+ };
45891
+ }
45892
+ //#endregion
44662
45893
  //#region src/catalogs/device-state-vocabulary.ts
44663
45894
  /**
44664
45895
  * Most specific first. Extending this list is how a new device kind becomes
@@ -48811,62 +50042,6 @@ function createRuntimeStateBridge(params) {
48811
50042
  };
48812
50043
  }
48813
50044
  //#endregion
48814
- //#region src/device/schema-fields.ts
48815
- /** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
48816
- * Zod v4 exposes `.unwrap()` on all three wrapper classes. */
48817
- function unwrap(schema) {
48818
- let s = schema;
48819
- while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
48820
- return s;
48821
- }
48822
- function leafKind(schema) {
48823
- const s = unwrap(schema);
48824
- if (s instanceof zod.z.ZodEnum) return {
48825
- kind: "enum",
48826
- enumValues: s.options
48827
- };
48828
- if (s instanceof zod.z.ZodNumber) return { kind: "number" };
48829
- if (s instanceof zod.z.ZodString) return { kind: "string" };
48830
- if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
48831
- return null;
48832
- }
48833
- /** Walk a cap status schema into flat wireable leaf fields (dotted paths).
48834
- * Recurses into nested ZodObject (unwrapping nullable/optional/default first),
48835
- * so fields with null live values are still offered. Arrays / unknown shapes
48836
- * are skipped — never throws. */
48837
- function enumerateSchemaFields(schema, prefix = "") {
48838
- const root = unwrap(schema);
48839
- if (!(root instanceof zod.z.ZodObject)) return [];
48840
- const out = [];
48841
- const shape = root.shape;
48842
- for (const [key, field] of Object.entries(shape)) {
48843
- const path = prefix ? `${prefix}.${key}` : key;
48844
- const inner = unwrap(field);
48845
- if (inner instanceof zod.z.ZodObject) {
48846
- out.push(...enumerateSchemaFields(inner, path));
48847
- continue;
48848
- }
48849
- const leaf = leafKind(field);
48850
- if (leaf) out.push({
48851
- path,
48852
- kind: leaf.kind,
48853
- ...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
48854
- });
48855
- }
48856
- return out;
48857
- }
48858
- /** Enumerate the per-item wireable fields of an item-array cap (see
48859
- * `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
48860
- * `keyField` (the key comes from the link's `itemKey`, never from a wired
48861
- * source), each tagged `item: true` so the authoring UI collects an
48862
- * `itemKey` alongside the field. Never throws. */
48863
- function enumerateItemArrayFields(itemArray) {
48864
- return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
48865
- ...f,
48866
- item: true
48867
- }));
48868
- }
48869
- //#endregion
48870
50045
  //#region src/device/system-mirror.ts
48871
50046
  var STATE_CHANGED_CATEGORY = "device.state-changed";
48872
50047
  var BINDING_CHANGED_CATEGORY = "capability.binding-changed";
@@ -49759,6 +50934,7 @@ var CAPABILITY_NAMES = {
49759
50934
  carbonMonoxide: "carbon-monoxide",
49760
50935
  climateControl: "climate-control",
49761
50936
  color: "color",
50937
+ composer: "composer",
49762
50938
  connectionTest: "connection-test",
49763
50939
  connectivity: "connectivity",
49764
50940
  consumables: "consumables",
@@ -50019,6 +51195,10 @@ var CAPABILITY_ROUTER_KEYS = [
50019
51195
  key: "color",
50020
51196
  name: "color"
50021
51197
  },
51198
+ {
51199
+ key: "composer",
51200
+ name: "composer"
51201
+ },
50022
51202
  {
50023
51203
  key: "connectionTest",
50024
51204
  name: "connection-test"
@@ -50578,6 +51758,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
50578
51758
  carbonMonoxideCapability,
50579
51759
  climateControlCapability,
50580
51760
  colorCapability,
51761
+ composerCapability,
50581
51762
  connectionTestCapability,
50582
51763
  connectivityCapability,
50583
51764
  consumablesCapability,
@@ -51653,6 +52834,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
51653
52834
  addonId: null,
51654
52835
  access: "create"
51655
52836
  },
52837
+ "composer.applyBlocks": {
52838
+ capName: "composer",
52839
+ capScope: "system",
52840
+ addonId: null,
52841
+ access: "create"
52842
+ },
52843
+ "composer.getStates": {
52844
+ capName: "composer",
52845
+ capScope: "system",
52846
+ addonId: null,
52847
+ access: "view"
52848
+ },
51656
52849
  "connectionTest.describeTest": {
51657
52850
  capName: "connection-test",
51658
52851
  capScope: "system",
@@ -51713,6 +52906,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
51713
52906
  addonId: null,
51714
52907
  access: "create"
51715
52908
  },
52909
+ "coreBlocks.createComposition": {
52910
+ capName: "core-blocks",
52911
+ capScope: "system",
52912
+ addonId: null,
52913
+ access: "create"
52914
+ },
51716
52915
  "coreBlocks.delete": {
51717
52916
  capName: "core-blocks",
51718
52917
  capScope: "system",
@@ -51725,6 +52924,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
51725
52924
  addonId: null,
51726
52925
  access: "view"
51727
52926
  },
52927
+ "coreBlocks.getComposition": {
52928
+ capName: "core-blocks",
52929
+ capScope: "system",
52930
+ addonId: null,
52931
+ access: "view"
52932
+ },
51728
52933
  "coreBlocks.getTypeDefs": {
51729
52934
  capName: "core-blocks",
51730
52935
  capScope: "system",
@@ -51737,6 +52942,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
51737
52942
  addonId: null,
51738
52943
  access: "view"
51739
52944
  },
52945
+ "coreBlocks.listCompositions": {
52946
+ capName: "core-blocks",
52947
+ capScope: "system",
52948
+ addonId: null,
52949
+ access: "view"
52950
+ },
51740
52951
  "coreBlocks.restart": {
51741
52952
  capName: "core-blocks",
51742
52953
  capScope: "system",
@@ -51755,6 +52966,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
51755
52966
  addonId: null,
51756
52967
  access: "create"
51757
52968
  },
52969
+ "coreBlocks.updateComposition": {
52970
+ capName: "core-blocks",
52971
+ capScope: "system",
52972
+ addonId: null,
52973
+ access: "create"
52974
+ },
52975
+ "coreBlocks.validateComposition": {
52976
+ capName: "core-blocks",
52977
+ capScope: "system",
52978
+ addonId: null,
52979
+ access: "view"
52980
+ },
51758
52981
  "cover.close": {
51759
52982
  capName: "cover",
51760
52983
  capScope: "device",
@@ -57814,6 +59037,7 @@ var KNOWN_CAP_NAMES = [
57814
59037
  "carbon-monoxide",
57815
59038
  "climate-control",
57816
59039
  "color",
59040
+ "composer",
57817
59041
  "connection-test",
57818
59042
  "connectivity",
57819
59043
  "consumables",
@@ -58051,6 +59275,7 @@ var SYSTEM_CAP_NAMES = [
58051
59275
  "auth-provider",
58052
59276
  "backup",
58053
59277
  "broker",
59278
+ "composer",
58054
59279
  "connection-test",
58055
59280
  "core-blocks",
58056
59281
  "custom-model-registry",
@@ -60876,7 +62101,12 @@ function createSystemProxy(api) {
60876
62101
  setEnabled: (input) => dispatch("coreBlocks", "setEnabled", "mutation", input),
60877
62102
  restart: (input) => dispatch("coreBlocks", "restart", "mutation", input),
60878
62103
  compile: (input) => dispatch("coreBlocks", "compile", "mutation", input),
60879
- getTypeDefs: (input) => dispatch("coreBlocks", "getTypeDefs", "query", input)
62104
+ getTypeDefs: (input) => dispatch("coreBlocks", "getTypeDefs", "query", input),
62105
+ createComposition: (input) => dispatch("coreBlocks", "createComposition", "mutation", input),
62106
+ updateComposition: (input) => dispatch("coreBlocks", "updateComposition", "mutation", input),
62107
+ getComposition: (input) => dispatch("coreBlocks", "getComposition", "query", input),
62108
+ listCompositions: (input) => dispatch("coreBlocks", "listCompositions", "query", input),
62109
+ validateComposition: (input) => dispatch("coreBlocks", "validateComposition", "mutation", input)
60880
62110
  },
60881
62111
  decoder: {
60882
62112
  supportsCodec: (input) => dispatch("decoder", "supportsCodec", "query", input),
@@ -67113,11 +68343,13 @@ exports.AutomationControlStatusSchema = AutomationControlStatusSchema;
67113
68343
  exports.AutomationRecipeSchema = AutomationRecipeSchema;
67114
68344
  exports.AutomationTriggerSchema = AutomationTriggerSchema;
67115
68345
  exports.AvailableIntegrationTypeSchema = AvailableIntegrationTypeSchema;
68346
+ exports.BASE_DEVICE_CAPS = BASE_DEVICE_CAPS;
67116
68347
  exports.BASE_LIVE_EGRESS_PROFILE = BASE_LIVE_EGRESS_PROFILE;
67117
68348
  exports.BATTERY_DEVICE_PROFILE = BATTERY_DEVICE_PROFILE;
67118
68349
  exports.BATTERY_UNREACHABLE_AFTER_MS = BATTERY_UNREACHABLE_AFTER_MS;
67119
68350
  exports.BIRTH_DECISION_DEFAULT_LIMIT = BIRTH_DECISION_DEFAULT_LIMIT;
67120
68351
  exports.BOOT_RECOVERY_BACKOFF_MS = require_sleep.BOOT_RECOVERY_BACKOFF_MS;
68352
+ exports.BUILTIN_RESULT_KINDS = BUILTIN_RESULT_KINDS;
67121
68353
  exports.BackgroundRunRegistry = BackgroundRunRegistry;
67122
68354
  exports.BacklightModeSchema = BacklightModeSchema;
67123
68355
  exports.BackupDestinationInfoSchema = BackupDestinationInfoSchema;
@@ -67185,6 +68417,9 @@ exports.CLUSTER_MODEL_SECTION_ID = CLUSTER_MODEL_SECTION_ID;
67185
68417
  exports.CLUSTER_STEP_SETTING_FIELDS = CLUSTER_STEP_SETTING_FIELDS;
67186
68418
  exports.COCO_80_LABELS = COCO_80_LABELS;
67187
68419
  exports.COCO_TO_MACRO = COCO_TO_MACRO;
68420
+ exports.COMPOSED_DEVICE_STABLE_ID_PREFIX = COMPOSED_DEVICE_STABLE_ID_PREFIX;
68421
+ exports.COMPOSER_ADDON_ID = COMPOSER_ADDON_ID;
68422
+ exports.COMPOSER_RUNNER_ID = COMPOSER_RUNNER_ID;
67188
68423
  exports.CONNECTION_TEST_TIMEOUT_MS = CONNECTION_TEST_TIMEOUT_MS;
67189
68424
  exports.CONTAINER_CHILD_PRIORITY = CONTAINER_CHILD_PRIORITY;
67190
68425
  exports.CORE_BLOCKS_ADDON_ID = CORE_BLOCKS_ADDON_ID;
@@ -67253,6 +68488,45 @@ exports.ClusterModelChoiceSchema = ClusterModelChoiceSchema;
67253
68488
  exports.CollectionColumnSchema = CollectionColumnSchema;
67254
68489
  exports.CollectionIndexSchema = CollectionIndexSchema;
67255
68490
  exports.ColorStatusSchema = ColorStatusSchema;
68491
+ exports.ComposerApplyInputSchema = ComposerApplyInputSchema;
68492
+ exports.ComposerApplyResultSchema = ComposerApplyResultSchema;
68493
+ exports.ComposerBlockSchema = ComposerBlockSchema;
68494
+ exports.ComposerBlockVerdictSchema = ComposerBlockVerdictSchema;
68495
+ exports.ComposerUnreadableBlockSchema = ComposerUnreadableBlockSchema;
68496
+ exports.CompositionBindingSchema = CompositionBindingSchema;
68497
+ exports.CompositionBlockInputSchema = CompositionBlockInputSchema;
68498
+ exports.CompositionBlockStateSchema = CompositionBlockStateSchema;
68499
+ exports.CompositionCodeSourceSchema = CompositionCodeSourceSchema;
68500
+ exports.CompositionCommandTargetSchema = CompositionCommandTargetSchema;
68501
+ exports.CompositionExistingTargetSchema = CompositionExistingTargetSchema;
68502
+ exports.CompositionExpressionSourceSchema = CompositionExpressionSourceSchema;
68503
+ exports.CompositionFeatureSchema = CompositionFeatureSchema;
68504
+ exports.CompositionFieldKindSchema = CompositionFieldKindSchema;
68505
+ exports.CompositionFieldPlanSchema = CompositionFieldPlanSchema;
68506
+ exports.CompositionFieldReadSchema = CompositionFieldReadSchema;
68507
+ exports.CompositionFieldRoleSchema = CompositionFieldRoleSchema;
68508
+ exports.CompositionFieldSourceSchema = CompositionFieldSourceSchema;
68509
+ exports.CompositionFieldStateKindSchema = CompositionFieldStateKindSchema;
68510
+ exports.CompositionFieldStateSchema = CompositionFieldStateSchema;
68511
+ exports.CompositionFieldsFeatureSchema = CompositionFieldsFeatureSchema;
68512
+ exports.CompositionForwardCommandSchema = CompositionForwardCommandSchema;
68513
+ exports.CompositionFromSourceSchema = CompositionFromSourceSchema;
68514
+ exports.CompositionGetReadSchema = CompositionGetReadSchema;
68515
+ exports.CompositionGraphError = CompositionGraphError;
68516
+ exports.CompositionListReadSchema = CompositionListReadSchema;
68517
+ exports.CompositionLiteralBindingSchema = CompositionLiteralBindingSchema;
68518
+ exports.CompositionNewTargetSchema = CompositionNewTargetSchema;
68519
+ exports.CompositionPassthroughFeatureSchema = CompositionPassthroughFeatureSchema;
68520
+ exports.CompositionProblemCodeSchema = CompositionProblemCodeSchema;
68521
+ exports.CompositionProblemSchema = CompositionProblemSchema;
68522
+ exports.CompositionRunStatusSchema = CompositionRunStatusSchema;
68523
+ exports.CompositionSchema = CompositionSchema;
68524
+ exports.CompositionSourceRefSchema = CompositionSourceRefSchema;
68525
+ exports.CompositionStateReadSchema = CompositionStateReadSchema;
68526
+ exports.CompositionTargetSchema = CompositionTargetSchema;
68527
+ exports.CompositionUnavailableModeSchema = CompositionUnavailableModeSchema;
68528
+ exports.CompositionValidationSchema = CompositionValidationSchema;
68529
+ exports.CompositionViewSchema = CompositionViewSchema;
67256
68530
  exports.ConfigEntrySchema = ConfigEntrySchema;
67257
68531
  exports.ConfigSectionWithValuesSchema = ConfigSectionWithValuesSchema;
67258
68532
  exports.ConfigTabDeclarationSchema = ConfigTabDeclarationSchema;
@@ -67272,6 +68546,7 @@ exports.CoreBlockCompileResultSchema = CoreBlockCompileResultSchema;
67272
68546
  exports.CoreBlockInputSchema = CoreBlockInputSchema;
67273
68547
  exports.CoreBlockPlacementSchema = CoreBlockPlacementSchema;
67274
68548
  exports.CoreBlockSchema = CoreBlockSchema;
68549
+ exports.CoreBlockSourceSchema = CoreBlockSourceSchema;
67275
68550
  exports.CoreBlockStatusSchema = CoreBlockStatusSchema;
67276
68551
  exports.CoverStateSchema = CoverStateSchema;
67277
68552
  exports.CoverStatusSchema = CoverStatusSchema;
@@ -67604,6 +68879,9 @@ exports.MAX_BIRTH_DECISION_RECORDS = MAX_BIRTH_DECISION_RECORDS;
67604
68879
  exports.MAX_BIRTH_LATENCY_PROXY_MS = MAX_BIRTH_LATENCY_PROXY_MS;
67605
68880
  exports.MAX_CLIP_EVENT_IDS = MAX_CLIP_EVENT_IDS;
67606
68881
  exports.MAX_CLIP_LABELS = MAX_CLIP_LABELS;
68882
+ exports.MAX_COMPOSITION_CODE_LENGTH = MAX_COMPOSITION_CODE_LENGTH;
68883
+ exports.MAX_COMPOSITION_FEATURES = MAX_COMPOSITION_FEATURES;
68884
+ exports.MAX_COMPOSITION_FIELDS_PER_FEATURE = MAX_COMPOSITION_FIELDS_PER_FEATURE;
67607
68885
  exports.MAX_CONDITION_DEPTH = MAX_CONDITION_DEPTH;
67608
68886
  exports.MAX_CONDITION_LEAVES = MAX_CONDITION_LEAVES;
67609
68887
  exports.MAX_EXPRESSION_AST_NODES = MAX_EXPRESSION_AST_NODES;
@@ -67621,6 +68899,7 @@ exports.METHOD_ACCESS_MAP = METHOD_ACCESS_MAP;
67621
68899
  exports.METHOD_DEVICE_SELECTORS = METHOD_DEVICE_SELECTORS;
67622
68900
  exports.MINUTES_PER_DAY = MINUTES_PER_DAY;
67623
68901
  exports.MODEL_FORMATS = MODEL_FORMATS;
68902
+ exports.MODEL_HOSTING = MODEL_HOSTING;
67624
68903
  exports.MODEL_PROVIDER_IDS = MODEL_PROVIDER_IDS;
67625
68904
  exports.MOTION_CLOSE_AFTER_MS = MOTION_CLOSE_AFTER_MS;
67626
68905
  exports.MOTION_TRIGGER_FEATURE = MOTION_TRIGGER_FEATURE;
@@ -67660,6 +68939,7 @@ exports.ModelDistributeResultSchema = ModelDistributeResultSchema;
67660
68939
  exports.ModelExtraFileSchema = ModelExtraFileSchema;
67661
68940
  exports.ModelFormatEntrySchema = ModelFormatEntrySchema;
67662
68941
  exports.ModelFormatsSchema = ModelFormatsSchema;
68942
+ exports.ModelLicenseSchema = ModelLicenseSchema;
67663
68943
  exports.ModelProviderIdSchema = ModelProviderIdSchema;
67664
68944
  exports.ModelSubstitutionSchema = ModelSubstitutionSchema;
67665
68945
  exports.ModelVariantGroupSchema = ModelVariantGroupSchema;
@@ -67968,6 +69248,9 @@ exports.REDACTED_SECRET = REDACTED_SECRET;
67968
69248
  exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
67969
69249
  exports.RESERVED_COLLECTION_SOURCES = RESERVED_COLLECTION_SOURCES;
67970
69250
  exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
69251
+ exports.ROOM_EXAMPLE_MOTION_SOURCES = ROOM_EXAMPLE_MOTION_SOURCES;
69252
+ exports.ROOM_EXAMPLE_TEMPERATURE_SOURCES = ROOM_EXAMPLE_TEMPERATURE_SOURCES;
69253
+ exports.ROOM_OCCUPANCY_WINDOW_MS = ROOM_OCCUPANCY_WINDOW_MS;
67971
69254
  exports.ROOT_BUCKET_KEY = ROOT_BUCKET_KEY;
67972
69255
  exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
67973
69256
  exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
@@ -68365,6 +69648,7 @@ exports.brightnessCapability = brightnessCapability;
68365
69648
  exports.brokerCapability = brokerCapability;
68366
69649
  exports.buildAddonRouteProvider = buildAddonRouteProvider;
68367
69650
  exports.buildAudioArgs = require_canonical_hash.buildAudioArgs;
69651
+ exports.buildCapabilityLookup = buildCapabilityLookup;
68368
69652
  exports.buildDeviceAdminUrl = buildDeviceAdminUrl;
68369
69653
  exports.buildEventKindDescriptor = buildEventKindDescriptor;
68370
69654
  exports.buildFfmpegArgs = require_canonical_hash.buildFfmpegArgs;
@@ -68404,6 +69688,14 @@ exports.compareToTarget = compareToTarget;
68404
69688
  exports.compileExpression = compileExpression;
68405
69689
  exports.compileExpressionSafe = compileExpressionSafe;
68406
69690
  exports.composeSwitchedOff = composeSwitchedOff;
69691
+ exports.composedDeviceRef = composedDeviceRef;
69692
+ exports.composedDeviceStableId = composedDeviceStableId;
69693
+ exports.composerCapability = composerCapability;
69694
+ exports.compositionEdges = compositionEdges;
69695
+ exports.compositionReadKey = compositionReadKey;
69696
+ exports.compositionSliceKey = compositionSliceKey;
69697
+ exports.compositionSourceKey = compositionSourceKey;
69698
+ exports.compositionSourceRefs = compositionSourceRefs;
68407
69699
  exports.conditionDepth = conditionDepth;
68408
69700
  exports.conditionExclusionReason = conditionExclusionReason;
68409
69701
  exports.conditionVisibleForKind = conditionVisibleForKind;
@@ -68415,7 +69707,9 @@ exports.contactCapability = contactCapability;
68415
69707
  exports.controlCapability = controlCapability;
68416
69708
  exports.convertUnit = convertUnit;
68417
69709
  exports.coreBlockAddonId = coreBlockAddonId;
69710
+ exports.coreBlockComposition = coreBlockComposition;
68418
69711
  exports.coreBlockIdFromAddonId = coreBlockIdFromAddonId;
69712
+ exports.coreBlockSourceKind = coreBlockSourceKind;
68419
69713
  exports.coreBlocksCapability = coreBlocksCapability;
68420
69714
  exports.cosineSimilarity = cosineSimilarity;
68421
69715
  exports.countConditionLeaves = countConditionLeaves;
@@ -68454,8 +69748,10 @@ exports.describeCustomActions = require_sleep.describeCustomActions;
68454
69748
  exports.describeModelVariant = describeModelVariant;
68455
69749
  exports.describeOnboardClamp = describeOnboardClamp;
68456
69750
  exports.describeOnboardRefusal = describeOnboardRefusal;
69751
+ exports.describeStateField = describeStateField;
68457
69752
  exports.describeSystemEventDeliveryProblem = describeSystemEventDeliveryProblem;
68458
69753
  exports.describeSystemEventFamilyProblem = describeSystemEventFamilyProblem;
69754
+ exports.describeTopLevelStateFields = describeTopLevelStateFields;
68459
69755
  exports.detectAccessRole = detectAccessRole;
68460
69756
  exports.detectionPipelineCapability = detectionPipelineCapability;
68461
69757
  exports.deviceAdminLinkCapability = deviceAdminLinkCapability;
@@ -68495,6 +69791,7 @@ exports.enumerateItemArrayFields = enumerateItemArrayFields;
68495
69791
  exports.enumerateSchemaFields = enumerateSchemaFields;
68496
69792
  exports.errMsg = require_err_msg.errMsg;
68497
69793
  exports.evaluateAst = evaluateAst;
69794
+ exports.evaluateCompositionField = evaluateCompositionField;
68498
69795
  exports.evaluateExpressionSource = evaluateExpressionSource;
68499
69796
  exports.evaluatePoolMemory = evaluatePoolMemory;
68500
69797
  exports.evaluateSensorEdge = evaluateSensorEdge;
@@ -68515,7 +69812,9 @@ exports.failureContributionCapability = failureContributionCapability;
68515
69812
  exports.failureRate = failureRate;
68516
69813
  exports.fanControlCapability = fanControlCapability;
68517
69814
  exports.featureProbeCapability = featureProbeCapability;
69815
+ exports.fieldReads = fieldReads;
68518
69816
  exports.filesystemBrowseCapability = filesystemBrowseCapability;
69817
+ exports.findCompositionCycles = findCompositionCycles;
68519
69818
  exports.findTimezone = findTimezone;
68520
69819
  exports.floodCapability = floodCapability;
68521
69820
  exports.foldSnapshotByFunction = foldSnapshotByFunction;
@@ -68539,6 +69838,7 @@ exports.humiditySensorCapability = humiditySensorCapability;
68539
69838
  exports.hydrateSchema = require_sleep.hydrateSchema;
68540
69839
  exports.imageCapability = imageCapability;
68541
69840
  exports.imageSettingsCapability = imageSettingsCapability;
69841
+ exports.inferExpressionKinds = inferExpressionKinds;
68542
69842
  exports.inferModelProvider = inferModelProvider;
68543
69843
  exports.initialPoolMemoryState = initialPoolMemoryState;
68544
69844
  exports.integrationsCapability = integrationsCapability;
@@ -68588,6 +69888,7 @@ exports.isVoidInput = isVoidInput;
68588
69888
  exports.ituDbfsFromPreEpoch = ituDbfsFromPreEpoch;
68589
69889
  exports.jobKindSchema = jobKindSchema;
68590
69890
  exports.kebabToCamel = kebabToCamel;
69891
+ exports.kindOfValue = kindOfValue;
68591
69892
  exports.kindsForDiscriminator = kindsForDiscriminator;
68592
69893
  exports.knownValues = knownValues;
68593
69894
  exports.lawnMowerControlCapability = lawnMowerControlCapability;
@@ -68690,6 +69991,7 @@ exports.pipelineAnalyticsCapability = pipelineAnalyticsCapability;
68690
69991
  exports.pipelineExecutorCapability = pipelineExecutorCapability;
68691
69992
  exports.pipelineOrchestratorCapability = pipelineOrchestratorCapability;
68692
69993
  exports.pipelineRunnerCapability = pipelineRunnerCapability;
69994
+ exports.planComposition = planComposition;
68693
69995
  exports.plateGalleryCapability = plateGalleryCapability;
68694
69996
  exports.platformProbeCapability = platformProbeCapability;
68695
69997
  exports.poolMemoryThreshold = poolMemoryThreshold;
@@ -68720,6 +70022,7 @@ exports.recordingOnboardCapability = recordingOnboardCapability;
68720
70022
  exports.recordingSignalCapability = recordingSignalCapability;
68721
70023
  exports.rectsToCells = rectsToCells;
68722
70024
  exports.reducePoints = reducePoints;
70025
+ exports.requiredTopLevelKeys = requiredTopLevelKeys;
68723
70026
  exports.requiresPython = requiresPython;
68724
70027
  exports.reservedSourceOwner = reservedSourceOwner;
68725
70028
  exports.resetPoolBaseline = resetPoolBaseline;
@@ -68749,6 +70052,7 @@ exports.resolveStreamMaps = require_canonical_hash.resolveStreamMaps;
68749
70052
  exports.resolveVariantModelId = resolveVariantModelId;
68750
70053
  exports.resolveViewableDeviceIds = resolveViewableDeviceIds;
68751
70054
  exports.roleSpec = roleSpec;
70055
+ exports.roomCompositionExample = roomCompositionExample;
68752
70056
  exports.ruleBelongsToAlarm = ruleBelongsToAlarm;
68753
70057
  exports.ruleEditorSectionsForKind = ruleEditorSectionsForKind;
68754
70058
  exports.ruleKindOf = ruleKindOf;
@@ -68854,6 +70158,7 @@ exports.updateCapability = updateCapability;
68854
70158
  exports.userManagementCapability = userManagementCapability;
68855
70159
  exports.userPasskeysCapability = userPasskeysCapability;
68856
70160
  exports.vacuumControlCapability = vacuumControlCapability;
70161
+ exports.validateComposition = validateComposition;
68857
70162
  exports.validateExpressionSource = validateExpressionSource;
68858
70163
  exports.validateRecipeBounds = validateRecipeBounds;
68859
70164
  exports.valveCapability = valveCapability;