@camstack/system 1.2.476 → 1.2.478
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/assets/display-palette/icon.svg +7 -0
- package/dist/{agent-https-vpf_D97z.mjs → agent-https-CXMsY05a.mjs} +1 -1
- package/dist/{agent-https-DkXBXEvz.js → agent-https-D6a1IRPK.js} +1 -1
- 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 +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.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-runtime-factory.d.ts +2 -1
- package/dist/builtins/composer/composer.addon.js +453 -6
- package/dist/builtins/composer/composer.addon.mjs +453 -6
- package/dist/builtins/composer/composer.d.ts +6 -0
- package/dist/builtins/composer/composition-runtime.d.ts +3 -0
- package/dist/builtins/composer/field-record.d.ts +7 -0
- package/dist/builtins/composer/pulse-seed-resolve.d.ts +59 -0
- package/dist/builtins/composer/pulse-seed.d.ts +75 -0
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-history/device-history.addon.js +63 -953
- package/dist/builtins/device-history/device-history.addon.mjs +2 -892
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/display-palette/display-palette.addon.d.ts +12 -0
- package/dist/builtins/display-palette/display-palette.addon.js +85 -0
- package/dist/builtins/display-palette/display-palette.addon.mjs +79 -0
- package/dist/builtins/display-palette/index.d.ts +1 -0
- package/dist/builtins/display-palette/index.js +8 -0
- package/dist/builtins/display-palette/index.mjs +2 -0
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +3 -3
- package/dist/builtins/keychain/index.mjs +3 -3
- 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 +2 -2
- package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
- package/dist/builtins/local-network/local-network.addon.js +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- 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/record-entries/index.js +1 -1
- package/dist/builtins/record-entries/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/index.js +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
- 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 +3 -3
- package/dist/builtins/system-config/system-config.addon.mjs +3 -3
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{composition-sources-B5nK7pHt.mjs → composition-sources-CQcYYZxL.mjs} +1 -1
- package/dist/{composition-sources-BNOP9mFp.js → composition-sources-yfxjp3qV.js} +1 -1
- package/dist/{constants-D4VigrMM.js → constants-D--JWd55.js} +1 -1
- package/dist/{constants-jbhoFV7w.mjs → constants-lWkcQxVE.mjs} +1 -1
- package/dist/{device-ca-DhyQeXsa.js → device-ca-D0KnLHMw.js} +1 -1
- package/dist/{device-ca-CvBZeyVX.mjs → device-ca-J9ZsyoFw.mjs} +1 -1
- package/dist/{dist-B_V9r61Z.mjs → dist-OC-L50JY.mjs} +751 -70
- package/dist/{dist-CNU7cdFf.js → dist-iVUkxljP.js} +792 -69
- package/dist/file-host-worker.js +1 -1
- package/dist/file-host-worker.mjs +1 -1
- package/dist/{hub-mdns-ciao-DFidf52M.js → hub-mdns-ciao-BDKiKV75.js} +2 -2
- package/dist/{hub-mdns-ciao-BuUg0gpI.mjs → hub-mdns-ciao-CvXWbJub.mjs} +2 -2
- package/dist/index.js +7 -7
- package/dist/index.mjs +7 -7
- package/dist/{restore-archive-CDo4Zlmx.mjs → restore-archive-D1Uzojhc.mjs} +1 -1
- package/dist/{restore-archive-BtCheeWu.js → restore-archive-DxkdJpWd.js} +1 -1
- package/dist/retention-B3z_lBP1.js +1024 -0
- package/dist/retention-DdVnmXXS.mjs +893 -0
- package/dist/{wal-maintenance-DItlr0Yu.mjs → wal-maintenance-DuO0D2H3.mjs} +0 -0
- package/dist/{wal-maintenance-B7xBeIFi.js → wal-maintenance-o9TyZeYp.js} +0 -0
- package/package.json +19 -1
|
@@ -3,8 +3,9 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
const require_composition_sources = require("../../composition-sources-
|
|
6
|
+
const require_dist = require("../../dist-iVUkxljP.js");
|
|
7
|
+
const require_composition_sources = require("../../composition-sources-yfxjp3qV.js");
|
|
8
|
+
const require_retention = require("../../retention-B3z_lBP1.js");
|
|
8
9
|
let zod = require("zod");
|
|
9
10
|
//#region src/builtins/shared/device-state-changed.ts
|
|
10
11
|
/** Structural narrowing helper for untrusted bus payloads. */
|
|
@@ -1201,9 +1202,33 @@ function createFieldRecord(plan, byCap, lookupCap) {
|
|
|
1201
1202
|
hasWritten: false,
|
|
1202
1203
|
written: void 0,
|
|
1203
1204
|
expressionMemory: require_dist.createStatefulExpressionMemory(),
|
|
1204
|
-
eventHighWater: typeof initial === "number" && Number.isFinite(initial) ? initial : null
|
|
1205
|
+
eventHighWater: typeof initial === "number" && Number.isFinite(initial) ? initial : null,
|
|
1206
|
+
pulseHold: false
|
|
1205
1207
|
};
|
|
1206
1208
|
}
|
|
1209
|
+
/** Apply a pulse start seed (D938). `keep` leaves the slice seed in place. */
|
|
1210
|
+
function applyPulseSeed(record, seed) {
|
|
1211
|
+
if (seed === void 0 || seed.kind === "keep") return record;
|
|
1212
|
+
if (seed.kind === "hold") return {
|
|
1213
|
+
...record,
|
|
1214
|
+
initial: void 0,
|
|
1215
|
+
eventHighWater: null,
|
|
1216
|
+
pulseHold: true
|
|
1217
|
+
};
|
|
1218
|
+
const value = seed.value;
|
|
1219
|
+
return {
|
|
1220
|
+
...record,
|
|
1221
|
+
initial: value,
|
|
1222
|
+
eventHighWater: typeof value === "number" && Number.isFinite(value) ? value : null,
|
|
1223
|
+
pulseHold: false
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
function fieldRecords(fields, byCap, lookupCap, pulseSeeds) {
|
|
1227
|
+
return new Map(fields.map((plan) => {
|
|
1228
|
+
const key = fieldKey(plan);
|
|
1229
|
+
return [key, applyPulseSeed(createFieldRecord(plan, byCap, lookupCap), pulseSeeds?.get(key))];
|
|
1230
|
+
}));
|
|
1231
|
+
}
|
|
1207
1232
|
function currentOutput(record) {
|
|
1208
1233
|
return record.hasWritten ? record.written : record.initial;
|
|
1209
1234
|
}
|
|
@@ -1257,6 +1282,7 @@ function namedField(r) {
|
|
|
1257
1282
|
function writable(record) {
|
|
1258
1283
|
const outcome = record.outcome;
|
|
1259
1284
|
if (outcome === null) return void 0;
|
|
1285
|
+
if (record.pulseHold && !record.hasWritten && (outcome.state === "unavailable" || outcome.value === null)) return;
|
|
1260
1286
|
if (outcome.state !== "unavailable") return outcome.value;
|
|
1261
1287
|
switch (record.plan.unavailable) {
|
|
1262
1288
|
case "null": return null;
|
|
@@ -1694,7 +1720,7 @@ var CompositionRuntime = class {
|
|
|
1694
1720
|
constructor(deps) {
|
|
1695
1721
|
this.deps = deps;
|
|
1696
1722
|
this.startedAtMs = deps.now();
|
|
1697
|
-
this.records =
|
|
1723
|
+
this.records = fieldRecords(deps.fields, deps.initialByCap, deps.lookupCap, deps.pulseSeeds);
|
|
1698
1724
|
deps.target.onAsyncFailure?.((cap, failure) => this.onTargetWriteFailed(cap, failure));
|
|
1699
1725
|
const eventContext = {
|
|
1700
1726
|
deviceId: deps.target.id,
|
|
@@ -2541,7 +2567,7 @@ function writerFor(deps, p, device, onClaimEvent) {
|
|
|
2541
2567
|
onClaim: (cap, event) => onClaimEvent(deviceId, cap, owner, event)
|
|
2542
2568
|
});
|
|
2543
2569
|
}
|
|
2544
|
-
function newCompositionRuntime(deps, p, target, seed) {
|
|
2570
|
+
function newCompositionRuntime(deps, p, target, seed, pulseSeeds) {
|
|
2545
2571
|
const self = p.target.kind === "existing" ? selfReadCaps(p.fields, p.target.ref, p.target.features) : null;
|
|
2546
2572
|
const targetKey = p.target.kind === "existing" ? require_dist.compositionSourceKey(p.target.ref) : null;
|
|
2547
2573
|
const nativeCaps = new Set(self?.native ?? []);
|
|
@@ -2554,6 +2580,7 @@ function newCompositionRuntime(deps, p, target, seed) {
|
|
|
2554
2580
|
target,
|
|
2555
2581
|
lookupCap: deps.lookupCap,
|
|
2556
2582
|
...seed === void 0 ? {} : { initialByCap: seed.byCap },
|
|
2583
|
+
...pulseSeeds === void 0 ? {} : { pulseSeeds },
|
|
2557
2584
|
read: (read) => deps.tracker.read(read, require_dist.compositionSourceKey(read.source) === targetKey && nativeCaps.has(read.cap) ? "native" : "effective"),
|
|
2558
2585
|
isOwnRead: (read) => require_dist.compositionSourceKey(read.source) === ownKey && !nativeCaps.has(read.cap),
|
|
2559
2586
|
now: deps.now,
|
|
@@ -2759,6 +2786,389 @@ function verdictOf(blockId, entry, unsynced, grafts = null) {
|
|
|
2759
2786
|
};
|
|
2760
2787
|
}
|
|
2761
2788
|
//#endregion
|
|
2789
|
+
//#region src/builtins/composer/pulse-seed.ts
|
|
2790
|
+
/** The last false→true in time order. A series that opens already on is not a transition. */
|
|
2791
|
+
function lastOffToOn(points, beforeOn) {
|
|
2792
|
+
const ordered = [...points].toSorted((a, b) => a.t - b.t);
|
|
2793
|
+
let previous = beforeOn;
|
|
2794
|
+
let rise = null;
|
|
2795
|
+
for (const point of ordered) {
|
|
2796
|
+
if (previous === false && point.on && Number.isFinite(point.t)) rise = point.t;
|
|
2797
|
+
previous = point.on;
|
|
2798
|
+
}
|
|
2799
|
+
return rise;
|
|
2800
|
+
}
|
|
2801
|
+
function finite(value) {
|
|
2802
|
+
return value !== null && Number.isFinite(value) ? value : null;
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* What the source proves. A failed read is `hold`, never `none`.
|
|
2806
|
+
* When the source records no history and it is off, `lastChangedAt` is the
|
|
2807
|
+
* end of the last ring: it bounds the ring from above (the press was at or
|
|
2808
|
+
* before that instant). It is used only when it is newer than the target's
|
|
2809
|
+
* native stamp — an older bound does not prove the native value.
|
|
2810
|
+
*/
|
|
2811
|
+
function proof(facts) {
|
|
2812
|
+
if (facts.history.kind === "unread") return {
|
|
2813
|
+
kind: "hold",
|
|
2814
|
+
reason: "history-unread"
|
|
2815
|
+
};
|
|
2816
|
+
if (facts.history.kind === "series") {
|
|
2817
|
+
const at = lastOffToOn(facts.history.points, facts.history.beforeOn);
|
|
2818
|
+
return at === null ? { kind: "none" } : {
|
|
2819
|
+
kind: "rise",
|
|
2820
|
+
at,
|
|
2821
|
+
via: "history"
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
if (facts.level.kind === "unread") return {
|
|
2825
|
+
kind: "hold",
|
|
2826
|
+
reason: "level-unread"
|
|
2827
|
+
};
|
|
2828
|
+
const changedAt = facts.level.kind === "off" ? finite(facts.level.changedAt) : null;
|
|
2829
|
+
const newerThanNative = changedAt !== null && (facts.nativeStamp === null || changedAt > facts.nativeStamp);
|
|
2830
|
+
if (changedAt !== null && newerThanNative) return {
|
|
2831
|
+
kind: "rise",
|
|
2832
|
+
at: changedAt,
|
|
2833
|
+
via: "changed-at-bound"
|
|
2834
|
+
};
|
|
2835
|
+
return { kind: "none" };
|
|
2836
|
+
}
|
|
2837
|
+
function claimed(owned) {
|
|
2838
|
+
return owned.kind === "stamp" || owned.kind === "never";
|
|
2839
|
+
}
|
|
2840
|
+
function decidePulseSeed(facts) {
|
|
2841
|
+
const proved = proof(facts);
|
|
2842
|
+
if (proved.kind === "hold") return claimed(facts.owned) ? { kind: "keep" } : proved;
|
|
2843
|
+
if (proved.kind === "rise") {
|
|
2844
|
+
if (facts.owned.kind === "stamp" && facts.owned.at >= proved.at) return { kind: "keep" };
|
|
2845
|
+
const count = facts.owned.kind === "stamp" ? facts.owned.count : 0;
|
|
2846
|
+
return {
|
|
2847
|
+
kind: "stamp",
|
|
2848
|
+
at: proved.at,
|
|
2849
|
+
count,
|
|
2850
|
+
via: proved.via
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
return claimed(facts.owned) ? { kind: "keep" } : {
|
|
2854
|
+
kind: "none",
|
|
2855
|
+
count: 0
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
/** One failed source blocks "no ring". A rise from another source still stands. */
|
|
2859
|
+
function mergePulseDecisions(decisions) {
|
|
2860
|
+
if (decisions.length === 0) return {
|
|
2861
|
+
kind: "none",
|
|
2862
|
+
count: 0
|
|
2863
|
+
};
|
|
2864
|
+
const stamps = decisions.flatMap((decision) => decision.kind === "stamp" ? [decision] : []);
|
|
2865
|
+
if (stamps.length > 0) return stamps.reduce((best, next) => next.at > best.at ? next : best);
|
|
2866
|
+
const hold = decisions.find((decision) => decision.kind === "hold");
|
|
2867
|
+
if (hold !== void 0) return hold;
|
|
2868
|
+
if (decisions.some((decision) => decision.kind === "keep")) return { kind: "keep" };
|
|
2869
|
+
return {
|
|
2870
|
+
kind: "none",
|
|
2871
|
+
count: 0
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
//#endregion
|
|
2875
|
+
//#region src/builtins/composer/pulse-seed-resolve.ts
|
|
2876
|
+
/**
|
|
2877
|
+
* Gather a pulse override's start seed (D938). The stored expression is not
|
|
2878
|
+
* changed. A failed read holds the field: no seed, and a claimed stamp is
|
|
2879
|
+
* not cleared (D49).
|
|
2880
|
+
*/
|
|
2881
|
+
var ROSE_CALL = String.raw`rose\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,\s*([A-Za-z_][A-Za-z0-9_]*)`;
|
|
2882
|
+
function historySeriesKey(cap, fieldPath) {
|
|
2883
|
+
return require_retention.capSeriesKey(cap, fieldPath);
|
|
2884
|
+
}
|
|
2885
|
+
async function resolvePulseSeeds(input) {
|
|
2886
|
+
try {
|
|
2887
|
+
return await resolveNow(input);
|
|
2888
|
+
} catch (err) {
|
|
2889
|
+
input.logger.warn("pulse override seed held — gathering the source facts threw, so nothing is seeded and nothing is cleared", {
|
|
2890
|
+
tags: { deviceId: input.deviceId },
|
|
2891
|
+
meta: {
|
|
2892
|
+
blockId: input.blockId,
|
|
2893
|
+
error: require_dist.errMsg(err)
|
|
2894
|
+
}
|
|
2895
|
+
});
|
|
2896
|
+
return /* @__PURE__ */ new Map();
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
async function resolveNow(input) {
|
|
2900
|
+
const roses = input.fields.filter(isPulseTimestamp);
|
|
2901
|
+
if (roses.length === 0) return /* @__PURE__ */ new Map();
|
|
2902
|
+
const listing = await readClaims(input);
|
|
2903
|
+
if (listing === null) return /* @__PURE__ */ new Map();
|
|
2904
|
+
const seeds = /* @__PURE__ */ new Map();
|
|
2905
|
+
for (const field of roses) {
|
|
2906
|
+
const decision = await decideField(input, field, listing);
|
|
2907
|
+
logDecision(input, field, decision);
|
|
2908
|
+
for (const [key, seed] of seedsFor(input.fields, field, decision)) seeds.set(key, seed);
|
|
2909
|
+
}
|
|
2910
|
+
return seeds;
|
|
2911
|
+
}
|
|
2912
|
+
function isPulseTimestamp(field) {
|
|
2913
|
+
return field.source.kind === "expression" && require_dist.expressionContainsBuiltinCall(field.source.expr, "rose");
|
|
2914
|
+
}
|
|
2915
|
+
async function readClaims(input) {
|
|
2916
|
+
try {
|
|
2917
|
+
const listing = await input.claims.listClaims({ ownerPrefix: input.blockId });
|
|
2918
|
+
if (listing.state === "loaded") return listing;
|
|
2919
|
+
input.logger.warn("pulse override seed held — the claims index has not loaded, so nothing is seeded and nothing is cleared", {
|
|
2920
|
+
tags: { deviceId: input.deviceId },
|
|
2921
|
+
meta: {
|
|
2922
|
+
blockId: input.blockId,
|
|
2923
|
+
reason: listing.reason
|
|
2924
|
+
}
|
|
2925
|
+
});
|
|
2926
|
+
return null;
|
|
2927
|
+
} catch (err) {
|
|
2928
|
+
input.logger.warn("pulse override seed held — the claims index could not be read, so nothing is seeded and nothing is cleared", {
|
|
2929
|
+
tags: { deviceId: input.deviceId },
|
|
2930
|
+
meta: {
|
|
2931
|
+
blockId: input.blockId,
|
|
2932
|
+
error: require_dist.errMsg(err)
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
async function decideField(input, field, listing) {
|
|
2939
|
+
if (listing.state !== "loaded") return {
|
|
2940
|
+
kind: "hold",
|
|
2941
|
+
reason: "claims-unread"
|
|
2942
|
+
};
|
|
2943
|
+
const claim = listing.claims.find((row) => row.owner === input.blockId && row.deviceId === input.deviceId && row.capName === field.cap);
|
|
2944
|
+
const slice = input.targetSeed.byCap.get(field.cap) ?? null;
|
|
2945
|
+
const counter = counterField(input.fields, field);
|
|
2946
|
+
const owned = ownedOf(claim, slice, field.path, counter?.path ?? null);
|
|
2947
|
+
const nativeStamp = stampOf(slice, field.path);
|
|
2948
|
+
const pairs = rosePairs(field);
|
|
2949
|
+
if (pairs.length === 0) return { kind: "keep" };
|
|
2950
|
+
const decisions = [];
|
|
2951
|
+
for (const pair of pairs) decisions.push(await decidePair(input, pair, owned, nativeStamp));
|
|
2952
|
+
return mergePulseDecisions(decisions);
|
|
2953
|
+
}
|
|
2954
|
+
async function decidePair(input, pair, owned, nativeStamp) {
|
|
2955
|
+
const resolution = await input.sources.resolve(pair.source, false);
|
|
2956
|
+
if (resolution.kind === "missing") return { kind: "keep" };
|
|
2957
|
+
if (resolution.kind !== "found") return decidePulseSeed({
|
|
2958
|
+
owned,
|
|
2959
|
+
nativeStamp,
|
|
2960
|
+
history: { kind: "unread" },
|
|
2961
|
+
level: { kind: "unread" }
|
|
2962
|
+
});
|
|
2963
|
+
return decidePulseSeed({
|
|
2964
|
+
owned,
|
|
2965
|
+
nativeStamp,
|
|
2966
|
+
history: await historyOf(input, resolution.deviceId, pair),
|
|
2967
|
+
level: await levelOf(input, resolution.deviceId, pair)
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
async function historyOf(input, deviceId, pair) {
|
|
2971
|
+
if (!recordsField(input.lookupCap, pair.cap, pair.activeField)) return { kind: "absent" };
|
|
2972
|
+
const now = input.now();
|
|
2973
|
+
const fromMs = now - require_retention.DEFAULT_RETENTION.rawDays * require_retention.DAY_MS;
|
|
2974
|
+
try {
|
|
2975
|
+
const answer = await input.readPulseHistory(deviceId, pair.cap, pair.activeField, fromMs, now);
|
|
2976
|
+
if (answer.kind === "unread") return { kind: "unread" };
|
|
2977
|
+
return {
|
|
2978
|
+
kind: "series",
|
|
2979
|
+
beforeOn: answer.series.beforeOn,
|
|
2980
|
+
points: answer.series.points
|
|
2981
|
+
};
|
|
2982
|
+
} catch (err) {
|
|
2983
|
+
input.logger.warn("pulse override history read threw — treated as unread", {
|
|
2984
|
+
tags: { deviceId: input.deviceId },
|
|
2985
|
+
meta: {
|
|
2986
|
+
blockId: input.blockId,
|
|
2987
|
+
sourceDeviceId: deviceId,
|
|
2988
|
+
cap: pair.cap,
|
|
2989
|
+
field: pair.activeField,
|
|
2990
|
+
error: require_dist.errMsg(err)
|
|
2991
|
+
}
|
|
2992
|
+
});
|
|
2993
|
+
return { kind: "unread" };
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
async function levelOf(input, deviceId, pair) {
|
|
2997
|
+
try {
|
|
2998
|
+
return levelFromSlice(await input.readCap(deviceId, pair.cap), pair);
|
|
2999
|
+
} catch (err) {
|
|
3000
|
+
input.logger.warn("pulse override source slice could not be read — treated as unread", {
|
|
3001
|
+
tags: { deviceId: input.deviceId },
|
|
3002
|
+
meta: {
|
|
3003
|
+
blockId: input.blockId,
|
|
3004
|
+
sourceDeviceId: deviceId,
|
|
3005
|
+
cap: pair.cap,
|
|
3006
|
+
error: require_dist.errMsg(err)
|
|
3007
|
+
}
|
|
3008
|
+
});
|
|
3009
|
+
return { kind: "unread" };
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
function levelFromSlice(slice, pair) {
|
|
3013
|
+
if (slice === null) return {
|
|
3014
|
+
kind: "off",
|
|
3015
|
+
changedAt: null
|
|
3016
|
+
};
|
|
3017
|
+
const active = slice[pair.activeField];
|
|
3018
|
+
if (typeof active !== "boolean") return { kind: "unread" };
|
|
3019
|
+
const changed = slice[pair.changedAtField];
|
|
3020
|
+
const changedAt = typeof changed === "number" && Number.isFinite(changed) ? changed : null;
|
|
3021
|
+
return active ? {
|
|
3022
|
+
kind: "on",
|
|
3023
|
+
changedAt
|
|
3024
|
+
} : {
|
|
3025
|
+
kind: "off",
|
|
3026
|
+
changedAt
|
|
3027
|
+
};
|
|
3028
|
+
}
|
|
3029
|
+
function recordsField(lookup, cap, fieldPath) {
|
|
3030
|
+
const history = lookup(cap)?.history;
|
|
3031
|
+
if (history === void 0 || history === "none") return false;
|
|
3032
|
+
return Object.hasOwn(history.fields, fieldPath);
|
|
3033
|
+
}
|
|
3034
|
+
function ownedOf(claim, slice, timestampPath, counterPath) {
|
|
3035
|
+
if (claim === void 0 || slice === null || !claim.fields.includes(timestampPath)) return { kind: "none" };
|
|
3036
|
+
const at = slice[timestampPath];
|
|
3037
|
+
if (at === null) return { kind: "never" };
|
|
3038
|
+
if (typeof at !== "number" || !Number.isFinite(at)) return { kind: "none" };
|
|
3039
|
+
const rawCount = counterPath === null ? void 0 : slice[counterPath];
|
|
3040
|
+
return {
|
|
3041
|
+
kind: "stamp",
|
|
3042
|
+
at,
|
|
3043
|
+
count: typeof rawCount === "number" && Number.isFinite(rawCount) && rawCount >= 0 ? rawCount : 0
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
function stampOf(slice, path) {
|
|
3047
|
+
const at = slice?.[path];
|
|
3048
|
+
return typeof at === "number" && Number.isFinite(at) ? at : null;
|
|
3049
|
+
}
|
|
3050
|
+
function counterField(fields, timestamp) {
|
|
3051
|
+
return fields.find((field) => {
|
|
3052
|
+
if (field.cap !== timestamp.cap || field.path === timestamp.path) return false;
|
|
3053
|
+
return field.source.kind === "expression" && field.source.expr === `count(${timestamp.path})`;
|
|
3054
|
+
});
|
|
3055
|
+
}
|
|
3056
|
+
function rosePairs(field) {
|
|
3057
|
+
if (field.source.kind !== "expression") return [];
|
|
3058
|
+
const found = [];
|
|
3059
|
+
for (const match of field.source.expr.matchAll(new RegExp(ROSE_CALL, "g"))) {
|
|
3060
|
+
const pair = rosePair(field, match[1], match[2]);
|
|
3061
|
+
if (pair !== null) found.push(pair);
|
|
3062
|
+
}
|
|
3063
|
+
return found;
|
|
3064
|
+
}
|
|
3065
|
+
function rosePair(field, activeName, changedName) {
|
|
3066
|
+
if (field.source.kind !== "expression") return null;
|
|
3067
|
+
if (activeName === void 0 || changedName === void 0) return null;
|
|
3068
|
+
const active = field.source.bindings[activeName];
|
|
3069
|
+
const changed = field.source.bindings[changedName];
|
|
3070
|
+
if (active === void 0 || changed === void 0) return null;
|
|
3071
|
+
if (active.kind !== "from" || changed.kind !== "from") return null;
|
|
3072
|
+
if (active.cap !== changed.cap) return null;
|
|
3073
|
+
if (active.source.addonId !== changed.source.addonId) return null;
|
|
3074
|
+
if (active.source.stableId !== changed.source.stableId) return null;
|
|
3075
|
+
return {
|
|
3076
|
+
source: active.source,
|
|
3077
|
+
cap: active.cap,
|
|
3078
|
+
activeField: active.fieldPath,
|
|
3079
|
+
changedAtField: changed.fieldPath
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
function seedsFor(fields, timestamp, decision) {
|
|
3083
|
+
const seeds = /* @__PURE__ */ new Map();
|
|
3084
|
+
if (decision.kind === "keep") return seeds;
|
|
3085
|
+
const counter = counterField(fields, timestamp);
|
|
3086
|
+
if (decision.kind === "hold") {
|
|
3087
|
+
seeds.set(fieldKey(timestamp), { kind: "hold" });
|
|
3088
|
+
if (counter !== void 0) seeds.set(fieldKey(counter), {
|
|
3089
|
+
kind: "value",
|
|
3090
|
+
value: 0
|
|
3091
|
+
});
|
|
3092
|
+
return seeds;
|
|
3093
|
+
}
|
|
3094
|
+
if (decision.kind === "none") {
|
|
3095
|
+
seeds.set(fieldKey(timestamp), {
|
|
3096
|
+
kind: "value",
|
|
3097
|
+
value: null
|
|
3098
|
+
});
|
|
3099
|
+
if (counter !== void 0) seeds.set(fieldKey(counter), {
|
|
3100
|
+
kind: "value",
|
|
3101
|
+
value: 0
|
|
3102
|
+
});
|
|
3103
|
+
return seeds;
|
|
3104
|
+
}
|
|
3105
|
+
seeds.set(fieldKey(timestamp), {
|
|
3106
|
+
kind: "value",
|
|
3107
|
+
value: decision.at
|
|
3108
|
+
});
|
|
3109
|
+
if (counter !== void 0) seeds.set(fieldKey(counter), {
|
|
3110
|
+
kind: "value",
|
|
3111
|
+
value: decision.count
|
|
3112
|
+
});
|
|
3113
|
+
return seeds;
|
|
3114
|
+
}
|
|
3115
|
+
/** Raw boolean points of the device-level series (`scope` empty). 1 is on. */
|
|
3116
|
+
function pulseHistoryFromQuery(series) {
|
|
3117
|
+
const row = series.find((item) => item.scope === "");
|
|
3118
|
+
if (row === void 0) return {
|
|
3119
|
+
beforeOn: null,
|
|
3120
|
+
points: []
|
|
3121
|
+
};
|
|
3122
|
+
return {
|
|
3123
|
+
beforeOn: levelBit(row.before?.value ?? null),
|
|
3124
|
+
points: row.points.flatMap((point) => {
|
|
3125
|
+
const on = levelBit(point.last);
|
|
3126
|
+
return on === null ? [] : [{
|
|
3127
|
+
t: point.t,
|
|
3128
|
+
on
|
|
3129
|
+
}];
|
|
3130
|
+
})
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
function levelBit(value) {
|
|
3134
|
+
if (value === 1) return true;
|
|
3135
|
+
if (value === 0) return false;
|
|
3136
|
+
return null;
|
|
3137
|
+
}
|
|
3138
|
+
function logDecision(input, field, decision) {
|
|
3139
|
+
const meta = {
|
|
3140
|
+
blockId: input.blockId,
|
|
3141
|
+
cap: field.cap,
|
|
3142
|
+
field: field.path
|
|
3143
|
+
};
|
|
3144
|
+
if (decision.kind === "stamp") {
|
|
3145
|
+
input.logger.info("pulse override seeded lastPressedAt from its source", {
|
|
3146
|
+
tags: { deviceId: input.deviceId },
|
|
3147
|
+
meta: {
|
|
3148
|
+
...meta,
|
|
3149
|
+
at: decision.at,
|
|
3150
|
+
via: decision.via,
|
|
3151
|
+
count: decision.count
|
|
3152
|
+
}
|
|
3153
|
+
});
|
|
3154
|
+
return;
|
|
3155
|
+
}
|
|
3156
|
+
if (decision.kind === "none") {
|
|
3157
|
+
input.logger.info("pulse override seeded no ring — the source proved none, so the native stamp is not used", {
|
|
3158
|
+
tags: { deviceId: input.deviceId },
|
|
3159
|
+
meta
|
|
3160
|
+
});
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
if (decision.kind === "hold") input.logger.warn("pulse override seed held — a source read failed, so nothing is seeded and nothing is cleared", {
|
|
3164
|
+
tags: { deviceId: input.deviceId },
|
|
3165
|
+
meta: {
|
|
3166
|
+
...meta,
|
|
3167
|
+
reason: decision.reason
|
|
3168
|
+
}
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
//#endregion
|
|
2762
3172
|
//#region src/builtins/composer/composer.ts
|
|
2763
3173
|
/**
|
|
2764
3174
|
* The composer: every declarative composition on this hub, in one runner
|
|
@@ -2996,6 +3406,7 @@ var Composer = class {
|
|
|
2996
3406
|
continue;
|
|
2997
3407
|
}
|
|
2998
3408
|
let targetSeed;
|
|
3409
|
+
let pulseSeeds;
|
|
2999
3410
|
if (p.target.kind === "existing" && p.target.deviceId !== null && p.target.waiting === null && p.target.gone === null && p.block.enabled && p.fields.length > 0) try {
|
|
3000
3411
|
targetSeed = await readExistingTargetSeeds(p.target.deviceId, p.fields.map((field) => field.cap), this.deps.readExistingTargetCap);
|
|
3001
3412
|
reportSeedlessRestoredCaps({
|
|
@@ -3006,6 +3417,20 @@ var Composer = class {
|
|
|
3006
3417
|
seed: targetSeed,
|
|
3007
3418
|
lookupCap: this.deps.lookupCap
|
|
3008
3419
|
});
|
|
3420
|
+
const readHistory = this.deps.readPulseHistory;
|
|
3421
|
+
if (readHistory !== void 0) pulseSeeds = await resolvePulseSeeds({
|
|
3422
|
+
blockId: p.block.id,
|
|
3423
|
+
deviceId: p.target.deviceId,
|
|
3424
|
+
fields: p.fields,
|
|
3425
|
+
targetSeed,
|
|
3426
|
+
lookupCap: this.deps.lookupCap,
|
|
3427
|
+
claims: this.deps.claims,
|
|
3428
|
+
sources: this.deps.tracker,
|
|
3429
|
+
readCap: this.deps.readExistingTargetCap,
|
|
3430
|
+
readPulseHistory: readHistory,
|
|
3431
|
+
now: this.deps.now,
|
|
3432
|
+
logger: this.deps.logger
|
|
3433
|
+
});
|
|
3009
3434
|
} catch (err) {
|
|
3010
3435
|
const reason = `the target device state could not be read before ownership (${require_dist.errMsg(err)}) — nothing is grafted or claimed until it is; retried on the next apply`;
|
|
3011
3436
|
this.deps.logger.warn(previous?.runtime === null || previous === void 0 ? "customization target state could not be read — ownership is held" : "customization edit target state could not be read — the previous runtime stays installed", {
|
|
@@ -3031,7 +3456,7 @@ var Composer = class {
|
|
|
3031
3456
|
const writer = writerFor(this.deps, p, device, (deviceId, cap, owner, event) => {
|
|
3032
3457
|
this.onClaimEvent(deviceId, cap, owner, event);
|
|
3033
3458
|
});
|
|
3034
|
-
const runtime = writer === null || p.fields.length === 0 ? null : newCompositionRuntime(this.deps, p, writer, targetSeed);
|
|
3459
|
+
const runtime = writer === null || p.fields.length === 0 ? null : newCompositionRuntime(this.deps, p, writer, targetSeed, pulseSeeds);
|
|
3035
3460
|
if (writer !== null && p.target.kind === "existing" && p.target.deviceId !== null) this.grafts.plan({
|
|
3036
3461
|
deviceId: p.target.deviceId,
|
|
3037
3462
|
ref: p.target.ref,
|
|
@@ -4164,6 +4589,8 @@ var SourceTracker = class {
|
|
|
4164
4589
|
*/
|
|
4165
4590
|
/** Ignored by `DeclaredDevices` whenever `integrationId` is supplied, which is always here. */
|
|
4166
4591
|
var DECLARED_INTEGRATION_NAME_UNUSED = "Blocks";
|
|
4592
|
+
/** The device-history query ceiling. A switch's raw tier is change-only, so this is the whole window. */
|
|
4593
|
+
var PULSE_HISTORY_MAX_POINTS = 5e3;
|
|
4167
4594
|
/** The owner-only shadow payload is not in the typed catalog: narrowed at the boundary, like `device.state-changed`. */
|
|
4168
4595
|
function parseNativeShadowChanged(data) {
|
|
4169
4596
|
if (!isRecord$2(data)) return null;
|
|
@@ -4312,6 +4739,26 @@ var ComposerAddon = class extends require_dist.BaseAddon {
|
|
|
4312
4739
|
deviceId,
|
|
4313
4740
|
capName
|
|
4314
4741
|
}) ?? null,
|
|
4742
|
+
readPulseHistory: async (deviceId, cap, fieldPath, fromMs, toMs) => {
|
|
4743
|
+
try {
|
|
4744
|
+
return {
|
|
4745
|
+
kind: "series",
|
|
4746
|
+
series: pulseHistoryFromQuery((await api.deviceHistory.query.query({
|
|
4747
|
+
deviceId,
|
|
4748
|
+
key: historySeriesKey(cap, fieldPath),
|
|
4749
|
+
from: fromMs,
|
|
4750
|
+
to: toMs,
|
|
4751
|
+
resolution: "raw",
|
|
4752
|
+
maxPoints: PULSE_HISTORY_MAX_POINTS
|
|
4753
|
+
})).series)
|
|
4754
|
+
};
|
|
4755
|
+
} catch (err) {
|
|
4756
|
+
return {
|
|
4757
|
+
kind: "unread",
|
|
4758
|
+
error: require_dist.errMsg(err)
|
|
4759
|
+
};
|
|
4760
|
+
}
|
|
4761
|
+
},
|
|
4315
4762
|
describeTarget: readBoundCaps,
|
|
4316
4763
|
grafts,
|
|
4317
4764
|
now: () => Date.now(),
|