@camstack/types 1.2.259 → 1.2.261
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/addon/base-addon.d.ts +9 -1
- package/dist/addon.js +15 -7
- package/dist/addon.mjs +15 -7
- package/dist/capabilities/camera-streams.cap.d.ts +5 -5
- package/dist/capabilities/capability-definition.d.ts +9 -0
- package/dist/capabilities/custom-model-registry.cap.d.ts +18 -12
- package/dist/capabilities/decoder.cap.d.ts +12 -12
- package/dist/capabilities/device-extension.cap.d.ts +370 -0
- package/dist/capabilities/device-manager.cap.d.ts +45 -5
- package/dist/capabilities/doorbell.cap.d.ts +41 -0
- package/dist/capabilities/embedding-encoder.cap.d.ts +175 -0
- package/dist/capabilities/index.d.ts +17 -14
- package/dist/capabilities/llm-runtime.cap.d.ts +4 -4
- package/dist/capabilities/llm-shared.d.ts +6 -6
- package/dist/capabilities/llm.cap.d.ts +6 -6
- package/dist/capabilities/load-contribution.cap.d.ts +2 -2
- package/dist/capabilities/model-convert.cap.d.ts +9 -6
- package/dist/capabilities/model-distributor.cap.d.ts +18 -12
- package/dist/capabilities/motion-detection.cap.d.ts +4 -4
- package/dist/capabilities/notification-rules.cap.d.ts +603 -31
- package/dist/capabilities/osd-manager.cap.d.ts +7 -7
- package/dist/capabilities/pipeline-analytics.cap.d.ts +130 -129
- package/dist/capabilities/pipeline-executor.cap.d.ts +78 -120
- package/dist/capabilities/pipeline-orchestrator.cap.d.ts +78 -65
- package/dist/capabilities/pipeline-runner.cap.d.ts +30 -6
- package/dist/capabilities/platform-probe.cap.d.ts +4 -13
- package/dist/capabilities/recording-export.cap.d.ts +7 -7
- package/dist/capabilities/schemas/detection-shared.d.ts +4 -4
- package/dist/capabilities/schemas/streaming-shared.d.ts +8 -8
- package/dist/capabilities/stream-broker.cap.d.ts +9 -9
- package/dist/capabilities/system.cap.d.ts +2 -2
- package/dist/capabilities/vacuum-control.cap.d.ts +13 -13
- package/dist/capabilities/vector-store.cap.d.ts +14 -0
- package/dist/capabilities/videoclips.cap.d.ts +5 -5
- package/dist/capabilities/webrtc-session.cap.d.ts +2 -2
- package/dist/device-extension/index.d.ts +3 -0
- package/dist/device-extension/slot-descriptor.d.ts +62 -0
- package/dist/device-extension/slot-provider.d.ts +68 -0
- package/dist/generated/addon-api.d.ts +59 -9
- package/dist/generated/cap-input-defaults.d.ts +1 -1
- package/dist/generated/capability-router-map.d.ts +5 -2
- package/dist/generated/collection-array-methods.d.ts +1 -1
- package/dist/generated/device-proxy.d.ts +7 -7
- package/dist/generated/device-scoped-caps.d.ts +1 -1
- package/dist/generated/extension-slots.d.ts +39 -0
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/method-device-selectors.d.ts +1 -1
- package/dist/generated/system-proxy.d.ts +4 -2
- package/dist/index.d.ts +8 -3
- package/dist/index.js +3125 -1792
- package/dist/index.mjs +3049 -1788
- package/dist/interfaces/addon.d.ts +41 -0
- package/dist/interfaces/event-bus.d.ts +11 -1
- package/dist/interfaces/pipeline-executor-capability.d.ts +15 -6
- package/dist/interfaces/platform.d.ts +2 -2
- package/dist/interfaces/stream-broker.d.ts +2 -2
- package/dist/node.d.ts +1 -0
- package/dist/node.js +73 -7
- package/dist/node.mjs +73 -8
- package/dist/notification/system-event-summary.d.ts +83 -0
- package/dist/notification/template-vars.d.ts +15 -0
- package/dist/pipeline/cluster-model-scope.d.ts +59 -3
- package/dist/{sleep-DU7mu4wb.mjs → sleep-BXv0ZcRa.mjs} +49 -33
- package/dist/{sleep-DtomDrRW.js → sleep-QxyLiUId.js} +54 -32
- package/dist/storage/atomic-write.d.ts +25 -0
- package/dist/types/clip-model.d.ts +64 -0
- package/dist/types/detection.d.ts +9 -0
- package/dist/types/models.d.ts +18 -19
- package/dist/types/pipeline-step.d.ts +9 -1
- package/dist/types/pipeline.d.ts +2 -1
- package/dist/utils/background-run-registry.d.ts +69 -0
- package/dist/utils/runtime-mapping.d.ts +9 -54
- package/package.json +1 -1
|
@@ -489,6 +489,17 @@ export interface AddonContext<TConfig = Record<string, unknown>> {
|
|
|
489
489
|
* allowlisted engine-cascade holdouts are the only callers.
|
|
490
490
|
*/
|
|
491
491
|
readAddonStore(): Promise<Record<string, unknown>>;
|
|
492
|
+
/**
|
|
493
|
+
* The same read, THREE-WAY: a stored blob, no blob, or a read that FAILED.
|
|
494
|
+
* `readAddonStore` folds a failure into `{}`, which a caller cannot tell
|
|
495
|
+
* from "never written"; this one does not (D651). Optional: a settings view
|
|
496
|
+
* that predates it answers nothing, and the caller must treat that as
|
|
497
|
+
* unreadable, never as empty.
|
|
498
|
+
*
|
|
499
|
+
* @internal Same rule as `readAddonStore`: addons reach it only through
|
|
500
|
+
* `BaseAddon.readStoredGlobalKey`.
|
|
501
|
+
*/
|
|
502
|
+
readAddonStoreResult?(): Promise<AddonStoreRead>;
|
|
492
503
|
/**
|
|
493
504
|
* Write a patch into the addon store. Keys not in the patch are
|
|
494
505
|
* preserved. Keys explicitly set to `null` are preserved as-is (they
|
|
@@ -1409,3 +1420,33 @@ export interface InternalAddonContext extends AddonContext {
|
|
|
1409
1420
|
/** Resolve the preferred provider for a capability. Kernel-only. */
|
|
1410
1421
|
resolveProvider<T = unknown>(capabilityName: string): T | null;
|
|
1411
1422
|
}
|
|
1423
|
+
/**
|
|
1424
|
+
* A three-way read of an addon's own store (D651). `missing` and `failed` are
|
|
1425
|
+
* distinct on purpose: folding them is how a failed read becomes "defaults".
|
|
1426
|
+
*/
|
|
1427
|
+
export type AddonStoreRead = {
|
|
1428
|
+
readonly kind: 'value';
|
|
1429
|
+
readonly value: Readonly<Record<string, unknown>>;
|
|
1430
|
+
} | {
|
|
1431
|
+
readonly kind: 'missing';
|
|
1432
|
+
} | {
|
|
1433
|
+
readonly kind: 'failed';
|
|
1434
|
+
readonly error: string;
|
|
1435
|
+
};
|
|
1436
|
+
/** One stored key of an addon's own store, read three-way (D651). */
|
|
1437
|
+
export type StoredKeyRead =
|
|
1438
|
+
/** The key was written; `value` is what is stored. */
|
|
1439
|
+
{
|
|
1440
|
+
readonly kind: 'present';
|
|
1441
|
+
readonly value: unknown;
|
|
1442
|
+
}
|
|
1443
|
+
/** The store answered and the key was never written. */
|
|
1444
|
+
| {
|
|
1445
|
+
readonly kind: 'absent';
|
|
1446
|
+
}
|
|
1447
|
+
/** The store did not answer, or this node's settings view cannot say. */
|
|
1448
|
+
| {
|
|
1449
|
+
readonly kind: 'unreadable';
|
|
1450
|
+
readonly reason: 'store-read-failed' | 'three-way-read-unavailable';
|
|
1451
|
+
readonly error?: string;
|
|
1452
|
+
};
|
|
@@ -165,6 +165,16 @@ export interface PipelineAnalyticsFrameTrackedPayload {
|
|
|
165
165
|
* a dropped frame just lets the session close slightly earlier.
|
|
166
166
|
*/
|
|
167
167
|
readonly hasLiveTrack?: boolean;
|
|
168
|
+
/**
|
|
169
|
+
* How many objects the TRACKER holds on this frame — moving OR still,
|
|
170
|
+
* i.e. `result.tracked.length` (D652). Diagnostics only: it decides nothing.
|
|
171
|
+
* {@link hasLiveTrack} deliberately subtracts a still subject, so a session
|
|
172
|
+
* that closed on a parked car and one that closed on an empty street both
|
|
173
|
+
* saw `false` last; this count is what tells them apart on the orchestrator's
|
|
174
|
+
* close line (`liveTracks`). Absent ⇒ unknown (an older post-analysis),
|
|
175
|
+
* which the consumer reports as `null`, never as 0.
|
|
176
|
+
*/
|
|
177
|
+
readonly liveTrackCount?: number;
|
|
168
178
|
[key: string]: unknown;
|
|
169
179
|
}
|
|
170
180
|
/** Lifecycle phase of a tracked object: it appears (`start`), its best
|
|
@@ -1260,7 +1270,7 @@ export interface EventCatalog {
|
|
|
1260
1270
|
readonly engine: PipelineEngineChoice;
|
|
1261
1271
|
readonly modelsLoaded: readonly string[];
|
|
1262
1272
|
readonly inUseByCameras: readonly number[];
|
|
1263
|
-
readonly kind: 'runtime' | '
|
|
1273
|
+
readonly kind: 'runtime' | 'device-pool';
|
|
1264
1274
|
readonly poolPid: number | null;
|
|
1265
1275
|
readonly idleMs: number | null;
|
|
1266
1276
|
readonly idleTtlMs: number | null;
|
|
@@ -33,14 +33,12 @@ export interface PipelineStepInput {
|
|
|
33
33
|
* - `imageBase64`: one-shot benchmark (ImageTab "Run")
|
|
34
34
|
* - `referenceImage`: named file from the reference-image store
|
|
35
35
|
*
|
|
36
|
-
* `
|
|
37
|
-
*
|
|
38
|
-
* the
|
|
39
|
-
* selection during the migration window).
|
|
36
|
+
* `deviceId` is an optional contextual hint — the executor uses it for
|
|
37
|
+
* per-device overrides and traces. There is no `engine` (D646): the pool is
|
|
38
|
+
* the node's own engine, or the `deviceKey` pool when one is named.
|
|
40
39
|
*/
|
|
41
40
|
export interface PipelineRunInput {
|
|
42
41
|
readonly steps: readonly PipelineStepInput[];
|
|
43
|
-
readonly engine?: PipelineEngineChoice;
|
|
44
42
|
readonly frame?: FrameInput;
|
|
45
43
|
/** Same-process lazy source; never forwarded over UDS/Moleculer. */
|
|
46
44
|
readonly frameRef?: FrameRef;
|
|
@@ -93,6 +91,16 @@ export interface PipelineRunInput {
|
|
|
93
91
|
* mutually-exclusive image inputs. Absent ⇒ tile-crop children.
|
|
94
92
|
*/
|
|
95
93
|
readonly nativeCropRef?: NativeCropRef;
|
|
94
|
+
/**
|
|
95
|
+
* Offline REPLAY of stored media for `deviceId` (D56, D646). The call runs
|
|
96
|
+
* the camera's structure — its detection-stage zone gate and per-device
|
|
97
|
+
* overrides, exactly as a live frame — over a recorded image, and it is
|
|
98
|
+
* PINNED: every executing step runs the model it names on the `deviceKey`
|
|
99
|
+
* pool it names, or the call refuses (`model-not-servable` /
|
|
100
|
+
* `device-not-servable`) before any pool is touched. Nothing is emitted on
|
|
101
|
+
* the camera's live trace stream. Absent ⇒ unchanged behaviour.
|
|
102
|
+
*/
|
|
103
|
+
readonly replay?: true;
|
|
96
104
|
}
|
|
97
105
|
/**
|
|
98
106
|
* Singleton capability provided by the pipeline-executor addon.
|
|
@@ -121,6 +129,8 @@ export interface IPipelineExecutorProvider {
|
|
|
121
129
|
*/
|
|
122
130
|
validatePipeline(input: {
|
|
123
131
|
readonly steps: readonly PipelineStepInput[];
|
|
132
|
+
/** Validate against this device pool's format; absent ⇒ the node default. */
|
|
133
|
+
readonly deviceKey?: string;
|
|
124
134
|
}): Promise<PipelineValidationResult>;
|
|
125
135
|
/** Get the currently selected engine (bootstrap default on the node). */
|
|
126
136
|
getSelectedEngine(): Promise<PipelineEngineChoice>;
|
|
@@ -172,7 +182,6 @@ export interface IPipelineExecutorProvider {
|
|
|
172
182
|
*/
|
|
173
183
|
runPipelineBatch(input: {
|
|
174
184
|
readonly steps: readonly PipelineStepInput[];
|
|
175
|
-
readonly engine?: PipelineEngineChoice;
|
|
176
185
|
readonly frames: readonly FrameInput[];
|
|
177
186
|
readonly deviceId?: number;
|
|
178
187
|
readonly sessionId?: string;
|
|
@@ -47,7 +47,7 @@ export interface CoralInfo {
|
|
|
47
47
|
}
|
|
48
48
|
/** A probed backend with availability and score */
|
|
49
49
|
export interface PlatformScore {
|
|
50
|
-
readonly runtime: '
|
|
50
|
+
readonly runtime: 'python';
|
|
51
51
|
readonly backend: string;
|
|
52
52
|
readonly format: 'onnx' | 'coreml' | 'openvino' | 'tflite';
|
|
53
53
|
readonly score: number;
|
|
@@ -77,7 +77,7 @@ export interface ModelRequirement {
|
|
|
77
77
|
/** Resolved config — the tripletta chosen by the scorer for an addon */
|
|
78
78
|
export interface ResolvedInferenceConfig {
|
|
79
79
|
readonly modelId: string;
|
|
80
|
-
readonly runtime: '
|
|
80
|
+
readonly runtime: 'python';
|
|
81
81
|
readonly backend: string;
|
|
82
82
|
readonly format: import('../types/models.js').ModelFormat;
|
|
83
83
|
readonly reason: string;
|
|
@@ -18,11 +18,11 @@ export declare const DecoderSessionConfigSchema: z.ZodObject<{
|
|
|
18
18
|
codec: z.ZodString;
|
|
19
19
|
maxFps: z.ZodDefault<z.ZodNumber>;
|
|
20
20
|
outputFormat: z.ZodDefault<z.ZodEnum<{
|
|
21
|
-
rgb: "rgb";
|
|
22
|
-
gray: "gray";
|
|
23
21
|
jpeg: "jpeg";
|
|
22
|
+
rgb: "rgb";
|
|
24
23
|
bgr: "bgr";
|
|
25
24
|
yuv420: "yuv420";
|
|
25
|
+
gray: "gray";
|
|
26
26
|
}>>;
|
|
27
27
|
scale: z.ZodDefault<z.ZodNumber>;
|
|
28
28
|
width: z.ZodOptional<z.ZodNumber>;
|
package/dist/node.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { FfmpegProcess } from './ffmpeg/process.js';
|
|
|
13
13
|
export type { ChildCostClaimHandle, ChildCostClaimInput, ProcStatReader, } from './process/child-cost-registry.js';
|
|
14
14
|
export { ChildCostRegistry, NO_COST_CLAIM, nodeProcStatReader, parseProcCpuSeconds, parseProcRssBytes, readProcessCost, } from './process/child-cost-registry.js';
|
|
15
15
|
export { FilesystemStorageProvider } from './storage/filesystem-storage-provider.js';
|
|
16
|
+
export { writeFileAtomic } from './storage/atomic-write.js';
|
|
16
17
|
export type { PhysicalRoot, RealpathSync } from './storage/physical-root.js';
|
|
17
18
|
export { containsOrEquals, physicalRootOf } from './storage/physical-root.js';
|
|
18
19
|
export { canonicalHash } from './utils/canonical-hash.js';
|
package/dist/node.js
CHANGED
|
@@ -1744,6 +1744,69 @@ var ChildCostRegistry = class {
|
|
|
1744
1744
|
}
|
|
1745
1745
|
};
|
|
1746
1746
|
//#endregion
|
|
1747
|
+
//#region src/storage/atomic-write.ts
|
|
1748
|
+
/** Errors a Windows rename over a file another process has open can raise. */
|
|
1749
|
+
var WINDOWS_RENAME_BUSY = new Set([
|
|
1750
|
+
"EPERM",
|
|
1751
|
+
"EACCES",
|
|
1752
|
+
"EBUSY"
|
|
1753
|
+
]);
|
|
1754
|
+
var WINDOWS_RENAME_RETRIES = 3;
|
|
1755
|
+
var WINDOWS_RENAME_RETRY_MS = 25;
|
|
1756
|
+
function errnoCode(err) {
|
|
1757
|
+
return err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : void 0;
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Replace `filePath` with `data` so that a concurrent reader sees EITHER the
|
|
1761
|
+
* old complete file OR the new complete file — never a prefix (D654).
|
|
1762
|
+
*
|
|
1763
|
+
* A plain `writeFile` truncates the target and then writes it in chunks, so a
|
|
1764
|
+
* reader in between gets a torn file: a fixed-path single-instance media kind
|
|
1765
|
+
* (`lastFrame`, `lastSceneFrame`) is overwritten every roll while a
|
|
1766
|
+
* notification may be reading it. Here the bytes go to a temp file in the SAME
|
|
1767
|
+
* directory (same filesystem, so the rename cannot degrade to a copy), and a
|
|
1768
|
+
* `rename` swaps it in — atomic on POSIX. A reader that already opened the old
|
|
1769
|
+
* file keeps reading the old inode to the end.
|
|
1770
|
+
*
|
|
1771
|
+
* The temp name is DOT-prefixed so every directory scan that skips dot entries
|
|
1772
|
+
* (occupancy, D327) never counts it; it is removed on any failure. A process
|
|
1773
|
+
* killed between the write and the rename leaves one `.<name>.<uuid>.tmp`.
|
|
1774
|
+
*
|
|
1775
|
+
* Windows: a rename over a file another process holds open can fail with
|
|
1776
|
+
* EPERM/EACCES/EBUSY. It is retried briefly and then falls back to copying the
|
|
1777
|
+
* temp over the target — non-atomic, exactly the pre-D654 behaviour, rather
|
|
1778
|
+
* than losing the write.
|
|
1779
|
+
*
|
|
1780
|
+
* Cost: one extra `rename` (and, on failure, one `rm`) per write — a metadata
|
|
1781
|
+
* operation, independent of the payload size.
|
|
1782
|
+
*/
|
|
1783
|
+
async function writeFileAtomic(filePath, data) {
|
|
1784
|
+
const tmp = node_path.join(node_path.dirname(filePath), `.${node_path.basename(filePath)}.${(0, node_crypto.randomUUID)()}.tmp`);
|
|
1785
|
+
try {
|
|
1786
|
+
await node_fs.promises.writeFile(tmp, data);
|
|
1787
|
+
await renameOver(tmp, filePath);
|
|
1788
|
+
} catch (err) {
|
|
1789
|
+
await node_fs.promises.rm(tmp, { force: true }).catch(() => void 0);
|
|
1790
|
+
throw err;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
async function renameOver(tmp, filePath) {
|
|
1794
|
+
for (let attempt = 0;; attempt += 1) try {
|
|
1795
|
+
await node_fs.promises.rename(tmp, filePath);
|
|
1796
|
+
return;
|
|
1797
|
+
} catch (err) {
|
|
1798
|
+
const code = errnoCode(err);
|
|
1799
|
+
if (!(process.platform === "win32" && code !== void 0 && WINDOWS_RENAME_BUSY.has(code))) throw err;
|
|
1800
|
+
if (attempt < WINDOWS_RENAME_RETRIES) {
|
|
1801
|
+
await new Promise((resolve) => setTimeout(resolve, WINDOWS_RENAME_RETRY_MS));
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
await node_fs.promises.copyFile(tmp, filePath);
|
|
1805
|
+
await node_fs.promises.rm(tmp, { force: true });
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
//#endregion
|
|
1747
1810
|
//#region src/storage/filesystem-storage-provider.ts
|
|
1748
1811
|
var STORAGE_LOCATION_TYPES = [
|
|
1749
1812
|
"data",
|
|
@@ -1817,14 +1880,16 @@ var FilesystemStorageProvider = class {
|
|
|
1817
1880
|
relativePath
|
|
1818
1881
|
});
|
|
1819
1882
|
await node_fs.promises.mkdir(node_path.dirname(filePath), { recursive: true });
|
|
1820
|
-
if (Buffer.isBuffer(data)) await
|
|
1883
|
+
if (Buffer.isBuffer(data)) await writeFileAtomic(filePath, data);
|
|
1821
1884
|
else {
|
|
1822
|
-
const
|
|
1823
|
-
|
|
1824
|
-
data.
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1885
|
+
const tmp = node_path.join(node_path.dirname(filePath), `.${node_path.basename(filePath)}.${(0, node_crypto.randomUUID)()}.tmp`);
|
|
1886
|
+
try {
|
|
1887
|
+
await (0, node_stream_promises.pipeline)(data, node_fs.createWriteStream(tmp));
|
|
1888
|
+
await node_fs.promises.rename(tmp, filePath);
|
|
1889
|
+
} catch (err) {
|
|
1890
|
+
await node_fs.promises.rm(tmp, { force: true }).catch(() => void 0);
|
|
1891
|
+
throw err;
|
|
1892
|
+
}
|
|
1828
1893
|
}
|
|
1829
1894
|
}
|
|
1830
1895
|
async read({ location, relativePath }) {
|
|
@@ -2090,3 +2155,4 @@ exports.readProcessCost = readProcessCost;
|
|
|
2090
2155
|
exports.resolveExportFingerprint = resolveExportFingerprint;
|
|
2091
2156
|
exports.signExpiringUrl = signExpiringUrl;
|
|
2092
2157
|
exports.verifyExpiringUrl = verifyExpiringUrl;
|
|
2158
|
+
exports.writeFileAtomic = writeFileAtomic;
|
package/dist/node.mjs
CHANGED
|
@@ -1721,6 +1721,69 @@ var ChildCostRegistry = class {
|
|
|
1721
1721
|
}
|
|
1722
1722
|
};
|
|
1723
1723
|
//#endregion
|
|
1724
|
+
//#region src/storage/atomic-write.ts
|
|
1725
|
+
/** Errors a Windows rename over a file another process has open can raise. */
|
|
1726
|
+
var WINDOWS_RENAME_BUSY = new Set([
|
|
1727
|
+
"EPERM",
|
|
1728
|
+
"EACCES",
|
|
1729
|
+
"EBUSY"
|
|
1730
|
+
]);
|
|
1731
|
+
var WINDOWS_RENAME_RETRIES = 3;
|
|
1732
|
+
var WINDOWS_RENAME_RETRY_MS = 25;
|
|
1733
|
+
function errnoCode(err) {
|
|
1734
|
+
return err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : void 0;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Replace `filePath` with `data` so that a concurrent reader sees EITHER the
|
|
1738
|
+
* old complete file OR the new complete file — never a prefix (D654).
|
|
1739
|
+
*
|
|
1740
|
+
* A plain `writeFile` truncates the target and then writes it in chunks, so a
|
|
1741
|
+
* reader in between gets a torn file: a fixed-path single-instance media kind
|
|
1742
|
+
* (`lastFrame`, `lastSceneFrame`) is overwritten every roll while a
|
|
1743
|
+
* notification may be reading it. Here the bytes go to a temp file in the SAME
|
|
1744
|
+
* directory (same filesystem, so the rename cannot degrade to a copy), and a
|
|
1745
|
+
* `rename` swaps it in — atomic on POSIX. A reader that already opened the old
|
|
1746
|
+
* file keeps reading the old inode to the end.
|
|
1747
|
+
*
|
|
1748
|
+
* The temp name is DOT-prefixed so every directory scan that skips dot entries
|
|
1749
|
+
* (occupancy, D327) never counts it; it is removed on any failure. A process
|
|
1750
|
+
* killed between the write and the rename leaves one `.<name>.<uuid>.tmp`.
|
|
1751
|
+
*
|
|
1752
|
+
* Windows: a rename over a file another process holds open can fail with
|
|
1753
|
+
* EPERM/EACCES/EBUSY. It is retried briefly and then falls back to copying the
|
|
1754
|
+
* temp over the target — non-atomic, exactly the pre-D654 behaviour, rather
|
|
1755
|
+
* than losing the write.
|
|
1756
|
+
*
|
|
1757
|
+
* Cost: one extra `rename` (and, on failure, one `rm`) per write — a metadata
|
|
1758
|
+
* operation, independent of the payload size.
|
|
1759
|
+
*/
|
|
1760
|
+
async function writeFileAtomic(filePath, data) {
|
|
1761
|
+
const tmp = path$1.join(path$1.dirname(filePath), `.${path$1.basename(filePath)}.${randomUUID()}.tmp`);
|
|
1762
|
+
try {
|
|
1763
|
+
await fs.promises.writeFile(tmp, data);
|
|
1764
|
+
await renameOver(tmp, filePath);
|
|
1765
|
+
} catch (err) {
|
|
1766
|
+
await fs.promises.rm(tmp, { force: true }).catch(() => void 0);
|
|
1767
|
+
throw err;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
async function renameOver(tmp, filePath) {
|
|
1771
|
+
for (let attempt = 0;; attempt += 1) try {
|
|
1772
|
+
await fs.promises.rename(tmp, filePath);
|
|
1773
|
+
return;
|
|
1774
|
+
} catch (err) {
|
|
1775
|
+
const code = errnoCode(err);
|
|
1776
|
+
if (!(process.platform === "win32" && code !== void 0 && WINDOWS_RENAME_BUSY.has(code))) throw err;
|
|
1777
|
+
if (attempt < WINDOWS_RENAME_RETRIES) {
|
|
1778
|
+
await new Promise((resolve) => setTimeout(resolve, WINDOWS_RENAME_RETRY_MS));
|
|
1779
|
+
continue;
|
|
1780
|
+
}
|
|
1781
|
+
await fs.promises.copyFile(tmp, filePath);
|
|
1782
|
+
await fs.promises.rm(tmp, { force: true });
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
//#endregion
|
|
1724
1787
|
//#region src/storage/filesystem-storage-provider.ts
|
|
1725
1788
|
var STORAGE_LOCATION_TYPES = [
|
|
1726
1789
|
"data",
|
|
@@ -1794,14 +1857,16 @@ var FilesystemStorageProvider = class {
|
|
|
1794
1857
|
relativePath
|
|
1795
1858
|
});
|
|
1796
1859
|
await fs.promises.mkdir(path$1.dirname(filePath), { recursive: true });
|
|
1797
|
-
if (Buffer.isBuffer(data)) await
|
|
1860
|
+
if (Buffer.isBuffer(data)) await writeFileAtomic(filePath, data);
|
|
1798
1861
|
else {
|
|
1799
|
-
const
|
|
1800
|
-
|
|
1801
|
-
data.
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1862
|
+
const tmp = path$1.join(path$1.dirname(filePath), `.${path$1.basename(filePath)}.${randomUUID()}.tmp`);
|
|
1863
|
+
try {
|
|
1864
|
+
await pipeline(data, fs.createWriteStream(tmp));
|
|
1865
|
+
await fs.promises.rename(tmp, filePath);
|
|
1866
|
+
} catch (err) {
|
|
1867
|
+
await fs.promises.rm(tmp, { force: true }).catch(() => void 0);
|
|
1868
|
+
throw err;
|
|
1869
|
+
}
|
|
1805
1870
|
}
|
|
1806
1871
|
}
|
|
1807
1872
|
async read({ location, relativePath }) {
|
|
@@ -2033,4 +2098,4 @@ function resolveExportFingerprint(input) {
|
|
|
2033
2098
|
return input.persisted ?? input.fresh;
|
|
2034
2099
|
}
|
|
2035
2100
|
//#endregion
|
|
2036
|
-
export { ChildCostRegistry, FFMPEG_RELEASE, FfmpegProcess, FilesystemStorageProvider, Fmp4FragmentChild, Fmp4FragmentPlane, NO_COST_CLAIM, PYTHON_VERSION, buildBinaryPath, canonicalDeviceFingerprint, canonicalHash, containsOrEquals, diffExportTargets, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegArtifact, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements, nodeProcStatReader, parseFfmpegCapabilities, parseProcCpuSeconds, parseProcRssBytes, physicalRootOf, probeFfmpegBinary, readProcessCost, resolveExportFingerprint, signExpiringUrl, verifyExpiringUrl };
|
|
2101
|
+
export { ChildCostRegistry, FFMPEG_RELEASE, FfmpegProcess, FilesystemStorageProvider, Fmp4FragmentChild, Fmp4FragmentPlane, NO_COST_CLAIM, PYTHON_VERSION, buildBinaryPath, canonicalDeviceFingerprint, canonicalHash, containsOrEquals, diffExportTargets, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegArtifact, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements, nodeProcStatReader, parseFfmpegCapabilities, parseProcCpuSeconds, parseProcRssBytes, physicalRootOf, probeFfmpegBinary, readProcessCost, resolveExportFingerprint, signExpiringUrl, verifyExpiringUrl, writeFileAtomic };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The STATE SUMMARY delivery of a system-event rule (D647): which rules may use
|
|
3
|
+
* it, and how an editor presents its schedule. One module, read by the hub's
|
|
4
|
+
* save guard, the notification centre's scheduler and both rule editors.
|
|
5
|
+
*
|
|
6
|
+
* A `device-events` or `system` rule may say `systemEventDelivery: { mode:
|
|
7
|
+
* 'summary', schedule }`: it then sends nothing per event, and at the END of
|
|
8
|
+
* each window of `schedule` it sends the current state of its kinds. The alarm
|
|
9
|
+
* stays instant (an alarm summarised at 08:00 is an alarm nobody heard), and a
|
|
10
|
+
* `mixed` rule is read-only (D637), so neither may carry it.
|
|
11
|
+
*
|
|
12
|
+
* Pure: no clock, no I/O.
|
|
13
|
+
*/
|
|
14
|
+
import type { NcDelivery, NcRuleActions, NcSchedule, NcSystemEventCondition, NcSystemEventDelivery } from '../capabilities/notification-rules.cap.js';
|
|
15
|
+
import { type NcSystemEventFamily } from './system-event-families.js';
|
|
16
|
+
/** The families a rule may summarise. The alarm is not one of them. */
|
|
17
|
+
export declare const NC_SYSTEM_EVENT_SUMMARY_FAMILIES: readonly NcSystemEventFamily[];
|
|
18
|
+
/** The part of a rule the summary delivery is judged on. */
|
|
19
|
+
export interface NcSystemEventDeliverySubject {
|
|
20
|
+
readonly delivery: NcDelivery;
|
|
21
|
+
readonly conditions: {
|
|
22
|
+
readonly systemEvent?: NcSystemEventCondition;
|
|
23
|
+
};
|
|
24
|
+
readonly systemEventDelivery?: NcSystemEventDelivery;
|
|
25
|
+
readonly schedule?: NcSchedule;
|
|
26
|
+
readonly actions?: NcRuleActions;
|
|
27
|
+
}
|
|
28
|
+
export type NcSystemEventDeliveryProblem = {
|
|
29
|
+
readonly code: 'not-a-system-rule';
|
|
30
|
+
readonly delivery: NcDelivery;
|
|
31
|
+
} | {
|
|
32
|
+
readonly code: 'family-not-summarisable';
|
|
33
|
+
readonly family: NcSystemEventFamily | 'mixed';
|
|
34
|
+
} | {
|
|
35
|
+
readonly code: 'empty-schedule';
|
|
36
|
+
} | {
|
|
37
|
+
readonly code: 'degenerate-window';
|
|
38
|
+
readonly index: number;
|
|
39
|
+
} | {
|
|
40
|
+
readonly code: 'schedule-beside-summary';
|
|
41
|
+
} | {
|
|
42
|
+
readonly code: 'actions-beside-summary';
|
|
43
|
+
};
|
|
44
|
+
/** The summary's schedule, or `null` for any rule that is not a summary system rule. */
|
|
45
|
+
export declare function systemEventSummaryScheduleOf(rule: Pick<NcSystemEventDeliverySubject, 'delivery' | 'systemEventDelivery'>): NcSchedule | null;
|
|
46
|
+
/** Absent and `instant` are the same answer: a notification per event. */
|
|
47
|
+
export declare function isSystemEventSummaryRule(rule: Pick<NcSystemEventDeliverySubject, 'delivery' | 'systemEventDelivery'>): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Why the hub refuses this rule's delivery. Empty = accepted. An instant rule
|
|
50
|
+
* (absent or `{ mode: 'instant' }`) is never refused here.
|
|
51
|
+
*
|
|
52
|
+
* Beside a summary, two fields are refused because they would do nothing:
|
|
53
|
+
* the rule's own `schedule` (the summary's schedule is the send time; a second
|
|
54
|
+
* active-hours gate would silently empty it) and `actions` (a summary is not a
|
|
55
|
+
* match, so nothing would ever run them).
|
|
56
|
+
*/
|
|
57
|
+
export declare function systemEventDeliveryProblems(rule: NcSystemEventDeliverySubject): readonly NcSystemEventDeliveryProblem[];
|
|
58
|
+
export declare function describeSystemEventDeliveryProblem(problem: NcSystemEventDeliveryProblem): string;
|
|
59
|
+
/** One send time as an editor presents it: the days it is sent on, and the minute. */
|
|
60
|
+
export interface NcSummarySendTime {
|
|
61
|
+
readonly days: readonly number[];
|
|
62
|
+
/** Minute of the day, 0–1439. */
|
|
63
|
+
readonly minute: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A send time is a ONE-MINUTE window that closes at that minute: the summary is
|
|
67
|
+
* sent when a window ends. Midnight starts the window at 23:59 of the day
|
|
68
|
+
* BEFORE, because a window's `days` are the days it STARTS on.
|
|
69
|
+
*/
|
|
70
|
+
export declare function scheduleForSendTimes(times: readonly NcSummarySendTime[], timezone?: string): NcSchedule;
|
|
71
|
+
/**
|
|
72
|
+
* The send times a schedule stands for, or `null` when it is not a list of
|
|
73
|
+
* one-minute windows (an inverted or a longer window): the editor then shows
|
|
74
|
+
* the raw windows and says the summary is sent when each one ENDS.
|
|
75
|
+
*/
|
|
76
|
+
export declare function sendTimesOfSchedule(schedule: NcSchedule): readonly NcSummarySendTime[] | null;
|
|
77
|
+
/**
|
|
78
|
+
* Does a custom BODY carry the summary's content (`{{summaryText}}` or a
|
|
79
|
+
* section variable)? A body that does not would send a summary with nothing
|
|
80
|
+
* in it; the hub then appends `{{summaryText}}` and the admin warns (review
|
|
81
|
+
* M3). An absent or blank body uses the hub's own wording, which does.
|
|
82
|
+
*/
|
|
83
|
+
export declare function templateCarriesSummary(body: string | undefined): boolean;
|
|
@@ -36,6 +36,21 @@ export declare const NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS: readonly NcSystem
|
|
|
36
36
|
* rule an installed viewer can author, which is exactly true.
|
|
37
37
|
*/
|
|
38
38
|
export declare function servedTemplateVars(): NcTemplateVarDescriptor[];
|
|
39
|
+
/**
|
|
40
|
+
* Is this descriptor held back from the SERVED catalog as a whole (D647)?
|
|
41
|
+
* A summary-only variable is: an installed viewer does not read the delivery
|
|
42
|
+
* axis, and would offer it on every instant system rule. The admin, built
|
|
43
|
+
* from the workspace, restores it from its own `NC_TEMPLATE_VARS`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isHeldBackTemplateVar(d: NcTemplateVarDescriptor): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Does a descriptor have a value on a system rule of this DELIVERY (D647)?
|
|
48
|
+
* Read only for a system rule kind. An absent axis is `instant` for a
|
|
49
|
+
* descriptor that names `systemEventKinds` (a per-event fact: a summary row
|
|
50
|
+
* has no one device, node or package) and BOTH for one that does not
|
|
51
|
+
* (`{{rule}}`, `{{time}}`).
|
|
52
|
+
*/
|
|
53
|
+
export declare function templateVarDeliveryApplies(d: NcTemplateVarDescriptor, ctx: NcTemplateVarContext): boolean;
|
|
39
54
|
/**
|
|
40
55
|
* Does a descriptor declared for `declared` kinds have a value for a rule of
|
|
41
56
|
* `kind`?
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why the orchestrator cannot say which model the operator chose for a
|
|
3
|
+
* cluster-scoped step (D651).
|
|
4
|
+
*/
|
|
5
|
+
export declare const CLUSTER_MODEL_CHOICE_UNREADABLE_REASONS: readonly ["owner-not-ready", "store-read-failed", "three-way-read-unavailable", "invalid-stored", "not-cluster-scoped"];
|
|
1
6
|
/**
|
|
2
7
|
* How a step's model is chosen.
|
|
3
8
|
*
|
|
@@ -66,13 +71,58 @@ export declare function isClusterScopedStep(stepId: string): boolean;
|
|
|
66
71
|
export type ClusterStepModels = Readonly<Record<string, string>>;
|
|
67
72
|
/** The cluster row when nobody has configured one — today's catalog defaults. */
|
|
68
73
|
export declare const DEFAULT_CLUSTER_STEP_MODELS: ClusterStepModels;
|
|
74
|
+
/**
|
|
75
|
+
* What ONE stored `clusterModel:<stepId>` value means (D651 fix round 4).
|
|
76
|
+
*
|
|
77
|
+
* - `absent` — nobody chose. The key was never written, or the settings form
|
|
78
|
+
* cleared it: a select clears a field with a `null` patch, and `hydrateSchema`
|
|
79
|
+
* reads a stored `null` as `value ?? default` on every hydrated surface, so
|
|
80
|
+
* `null` and `''` are the same statement as "never written".
|
|
81
|
+
* - `chosen` — a non-empty string, taken VERBATIM. Not checked against the
|
|
82
|
+
* step's `options`: the runtime pins an id the catalog does not list on
|
|
83
|
+
* purpose (`cluster-model-resolution.ts`: a custom encoder from
|
|
84
|
+
* `custom-model-registry` is the operator's own compatibility problem), and
|
|
85
|
+
* the dropdown is guarded separately by `scripts/check-cluster-model-scope.ts`.
|
|
86
|
+
* An owner that called such an id invalid while the runner ran it would give
|
|
87
|
+
* the same stored key two meanings — the defect this function removes.
|
|
88
|
+
* - `invalid` — anything else (a number, an object). The form cannot write it;
|
|
89
|
+
* it is a hand edit or a corrupted row.
|
|
90
|
+
*
|
|
91
|
+
* Both readers of the key go through this function: the runner's
|
|
92
|
+
* {@link readClusterStepModels} and the owner's `decideClusterModelChoice`
|
|
93
|
+
* (addon-pipeline-orchestrator). They differ only in what they DO with
|
|
94
|
+
* `invalid`: a live row is total, so the runner falls to the catalog default
|
|
95
|
+
* and says so in its log (`ClusterModelSource`); a pass that would rewrite an
|
|
96
|
+
* index on it refuses (`invalid-stored`).
|
|
97
|
+
*/
|
|
98
|
+
export type StoredClusterModelInterpretation = {
|
|
99
|
+
readonly kind: 'absent';
|
|
100
|
+
} | {
|
|
101
|
+
readonly kind: 'chosen';
|
|
102
|
+
readonly modelId: string;
|
|
103
|
+
} | {
|
|
104
|
+
readonly kind: 'invalid';
|
|
105
|
+
readonly value: unknown;
|
|
106
|
+
};
|
|
107
|
+
export declare function interpretStoredClusterModel(raw: unknown): StoredClusterModelInterpretation;
|
|
108
|
+
/** One stored cluster model value the interpretation rejected. */
|
|
109
|
+
export interface InvalidClusterStepModelEntry {
|
|
110
|
+
readonly stepId: string;
|
|
111
|
+
readonly value: unknown;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The cluster-scoped steps whose stored value is `invalid` under
|
|
115
|
+
* {@link interpretStoredClusterModel} — for the runtime's log line, so the
|
|
116
|
+
* default it runs on such a row is never run in silence.
|
|
117
|
+
*/
|
|
118
|
+
export declare function invalidClusterStepModelEntries(config: Readonly<Record<string, unknown>>): readonly InvalidClusterStepModelEntry[];
|
|
69
119
|
/**
|
|
70
120
|
* Narrow a FLAT settings record to the cluster row.
|
|
71
121
|
*
|
|
72
122
|
* Per-FIELD fallback, deliberately (same rule as `readDetailCropConvention`): a
|
|
73
|
-
* junk face model must not also discard a valid clip model. An absent
|
|
74
|
-
*
|
|
75
|
-
*
|
|
123
|
+
* junk face model must not also discard a valid clip model. An `absent` or
|
|
124
|
+
* `invalid` value ({@link interpretStoredClusterModel}) resolves to the step's
|
|
125
|
+
* catalog default — never to a blank id, because a blank id downstream becomes
|
|
76
126
|
* "substitute the format default", which is precisely the substitution this
|
|
77
127
|
* scope exists to forbid.
|
|
78
128
|
*/
|
|
@@ -98,6 +148,12 @@ export interface HydratedClusterView {
|
|
|
98
148
|
* (addon mid-boot) is the defaults.
|
|
99
149
|
*/
|
|
100
150
|
export declare function pickClusterStepModels(view: HydratedClusterView | null): ClusterStepModels;
|
|
151
|
+
/**
|
|
152
|
+
* The `invalid` stored cluster model values a hydrated view carries — what the
|
|
153
|
+
* runtime logs when it runs a default on a row it could not read as a choice.
|
|
154
|
+
* A `null` view (owner mid-boot) carries nothing to judge.
|
|
155
|
+
*/
|
|
156
|
+
export declare function pickInvalidClusterStepModels(view: HydratedClusterView | null): readonly InvalidClusterStepModelEntry[];
|
|
101
157
|
/**
|
|
102
158
|
* The cluster model for `stepId`, or `null` when the step is node-scoped.
|
|
103
159
|
*
|