ftown-bridge 0.11.2 → 0.12.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.
@@ -0,0 +1,487 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { computeNextRun, isDue } from './loop-schedule.js';
3
+ import { listLoops, mutateLoopRuntime } from './loop-store.js';
4
+ /** Base tick cadence; also the finalize grace so a just-spawned PTY is not mistaken for exited. */
5
+ export const LOOP_TICK_INTERVAL_MS = 30_000;
6
+ const iso = (ms) => new Date(ms).toISOString();
7
+ /**
8
+ * Runs child_process.exec and normalizes the result to { stdout, stderr,
9
+ * exitCode }. Never rejects — the exit code is the signal (a timeout maps to
10
+ * 124, any other failure to the real exit code or 1).
11
+ *
12
+ * The hard budget is enforced by SIGKILL on the whole process GROUP, NOT by
13
+ * exec's built-in `timeout`. exec's timeout only sends SIGTERM to the spawned
14
+ * `/bin/sh`; a detached grandchild that keeps the stdout pipe open, or a child
15
+ * that traps SIGTERM, would keep the exec callback from ever firing — and since
16
+ * the scheduler awaits every flight inside a single re-entrancy-guarded tick,
17
+ * ONE such flight would wedge the entire scheduler permanently. Spawning
18
+ * `detached` makes the child its own group leader, so `kill(-pid, SIGKILL)`
19
+ * takes down the whole tree and the flight can never exceed its budget.
20
+ */
21
+ export function runFlightCommand(command, cwd, timeoutMs = 30_000, extraEnv) {
22
+ const env = extraEnv ? { ...process.env, ...extraEnv } : process.env;
23
+ const MAX_CAPTURE = 1024 * 1024;
24
+ return new Promise((resolve) => {
25
+ let settled = false;
26
+ let stdout = '';
27
+ let stderr = '';
28
+ let exitCode = null;
29
+ let killedByTimeout = false;
30
+ let exitGrace;
31
+ // Own process group (detached ⇒ the sh is its own group leader) so the hard
32
+ // timeout can SIGKILL the WHOLE tree, including a grandchild that outlived
33
+ // its parent's SIGTERM or kept the stdout pipe open. This is what stops one
34
+ // runaway flight from wedging the awaiting tick forever.
35
+ const child = spawn('/bin/sh', ['-c', command], {
36
+ cwd: cwd ?? process.cwd(),
37
+ env,
38
+ detached: true,
39
+ });
40
+ const settle = () => {
41
+ if (settled)
42
+ return;
43
+ settled = true;
44
+ clearTimeout(hardTimer);
45
+ clearTimeout(exitGrace);
46
+ // Release the pipe read-ends so a still-open grandchild write-end cannot
47
+ // keep the bridge's event loop (or the test runner) alive after we return.
48
+ child.stdout?.destroy();
49
+ child.stderr?.destroy();
50
+ child.unref?.();
51
+ resolve({ stdout, stderr, exitCode: killedByTimeout ? 124 : (exitCode ?? 1) });
52
+ };
53
+ const recordExit = (code, signal) => {
54
+ if (exitCode === null)
55
+ exitCode = typeof code === 'number' ? code : signal ? 1 : 0;
56
+ };
57
+ child.stdout?.on('data', (d) => {
58
+ if (stdout.length < MAX_CAPTURE)
59
+ stdout += d.toString('utf8');
60
+ });
61
+ child.stderr?.on('data', (d) => {
62
+ if (stderr.length < MAX_CAPTURE)
63
+ stderr += d.toString('utf8');
64
+ });
65
+ child.on('error', () => {
66
+ if (exitCode === null)
67
+ exitCode = 1;
68
+ settle();
69
+ });
70
+ // Settle on 'exit' (the shell terminated), NOT 'close' (all stdio closed):
71
+ // a backgrounded/detached grandchild keeps the pipe open, so 'close' may
72
+ // never come. 'close' still wins the race when it fires first (full stdout);
73
+ // otherwise a short grace after 'exit' lets the parent's own output flush.
74
+ child.on('exit', (code, signal) => {
75
+ recordExit(code, signal);
76
+ exitGrace = setTimeout(settle, 150);
77
+ exitGrace.unref?.();
78
+ });
79
+ child.on('close', (code, signal) => {
80
+ recordExit(code, signal);
81
+ settle();
82
+ });
83
+ const hardTimer = setTimeout(() => {
84
+ killedByTimeout = true;
85
+ if (child.pid) {
86
+ try {
87
+ process.kill(-child.pid, 'SIGKILL'); // whole process group
88
+ }
89
+ catch {
90
+ try {
91
+ child.kill('SIGKILL');
92
+ }
93
+ catch {
94
+ /* already gone */
95
+ }
96
+ }
97
+ }
98
+ settle();
99
+ }, timeoutMs);
100
+ hardTimer.unref?.();
101
+ });
102
+ }
103
+ /** Byte-accurate tail: keep the last `maxBytes` of a (possibly huge) terminal log. */
104
+ function truncateTail(text, maxBytes) {
105
+ const buf = Buffer.from(text, 'utf8');
106
+ if (buf.length <= maxBytes)
107
+ return text;
108
+ return buf.subarray(buf.length - maxBytes).toString('utf8');
109
+ }
110
+ /**
111
+ * Resolve a finished run to ok/error. ONLY a cleanly `completed` run is 'ok';
112
+ * a missing record (removed/lost) OR a store status still stuck at
113
+ * running/pending at finalize time (the process died without a clean status
114
+ * transition) is a crash ⇒ 'error'. Never reports a crashed run as success.
115
+ */
116
+ function resolveRunStatus(run) {
117
+ return run?.status === 'completed' ? 'ok' : 'error';
118
+ }
119
+ /**
120
+ * The scheduled-loops engine. On each 30s tick it FINALIZES each loop's
121
+ * in-flight run(s) (Phase A) before deciding whether to FIRE a new one
122
+ * (Phase B). All side effects go through injected collaborators so it is
123
+ * unit-testable without a live bridge, real fs or real timers (mirrors
124
+ * workflow-runner.ts).
125
+ *
126
+ * Persistence rule: the scheduler NEVER writes a whole detached Loop back
127
+ * across an await. Every runtime-field change goes through
128
+ * store.mutateLoopRuntime (fresh-read → mutate → save), so a loop deleted or
129
+ * user-edited during a long flight is neither resurrected nor clobbered.
130
+ */
131
+ export class LoopScheduler {
132
+ store;
133
+ runner;
134
+ centrifugo;
135
+ userId;
136
+ spawnSession;
137
+ removeSession;
138
+ loops;
139
+ runFlight;
140
+ now;
141
+ /** Re-entrancy guard: tick N+1 never overlaps N. */
142
+ tickRunning = false;
143
+ /** Set once start() runs (after reconcileOnStart). kick() no-ops before this so an
144
+ * early run_loop_now cannot trigger an un-reconciled tick that stampedes overdue loops. */
145
+ started = false;
146
+ /** Per-loop in-memory fire lock, shared by tick + run_loop_now/kick. */
147
+ firingLoops = new Set();
148
+ /** loopId -> (runSessionId -> fire-time ms). Every run THIS process spawned, so under
149
+ * overlapPolicy:'allow' each concurrent run is finalized/postflighted/maxRuntime-checked
150
+ * independently — not just the newest. Rebuilt lazily from the persisted primary on restart. */
151
+ inFlight = new Map();
152
+ timer;
153
+ constructor(deps) {
154
+ this.store = deps.store;
155
+ this.runner = deps.runner;
156
+ this.centrifugo = deps.centrifugo;
157
+ this.userId = deps.userId;
158
+ this.spawnSession = deps.spawnSession;
159
+ this.removeSession = deps.removeSession;
160
+ this.loops = deps.loops ?? { listLoops, mutateLoopRuntime };
161
+ this.runFlight = deps.runFlight ?? runFlightCommand;
162
+ this.now = deps.now ?? (() => Date.now());
163
+ }
164
+ start() {
165
+ this.started = true;
166
+ if (this.timer)
167
+ return;
168
+ this.timer = setInterval(() => {
169
+ void this.tick();
170
+ }, LOOP_TICK_INTERVAL_MS);
171
+ }
172
+ stop() {
173
+ if (this.timer) {
174
+ clearInterval(this.timer);
175
+ this.timer = undefined;
176
+ }
177
+ }
178
+ /** Immediate, guarded, out-of-band tick (used by run_loop_now). No-op until start()
179
+ * has run, so a kick that races startup cannot fire before reconcileOnStart. */
180
+ kick() {
181
+ if (!this.started)
182
+ return;
183
+ if (!this.tickRunning)
184
+ void this.tick();
185
+ }
186
+ /** Drop scheduler tracking for a deleted loop and stop any run it left alive, so a
187
+ * just-deleted loop never leaks a live AI session with nothing left to finalize it. */
188
+ onLoopDeleted(loop) {
189
+ const ids = new Set();
190
+ const tracked = this.inFlight.get(loop.id);
191
+ if (tracked)
192
+ for (const id of tracked.keys())
193
+ ids.add(id);
194
+ if (loop.lastStatus === 'running' && loop.lastSessionId)
195
+ ids.add(loop.lastSessionId);
196
+ for (const id of ids) {
197
+ if (this.runner.isRunning(id))
198
+ this.runner.stop(id);
199
+ }
200
+ this.inFlight.delete(loop.id);
201
+ }
202
+ /**
203
+ * Missed-schedule policy, run once before the first tick: for every loop whose
204
+ * nextRunAt is missing or already past, recompute it from now (skip missed
205
+ * occurrences; never stampede overdue loops). runNowRequested is preserved so a
206
+ * manual override survives a restart and still fires on the first tick. A loop
207
+ * with a corrupt persisted schedule is skipped here (logged) and reported as an
208
+ * error on its first fire.
209
+ */
210
+ async reconcileOnStart(now = this.now()) {
211
+ for (const loop of this.loops.listLoops()) {
212
+ try {
213
+ const overdue = loop.nextRunAt ? Date.parse(loop.nextRunAt) <= now : true;
214
+ if (!overdue)
215
+ continue;
216
+ const nextRunMs = computeNextRun(loop.schedule, now); // may throw on a corrupt cron
217
+ await this.persist(loop.id, (l) => {
218
+ l.nextRunAt = iso(nextRunMs);
219
+ l.updatedAt = iso(now);
220
+ });
221
+ }
222
+ catch (err) {
223
+ console.error(`[LoopScheduler] reconcile failed for loop ${loop.id}:`, err);
224
+ }
225
+ }
226
+ }
227
+ async tick(now = this.now()) {
228
+ if (this.tickRunning)
229
+ return; // tick N+1 never overlaps N
230
+ this.tickRunning = true;
231
+ try {
232
+ for (const loop of this.loops.listLoops()) {
233
+ try {
234
+ await this.processLoop(loop, now);
235
+ }
236
+ catch (err) {
237
+ // One bad loop must not kill the tick (mirrors resurrectSessions).
238
+ console.error(`[LoopScheduler] loop ${loop.id} failed:`, err);
239
+ }
240
+ }
241
+ }
242
+ finally {
243
+ this.tickRunning = false;
244
+ }
245
+ }
246
+ async processLoop(loop, now) {
247
+ await this.finalizePhase(loop, now); // Phase A — finalize before fire
248
+ await this.firePhase(loop, now); // Phase B
249
+ }
250
+ /** Phase A: finalize each in-flight run once its PTY is confirmed gone (past grace)
251
+ * or over its per-run maxRuntime budget. Under 'allow' this walks every tracked run,
252
+ * not just the newest, so none is orphaned. */
253
+ async finalizePhase(loop, now) {
254
+ this.ensureTracked(loop);
255
+ const tracked = this.inFlight.get(loop.id);
256
+ if (!tracked || tracked.size === 0)
257
+ return;
258
+ // Snapshot: finalizeRun mutates the map while we iterate.
259
+ for (const [runId, startedMs] of [...tracked]) {
260
+ const running = this.runner.isRunning(runId);
261
+ const elapsed = now - startedMs;
262
+ if (running && loop.maxRuntimeMs && elapsed > loop.maxRuntimeMs) {
263
+ this.runner.stop(runId);
264
+ await this.finalizeRun(loop, now, runId, true);
265
+ }
266
+ else if (!running && elapsed >= LOOP_TICK_INTERVAL_MS) {
267
+ await this.finalizeRun(loop, now, runId, false);
268
+ }
269
+ // else: still running, or still inside the grace window — leave it.
270
+ }
271
+ }
272
+ /** Seed the persisted primary run into in-memory tracking after a restart (when this
273
+ * process has spawned nothing yet for the loop), so a run left 'running' by a prior
274
+ * process is still finalized. */
275
+ ensureTracked(loop) {
276
+ if (loop.lastStatus !== 'running' || !loop.lastSessionId)
277
+ return;
278
+ const existing = this.inFlight.get(loop.id);
279
+ if (existing && existing.size > 0)
280
+ return; // already tracking this process's run(s)
281
+ const parsed = Date.parse(loop.lastRunAt ?? '');
282
+ const startedMs = Number.isNaN(parsed) ? this.now() : parsed;
283
+ const map = existing ?? new Map();
284
+ map.set(loop.lastSessionId, startedMs);
285
+ this.inFlight.set(loop.id, map);
286
+ }
287
+ track(loopId, sessionId, startedMs) {
288
+ const map = this.inFlight.get(loopId) ?? new Map();
289
+ map.set(sessionId, startedMs);
290
+ this.inFlight.set(loopId, map);
291
+ }
292
+ /** Phase B: fire the loop if due, honoring the per-loop lock and the overlap policy. */
293
+ async firePhase(loop, now) {
294
+ if (!isDue(loop, now))
295
+ return;
296
+ if (this.firingLoops.has(loop.id))
297
+ return;
298
+ // A previous run that has EXITED but was not yet finalized (still inside the
299
+ // grace window) must be finalized BEFORE we decide to fire. Otherwise a
300
+ // skip-policy loop double-fires in the grace window (isRunning is already
301
+ // false, so the overlap guard below misses it) and the just-finished run is
302
+ // orphaned — its finalize/postflight never runs.
303
+ if (loop.lastStatus === 'running' && loop.lastSessionId && !this.runner.isRunning(loop.lastSessionId)) {
304
+ await this.finalizeRun(loop, now, loop.lastSessionId, false);
305
+ const fresh = this.loops.listLoops().find((l) => l.id === loop.id);
306
+ if (!fresh)
307
+ return; // deleted during finalize
308
+ loop = fresh;
309
+ }
310
+ // Overlap guard: a skip-policy loop whose previous run is STILL alive advances
311
+ // its schedule only — no new fire, no skipCount bump (overlap-skip is not a
312
+ // preflight-skip).
313
+ if (loop.overlapPolicy === 'skip' &&
314
+ loop.lastStatus === 'running' &&
315
+ loop.lastSessionId &&
316
+ this.runner.isRunning(loop.lastSessionId)) {
317
+ await this.persist(loop.id, (l) => {
318
+ try {
319
+ l.nextRunAt = iso(computeNextRun(l.schedule, now));
320
+ }
321
+ catch {
322
+ /* corrupt schedule surfaces as an error on the fire path, not here */
323
+ }
324
+ l.runNowRequested = false;
325
+ l.updatedAt = iso(now);
326
+ });
327
+ return;
328
+ }
329
+ this.firingLoops.add(loop.id);
330
+ try {
331
+ await this.fireLoop(loop, now);
332
+ }
333
+ finally {
334
+ this.firingLoops.delete(loop.id);
335
+ }
336
+ }
337
+ /** Advance the schedule up front (so failures/skips never stampede), then preflight → flight. */
338
+ async fireLoop(loop, now) {
339
+ // Compute the next fire first so a persisted-corrupt schedule is reported as an
340
+ // error (with a bounded backoff) instead of silently re-throwing every tick.
341
+ let nextRunMs;
342
+ try {
343
+ nextRunMs = computeNextRun(loop.schedule, now);
344
+ }
345
+ catch (err) {
346
+ await this.persist(loop.id, (l) => {
347
+ l.lastRunAt = iso(now);
348
+ l.nextRunAt = iso(now + LOOP_TICK_INTERVAL_MS); // bounded backoff — no stampede
349
+ l.runNowRequested = false;
350
+ l.lastStatus = 'error';
351
+ l.updatedAt = iso(now);
352
+ });
353
+ console.error(`[LoopScheduler] bad schedule for loop ${loop.id}:`, err);
354
+ return;
355
+ }
356
+ try {
357
+ let preflightOut = '';
358
+ if (loop.preflight) {
359
+ const r = await this.runFlight(loop.preflight.command, loop.workdir, loop.preflight.timeoutMs);
360
+ preflightOut = r.stdout;
361
+ if (r.exitCode !== 0) {
362
+ // ABORT: skip (not error), no session, no run-node.
363
+ const skipped = await this.persist(loop.id, (l) => {
364
+ l.lastRunAt = iso(now);
365
+ l.nextRunAt = iso(nextRunMs);
366
+ l.runNowRequested = false;
367
+ l.lastStatus = 'skipped';
368
+ l.skipCount += 1;
369
+ l.updatedAt = iso(now);
370
+ });
371
+ if (skipped && loop.postflight?.runOnSkip) {
372
+ await this.runPostflight(loop, { status: 'skipped', sessionId: '', output: '' });
373
+ }
374
+ return;
375
+ }
376
+ }
377
+ const task = loop.task.replaceAll('{{preflight}}', preflightOut);
378
+ const session = await this.spawnSession({
379
+ shellType: loop.harness,
380
+ prompt: task,
381
+ workingDir: loop.workdir,
382
+ model: loop.model,
383
+ env: preflightOut ? { FTOWN_PREFLIGHT_OUTPUT: preflightOut } : undefined,
384
+ loopId: loop.id,
385
+ suppressBriefing: true, // no child/orchestrator briefing paragraph in the task
386
+ name: `${loop.name} · ${iso(now)}`,
387
+ // parentSessionId intentionally omitted — loopId is the sole grouping key.
388
+ });
389
+ const updated = await this.persist(loop.id, (l) => {
390
+ l.lastRunAt = iso(now);
391
+ l.nextRunAt = iso(nextRunMs);
392
+ l.runNowRequested = false;
393
+ l.lastSessionId = session.id;
394
+ l.lastStatus = 'running';
395
+ l.runCount += 1;
396
+ l.updatedAt = iso(now);
397
+ });
398
+ if (!updated) {
399
+ // The loop was deleted during preflight/spawn: do not resurrect it and do
400
+ // not leave an orphan run that nothing would ever finalize or prune.
401
+ this.runner.stop(session.id);
402
+ await this.removeSession(session.id).catch(() => undefined);
403
+ return;
404
+ }
405
+ this.track(loop.id, session.id, now);
406
+ }
407
+ catch (err) {
408
+ // A failure after the schedule was computed: record error + persist so the
409
+ // loop resumes its cadence instead of stampede-retrying every tick.
410
+ await this.persist(loop.id, (l) => {
411
+ l.lastRunAt = iso(now);
412
+ l.nextRunAt = iso(nextRunMs);
413
+ l.runNowRequested = false;
414
+ l.lastStatus = 'error';
415
+ l.updatedAt = iso(now);
416
+ });
417
+ console.error(`[LoopScheduler] fire failed for loop ${loop.id}:`, err);
418
+ }
419
+ }
420
+ /** Resolve one finished run to ok/error, update the loop badge (only if this is the
421
+ * loop's tracked/latest run), then run postflight + retention for it. */
422
+ async finalizeRun(loop, now, runId, forcedError) {
423
+ const tracked = this.inFlight.get(loop.id);
424
+ if (tracked) {
425
+ tracked.delete(runId);
426
+ if (tracked.size === 0)
427
+ this.inFlight.delete(loop.id);
428
+ }
429
+ const run = runId ? await this.store.loadSession(runId) : null;
430
+ const status = forcedError ? 'error' : resolveRunStatus(run);
431
+ const output = truncateTail(runId ? await this.store.loadTerminalLog(runId) : '', 65_536);
432
+ // Only the loop's most-recently-STARTED run (lastSessionId) drives the badge;
433
+ // an older overlapping 'allow' run finalizes silently (still postflight +
434
+ // retention) without flipping the badge away from a newer run's state.
435
+ const updated = await this.persist(loop.id, (l) => {
436
+ if (l.lastSessionId === runId)
437
+ l.lastStatus = status;
438
+ l.updatedAt = iso(now);
439
+ });
440
+ void updated;
441
+ if (loop.postflight) {
442
+ await this.runPostflight(loop, { status, sessionId: runId, output });
443
+ }
444
+ await this.pruneRuns(loop);
445
+ }
446
+ async runPostflight(loop, ctx) {
447
+ if (!loop.postflight)
448
+ return;
449
+ await this.runFlight(loop.postflight.command, loop.workdir, loop.postflight.timeoutMs, {
450
+ FTOWN_RUN_STATUS: ctx.status,
451
+ FTOWN_RUN_SESSION_ID: ctx.sessionId,
452
+ FTOWN_RUN_OUTPUT: ctx.output,
453
+ });
454
+ }
455
+ /** Keep the newest N run-sessions for this loop; prune older finished ones. */
456
+ async pruneRuns(loop) {
457
+ const keep = loop.retention.autoClearAfterRuns;
458
+ if (keep == null)
459
+ return;
460
+ const runs = (await this.store.listSessions())
461
+ .filter((s) => s.loopId === loop.id)
462
+ .sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
463
+ for (const run of runs.slice(keep)) {
464
+ if (this.runner.isRunning(run.id))
465
+ continue;
466
+ if (run.id === loop.lastSessionId)
467
+ continue;
468
+ await this.removeSession(run.id, { onlyIfFinished: true });
469
+ }
470
+ }
471
+ async persist(id, fn) {
472
+ const updated = this.loops.mutateLoopRuntime(id, fn);
473
+ if (updated)
474
+ await this.publish(updated);
475
+ return updated;
476
+ }
477
+ async publish(loop) {
478
+ try {
479
+ await this.centrifugo.publishLoopUpdate(this.userId, loop);
480
+ }
481
+ catch (err) {
482
+ // A UI-sync failure must never break the scheduler (matches session create).
483
+ console.error(`[LoopScheduler] Failed to publish loop update for ${loop.id}:`, err);
484
+ }
485
+ }
486
+ }
487
+ //# sourceMappingURL=loop-scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-scheduler.js","sourceRoot":"","sources":["../src/loop-scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { Loop, LoopDraft } from './types.js';
2
+ export declare function listLoops(): Loop[];
3
+ export declare function getLoop(id: string): Loop | undefined;
4
+ /**
5
+ * Mint a Loop from a client draft: fresh id/timestamps, zeroed counters, and a
6
+ * nextRunAt computed from now — even when created disabled, so re-enabling has a
7
+ * target. Throws (via computeNextRun) on a malformed cron expression.
8
+ */
9
+ export declare function createLoop(draft: LoopDraft): Loop;
10
+ /**
11
+ * Merge `patch` over an existing loop. id + createdAt are immutable; updatedAt is
12
+ * bumped. A schedule change recomputes nextRunAt from now (the old target is
13
+ * stale). Returns null when no loop has that id.
14
+ */
15
+ export declare function updateLoop(id: string, patch: Partial<LoopDraft>): Loop | null;
16
+ export declare function deleteLoop(id: string): boolean;
17
+ /** Insert a loop, or replace the existing one with the same id, in place. */
18
+ export declare function upsertLoop(loop: Loop): void;
19
+ /** The scheduler-owned runtime fields a tick is allowed to mutate. Everything
20
+ * else on a Loop (name, schedule, enabled, task, retention, …) is user-owned
21
+ * and edited exclusively through updateLoop. */
22
+ export type LoopRuntimeMutator = (loop: Loop) => void;
23
+ /**
24
+ * Atomically apply a scheduler-owned mutation: reload the loop FRESH from disk,
25
+ * apply `fn`, then save. Returns the merged loop, or null when the id no longer
26
+ * exists (deleted concurrently) — the caller then skips its publish so a deleted
27
+ * loop is never resurrected.
28
+ *
29
+ * The scheduler holds a detached Loop snapshot across long awaits (a 20s
30
+ * preflight, an in-process spawn). Writing that whole stale snapshot back would
31
+ * (a) resurrect a loop deleted mid-flight and (b) clobber a concurrent
32
+ * update_loop patch to user-owned fields. Because `fn` runs on the
33
+ * freshly-loaded record and touches only runtime fields, both hazards are gone:
34
+ * a delete wins (null), and an unrelated enabled/schedule/task patch survives.
35
+ */
36
+ export declare function mutateLoopRuntime(id: string, fn: LoopRuntimeMutator): Loop | null;