@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
@@ -14,13 +14,19 @@
14
14
  * Later slices EXTEND these unions and do not reshape them. The variants a later
15
15
  * slice implements are already in the wire shape, and `validateComposition`
16
16
  * refuses them BY NAME until then:
17
- * target `existing` → slice 2 · feature `passthrough` → slice 3 ·
18
- * `commands` (`forward`) → slice 3 · source `code` → slice 4.
17
+ * feature `passthrough` → slice 3 · `commands` (`forward`) → slice 3 ·
18
+ * source `code` → slice 4. Target `existing` (customize a device) is slice 2 (D663).
19
19
  */
20
20
  import { z } from 'zod';
21
21
  import { DeviceRole, DeviceType } from '../device/device-type.js';
22
22
  /** The owner of every composed device: the `composer` builtin. */
23
23
  export declare const COMPOSER_ADDON_ID = "composer";
24
+ /**
25
+ * The stored name of a block that customizes an EXISTING device starts with this
26
+ * (`customizationBlockName`, `@camstack/types/node`). Never parsed to tell a
27
+ * customization apart: `composition.target.kind === 'existing'` says that (D663).
28
+ */
29
+ export declare const CUSTOMIZATION_BLOCK_NAME_PREFIX = "customize:";
24
30
  /** A composed device's stableId is this prefix plus the block id. */
25
31
  export declare const COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
26
32
  /** A device with more capabilities than this is two devices. */
@@ -153,6 +159,47 @@ export declare const CompositionCommandTargetSchema: z.ZodDiscriminatedUnion<[z.
153
159
  code: z.ZodString;
154
160
  }, z.core.$strip>], "kind">;
155
161
  export type CompositionCommandTarget = z.infer<typeof CompositionCommandTargetSchema>;
162
+ /** An item key inside an item-array cap (`consumables.items[<key>]`). */
163
+ export declare const COMPOSITION_ITEM_KEY_RE: RegExp;
164
+ export declare const MAX_COMPOSITION_ITEMS = 16;
165
+ /** The item-array path of every item-array cap that exists (`consumables.items`). */
166
+ export declare const COMPOSITION_ITEM_ARRAY_PATH = "items";
167
+ /**
168
+ * One item of an item-array cap (D663), addressed through the cap's
169
+ * `status.itemArray` descriptor. `fields` are paths INSIDE the item, nested
170
+ * allowed (`remaining.value`); the key and label come from the entry itself.
171
+ */
172
+ export declare const CompositionItemEntrySchema: z.ZodObject<{
173
+ label: z.ZodString;
174
+ fields: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
175
+ source: z.ZodObject<{
176
+ addonId: z.ZodString;
177
+ stableId: z.ZodString;
178
+ }, z.core.$strip>;
179
+ cap: z.ZodString;
180
+ fieldPath: z.ZodString;
181
+ kind: z.ZodLiteral<"from">;
182
+ }, z.core.$strip>, z.ZodObject<{
183
+ kind: z.ZodLiteral<"expression">;
184
+ expr: z.ZodString;
185
+ bindings: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
186
+ source: z.ZodObject<{
187
+ addonId: z.ZodString;
188
+ stableId: z.ZodString;
189
+ }, z.core.$strip>;
190
+ cap: z.ZodString;
191
+ fieldPath: z.ZodString;
192
+ kind: z.ZodLiteral<"from">;
193
+ }, z.core.$strip>, z.ZodObject<{
194
+ kind: z.ZodLiteral<"literal">;
195
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
196
+ }, z.core.$strip>], "kind">>;
197
+ }, z.core.$strip>, z.ZodObject<{
198
+ kind: z.ZodLiteral<"code">;
199
+ code: z.ZodString;
200
+ }, z.core.$strip>], "kind">>;
201
+ }, z.core.$strip>;
202
+ export type CompositionItemEntry = z.infer<typeof CompositionItemEntrySchema>;
156
203
  export declare const CompositionFieldsFeatureSchema: z.ZodObject<{
157
204
  kind: z.ZodLiteral<"fields">;
158
205
  cap: z.ZodString;
@@ -183,6 +230,36 @@ export declare const CompositionFieldsFeatureSchema: z.ZodObject<{
183
230
  kind: z.ZodLiteral<"code">;
184
231
  code: z.ZodString;
185
232
  }, z.core.$strip>], "kind">>;
233
+ items: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
234
+ label: z.ZodString;
235
+ fields: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
236
+ source: z.ZodObject<{
237
+ addonId: z.ZodString;
238
+ stableId: z.ZodString;
239
+ }, z.core.$strip>;
240
+ cap: z.ZodString;
241
+ fieldPath: z.ZodString;
242
+ kind: z.ZodLiteral<"from">;
243
+ }, z.core.$strip>, z.ZodObject<{
244
+ kind: z.ZodLiteral<"expression">;
245
+ expr: z.ZodString;
246
+ bindings: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
247
+ source: z.ZodObject<{
248
+ addonId: z.ZodString;
249
+ stableId: z.ZodString;
250
+ }, z.core.$strip>;
251
+ cap: z.ZodString;
252
+ fieldPath: z.ZodString;
253
+ kind: z.ZodLiteral<"from">;
254
+ }, z.core.$strip>, z.ZodObject<{
255
+ kind: z.ZodLiteral<"literal">;
256
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
257
+ }, z.core.$strip>], "kind">>;
258
+ }, z.core.$strip>, z.ZodObject<{
259
+ kind: z.ZodLiteral<"code">;
260
+ code: z.ZodString;
261
+ }, z.core.$strip>], "kind">>;
262
+ }, z.core.$strip>>>;
186
263
  commands: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
187
264
  kind: z.ZodLiteral<"forward">;
188
265
  source: z.ZodObject<{
@@ -237,6 +314,36 @@ export declare const CompositionFeatureSchema: z.ZodDiscriminatedUnion<[z.ZodObj
237
314
  kind: z.ZodLiteral<"code">;
238
315
  code: z.ZodString;
239
316
  }, z.core.$strip>], "kind">>;
317
+ items: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
318
+ label: z.ZodString;
319
+ fields: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
320
+ source: z.ZodObject<{
321
+ addonId: z.ZodString;
322
+ stableId: z.ZodString;
323
+ }, z.core.$strip>;
324
+ cap: z.ZodString;
325
+ fieldPath: z.ZodString;
326
+ kind: z.ZodLiteral<"from">;
327
+ }, z.core.$strip>, z.ZodObject<{
328
+ kind: z.ZodLiteral<"expression">;
329
+ expr: z.ZodString;
330
+ bindings: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
331
+ source: z.ZodObject<{
332
+ addonId: z.ZodString;
333
+ stableId: z.ZodString;
334
+ }, z.core.$strip>;
335
+ cap: z.ZodString;
336
+ fieldPath: z.ZodString;
337
+ kind: z.ZodLiteral<"from">;
338
+ }, z.core.$strip>, z.ZodObject<{
339
+ kind: z.ZodLiteral<"literal">;
340
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
341
+ }, z.core.$strip>], "kind">>;
342
+ }, z.core.$strip>, z.ZodObject<{
343
+ kind: z.ZodLiteral<"code">;
344
+ code: z.ZodString;
345
+ }, z.core.$strip>], "kind">>;
346
+ }, z.core.$strip>>>;
240
347
  commands: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
241
348
  kind: z.ZodLiteral<"forward">;
242
349
  source: z.ZodObject<{
@@ -268,7 +375,7 @@ export declare const CompositionNewTargetSchema: z.ZodObject<{
268
375
  role: z.ZodOptional<z.ZodEnum<typeof DeviceRole>>;
269
376
  }, z.core.$strip>;
270
377
  export type CompositionNewTarget = z.infer<typeof CompositionNewTargetSchema>;
271
- /** RESERVED for slice 2: customize an existing device. */
378
+ /** Customize an EXISTING device: each feature adds a capability or replaces fields of a native one (D663). */
272
379
  export declare const CompositionExistingTargetSchema: z.ZodObject<{
273
380
  kind: z.ZodLiteral<"existing">;
274
381
  device: z.ZodObject<{
@@ -331,6 +438,36 @@ export declare const CompositionSchema: z.ZodObject<{
331
438
  kind: z.ZodLiteral<"code">;
332
439
  code: z.ZodString;
333
440
  }, z.core.$strip>], "kind">>;
441
+ items: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
442
+ label: z.ZodString;
443
+ fields: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
444
+ source: z.ZodObject<{
445
+ addonId: z.ZodString;
446
+ stableId: z.ZodString;
447
+ }, z.core.$strip>;
448
+ cap: z.ZodString;
449
+ fieldPath: z.ZodString;
450
+ kind: z.ZodLiteral<"from">;
451
+ }, z.core.$strip>, z.ZodObject<{
452
+ kind: z.ZodLiteral<"expression">;
453
+ expr: z.ZodString;
454
+ bindings: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
455
+ source: z.ZodObject<{
456
+ addonId: z.ZodString;
457
+ stableId: z.ZodString;
458
+ }, z.core.$strip>;
459
+ cap: z.ZodString;
460
+ fieldPath: z.ZodString;
461
+ kind: z.ZodLiteral<"from">;
462
+ }, z.core.$strip>, z.ZodObject<{
463
+ kind: z.ZodLiteral<"literal">;
464
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
465
+ }, z.core.$strip>], "kind">>;
466
+ }, z.core.$strip>, z.ZodObject<{
467
+ kind: z.ZodLiteral<"code">;
468
+ code: z.ZodString;
469
+ }, z.core.$strip>], "kind">>;
470
+ }, z.core.$strip>>>;
334
471
  commands: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
335
472
  kind: z.ZodLiteral<"forward">;
336
473
  source: z.ZodObject<{
@@ -360,3 +497,17 @@ export declare function composedDeviceStableId(blockId: string): string;
360
497
  export declare function composedDeviceRef(blockId: string): CompositionSourceRef;
361
498
  /** Every device field a source reads: `from` is one read, an expression is its `from` bindings. */
362
499
  export declare function fieldReads(source: CompositionFieldSource): readonly CompositionFieldRead[];
500
+ /** The ONE spelling of an item leaf as a field path: `items[desiccant].remaining.value`. */
501
+ export declare function compositionItemFieldPath(arrayPath: string, key: string, path: string): string;
502
+ export interface CompositionFeatureFieldEntry {
503
+ readonly fieldPath: string;
504
+ readonly source: CompositionFieldSource;
505
+ }
506
+ /**
507
+ * Every written field of a feature, item leaves under their bracketed path.
508
+ * `arrayPath` is `'items'` for every item-array cap that exists
509
+ * (`consumables.cap.ts`); the graph has no cap lookup, so the default stands
510
+ * and `planItems` asserts it agrees with the cap's descriptor.
511
+ */
512
+ export declare function featureFieldEntries(feature: CompositionFieldsFeature, arrayPath?: string): readonly CompositionFeatureFieldEntry[];
513
+ export declare function featureSources(feature: CompositionFieldsFeature): readonly CompositionFieldSource[];
@@ -0,0 +1,6 @@
1
+ import { type CompositionSourceRef } from './composition.js';
2
+ /**
3
+ * Derived by the SERVER on save, never sent by the client; never shown as the
4
+ * device's name. Unique per target: the plain ref when it fits, else a hash.
5
+ */
6
+ export declare function customizationBlockName(ref: CompositionSourceRef): string;
@@ -15,6 +15,14 @@
15
15
  * - an expression that fails on a value makes the field unavailable for that
16
16
  * value, and NOT fatal: the next value may compute.
17
17
  *
18
+ * `confirmed` (D663, D49): whether a NON-fatal unavailability is an ANSWER — a
19
+ * cap the provider unregistered and a re-read agreed, a field the source
20
+ * reported without, an expression that failed on a value — or only the
21
+ * absence of one (a failed read, a first or empty read, a source not tracked
22
+ * yet). Only a confirmed unavailability may hand a `release`-mode field back
23
+ * to the native provider; an unconfirmed one holds it on its last value.
24
+ * Absent means NOT confirmed. Fatal is always confirmed.
25
+ *
18
26
  * When an expression has more than one unavailable binding, a FATAL one always
19
27
  * wins the outcome, regardless of position: every binding is scanned before
20
28
  * the field is judged, so an earlier non-fatal binding (e.g. a cap that is
@@ -37,6 +45,8 @@ export interface SourceUnavailableReading {
37
45
  readonly reason: string;
38
46
  /** True when the source is GONE: device or cap missing, two reads agreed. */
39
47
  readonly fatal: boolean;
48
+ /** The unavailability is an answer, not a missing one (see the header). Absent = not confirmed. */
49
+ readonly confirmed?: boolean;
40
50
  }
41
51
  export type SourceReading = SourceFreshReading | SourceStaleReading | SourceUnavailableReading;
42
52
  export type SourceReader = (read: CompositionFieldRead) => SourceReading;
@@ -53,6 +63,8 @@ export interface FieldUnavailableOutcome {
53
63
  readonly value: null;
54
64
  readonly reason: string;
55
65
  readonly fatal: boolean;
66
+ /** Fatal, or a non-fatal unavailability that is an answer (see the header). Absent = not confirmed. */
67
+ readonly confirmed?: boolean;
56
68
  }
57
69
  export type FieldOutcome = FieldValueOutcome | FieldUnavailableOutcome;
58
70
  export interface EvaluateCompositionFieldInput {
@@ -8,3 +8,36 @@ export interface RoomExampleInput {
8
8
  readonly occupancyWindowMs: number;
9
9
  }
10
10
  export declare function roomCompositionExample(input: RoomExampleInput): Composition;
11
+ /**
12
+ * The slice-2 worked customization (D663): the PetKit "Fresh Element Infinity"
13
+ * feeder as Home Assistant imports it — ONE `Container` with a child per entity
14
+ * (`<container stableId>-<entityId>`), and NO native battery (its
15
+ * `binary_sensor.*_battery` is `battery_charging`, which `isBatteryEntity` does
16
+ * not match). The customization ADDS `consumables` (the desiccant) and
17
+ * `battery`. It is the Task 14 acceptance case and the template slice 5's
18
+ * presets start from.
19
+ *
20
+ * Every read names what the HA provider REALLY registers (read 2026-09-28):
21
+ * the desiccant sensor has a unit (`d`) but no device_class, so HA hosts it as
22
+ * an `enum-sensor` whose `value` is the raw STRING state — `number(d)` reads it,
23
+ * and anything that is not a number is `null`, never 0 (D393). The battery
24
+ * status sensor is an `enum-sensor` too (`unavailable` keeps its cold `''`, so
25
+ * `percentage` is `null`). `charging` maps `battery_charging` off to
26
+ * `'unknown'`, never `'none'`: off does not prove "on battery alone" (the
27
+ * operator confirms the real semantics live).
28
+ */
29
+ export declare const FEEDER_EXAMPLE_HA_DEVICE_ID = "8dec3b512fda40cbb8cd121c7a99f890";
30
+ export declare const FEEDER_EXAMPLE_ADDON_ID = "provider-homeassistant";
31
+ /** The container and the three children the feeder customization reads, under one HA broker. */
32
+ export interface FeederExampleSources {
33
+ readonly feeder: CompositionSourceRef;
34
+ /** `sensor.freshelement_3_desiccant_days_remaining` — `enum-sensor.value`, a numeric string. */
35
+ readonly desiccantDays: CompositionSourceRef;
36
+ /** `sensor.freshelement_3_battery_status` — `enum-sensor.value`. */
37
+ readonly batteryStatus: CompositionSourceRef;
38
+ /** `binary_sensor.freshelement_3_battery` (`battery_charging`) — `binary.on`. */
39
+ readonly batteryCharging: CompositionSourceRef;
40
+ }
41
+ /** The HA container's stableId (`ha-discovery.ts`) and a child's (`<container>-<entityId>`). */
42
+ export declare function feederExampleSources(brokerId: string): FeederExampleSources;
43
+ export declare function feederCustomizationExample(sources: FeederExampleSources): Composition;
@@ -0,0 +1,42 @@
1
+ import { type CompositionFieldSource } from './composition.js';
2
+ import type { CompositionFeatureMode, CompositionFieldRole, CompositionProblem, CompositionProblemCode, CompositionUnavailableMode } from './composition-report.js';
3
+ import { type ExpressionKindSet } from './expression-kinds.js';
4
+ import { type StateFieldDescription } from './state-fields.js';
5
+ import type { CapabilityLookup } from './validate-composition.js';
6
+ interface KindsFound {
7
+ readonly ok: true;
8
+ readonly kinds: ExpressionKindSet;
9
+ }
10
+ interface KindsRefused {
11
+ readonly ok: false;
12
+ readonly code: CompositionProblemCode;
13
+ readonly message: string;
14
+ }
15
+ type KindsResult = KindsFound | KindsRefused;
16
+ export declare function sourceKinds(source: CompositionFieldSource, lookupCap: CapabilityLookup): KindsResult;
17
+ export declare function compatibility(cap: string, target: StateFieldDescription, kinds: ExpressionKindSet, at: string): readonly CompositionProblem[];
18
+ /**
19
+ * An enum target whose source is a save-time-KNOWN string literal outside its
20
+ * `enumValues` is refused now rather than left to fail the write later. A
21
+ * dynamic source (a `from` read, a non-literal expression) is left to
22
+ * runtime — see {@link literalStringValue}.
23
+ */
24
+ export declare function enumLiteralProblem(cap: string, target: StateFieldDescription, source: CompositionFieldSource, at: string): CompositionProblem | null;
25
+ export declare function roleOf(source: CompositionFieldSource): CompositionFieldRole;
26
+ interface UnavailableChosen {
27
+ readonly ok: true;
28
+ readonly unavailable: CompositionUnavailableMode;
29
+ }
30
+ interface UnavailableRefused {
31
+ readonly ok: false;
32
+ readonly problem: CompositionProblem;
33
+ }
34
+ export type UnavailableResult = UnavailableChosen | UnavailableRefused;
35
+ /**
36
+ * How a planned field says "unavailable", per feature mode and AFTER `roleOf`
37
+ * (D393, D663 rulings S1 + S1b). The composer cannot take a device it does not
38
+ * own offline, and D393 forbids a stale number, so on an existing target a
39
+ * field that READS a source never gets `offline`.
40
+ */
41
+ export declare function unavailableFor(tf: StateFieldDescription, role: CompositionFieldRole, mode: CompositionFeatureMode, cap: string, at: string): UnavailableResult;
42
+ export {};
@@ -4,6 +4,7 @@
4
4
  */
5
5
  export * from './composition.js';
6
6
  export * from './composition-graph.js';
7
+ export * from './composition-items.js';
7
8
  export * from './composition-report.js';
8
9
  export * from './evaluate-field.js';
9
10
  export * from './examples.js';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The result of planning part of a composition, and the two helpers every
3
+ * planner builds it with. Internal to the composition module — NOT in the
4
+ * barrel (`problem`/`refused` are too generic a name for `@camstack/types`).
5
+ */
6
+ import type { CompositionFeaturePlan, CompositionFieldPlan, CompositionProblem, CompositionProblemCode } from './composition-report.js';
7
+ export interface CompositionPlanResult {
8
+ readonly problems: readonly CompositionProblem[];
9
+ readonly fields: readonly CompositionFieldPlan[];
10
+ /** One entry per planned feature, with its mode; empty for a per-field or per-item result. */
11
+ readonly features: readonly CompositionFeaturePlan[];
12
+ }
13
+ export declare function problem(code: CompositionProblemCode, path: string, message: string): CompositionProblem;
14
+ export declare function refused(...problems: readonly CompositionProblem[]): CompositionPlanResult;
15
+ /** Where a planned field sits in the composition: `features[N].fields.<path>` or `features[N].items.<key>.fields.<path>`. */
16
+ export declare function compositionFieldAt(field: CompositionFieldPlan): string;
@@ -17,3 +17,9 @@ export declare function describeStateField(schema: z.ZodType, path: string): Sta
17
17
  export declare function describeTopLevelStateFields(schema: z.ZodType): ReadonlyMap<string, StateFieldDescription>;
18
18
  /** Top-level keys a value MUST carry: those whose schema refuses `undefined`. */
19
19
  export declare function requiredTopLevelKeys(schema: z.ZodType): readonly string[];
20
+ /**
21
+ * The proper prefixes of `path` whose schema accepts `undefined`: a required
22
+ * leaf under one of them is required only once something writes that parent
23
+ * (`remaining.unit` is required only when `remaining` is present).
24
+ */
25
+ export declare function optionalAncestors(schema: z.ZodType, path: string): readonly string[];
@@ -1,16 +1,19 @@
1
1
  /**
2
2
  * Save-time validation of a composition (D659): shape, capability fit, field
3
- * kinds, nullability, sources, cycles. Every refusal names its reason (D391),
3
+ * kinds, nullability, sources, cycles — and on an EXISTING target (D663),
4
+ * add vs replace per feature, self reads, one composition per device. Every refusal names its reason (D391),
4
5
  * and every variant a later slice implements is refused BY NAME with that slice.
5
6
  *
6
- * Pure: the caller DESCRIBES the sources (one read each) and passes the other
7
- * stored compositions. `planComposition` is the static half the composer
7
+ * Pure: the caller DESCRIBES the sources and an existing target (one read
8
+ * each) and passes the other stored compositions. `planComposition` is the static half the composer
8
9
  * re-runs at apply time, where the sources are the tracker's business.
9
10
  */
10
11
  import type { CapabilityDefinition } from '../capabilities/capability-definition.js';
12
+ import type { DeviceType } from '../device/device-type.js';
11
13
  import { type Composition, type CompositionSourceRef } from './composition.js';
14
+ import { type CompositionPlanResult } from './plan-result.js';
12
15
  import { type CompositionPeer } from './composition-graph.js';
13
- import type { CompositionFieldPlan, CompositionProblem, CompositionValidation } from './composition-report.js';
16
+ import { type CompositionValidation } from './composition-report.js';
14
17
  export type CapabilityLookup = (capName: string) => CapabilityDefinition | null;
15
18
  export declare function buildCapabilityLookup(defs: readonly CapabilityDefinition[]): CapabilityLookup;
16
19
  /** Caps every `BaseDevice` registers on itself; a second registration throws. */
@@ -28,19 +31,51 @@ export interface CompositionSourceUnreadable {
28
31
  readonly error: string;
29
32
  }
30
33
  export type CompositionSourceDescription = CompositionSourceFound | CompositionSourceAbsent | CompositionSourceUnreadable;
34
+ /** An existing target device, read once: its type and each cap bound to it, with the addon serving it. */
35
+ export interface CompositionTargetFound {
36
+ readonly kind: 'found';
37
+ readonly deviceId: number;
38
+ readonly type: DeviceType;
39
+ /** cap name → `nativeAddonId` (`getBindings`). A cap the composer grafted carries `COMPOSER_ADDON_ID`. */
40
+ readonly nativeCaps: ReadonlyMap<string, string>;
41
+ }
42
+ export type CompositionTargetDescription = CompositionTargetFound | CompositionSourceAbsent | CompositionSourceUnreadable;
31
43
  export interface ValidateCompositionContext {
32
44
  readonly lookupCap: CapabilityLookup;
33
45
  /** Keyed by `compositionSourceKey`. A ref with no entry was not read, and is refused as unreadable. */
34
46
  readonly sources: ReadonlyMap<string, CompositionSourceDescription>;
35
47
  /** The device this composition writes; null on create, when the block has no id yet. */
36
48
  readonly self: CompositionSourceRef | null;
49
+ /** The block being saved; null on create. Its stored old version is recognised by this id (D663). */
50
+ readonly blockId: string | null;
51
+ /** The EXISTING target device, read once; null for a new-device composition (a missing read is refused). */
52
+ readonly target: CompositionTargetDescription | null;
37
53
  /** Every OTHER stored composition; cycles run through them. */
38
54
  readonly peers: readonly CompositionPeer[];
55
+ /**
56
+ * Devices of stored customizations whose target could not be read, by
57
+ * `compositionSourceKey` → why. Their peers are OVER-approximated (nothing
58
+ * `replaced`, so every self-read is an edge — edges are only added, a cycle
59
+ * never hidden). A cyclic component holding a candidate node and such a
60
+ * device refuses NAMING the device ("possible cycle"); a component without a
61
+ * candidate node is never this save's cycle (`findCandidateCycles`).
62
+ */
63
+ readonly unreadTargets?: ReadonlyMap<string, string>;
39
64
  }
40
- export interface CompositionPlanResult {
41
- readonly problems: readonly CompositionProblem[];
42
- readonly fields: readonly CompositionFieldPlan[];
43
- }
44
- export declare function planComposition(composition: Composition, lookupCap: CapabilityLookup): CompositionPlanResult;
65
+ export type { CompositionPlanResult } from './plan-result.js';
66
+ /** The candidate's block id before its first save. Stored block ids are `randomUUID()`, so none can carry it. */
67
+ export declare const PENDING_BLOCK_ID = "(unsaved)";
68
+ /**
69
+ * The static plan. `target` describes an EXISTING target device (one read);
70
+ * it is ignored for a new one. On an existing target each feature REPLACES a
71
+ * cap the device has natively, else ADDS it (D663).
72
+ */
73
+ export declare function planComposition(composition: Composition, lookupCap: CapabilityLookup, target?: CompositionTargetDescription): CompositionPlanResult;
45
74
  export declare function compositionSourceRefs(composition: Composition): readonly CompositionSourceRef[];
75
+ /**
76
+ * `compositionFieldKey` of every field a REPLACE feature of `plan` owns — the
77
+ * candidate's own, and a stored peer's `CompositionPeer.replaced` (D663): one
78
+ * derivation, so the graph reads both the same way.
79
+ */
80
+ export declare function replacedFieldKeys(plan: CompositionPlanResult): ReadonlySet<string>;
46
81
  export declare function validateComposition(composition: Composition, ctx: ValidateCompositionContext): CompositionValidation;