@camstack/types 1.2.264 → 1.2.266

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 (35) 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 +4 -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 +4 -3
  10. package/dist/capabilities/model-distributor.cap.d.ts +4 -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/capabilities/pipeline-executor.cap.d.ts +5 -0
  14. package/dist/composition/composition-graph.d.ts +36 -0
  15. package/dist/composition/composition-report.d.ts +264 -0
  16. package/dist/composition/composition.d.ts +362 -0
  17. package/dist/composition/evaluate-field.d.ts +63 -0
  18. package/dist/composition/examples.d.ts +10 -0
  19. package/dist/composition/expression-kinds.d.ts +36 -0
  20. package/dist/composition/index.d.ts +12 -0
  21. package/dist/composition/state-fields.d.ts +19 -0
  22. package/dist/composition/validate-composition.d.ts +46 -0
  23. package/dist/device/schema-fields.d.ts +3 -0
  24. package/dist/expression/builtins.d.ts +30 -2
  25. package/dist/generated/addon-api.d.ts +57 -0
  26. package/dist/generated/capability-router-map.d.ts +5 -2
  27. package/dist/generated/method-access-map.d.ts +1 -1
  28. package/dist/generated/system-proxy.d.ts +1 -1
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +1350 -80
  31. package/dist/index.mjs +1278 -81
  32. package/dist/notification/rule-kinds.d.ts +3 -2
  33. package/dist/types/models.d.ts +5 -3
  34. package/dist/types/pipeline-step.d.ts +6 -1
  35. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5314,6 +5314,16 @@ var ModelCatalogEntrySchema = zod.z.object({
5314
5314
  */
5315
5315
  postprocessor: zod.z.custom().optional(),
5316
5316
  /**
5317
+ * Per-MODEL default confidence floor. Absent ⇒ the step's
5318
+ * `StepDefinition.defaultConfidence` applies. A score is a property of one
5319
+ * model's output scale, not of the step slot it sits in: YuNet's
5320
+ * `sqrt(cls·obj)` is not SCRFD's score, and inheriting SCRFD's 0.5 filled the
5321
+ * face gallery with wheels and hands (D662, amends D645). An operator's
5322
+ * explicit value still wins over it. Resolved in ONE place —
5323
+ * `addon-pipeline/.../registry/effective-confidence.ts`.
5324
+ */
5325
+ defaultConfidence: zod.z.number().min(0).max(1).optional(),
5326
+ /**
5317
5327
  * When true, the executor produces a landmark-aligned crop (similarity warp
5318
5328
  * onto the canonical template) before this step runs, instead of a plain
5319
5329
  * axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
@@ -5547,23 +5557,6 @@ var ExpressionEvalError = class extends Error {
5547
5557
  };
5548
5558
  //#endregion
5549
5559
  //#region src/expression/builtins.ts
5550
- /**
5551
- * Frozen, null-prototype builtin function table for the expression engine
5552
- * (spec §4 rule 4). The table is the SOLE surface of callable functions: the
5553
- * parser rejects any callee not in it, and the evaluator gates each call on an
5554
- * own-property check against it.
5555
- *
5556
- * Because the object has a NULL prototype AND is `Object.freeze`d:
5557
- * - it cannot be polluted (no `__proto__` / `constructor` write reaches it);
5558
- * - a lookup for `toString` / `hasOwnProperty` / `constructor` finds NOTHING
5559
- * (there is no `Object.prototype` in the chain), so those names are not
5560
- * callable — they are simply "unknown function" at parse time.
5561
- *
5562
- * Every numeric argument is validated as a finite number and every numeric
5563
- * RESULT is re-checked finite, so `/0`, `sqrt(-1)` (→ NaN) and overflow
5564
- * (`pow(10,400)` → Infinity) all raise `ExpressionEvalError` and fail the link
5565
- * closed rather than emitting a garbage value.
5566
- */
5567
5560
  function asFiniteNumber(value, name, index) {
5568
5561
  if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
5569
5562
  return value;
@@ -5579,6 +5572,10 @@ function finiteResult(value, name) {
5579
5572
  function allFiniteNumbers(args, name) {
5580
5573
  return args.map((a, idx) => asFiniteNumber(a, name, idx));
5581
5574
  }
5575
+ function asBoolean$1(value, name, index) {
5576
+ if (typeof value !== "boolean") throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a boolean`);
5577
+ return value;
5578
+ }
5582
5579
  var INF = Number.POSITIVE_INFINITY;
5583
5580
  var table = {
5584
5581
  min: {
@@ -5679,6 +5676,40 @@ var table = {
5679
5676
  if (from === to) return x;
5680
5677
  throw new ExpressionEvalError("convert: unit conversion table not installed");
5681
5678
  }
5679
+ },
5680
+ any: {
5681
+ minArgs: 1,
5682
+ maxArgs: INF,
5683
+ apply: (args) => args.map((a, i) => asBoolean$1(a, "any", i)).some((b) => b)
5684
+ },
5685
+ all: {
5686
+ minArgs: 1,
5687
+ maxArgs: INF,
5688
+ apply: (args) => args.map((a, i) => asBoolean$1(a, "all", i)).every((b) => b)
5689
+ },
5690
+ within: {
5691
+ minArgs: 2,
5692
+ maxArgs: 2,
5693
+ apply: (args, hooks) => {
5694
+ const windowMs = asFiniteNumber(args[1], "within", 1);
5695
+ if (windowMs < 0) throw new ExpressionEvalError("within: the window must not be negative");
5696
+ const at = args[0];
5697
+ if (at === null) return false;
5698
+ const ts = asFiniteNumber(at, "within", 0);
5699
+ const now = hooks.now;
5700
+ if (now === void 0 || !Number.isFinite(now)) throw new ExpressionEvalError("within: no clock was supplied to this evaluation");
5701
+ const inside = now - ts <= windowMs;
5702
+ if (inside) hooks.noteDeadline?.(ts + windowMs + 1);
5703
+ return inside;
5704
+ }
5705
+ },
5706
+ latest: {
5707
+ minArgs: 1,
5708
+ maxArgs: INF,
5709
+ apply: (args) => {
5710
+ const present = args.flatMap((a, i) => a === null ? [] : [asFiniteNumber(a, "latest", i)]);
5711
+ return present.length === 0 ? null : finiteResult(Math.max(...present), "latest");
5712
+ }
5682
5713
  }
5683
5714
  };
5684
5715
  /** Frozen, null-prototype builtin table. */
@@ -6283,10 +6314,17 @@ function evaluateExpressionSource(expr, bindingValues, now, opts) {
6283
6314
  ...bindingValues,
6284
6315
  ["now"]: now
6285
6316
  });
6317
+ const hooks = {
6318
+ ...opts?.hooks,
6319
+ now
6320
+ };
6286
6321
  try {
6287
6322
  return {
6288
6323
  ok: true,
6289
- value: evaluateAst(compiled.parsed.ast, scope, opts)
6324
+ value: evaluateAst(compiled.parsed.ast, scope, {
6325
+ ...opts,
6326
+ hooks
6327
+ })
6290
6328
  };
6291
6329
  } catch (err) {
6292
6330
  return {
@@ -10098,6 +10136,277 @@ function kebabToCamel(s) {
10098
10136
  return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
10099
10137
  }
10100
10138
  //#endregion
10139
+ //#region src/composition/composition.ts
10140
+ /**
10141
+ * A COMPOSITION: a device assembled from other devices' state (D659).
10142
+ *
10143
+ * The data half of the composed-devices spec §1. A composition is stored inside
10144
+ * a core block (`CoreBlock.source.kind === 'composition'`) and run by the shared
10145
+ * `composer` runner. It names a TARGET and, for each capability of the target, a
10146
+ * FIELD MAP: every state field has exactly one source.
10147
+ *
10148
+ * Sources are keyed by `(addonId, stableId)`, NEVER by numeric device id. A
10149
+ * resync reissues numeric ids, and a composition that stored them would be
10150
+ * orphaned by the first re-adoption (the linked-devices lesson). A `stableId` is
10151
+ * unique only within its owning addon, so the owner travels with it.
10152
+ *
10153
+ * Later slices EXTEND these unions and do not reshape them. The variants a later
10154
+ * slice implements are already in the wire shape, and `validateComposition`
10155
+ * refuses them BY NAME until then:
10156
+ * target `existing` → slice 2 · feature `passthrough` → slice 3 ·
10157
+ * `commands` (`forward`) → slice 3 · source `code` → slice 4.
10158
+ */
10159
+ /** The owner of every composed device: the `composer` builtin. */
10160
+ var COMPOSER_ADDON_ID = "composer";
10161
+ /** A composed device's stableId is this prefix plus the block id. */
10162
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
10163
+ /** A device with more capabilities than this is two devices. */
10164
+ var MAX_COMPOSITION_FEATURES = 16;
10165
+ var MAX_COMPOSITION_FIELDS_PER_FEATURE = 32;
10166
+ /** Longest snippet a `code` source may carry (slice 4). */
10167
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
10168
+ var CompositionSourceRefSchema = zod.z.object({
10169
+ /** The owning addon; `stableId` is unique only within it. */
10170
+ addonId: zod.z.string().min(1),
10171
+ stableId: zod.z.string().min(1)
10172
+ });
10173
+ /** One field of one capability of one source device. */
10174
+ var CompositionFieldReadSchema = zod.z.object({
10175
+ source: CompositionSourceRefSchema,
10176
+ cap: zod.z.string().min(1),
10177
+ /** Dotted path into the source cap's runtime-state slice. */
10178
+ fieldPath: zod.z.string().min(1)
10179
+ });
10180
+ /** `from`: copy one source field verbatim. It is also an expression's `from` binding. */
10181
+ var CompositionFromSourceSchema = CompositionFieldReadSchema.extend({ kind: zod.z.literal("from") });
10182
+ /** A constant binding. No device is read. */
10183
+ var CompositionLiteralBindingSchema = zod.z.object({
10184
+ kind: zod.z.literal("literal"),
10185
+ value: zod.z.union([
10186
+ zod.z.string(),
10187
+ zod.z.number(),
10188
+ zod.z.boolean(),
10189
+ zod.z.null()
10190
+ ])
10191
+ });
10192
+ var CompositionBindingSchema = zod.z.discriminatedUnion("kind", [CompositionFromSourceSchema, CompositionLiteralBindingSchema]);
10193
+ /**
10194
+ * `expression`: a formula over named bindings, in the salvaged expression
10195
+ * engine. Validated at parse by the SAME function every other consumer runs,
10196
+ * so the editor and the store cannot disagree.
10197
+ */
10198
+ var CompositionExpressionSourceSchema = zod.z.object({
10199
+ kind: zod.z.literal("expression"),
10200
+ expr: zod.z.string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
10201
+ bindings: zod.z.record(zod.z.string().regex(EXPRESSION_IDENTIFIER_RE), CompositionBindingSchema)
10202
+ }).superRefine((src, ctx) => {
10203
+ const err = validateExpressionSource(src);
10204
+ if (err !== null) ctx.addIssue({
10205
+ code: "custom",
10206
+ message: err,
10207
+ path: ["expr"]
10208
+ });
10209
+ });
10210
+ /** `code`: RESERVED for slice 4 (own runner, one-way eject). */
10211
+ var CompositionCodeSourceSchema = zod.z.object({
10212
+ kind: zod.z.literal("code"),
10213
+ code: zod.z.string().min(1).max(MAX_COMPOSITION_CODE_LENGTH)
10214
+ });
10215
+ var CompositionFieldSourceSchema = zod.z.discriminatedUnion("kind", [
10216
+ CompositionFromSourceSchema,
10217
+ CompositionExpressionSourceSchema,
10218
+ CompositionCodeSourceSchema
10219
+ ]);
10220
+ /** `forward`: RESERVED for slice 3. Argument mapping arrives there as an additive optional `args`. */
10221
+ var CompositionForwardCommandSchema = zod.z.object({
10222
+ kind: zod.z.literal("forward"),
10223
+ source: CompositionSourceRefSchema,
10224
+ cap: zod.z.string().min(1),
10225
+ method: zod.z.string().min(1)
10226
+ });
10227
+ var CompositionCommandTargetSchema = zod.z.discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
10228
+ var CompositionFieldsFeatureSchema = zod.z.object({
10229
+ kind: zod.z.literal("fields"),
10230
+ cap: zod.z.string().min(1),
10231
+ fields: zod.z.record(zod.z.string().min(1), CompositionFieldSourceSchema).refine((fields) => Object.keys(fields).length <= 32, { message: `at most 32 fields per capability` }),
10232
+ /** RESERVED for slice 3; refused by the validator when non-empty. */
10233
+ commands: zod.z.record(zod.z.string().min(1), CompositionCommandTargetSchema).optional()
10234
+ });
10235
+ /** RESERVED for slice 3: a media / data-plane cap, whole, from one source. */
10236
+ var CompositionPassthroughFeatureSchema = zod.z.object({
10237
+ kind: zod.z.literal("passthrough"),
10238
+ cap: zod.z.string().min(1),
10239
+ source: CompositionSourceRefSchema
10240
+ });
10241
+ var CompositionFeatureSchema = zod.z.discriminatedUnion("kind", [CompositionFieldsFeatureSchema, CompositionPassthroughFeatureSchema]);
10242
+ /**
10243
+ * A NEW device. It has no `name` of its own: the block's name IS the device's
10244
+ * name (2026-08-08). A second copy here would be a second writer of one string (D62).
10245
+ */
10246
+ var CompositionNewTargetSchema = zod.z.object({
10247
+ kind: zod.z.literal("new"),
10248
+ type: zod.z.enum(require_sleep.DeviceType),
10249
+ role: zod.z.enum(require_sleep.DeviceRole).optional()
10250
+ });
10251
+ /** RESERVED for slice 2: customize an existing device. */
10252
+ var CompositionExistingTargetSchema = zod.z.object({
10253
+ kind: zod.z.literal("existing"),
10254
+ device: CompositionSourceRefSchema
10255
+ });
10256
+ var CompositionTargetSchema = zod.z.discriminatedUnion("kind", [CompositionNewTargetSchema, CompositionExistingTargetSchema]);
10257
+ var CompositionSchema = zod.z.object({
10258
+ target: CompositionTargetSchema,
10259
+ features: zod.z.array(CompositionFeatureSchema).min(1).max(16)
10260
+ }).superRefine((composition, ctx) => {
10261
+ const seen = /* @__PURE__ */ new Set();
10262
+ composition.features.forEach((feature, index) => {
10263
+ if (seen.has(feature.cap)) ctx.addIssue({
10264
+ code: "custom",
10265
+ message: `capability \`${feature.cap}\` is composed twice — one feature per capability`,
10266
+ path: [
10267
+ "features",
10268
+ index,
10269
+ "cap"
10270
+ ]
10271
+ });
10272
+ seen.add(feature.cap);
10273
+ });
10274
+ });
10275
+ function compositionSourceKey(ref) {
10276
+ return `${ref.addonId}/${ref.stableId}`;
10277
+ }
10278
+ function compositionSliceKey(ref, cap) {
10279
+ return `${compositionSourceKey(ref)}#${cap}`;
10280
+ }
10281
+ function compositionReadKey(read) {
10282
+ return `${compositionSliceKey(read.source, read.cap)}.${read.fieldPath}`;
10283
+ }
10284
+ function composedDeviceStableId(blockId) {
10285
+ return `${COMPOSED_DEVICE_STABLE_ID_PREFIX}${blockId}`;
10286
+ }
10287
+ function composedDeviceRef(blockId) {
10288
+ return {
10289
+ addonId: COMPOSER_ADDON_ID,
10290
+ stableId: composedDeviceStableId(blockId)
10291
+ };
10292
+ }
10293
+ /** Every device field a source reads: `from` is one read, an expression is its `from` bindings. */
10294
+ function fieldReads(source) {
10295
+ switch (source.kind) {
10296
+ case "from": return [{
10297
+ source: source.source,
10298
+ cap: source.cap,
10299
+ fieldPath: source.fieldPath
10300
+ }];
10301
+ case "expression": return Object.values(source.bindings).flatMap((binding) => binding.kind === "from" ? [{
10302
+ source: binding.source,
10303
+ cap: binding.cap,
10304
+ fieldPath: binding.fieldPath
10305
+ }] : []);
10306
+ case "code": return [];
10307
+ }
10308
+ }
10309
+ //#endregion
10310
+ //#region src/composition/composition-report.ts
10311
+ /**
10312
+ * What the validator says about a composition and what the composer says about
10313
+ * a running one: the wire shapes the API returns (D659).
10314
+ */
10315
+ var CompositionProblemCodeSchema = zod.z.enum([
10316
+ "reserved-variant",
10317
+ "unknown-capability",
10318
+ "not-composable",
10319
+ "wrong-device-type",
10320
+ "has-commands",
10321
+ "unknown-field",
10322
+ "nested-field",
10323
+ "missing-field",
10324
+ "kind-mismatch",
10325
+ "may-be-null",
10326
+ "source-missing",
10327
+ "source-unreadable",
10328
+ "source-capability-missing",
10329
+ "source-field-unknown",
10330
+ "expression-invalid",
10331
+ "cycle",
10332
+ "duplicate-target"
10333
+ ]);
10334
+ /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
10335
+ var CompositionProblemSchema = zod.z.object({
10336
+ code: CompositionProblemCodeSchema,
10337
+ path: zod.z.string(),
10338
+ message: zod.z.string()
10339
+ });
10340
+ var CompositionFieldKindSchema = zod.z.enum([
10341
+ "string",
10342
+ "number",
10343
+ "boolean",
10344
+ "enum"
10345
+ ]);
10346
+ /** How a field says "unavailable": `null` when its schema allows it, else the device goes offline (D393). */
10347
+ var CompositionUnavailableModeSchema = zod.z.enum(["null", "offline"]);
10348
+ /**
10349
+ * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
10350
+ * field of its cap changes value · `constant` reads nothing, computed once.
10351
+ */
10352
+ var CompositionFieldRoleSchema = zod.z.enum([
10353
+ "derived",
10354
+ "clock",
10355
+ "constant"
10356
+ ]);
10357
+ var CompositionFieldPlanSchema = zod.z.object({
10358
+ cap: zod.z.string(),
10359
+ path: zod.z.string(),
10360
+ /** Index into `composition.features` this field's capability came from —
10361
+ * lets a source/cycle problem name its path as `features[N].fields.<path>`
10362
+ * (the same convention `planComposition`'s own problems already use). */
10363
+ featureIndex: zod.z.number().int().nonnegative(),
10364
+ kind: CompositionFieldKindSchema,
10365
+ nullable: zod.z.boolean(),
10366
+ required: zod.z.boolean(),
10367
+ unavailable: CompositionUnavailableModeSchema,
10368
+ role: CompositionFieldRoleSchema,
10369
+ source: CompositionFieldSourceSchema,
10370
+ reads: zod.z.array(CompositionFieldReadSchema)
10371
+ });
10372
+ var CompositionValidationSchema = zod.z.object({
10373
+ ok: zod.z.boolean(),
10374
+ problems: zod.z.array(CompositionProblemSchema),
10375
+ fields: zod.z.array(CompositionFieldPlanSchema)
10376
+ });
10377
+ /** Mirrors `CoreBlockStatus`, pinned by `core-blocks-source.spec.ts` (Task 11). */
10378
+ var CompositionRunStatusSchema = zod.z.enum([
10379
+ "stopped",
10380
+ "starting",
10381
+ "running",
10382
+ "failed"
10383
+ ]);
10384
+ var CompositionFieldStateKindSchema = zod.z.enum([
10385
+ "fresh",
10386
+ "stale",
10387
+ "unavailable"
10388
+ ]);
10389
+ var CompositionFieldStateSchema = zod.z.object({
10390
+ cap: zod.z.string(),
10391
+ path: zod.z.string(),
10392
+ state: CompositionFieldStateKindSchema,
10393
+ /** `null` whenever `state === 'unavailable'`; never a plausible stand-in (D393). */
10394
+ value: zod.z.union([
10395
+ zod.z.string(),
10396
+ zod.z.number(),
10397
+ zod.z.boolean(),
10398
+ zod.z.null()
10399
+ ]),
10400
+ reason: zod.z.string().optional()
10401
+ });
10402
+ var CompositionBlockStateSchema = zod.z.object({
10403
+ blockId: zod.z.string(),
10404
+ deviceId: zod.z.number().nullable(),
10405
+ status: CompositionRunStatusSchema,
10406
+ reason: zod.z.string().optional(),
10407
+ fields: zod.z.array(CompositionFieldStateSchema)
10408
+ });
10409
+ //#endregion
10101
10410
  //#region src/capabilities/core-blocks.cap.ts
10102
10411
  /**
10103
10412
  * core-blocks — user-authored TypeScript, stored in the kernel and executed in
@@ -10183,6 +10492,30 @@ var CoreBlockInputSchema = zod.z.object({
10183
10492
  */
10184
10493
  integrationId: zod.z.string().optional()
10185
10494
  });
10495
+ /**
10496
+ * What a block IS (D659). Absent means `code` — every row stored before this
10497
+ * union keeps working unchanged.
10498
+ * - `code`: the TypeScript in `code`, compiled and run in its own runner;
10499
+ * - `recipe`: RESERVED (automations), stored shape only; refused at run by name;
10500
+ * - `composition`: declarative, run by the shared `composer`; `code` is `''`.
10501
+ */
10502
+ var CoreBlockSourceSchema = zod.z.discriminatedUnion("kind", [
10503
+ zod.z.object({ kind: zod.z.literal("code") }),
10504
+ zod.z.object({
10505
+ kind: zod.z.literal("recipe"),
10506
+ recipe: AutomationRecipeSchema
10507
+ }),
10508
+ zod.z.object({
10509
+ kind: zod.z.literal("composition"),
10510
+ composition: CompositionSchema
10511
+ })
10512
+ ]);
10513
+ /** The client-authored half of a composition block. No `code`, no placement (hub-only; Phase 7). */
10514
+ var CompositionBlockInputSchema = zod.z.object({
10515
+ name: zod.z.string().min(1).max(120),
10516
+ enabled: zod.z.boolean(),
10517
+ composition: CompositionSchema
10518
+ });
10186
10519
  /** A stored block. */
10187
10520
  var CoreBlockSchema = CoreBlockInputSchema.extend({
10188
10521
  id: zod.z.string(),
@@ -10198,8 +10531,46 @@ var CoreBlockSchema = CoreBlockInputSchema.extend({
10198
10531
  */
10199
10532
  lastError: zod.z.string().optional(),
10200
10533
  /** Ms epoch of the last state change. */
10201
- lastChangedAt: zod.z.number()
10534
+ lastChangedAt: zod.z.number(),
10535
+ /** What the block is. Absent means `code` (D659). */
10536
+ source: CoreBlockSourceSchema.optional()
10537
+ });
10538
+ function coreBlockSourceKind(block) {
10539
+ return block.source?.kind ?? "code";
10540
+ }
10541
+ function coreBlockComposition(block) {
10542
+ return block.source?.kind === "composition" ? block.source.composition : null;
10543
+ }
10544
+ /** What the composer said, or that it could not be asked. Unknown never looks like absent (D315). */
10545
+ var CompositionStateReadSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
10546
+ kind: zod.z.literal("answered"),
10547
+ state: CompositionBlockStateSchema.nullable()
10548
+ }), zod.z.object({
10549
+ kind: zod.z.literal("unreachable"),
10550
+ error: zod.z.string()
10551
+ })]);
10552
+ /** A stored composition block and what the composer says about it. */
10553
+ var CompositionViewSchema = zod.z.object({
10554
+ block: CoreBlockSchema,
10555
+ state: CompositionStateReadSchema
10556
+ });
10557
+ /**
10558
+ * A read of the stored compositions. `not-loaded` while the `core_blocks`
10559
+ * table has not been READ: an empty answer then would make "not yet known"
10560
+ * look like "none", and invite the operator to recreate what exists (D315).
10561
+ */
10562
+ var CompositionsNotLoadedSchema = zod.z.object({
10563
+ state: zod.z.literal("not-loaded"),
10564
+ reason: zod.z.string()
10202
10565
  });
10566
+ var CompositionListReadSchema = zod.z.discriminatedUnion("state", [zod.z.object({
10567
+ state: zod.z.literal("loaded"),
10568
+ views: zod.z.array(CompositionViewSchema)
10569
+ }), CompositionsNotLoadedSchema]);
10570
+ var CompositionGetReadSchema = zod.z.discriminatedUnion("state", [zod.z.object({
10571
+ state: zod.z.literal("loaded"),
10572
+ view: CompositionViewSchema.nullable()
10573
+ }), CompositionsNotLoadedSchema]);
10203
10574
  /** What a compile attempt produced. */
10204
10575
  var CoreBlockCompileResultSchema = zod.z.object({
10205
10576
  ok: zod.z.boolean(),
@@ -10281,7 +10652,119 @@ var coreBlocksCapability = {
10281
10652
  getTypeDefs: require_sleep.method(zod.z.object({}), zod.z.object({ libs: zod.z.array(zod.z.object({
10282
10653
  filePath: zod.z.string(),
10283
10654
  content: zod.z.string()
10284
- })) }), { auth: "admin" })
10655
+ })) }), { auth: "admin" }),
10656
+ /**
10657
+ * Create a composition block (D659). VALIDATED first against the live
10658
+ * sources and every stored composition; a refusal names every problem by
10659
+ * path and reason (D391) and stores nothing.
10660
+ */
10661
+ createComposition: require_sleep.method(zod.z.object({ block: CompositionBlockInputSchema }), zod.z.object({ block: CoreBlockSchema }), {
10662
+ kind: "mutation",
10663
+ auth: "admin",
10664
+ caller: "required"
10665
+ }),
10666
+ /**
10667
+ * Patch a composition block. A changed `composition` is validated exactly
10668
+ * as on create. `expectedUpdatedAt` is the `updatedAt` the caller READ: a
10669
+ * block changed since is refused by name, never silently overwritten.
10670
+ */
10671
+ updateComposition: require_sleep.method(zod.z.object({
10672
+ blockId: zod.z.string(),
10673
+ expectedUpdatedAt: zod.z.number(),
10674
+ block: CompositionBlockInputSchema.partial()
10675
+ }), zod.z.object({ block: CoreBlockSchema }), {
10676
+ kind: "mutation",
10677
+ auth: "admin",
10678
+ caller: "required"
10679
+ }),
10680
+ /** One composition and the composer's state for it, or `not-loaded` (D315). */
10681
+ getComposition: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), CompositionGetReadSchema, { auth: "admin" }),
10682
+ listCompositions: require_sleep.method(zod.z.object({}), CompositionListReadSchema, { auth: "admin" }),
10683
+ /**
10684
+ * Validate without saving — the same verdict the save reaches. A mutation
10685
+ * for its body size; it writes nothing, so its access is `view`.
10686
+ */
10687
+ validateComposition: require_sleep.method(zod.z.object({
10688
+ composition: CompositionSchema,
10689
+ blockId: zod.z.string().optional()
10690
+ }), CompositionValidationSchema, {
10691
+ kind: "mutation",
10692
+ auth: "admin",
10693
+ access: "view"
10694
+ })
10695
+ }
10696
+ };
10697
+ //#endregion
10698
+ //#region src/capabilities/composer.cap.ts
10699
+ /**
10700
+ * composer — runs every DECLARATIVE composition (D659).
10701
+ *
10702
+ * Internal and admin-only: its one caller is `core-blocks`' supervisor, which
10703
+ * sends the WHOLE set of composition blocks on every converge (idempotent),
10704
+ * and reads their state for the API. Nothing else talks to it.
10705
+ */
10706
+ /** The composer's runner id, `resolveRunnerId(decl, 'composer')`; pinned by `composer-manifest.spec.ts`. */
10707
+ var COMPOSER_RUNNER_ID = COMPOSER_ADDON_ID;
10708
+ var ComposerBlockSchema = zod.z.object({
10709
+ id: zod.z.string().min(1),
10710
+ name: zod.z.string().min(1).max(120),
10711
+ enabled: zod.z.boolean(),
10712
+ updatedAt: zod.z.number(),
10713
+ composition: CompositionSchema
10714
+ });
10715
+ /**
10716
+ * A stored composition row that no longer PARSES — a schema a release
10717
+ * tightened, a row written by a newer build. Sent by id so the composer keeps
10718
+ * its device as a placeholder and reports the block `failed` naming the parse
10719
+ * error. Leaving it out would read, twice, exactly like a delete (D49).
10720
+ */
10721
+ var ComposerUnreadableBlockSchema = zod.z.object({
10722
+ id: zod.z.string().min(1),
10723
+ /** The row's stored name column; null when even that is unreadable (the device keeps the name it has). */
10724
+ name: zod.z.string().nullable(),
10725
+ /** The parse error, in the operator's words. */
10726
+ error: zod.z.string()
10727
+ });
10728
+ var ComposerApplyInputSchema = zod.z.object({
10729
+ /** The shared Blocks integration; null until `core-blocks` has reconciled it. */
10730
+ integrationId: zod.z.string().nullable(),
10731
+ /** False when the `core_blocks` read failed: then nothing may be withdrawn (D49). */
10732
+ storeLoaded: zod.z.boolean(),
10733
+ blocks: zod.z.array(ComposerBlockSchema),
10734
+ /** Operator restarts: rebuild these runtimes and re-resolve their sources. Stores nothing (D62). */
10735
+ restartBlockIds: zod.z.array(zod.z.string()),
10736
+ /** Composition rows that exist but do not parse: held, never withdrawn, reported failed. */
10737
+ unreadableBlocks: zod.z.array(ComposerUnreadableBlockSchema)
10738
+ });
10739
+ var ComposerBlockVerdictSchema = zod.z.object({
10740
+ blockId: zod.z.string(),
10741
+ status: CompositionRunStatusSchema,
10742
+ reason: zod.z.string().optional()
10743
+ });
10744
+ var ComposerApplyResultSchema = zod.z.object({
10745
+ blocks: zod.z.array(ComposerBlockVerdictSchema),
10746
+ /**
10747
+ * False when the composer declared and withdrew NOTHING (store not loaded,
10748
+ * integration unknown, its own rows unread or read empty once). An empty
10749
+ * apply the caller owes is paid off only by one the composer acted on (D49).
10750
+ */
10751
+ acted: zod.z.boolean()
10752
+ });
10753
+ var composerCapability = {
10754
+ name: "composer",
10755
+ scope: "system",
10756
+ mode: "singleton",
10757
+ internal: true,
10758
+ methods: {
10759
+ /**
10760
+ * Not `apply`: tRPC reserves `apply`, `call` and `then` as router keys, and
10761
+ * `router({})` throws on one — every cap router is built at boot.
10762
+ */
10763
+ applyBlocks: require_sleep.method(ComposerApplyInputSchema, ComposerApplyResultSchema, {
10764
+ kind: "mutation",
10765
+ auth: "admin"
10766
+ }),
10767
+ getStates: require_sleep.method(zod.z.object({ blockIds: zod.z.array(zod.z.string()) }), zod.z.object({ states: zod.z.array(CompositionBlockStateSchema) }), { auth: "admin" })
10285
10768
  }
10286
10769
  };
10287
10770
  //#endregion
@@ -18851,8 +19334,8 @@ var NcTestResultSchema = zod.z.object({
18851
19334
  className: zod.z.string().optional(),
18852
19335
  label: zod.z.string().optional()
18853
19336
  });
18854
- /** The rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts` is the
18855
- * same union; the template-vars spec pins the two together. `mixed` is DERIVED
19337
+ /** The rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts` is
19338
+ * `z.infer` of this schema — one shape, no twin. `mixed` is DERIVED
18856
19339
  * (a stored system rule whose kinds span families, D637) and never authored. */
18857
19340
  var NcRuleKindSchema = zod.z.enum([
18858
19341
  "detection",
@@ -23712,7 +24195,8 @@ var PipelineModelOptionSchema = zod.z.object({
23712
24195
  })),
23713
24196
  group: ModelVariantGroupSchema.optional(),
23714
24197
  legacy: zod.z.boolean().optional(),
23715
- provider: ModelProviderIdSchema.optional()
24198
+ provider: ModelProviderIdSchema.optional(),
24199
+ defaultConfidence: zod.z.number().optional()
23716
24200
  });
23717
24201
  var ConfigFieldBridge = zod.z.custom();
23718
24202
  var PipelineAddonSchemaSchema = zod.z.object({
@@ -44703,6 +45187,720 @@ function createDeviceExtensionProvider(options) {
44703
45187
  };
44704
45188
  }
44705
45189
  //#endregion
45190
+ //#region src/composition/composition-graph.ts
45191
+ /**
45192
+ * The field graph across every composition, and the cycles in it (D659).
45193
+ *
45194
+ * A node is ONE field of ONE device (`<addon>/<stableId>#<cap>.<field>`); an
45195
+ * edge points from a composed field to each field it reads. A cycle, direct or
45196
+ * through another composition, is refused at save by name (spec §2).
45197
+ *
45198
+ * The graph is built for ONE save: the CANDIDATE composition (the one being
45199
+ * validated) plus every STORED composition. The candidate ALWAYS replaces any
45200
+ * stored peer that targets the same device, regardless of where that stored
45201
+ * peer sits in `stored` — whether a save introduces or removes a cycle must
45202
+ * never depend on array order. Two DIFFERENT stored peers that (wrongly)
45203
+ * target the same device are corrupted state, not a cycle to resolve by
45204
+ * picking one silently; that is refused by name (D391).
45205
+ *
45206
+ * `findCompositionCycles` reports at least one cycle per cycle-SET (the DFS
45207
+ * marks a node `done` after its first expansion) — enough to refuse a save,
45208
+ * not to enumerate every elementary cycle through a shared node. A dotted
45209
+ * `fieldPath` is matched only as an exact string: a write to `state.value` and
45210
+ * a read of `state` would not connect (no nested field writes exist yet).
45211
+ */
45212
+ /** Two stored compositions both target the same device — refused by name, never silently resolved. */
45213
+ var CompositionGraphError = class extends Error {};
45214
+ function peerEdges(peer) {
45215
+ return peer.composition.features.flatMap((feature) => feature.kind !== "fields" ? [] : Object.entries(feature.fields).map(([path, source]) => [compositionReadKey({
45216
+ source: peer.target,
45217
+ cap: feature.cap,
45218
+ fieldPath: path
45219
+ }), fieldReads(source).map(compositionReadKey)]));
45220
+ }
45221
+ /**
45222
+ * Every peer that actually takes part in the graph: each stored peer minus
45223
+ * whichever one targets the same device as the candidate (the candidate
45224
+ * replaces it — this drop happens before any edge is built, so the
45225
+ * candidate's old edges never enter the map to begin with), plus the
45226
+ * candidate itself, exactly once. Throws {@link CompositionGraphError} if two
45227
+ * DIFFERENT stored peers (neither of them the candidate's own old version)
45228
+ * target the same device.
45229
+ */
45230
+ function resolvedPeers(candidate, stored) {
45231
+ const candidateKey = compositionSourceKey(candidate.target);
45232
+ const survivors = /* @__PURE__ */ new Map();
45233
+ for (const peer of stored) {
45234
+ const key = compositionSourceKey(peer.target);
45235
+ if (key === candidateKey) continue;
45236
+ if (survivors.has(key)) throw new CompositionGraphError(`two stored compositions both target device \`${key}\` — refusing to build the field graph`);
45237
+ survivors.set(key, peer);
45238
+ }
45239
+ return [...survivors.values(), candidate];
45240
+ }
45241
+ function compositionEdges(candidate, stored) {
45242
+ const edges = resolvedPeers(candidate, stored).flatMap(peerEdges);
45243
+ return new Map(edges);
45244
+ }
45245
+ function findCompositionCycles(candidate, stored) {
45246
+ const edges = compositionEdges(candidate, stored);
45247
+ const marks = /* @__PURE__ */ new Map();
45248
+ const found = /* @__PURE__ */ new Map();
45249
+ const visit = (node, stack) => {
45250
+ const mark = marks.get(node);
45251
+ if (mark === "done") return;
45252
+ if (mark === "visiting") {
45253
+ const path = [...stack.slice(stack.indexOf(node)), node];
45254
+ const id = [...new Set(path)].toSorted().join("|");
45255
+ if (!found.has(id)) found.set(id, { path });
45256
+ return;
45257
+ }
45258
+ marks.set(node, "visiting");
45259
+ for (const next of edges.get(node) ?? []) visit(next, [...stack, node]);
45260
+ marks.set(node, "done");
45261
+ };
45262
+ for (const node of edges.keys()) visit(node, []);
45263
+ return [...found.values()];
45264
+ }
45265
+ //#endregion
45266
+ //#region src/composition/evaluate-field.ts
45267
+ function describeRead(read) {
45268
+ return `${compositionSourceKey(read.source)} ${read.cap}.${read.fieldPath}`;
45269
+ }
45270
+ function fromReading(reading, read) {
45271
+ if (reading.state === "unavailable") return {
45272
+ state: "unavailable",
45273
+ value: null,
45274
+ reason: `${describeRead(read)}: ${reading.reason}`,
45275
+ fatal: reading.fatal
45276
+ };
45277
+ return {
45278
+ state: reading.state,
45279
+ value: reading.value,
45280
+ reason: reading.state === "stale" ? `${describeRead(read)}: ${reading.reason}` : null,
45281
+ deadline: null
45282
+ };
45283
+ }
45284
+ function expressionOutcome(source, read, now) {
45285
+ const values = {};
45286
+ const staleReasons = [];
45287
+ let fatalUnavailable = null;
45288
+ let nonFatalUnavailable = null;
45289
+ for (const [name, binding] of Object.entries(source.bindings)) {
45290
+ if (binding.kind === "literal") {
45291
+ values[name] = binding.value;
45292
+ continue;
45293
+ }
45294
+ const reading = read(binding);
45295
+ if (reading.state === "unavailable") {
45296
+ const outcome = {
45297
+ state: "unavailable",
45298
+ value: null,
45299
+ reason: `binding \`${name}\` (${describeRead(binding)}): ${reading.reason}`,
45300
+ fatal: reading.fatal
45301
+ };
45302
+ if (reading.fatal) {
45303
+ fatalUnavailable = outcome;
45304
+ break;
45305
+ }
45306
+ if (nonFatalUnavailable === null) nonFatalUnavailable = outcome;
45307
+ continue;
45308
+ }
45309
+ values[name] = reading.value;
45310
+ if (reading.state === "stale") staleReasons.push(`\`${name}\`: ${reading.reason}`);
45311
+ }
45312
+ if (fatalUnavailable !== null) return fatalUnavailable;
45313
+ if (nonFatalUnavailable !== null) return nonFatalUnavailable;
45314
+ const deadlines = [];
45315
+ const result = evaluateExpressionSource(source.expr, values, now, { hooks: { noteDeadline: (at) => deadlines.push(at) } });
45316
+ if (!result.ok) return {
45317
+ state: "unavailable",
45318
+ value: null,
45319
+ reason: `the expression failed: ${result.error}`,
45320
+ fatal: false
45321
+ };
45322
+ return {
45323
+ state: staleReasons.length > 0 ? "stale" : "fresh",
45324
+ value: result.value,
45325
+ reason: staleReasons.length > 0 ? staleReasons.join("; ") : null,
45326
+ deadline: deadlines.length > 0 ? Math.min(...deadlines) : null
45327
+ };
45328
+ }
45329
+ function evaluateCompositionField(input) {
45330
+ const { source } = input;
45331
+ switch (source.kind) {
45332
+ case "from": return fromReading(input.read(source), source);
45333
+ case "expression": return expressionOutcome(source, input.read, input.now);
45334
+ case "code": return {
45335
+ state: "unavailable",
45336
+ value: null,
45337
+ reason: "code fields are not runnable in the composer — they arrive in slice 4",
45338
+ fatal: true
45339
+ };
45340
+ }
45341
+ }
45342
+ //#endregion
45343
+ //#region src/composition/examples.ts
45344
+ /**
45345
+ * The worked composed-device example: a ROOM. Temperature is the average of N
45346
+ * sensors, and occupancy is any motion within the last five minutes. It is the
45347
+ * slice-1 acceptance case and the template an author starts from (Phase 4
45348
+ * §4 item 4 asked for one).
45349
+ */
45350
+ var ROOM_OCCUPANCY_WINDOW_MS = 3e5;
45351
+ var ROOM_EXAMPLE_TEMPERATURE_SOURCES = [
45352
+ {
45353
+ addonId: "provider-homeassistant",
45354
+ stableId: "sensor.living_room_temperature"
45355
+ },
45356
+ {
45357
+ addonId: "provider-homeassistant",
45358
+ stableId: "sensor.sofa_temperature"
45359
+ },
45360
+ {
45361
+ addonId: "provider-homeassistant",
45362
+ stableId: "sensor.window_temperature"
45363
+ }
45364
+ ];
45365
+ var ROOM_EXAMPLE_MOTION_SOURCES = [{
45366
+ addonId: "provider-homeassistant",
45367
+ stableId: "binary_sensor.living_room_motion"
45368
+ }, {
45369
+ addonId: "provider-homeassistant",
45370
+ stableId: "binary_sensor.living_room_presence"
45371
+ }];
45372
+ function from(source, cap, fieldPath) {
45373
+ return {
45374
+ kind: "from",
45375
+ source,
45376
+ cap,
45377
+ fieldPath
45378
+ };
45379
+ }
45380
+ function named(prefix, sources, cap, fieldPath) {
45381
+ return Object.fromEntries(sources.map((s, i) => [`${prefix}${i + 1}`, from(s, cap, fieldPath)]));
45382
+ }
45383
+ function roomCompositionExample(input) {
45384
+ const t = named("t", input.temperatureSources, "temperature-sensor", "celsius");
45385
+ const f = named("f", input.temperatureSources, "temperature-sensor", "lastFetchedAt");
45386
+ const d = named("d", input.motionSources, "motion", "detected");
45387
+ const m = named("m", input.motionSources, "motion", "lastDetectedAt");
45388
+ const windows = Object.keys(m).map((name) => `within(${name}, ${input.occupancyWindowMs})`);
45389
+ return CompositionSchema.parse({
45390
+ target: {
45391
+ kind: "new",
45392
+ type: require_sleep.DeviceType.Sensor
45393
+ },
45394
+ features: [{
45395
+ kind: "fields",
45396
+ cap: "temperature-sensor",
45397
+ fields: {
45398
+ celsius: {
45399
+ kind: "expression",
45400
+ expr: `round(avg(${Object.keys(t).join(", ")}), 1)`,
45401
+ bindings: t
45402
+ },
45403
+ lastFetchedAt: {
45404
+ kind: "expression",
45405
+ expr: `max(${Object.keys(f).join(", ")})`,
45406
+ bindings: f
45407
+ }
45408
+ }
45409
+ }, {
45410
+ kind: "fields",
45411
+ cap: "binary",
45412
+ fields: {
45413
+ on: {
45414
+ kind: "expression",
45415
+ expr: `any(${[...Object.keys(d), ...windows].join(", ")})`,
45416
+ bindings: {
45417
+ ...d,
45418
+ ...m
45419
+ }
45420
+ },
45421
+ lastChangedAt: {
45422
+ kind: "expression",
45423
+ expr: "now",
45424
+ bindings: {}
45425
+ }
45426
+ }
45427
+ }]
45428
+ });
45429
+ }
45430
+ //#endregion
45431
+ //#region src/composition/expression-kinds.ts
45432
+ var NUMBER = {
45433
+ kind: "fixed",
45434
+ result: ["number"]
45435
+ };
45436
+ var BOOLEAN = {
45437
+ kind: "fixed",
45438
+ result: ["boolean"]
45439
+ };
45440
+ /** One rule per builtin. `expression-kinds.spec.ts` fails for a builtin missing here. */
45441
+ var BUILTIN_RESULT_KINDS = Object.freeze({
45442
+ min: NUMBER,
45443
+ max: NUMBER,
45444
+ abs: NUMBER,
45445
+ floor: NUMBER,
45446
+ ceil: NUMBER,
45447
+ sqrt: NUMBER,
45448
+ round: NUMBER,
45449
+ pow: NUMBER,
45450
+ clamp: NUMBER,
45451
+ avg: NUMBER,
45452
+ sum: NUMBER,
45453
+ age: NUMBER,
45454
+ convert: NUMBER,
45455
+ coalesce: { kind: "coalesce" },
45456
+ any: BOOLEAN,
45457
+ all: BOOLEAN,
45458
+ within: BOOLEAN,
45459
+ latest: {
45460
+ kind: "fixed",
45461
+ result: ["number", "null"]
45462
+ }
45463
+ });
45464
+ var KindInferenceError = class extends Error {};
45465
+ function kindOfValue(value) {
45466
+ if (value === null) return "null";
45467
+ if (typeof value === "number") return "number";
45468
+ if (typeof value === "string") return "string";
45469
+ return "boolean";
45470
+ }
45471
+ function only(kind) {
45472
+ return new Set([kind]);
45473
+ }
45474
+ function union(...sets) {
45475
+ return new Set(sets.flatMap((s) => [...s]));
45476
+ }
45477
+ function withoutNull(set) {
45478
+ return new Set([...set].filter((k) => k !== "null"));
45479
+ }
45480
+ function binaryKinds(op, left, right) {
45481
+ if (op === "+") {
45482
+ const all = [...left, ...right];
45483
+ if (all.every((k) => k === "string")) return only("string");
45484
+ if (all.every((k) => k === "number")) return only("number");
45485
+ return new Set(["number", "string"]);
45486
+ }
45487
+ if (op === "-" || op === "*" || op === "/" || op === "%") return only("number");
45488
+ return only("boolean");
45489
+ }
45490
+ /** "exactly N" / "at least N" / "N–M", for an arity refusal that says what IS allowed. */
45491
+ function arityRange(builtin) {
45492
+ if (builtin.minArgs === builtin.maxArgs) return `exactly ${builtin.minArgs}`;
45493
+ if (builtin.maxArgs === Number.POSITIVE_INFINITY) return `at least ${builtin.minArgs}`;
45494
+ return `${builtin.minArgs}–${builtin.maxArgs}`;
45495
+ }
45496
+ function callKinds(callee, args) {
45497
+ const builtin = EXPRESSION_BUILTINS[callee];
45498
+ 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)}`);
45499
+ const rule = BUILTIN_RESULT_KINDS[callee];
45500
+ if (rule === void 0) throw new KindInferenceError(`no result-kind rule for builtin '${callee}'`);
45501
+ if (rule.kind === "fixed") return new Set(rule.result);
45502
+ const nonNull = union(...args.map(withoutNull));
45503
+ return args.every((a) => a.has("null")) ? union(nonNull, only("null")) : nonNull;
45504
+ }
45505
+ function kindsOf(node, bindings) {
45506
+ switch (node.kind) {
45507
+ case "literal": return only(kindOfValue(node.value));
45508
+ case "identifier": {
45509
+ if (node.name === "now") return only("number");
45510
+ const bound = bindings.get(node.name);
45511
+ if (bound === void 0) throw new KindInferenceError(`unbound identifier '${node.name}'`);
45512
+ return bound;
45513
+ }
45514
+ case "unary": return only(node.op === "!" ? "boolean" : "number");
45515
+ case "binary": return binaryKinds(node.op, kindsOf(node.left, bindings), kindsOf(node.right, bindings));
45516
+ case "logical": return union(kindsOf(node.left, bindings), kindsOf(node.right, bindings));
45517
+ case "conditional": return union(kindsOf(node.consequent, bindings), kindsOf(node.alternate, bindings));
45518
+ case "call": return callKinds(node.callee, node.args.map((a) => kindsOf(a, bindings)));
45519
+ }
45520
+ }
45521
+ function inferExpressionKinds(input) {
45522
+ const compiled = compileExpressionSafe(input.expr);
45523
+ if (!compiled.ok) return {
45524
+ ok: false,
45525
+ error: compiled.error
45526
+ };
45527
+ try {
45528
+ return {
45529
+ ok: true,
45530
+ kinds: kindsOf(compiled.parsed.ast, input.bindingKinds)
45531
+ };
45532
+ } catch (err) {
45533
+ if (err instanceof KindInferenceError) return {
45534
+ ok: false,
45535
+ error: err.message
45536
+ };
45537
+ throw err;
45538
+ }
45539
+ }
45540
+ //#endregion
45541
+ //#region src/device/schema-fields.ts
45542
+ /** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
45543
+ * Zod v4 exposes `.unwrap()` on all three wrapper classes. */
45544
+ function unwrapSchema(schema) {
45545
+ let s = schema;
45546
+ while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
45547
+ return s;
45548
+ }
45549
+ function leafKind(schema) {
45550
+ const s = unwrapSchema(schema);
45551
+ if (s instanceof zod.z.ZodEnum) return {
45552
+ kind: "enum",
45553
+ enumValues: s.options
45554
+ };
45555
+ if (s instanceof zod.z.ZodNumber) return { kind: "number" };
45556
+ if (s instanceof zod.z.ZodString) return { kind: "string" };
45557
+ if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
45558
+ return null;
45559
+ }
45560
+ /** Walk a cap status schema into flat wireable leaf fields (dotted paths).
45561
+ * Recurses into nested ZodObject (unwrapping nullable/optional/default first),
45562
+ * so fields with null live values are still offered. Arrays / unknown shapes
45563
+ * are skipped — never throws. */
45564
+ function enumerateSchemaFields(schema, prefix = "") {
45565
+ const root = unwrapSchema(schema);
45566
+ if (!(root instanceof zod.z.ZodObject)) return [];
45567
+ const out = [];
45568
+ const shape = root.shape;
45569
+ for (const [key, field] of Object.entries(shape)) {
45570
+ const path = prefix ? `${prefix}.${key}` : key;
45571
+ const inner = unwrapSchema(field);
45572
+ if (inner instanceof zod.z.ZodObject) {
45573
+ out.push(...enumerateSchemaFields(inner, path));
45574
+ continue;
45575
+ }
45576
+ const leaf = leafKind(field);
45577
+ if (leaf) out.push({
45578
+ path,
45579
+ kind: leaf.kind,
45580
+ ...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
45581
+ });
45582
+ }
45583
+ return out;
45584
+ }
45585
+ /** Enumerate the per-item wireable fields of an item-array cap (see
45586
+ * `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
45587
+ * `keyField` (the key comes from the link's `itemKey`, never from a wired
45588
+ * source), each tagged `item: true` so the authoring UI collects an
45589
+ * `itemKey` alongside the field. Never throws. */
45590
+ function enumerateItemArrayFields(itemArray) {
45591
+ return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
45592
+ ...f,
45593
+ item: true
45594
+ }));
45595
+ }
45596
+ //#endregion
45597
+ //#region src/composition/state-fields.ts
45598
+ /**
45599
+ * What a capability's state field IS: kind, nullability, whether required. The
45600
+ * validator needs nullability, which `enumerateSchemaFields` does not report,
45601
+ * and that decides a field's unavailable mode (D393).
45602
+ */
45603
+ function fieldAt(schema, path) {
45604
+ let current = schema;
45605
+ for (const segment of path.split(".")) {
45606
+ const container = unwrapSchema(current);
45607
+ if (!(container instanceof zod.z.ZodObject)) return null;
45608
+ const next = container.shape[segment];
45609
+ if (!(next instanceof zod.z.ZodType)) return null;
45610
+ current = next;
45611
+ }
45612
+ return current;
45613
+ }
45614
+ function describeStateField(schema, path) {
45615
+ const leaf = enumerateSchemaFields(schema).find((f) => f.path === path);
45616
+ if (leaf === void 0) return null;
45617
+ const field = fieldAt(schema, path);
45618
+ if (field === null) return null;
45619
+ return {
45620
+ path,
45621
+ kind: leaf.kind,
45622
+ nullable: field.safeParse(null).success,
45623
+ required: !field.safeParse(void 0).success,
45624
+ ...leaf.enumValues === void 0 ? {} : { enumValues: leaf.enumValues }
45625
+ };
45626
+ }
45627
+ function describeTopLevelStateFields(schema) {
45628
+ return new Map(enumerateSchemaFields(schema).filter((f) => !f.path.includes(".")).flatMap((f) => {
45629
+ const described = describeStateField(schema, f.path);
45630
+ return described === null ? [] : [[f.path, described]];
45631
+ }));
45632
+ }
45633
+ /** Top-level keys a value MUST carry: those whose schema refuses `undefined`. */
45634
+ function requiredTopLevelKeys(schema) {
45635
+ const root = unwrapSchema(schema);
45636
+ if (!(root instanceof zod.z.ZodObject)) return [];
45637
+ return Object.entries(root.shape).flatMap(([key, field]) => field instanceof zod.z.ZodType && !field.safeParse(void 0).success ? [key] : []);
45638
+ }
45639
+ //#endregion
45640
+ //#region src/composition/validate-composition.ts
45641
+ function buildCapabilityLookup(defs) {
45642
+ const byName = new Map(defs.map((d) => [d.name, d]));
45643
+ return (capName) => byName.get(capName) ?? null;
45644
+ }
45645
+ /** Caps every `BaseDevice` registers on itself; a second registration throws. */
45646
+ var BASE_DEVICE_CAPS = new Set(["device-status", "feature-probe"]);
45647
+ var PENDING_SELF = {
45648
+ addonId: COMPOSER_ADDON_ID,
45649
+ stableId: `${COMPOSED_DEVICE_STABLE_ID_PREFIX}(unsaved)`
45650
+ };
45651
+ function problem(code, path, message) {
45652
+ return {
45653
+ code,
45654
+ path,
45655
+ message
45656
+ };
45657
+ }
45658
+ function refused(...problems) {
45659
+ return {
45660
+ problems,
45661
+ fields: []
45662
+ };
45663
+ }
45664
+ function readKinds(read, lookupCap) {
45665
+ const def = lookupCap(read.cap);
45666
+ const schema = def?.runtimeState ?? def?.status?.schema;
45667
+ if (def === null || schema === void 0) return {
45668
+ ok: false,
45669
+ code: "source-field-unknown",
45670
+ message: `no capability \`${read.cap}\` with state to read`
45671
+ };
45672
+ const field = describeStateField(schema, read.fieldPath);
45673
+ if (field === null) return {
45674
+ ok: false,
45675
+ code: "source-field-unknown",
45676
+ message: `\`${read.cap}\` has no field \`${read.fieldPath}\``
45677
+ };
45678
+ const base = field.kind === "enum" ? "string" : field.kind;
45679
+ return {
45680
+ ok: true,
45681
+ kinds: new Set(field.nullable ? [base, "null"] : [base])
45682
+ };
45683
+ }
45684
+ function sourceKinds(source, lookupCap) {
45685
+ if (source.kind === "from") return readKinds(source, lookupCap);
45686
+ if (source.kind === "code") return {
45687
+ ok: false,
45688
+ code: "reserved-variant",
45689
+ message: "code fields arrive in slice 4"
45690
+ };
45691
+ const bindingKinds = /* @__PURE__ */ new Map();
45692
+ for (const [name, binding] of Object.entries(source.bindings)) {
45693
+ if (binding.kind === "literal") {
45694
+ bindingKinds.set(name, new Set([kindOfValue(binding.value)]));
45695
+ continue;
45696
+ }
45697
+ const kinds = readKinds(binding, lookupCap);
45698
+ if (!kinds.ok) return {
45699
+ ...kinds,
45700
+ message: `binding \`${name}\`: ${kinds.message}`
45701
+ };
45702
+ bindingKinds.set(name, kinds.kinds);
45703
+ }
45704
+ const inferred = inferExpressionKinds({
45705
+ expr: source.expr,
45706
+ bindingKinds
45707
+ });
45708
+ return inferred.ok ? {
45709
+ ok: true,
45710
+ kinds: inferred.kinds
45711
+ } : {
45712
+ ok: false,
45713
+ code: "expression-invalid",
45714
+ message: inferred.error
45715
+ };
45716
+ }
45717
+ function compatibility(cap, target, kinds, at) {
45718
+ const allowed = target.kind === "enum" ? "string" : target.kind;
45719
+ const offending = [...kinds].filter((k) => k !== "null" && k !== allowed);
45720
+ const problems = [];
45721
+ if (offending.length > 0) problems.push(problem("kind-mismatch", at, `\`${cap}.${target.path}\` is a ${target.kind}; this source can produce ${offending.join(" or ")}`));
45722
+ 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`));
45723
+ return problems;
45724
+ }
45725
+ /**
45726
+ * A string a source produces WITHOUT reading any device — a field-level
45727
+ * `expression` source whose compiled AST is a bare string literal (`"red"`,
45728
+ * never `a` or `a + "x"`). Anything else that can only produce `'string'` at
45729
+ * the kind level (a `from` read of an enum-typed source field, a computed
45730
+ * expression) is NOT checked against the target's `enumValues` here: the
45731
+ * kind inference already collapsed it to the coarse `'string'` kind, losing
45732
+ * whichever enum it actually came from, so membership can only be judged at
45733
+ * write time against the runtime-state schema. Known limitation — see the
45734
+ * fix report for Task 6 round 1.
45735
+ */
45736
+ function literalStringValue(source) {
45737
+ if (source.kind !== "expression") return null;
45738
+ const compiled = compileExpressionSafe(source.expr);
45739
+ if (!compiled.ok) return null;
45740
+ const ast = compiled.parsed.ast;
45741
+ return ast.kind === "literal" && typeof ast.value === "string" ? ast.value : null;
45742
+ }
45743
+ /**
45744
+ * An enum target whose source is a save-time-KNOWN string literal outside its
45745
+ * `enumValues` is refused now rather than left to fail the write later. A
45746
+ * dynamic source (a `from` read, a non-literal expression) is left to
45747
+ * runtime — see {@link literalStringValue}.
45748
+ */
45749
+ function enumLiteralProblem(cap, target, source, at) {
45750
+ if (target.kind !== "enum" || target.enumValues === void 0) return null;
45751
+ const value = literalStringValue(source);
45752
+ if (value === null || target.enumValues.includes(value)) return null;
45753
+ const allowed = target.enumValues.map((v) => `\`${v}\``).join(", ");
45754
+ return problem("kind-mismatch", at, `\`${cap}.${target.path}\` accepts only ${allowed}; this source is the literal \`${value}\``);
45755
+ }
45756
+ function roleOf(source) {
45757
+ if (source.kind !== "expression") return "derived";
45758
+ if (fieldReads(source).length > 0) return "derived";
45759
+ const compiled = compileExpressionSafe(source.expr);
45760
+ return compiled.ok && compiled.parsed.identifiers.has("now") ? "clock" : "constant";
45761
+ }
45762
+ function planFeature(feature, featureIndex, target, lookupCap) {
45763
+ const at = `features[${featureIndex}]`;
45764
+ const def = lookupCap(feature.cap);
45765
+ if (def === null) return refused(problem("unknown-capability", `${at}.cap`, `no capability is named \`${feature.cap}\``));
45766
+ 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`));
45767
+ const state = def.runtimeState;
45768
+ if (def.scope !== "device" || state === void 0) return refused(problem("not-composable", `${at}.cap`, `\`${def.name}\` has no per-device state to compose`));
45769
+ const capProblems = [];
45770
+ 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(", ")})`));
45771
+ const served = Object.entries(def.methods).filter(([, m]) => m.systemOnly !== true).map(([name]) => `\`${name}\``);
45772
+ 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)`));
45773
+ if (feature.commands !== void 0 && Object.keys(feature.commands).length > 0) capProblems.push(problem("reserved-variant", `${at}.commands`, "commands (`forward`) arrive in slice 3"));
45774
+ const targetFields = describeTopLevelStateFields(state);
45775
+ 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`));
45776
+ else if (!(key in feature.fields)) capProblems.push(problem("missing-field", `${at}.fields.${key}`, `\`${def.name}.${key}\` is required and has no source`));
45777
+ const perField = Object.entries(feature.fields).map(([path, source]) => {
45778
+ const fieldAt = `${at}.fields.${path}`;
45779
+ if (path.includes(".")) return refused(problem("nested-field", fieldAt, `\`${path}\` is nested — nested fields arrive in a later slice`));
45780
+ const tf = targetFields.get(path);
45781
+ if (tf === void 0) return refused(problem("unknown-field", fieldAt, `\`${def.name}\` has no field \`${path}\``));
45782
+ const kinds = sourceKinds(source, lookupCap);
45783
+ if (!kinds.ok) return refused(problem(kinds.code, fieldAt, kinds.message));
45784
+ const plan = {
45785
+ cap: def.name,
45786
+ path,
45787
+ featureIndex,
45788
+ kind: tf.kind,
45789
+ nullable: tf.nullable,
45790
+ required: tf.required,
45791
+ unavailable: tf.nullable ? "null" : "offline",
45792
+ role: roleOf(source),
45793
+ source,
45794
+ reads: [...fieldReads(source)]
45795
+ };
45796
+ const enumProblem = enumLiteralProblem(def.name, tf, source, fieldAt);
45797
+ return {
45798
+ problems: [...compatibility(def.name, tf, kinds.kinds, fieldAt), ...enumProblem === null ? [] : [enumProblem]],
45799
+ fields: [plan]
45800
+ };
45801
+ });
45802
+ return {
45803
+ problems: [...capProblems, ...perField.flatMap((r) => r.problems)],
45804
+ fields: perField.flatMap((r) => r.fields)
45805
+ };
45806
+ }
45807
+ function planComposition(composition, lookupCap) {
45808
+ const target = composition.target;
45809
+ 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"));
45810
+ const results = composition.features.map((feature, index) => {
45811
+ const at = `features[${index}]`;
45812
+ if (feature.kind === "passthrough") return refused(problem("reserved-variant", at, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`));
45813
+ return planFeature(feature, index, target, lookupCap);
45814
+ });
45815
+ return {
45816
+ problems: results.flatMap((r) => r.problems),
45817
+ fields: results.flatMap((r) => r.fields)
45818
+ };
45819
+ }
45820
+ function compositionSourceRefs(composition) {
45821
+ const refs = composition.features.flatMap((f) => f.kind === "fields" ? Object.values(f.fields).flatMap(fieldReads).map((r) => r.source) : []);
45822
+ return [...new Map(refs.map((r) => [compositionSourceKey(r), r])).values()];
45823
+ }
45824
+ function sourceProblems(fields, ctx) {
45825
+ const selfKey = ctx.self === null ? null : compositionSourceKey(ctx.self);
45826
+ return fields.flatMap((field) => field.reads.flatMap((read) => {
45827
+ const at = `features[${field.featureIndex}].fields.${field.path}`;
45828
+ const key = compositionSourceKey(read.source);
45829
+ 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}\``)];
45830
+ const desc = ctx.sources.get(key);
45831
+ 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`)];
45832
+ if (desc.kind === "absent") return [problem("source-missing", at, `source \`${key}\` does not exist`)];
45833
+ if (!desc.caps.has(read.cap)) return [problem("source-capability-missing", at, `source \`${key}\` (device ${desc.deviceId}) has no \`${read.cap}\``)];
45834
+ return [];
45835
+ }));
45836
+ }
45837
+ /**
45838
+ * A cycle's node keys are `compositionReadKey` strings:
45839
+ * `<addonId>/<stableId>#<cap>.<fieldPath>`. When a node belongs to the
45840
+ * CANDIDATE itself (this composition being saved), pull its `cap`/`fieldPath`
45841
+ * back out so the refusal can point at that exact field, matching the
45842
+ * documented `features[N].fields.<path>` convention — slice 1 has no nested
45843
+ * field writes, so splitting `rest` on the FIRST `.` is exact.
45844
+ */
45845
+ function candidateFieldOfNode(nodeKey, candidateKey) {
45846
+ const prefix = `${candidateKey}#`;
45847
+ if (!nodeKey.startsWith(prefix)) return null;
45848
+ const rest = nodeKey.slice(prefix.length);
45849
+ const dot = rest.indexOf(".");
45850
+ if (dot === -1) return null;
45851
+ return {
45852
+ cap: rest.slice(0, dot),
45853
+ fieldPath: rest.slice(dot + 1)
45854
+ };
45855
+ }
45856
+ /** The first node of a cycle's path that is one of THIS composition's own
45857
+ * planned fields, as a documented-convention path; `'features'` when the
45858
+ * cycle runs entirely through other stored compositions. */
45859
+ function cyclePath(nodePath, candidateKey, fields) {
45860
+ for (const nodeKey of nodePath) {
45861
+ const hit = candidateFieldOfNode(nodeKey, candidateKey);
45862
+ if (hit === null) continue;
45863
+ const field = fields.find((f) => f.cap === hit.cap && f.path === hit.fieldPath);
45864
+ if (field !== void 0) return `features[${field.featureIndex}].fields.${field.path}`;
45865
+ }
45866
+ return "features";
45867
+ }
45868
+ /**
45869
+ * Cycle check runs against the CANDIDATE (this composition, targeting `self`
45870
+ * when it already exists, else a placeholder that cannot collide with a
45871
+ * stored target) plus every OTHER stored composition. Two stored peers that
45872
+ * (wrongly) share a target are corrupted state — `findCompositionCycles`
45873
+ * throws `CompositionGraphError` rather than silently picking one, and this
45874
+ * is refused BY NAME (D391) instead of escaping as an uncaught throw.
45875
+ */
45876
+ function cycleProblems(composition, ctx, fields) {
45877
+ const candidate = {
45878
+ target: ctx.self ?? PENDING_SELF,
45879
+ composition
45880
+ };
45881
+ const candidateKey = compositionSourceKey(candidate.target);
45882
+ try {
45883
+ return findCompositionCycles(candidate, ctx.peers).map((c) => problem("cycle", cyclePath(c.path, candidateKey, fields), `a field reads itself: ${c.path.join(" → ")}`));
45884
+ } catch (err) {
45885
+ if (err instanceof CompositionGraphError) return [problem("duplicate-target", "features", err.message)];
45886
+ throw err;
45887
+ }
45888
+ }
45889
+ function validateComposition(composition, ctx) {
45890
+ const plan = planComposition(composition, ctx.lookupCap);
45891
+ const cycles = cycleProblems(composition, ctx, plan.fields);
45892
+ const problems = [
45893
+ ...plan.problems,
45894
+ ...sourceProblems(plan.fields, ctx),
45895
+ ...cycles
45896
+ ];
45897
+ return {
45898
+ ok: problems.length === 0,
45899
+ problems,
45900
+ fields: [...plan.fields]
45901
+ };
45902
+ }
45903
+ //#endregion
44706
45904
  //#region src/catalogs/device-state-vocabulary.ts
44707
45905
  /**
44708
45906
  * Most specific first. Extending this list is how a new device kind becomes
@@ -48855,62 +50053,6 @@ function createRuntimeStateBridge(params) {
48855
50053
  };
48856
50054
  }
48857
50055
  //#endregion
48858
- //#region src/device/schema-fields.ts
48859
- /** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
48860
- * Zod v4 exposes `.unwrap()` on all three wrapper classes. */
48861
- function unwrap(schema) {
48862
- let s = schema;
48863
- while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
48864
- return s;
48865
- }
48866
- function leafKind(schema) {
48867
- const s = unwrap(schema);
48868
- if (s instanceof zod.z.ZodEnum) return {
48869
- kind: "enum",
48870
- enumValues: s.options
48871
- };
48872
- if (s instanceof zod.z.ZodNumber) return { kind: "number" };
48873
- if (s instanceof zod.z.ZodString) return { kind: "string" };
48874
- if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
48875
- return null;
48876
- }
48877
- /** Walk a cap status schema into flat wireable leaf fields (dotted paths).
48878
- * Recurses into nested ZodObject (unwrapping nullable/optional/default first),
48879
- * so fields with null live values are still offered. Arrays / unknown shapes
48880
- * are skipped — never throws. */
48881
- function enumerateSchemaFields(schema, prefix = "") {
48882
- const root = unwrap(schema);
48883
- if (!(root instanceof zod.z.ZodObject)) return [];
48884
- const out = [];
48885
- const shape = root.shape;
48886
- for (const [key, field] of Object.entries(shape)) {
48887
- const path = prefix ? `${prefix}.${key}` : key;
48888
- const inner = unwrap(field);
48889
- if (inner instanceof zod.z.ZodObject) {
48890
- out.push(...enumerateSchemaFields(inner, path));
48891
- continue;
48892
- }
48893
- const leaf = leafKind(field);
48894
- if (leaf) out.push({
48895
- path,
48896
- kind: leaf.kind,
48897
- ...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
48898
- });
48899
- }
48900
- return out;
48901
- }
48902
- /** Enumerate the per-item wireable fields of an item-array cap (see
48903
- * `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
48904
- * `keyField` (the key comes from the link's `itemKey`, never from a wired
48905
- * source), each tagged `item: true` so the authoring UI collects an
48906
- * `itemKey` alongside the field. Never throws. */
48907
- function enumerateItemArrayFields(itemArray) {
48908
- return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
48909
- ...f,
48910
- item: true
48911
- }));
48912
- }
48913
- //#endregion
48914
50056
  //#region src/device/system-mirror.ts
48915
50057
  var STATE_CHANGED_CATEGORY = "device.state-changed";
48916
50058
  var BINDING_CHANGED_CATEGORY = "capability.binding-changed";
@@ -49803,6 +50945,7 @@ var CAPABILITY_NAMES = {
49803
50945
  carbonMonoxide: "carbon-monoxide",
49804
50946
  climateControl: "climate-control",
49805
50947
  color: "color",
50948
+ composer: "composer",
49806
50949
  connectionTest: "connection-test",
49807
50950
  connectivity: "connectivity",
49808
50951
  consumables: "consumables",
@@ -50063,6 +51206,10 @@ var CAPABILITY_ROUTER_KEYS = [
50063
51206
  key: "color",
50064
51207
  name: "color"
50065
51208
  },
51209
+ {
51210
+ key: "composer",
51211
+ name: "composer"
51212
+ },
50066
51213
  {
50067
51214
  key: "connectionTest",
50068
51215
  name: "connection-test"
@@ -50622,6 +51769,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
50622
51769
  carbonMonoxideCapability,
50623
51770
  climateControlCapability,
50624
51771
  colorCapability,
51772
+ composerCapability,
50625
51773
  connectionTestCapability,
50626
51774
  connectivityCapability,
50627
51775
  consumablesCapability,
@@ -51697,6 +52845,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
51697
52845
  addonId: null,
51698
52846
  access: "create"
51699
52847
  },
52848
+ "composer.applyBlocks": {
52849
+ capName: "composer",
52850
+ capScope: "system",
52851
+ addonId: null,
52852
+ access: "create"
52853
+ },
52854
+ "composer.getStates": {
52855
+ capName: "composer",
52856
+ capScope: "system",
52857
+ addonId: null,
52858
+ access: "view"
52859
+ },
51700
52860
  "connectionTest.describeTest": {
51701
52861
  capName: "connection-test",
51702
52862
  capScope: "system",
@@ -51757,6 +52917,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
51757
52917
  addonId: null,
51758
52918
  access: "create"
51759
52919
  },
52920
+ "coreBlocks.createComposition": {
52921
+ capName: "core-blocks",
52922
+ capScope: "system",
52923
+ addonId: null,
52924
+ access: "create"
52925
+ },
51760
52926
  "coreBlocks.delete": {
51761
52927
  capName: "core-blocks",
51762
52928
  capScope: "system",
@@ -51769,6 +52935,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
51769
52935
  addonId: null,
51770
52936
  access: "view"
51771
52937
  },
52938
+ "coreBlocks.getComposition": {
52939
+ capName: "core-blocks",
52940
+ capScope: "system",
52941
+ addonId: null,
52942
+ access: "view"
52943
+ },
51772
52944
  "coreBlocks.getTypeDefs": {
51773
52945
  capName: "core-blocks",
51774
52946
  capScope: "system",
@@ -51781,6 +52953,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
51781
52953
  addonId: null,
51782
52954
  access: "view"
51783
52955
  },
52956
+ "coreBlocks.listCompositions": {
52957
+ capName: "core-blocks",
52958
+ capScope: "system",
52959
+ addonId: null,
52960
+ access: "view"
52961
+ },
51784
52962
  "coreBlocks.restart": {
51785
52963
  capName: "core-blocks",
51786
52964
  capScope: "system",
@@ -51799,6 +52977,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
51799
52977
  addonId: null,
51800
52978
  access: "create"
51801
52979
  },
52980
+ "coreBlocks.updateComposition": {
52981
+ capName: "core-blocks",
52982
+ capScope: "system",
52983
+ addonId: null,
52984
+ access: "create"
52985
+ },
52986
+ "coreBlocks.validateComposition": {
52987
+ capName: "core-blocks",
52988
+ capScope: "system",
52989
+ addonId: null,
52990
+ access: "view"
52991
+ },
51802
52992
  "cover.close": {
51803
52993
  capName: "cover",
51804
52994
  capScope: "device",
@@ -57858,6 +59048,7 @@ var KNOWN_CAP_NAMES = [
57858
59048
  "carbon-monoxide",
57859
59049
  "climate-control",
57860
59050
  "color",
59051
+ "composer",
57861
59052
  "connection-test",
57862
59053
  "connectivity",
57863
59054
  "consumables",
@@ -58095,6 +59286,7 @@ var SYSTEM_CAP_NAMES = [
58095
59286
  "auth-provider",
58096
59287
  "backup",
58097
59288
  "broker",
59289
+ "composer",
58098
59290
  "connection-test",
58099
59291
  "core-blocks",
58100
59292
  "custom-model-registry",
@@ -60920,7 +62112,12 @@ function createSystemProxy(api) {
60920
62112
  setEnabled: (input) => dispatch("coreBlocks", "setEnabled", "mutation", input),
60921
62113
  restart: (input) => dispatch("coreBlocks", "restart", "mutation", input),
60922
62114
  compile: (input) => dispatch("coreBlocks", "compile", "mutation", input),
60923
- getTypeDefs: (input) => dispatch("coreBlocks", "getTypeDefs", "query", input)
62115
+ getTypeDefs: (input) => dispatch("coreBlocks", "getTypeDefs", "query", input),
62116
+ createComposition: (input) => dispatch("coreBlocks", "createComposition", "mutation", input),
62117
+ updateComposition: (input) => dispatch("coreBlocks", "updateComposition", "mutation", input),
62118
+ getComposition: (input) => dispatch("coreBlocks", "getComposition", "query", input),
62119
+ listCompositions: (input) => dispatch("coreBlocks", "listCompositions", "query", input),
62120
+ validateComposition: (input) => dispatch("coreBlocks", "validateComposition", "mutation", input)
60924
62121
  },
60925
62122
  decoder: {
60926
62123
  supportsCodec: (input) => dispatch("decoder", "supportsCodec", "query", input),
@@ -67157,11 +68354,13 @@ exports.AutomationControlStatusSchema = AutomationControlStatusSchema;
67157
68354
  exports.AutomationRecipeSchema = AutomationRecipeSchema;
67158
68355
  exports.AutomationTriggerSchema = AutomationTriggerSchema;
67159
68356
  exports.AvailableIntegrationTypeSchema = AvailableIntegrationTypeSchema;
68357
+ exports.BASE_DEVICE_CAPS = BASE_DEVICE_CAPS;
67160
68358
  exports.BASE_LIVE_EGRESS_PROFILE = BASE_LIVE_EGRESS_PROFILE;
67161
68359
  exports.BATTERY_DEVICE_PROFILE = BATTERY_DEVICE_PROFILE;
67162
68360
  exports.BATTERY_UNREACHABLE_AFTER_MS = BATTERY_UNREACHABLE_AFTER_MS;
67163
68361
  exports.BIRTH_DECISION_DEFAULT_LIMIT = BIRTH_DECISION_DEFAULT_LIMIT;
67164
68362
  exports.BOOT_RECOVERY_BACKOFF_MS = require_sleep.BOOT_RECOVERY_BACKOFF_MS;
68363
+ exports.BUILTIN_RESULT_KINDS = BUILTIN_RESULT_KINDS;
67165
68364
  exports.BackgroundRunRegistry = BackgroundRunRegistry;
67166
68365
  exports.BacklightModeSchema = BacklightModeSchema;
67167
68366
  exports.BackupDestinationInfoSchema = BackupDestinationInfoSchema;
@@ -67229,6 +68428,9 @@ exports.CLUSTER_MODEL_SECTION_ID = CLUSTER_MODEL_SECTION_ID;
67229
68428
  exports.CLUSTER_STEP_SETTING_FIELDS = CLUSTER_STEP_SETTING_FIELDS;
67230
68429
  exports.COCO_80_LABELS = COCO_80_LABELS;
67231
68430
  exports.COCO_TO_MACRO = COCO_TO_MACRO;
68431
+ exports.COMPOSED_DEVICE_STABLE_ID_PREFIX = COMPOSED_DEVICE_STABLE_ID_PREFIX;
68432
+ exports.COMPOSER_ADDON_ID = COMPOSER_ADDON_ID;
68433
+ exports.COMPOSER_RUNNER_ID = COMPOSER_RUNNER_ID;
67232
68434
  exports.CONNECTION_TEST_TIMEOUT_MS = CONNECTION_TEST_TIMEOUT_MS;
67233
68435
  exports.CONTAINER_CHILD_PRIORITY = CONTAINER_CHILD_PRIORITY;
67234
68436
  exports.CORE_BLOCKS_ADDON_ID = CORE_BLOCKS_ADDON_ID;
@@ -67297,6 +68499,45 @@ exports.ClusterModelChoiceSchema = ClusterModelChoiceSchema;
67297
68499
  exports.CollectionColumnSchema = CollectionColumnSchema;
67298
68500
  exports.CollectionIndexSchema = CollectionIndexSchema;
67299
68501
  exports.ColorStatusSchema = ColorStatusSchema;
68502
+ exports.ComposerApplyInputSchema = ComposerApplyInputSchema;
68503
+ exports.ComposerApplyResultSchema = ComposerApplyResultSchema;
68504
+ exports.ComposerBlockSchema = ComposerBlockSchema;
68505
+ exports.ComposerBlockVerdictSchema = ComposerBlockVerdictSchema;
68506
+ exports.ComposerUnreadableBlockSchema = ComposerUnreadableBlockSchema;
68507
+ exports.CompositionBindingSchema = CompositionBindingSchema;
68508
+ exports.CompositionBlockInputSchema = CompositionBlockInputSchema;
68509
+ exports.CompositionBlockStateSchema = CompositionBlockStateSchema;
68510
+ exports.CompositionCodeSourceSchema = CompositionCodeSourceSchema;
68511
+ exports.CompositionCommandTargetSchema = CompositionCommandTargetSchema;
68512
+ exports.CompositionExistingTargetSchema = CompositionExistingTargetSchema;
68513
+ exports.CompositionExpressionSourceSchema = CompositionExpressionSourceSchema;
68514
+ exports.CompositionFeatureSchema = CompositionFeatureSchema;
68515
+ exports.CompositionFieldKindSchema = CompositionFieldKindSchema;
68516
+ exports.CompositionFieldPlanSchema = CompositionFieldPlanSchema;
68517
+ exports.CompositionFieldReadSchema = CompositionFieldReadSchema;
68518
+ exports.CompositionFieldRoleSchema = CompositionFieldRoleSchema;
68519
+ exports.CompositionFieldSourceSchema = CompositionFieldSourceSchema;
68520
+ exports.CompositionFieldStateKindSchema = CompositionFieldStateKindSchema;
68521
+ exports.CompositionFieldStateSchema = CompositionFieldStateSchema;
68522
+ exports.CompositionFieldsFeatureSchema = CompositionFieldsFeatureSchema;
68523
+ exports.CompositionForwardCommandSchema = CompositionForwardCommandSchema;
68524
+ exports.CompositionFromSourceSchema = CompositionFromSourceSchema;
68525
+ exports.CompositionGetReadSchema = CompositionGetReadSchema;
68526
+ exports.CompositionGraphError = CompositionGraphError;
68527
+ exports.CompositionListReadSchema = CompositionListReadSchema;
68528
+ exports.CompositionLiteralBindingSchema = CompositionLiteralBindingSchema;
68529
+ exports.CompositionNewTargetSchema = CompositionNewTargetSchema;
68530
+ exports.CompositionPassthroughFeatureSchema = CompositionPassthroughFeatureSchema;
68531
+ exports.CompositionProblemCodeSchema = CompositionProblemCodeSchema;
68532
+ exports.CompositionProblemSchema = CompositionProblemSchema;
68533
+ exports.CompositionRunStatusSchema = CompositionRunStatusSchema;
68534
+ exports.CompositionSchema = CompositionSchema;
68535
+ exports.CompositionSourceRefSchema = CompositionSourceRefSchema;
68536
+ exports.CompositionStateReadSchema = CompositionStateReadSchema;
68537
+ exports.CompositionTargetSchema = CompositionTargetSchema;
68538
+ exports.CompositionUnavailableModeSchema = CompositionUnavailableModeSchema;
68539
+ exports.CompositionValidationSchema = CompositionValidationSchema;
68540
+ exports.CompositionViewSchema = CompositionViewSchema;
67300
68541
  exports.ConfigEntrySchema = ConfigEntrySchema;
67301
68542
  exports.ConfigSectionWithValuesSchema = ConfigSectionWithValuesSchema;
67302
68543
  exports.ConfigTabDeclarationSchema = ConfigTabDeclarationSchema;
@@ -67316,6 +68557,7 @@ exports.CoreBlockCompileResultSchema = CoreBlockCompileResultSchema;
67316
68557
  exports.CoreBlockInputSchema = CoreBlockInputSchema;
67317
68558
  exports.CoreBlockPlacementSchema = CoreBlockPlacementSchema;
67318
68559
  exports.CoreBlockSchema = CoreBlockSchema;
68560
+ exports.CoreBlockSourceSchema = CoreBlockSourceSchema;
67319
68561
  exports.CoreBlockStatusSchema = CoreBlockStatusSchema;
67320
68562
  exports.CoverStateSchema = CoverStateSchema;
67321
68563
  exports.CoverStatusSchema = CoverStatusSchema;
@@ -67648,6 +68890,9 @@ exports.MAX_BIRTH_DECISION_RECORDS = MAX_BIRTH_DECISION_RECORDS;
67648
68890
  exports.MAX_BIRTH_LATENCY_PROXY_MS = MAX_BIRTH_LATENCY_PROXY_MS;
67649
68891
  exports.MAX_CLIP_EVENT_IDS = MAX_CLIP_EVENT_IDS;
67650
68892
  exports.MAX_CLIP_LABELS = MAX_CLIP_LABELS;
68893
+ exports.MAX_COMPOSITION_CODE_LENGTH = MAX_COMPOSITION_CODE_LENGTH;
68894
+ exports.MAX_COMPOSITION_FEATURES = MAX_COMPOSITION_FEATURES;
68895
+ exports.MAX_COMPOSITION_FIELDS_PER_FEATURE = MAX_COMPOSITION_FIELDS_PER_FEATURE;
67651
68896
  exports.MAX_CONDITION_DEPTH = MAX_CONDITION_DEPTH;
67652
68897
  exports.MAX_CONDITION_LEAVES = MAX_CONDITION_LEAVES;
67653
68898
  exports.MAX_EXPRESSION_AST_NODES = MAX_EXPRESSION_AST_NODES;
@@ -68014,6 +69259,9 @@ exports.REDACTED_SECRET = REDACTED_SECRET;
68014
69259
  exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
68015
69260
  exports.RESERVED_COLLECTION_SOURCES = RESERVED_COLLECTION_SOURCES;
68016
69261
  exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
69262
+ exports.ROOM_EXAMPLE_MOTION_SOURCES = ROOM_EXAMPLE_MOTION_SOURCES;
69263
+ exports.ROOM_EXAMPLE_TEMPERATURE_SOURCES = ROOM_EXAMPLE_TEMPERATURE_SOURCES;
69264
+ exports.ROOM_OCCUPANCY_WINDOW_MS = ROOM_OCCUPANCY_WINDOW_MS;
68017
69265
  exports.ROOT_BUCKET_KEY = ROOT_BUCKET_KEY;
68018
69266
  exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
68019
69267
  exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
@@ -68411,6 +69659,7 @@ exports.brightnessCapability = brightnessCapability;
68411
69659
  exports.brokerCapability = brokerCapability;
68412
69660
  exports.buildAddonRouteProvider = buildAddonRouteProvider;
68413
69661
  exports.buildAudioArgs = require_canonical_hash.buildAudioArgs;
69662
+ exports.buildCapabilityLookup = buildCapabilityLookup;
68414
69663
  exports.buildDeviceAdminUrl = buildDeviceAdminUrl;
68415
69664
  exports.buildEventKindDescriptor = buildEventKindDescriptor;
68416
69665
  exports.buildFfmpegArgs = require_canonical_hash.buildFfmpegArgs;
@@ -68450,6 +69699,14 @@ exports.compareToTarget = compareToTarget;
68450
69699
  exports.compileExpression = compileExpression;
68451
69700
  exports.compileExpressionSafe = compileExpressionSafe;
68452
69701
  exports.composeSwitchedOff = composeSwitchedOff;
69702
+ exports.composedDeviceRef = composedDeviceRef;
69703
+ exports.composedDeviceStableId = composedDeviceStableId;
69704
+ exports.composerCapability = composerCapability;
69705
+ exports.compositionEdges = compositionEdges;
69706
+ exports.compositionReadKey = compositionReadKey;
69707
+ exports.compositionSliceKey = compositionSliceKey;
69708
+ exports.compositionSourceKey = compositionSourceKey;
69709
+ exports.compositionSourceRefs = compositionSourceRefs;
68453
69710
  exports.conditionDepth = conditionDepth;
68454
69711
  exports.conditionExclusionReason = conditionExclusionReason;
68455
69712
  exports.conditionVisibleForKind = conditionVisibleForKind;
@@ -68461,7 +69718,9 @@ exports.contactCapability = contactCapability;
68461
69718
  exports.controlCapability = controlCapability;
68462
69719
  exports.convertUnit = convertUnit;
68463
69720
  exports.coreBlockAddonId = coreBlockAddonId;
69721
+ exports.coreBlockComposition = coreBlockComposition;
68464
69722
  exports.coreBlockIdFromAddonId = coreBlockIdFromAddonId;
69723
+ exports.coreBlockSourceKind = coreBlockSourceKind;
68465
69724
  exports.coreBlocksCapability = coreBlocksCapability;
68466
69725
  exports.cosineSimilarity = cosineSimilarity;
68467
69726
  exports.countConditionLeaves = countConditionLeaves;
@@ -68500,8 +69759,10 @@ exports.describeCustomActions = require_sleep.describeCustomActions;
68500
69759
  exports.describeModelVariant = describeModelVariant;
68501
69760
  exports.describeOnboardClamp = describeOnboardClamp;
68502
69761
  exports.describeOnboardRefusal = describeOnboardRefusal;
69762
+ exports.describeStateField = describeStateField;
68503
69763
  exports.describeSystemEventDeliveryProblem = describeSystemEventDeliveryProblem;
68504
69764
  exports.describeSystemEventFamilyProblem = describeSystemEventFamilyProblem;
69765
+ exports.describeTopLevelStateFields = describeTopLevelStateFields;
68505
69766
  exports.detectAccessRole = detectAccessRole;
68506
69767
  exports.detectionPipelineCapability = detectionPipelineCapability;
68507
69768
  exports.deviceAdminLinkCapability = deviceAdminLinkCapability;
@@ -68541,6 +69802,7 @@ exports.enumerateItemArrayFields = enumerateItemArrayFields;
68541
69802
  exports.enumerateSchemaFields = enumerateSchemaFields;
68542
69803
  exports.errMsg = require_err_msg.errMsg;
68543
69804
  exports.evaluateAst = evaluateAst;
69805
+ exports.evaluateCompositionField = evaluateCompositionField;
68544
69806
  exports.evaluateExpressionSource = evaluateExpressionSource;
68545
69807
  exports.evaluatePoolMemory = evaluatePoolMemory;
68546
69808
  exports.evaluateSensorEdge = evaluateSensorEdge;
@@ -68561,7 +69823,9 @@ exports.failureContributionCapability = failureContributionCapability;
68561
69823
  exports.failureRate = failureRate;
68562
69824
  exports.fanControlCapability = fanControlCapability;
68563
69825
  exports.featureProbeCapability = featureProbeCapability;
69826
+ exports.fieldReads = fieldReads;
68564
69827
  exports.filesystemBrowseCapability = filesystemBrowseCapability;
69828
+ exports.findCompositionCycles = findCompositionCycles;
68565
69829
  exports.findTimezone = findTimezone;
68566
69830
  exports.floodCapability = floodCapability;
68567
69831
  exports.foldSnapshotByFunction = foldSnapshotByFunction;
@@ -68585,6 +69849,7 @@ exports.humiditySensorCapability = humiditySensorCapability;
68585
69849
  exports.hydrateSchema = require_sleep.hydrateSchema;
68586
69850
  exports.imageCapability = imageCapability;
68587
69851
  exports.imageSettingsCapability = imageSettingsCapability;
69852
+ exports.inferExpressionKinds = inferExpressionKinds;
68588
69853
  exports.inferModelProvider = inferModelProvider;
68589
69854
  exports.initialPoolMemoryState = initialPoolMemoryState;
68590
69855
  exports.integrationsCapability = integrationsCapability;
@@ -68634,6 +69899,7 @@ exports.isVoidInput = isVoidInput;
68634
69899
  exports.ituDbfsFromPreEpoch = ituDbfsFromPreEpoch;
68635
69900
  exports.jobKindSchema = jobKindSchema;
68636
69901
  exports.kebabToCamel = kebabToCamel;
69902
+ exports.kindOfValue = kindOfValue;
68637
69903
  exports.kindsForDiscriminator = kindsForDiscriminator;
68638
69904
  exports.knownValues = knownValues;
68639
69905
  exports.lawnMowerControlCapability = lawnMowerControlCapability;
@@ -68736,6 +70002,7 @@ exports.pipelineAnalyticsCapability = pipelineAnalyticsCapability;
68736
70002
  exports.pipelineExecutorCapability = pipelineExecutorCapability;
68737
70003
  exports.pipelineOrchestratorCapability = pipelineOrchestratorCapability;
68738
70004
  exports.pipelineRunnerCapability = pipelineRunnerCapability;
70005
+ exports.planComposition = planComposition;
68739
70006
  exports.plateGalleryCapability = plateGalleryCapability;
68740
70007
  exports.platformProbeCapability = platformProbeCapability;
68741
70008
  exports.poolMemoryThreshold = poolMemoryThreshold;
@@ -68766,6 +70033,7 @@ exports.recordingOnboardCapability = recordingOnboardCapability;
68766
70033
  exports.recordingSignalCapability = recordingSignalCapability;
68767
70034
  exports.rectsToCells = rectsToCells;
68768
70035
  exports.reducePoints = reducePoints;
70036
+ exports.requiredTopLevelKeys = requiredTopLevelKeys;
68769
70037
  exports.requiresPython = requiresPython;
68770
70038
  exports.reservedSourceOwner = reservedSourceOwner;
68771
70039
  exports.resetPoolBaseline = resetPoolBaseline;
@@ -68795,6 +70063,7 @@ exports.resolveStreamMaps = require_canonical_hash.resolveStreamMaps;
68795
70063
  exports.resolveVariantModelId = resolveVariantModelId;
68796
70064
  exports.resolveViewableDeviceIds = resolveViewableDeviceIds;
68797
70065
  exports.roleSpec = roleSpec;
70066
+ exports.roomCompositionExample = roomCompositionExample;
68798
70067
  exports.ruleBelongsToAlarm = ruleBelongsToAlarm;
68799
70068
  exports.ruleEditorSectionsForKind = ruleEditorSectionsForKind;
68800
70069
  exports.ruleKindOf = ruleKindOf;
@@ -68900,6 +70169,7 @@ exports.updateCapability = updateCapability;
68900
70169
  exports.userManagementCapability = userManagementCapability;
68901
70170
  exports.userPasskeysCapability = userPasskeysCapability;
68902
70171
  exports.vacuumControlCapability = vacuumControlCapability;
70172
+ exports.validateComposition = validateComposition;
68903
70173
  exports.validateExpressionSource = validateExpressionSource;
68904
70174
  exports.validateRecipeBounds = validateRecipeBounds;
68905
70175
  exports.valveCapability = valveCapability;