@fnndsc/cumin 3.8.1 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +2 -2
  2. package/dist/cache/index.d.ts +5 -1
  3. package/dist/cache/index.js +9 -1
  4. package/dist/cache/index.js.map +1 -1
  5. package/dist/cache/listCache.d.ts +11 -0
  6. package/dist/cache/listCache.js +19 -0
  7. package/dist/cache/listCache.js.map +1 -1
  8. package/dist/cache/procCache.d.ts +118 -5
  9. package/dist/cache/procCache.js +213 -6
  10. package/dist/cache/procCache.js.map +1 -1
  11. package/dist/cache/procCheckpoint.d.ts +47 -0
  12. package/dist/cache/procCheckpoint.js +217 -0
  13. package/dist/cache/procCheckpoint.js.map +1 -0
  14. package/dist/cache/procPrompt.d.ts +34 -0
  15. package/dist/cache/procPrompt.js +25 -0
  16. package/dist/cache/procPrompt.js.map +1 -0
  17. package/dist/chrisapi/adapter.d.ts +12 -1
  18. package/dist/chrisapi/adapter.js +25 -0
  19. package/dist/chrisapi/adapter.js.map +1 -1
  20. package/dist/connect/chrisConnection.d.ts +20 -0
  21. package/dist/connect/chrisConnection.js +33 -0
  22. package/dist/connect/chrisConnection.js.map +1 -1
  23. package/dist/feeds/chrisFeed.js +2 -1
  24. package/dist/feeds/chrisFeed.js.map +1 -1
  25. package/dist/groups/chrisGroups.d.ts +67 -1
  26. package/dist/groups/chrisGroups.js +171 -1
  27. package/dist/groups/chrisGroups.js.map +1 -1
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +2 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/io/chrisIO.d.ts +14 -0
  32. package/dist/io/chrisIO.js +32 -2
  33. package/dist/io/chrisIO.js.map +1 -1
  34. package/dist/jobs/chrisJob.js +4 -0
  35. package/dist/jobs/chrisJob.js.map +1 -1
  36. package/dist/output/runtimeOutput.d.ts +49 -0
  37. package/dist/output/runtimeOutput.js +51 -0
  38. package/dist/output/runtimeOutput.js.map +1 -0
  39. package/dist/pacs/chrisPACS.d.ts +28 -0
  40. package/dist/pacs/chrisPACS.js +40 -21
  41. package/dist/pacs/chrisPACS.js.map +1 -1
  42. package/dist/path/chrisPath.js +3 -3
  43. package/dist/path/chrisPath.js.map +1 -1
  44. package/dist/pipelines/chrisPipeline.d.ts +4 -0
  45. package/dist/pipelines/chrisPipeline.js.map +1 -1
  46. package/dist/plugins/chrisPlugins.d.ts +8 -1
  47. package/dist/plugins/chrisPlugins.js +8 -1
  48. package/dist/plugins/chrisPlugins.js.map +1 -1
  49. package/dist/resources/chrisObjContext.d.ts +11 -0
  50. package/dist/resources/chrisObjContext.js +38 -2
  51. package/dist/resources/chrisObjContext.js.map +1 -1
  52. package/dist/resources/chrisResources.js +4 -0
  53. package/dist/resources/chrisResources.js.map +1 -1
  54. package/dist/users/chrisUsers.d.ts +56 -10
  55. package/dist/users/chrisUsers.js +149 -20
  56. package/dist/users/chrisUsers.js.map +1 -1
  57. package/dist/utils/retry.d.ts +29 -0
  58. package/dist/utils/retry.js +44 -0
  59. package/dist/utils/retry.js.map +1 -0
  60. package/package.json +19 -8
package/README.md CHANGED
@@ -54,7 +54,7 @@ In the "Sandwich Model" architecture, `cumin` is the bottom layer (just above th
54
54
  - **`ChrisIO`**: High-level IO operations, including recursive directory uploading (`uploadLocalPath`).
55
55
  - **`ChRISResource.resources_getAll()`**: Generic pagination handler.
56
56
  - **`ListCache`** (`listCache_get()`): Session-scoped directory listing cache for the VFS.
57
- - **`ProcCache`** (`procCache_get()`): Session-scoped job monitoring cache. Stores all visible plugin instances as a DAG (two flat maps: `instances` keyed by ID, `children`/`feedRoots` for tree traversal). Structure is permanent once built; only non-terminal instance statuses are refreshed on read. Powers the `/proc/jobs/` VFS provider in salsa.
57
+ - **`ProcCache`** (`procCache_get()`): Session-scoped job monitoring DAG with versioned, identity-scoped daemon checkpoints. Restored topology remains usable while CUBE reconciliation runs; terminal status persists and active status refreshes on read. Powers the `/proc/jobs/` VFS provider in salsa.
58
58
 
59
59
  ## Developer Setup
60
60
 
@@ -75,4 +75,4 @@ To build the `cumin` library from source:
75
75
  MIT — part of the [ChRIS Project](https://chrisproject.org).
76
76
 
77
77
  ---
78
- _-30-_
78
+ _-30-_
@@ -6,4 +6,8 @@
6
6
  export { ListCache, listCache_get } from './listCache.js';
7
7
  export type { CacheStats, CacheResult, CacheOptions } from './listCache.js';
8
8
  export { ProcCache, procCache_get, status_isTerminal, PROC_TERMINAL_STATUSES } from './procCache.js';
9
- export type { ProcInstance, ProcFeed, ProcFeedScopeCounts, ProcWarmupProgress } from './procCache.js';
9
+ export type { ProcInstance, ProcFeed, ProcFeedScopeCounts, ProcWarmupProgress, ProcCacheState, ProcCacheLifecycle, ProcCacheSnapshot, } from './procCache.js';
10
+ export { procPromptState_get, PROC_PROMPT_STATES } from './procPrompt.js';
11
+ export type { ProcPromptState, ProcPromptProgress } from './procPrompt.js';
12
+ export { procCheckpointPath_get, procCheckpoint_restore, procCheckpoint_save, procCheckpoint_watch, } from './procCheckpoint.js';
13
+ export type { ProcCheckpointRestoreResult } from './procCheckpoint.js';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PROC_TERMINAL_STATUSES = exports.status_isTerminal = exports.procCache_get = exports.ProcCache = exports.listCache_get = exports.ListCache = void 0;
3
+ exports.procCheckpoint_watch = exports.procCheckpoint_save = exports.procCheckpoint_restore = exports.procCheckpointPath_get = exports.PROC_PROMPT_STATES = exports.procPromptState_get = exports.PROC_TERMINAL_STATUSES = exports.status_isTerminal = exports.procCache_get = exports.ProcCache = exports.listCache_get = exports.ListCache = void 0;
4
4
  /**
5
5
  * @file Cache module exports.
6
6
  *
@@ -14,4 +14,12 @@ Object.defineProperty(exports, "ProcCache", { enumerable: true, get: function ()
14
14
  Object.defineProperty(exports, "procCache_get", { enumerable: true, get: function () { return procCache_js_1.procCache_get; } });
15
15
  Object.defineProperty(exports, "status_isTerminal", { enumerable: true, get: function () { return procCache_js_1.status_isTerminal; } });
16
16
  Object.defineProperty(exports, "PROC_TERMINAL_STATUSES", { enumerable: true, get: function () { return procCache_js_1.PROC_TERMINAL_STATUSES; } });
17
+ var procPrompt_js_1 = require("./procPrompt.js");
18
+ Object.defineProperty(exports, "procPromptState_get", { enumerable: true, get: function () { return procPrompt_js_1.procPromptState_get; } });
19
+ Object.defineProperty(exports, "PROC_PROMPT_STATES", { enumerable: true, get: function () { return procPrompt_js_1.PROC_PROMPT_STATES; } });
20
+ var procCheckpoint_js_1 = require("./procCheckpoint.js");
21
+ Object.defineProperty(exports, "procCheckpointPath_get", { enumerable: true, get: function () { return procCheckpoint_js_1.procCheckpointPath_get; } });
22
+ Object.defineProperty(exports, "procCheckpoint_restore", { enumerable: true, get: function () { return procCheckpoint_js_1.procCheckpoint_restore; } });
23
+ Object.defineProperty(exports, "procCheckpoint_save", { enumerable: true, get: function () { return procCheckpoint_js_1.procCheckpoint_save; } });
24
+ Object.defineProperty(exports, "procCheckpoint_watch", { enumerable: true, get: function () { return procCheckpoint_js_1.procCheckpoint_watch; } });
17
25
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,+CAA0D;AAAjD,yGAAA,SAAS,OAAA;AAAE,6GAAA,aAAa,OAAA;AAEjC,+CAAqG;AAA5F,yGAAA,SAAS,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,iHAAA,iBAAiB,OAAA;AAAE,sHAAA,sBAAsB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,+CAA0D;AAAjD,yGAAA,SAAS,OAAA;AAAE,6GAAA,aAAa,OAAA;AAEjC,+CAAqG;AAA5F,yGAAA,SAAS,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,iHAAA,iBAAiB,OAAA;AAAE,sHAAA,sBAAsB,OAAA;AAU5E,iDAA0E;AAAjE,oHAAA,mBAAmB,OAAA;AAAE,mHAAA,kBAAkB,OAAA;AAEhD,yDAK6B;AAJ3B,2HAAA,sBAAsB,OAAA;AACtB,2HAAA,sBAAsB,OAAA;AACtB,wHAAA,mBAAmB,OAAA;AACnB,yHAAA,oBAAoB,OAAA"}
@@ -144,6 +144,17 @@ export declare class ListCache {
144
144
  * @param path - Optional specific path to invalidate. If omitted, clears entire cache.
145
145
  */
146
146
  cache_invalidate(path?: string): void;
147
+ /**
148
+ * Invalidates a path and every cached listing beneath it.
149
+ * Called by operations that remove or replace an entire directory tree
150
+ * (rm -r, a directory re-upload): nested listings cached before the
151
+ * mutation would otherwise serve the old tree's contents until their TTL
152
+ * expires, with no refresh hint.
153
+ *
154
+ * @param path - Root of the subtree to invalidate. The entry for the path
155
+ * itself is removed along with all entries under `path + '/'`.
156
+ */
157
+ cache_invalidateTree(path: string): void;
147
158
  /**
148
159
  * @deprecated Use cache without cwd tracking. Cache persists across navigation.
149
160
  * This method is kept for backward compatibility but does nothing.
@@ -178,6 +178,25 @@ class ListCache {
178
178
  this.cache.clear();
179
179
  }
180
180
  }
181
+ /**
182
+ * Invalidates a path and every cached listing beneath it.
183
+ * Called by operations that remove or replace an entire directory tree
184
+ * (rm -r, a directory re-upload): nested listings cached before the
185
+ * mutation would otherwise serve the old tree's contents until their TTL
186
+ * expires, with no refresh hint.
187
+ *
188
+ * @param path - Root of the subtree to invalidate. The entry for the path
189
+ * itself is removed along with all entries under `path + '/'`.
190
+ */
191
+ cache_invalidateTree(path) {
192
+ this.cache.delete(path);
193
+ const prefix = path.endsWith("/") ? path : `${path}/`;
194
+ for (const key of this.cache.keys()) {
195
+ if (key.startsWith(prefix)) {
196
+ this.cache.delete(key);
197
+ }
198
+ }
199
+ }
181
200
  /**
182
201
  * @deprecated Use cache without cwd tracking. Cache persists across navigation.
183
202
  * This method is kept for backward compatibility but does nothing.
@@ -1 +1 @@
1
- {"version":3,"file":"listCache.js","sourceRoot":"","sources":["../../src/cache/listCache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"listCache.js","sourceRoot":"","sources":["../../src/cache/listCache.ts"],"names":[],"mappings":"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"}
@@ -2,11 +2,11 @@
2
2
  * @file Process Cache
3
3
  *
4
4
  * Session-scoped cache of ChRIS feed topology. Separates permanent data
5
- * (instance DAG structure) from volatile data (status — never stored).
5
+ * (instance DAG structure and terminal status) from volatile active status.
6
6
  *
7
7
  * Design decisions:
8
8
  * - Topology (id, feedID, parentID, pluginName) is permanent once written.
9
- * - Status is always fetched live from the API — never stored here.
9
+ * - Terminal status is immutable and checkpointed; active status refreshes live.
10
10
  * - Feed job counters (finishedJobs etc.) give aggregate feed status
11
11
  * without per-instance API calls.
12
12
  * - topologyLoaded distinguishes "loaded but 0 instances" from "not yet loaded".
@@ -67,6 +67,12 @@ export interface ProcInstance {
67
67
  pluginType?: string;
68
68
  /** null until first cat — immutable once populated. */
69
69
  params: Record<string, unknown> | null;
70
+ /**
71
+ * Authoritative CUBE output directory, resolved lazily for the `/proc` data
72
+ * link. `undefined` means not looked up; `null` means CUBE reported none.
73
+ * This navigation convenience is deliberately excluded from checkpoints.
74
+ */
75
+ outputPath?: string | null;
70
76
  /**
71
77
  * Last known job status. Terminal statuses (see {@link PROC_TERMINAL_STATUSES})
72
78
  * are immutable and kept permanently; active statuses are refreshed live.
@@ -98,16 +104,39 @@ export declare function status_isTerminal(status: string | null | undefined): bo
98
104
  * total is zero until the server reports the visible plugin-instance count.
99
105
  * active becomes true when observable page progress begins, and false when the
100
106
  * sweep completes or aborts. Use the topology lifecycle to detect an earlier
101
- * running phase before the first page arrives.
107
+ * running state before the first page arrives.
102
108
  */
103
109
  export interface ProcWarmupProgress {
104
110
  loaded: number;
105
111
  total: number;
106
112
  active: boolean;
107
113
  }
114
+ /** Availability and freshness states for the persistent process cache. */
115
+ export type ProcCacheState = 'empty' | 'restored' | 'reconciling' | 'current' | 'failed';
108
116
  /**
109
- * Session-scoped cache of feed and instance topology.
110
- * Status is never stored — always fetched live from the API.
117
+ * Persistent process-cache freshness metadata.
118
+ *
119
+ * @property state - Current availability and reconciliation state.
120
+ * @property checkpointAt - ISO timestamp of the latest durable checkpoint.
121
+ */
122
+ export interface ProcCacheLifecycle {
123
+ state: ProcCacheState;
124
+ checkpointAt?: string;
125
+ }
126
+ /**
127
+ * Serializable topology retained across daemon restarts.
128
+ *
129
+ * @property feeds - Visible feed metadata.
130
+ * @property instances - Normalized plugin-instance topology.
131
+ * @property topologyLoaded - Feed IDs whose instance topology is complete.
132
+ */
133
+ export interface ProcCacheSnapshot {
134
+ feeds: ProcFeed[];
135
+ instances: ProcInstance[];
136
+ topologyLoaded: number[];
137
+ }
138
+ /**
139
+ * Session-scoped cache of feed and instance topology with restorable terminal state.
111
140
  */
112
141
  export declare class ProcCache {
113
142
  private static _instance;
@@ -124,10 +153,33 @@ export declare class ProcCache {
124
153
  private _warmupProgress;
125
154
  /** Whether initial feed index has been built. */
126
155
  private _built;
156
+ private lifecycle;
157
+ private listeners;
127
158
  private constructor();
128
159
  static instance_get(): ProcCache;
129
160
  get built(): boolean;
130
161
  get warmupComplete(): boolean;
162
+ /**
163
+ * Returns a copy of the current cache lifecycle metadata.
164
+ *
165
+ * @returns Current cache state and checkpoint timestamp.
166
+ */
167
+ lifecycle_get(): ProcCacheLifecycle;
168
+ /**
169
+ * Changes the cache freshness state.
170
+ *
171
+ * @param state - New lifecycle state.
172
+ * @returns Nothing.
173
+ */
174
+ lifecycle_set(state: ProcCacheState): void;
175
+ /**
176
+ * Registers a callback for checkpoint-worthy cache mutations.
177
+ *
178
+ * @param listener - Callback invoked after a cache mutation.
179
+ * @returns Function that unregisters the callback.
180
+ */
181
+ changeListener_add(listener: () => void): () => void;
182
+ private change_emit;
131
183
  /**
132
184
  * Adds or updates a feed entry.
133
185
  */
@@ -149,6 +201,14 @@ export declare class ProcCache {
149
201
  * Removes a feed and all its instances from the cache.
150
202
  */
151
203
  feed_remove(feedID: number): void;
204
+ /**
205
+ * Replaces the visible feed set while preserving topology for retained feeds.
206
+ *
207
+ * @param feeds - Authoritative feeds visible to the current identity.
208
+ * @returns IDs requiring topology reconciliation because they are new, their
209
+ * aggregate job counts changed, or they still have active work.
210
+ */
211
+ feeds_reconcile(feeds: ProcFeed[]): number[];
152
212
  /**
153
213
  * Adds a plugin instance to the topology cache.
154
214
  */
@@ -169,10 +229,36 @@ export declare class ProcCache {
169
229
  * Removes an instance from the topology cache.
170
230
  */
171
231
  instance_remove(id: number): void;
232
+ /**
233
+ * Removes instances absent from an authoritative topology sweep.
234
+ *
235
+ * @param instanceIDs - Instance IDs returned by the completed CUBE sweep.
236
+ * @returns Nothing.
237
+ */
238
+ topology_reconcile(instanceIDs: Set<number>): void;
172
239
  /**
173
240
  * Updates cached params for an instance (on first cat).
174
241
  */
175
242
  params_update(id: number, params: Record<string, unknown>): void;
243
+ /**
244
+ * Stores the lazily resolved CUBE output path for one instance.
245
+ *
246
+ * @param id - Plugin-instance ID whose output location was resolved.
247
+ * @param outputPath - Absolute CFS output directory, or null when absent.
248
+ * @returns Nothing.
249
+ */
250
+ outputPath_update(id: number, outputPath: string | null): void;
251
+ /**
252
+ * Finds the closest cached job output containing a CFS path.
253
+ *
254
+ * An output path is a directory boundary: `/home/alice/run` contains
255
+ * `/home/alice/run/result.txt`, but not `/home/alice/run-old`.
256
+ *
257
+ * @param cfsPath - Absolute CFS path to resolve against live-session outputs.
258
+ * @returns The closest producing instance, or undefined when no output path
259
+ * known in this session contains the path.
260
+ */
261
+ outputPath_match(cfsPath: string): ProcInstance | undefined;
176
262
  /**
177
263
  * Updates the cached status for an instance. Terminal (settled) statuses are
178
264
  * never overwritten — once finished, a job's status is immutable.
@@ -211,6 +297,12 @@ export declare class ProcCache {
211
297
  warmup_abort(): void;
212
298
  warmup_progress(loaded: number, total: number): void;
213
299
  warmupProgress_get(): ProcWarmupProgress;
300
+ /**
301
+ * Resets reconciliation progress while retaining restored topology.
302
+ *
303
+ * @returns Nothing.
304
+ */
305
+ warmup_reset(): void;
214
306
  /**
215
307
  * Reconstructs the full /proc/jobs path for a given instance ID.
216
308
  *
@@ -233,6 +325,27 @@ export declare class ProcCache {
233
325
  */
234
326
  instances_find(term: string): ProcInstance[];
235
327
  built_set(): void;
328
+ /**
329
+ * Creates a persistence-safe snapshot without parameters or active statuses.
330
+ *
331
+ * @returns Serializable feed and terminal-topology state.
332
+ */
333
+ snapshot_create(): ProcCacheSnapshot;
334
+ /**
335
+ * Replaces in-memory topology with a validated persistent snapshot.
336
+ *
337
+ * @param snapshot - Previously validated snapshot data.
338
+ * @param checkpointAt - ISO timestamp when the checkpoint was written.
339
+ * @returns Nothing.
340
+ */
341
+ snapshot_restore(snapshot: ProcCacheSnapshot, checkpointAt: string): void;
342
+ /**
343
+ * Records the timestamp of a successful durable save.
344
+ *
345
+ * @param checkpointAt - ISO timestamp written into the checkpoint.
346
+ * @returns Nothing.
347
+ */
348
+ checkpoint_mark(checkpointAt: string): void;
236
349
  /**
237
350
  * Clears all cache data. Called before a full rebuild.
238
351
  */
@@ -3,11 +3,11 @@
3
3
  * @file Process Cache
4
4
  *
5
5
  * Session-scoped cache of ChRIS feed topology. Separates permanent data
6
- * (instance DAG structure) from volatile data (status — never stored).
6
+ * (instance DAG structure and terminal status) from volatile active status.
7
7
  *
8
8
  * Design decisions:
9
9
  * - Topology (id, feedID, parentID, pluginName) is permanent once written.
10
- * - Status is always fetched live from the API — never stored here.
10
+ * - Terminal status is immutable and checkpointed; active status refreshes live.
11
11
  * - Feed job counters (finishedJobs etc.) give aggregate feed status
12
12
  * without per-instance API calls.
13
13
  * - topologyLoaded distinguishes "loaded but 0 instances" from "not yet loaded".
@@ -21,6 +21,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.ProcCache = exports.PROC_TERMINAL_STATUSES = void 0;
22
22
  exports.status_isTerminal = status_isTerminal;
23
23
  exports.procCache_get = procCache_get;
24
+ /** Feed counters whose changes can alter a feed's plugin-instance topology. */
25
+ const FEED_TOPOLOGY_COUNTERS = [
26
+ 'finishedJobs',
27
+ 'erroredJobs',
28
+ 'startedJobs',
29
+ 'scheduledJobs',
30
+ 'cancelledJobs',
31
+ 'createdJobs',
32
+ ];
33
+ /** Reports whether a feed has work which can still change after checkpointing. */
34
+ function feed_isActive(feed) {
35
+ return feed.startedJobs > 0 || feed.scheduledJobs > 0 || feed.createdJobs > 0;
36
+ }
37
+ /** Reports whether two feed summaries indicate different instance topology. */
38
+ function feedTopology_changed(previous, current) {
39
+ return FEED_TOPOLOGY_COUNTERS.some((counter) => previous[counter] !== current[counter]);
40
+ }
24
41
  /**
25
42
  * Job statuses that never change once reached. An instance in one of these states
26
43
  * is settled — its status can be cached permanently rather than re-fetched.
@@ -40,8 +57,7 @@ function status_isTerminal(status) {
40
57
  return status != null && exports.PROC_TERMINAL_STATUSES.has(status);
41
58
  }
42
59
  /**
43
- * Session-scoped cache of feed and instance topology.
44
- * Status is never stored — always fetched live from the API.
60
+ * Session-scoped cache of feed and instance topology with restorable terminal state.
45
61
  */
46
62
  class ProcCache {
47
63
  constructor() {
@@ -58,6 +74,8 @@ class ProcCache {
58
74
  this._warmupProgress = { loaded: 0, total: 0, active: false };
59
75
  /** Whether initial feed index has been built. */
60
76
  this._built = false;
77
+ this.lifecycle = { state: 'empty' };
78
+ this.listeners = new Set();
61
79
  }
62
80
  static instance_get() {
63
81
  if (!ProcCache._instance) {
@@ -67,6 +85,36 @@ class ProcCache {
67
85
  }
68
86
  get built() { return this._built; }
69
87
  get warmupComplete() { return this._warmupComplete; }
88
+ /**
89
+ * Returns a copy of the current cache lifecycle metadata.
90
+ *
91
+ * @returns Current cache state and checkpoint timestamp.
92
+ */
93
+ lifecycle_get() { return { ...this.lifecycle }; }
94
+ /**
95
+ * Changes the cache freshness state.
96
+ *
97
+ * @param state - New lifecycle state.
98
+ * @returns Nothing.
99
+ */
100
+ lifecycle_set(state) {
101
+ this.lifecycle = { ...this.lifecycle, state };
102
+ this.change_emit();
103
+ }
104
+ /**
105
+ * Registers a callback for checkpoint-worthy cache mutations.
106
+ *
107
+ * @param listener - Callback invoked after a cache mutation.
108
+ * @returns Function that unregisters the callback.
109
+ */
110
+ changeListener_add(listener) {
111
+ this.listeners.add(listener);
112
+ return () => { this.listeners.delete(listener); };
113
+ }
114
+ change_emit() {
115
+ for (const listener of this.listeners)
116
+ listener();
117
+ }
70
118
  // ── Feed ──────────────────────────────────────────────────────────────────
71
119
  /**
72
120
  * Adds or updates a feed entry.
@@ -76,6 +124,7 @@ class ProcCache {
76
124
  if (!this.feedRoots.has(feed.id)) {
77
125
  this.feedRoots.set(feed.id, []);
78
126
  }
127
+ this.change_emit();
79
128
  }
80
129
  feed_get(feedID) {
81
130
  return this.feeds.get(feedID);
@@ -119,6 +168,30 @@ class ProcCache {
119
168
  this.instance_remove(inst.id);
120
169
  }
121
170
  this.feedRoots.delete(feedID);
171
+ this.change_emit();
172
+ }
173
+ /**
174
+ * Replaces the visible feed set while preserving topology for retained feeds.
175
+ *
176
+ * @param feeds - Authoritative feeds visible to the current identity.
177
+ * @returns IDs requiring topology reconciliation because they are new, their
178
+ * aggregate job counts changed, or they still have active work.
179
+ */
180
+ feeds_reconcile(feeds) {
181
+ const reconciliationTargets = [];
182
+ const visible = new Set(feeds.map((feed) => feed.id));
183
+ for (const feedID of this.feedIDs_get()) {
184
+ if (!visible.has(feedID))
185
+ this.feed_remove(feedID);
186
+ }
187
+ for (const feed of feeds) {
188
+ const previous = this.feed_get(feed.id);
189
+ if (!previous || feedTopology_changed(previous, feed) || feed_isActive(feed)) {
190
+ reconciliationTargets.push(feed.id);
191
+ }
192
+ this.feed_add(feed);
193
+ }
194
+ return reconciliationTargets;
122
195
  }
123
196
  // ── Instance ──────────────────────────────────────────────────────────────
124
197
  /**
@@ -140,6 +213,7 @@ class ProcCache {
140
213
  this.children.set(inst.parentID, kids);
141
214
  }
142
215
  }
216
+ this.change_emit();
143
217
  }
144
218
  instance_get(id) {
145
219
  return this.instances.get(id);
@@ -191,6 +265,22 @@ class ProcCache {
191
265
  const kids = this.children.get(inst.parentID) ?? [];
192
266
  this.children.set(inst.parentID, kids.filter((k) => k !== id));
193
267
  }
268
+ this.change_emit();
269
+ }
270
+ /**
271
+ * Removes instances absent from an authoritative topology sweep.
272
+ *
273
+ * @param instanceIDs - Instance IDs returned by the completed CUBE sweep.
274
+ * @returns Nothing.
275
+ */
276
+ topology_reconcile(instanceIDs) {
277
+ for (const id of Array.from(this.instances.keys())) {
278
+ if (!instanceIDs.has(id))
279
+ this.instance_remove(id);
280
+ }
281
+ for (const feedID of this.feedIDs_get())
282
+ this.topologyLoaded.add(feedID);
283
+ this.change_emit();
194
284
  }
195
285
  /**
196
286
  * Updates cached params for an instance (on first cat).
@@ -200,6 +290,46 @@ class ProcCache {
200
290
  if (inst)
201
291
  inst.params = params;
202
292
  }
293
+ /**
294
+ * Stores the lazily resolved CUBE output path for one instance.
295
+ *
296
+ * @param id - Plugin-instance ID whose output location was resolved.
297
+ * @param outputPath - Absolute CFS output directory, or null when absent.
298
+ * @returns Nothing.
299
+ */
300
+ outputPath_update(id, outputPath) {
301
+ const inst = this.instances.get(id);
302
+ if (inst)
303
+ inst.outputPath = outputPath;
304
+ }
305
+ /**
306
+ * Finds the closest cached job output containing a CFS path.
307
+ *
308
+ * An output path is a directory boundary: `/home/alice/run` contains
309
+ * `/home/alice/run/result.txt`, but not `/home/alice/run-old`.
310
+ *
311
+ * @param cfsPath - Absolute CFS path to resolve against live-session outputs.
312
+ * @returns The closest producing instance, or undefined when no output path
313
+ * known in this session contains the path.
314
+ */
315
+ outputPath_match(cfsPath) {
316
+ const cleanPath = cfsPath.length > 1 && cfsPath.endsWith('/')
317
+ ? cfsPath.slice(0, -1)
318
+ : cfsPath;
319
+ let closest;
320
+ for (const inst of this.instances.values()) {
321
+ const outputPath = inst.outputPath;
322
+ if (!outputPath)
323
+ continue;
324
+ const isAncestor = outputPath === '/'
325
+ ? cleanPath.startsWith('/')
326
+ : cleanPath === outputPath || cleanPath.startsWith(`${outputPath}/`);
327
+ if (isAncestor && (!closest || outputPath.length > (closest.outputPath?.length ?? 0))) {
328
+ closest = inst;
329
+ }
330
+ }
331
+ return closest;
332
+ }
203
333
  /**
204
334
  * Updates the cached status for an instance. Terminal (settled) statuses are
205
335
  * never overwritten — once finished, a job's status is immutable.
@@ -213,7 +343,10 @@ class ProcCache {
213
343
  return;
214
344
  if (status_isTerminal(inst.status))
215
345
  return;
216
- inst.status = status;
346
+ if (inst.status !== status) {
347
+ inst.status = status;
348
+ this.change_emit();
349
+ }
217
350
  }
218
351
  /**
219
352
  * Records the resolved topological-join parent IDs for a `ts` instance. Immutable
@@ -224,8 +357,10 @@ class ProcCache {
224
357
  */
225
358
  joinParents_update(id, ids) {
226
359
  const inst = this.instances.get(id);
227
- if (inst)
360
+ if (inst) {
228
361
  inst.joinParentIDs = ids;
362
+ this.change_emit();
363
+ }
229
364
  }
230
365
  /**
231
366
  * Returns the resolved join parent IDs for an instance, or an empty array when the
@@ -240,6 +375,7 @@ class ProcCache {
240
375
  // ── Topology loaded tracking ───────────────────────────────────────────────
241
376
  topologyLoaded_mark(feedID) {
242
377
  this.topologyLoaded.add(feedID);
378
+ this.change_emit();
243
379
  }
244
380
  topologyLoaded_has(feedID) {
245
381
  return this.topologyLoaded.has(feedID);
@@ -258,6 +394,8 @@ class ProcCache {
258
394
  warmup_complete() {
259
395
  this._warmupComplete = true;
260
396
  this._warmupProgress = { ...this._warmupProgress, active: false };
397
+ this.lifecycle = { ...this.lifecycle, state: 'current' };
398
+ this.change_emit();
261
399
  }
262
400
  /**
263
401
  * Stops an unsuccessful warm-up without marking its topology complete.
@@ -267,6 +405,8 @@ class ProcCache {
267
405
  warmup_abort() {
268
406
  this._warmupComplete = false;
269
407
  this._warmupProgress = { ...this._warmupProgress, active: false };
408
+ this.lifecycle = { ...this.lifecycle, state: 'failed' };
409
+ this.change_emit();
270
410
  }
271
411
  warmup_progress(loaded, total) {
272
412
  this._warmupProgress = { loaded, total, active: true };
@@ -274,6 +414,19 @@ class ProcCache {
274
414
  warmupProgress_get() {
275
415
  return { ...this._warmupProgress };
276
416
  }
417
+ /**
418
+ * Resets reconciliation progress while retaining restored topology.
419
+ *
420
+ * @returns Nothing.
421
+ */
422
+ warmup_reset() {
423
+ this._warmupComplete = false;
424
+ this._warmupProgress = { loaded: this.instances.size, total: this.instances.size, active: false };
425
+ this.lifecycle = this.lifecycle.checkpointAt
426
+ ? { ...this.lifecycle, state: 'restored' }
427
+ : { state: 'empty' };
428
+ this.change_emit();
429
+ }
277
430
  // ── Path reconstruction ───────────────────────────────────────────────────
278
431
  /**
279
432
  * Reconstructs the full /proc/jobs path for a given instance ID.
@@ -327,6 +480,58 @@ class ProcCache {
327
480
  built_set() {
328
481
  this._built = true;
329
482
  }
483
+ /**
484
+ * Creates a persistence-safe snapshot without parameters or active statuses.
485
+ *
486
+ * @returns Serializable feed and terminal-topology state.
487
+ */
488
+ snapshot_create() {
489
+ const instances = Array.from(this.instances.values()).map((inst) => {
490
+ const { outputPath: _outputPath, ...persistent } = inst;
491
+ return {
492
+ ...persistent,
493
+ params: null,
494
+ status: status_isTerminal(inst.status) ? inst.status : null,
495
+ joinParentIDs: inst.joinParentIDs ? [...inst.joinParentIDs] : undefined,
496
+ };
497
+ });
498
+ return {
499
+ feeds: Array.from(this.feeds.values()).map((feed) => ({ ...feed })),
500
+ instances,
501
+ topologyLoaded: Array.from(this.topologyLoaded),
502
+ };
503
+ }
504
+ /**
505
+ * Replaces in-memory topology with a validated persistent snapshot.
506
+ *
507
+ * @param snapshot - Previously validated snapshot data.
508
+ * @param checkpointAt - ISO timestamp when the checkpoint was written.
509
+ * @returns Nothing.
510
+ */
511
+ snapshot_restore(snapshot, checkpointAt) {
512
+ this.cache_clear();
513
+ for (const feed of snapshot.feeds) {
514
+ this.feeds.set(feed.id, { ...feed });
515
+ this.feedRoots.set(feed.id, []);
516
+ }
517
+ for (const inst of snapshot.instances) {
518
+ const { outputPath: _outputPath, ...restored } = inst;
519
+ this.instance_add({ ...restored, params: null, status: status_isTerminal(inst.status) ? inst.status : null });
520
+ }
521
+ this.topologyLoaded = new Set(snapshot.topologyLoaded.filter((id) => this.feeds.has(id)));
522
+ this._warmupProgress = { loaded: this.instances.size, total: this.instances.size, active: false };
523
+ this.lifecycle = { state: 'restored', checkpointAt };
524
+ this.change_emit();
525
+ }
526
+ /**
527
+ * Records the timestamp of a successful durable save.
528
+ *
529
+ * @param checkpointAt - ISO timestamp written into the checkpoint.
530
+ * @returns Nothing.
531
+ */
532
+ checkpoint_mark(checkpointAt) {
533
+ this.lifecycle = { ...this.lifecycle, checkpointAt };
534
+ }
330
535
  /**
331
536
  * Clears all cache data. Called before a full rebuild.
332
537
  */
@@ -340,6 +545,8 @@ class ProcCache {
340
545
  this._warmupComplete = false;
341
546
  this._warmupProgress = { loaded: 0, total: 0, active: false };
342
547
  this._built = false;
548
+ this.lifecycle = { state: 'empty' };
549
+ this.change_emit();
343
550
  }
344
551
  }
345
552
  exports.ProcCache = ProcCache;