@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.
Files changed (128) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +157 -2
  3. package/dist/cli/client.d.ts +80 -0
  4. package/dist/cli/client.js +294 -0
  5. package/dist/cli/client.js.map +1 -0
  6. package/dist/cli/daemon-unit.d.ts +58 -0
  7. package/dist/cli/daemon-unit.js +289 -0
  8. package/dist/cli/daemon-unit.js.map +1 -0
  9. package/dist/cli/index.d.ts +2 -0
  10. package/dist/cli/index.js +1482 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/cli/install.d.ts +9 -0
  13. package/dist/cli/install.js +53 -0
  14. package/dist/cli/install.js.map +1 -0
  15. package/dist/cli/logs.d.ts +43 -0
  16. package/dist/cli/logs.js +232 -0
  17. package/dist/cli/logs.js.map +1 -0
  18. package/dist/cli/tether.d.ts +3 -0
  19. package/dist/cli/tether.js +65 -0
  20. package/dist/cli/tether.js.map +1 -0
  21. package/dist/cli/watchdog.d.ts +1 -0
  22. package/dist/cli/watchdog.js +81 -0
  23. package/dist/cli/watchdog.js.map +1 -0
  24. package/dist/core/builds.d.ts +20 -0
  25. package/dist/core/builds.js +93 -0
  26. package/dist/core/builds.js.map +1 -0
  27. package/dist/core/caller-env.d.ts +17 -0
  28. package/dist/core/caller-env.js +77 -0
  29. package/dist/core/caller-env.js.map +1 -0
  30. package/dist/core/diagnostics.d.ts +68 -0
  31. package/dist/core/diagnostics.js +34 -0
  32. package/dist/core/diagnostics.js.map +1 -0
  33. package/dist/core/env-vars.d.ts +35 -0
  34. package/dist/core/env-vars.js +32 -0
  35. package/dist/core/env-vars.js.map +1 -0
  36. package/dist/core/events.d.ts +10 -0
  37. package/dist/core/events.js +42 -0
  38. package/dist/core/events.js.map +1 -0
  39. package/dist/core/exec.d.ts +22 -0
  40. package/dist/core/exec.js +84 -0
  41. package/dist/core/exec.js.map +1 -0
  42. package/dist/core/journal.d.ts +227 -0
  43. package/dist/core/journal.js +395 -0
  44. package/dist/core/journal.js.map +1 -0
  45. package/dist/core/logs.d.ts +56 -0
  46. package/dist/core/logs.js +259 -0
  47. package/dist/core/logs.js.map +1 -0
  48. package/dist/core/manifest.d.ts +292 -0
  49. package/dist/core/manifest.js +179 -0
  50. package/dist/core/manifest.js.map +1 -0
  51. package/dist/core/namespaces.d.ts +7 -0
  52. package/dist/core/namespaces.js +59 -0
  53. package/dist/core/namespaces.js.map +1 -0
  54. package/dist/core/paths.d.ts +31 -0
  55. package/dist/core/paths.js +65 -0
  56. package/dist/core/paths.js.map +1 -0
  57. package/dist/core/policy.d.ts +94 -0
  58. package/dist/core/policy.js +122 -0
  59. package/dist/core/policy.js.map +1 -0
  60. package/dist/core/ports.d.ts +52 -0
  61. package/dist/core/ports.js +94 -0
  62. package/dist/core/ports.js.map +1 -0
  63. package/dist/core/presets.d.ts +25 -0
  64. package/dist/core/presets.js +86 -0
  65. package/dist/core/presets.js.map +1 -0
  66. package/dist/core/procscan.d.ts +111 -0
  67. package/dist/core/procscan.js +342 -0
  68. package/dist/core/procscan.js.map +1 -0
  69. package/dist/core/retention.d.ts +97 -0
  70. package/dist/core/retention.js +322 -0
  71. package/dist/core/retention.js.map +1 -0
  72. package/dist/core/sleep.d.ts +51 -0
  73. package/dist/core/sleep.js +99 -0
  74. package/dist/core/sleep.js.map +1 -0
  75. package/dist/core/tree-ledger.d.ts +39 -0
  76. package/dist/core/tree-ledger.js +92 -0
  77. package/dist/core/tree-ledger.js.map +1 -0
  78. package/dist/core/types.d.ts +21 -0
  79. package/dist/core/types.js +6 -0
  80. package/dist/core/types.js.map +1 -0
  81. package/dist/core/units.d.ts +20 -0
  82. package/dist/core/units.js +69 -0
  83. package/dist/core/units.js.map +1 -0
  84. package/dist/core/upkeep.d.ts +90 -0
  85. package/dist/core/upkeep.js +176 -0
  86. package/dist/core/upkeep.js.map +1 -0
  87. package/dist/core/util.d.ts +46 -0
  88. package/dist/core/util.js +168 -0
  89. package/dist/core/util.js.map +1 -0
  90. package/dist/core/version.d.ts +39 -0
  91. package/dist/core/version.js +144 -0
  92. package/dist/core/version.js.map +1 -0
  93. package/dist/core/worktree.d.ts +51 -0
  94. package/dist/core/worktree.js +309 -0
  95. package/dist/core/worktree.js.map +1 -0
  96. package/dist/daemon/budget.d.ts +155 -0
  97. package/dist/daemon/budget.js +288 -0
  98. package/dist/daemon/budget.js.map +1 -0
  99. package/dist/daemon/election.d.ts +9 -0
  100. package/dist/daemon/election.js +140 -0
  101. package/dist/daemon/election.js.map +1 -0
  102. package/dist/daemon/engine.d.ts +1703 -0
  103. package/dist/daemon/engine.js +5305 -0
  104. package/dist/daemon/engine.js.map +1 -0
  105. package/dist/daemon/index.d.ts +2 -0
  106. package/dist/daemon/index.js +412 -0
  107. package/dist/daemon/index.js.map +1 -0
  108. package/dist/daemon/proxy.d.ts +108 -0
  109. package/dist/daemon/proxy.js +615 -0
  110. package/dist/daemon/proxy.js.map +1 -0
  111. package/dist/daemon/supervisor.d.ts +101 -0
  112. package/dist/daemon/supervisor.js +493 -0
  113. package/dist/daemon/supervisor.js.map +1 -0
  114. package/dist/drivers/appliances.d.ts +11 -0
  115. package/dist/drivers/appliances.js +174 -0
  116. package/dist/drivers/appliances.js.map +1 -0
  117. package/dist/drivers/datastores.d.ts +144 -0
  118. package/dist/drivers/datastores.js +775 -0
  119. package/dist/drivers/datastores.js.map +1 -0
  120. package/dist/drivers/preview.d.ts +69 -0
  121. package/dist/drivers/preview.js +605 -0
  122. package/dist/drivers/preview.js.map +1 -0
  123. package/dist/drivers/types.d.ts +70 -0
  124. package/dist/drivers/types.js +13 -0
  125. package/dist/drivers/types.js.map +1 -0
  126. package/package.json +63 -4
  127. package/schema/backlot.schema.json +549 -0
  128. 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":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAQ3C,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,OAAO,CAAC,CAAC;AAC5F,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,CAAC;AAEvF,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAA4B,CAAC;QAC7F,OAAO,GAAG,CAAC,YAAY,IAAI,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACjG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,iEAAiE;IAC9E,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,IAAY,EAAE,YAAoC;IACjG,MAAM,GAAG,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACtC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC;IACxD,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,YAAY,EAA2B,CAAC,CAAC,CAAC;IACpF,UAAU,CAAC,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAAe,EAAE,IAAY,EAAE,IAA8B;IACrG,MAAM,GAAG,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IACnE,IAAI,OAAO,GAAG,CAAC;QAAE,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACtD,OAAO,OAAO,CAAC;AACjB,CAAC;AAiBD,MAAM,aAAa,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC,CAAC;AAE7F,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAmC,CAAC;QACvG,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QAChG,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAe,EAAE,IAAY,EAAE,GAAW,EAAE,GAAW;IAC5F,MAAM,GAAG,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,GAAG,EAAE,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG;QAAE,OAAO;IACxD,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;IACrE,MAAM,GAAG,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,CAAC;QACH,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,kBAAkB,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC;QAC3D,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAkC,CAAC,CAAC,CAAC;QACnF,UAAU,CAAC,GAAG,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;IAC7E,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,uCAAuC;IAC9E,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAA2B,EAA0B,EAAE,CAClF,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACxE,6FAA6F;AAC7F,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAA2B,EAA0B,EAAE,CACjF,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC"}