@camstack/types 1.2.267 → 1.2.268

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 (56) hide show
  1. package/dist/addon.js +4 -4
  2. package/dist/addon.mjs +3 -3
  3. package/dist/capabilities/battery.cap.d.ts +17 -2
  4. package/dist/capabilities/capability-definition.d.ts +22 -0
  5. package/dist/capabilities/composer.cap.d.ts +99 -0
  6. package/dist/capabilities/consumables.cap.d.ts +79 -0
  7. package/dist/capabilities/core-blocks.cap.d.ts +1149 -91
  8. package/dist/capabilities/device-extension.cap.d.ts +12 -13
  9. package/dist/capabilities/device-state.cap.d.ts +186 -2
  10. package/dist/capabilities/index.d.ts +3 -3
  11. package/dist/composition/composition-graph.d.ts +42 -9
  12. package/dist/composition/composition-items.d.ts +25 -0
  13. package/dist/composition/composition-report.d.ts +93 -2
  14. package/dist/composition/composition.d.ts +154 -3
  15. package/dist/composition/customization-name.d.ts +6 -0
  16. package/dist/composition/evaluate-field.d.ts +12 -0
  17. package/dist/composition/examples.d.ts +33 -0
  18. package/dist/composition/field-kinds.d.ts +42 -0
  19. package/dist/composition/index.d.ts +1 -0
  20. package/dist/composition/plan-result.d.ts +16 -0
  21. package/dist/composition/state-fields.d.ts +6 -0
  22. package/dist/composition/validate-composition.d.ts +44 -9
  23. package/dist/composition-CPFIlFfw.mjs +1759 -0
  24. package/dist/composition-QlOB7QTG.js +2184 -0
  25. package/dist/device/device-context.d.ts +9 -0
  26. package/dist/device/device-runtime-state.d.ts +15 -1
  27. package/dist/device/index.d.ts +1 -1
  28. package/dist/device-extension/slot-descriptor.d.ts +8 -11
  29. package/dist/device-extension/slot-provider.d.ts +0 -2
  30. package/dist/enums/event-category.d.ts +25 -0
  31. package/dist/enums.js +1 -1
  32. package/dist/enums.mjs +1 -1
  33. package/dist/err-msg-DX6i_MY4.mjs +339 -0
  34. package/dist/err-msg-Dx2Kor0g.js +368 -0
  35. package/dist/{event-category-BVDXG4tB.mjs → event-category-C5xZWqz6.mjs} +31 -1
  36. package/dist/{event-category-BVfsrBYA.js → event-category-MbnB-6AN.js} +48 -0
  37. package/dist/generated/addon-api.d.ts +42 -0
  38. package/dist/generated/device-proxy.d.ts +1 -1
  39. package/dist/generated/method-access-map.d.ts +1 -1
  40. package/dist/generated/method-device-selectors.d.ts +2 -2
  41. package/dist/generated/system-proxy.d.ts +2 -2
  42. package/dist/index.d.ts +4 -3
  43. package/dist/index.js +1513 -1656
  44. package/dist/index.mjs +1208 -1384
  45. package/dist/interfaces/kernel-abstractions.d.ts +16 -0
  46. package/dist/interfaces/status-overlay.d.ts +42 -0
  47. package/dist/node.d.ts +1 -0
  48. package/dist/node.js +29 -8
  49. package/dist/node.mjs +23 -3
  50. package/dist/{sleep-8K9gue-H.js → sleep-BSzia-xT.js} +6 -352
  51. package/dist/{sleep-DC-wdyeS.mjs → sleep-jkRQgtSc.mjs} +7 -329
  52. package/package.json +1 -1
  53. package/dist/canonical-hash-CPK2Dy60.mjs +0 -715
  54. package/dist/canonical-hash-CSE4ioRi.js +0 -792
  55. package/dist/err-msg-COpsHMw2.js +0 -18
  56. package/dist/err-msg-IQTHeDzc.mjs +0 -13
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-8K9gue-H.js");
3
- const require_event_category = require("./event-category-BVfsrBYA.js");
4
- const require_canonical_hash = require("./canonical-hash-CSE4ioRi.js");
2
+ const require_sleep = require("./sleep-BSzia-xT.js");
3
+ const require_event_category = require("./event-category-MbnB-6AN.js");
4
+ const require_composition = require("./composition-QlOB7QTG.js");
5
+ const require_err_msg = require("./err-msg-Dx2Kor0g.js");
5
6
  const require_enums = require("./enums.js");
6
- const require_err_msg = require("./err-msg-COpsHMw2.js");
7
7
  let zod = require("zod");
8
8
  //#region src/media/media-owner-key.ts
9
9
  /**
@@ -741,7 +741,7 @@ function canonicalEgressPlan(request, delivery = egressTransportFromRequest(requ
741
741
  * every other consumer to 360p.
742
742
  */
743
743
  function egressTranscodeSharingKey(request, delivery = egressTransportFromRequest(request)) {
744
- return `egress:${require_canonical_hash.canonicalHash(canonicalEgressPlan(request, delivery))}`;
744
+ return `egress:${require_composition.canonicalHash(canonicalEgressPlan(request, delivery))}`;
745
745
  }
746
746
  //#endregion
747
747
  //#region src/health/wiring-health.ts
@@ -5531,809 +5531,6 @@ function principalMayReachAddon(input) {
5531
5531
  };
5532
5532
  }
5533
5533
  //#endregion
5534
- //#region src/expression/errors.ts
5535
- /**
5536
- * Error types for the safe expression engine. Two distinct classes so callers
5537
- * can tell a compile-time (grammar) failure from a runtime (evaluation)
5538
- * failure — both are non-fatal to the host: read paths degrade to "skip link".
5539
- */
5540
- /** Thrown by the tokenizer / parser. Carries a 0-based source `position` when
5541
- * the failure is anchored to a character (author-facing inline feedback). */
5542
- var ExpressionParseError = class extends Error {
5543
- position;
5544
- constructor(message, position) {
5545
- super(message);
5546
- this.name = "ExpressionParseError";
5547
- this.position = position;
5548
- }
5549
- };
5550
- /** Thrown by the evaluator (unknown identifier, type mismatch, non-finite
5551
- * result, unknown builtin, step-budget exceeded). */
5552
- var ExpressionEvalError = class extends Error {
5553
- constructor(message) {
5554
- super(message);
5555
- this.name = "ExpressionEvalError";
5556
- }
5557
- };
5558
- //#endregion
5559
- //#region src/expression/builtins.ts
5560
- function asFiniteNumber(value, name, index) {
5561
- if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
5562
- return value;
5563
- }
5564
- function asString$1(value, name, index) {
5565
- if (typeof value !== "string") throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a string`);
5566
- return value;
5567
- }
5568
- function finiteResult(value, name) {
5569
- if (!Number.isFinite(value)) throw new ExpressionEvalError(`${name}: produced a non-finite result`);
5570
- return value;
5571
- }
5572
- function allFiniteNumbers(args, name) {
5573
- return args.map((a, idx) => asFiniteNumber(a, name, idx));
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
- }
5579
- var INF = Number.POSITIVE_INFINITY;
5580
- var table = {
5581
- min: {
5582
- minArgs: 1,
5583
- maxArgs: INF,
5584
- apply: (args) => finiteResult(Math.min(...allFiniteNumbers(args, "min")), "min")
5585
- },
5586
- max: {
5587
- minArgs: 1,
5588
- maxArgs: INF,
5589
- apply: (args) => finiteResult(Math.max(...allFiniteNumbers(args, "max")), "max")
5590
- },
5591
- abs: {
5592
- minArgs: 1,
5593
- maxArgs: 1,
5594
- apply: (args) => finiteResult(Math.abs(asFiniteNumber(args[0], "abs", 0)), "abs")
5595
- },
5596
- floor: {
5597
- minArgs: 1,
5598
- maxArgs: 1,
5599
- apply: (args) => finiteResult(Math.floor(asFiniteNumber(args[0], "floor", 0)), "floor")
5600
- },
5601
- ceil: {
5602
- minArgs: 1,
5603
- maxArgs: 1,
5604
- apply: (args) => finiteResult(Math.ceil(asFiniteNumber(args[0], "ceil", 0)), "ceil")
5605
- },
5606
- sqrt: {
5607
- minArgs: 1,
5608
- maxArgs: 1,
5609
- apply: (args) => finiteResult(Math.sqrt(asFiniteNumber(args[0], "sqrt", 0)), "sqrt")
5610
- },
5611
- round: {
5612
- minArgs: 1,
5613
- maxArgs: 2,
5614
- apply: (args) => {
5615
- const x = asFiniteNumber(args[0], "round", 0);
5616
- const digits = args.length > 1 ? Math.trunc(asFiniteNumber(args[1], "round", 1)) : 0;
5617
- if (digits < 0 || digits > 100) throw new ExpressionEvalError("round: digits must be between 0 and 100");
5618
- const factor = 10 ** digits;
5619
- return finiteResult(Math.round(x * factor) / factor, "round");
5620
- }
5621
- },
5622
- pow: {
5623
- minArgs: 2,
5624
- maxArgs: 2,
5625
- apply: (args) => finiteResult(asFiniteNumber(args[0], "pow", 0) ** asFiniteNumber(args[1], "pow", 1), "pow")
5626
- },
5627
- clamp: {
5628
- minArgs: 3,
5629
- maxArgs: 3,
5630
- apply: (args) => {
5631
- const x = asFiniteNumber(args[0], "clamp", 0);
5632
- const lo = asFiniteNumber(args[1], "clamp", 1);
5633
- const hi = asFiniteNumber(args[2], "clamp", 2);
5634
- if (lo > hi) throw new ExpressionEvalError("clamp: lower bound is greater than upper bound");
5635
- return finiteResult(Math.min(hi, Math.max(lo, x)), "clamp");
5636
- }
5637
- },
5638
- avg: {
5639
- minArgs: 1,
5640
- maxArgs: INF,
5641
- apply: (args) => {
5642
- const nums = allFiniteNumbers(args, "avg");
5643
- return finiteResult(nums.reduce((acc, v) => acc + v, 0) / nums.length, "avg");
5644
- }
5645
- },
5646
- sum: {
5647
- minArgs: 1,
5648
- maxArgs: INF,
5649
- apply: (args) => finiteResult(allFiniteNumbers(args, "sum").reduce((acc, v) => acc + v, 0), "sum")
5650
- },
5651
- coalesce: {
5652
- minArgs: 1,
5653
- maxArgs: INF,
5654
- apply: (args) => {
5655
- for (const a of args) if (a !== null) return a;
5656
- return null;
5657
- }
5658
- },
5659
- age: {
5660
- minArgs: 2,
5661
- maxArgs: 2,
5662
- apply: (args) => finiteResult(asFiniteNumber(args[0], "age", 0) - asFiniteNumber(args[1], "age", 1), "age")
5663
- },
5664
- convert: {
5665
- minArgs: 3,
5666
- maxArgs: 3,
5667
- apply: (args, hooks) => {
5668
- const x = asFiniteNumber(args[0], "convert", 0);
5669
- const from = asString$1(args[1], "convert", 1).trim();
5670
- const to = asString$1(args[2], "convert", 2).trim();
5671
- if (hooks.convert) {
5672
- const out = hooks.convert(x, from, to);
5673
- if (out === null) throw new ExpressionEvalError(`convert: cannot convert '${from}' to '${to}'`);
5674
- return finiteResult(out, "convert");
5675
- }
5676
- if (from === to) return x;
5677
- throw new ExpressionEvalError("convert: unit conversion table not installed");
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
- }
5713
- }
5714
- };
5715
- /** Frozen, null-prototype builtin table. */
5716
- var EXPRESSION_BUILTINS = Object.freeze(Object.assign(Object.create(null), table));
5717
- /** The set of valid builtin names — used by the parser to reject unknown
5718
- * callees at parse time (immediate author feedback). */
5719
- var EXPRESSION_BUILTIN_NAMES = new Set(Object.keys(table));
5720
- //#endregion
5721
- //#region src/expression/limits.ts
5722
- /**
5723
- * Resource-bound constants for the safe expression engine.
5724
- *
5725
- * Every bound is defense-in-depth: the grammar is non-Turing-complete (no
5726
- * loops, recursion, lambdas or member access — see `ast.ts`), so evaluation is
5727
- * O(nodeCount) by construction. These caps merely put a hard ceiling on the
5728
- * work a single author-supplied expression can request, so a hostile or
5729
- * accidental pathological string can never spend unbounded CPU/memory.
5730
- */
5731
- /** Max source length (chars) — checked BEFORE tokenizing so a huge string is
5732
- * rejected without allocation. */
5733
- var MAX_EXPRESSION_SOURCE_LENGTH = 2048;
5734
- /** Max AST nodes — checked during parse; a deeply nested grouping that exceeds
5735
- * this is rejected as "expression too complex". */
5736
- var MAX_EXPRESSION_AST_NODES = 256;
5737
- /** Defense-in-depth walker step budget — one increment per node visit during
5738
- * evaluation. The grammar guarantees O(nodeCount) walks, so this can only trip
5739
- * on a crafted maximum-size AST. */
5740
- var MAX_EXPRESSION_EVAL_STEPS = 4096;
5741
- /** Max named bindings on one {@link ExpressionSource}. */
5742
- var MAX_EXPRESSION_BINDINGS = 32;
5743
- /** Max positional arguments to any builtin call. */
5744
- var MAX_EXPRESSION_CALL_ARGS = 16;
5745
- /** LRU compile-cache capacity (parsed ASTs keyed by raw source string). */
5746
- var EXPRESSION_COMPILE_CACHE_CAPACITY = 256;
5747
- /** A legal binding / identifier name. */
5748
- var EXPRESSION_IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
5749
- /** Binding names an author may NOT use: `now` is auto-injected; the literal
5750
- * keywords lex as values, not identifiers, so binding to them is meaningless. */
5751
- var RESERVED_BINDING_NAMES = new Set([
5752
- "now",
5753
- "true",
5754
- "false",
5755
- "null"
5756
- ]);
5757
- //#endregion
5758
- //#region src/expression/tokenizer.ts
5759
- /**
5760
- * Tokenizer for the safe expression mini-language. Hand-rolled, single-pass,
5761
- * zero-dependency. The grammar is deliberately boring: decimal numbers,
5762
- * single/double-quoted strings with a tiny escape set, identifiers, the three
5763
- * value keywords (`true`/`false`/`null`) and a fixed punctuator set. Anything
5764
- * outside that — a bare `.`, `=`, `[`, `]`, `{`, `}`, `;`, backtick, `&`, `|` —
5765
- * is a parse error with a source position, so member access / assignment /
5766
- * template literals are lexically impossible.
5767
- */
5768
- var KEYWORDS = new Set([
5769
- "true",
5770
- "false",
5771
- "null"
5772
- ]);
5773
- function isDigit(ch) {
5774
- return ch >= "0" && ch <= "9";
5775
- }
5776
- function isIdentStart(ch) {
5777
- return ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch === "_";
5778
- }
5779
- function isIdentPart(ch) {
5780
- return isIdentStart(ch) || isDigit(ch);
5781
- }
5782
- function isWhitespace(ch) {
5783
- return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
5784
- }
5785
- /** Tokenize `source` into a flat token list ending with a single `eof` token.
5786
- * Throws `ExpressionParseError` on any illegal character or unterminated
5787
- * string. */
5788
- function tokenize(source) {
5789
- if (source.length > 2048) throw new ExpressionParseError(`expression too long (${source.length} > ${MAX_EXPRESSION_SOURCE_LENGTH} chars)`, 0);
5790
- const tokens = [];
5791
- let i = 0;
5792
- const n = source.length;
5793
- while (i < n) {
5794
- const ch = source[i];
5795
- if (isWhitespace(ch)) {
5796
- i += 1;
5797
- continue;
5798
- }
5799
- if (isDigit(ch)) {
5800
- const start = i;
5801
- while (i < n && isDigit(source[i])) i += 1;
5802
- if (i < n && source[i] === ".") {
5803
- if (i + 1 >= n || !isDigit(source[i + 1])) throw new ExpressionParseError("malformed number: decimal point needs a digit", i);
5804
- i += 1;
5805
- while (i < n && isDigit(source[i])) i += 1;
5806
- }
5807
- const text = source.slice(start, i);
5808
- const value = Number(text);
5809
- if (!Number.isFinite(value)) throw new ExpressionParseError(`malformed number: '${text}'`, start);
5810
- tokens.push({
5811
- type: "number",
5812
- value,
5813
- pos: start
5814
- });
5815
- continue;
5816
- }
5817
- if (ch === "'" || ch === "\"") {
5818
- const quote = ch;
5819
- const start = i;
5820
- i += 1;
5821
- let out = "";
5822
- let closed = false;
5823
- while (i < n) {
5824
- const c = source[i];
5825
- if (c === "\\") {
5826
- const next = i + 1 < n ? source[i + 1] : "";
5827
- if (next === "\\" || next === "'" || next === "\"") {
5828
- out += next;
5829
- i += 2;
5830
- continue;
5831
- }
5832
- throw new ExpressionParseError(`invalid string escape: '\\${next}'`, i);
5833
- }
5834
- if (c === quote) {
5835
- closed = true;
5836
- i += 1;
5837
- break;
5838
- }
5839
- out += c;
5840
- i += 1;
5841
- }
5842
- if (!closed) throw new ExpressionParseError("unterminated string literal", start);
5843
- tokens.push({
5844
- type: "string",
5845
- value: out,
5846
- pos: start
5847
- });
5848
- continue;
5849
- }
5850
- if (isIdentStart(ch)) {
5851
- const start = i;
5852
- while (i < n && isIdentPart(source[i])) i += 1;
5853
- const text = source.slice(start, i);
5854
- if (KEYWORDS.has(text)) tokens.push({
5855
- type: "keyword",
5856
- keyword: keywordOf(text),
5857
- pos: start
5858
- });
5859
- else tokens.push({
5860
- type: "identifier",
5861
- name: text,
5862
- pos: start
5863
- });
5864
- continue;
5865
- }
5866
- const two = i + 1 < n ? source.slice(i, i + 2) : "";
5867
- if (two === "<=" || two === ">=" || two === "==" || two === "!=" || two === "&&" || two === "||") {
5868
- tokens.push({
5869
- type: "punct",
5870
- punct: two,
5871
- pos: i
5872
- });
5873
- i += 2;
5874
- continue;
5875
- }
5876
- if (isSinglePunct(ch)) {
5877
- tokens.push({
5878
- type: "punct",
5879
- punct: ch,
5880
- pos: i
5881
- });
5882
- i += 1;
5883
- continue;
5884
- }
5885
- throw new ExpressionParseError(`unexpected character '${ch}'`, i);
5886
- }
5887
- tokens.push({
5888
- type: "eof",
5889
- pos: n
5890
- });
5891
- return tokens;
5892
- }
5893
- function keywordOf(text) {
5894
- if (text === "true") return "true";
5895
- if (text === "false") return "false";
5896
- return "null";
5897
- }
5898
- function isSinglePunct(ch) {
5899
- return ch === "(" || ch === ")" || ch === "," || ch === "?" || ch === ":" || ch === "+" || ch === "-" || ch === "*" || ch === "/" || ch === "%" || ch === "!" || ch === "<" || ch === ">";
5900
- }
5901
- //#endregion
5902
- //#region src/expression/parser.ts
5903
- /**
5904
- * Pratt (precedence-climbing) parser for the safe expression mini-language.
5905
- *
5906
- * Precedence (low → high): ternary `?:` (right-assoc) → `||` → `&&` → equality
5907
- * → relational → additive → multiplicative → unary `! -` → call / primary.
5908
- * Calls are ONLY `IDENT '(' args? ')'` at primary position — the callee is a
5909
- * string validated against the builtin table at parse time, so an unknown
5910
- * function is rejected immediately (author feedback) and a persisted expression
5911
- * that references a since-removed builtin degrades at read.
5912
- *
5913
- * A node counter caps total AST size (`MAX_EXPRESSION_AST_NODES`) and call
5914
- * arity is capped (`MAX_EXPRESSION_CALL_ARGS`) — both raise `ExpressionParseError`.
5915
- */
5916
- /** Binary/logical operator precedence (higher binds tighter). */
5917
- var BINARY_PRECEDENCE = {
5918
- "||": 1,
5919
- "&&": 2,
5920
- "==": 3,
5921
- "!=": 3,
5922
- "<": 4,
5923
- "<=": 4,
5924
- ">": 4,
5925
- ">=": 4,
5926
- "+": 5,
5927
- "-": 5,
5928
- "*": 6,
5929
- "/": 6,
5930
- "%": 6
5931
- };
5932
- function isLogicalOp(op) {
5933
- return op === "&&" || op === "||";
5934
- }
5935
- function isBinaryOp(op) {
5936
- return op === "+" || op === "-" || op === "*" || op === "/" || op === "%" || op === "==" || op === "!=" || op === "<" || op === "<=" || op === ">" || op === ">=";
5937
- }
5938
- var Parser = class {
5939
- tokens;
5940
- pos = 0;
5941
- nodeCount = 0;
5942
- identifiers = /* @__PURE__ */ new Set();
5943
- callees = /* @__PURE__ */ new Set();
5944
- constructor(tokens) {
5945
- this.tokens = tokens;
5946
- }
5947
- parse() {
5948
- const ast = this.parseTernary();
5949
- const tok = this.peek();
5950
- if (tok.type !== "eof") throw new ExpressionParseError("unexpected trailing input", tok.pos);
5951
- return {
5952
- ast,
5953
- identifiers: this.identifiers,
5954
- callees: this.callees,
5955
- nodeCount: this.nodeCount
5956
- };
5957
- }
5958
- peek() {
5959
- return this.tokens[this.pos];
5960
- }
5961
- next() {
5962
- return this.tokens[this.pos++];
5963
- }
5964
- /** Consume a punctuator token, erroring if the next token isn't it. */
5965
- expectPunct(punct) {
5966
- const tok = this.peek();
5967
- if (tok.type !== "punct" || tok.punct !== punct) throw new ExpressionParseError(`expected '${punct}'`, tok.pos);
5968
- this.pos += 1;
5969
- }
5970
- matchPunct(punct) {
5971
- const tok = this.peek();
5972
- if (tok.type === "punct" && tok.punct === punct) {
5973
- this.pos += 1;
5974
- return true;
5975
- }
5976
- return false;
5977
- }
5978
- countNode() {
5979
- this.nodeCount += 1;
5980
- if (this.nodeCount > 256) throw new ExpressionParseError("expression too complex", this.peek().pos);
5981
- }
5982
- parseTernary() {
5983
- const test = this.parseBinary(1);
5984
- if (this.matchPunct("?")) {
5985
- const consequent = this.parseTernary();
5986
- this.expectPunct(":");
5987
- const alternate = this.parseTernary();
5988
- this.countNode();
5989
- return {
5990
- kind: "conditional",
5991
- test,
5992
- consequent,
5993
- alternate
5994
- };
5995
- }
5996
- return test;
5997
- }
5998
- parseBinary(minPrec) {
5999
- let left = this.parseUnary();
6000
- for (;;) {
6001
- const tok = this.peek();
6002
- if (tok.type !== "punct") break;
6003
- const prec = BINARY_PRECEDENCE[tok.punct];
6004
- if (prec === void 0 || prec < minPrec) break;
6005
- const op = tok.punct;
6006
- this.pos += 1;
6007
- const right = this.parseBinary(prec + 1);
6008
- this.countNode();
6009
- if (isLogicalOp(op)) left = {
6010
- kind: "logical",
6011
- op,
6012
- left,
6013
- right
6014
- };
6015
- else if (isBinaryOp(op)) left = {
6016
- kind: "binary",
6017
- op,
6018
- left,
6019
- right
6020
- };
6021
- else throw new ExpressionParseError(`unexpected operator '${op}'`, tok.pos);
6022
- }
6023
- return left;
6024
- }
6025
- parseUnary() {
6026
- const tok = this.peek();
6027
- if (tok.type === "punct" && (tok.punct === "!" || tok.punct === "-")) {
6028
- const op = tok.punct;
6029
- this.pos += 1;
6030
- const operand = this.parseUnary();
6031
- this.countNode();
6032
- return {
6033
- kind: "unary",
6034
- op,
6035
- operand
6036
- };
6037
- }
6038
- return this.parsePrimary();
6039
- }
6040
- parsePrimary() {
6041
- const tok = this.next();
6042
- switch (tok.type) {
6043
- case "number":
6044
- this.countNode();
6045
- return {
6046
- kind: "literal",
6047
- value: tok.value
6048
- };
6049
- case "string":
6050
- this.countNode();
6051
- return {
6052
- kind: "literal",
6053
- value: tok.value
6054
- };
6055
- case "keyword":
6056
- this.countNode();
6057
- return {
6058
- kind: "literal",
6059
- value: tok.keyword === "null" ? null : tok.keyword === "true"
6060
- };
6061
- case "identifier": {
6062
- const nextTok = this.peek();
6063
- if (nextTok.type === "punct" && nextTok.punct === "(") return this.parseCall(tok.name, tok.pos);
6064
- this.identifiers.add(tok.name);
6065
- this.countNode();
6066
- return {
6067
- kind: "identifier",
6068
- name: tok.name
6069
- };
6070
- }
6071
- case "punct":
6072
- if (tok.punct === "(") {
6073
- const inner = this.parseTernary();
6074
- this.expectPunct(")");
6075
- return inner;
6076
- }
6077
- throw new ExpressionParseError(`unexpected token '${tok.punct}'`, tok.pos);
6078
- case "eof": throw new ExpressionParseError("unexpected end of expression", tok.pos);
6079
- }
6080
- }
6081
- parseCall(callee, pos) {
6082
- if (!EXPRESSION_BUILTIN_NAMES.has(callee)) throw new ExpressionParseError(`unknown function '${callee}'`, pos);
6083
- this.expectPunct("(");
6084
- const args = [];
6085
- if (!this.matchPunct(")")) for (;;) {
6086
- args.push(this.parseTernary());
6087
- if (args.length > 16) throw new ExpressionParseError(`too many arguments to '${callee}'`, pos);
6088
- if (this.matchPunct(",")) continue;
6089
- this.expectPunct(")");
6090
- break;
6091
- }
6092
- this.callees.add(callee);
6093
- this.countNode();
6094
- return {
6095
- kind: "call",
6096
- callee,
6097
- args
6098
- };
6099
- }
6100
- };
6101
- /** Tokenize + parse `source` into a validated `ParsedExpression`. Throws
6102
- * `ExpressionParseError` on any lexical or grammatical failure. */
6103
- function parseExpression(source) {
6104
- return new Parser(tokenize(source)).parse();
6105
- }
6106
- //#endregion
6107
- //#region src/expression/compile.ts
6108
- /**
6109
- * LRU compile cache for parsed expressions (spec §2.4 "parse once … LRU keyed
6110
- * by expr"). The cache stores BOTH successes and failures (negative caching),
6111
- * so a corrupt persisted string costs exactly one tokenize+parse total — not
6112
- * one per read on a hot resolve path.
6113
- *
6114
- * The cache is a module-level singleton: entries are pure, content-addressed
6115
- * ASTs keyed by the raw source string, so sharing one instance across all
6116
- * callers is safe and maximises hit rate.
6117
- */
6118
- var cache = /* @__PURE__ */ new Map();
6119
- function getCached(source) {
6120
- const hit = cache.get(source);
6121
- if (hit !== void 0) {
6122
- cache.delete(source);
6123
- cache.set(source, hit);
6124
- return hit;
6125
- }
6126
- let result;
6127
- try {
6128
- result = {
6129
- ok: true,
6130
- parsed: parseExpression(source)
6131
- };
6132
- } catch (err) {
6133
- result = {
6134
- ok: false,
6135
- error: err instanceof ExpressionParseError ? err.message : String(err)
6136
- };
6137
- }
6138
- cache.set(source, result);
6139
- if (cache.size > 256) {
6140
- const oldest = cache.keys().next().value;
6141
- if (oldest !== void 0) cache.delete(oldest);
6142
- }
6143
- return result;
6144
- }
6145
- /** Compile `source` to a `ParsedExpression`, throwing `ExpressionParseError`
6146
- * on failure. LRU/negative-cached. */
6147
- function compileExpression(source) {
6148
- const result = getCached(source);
6149
- if (result.ok) return result.parsed;
6150
- throw new ExpressionParseError(result.error);
6151
- }
6152
- /** Compile `source`, returning a discriminated result instead of throwing.
6153
- * Used by read paths that must degrade rather than raise. LRU/negative-cached. */
6154
- function compileExpressionSafe(source) {
6155
- return getCached(source);
6156
- }
6157
- //#endregion
6158
- //#region src/expression/evaluator.ts
6159
- /**
6160
- * Tree-walking evaluator for the safe expression mini-language.
6161
- *
6162
- * SECURITY (spec §4 rule 2/5):
6163
- * - The scope is an `Object.create(null)` copy of ONLY the caller's own
6164
- * enumerable binding entries, so `name in scope` is a pure own-key check and
6165
- * `constructor` / `__proto__` / `toString` are plain unknown identifiers.
6166
- * - Performs ZERO I/O and never touches `globalThis` / `Date` / `Math`
6167
- * directly — the only external calls are into the frozen builtin table.
6168
- * - The grammar has no loops/recursion/lambdas, so a walk is O(nodeCount) by
6169
- * construction; the step counter is defense-in-depth for a crafted max-size
6170
- * AST. Nothing blocks: there are no timers, awaits or unbounded loops.
6171
- */
6172
- var EMPTY_HOOKS = Object.freeze({});
6173
- /** Build a null-prototype scope from own-enumerable binding entries. Inherited
6174
- * keys of the input (e.g. from a `{__proto__: {...}}` payload) are NOT copied,
6175
- * so nothing smuggles in via the prototype chain. */
6176
- function createExpressionScope(bindings) {
6177
- const scope = Object.create(null);
6178
- for (const key of Object.keys(bindings)) if (Object.prototype.hasOwnProperty.call(bindings, key)) scope[key] = bindings[key];
6179
- return scope;
6180
- }
6181
- function isFiniteNumber(value) {
6182
- return typeof value === "number" && Number.isFinite(value);
6183
- }
6184
- /** JS truthiness of a primitive value. */
6185
- function truthy(value) {
6186
- return Boolean(value);
6187
- }
6188
- function requireFinite(value, context) {
6189
- if (!Number.isFinite(value)) throw new ExpressionEvalError(`${context} produced a non-finite result`);
6190
- return value;
6191
- }
6192
- function step(ctx) {
6193
- ctx.steps += 1;
6194
- if (ctx.steps > ctx.maxSteps) throw new ExpressionEvalError("expression evaluation step budget exceeded");
6195
- }
6196
- function evalNode(node, ctx) {
6197
- step(ctx);
6198
- switch (node.kind) {
6199
- case "literal": return node.value;
6200
- case "identifier":
6201
- if (!(node.name in ctx.scope)) throw new ExpressionEvalError(`unknown identifier: ${node.name}`);
6202
- return ctx.scope[node.name];
6203
- case "unary": return evalUnary(node.op, evalNode(node.operand, ctx));
6204
- case "binary": return evalBinary(node.op, evalNode(node.left, ctx), evalNode(node.right, ctx));
6205
- case "logical": {
6206
- const left = evalNode(node.left, ctx);
6207
- if (node.op === "&&") return truthy(left) ? evalNode(node.right, ctx) : left;
6208
- return truthy(left) ? left : evalNode(node.right, ctx);
6209
- }
6210
- case "conditional": return truthy(evalNode(node.test, ctx)) ? evalNode(node.consequent, ctx) : evalNode(node.alternate, ctx);
6211
- case "call": return evalCall(node.callee, node.args.map((a) => evalNode(a, ctx)), ctx.hooks);
6212
- }
6213
- }
6214
- function evalUnary(op, operand) {
6215
- if (op === "!") return !truthy(operand);
6216
- if (!isFiniteNumber(operand)) throw new ExpressionEvalError("unary \"-\" requires a finite number");
6217
- return requireFinite(-operand, "unary \"-\"");
6218
- }
6219
- function evalBinary(op, left, right) {
6220
- switch (op) {
6221
- case "==": return left === right;
6222
- case "!=": return left !== right;
6223
- case "+":
6224
- if (typeof left === "string" && typeof right === "string") return left + right;
6225
- if (isFiniteNumber(left) && isFiniteNumber(right)) return requireFinite(left + right, "\"+\"");
6226
- throw new ExpressionEvalError("\"+\" requires two numbers or two strings");
6227
- case "-":
6228
- case "*":
6229
- case "/":
6230
- case "%":
6231
- if (!isFiniteNumber(left) || !isFiniteNumber(right)) throw new ExpressionEvalError(`"${op}" requires two finite numbers`);
6232
- return requireFinite(op === "-" ? left - right : op === "*" ? left * right : op === "/" ? left / right : left % right, `"${op}"`);
6233
- case "<":
6234
- case "<=":
6235
- case ">":
6236
- case ">=":
6237
- if (isFiniteNumber(left) && isFiniteNumber(right)) return op === "<" ? left < right : op === "<=" ? left <= right : op === ">" ? left > right : left >= right;
6238
- if (typeof left === "string" && typeof right === "string") return op === "<" ? left < right : op === "<=" ? left <= right : op === ">" ? left > right : left >= right;
6239
- throw new ExpressionEvalError(`"${op}" requires two numbers or two strings`);
6240
- }
6241
- }
6242
- function evalCall(callee, args, hooks) {
6243
- if (!Object.prototype.hasOwnProperty.call(EXPRESSION_BUILTINS, callee)) throw new ExpressionEvalError(`unknown function: ${callee}`);
6244
- const builtin = EXPRESSION_BUILTINS[callee];
6245
- if (args.length < builtin.minArgs || args.length > builtin.maxArgs) throw new ExpressionEvalError(`${callee}: wrong number of arguments (${args.length})`);
6246
- return builtin.apply(args, hooks);
6247
- }
6248
- /** Evaluate an AST node against a scope. Throws `ExpressionEvalError` on any
6249
- * runtime failure (unknown identifier, type mismatch, non-finite result,
6250
- * step-budget exhaustion). */
6251
- function evaluateAst(node, scope, opts) {
6252
- return evalNode(node, {
6253
- scope,
6254
- hooks: opts?.hooks ?? EMPTY_HOOKS,
6255
- maxSteps: opts?.maxSteps ?? 4096,
6256
- steps: 0
6257
- });
6258
- }
6259
- //#endregion
6260
- //#region src/expression/expression-source.ts
6261
- /** The `now` epoch-ms binding is auto-injected into every evaluation and is a
6262
- * reserved binding name (authors may not rebind it). */
6263
- var EXPRESSION_INJECTED_NOW = "now";
6264
- /**
6265
- * Coerce an untrusted `getByPath` / mirror read to an `ExpressionValue`.
6266
- * Non-primitive values (objects, arrays, `undefined`, functions, bigint,
6267
- * symbol) and non-finite numbers become `undefined` so the caller can apply
6268
- * its binding-miss policy (→ `null`). `null` itself is a valid value.
6269
- */
6270
- function toExpressionValue(raw) {
6271
- if (raw === null) return null;
6272
- if (typeof raw === "string") return raw;
6273
- if (typeof raw === "boolean") return raw;
6274
- if (typeof raw === "number") return Number.isFinite(raw) ? raw : void 0;
6275
- }
6276
- /**
6277
- * Author-time validation. Returns `null` when the source is valid, else a
6278
- * human-readable error message. Checks: the expression compiles; binding count
6279
- * is within `MAX_EXPRESSION_BINDINGS`; every binding name is a legal identifier,
6280
- * is not reserved (`now`/keywords) and does not shadow a builtin; and every
6281
- * FREE identifier of the AST is covered by a binding or the injected `now`.
6282
- */
6283
- function validateExpressionSource(src) {
6284
- const names = Object.keys(src.bindings);
6285
- if (names.length > 32) return `too many bindings (${names.length} > 32)`;
6286
- for (const name of names) {
6287
- if (!EXPRESSION_IDENTIFIER_RE.test(name)) return `invalid binding name '${name}'`;
6288
- if (RESERVED_BINDING_NAMES.has(name)) return `binding name '${name}' is reserved`;
6289
- if (EXPRESSION_BUILTIN_NAMES.has(name)) return `binding name '${name}' shadows a builtin function`;
6290
- }
6291
- const compiled = compileExpressionSafe(src.expr);
6292
- if (!compiled.ok) return compiled.error;
6293
- const bound = new Set(names);
6294
- for (const id of compiled.parsed.identifiers) {
6295
- if (id === "now") continue;
6296
- if (!bound.has(id)) return `expression references unbound identifier '${id}'`;
6297
- }
6298
- return null;
6299
- }
6300
- /**
6301
- * Shared read-path evaluation. Builds a null-proto scope from `bindingValues`
6302
- * plus the injected `now` (supplied by the caller for determinism and
6303
- * testability), compiles via the LRU, and evaluates. Any failure (parse or
6304
- * eval) returns `{ ok: false }` — the caller treats that as "skip this
6305
- * derivation", never as a throw that takes the pass down.
6306
- */
6307
- function evaluateExpressionSource(expr, bindingValues, now, opts) {
6308
- const compiled = compileExpressionSafe(expr);
6309
- if (!compiled.ok) return {
6310
- ok: false,
6311
- error: compiled.error
6312
- };
6313
- const scope = createExpressionScope({
6314
- ...bindingValues,
6315
- ["now"]: now
6316
- });
6317
- const hooks = {
6318
- ...opts?.hooks,
6319
- now
6320
- };
6321
- try {
6322
- return {
6323
- ok: true,
6324
- value: evaluateAst(compiled.parsed.ast, scope, {
6325
- ...opts,
6326
- hooks
6327
- })
6328
- };
6329
- } catch (err) {
6330
- return {
6331
- ok: false,
6332
- error: err instanceof ExpressionEvalError ? err.message : String(err)
6333
- };
6334
- }
6335
- }
6336
- //#endregion
6337
5534
  //#region src/expression/binding-source.ts
6338
5535
  /**
6339
5536
  * What an expression's named bindings READ from.
@@ -6394,10 +5591,10 @@ var ExpressionBindingSourceSchema = zod.z.union([
6394
5591
  * Compiles are LRU-cached, so repeated validation of the same string is a hit.
6395
5592
  */
6396
5593
  var ExpressionSourceSchema = zod.z.object({
6397
- expr: zod.z.string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
6398
- bindings: zod.z.record(zod.z.string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
5594
+ expr: zod.z.string().min(1).max(require_composition.MAX_EXPRESSION_SOURCE_LENGTH),
5595
+ bindings: zod.z.record(zod.z.string().regex(require_composition.EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
6399
5596
  }).superRefine((src, ctx) => {
6400
- const err = validateExpressionSource(src);
5597
+ const err = require_composition.validateExpressionSource(src);
6401
5598
  if (err !== null) ctx.addIssue({
6402
5599
  code: "custom",
6403
5600
  message: err,
@@ -6478,10 +5675,10 @@ var AutomationConditionLeafSchema = zod.z.object({
6478
5675
  */
6479
5676
  var AutomationConditionExpressionSchema = zod.z.object({
6480
5677
  kind: zod.z.literal("expression"),
6481
- expr: zod.z.string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
6482
- bindings: zod.z.record(zod.z.string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
5678
+ expr: zod.z.string().min(1).max(require_composition.MAX_EXPRESSION_SOURCE_LENGTH),
5679
+ bindings: zod.z.record(zod.z.string().regex(require_composition.EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
6483
5680
  }).superRefine((src, ctx) => {
6484
- const err = validateExpressionSource(src);
5681
+ const err = require_composition.validateExpressionSource(src);
6485
5682
  if (err !== null) ctx.addIssue({
6486
5683
  code: "custom",
6487
5684
  message: err,
@@ -7555,7 +6752,7 @@ var audioAnalysisCapability = {
7555
6752
  mode: "singleton",
7556
6753
  kind: "wrapper",
7557
6754
  defaultActive: true,
7558
- deviceTypes: [require_sleep.DeviceType.Camera],
6755
+ deviceTypes: [require_err_msg.DeviceType.Camera],
7559
6756
  exposesDeviceSettings: true,
7560
6757
  methods: {
7561
6758
  /**
@@ -8702,7 +7899,7 @@ var cameraPipelineConfigCapability = {
8702
7899
  mode: "singleton",
8703
7900
  kind: "wrapper",
8704
7901
  defaultActive: true,
8705
- deviceTypes: [require_sleep.DeviceType.Camera],
7902
+ deviceTypes: [require_err_msg.DeviceType.Camera],
8706
7903
  exposesDeviceSettings: true,
8707
7904
  methods: {}
8708
7905
  };
@@ -9812,7 +9009,7 @@ var cameraStreamsCapability = {
9812
9009
  mode: "singleton",
9813
9010
  kind: "wrapper",
9814
9011
  defaultActive: true,
9815
- deviceTypes: [require_sleep.DeviceType.Camera],
9012
+ deviceTypes: [require_err_msg.DeviceType.Camera],
9816
9013
  methods: {
9817
9014
  getCameraStreams: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.array(require_sleep.CameraStreamSchema).readonly()),
9818
9015
  getBrokerStreams: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.array(require_sleep.ProfileSlotSchema).readonly()),
@@ -10136,177 +9333,6 @@ function kebabToCamel(s) {
10136
9333
  return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
10137
9334
  }
10138
9335
  //#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
9336
  //#region src/composition/composition-report.ts
10311
9337
  /**
10312
9338
  * What the validator says about a composition and what the composer says about
@@ -10329,7 +9355,12 @@ var CompositionProblemCodeSchema = zod.z.enum([
10329
9355
  "source-field-unknown",
10330
9356
  "expression-invalid",
10331
9357
  "cycle",
10332
- "duplicate-target"
9358
+ "duplicate-target",
9359
+ "not-item-array",
9360
+ "item-field-reserved",
9361
+ "target-missing",
9362
+ "target-unreadable",
9363
+ "not-replaceable"
10333
9364
  ]);
10334
9365
  /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
10335
9366
  var CompositionProblemSchema = zod.z.object({
@@ -10343,8 +9374,18 @@ var CompositionFieldKindSchema = zod.z.enum([
10343
9374
  "boolean",
10344
9375
  "enum"
10345
9376
  ]);
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"]);
9377
+ /**
9378
+ * How a field says "unavailable" (D393, D663): `null` when its schema allows it;
9379
+ * `unknown` — the enum's own word — for an ADDED field that has one; `release`
9380
+ * for a REPLACED field of an existing device, whose claim is released so the
9381
+ * native provider's reading shows again; else the (composed) device goes offline.
9382
+ */
9383
+ var CompositionUnavailableModeSchema = zod.z.enum([
9384
+ "null",
9385
+ "offline",
9386
+ "unknown",
9387
+ "release"
9388
+ ]);
10348
9389
  /**
10349
9390
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
10350
9391
  * field of its cap changes value · `constant` reads nothing, computed once.
@@ -10354,9 +9395,17 @@ var CompositionFieldRoleSchema = zod.z.enum([
10354
9395
  "clock",
10355
9396
  "constant"
10356
9397
  ]);
9398
+ /** The item a planned field belongs to; its `path` is then the dotted path INSIDE the item. */
9399
+ var CompositionFieldItemSchema = zod.z.object({
9400
+ arrayPath: zod.z.string(),
9401
+ key: zod.z.string(),
9402
+ label: zod.z.string()
9403
+ });
10357
9404
  var CompositionFieldPlanSchema = zod.z.object({
10358
9405
  cap: zod.z.string(),
9406
+ /** Top-level field, or — when `item` is set — the dotted path inside that item. */
10359
9407
  path: zod.z.string(),
9408
+ item: CompositionFieldItemSchema.optional(),
10360
9409
  /** Index into `composition.features` this field's capability came from —
10361
9410
  * lets a source/cycle problem name its path as `features[N].fields.<path>`
10362
9411
  * (the same convention `planComposition`'s own problems already use). */
@@ -10366,19 +9415,45 @@ var CompositionFieldPlanSchema = zod.z.object({
10366
9415
  required: zod.z.boolean(),
10367
9416
  unavailable: CompositionUnavailableModeSchema,
10368
9417
  role: CompositionFieldRoleSchema,
10369
- source: CompositionFieldSourceSchema,
10370
- reads: zod.z.array(CompositionFieldReadSchema)
9418
+ source: require_composition.CompositionFieldSourceSchema,
9419
+ reads: zod.z.array(require_composition.CompositionFieldReadSchema)
9420
+ });
9421
+ /** The key every record map, log line and graph node uses for a planned field. Two items' `status` are two keys. */
9422
+ function compositionFieldKey(plan) {
9423
+ const path = plan.item === void 0 ? plan.path : require_composition.compositionItemFieldPath(plan.item.arrayPath, plan.item.key, plan.path);
9424
+ return `${plan.cap}.${path}`;
9425
+ }
9426
+ /**
9427
+ * What a feature does to its target (D663): `new` builds a capability on a new
9428
+ * device; on an existing device it `add`s a capability the device lacks, or
9429
+ * `replace`s named fields of one it has natively.
9430
+ */
9431
+ var CompositionFeatureModeSchema = zod.z.enum([
9432
+ "new",
9433
+ "add",
9434
+ "replace"
9435
+ ]);
9436
+ var CompositionFeaturePlanSchema = zod.z.object({
9437
+ featureIndex: zod.z.number().int().nonnegative(),
9438
+ cap: zod.z.string(),
9439
+ mode: CompositionFeatureModeSchema
10371
9440
  });
10372
9441
  var CompositionValidationSchema = zod.z.object({
10373
9442
  ok: zod.z.boolean(),
10374
9443
  problems: zod.z.array(CompositionProblemSchema),
10375
- fields: zod.z.array(CompositionFieldPlanSchema)
9444
+ fields: zod.z.array(CompositionFieldPlanSchema),
9445
+ features: zod.z.array(CompositionFeaturePlanSchema)
10376
9446
  });
10377
- /** Mirrors `CoreBlockStatus`, pinned by `core-blocks-source.spec.ts` (Task 11). */
9447
+ /**
9448
+ * Mirrors `CoreBlockStatus`, pinned by `core-blocks-source.spec.ts`. `degraded`:
9449
+ * running, but a replaced field is released to the native provider while its
9450
+ * source is unavailable (ruling S1b, D663) — the reason names each such field.
9451
+ */
10378
9452
  var CompositionRunStatusSchema = zod.z.enum([
10379
9453
  "stopped",
10380
9454
  "starting",
10381
9455
  "running",
9456
+ "degraded",
10382
9457
  "failed"
10383
9458
  ]);
10384
9459
  var CompositionFieldStateKindSchema = zod.z.enum([
@@ -10389,6 +9464,8 @@ var CompositionFieldStateKindSchema = zod.z.enum([
10389
9464
  var CompositionFieldStateSchema = zod.z.object({
10390
9465
  cap: zod.z.string(),
10391
9466
  path: zod.z.string(),
9467
+ /** Same as the plan's: two items' `status` are told apart by it. */
9468
+ item: CompositionFieldItemSchema.optional(),
10392
9469
  state: CompositionFieldStateKindSchema,
10393
9470
  /** `null` whenever `state === 'unavailable'`; never a plausible stand-in (D393). */
10394
9471
  value: zod.z.union([
@@ -10470,11 +9547,14 @@ function coreBlockIdFromAddonId(addonId) {
10470
9547
  * agent is the reason placement is not fixed to the hub. */
10471
9548
  var CoreBlockPlacementSchema = zod.z.union([zod.z.literal("hub"), zod.z.string().min(1)]);
10472
9549
  /** What a block's process is doing. Mirrors the addon runner's own lifecycle so
10473
- * a failing block reads the same way a failing addon does. */
9550
+ * a failing block reads the same way a failing addon does. `degraded` is a
9551
+ * composition on an existing device with a field handed back to the native
9552
+ * provider while its source is unavailable (S1b, D663); the error names it. */
10474
9553
  var CoreBlockStatusSchema = zod.z.enum([
10475
9554
  "stopped",
10476
9555
  "starting",
10477
9556
  "running",
9557
+ "degraded",
10478
9558
  "failed"
10479
9559
  ]);
10480
9560
  /** Client-authored fields. */
@@ -10507,14 +9587,21 @@ var CoreBlockSourceSchema = zod.z.discriminatedUnion("kind", [
10507
9587
  }),
10508
9588
  zod.z.object({
10509
9589
  kind: zod.z.literal("composition"),
10510
- composition: CompositionSchema
9590
+ composition: require_composition.CompositionSchema
10511
9591
  })
10512
9592
  ]);
10513
- /** The client-authored half of a composition block. No `code`, no placement (hub-only; Phase 7). */
9593
+ /**
9594
+ * The client-authored half of a composition block. No `code`, no placement (hub-only; Phase 7).
9595
+ *
9596
+ * `name`: REQUIRED for a `new` target — the block's name IS the device's name
9597
+ * (D659). ABSENT for an `existing` target — a customization has no name of its
9598
+ * own; the server derives one (`customizationBlockName`, D663) and refuses a
9599
+ * client-sent name by name.
9600
+ */
10514
9601
  var CompositionBlockInputSchema = zod.z.object({
10515
- name: zod.z.string().min(1).max(120),
9602
+ name: zod.z.string().min(1).max(120).optional(),
10516
9603
  enabled: zod.z.boolean(),
10517
- composition: CompositionSchema
9604
+ composition: require_composition.CompositionSchema
10518
9605
  });
10519
9606
  /** A stored block. */
10520
9607
  var CoreBlockSchema = CoreBlockInputSchema.extend({
@@ -10571,6 +9658,11 @@ var CompositionGetReadSchema = zod.z.discriminatedUnion("state", [zod.z.object({
10571
9658
  state: zod.z.literal("loaded"),
10572
9659
  view: CompositionViewSchema.nullable()
10573
9660
  }), CompositionsNotLoadedSchema]);
9661
+ /** The customization of one existing device, found by its TARGET, or `not-loaded` (D315). */
9662
+ var CompositionCustomizationReadSchema = zod.z.discriminatedUnion("state", [zod.z.object({
9663
+ state: zod.z.literal("loaded"),
9664
+ view: CompositionViewSchema.nullable()
9665
+ }), CompositionsNotLoadedSchema]);
10574
9666
  /** What a compile attempt produced. */
10575
9667
  var CoreBlockCompileResultSchema = zod.z.object({
10576
9668
  ok: zod.z.boolean(),
@@ -10681,11 +9773,19 @@ var coreBlocksCapability = {
10681
9773
  getComposition: require_sleep.method(zod.z.object({ blockId: zod.z.string() }), CompositionGetReadSchema, { auth: "admin" }),
10682
9774
  listCompositions: require_sleep.method(zod.z.object({}), CompositionListReadSchema, { auth: "admin" }),
10683
9775
  /**
9776
+ * The composition that customizes an existing device (D663), found by its
9777
+ * TARGET — never by the block's derived name. One per device at most.
9778
+ */
9779
+ getCustomization: require_sleep.method(zod.z.object({
9780
+ addonId: zod.z.string().min(1),
9781
+ stableId: zod.z.string().min(1)
9782
+ }), CompositionCustomizationReadSchema, { auth: "admin" }),
9783
+ /**
10684
9784
  * Validate without saving — the same verdict the save reaches. A mutation
10685
9785
  * for its body size; it writes nothing, so its access is `view`.
10686
9786
  */
10687
9787
  validateComposition: require_sleep.method(zod.z.object({
10688
- composition: CompositionSchema,
9788
+ composition: require_composition.CompositionSchema,
10689
9789
  blockId: zod.z.string().optional()
10690
9790
  }), CompositionValidationSchema, {
10691
9791
  kind: "mutation",
@@ -10704,13 +9804,13 @@ var coreBlocksCapability = {
10704
9804
  * and reads their state for the API. Nothing else talks to it.
10705
9805
  */
10706
9806
  /** The composer's runner id, `resolveRunnerId(decl, 'composer')`; pinned by `composer-manifest.spec.ts`. */
10707
- var COMPOSER_RUNNER_ID = COMPOSER_ADDON_ID;
9807
+ var COMPOSER_RUNNER_ID = require_composition.COMPOSER_ADDON_ID;
10708
9808
  var ComposerBlockSchema = zod.z.object({
10709
9809
  id: zod.z.string().min(1),
10710
9810
  name: zod.z.string().min(1).max(120),
10711
9811
  enabled: zod.z.boolean(),
10712
9812
  updatedAt: zod.z.number(),
10713
- composition: CompositionSchema
9813
+ composition: require_composition.CompositionSchema
10714
9814
  });
10715
9815
  /**
10716
9816
  * A stored composition row that no longer PARSES — a schema a release
@@ -11660,7 +10760,7 @@ var detectionPipelineCapability = {
11660
10760
  mode: "singleton",
11661
10761
  kind: "wrapper",
11662
10762
  defaultActive: true,
11663
- deviceTypes: [require_sleep.DeviceType.Camera],
10763
+ deviceTypes: [require_err_msg.DeviceType.Camera],
11664
10764
  exposesDeviceSettings: true,
11665
10765
  methods: {}
11666
10766
  };
@@ -12093,7 +11193,7 @@ var DiscoveredChildDeviceSchema = zod.z.lazy(() => zod.z.object({
12093
11193
  /** Friendly name as reported by the source (Reolink camera name, ONVIF profile, ...). */
12094
11194
  name: zod.z.string(),
12095
11195
  /** DeviceType the child should be created with on adopt. */
12096
- type: zod.z.enum(require_sleep.DeviceType),
11196
+ type: zod.z.enum(require_err_msg.DeviceType),
12097
11197
  status: DiscoveredChildStatusSchema,
12098
11198
  /** Free-form integration-specific metadata surfaced in the panel. */
12099
11199
  metadata: zod.z.object({
@@ -12149,7 +11249,7 @@ var deviceDiscoveryCapability = {
12149
11249
  scope: "device",
12150
11250
  deviceNative: true,
12151
11251
  mode: "singleton",
12152
- deviceTypes: [require_sleep.DeviceType.Hub],
11252
+ deviceTypes: [require_err_msg.DeviceType.Hub],
12153
11253
  status: {
12154
11254
  schema: DeviceDiscoveryStatusSchema,
12155
11255
  kind: "poll"
@@ -12751,7 +11851,7 @@ var DiscoveredDeviceSchema = zod.z.object({
12751
11851
  */
12752
11852
  var DiscoveryCandidateSchema = zod.z.object({
12753
11853
  stableId: zod.z.string(),
12754
- type: zod.z.enum(require_sleep.DeviceType),
11854
+ type: zod.z.enum(require_err_msg.DeviceType),
12755
11855
  suggestedName: zod.z.string(),
12756
11856
  prefilledConfig: zod.z.record(zod.z.string(), zod.z.unknown()),
12757
11857
  /**
@@ -12888,7 +11988,7 @@ var deviceProviderCapability = {
12888
11988
  * provider does not support manual creation. Lets the Add-Device dialog
12889
11989
  * pick the right type instead of assuming Camera.
12890
11990
  */
12891
- getManualCreationType: require_sleep.method(zod.z.object({}), zod.z.object({ deviceType: zod.z.enum(require_sleep.DeviceType).nullable() })),
11991
+ getManualCreationType: require_sleep.method(zod.z.object({}), zod.z.object({ deviceType: zod.z.enum(require_err_msg.DeviceType).nullable() })),
12892
11992
  adoptDiscoveredDevice: require_sleep.method(zod.z.object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
12893
11993
  kind: "mutation",
12894
11994
  auth: "admin"
@@ -12900,9 +12000,9 @@ var deviceProviderCapability = {
12900
12000
  * devices of that type. The output is a `ConfigUISchema` — the
12901
12001
  * router type-asserts it at the boundary.
12902
12002
  */
12903
- getChildCreationSchema: require_sleep.method(zod.z.object({ type: zod.z.enum(require_sleep.DeviceType) }), CreationSchemaOutputSchema),
12003
+ getChildCreationSchema: require_sleep.method(zod.z.object({ type: zod.z.enum(require_err_msg.DeviceType) }), CreationSchemaOutputSchema),
12904
12004
  createDevice: require_sleep.method(zod.z.object({
12905
- type: zod.z.enum(require_sleep.DeviceType),
12005
+ type: zod.z.enum(require_err_msg.DeviceType),
12906
12006
  config: zod.z.record(zod.z.string(), zod.z.unknown())
12907
12007
  }), DeviceSummarySchema, {
12908
12008
  kind: "mutation",
@@ -12921,7 +12021,7 @@ var deviceProviderCapability = {
12921
12021
  * for backwards compatibility; providers free to ignore it.
12922
12022
  */
12923
12023
  testCreationField: require_sleep.method(zod.z.object({
12924
- type: zod.z.enum(require_sleep.DeviceType),
12024
+ type: zod.z.enum(require_err_msg.DeviceType),
12925
12025
  key: zod.z.string(),
12926
12026
  value: zod.z.unknown(),
12927
12027
  formValues: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
@@ -13001,7 +12101,7 @@ var DeviceInfoSchema = zod.z.object({
13001
12101
  id: zod.z.number(),
13002
12102
  stableId: zod.z.string(),
13003
12103
  addonId: zod.z.string(),
13004
- type: zod.z.enum(require_sleep.DeviceType),
12104
+ type: zod.z.enum(require_err_msg.DeviceType),
13005
12105
  name: zod.z.string(),
13006
12106
  /** Operator-organisational location label. `null` when unset. */
13007
12107
  location: zod.z.string().nullable(),
@@ -13094,7 +12194,7 @@ var LINKED_DEVICES_SLOT = {
13094
12194
  label: "Linked devices",
13095
12195
  description: "Companion devices of this camera: their controls appear with it, and the ones you opt in produce tracked events on it.",
13096
12196
  shape: "association",
13097
- hostDeviceTypes: [require_sleep.DeviceType.Camera],
12197
+ hostDeviceTypes: [require_err_msg.DeviceType.Camera],
13098
12198
  sourceCaps: "any",
13099
12199
  cardinality: "many",
13100
12200
  value: LinkedDevicesSlotValueSchema
@@ -13336,7 +12436,7 @@ var deviceManagerCapability = {
13336
12436
  * placeholder `'generic'`. */
13337
12437
  setType: require_sleep.method(zod.z.object({
13338
12438
  deviceId: zod.z.number(),
13339
- type: zod.z.enum(require_sleep.DeviceType)
12439
+ type: zod.z.enum(require_err_msg.DeviceType)
13340
12440
  }), zod.z.void(), {
13341
12441
  kind: "mutation",
13342
12442
  auth: "admin"
@@ -13464,7 +12564,7 @@ var deviceManagerCapability = {
13464
12564
  deviceId: zod.z.number(),
13465
12565
  name: zod.z.string().optional(),
13466
12566
  location: zod.z.string().nullable().optional(),
13467
- type: zod.z.enum(require_sleep.DeviceType).optional(),
12567
+ type: zod.z.enum(require_err_msg.DeviceType).optional(),
13468
12568
  integrationId: zod.z.string().optional(),
13469
12569
  linkDeviceId: zod.z.number().nullable().optional(),
13470
12570
  role: zod.z.string().nullable().optional()
@@ -14015,12 +13115,12 @@ var deviceManagerCapability = {
14015
13115
  */
14016
13116
  getCreationSchema: require_sleep.method(zod.z.object({
14017
13117
  addonId: zod.z.string(),
14018
- type: zod.z.enum(require_sleep.DeviceType)
13118
+ type: zod.z.enum(require_err_msg.DeviceType)
14019
13119
  }), zod.z.unknown().nullable()),
14020
13120
  /** Create a new device via the device-provider capability. */
14021
13121
  createDevice: require_sleep.method(zod.z.object({
14022
13122
  addonId: zod.z.string(),
14023
- type: zod.z.enum(require_sleep.DeviceType),
13123
+ type: zod.z.enum(require_err_msg.DeviceType),
14024
13124
  config: zod.z.record(zod.z.string(), zod.z.unknown()),
14025
13125
  /** Owning integration id, stamped onto the new device's meta by the
14026
13126
  * device-manager forwarder so `removeByIntegration` can cascade it.
@@ -14040,7 +13140,7 @@ var deviceManagerCapability = {
14040
13140
  */
14041
13141
  testCreationField: require_sleep.method(zod.z.object({
14042
13142
  addonId: zod.z.string(),
14043
- type: zod.z.enum(require_sleep.DeviceType),
13143
+ type: zod.z.enum(require_err_msg.DeviceType),
14044
13144
  key: zod.z.string(),
14045
13145
  value: zod.z.unknown(),
14046
13146
  formValues: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
@@ -14135,7 +13235,7 @@ var deviceManagerCapability = {
14135
13235
  }),
14136
13236
  /** The device type a provider creates via manual add (Camera/Container/Hub),
14137
13237
  * so the Add-Device dialog uses the right type instead of assuming Camera. */
14138
- providerCreationType: require_sleep.method(zod.z.object({ addonId: zod.z.string() }), zod.z.object({ deviceType: zod.z.enum(require_sleep.DeviceType).nullable() }), { auth: "admin" }),
13238
+ providerCreationType: require_sleep.method(zod.z.object({ addonId: zod.z.string() }), zod.z.object({ deviceType: zod.z.enum(require_err_msg.DeviceType).nullable() }), { auth: "admin" }),
14139
13239
  /** A provider's extra per-scan param form (ConfigUISchema) for the
14140
13240
  * per-integration scan, or null when it takes none. */
14141
13241
  providerDiscoveryParamsSchema: require_sleep.method(zod.z.object({ addonId: zod.z.string() }), zod.z.unknown(), { auth: "admin" }),
@@ -14201,6 +13301,52 @@ var deviceManagerCapability = {
14201
13301
  //#endregion
14202
13302
  //#region src/capabilities/device-state.cap.ts
14203
13303
  /**
13304
+ * Per-field ownership (D663). A composition may CLAIM fields of a native
13305
+ * cap's runtime-state slice on an existing device: `replace` overlays the
13306
+ * claimed fields on the native slice, `add` builds a slice the device does
13307
+ * not natively have. Declared here — once — so the hub mirror (Task 5) and
13308
+ * the claim methods (Task 6) share one union.
13309
+ */
13310
+ var FieldClaimModeSchema = zod.z.enum(["replace", "add"]);
13311
+ /** What a claim, patch or release answers. A refusal always names its reason. */
13312
+ var ClaimOutcomeSchema = zod.z.discriminatedUnion("ok", [zod.z.object({
13313
+ ok: zod.z.literal(true),
13314
+ changed: zod.z.boolean()
13315
+ }), zod.z.object({
13316
+ ok: zod.z.literal(false),
13317
+ code: zod.z.enum([
13318
+ "owned-by-other",
13319
+ "not-claimed",
13320
+ "field-not-claimed",
13321
+ "invalid-slice",
13322
+ "unknown-device",
13323
+ "row-unreadable",
13324
+ "migration-in-flight"
13325
+ ]),
13326
+ message: zod.z.string()
13327
+ })]);
13328
+ /** One claim as `listClaims` reports it. `fields` is the PLANNED set, explicit (ruling S5). */
13329
+ var FieldClaimSchema = zod.z.object({
13330
+ deviceId: zod.z.number(),
13331
+ capName: zod.z.string(),
13332
+ owner: zod.z.string().min(1),
13333
+ mode: FieldClaimModeSchema,
13334
+ fields: zod.z.array(zod.z.string().min(1)).min(1)
13335
+ });
13336
+ /**
13337
+ * What `listClaims` answers. The claims INDEX is a listing projection loaded
13338
+ * from the settings store at boot; until a load has succeeded it is
13339
+ * `not-loaded`, never `[]` — an unanswered question must not look like an
13340
+ * empty answer (D315).
13341
+ */
13342
+ var ClaimsListingSchema = zod.z.discriminatedUnion("state", [zod.z.object({
13343
+ state: zod.z.literal("loaded"),
13344
+ claims: zod.z.array(FieldClaimSchema)
13345
+ }), zod.z.object({
13346
+ state: zod.z.literal("not-loaded"),
13347
+ reason: zod.z.string()
13348
+ })]);
13349
+ /**
14204
13350
  * `deviceState` — generic, cap-agnostic surface for cross-process
14205
13351
  * access to per-device runtime state. Read pendant for every cap's
14206
13352
  * runtime-state slice + the canonical write entrypoint. Mirrors the
@@ -14231,8 +13377,12 @@ var deviceStateCapability = {
14231
13377
  /** Full state snapshot for one device. Empty object if no
14232
13378
  * cap-keyed slice has been written yet. */
14233
13379
  getSnapshot: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.unknown()))),
14234
- /** Single cap slice. Returns null when the slice hasn't been
14235
- * written or the device isn't registered. */
13380
+ /**
13381
+ * Single cap slice — the EFFECTIVE one: the merge every consumer sees, null
13382
+ * when the slice hasn't been written, the device isn't registered, or a
13383
+ * claim HOLDS the cap (D663). Never the native shadow — that is
13384
+ * `getNativeCapSlice`, admin-only.
13385
+ */
14236
13386
  getCapSlice: require_sleep.method(zod.z.object({
14237
13387
  deviceId: zod.z.number(),
14238
13388
  capName: zod.z.string()
@@ -14277,7 +13427,66 @@ var deviceStateCapability = {
14277
13427
  deviceId: zod.z.number(),
14278
13428
  capName: zod.z.string(),
14279
13429
  slice: zod.z.record(zod.z.string(), zod.z.unknown())
14280
- }), zod.z.void(), { kind: "mutation" })
13430
+ }), zod.z.void(), { kind: "mutation" }),
13431
+ /**
13432
+ * Claim `fields` of `capName` on a device for `owner`. The PLANNED field set
13433
+ * is explicit (ruling S5): `values` may be partial and MUST be a subset of
13434
+ * `fields` (else `field-not-claimed`); a claimed field with no value is HELD,
13435
+ * never answered from the native. A re-claim by the same owner REPLACES
13436
+ * `fields` and MERGES `values` — a field dropped from `fields` loses its
13437
+ * value. Another owner's claim → `owned-by-other`.
13438
+ */
13439
+ claimFields: require_sleep.method(zod.z.object({
13440
+ deviceId: zod.z.number(),
13441
+ capName: zod.z.string(),
13442
+ owner: zod.z.string().min(1),
13443
+ mode: FieldClaimModeSchema,
13444
+ fields: zod.z.array(zod.z.string().min(1)).min(1),
13445
+ values: zod.z.record(zod.z.string(), zod.z.unknown())
13446
+ }), ClaimOutcomeSchema, {
13447
+ kind: "mutation",
13448
+ auth: "admin"
13449
+ }),
13450
+ /**
13451
+ * Write owned values. Every key of `values` must be in the claim's `fields`
13452
+ * (`field-not-claimed`); no claim → `not-claimed`; another owner's →
13453
+ * `owned-by-other`. Partial patches merge (S5); debounced like a native slice.
13454
+ */
13455
+ patchOwnedFields: require_sleep.method(zod.z.object({
13456
+ deviceId: zod.z.number(),
13457
+ capName: zod.z.string(),
13458
+ owner: zod.z.string().min(1),
13459
+ values: zod.z.record(zod.z.string(), zod.z.unknown())
13460
+ }), ClaimOutcomeSchema, {
13461
+ kind: "mutation",
13462
+ auth: "admin"
13463
+ }),
13464
+ /**
13465
+ * Hand the cap back. A `replace` re-emits the native slice exactly as last
13466
+ * written; an `add` leaves the mirror. `not-claimed` also removes a stale
13467
+ * index entry — the index self-heals through this method.
13468
+ */
13469
+ releaseClaim: require_sleep.method(zod.z.object({
13470
+ deviceId: zod.z.number(),
13471
+ capName: zod.z.string(),
13472
+ owner: zod.z.string().min(1)
13473
+ }), ClaimOutcomeSchema, {
13474
+ kind: "mutation",
13475
+ auth: "admin"
13476
+ }),
13477
+ /** Every claim the index lists, optionally those whose owner starts with `ownerPrefix`. */
13478
+ listClaims: require_sleep.method(zod.z.object({ ownerPrefix: zod.z.string().optional() }), ClaimsListingSchema, { auth: "admin" }),
13479
+ /**
13480
+ * The NATIVE slice — what the provider wrote, the shadow under a `replace`
13481
+ * claim — for the owner's derived reads. Admin-only (D224, ruled in the
13482
+ * Task 6 review): the shadow is a second truth beside the merge, and the
13483
+ * event channel already refuses it to everyone but the composer; a
13484
+ * protected read of it would hand out what the events withhold.
13485
+ */
13486
+ getNativeCapSlice: require_sleep.method(zod.z.object({
13487
+ deviceId: zod.z.number(),
13488
+ capName: zod.z.string()
13489
+ }), zod.z.record(zod.z.string(), zod.z.unknown()).nullable(), { auth: "admin" })
14281
13490
  },
14282
13491
  events: {
14283
13492
  /**
@@ -16269,7 +15478,7 @@ var motionDetectionCapability = {
16269
15478
  /** Frame-differencing motion analysis over a video stream — only a camera
16270
15479
  * produces the frames. Read by the `defaultActive` auto-bind in
16271
15480
  * `device-manager.getBindings` to decide which devices it may claim. */
16272
- deviceTypes: [require_sleep.DeviceType.Camera],
15481
+ deviceTypes: [require_err_msg.DeviceType.Camera],
16273
15482
  exposesDeviceSettings: true,
16274
15483
  methods: {
16275
15484
  analyze: require_sleep.method(zod.z.object({
@@ -17854,7 +17063,7 @@ var alarmPanelCapability = {
17854
17063
  scope: "device",
17855
17064
  deviceNative: true,
17856
17065
  mode: "singleton",
17857
- deviceTypes: [require_sleep.DeviceType.AlarmPanel],
17066
+ deviceTypes: [require_err_msg.DeviceType.AlarmPanel],
17858
17067
  methods: {
17859
17068
  arm: require_sleep.method(zod.z.object({
17860
17069
  deviceId: zod.z.number().int().nonnegative(),
@@ -20814,7 +20023,7 @@ var DeviceSelectorSchema = zod.z.discriminatedUnion("kind", [
20814
20023
  }),
20815
20024
  zod.z.object({
20816
20025
  kind: zod.z.literal("types"),
20817
- types: zod.z.array(zod.z.enum(require_sleep.DeviceType)).min(1)
20026
+ types: zod.z.array(zod.z.enum(require_err_msg.DeviceType)).min(1)
20818
20027
  }),
20819
20028
  zod.z.object({
20820
20029
  kind: zod.z.literal("locations"),
@@ -23177,7 +22386,7 @@ var pipelineAnalyticsCapability = {
23177
22386
  mode: "singleton",
23178
22387
  kind: "wrapper",
23179
22388
  defaultActive: true,
23180
- deviceTypes: [require_sleep.DeviceType.Camera],
22389
+ deviceTypes: [require_err_msg.DeviceType.Camera],
23181
22390
  exposesDeviceSettings: true,
23182
22391
  methods: {
23183
22392
  getActiveTracks: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(TrackSchema).readonly()),
@@ -24860,7 +24069,7 @@ var zonesCapability = {
24860
24069
  name: "zones",
24861
24070
  scope: "device",
24862
24071
  mode: "singleton",
24863
- deviceTypes: [require_sleep.DeviceType.Camera],
24072
+ deviceTypes: [require_err_msg.DeviceType.Camera],
24864
24073
  methods: {
24865
24074
  listZones: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(ZoneSchema).readonly()),
24866
24075
  addZone: require_sleep.method(zod.z.object({
@@ -27842,7 +27051,7 @@ var snapshotCapability = {
27842
27051
  mode: "singleton",
27843
27052
  kind: "wrapper",
27844
27053
  defaultActive: true,
27845
- deviceTypes: [require_sleep.DeviceType.Camera],
27054
+ deviceTypes: [require_err_msg.DeviceType.Camera],
27846
27055
  exposesDeviceSettings: true,
27847
27056
  methods: {
27848
27057
  getSnapshot: require_sleep.method(zod.z.object({
@@ -28698,7 +27907,7 @@ var streamSignalsCapability = {
28698
27907
  scope: "device",
28699
27908
  deviceNative: true,
28700
27909
  mode: "singleton",
28701
- deviceTypes: Object.values(require_sleep.DeviceType),
27910
+ deviceTypes: Object.values(require_err_msg.DeviceType),
28702
27911
  runtimeState: StreamSignalsStatusSchema,
28703
27912
  /**
28704
27913
  * Runtime-state durability: **session** — mirrored in RAM, never written.
@@ -30197,7 +29406,7 @@ var videoclipsCapability = {
30197
29406
  /** A clip is a window over a camera's footage — the cap is meaningless on a
30198
29407
  * sensor, a button or an event emitter, and the `defaultActive` auto-bind
30199
29408
  * reads this to decide which devices it may claim. */
30200
- deviceTypes: [require_sleep.DeviceType.Camera],
29409
+ deviceTypes: [require_err_msg.DeviceType.Camera],
30201
29410
  /**
30202
29411
  * The Clips section of a camera's device details is FRAMEWORK-DERIVED (D14):
30203
29412
  * the aggregator turns this declaration into the `type:'widget'` section and
@@ -30791,7 +30000,7 @@ var DOORBELL_SOURCE_SLOT = {
30791
30000
  label: "Doorbell trigger devices",
30792
30001
  description: "Any of these becoming active rings this camera's virtual doorbell: a binary sensor when it goes active, a switch when it turns on.",
30793
30002
  shape: "association",
30794
- hostDeviceTypes: [require_sleep.DeviceType.Camera],
30003
+ hostDeviceTypes: [require_err_msg.DeviceType.Camera],
30795
30004
  sourceCaps: SOURCE_CAPS,
30796
30005
  cardinality: "many",
30797
30006
  value: DoorbellSourceSlotValueSchema
@@ -30803,7 +30012,7 @@ var doorbellCapability = {
30803
30012
  mode: "singleton",
30804
30013
  kind: "wrapper",
30805
30014
  defaultActive: false,
30806
- deviceTypes: [require_sleep.DeviceType.Button, require_sleep.DeviceType.Camera],
30015
+ deviceTypes: [require_err_msg.DeviceType.Button, require_err_msg.DeviceType.Camera],
30807
30016
  extensionSlots: [DOORBELL_SOURCE_SLOT],
30808
30017
  methods: {},
30809
30018
  events: {
@@ -30851,29 +30060,28 @@ var EXTENSION_SLOTS = {
30851
30060
  "doorbell-source": {
30852
30061
  descriptor: DOORBELL_SOURCE_SLOT,
30853
30062
  declaringCap: "doorbell",
30854
- hostDeviceTypes: [require_sleep.DeviceType.Camera]
30063
+ hostDeviceTypes: [require_err_msg.DeviceType.Camera]
30855
30064
  },
30856
30065
  "linked-devices": {
30857
30066
  descriptor: LINKED_DEVICES_SLOT,
30858
30067
  declaringCap: "device-manager",
30859
- hostDeviceTypes: [require_sleep.DeviceType.Camera]
30068
+ hostDeviceTypes: [require_err_msg.DeviceType.Camera]
30860
30069
  }
30861
30070
  };
30862
30071
  /** A slot's editable value, discriminated on `slot`. */
30863
30072
  var ExtensionSlotValueSchema = zod.z.discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: zod.z.literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: zod.z.literal("linked-devices") })]);
30864
30073
  /** Every device type at least one slot is offered on — `device-extension`'s `deviceTypes`. */
30865
- var EXTENSION_HOST_DEVICE_TYPES = [require_sleep.DeviceType.Camera];
30074
+ var EXTENSION_HOST_DEVICE_TYPES = [require_err_msg.DeviceType.Camera];
30866
30075
  //#endregion
30867
30076
  //#region src/capabilities/device-extension.cap.ts
30868
30077
  /**
30869
30078
  * `device-extension` — ONE place to see and author what a device is extended
30870
30079
  * with by OTHER devices (D648).
30871
30080
  *
30872
- * **A contract, not a store.** Every slot (`linked-devices`,
30873
- * `doorbell-source`, later `consumables-graft`) is declared by the cap that
30874
- * owns the function (`extensionSlots` in its `*.cap.ts`, D4) and SERVED by the
30875
- * addon that already owns its store, over that store and its existing write
30876
- * path. This cap persists nothing and has no config method (guarded by
30081
+ * **A contract, not a store.** Every slot (`linked-devices`, `doorbell-source`)
30082
+ * is declared by the cap that owns the function (`extensionSlots` in its
30083
+ * `*.cap.ts`, D4) and SERVED by the addon that already owns its store, over
30084
+ * that store and its existing write path. This cap persists nothing and has no config method (guarded by
30877
30085
  * `scripts/check-extension-slots.ts`): D62 applied to the whole feature, and
30878
30086
  * D3 — no shadow registry.
30879
30087
  *
@@ -30894,6 +30102,11 @@ var EXTENSION_HOST_DEVICE_TYPES = [require_sleep.DeviceType.Camera];
30894
30102
  * The slot's value travels as the generated discriminated union
30895
30103
  * (`ExtensionSlotValue`, keyed on `slot`), so a value under the wrong slot is
30896
30104
  * a schema error before it reaches any owner.
30105
+ *
30106
+ * **Associations only.** A slot LISTS other devices on its host. A host that
30107
+ * GAINS a capability from other devices is a composition (D663, amends D648:
30108
+ * the reserved `graft` shape, its `projects` and its `enabled` switch were
30109
+ * retired unimplemented).
30897
30110
  */
30898
30111
  /** Separator between the source and the slot id in a `slotRef`. It is the
30899
30112
  * D554 routing separator: the dispatcher matches `<source>:` prefixes. */
@@ -30916,7 +30129,7 @@ var ExtensionSourceSchema = zod.z.object({
30916
30129
  /**
30917
30130
  * How one picked source stands. `missing` — no such device (deleted, or an id
30918
30131
  * that never existed); `foreign` — a device the slot's owner cannot read from
30919
- * (slice 2's cross-owner graft source); `unavailable` — it exists but is not a
30132
+ * (a source owned by another addon); `unavailable` — it exists but is not a
30920
30133
  * valid source now (a container, which is never a pick). A source in any of
30921
30134
  * these states is SHOWN, never silently dropped or re-pointed.
30922
30135
  */
@@ -30957,10 +30170,6 @@ var ExtensionSlotStateSchema = zod.z.object({
30957
30170
  slot: ExtensionSlotIdSchema,
30958
30171
  /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
30959
30172
  authority: zod.z.string(),
30960
- /** graft: its switch. association: null — removing every source is "off". */
30961
- enabled: zod.z.boolean().nullable(),
30962
- /** graft: the cap registered on the host. association: null. */
30963
- projects: zod.z.string().nullable(),
30964
30173
  activation: ExtensionSlotActivationSchema.nullable(),
30965
30174
  value: ExtensionSlotValueSchema,
30966
30175
  sources: zod.z.array(ExtensionSlotSourceSchema),
@@ -31011,7 +30220,8 @@ var ExtensionCandidatesSchema = zod.z.object({ candidates: zod.z.array(Extension
31011
30220
  /**
31012
30221
  * What a write did. `none` — applied in place. `host` / `parent` — the owner
31013
30222
  * reloaded the host (or, for an accessory child, its parent) because the
31014
- * write changed what the host REGISTERS (a graft switched on or off, slice 2).
30223
+ * write changed what the host REGISTERS. A slot never adds a capability to its
30224
+ * host — that is a composition (D663) — so today's owners answer `none`.
31015
30225
  */
31016
30226
  var ExtensionSetResultSchema = zod.z.object({
31017
30227
  applied: zod.z.literal(true),
@@ -31349,7 +30559,7 @@ var webrtcSessionCapability = {
31349
30559
  kind: "wrapper",
31350
30560
  defaultActive: true,
31351
30561
  preferredProvider: "stream-broker",
31352
- deviceTypes: [require_sleep.DeviceType.Camera],
30562
+ deviceTypes: [require_err_msg.DeviceType.Camera],
31353
30563
  methods: {
31354
30564
  /**
31355
30565
  * List available WebRTC streams for a device.
@@ -31630,18 +30840,18 @@ var accessoriesCapability = {
31630
30840
  deviceNative: true,
31631
30841
  mode: "singleton",
31632
30842
  deviceTypes: [
31633
- require_sleep.DeviceType.Camera,
31634
- require_sleep.DeviceType.Hub,
31635
- require_sleep.DeviceType.Switch,
31636
- require_sleep.DeviceType.Light,
31637
- require_sleep.DeviceType.Sensor,
31638
- require_sleep.DeviceType.Thermostat,
31639
- require_sleep.DeviceType.Cover,
31640
- require_sleep.DeviceType.Lock,
31641
- require_sleep.DeviceType.Fan,
31642
- require_sleep.DeviceType.MediaPlayer,
31643
- require_sleep.DeviceType.AlarmPanel,
31644
- require_sleep.DeviceType.Generic
30843
+ require_err_msg.DeviceType.Camera,
30844
+ require_err_msg.DeviceType.Hub,
30845
+ require_err_msg.DeviceType.Switch,
30846
+ require_err_msg.DeviceType.Light,
30847
+ require_err_msg.DeviceType.Sensor,
30848
+ require_err_msg.DeviceType.Thermostat,
30849
+ require_err_msg.DeviceType.Cover,
30850
+ require_err_msg.DeviceType.Lock,
30851
+ require_err_msg.DeviceType.Fan,
30852
+ require_err_msg.DeviceType.MediaPlayer,
30853
+ require_err_msg.DeviceType.AlarmPanel,
30854
+ require_err_msg.DeviceType.Generic
31645
30855
  ],
31646
30856
  methods: {
31647
30857
  /**
@@ -32282,7 +31492,7 @@ var airQualitySensorCapability = {
32282
31492
  scope: "device",
32283
31493
  deviceNative: true,
32284
31494
  mode: "singleton",
32285
- deviceTypes: [require_sleep.DeviceType.Sensor],
31495
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
32286
31496
  methods: {},
32287
31497
  status: {
32288
31498
  schema: AirQualitySensorStatusSchema,
@@ -32326,7 +31536,7 @@ var ambientLightSensorCapability = {
32326
31536
  scope: "device",
32327
31537
  deviceNative: true,
32328
31538
  mode: "singleton",
32329
- deviceTypes: [require_sleep.DeviceType.Sensor],
31539
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
32330
31540
  methods: {},
32331
31541
  status: {
32332
31542
  schema: AmbientLightSensorStatusSchema,
@@ -32444,7 +31654,7 @@ var audioMetricsCapability = {
32444
31654
  name: "audio-metrics",
32445
31655
  scope: "device",
32446
31656
  mode: "singleton",
32447
- deviceTypes: [require_sleep.DeviceType.Camera],
31657
+ deviceTypes: [require_err_msg.DeviceType.Camera],
32448
31658
  methods: {
32449
31659
  /** Latest snapshot for this device. Null until the analytics
32450
31660
  * pipeline has processed at least one audio window. */
@@ -32512,7 +31722,7 @@ var automationControlCapability = {
32512
31722
  scope: "device",
32513
31723
  deviceNative: true,
32514
31724
  mode: "singleton",
32515
- deviceTypes: [require_sleep.DeviceType.Automation],
31725
+ deviceTypes: [require_err_msg.DeviceType.Automation],
32516
31726
  methods: {
32517
31727
  enable: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
32518
31728
  kind: "mutation",
@@ -32638,10 +31848,12 @@ var batteryCapability = {
32638
31848
  deviceNative: true,
32639
31849
  mode: "singleton",
32640
31850
  deviceTypes: [
32641
- require_sleep.DeviceType.Camera,
32642
- require_sleep.DeviceType.Sensor,
32643
- require_sleep.DeviceType.Button,
32644
- require_sleep.DeviceType.Switch
31851
+ require_err_msg.DeviceType.Camera,
31852
+ require_err_msg.DeviceType.Sensor,
31853
+ require_err_msg.DeviceType.Button,
31854
+ require_err_msg.DeviceType.Switch,
31855
+ require_err_msg.DeviceType.PetFeeder,
31856
+ require_err_msg.DeviceType.Container
32645
31857
  ],
32646
31858
  methods: {
32647
31859
  /**
@@ -32668,6 +31880,19 @@ wakeForStream: require_sleep.method(zod.z.object({
32668
31880
  awoke: zod.z.boolean(),
32669
31881
  durationMs: zod.z.number()
32670
31882
  }), { kind: "mutation" }) },
31883
+ /**
31884
+ * A composed provider (battery ADDED to a pet feeder or container that has
31885
+ * no wake surface) answers `wakeForStream` with the same "unavailable"
31886
+ * result the contract already defines above: "Returns `awoke: false` when
31887
+ * … the cap surface is unavailable." (D663).
31888
+ */
31889
+ composedMethods: { wakeForStream: {
31890
+ kind: "answer",
31891
+ value: {
31892
+ awoke: false,
31893
+ durationMs: 0
31894
+ }
31895
+ } },
32671
31896
  events: {
32672
31897
  /**
32673
31898
  * Emitted whenever the cached status changes (firmware push OR
@@ -32683,7 +31908,7 @@ onStatusChanged: { data: zod.z.object({
32683
31908
  schema: BatteryStatusSchema,
32684
31909
  kind: "push",
32685
31910
  empty: {
32686
- percentage: 0,
31911
+ percentage: null,
32687
31912
  charging: "unknown",
32688
31913
  sleeping: false,
32689
31914
  lastUpdated: 0
@@ -32733,7 +31958,7 @@ var binaryCapability = {
32733
31958
  scope: "device",
32734
31959
  deviceNative: true,
32735
31960
  mode: "singleton",
32736
- deviceTypes: [require_sleep.DeviceType.Sensor],
31961
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
32737
31962
  methods: {},
32738
31963
  status: {
32739
31964
  schema: BinaryStatusSchema,
@@ -32776,7 +32001,7 @@ var brightnessCapability = {
32776
32001
  scope: "device",
32777
32002
  deviceNative: true,
32778
32003
  mode: "singleton",
32779
- deviceTypes: [require_sleep.DeviceType.Light],
32004
+ deviceTypes: [require_err_msg.DeviceType.Light],
32780
32005
  methods: { setBrightness: require_sleep.method(zod.z.object({
32781
32006
  deviceId: zod.z.number().int().nonnegative(),
32782
32007
  percentage: zod.z.number().min(0).max(100)
@@ -32830,7 +32055,7 @@ var buttonCapability = {
32830
32055
  scope: "device",
32831
32056
  deviceNative: true,
32832
32057
  mode: "singleton",
32833
- deviceTypes: [require_sleep.DeviceType.Button],
32058
+ deviceTypes: [require_err_msg.DeviceType.Button],
32834
32059
  methods: { press: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
32835
32060
  kind: "mutation",
32836
32061
  auth: "protected"
@@ -32907,7 +32132,7 @@ var cameraCredentialsCapability = {
32907
32132
  scope: "device",
32908
32133
  deviceNative: true,
32909
32134
  mode: "singleton",
32910
- deviceTypes: [require_sleep.DeviceType.Camera],
32135
+ deviceTypes: [require_err_msg.DeviceType.Camera],
32911
32136
  methods: {
32912
32137
  /**
32913
32138
  * Return the camera's full credential bundle for admin consumers.
@@ -33064,7 +32289,7 @@ var cameraGridLayoutCapability = {
33064
32289
  scope: "device",
33065
32290
  deviceNative: true,
33066
32291
  mode: "singleton",
33067
- deviceTypes: [require_sleep.DeviceType.Camera],
32292
+ deviceTypes: [require_err_msg.DeviceType.Camera],
33068
32293
  /**
33069
32294
  * The section is built by the ADDON and returned from
33070
32295
  * `getDeviceSettingsContribution`, not derived by the framework from a
@@ -33115,7 +32340,7 @@ var carbonMonoxideCapability = {
33115
32340
  scope: "device",
33116
32341
  deviceNative: true,
33117
32342
  mode: "singleton",
33118
- deviceTypes: [require_sleep.DeviceType.Sensor],
32343
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
33119
32344
  methods: {},
33120
32345
  status: {
33121
32346
  schema: CarbonMonoxideStatusSchema,
@@ -33224,7 +32449,7 @@ var climateControlCapability = {
33224
32449
  scope: "device",
33225
32450
  deviceNative: true,
33226
32451
  mode: "singleton",
33227
- deviceTypes: [require_sleep.DeviceType.Thermostat, require_sleep.DeviceType.Climate],
32452
+ deviceTypes: [require_err_msg.DeviceType.Thermostat, require_err_msg.DeviceType.Climate],
33228
32453
  methods: {
33229
32454
  setMode: require_sleep.method(zod.z.object({
33230
32455
  deviceId: zod.z.number().int().nonnegative(),
@@ -33377,7 +32602,7 @@ var colorCapability = {
33377
32602
  scope: "device",
33378
32603
  deviceNative: true,
33379
32604
  mode: "singleton",
33380
- deviceTypes: [require_sleep.DeviceType.Light],
32605
+ deviceTypes: [require_err_msg.DeviceType.Light],
33381
32606
  methods: { setColor: require_sleep.method(zod.z.object({
33382
32607
  deviceId: zod.z.number().int().nonnegative(),
33383
32608
  color: ColorInputSchema
@@ -33537,7 +32762,7 @@ var connectivityCapability = {
33537
32762
  scope: "device",
33538
32763
  deviceNative: true,
33539
32764
  mode: "singleton",
33540
- deviceTypes: [require_sleep.DeviceType.Sensor],
32765
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
33541
32766
  methods: {},
33542
32767
  status: {
33543
32768
  schema: ConnectivityStatusSchema,
@@ -33569,6 +32794,19 @@ var connectivityCapability = {
33569
32794
  * provider populates it by guessing (no HA inference). The UI renders a
33570
32795
  * "No consumables reported" placeholder when `items` is empty.
33571
32796
  */
32797
+ /** Units a remaining-life counter is reported in. Closed: the panel renders
32798
+ * each. */
32799
+ var ConsumableRemainingUnitSchema = zod.z.enum([
32800
+ "days",
32801
+ "hours",
32802
+ "cycles"
32803
+ ]);
32804
+ /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
32805
+ * days). `value` null = unknown (D393). */
32806
+ var ConsumableRemainingSchema = zod.z.object({
32807
+ value: zod.z.number().min(0).nullable(),
32808
+ unit: ConsumableRemainingUnitSchema
32809
+ });
33572
32810
  /** A single consumable item. Either a continuous `level` (remaining
33573
32811
  * life %) or a discrete `status` may be known — both may be null when a
33574
32812
  * provider only knows the item exists. `level` and `status` are not
@@ -33585,7 +32823,10 @@ var ConsumableItemSchema = zod.z.object({
33585
32823
  /** Ms epoch of the last replace, when known. */
33586
32824
  lastResetAt: zod.z.number().nullable(),
33587
32825
  /** Whether `reset()` is meaningful for this item. */
33588
- resettable: zod.z.boolean()
32826
+ resettable: zod.z.boolean(),
32827
+ /** Remaining life as a count (days/hours/cycles), when a provider reports
32828
+ * it that way instead of — or alongside — `level`. Absent = not reported. */
32829
+ remaining: ConsumableRemainingSchema.optional()
33589
32830
  });
33590
32831
  var ConsumablesStatusSchema = zod.z.object({
33591
32832
  items: zod.z.array(ConsumableItemSchema),
@@ -33596,7 +32837,7 @@ var consumablesCapability = {
33596
32837
  scope: "device",
33597
32838
  deviceNative: true,
33598
32839
  mode: "singleton",
33599
- deviceTypes: Object.values(require_sleep.DeviceType),
32840
+ deviceTypes: Object.values(require_err_msg.DeviceType),
33600
32841
  deviceConfig: { ui: {
33601
32842
  kind: "widget",
33602
32843
  widgetId: "host/consumables-panel",
@@ -33615,6 +32856,16 @@ reset: require_sleep.method(zod.z.object({
33615
32856
  kind: "mutation",
33616
32857
  auth: "admin"
33617
32858
  }) },
32859
+ /**
32860
+ * A composed consumable (item added to a device with no native consumables
32861
+ * provider) refuses `reset` by name: "Only meaningful when the item's
32862
+ * `resettable` is true" — a composed item is never resettable until
32863
+ * commands (`forward`) arrive in slice 3 (D663).
32864
+ */
32865
+ composedMethods: { reset: {
32866
+ kind: "refuse",
32867
+ reason: "a composed consumable is not resettable until commands (forward) arrive — slice 3"
32868
+ } },
33618
32869
  status: {
33619
32870
  schema: ConsumablesStatusSchema,
33620
32871
  kind: "push",
@@ -33679,7 +32930,7 @@ var contactCapability = {
33679
32930
  scope: "device",
33680
32931
  deviceNative: true,
33681
32932
  mode: "singleton",
33682
- deviceTypes: [require_sleep.DeviceType.Sensor],
32933
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
33683
32934
  methods: {},
33684
32935
  status: {
33685
32936
  schema: ContactStatusSchema,
@@ -33810,7 +33061,7 @@ var controlCapability = {
33810
33061
  scope: "device",
33811
33062
  deviceNative: true,
33812
33063
  mode: "singleton",
33813
- deviceTypes: [require_sleep.DeviceType.Control],
33064
+ deviceTypes: [require_err_msg.DeviceType.Control],
33814
33065
  methods: { setValue: require_sleep.method(zod.z.object({
33815
33066
  deviceId: zod.z.number().int().nonnegative(),
33816
33067
  control: ControlSetValueInputSchema
@@ -33882,7 +33133,7 @@ var coverCapability = {
33882
33133
  scope: "device",
33883
33134
  deviceNative: true,
33884
33135
  mode: "singleton",
33885
- deviceTypes: [require_sleep.DeviceType.Cover],
33136
+ deviceTypes: [require_err_msg.DeviceType.Cover],
33886
33137
  methods: {
33887
33138
  open: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
33888
33139
  kind: "mutation",
@@ -34005,7 +33256,7 @@ var dayNightCapability = {
34005
33256
  scope: "device",
34006
33257
  deviceNative: true,
34007
33258
  mode: "singleton",
34008
- deviceTypes: [require_sleep.DeviceType.Camera],
33259
+ deviceTypes: [require_err_msg.DeviceType.Camera],
34009
33260
  deviceConfig: { ui: {
34010
33261
  kind: "derived-form",
34011
33262
  builderId: "day-night",
@@ -34130,7 +33381,7 @@ var enumSensorCapability = {
34130
33381
  scope: "device",
34131
33382
  deviceNative: true,
34132
33383
  mode: "singleton",
34133
- deviceTypes: [require_sleep.DeviceType.Sensor],
33384
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
34134
33385
  methods: {},
34135
33386
  status: {
34136
33387
  schema: EnumSensorStatusSchema,
@@ -34177,7 +33428,7 @@ var eventEmitterCapability = {
34177
33428
  scope: "device",
34178
33429
  deviceNative: true,
34179
33430
  mode: "singleton",
34180
- deviceTypes: [require_sleep.DeviceType.EventEmitter],
33431
+ deviceTypes: [require_err_msg.DeviceType.EventEmitter],
34181
33432
  methods: {},
34182
33433
  events: { onEvent: { data: EventFireSchema } },
34183
33434
  status: {
@@ -34645,7 +33896,7 @@ var fanControlCapability = {
34645
33896
  scope: "device",
34646
33897
  deviceNative: true,
34647
33898
  mode: "singleton",
34648
- deviceTypes: [require_sleep.DeviceType.Fan],
33899
+ deviceTypes: [require_err_msg.DeviceType.Fan],
34649
33900
  methods: {
34650
33901
  setPercentage: require_sleep.method(zod.z.object({
34651
33902
  deviceId: zod.z.number().int().nonnegative(),
@@ -34799,7 +34050,7 @@ var floodCapability = {
34799
34050
  scope: "device",
34800
34051
  deviceNative: true,
34801
34052
  mode: "singleton",
34802
- deviceTypes: [require_sleep.DeviceType.Sensor],
34053
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
34803
34054
  methods: {},
34804
34055
  status: {
34805
34056
  schema: FloodStatusSchema,
@@ -34834,7 +34085,7 @@ var gasCapability = {
34834
34085
  scope: "device",
34835
34086
  deviceNative: true,
34836
34087
  mode: "singleton",
34837
- deviceTypes: [require_sleep.DeviceType.Sensor],
34088
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
34838
34089
  methods: {},
34839
34090
  status: {
34840
34091
  schema: GasStatusSchema,
@@ -34896,7 +34147,7 @@ var humidifierCapability = {
34896
34147
  scope: "device",
34897
34148
  deviceNative: true,
34898
34149
  mode: "singleton",
34899
- deviceTypes: [require_sleep.DeviceType.Humidifier],
34150
+ deviceTypes: [require_err_msg.DeviceType.Humidifier],
34900
34151
  methods: {
34901
34152
  setOn: require_sleep.method(zod.z.object({
34902
34153
  deviceId: zod.z.number().int().nonnegative(),
@@ -34967,7 +34218,7 @@ var humiditySensorCapability = {
34967
34218
  scope: "device",
34968
34219
  deviceNative: true,
34969
34220
  mode: "singleton",
34970
- deviceTypes: [require_sleep.DeviceType.Sensor, require_sleep.DeviceType.Thermostat],
34221
+ deviceTypes: [require_err_msg.DeviceType.Sensor, require_err_msg.DeviceType.Thermostat],
34971
34222
  methods: {},
34972
34223
  status: {
34973
34224
  schema: HumiditySensorStatusSchema,
@@ -35013,7 +34264,7 @@ var imageCapability = {
35013
34264
  scope: "device",
35014
34265
  deviceNative: true,
35015
34266
  mode: "singleton",
35016
- deviceTypes: [require_sleep.DeviceType.Image],
34267
+ deviceTypes: [require_err_msg.DeviceType.Image],
35017
34268
  methods: {},
35018
34269
  status: {
35019
34270
  schema: ImageStatusSchema,
@@ -35161,7 +34412,7 @@ var imageSettingsCapability = {
35161
34412
  scope: "device",
35162
34413
  deviceNative: true,
35163
34414
  mode: "singleton",
35164
- deviceTypes: [require_sleep.DeviceType.Camera],
34415
+ deviceTypes: [require_err_msg.DeviceType.Camera],
35165
34416
  deviceConfig: { ui: {
35166
34417
  kind: "derived-form",
35167
34418
  builderId: "image-settings",
@@ -35438,7 +34689,7 @@ var intercomCapability = {
35438
34689
  scope: "device",
35439
34690
  deviceNative: true,
35440
34691
  mode: "singleton",
35441
- deviceTypes: [require_sleep.DeviceType.Camera],
34692
+ deviceTypes: [require_err_msg.DeviceType.Camera],
35442
34693
  /**
35443
34694
  * **Auth tier: `protected` on every method — deliberate, and load-bearing.**
35444
34695
  *
@@ -35644,7 +34895,7 @@ var lawnMowerControlCapability = {
35644
34895
  scope: "device",
35645
34896
  deviceNative: true,
35646
34897
  mode: "singleton",
35647
- deviceTypes: [require_sleep.DeviceType.LawnMower],
34898
+ deviceTypes: [require_err_msg.DeviceType.LawnMower],
35648
34899
  methods: {
35649
34900
  startMowing: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
35650
34901
  kind: "mutation",
@@ -36074,7 +35325,7 @@ var lockControlCapability = {
36074
35325
  scope: "device",
36075
35326
  deviceNative: true,
36076
35327
  mode: "singleton",
36077
- deviceTypes: [require_sleep.DeviceType.Lock],
35328
+ deviceTypes: [require_err_msg.DeviceType.Lock],
36078
35329
  methods: {
36079
35330
  lock: require_sleep.method(zod.z.object({
36080
35331
  deviceId: zod.z.number().int().nonnegative(),
@@ -36195,7 +35446,7 @@ var mediaPlayerCapability = {
36195
35446
  scope: "device",
36196
35447
  deviceNative: true,
36197
35448
  mode: "singleton",
36198
- deviceTypes: [require_sleep.DeviceType.MediaPlayer],
35449
+ deviceTypes: [require_err_msg.DeviceType.MediaPlayer],
36199
35450
  methods: {
36200
35451
  play: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
36201
35452
  kind: "mutation",
@@ -36639,7 +35890,7 @@ var motionCapability = {
36639
35890
  * `snapshot` and every other per-device native cap.
36640
35891
  */
36641
35892
  deviceNative: true,
36642
- deviceTypes: [require_sleep.DeviceType.Camera, require_sleep.DeviceType.Sensor],
35893
+ deviceTypes: [require_err_msg.DeviceType.Camera, require_err_msg.DeviceType.Sensor],
36643
35894
  methods: {
36644
35895
  /**
36645
35896
  * Pull the current motion state synchronously. Convenience shortcut
@@ -36732,9 +35983,9 @@ var motionTriggerCapability = {
36732
35983
  deviceNative: true,
36733
35984
  mode: "singleton",
36734
35985
  deviceTypes: [
36735
- require_sleep.DeviceType.Light,
36736
- require_sleep.DeviceType.Siren,
36737
- require_sleep.DeviceType.Switch
35986
+ require_err_msg.DeviceType.Light,
35987
+ require_err_msg.DeviceType.Siren,
35988
+ require_err_msg.DeviceType.Switch
36738
35989
  ],
36739
35990
  methods: { setMotionTrigger: require_sleep.method(zod.z.object({
36740
35991
  deviceId: zod.z.number().int().nonnegative(),
@@ -36807,7 +36058,7 @@ var motionZonesCapability = {
36807
36058
  scope: "device",
36808
36059
  deviceNative: true,
36809
36060
  mode: "singleton",
36810
- deviceTypes: [require_sleep.DeviceType.Camera],
36061
+ deviceTypes: [require_err_msg.DeviceType.Camera],
36811
36062
  deviceConfig: { ui: {
36812
36063
  kind: "widget",
36813
36064
  widgetId: "host/motion-zones-grid",
@@ -36933,7 +36184,7 @@ var nativeObjectDetectionCapability = {
36933
36184
  scope: "device",
36934
36185
  deviceNative: true,
36935
36186
  mode: "singleton",
36936
- deviceTypes: [require_sleep.DeviceType.Camera],
36187
+ deviceTypes: [require_err_msg.DeviceType.Camera],
36937
36188
  methods: {
36938
36189
  getOptions: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), NativeObjectDetectionOptionsSchema),
36939
36190
  setSettings: require_sleep.method(zod.z.object({
@@ -37302,7 +36553,7 @@ var navigationCapability = {
37302
36553
  scope: "device",
37303
36554
  deviceNative: true,
37304
36555
  mode: "singleton",
37305
- deviceTypes: [require_sleep.DeviceType.Camera],
36556
+ deviceTypes: [require_err_msg.DeviceType.Camera],
37306
36557
  deviceConfig: { ui: {
37307
36558
  kind: "widget",
37308
36559
  widgetId: "host/navigation-panel",
@@ -37472,13 +36723,13 @@ var networkLinkCapability = {
37472
36723
  deviceNative: true,
37473
36724
  mode: "singleton",
37474
36725
  deviceTypes: [
37475
- require_sleep.DeviceType.Camera,
37476
- require_sleep.DeviceType.Sensor,
37477
- require_sleep.DeviceType.Button,
37478
- require_sleep.DeviceType.Switch,
37479
- require_sleep.DeviceType.Light,
37480
- require_sleep.DeviceType.Lock,
37481
- require_sleep.DeviceType.Siren
36726
+ require_err_msg.DeviceType.Camera,
36727
+ require_err_msg.DeviceType.Sensor,
36728
+ require_err_msg.DeviceType.Button,
36729
+ require_err_msg.DeviceType.Switch,
36730
+ require_err_msg.DeviceType.Light,
36731
+ require_err_msg.DeviceType.Lock,
36732
+ require_err_msg.DeviceType.Siren
37482
36733
  ],
37483
36734
  methods: {},
37484
36735
  events: {
@@ -37914,7 +37165,7 @@ var notifierCapability = {
37914
37165
  scope: "device",
37915
37166
  deviceNative: true,
37916
37167
  mode: "singleton",
37917
- deviceTypes: [require_sleep.DeviceType.Notifier],
37168
+ deviceTypes: [require_err_msg.DeviceType.Notifier],
37918
37169
  methods: {
37919
37170
  send: require_sleep.method(NotifierSendInputSchema, NotifierSendResultSchema, {
37920
37171
  kind: "mutation",
@@ -37996,7 +37247,7 @@ var numericSensorCapability = {
37996
37247
  scope: "device",
37997
37248
  deviceNative: true,
37998
37249
  mode: "singleton",
37999
- deviceTypes: [require_sleep.DeviceType.Sensor],
37250
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
38000
37251
  methods: {},
38001
37252
  status: {
38002
37253
  schema: NumericSensorStatusSchema,
@@ -38065,7 +37316,7 @@ var osdCapability = {
38065
37316
  scope: "device",
38066
37317
  deviceNative: true,
38067
37318
  mode: "singleton",
38068
- deviceTypes: [require_sleep.DeviceType.Camera],
37319
+ deviceTypes: [require_err_msg.DeviceType.Camera],
38069
37320
  methods: {
38070
37321
  /**
38071
37322
  * Patch a single overlay by id. Provider handles read-modify-write
@@ -38441,7 +37692,7 @@ var petFeederCapability = {
38441
37692
  scope: "device",
38442
37693
  deviceNative: true,
38443
37694
  mode: "singleton",
38444
- deviceTypes: [require_sleep.DeviceType.PetFeeder],
37695
+ deviceTypes: [require_err_msg.DeviceType.PetFeeder],
38445
37696
  methods: {
38446
37697
  /**
38447
37698
  * Dispense food now. Single-hopper feeders take `grams`; dual-hopper
@@ -38943,7 +38194,7 @@ var powerMeterCapability = {
38943
38194
  scope: "device",
38944
38195
  deviceNative: true,
38945
38196
  mode: "singleton",
38946
- deviceTypes: [require_sleep.DeviceType.Sensor],
38197
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
38947
38198
  methods: {},
38948
38199
  status: {
38949
38200
  schema: PowerMeterStatusSchema,
@@ -39006,7 +38257,7 @@ var presenceCapability = {
39006
38257
  scope: "device",
39007
38258
  deviceNative: true,
39008
38259
  mode: "singleton",
39009
- deviceTypes: [require_sleep.DeviceType.Presence],
38260
+ deviceTypes: [require_err_msg.DeviceType.Presence],
39010
38261
  methods: {},
39011
38262
  status: {
39012
38263
  schema: PresenceStatusSchema,
@@ -39060,7 +38311,7 @@ var pressureSensorCapability = {
39060
38311
  scope: "device",
39061
38312
  deviceNative: true,
39062
38313
  mode: "singleton",
39063
- deviceTypes: [require_sleep.DeviceType.Sensor],
38314
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
39064
38315
  methods: {},
39065
38316
  status: {
39066
38317
  schema: PressureSensorStatusSchema,
@@ -39185,7 +38436,7 @@ var privacyMaskCapability = {
39185
38436
  scope: "device",
39186
38437
  deviceNative: true,
39187
38438
  mode: "singleton",
39188
- deviceTypes: [require_sleep.DeviceType.Camera],
38439
+ deviceTypes: [require_err_msg.DeviceType.Camera],
39189
38440
  deviceConfig: { ui: {
39190
38441
  kind: "widget",
39191
38442
  widgetId: "host/privacy-mask-grid",
@@ -39449,7 +38700,7 @@ var ptzCapability = {
39449
38700
  scope: "device",
39450
38701
  deviceNative: true,
39451
38702
  mode: "singleton",
39452
- deviceTypes: [require_sleep.DeviceType.Camera],
38703
+ deviceTypes: [require_err_msg.DeviceType.Camera],
39453
38704
  deviceConfig: { ui: {
39454
38705
  kind: "widget",
39455
38706
  widgetId: "host/ptz-panel",
@@ -39681,7 +38932,7 @@ var ptzAutotrackCapability = {
39681
38932
  */
39682
38933
  kind: "wrapper",
39683
38934
  defaultActive: false,
39684
- deviceTypes: [require_sleep.DeviceType.Camera],
38935
+ deviceTypes: [require_err_msg.DeviceType.Camera],
39685
38936
  deviceConfig: { ui: {
39686
38937
  kind: "widget",
39687
38938
  widgetId: "host/ptz-autotrack",
@@ -39772,9 +39023,9 @@ var rebootCapability = {
39772
39023
  deviceNative: true,
39773
39024
  mode: "singleton",
39774
39025
  deviceTypes: [
39775
- require_sleep.DeviceType.Camera,
39776
- require_sleep.DeviceType.Sensor,
39777
- require_sleep.DeviceType.Switch
39026
+ require_err_msg.DeviceType.Camera,
39027
+ require_err_msg.DeviceType.Sensor,
39028
+ require_err_msg.DeviceType.Switch
39778
39029
  ],
39779
39030
  methods: { reboot: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.object({ success: zod.z.literal(true) }), {
39780
39031
  kind: "mutation",
@@ -40054,7 +39305,7 @@ var recordingCapability = {
40054
39305
  /** Recorded coverage is a property of a camera — the cap is meaningless on a
40055
39306
  * sensor, a button or an event emitter, and the `defaultActive` auto-bind
40056
39307
  * reads this to decide which devices it may claim. */
40057
- deviceTypes: [require_sleep.DeviceType.Camera],
39308
+ deviceTypes: [require_err_msg.DeviceType.Camera],
40058
39309
  methods: {
40059
39310
  /**
40060
39311
  * The sources this camera has, WITH the reason any of them cannot answer.
@@ -41742,7 +40993,7 @@ var recordingOnboardCapability = {
41742
40993
  scope: "device",
41743
40994
  deviceNative: true,
41744
40995
  mode: "singleton",
41745
- deviceTypes: [require_sleep.DeviceType.Camera],
40996
+ deviceTypes: [require_err_msg.DeviceType.Camera],
41746
40997
  deviceConfig: { ui: {
41747
40998
  kind: "derived-form",
41748
40999
  builderId: "recording-onboard",
@@ -42085,7 +41336,7 @@ var recordingSignalCapability = {
42085
41336
  scope: "device",
42086
41337
  deviceNative: true,
42087
41338
  mode: "singleton",
42088
- deviceTypes: [require_sleep.DeviceType.Camera],
41339
+ deviceTypes: [require_err_msg.DeviceType.Camera],
42089
41340
  methods: {
42090
41341
  /** The current level, straight from the slice the provider keeps fresh. */
42091
41342
  getStatus: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), RecordingSignalStatusSchema) },
@@ -42404,7 +41655,7 @@ var sceneMonitorCapability = {
42404
41655
  mode: "singleton",
42405
41656
  kind: "wrapper",
42406
41657
  defaultActive: true,
42407
- deviceTypes: [require_sleep.DeviceType.Camera],
41658
+ deviceTypes: [require_err_msg.DeviceType.Camera],
42408
41659
  methods: {
42409
41660
  listScenes: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), SceneMonitorStatusSchema),
42410
41661
  /**
@@ -42657,7 +41908,7 @@ var scriptRunnerCapability = {
42657
41908
  scope: "device",
42658
41909
  deviceNative: true,
42659
41910
  mode: "singleton",
42660
- deviceTypes: [require_sleep.DeviceType.Script],
41911
+ deviceTypes: [require_err_msg.DeviceType.Script],
42661
41912
  methods: {
42662
41913
  run: require_sleep.method(zod.z.object({
42663
41914
  deviceId: zod.z.number().int().nonnegative(),
@@ -42715,7 +41966,7 @@ var smokeCapability = {
42715
41966
  scope: "device",
42716
41967
  deviceNative: true,
42717
41968
  mode: "singleton",
42718
- deviceTypes: [require_sleep.DeviceType.Sensor],
41969
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
42719
41970
  methods: {},
42720
41971
  status: {
42721
41972
  schema: SmokeStatusSchema,
@@ -42814,7 +42065,7 @@ var streamCatalogCapability = {
42814
42065
  scope: "device",
42815
42066
  deviceNative: true,
42816
42067
  mode: "singleton",
42817
- deviceTypes: [require_sleep.DeviceType.Camera],
42068
+ deviceTypes: [require_err_msg.DeviceType.Camera],
42818
42069
  methods: { getCatalog: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.array(CamStreamDescriptorSchema).readonly()) },
42819
42070
  runtimeState: StreamCatalogStateSchema,
42820
42071
  /**
@@ -42932,7 +42183,7 @@ var streamParamsCapability = {
42932
42183
  scope: "device",
42933
42184
  deviceNative: true,
42934
42185
  mode: "singleton",
42935
- deviceTypes: [require_sleep.DeviceType.Camera],
42186
+ deviceTypes: [require_err_msg.DeviceType.Camera],
42936
42187
  deviceConfig: { ui: {
42937
42188
  kind: "derived-form",
42938
42189
  builderId: "stream-params",
@@ -43264,9 +42515,9 @@ var switchCapability = {
43264
42515
  deviceNative: true,
43265
42516
  mode: "singleton",
43266
42517
  deviceTypes: [
43267
- require_sleep.DeviceType.Switch,
43268
- require_sleep.DeviceType.Siren,
43269
- require_sleep.DeviceType.Light
42518
+ require_err_msg.DeviceType.Switch,
42519
+ require_err_msg.DeviceType.Siren,
42520
+ require_err_msg.DeviceType.Light
43270
42521
  ],
43271
42522
  methods: { setState: require_sleep.method(zod.z.object({
43272
42523
  deviceId: zod.z.number(),
@@ -43961,7 +43212,7 @@ var tamperCapability = {
43961
43212
  scope: "device",
43962
43213
  deviceNative: true,
43963
43214
  mode: "singleton",
43964
- deviceTypes: [require_sleep.DeviceType.Sensor],
43215
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
43965
43216
  methods: {},
43966
43217
  status: {
43967
43218
  schema: TamperStatusSchema,
@@ -44018,7 +43269,7 @@ var temperatureSensorCapability = {
44018
43269
  scope: "device",
44019
43270
  deviceNative: true,
44020
43271
  mode: "singleton",
44021
- deviceTypes: [require_sleep.DeviceType.Sensor, require_sleep.DeviceType.Thermostat],
43272
+ deviceTypes: [require_err_msg.DeviceType.Sensor, require_err_msg.DeviceType.Thermostat],
44022
43273
  methods: {},
44023
43274
  status: {
44024
43275
  schema: TemperatureSensorStatusSchema,
@@ -44105,7 +43356,7 @@ var updateCapability = {
44105
43356
  scope: "device",
44106
43357
  deviceNative: true,
44107
43358
  mode: "singleton",
44108
- deviceTypes: [require_sleep.DeviceType.Update],
43359
+ deviceTypes: [require_err_msg.DeviceType.Update],
44109
43360
  methods: { installUpdate: require_sleep.method(zod.z.void(), zod.z.void(), {
44110
43361
  kind: "mutation",
44111
43362
  auth: "admin"
@@ -44463,7 +43714,7 @@ var vacuumControlCapability = {
44463
43714
  scope: "device",
44464
43715
  deviceNative: true,
44465
43716
  mode: "singleton",
44466
- deviceTypes: [require_sleep.DeviceType.Vacuum],
43717
+ deviceTypes: [require_err_msg.DeviceType.Vacuum],
44467
43718
  methods: {
44468
43719
  start: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
44469
43720
  kind: "mutation",
@@ -44550,7 +43801,7 @@ var valveCapability = {
44550
43801
  scope: "device",
44551
43802
  deviceNative: true,
44552
43803
  mode: "singleton",
44553
- deviceTypes: [require_sleep.DeviceType.Valve],
43804
+ deviceTypes: [require_err_msg.DeviceType.Valve],
44554
43805
  methods: {
44555
43806
  open: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
44556
43807
  kind: "mutation",
@@ -44605,7 +43856,7 @@ var vibrationCapability = {
44605
43856
  scope: "device",
44606
43857
  deviceNative: true,
44607
43858
  mode: "singleton",
44608
- deviceTypes: [require_sleep.DeviceType.Sensor],
43859
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
44609
43860
  methods: {},
44610
43861
  status: {
44611
43862
  schema: VibrationStatusSchema,
@@ -44665,7 +43916,7 @@ var waterHeaterCapability = {
44665
43916
  scope: "device",
44666
43917
  deviceNative: true,
44667
43918
  mode: "singleton",
44668
- deviceTypes: [require_sleep.DeviceType.WaterHeater],
43919
+ deviceTypes: [require_err_msg.DeviceType.WaterHeater],
44669
43920
  methods: {
44670
43921
  setTargetTemp: require_sleep.method(zod.z.object({
44671
43922
  deviceId: zod.z.number().int().nonnegative(),
@@ -44755,7 +44006,7 @@ var weatherCapability = {
44755
44006
  scope: "device",
44756
44007
  deviceNative: true,
44757
44008
  mode: "singleton",
44758
- deviceTypes: [require_sleep.DeviceType.Weather],
44009
+ deviceTypes: [require_err_msg.DeviceType.Weather],
44759
44010
  methods: {},
44760
44011
  status: {
44761
44012
  schema: WeatherStatusSchema,
@@ -44963,7 +44214,7 @@ var zoneAnalyticsCapability = {
44963
44214
  name: "zone-analytics",
44964
44215
  scope: "device",
44965
44216
  mode: "singleton",
44966
- deviceTypes: [require_sleep.DeviceType.Camera],
44217
+ deviceTypes: [require_err_msg.DeviceType.Camera],
44967
44218
  methods: {
44968
44219
  /** Latest computed occupancy snapshot for this camera. Null when
44969
44220
  * the analytics pipeline hasn't seen a frame for this device yet
@@ -45055,7 +44306,7 @@ var zoneRulesCapability = {
45055
44306
  name: "zone-rules",
45056
44307
  scope: "device",
45057
44308
  mode: "singleton",
45058
- deviceTypes: [require_sleep.DeviceType.Camera],
44309
+ deviceTypes: [require_err_msg.DeviceType.Camera],
45059
44310
  methods: {
45060
44311
  /** Read the full rule list for a given stage (empty when no rules
45061
44312
  * are defined yet). */
@@ -45191,8 +44442,6 @@ function createDeviceExtensionProvider(options) {
45191
44442
  slotRef: extensionSlotRef(source, slot.slot),
45192
44443
  slot: slot.slot,
45193
44444
  authority: listing.authority,
45194
- enabled: listing.enabled,
45195
- projects: EXTENSION_SLOTS[slot.slot].descriptor.projects ?? null,
45196
44445
  activation: listing.activation,
45197
44446
  value: listing.value,
45198
44447
  sources: [...listing.sources],
@@ -45227,44 +44476,68 @@ function createDeviceExtensionProvider(options) {
45227
44476
  * through another composition, is refused at save by name (spec §2).
45228
44477
  *
45229
44478
  * The graph is built for ONE save: the CANDIDATE composition (the one being
45230
- * validated) plus every STORED composition. The candidate ALWAYS replaces any
45231
- * stored peer that targets the same device, regardless of where that stored
45232
- * peer sits in `stored` — whether a save introduces or removes a cycle must
45233
- * never depend on array order. Two DIFFERENT stored peers that (wrongly)
45234
- * target the same device are corrupted state, not a cycle to resolve by
45235
- * picking one silently; that is refused by name (D391).
44479
+ * validated) plus every STORED composition. The candidate replaces its own old
44480
+ * version, identified BY BLOCK ID wherever it sits in `stored` — whether a save
44481
+ * introduces or removes a cycle must never depend on array order. A DIFFERENT
44482
+ * block targeting the candidate's device is refused by name: one composition
44483
+ * per device (D663). Two different stored peers that (wrongly) target the same
44484
+ * device are corrupted state, not a cycle to resolve by picking one silently;
44485
+ * that is refused by name too (D391).
44486
+ *
44487
+ * A customization of an EXISTING device reads that device's NATIVE value for a
44488
+ * field it replaces or does not write ("Invert state" reads the `on` it
44489
+ * replaces): such a self-read is no edge, so it is never a cycle (D663).
44490
+ * Another composition reading that field reads the customizer's output.
45236
44491
  *
45237
44492
  * `findCompositionCycles` reports at least one cycle per cycle-SET (the DFS
45238
- * marks a node `done` after its first expansion) — enough to refuse a save,
45239
- * not to enumerate every elementary cycle through a shared node. A dotted
44493
+ * marks a node `done` after its first expansion) — not every elementary cycle
44494
+ * through a shared node, so the path it names may AVOID the candidate while
44495
+ * one through it goes unreported. What a save refuses is therefore decided per
44496
+ * strongly connected component (`findCandidateCycles`, C-1). A dotted
45240
44497
  * `fieldPath` is matched only as an exact string: a write to `state.value` and
45241
44498
  * a read of `state` would not connect (no nested field writes exist yet).
44499
+ *
44500
+ * An item leaf is its own node, `…#consumables.items[desiccant].status`
44501
+ * (`featureFieldEntries`), so two items sharing an inner path never merge. No
44502
+ * READ can name an array element (`describeStateField` never describes one),
44503
+ * so an item node is write-only: its edges to sources are what the check needs.
45242
44504
  */
45243
- /** Two stored compositions both target the same device — refused by name, never silently resolved. */
44505
+ /** Two blocks' compositions target the same device — refused by name, never silently resolved. */
45244
44506
  var CompositionGraphError = class extends Error {};
44507
+ /** `"cap.field"` of every field a peer writes — the same spelling as `compositionFieldKey`. */
44508
+ function writtenFields(peer) {
44509
+ return new Set(peer.composition.features.flatMap((feature) => feature.kind !== "fields" ? [] : require_composition.featureFieldEntries(feature).map(({ fieldPath }) => `${feature.cap}.${fieldPath}`)));
44510
+ }
44511
+ /** A read of the peer's OWN device is an edge only when it reads the peer's own output (rule 5, D663). */
44512
+ function readsComposedValue(peer, written, read) {
44513
+ if (require_composition.compositionSourceKey(read.source) !== require_composition.compositionSourceKey(peer.target)) return true;
44514
+ const field = `${read.cap}.${read.fieldPath}`;
44515
+ return written.has(field) && peer.replaced?.has(field) !== true;
44516
+ }
45245
44517
  function peerEdges(peer) {
45246
- return peer.composition.features.flatMap((feature) => feature.kind !== "fields" ? [] : Object.entries(feature.fields).map(([path, source]) => [compositionReadKey({
44518
+ const written = writtenFields(peer);
44519
+ return peer.composition.features.flatMap((feature) => feature.kind !== "fields" ? [] : require_composition.featureFieldEntries(feature).map(({ fieldPath, source }) => [require_composition.compositionReadKey({
45247
44520
  source: peer.target,
45248
44521
  cap: feature.cap,
45249
- fieldPath: path
45250
- }), fieldReads(source).map(compositionReadKey)]));
44522
+ fieldPath
44523
+ }), require_composition.fieldReads(source).filter((read) => readsComposedValue(peer, written, read)).map(require_composition.compositionReadKey)]));
45251
44524
  }
45252
44525
  /**
45253
- * Every peer that actually takes part in the graph: each stored peer minus
45254
- * whichever one targets the same device as the candidate (the candidate
45255
- * replaces it — this drop happens before any edge is built, so the
45256
- * candidate's old edges never enter the map to begin with), plus the
45257
- * candidate itself, exactly once. Throws {@link CompositionGraphError} if two
45258
- * DIFFERENT stored peers (neither of them the candidate's own old version)
45259
- * target the same device.
44526
+ * Every peer that actually takes part in the graph: each stored peer minus the
44527
+ * candidate's own old version (same block id — the drop happens before any
44528
+ * edge is built, so its old edges never enter the map), plus the candidate
44529
+ * itself, exactly once. Throws {@link CompositionGraphError} when a DIFFERENT
44530
+ * block targets the candidate's device, or two stored peers share a device.
45260
44531
  */
45261
44532
  function resolvedPeers(candidate, stored) {
45262
- const candidateKey = compositionSourceKey(candidate.target);
44533
+ const candidateKey = require_composition.compositionSourceKey(candidate.target);
45263
44534
  const survivors = /* @__PURE__ */ new Map();
45264
44535
  for (const peer of stored) {
45265
- const key = compositionSourceKey(peer.target);
45266
- if (key === candidateKey) continue;
45267
- if (survivors.has(key)) throw new CompositionGraphError(`two stored compositions both target device \`${key}\` — refusing to build the field graph`);
44536
+ if (peer.blockId === candidate.blockId) continue;
44537
+ const key = require_composition.compositionSourceKey(peer.target);
44538
+ if (key === candidateKey) throw new CompositionGraphError(`block \`${peer.blockId}\` and block \`${candidate.blockId}\` both target device \`${key}\` — one composition per device; delete or edit the existing one`);
44539
+ const clash = survivors.get(key);
44540
+ if (clash !== void 0) throw new CompositionGraphError(`two stored compositions (\`${clash.blockId}\`, \`${peer.blockId}\`) both target device \`${key}\` — refusing to build the field graph`);
45268
44541
  survivors.set(key, peer);
45269
44542
  }
45270
44543
  return [...survivors.values(), candidate];
@@ -45293,172 +44566,215 @@ function findCompositionCycles(candidate, stored) {
45293
44566
  for (const node of edges.keys()) visit(node, []);
45294
44567
  return [...found.values()];
45295
44568
  }
44569
+ /** Tarjan's strongly connected components over every node of the graph (sources included). */
44570
+ function stronglyConnected(edges) {
44571
+ const index = /* @__PURE__ */ new Map();
44572
+ const low = /* @__PURE__ */ new Map();
44573
+ const onStack = /* @__PURE__ */ new Set();
44574
+ const stack = [];
44575
+ const components = [];
44576
+ const connect = (node) => {
44577
+ index.set(node, index.size);
44578
+ low.set(node, index.get(node) ?? 0);
44579
+ stack.push(node);
44580
+ onStack.add(node);
44581
+ for (const next of edges.get(node) ?? []) if (!index.has(next)) {
44582
+ connect(next);
44583
+ low.set(node, Math.min(low.get(node) ?? 0, low.get(next) ?? 0));
44584
+ } else if (onStack.has(next)) low.set(node, Math.min(low.get(node) ?? 0, index.get(next) ?? 0));
44585
+ if (low.get(node) !== index.get(node)) return;
44586
+ const component = [];
44587
+ for (let member = stack.pop(); member !== void 0; member = stack.pop()) {
44588
+ onStack.delete(member);
44589
+ component.push(member);
44590
+ if (member === node) break;
44591
+ }
44592
+ components.push(component);
44593
+ };
44594
+ for (const node of edges.keys()) if (!index.has(node)) connect(node);
44595
+ return components;
44596
+ }
44597
+ /** The shortest walk from `start` back to itself inside `component` (one exists: the component has a cycle). */
44598
+ function cycleThrough(start, component, edges) {
44599
+ const parent = /* @__PURE__ */ new Map();
44600
+ const queue = [start];
44601
+ for (let head = 0; head < queue.length; head += 1) {
44602
+ const node = queue[head];
44603
+ if (node === void 0) break;
44604
+ for (const next of edges.get(node) ?? []) {
44605
+ if (next === start) {
44606
+ const chain = [];
44607
+ for (let at = node; at !== void 0 && at !== start; at = parent.get(at)) chain.push(at);
44608
+ return [
44609
+ start,
44610
+ ...chain.reverse(),
44611
+ start
44612
+ ];
44613
+ }
44614
+ if (!component.has(next) || parent.has(next)) continue;
44615
+ parent.set(next, node);
44616
+ queue.push(next);
44617
+ }
44618
+ }
44619
+ return [start, start];
44620
+ }
44621
+ /**
44622
+ * The cycles THIS save closes, decided per strongly connected component — never
44623
+ * per reported DFS path, which names one back-edge per revisit and can report a
44624
+ * component's cycle by a path that avoids the candidate while the one through it
44625
+ * goes unreported. A component refuses when it holds a candidate node and a
44626
+ * cycle (more than one node, or a self-loop). Components without a candidate
44627
+ * node are not this save's cycles.
44628
+ */
44629
+ function findCandidateCycles(candidate, stored) {
44630
+ const edges = compositionEdges(candidate, stored);
44631
+ const prefix = `${require_composition.compositionSourceKey(candidate.target)}#`;
44632
+ return stronglyConnected(edges).flatMap((members) => {
44633
+ const start = members.find((n) => n.startsWith(prefix));
44634
+ if (start === void 0) return [];
44635
+ const component = new Set(members);
44636
+ const selfLoop = members.length === 1 && (edges.get(start) ?? []).includes(start);
44637
+ if (members.length === 1 && !selfLoop) return [];
44638
+ return [{
44639
+ path: cycleThrough(start, component, edges),
44640
+ component
44641
+ }];
44642
+ });
44643
+ }
45296
44644
  //#endregion
45297
- //#region src/composition/evaluate-field.ts
45298
- function describeRead(read) {
45299
- return `${compositionSourceKey(read.source)} ${read.cap}.${read.fieldPath}`;
44645
+ //#region src/device/schema-fields.ts
44646
+ /** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
44647
+ * Zod v4 exposes `.unwrap()` on all three wrapper classes. */
44648
+ function unwrapSchema(schema) {
44649
+ let s = schema;
44650
+ while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
44651
+ return s;
45300
44652
  }
45301
- function fromReading(reading, read) {
45302
- if (reading.state === "unavailable") return {
45303
- state: "unavailable",
45304
- value: null,
45305
- reason: `${describeRead(read)}: ${reading.reason}`,
45306
- fatal: reading.fatal
45307
- };
45308
- return {
45309
- state: reading.state,
45310
- value: reading.value,
45311
- reason: reading.state === "stale" ? `${describeRead(read)}: ${reading.reason}` : null,
45312
- deadline: null
44653
+ function leafKind(schema) {
44654
+ const s = unwrapSchema(schema);
44655
+ if (s instanceof zod.z.ZodEnum) return {
44656
+ kind: "enum",
44657
+ enumValues: s.options
45313
44658
  };
44659
+ if (s instanceof zod.z.ZodNumber) return { kind: "number" };
44660
+ if (s instanceof zod.z.ZodString) return { kind: "string" };
44661
+ if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
44662
+ return null;
45314
44663
  }
45315
- function expressionOutcome(source, read, now) {
45316
- const values = {};
45317
- const staleReasons = [];
45318
- let fatalUnavailable = null;
45319
- let nonFatalUnavailable = null;
45320
- for (const [name, binding] of Object.entries(source.bindings)) {
45321
- if (binding.kind === "literal") {
45322
- values[name] = binding.value;
45323
- continue;
45324
- }
45325
- const reading = read(binding);
45326
- if (reading.state === "unavailable") {
45327
- const outcome = {
45328
- state: "unavailable",
45329
- value: null,
45330
- reason: `binding \`${name}\` (${describeRead(binding)}): ${reading.reason}`,
45331
- fatal: reading.fatal
45332
- };
45333
- if (reading.fatal) {
45334
- fatalUnavailable = outcome;
45335
- break;
45336
- }
45337
- if (nonFatalUnavailable === null) nonFatalUnavailable = outcome;
44664
+ /** Walk a cap status schema into flat wireable leaf fields (dotted paths).
44665
+ * Recurses into nested ZodObject (unwrapping nullable/optional/default first),
44666
+ * so fields with null live values are still offered. Arrays / unknown shapes
44667
+ * are skipped — never throws. */
44668
+ function enumerateSchemaFields(schema, prefix = "") {
44669
+ const root = unwrapSchema(schema);
44670
+ if (!(root instanceof zod.z.ZodObject)) return [];
44671
+ const out = [];
44672
+ const shape = root.shape;
44673
+ for (const [key, field] of Object.entries(shape)) {
44674
+ const path = prefix ? `${prefix}.${key}` : key;
44675
+ const inner = unwrapSchema(field);
44676
+ if (inner instanceof zod.z.ZodObject) {
44677
+ out.push(...enumerateSchemaFields(inner, path));
45338
44678
  continue;
45339
44679
  }
45340
- values[name] = reading.value;
45341
- if (reading.state === "stale") staleReasons.push(`\`${name}\`: ${reading.reason}`);
44680
+ const leaf = leafKind(field);
44681
+ if (leaf) out.push({
44682
+ path,
44683
+ kind: leaf.kind,
44684
+ ...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
44685
+ });
45342
44686
  }
45343
- if (fatalUnavailable !== null) return fatalUnavailable;
45344
- if (nonFatalUnavailable !== null) return nonFatalUnavailable;
45345
- const deadlines = [];
45346
- const result = evaluateExpressionSource(source.expr, values, now, { hooks: { noteDeadline: (at) => deadlines.push(at) } });
45347
- if (!result.ok) return {
45348
- state: "unavailable",
45349
- value: null,
45350
- reason: `the expression failed: ${result.error}`,
45351
- fatal: false
45352
- };
45353
- return {
45354
- state: staleReasons.length > 0 ? "stale" : "fresh",
45355
- value: result.value,
45356
- reason: staleReasons.length > 0 ? staleReasons.join("; ") : null,
45357
- deadline: deadlines.length > 0 ? Math.min(...deadlines) : null
45358
- };
44687
+ return out;
45359
44688
  }
45360
- function evaluateCompositionField(input) {
45361
- const { source } = input;
45362
- switch (source.kind) {
45363
- case "from": return fromReading(input.read(source), source);
45364
- case "expression": return expressionOutcome(source, input.read, input.now);
45365
- case "code": return {
45366
- state: "unavailable",
45367
- value: null,
45368
- reason: "code fields are not runnable in the composer — they arrive in slice 4",
45369
- fatal: true
45370
- };
45371
- }
44689
+ /** Enumerate the per-item wireable fields of an item-array cap (see
44690
+ * `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
44691
+ * `keyField` (the key comes from the link's `itemKey`, never from a wired
44692
+ * source), each tagged `item: true` so the authoring UI collects an
44693
+ * `itemKey` alongside the field. Never throws. */
44694
+ function enumerateItemArrayFields(itemArray) {
44695
+ return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
44696
+ ...f,
44697
+ item: true
44698
+ }));
45372
44699
  }
45373
44700
  //#endregion
45374
- //#region src/composition/examples.ts
44701
+ //#region src/composition/state-fields.ts
45375
44702
  /**
45376
- * The worked composed-device example: a ROOM. Temperature is the average of N
45377
- * sensors, and occupancy is any motion within the last five minutes. It is the
45378
- * slice-1 acceptance case and the template an author starts from (Phase 4
45379
- * §4 item 4 asked for one).
44703
+ * What a capability's state field IS: kind, nullability, whether required. The
44704
+ * validator needs nullability, which `enumerateSchemaFields` does not report,
44705
+ * and that decides a field's unavailable mode (D393).
45380
44706
  */
45381
- var ROOM_OCCUPANCY_WINDOW_MS = 3e5;
45382
- var ROOM_EXAMPLE_TEMPERATURE_SOURCES = [
45383
- {
45384
- addonId: "provider-homeassistant",
45385
- stableId: "sensor.living_room_temperature"
45386
- },
45387
- {
45388
- addonId: "provider-homeassistant",
45389
- stableId: "sensor.sofa_temperature"
45390
- },
45391
- {
45392
- addonId: "provider-homeassistant",
45393
- stableId: "sensor.window_temperature"
44707
+ function fieldAt(schema, path) {
44708
+ let current = schema;
44709
+ for (const segment of path.split(".")) {
44710
+ const container = unwrapSchema(current);
44711
+ if (!(container instanceof zod.z.ZodObject)) return null;
44712
+ const next = container.shape[segment];
44713
+ if (!(next instanceof zod.z.ZodType)) return null;
44714
+ current = next;
45394
44715
  }
45395
- ];
45396
- var ROOM_EXAMPLE_MOTION_SOURCES = [{
45397
- addonId: "provider-homeassistant",
45398
- stableId: "binary_sensor.living_room_motion"
45399
- }, {
45400
- addonId: "provider-homeassistant",
45401
- stableId: "binary_sensor.living_room_presence"
45402
- }];
45403
- function from(source, cap, fieldPath) {
44716
+ return current;
44717
+ }
44718
+ function describeStateField(schema, path) {
44719
+ const leaf = enumerateSchemaFields(schema).find((f) => f.path === path);
44720
+ if (leaf === void 0) return null;
44721
+ const field = fieldAt(schema, path);
44722
+ if (field === null) return null;
45404
44723
  return {
45405
- kind: "from",
45406
- source,
45407
- cap,
45408
- fieldPath
44724
+ path,
44725
+ kind: leaf.kind,
44726
+ nullable: field.safeParse(null).success,
44727
+ required: !field.safeParse(void 0).success,
44728
+ ...leaf.enumValues === void 0 ? {} : { enumValues: leaf.enumValues }
45409
44729
  };
45410
44730
  }
45411
- function named(prefix, sources, cap, fieldPath) {
45412
- return Object.fromEntries(sources.map((s, i) => [`${prefix}${i + 1}`, from(s, cap, fieldPath)]));
44731
+ function describeTopLevelStateFields(schema) {
44732
+ return new Map(enumerateSchemaFields(schema).filter((f) => !f.path.includes(".")).flatMap((f) => {
44733
+ const described = describeStateField(schema, f.path);
44734
+ return described === null ? [] : [[f.path, described]];
44735
+ }));
45413
44736
  }
45414
- function roomCompositionExample(input) {
45415
- const t = named("t", input.temperatureSources, "temperature-sensor", "celsius");
45416
- const f = named("f", input.temperatureSources, "temperature-sensor", "lastFetchedAt");
45417
- const d = named("d", input.motionSources, "motion", "detected");
45418
- const m = named("m", input.motionSources, "motion", "lastDetectedAt");
45419
- const windows = Object.keys(m).map((name) => `within(${name}, ${input.occupancyWindowMs})`);
45420
- return CompositionSchema.parse({
45421
- target: {
45422
- kind: "new",
45423
- type: require_sleep.DeviceType.Sensor
45424
- },
45425
- features: [{
45426
- kind: "fields",
45427
- cap: "temperature-sensor",
45428
- fields: {
45429
- celsius: {
45430
- kind: "expression",
45431
- expr: `round(avg(${Object.keys(t).join(", ")}), 1)`,
45432
- bindings: t
45433
- },
45434
- lastFetchedAt: {
45435
- kind: "expression",
45436
- expr: `max(${Object.keys(f).join(", ")})`,
45437
- bindings: f
45438
- }
45439
- }
45440
- }, {
45441
- kind: "fields",
45442
- cap: "binary",
45443
- fields: {
45444
- on: {
45445
- kind: "expression",
45446
- expr: `any(${[...Object.keys(d), ...windows].join(", ")})`,
45447
- bindings: {
45448
- ...d,
45449
- ...m
45450
- }
45451
- },
45452
- lastChangedAt: {
45453
- kind: "expression",
45454
- expr: "now",
45455
- bindings: {}
45456
- }
45457
- }
45458
- }]
44737
+ /** Top-level keys a value MUST carry: those whose schema refuses `undefined`. */
44738
+ function requiredTopLevelKeys(schema) {
44739
+ const root = unwrapSchema(schema);
44740
+ if (!(root instanceof zod.z.ZodObject)) return [];
44741
+ return Object.entries(root.shape).flatMap(([key, field]) => field instanceof zod.z.ZodType && !field.safeParse(void 0).success ? [key] : []);
44742
+ }
44743
+ /**
44744
+ * The proper prefixes of `path` whose schema accepts `undefined`: a required
44745
+ * leaf under one of them is required only once something writes that parent
44746
+ * (`remaining.unit` is required only when `remaining` is present).
44747
+ */
44748
+ function optionalAncestors(schema, path) {
44749
+ const segments = path.split(".");
44750
+ return segments.slice(0, -1).flatMap((_, i) => {
44751
+ const prefix = segments.slice(0, i + 1).join(".");
44752
+ const field = fieldAt(schema, prefix);
44753
+ return field !== null && field.safeParse(void 0).success ? [prefix] : [];
45459
44754
  });
45460
44755
  }
45461
44756
  //#endregion
44757
+ //#region src/composition/plan-result.ts
44758
+ function problem(code, path, message) {
44759
+ return {
44760
+ code,
44761
+ path,
44762
+ message
44763
+ };
44764
+ }
44765
+ function refused(...problems) {
44766
+ return {
44767
+ problems,
44768
+ fields: [],
44769
+ features: []
44770
+ };
44771
+ }
44772
+ /** Where a planned field sits in the composition: `features[N].fields.<path>` or `features[N].items.<key>.fields.<path>`. */
44773
+ function compositionFieldAt(field) {
44774
+ const inner = field.item === void 0 ? "fields" : `items.${field.item.key}.fields`;
44775
+ return `features[${field.featureIndex}].${inner}.${field.path}`;
44776
+ }
44777
+ //#endregion
45462
44778
  //#region src/composition/expression-kinds.ts
45463
44779
  var NUMBER = {
45464
44780
  kind: "fixed",
@@ -45490,6 +44806,10 @@ var BUILTIN_RESULT_KINDS = Object.freeze({
45490
44806
  latest: {
45491
44807
  kind: "fixed",
45492
44808
  result: ["number", "null"]
44809
+ },
44810
+ number: {
44811
+ kind: "fixed",
44812
+ result: ["number", "null"]
45493
44813
  }
45494
44814
  });
45495
44815
  var KindInferenceError = class extends Error {};
@@ -45525,7 +44845,7 @@ function arityRange(builtin) {
45525
44845
  return `${builtin.minArgs}–${builtin.maxArgs}`;
45526
44846
  }
45527
44847
  function callKinds(callee, args) {
45528
- const builtin = EXPRESSION_BUILTINS[callee];
44848
+ const builtin = require_composition.EXPRESSION_BUILTINS[callee];
45529
44849
  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)}`);
45530
44850
  const rule = BUILTIN_RESULT_KINDS[callee];
45531
44851
  if (rule === void 0) throw new KindInferenceError(`no result-kind rule for builtin '${callee}'`);
@@ -45550,7 +44870,7 @@ function kindsOf(node, bindings) {
45550
44870
  }
45551
44871
  }
45552
44872
  function inferExpressionKinds(input) {
45553
- const compiled = compileExpressionSafe(input.expr);
44873
+ const compiled = require_composition.compileExpressionSafe(input.expr);
45554
44874
  if (!compiled.ok) return {
45555
44875
  ok: false,
45556
44876
  error: compiled.error
@@ -45569,129 +44889,14 @@ function inferExpressionKinds(input) {
45569
44889
  }
45570
44890
  }
45571
44891
  //#endregion
45572
- //#region src/device/schema-fields.ts
45573
- /** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
45574
- * Zod v4 exposes `.unwrap()` on all three wrapper classes. */
45575
- function unwrapSchema(schema) {
45576
- let s = schema;
45577
- while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
45578
- return s;
45579
- }
45580
- function leafKind(schema) {
45581
- const s = unwrapSchema(schema);
45582
- if (s instanceof zod.z.ZodEnum) return {
45583
- kind: "enum",
45584
- enumValues: s.options
45585
- };
45586
- if (s instanceof zod.z.ZodNumber) return { kind: "number" };
45587
- if (s instanceof zod.z.ZodString) return { kind: "string" };
45588
- if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
45589
- return null;
45590
- }
45591
- /** Walk a cap status schema into flat wireable leaf fields (dotted paths).
45592
- * Recurses into nested ZodObject (unwrapping nullable/optional/default first),
45593
- * so fields with null live values are still offered. Arrays / unknown shapes
45594
- * are skipped — never throws. */
45595
- function enumerateSchemaFields(schema, prefix = "") {
45596
- const root = unwrapSchema(schema);
45597
- if (!(root instanceof zod.z.ZodObject)) return [];
45598
- const out = [];
45599
- const shape = root.shape;
45600
- for (const [key, field] of Object.entries(shape)) {
45601
- const path = prefix ? `${prefix}.${key}` : key;
45602
- const inner = unwrapSchema(field);
45603
- if (inner instanceof zod.z.ZodObject) {
45604
- out.push(...enumerateSchemaFields(inner, path));
45605
- continue;
45606
- }
45607
- const leaf = leafKind(field);
45608
- if (leaf) out.push({
45609
- path,
45610
- kind: leaf.kind,
45611
- ...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
45612
- });
45613
- }
45614
- return out;
45615
- }
45616
- /** Enumerate the per-item wireable fields of an item-array cap (see
45617
- * `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
45618
- * `keyField` (the key comes from the link's `itemKey`, never from a wired
45619
- * source), each tagged `item: true` so the authoring UI collects an
45620
- * `itemKey` alongside the field. Never throws. */
45621
- function enumerateItemArrayFields(itemArray) {
45622
- return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
45623
- ...f,
45624
- item: true
45625
- }));
45626
- }
45627
- //#endregion
45628
- //#region src/composition/state-fields.ts
44892
+ //#region src/composition/field-kinds.ts
45629
44893
  /**
45630
- * What a capability's state field IS: kind, nullability, whether required. The
45631
- * validator needs nullability, which `enumerateSchemaFields` does not report,
45632
- * and that decides a field's unavailable mode (D393).
44894
+ * The kind half of planning a composition field (D659): which kinds a source
44895
+ * can produce, whether they fit a target field, and the field's role.
44896
+ * Shared by the top-level planner (`validate-composition.ts`) and the item
44897
+ * planner (`composition-items.ts`). Internal to the composition module — NOT
44898
+ * in the barrel: these are planner internals, not a published contract.
45633
44899
  */
45634
- function fieldAt(schema, path) {
45635
- let current = schema;
45636
- for (const segment of path.split(".")) {
45637
- const container = unwrapSchema(current);
45638
- if (!(container instanceof zod.z.ZodObject)) return null;
45639
- const next = container.shape[segment];
45640
- if (!(next instanceof zod.z.ZodType)) return null;
45641
- current = next;
45642
- }
45643
- return current;
45644
- }
45645
- function describeStateField(schema, path) {
45646
- const leaf = enumerateSchemaFields(schema).find((f) => f.path === path);
45647
- if (leaf === void 0) return null;
45648
- const field = fieldAt(schema, path);
45649
- if (field === null) return null;
45650
- return {
45651
- path,
45652
- kind: leaf.kind,
45653
- nullable: field.safeParse(null).success,
45654
- required: !field.safeParse(void 0).success,
45655
- ...leaf.enumValues === void 0 ? {} : { enumValues: leaf.enumValues }
45656
- };
45657
- }
45658
- function describeTopLevelStateFields(schema) {
45659
- return new Map(enumerateSchemaFields(schema).filter((f) => !f.path.includes(".")).flatMap((f) => {
45660
- const described = describeStateField(schema, f.path);
45661
- return described === null ? [] : [[f.path, described]];
45662
- }));
45663
- }
45664
- /** Top-level keys a value MUST carry: those whose schema refuses `undefined`. */
45665
- function requiredTopLevelKeys(schema) {
45666
- const root = unwrapSchema(schema);
45667
- if (!(root instanceof zod.z.ZodObject)) return [];
45668
- return Object.entries(root.shape).flatMap(([key, field]) => field instanceof zod.z.ZodType && !field.safeParse(void 0).success ? [key] : []);
45669
- }
45670
- //#endregion
45671
- //#region src/composition/validate-composition.ts
45672
- function buildCapabilityLookup(defs) {
45673
- const byName = new Map(defs.map((d) => [d.name, d]));
45674
- return (capName) => byName.get(capName) ?? null;
45675
- }
45676
- /** Caps every `BaseDevice` registers on itself; a second registration throws. */
45677
- var BASE_DEVICE_CAPS = new Set(["device-status", "feature-probe"]);
45678
- var PENDING_SELF = {
45679
- addonId: COMPOSER_ADDON_ID,
45680
- stableId: `${COMPOSED_DEVICE_STABLE_ID_PREFIX}(unsaved)`
45681
- };
45682
- function problem(code, path, message) {
45683
- return {
45684
- code,
45685
- path,
45686
- message
45687
- };
45688
- }
45689
- function refused(...problems) {
45690
- return {
45691
- problems,
45692
- fields: []
45693
- };
45694
- }
45695
44900
  function readKinds(read, lookupCap) {
45696
44901
  const def = lookupCap(read.cap);
45697
44902
  const schema = def?.runtimeState ?? def?.status?.schema;
@@ -45766,7 +44971,7 @@ function compatibility(cap, target, kinds, at) {
45766
44971
  */
45767
44972
  function literalStringValue(source) {
45768
44973
  if (source.kind !== "expression") return null;
45769
- const compiled = compileExpressionSafe(source.expr);
44974
+ const compiled = require_composition.compileExpressionSafe(source.expr);
45770
44975
  if (!compiled.ok) return null;
45771
44976
  const ast = compiled.parsed.ast;
45772
44977
  return ast.kind === "literal" && typeof ast.value === "string" ? ast.value : null;
@@ -45786,78 +44991,604 @@ function enumLiteralProblem(cap, target, source, at) {
45786
44991
  }
45787
44992
  function roleOf(source) {
45788
44993
  if (source.kind !== "expression") return "derived";
45789
- if (fieldReads(source).length > 0) return "derived";
45790
- const compiled = compileExpressionSafe(source.expr);
44994
+ if (require_composition.fieldReads(source).length > 0) return "derived";
44995
+ const compiled = require_composition.compileExpressionSafe(source.expr);
45791
44996
  return compiled.ok && compiled.parsed.identifiers.has("now") ? "clock" : "constant";
45792
44997
  }
45793
- function planFeature(feature, featureIndex, target, lookupCap) {
45794
- const at = `features[${featureIndex}]`;
44998
+ /**
44999
+ * How a planned field says "unavailable", per feature mode and AFTER `roleOf`
45000
+ * (D393, D663 rulings S1 + S1b). The composer cannot take a device it does not
45001
+ * own offline, and D393 forbids a stale number, so on an existing target a
45002
+ * field that READS a source never gets `offline`.
45003
+ */
45004
+ function unavailableFor(tf, role, mode, cap, at) {
45005
+ if (mode === "new" || role !== "derived") return {
45006
+ ok: true,
45007
+ unavailable: tf.nullable ? "null" : "offline"
45008
+ };
45009
+ if (mode === "replace") return {
45010
+ ok: true,
45011
+ unavailable: "release"
45012
+ };
45013
+ if (tf.nullable) return {
45014
+ ok: true,
45015
+ unavailable: "null"
45016
+ };
45017
+ if (tf.enumValues?.includes("unknown") === true) return {
45018
+ ok: true,
45019
+ unavailable: "unknown"
45020
+ };
45021
+ return {
45022
+ ok: false,
45023
+ problem: problem("not-replaceable", at, `\`${cap}.${tf.path}\` reads a source but can say neither null nor 'unknown', and an ADDED capability has no native reading to fall back to (D393, D663)`)
45024
+ };
45025
+ }
45026
+ //#endregion
45027
+ //#region src/composition/composition-items.ts
45028
+ /** Item fields the composer writes itself, with the value it writes. */
45029
+ var COMPOSED_ITEM_FIXED_FIELDS = { resettable: false };
45030
+ function reservedItemFields(itemArray) {
45031
+ return new Set([
45032
+ itemArray.keyField,
45033
+ ...itemArray.labelField === void 0 ? [] : [itemArray.labelField],
45034
+ ...Object.keys(COMPOSED_ITEM_FIXED_FIELDS)
45035
+ ]);
45036
+ }
45037
+ /** A required leaf under an optional parent is required only once something writes that parent. */
45038
+ function leafRequired(itemArray, path, written) {
45039
+ const described = describeStateField(itemArray.itemSchema, path);
45040
+ if (described === null || !described.required) return false;
45041
+ return optionalAncestors(itemArray.itemSchema, path).every((parent) => written.some((w) => w.startsWith(`${parent}.`)));
45042
+ }
45043
+ function missingLeaves({ capName, itemArray, reserved }, item, at) {
45044
+ const written = Object.keys(item.fields);
45045
+ return enumerateSchemaFields(itemArray.itemSchema).flatMap((leaf) => {
45046
+ if (reserved.has(leaf.path) || leaf.path in item.fields) return [];
45047
+ if (!leafRequired(itemArray, leaf.path, written)) return [];
45048
+ const described = describeStateField(itemArray.itemSchema, leaf.path);
45049
+ if (described === null || described.nullable) return [];
45050
+ return [problem("missing-field", `${at}.fields.${leaf.path}`, `\`${capName}.${itemArray.path}[].${leaf.path}\` is required and has no source`)];
45051
+ });
45052
+ }
45053
+ function planItemField(ctx, leaf) {
45054
+ const { capName, itemArray, featureIndex, lookupCap } = ctx;
45055
+ const { key, label, path, source } = leaf;
45056
+ const at = `features[${featureIndex}].items.${key}.fields.${path}`;
45057
+ if (ctx.reserved.has(path)) return refused(problem("item-field-reserved", at, `\`${capName}.${itemArray.path}[].${path}\` is set by the composer itself — it takes no source`));
45058
+ const tf = describeStateField(itemArray.itemSchema, path);
45059
+ if (tf === null) return refused(problem("unknown-field", at, `a \`${capName}\` item has no field \`${path}\``));
45060
+ const kinds = sourceKinds(source, lookupCap);
45061
+ if (!kinds.ok) return refused(problem(kinds.code, at, kinds.message));
45062
+ const role = roleOf(source);
45063
+ const unavailable = unavailableFor(tf, role, ctx.mode, capName, at);
45064
+ if (!unavailable.ok) return refused(unavailable.problem);
45065
+ const plan = {
45066
+ cap: capName,
45067
+ path,
45068
+ item: {
45069
+ arrayPath: itemArray.path,
45070
+ key,
45071
+ label
45072
+ },
45073
+ featureIndex,
45074
+ kind: tf.kind,
45075
+ nullable: tf.nullable,
45076
+ required: tf.required,
45077
+ unavailable: unavailable.unavailable,
45078
+ role,
45079
+ source,
45080
+ reads: [...require_composition.fieldReads(source)]
45081
+ };
45082
+ const enumProblem = enumLiteralProblem(capName, tf, source, at);
45083
+ return {
45084
+ problems: [...compatibility(capName, tf, kinds.kinds, at), ...enumProblem === null ? [] : [enumProblem]],
45085
+ fields: [plan],
45086
+ features: []
45087
+ };
45088
+ }
45089
+ function planItems(def, feature, featureIndex, lookupCap, mode = "new") {
45090
+ const at = `features[${featureIndex}].items`;
45091
+ const itemArray = def.status?.itemArray;
45092
+ if (itemArray === void 0) return refused(problem("not-item-array", at, `\`${def.name}\` has no item array — it takes no \`items\``));
45093
+ if (itemArray.path !== "items") return refused(problem("not-item-array", at, `\`${def.name}\`'s item array is at \`${itemArray.path}\`, not \`${require_composition.COMPOSITION_ITEM_ARRAY_PATH}\` — not composable yet`));
45094
+ const ctx = {
45095
+ capName: def.name,
45096
+ itemArray,
45097
+ reserved: reservedItemFields(itemArray),
45098
+ featureIndex,
45099
+ lookupCap,
45100
+ mode
45101
+ };
45102
+ const results = Object.entries(feature.items ?? {}).flatMap(([key, item]) => [{
45103
+ problems: missingLeaves(ctx, item, `${at}.${key}`),
45104
+ fields: [],
45105
+ features: []
45106
+ }, ...Object.entries(item.fields).map(([path, source]) => planItemField(ctx, {
45107
+ key,
45108
+ label: item.label,
45109
+ path,
45110
+ source
45111
+ }))]);
45112
+ return {
45113
+ problems: results.flatMap((r) => r.problems),
45114
+ fields: results.flatMap((r) => r.fields),
45115
+ features: []
45116
+ };
45117
+ }
45118
+ //#endregion
45119
+ //#region src/composition/evaluate-field.ts
45120
+ function describeRead(read) {
45121
+ return `${require_composition.compositionSourceKey(read.source)} ${read.cap}.${read.fieldPath}`;
45122
+ }
45123
+ function isConfirmed(reading) {
45124
+ return reading.fatal || reading.confirmed === true;
45125
+ }
45126
+ function fromReading(reading, read) {
45127
+ if (reading.state === "unavailable") return {
45128
+ state: "unavailable",
45129
+ value: null,
45130
+ reason: `${describeRead(read)}: ${reading.reason}`,
45131
+ fatal: reading.fatal,
45132
+ confirmed: isConfirmed(reading)
45133
+ };
45134
+ return {
45135
+ state: reading.state,
45136
+ value: reading.value,
45137
+ reason: reading.state === "stale" ? `${describeRead(read)}: ${reading.reason}` : null,
45138
+ deadline: null
45139
+ };
45140
+ }
45141
+ function expressionOutcome(source, read, now) {
45142
+ const values = {};
45143
+ const staleReasons = [];
45144
+ let fatalUnavailable = null;
45145
+ let nonFatalUnavailable = null;
45146
+ for (const [name, binding] of Object.entries(source.bindings)) {
45147
+ if (binding.kind === "literal") {
45148
+ values[name] = binding.value;
45149
+ continue;
45150
+ }
45151
+ const reading = read(binding);
45152
+ if (reading.state === "unavailable") {
45153
+ const outcome = {
45154
+ state: "unavailable",
45155
+ value: null,
45156
+ reason: `binding \`${name}\` (${describeRead(binding)}): ${reading.reason}`,
45157
+ fatal: reading.fatal,
45158
+ confirmed: isConfirmed(reading)
45159
+ };
45160
+ if (reading.fatal) {
45161
+ fatalUnavailable = outcome;
45162
+ break;
45163
+ }
45164
+ if (nonFatalUnavailable === null || outcome.confirmed && !nonFatalUnavailable.confirmed) nonFatalUnavailable = outcome;
45165
+ continue;
45166
+ }
45167
+ values[name] = reading.value;
45168
+ if (reading.state === "stale") staleReasons.push(`\`${name}\`: ${reading.reason}`);
45169
+ }
45170
+ if (fatalUnavailable !== null) return fatalUnavailable;
45171
+ if (nonFatalUnavailable !== null) return nonFatalUnavailable;
45172
+ const deadlines = [];
45173
+ const result = require_composition.evaluateExpressionSource(source.expr, values, now, { hooks: { noteDeadline: (at) => deadlines.push(at) } });
45174
+ if (!result.ok) return {
45175
+ state: "unavailable",
45176
+ value: null,
45177
+ reason: `the expression failed: ${result.error}`,
45178
+ fatal: false,
45179
+ confirmed: true
45180
+ };
45181
+ return {
45182
+ state: staleReasons.length > 0 ? "stale" : "fresh",
45183
+ value: result.value,
45184
+ reason: staleReasons.length > 0 ? staleReasons.join("; ") : null,
45185
+ deadline: deadlines.length > 0 ? Math.min(...deadlines) : null
45186
+ };
45187
+ }
45188
+ function evaluateCompositionField(input) {
45189
+ const { source } = input;
45190
+ switch (source.kind) {
45191
+ case "from": return fromReading(input.read(source), source);
45192
+ case "expression": return expressionOutcome(source, input.read, input.now);
45193
+ case "code": return {
45194
+ state: "unavailable",
45195
+ value: null,
45196
+ reason: "code fields are not runnable in the composer — they arrive in slice 4",
45197
+ fatal: true,
45198
+ confirmed: true
45199
+ };
45200
+ }
45201
+ }
45202
+ //#endregion
45203
+ //#region src/composition/examples.ts
45204
+ /**
45205
+ * The worked composed-device example: a ROOM. Temperature is the average of N
45206
+ * sensors, and occupancy is any motion within the last five minutes. It is the
45207
+ * slice-1 acceptance case and the template an author starts from (Phase 4
45208
+ * §4 item 4 asked for one).
45209
+ */
45210
+ var ROOM_OCCUPANCY_WINDOW_MS = 3e5;
45211
+ var ROOM_EXAMPLE_TEMPERATURE_SOURCES = [
45212
+ {
45213
+ addonId: "provider-homeassistant",
45214
+ stableId: "sensor.living_room_temperature"
45215
+ },
45216
+ {
45217
+ addonId: "provider-homeassistant",
45218
+ stableId: "sensor.sofa_temperature"
45219
+ },
45220
+ {
45221
+ addonId: "provider-homeassistant",
45222
+ stableId: "sensor.window_temperature"
45223
+ }
45224
+ ];
45225
+ var ROOM_EXAMPLE_MOTION_SOURCES = [{
45226
+ addonId: "provider-homeassistant",
45227
+ stableId: "binary_sensor.living_room_motion"
45228
+ }, {
45229
+ addonId: "provider-homeassistant",
45230
+ stableId: "binary_sensor.living_room_presence"
45231
+ }];
45232
+ function from(source, cap, fieldPath) {
45233
+ return {
45234
+ kind: "from",
45235
+ source,
45236
+ cap,
45237
+ fieldPath
45238
+ };
45239
+ }
45240
+ function named(prefix, sources, cap, fieldPath) {
45241
+ return Object.fromEntries(sources.map((s, i) => [`${prefix}${i + 1}`, from(s, cap, fieldPath)]));
45242
+ }
45243
+ function roomCompositionExample(input) {
45244
+ const t = named("t", input.temperatureSources, "temperature-sensor", "celsius");
45245
+ const f = named("f", input.temperatureSources, "temperature-sensor", "lastFetchedAt");
45246
+ const d = named("d", input.motionSources, "motion", "detected");
45247
+ const m = named("m", input.motionSources, "motion", "lastDetectedAt");
45248
+ const windows = Object.keys(m).map((name) => `within(${name}, ${input.occupancyWindowMs})`);
45249
+ return require_composition.CompositionSchema.parse({
45250
+ target: {
45251
+ kind: "new",
45252
+ type: require_err_msg.DeviceType.Sensor
45253
+ },
45254
+ features: [{
45255
+ kind: "fields",
45256
+ cap: "temperature-sensor",
45257
+ fields: {
45258
+ celsius: {
45259
+ kind: "expression",
45260
+ expr: `round(avg(${Object.keys(t).join(", ")}), 1)`,
45261
+ bindings: t
45262
+ },
45263
+ lastFetchedAt: {
45264
+ kind: "expression",
45265
+ expr: `max(${Object.keys(f).join(", ")})`,
45266
+ bindings: f
45267
+ }
45268
+ }
45269
+ }, {
45270
+ kind: "fields",
45271
+ cap: "binary",
45272
+ fields: {
45273
+ on: {
45274
+ kind: "expression",
45275
+ expr: `any(${[...Object.keys(d), ...windows].join(", ")})`,
45276
+ bindings: {
45277
+ ...d,
45278
+ ...m
45279
+ }
45280
+ },
45281
+ lastChangedAt: {
45282
+ kind: "expression",
45283
+ expr: "now",
45284
+ bindings: {}
45285
+ }
45286
+ }
45287
+ }]
45288
+ });
45289
+ }
45290
+ /**
45291
+ * The slice-2 worked customization (D663): the PetKit "Fresh Element Infinity"
45292
+ * feeder as Home Assistant imports it — ONE `Container` with a child per entity
45293
+ * (`<container stableId>-<entityId>`), and NO native battery (its
45294
+ * `binary_sensor.*_battery` is `battery_charging`, which `isBatteryEntity` does
45295
+ * not match). The customization ADDS `consumables` (the desiccant) and
45296
+ * `battery`. It is the Task 14 acceptance case and the template slice 5's
45297
+ * presets start from.
45298
+ *
45299
+ * Every read names what the HA provider REALLY registers (read 2026-09-28):
45300
+ * the desiccant sensor has a unit (`d`) but no device_class, so HA hosts it as
45301
+ * an `enum-sensor` whose `value` is the raw STRING state — `number(d)` reads it,
45302
+ * and anything that is not a number is `null`, never 0 (D393). The battery
45303
+ * status sensor is an `enum-sensor` too (`unavailable` keeps its cold `''`, so
45304
+ * `percentage` is `null`). `charging` maps `battery_charging` off to
45305
+ * `'unknown'`, never `'none'`: off does not prove "on battery alone" (the
45306
+ * operator confirms the real semantics live).
45307
+ */
45308
+ var FEEDER_EXAMPLE_HA_DEVICE_ID = "8dec3b512fda40cbb8cd121c7a99f890";
45309
+ var FEEDER_EXAMPLE_ADDON_ID = "provider-homeassistant";
45310
+ /** The HA container's stableId (`ha-discovery.ts`) and a child's (`<container>-<entityId>`). */
45311
+ function feederExampleSources(brokerId) {
45312
+ const feeder = {
45313
+ addonId: FEEDER_EXAMPLE_ADDON_ID,
45314
+ stableId: `ha:${brokerId}:dev:${FEEDER_EXAMPLE_HA_DEVICE_ID}`
45315
+ };
45316
+ const child = (entityId) => ({
45317
+ addonId: FEEDER_EXAMPLE_ADDON_ID,
45318
+ stableId: `${feeder.stableId}-${entityId}`
45319
+ });
45320
+ return {
45321
+ feeder,
45322
+ desiccantDays: child("sensor.freshelement_3_desiccant_days_remaining"),
45323
+ batteryStatus: child("sensor.freshelement_3_battery_status"),
45324
+ batteryCharging: child("binary_sensor.freshelement_3_battery")
45325
+ };
45326
+ }
45327
+ function feederCustomizationExample(sources) {
45328
+ const days = { d: from(sources.desiccantDays, "enum-sensor", "value") };
45329
+ return require_composition.CompositionSchema.parse({
45330
+ target: {
45331
+ kind: "existing",
45332
+ device: sources.feeder
45333
+ },
45334
+ features: [{
45335
+ kind: "fields",
45336
+ cap: "consumables",
45337
+ fields: {
45338
+ lastChangedAt: {
45339
+ kind: "expression",
45340
+ expr: "now",
45341
+ bindings: {}
45342
+ },
45343
+ lastFetchedAt: {
45344
+ kind: "expression",
45345
+ expr: "now",
45346
+ bindings: {}
45347
+ }
45348
+ },
45349
+ items: { desiccant: {
45350
+ label: "Desiccant",
45351
+ fields: {
45352
+ "remaining.value": {
45353
+ kind: "expression",
45354
+ expr: "number(d)",
45355
+ bindings: days
45356
+ },
45357
+ "remaining.unit": {
45358
+ kind: "expression",
45359
+ expr: "\"days\"",
45360
+ bindings: {}
45361
+ },
45362
+ status: {
45363
+ kind: "expression",
45364
+ expr: "number(d) == null ? null : (number(d) <= 0 ? \"replace\" : \"ok\")",
45365
+ bindings: days
45366
+ }
45367
+ }
45368
+ } }
45369
+ }, {
45370
+ kind: "fields",
45371
+ cap: "battery",
45372
+ fields: {
45373
+ percentage: {
45374
+ kind: "expression",
45375
+ expr: "s == \"low\" ? 15 : (s == \"normal\" ? 100 : null)",
45376
+ bindings: { s: from(sources.batteryStatus, "enum-sensor", "value") }
45377
+ },
45378
+ binary: {
45379
+ kind: "expression",
45380
+ expr: "true",
45381
+ bindings: {}
45382
+ },
45383
+ charging: {
45384
+ kind: "expression",
45385
+ expr: "c ? \"dc\" : \"unknown\"",
45386
+ bindings: { c: from(sources.batteryCharging, "binary", "on") }
45387
+ },
45388
+ sleeping: {
45389
+ kind: "expression",
45390
+ expr: "false",
45391
+ bindings: {}
45392
+ },
45393
+ lastUpdated: {
45394
+ kind: "expression",
45395
+ expr: "now",
45396
+ bindings: {}
45397
+ }
45398
+ }
45399
+ }]
45400
+ });
45401
+ }
45402
+ //#endregion
45403
+ //#region src/composition/validate-composition.ts
45404
+ function buildCapabilityLookup(defs) {
45405
+ const byName = new Map(defs.map((d) => [d.name, d]));
45406
+ return (capName) => byName.get(capName) ?? null;
45407
+ }
45408
+ /** Caps every `BaseDevice` registers on itself; a second registration throws. */
45409
+ var BASE_DEVICE_CAPS = new Set(["device-status", "feature-probe"]);
45410
+ var PENDING_SELF = {
45411
+ addonId: require_composition.COMPOSER_ADDON_ID,
45412
+ stableId: `${require_composition.COMPOSED_DEVICE_STABLE_ID_PREFIX}(unsaved)`
45413
+ };
45414
+ /** The candidate's block id before its first save. Stored block ids are `randomUUID()`, so none can carry it. */
45415
+ var PENDING_BLOCK_ID = "(unsaved)";
45416
+ function composableCap(feature, at, lookupCap) {
45795
45417
  const def = lookupCap(feature.cap);
45796
- if (def === null) return refused(problem("unknown-capability", `${at}.cap`, `no capability is named \`${feature.cap}\``));
45797
- 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`));
45418
+ if (def === null) return problem("unknown-capability", `${at}.cap`, `no capability is named \`${feature.cap}\``);
45419
+ if (BASE_DEVICE_CAPS.has(def.name)) return problem("not-composable", `${at}.cap`, `\`${def.name}\` is registered by every device itself — it cannot be composed`);
45798
45420
  const state = def.runtimeState;
45799
- if (def.scope !== "device" || state === void 0) return refused(problem("not-composable", `${at}.cap`, `\`${def.name}\` has no per-device state to compose`));
45800
- const capProblems = [];
45801
- 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(", ")})`));
45802
- const served = Object.entries(def.methods).filter(([, m]) => m.systemOnly !== true).map(([name]) => `\`${name}\``);
45803
- 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)`));
45421
+ if (def.scope !== "device" || state === void 0) return problem("not-composable", `${at}.cap`, `\`${def.name}\` has no per-device state to compose`);
45422
+ return {
45423
+ def,
45424
+ state,
45425
+ fields: describeTopLevelStateFields(state)
45426
+ };
45427
+ }
45428
+ /**
45429
+ * A capability BUILT by the composition (a new device, or one ADDED to an
45430
+ * existing device) must fit the device type, serve no command, and have every
45431
+ * required field. A REPLACE needs none of it: the cap is bound (a fact), the
45432
+ * native keeps serving its methods, and only the named fields are owned (D663).
45433
+ */
45434
+ function builtCapProblems({ def, state, fields: targetFields }, feature, at, deviceType) {
45435
+ const problems = [];
45436
+ if (def.deviceTypes !== void 0 && !def.deviceTypes.includes(deviceType)) problems.push(problem("wrong-device-type", `${at}.cap`, `\`${def.name}\` is not offered on a \`${deviceType}\` device (it allows ${def.deviceTypes.join(", ")})`));
45437
+ const served = Object.entries(def.methods).filter(([name, m]) => m.systemOnly !== true && def.composedMethods?.[name] === void 0).map(([name]) => `\`${name}\``);
45438
+ if (served.length > 0) problems.push(problem("has-commands", `${at}.cap`, `\`${def.name}\` has ${served.join(", ")}, which a composed device cannot serve until commands arrive (slice 3)`));
45439
+ const itemArrayPath = feature.items === void 0 ? null : def.status?.itemArray?.path ?? null;
45440
+ for (const key of requiredTopLevelKeys(state)) {
45441
+ if (key === itemArrayPath) continue;
45442
+ if (!targetFields.has(key)) problems.push(problem("not-composable", `${at}.fields`, `\`${def.name}.${key}\` is not a scalar — nested fields arrive in a later slice`));
45443
+ else if (!(key in feature.fields)) problems.push(problem("missing-field", `${at}.fields.${key}`, `\`${def.name}.${key}\` is required and has no source`));
45444
+ }
45445
+ return problems;
45446
+ }
45447
+ function planTopLevelField({ def, fields }, field, mode, lookupCap) {
45448
+ const { featureIndex, path, source } = field;
45449
+ const fieldAt = `features[${featureIndex}].fields.${path}`;
45450
+ if (path.includes(".")) return refused(problem("nested-field", fieldAt, `\`${path}\` is nested — nested fields arrive in a later slice`));
45451
+ const tf = fields.get(path);
45452
+ if (tf === void 0) return refused(problem("unknown-field", fieldAt, `\`${def.name}\` has no field \`${path}\``));
45453
+ const kinds = sourceKinds(source, lookupCap);
45454
+ if (!kinds.ok) return refused(problem(kinds.code, fieldAt, kinds.message));
45455
+ const role = roleOf(source);
45456
+ const unavailable = unavailableFor(tf, role, mode, def.name, fieldAt);
45457
+ if (!unavailable.ok) return refused(unavailable.problem);
45458
+ const plan = {
45459
+ cap: def.name,
45460
+ path,
45461
+ featureIndex,
45462
+ kind: tf.kind,
45463
+ nullable: tf.nullable,
45464
+ required: tf.required,
45465
+ unavailable: unavailable.unavailable,
45466
+ role,
45467
+ source,
45468
+ reads: [...require_composition.fieldReads(source)]
45469
+ };
45470
+ const enumProblem = enumLiteralProblem(def.name, tf, source, fieldAt);
45471
+ return {
45472
+ problems: [...compatibility(def.name, tf, kinds.kinds, fieldAt), ...enumProblem === null ? [] : [enumProblem]],
45473
+ fields: [plan],
45474
+ features: []
45475
+ };
45476
+ }
45477
+ function planFeature(feature, featureIndex, target, lookupCap) {
45478
+ const at = `features[${featureIndex}]`;
45479
+ const cap = composableCap(feature, at, lookupCap);
45480
+ if ("code" in cap) return refused(cap);
45481
+ const capProblems = target.mode === "replace" ? [] : [...builtCapProblems(cap, feature, at, target.deviceType)];
45804
45482
  if (feature.commands !== void 0 && Object.keys(feature.commands).length > 0) capProblems.push(problem("reserved-variant", `${at}.commands`, "commands (`forward`) arrive in slice 3"));
45805
- const targetFields = describeTopLevelStateFields(state);
45806
- 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`));
45807
- else if (!(key in feature.fields)) capProblems.push(problem("missing-field", `${at}.fields.${key}`, `\`${def.name}.${key}\` is required and has no source`));
45808
- const perField = Object.entries(feature.fields).map(([path, source]) => {
45809
- const fieldAt = `${at}.fields.${path}`;
45810
- if (path.includes(".")) return refused(problem("nested-field", fieldAt, `\`${path}\` is nested — nested fields arrive in a later slice`));
45811
- const tf = targetFields.get(path);
45812
- if (tf === void 0) return refused(problem("unknown-field", fieldAt, `\`${def.name}\` has no field \`${path}\``));
45813
- const kinds = sourceKinds(source, lookupCap);
45814
- if (!kinds.ok) return refused(problem(kinds.code, fieldAt, kinds.message));
45815
- const plan = {
45816
- cap: def.name,
45817
- path,
45483
+ const perField = Object.entries(feature.fields).map(([path, source]) => planTopLevelField(cap, {
45484
+ featureIndex,
45485
+ path,
45486
+ source
45487
+ }, target.mode, lookupCap));
45488
+ const items = feature.items === void 0 ? [] : [planItems(cap.def, feature, featureIndex, lookupCap, target.mode)];
45489
+ const all = [...perField, ...items];
45490
+ return {
45491
+ problems: [...capProblems, ...all.flatMap((r) => r.problems)],
45492
+ fields: all.flatMap((r) => r.fields),
45493
+ features: [{
45818
45494
  featureIndex,
45819
- kind: tf.kind,
45820
- nullable: tf.nullable,
45821
- required: tf.required,
45822
- unavailable: tf.nullable ? "null" : "offline",
45823
- role: roleOf(source),
45824
- source,
45825
- reads: [...fieldReads(source)]
45826
- };
45827
- const enumProblem = enumLiteralProblem(def.name, tf, source, fieldAt);
45828
- return {
45829
- problems: [...compatibility(def.name, tf, kinds.kinds, fieldAt), ...enumProblem === null ? [] : [enumProblem]],
45830
- fields: [plan]
45831
- };
45832
- });
45495
+ cap: cap.def.name,
45496
+ mode: target.mode
45497
+ }]
45498
+ };
45499
+ }
45500
+ /** A cap the device serves itself; one the composer grafted (an earlier ADD) is not native. */
45501
+ function isNativeCap(target, cap) {
45502
+ const owner = target.nativeCaps.get(cap);
45503
+ return owner !== void 0 && owner !== "composer";
45504
+ }
45505
+ /** The target read, or the refusal that replaces the whole plan when it could not be (D391). */
45506
+ function planTargetOf(composition, target) {
45507
+ if (composition.target.kind === "new") return {
45508
+ kind: "new",
45509
+ type: composition.target.type
45510
+ };
45511
+ const key = require_composition.compositionSourceKey(composition.target.device);
45512
+ if (target === void 0) return problem("target-unreadable", "target", `target device \`${key}\` was not read — the save is refused rather than stored unchecked`);
45513
+ if (target.kind === "absent") return problem("target-missing", "target", `target device \`${key}\` does not exist`);
45514
+ if (target.kind === "unreadable") return problem("target-unreadable", "target", `could not read target device \`${key}\` (${target.error}) — the save is refused rather than stored unchecked`);
45833
45515
  return {
45834
- problems: [...capProblems, ...perField.flatMap((r) => r.problems)],
45835
- fields: perField.flatMap((r) => r.fields)
45516
+ kind: "existing",
45517
+ found: target
45836
45518
  };
45837
45519
  }
45838
- function planComposition(composition, lookupCap) {
45839
- const target = composition.target;
45840
- 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"));
45520
+ function featureTargetOf(target, cap) {
45521
+ if (target.kind === "new") return {
45522
+ mode: "new",
45523
+ deviceType: target.type
45524
+ };
45525
+ return {
45526
+ mode: isNativeCap(target.found, cap) ? "replace" : "add",
45527
+ deviceType: target.found.type
45528
+ };
45529
+ }
45530
+ /**
45531
+ * The static plan. `target` describes an EXISTING target device (one read);
45532
+ * it is ignored for a new one. On an existing target each feature REPLACES a
45533
+ * cap the device has natively, else ADDS it (D663).
45534
+ */
45535
+ function planComposition(composition, lookupCap, target) {
45536
+ const planTarget = planTargetOf(composition, target);
45537
+ if ("code" in planTarget) return refused(planTarget);
45841
45538
  const results = composition.features.map((feature, index) => {
45842
- const at = `features[${index}]`;
45843
- if (feature.kind === "passthrough") return refused(problem("reserved-variant", at, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`));
45844
- return planFeature(feature, index, target, lookupCap);
45539
+ if (feature.kind === "passthrough") return refused(problem("reserved-variant", `features[${index}]`, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`));
45540
+ return planFeature(feature, index, featureTargetOf(planTarget, feature.cap), lookupCap);
45845
45541
  });
45846
45542
  return {
45847
45543
  problems: results.flatMap((r) => r.problems),
45848
- fields: results.flatMap((r) => r.fields)
45544
+ fields: results.flatMap((r) => r.fields),
45545
+ features: results.flatMap((r) => r.features)
45849
45546
  };
45850
45547
  }
45851
45548
  function compositionSourceRefs(composition) {
45852
- const refs = composition.features.flatMap((f) => f.kind === "fields" ? Object.values(f.fields).flatMap(fieldReads).map((r) => r.source) : []);
45853
- return [...new Map(refs.map((r) => [compositionSourceKey(r), r])).values()];
45549
+ const refs = composition.features.flatMap((f) => f.kind === "fields" ? require_composition.featureSources(f).flatMap(require_composition.fieldReads).map((r) => r.source) : []);
45550
+ return [...new Map(refs.map((r) => [require_composition.compositionSourceKey(r), r])).values()];
45854
45551
  }
45855
- function sourceProblems(fields, ctx) {
45856
- const selfKey = ctx.self === null ? null : compositionSourceKey(ctx.self);
45552
+ /**
45553
+ * The candidate's device: the existing target itself, else `self`, else a
45554
+ * placeholder that cannot collide with a stored target. An existing target is
45555
+ * known even on create, so a second customization of it is refused on create too.
45556
+ */
45557
+ function candidateSelf(composition, ctx, plan) {
45558
+ return {
45559
+ ref: composition.target.kind === "existing" ? composition.target.device : ctx.self ?? PENDING_SELF,
45560
+ native: composition.target.kind === "existing" && ctx.target?.kind === "found" ? ctx.target : null,
45561
+ replaced: replacedFieldKeys(plan)
45562
+ };
45563
+ }
45564
+ /**
45565
+ * `compositionFieldKey` of every field a REPLACE feature of `plan` owns — the
45566
+ * candidate's own, and a stored peer's `CompositionPeer.replaced` (D663): one
45567
+ * derivation, so the graph reads both the same way.
45568
+ */
45569
+ function replacedFieldKeys(plan) {
45570
+ const replacing = new Set(plan.features.filter((f) => f.mode === "replace").map((f) => f.featureIndex));
45571
+ return new Set(plan.fields.filter((f) => replacing.has(f.featureIndex)).map(compositionFieldKey));
45572
+ }
45573
+ /**
45574
+ * A read of the candidate's own device. Its own composed output (a new device's
45575
+ * field, an ADDED field) is the slice-1 own-field rule; on an existing target
45576
+ * every other field — replaced, or not composed at all — is the NATIVE value,
45577
+ * valid when the device serves that cap natively (D663, rule 5).
45578
+ */
45579
+ function selfReadProblems(read, at, fields, self) {
45580
+ const readKey = `${read.cap}.${read.fieldPath}`;
45581
+ if (fields.some((f) => compositionFieldKey(f) === readKey && !self.replaced.has(readKey))) return [];
45582
+ if (self.native === null) return [problem("source-field-unknown", at, `this device has no composed field \`${read.cap}.${read.fieldPath}\``)];
45583
+ if (isNativeCap(self.native, read.cap)) return [];
45584
+ return [problem("source-capability-missing", at, `the target device (device ${self.native.deviceId}) has no native \`${read.cap}\``)];
45585
+ }
45586
+ function sourceProblems(fields, ctx, self) {
45587
+ const selfKey = require_composition.compositionSourceKey(self.ref);
45857
45588
  return fields.flatMap((field) => field.reads.flatMap((read) => {
45858
- const at = `features[${field.featureIndex}].fields.${field.path}`;
45859
- const key = compositionSourceKey(read.source);
45860
- 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}\``)];
45589
+ const at = compositionFieldAt(field);
45590
+ const key = require_composition.compositionSourceKey(read.source);
45591
+ if (key === selfKey) return selfReadProblems(read, at, fields, self);
45861
45592
  const desc = ctx.sources.get(key);
45862
45593
  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`)];
45863
45594
  if (desc.kind === "absent") return [problem("source-missing", at, `source \`${key}\` does not exist`)];
@@ -45870,8 +45601,9 @@ function sourceProblems(fields, ctx) {
45870
45601
  * `<addonId>/<stableId>#<cap>.<fieldPath>`. When a node belongs to the
45871
45602
  * CANDIDATE itself (this composition being saved), pull its `cap`/`fieldPath`
45872
45603
  * back out so the refusal can point at that exact field, matching the
45873
- * documented `features[N].fields.<path>` convention — slice 1 has no nested
45874
- * field writes, so splitting `rest` on the FIRST `.` is exact.
45604
+ * documented `features[N].fields.<path>` convention. A cap name has no `.`,
45605
+ * so splitting `rest` on the FIRST `.` is exact; an item leaf's remainder
45606
+ * (`items[desiccant].status`) is matched through `compositionFieldKey`.
45875
45607
  */
45876
45608
  function candidateFieldOfNode(nodeKey, candidateKey) {
45877
45609
  const prefix = `${candidateKey}#`;
@@ -45891,44 +45623,65 @@ function cyclePath(nodePath, candidateKey, fields) {
45891
45623
  for (const nodeKey of nodePath) {
45892
45624
  const hit = candidateFieldOfNode(nodeKey, candidateKey);
45893
45625
  if (hit === null) continue;
45894
- const field = fields.find((f) => f.cap === hit.cap && f.path === hit.fieldPath);
45895
- if (field !== void 0) return `features[${field.featureIndex}].fields.${field.path}`;
45626
+ const hitKey = `${hit.cap}.${hit.fieldPath}`;
45627
+ const field = fields.find((f) => compositionFieldKey(f) === hitKey);
45628
+ if (field !== void 0) return compositionFieldAt(field);
45896
45629
  }
45897
45630
  return "features";
45898
45631
  }
45632
+ /** The first unread device any of `nodes` belongs to; null when none does. */
45633
+ function unreadDeviceOf(nodes, unread) {
45634
+ for (const [key, error] of unread) if (nodes.some((node) => node.startsWith(`${key}#`))) return {
45635
+ key,
45636
+ error
45637
+ };
45638
+ return null;
45639
+ }
45899
45640
  /**
45900
- * Cycle check runs against the CANDIDATE (this composition, targeting `self`
45901
- * when it already exists, else a placeholder that cannot collide with a
45902
- * stored target) plus every OTHER stored composition. Two stored peers that
45903
- * (wrongly) share a target are corrupted state — `findCompositionCycles`
45904
- * throws `CompositionGraphError` rather than silently picking one, and this
45905
- * is refused BY NAME (D391) instead of escaping as an uncaught throw.
45641
+ * Cycle check runs against the CANDIDATE (this composition, as block
45642
+ * `ctx.blockId`, writing `self.ref`) plus every OTHER stored composition,
45643
+ * decided per strongly connected component (`findCandidateCycles`): a cyclic
45644
+ * component holding a candidate node refuses, with one representative cycle
45645
+ * through it; one without is not this save's cycle. A
45646
+ * stored composition of the same block is its old version; another block on
45647
+ * the same device, or two stored peers sharing a device, make
45648
+ * `compositionEdges` throw `CompositionGraphError` — refused BY NAME as
45649
+ * `duplicate-target` (D391, D663) instead of escaping as an uncaught throw.
45906
45650
  */
45907
- function cycleProblems(composition, ctx, fields) {
45651
+ function cycleProblems(composition, ctx, fields, self) {
45908
45652
  const candidate = {
45909
- target: ctx.self ?? PENDING_SELF,
45910
- composition
45653
+ blockId: ctx.blockId ?? "(unsaved)",
45654
+ target: self.ref,
45655
+ composition,
45656
+ replaced: self.replaced
45911
45657
  };
45912
- const candidateKey = compositionSourceKey(candidate.target);
45658
+ const candidateKey = require_composition.compositionSourceKey(candidate.target);
45659
+ const unread = ctx.unreadTargets ?? /* @__PURE__ */ new Map();
45913
45660
  try {
45914
- return findCompositionCycles(candidate, ctx.peers).map((c) => problem("cycle", cyclePath(c.path, candidateKey, fields), `a field reads itself: ${c.path.join(" → ")}`));
45661
+ return findCandidateCycles(candidate, ctx.peers).map((c) => {
45662
+ const at = cyclePath(c.path, candidateKey, fields);
45663
+ const through = unreadDeviceOf([...c.component], unread);
45664
+ return through === null ? problem("cycle", at, `a field reads itself: ${c.path.join(" → ")}`) : problem("cycle", at, `possible cycle through \`${through.key}\` — its state could not be read (${through.error}): ${c.path.join(" → ")}`);
45665
+ });
45915
45666
  } catch (err) {
45916
45667
  if (err instanceof CompositionGraphError) return [problem("duplicate-target", "features", err.message)];
45917
45668
  throw err;
45918
45669
  }
45919
45670
  }
45920
45671
  function validateComposition(composition, ctx) {
45921
- const plan = planComposition(composition, ctx.lookupCap);
45922
- const cycles = cycleProblems(composition, ctx, plan.fields);
45672
+ const plan = planComposition(composition, ctx.lookupCap, ctx.target ?? void 0);
45673
+ const self = candidateSelf(composition, ctx, plan);
45674
+ const cycles = cycleProblems(composition, ctx, plan.fields, self);
45923
45675
  const problems = [
45924
45676
  ...plan.problems,
45925
- ...sourceProblems(plan.fields, ctx),
45677
+ ...sourceProblems(plan.fields, ctx, self),
45926
45678
  ...cycles
45927
45679
  ];
45928
45680
  return {
45929
45681
  ok: problems.length === 0,
45930
45682
  problems,
45931
- fields: [...plan.fields]
45683
+ fields: [...plan.fields],
45684
+ features: [...plan.features]
45932
45685
  };
45933
45686
  }
45934
45687
  //#endregion
@@ -46093,14 +45846,14 @@ var RUNTIME_DEFAULTS = {
46093
45846
  * any role works, including non-accessory ones like Doorbell).
46094
45847
  */
46095
45848
  var AccessoryKind = {
46096
- Siren: require_sleep.DeviceRole.Siren,
46097
- Floodlight: require_sleep.DeviceRole.Floodlight,
46098
- Spotlight: require_sleep.DeviceRole.Spotlight,
46099
- PirSensor: require_sleep.DeviceRole.PirSensor,
46100
- Chime: require_sleep.DeviceRole.Chime,
46101
- Autotrack: require_sleep.DeviceRole.Autotrack,
46102
- Nightvision: require_sleep.DeviceRole.Nightvision,
46103
- PrivacyMask: require_sleep.DeviceRole.PrivacyMask
45849
+ Siren: require_err_msg.DeviceRole.Siren,
45850
+ Floodlight: require_err_msg.DeviceRole.Floodlight,
45851
+ Spotlight: require_err_msg.DeviceRole.Spotlight,
45852
+ PirSensor: require_err_msg.DeviceRole.PirSensor,
45853
+ Chime: require_err_msg.DeviceRole.Chime,
45854
+ Autotrack: require_err_msg.DeviceRole.Autotrack,
45855
+ Nightvision: require_err_msg.DeviceRole.Nightvision,
45856
+ PrivacyMask: require_err_msg.DeviceRole.PrivacyMask
46104
45857
  };
46105
45858
  /**
46106
45859
  * Default English label for each accessory kind. Drivers append this
@@ -46243,38 +45996,31 @@ var DeviceConfig = class DeviceConfig {
46243
45996
  };
46244
45997
  //#endregion
46245
45998
  //#region src/device/device-runtime-state.ts
46246
- /**
46247
- * Concrete implementation. Routes every successful write through
46248
- * `writer(capName, slice)` — the kernel hooks this up to
46249
- * `device-state.setCapSlice`, the canonical cross-layer write
46250
- * entrypoint, which handles disk persistence (debounced on the hub)
46251
- * and mirror updates.
46252
- *
46253
- * Schema validation runs in-process before the writer is called —
46254
- * the round-trip should never carry an invalid slice. `flush()`
46255
- * awaits any in-flight writer promises so shutdown is lossless.
46256
- *
46257
- * `initial` is the persisted blob loaded at boot. Slices for caps
46258
- * whose schema hasn't been installed yet are kept in-memory verbatim
46259
- * and validated when the cap registers later.
46260
- */
46261
45999
  var DeviceRuntimeState = class DeviceRuntimeState {
46262
46000
  writer;
46001
+ onWriteFailed;
46263
46002
  /** In-flight writer promises tracked so `flush()` can await them. */
46264
46003
  pendingWrites = /* @__PURE__ */ new Set();
46004
+ /**
46005
+ * Caps whose last write the hub refused. The equality gate in
46006
+ * `applyCapWrite` is bypassed for these, so the SAME value is re-sent on the
46007
+ * next write instead of being swallowed forever; a landing write clears it.
46008
+ */
46009
+ failedWrites = /* @__PURE__ */ new Set();
46265
46010
  /** Per-cap committed slice — after schema validation when known. */
46266
46011
  slices;
46267
46012
  /** Per-cap registered schema (set by `installCapSchema`). */
46268
46013
  schemas = /* @__PURE__ */ new Map();
46269
46014
  listeners = /* @__PURE__ */ new Set();
46270
46015
  capListeners = /* @__PURE__ */ new Map();
46271
- constructor(initial, writer) {
46016
+ constructor(initial, writer, onWriteFailed) {
46272
46017
  this.writer = writer;
46018
+ this.onWriteFailed = onWriteFailed;
46273
46019
  this.slices = /* @__PURE__ */ new Map();
46274
46020
  for (const [k, v] of Object.entries(initial)) if (v && typeof v === "object" && !Array.isArray(v)) this.slices.set(k, { ...v });
46275
46021
  }
46276
- static fromInitial(initial, writer) {
46277
- return new DeviceRuntimeState(initial, writer);
46022
+ static fromInitial(initial, writer, onWriteFailed) {
46023
+ return new DeviceRuntimeState(initial, writer, onWriteFailed);
46278
46024
  }
46279
46025
  installCapSchema(capName, schema) {
46280
46026
  const existing = this.schemas.get(capName);
@@ -46318,10 +46064,15 @@ var DeviceRuntimeState = class DeviceRuntimeState {
46318
46064
  ...value
46319
46065
  } : { ...value };
46320
46066
  const parsed = schema.parse(next);
46321
- if (shallowEqual(current, parsed)) return;
46067
+ if (shallowEqual(current, parsed) && !this.failedWrites.has(capName)) return;
46322
46068
  this.slices.set(capName, parsed);
46323
46069
  this.fireListeners([capName]);
46324
- const writePromise = this.writer(capName, { ...parsed }).catch(() => {});
46070
+ const writePromise = this.writer(capName, { ...parsed }).then(() => {
46071
+ this.failedWrites.delete(capName);
46072
+ }).catch((err) => {
46073
+ this.failedWrites.add(capName);
46074
+ this.onWriteFailed?.(capName, err instanceof Error ? err.message : String(err));
46075
+ });
46325
46076
  this.pendingWrites.add(writePromise);
46326
46077
  writePromise.finally(() => {
46327
46078
  this.pendingWrites.delete(writePromise);
@@ -46602,7 +46353,16 @@ var BaseDevice = class {
46602
46353
  });
46603
46354
  };
46604
46355
  const initial = ctx.initialRuntimeState ?? {};
46605
- this.runtimeState = DeviceRuntimeState.fromInitial(initial, writer);
46356
+ const onWriteFailed = (cap, error) => {
46357
+ ctx.logger.warn("runtime-state write refused by the hub — the slice is not mirrored; it is re-sent on the next write", {
46358
+ tags: { deviceId: ctx.id },
46359
+ meta: {
46360
+ cap,
46361
+ error
46362
+ }
46363
+ });
46364
+ };
46365
+ this.runtimeState = DeviceRuntimeState.fromInitial(initial, writer, onWriteFailed);
46606
46366
  ctx.bindRuntimeState?.(this.runtimeState);
46607
46367
  ctx.registerNativeCap?.(deviceStatusCapability, {});
46608
46368
  const seed = {
@@ -47197,7 +46957,7 @@ var BaseDeviceProvider = class extends require_sleep.BaseAddon {
47197
46957
  */
47198
46958
  async getManualCreationType() {
47199
46959
  if (!await this.supportsManualCreation()) return { deviceType: null };
47200
- return { deviceType: Object.values(require_sleep.DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
46960
+ return { deviceType: Object.values(require_err_msg.DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
47201
46961
  }
47202
46962
  async adoptDiscoveredDevice(_input) {
47203
46963
  throw new Error(`${this.providerName} provider does not support discovery-based adoption`);
@@ -47404,14 +47164,14 @@ var BaseDeviceProvider = class extends require_sleep.BaseAddon {
47404
47164
  });
47405
47165
  const meta = await this.ctx.api.deviceManager.loadMeta.query({ deviceId: id });
47406
47166
  if (meta === null) throw new Error(`${this.providerName}: no persisted meta for "${stableId}" (id ${id}) — cannot reload`);
47407
- const deviceType = Object.values(require_sleep.DeviceType).find((t) => t === meta.type);
47167
+ const deviceType = Object.values(require_err_msg.DeviceType).find((t) => t === meta.type);
47408
47168
  const Class = deviceType !== void 0 ? this.deviceClasses[deviceType] : void 0;
47409
47169
  if (!Class) throw new Error(`${this.providerName}: no device class for type "${meta.type}" — "${stableId}" is an accessory child; reload its parent instead`);
47410
47170
  await devices.create(stableId, Class, {}, meta.parentDeviceId ?? null);
47411
47171
  const rows = await this.ctx.api.deviceManager.listPersistedByAddon.query({ addonId: this.addonId });
47412
47172
  for (const row of rows) {
47413
47173
  if (row.parentDeviceId !== id) continue;
47414
- const childType = Object.values(require_sleep.DeviceType).find((t) => t === row.type);
47174
+ const childType = Object.values(require_err_msg.DeviceType).find((t) => t === row.type);
47415
47175
  const ChildClass = childType !== void 0 ? this.deviceClasses[childType] : void 0;
47416
47176
  if (!ChildClass) continue;
47417
47177
  try {
@@ -47838,7 +47598,7 @@ function expandContainer(id, byId, byParent, visited, out) {
47838
47598
  if (visited.has(id)) return;
47839
47599
  const candidate = byId.get(id);
47840
47600
  if (!candidate) return;
47841
- if (candidate.type !== require_sleep.DeviceType.Container) {
47601
+ if (candidate.type !== require_err_msg.DeviceType.Container) {
47842
47602
  out.add(id);
47843
47603
  return;
47844
47604
  }
@@ -47882,11 +47642,11 @@ function legacyAutoLinkIds(params) {
47882
47642
  const ids = /* @__PURE__ */ new Set();
47883
47643
  const expand = (id) => expandContainer(id, byId, byParent, /* @__PURE__ */ new Set(), ids);
47884
47644
  for (const c of candidates) {
47885
- if (c.type === require_sleep.DeviceType.Container) continue;
47645
+ if (c.type === require_err_msg.DeviceType.Container) continue;
47886
47646
  if (c.location !== selfLocation) continue;
47887
47647
  if (c.parentDeviceId === selfId) continue;
47888
47648
  const parent = c.parentDeviceId !== null ? byId.get(c.parentDeviceId) : void 0;
47889
- if (parent !== void 0 && parent.type === require_sleep.DeviceType.Container) {
47649
+ if (parent !== void 0 && parent.type === require_err_msg.DeviceType.Container) {
47890
47650
  expand(parent.id);
47891
47651
  continue;
47892
47652
  }
@@ -48178,39 +47938,39 @@ var DeclaredDevices = class {
48178
47938
  * placeholder HA types whose inline control renders a dash).
48179
47939
  */
48180
47940
  var DEVICE_TYPE_CONTROL_KIND = {
48181
- [require_sleep.DeviceType.Cover]: "cover",
48182
- [require_sleep.DeviceType.Valve]: "valve",
48183
- [require_sleep.DeviceType.Humidifier]: "humidifier",
48184
- [require_sleep.DeviceType.WaterHeater]: "water-heater",
48185
- [require_sleep.DeviceType.Camera]: "dummy",
48186
- [require_sleep.DeviceType.Hub]: "dummy",
48187
- [require_sleep.DeviceType.Switch]: "switch",
48188
- [require_sleep.DeviceType.Siren]: "switch",
48189
- [require_sleep.DeviceType.Light]: "light",
48190
- [require_sleep.DeviceType.Fan]: "fan",
48191
- [require_sleep.DeviceType.Sensor]: "sensor",
48192
- [require_sleep.DeviceType.Thermostat]: "thermostat",
48193
- [require_sleep.DeviceType.Climate]: "climate",
48194
- [require_sleep.DeviceType.Button]: "button",
48195
- [require_sleep.DeviceType.EventEmitter]: "dummy",
48196
- [require_sleep.DeviceType.Update]: "update",
48197
- [require_sleep.DeviceType.Generic]: "dummy",
48198
- [require_sleep.DeviceType.Notifier]: "dummy",
48199
- [require_sleep.DeviceType.Script]: "script",
48200
- [require_sleep.DeviceType.Automation]: "automation",
48201
- [require_sleep.DeviceType.Lock]: "lock",
48202
- [require_sleep.DeviceType.MediaPlayer]: "media-player",
48203
- [require_sleep.DeviceType.AlarmPanel]: "alarm",
48204
- [require_sleep.DeviceType.Control]: "control",
48205
- [require_sleep.DeviceType.Presence]: "sensor",
48206
- [require_sleep.DeviceType.Weather]: "weather",
48207
- [require_sleep.DeviceType.Vacuum]: "vacuum",
48208
- [require_sleep.DeviceType.LawnMower]: "lawn-mower",
48209
- [require_sleep.DeviceType.Container]: "dummy",
48210
- [require_sleep.DeviceType.Image]: "image",
48211
- [require_sleep.DeviceType.PetFeeder]: "pet-feeder"
47941
+ [require_err_msg.DeviceType.Cover]: "cover",
47942
+ [require_err_msg.DeviceType.Valve]: "valve",
47943
+ [require_err_msg.DeviceType.Humidifier]: "humidifier",
47944
+ [require_err_msg.DeviceType.WaterHeater]: "water-heater",
47945
+ [require_err_msg.DeviceType.Camera]: "dummy",
47946
+ [require_err_msg.DeviceType.Hub]: "dummy",
47947
+ [require_err_msg.DeviceType.Switch]: "switch",
47948
+ [require_err_msg.DeviceType.Siren]: "switch",
47949
+ [require_err_msg.DeviceType.Light]: "light",
47950
+ [require_err_msg.DeviceType.Fan]: "fan",
47951
+ [require_err_msg.DeviceType.Sensor]: "sensor",
47952
+ [require_err_msg.DeviceType.Thermostat]: "thermostat",
47953
+ [require_err_msg.DeviceType.Climate]: "climate",
47954
+ [require_err_msg.DeviceType.Button]: "button",
47955
+ [require_err_msg.DeviceType.EventEmitter]: "dummy",
47956
+ [require_err_msg.DeviceType.Update]: "update",
47957
+ [require_err_msg.DeviceType.Generic]: "dummy",
47958
+ [require_err_msg.DeviceType.Notifier]: "dummy",
47959
+ [require_err_msg.DeviceType.Script]: "script",
47960
+ [require_err_msg.DeviceType.Automation]: "automation",
47961
+ [require_err_msg.DeviceType.Lock]: "lock",
47962
+ [require_err_msg.DeviceType.MediaPlayer]: "media-player",
47963
+ [require_err_msg.DeviceType.AlarmPanel]: "alarm",
47964
+ [require_err_msg.DeviceType.Control]: "control",
47965
+ [require_err_msg.DeviceType.Presence]: "sensor",
47966
+ [require_err_msg.DeviceType.Weather]: "weather",
47967
+ [require_err_msg.DeviceType.Vacuum]: "vacuum",
47968
+ [require_err_msg.DeviceType.LawnMower]: "lawn-mower",
47969
+ [require_err_msg.DeviceType.Container]: "dummy",
47970
+ [require_err_msg.DeviceType.Image]: "image",
47971
+ [require_err_msg.DeviceType.PetFeeder]: "pet-feeder"
48212
47972
  };
48213
- var DEVICE_TYPE_VALUES = new Set(Object.values(require_sleep.DeviceType));
47973
+ var DEVICE_TYPE_VALUES = new Set(Object.values(require_err_msg.DeviceType));
48214
47974
  /**
48215
47975
  * Runtime-string-safe base resolver: a device row's `type` crosses the wire as
48216
47976
  * a plain string, so an unrecognised value resolves to `null` instead of
@@ -48254,15 +48014,15 @@ var SENSOR_FEATURES = [
48254
48014
  function pickAccessoryControl(device) {
48255
48015
  const set = new Set(device.features);
48256
48016
  switch (device.type) {
48257
- case require_sleep.DeviceType.Cover: return { kind: "cover" };
48258
- case require_sleep.DeviceType.Valve: return { kind: "valve" };
48259
- case require_sleep.DeviceType.Lock: return { kind: "lock" };
48260
- case require_sleep.DeviceType.AlarmPanel: return { kind: "alarm" };
48261
- case require_sleep.DeviceType.Light: return set.has("brightness") ? { kind: "brightness" } : { kind: "switch" };
48262
- case require_sleep.DeviceType.Switch:
48263
- case require_sleep.DeviceType.Siren: return { kind: "switch" };
48264
- case require_sleep.DeviceType.Sensor:
48265
- case require_sleep.DeviceType.Presence: {
48017
+ case require_err_msg.DeviceType.Cover: return { kind: "cover" };
48018
+ case require_err_msg.DeviceType.Valve: return { kind: "valve" };
48019
+ case require_err_msg.DeviceType.Lock: return { kind: "lock" };
48020
+ case require_err_msg.DeviceType.AlarmPanel: return { kind: "alarm" };
48021
+ case require_err_msg.DeviceType.Light: return set.has("brightness") ? { kind: "brightness" } : { kind: "switch" };
48022
+ case require_err_msg.DeviceType.Switch:
48023
+ case require_err_msg.DeviceType.Siren: return { kind: "switch" };
48024
+ case require_err_msg.DeviceType.Sensor:
48025
+ case require_err_msg.DeviceType.Presence: {
48266
48026
  const sensor = SENSOR_FEATURES.find((f) => set.has(f));
48267
48027
  return sensor ? {
48268
48028
  kind: "sensor",
@@ -48341,7 +48101,7 @@ function deviceMatchesProfile(features, profile) {
48341
48101
  var BATTERY_DEVICE_PROFILE = {
48342
48102
  id: "battery",
48343
48103
  label: "Battery-operated camera",
48344
- when: { hasFeature: require_sleep.DeviceFeature.BatteryOperated },
48104
+ when: { hasFeature: require_err_msg.DeviceFeature.BatteryOperated },
48345
48105
  defaults: {
48346
48106
  audioMode: "disabled",
48347
48107
  detectionMode: "on-motion"
@@ -52972,6 +52732,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
52972
52732
  addonId: null,
52973
52733
  access: "view"
52974
52734
  },
52735
+ "coreBlocks.getCustomization": {
52736
+ capName: "core-blocks",
52737
+ capScope: "system",
52738
+ addonId: null,
52739
+ access: "view"
52740
+ },
52975
52741
  "coreBlocks.getTypeDefs": {
52976
52742
  capName: "core-blocks",
52977
52743
  capScope: "system",
@@ -54094,6 +53860,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
54094
53860
  addonId: null,
54095
53861
  access: "create"
54096
53862
  },
53863
+ "deviceState.claimFields": {
53864
+ capName: "device-state",
53865
+ capScope: "system",
53866
+ addonId: null,
53867
+ access: "create"
53868
+ },
54097
53869
  "deviceState.getAllSnapshots": {
54098
53870
  capName: "device-state",
54099
53871
  capScope: "system",
@@ -54106,12 +53878,36 @@ var METHOD_ACCESS_MAP = Object.freeze({
54106
53878
  addonId: null,
54107
53879
  access: "view"
54108
53880
  },
53881
+ "deviceState.getNativeCapSlice": {
53882
+ capName: "device-state",
53883
+ capScope: "system",
53884
+ addonId: null,
53885
+ access: "view"
53886
+ },
54109
53887
  "deviceState.getSnapshot": {
54110
53888
  capName: "device-state",
54111
53889
  capScope: "system",
54112
53890
  addonId: null,
54113
53891
  access: "view"
54114
53892
  },
53893
+ "deviceState.listClaims": {
53894
+ capName: "device-state",
53895
+ capScope: "system",
53896
+ addonId: null,
53897
+ access: "view"
53898
+ },
53899
+ "deviceState.patchOwnedFields": {
53900
+ capName: "device-state",
53901
+ capScope: "system",
53902
+ addonId: null,
53903
+ access: "create"
53904
+ },
53905
+ "deviceState.releaseClaim": {
53906
+ capName: "device-state",
53907
+ capScope: "system",
53908
+ addonId: null,
53909
+ access: "create"
53910
+ },
54115
53911
  "deviceState.setCapSlice": {
54116
53912
  capName: "device-state",
54117
53913
  capScope: "system",
@@ -59962,16 +59758,36 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
59962
59758
  form: "single",
59963
59759
  optional: false
59964
59760
  }],
59761
+ "deviceState.claimFields": [{
59762
+ name: "deviceId",
59763
+ form: "single",
59764
+ optional: false
59765
+ }],
59965
59766
  "deviceState.getCapSlice": [{
59966
59767
  name: "deviceId",
59967
59768
  form: "single",
59968
59769
  optional: false
59969
59770
  }],
59771
+ "deviceState.getNativeCapSlice": [{
59772
+ name: "deviceId",
59773
+ form: "single",
59774
+ optional: false
59775
+ }],
59970
59776
  "deviceState.getSnapshot": [{
59971
59777
  name: "deviceId",
59972
59778
  form: "single",
59973
59779
  optional: false
59974
59780
  }],
59781
+ "deviceState.patchOwnedFields": [{
59782
+ name: "deviceId",
59783
+ form: "single",
59784
+ optional: false
59785
+ }],
59786
+ "deviceState.releaseClaim": [{
59787
+ name: "deviceId",
59788
+ form: "single",
59789
+ optional: false
59790
+ }],
59975
59791
  "deviceState.setCapSlice": [{
59976
59792
  name: "deviceId",
59977
59793
  form: "single",
@@ -61467,8 +61283,12 @@ var SYSTEM_SCOPE_DEVICE_METHODS = [
61467
61283
  "deviceManager.updateConfig",
61468
61284
  "deviceManager.updateDeviceField",
61469
61285
  "deviceManager.updateDeviceFieldsBatch",
61286
+ "deviceState.claimFields",
61470
61287
  "deviceState.getCapSlice",
61288
+ "deviceState.getNativeCapSlice",
61471
61289
  "deviceState.getSnapshot",
61290
+ "deviceState.patchOwnedFields",
61291
+ "deviceState.releaseClaim",
61472
61292
  "deviceState.setCapSlice",
61473
61293
  "faceGallery.getFaceByTrack",
61474
61294
  "faceGallery.listRecentFaces",
@@ -62148,6 +61968,7 @@ function createSystemProxy(api) {
62148
61968
  updateComposition: (input) => dispatch("coreBlocks", "updateComposition", "mutation", input),
62149
61969
  getComposition: (input) => dispatch("coreBlocks", "getComposition", "query", input),
62150
61970
  listCompositions: (input) => dispatch("coreBlocks", "listCompositions", "query", input),
61971
+ getCustomization: (input) => dispatch("coreBlocks", "getCustomization", "query", input),
62151
61972
  validateComposition: (input) => dispatch("coreBlocks", "validateComposition", "mutation", input)
62152
61973
  },
62153
61974
  decoder: {
@@ -62251,7 +62072,10 @@ function createSystemProxy(api) {
62251
62072
  createDevice: (input) => dispatch("deviceProvider", "createDevice", "mutation", input),
62252
62073
  testCreationField: (input) => dispatch("deviceProvider", "testCreationField", "mutation", input)
62253
62074
  },
62254
- deviceState: { getAllSnapshots: (input) => dispatch("deviceState", "getAllSnapshots", "query", input) },
62075
+ deviceState: {
62076
+ getAllSnapshots: (input) => dispatch("deviceState", "getAllSnapshots", "query", input),
62077
+ listClaims: (input) => dispatch("deviceState", "listClaims", "query", input)
62078
+ },
62255
62079
  faceGallery: {
62256
62080
  listIdentities: (input) => dispatch("faceGallery", "listIdentities", "query", input),
62257
62081
  createIdentity: (input) => dispatch("faceGallery", "createIdentity", "mutation", input),
@@ -68309,7 +68133,7 @@ exports.AUDIO_BACKEND_CHOICES = AUDIO_BACKEND_CHOICES;
68309
68133
  exports.AUDIO_CHUNK_FORMATS = require_sleep.AUDIO_CHUNK_FORMATS;
68310
68134
  exports.AUDIO_MACRO_LABELS = AUDIO_MACRO_LABELS;
68311
68135
  exports.AUDIO_NOT_CLASSIFIED_REASONS = AUDIO_NOT_CLASSIFIED_REASONS;
68312
- exports.AUDIO_PRESETS = require_canonical_hash.AUDIO_PRESETS;
68136
+ exports.AUDIO_PRESETS = require_composition.AUDIO_PRESETS;
68313
68137
  exports.AUDIO_SCALE_EPOCH_ISO = AUDIO_SCALE_EPOCH_ISO;
68314
68138
  exports.AUDIO_SCALE_EPOCH_MS = AUDIO_SCALE_EPOCH_MS;
68315
68139
  exports.AccessoriesStatusSchema = AccessoriesStatusSchema;
@@ -68459,13 +68283,17 @@ exports.CLUSTER_MODEL_SECTION_ID = CLUSTER_MODEL_SECTION_ID;
68459
68283
  exports.CLUSTER_STEP_SETTING_FIELDS = CLUSTER_STEP_SETTING_FIELDS;
68460
68284
  exports.COCO_80_LABELS = COCO_80_LABELS;
68461
68285
  exports.COCO_TO_MACRO = COCO_TO_MACRO;
68462
- exports.COMPOSED_DEVICE_STABLE_ID_PREFIX = COMPOSED_DEVICE_STABLE_ID_PREFIX;
68463
- exports.COMPOSER_ADDON_ID = COMPOSER_ADDON_ID;
68286
+ exports.COMPOSED_DEVICE_STABLE_ID_PREFIX = require_composition.COMPOSED_DEVICE_STABLE_ID_PREFIX;
68287
+ exports.COMPOSED_ITEM_FIXED_FIELDS = COMPOSED_ITEM_FIXED_FIELDS;
68288
+ exports.COMPOSER_ADDON_ID = require_composition.COMPOSER_ADDON_ID;
68464
68289
  exports.COMPOSER_RUNNER_ID = COMPOSER_RUNNER_ID;
68290
+ exports.COMPOSITION_ITEM_ARRAY_PATH = require_composition.COMPOSITION_ITEM_ARRAY_PATH;
68291
+ exports.COMPOSITION_ITEM_KEY_RE = require_composition.COMPOSITION_ITEM_KEY_RE;
68465
68292
  exports.CONNECTION_TEST_TIMEOUT_MS = CONNECTION_TEST_TIMEOUT_MS;
68466
68293
  exports.CONTAINER_CHILD_PRIORITY = CONTAINER_CHILD_PRIORITY;
68467
68294
  exports.CORE_BLOCKS_ADDON_ID = CORE_BLOCKS_ADDON_ID;
68468
68295
  exports.CORE_BLOCK_ADDON_PREFIX = CORE_BLOCK_ADDON_PREFIX;
68296
+ exports.CUSTOMIZATION_BLOCK_NAME_PREFIX = require_composition.CUSTOMIZATION_BLOCK_NAME_PREFIX;
68469
68297
  exports.CamProfileSchema = require_sleep.CamProfileSchema;
68470
68298
  exports.CamStreamDescriptorSchema = CamStreamDescriptorSchema;
68471
68299
  exports.CamStreamKindSchema = require_sleep.CamStreamKindSchema;
@@ -68505,7 +68333,9 @@ exports.CandidateQueryFilterSchema = CandidateQueryFilterSchema;
68505
68333
  exports.CapScopeSchema = CapScopeSchema;
68506
68334
  exports.CapabilityBindingsSchema = CapabilityBindingsSchema;
68507
68335
  exports.CarbonMonoxideStatusSchema = CarbonMonoxideStatusSchema;
68508
- exports.ChargingStatus = require_sleep.ChargingStatus;
68336
+ exports.ChargingStatus = require_err_msg.ChargingStatus;
68337
+ exports.ClaimOutcomeSchema = ClaimOutcomeSchema;
68338
+ exports.ClaimsListingSchema = ClaimsListingSchema;
68509
68339
  exports.ClientNetworkStatsSchema = ClientNetworkStatsSchema;
68510
68340
  exports.ClimateControlStatusSchema = ClimateControlStatusSchema;
68511
68341
  exports.ClipBytesOfferSchema = ClipBytesOfferSchema;
@@ -68535,37 +68365,42 @@ exports.ComposerApplyResultSchema = ComposerApplyResultSchema;
68535
68365
  exports.ComposerBlockSchema = ComposerBlockSchema;
68536
68366
  exports.ComposerBlockVerdictSchema = ComposerBlockVerdictSchema;
68537
68367
  exports.ComposerUnreadableBlockSchema = ComposerUnreadableBlockSchema;
68538
- exports.CompositionBindingSchema = CompositionBindingSchema;
68368
+ exports.CompositionBindingSchema = require_composition.CompositionBindingSchema;
68539
68369
  exports.CompositionBlockInputSchema = CompositionBlockInputSchema;
68540
68370
  exports.CompositionBlockStateSchema = CompositionBlockStateSchema;
68541
- exports.CompositionCodeSourceSchema = CompositionCodeSourceSchema;
68542
- exports.CompositionCommandTargetSchema = CompositionCommandTargetSchema;
68543
- exports.CompositionExistingTargetSchema = CompositionExistingTargetSchema;
68544
- exports.CompositionExpressionSourceSchema = CompositionExpressionSourceSchema;
68545
- exports.CompositionFeatureSchema = CompositionFeatureSchema;
68371
+ exports.CompositionCodeSourceSchema = require_composition.CompositionCodeSourceSchema;
68372
+ exports.CompositionCommandTargetSchema = require_composition.CompositionCommandTargetSchema;
68373
+ exports.CompositionCustomizationReadSchema = CompositionCustomizationReadSchema;
68374
+ exports.CompositionExistingTargetSchema = require_composition.CompositionExistingTargetSchema;
68375
+ exports.CompositionExpressionSourceSchema = require_composition.CompositionExpressionSourceSchema;
68376
+ exports.CompositionFeatureModeSchema = CompositionFeatureModeSchema;
68377
+ exports.CompositionFeaturePlanSchema = CompositionFeaturePlanSchema;
68378
+ exports.CompositionFeatureSchema = require_composition.CompositionFeatureSchema;
68379
+ exports.CompositionFieldItemSchema = CompositionFieldItemSchema;
68546
68380
  exports.CompositionFieldKindSchema = CompositionFieldKindSchema;
68547
68381
  exports.CompositionFieldPlanSchema = CompositionFieldPlanSchema;
68548
- exports.CompositionFieldReadSchema = CompositionFieldReadSchema;
68382
+ exports.CompositionFieldReadSchema = require_composition.CompositionFieldReadSchema;
68549
68383
  exports.CompositionFieldRoleSchema = CompositionFieldRoleSchema;
68550
- exports.CompositionFieldSourceSchema = CompositionFieldSourceSchema;
68384
+ exports.CompositionFieldSourceSchema = require_composition.CompositionFieldSourceSchema;
68551
68385
  exports.CompositionFieldStateKindSchema = CompositionFieldStateKindSchema;
68552
68386
  exports.CompositionFieldStateSchema = CompositionFieldStateSchema;
68553
- exports.CompositionFieldsFeatureSchema = CompositionFieldsFeatureSchema;
68554
- exports.CompositionForwardCommandSchema = CompositionForwardCommandSchema;
68555
- exports.CompositionFromSourceSchema = CompositionFromSourceSchema;
68387
+ exports.CompositionFieldsFeatureSchema = require_composition.CompositionFieldsFeatureSchema;
68388
+ exports.CompositionForwardCommandSchema = require_composition.CompositionForwardCommandSchema;
68389
+ exports.CompositionFromSourceSchema = require_composition.CompositionFromSourceSchema;
68556
68390
  exports.CompositionGetReadSchema = CompositionGetReadSchema;
68557
68391
  exports.CompositionGraphError = CompositionGraphError;
68392
+ exports.CompositionItemEntrySchema = require_composition.CompositionItemEntrySchema;
68558
68393
  exports.CompositionListReadSchema = CompositionListReadSchema;
68559
- exports.CompositionLiteralBindingSchema = CompositionLiteralBindingSchema;
68560
- exports.CompositionNewTargetSchema = CompositionNewTargetSchema;
68561
- exports.CompositionPassthroughFeatureSchema = CompositionPassthroughFeatureSchema;
68394
+ exports.CompositionLiteralBindingSchema = require_composition.CompositionLiteralBindingSchema;
68395
+ exports.CompositionNewTargetSchema = require_composition.CompositionNewTargetSchema;
68396
+ exports.CompositionPassthroughFeatureSchema = require_composition.CompositionPassthroughFeatureSchema;
68562
68397
  exports.CompositionProblemCodeSchema = CompositionProblemCodeSchema;
68563
68398
  exports.CompositionProblemSchema = CompositionProblemSchema;
68564
68399
  exports.CompositionRunStatusSchema = CompositionRunStatusSchema;
68565
- exports.CompositionSchema = CompositionSchema;
68566
- exports.CompositionSourceRefSchema = CompositionSourceRefSchema;
68400
+ exports.CompositionSchema = require_composition.CompositionSchema;
68401
+ exports.CompositionSourceRefSchema = require_composition.CompositionSourceRefSchema;
68567
68402
  exports.CompositionStateReadSchema = CompositionStateReadSchema;
68568
- exports.CompositionTargetSchema = CompositionTargetSchema;
68403
+ exports.CompositionTargetSchema = require_composition.CompositionTargetSchema;
68569
68404
  exports.CompositionUnavailableModeSchema = CompositionUnavailableModeSchema;
68570
68405
  exports.CompositionValidationSchema = CompositionValidationSchema;
68571
68406
  exports.CompositionViewSchema = CompositionViewSchema;
@@ -68577,6 +68412,8 @@ exports.ConnectionTestInputSchema = ConnectionTestInputSchema;
68577
68412
  exports.ConnectionTestOutcomeSchema = ConnectionTestOutcomeSchema;
68578
68413
  exports.ConnectivityStatusSchema = ConnectivityStatusSchema;
68579
68414
  exports.ConsumableItemSchema = ConsumableItemSchema;
68415
+ exports.ConsumableRemainingSchema = ConsumableRemainingSchema;
68416
+ exports.ConsumableRemainingUnitSchema = ConsumableRemainingUnitSchema;
68580
68417
  exports.ConsumablesStatusSchema = ConsumablesStatusSchema;
68581
68418
  exports.ContactStatusSchema = ContactStatusSchema;
68582
68419
  exports.ControlKindSchema = ControlKindSchema;
@@ -68670,15 +68507,15 @@ exports.DeviceDiscoveryStatusSchema = DeviceDiscoveryStatusSchema;
68670
68507
  exports.DeviceExportExposeInputSchema = ExposeInputSchema;
68671
68508
  exports.DeviceExportStatusSchema = DeviceExportStatusSchema;
68672
68509
  exports.DeviceExportUnexposeInputSchema = UnexposeInputSchema;
68673
- exports.DeviceFeature = require_sleep.DeviceFeature;
68510
+ exports.DeviceFeature = require_err_msg.DeviceFeature;
68674
68511
  exports.DeviceInfoSchema = DeviceInfoSchema;
68675
68512
  exports.DeviceNetworkStatsSchema = DeviceNetworkStatsSchema;
68676
68513
  exports.DeviceRestoreRetryScheduler = DeviceRestoreRetryScheduler;
68677
- exports.DeviceRole = require_sleep.DeviceRole;
68514
+ exports.DeviceRole = require_err_msg.DeviceRole;
68678
68515
  exports.DeviceRuntimeState = DeviceRuntimeState;
68679
68516
  exports.DeviceSelectorSchema = DeviceSelectorSchema;
68680
68517
  exports.DeviceStatusSchema = DeviceStatusSchema;
68681
- exports.DeviceType = require_sleep.DeviceType;
68518
+ exports.DeviceType = require_err_msg.DeviceType;
68682
68519
  exports.DiagnosticIdSchema = DiagnosticIdSchema;
68683
68520
  exports.DiagnosticWindowPatchSchema = DiagnosticWindowPatchSchema;
68684
68521
  exports.DiagnosticWindowSchema = DiagnosticWindowSchema;
@@ -68701,11 +68538,11 @@ exports.EVENT_OWNER_TYPES = EVENT_OWNER_TYPES;
68701
68538
  exports.EVENT_PAD_MS = EVENT_PAD_MS;
68702
68539
  exports.EVENT_TAXONOMY = EVENT_TAXONOMY;
68703
68540
  exports.EXPORT_DENSE_MAX_RANGES = EXPORT_DENSE_MAX_RANGES;
68704
- exports.EXPRESSION_BUILTINS = EXPRESSION_BUILTINS;
68705
- exports.EXPRESSION_BUILTIN_NAMES = EXPRESSION_BUILTIN_NAMES;
68706
- exports.EXPRESSION_COMPILE_CACHE_CAPACITY = EXPRESSION_COMPILE_CACHE_CAPACITY;
68707
- exports.EXPRESSION_IDENTIFIER_RE = EXPRESSION_IDENTIFIER_RE;
68708
- exports.EXPRESSION_INJECTED_NOW = EXPRESSION_INJECTED_NOW;
68541
+ exports.EXPRESSION_BUILTINS = require_composition.EXPRESSION_BUILTINS;
68542
+ exports.EXPRESSION_BUILTIN_NAMES = require_composition.EXPRESSION_BUILTIN_NAMES;
68543
+ exports.EXPRESSION_COMPILE_CACHE_CAPACITY = require_composition.EXPRESSION_COMPILE_CACHE_CAPACITY;
68544
+ exports.EXPRESSION_IDENTIFIER_RE = require_composition.EXPRESSION_IDENTIFIER_RE;
68545
+ exports.EXPRESSION_INJECTED_NOW = require_composition.EXPRESSION_INJECTED_NOW;
68709
68546
  exports.EXTENSION_HOST_DEVICE_TYPES = EXTENSION_HOST_DEVICE_TYPES;
68710
68547
  exports.EXTENSION_SLOTS = EXTENSION_SLOTS;
68711
68548
  exports.EXTENSION_SLOT_IDS = EXTENSION_SLOT_IDS;
@@ -68761,11 +68598,11 @@ exports.ExportTimelapseSchema = ExportTimelapseSchema;
68761
68598
  exports.ExposedDeviceSchema = ExposedDeviceSchema;
68762
68599
  exports.ExposureModeSchema = ExposureModeSchema;
68763
68600
  exports.ExpressionBindingSourceSchema = ExpressionBindingSourceSchema;
68764
- exports.ExpressionEvalError = ExpressionEvalError;
68601
+ exports.ExpressionEvalError = require_composition.ExpressionEvalError;
68765
68602
  exports.ExpressionFieldBindingSchema = ExpressionFieldBindingSchema;
68766
68603
  exports.ExpressionGlobalBindingSchema = ExpressionGlobalBindingSchema;
68767
68604
  exports.ExpressionLiteralBindingSchema = ExpressionLiteralBindingSchema;
68768
- exports.ExpressionParseError = ExpressionParseError;
68605
+ exports.ExpressionParseError = require_composition.ExpressionParseError;
68769
68606
  exports.ExpressionSourceSchema = ExpressionSourceSchema;
68770
68607
  exports.ExtensionCandidateSchema = ExtensionCandidateSchema;
68771
68608
  exports.ExtensionCandidatesSchema = ExtensionCandidatesSchema;
@@ -68782,6 +68619,8 @@ exports.ExtensionSlotValueSchema = ExtensionSlotValueSchema;
68782
68619
  exports.ExtensionSourceAvailabilitySchema = ExtensionSourceAvailabilitySchema;
68783
68620
  exports.ExtensionSourceHealthSchema = ExtensionSourceHealthSchema;
68784
68621
  exports.ExtensionSourceSchema = ExtensionSourceSchema;
68622
+ exports.FEEDER_EXAMPLE_ADDON_ID = FEEDER_EXAMPLE_ADDON_ID;
68623
+ exports.FEEDER_EXAMPLE_HA_DEVICE_ID = FEEDER_EXAMPLE_HA_DEVICE_ID;
68785
68624
  exports.FIRST_LEVEL_MACRO_CLASSES = FIRST_LEVEL_MACRO_CLASSES;
68786
68625
  exports.FULL_IMAGE_BBOX = FULL_IMAGE_BBOX;
68787
68626
  exports.FailureContributionSchema = FailureContributionSchema;
@@ -68791,8 +68630,10 @@ exports.FanControlStatusSchema = FanControlStatusSchema;
68791
68630
  exports.FanDirectionSchema = FanDirectionSchema;
68792
68631
  exports.FeatureManifestSchema = FeatureManifestSchema;
68793
68632
  exports.FeatureProbeStatusSchema = FeatureProbeStatusSchema;
68633
+ exports.FieldClaimModeSchema = FieldClaimModeSchema;
68634
+ exports.FieldClaimSchema = FieldClaimSchema;
68794
68635
  exports.FloodStatusSchema = FloodStatusSchema;
68795
- exports.Fmp4BoxSplitter = require_canonical_hash.Fmp4BoxSplitter;
68636
+ exports.Fmp4BoxSplitter = require_composition.Fmp4BoxSplitter;
68796
68637
  exports.FrameHandleFormatSchema = require_sleep.FrameHandleFormatSchema;
68797
68638
  exports.FrameHandleSchema = require_sleep.FrameHandleSchema;
68798
68639
  exports.FrameInputSchema = FrameInputSchema;
@@ -68921,16 +68762,17 @@ exports.MAX_BIRTH_DECISION_RECORDS = MAX_BIRTH_DECISION_RECORDS;
68921
68762
  exports.MAX_BIRTH_LATENCY_PROXY_MS = MAX_BIRTH_LATENCY_PROXY_MS;
68922
68763
  exports.MAX_CLIP_EVENT_IDS = MAX_CLIP_EVENT_IDS;
68923
68764
  exports.MAX_CLIP_LABELS = MAX_CLIP_LABELS;
68924
- exports.MAX_COMPOSITION_CODE_LENGTH = MAX_COMPOSITION_CODE_LENGTH;
68925
- exports.MAX_COMPOSITION_FEATURES = MAX_COMPOSITION_FEATURES;
68926
- exports.MAX_COMPOSITION_FIELDS_PER_FEATURE = MAX_COMPOSITION_FIELDS_PER_FEATURE;
68765
+ exports.MAX_COMPOSITION_CODE_LENGTH = require_composition.MAX_COMPOSITION_CODE_LENGTH;
68766
+ exports.MAX_COMPOSITION_FEATURES = require_composition.MAX_COMPOSITION_FEATURES;
68767
+ exports.MAX_COMPOSITION_FIELDS_PER_FEATURE = require_composition.MAX_COMPOSITION_FIELDS_PER_FEATURE;
68768
+ exports.MAX_COMPOSITION_ITEMS = require_composition.MAX_COMPOSITION_ITEMS;
68927
68769
  exports.MAX_CONDITION_DEPTH = MAX_CONDITION_DEPTH;
68928
68770
  exports.MAX_CONDITION_LEAVES = MAX_CONDITION_LEAVES;
68929
- exports.MAX_EXPRESSION_AST_NODES = MAX_EXPRESSION_AST_NODES;
68930
- exports.MAX_EXPRESSION_BINDINGS = MAX_EXPRESSION_BINDINGS;
68931
- exports.MAX_EXPRESSION_CALL_ARGS = MAX_EXPRESSION_CALL_ARGS;
68932
- exports.MAX_EXPRESSION_EVAL_STEPS = MAX_EXPRESSION_EVAL_STEPS;
68933
- exports.MAX_EXPRESSION_SOURCE_LENGTH = MAX_EXPRESSION_SOURCE_LENGTH;
68771
+ exports.MAX_EXPRESSION_AST_NODES = require_composition.MAX_EXPRESSION_AST_NODES;
68772
+ exports.MAX_EXPRESSION_BINDINGS = require_composition.MAX_EXPRESSION_BINDINGS;
68773
+ exports.MAX_EXPRESSION_CALL_ARGS = require_composition.MAX_EXPRESSION_CALL_ARGS;
68774
+ exports.MAX_EXPRESSION_EVAL_STEPS = require_composition.MAX_EXPRESSION_EVAL_STEPS;
68775
+ exports.MAX_EXPRESSION_SOURCE_LENGTH = require_composition.MAX_EXPRESSION_SOURCE_LENGTH;
68934
68776
  exports.MAX_KEYS = MAX_KEYS;
68935
68777
  exports.MAX_REASONS_PER_KEY = MAX_REASONS_PER_KEY;
68936
68778
  exports.MAX_SENSOR_TRIGGER_DEVICES = MAX_SENSOR_TRIGGER_DEVICES;
@@ -69186,6 +69028,7 @@ exports.OPS_LOG_DEFAULT_LIMIT = OPS_LOG_DEFAULT_LIMIT;
69186
69028
  exports.OPS_LOG_RING_DEFAULT_MAX = OPS_LOG_RING_DEFAULT_MAX;
69187
69029
  exports.OVERFLOW_REASON = OVERFLOW_REASON;
69188
69030
  exports.OVERLAY_LAYERS = OVERLAY_LAYERS;
69031
+ exports.OWNER_ONLY_EVENT_CATEGORIES = require_event_category.OWNER_ONLY_EVENT_CATEGORIES;
69189
69032
  exports.OauthIntegrationDescriptorSchema = OauthIntegrationDescriptorSchema;
69190
69033
  exports.ObjectEventSchema = ObjectEventSchema;
69191
69034
  exports.OccupancyBurstRefusalSchema = OccupancyBurstRefusalSchema;
@@ -69216,6 +69059,7 @@ exports.PEER_BYTES_CHUNK_BYTES = require_sleep.PEER_BYTES_CHUNK_BYTES;
69216
69059
  exports.PEER_BYTES_MAX_OUTSTANDING = require_sleep.PEER_BYTES_MAX_OUTSTANDING;
69217
69060
  exports.PEER_BYTES_STALL_MS = require_sleep.PEER_BYTES_STALL_MS;
69218
69061
  exports.PEER_BYTES_TICKET_TTL_MS = require_sleep.PEER_BYTES_TICKET_TTL_MS;
69062
+ exports.PENDING_BLOCK_ID = PENDING_BLOCK_ID;
69219
69063
  exports.PET_FEEDER_MANUAL_FEED_MAX = PET_FEEDER_MANUAL_FEED_MAX;
69220
69064
  exports.PET_FEEDER_MANUAL_FEED_MIN = PET_FEEDER_MANUAL_FEED_MIN;
69221
69065
  exports.PIPELINE_ANALYTICS_CAP_NAME = PIPELINE_ANALYTICS_CAP_NAME;
@@ -69287,7 +69131,7 @@ exports.RECORDING_SOURCE_CAMSTACK = RECORDING_SOURCE_CAMSTACK;
69287
69131
  exports.RECORDING_SOURCE_CAMSTACK_ADDON = RECORDING_SOURCE_CAMSTACK_ADDON;
69288
69132
  exports.RECORDING_TIMELINE_BATCH_MAX = RECORDING_TIMELINE_BATCH_MAX;
69289
69133
  exports.REDACTED_SECRET = REDACTED_SECRET;
69290
- exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
69134
+ exports.RESERVED_BINDING_NAMES = require_composition.RESERVED_BINDING_NAMES;
69291
69135
  exports.RESERVED_COLLECTION_SOURCES = RESERVED_COLLECTION_SOURCES;
69292
69136
  exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
69293
69137
  exports.ROOM_EXAMPLE_MOTION_SOURCES = ROOM_EXAMPLE_MOTION_SOURCES;
@@ -69675,7 +69519,7 @@ exports.audioLabelChoices = audioLabelChoices;
69675
69519
  exports.audioMetricsCapability = audioMetricsCapability;
69676
69520
  exports.audioModeOf = audioModeOf;
69677
69521
  exports.audioOrDefaults = audioOrDefaults;
69678
- exports.audioPlanFromEncodeProfile = require_canonical_hash.audioPlanFromEncodeProfile;
69522
+ exports.audioPlanFromEncodeProfile = require_composition.audioPlanFromEncodeProfile;
69679
69523
  exports.audioScaleEraOf = audioScaleEraOf;
69680
69524
  exports.authProviderCapability = authProviderCapability;
69681
69525
  exports.autoAssignProfiles = autoAssignProfiles;
@@ -69689,17 +69533,17 @@ exports.bindAddonActions = bindAddonActions;
69689
69533
  exports.brightnessCapability = brightnessCapability;
69690
69534
  exports.brokerCapability = brokerCapability;
69691
69535
  exports.buildAddonRouteProvider = buildAddonRouteProvider;
69692
- exports.buildAudioArgs = require_canonical_hash.buildAudioArgs;
69536
+ exports.buildAudioArgs = require_composition.buildAudioArgs;
69693
69537
  exports.buildCapabilityLookup = buildCapabilityLookup;
69694
69538
  exports.buildDeviceAdminUrl = buildDeviceAdminUrl;
69695
69539
  exports.buildEventKindDescriptor = buildEventKindDescriptor;
69696
- exports.buildFfmpegArgs = require_canonical_hash.buildFfmpegArgs;
69697
- exports.buildInputArgs = require_canonical_hash.buildInputArgs;
69540
+ exports.buildFfmpegArgs = require_composition.buildFfmpegArgs;
69541
+ exports.buildInputArgs = require_composition.buildInputArgs;
69698
69542
  exports.buildModelVariantGroups = buildModelVariantGroups;
69699
69543
  exports.buildNcTaxonomy = buildNcTaxonomy;
69700
69544
  exports.buildRoleScopes = buildRoleScopes;
69701
69545
  exports.buildStreamParamsConfigSchema = buildStreamParamsConfigSchema;
69702
- exports.buildVideoArgs = require_canonical_hash.buildVideoArgs;
69546
+ exports.buildVideoArgs = require_composition.buildVideoArgs;
69703
69547
  exports.buttonCapability = buttonCapability;
69704
69548
  exports.cameraCredentialsCapability = cameraCredentialsCapability;
69705
69549
  exports.cameraGridLayoutCapability = cameraGridLayoutCapability;
@@ -69727,16 +69571,18 @@ exports.colorCapability = colorCapability;
69727
69571
  exports.colorForKind = colorForKind;
69728
69572
  exports.commitWatchdogRestart = commitWatchdogRestart;
69729
69573
  exports.compareToTarget = compareToTarget;
69730
- exports.compileExpression = compileExpression;
69731
- exports.compileExpressionSafe = compileExpressionSafe;
69574
+ exports.compileExpression = require_composition.compileExpression;
69575
+ exports.compileExpressionSafe = require_composition.compileExpressionSafe;
69732
69576
  exports.composeSwitchedOff = composeSwitchedOff;
69733
- exports.composedDeviceRef = composedDeviceRef;
69734
- exports.composedDeviceStableId = composedDeviceStableId;
69577
+ exports.composedDeviceRef = require_composition.composedDeviceRef;
69578
+ exports.composedDeviceStableId = require_composition.composedDeviceStableId;
69735
69579
  exports.composerCapability = composerCapability;
69736
69580
  exports.compositionEdges = compositionEdges;
69737
- exports.compositionReadKey = compositionReadKey;
69738
- exports.compositionSliceKey = compositionSliceKey;
69739
- exports.compositionSourceKey = compositionSourceKey;
69581
+ exports.compositionFieldKey = compositionFieldKey;
69582
+ exports.compositionItemFieldPath = require_composition.compositionItemFieldPath;
69583
+ exports.compositionReadKey = require_composition.compositionReadKey;
69584
+ exports.compositionSliceKey = require_composition.compositionSliceKey;
69585
+ exports.compositionSourceKey = require_composition.compositionSourceKey;
69740
69586
  exports.compositionSourceRefs = compositionSourceRefs;
69741
69587
  exports.conditionDepth = conditionDepth;
69742
69588
  exports.conditionExclusionReason = conditionExclusionReason;
@@ -69761,7 +69607,7 @@ exports.createDeviceProxy = require_sleep.createDeviceProxy;
69761
69607
  exports.createDurableState = require_sleep.createDurableState;
69762
69608
  exports.createEvent = require_sleep.createEvent;
69763
69609
  exports.createEventBusSliceSource = require_sleep.createEventBusSliceSource;
69764
- exports.createExpressionScope = createExpressionScope;
69610
+ exports.createExpressionScope = require_composition.createExpressionScope;
69765
69611
  exports.createHwAccelCache = createHwAccelCache;
69766
69612
  exports.createLazyTrpcSource = require_sleep.createLazyTrpcSource;
69767
69613
  exports.createLogChannelsProvider = createLogChannelsProvider;
@@ -69832,9 +69678,9 @@ exports.enumerateInferenceDevices = enumerateInferenceDevices;
69832
69678
  exports.enumerateItemArrayFields = enumerateItemArrayFields;
69833
69679
  exports.enumerateSchemaFields = enumerateSchemaFields;
69834
69680
  exports.errMsg = require_err_msg.errMsg;
69835
- exports.evaluateAst = evaluateAst;
69681
+ exports.evaluateAst = require_composition.evaluateAst;
69836
69682
  exports.evaluateCompositionField = evaluateCompositionField;
69837
- exports.evaluateExpressionSource = evaluateExpressionSource;
69683
+ exports.evaluateExpressionSource = require_composition.evaluateExpressionSource;
69838
69684
  exports.evaluatePoolMemory = evaluatePoolMemory;
69839
69685
  exports.evaluateSensorEdge = evaluateSensorEdge;
69840
69686
  exports.evaluateZoneRules = evaluateZoneRules;
@@ -69853,9 +69699,14 @@ exports.faceGalleryCapability = faceGalleryCapability;
69853
69699
  exports.failureContributionCapability = failureContributionCapability;
69854
69700
  exports.failureRate = failureRate;
69855
69701
  exports.fanControlCapability = fanControlCapability;
69702
+ exports.featureFieldEntries = require_composition.featureFieldEntries;
69856
69703
  exports.featureProbeCapability = featureProbeCapability;
69857
- exports.fieldReads = fieldReads;
69704
+ exports.featureSources = require_composition.featureSources;
69705
+ exports.feederCustomizationExample = feederCustomizationExample;
69706
+ exports.feederExampleSources = feederExampleSources;
69707
+ exports.fieldReads = require_composition.fieldReads;
69858
69708
  exports.filesystemBrowseCapability = filesystemBrowseCapability;
69709
+ exports.findCandidateCycles = findCandidateCycles;
69859
69710
  exports.findCompositionCycles = findCompositionCycles;
69860
69711
  exports.findTimezone = findTimezone;
69861
69712
  exports.floodCapability = floodCapability;
@@ -69887,7 +69738,7 @@ exports.integrationsCapability = integrationsCapability;
69887
69738
  exports.intercomCapability = intercomCapability;
69888
69739
  exports.interpretStoredClusterModel = interpretStoredClusterModel;
69889
69740
  exports.invalidClusterStepModelEntries = invalidClusterStepModelEntries;
69890
- exports.invocationFromEncodeProfile = require_canonical_hash.invocationFromEncodeProfile;
69741
+ exports.invocationFromEncodeProfile = require_composition.invocationFromEncodeProfile;
69891
69742
  exports.isAgentOnlyPlacement = isAgentOnlyPlacement;
69892
69743
  exports.isArrayOutputSchema = isArrayOutputSchema;
69893
69744
  exports.isAudioChunkFormat = require_sleep.isAudioChunkFormat;
@@ -69914,11 +69765,12 @@ exports.isMediaOwnerType = isMediaOwnerType;
69914
69765
  exports.isNode = isNode;
69915
69766
  exports.isObjectInput = isObjectInput;
69916
69767
  exports.isOccupancyRule = isOccupancyRule;
69768
+ exports.isOwnerOnlyEventCategory = require_event_category.isOwnerOnlyEventCategory;
69917
69769
  exports.isRestoredCap = isRestoredCap;
69918
69770
  exports.isSameAddonId = isSameAddonId;
69919
69771
  exports.isScheduleActive = isScheduleActive;
69920
69772
  exports.isSecretConfigField = isSecretConfigField;
69921
- exports.isSoftwareDecode = require_canonical_hash.isSoftwareDecode;
69773
+ exports.isSoftwareDecode = require_composition.isSoftwareDecode;
69922
69774
  exports.isSourceCap = isSourceCap;
69923
69775
  exports.isStorageLocationMode = isStorageLocationMode;
69924
69776
  exports.isSystemDelivery = isSystemDelivery;
@@ -69947,7 +69799,7 @@ exports.localNetworkCapability = localNetworkCapability;
69947
69799
  exports.locationIconKey = locationIconKey;
69948
69800
  exports.locationSimilarity = locationSimilarity;
69949
69801
  exports.lockControlCapability = lockControlCapability;
69950
- exports.logBannerArgs = require_canonical_hash.logBannerArgs;
69802
+ exports.logBannerArgs = require_composition.logBannerArgs;
69951
69803
  exports.logChannelsCapability = logChannelsCapability;
69952
69804
  exports.logDestinationCapability = logDestinationCapability;
69953
69805
  exports.logLevelAtMost = logLevelAtMost;
@@ -70000,6 +69852,7 @@ exports.oauthIntegrationCapability = oauthIntegrationCapability;
70000
69852
  exports.objectInputDeclaresAddonId = objectInputDeclaresAddonId;
70001
69853
  exports.objectInputDeclaresRequiredDeviceId = objectInputDeclaresRequiredDeviceId;
70002
69854
  exports.occupancyScope = occupancyScope;
69855
+ exports.optionalAncestors = optionalAncestors;
70003
69856
  exports.optionalMethod = require_sleep.optionalMethod;
70004
69857
  exports.osdCapability = osdCapability;
70005
69858
  exports.osdManagerCapability = osdManagerCapability;
@@ -70007,7 +69860,7 @@ exports.overlayClusterStepSettings = overlayClusterStepSettings;
70007
69860
  exports.packagePickerItems = packagePickerItems;
70008
69861
  exports.parseAdminBase = parseAdminBase;
70009
69862
  exports.parseCameraStreamConfig = parseCameraStreamConfig;
70010
- exports.parseExpression = parseExpression;
69863
+ exports.parseExpression = require_composition.parseExpression;
70011
69864
  exports.parseJsonArray = require_sleep.parseJsonArray;
70012
69865
  exports.parseJsonObject = require_sleep.parseJsonObject;
70013
69866
  exports.parseJsonUnknown = require_sleep.parseJsonUnknown;
@@ -70027,13 +69880,14 @@ exports.pickInvalidClusterStepModels = pickInvalidClusterStepModels;
70027
69880
  exports.pickNativeLeaseOverride = pickNativeLeaseOverride;
70028
69881
  exports.pickPreferredRtspEntry = pickPreferredRtspEntry;
70029
69882
  exports.pickRestartCandidate = pickRestartCandidate;
70030
- exports.pickVideoEncoder = require_canonical_hash.pickVideoEncoder;
69883
+ exports.pickVideoEncoder = require_composition.pickVideoEncoder;
70031
69884
  exports.pickerForCondition = pickerForCondition;
70032
69885
  exports.pipelineAnalyticsCapability = pipelineAnalyticsCapability;
70033
69886
  exports.pipelineExecutorCapability = pipelineExecutorCapability;
70034
69887
  exports.pipelineOrchestratorCapability = pipelineOrchestratorCapability;
70035
69888
  exports.pipelineRunnerCapability = pipelineRunnerCapability;
70036
69889
  exports.planComposition = planComposition;
69890
+ exports.planItems = planItems;
70037
69891
  exports.plateGalleryCapability = plateGalleryCapability;
70038
69892
  exports.platformProbeCapability = platformProbeCapability;
70039
69893
  exports.poolMemoryThreshold = poolMemoryThreshold;
@@ -70046,6 +69900,7 @@ exports.privacyMaskCapability = privacyMaskCapability;
70046
69900
  exports.procedureAuthKey = procedureAuthKey;
70047
69901
  exports.ptzAutotrackCapability = ptzAutotrackCapability;
70048
69902
  exports.ptzCapability = ptzCapability;
69903
+ exports.publicEventCategories = require_event_category.publicEventCategories;
70049
69904
  exports.pythonScriptForBackend = pythonScriptForBackend;
70050
69905
  exports.rankStreams = rankStreams;
70051
69906
  exports.readClusterStepModels = readClusterStepModels;
@@ -70064,6 +69919,7 @@ exports.recordingOnboardCapability = recordingOnboardCapability;
70064
69919
  exports.recordingSignalCapability = recordingSignalCapability;
70065
69920
  exports.rectsToCells = rectsToCells;
70066
69921
  exports.reducePoints = reducePoints;
69922
+ exports.replacedFieldKeys = replacedFieldKeys;
70067
69923
  exports.requiredTopLevelKeys = requiredTopLevelKeys;
70068
69924
  exports.requiresPython = requiresPython;
70069
69925
  exports.reservedSourceOwner = reservedSourceOwner;
@@ -70090,7 +69946,7 @@ exports.resolveMutate = resolveMutate;
70090
69946
  exports.resolvePoolMemoryPolicy = resolvePoolMemoryPolicy;
70091
69947
  exports.resolveRecordingProfiles = resolveRecordingProfiles;
70092
69948
  exports.resolveRunnerId = resolveRunnerId;
70093
- exports.resolveStreamMaps = require_canonical_hash.resolveStreamMaps;
69949
+ exports.resolveStreamMaps = require_composition.resolveStreamMaps;
70094
69950
  exports.resolveVariantModelId = resolveVariantModelId;
70095
69951
  exports.resolveViewableDeviceIds = resolveViewableDeviceIds;
70096
69952
  exports.roleSpec = roleSpec;
@@ -70184,24 +70040,25 @@ exports.terminalSessionCapability = terminalSessionCapability;
70184
70040
  exports.textToHtml = textToHtml;
70185
70041
  exports.timeOfDayToMinutes = timeOfDayToMinutes;
70186
70042
  exports.toDeviceSummary = toDeviceSummary;
70187
- exports.toExpressionValue = toExpressionValue;
70043
+ exports.toExpressionValue = require_composition.toExpressionValue;
70188
70044
  exports.toNodeId = require_sleep.toNodeId;
70189
70045
  exports.toStreamSourceEntry = toStreamSourceEntry;
70190
70046
  exports.toastCapability = toastCapability;
70191
70047
  exports.toggleAudioLabel = toggleAudioLabel;
70192
- exports.tokenize = tokenize;
70048
+ exports.tokenize = require_composition.tokenize;
70193
70049
  exports.transcodeBody = transcodeBody;
70194
70050
  exports.tryConvertUnit = tryConvertUnit;
70195
70051
  exports.turnProviderCapability = turnProviderCapability;
70196
70052
  exports.unitDimension = unitDimension;
70197
70053
  exports.unitsForDimension = unitsForDimension;
70198
70054
  exports.unreachableExtensionSlotRow = unreachableExtensionSlotRow;
70055
+ exports.unwrapSchema = unwrapSchema;
70199
70056
  exports.updateCapability = updateCapability;
70200
70057
  exports.userManagementCapability = userManagementCapability;
70201
70058
  exports.userPasskeysCapability = userPasskeysCapability;
70202
70059
  exports.vacuumControlCapability = vacuumControlCapability;
70203
70060
  exports.validateComposition = validateComposition;
70204
- exports.validateExpressionSource = validateExpressionSource;
70061
+ exports.validateExpressionSource = require_composition.validateExpressionSource;
70205
70062
  exports.validateRecipeBounds = validateRecipeBounds;
70206
70063
  exports.valveCapability = valveCapability;
70207
70064
  exports.vectorDimFromBase64 = vectorDimFromBase64;