@theford/runly 0.0.0-stage → 0.20.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.
- package/LICENSE +202 -0
- package/README.md +157 -2
- package/dist/cli/client.d.ts +80 -0
- package/dist/cli/client.js +294 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/daemon-unit.d.ts +58 -0
- package/dist/cli/daemon-unit.js +289 -0
- package/dist/cli/daemon-unit.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1482 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.d.ts +9 -0
- package/dist/cli/install.js +53 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/logs.d.ts +43 -0
- package/dist/cli/logs.js +232 -0
- package/dist/cli/logs.js.map +1 -0
- package/dist/cli/tether.d.ts +3 -0
- package/dist/cli/tether.js +65 -0
- package/dist/cli/tether.js.map +1 -0
- package/dist/cli/watchdog.d.ts +1 -0
- package/dist/cli/watchdog.js +81 -0
- package/dist/cli/watchdog.js.map +1 -0
- package/dist/core/builds.d.ts +20 -0
- package/dist/core/builds.js +93 -0
- package/dist/core/builds.js.map +1 -0
- package/dist/core/caller-env.d.ts +17 -0
- package/dist/core/caller-env.js +77 -0
- package/dist/core/caller-env.js.map +1 -0
- package/dist/core/diagnostics.d.ts +68 -0
- package/dist/core/diagnostics.js +34 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/env-vars.d.ts +35 -0
- package/dist/core/env-vars.js +32 -0
- package/dist/core/env-vars.js.map +1 -0
- package/dist/core/events.d.ts +10 -0
- package/dist/core/events.js +42 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/exec.d.ts +22 -0
- package/dist/core/exec.js +84 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/journal.d.ts +227 -0
- package/dist/core/journal.js +395 -0
- package/dist/core/journal.js.map +1 -0
- package/dist/core/logs.d.ts +56 -0
- package/dist/core/logs.js +259 -0
- package/dist/core/logs.js.map +1 -0
- package/dist/core/manifest.d.ts +292 -0
- package/dist/core/manifest.js +179 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/namespaces.d.ts +7 -0
- package/dist/core/namespaces.js +59 -0
- package/dist/core/namespaces.js.map +1 -0
- package/dist/core/paths.d.ts +31 -0
- package/dist/core/paths.js +65 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/policy.d.ts +94 -0
- package/dist/core/policy.js +122 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/ports.d.ts +52 -0
- package/dist/core/ports.js +94 -0
- package/dist/core/ports.js.map +1 -0
- package/dist/core/presets.d.ts +25 -0
- package/dist/core/presets.js +86 -0
- package/dist/core/presets.js.map +1 -0
- package/dist/core/procscan.d.ts +111 -0
- package/dist/core/procscan.js +342 -0
- package/dist/core/procscan.js.map +1 -0
- package/dist/core/retention.d.ts +97 -0
- package/dist/core/retention.js +322 -0
- package/dist/core/retention.js.map +1 -0
- package/dist/core/sleep.d.ts +51 -0
- package/dist/core/sleep.js +99 -0
- package/dist/core/sleep.js.map +1 -0
- package/dist/core/tree-ledger.d.ts +39 -0
- package/dist/core/tree-ledger.js +92 -0
- package/dist/core/tree-ledger.js.map +1 -0
- package/dist/core/types.d.ts +21 -0
- package/dist/core/types.js +6 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/units.d.ts +20 -0
- package/dist/core/units.js +69 -0
- package/dist/core/units.js.map +1 -0
- package/dist/core/upkeep.d.ts +90 -0
- package/dist/core/upkeep.js +176 -0
- package/dist/core/upkeep.js.map +1 -0
- package/dist/core/util.d.ts +46 -0
- package/dist/core/util.js +168 -0
- package/dist/core/util.js.map +1 -0
- package/dist/core/version.d.ts +39 -0
- package/dist/core/version.js +144 -0
- package/dist/core/version.js.map +1 -0
- package/dist/core/worktree.d.ts +51 -0
- package/dist/core/worktree.js +309 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/daemon/budget.d.ts +155 -0
- package/dist/daemon/budget.js +288 -0
- package/dist/daemon/budget.js.map +1 -0
- package/dist/daemon/election.d.ts +9 -0
- package/dist/daemon/election.js +140 -0
- package/dist/daemon/election.js.map +1 -0
- package/dist/daemon/engine.d.ts +1703 -0
- package/dist/daemon/engine.js +5305 -0
- package/dist/daemon/engine.js.map +1 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +412 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/proxy.d.ts +108 -0
- package/dist/daemon/proxy.js +615 -0
- package/dist/daemon/proxy.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +101 -0
- package/dist/daemon/supervisor.js +493 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/drivers/appliances.d.ts +11 -0
- package/dist/drivers/appliances.js +174 -0
- package/dist/drivers/appliances.js.map +1 -0
- package/dist/drivers/datastores.d.ts +144 -0
- package/dist/drivers/datastores.js +775 -0
- package/dist/drivers/datastores.js.map +1 -0
- package/dist/drivers/preview.d.ts +69 -0
- package/dist/drivers/preview.js +605 -0
- package/dist/drivers/preview.js.map +1 -0
- package/dist/drivers/types.d.ts +70 -0
- package/dist/drivers/types.js +13 -0
- package/dist/drivers/types.js.map +1 -0
- package/package.json +63 -4
- package/schema/backlot.schema.json +549 -0
- package/schema/runly.schema.json +556 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process identity and orphan discovery.
|
|
3
|
+
*
|
|
4
|
+
* Runly spawns each service detached (its own process group) and kills the
|
|
5
|
+
* group on teardown — but a daemon that dies ungracefully leaves those groups
|
|
6
|
+
* running with nothing tracking them. Recorded pids alone are not enough to
|
|
7
|
+
* clean up afterwards: by the time a new daemon reads them they may have been
|
|
8
|
+
* recycled by the OS onto an unrelated process, and signalling those is worse
|
|
9
|
+
* than leaking.
|
|
10
|
+
*
|
|
11
|
+
* So every supervised service carries a tag in its environment (see
|
|
12
|
+
* `serviceTag`), and every recorded pid carries the kernel's start-time for
|
|
13
|
+
* that pid. Together they answer the two questions cleanup needs:
|
|
14
|
+
*
|
|
15
|
+
* - "is pid N still the process I recorded?" -> `sameProcess`
|
|
16
|
+
* - "which runly processes has everyone lost?" -> `scanTagged`
|
|
17
|
+
*
|
|
18
|
+
* Identity works on every platform: /proc where it exists, `ps -o lstart=`
|
|
19
|
+
* otherwise, so a recycled pid is never mistaken for the process we recorded.
|
|
20
|
+
* Orphan SCANNING needs to read other processes' environments and so is
|
|
21
|
+
* Linux-only; elsewhere `pool gc` reports unsupported rather than guessing.
|
|
22
|
+
* That asymmetry is deliberate — skipping a sweep is safe, signalling a
|
|
23
|
+
* stranger's process is not.
|
|
24
|
+
*/
|
|
25
|
+
import { readFileSync, readdirSync, readlinkSync } from 'node:fs';
|
|
26
|
+
import { execFileSync } from 'node:child_process';
|
|
27
|
+
export const ENV_TAG = 'BACKLOT_ENV_ID';
|
|
28
|
+
export const SERVICE_TAG = 'BACKLOT_SERVICE';
|
|
29
|
+
/** Scopes a tag to one state root so parallel installs never reap each other. */
|
|
30
|
+
export const ROOT_TAG = 'BACKLOT_STATE_ROOT';
|
|
31
|
+
/** The environment every supervised service is spawned with. */
|
|
32
|
+
/** The service name `runly exec`'s command is tagged with; never reaped (see scanTagged). */
|
|
33
|
+
export const EXEC_SERVICE = 'exec';
|
|
34
|
+
export function serviceTag(envId, service, stateRoot) {
|
|
35
|
+
return { [ENV_TAG]: envId, [SERVICE_TAG]: service, [ROOT_TAG]: stateRoot };
|
|
36
|
+
}
|
|
37
|
+
export const procScanSupported = () => process.platform === 'linux';
|
|
38
|
+
/**
|
|
39
|
+
* Field 22 of /proc/<pid>/stat: the process start time in clock ticks since
|
|
40
|
+
* boot. Unique per pid *life*, so (pid, startTime) survives pid reuse.
|
|
41
|
+
*
|
|
42
|
+
* comm (field 2) is an arbitrary string wrapped in parens and may itself
|
|
43
|
+
* contain spaces or parens, so parse after the LAST ')' rather than splitting
|
|
44
|
+
* the whole line.
|
|
45
|
+
*/
|
|
46
|
+
export function startTime(pid) {
|
|
47
|
+
if (!procScanSupported())
|
|
48
|
+
return darwinStartTime(pid);
|
|
49
|
+
try {
|
|
50
|
+
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
51
|
+
const rest = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
52
|
+
const ticks = Number(rest[19]); // field 22 == index 19 after pid and comm
|
|
53
|
+
return Number.isFinite(ticks) ? ticks : undefined;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return undefined; // gone, or not ours to read
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A portable identity for platforms without /proc.
|
|
61
|
+
*
|
|
62
|
+
* Without this, non-Linux recovery fell back to a bare liveness check, which is
|
|
63
|
+
* exactly the pid-reuse hazard the start time exists to close — and macOS is
|
|
64
|
+
* the platform runly's primary users are on. `ps -o lstart=` reports a
|
|
65
|
+
* process's start wall-clock to the second, which is enough to distinguish a
|
|
66
|
+
* recycled pid from the process we recorded.
|
|
67
|
+
*
|
|
68
|
+
* Returns undefined if ps is unavailable or the pid is gone; callers treat an
|
|
69
|
+
* un-verifiable pid as un-signallable, so failing to read is safe.
|
|
70
|
+
*/
|
|
71
|
+
function darwinStartTime(pid) {
|
|
72
|
+
try {
|
|
73
|
+
const out = execFileSync('ps', ['-o', 'lstart=', '-p', String(pid)], {
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
76
|
+
timeout: 2000,
|
|
77
|
+
}).trim();
|
|
78
|
+
if (!out)
|
|
79
|
+
return undefined;
|
|
80
|
+
const ms = Date.parse(out);
|
|
81
|
+
return Number.isFinite(ms) ? ms : undefined;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined; // gone, not ours to read, or no ps
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Field 5 of /proc/<pid>/stat: the process group id.
|
|
89
|
+
*
|
|
90
|
+
* `sh -c <cmd>` does NOT reliably exec: dash forks for anything non-trivial,
|
|
91
|
+
* so the pid runly records is often only the wrapper, and the real server is
|
|
92
|
+
* a sibling in the same group. Killing "the leader" and then checking the
|
|
93
|
+
* leader's liveness therefore reports success while the server keeps running —
|
|
94
|
+
* group membership is the only honest liveness question.
|
|
95
|
+
*/
|
|
96
|
+
export function processGroup(pid) {
|
|
97
|
+
if (!procScanSupported())
|
|
98
|
+
return undefined;
|
|
99
|
+
try {
|
|
100
|
+
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
101
|
+
const rest = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
102
|
+
const pgrp = Number(rest[2]); // field 5 == index 2 after pid and comm
|
|
103
|
+
return Number.isFinite(pgrp) ? pgrp : undefined;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Is ANY process still in this group?
|
|
111
|
+
*
|
|
112
|
+
* Without /proc, `kill(-pgid, 0)` is still a genuine GROUP query — ESRCH means
|
|
113
|
+
* the group is empty. Checking the leader instead would report "gone" the
|
|
114
|
+
* moment an `sh -c` wrapper exited, while the real server kept running, which
|
|
115
|
+
* is the whole failure this function exists to detect.
|
|
116
|
+
*/
|
|
117
|
+
export function groupAlive(pgid) {
|
|
118
|
+
// `kill(-pgid, 0)` is a SINGLE syscall and an authoritative group query:
|
|
119
|
+
// ESRCH means no process remains in the group. An earlier version walked all
|
|
120
|
+
// of /proc here, which is O(processes) file reads per call — and
|
|
121
|
+
// killGroupVerified polls this every 50ms, so on a busy host a single reap
|
|
122
|
+
// issued hundreds of thousands of reads and stalled the daemon.
|
|
123
|
+
try {
|
|
124
|
+
process.kill(-pgid, 0);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
// EPERM: the group exists but belongs to another user — still alive.
|
|
129
|
+
return err.code === 'EPERM';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** Is this pid alive at all? Says nothing about whose it is. */
|
|
133
|
+
export function isAlive(pid) {
|
|
134
|
+
try {
|
|
135
|
+
process.kill(pid, 0);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
// EPERM means alive but owned by another user — still alive.
|
|
140
|
+
return err.code === 'EPERM';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Is pid N still the same process that was recorded with `recordedStart`?
|
|
145
|
+
*
|
|
146
|
+
* With no recorded start time (a journal written by an older runly, or a
|
|
147
|
+
* non-Linux host) this can only fall back to liveness, which is exactly the
|
|
148
|
+
* pid-reuse hazard this exists to close — so callers should treat an
|
|
149
|
+
* un-verifiable pid as un-signallable where a mistake would be costly.
|
|
150
|
+
*/
|
|
151
|
+
export function sameProcess(pid, recordedStart) {
|
|
152
|
+
if (!isAlive(pid))
|
|
153
|
+
return false;
|
|
154
|
+
if (recordedStart === undefined)
|
|
155
|
+
return true;
|
|
156
|
+
const current = startTime(pid);
|
|
157
|
+
if (current === undefined)
|
|
158
|
+
return false;
|
|
159
|
+
return current === recordedStart;
|
|
160
|
+
}
|
|
161
|
+
/** Read a process's environment as a map, or undefined if unreadable. */
|
|
162
|
+
function readEnviron(pid) {
|
|
163
|
+
try {
|
|
164
|
+
const raw = readFileSync(`/proc/${pid}/environ`, 'utf8');
|
|
165
|
+
const out = {};
|
|
166
|
+
for (const entry of raw.split('\0')) {
|
|
167
|
+
if (!entry)
|
|
168
|
+
continue;
|
|
169
|
+
const eq = entry.indexOf('=');
|
|
170
|
+
if (eq > 0)
|
|
171
|
+
out[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return undefined; // exited mid-scan, or another user's process
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Every live process tagged as belonging to this state root.
|
|
181
|
+
*
|
|
182
|
+
* Note this finds *descendants* too — an `ng serve` inherits the tag from the
|
|
183
|
+
* `sh -c` wrapper runly spawned, which is the whole point: after the wrapper
|
|
184
|
+
* dies the child is still identifiable.
|
|
185
|
+
*/
|
|
186
|
+
export function scanTagged(stateRoot) {
|
|
187
|
+
if (!procScanSupported())
|
|
188
|
+
return [];
|
|
189
|
+
const found = [];
|
|
190
|
+
let entries;
|
|
191
|
+
try {
|
|
192
|
+
entries = readdirSync('/proc');
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
for (const entry of entries) {
|
|
198
|
+
const pid = Number(entry);
|
|
199
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
200
|
+
continue;
|
|
201
|
+
const env = readEnviron(pid);
|
|
202
|
+
if (!env)
|
|
203
|
+
continue;
|
|
204
|
+
if (env[ROOT_TAG] !== stateRoot)
|
|
205
|
+
continue;
|
|
206
|
+
const envId = env[ENV_TAG];
|
|
207
|
+
if (!envId)
|
|
208
|
+
continue;
|
|
209
|
+
// `runly exec`'s command (decision 0040) carries the tag so it can be told
|
|
210
|
+
// apart, but it is the CALLER's process, run by the CLI: no daemon path
|
|
211
|
+
// (gc, recycle, shutdown) may reap it as an environment's orphan.
|
|
212
|
+
if (env[SERVICE_TAG] === EXEC_SERVICE)
|
|
213
|
+
continue;
|
|
214
|
+
const st = startTime(pid);
|
|
215
|
+
if (st === undefined)
|
|
216
|
+
continue; // exited between the two reads
|
|
217
|
+
found.push({ pid, envId, service: env[SERVICE_TAG] ?? '?', startTime: st });
|
|
218
|
+
}
|
|
219
|
+
return found;
|
|
220
|
+
}
|
|
221
|
+
/** Carried by `runly db with`'s command and its descendants: the copy it runs against (decision 0039). */
|
|
222
|
+
export const DB_COPY_TAG = 'BACKLOT_DB_COPY';
|
|
223
|
+
/** The environment `runly db with` runs its command with, so the daemon can find it again. */
|
|
224
|
+
export function dbCopyTag(copy, stateRoot) {
|
|
225
|
+
return { [DB_COPY_TAG]: copy, [ROOT_TAG]: stateRoot };
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Every live process of this state root running against database copy
|
|
229
|
+
* `copy` — `runly db with`'s command and whatever it started (the tag is
|
|
230
|
+
* inherited). Linux only, like `scanTagged`; elsewhere the recorded pid is
|
|
231
|
+
* all there is.
|
|
232
|
+
*/
|
|
233
|
+
export function scanDbCopy(copy, stateRoot) {
|
|
234
|
+
if (!procScanSupported())
|
|
235
|
+
return [];
|
|
236
|
+
const found = [];
|
|
237
|
+
let entries;
|
|
238
|
+
try {
|
|
239
|
+
entries = readdirSync('/proc');
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
for (const entry of entries) {
|
|
245
|
+
const pid = Number(entry);
|
|
246
|
+
if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid)
|
|
247
|
+
continue;
|
|
248
|
+
const env = readEnviron(pid);
|
|
249
|
+
if (!env || env[ROOT_TAG] !== stateRoot || env[DB_COPY_TAG] !== copy)
|
|
250
|
+
continue;
|
|
251
|
+
const st = startTime(pid);
|
|
252
|
+
if (st !== undefined)
|
|
253
|
+
found.push({ pid, startTime: st });
|
|
254
|
+
}
|
|
255
|
+
return found;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Field 7 of /proc/<pid>/stat: the controlling terminal, 0 for none.
|
|
259
|
+
*
|
|
260
|
+
* Runly spawns every service with `stdio: ['ignore', 'pipe', 'pipe']` and
|
|
261
|
+
* detached, so it and all its descendants have NO controlling terminal. A human's
|
|
262
|
+
* interactive shell always has one. That difference is the only cheap evidence
|
|
263
|
+
* available for "is a person sitting in front of this process".
|
|
264
|
+
*/
|
|
265
|
+
function hasControllingTty(pid) {
|
|
266
|
+
try {
|
|
267
|
+
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
268
|
+
const rest = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
269
|
+
const ttyNr = Number(rest[4]); // field 7 == index 4 after pid and comm
|
|
270
|
+
return Number.isFinite(ttyNr) && ttyNr !== 0;
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return false; // unreadable — the caller's other guards decide
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Every live process whose working directory sits inside `prefix` AND which has
|
|
278
|
+
* no controlling terminal.
|
|
279
|
+
*
|
|
280
|
+
* The tag scan above misses a descendant that scrubbed its environment (a
|
|
281
|
+
* process launched through `env -i`, a re-exec that rebuilds environ, some
|
|
282
|
+
* language runtimes' worker pools). Those still have to live SOMEWHERE. Under
|
|
283
|
+
* the old projection that somewhere was the environment's own tree; since
|
|
284
|
+
* decision 0032 services run in the caller's worktree, where cwd proves
|
|
285
|
+
* nothing, so the engine only ever points this at an environment's PRIVATE
|
|
286
|
+
* directory (see Engine.reapEnvTree).
|
|
287
|
+
*
|
|
288
|
+
* Linux keeps the cwd link readable after the directory is unlinked, appending
|
|
289
|
+
* " (deleted)" to the target — which is precisely the shape of the leak this
|
|
290
|
+
* exists to catch: hundreds of service children still running out of an
|
|
291
|
+
* environment tree that was removed a day earlier. The suffix is stripped so a
|
|
292
|
+
* deleted tree still matches its prefix.
|
|
293
|
+
*
|
|
294
|
+
* The tty exclusion is not a nicety. cwd is NOT proof of ownership — a developer
|
|
295
|
+
* who runs `cd <env-tree>` to look around matches this scan exactly, and callers
|
|
296
|
+
* signal a matched process's whole GROUP, so without the exclusion a teardown
|
|
297
|
+
* would kill that person's shell and every job in it. That is the one outcome
|
|
298
|
+
* this module exists to prevent (see the header): skipping a sweep is safe,
|
|
299
|
+
* signalling a stranger's process is not. A runly service can never be
|
|
300
|
+
* excluded by it, because it never has a terminal to begin with.
|
|
301
|
+
*
|
|
302
|
+
* Even so, deliberately NOT used on the quiesce path, and never on a worktree:
|
|
303
|
+
* only teardown, which is about to delete the environment's private directory
|
|
304
|
+
* anyway, may reap by cwd — and only inside that directory.
|
|
305
|
+
*/
|
|
306
|
+
export function scanByCwd(prefix) {
|
|
307
|
+
if (!procScanSupported())
|
|
308
|
+
return [];
|
|
309
|
+
const self = process.pid;
|
|
310
|
+
const found = [];
|
|
311
|
+
let entries;
|
|
312
|
+
try {
|
|
313
|
+
entries = readdirSync('/proc');
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
for (const entry of entries) {
|
|
319
|
+
const pid = Number(entry);
|
|
320
|
+
if (!Number.isInteger(pid) || pid <= 0 || pid === self)
|
|
321
|
+
continue;
|
|
322
|
+
let cwd;
|
|
323
|
+
try {
|
|
324
|
+
cwd = readlinkSync(`/proc/${pid}/cwd`);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
continue; // exited, or another user's process — not ours to touch
|
|
328
|
+
}
|
|
329
|
+
const path = cwd.endsWith(' (deleted)') ? cwd.slice(0, -' (deleted)'.length) : cwd;
|
|
330
|
+
// Prefix match on a path BOUNDARY, so `…/env-1` never matches `…/env-10`.
|
|
331
|
+
if (path !== prefix && !path.startsWith(prefix.endsWith('/') ? prefix : prefix + '/'))
|
|
332
|
+
continue;
|
|
333
|
+
if (hasControllingTty(pid))
|
|
334
|
+
continue; // a person is using this one
|
|
335
|
+
const st = startTime(pid);
|
|
336
|
+
if (st === undefined)
|
|
337
|
+
continue; // exited between the two reads
|
|
338
|
+
found.push({ pid, startTime: st, cwd });
|
|
339
|
+
}
|
|
340
|
+
return found;
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=procscan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procscan.js","sourceRoot":"","sources":["../../src/core/procscan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,CAAC,MAAM,OAAO,GAAG,gBAAgB,CAAC;AACxC,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAC7C,iFAAiF;AACjF,MAAM,CAAC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;AAS7C,gEAAgE;AAChE,6FAA6F;AAC7F,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AAEnC,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,OAAe,EAAE,SAAiB;IAC1E,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAY,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAE7E;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,CAAC,iBAAiB,EAAE;QAAE,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,0CAA0C;QAC1E,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC,CAAC,4BAA4B;IAChD,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,eAAe,CAAC,GAAW;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE;YACnE,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,OAAO,EAAE,IAAI;SACd,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC,CAAC,mCAAmC;IACvD,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC,iBAAiB,EAAE;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,wCAAwC;QACtE,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,yEAAyE;IACzE,6EAA6E;IAC7E,iEAAiE;IACjE,2EAA2E;IAC3E,gEAAgE;IAChE,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,qEAAqE;QACrE,OAAQ,GAA6B,CAAC,IAAI,KAAK,OAAO,CAAC;IACzD,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,6DAA6D;QAC7D,OAAQ,GAA6B,CAAC,IAAI,KAAK,OAAO,CAAC;IACzD,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,aAAsB;IAC7D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAChC,IAAI,aAAa,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,OAAO,KAAK,aAAa,CAAC;AACnC,CAAC;AAED,yEAAyE;AACzE,SAAS,WAAW,CAAC,GAAW;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,GAAG,UAAU,EAAE,MAAM,CAAC,CAAC;QACzD,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,EAAE,GAAG,CAAC;gBAAE,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC,CAAC,6CAA6C;IACjE,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,IAAI,CAAC,iBAAiB,EAAE;QAAE,OAAO,EAAE,CAAC;IACpC,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAAE,SAAS;QACjD,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,SAAS;YAAE,SAAS;QAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,2EAA2E;QAC3E,wEAAwE;QACxE,kEAAkE;QAClE,IAAI,GAAG,CAAC,WAAW,CAAC,KAAK,YAAY;YAAE,SAAS;QAChD,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,EAAE,KAAK,SAAS;YAAE,SAAS,CAAC,+BAA+B;QAC/D,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC,IAAI,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,0GAA0G;AAC1G,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAE7C,8FAA8F;AAC9F,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,SAAiB;IACvD,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;AACxD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,SAAiB;IACxD,IAAI,CAAC,iBAAiB,EAAE;QAAE,OAAO,EAAE,CAAC;IACpC,MAAM,KAAK,GAA8C,EAAE,CAAC;IAC5D,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG;YAAE,SAAS;QACxE,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,SAAS,IAAI,GAAG,CAAC,WAAW,CAAC,KAAK,IAAI;YAAE,SAAS;QAC/E,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,EAAE,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,iBAAiB,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,wCAAwC;QACvE,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC,CAAC,gDAAgD;IAChE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,IAAI,CAAC,iBAAiB,EAAE;QAAE,OAAO,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC;IACzB,MAAM,KAAK,GAA2D,EAAE,CAAC;IACzE,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,IAAI;YAAE,SAAS;QACjE,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,SAAS,CAAC,wDAAwD;QACpE,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACnF,0EAA0E;QAC1E,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC;YAAE,SAAS;QAChG,IAAI,iBAAiB,CAAC,GAAG,CAAC;YAAE,SAAS,CAAC,6BAA6B;QACnE,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,EAAE,KAAK,SAAS;YAAE,SAAS,CAAC,+BAA+B;QAC/D,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Journal } from './journal.js';
|
|
2
|
+
import type { Policy } from './policy.js';
|
|
3
|
+
/**
|
|
4
|
+
* Service logs past the cap are rotated once (decision 0038): `<name>.log`
|
|
5
|
+
* becomes `<name>.log.1`, replacing the previous rotation. The writer rotates
|
|
6
|
+
* as it writes; this is the backstop for a file it never reached (a writer
|
|
7
|
+
* from an older daemon, or a log that grew between restarts). Only `*.log`
|
|
8
|
+
* files are touched.
|
|
9
|
+
*/
|
|
10
|
+
export declare function truncateLogs(p: Policy, root?: string): number;
|
|
11
|
+
/**
|
|
12
|
+
* Which stacks a template may still serve: anything a journal row names, or
|
|
13
|
+
* whose worktree is still on disk (decision 0037). A stack whose worktree is
|
|
14
|
+
* gone and that no row names keeps no "current" template — nobody can bind
|
|
15
|
+
* it again.
|
|
16
|
+
*/
|
|
17
|
+
export interface TemplateRefs {
|
|
18
|
+
/** `<stack>/<file>` of every template an environment or copy was restored from. */
|
|
19
|
+
referenced: ReadonlySet<string>;
|
|
20
|
+
/** False when the stack can never be bound again. */
|
|
21
|
+
stackAlive: (stackId: string) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Where a template's recorded drop command runs: it comes from the
|
|
24
|
+
* manifest and is written to run in the repo (`node scripts/db.cjs drop …`).
|
|
25
|
+
* The stack's own worktree while it exists, else a live worktree of the same
|
|
26
|
+
* project (same manifest name; from the journal or a sibling's `.root`),
|
|
27
|
+
* else the templates root.
|
|
28
|
+
*/
|
|
29
|
+
dropCwd?: (stackId: string) => string;
|
|
30
|
+
}
|
|
31
|
+
export type TemplateVerdict = {
|
|
32
|
+
f: string;
|
|
33
|
+
keep: true;
|
|
34
|
+
why: 'current' | 'referenced' | 'grace';
|
|
35
|
+
} | {
|
|
36
|
+
f: string;
|
|
37
|
+
keep: false;
|
|
38
|
+
why: 'superseded' | 'dead' | 'private' | 'duplicate';
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* What retention (and `pool doctor`, without the grace) does with each
|
|
42
|
+
* template in one templates dir (decisions 0037, 0044):
|
|
43
|
+
*
|
|
44
|
+
* - per datastore and preset, the newest `keep` are current — in a stack dir
|
|
45
|
+
* that can be bound again, or in a `<name>@shared` dir while any worktree of
|
|
46
|
+
* that name can;
|
|
47
|
+
* - a template a row or a live worktree's record references stays;
|
|
48
|
+
* - a private (`--pristine`) template is never "current": it stays only while
|
|
49
|
+
* referenced;
|
|
50
|
+
* - a per-worktree template whose file name the shared dir also holds is a
|
|
51
|
+
* duplicate of it (the migration adopted it), never "current" either;
|
|
52
|
+
* - anything else goes once older than `grace`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function templateVerdicts(dir: string, files: Array<{
|
|
55
|
+
f: string;
|
|
56
|
+
mtime: number;
|
|
57
|
+
}>, refs: TemplateRefs, keepNewest: number, graceMs: number, sharedFiles?: ReadonlySet<string>): TemplateVerdict[];
|
|
58
|
+
/** The template files of one dir with their mtimes (dot-files — `.root` — excluded). */
|
|
59
|
+
export declare function templateFiles(dir: string): Array<{
|
|
60
|
+
f: string;
|
|
61
|
+
mtime: number;
|
|
62
|
+
}>;
|
|
63
|
+
/** The file names in the shared dir of `dir`'s project (empty for a shared dir itself). */
|
|
64
|
+
export declare function sharedFilesFor(dir: string, root?: string): Set<string>;
|
|
65
|
+
/**
|
|
66
|
+
* Templates are collected by reference (decisions 0037, 0044): see
|
|
67
|
+
* `templateVerdicts`. A marker for a server-side template is dropped WITH its
|
|
68
|
+
* database (its persisted drop command), unless another marker still names it.
|
|
69
|
+
*/
|
|
70
|
+
export declare function pruneTemplates(p: Pick<Policy, 'templatesKeep'> & Partial<Pick<Policy, 'templateGraceMs'>>, root?: string, refs?: TemplateRefs): Promise<number>;
|
|
71
|
+
/**
|
|
72
|
+
* Per-worktree state (decision 0032 — the trigger-file hash cache and the upkeep
|
|
73
|
+
* ledger) for a worktree that no longer exists. It must outlive every
|
|
74
|
+
* environment, because `runly warm` writes it for a worktree with none; so it
|
|
75
|
+
* goes only when its recorded root is gone AND no environment still names the
|
|
76
|
+
* stack. A missing or unreadable record is left alone: it proves nothing.
|
|
77
|
+
*/
|
|
78
|
+
export declare function pruneWorktreeState(journal: Journal, root?: string): number;
|
|
79
|
+
/**
|
|
80
|
+
* What the journal says about templates (decision 0037): the ones rows
|
|
81
|
+
* reference, and which stacks are alive. A stack no row names is alive while
|
|
82
|
+
* the worktree it was recorded for exists — recorded by its worktree state
|
|
83
|
+
* (`worktrees/<stack>/*.json`) or by the `.root` its first bake wrote next to
|
|
84
|
+
* its templates. A stack with no record at all is NOT alive (0.19): its
|
|
85
|
+
* worktree records were pruned because the worktree was gone, and treating it
|
|
86
|
+
* as alive kept the templates of every pruned worktree forever.
|
|
87
|
+
*
|
|
88
|
+
* Shared templates (decision 0044): `<name>@shared` is alive while any stack
|
|
89
|
+
* of that name is; a live worktree's `templates.json` (the template each of
|
|
90
|
+
* its datastores was last restored from) counts as a reference.
|
|
91
|
+
*/
|
|
92
|
+
export declare function templateRefs(journal: Journal, root?: string, templates?: string): TemplateRefs;
|
|
93
|
+
export declare function retentionSweep(journal: Journal, p: Policy): Promise<{
|
|
94
|
+
logs: number;
|
|
95
|
+
templates: number;
|
|
96
|
+
worktrees: number;
|
|
97
|
+
}>;
|