@camstack/system 1.2.314 → 1.2.316
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.
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer-plan.d.ts +8 -1
- package/dist/builtins/composer/composer.addon.js +191 -29
- package/dist/builtins/composer/composer.addon.mjs +195 -33
- package/dist/builtins/composer/composer.d.ts +5 -1
- package/dist/builtins/composer/composition-runtime.d.ts +10 -1
- package/dist/builtins/composer/existing-target.d.ts +9 -0
- package/dist/builtins/composer/field-record.d.ts +12 -3
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/composition-sources.d.ts +3 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +3 -103
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -102
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/doorbell/binding-mirror.d.ts +1 -0
- package/dist/builtins/doorbell/doorbell-composition-migration.d.ts +59 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +3 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +315 -8
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +315 -8
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/composition-sources-BwE1POnF.js +122 -0
- package/dist/composition-sources-fUyF4z39.mjs +111 -0
- package/dist/{dist-cH4t1ZEm.mjs → dist-BPvku43i.mjs} +596 -305
- package/dist/{dist-BtNkk3Qz.js → dist-RAl10EfG.js} +625 -304
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{retired-settings-keys-B7imb-qH.js → retired-settings-keys-D3rGTGq5.js} +1 -1
- package/dist/{retired-settings-keys-DQYHfZ0S.mjs → retired-settings-keys-DqVWSrVw.mjs} +1 -1
- package/package.json +1 -1
|
@@ -701,7 +701,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
701
701
|
return EventCategory;
|
|
702
702
|
}({});
|
|
703
703
|
//#endregion
|
|
704
|
-
//#region ../types/dist/sleep-
|
|
704
|
+
//#region ../types/dist/sleep-E6eD4yQS.mjs
|
|
705
705
|
/**
|
|
706
706
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
707
707
|
* window to float samples (D455).
|
|
@@ -2777,6 +2777,10 @@ function toNodeId(idOrRunnerId) {
|
|
|
2777
2777
|
const head = idOrRunnerId.split("/")[0];
|
|
2778
2778
|
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
2779
2779
|
}
|
|
2780
|
+
zod.z.object({
|
|
2781
|
+
field: zod.z.string().min(1),
|
|
2782
|
+
on: zod.z.literal("advance")
|
|
2783
|
+
});
|
|
2780
2784
|
/**
|
|
2781
2785
|
* Resolve the EFFECTIVE mount hint for a cap — the explicit `mount` when
|
|
2782
2786
|
* present, else the scope/mode/deviceNative-derived default. Single source
|
|
@@ -3508,7 +3512,7 @@ function errMsg(err) {
|
|
|
3508
3512
|
return String(err);
|
|
3509
3513
|
}
|
|
3510
3514
|
//#endregion
|
|
3511
|
-
//#region ../types/dist/composition-
|
|
3515
|
+
//#region ../types/dist/composition-D5BQywfe.mjs
|
|
3512
3516
|
var AUDIO_ENCODER_BY_CODEC = {
|
|
3513
3517
|
opus: "libopus",
|
|
3514
3518
|
aac: "aac",
|
|
@@ -3902,6 +3906,173 @@ function buildFfmpegArgs(inv) {
|
|
|
3902
3906
|
];
|
|
3903
3907
|
}
|
|
3904
3908
|
/**
|
|
3909
|
+
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
3910
|
+
* valutatore puro del suo FRONTE.
|
|
3911
|
+
*
|
|
3912
|
+
* Viveva dentro il builtin virtual-doorbell
|
|
3913
|
+
* (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
|
|
3914
|
+
* predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
|
|
3915
|
+
* `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
|
|
3916
|
+
* tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
|
|
3917
|
+
* fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
|
|
3918
|
+
* SPOSTA qui e il doorbell la ri-esporta.
|
|
3919
|
+
*
|
|
3920
|
+
* ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
|
|
3921
|
+
* due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
|
|
3922
|
+
* regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
|
|
3923
|
+
* booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
|
|
3924
|
+
*/
|
|
3925
|
+
/**
|
|
3926
|
+
* Known binary / switch source caps → the boolean slice field whose
|
|
3927
|
+
* false→true rise counts as ACTIVE. Every entry is "fire on active".
|
|
3928
|
+
* Sensors whose "active" reading is not a plain boolean (presence's string
|
|
3929
|
+
* state, connectivity's connected flag) are deliberately excluded — a
|
|
3930
|
+
* reconnect is not a doorbell press, and it is not a recording either.
|
|
3931
|
+
*/
|
|
3932
|
+
var SOURCE_CAP_ACTIVE_FIELD = {
|
|
3933
|
+
contact: "entryOpen",
|
|
3934
|
+
binary: "on",
|
|
3935
|
+
switch: "on",
|
|
3936
|
+
motion: "detected",
|
|
3937
|
+
flood: "flooded",
|
|
3938
|
+
gas: "detected",
|
|
3939
|
+
smoke: "detected",
|
|
3940
|
+
"carbon-monoxide": "detected",
|
|
3941
|
+
vibration: "detected",
|
|
3942
|
+
tamper: "tampered"
|
|
3943
|
+
};
|
|
3944
|
+
/**
|
|
3945
|
+
* The same caps → the slice field carrying the ms-epoch timestamp of the
|
|
3946
|
+
* last transition. Every source cap MUST appear here (guarded by a spec):
|
|
3947
|
+
* without a transition timestamp the evaluator cannot tell a genuine rise
|
|
3948
|
+
* from a boot-time hydration when the FIRST slice it ever sees is already
|
|
3949
|
+
* active, and errs towards silence — swallowing the rise.
|
|
3950
|
+
*
|
|
3951
|
+
* These timestamps are UPSTREAM ones, not ingest ones: the Home Assistant
|
|
3952
|
+
* provider derives them from `state.last_changed`, so they survive our own
|
|
3953
|
+
* restarts and correctly read as "hours ago" for a state that has been
|
|
3954
|
+
* active for hours. `motion` names its rise timestamp `lastDetectedAt`.
|
|
3955
|
+
*/
|
|
3956
|
+
var SOURCE_CAP_CHANGED_AT_FIELD = {
|
|
3957
|
+
contact: "lastChangedAt",
|
|
3958
|
+
binary: "lastChangedAt",
|
|
3959
|
+
switch: "lastChangedAt",
|
|
3960
|
+
motion: "lastDetectedAt",
|
|
3961
|
+
flood: "lastChangedAt",
|
|
3962
|
+
gas: "lastChangedAt",
|
|
3963
|
+
smoke: "lastChangedAt",
|
|
3964
|
+
"carbon-monoxide": "lastChangedAt",
|
|
3965
|
+
vibration: "lastChangedAt",
|
|
3966
|
+
tamper: "lastChangedAt"
|
|
3967
|
+
};
|
|
3968
|
+
/** Cap names whose presence in a device's bindings qualify it as a source. */
|
|
3969
|
+
var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
|
|
3970
|
+
/** True when a cap is a recognised binary/switch source. */
|
|
3971
|
+
function isSourceCap(capName) {
|
|
3972
|
+
return Object.prototype.hasOwnProperty.call(SOURCE_CAP_ACTIVE_FIELD, capName);
|
|
3973
|
+
}
|
|
3974
|
+
/** Extract the "active" boolean a source cap's slice carries, or null when
|
|
3975
|
+
* the cap is unknown or the field is missing / non-boolean. */
|
|
3976
|
+
function sliceActiveValue(capName, slice) {
|
|
3977
|
+
const field = SOURCE_CAP_ACTIVE_FIELD[capName];
|
|
3978
|
+
if (field === void 0) return null;
|
|
3979
|
+
const raw = slice[field];
|
|
3980
|
+
return typeof raw === "boolean" ? raw : null;
|
|
3981
|
+
}
|
|
3982
|
+
/** Ms-epoch transition timestamp a source cap's slice carries, or null when
|
|
3983
|
+
* it is absent, non-numeric or the zero "never observed" sentinel. */
|
|
3984
|
+
function sliceChangedAt(capName, slice) {
|
|
3985
|
+
const field = SOURCE_CAP_CHANGED_AT_FIELD[capName];
|
|
3986
|
+
if (field === void 0) return null;
|
|
3987
|
+
const raw = slice[field];
|
|
3988
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return null;
|
|
3989
|
+
return raw;
|
|
3990
|
+
}
|
|
3991
|
+
/**
|
|
3992
|
+
* How recent a source's own transition timestamp must be for a FIRST
|
|
3993
|
+
* sighting that is already active to count as a genuine rise rather than a
|
|
3994
|
+
* hydration of long-standing state.
|
|
3995
|
+
*/
|
|
3996
|
+
var DEFAULT_FIRST_SIGHTING_FRESHNESS_MS = 3e4;
|
|
3997
|
+
/**
|
|
3998
|
+
* The source-independent edge rule. The caller supplies an already extracted
|
|
3999
|
+
* boolean level and source timestamp; this function owns first-sighting
|
|
4000
|
+
* freshness and false-to-true detection.
|
|
4001
|
+
*/
|
|
4002
|
+
function evaluateSensorLevelEdge(input) {
|
|
4003
|
+
const timestamp = input.changedAt;
|
|
4004
|
+
const freshFloor = Math.max(input.startedAtMs, input.nowMs - input.firstSightingFreshnessMs);
|
|
4005
|
+
const firstRise = input.priorLevel === void 0 && input.level && timestamp !== null && Number.isFinite(timestamp) && timestamp >= freshFloor;
|
|
4006
|
+
const transitionRise = input.priorLevel === false && input.level && timestamp !== null && Number.isFinite(timestamp);
|
|
4007
|
+
if ((firstRise || transitionRise) && timestamp !== null) return {
|
|
4008
|
+
kind: "rise",
|
|
4009
|
+
timestamp
|
|
4010
|
+
};
|
|
4011
|
+
return {
|
|
4012
|
+
kind: "none",
|
|
4013
|
+
nextPriorLevel: input.level
|
|
4014
|
+
};
|
|
4015
|
+
}
|
|
4016
|
+
/**
|
|
4017
|
+
* IL fronte. Puro: nessun orologio proprio, nessuna memoria — il chiamante
|
|
4018
|
+
* porta `prior`, `nowMs` e il proprio `startedAtMs`.
|
|
4019
|
+
*
|
|
4020
|
+
* Il caso della PRIMA slice già attiva è trattato esplicitamente e vale come
|
|
4021
|
+
* fronte solo se il timestamp UPSTREAM della transizione è posteriore a
|
|
4022
|
+
* `startedAtMs` **e** entro `firstSightingFreshnessMs`. Entrambe le metà
|
|
4023
|
+
* servono: la sola freschezza scatterebbe su un'idratazione al boot di uno
|
|
4024
|
+
* stato flippato pochi secondi prima del riavvio, e il solo "dopo che abbiamo
|
|
4025
|
+
* iniziato" scatterebbe, su un processo di lunga vita, per una sorgente
|
|
4026
|
+
* adottata oggi il cui stato è cambiato ieri. Il caso ambiguo ERRA VERSO IL
|
|
4027
|
+
* SILENZIO e lo dichiara (`baseline-seeded-stale-active`).
|
|
4028
|
+
*/
|
|
4029
|
+
function evaluateSensorEdge(input) {
|
|
4030
|
+
const value = sliceActiveValue(input.capName, input.slice);
|
|
4031
|
+
if (value === null) return {
|
|
4032
|
+
edge: "none",
|
|
4033
|
+
value: null,
|
|
4034
|
+
reason: isSourceCap(input.capName) ? "non-boolean-value" : "unknown-cap"
|
|
4035
|
+
};
|
|
4036
|
+
if (evaluateSensorLevelEdge({
|
|
4037
|
+
level: value,
|
|
4038
|
+
changedAt: sliceChangedAt(input.capName, input.slice),
|
|
4039
|
+
priorLevel: input.prior,
|
|
4040
|
+
startedAtMs: input.startedAtMs,
|
|
4041
|
+
nowMs: input.nowMs,
|
|
4042
|
+
firstSightingFreshnessMs: input.firstSightingFreshnessMs
|
|
4043
|
+
}).kind === "rise") return {
|
|
4044
|
+
edge: "rising",
|
|
4045
|
+
value: true
|
|
4046
|
+
};
|
|
4047
|
+
if (input.prior === void 0) {
|
|
4048
|
+
if (!value) return {
|
|
4049
|
+
edge: "none",
|
|
4050
|
+
value,
|
|
4051
|
+
reason: "baseline-seeded-inactive"
|
|
4052
|
+
};
|
|
4053
|
+
return {
|
|
4054
|
+
edge: "none",
|
|
4055
|
+
value,
|
|
4056
|
+
reason: "baseline-seeded-stale-active"
|
|
4057
|
+
};
|
|
4058
|
+
}
|
|
4059
|
+
if (input.prior === value) return {
|
|
4060
|
+
edge: "none",
|
|
4061
|
+
value,
|
|
4062
|
+
reason: "no-change"
|
|
4063
|
+
};
|
|
4064
|
+
if (!value) return {
|
|
4065
|
+
edge: "none",
|
|
4066
|
+
value,
|
|
4067
|
+
reason: "falling-edge"
|
|
4068
|
+
};
|
|
4069
|
+
return {
|
|
4070
|
+
edge: "none",
|
|
4071
|
+
value,
|
|
4072
|
+
reason: "missing-timestamp"
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
/**
|
|
3905
4076
|
* Error types for the safe expression engine. Two distinct classes so callers
|
|
3906
4077
|
* can tell a compile-time (grammar) failure from a runtime (evaluation)
|
|
3907
4078
|
* failure — both are non-fatal to the host: read paths degrade to "skip link".
|
|
@@ -3924,6 +4095,9 @@ var ExpressionEvalError = class extends Error {
|
|
|
3924
4095
|
this.name = "ExpressionEvalError";
|
|
3925
4096
|
}
|
|
3926
4097
|
};
|
|
4098
|
+
function isStatefulBuiltin(name) {
|
|
4099
|
+
return name === "rose" || name === "count";
|
|
4100
|
+
}
|
|
3927
4101
|
function asFiniteNumber(value, name, index) {
|
|
3928
4102
|
if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
|
|
3929
4103
|
return value;
|
|
@@ -3959,6 +4133,37 @@ function toNumberOrNull(value) {
|
|
|
3959
4133
|
const parsed = Number(trimmed);
|
|
3960
4134
|
return Number.isFinite(parsed) ? parsed : null;
|
|
3961
4135
|
}
|
|
4136
|
+
var DEFAULT_ROSE_DEBOUNCE_MS = 2e3;
|
|
4137
|
+
function evaluateRose(level, changedAt, debounceMs, call, field, env) {
|
|
4138
|
+
const active = asBoolean(level, "rose", 0);
|
|
4139
|
+
const timestamp = typeof changedAt === "number" && Number.isFinite(changedAt) ? changedAt : null;
|
|
4140
|
+
const debounce = debounceMs === void 0 ? DEFAULT_ROSE_DEBOUNCE_MS : asFiniteNumber(debounceMs, "rose", 2);
|
|
4141
|
+
if (debounce < 0) throw new ExpressionEvalError("rose: debounce must be a non-negative finite number");
|
|
4142
|
+
const verdict = evaluateSensorLevelEdge({
|
|
4143
|
+
level: active,
|
|
4144
|
+
changedAt: timestamp,
|
|
4145
|
+
priorLevel: call.priorLevel,
|
|
4146
|
+
startedAtMs: env.startedAtMs,
|
|
4147
|
+
nowMs: env.nowMs,
|
|
4148
|
+
firstSightingFreshnessMs: DEFAULT_FIRST_SIGHTING_FRESHNESS_MS
|
|
4149
|
+
});
|
|
4150
|
+
call.priorLevel = verdict.kind === "rise" ? true : verdict.nextPriorLevel;
|
|
4151
|
+
if (verdict.kind === "none") return env.currentOutput;
|
|
4152
|
+
if (field.acceptedEdgeAt !== null && verdict.timestamp - field.acceptedEdgeAt < debounce) return env.currentOutput;
|
|
4153
|
+
field.acceptedEdgeAt = verdict.timestamp;
|
|
4154
|
+
return verdict.timestamp;
|
|
4155
|
+
}
|
|
4156
|
+
function evaluateCount(input, call, currentOutput) {
|
|
4157
|
+
const currentCount = typeof currentOutput === "number" && Number.isFinite(currentOutput) && currentOutput >= 0 ? currentOutput : 0;
|
|
4158
|
+
if (typeof input !== "number" || !Number.isFinite(input)) return currentCount;
|
|
4159
|
+
if (call.highWater === null) {
|
|
4160
|
+
call.highWater = input;
|
|
4161
|
+
return currentCount;
|
|
4162
|
+
}
|
|
4163
|
+
if (input <= call.highWater) return currentCount;
|
|
4164
|
+
call.highWater = input;
|
|
4165
|
+
return currentCount + 1;
|
|
4166
|
+
}
|
|
3962
4167
|
var INF = Number.POSITIVE_INFINITY;
|
|
3963
4168
|
var table = {
|
|
3964
4169
|
min: {
|
|
@@ -4098,6 +4303,20 @@ var table = {
|
|
|
4098
4303
|
const present = args.flatMap((a, i) => a === null ? [] : [asFiniteNumber(a, "latest", i)]);
|
|
4099
4304
|
return present.length === 0 ? null : finiteResult(Math.max(...present), "latest");
|
|
4100
4305
|
}
|
|
4306
|
+
},
|
|
4307
|
+
rose: {
|
|
4308
|
+
minArgs: 2,
|
|
4309
|
+
maxArgs: 3,
|
|
4310
|
+
apply: () => {
|
|
4311
|
+
throw new ExpressionEvalError("rose: stateful evaluation context is required");
|
|
4312
|
+
}
|
|
4313
|
+
},
|
|
4314
|
+
count: {
|
|
4315
|
+
minArgs: 1,
|
|
4316
|
+
maxArgs: 1,
|
|
4317
|
+
apply: () => {
|
|
4318
|
+
throw new ExpressionEvalError("count: stateful evaluation context is required");
|
|
4319
|
+
}
|
|
4101
4320
|
}
|
|
4102
4321
|
};
|
|
4103
4322
|
/** Frozen, null-prototype builtin table. */
|
|
@@ -4551,22 +4770,28 @@ function step(ctx) {
|
|
|
4551
4770
|
ctx.steps += 1;
|
|
4552
4771
|
if (ctx.steps > ctx.maxSteps) throw new ExpressionEvalError("expression evaluation step budget exceeded");
|
|
4553
4772
|
}
|
|
4554
|
-
function
|
|
4773
|
+
function childPath(path, child) {
|
|
4774
|
+
return `${path}/${child}`;
|
|
4775
|
+
}
|
|
4776
|
+
function evalNode(node, ctx, path) {
|
|
4555
4777
|
step(ctx);
|
|
4556
4778
|
switch (node.kind) {
|
|
4557
4779
|
case "literal": return node.value;
|
|
4558
4780
|
case "identifier":
|
|
4559
4781
|
if (!(node.name in ctx.scope)) throw new ExpressionEvalError(`unknown identifier: ${node.name}`);
|
|
4560
4782
|
return ctx.scope[node.name];
|
|
4561
|
-
case "unary": return evalUnary(node.op, evalNode(node.operand, ctx));
|
|
4562
|
-
case "binary": return evalBinary(node.op, evalNode(node.left, ctx), evalNode(node.right, ctx));
|
|
4783
|
+
case "unary": return evalUnary(node.op, evalNode(node.operand, ctx, childPath(path, "operand")));
|
|
4784
|
+
case "binary": return evalBinary(node.op, evalNode(node.left, ctx, childPath(path, "left")), evalNode(node.right, ctx, childPath(path, "right")));
|
|
4563
4785
|
case "logical": {
|
|
4564
|
-
const left = evalNode(node.left, ctx);
|
|
4565
|
-
if (node.op === "&&") return truthy(left) ? evalNode(node.right, ctx) : left;
|
|
4566
|
-
return truthy(left) ? left : evalNode(node.right, ctx);
|
|
4786
|
+
const left = evalNode(node.left, ctx, childPath(path, "left"));
|
|
4787
|
+
if (node.op === "&&") return truthy(left) ? evalNode(node.right, ctx, childPath(path, "right")) : left;
|
|
4788
|
+
return truthy(left) ? left : evalNode(node.right, ctx, childPath(path, "right"));
|
|
4789
|
+
}
|
|
4790
|
+
case "conditional": return truthy(evalNode(node.test, ctx, childPath(path, "test"))) ? evalNode(node.consequent, ctx, childPath(path, "consequent")) : evalNode(node.alternate, ctx, childPath(path, "alternate"));
|
|
4791
|
+
case "call": {
|
|
4792
|
+
const args = node.args.map((arg, index) => evalNode(arg, ctx, childPath(path, `arg:${index}`)));
|
|
4793
|
+
return invokeCall(node.callee, args, path, ctx);
|
|
4567
4794
|
}
|
|
4568
|
-
case "conditional": return truthy(evalNode(node.test, ctx)) ? evalNode(node.consequent, ctx) : evalNode(node.alternate, ctx);
|
|
4569
|
-
case "call": return evalCall(node.callee, node.args.map((a) => evalNode(a, ctx)), ctx.hooks);
|
|
4570
4795
|
}
|
|
4571
4796
|
}
|
|
4572
4797
|
function evalUnary(op, operand) {
|
|
@@ -4597,11 +4822,19 @@ function evalBinary(op, left, right) {
|
|
|
4597
4822
|
throw new ExpressionEvalError(`"${op}" requires two numbers or two strings`);
|
|
4598
4823
|
}
|
|
4599
4824
|
}
|
|
4600
|
-
function
|
|
4825
|
+
function invokeCall(callee, args, path, ctx) {
|
|
4601
4826
|
if (!Object.prototype.hasOwnProperty.call(EXPRESSION_BUILTINS, callee)) throw new ExpressionEvalError(`unknown function: ${callee}`);
|
|
4602
4827
|
const builtin = EXPRESSION_BUILTINS[callee];
|
|
4603
4828
|
if (args.length < builtin.minArgs || args.length > builtin.maxArgs) throw new ExpressionEvalError(`${callee}: wrong number of arguments (${args.length})`);
|
|
4604
|
-
|
|
4829
|
+
if (isStatefulBuiltin(callee)) {
|
|
4830
|
+
if (ctx.invokeStateful === void 0) throw new ExpressionEvalError(`${callee}: stateful evaluation context is required`);
|
|
4831
|
+
return ctx.invokeStateful({
|
|
4832
|
+
name: callee,
|
|
4833
|
+
path,
|
|
4834
|
+
args
|
|
4835
|
+
});
|
|
4836
|
+
}
|
|
4837
|
+
return builtin.apply(args, ctx.hooks);
|
|
4605
4838
|
}
|
|
4606
4839
|
/** Evaluate an AST node against a scope. Throws `ExpressionEvalError` on any
|
|
4607
4840
|
* runtime failure (unknown identifier, type mismatch, non-finite result,
|
|
@@ -4611,8 +4844,44 @@ function evaluateAst(node, scope, opts) {
|
|
|
4611
4844
|
scope,
|
|
4612
4845
|
hooks: opts?.hooks ?? EMPTY_HOOKS,
|
|
4613
4846
|
maxSteps: opts?.maxSteps ?? 4096,
|
|
4847
|
+
...opts?.invokeStateful === void 0 ? {} : { invokeStateful: opts.invokeStateful },
|
|
4614
4848
|
steps: 0
|
|
4615
|
-
});
|
|
4849
|
+
}, "root");
|
|
4850
|
+
}
|
|
4851
|
+
function createStatefulExpressionMemory() {
|
|
4852
|
+
return {
|
|
4853
|
+
calls: /* @__PURE__ */ new Map(),
|
|
4854
|
+
acceptedEdgeAt: null
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4857
|
+
function cloneStatefulExpressionMemory(memory) {
|
|
4858
|
+
return {
|
|
4859
|
+
calls: new Map([...memory.calls].map(([path, call]) => [path, { ...call }])),
|
|
4860
|
+
acceptedEdgeAt: memory.acceptedEdgeAt
|
|
4861
|
+
};
|
|
4862
|
+
}
|
|
4863
|
+
function evaluateStatefulExpressionCall(invocation, memory, env) {
|
|
4864
|
+
const existing = memory.calls.get(invocation.path);
|
|
4865
|
+
switch (invocation.name) {
|
|
4866
|
+
case "rose": {
|
|
4867
|
+
const call = existing?.kind === "rose" ? existing : { kind: "rose" };
|
|
4868
|
+
memory.calls.set(invocation.path, call);
|
|
4869
|
+
const currentOutput = env.currentOutput === null || typeof env.currentOutput === "number" && Number.isFinite(env.currentOutput) ? env.currentOutput : null;
|
|
4870
|
+
return evaluateRose(invocation.args[0], invocation.args[1], invocation.args[2], call, memory, {
|
|
4871
|
+
currentOutput,
|
|
4872
|
+
startedAtMs: env.startedAtMs,
|
|
4873
|
+
nowMs: env.nowMs
|
|
4874
|
+
});
|
|
4875
|
+
}
|
|
4876
|
+
case "count": {
|
|
4877
|
+
const call = existing?.kind === "count" ? existing : {
|
|
4878
|
+
kind: "count",
|
|
4879
|
+
highWater: null
|
|
4880
|
+
};
|
|
4881
|
+
memory.calls.set(invocation.path, call);
|
|
4882
|
+
return evaluateCount(invocation.args[0], call, env.currentOutput);
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4616
4885
|
}
|
|
4617
4886
|
/**
|
|
4618
4887
|
* Coerce an untrusted `getByPath` / mirror read to an `ExpressionValue`.
|
|
@@ -4653,9 +4922,10 @@ function validateExpressionSource(src) {
|
|
|
4653
4922
|
/**
|
|
4654
4923
|
* Shared read-path evaluation. Builds a null-proto scope from `bindingValues`
|
|
4655
4924
|
* plus the injected `now` (supplied by the caller for determinism and
|
|
4656
|
-
* testability), compiles via the LRU, and evaluates.
|
|
4657
|
-
*
|
|
4658
|
-
*
|
|
4925
|
+
* testability), compiles via the LRU, and evaluates. Stateful calls are
|
|
4926
|
+
* delegated through `opts.invokeStateful` with their stable AST path. Any
|
|
4927
|
+
* failure (parse or eval) returns `{ ok: false }` — the caller treats that as
|
|
4928
|
+
* "skip this derivation", never as a throw that takes the pass down.
|
|
4659
4929
|
*/
|
|
4660
4930
|
function evaluateExpressionSource(expr, bindingValues, now, opts) {
|
|
4661
4931
|
const compiled = compileExpressionSafe(expr);
|
|
@@ -4787,6 +5057,7 @@ var CompositionItemEntrySchema = zod.z.object({
|
|
|
4787
5057
|
var CompositionFieldsFeatureSchema = zod.z.object({
|
|
4788
5058
|
kind: zod.z.literal("fields"),
|
|
4789
5059
|
cap: zod.z.string().min(1),
|
|
5060
|
+
enabled: zod.z.boolean().optional(),
|
|
4790
5061
|
fields: zod.z.record(zod.z.string().min(1), CompositionFieldSourceSchema).refine((fields) => Object.keys(fields).length <= 32, { message: `at most 32 fields per capability` }),
|
|
4791
5062
|
/**
|
|
4792
5063
|
* Items of the cap's item array, keyed by item key (D663). Naming `items`
|
|
@@ -4800,9 +5071,14 @@ var CompositionFieldsFeatureSchema = zod.z.object({
|
|
|
4800
5071
|
var CompositionPassthroughFeatureSchema = zod.z.object({
|
|
4801
5072
|
kind: zod.z.literal("passthrough"),
|
|
4802
5073
|
cap: zod.z.string().min(1),
|
|
5074
|
+
enabled: zod.z.boolean().optional(),
|
|
4803
5075
|
source: CompositionSourceRefSchema
|
|
4804
5076
|
});
|
|
4805
5077
|
var CompositionFeatureSchema = zod.z.discriminatedUnion("kind", [CompositionFieldsFeatureSchema, CompositionPassthroughFeatureSchema]);
|
|
5078
|
+
/** Only an explicit `false` disables a feature; omitted preserves the existing wire default. */
|
|
5079
|
+
function compositionFeatureEnabled(feature) {
|
|
5080
|
+
return feature.enabled !== false;
|
|
5081
|
+
}
|
|
4806
5082
|
/**
|
|
4807
5083
|
* A NEW device. It has no `name` of its own: the block's name IS the device's
|
|
4808
5084
|
* name (2026-08-08). A second copy here would be a second writer of one string (D62).
|
|
@@ -11009,6 +11285,190 @@ function procedureAuthKey(auth) {
|
|
|
11009
11285
|
function kebabToCamel(s) {
|
|
11010
11286
|
return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
11011
11287
|
}
|
|
11288
|
+
/** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
|
|
11289
|
+
* Zod v4 exposes `.unwrap()` on all three wrapper classes. */
|
|
11290
|
+
function unwrapSchema(schema) {
|
|
11291
|
+
let s = schema;
|
|
11292
|
+
while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
|
|
11293
|
+
return s;
|
|
11294
|
+
}
|
|
11295
|
+
function leafKind(schema) {
|
|
11296
|
+
const s = unwrapSchema(schema);
|
|
11297
|
+
if (s instanceof zod.z.ZodEnum) return {
|
|
11298
|
+
kind: "enum",
|
|
11299
|
+
enumValues: s.options
|
|
11300
|
+
};
|
|
11301
|
+
if (s instanceof zod.z.ZodNumber) return { kind: "number" };
|
|
11302
|
+
if (s instanceof zod.z.ZodString) return { kind: "string" };
|
|
11303
|
+
if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
|
|
11304
|
+
return null;
|
|
11305
|
+
}
|
|
11306
|
+
/** Walk a cap status schema into flat wireable leaf fields (dotted paths).
|
|
11307
|
+
* Recurses into nested ZodObject (unwrapping nullable/optional/default first),
|
|
11308
|
+
* so fields with null live values are still offered. Arrays / unknown shapes
|
|
11309
|
+
* are skipped — never throws. */
|
|
11310
|
+
function enumerateSchemaFields(schema, prefix = "") {
|
|
11311
|
+
const root = unwrapSchema(schema);
|
|
11312
|
+
if (!(root instanceof zod.z.ZodObject)) return [];
|
|
11313
|
+
const out = [];
|
|
11314
|
+
const shape = root.shape;
|
|
11315
|
+
for (const [key, field] of Object.entries(shape)) {
|
|
11316
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
11317
|
+
const inner = unwrapSchema(field);
|
|
11318
|
+
if (inner instanceof zod.z.ZodObject) {
|
|
11319
|
+
out.push(...enumerateSchemaFields(inner, path));
|
|
11320
|
+
continue;
|
|
11321
|
+
}
|
|
11322
|
+
const leaf = leafKind(field);
|
|
11323
|
+
if (leaf) out.push({
|
|
11324
|
+
path,
|
|
11325
|
+
kind: leaf.kind,
|
|
11326
|
+
...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
|
|
11327
|
+
});
|
|
11328
|
+
}
|
|
11329
|
+
return out;
|
|
11330
|
+
}
|
|
11331
|
+
/** Enumerate the per-item wireable fields of an item-array cap (see
|
|
11332
|
+
* `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
|
|
11333
|
+
* `keyField` (the key comes from the link's `itemKey`, never from a wired
|
|
11334
|
+
* source), each tagged `item: true` so the authoring UI collects an
|
|
11335
|
+
* `itemKey` alongside the field. Never throws. */
|
|
11336
|
+
function enumerateItemArrayFields(itemArray) {
|
|
11337
|
+
return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
|
|
11338
|
+
...f,
|
|
11339
|
+
item: true
|
|
11340
|
+
}));
|
|
11341
|
+
}
|
|
11342
|
+
/**
|
|
11343
|
+
* What a capability's state field IS: kind, nullability, whether required. The
|
|
11344
|
+
* validator needs nullability, which `enumerateSchemaFields` does not report,
|
|
11345
|
+
* and that decides a field's unavailable mode (D393).
|
|
11346
|
+
*/
|
|
11347
|
+
function fieldAt(schema, path) {
|
|
11348
|
+
let current = schema;
|
|
11349
|
+
for (const segment of path.split(".")) {
|
|
11350
|
+
const container = unwrapSchema(current);
|
|
11351
|
+
if (!(container instanceof zod.z.ZodObject)) return null;
|
|
11352
|
+
const next = container.shape[segment];
|
|
11353
|
+
if (!(next instanceof zod.z.ZodType)) return null;
|
|
11354
|
+
current = next;
|
|
11355
|
+
}
|
|
11356
|
+
return current;
|
|
11357
|
+
}
|
|
11358
|
+
function describeStateField(schema, path) {
|
|
11359
|
+
const leaf = enumerateSchemaFields(schema).find((f) => f.path === path);
|
|
11360
|
+
if (leaf === void 0) return null;
|
|
11361
|
+
const field = fieldAt(schema, path);
|
|
11362
|
+
if (field === null) return null;
|
|
11363
|
+
return {
|
|
11364
|
+
path,
|
|
11365
|
+
kind: leaf.kind,
|
|
11366
|
+
nullable: field.safeParse(null).success,
|
|
11367
|
+
required: !field.safeParse(void 0).success,
|
|
11368
|
+
...leaf.enumValues === void 0 ? {} : { enumValues: leaf.enumValues }
|
|
11369
|
+
};
|
|
11370
|
+
}
|
|
11371
|
+
function describeTopLevelStateFields(schema) {
|
|
11372
|
+
return new Map(enumerateSchemaFields(schema).filter((f) => !f.path.includes(".")).flatMap((f) => {
|
|
11373
|
+
const described = describeStateField(schema, f.path);
|
|
11374
|
+
return described === null ? [] : [[f.path, described]];
|
|
11375
|
+
}));
|
|
11376
|
+
}
|
|
11377
|
+
/** Top-level keys a value MUST carry: those whose schema refuses `undefined`. */
|
|
11378
|
+
function requiredTopLevelKeys(schema) {
|
|
11379
|
+
const root = unwrapSchema(schema);
|
|
11380
|
+
if (!(root instanceof zod.z.ZodObject)) return [];
|
|
11381
|
+
return Object.entries(root.shape).flatMap(([key, field]) => field instanceof zod.z.ZodType && !field.safeParse(void 0).success ? [key] : []);
|
|
11382
|
+
}
|
|
11383
|
+
/**
|
|
11384
|
+
* The proper prefixes of `path` whose schema accepts `undefined`: a required
|
|
11385
|
+
* leaf under one of them is required only once something writes that parent
|
|
11386
|
+
* (`remaining.unit` is required only when `remaining` is present).
|
|
11387
|
+
*/
|
|
11388
|
+
function optionalAncestors(schema, path) {
|
|
11389
|
+
const segments = path.split(".");
|
|
11390
|
+
return segments.slice(0, -1).flatMap((_, i) => {
|
|
11391
|
+
const prefix = segments.slice(0, i + 1).join(".");
|
|
11392
|
+
const field = fieldAt(schema, prefix);
|
|
11393
|
+
return field !== null && field.safeParse(void 0).success ? [prefix] : [];
|
|
11394
|
+
});
|
|
11395
|
+
}
|
|
11396
|
+
var EVENT_CATEGORIES = { "doorbell.onPressed": EventCategory.DoorbellOnPressed };
|
|
11397
|
+
var CompositionEventPlanSchema = zod.z.object({
|
|
11398
|
+
featureIndex: zod.z.number().int().nonnegative(),
|
|
11399
|
+
cap: zod.z.string(),
|
|
11400
|
+
eventName: zod.z.string(),
|
|
11401
|
+
category: zod.z.enum(EventCategory),
|
|
11402
|
+
stateField: zod.z.string(),
|
|
11403
|
+
fieldKey: zod.z.string()
|
|
11404
|
+
});
|
|
11405
|
+
function eventProblem(code, path, message) {
|
|
11406
|
+
return {
|
|
11407
|
+
code,
|
|
11408
|
+
path,
|
|
11409
|
+
message
|
|
11410
|
+
};
|
|
11411
|
+
}
|
|
11412
|
+
function payloadReason(error) {
|
|
11413
|
+
return error.issues.map((issue) => `${issue.path.length === 0 ? "payload" : issue.path.join(".")}: ${issue.message}`).join("; ");
|
|
11414
|
+
}
|
|
11415
|
+
/**
|
|
11416
|
+
* Plan one capability's state-derived events after its feature mode and fields
|
|
11417
|
+
* are known. A native replacement never becomes a second event authority.
|
|
11418
|
+
*/
|
|
11419
|
+
function planCompositionEvents(def, feature, fields) {
|
|
11420
|
+
if (feature.mode === "replace") return {
|
|
11421
|
+
problems: [],
|
|
11422
|
+
events: []
|
|
11423
|
+
};
|
|
11424
|
+
const problems = [];
|
|
11425
|
+
const events = [];
|
|
11426
|
+
for (const [eventName, declaration] of Object.entries(def.events ?? {})) {
|
|
11427
|
+
const fromState = declaration.fromState;
|
|
11428
|
+
if (fromState === void 0) continue;
|
|
11429
|
+
const statePath = `features[${feature.featureIndex}].fields.${fromState.field}`;
|
|
11430
|
+
const state = def.runtimeState ? describeStateField(def.runtimeState, fromState.field) : null;
|
|
11431
|
+
if (state === null) {
|
|
11432
|
+
problems.push(eventProblem("unknown-field", statePath, `\`${def.name}.${eventName}\` derives from missing state field \`${fromState.field}\``));
|
|
11433
|
+
continue;
|
|
11434
|
+
}
|
|
11435
|
+
if (state.kind !== "number") {
|
|
11436
|
+
problems.push(eventProblem("kind-mismatch", statePath, `\`${def.name}.${eventName}\` needs a numeric state field; \`${fromState.field}\` is ${state.kind}`));
|
|
11437
|
+
continue;
|
|
11438
|
+
}
|
|
11439
|
+
const eventKey = `${def.name}.${eventName}`;
|
|
11440
|
+
const category = EVENT_CATEGORIES[eventKey];
|
|
11441
|
+
if (category === void 0) {
|
|
11442
|
+
problems.push(eventProblem("not-composable", `features[${feature.featureIndex}].cap`, `\`${eventKey}\` has no explicit composition event-category mapping`));
|
|
11443
|
+
continue;
|
|
11444
|
+
}
|
|
11445
|
+
const payload = declaration.data.safeParse({
|
|
11446
|
+
deviceId: 1,
|
|
11447
|
+
timestamp: 1
|
|
11448
|
+
});
|
|
11449
|
+
if (!payload.success) {
|
|
11450
|
+
problems.push(eventProblem("not-composable", `features[${feature.featureIndex}].cap`, `\`${eventKey}\` cannot be derived from { deviceId, timestamp }: ${payloadReason(payload.error)}`));
|
|
11451
|
+
continue;
|
|
11452
|
+
}
|
|
11453
|
+
const field = fields.find((candidate) => candidate.featureIndex === feature.featureIndex && candidate.cap === def.name && candidate.path === fromState.field && candidate.item === void 0);
|
|
11454
|
+
if (field === void 0) {
|
|
11455
|
+
problems.push(eventProblem("unknown-field", statePath, `\`${eventKey}\` derives from \`${fromState.field}\`, but that field is not composed`));
|
|
11456
|
+
continue;
|
|
11457
|
+
}
|
|
11458
|
+
events.push({
|
|
11459
|
+
featureIndex: feature.featureIndex,
|
|
11460
|
+
cap: def.name,
|
|
11461
|
+
eventName,
|
|
11462
|
+
category,
|
|
11463
|
+
stateField: fromState.field,
|
|
11464
|
+
fieldKey: `${field.cap}.${field.path}`
|
|
11465
|
+
});
|
|
11466
|
+
}
|
|
11467
|
+
return {
|
|
11468
|
+
problems,
|
|
11469
|
+
events
|
|
11470
|
+
};
|
|
11471
|
+
}
|
|
11012
11472
|
/**
|
|
11013
11473
|
* What the validator says about a composition and what the composer says about
|
|
11014
11474
|
* a running one: the wire shapes the API returns (D659).
|
|
@@ -11063,12 +11523,14 @@ var CompositionUnavailableModeSchema = zod.z.enum([
|
|
|
11063
11523
|
]);
|
|
11064
11524
|
/**
|
|
11065
11525
|
* `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
|
|
11066
|
-
* field of its cap changes value · `constant` reads nothing, computed once
|
|
11526
|
+
* field of its cap changes value · `constant` reads nothing, computed once ·
|
|
11527
|
+
* `accumulator` advances from the target's seed/current output.
|
|
11067
11528
|
*/
|
|
11068
11529
|
var CompositionFieldRoleSchema = zod.z.enum([
|
|
11069
11530
|
"derived",
|
|
11070
11531
|
"clock",
|
|
11071
|
-
"constant"
|
|
11532
|
+
"constant",
|
|
11533
|
+
"accumulator"
|
|
11072
11534
|
]);
|
|
11073
11535
|
/** The item a planned field belongs to; its `path` is then the dotted path INSIDE the item. */
|
|
11074
11536
|
var CompositionFieldItemSchema = zod.z.object({
|
|
@@ -11109,6 +11571,7 @@ var CompositionFeatureModeSchema = zod.z.enum([
|
|
|
11109
11571
|
"replace"
|
|
11110
11572
|
]);
|
|
11111
11573
|
var CompositionFeaturePlanSchema = zod.z.object({
|
|
11574
|
+
/** Index in the stored feature array; disabled features are omitted rather than re-indexed. */
|
|
11112
11575
|
featureIndex: zod.z.number().int().nonnegative(),
|
|
11113
11576
|
cap: zod.z.string(),
|
|
11114
11577
|
mode: CompositionFeatureModeSchema
|
|
@@ -11117,7 +11580,8 @@ var CompositionValidationSchema = zod.z.object({
|
|
|
11117
11580
|
ok: zod.z.boolean(),
|
|
11118
11581
|
problems: zod.z.array(CompositionProblemSchema),
|
|
11119
11582
|
fields: zod.z.array(CompositionFieldPlanSchema),
|
|
11120
|
-
features: zod.z.array(CompositionFeaturePlanSchema)
|
|
11583
|
+
features: zod.z.array(CompositionFeaturePlanSchema),
|
|
11584
|
+
events: zod.z.array(CompositionEventPlanSchema)
|
|
11121
11585
|
});
|
|
11122
11586
|
/**
|
|
11123
11587
|
* Mirrors `CoreBlockStatus`, pinned by `core-blocks-source.spec.ts`. `degraded`:
|
|
@@ -29024,148 +29488,6 @@ var videoclipsCapability = {
|
|
|
29024
29488
|
}
|
|
29025
29489
|
};
|
|
29026
29490
|
/**
|
|
29027
|
-
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
29028
|
-
* valutatore puro del suo FRONTE.
|
|
29029
|
-
*
|
|
29030
|
-
* Viveva dentro il builtin virtual-doorbell
|
|
29031
|
-
* (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
|
|
29032
|
-
* predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
|
|
29033
|
-
* `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
|
|
29034
|
-
* tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
|
|
29035
|
-
* fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
|
|
29036
|
-
* SPOSTA qui e il doorbell la ri-esporta.
|
|
29037
|
-
*
|
|
29038
|
-
* ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
|
|
29039
|
-
* due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
|
|
29040
|
-
* regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
|
|
29041
|
-
* booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
|
|
29042
|
-
*/
|
|
29043
|
-
/**
|
|
29044
|
-
* Known binary / switch source caps → the boolean slice field whose
|
|
29045
|
-
* false→true rise counts as ACTIVE. Every entry is "fire on active".
|
|
29046
|
-
* Sensors whose "active" reading is not a plain boolean (presence's string
|
|
29047
|
-
* state, connectivity's connected flag) are deliberately excluded — a
|
|
29048
|
-
* reconnect is not a doorbell press, and it is not a recording either.
|
|
29049
|
-
*/
|
|
29050
|
-
var SOURCE_CAP_ACTIVE_FIELD = {
|
|
29051
|
-
contact: "entryOpen",
|
|
29052
|
-
binary: "on",
|
|
29053
|
-
switch: "on",
|
|
29054
|
-
motion: "detected",
|
|
29055
|
-
flood: "flooded",
|
|
29056
|
-
gas: "detected",
|
|
29057
|
-
smoke: "detected",
|
|
29058
|
-
"carbon-monoxide": "detected",
|
|
29059
|
-
vibration: "detected",
|
|
29060
|
-
tamper: "tampered"
|
|
29061
|
-
};
|
|
29062
|
-
/**
|
|
29063
|
-
* The same caps → the slice field carrying the ms-epoch timestamp of the
|
|
29064
|
-
* last transition. Every source cap MUST appear here (guarded by a spec):
|
|
29065
|
-
* without a transition timestamp the evaluator cannot tell a genuine rise
|
|
29066
|
-
* from a boot-time hydration when the FIRST slice it ever sees is already
|
|
29067
|
-
* active, and errs towards silence — swallowing the rise.
|
|
29068
|
-
*
|
|
29069
|
-
* These timestamps are UPSTREAM ones, not ingest ones: the Home Assistant
|
|
29070
|
-
* provider derives them from `state.last_changed`, so they survive our own
|
|
29071
|
-
* restarts and correctly read as "hours ago" for a state that has been
|
|
29072
|
-
* active for hours. `motion` names its rise timestamp `lastDetectedAt`.
|
|
29073
|
-
*/
|
|
29074
|
-
var SOURCE_CAP_CHANGED_AT_FIELD = {
|
|
29075
|
-
contact: "lastChangedAt",
|
|
29076
|
-
binary: "lastChangedAt",
|
|
29077
|
-
switch: "lastChangedAt",
|
|
29078
|
-
motion: "lastDetectedAt",
|
|
29079
|
-
flood: "lastChangedAt",
|
|
29080
|
-
gas: "lastChangedAt",
|
|
29081
|
-
smoke: "lastChangedAt",
|
|
29082
|
-
"carbon-monoxide": "lastChangedAt",
|
|
29083
|
-
vibration: "lastChangedAt",
|
|
29084
|
-
tamper: "lastChangedAt"
|
|
29085
|
-
};
|
|
29086
|
-
/** Cap names whose presence in a device's bindings qualify it as a source. */
|
|
29087
|
-
var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
|
|
29088
|
-
/** True when a cap is a recognised binary/switch source. */
|
|
29089
|
-
function isSourceCap(capName) {
|
|
29090
|
-
return Object.prototype.hasOwnProperty.call(SOURCE_CAP_ACTIVE_FIELD, capName);
|
|
29091
|
-
}
|
|
29092
|
-
/** Extract the "active" boolean a source cap's slice carries, or null when
|
|
29093
|
-
* the cap is unknown or the field is missing / non-boolean. */
|
|
29094
|
-
function sliceActiveValue(capName, slice) {
|
|
29095
|
-
const field = SOURCE_CAP_ACTIVE_FIELD[capName];
|
|
29096
|
-
if (field === void 0) return null;
|
|
29097
|
-
const raw = slice[field];
|
|
29098
|
-
return typeof raw === "boolean" ? raw : null;
|
|
29099
|
-
}
|
|
29100
|
-
/** Ms-epoch transition timestamp a source cap's slice carries, or null when
|
|
29101
|
-
* it is absent, non-numeric or the zero "never observed" sentinel. */
|
|
29102
|
-
function sliceChangedAt(capName, slice) {
|
|
29103
|
-
const field = SOURCE_CAP_CHANGED_AT_FIELD[capName];
|
|
29104
|
-
if (field === void 0) return null;
|
|
29105
|
-
const raw = slice[field];
|
|
29106
|
-
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return null;
|
|
29107
|
-
return raw;
|
|
29108
|
-
}
|
|
29109
|
-
/**
|
|
29110
|
-
* How recent a source's own transition timestamp must be for a FIRST
|
|
29111
|
-
* sighting that is already active to count as a genuine rise rather than a
|
|
29112
|
-
* hydration of long-standing state.
|
|
29113
|
-
*/
|
|
29114
|
-
var DEFAULT_FIRST_SIGHTING_FRESHNESS_MS = 3e4;
|
|
29115
|
-
/**
|
|
29116
|
-
* IL fronte. Puro: nessun orologio proprio, nessuna memoria — il chiamante
|
|
29117
|
-
* porta `prior`, `nowMs` e il proprio `startedAtMs`.
|
|
29118
|
-
*
|
|
29119
|
-
* Il caso della PRIMA slice già attiva è trattato esplicitamente e vale come
|
|
29120
|
-
* fronte solo se il timestamp UPSTREAM della transizione è posteriore a
|
|
29121
|
-
* `startedAtMs` **e** entro `firstSightingFreshnessMs`. Entrambe le metà
|
|
29122
|
-
* servono: la sola freschezza scatterebbe su un'idratazione al boot di uno
|
|
29123
|
-
* stato flippato pochi secondi prima del riavvio, e il solo "dopo che abbiamo
|
|
29124
|
-
* iniziato" scatterebbe, su un processo di lunga vita, per una sorgente
|
|
29125
|
-
* adottata oggi il cui stato è cambiato ieri. Il caso ambiguo ERRA VERSO IL
|
|
29126
|
-
* SILENZIO e lo dichiara (`baseline-seeded-stale-active`).
|
|
29127
|
-
*/
|
|
29128
|
-
function evaluateSensorEdge(input) {
|
|
29129
|
-
const value = sliceActiveValue(input.capName, input.slice);
|
|
29130
|
-
if (value === null) return {
|
|
29131
|
-
edge: "none",
|
|
29132
|
-
value: null,
|
|
29133
|
-
reason: isSourceCap(input.capName) ? "non-boolean-value" : "unknown-cap"
|
|
29134
|
-
};
|
|
29135
|
-
if (input.prior === void 0) {
|
|
29136
|
-
if (!value) return {
|
|
29137
|
-
edge: "none",
|
|
29138
|
-
value,
|
|
29139
|
-
reason: "baseline-seeded-inactive"
|
|
29140
|
-
};
|
|
29141
|
-
const changedAt = sliceChangedAt(input.capName, input.slice);
|
|
29142
|
-
const floor = Math.max(input.startedAtMs, input.nowMs - input.firstSightingFreshnessMs);
|
|
29143
|
-
if (changedAt === null || changedAt < floor) return {
|
|
29144
|
-
edge: "none",
|
|
29145
|
-
value,
|
|
29146
|
-
reason: "baseline-seeded-stale-active"
|
|
29147
|
-
};
|
|
29148
|
-
return {
|
|
29149
|
-
edge: "rising",
|
|
29150
|
-
value: true
|
|
29151
|
-
};
|
|
29152
|
-
}
|
|
29153
|
-
if (input.prior === value) return {
|
|
29154
|
-
edge: "none",
|
|
29155
|
-
value,
|
|
29156
|
-
reason: "no-change"
|
|
29157
|
-
};
|
|
29158
|
-
if (!value) return {
|
|
29159
|
-
edge: "none",
|
|
29160
|
-
value,
|
|
29161
|
-
reason: "falling-edge"
|
|
29162
|
-
};
|
|
29163
|
-
return {
|
|
29164
|
-
edge: "rising",
|
|
29165
|
-
value: true
|
|
29166
|
-
};
|
|
29167
|
-
}
|
|
29168
|
-
/**
|
|
29169
29491
|
* Doorbell button cap. Two kinds of providers coexist behind this cap
|
|
29170
29492
|
* name (same pattern as `snapshot`):
|
|
29171
29493
|
*
|
|
@@ -29238,7 +29560,13 @@ var doorbellCapability = {
|
|
|
29238
29560
|
* push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
|
|
29239
29561
|
* release/duration — it's a pulse.
|
|
29240
29562
|
*/
|
|
29241
|
-
onPressed: {
|
|
29563
|
+
onPressed: {
|
|
29564
|
+
data: DoorbellPressEventSchema,
|
|
29565
|
+
fromState: {
|
|
29566
|
+
field: "lastPressedAt",
|
|
29567
|
+
on: "advance"
|
|
29568
|
+
}
|
|
29569
|
+
} },
|
|
29242
29570
|
status: {
|
|
29243
29571
|
schema: DoorbellStatusSchema,
|
|
29244
29572
|
kind: "push"
|
|
@@ -38263,12 +38591,21 @@ var RecordingStorageUsageSchema = zod.z.object({
|
|
|
38263
38591
|
*/
|
|
38264
38592
|
devicesRead: zod.z.enum(["archive", "unavailable"]).optional(),
|
|
38265
38593
|
/**
|
|
38594
|
+
* Is the `archive` answer WHOLE (D674): the hour ledger loaded without
|
|
38595
|
+
* hitting its row cap, and at least one location resolved? Only then may a
|
|
38596
|
+
* camera ABSENT from `devices[]` be read as "no footage" — and only then is a
|
|
38597
|
+
* row's `oldestMs` the archive's oldest rather than the oldest of a fraction.
|
|
38598
|
+
* OPTIONAL: a recorder predating D674 omits it, and a consumer must read the
|
|
38599
|
+
* absence as `false`. A retention floor is a delete; it never guesses.
|
|
38600
|
+
*/
|
|
38601
|
+
complete: zod.z.boolean().optional(),
|
|
38602
|
+
/**
|
|
38266
38603
|
* Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
|
|
38267
|
-
* of the payload without it:
|
|
38268
|
-
*
|
|
38269
|
-
*
|
|
38270
|
-
*
|
|
38271
|
-
*
|
|
38604
|
+
* of the payload without it: a recorder that could not build it omits it. A
|
|
38605
|
+
* UI that dereferences it unconditionally crashes the whole Storage page over
|
|
38606
|
+
* a missing disk bar. (The hub's cap routers pass output through unvalidated
|
|
38607
|
+
* since 2026-07-11 — `cap-router-builder.ts` — so a newer recorder's fields
|
|
38608
|
+
* are not stripped by an older hub.)
|
|
38272
38609
|
*/
|
|
38273
38610
|
locations: zod.z.array(RecordingLocationUsageSchema).optional()
|
|
38274
38611
|
});
|
|
@@ -42640,7 +42977,7 @@ function createDeviceExtensionProvider(options) {
|
|
|
42640
42977
|
var CompositionGraphError = class extends Error {};
|
|
42641
42978
|
/** `"cap.field"` of every field a peer writes — the same spelling as `compositionFieldKey`. */
|
|
42642
42979
|
function writtenFields(peer) {
|
|
42643
|
-
return new Set(peer.composition.features.flatMap((feature) => feature.kind !== "fields" ? [] : featureFieldEntries(feature).map(({ fieldPath }) => `${feature.cap}.${fieldPath}`)));
|
|
42980
|
+
return new Set(peer.composition.features.flatMap((feature) => !compositionFeatureEnabled(feature) || feature.kind !== "fields" ? [] : featureFieldEntries(feature).map(({ fieldPath }) => `${feature.cap}.${fieldPath}`)));
|
|
42644
42981
|
}
|
|
42645
42982
|
/** A read of the peer's OWN device is an edge only when it reads the peer's own output (rule 5, D663). */
|
|
42646
42983
|
function readsComposedValue(peer, written, read) {
|
|
@@ -42650,7 +42987,7 @@ function readsComposedValue(peer, written, read) {
|
|
|
42650
42987
|
}
|
|
42651
42988
|
function peerEdges(peer) {
|
|
42652
42989
|
const written = writtenFields(peer);
|
|
42653
|
-
return peer.composition.features.flatMap((feature) => feature.kind !== "fields" ? [] : featureFieldEntries(feature).map(({ fieldPath, source }) => [compositionReadKey({
|
|
42990
|
+
return peer.composition.features.flatMap((feature) => !compositionFeatureEnabled(feature) || feature.kind !== "fields" ? [] : featureFieldEntries(feature).map(({ fieldPath, source }) => [compositionReadKey({
|
|
42654
42991
|
source: peer.target,
|
|
42655
42992
|
cap: feature.cap,
|
|
42656
42993
|
fieldPath
|
|
@@ -42755,114 +43092,6 @@ function findCandidateCycles(candidate, stored) {
|
|
|
42755
43092
|
}];
|
|
42756
43093
|
});
|
|
42757
43094
|
}
|
|
42758
|
-
/** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
|
|
42759
|
-
* Zod v4 exposes `.unwrap()` on all three wrapper classes. */
|
|
42760
|
-
function unwrapSchema(schema) {
|
|
42761
|
-
let s = schema;
|
|
42762
|
-
while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
|
|
42763
|
-
return s;
|
|
42764
|
-
}
|
|
42765
|
-
function leafKind(schema) {
|
|
42766
|
-
const s = unwrapSchema(schema);
|
|
42767
|
-
if (s instanceof zod.z.ZodEnum) return {
|
|
42768
|
-
kind: "enum",
|
|
42769
|
-
enumValues: s.options
|
|
42770
|
-
};
|
|
42771
|
-
if (s instanceof zod.z.ZodNumber) return { kind: "number" };
|
|
42772
|
-
if (s instanceof zod.z.ZodString) return { kind: "string" };
|
|
42773
|
-
if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
|
|
42774
|
-
return null;
|
|
42775
|
-
}
|
|
42776
|
-
/** Walk a cap status schema into flat wireable leaf fields (dotted paths).
|
|
42777
|
-
* Recurses into nested ZodObject (unwrapping nullable/optional/default first),
|
|
42778
|
-
* so fields with null live values are still offered. Arrays / unknown shapes
|
|
42779
|
-
* are skipped — never throws. */
|
|
42780
|
-
function enumerateSchemaFields(schema, prefix = "") {
|
|
42781
|
-
const root = unwrapSchema(schema);
|
|
42782
|
-
if (!(root instanceof zod.z.ZodObject)) return [];
|
|
42783
|
-
const out = [];
|
|
42784
|
-
const shape = root.shape;
|
|
42785
|
-
for (const [key, field] of Object.entries(shape)) {
|
|
42786
|
-
const path = prefix ? `${prefix}.${key}` : key;
|
|
42787
|
-
const inner = unwrapSchema(field);
|
|
42788
|
-
if (inner instanceof zod.z.ZodObject) {
|
|
42789
|
-
out.push(...enumerateSchemaFields(inner, path));
|
|
42790
|
-
continue;
|
|
42791
|
-
}
|
|
42792
|
-
const leaf = leafKind(field);
|
|
42793
|
-
if (leaf) out.push({
|
|
42794
|
-
path,
|
|
42795
|
-
kind: leaf.kind,
|
|
42796
|
-
...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
|
|
42797
|
-
});
|
|
42798
|
-
}
|
|
42799
|
-
return out;
|
|
42800
|
-
}
|
|
42801
|
-
/** Enumerate the per-item wireable fields of an item-array cap (see
|
|
42802
|
-
* `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
|
|
42803
|
-
* `keyField` (the key comes from the link's `itemKey`, never from a wired
|
|
42804
|
-
* source), each tagged `item: true` so the authoring UI collects an
|
|
42805
|
-
* `itemKey` alongside the field. Never throws. */
|
|
42806
|
-
function enumerateItemArrayFields(itemArray) {
|
|
42807
|
-
return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
|
|
42808
|
-
...f,
|
|
42809
|
-
item: true
|
|
42810
|
-
}));
|
|
42811
|
-
}
|
|
42812
|
-
/**
|
|
42813
|
-
* What a capability's state field IS: kind, nullability, whether required. The
|
|
42814
|
-
* validator needs nullability, which `enumerateSchemaFields` does not report,
|
|
42815
|
-
* and that decides a field's unavailable mode (D393).
|
|
42816
|
-
*/
|
|
42817
|
-
function fieldAt(schema, path) {
|
|
42818
|
-
let current = schema;
|
|
42819
|
-
for (const segment of path.split(".")) {
|
|
42820
|
-
const container = unwrapSchema(current);
|
|
42821
|
-
if (!(container instanceof zod.z.ZodObject)) return null;
|
|
42822
|
-
const next = container.shape[segment];
|
|
42823
|
-
if (!(next instanceof zod.z.ZodType)) return null;
|
|
42824
|
-
current = next;
|
|
42825
|
-
}
|
|
42826
|
-
return current;
|
|
42827
|
-
}
|
|
42828
|
-
function describeStateField(schema, path) {
|
|
42829
|
-
const leaf = enumerateSchemaFields(schema).find((f) => f.path === path);
|
|
42830
|
-
if (leaf === void 0) return null;
|
|
42831
|
-
const field = fieldAt(schema, path);
|
|
42832
|
-
if (field === null) return null;
|
|
42833
|
-
return {
|
|
42834
|
-
path,
|
|
42835
|
-
kind: leaf.kind,
|
|
42836
|
-
nullable: field.safeParse(null).success,
|
|
42837
|
-
required: !field.safeParse(void 0).success,
|
|
42838
|
-
...leaf.enumValues === void 0 ? {} : { enumValues: leaf.enumValues }
|
|
42839
|
-
};
|
|
42840
|
-
}
|
|
42841
|
-
function describeTopLevelStateFields(schema) {
|
|
42842
|
-
return new Map(enumerateSchemaFields(schema).filter((f) => !f.path.includes(".")).flatMap((f) => {
|
|
42843
|
-
const described = describeStateField(schema, f.path);
|
|
42844
|
-
return described === null ? [] : [[f.path, described]];
|
|
42845
|
-
}));
|
|
42846
|
-
}
|
|
42847
|
-
/** Top-level keys a value MUST carry: those whose schema refuses `undefined`. */
|
|
42848
|
-
function requiredTopLevelKeys(schema) {
|
|
42849
|
-
const root = unwrapSchema(schema);
|
|
42850
|
-
if (!(root instanceof zod.z.ZodObject)) return [];
|
|
42851
|
-
return Object.entries(root.shape).flatMap(([key, field]) => field instanceof zod.z.ZodType && !field.safeParse(void 0).success ? [key] : []);
|
|
42852
|
-
}
|
|
42853
|
-
/**
|
|
42854
|
-
* The proper prefixes of `path` whose schema accepts `undefined`: a required
|
|
42855
|
-
* leaf under one of them is required only once something writes that parent
|
|
42856
|
-
* (`remaining.unit` is required only when `remaining` is present).
|
|
42857
|
-
*/
|
|
42858
|
-
function optionalAncestors(schema, path) {
|
|
42859
|
-
const segments = path.split(".");
|
|
42860
|
-
return segments.slice(0, -1).flatMap((_, i) => {
|
|
42861
|
-
const prefix = segments.slice(0, i + 1).join(".");
|
|
42862
|
-
const field = fieldAt(schema, prefix);
|
|
42863
|
-
return field !== null && field.safeParse(void 0).success ? [prefix] : [];
|
|
42864
|
-
});
|
|
42865
|
-
}
|
|
42866
43095
|
function problem(code, path, message) {
|
|
42867
43096
|
return {
|
|
42868
43097
|
code,
|
|
@@ -42874,7 +43103,8 @@ function refused(...problems) {
|
|
|
42874
43103
|
return {
|
|
42875
43104
|
problems,
|
|
42876
43105
|
fields: [],
|
|
42877
|
-
features: []
|
|
43106
|
+
features: [],
|
|
43107
|
+
events: []
|
|
42878
43108
|
};
|
|
42879
43109
|
}
|
|
42880
43110
|
/** Where a planned field sits in the composition: `features[N].fields.<path>` or `features[N].items.<key>.fields.<path>`. */
|
|
@@ -42916,7 +43146,12 @@ var BUILTIN_RESULT_KINDS = Object.freeze({
|
|
|
42916
43146
|
number: {
|
|
42917
43147
|
kind: "fixed",
|
|
42918
43148
|
result: ["number", "null"]
|
|
42919
|
-
}
|
|
43149
|
+
},
|
|
43150
|
+
rose: {
|
|
43151
|
+
kind: "fixed",
|
|
43152
|
+
result: ["number", "null"]
|
|
43153
|
+
},
|
|
43154
|
+
count: NUMBER
|
|
42920
43155
|
});
|
|
42921
43156
|
var KindInferenceError = class extends Error {};
|
|
42922
43157
|
function kindOfValue(value) {
|
|
@@ -42975,6 +43210,21 @@ function kindsOf(node, bindings) {
|
|
|
42975
43210
|
case "call": return callKinds(node.callee, node.args.map((a) => kindsOf(a, bindings)));
|
|
42976
43211
|
}
|
|
42977
43212
|
}
|
|
43213
|
+
function nodeContainsCall(node, callee) {
|
|
43214
|
+
switch (node.kind) {
|
|
43215
|
+
case "literal":
|
|
43216
|
+
case "identifier": return false;
|
|
43217
|
+
case "unary": return nodeContainsCall(node.operand, callee);
|
|
43218
|
+
case "binary":
|
|
43219
|
+
case "logical": return nodeContainsCall(node.left, callee) || nodeContainsCall(node.right, callee);
|
|
43220
|
+
case "conditional": return nodeContainsCall(node.test, callee) || nodeContainsCall(node.consequent, callee) || nodeContainsCall(node.alternate, callee);
|
|
43221
|
+
case "call": return node.callee === callee || node.args.some((arg) => nodeContainsCall(arg, callee));
|
|
43222
|
+
}
|
|
43223
|
+
}
|
|
43224
|
+
function expressionContainsBuiltinCall(expr, callee) {
|
|
43225
|
+
const compiled = compileExpressionSafe(expr);
|
|
43226
|
+
return compiled.ok && nodeContainsCall(compiled.parsed.ast, callee);
|
|
43227
|
+
}
|
|
42978
43228
|
function inferExpressionKinds(input) {
|
|
42979
43229
|
const compiled = compileExpressionSafe(input.expr);
|
|
42980
43230
|
if (!compiled.ok) return {
|
|
@@ -43095,6 +43345,7 @@ function enumLiteralProblem(cap, target, source, at) {
|
|
|
43095
43345
|
}
|
|
43096
43346
|
function roleOf(source) {
|
|
43097
43347
|
if (source.kind !== "expression") return "derived";
|
|
43348
|
+
if (expressionContainsBuiltinCall(source.expr, "count")) return "accumulator";
|
|
43098
43349
|
if (fieldReads(source).length > 0) return "derived";
|
|
43099
43350
|
const compiled = compileExpressionSafe(source.expr);
|
|
43100
43351
|
return compiled.ok && compiled.parsed.identifiers.has("now") ? "clock" : "constant";
|
|
@@ -43106,13 +43357,13 @@ function roleOf(source) {
|
|
|
43106
43357
|
* field that READS a source never gets `offline`.
|
|
43107
43358
|
*/
|
|
43108
43359
|
function unavailableFor(tf, role, mode, cap, at) {
|
|
43109
|
-
if (mode === "
|
|
43360
|
+
if (mode === "replace" && (role === "derived" || role === "accumulator")) return {
|
|
43110
43361
|
ok: true,
|
|
43111
|
-
unavailable:
|
|
43362
|
+
unavailable: "release"
|
|
43112
43363
|
};
|
|
43113
|
-
if (mode === "
|
|
43364
|
+
if (mode === "new" || role !== "derived") return {
|
|
43114
43365
|
ok: true,
|
|
43115
|
-
unavailable: "
|
|
43366
|
+
unavailable: tf.nullable ? "null" : "offline"
|
|
43116
43367
|
};
|
|
43117
43368
|
if (tf.nullable) return {
|
|
43118
43369
|
ok: true,
|
|
@@ -43185,7 +43436,8 @@ function planItemField(ctx, leaf) {
|
|
|
43185
43436
|
return {
|
|
43186
43437
|
problems: [...compatibility(capName, tf, kinds.kinds, at), ...enumProblem === null ? [] : [enumProblem]],
|
|
43187
43438
|
fields: [plan],
|
|
43188
|
-
features: []
|
|
43439
|
+
features: [],
|
|
43440
|
+
events: []
|
|
43189
43441
|
};
|
|
43190
43442
|
}
|
|
43191
43443
|
function planItems(def, feature, featureIndex, lookupCap, mode = "new") {
|
|
@@ -43204,7 +43456,8 @@ function planItems(def, feature, featureIndex, lookupCap, mode = "new") {
|
|
|
43204
43456
|
const results = Object.entries(feature.items ?? {}).flatMap(([key, item]) => [{
|
|
43205
43457
|
problems: missingLeaves(ctx, item, `${at}.${key}`),
|
|
43206
43458
|
fields: [],
|
|
43207
|
-
features: []
|
|
43459
|
+
features: [],
|
|
43460
|
+
events: []
|
|
43208
43461
|
}, ...Object.entries(item.fields).map(([path, source]) => planItemField(ctx, {
|
|
43209
43462
|
key,
|
|
43210
43463
|
label: item.label,
|
|
@@ -43214,7 +43467,8 @@ function planItems(def, feature, featureIndex, lookupCap, mode = "new") {
|
|
|
43214
43467
|
return {
|
|
43215
43468
|
problems: results.flatMap((r) => r.problems),
|
|
43216
43469
|
fields: results.flatMap((r) => r.fields),
|
|
43217
|
-
features: []
|
|
43470
|
+
features: [],
|
|
43471
|
+
events: []
|
|
43218
43472
|
};
|
|
43219
43473
|
}
|
|
43220
43474
|
function describeRead(read) {
|
|
@@ -43238,7 +43492,7 @@ function fromReading(reading, read) {
|
|
|
43238
43492
|
deadline: null
|
|
43239
43493
|
};
|
|
43240
43494
|
}
|
|
43241
|
-
function expressionOutcome(source, read, now) {
|
|
43495
|
+
function expressionOutcome(source, read, now, stateful) {
|
|
43242
43496
|
const values = {};
|
|
43243
43497
|
const staleReasons = [];
|
|
43244
43498
|
let fatalUnavailable = null;
|
|
@@ -43267,10 +43521,31 @@ function expressionOutcome(source, read, now) {
|
|
|
43267
43521
|
values[name] = reading.value;
|
|
43268
43522
|
if (reading.state === "stale") staleReasons.push(`\`${name}\`: ${reading.reason}`);
|
|
43269
43523
|
}
|
|
43270
|
-
|
|
43271
|
-
if (
|
|
43524
|
+
const unavailable = fatalUnavailable ?? nonFatalUnavailable;
|
|
43525
|
+
if (unavailable !== null) {
|
|
43526
|
+
if (stateful !== void 0 && stateful.currentOutput !== void 0 && expressionContainsBuiltinCall(source.expr, "count")) return {
|
|
43527
|
+
state: "stale",
|
|
43528
|
+
value: evaluateCount(null, {
|
|
43529
|
+
kind: "count",
|
|
43530
|
+
highWater: null
|
|
43531
|
+
}, stateful.currentOutput),
|
|
43532
|
+
reason: `${unavailable.reason}; holding the last accumulator value`,
|
|
43533
|
+
deadline: null
|
|
43534
|
+
};
|
|
43535
|
+
return unavailable;
|
|
43536
|
+
}
|
|
43272
43537
|
const deadlines = [];
|
|
43273
|
-
const result = evaluateExpressionSource(source.expr, values, now, {
|
|
43538
|
+
const result = evaluateExpressionSource(source.expr, values, now, {
|
|
43539
|
+
hooks: { noteDeadline: (at) => deadlines.push(at) },
|
|
43540
|
+
...stateful === void 0 ? {} : { invokeStateful: (invocation) => evaluateStatefulExpressionCall(stateful.callPathPrefix === void 0 ? invocation : {
|
|
43541
|
+
...invocation,
|
|
43542
|
+
path: `${stateful.callPathPrefix}@${invocation.path}`
|
|
43543
|
+
}, stateful.expressionMemory, {
|
|
43544
|
+
currentOutput: stateful.currentOutput,
|
|
43545
|
+
startedAtMs: stateful.startedAtMs,
|
|
43546
|
+
nowMs: now
|
|
43547
|
+
}) }
|
|
43548
|
+
});
|
|
43274
43549
|
if (!result.ok) return {
|
|
43275
43550
|
state: "unavailable",
|
|
43276
43551
|
value: null,
|
|
@@ -43289,7 +43564,7 @@ function evaluateCompositionField(input) {
|
|
|
43289
43564
|
const { source } = input;
|
|
43290
43565
|
switch (source.kind) {
|
|
43291
43566
|
case "from": return fromReading(input.read(source), source);
|
|
43292
|
-
case "expression": return expressionOutcome(source, input.read, input.now);
|
|
43567
|
+
case "expression": return expressionOutcome(source, input.read, input.now, input.stateful);
|
|
43293
43568
|
case "code": return {
|
|
43294
43569
|
state: "unavailable",
|
|
43295
43570
|
value: null,
|
|
@@ -43367,7 +43642,8 @@ function planTopLevelField({ def, fields }, field, mode, lookupCap) {
|
|
|
43367
43642
|
return {
|
|
43368
43643
|
problems: [...compatibility(def.name, tf, kinds.kinds, fieldAt), ...enumProblem === null ? [] : [enumProblem]],
|
|
43369
43644
|
fields: [plan],
|
|
43370
|
-
features: []
|
|
43645
|
+
features: [],
|
|
43646
|
+
events: []
|
|
43371
43647
|
};
|
|
43372
43648
|
}
|
|
43373
43649
|
function planFeature(feature, featureIndex, target, lookupCap) {
|
|
@@ -43383,14 +43659,22 @@ function planFeature(feature, featureIndex, target, lookupCap) {
|
|
|
43383
43659
|
}, target.mode, lookupCap));
|
|
43384
43660
|
const items = feature.items === void 0 ? [] : [planItems(cap.def, feature, featureIndex, lookupCap, target.mode)];
|
|
43385
43661
|
const all = [...perField, ...items];
|
|
43662
|
+
const fields = all.flatMap((result) => result.fields);
|
|
43663
|
+
const featurePlan = {
|
|
43664
|
+
featureIndex,
|
|
43665
|
+
cap: cap.def.name,
|
|
43666
|
+
mode: target.mode
|
|
43667
|
+
};
|
|
43668
|
+
const eventPlan = planCompositionEvents(cap.def, featurePlan, fields);
|
|
43386
43669
|
return {
|
|
43387
|
-
problems: [
|
|
43388
|
-
|
|
43389
|
-
|
|
43390
|
-
|
|
43391
|
-
|
|
43392
|
-
|
|
43393
|
-
|
|
43670
|
+
problems: [
|
|
43671
|
+
...capProblems,
|
|
43672
|
+
...all.flatMap((r) => r.problems),
|
|
43673
|
+
...eventPlan.problems
|
|
43674
|
+
],
|
|
43675
|
+
fields,
|
|
43676
|
+
features: [featurePlan],
|
|
43677
|
+
events: eventPlan.events
|
|
43394
43678
|
};
|
|
43395
43679
|
}
|
|
43396
43680
|
/** A cap the device serves itself; one the composer grafted (an earlier ADD) is not native. */
|
|
@@ -43432,17 +43716,23 @@ function planComposition(composition, lookupCap, target) {
|
|
|
43432
43716
|
const planTarget = planTargetOf(composition, target);
|
|
43433
43717
|
if ("code" in planTarget) return refused(planTarget);
|
|
43434
43718
|
const results = composition.features.map((feature, index) => {
|
|
43435
|
-
|
|
43436
|
-
return
|
|
43719
|
+
const result = feature.kind === "passthrough" ? refused(problem("reserved-variant", `features[${index}]`, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`)) : planFeature(feature, index, featureTargetOf(planTarget, feature.cap), lookupCap);
|
|
43720
|
+
return compositionFeatureEnabled(feature) ? result : {
|
|
43721
|
+
problems: result.problems,
|
|
43722
|
+
fields: [],
|
|
43723
|
+
features: [],
|
|
43724
|
+
events: []
|
|
43725
|
+
};
|
|
43437
43726
|
});
|
|
43438
43727
|
return {
|
|
43439
43728
|
problems: results.flatMap((r) => r.problems),
|
|
43440
43729
|
fields: results.flatMap((r) => r.fields),
|
|
43441
|
-
features: results.flatMap((r) => r.features)
|
|
43730
|
+
features: results.flatMap((r) => r.features),
|
|
43731
|
+
events: results.flatMap((r) => r.events)
|
|
43442
43732
|
};
|
|
43443
43733
|
}
|
|
43444
43734
|
function compositionSourceRefs(composition) {
|
|
43445
|
-
const refs = composition.features.flatMap((f) => f.kind === "fields" ? featureSources(f).flatMap(fieldReads).map((r) => r.source) : []);
|
|
43735
|
+
const refs = composition.features.flatMap((f) => compositionFeatureEnabled(f) && f.kind === "fields" ? featureSources(f).flatMap(fieldReads).map((r) => r.source) : []);
|
|
43446
43736
|
return [...new Map(refs.map((r) => [compositionSourceKey(r), r])).values()];
|
|
43447
43737
|
}
|
|
43448
43738
|
/**
|
|
@@ -43577,7 +43867,8 @@ function validateComposition(composition, ctx) {
|
|
|
43577
43867
|
ok: problems.length === 0,
|
|
43578
43868
|
problems,
|
|
43579
43869
|
fields: [...plan.fields],
|
|
43580
|
-
features: [...plan.features]
|
|
43870
|
+
features: [...plan.features],
|
|
43871
|
+
events: [...plan.events]
|
|
43581
43872
|
};
|
|
43582
43873
|
}
|
|
43583
43874
|
/**
|
|
@@ -57364,6 +57655,18 @@ Object.defineProperty(exports, "SOURCE_CAPS", {
|
|
|
57364
57655
|
return SOURCE_CAPS;
|
|
57365
57656
|
}
|
|
57366
57657
|
});
|
|
57658
|
+
Object.defineProperty(exports, "SOURCE_CAP_ACTIVE_FIELD", {
|
|
57659
|
+
enumerable: true,
|
|
57660
|
+
get: function() {
|
|
57661
|
+
return SOURCE_CAP_ACTIVE_FIELD;
|
|
57662
|
+
}
|
|
57663
|
+
});
|
|
57664
|
+
Object.defineProperty(exports, "SOURCE_CAP_CHANGED_AT_FIELD", {
|
|
57665
|
+
enumerable: true,
|
|
57666
|
+
get: function() {
|
|
57667
|
+
return SOURCE_CAP_CHANGED_AT_FIELD;
|
|
57668
|
+
}
|
|
57669
|
+
});
|
|
57367
57670
|
Object.defineProperty(exports, "STORAGE_ACCESS_FALLBACK", {
|
|
57368
57671
|
enumerable: true,
|
|
57369
57672
|
get: function() {
|
|
@@ -57508,6 +57811,12 @@ Object.defineProperty(exports, "cameraGridLayoutCapability", {
|
|
|
57508
57811
|
return cameraGridLayoutCapability;
|
|
57509
57812
|
}
|
|
57510
57813
|
});
|
|
57814
|
+
Object.defineProperty(exports, "cloneStatefulExpressionMemory", {
|
|
57815
|
+
enumerable: true,
|
|
57816
|
+
get: function() {
|
|
57817
|
+
return cloneStatefulExpressionMemory;
|
|
57818
|
+
}
|
|
57819
|
+
});
|
|
57511
57820
|
Object.defineProperty(exports, "collectSecretConfigKeys", {
|
|
57512
57821
|
enumerable: true,
|
|
57513
57822
|
get: function() {
|
|
@@ -57532,6 +57841,12 @@ Object.defineProperty(exports, "composerCapability", {
|
|
|
57532
57841
|
return composerCapability;
|
|
57533
57842
|
}
|
|
57534
57843
|
});
|
|
57844
|
+
Object.defineProperty(exports, "compositionFeatureEnabled", {
|
|
57845
|
+
enumerable: true,
|
|
57846
|
+
get: function() {
|
|
57847
|
+
return compositionFeatureEnabled;
|
|
57848
|
+
}
|
|
57849
|
+
});
|
|
57535
57850
|
Object.defineProperty(exports, "compositionFieldKey", {
|
|
57536
57851
|
enumerable: true,
|
|
57537
57852
|
get: function() {
|
|
@@ -57604,6 +57919,12 @@ Object.defineProperty(exports, "createEvent", {
|
|
|
57604
57919
|
return createEvent;
|
|
57605
57920
|
}
|
|
57606
57921
|
});
|
|
57922
|
+
Object.defineProperty(exports, "createStatefulExpressionMemory", {
|
|
57923
|
+
enumerable: true,
|
|
57924
|
+
get: function() {
|
|
57925
|
+
return createStatefulExpressionMemory;
|
|
57926
|
+
}
|
|
57927
|
+
});
|
|
57607
57928
|
Object.defineProperty(exports, "dataStoreProviderCapability", {
|
|
57608
57929
|
enumerable: true,
|
|
57609
57930
|
get: function() {
|