@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,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk retention: nothing runly writes may grow forever. Called from the
|
|
3
|
+
* daemon sweeper (~10 min cadence); every function is idempotent, best-effort,
|
|
4
|
+
* and unit-testable in isolation.
|
|
5
|
+
*/
|
|
6
|
+
import { readdirSync, statSync, rmSync, readFileSync, existsSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { templatesRoot, envsRoot, worktreesRoot } from './paths.js';
|
|
9
|
+
import { logEvent } from './events.js';
|
|
10
|
+
import { cmdTimeoutS, runBounded } from './exec.js';
|
|
11
|
+
import { rotateIfOver } from './logs.js';
|
|
12
|
+
import { hasOtherTemplateOwner, isPrivateTemplateFile, isSharedTemplateDir, parseBakedMarker, sharedTemplateDir, templateProject, withBakeLock } from '../drivers/datastores.js';
|
|
13
|
+
import { readWorktreeTemplates } from './tree-ledger.js';
|
|
14
|
+
const entriesOf = (dir) => {
|
|
15
|
+
try {
|
|
16
|
+
return readdirSync(dir);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Service logs past the cap are rotated once (decision 0038): `<name>.log`
|
|
24
|
+
* becomes `<name>.log.1`, replacing the previous rotation. The writer rotates
|
|
25
|
+
* as it writes; this is the backstop for a file it never reached (a writer
|
|
26
|
+
* from an older daemon, or a log that grew between restarts). Only `*.log`
|
|
27
|
+
* files are touched.
|
|
28
|
+
*/
|
|
29
|
+
export function truncateLogs(p, root = envsRoot()) {
|
|
30
|
+
let rotated = 0;
|
|
31
|
+
for (const envDir of entriesOf(root)) {
|
|
32
|
+
const logDir = join(root, envDir, 'logs');
|
|
33
|
+
for (const logFile of entriesOf(logDir)) {
|
|
34
|
+
if (!logFile.endsWith('.log'))
|
|
35
|
+
continue;
|
|
36
|
+
if (rotateIfOver(join(logDir, logFile), p.logCapBytes))
|
|
37
|
+
rotated++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return rotated;
|
|
41
|
+
}
|
|
42
|
+
/** The group a template file belongs to: `<datastore>-<preset>` before the content key. */
|
|
43
|
+
const templateGroup = (file) => {
|
|
44
|
+
const at = file.lastIndexOf('@');
|
|
45
|
+
return at > 0 ? file.slice(0, at) : '';
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* What retention (and `pool doctor`, without the grace) does with each
|
|
49
|
+
* template in one templates dir (decisions 0037, 0044):
|
|
50
|
+
*
|
|
51
|
+
* - per datastore and preset, the newest `keep` are current — in a stack dir
|
|
52
|
+
* that can be bound again, or in a `<name>@shared` dir while any worktree of
|
|
53
|
+
* that name can;
|
|
54
|
+
* - a template a row or a live worktree's record references stays;
|
|
55
|
+
* - a private (`--pristine`) template is never "current": it stays only while
|
|
56
|
+
* referenced;
|
|
57
|
+
* - a per-worktree template whose file name the shared dir also holds is a
|
|
58
|
+
* duplicate of it (the migration adopted it), never "current" either;
|
|
59
|
+
* - anything else goes once older than `grace`.
|
|
60
|
+
*/
|
|
61
|
+
export function templateVerdicts(dir, files, refs, keepNewest, graceMs, sharedFiles = new Set()) {
|
|
62
|
+
const alive = refs.stackAlive(dir);
|
|
63
|
+
const keep = alive ? keepNewest : 0;
|
|
64
|
+
const seen = new Map();
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const { f, mtime } of [...files].sort((a, b) => b.mtime - a.mtime)) {
|
|
67
|
+
const priv = isPrivateTemplateFile(f);
|
|
68
|
+
const duplicate = !priv && !isSharedTemplateDir(dir) && sharedFiles.has(f);
|
|
69
|
+
let current = false;
|
|
70
|
+
if (!priv && !duplicate) {
|
|
71
|
+
const group = templateGroup(f);
|
|
72
|
+
const rank = seen.get(group) ?? 0;
|
|
73
|
+
seen.set(group, rank + 1);
|
|
74
|
+
current = rank < keep;
|
|
75
|
+
}
|
|
76
|
+
if (current)
|
|
77
|
+
out.push({ f, keep: true, why: 'current' });
|
|
78
|
+
else if (refs.referenced.has(`${dir}/${f}`))
|
|
79
|
+
out.push({ f, keep: true, why: 'referenced' });
|
|
80
|
+
else if (Date.now() - mtime < graceMs)
|
|
81
|
+
out.push({ f, keep: true, why: 'grace' });
|
|
82
|
+
else
|
|
83
|
+
out.push({ f, keep: false, why: priv ? 'private' : duplicate ? 'duplicate' : alive ? 'superseded' : 'dead' });
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/** The template files of one dir with their mtimes (dot-files — `.root` — excluded). */
|
|
88
|
+
export function templateFiles(dir) {
|
|
89
|
+
return entriesOf(dir)
|
|
90
|
+
.filter((f) => !f.startsWith('.'))
|
|
91
|
+
.map((f) => {
|
|
92
|
+
try {
|
|
93
|
+
return { f, mtime: statSync(join(dir, f)).mtimeMs };
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.filter((x) => x !== null);
|
|
100
|
+
}
|
|
101
|
+
/** The file names in the shared dir of `dir`'s project (empty for a shared dir itself). */
|
|
102
|
+
export function sharedFilesFor(dir, root = templatesRoot()) {
|
|
103
|
+
if (isSharedTemplateDir(dir))
|
|
104
|
+
return new Set();
|
|
105
|
+
return new Set(entriesOf(join(root, sharedTemplateDir(templateProject(dir)))));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Templates are collected by reference (decisions 0037, 0044): see
|
|
109
|
+
* `templateVerdicts`. A marker for a server-side template is dropped WITH its
|
|
110
|
+
* database (its persisted drop command), unless another marker still names it.
|
|
111
|
+
*/
|
|
112
|
+
export async function pruneTemplates(p, root = templatesRoot(), refs = { referenced: new Set(), stackAlive: () => true }) {
|
|
113
|
+
let pruned = 0;
|
|
114
|
+
const grace = p.templateGraceMs ?? 0;
|
|
115
|
+
for (const stackDir of entriesOf(root)) {
|
|
116
|
+
const dir = join(root, stackDir);
|
|
117
|
+
if (!existsSync(dir))
|
|
118
|
+
continue;
|
|
119
|
+
// The dir-scoped bake lock (the dir name IS the lock key — a stack id or
|
|
120
|
+
// `<name>@shared`): pruning was the one remaining writer mutating this
|
|
121
|
+
// dir outside it, reopening the deleted-mid-restore race the lock exists
|
|
122
|
+
// to close.
|
|
123
|
+
pruned += await withBakeLock(stackDir, async () => {
|
|
124
|
+
let count = 0;
|
|
125
|
+
for (const v of templateVerdicts(stackDir, templateFiles(dir), refs, p.templatesKeep, grace, sharedFilesFor(stackDir, root))) {
|
|
126
|
+
if (v.keep)
|
|
127
|
+
continue;
|
|
128
|
+
const f = v.f;
|
|
129
|
+
const full = join(dir, f);
|
|
130
|
+
if (f.endsWith('.baked')) {
|
|
131
|
+
try {
|
|
132
|
+
const marker = parseBakedMarker(readFileSync(full, 'utf8'));
|
|
133
|
+
// Another marker naming the same database (an adopted template's
|
|
134
|
+
// other copy): only this marker goes, the database stays.
|
|
135
|
+
if (!hasOtherTemplateOwner(full, marker.ns) && marker.drop) {
|
|
136
|
+
// This command came from a manifest that may no longer exist on
|
|
137
|
+
// disk. Re-executing it silently is the part that deserves a
|
|
138
|
+
// record, so the state dir stays auditable.
|
|
139
|
+
logEvent({ level: 'info', kind: 'retention', detail: `dropping baked template via persisted command from ${f}` });
|
|
140
|
+
const r = await runBounded(marker.drop, refs.dropCwd?.(stackDir) ?? root, Math.min(60, cmdTimeoutS()));
|
|
141
|
+
if (r.code !== 0 || r.timedOut) {
|
|
142
|
+
// The marker is the only record that names the database: keep it
|
|
143
|
+
// for the next sweep, and for 'runly pool doctor' to show.
|
|
144
|
+
logEvent({ level: 'warn', kind: 'retention', detail: `the drop of template ${marker.ns} (${f}) did not confirm (${r.timedOut ? 'timed out' : `exit ${r.code}`}) — its marker stays: ${r.output.slice(-200)}` });
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
/* unreadable marker — still prune the file */
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
rmSync(full, { force: true });
|
|
154
|
+
count++;
|
|
155
|
+
}
|
|
156
|
+
// A stack dir with nothing left goes too (its `.root` record with it).
|
|
157
|
+
if (entriesOf(dir).every((f) => f.startsWith('.')))
|
|
158
|
+
rmSync(dir, { recursive: true, force: true });
|
|
159
|
+
return count;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return pruned;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Per-worktree state (decision 0032 — the trigger-file hash cache and the upkeep
|
|
166
|
+
* ledger) for a worktree that no longer exists. It must outlive every
|
|
167
|
+
* environment, because `runly warm` writes it for a worktree with none; so it
|
|
168
|
+
* goes only when its recorded root is gone AND no environment still names the
|
|
169
|
+
* stack. A missing or unreadable record is left alone: it proves nothing.
|
|
170
|
+
*/
|
|
171
|
+
export function pruneWorktreeState(journal, root = worktreesRoot()) {
|
|
172
|
+
let pruned = 0;
|
|
173
|
+
for (const stackId of entriesOf(root)) {
|
|
174
|
+
const dir = join(root, stackId);
|
|
175
|
+
try {
|
|
176
|
+
const recordedRoot = ['ledger.json', 'triggers.json', 'builds.json', 'templates.json']
|
|
177
|
+
.map((f) => {
|
|
178
|
+
try {
|
|
179
|
+
return JSON.parse(readFileSync(join(dir, f), 'utf8')).root;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
.find((r) => typeof r === 'string');
|
|
186
|
+
if (recordedRoot === undefined || existsSync(recordedRoot))
|
|
187
|
+
continue;
|
|
188
|
+
if (journal.envsForStack(stackId).length > 0)
|
|
189
|
+
continue;
|
|
190
|
+
rmSync(dir, { recursive: true, force: true });
|
|
191
|
+
pruned++;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
/* no readable ledger — leave it */
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return pruned;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* What the journal says about templates (decision 0037): the ones rows
|
|
201
|
+
* reference, and which stacks are alive. A stack no row names is alive while
|
|
202
|
+
* the worktree it was recorded for exists — recorded by its worktree state
|
|
203
|
+
* (`worktrees/<stack>/*.json`) or by the `.root` its first bake wrote next to
|
|
204
|
+
* its templates. A stack with no record at all is NOT alive (0.19): its
|
|
205
|
+
* worktree records were pruned because the worktree was gone, and treating it
|
|
206
|
+
* as alive kept the templates of every pruned worktree forever.
|
|
207
|
+
*
|
|
208
|
+
* Shared templates (decision 0044): `<name>@shared` is alive while any stack
|
|
209
|
+
* of that name is; a live worktree's `templates.json` (the template each of
|
|
210
|
+
* its datastores was last restored from) counts as a reference.
|
|
211
|
+
*/
|
|
212
|
+
export function templateRefs(journal, root = worktreesRoot(), templates = templatesRoot()) {
|
|
213
|
+
const referenced = new Set();
|
|
214
|
+
const named = new Set();
|
|
215
|
+
for (const env of journal.allEnvs()) {
|
|
216
|
+
named.add(env.stack);
|
|
217
|
+
for (const ref of Object.values(env.templates ?? {}))
|
|
218
|
+
referenced.add(ref);
|
|
219
|
+
}
|
|
220
|
+
for (const copy of journal.allDbCopies()) {
|
|
221
|
+
named.add(copy.stack);
|
|
222
|
+
if (copy.template)
|
|
223
|
+
referenced.add(copy.template);
|
|
224
|
+
}
|
|
225
|
+
const recordedRootOf = (stackId) => {
|
|
226
|
+
try {
|
|
227
|
+
const r = readFileSync(join(templates, stackId, '.root'), 'utf8').trim();
|
|
228
|
+
if (r)
|
|
229
|
+
return r;
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
/* not recorded at bake time (an older runly) */
|
|
233
|
+
}
|
|
234
|
+
for (const f of ['ledger.json', 'triggers.json', 'builds.json', 'templates.json']) {
|
|
235
|
+
try {
|
|
236
|
+
const r = JSON.parse(readFileSync(join(root, stackId, f), 'utf8')).root;
|
|
237
|
+
if (typeof r === 'string')
|
|
238
|
+
return r;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
/* next */
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
};
|
|
246
|
+
const stackAliveOne = (stackId) => {
|
|
247
|
+
if (named.has(stackId))
|
|
248
|
+
return true;
|
|
249
|
+
// No row names it: alive while the worktree it describes exists. No
|
|
250
|
+
// record at all means that worktree's records were pruned: not alive.
|
|
251
|
+
const recorded = recordedRootOf(stackId);
|
|
252
|
+
return recorded !== undefined && existsSync(recorded);
|
|
253
|
+
};
|
|
254
|
+
// A live worktree's current templates (decision 0044).
|
|
255
|
+
for (const stackId of entriesOf(root)) {
|
|
256
|
+
const rec = readWorktreeTemplates(stackId);
|
|
257
|
+
if (!rec || !existsSync(rec.root))
|
|
258
|
+
continue;
|
|
259
|
+
for (const ref of Object.values(rec.refs))
|
|
260
|
+
referenced.add(ref);
|
|
261
|
+
}
|
|
262
|
+
const projectStacks = (project) => {
|
|
263
|
+
const ids = new Set();
|
|
264
|
+
for (const id of named)
|
|
265
|
+
if (templateProject(id) === project)
|
|
266
|
+
ids.add(id);
|
|
267
|
+
for (const d of [root, templates])
|
|
268
|
+
for (const id of entriesOf(d))
|
|
269
|
+
if (!isSharedTemplateDir(id) && templateProject(id) === project)
|
|
270
|
+
ids.add(id);
|
|
271
|
+
return [...ids];
|
|
272
|
+
};
|
|
273
|
+
const stackAlive = (stackId) => {
|
|
274
|
+
if (!isSharedTemplateDir(stackId))
|
|
275
|
+
return stackAliveOne(stackId);
|
|
276
|
+
return projectStacks(templateProject(stackId)).some(stackAliveOne);
|
|
277
|
+
};
|
|
278
|
+
const ownRoot = (stackId) => {
|
|
279
|
+
for (const env of journal.allEnvs())
|
|
280
|
+
if (env.stack === stackId)
|
|
281
|
+
return env.stackRoot;
|
|
282
|
+
try {
|
|
283
|
+
const r = readFileSync(join(templates, stackId, '.root'), 'utf8').trim();
|
|
284
|
+
if (r)
|
|
285
|
+
return r;
|
|
286
|
+
}
|
|
287
|
+
catch { /* none recorded */ }
|
|
288
|
+
return undefined;
|
|
289
|
+
};
|
|
290
|
+
const dropCwd = (stackId) => {
|
|
291
|
+
const own = ownRoot(stackId);
|
|
292
|
+
if (own !== undefined && existsSync(own))
|
|
293
|
+
return own;
|
|
294
|
+
// Stack ids are `<manifest name>-<8-char hash of the root>`; a shared dir is `<name>@shared`.
|
|
295
|
+
const project = templateProject(stackId);
|
|
296
|
+
for (const env of journal.allEnvs())
|
|
297
|
+
if (templateProject(env.stack) === project && existsSync(env.stackRoot))
|
|
298
|
+
return env.stackRoot;
|
|
299
|
+
for (const copy of journal.allDbCopies())
|
|
300
|
+
if (templateProject(copy.stack) === project && existsSync(copy.stackRoot))
|
|
301
|
+
return copy.stackRoot;
|
|
302
|
+
for (const sibling of entriesOf(templates)) {
|
|
303
|
+
if (sibling === stackId || templateProject(sibling) !== project)
|
|
304
|
+
continue;
|
|
305
|
+
const r = ownRoot(sibling);
|
|
306
|
+
if (r !== undefined && existsSync(r))
|
|
307
|
+
return r;
|
|
308
|
+
}
|
|
309
|
+
return templates;
|
|
310
|
+
};
|
|
311
|
+
return { referenced, stackAlive, dropCwd };
|
|
312
|
+
}
|
|
313
|
+
export async function retentionSweep(journal, p) {
|
|
314
|
+
// Templates first: they read the worktree records pruneWorktreeState removes.
|
|
315
|
+
const templates = await pruneTemplates(p, templatesRoot(), templateRefs(journal));
|
|
316
|
+
return {
|
|
317
|
+
worktrees: pruneWorktreeState(journal),
|
|
318
|
+
logs: truncateLogs(p),
|
|
319
|
+
templates,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=retention.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/core/retention.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cap on a kernel-reported sleep gap. A pardon shifts EVERY lease and idle
|
|
3
|
+
* deadline by the gap, so a corrupt or absurd record (bad clock at sleep time,
|
|
4
|
+
* garbage sysctl) must not push deadlines out by years. Past the cap we prefer
|
|
5
|
+
* letting deadlines lapse — losing a lease is designed to be worthless (§10):
|
|
6
|
+
* the env returns warm and the next verb rebinds.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MAX_KERNEL_PARDON_MS: number;
|
|
9
|
+
/**
|
|
10
|
+
* Parse one sysctl timeval line — `{ sec = 1784497430, usec = 123456 } Sat
|
|
11
|
+
* Jul 19 23:43:50 2026` — to milliseconds since the epoch. Returns null on
|
|
12
|
+
* anything else: a missing or garbage sysctl must never break the sweep.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseSysctlTimeval(line: string | null | undefined): number | null;
|
|
15
|
+
export interface KernelSleepArgs {
|
|
16
|
+
/** kern.sleeptime as epoch ms, or null when unreadable. */
|
|
17
|
+
sleeptime: number | null;
|
|
18
|
+
/** kern.waketime as epoch ms, or null when unreadable. */
|
|
19
|
+
waketime: number | null;
|
|
20
|
+
/** waketime of the last sleep this daemon already pardoned (0 = none yet). */
|
|
21
|
+
lastPardonedWake: number;
|
|
22
|
+
/** Wall-clock instant of the PREVIOUS sweep tick. */
|
|
23
|
+
lastSweepWall: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The pardon decision: did the kernel record a wake that happened since the
|
|
27
|
+
* previous sweep tick, for a sleep this daemon has not pardoned yet? Returns
|
|
28
|
+
* the gap to pardon (waketime − sleeptime) or null.
|
|
29
|
+
*
|
|
30
|
+
* Guards, in order:
|
|
31
|
+
* - unreadable record → null (fall back to the wall-vs-monotonic detector);
|
|
32
|
+
* - wake already pardoned → null (the two detectors must not double-pardon
|
|
33
|
+
* one sleep — the caller tracks the last pardoned waketime);
|
|
34
|
+
* - wake at/before the previous sweep tick → null (a stale record: the
|
|
35
|
+
* machine's last wake predates this daemon's current sweep window, e.g.
|
|
36
|
+
* the boot-era record seen on daemon start, or a sleep the wall-vs-mono
|
|
37
|
+
* detector already pardoned on an earlier sweep);
|
|
38
|
+
* - sleeptime not strictly before waketime → null (mid-transition/corrupt);
|
|
39
|
+
* - gap beyond MAX_KERNEL_PARDON_MS → null (absurd value).
|
|
40
|
+
*/
|
|
41
|
+
export declare function kernelSleepGap(a: KernelSleepArgs): number | null;
|
|
42
|
+
/**
|
|
43
|
+
* Read the kernel's sleep/wake record. Synchronous and cheap (~1 ms) at the
|
|
44
|
+
* 15 s sweep cadence; darwin-only callers. Any failure — missing binary,
|
|
45
|
+
* unknown OID, timeout — returns null legs: the sweep falls back to the
|
|
46
|
+
* wall-vs-monotonic detector and must never be broken by sysctl.
|
|
47
|
+
*/
|
|
48
|
+
export declare function readKernelSleepRecord(): {
|
|
49
|
+
sleeptime: number | null;
|
|
50
|
+
waketime: number | null;
|
|
51
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel-record sleep detection (macOS).
|
|
3
|
+
*
|
|
4
|
+
* The sweeper's original sleep pardon inferred suspend from wall-clock
|
|
5
|
+
* outrunning performance.now(). That is correct on Linux, where
|
|
6
|
+
* CLOCK_MONOTONIC halts through suspend — but INERT on Apple Silicon, where
|
|
7
|
+
* mach_absolute_time keeps advancing through real sleep: a genuine lid-close
|
|
8
|
+
* test (2026-07-19) produced no divergence, no pardon, no event of any kind,
|
|
9
|
+
* and a dead lease. On darwin the daemon therefore reads the kernel's OWN
|
|
10
|
+
* record of the last sleep/wake transition instead:
|
|
11
|
+
*
|
|
12
|
+
* $ sysctl -n kern.sleeptime kern.waketime
|
|
13
|
+
* { sec = 1784497430, usec = 123456 } Sat Jul 19 23:43:50 2026
|
|
14
|
+
* { sec = 1784499830, usec = 654321 } Sun Jul 20 00:23:50 2026
|
|
15
|
+
*
|
|
16
|
+
* The parsing and the pardon decision are pure exported functions (the
|
|
17
|
+
* policy() pattern) because a real lid close cannot be
|
|
18
|
+
* automated and this repo forbids test-only interleaving hooks — the unit
|
|
19
|
+
* tests in tests/sleep-pardon.test.ts encode the confirmed scenario; the
|
|
20
|
+
* end-to-end proof remains the manual lid-close protocol (docs/soak.md).
|
|
21
|
+
*/
|
|
22
|
+
import { execFileSync } from 'node:child_process';
|
|
23
|
+
/**
|
|
24
|
+
* Cap on a kernel-reported sleep gap. A pardon shifts EVERY lease and idle
|
|
25
|
+
* deadline by the gap, so a corrupt or absurd record (bad clock at sleep time,
|
|
26
|
+
* garbage sysctl) must not push deadlines out by years. Past the cap we prefer
|
|
27
|
+
* letting deadlines lapse — losing a lease is designed to be worthless (§10):
|
|
28
|
+
* the env returns warm and the next verb rebinds.
|
|
29
|
+
*/
|
|
30
|
+
export const MAX_KERNEL_PARDON_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
31
|
+
/**
|
|
32
|
+
* Parse one sysctl timeval line — `{ sec = 1784497430, usec = 123456 } Sat
|
|
33
|
+
* Jul 19 23:43:50 2026` — to milliseconds since the epoch. Returns null on
|
|
34
|
+
* anything else: a missing or garbage sysctl must never break the sweep.
|
|
35
|
+
*/
|
|
36
|
+
export function parseSysctlTimeval(line) {
|
|
37
|
+
if (typeof line !== 'string')
|
|
38
|
+
return null;
|
|
39
|
+
const m = /\{\s*sec\s*=\s*(\d+)\s*,\s*usec\s*=\s*(\d+)\s*\}/.exec(line);
|
|
40
|
+
if (!m)
|
|
41
|
+
return null;
|
|
42
|
+
const sec = Number(m[1]);
|
|
43
|
+
const usec = Number(m[2]);
|
|
44
|
+
if (!Number.isFinite(sec) || !Number.isFinite(usec))
|
|
45
|
+
return null;
|
|
46
|
+
return sec * 1000 + Math.floor(usec / 1000);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The pardon decision: did the kernel record a wake that happened since the
|
|
50
|
+
* previous sweep tick, for a sleep this daemon has not pardoned yet? Returns
|
|
51
|
+
* the gap to pardon (waketime − sleeptime) or null.
|
|
52
|
+
*
|
|
53
|
+
* Guards, in order:
|
|
54
|
+
* - unreadable record → null (fall back to the wall-vs-monotonic detector);
|
|
55
|
+
* - wake already pardoned → null (the two detectors must not double-pardon
|
|
56
|
+
* one sleep — the caller tracks the last pardoned waketime);
|
|
57
|
+
* - wake at/before the previous sweep tick → null (a stale record: the
|
|
58
|
+
* machine's last wake predates this daemon's current sweep window, e.g.
|
|
59
|
+
* the boot-era record seen on daemon start, or a sleep the wall-vs-mono
|
|
60
|
+
* detector already pardoned on an earlier sweep);
|
|
61
|
+
* - sleeptime not strictly before waketime → null (mid-transition/corrupt);
|
|
62
|
+
* - gap beyond MAX_KERNEL_PARDON_MS → null (absurd value).
|
|
63
|
+
*/
|
|
64
|
+
export function kernelSleepGap(a) {
|
|
65
|
+
if (a.sleeptime === null || a.waketime === null)
|
|
66
|
+
return null;
|
|
67
|
+
if (a.waketime <= a.lastPardonedWake)
|
|
68
|
+
return null;
|
|
69
|
+
if (a.waketime <= a.lastSweepWall)
|
|
70
|
+
return null;
|
|
71
|
+
if (a.sleeptime >= a.waketime)
|
|
72
|
+
return null;
|
|
73
|
+
const gap = a.waketime - a.sleeptime;
|
|
74
|
+
if (gap > MAX_KERNEL_PARDON_MS)
|
|
75
|
+
return null;
|
|
76
|
+
return gap;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Read the kernel's sleep/wake record. Synchronous and cheap (~1 ms) at the
|
|
80
|
+
* 15 s sweep cadence; darwin-only callers. Any failure — missing binary,
|
|
81
|
+
* unknown OID, timeout — returns null legs: the sweep falls back to the
|
|
82
|
+
* wall-vs-monotonic detector and must never be broken by sysctl.
|
|
83
|
+
*/
|
|
84
|
+
export function readKernelSleepRecord() {
|
|
85
|
+
try {
|
|
86
|
+
const out = execFileSync('/usr/sbin/sysctl', ['-n', 'kern.sleeptime', 'kern.waketime'], {
|
|
87
|
+
encoding: 'utf8',
|
|
88
|
+
// Never the daemon's stderr: that is daemon.log.
|
|
89
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
90
|
+
timeout: 2_000,
|
|
91
|
+
});
|
|
92
|
+
const [sleepLine, wakeLine] = out.split('\n');
|
|
93
|
+
return { sleeptime: parseSysctlTimeval(sleepLine), waketime: parseSysctlTimeval(wakeLine) };
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return { sleeptime: null, waketime: null };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=sleep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/core/sleep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,UAAU;AAExE;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAA+B;IAChE,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,CAAC,GAAG,kDAAkD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxE,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACjE,OAAO,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AAC9C,CAAC;AAaD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,cAAc,CAAC,CAAkB;IAC/C,IAAI,CAAC,CAAC,SAAS,KAAK,IAAI,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7D,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,gBAAgB;QAAE,OAAO,IAAI,CAAC;IAClD,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,aAAa;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,SAAS,CAAC;IACrC,IAAI,GAAG,GAAG,oBAAoB;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,kBAAkB,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,CAAC,EAAE;YACtF,QAAQ,EAAE,MAAM;YAChB,iDAAiD;YACjD,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;QACH,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,EAAE,SAAS,EAAE,kBAAkB,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC9F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface TreeLedgerFile {
|
|
2
|
+
/** The physical worktree this ledger describes — retention reads it. */
|
|
3
|
+
root: string;
|
|
4
|
+
fingerprints: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export declare const worktreeStateDir: (stackId: string) => string;
|
|
7
|
+
export declare function readTreeLedger(stackId: string): Record<string, string>;
|
|
8
|
+
export declare function writeTreeLedger(stackId: string, root: string, fingerprints: Record<string, string>): void;
|
|
9
|
+
/**
|
|
10
|
+
* `runly destroy`: forget the command rules that declare no `outputs:` — the
|
|
11
|
+
* pool that destroys an environment usually resets the worktree next
|
|
12
|
+
* (`git clean -fdx`), which removes what they produced, and nothing else could
|
|
13
|
+
* tell. Rules with `outputs:` stay: their outputs are checked at every bind.
|
|
14
|
+
*/
|
|
15
|
+
export declare function forgetRulesWithoutOutputs(stackId: string, root: string, keep: (key: string) => boolean): number;
|
|
16
|
+
/**
|
|
17
|
+
* The template each datastore and preset of this worktree was last restored
|
|
18
|
+
* from (`worktrees/<stack>/templates.json`, decision 0044). Templates are
|
|
19
|
+
* shared by every worktree of a manifest name, so "the newest per datastore
|
|
20
|
+
* and preset" no longer says which one a given worktree uses; retention keeps
|
|
21
|
+
* every template a live worktree recorded here, with or without an
|
|
22
|
+
* environment — an `up` after `destroy` stays a restore, not a bake (0039).
|
|
23
|
+
* Written synchronously (no await between read and write), so the daemon's
|
|
24
|
+
* parallel datastore restores cannot lose each other's entries.
|
|
25
|
+
*/
|
|
26
|
+
export interface WorktreeTemplatesFile {
|
|
27
|
+
root: string;
|
|
28
|
+
/** `<datastore>/<preset>` → `<templates dir>/<file>`. */
|
|
29
|
+
refs: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
export declare function readWorktreeTemplates(stackId: string): WorktreeTemplatesFile | undefined;
|
|
32
|
+
export declare function recordWorktreeTemplate(stackId: string, root: string, key: string, ref: string): void;
|
|
33
|
+
/** `--pristine`: forget what was applied, so every rule runs again. */
|
|
34
|
+
export declare function clearTreeLedger(stackId: string): void;
|
|
35
|
+
/** Which ledger a key belongs to: command rules are the worktree's, `@` keys the environment's. */
|
|
36
|
+
export declare function isTreeKey(key: string): boolean;
|
|
37
|
+
export declare const pickTreeKeys: (fps: Record<string, string>) => Record<string, string>;
|
|
38
|
+
/** The environment's half: `@`-built-in rules only (`@source`/`@built:` stamps are gone). */
|
|
39
|
+
export declare const pickEnvKeys: (fps: Record<string, string>) => Record<string, string>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worktree's upkeep ledger (decision 0032): which command upkeep rule was
|
|
3
|
+
* last applied for which content of its `when:` files.
|
|
4
|
+
*
|
|
5
|
+
* Upkeep commands (an install, a codegen) write into the caller's worktree, so
|
|
6
|
+
* whether one is due is a fact about the worktree, not about an environment;
|
|
7
|
+
* `runly warm` reads and writes the same ledger a bind does. It is kept in the
|
|
8
|
+
* state root, keyed by stack id (one stack = one physical worktree = one
|
|
9
|
+
* environment), never in the worktree. The `@`-built-in rules act on an
|
|
10
|
+
* environment's data and stay on the environment row. Writers hold the
|
|
11
|
+
* engine's per-worktree lock; the file is replaced atomically.
|
|
12
|
+
*
|
|
13
|
+
* Builds are NOT recorded here or anywhere: a service's `build:` runs on every
|
|
14
|
+
* bind that starts it, and the build tool decides what is up to date.
|
|
15
|
+
*/
|
|
16
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { worktreesRoot } from './paths.js';
|
|
19
|
+
export const worktreeStateDir = (stackId) => join(worktreesRoot(), stackId);
|
|
20
|
+
const ledgerPath = (stackId) => join(worktreeStateDir(stackId), 'ledger.json');
|
|
21
|
+
export function readTreeLedger(stackId) {
|
|
22
|
+
try {
|
|
23
|
+
const raw = JSON.parse(readFileSync(ledgerPath(stackId), 'utf8'));
|
|
24
|
+
return raw.fingerprints && typeof raw.fingerprints === 'object' ? { ...raw.fingerprints } : {};
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return {}; // absent or torn: every rule and build counts as not yet applied
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function writeTreeLedger(stackId, root, fingerprints) {
|
|
31
|
+
const dir = worktreeStateDir(stackId);
|
|
32
|
+
mkdirSync(dir, { recursive: true });
|
|
33
|
+
const tmp = join(dir, `ledger.json.${process.pid}.tmp`);
|
|
34
|
+
writeFileSync(tmp, JSON.stringify({ root, fingerprints }));
|
|
35
|
+
renameSync(tmp, ledgerPath(stackId));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `runly destroy`: forget the command rules that declare no `outputs:` — the
|
|
39
|
+
* pool that destroys an environment usually resets the worktree next
|
|
40
|
+
* (`git clean -fdx`), which removes what they produced, and nothing else could
|
|
41
|
+
* tell. Rules with `outputs:` stay: their outputs are checked at every bind.
|
|
42
|
+
*/
|
|
43
|
+
export function forgetRulesWithoutOutputs(stackId, root, keep) {
|
|
44
|
+
const fps = readTreeLedger(stackId);
|
|
45
|
+
const kept = Object.fromEntries(Object.entries(fps).filter(([k]) => keep(k)));
|
|
46
|
+
const dropped = Object.keys(fps).length - Object.keys(kept).length;
|
|
47
|
+
if (dropped > 0)
|
|
48
|
+
writeTreeLedger(stackId, root, kept);
|
|
49
|
+
return dropped;
|
|
50
|
+
}
|
|
51
|
+
const templatesPath = (stackId) => join(worktreeStateDir(stackId), 'templates.json');
|
|
52
|
+
export function readWorktreeTemplates(stackId) {
|
|
53
|
+
try {
|
|
54
|
+
const raw = JSON.parse(readFileSync(templatesPath(stackId), 'utf8'));
|
|
55
|
+
if (typeof raw.root !== 'string' || !raw.refs || typeof raw.refs !== 'object')
|
|
56
|
+
return undefined;
|
|
57
|
+
return { root: raw.root, refs: { ...raw.refs } };
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function recordWorktreeTemplate(stackId, root, key, ref) {
|
|
64
|
+
const cur = readWorktreeTemplates(stackId);
|
|
65
|
+
if (cur?.root === root && cur.refs[key] === ref)
|
|
66
|
+
return;
|
|
67
|
+
const refs = { ...(cur?.root === root ? cur.refs : {}), [key]: ref };
|
|
68
|
+
const dir = worktreeStateDir(stackId);
|
|
69
|
+
try {
|
|
70
|
+
mkdirSync(dir, { recursive: true });
|
|
71
|
+
const tmp = join(dir, `templates.json.${process.pid}.tmp`);
|
|
72
|
+
writeFileSync(tmp, JSON.stringify({ root, refs }));
|
|
73
|
+
renameSync(tmp, templatesPath(stackId));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* best effort: the template is then kept only while a row references it */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** `--pristine`: forget what was applied, so every rule runs again. */
|
|
80
|
+
export function clearTreeLedger(stackId) {
|
|
81
|
+
rmSync(ledgerPath(stackId), { force: true });
|
|
82
|
+
}
|
|
83
|
+
/** Which ledger a key belongs to: command rules are the worktree's, `@` keys the environment's. */
|
|
84
|
+
export function isTreeKey(key) {
|
|
85
|
+
if (key.startsWith('@'))
|
|
86
|
+
return false; // a leftover stamp from an older runly
|
|
87
|
+
return !key.includes(' -> @');
|
|
88
|
+
}
|
|
89
|
+
export const pickTreeKeys = (fps) => Object.fromEntries(Object.entries(fps).filter(([k]) => isTreeKey(k)));
|
|
90
|
+
/** The environment's half: `@`-built-in rules only (`@source`/`@built:` stamps are gone). */
|
|
91
|
+
export const pickEnvKeys = (fps) => Object.fromEntries(Object.entries(fps).filter(([k]) => !k.startsWith('@') && k.includes(' -> @')));
|
|
92
|
+
//# sourceMappingURL=tree-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-ledger.js","sourceRoot":"","sources":["../../src/core/tree-ledger.ts"],"names":[],"mappings":"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"}
|