@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,21 @@
1
+ /**
2
+ * Core domain model (see docs/architecture.md §3).
3
+ * Persisted in the per-machine SQLite journal; the daemon's memory is a cache.
4
+ */
5
+ export type EnvState = 'provisioning' | 'hot' | 'warm' | 'degraded' | 'recycling';
6
+ export type Hygiene = 'reuse' | 'reset-data' | 'pristine';
7
+ /** Every lease is a session lease (decision 0032); a `run` row from a pre-0.13 journal is read as one. */
8
+ export type LeaseKind = 'session';
9
+ /** The field an agent branches on mechanically (decision 0010). */
10
+ export type ErrorClass = 'work-error' | 'env-error' | 'infra-error';
11
+ /**
12
+ * A recorded service process. `startTime` (kernel clock ticks since boot,
13
+ * Linux only) pins the pid to one process *life*, so a later daemon can tell
14
+ * "still my service" from "the OS reused that pid" before signalling it.
15
+ */
16
+ export interface ServicePid {
17
+ pid: number;
18
+ startTime?: number;
19
+ pgid?: number;
20
+ pgids?: number[];
21
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Core domain model (see docs/architecture.md §3).
3
+ * Persisted in the per-machine SQLite journal; the daemon's memory is a cache.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The two units a manifest and the policy knobs speak: durations (`90s`,
3
+ * `10m`, `2h`) and sizes (`600M`, `2G`). Parsing lives in one place so the
4
+ * schema, the engine and `runly plan` can never disagree about what `2G` is.
5
+ */
6
+ /**
7
+ * A duration in milliseconds. Strings take a unit (`ms`, `s`, `m`, `h`, `d`);
8
+ * a bare number is SECONDS (the manifest convention, like `timeout:`).
9
+ * `never`/`off` is Infinity. Returns undefined for anything unreadable.
10
+ */
11
+ export declare function parseDuration(v: unknown): number | undefined;
12
+ /**
13
+ * A size in bytes. Numbers are bytes; strings take K/M/G/T (binary multiples,
14
+ * with or without `i`/`B`): `600M`, `2G`, `1.5GiB`. Undefined when unreadable.
15
+ */
16
+ export declare function parseSize(v: unknown): number | undefined;
17
+ /** `2.0G`, `600M`, `12K` — for humans, one decimal at most. */
18
+ export declare function formatSize(bytes: number): string;
19
+ /** `45s`, `10m`, `1.5h`. */
20
+ export declare function formatDuration(ms: number): string;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The two units a manifest and the policy knobs speak: durations (`90s`,
3
+ * `10m`, `2h`) and sizes (`600M`, `2G`). Parsing lives in one place so the
4
+ * schema, the engine and `runly plan` can never disagree about what `2G` is.
5
+ */
6
+ /**
7
+ * A duration in milliseconds. Strings take a unit (`ms`, `s`, `m`, `h`, `d`);
8
+ * a bare number is SECONDS (the manifest convention, like `timeout:`).
9
+ * `never`/`off` is Infinity. Returns undefined for anything unreadable.
10
+ */
11
+ export function parseDuration(v) {
12
+ if (typeof v === 'number')
13
+ return Number.isFinite(v) && v >= 0 ? v * 1000 : undefined;
14
+ if (typeof v !== 'string')
15
+ return undefined;
16
+ const s = v.trim().toLowerCase();
17
+ if (s === 'never' || s === 'off')
18
+ return Infinity;
19
+ const m = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/.exec(s);
20
+ if (!m)
21
+ return undefined;
22
+ const n = Number(m[1]);
23
+ const unit = m[2] ?? 's';
24
+ const mult = unit === 'ms' ? 1 : unit === 's' ? 1000 : unit === 'm' ? 60_000 : unit === 'h' ? 3_600_000 : 86_400_000;
25
+ return n * mult;
26
+ }
27
+ /**
28
+ * A size in bytes. Numbers are bytes; strings take K/M/G/T (binary multiples,
29
+ * with or without `i`/`B`): `600M`, `2G`, `1.5GiB`. Undefined when unreadable.
30
+ */
31
+ export function parseSize(v) {
32
+ if (typeof v === 'number')
33
+ return Number.isFinite(v) && v >= 0 ? v : undefined;
34
+ if (typeof v !== 'string')
35
+ return undefined;
36
+ const m = /^(\d+(?:\.\d+)?)\s*([kmgt])?(i?b)?$/i.exec(v.trim());
37
+ if (!m)
38
+ return undefined;
39
+ const n = Number(m[1]);
40
+ const unit = (m[2] ?? '').toLowerCase();
41
+ const exp = unit === 'k' ? 1 : unit === 'm' ? 2 : unit === 'g' ? 3 : unit === 't' ? 4 : 0;
42
+ return Math.round(n * 1024 ** exp);
43
+ }
44
+ /** `2.0G`, `600M`, `12K` — for humans, one decimal at most. */
45
+ export function formatSize(bytes) {
46
+ if (!Number.isFinite(bytes))
47
+ return '∞';
48
+ const units = ['B', 'K', 'M', 'G', 'T'];
49
+ let v = Math.max(0, bytes);
50
+ let i = 0;
51
+ while (v >= 1024 && i < units.length - 1) {
52
+ v /= 1024;
53
+ i++;
54
+ }
55
+ return `${v >= 10 || i === 0 ? Math.round(v) : v.toFixed(1)}${units[i]}`;
56
+ }
57
+ /** `45s`, `10m`, `1.5h`. */
58
+ export function formatDuration(ms) {
59
+ if (!Number.isFinite(ms))
60
+ return 'never';
61
+ if (ms < 1000)
62
+ return `${Math.round(ms)}ms`;
63
+ if (ms < 60_000)
64
+ return `${Math.round(ms / 1000)}s`;
65
+ if (ms < 3_600_000)
66
+ return `${Math.round(ms / 60_000)}m`;
67
+ return `${(ms / 3_600_000).toFixed(1)}h`;
68
+ }
69
+ //# sourceMappingURL=units.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"units.js","sourceRoot":"","sources":["../../src/core/units.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import { type Manifest } from './manifest.js';
2
+ export interface UpkeepStep {
3
+ /** 1-based position in the manifest's upkeep list — progress never names the command. */
4
+ index: number;
5
+ when: string;
6
+ /** `ran` executed now; `fresh` matched the ledger; `skipped` was not this caller's to run. */
7
+ status: 'ran' | 'fresh' | 'skipped';
8
+ durationMs: number;
9
+ reason?: string;
10
+ }
11
+ export interface UpkeepOutcome {
12
+ ran: Array<{
13
+ when: string;
14
+ run: string;
15
+ }>;
16
+ fingerprints: Record<string, string>;
17
+ /** Names of datastores whose templates must be rebaked (@rebake-template). */
18
+ rebakeTemplates: string[];
19
+ steps: UpkeepStep[];
20
+ }
21
+ export interface UpkeepOptions {
22
+ /**
23
+ * Evaluate `@` built-ins (default true). They act on an environment's data
24
+ * (`@rebake-template`), so `runly warm` — which has no environment — skips
25
+ * them and reports why.
26
+ */
27
+ builtins?: boolean;
28
+ /**
29
+ * Persist the ledger as it moves. Called with the rule's key REMOVED before
30
+ * its command runs, and with the new hash after it succeeds: a rule that fails
31
+ * half-way has left the worktree in neither state, and a ledger still naming
32
+ * the old trigger would let a later revert to that trigger skip the repair.
33
+ */
34
+ commit?: (fingerprints: Record<string, string>) => void;
35
+ /** Receives each command rule's output as it runs (`upkeep.build.log`, decision 0038). */
36
+ onOutput?: {
37
+ begin: (label: string) => void;
38
+ data: (stream: 'out' | 'err', chunk: string) => void;
39
+ end: (stream: 'out' | 'err') => void;
40
+ };
41
+ }
42
+ export declare const ruleKey: (rule: {
43
+ when: string;
44
+ run: string;
45
+ }) => string;
46
+ /**
47
+ * The worktree files any upkeep rule's `when:` glob matches — the only files a
48
+ * bind reads to decide what upkeep is due. Empty, without asking git, when the
49
+ * manifest declares no rules.
50
+ */
51
+ export declare function triggerFiles(root: string, manifest: Manifest): Promise<string[]>;
52
+ /**
53
+ * The trigger files with their content hashes, read once per bind.
54
+ *
55
+ * Trigger globs can match large files — a consumer's database backups are
56
+ * 160 MB, named by several @rebake-template rules each — so hashing them per
57
+ * rule on every bind would re-read the same bytes several times on the
58
+ * daemon's thread. Each file is hashed once per bind, and only when its
59
+ * (size, mtime) moved since the cache under `cacheDir` recorded it. The cache
60
+ * holds the trigger files and nothing else, and lives in the state root.
61
+ *
62
+ * Filesystem mtimes are coarse, so a same-size rewrite inside one timestamp
63
+ * tick of the recorded stat is indistinguishable from the file already hashed.
64
+ * git's "racily clean" rule applies: an entry whose mtime is not strictly older
65
+ * than the cache write (minus a window) is re-hashed.
66
+ */
67
+ export interface TriggerSet {
68
+ files: string[];
69
+ hashOf: (rel: string) => string | null;
70
+ }
71
+ export declare function triggerSet(root: string, manifest: Manifest, cacheDir?: string): Promise<TriggerSet>;
72
+ /** The fingerprint of one `when:` glob: the content of exactly the files it matches. */
73
+ export declare function triggerHash(root: string, files: string[] | TriggerSet, when: string): string;
74
+ /**
75
+ * Content-derived bake key per datastore (vetbill-1i49).
76
+ *
77
+ * The baked-template identity used to hash only the static `create:` command
78
+ * string — identical for every branch — so two environments of the same stack
79
+ * whose trees carry *different* migrations/seeds could silently share one
80
+ * template with the wrong schema. The @rebake-template rules already declare
81
+ * exactly which files define a datastore's baked content; hashing those
82
+ * files' current contents into the template name makes divergent content
83
+ * yield disjoint templates by construction (rebake becomes cleanup, not the
84
+ * only line of defense).
85
+ *
86
+ * Datastores with no @rebake-template rule get no key (undefined) and keep
87
+ * their historical template names — existing bakes stay valid.
88
+ */
89
+ export declare function templateBakeKeys(manifest: Manifest, root: string, files: string[] | TriggerSet): Record<string, string>;
90
+ export declare function runUpkeep(root: string, files: string[] | TriggerSet, manifest: Manifest, previous: Record<string, string>, onProgress?: (phase: string) => void, opts?: UpkeepOptions): Promise<UpkeepOutcome>;
@@ -0,0 +1,176 @@
1
+ /**
2
+ * The fingerprint ledger (decision 0008): a closed list of (trigger -> action)
3
+ * rules, evaluated at bind (and `runly warm`) time, direction-agnostic. Actions
4
+ * are repo commands, or engine built-ins prefixed with @.
5
+ *
6
+ * A rule's fingerprint is the content of the files its `when:` glob matches —
7
+ * those files and nothing else (decision 0032). runly keeps no identity of the
8
+ * rest of the worktree and no record of builds.
9
+ */
10
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { cmdTimeoutS, runBounded } from './exec.js';
13
+ import { existsSync } from 'node:fs';
14
+ import { globToRegex, sha256, fileHash, BrokerError, commandFailure, safeJoin } from './util.js';
15
+ import { enumerateSource, enumerateSourceStats } from './worktree.js';
16
+ import { upkeepOutputs } from './manifest.js';
17
+ export const ruleKey = (rule) => `${rule.when} -> ${rule.run}`;
18
+ /**
19
+ * The worktree files any upkeep rule's `when:` glob matches — the only files a
20
+ * bind reads to decide what upkeep is due. Empty, without asking git, when the
21
+ * manifest declares no rules.
22
+ */
23
+ export async function triggerFiles(root, manifest) {
24
+ const whens = (manifest.upkeep ?? []).map((rule) => rule.when);
25
+ return whens.length === 0 ? [] : enumerateSource(root, manifest, whens);
26
+ }
27
+ const RACY_WINDOW_MS = 2000;
28
+ export async function triggerSet(root, manifest, cacheDir) {
29
+ // The listing's own stat is the cache's check: one stat per trigger file.
30
+ const whens = (manifest.upkeep ?? []).map((rule) => rule.when);
31
+ const listed = whens.length === 0 ? [] : await enumerateSourceStats(root, manifest, whens);
32
+ const files = listed.map((f) => f.path);
33
+ const statOfListed = new Map(listed.map((f) => [f.path, f]));
34
+ let cache = { entries: {} };
35
+ if (cacheDir) {
36
+ try {
37
+ const raw = JSON.parse(readFileSync(join(cacheDir, 'triggers.json'), 'utf8'));
38
+ if (raw && typeof raw === 'object' && raw.entries && typeof raw.entries === 'object')
39
+ cache = raw;
40
+ }
41
+ catch {
42
+ /* absent or torn: hash everything once */
43
+ }
44
+ }
45
+ const racyFrom = cache.writtenAt === undefined ? -Infinity : cache.writtenAt - RACY_WINDOW_MS;
46
+ const next = {};
47
+ const hashes = new Map();
48
+ for (const rel of files) {
49
+ const abs = join(root, rel);
50
+ const st = statOfListed.get(rel);
51
+ if (!st) {
52
+ hashes.set(rel, null);
53
+ continue;
54
+ }
55
+ const cached = cache.entries[rel];
56
+ const trust = cached !== undefined && cached.size === st.size && cached.mtime === st.mtimeMs && cached.mtime < racyFrom;
57
+ const hash = trust ? cached.hash : fileHash(abs);
58
+ hashes.set(rel, hash);
59
+ if (hash !== null)
60
+ next[rel] = { hash, size: st.size, mtime: st.mtimeMs };
61
+ }
62
+ if (cacheDir) {
63
+ mkdirSync(cacheDir, { recursive: true });
64
+ const tmp = join(cacheDir, `triggers.json.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`);
65
+ writeFileSync(tmp, JSON.stringify({ root, writtenAt: Date.now(), entries: next }));
66
+ renameSync(tmp, join(cacheDir, 'triggers.json'));
67
+ }
68
+ return { files, hashOf: (rel) => (hashes.has(rel) ? (hashes.get(rel) ?? null) : fileHash(join(root, rel))) };
69
+ }
70
+ /** The fingerprint of one `when:` glob: the content of exactly the files it matches. */
71
+ export function triggerHash(root, files, when) {
72
+ const re = globToRegex(when);
73
+ const list = Array.isArray(files) ? files : files.files;
74
+ const hashOf = Array.isArray(files) ? (rel) => fileHash(join(root, rel)) : files.hashOf;
75
+ const matching = list.filter((f) => re.test(f)).sort();
76
+ return sha256(matching.map((f) => `${f}:${hashOf(f) ?? 'gone'}`).join('\n'));
77
+ }
78
+ /**
79
+ * Content-derived bake key per datastore (vetbill-1i49).
80
+ *
81
+ * The baked-template identity used to hash only the static `create:` command
82
+ * string — identical for every branch — so two environments of the same stack
83
+ * whose trees carry *different* migrations/seeds could silently share one
84
+ * template with the wrong schema. The @rebake-template rules already declare
85
+ * exactly which files define a datastore's baked content; hashing those
86
+ * files' current contents into the template name makes divergent content
87
+ * yield disjoint templates by construction (rebake becomes cleanup, not the
88
+ * only line of defense).
89
+ *
90
+ * Datastores with no @rebake-template rule get no key (undefined) and keep
91
+ * their historical template names — existing bakes stay valid.
92
+ */
93
+ export function templateBakeKeys(manifest, root, files) {
94
+ const perDs = {};
95
+ for (const rule of manifest.upkeep ?? []) {
96
+ if (!rule.run.startsWith('@rebake-template'))
97
+ continue;
98
+ const target = rule.run.slice('@rebake-template'.length).trim() || 'main';
99
+ (perDs[target] ??= []).push(triggerHash(root, files, rule.when));
100
+ }
101
+ return Object.fromEntries(Object.entries(perDs).map(([ds, hashes]) => [ds, sha256(hashes.sort().join('\n')).slice(0, 12)]));
102
+ }
103
+ export async function runUpkeep(root, files, manifest, previous, onProgress, opts = {}) {
104
+ const outcome = { ran: [], fingerprints: { ...previous }, rebakeTemplates: [], steps: [] };
105
+ const builtins = opts.builtins ?? true;
106
+ for (const [index, rule] of (manifest.upkeep ?? []).entries()) {
107
+ const key = ruleKey(rule);
108
+ const position = index + 1;
109
+ if (rule.run.startsWith('@') && !builtins) {
110
+ outcome.steps.push({ index: position, when: rule.when, status: 'skipped', durationMs: 0, reason: 'acts on an environment\'s data; the next bind applies it' });
111
+ continue;
112
+ }
113
+ const hash = triggerHash(root, files, rule.when);
114
+ // Its trigger is unchanged — but a rule that declares what it leaves
115
+ // behind is due again when that is gone (a pool's `git clean -fdx`).
116
+ const missing = rule.run.startsWith('@') ? undefined : upkeepOutputs(rule).find((o) => !existsSync(safeJoin(root, o, 'upkeep outputs')));
117
+ if (previous[key] === hash && missing === undefined) {
118
+ outcome.steps.push({ index: position, when: rule.when, status: 'fresh', durationMs: 0 });
119
+ continue;
120
+ }
121
+ if (previous[key] === hash)
122
+ onProgress?.(`upkeep rule ${position}: its output ${missing} is missing — running it again`);
123
+ const started = Date.now();
124
+ if (rule.run.startsWith('@')) {
125
+ const [builtin, ...args] = rule.run.slice(1).split(/\s+/);
126
+ if (builtin === 'rebake-template') {
127
+ outcome.rebakeTemplates.push(args[0] ?? 'main');
128
+ }
129
+ else {
130
+ throw new BrokerError('work-error', `unknown upkeep built-in '@${builtin}'`, 'upkeep');
131
+ }
132
+ }
133
+ else {
134
+ // Bounded like every other repo-declared command: an install blocking on
135
+ // a half-up registry used to hold the env's busy bit until the daemon
136
+ // was killed.
137
+ const timeoutS = cmdTimeoutS(rule.timeout);
138
+ // Commands and their output may contain credentials. Progress names only
139
+ // the manifest rule's position, never its command or captured output.
140
+ const label = `upkeep rule ${position}`;
141
+ onProgress?.(`${label}: starting (timeout ${timeoutS}s)`);
142
+ delete outcome.fingerprints[key];
143
+ opts.commit?.(outcome.fingerprints);
144
+ const heartbeat = onProgress && setInterval(() => {
145
+ onProgress(`${label}: running (${Math.floor((Date.now() - started) / 1000)}s elapsed)`);
146
+ }, 5000);
147
+ heartbeat?.unref();
148
+ let r;
149
+ try {
150
+ opts.onOutput?.begin(`${label} (when: ${rule.when})`);
151
+ r = await runBounded(rule.run, root, timeoutS, undefined, opts.onOutput);
152
+ }
153
+ finally {
154
+ if (heartbeat)
155
+ clearInterval(heartbeat);
156
+ }
157
+ if (r.timedOut) {
158
+ onProgress?.(`${label}: timed out after ${timeoutS}s`);
159
+ throw new BrokerError('work-error', `upkeep rule timed out after ${timeoutS}s (process group killed): ${rule.run}`, rule.when, r.output.slice(-800));
160
+ }
161
+ if (r.code !== 0) {
162
+ onProgress?.(`${label}: failed (${Math.floor((Date.now() - started) / 1000)}s elapsed)`);
163
+ // Triggered by the binding's own change -> work-error by default (decision 0008).
164
+ throw commandFailure(`upkeep rule failed: ${rule.run}`, rule.when, r.output);
165
+ }
166
+ onProgress?.(`${label}: finished (${Math.floor((Date.now() - started) / 1000)}s elapsed)`);
167
+ }
168
+ outcome.ran.push({ when: rule.when, run: rule.run });
169
+ outcome.fingerprints[key] = hash;
170
+ if (!rule.run.startsWith('@'))
171
+ opts.commit?.(outcome.fingerprints);
172
+ outcome.steps.push({ index: position, when: rule.when, status: 'ran', durationMs: Date.now() - started });
173
+ }
174
+ return outcome;
175
+ }
176
+ //# sourceMappingURL=upkeep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upkeep.js","sourceRoot":"","sources":["../../src/core/upkeep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ export declare const sha256: (data: string | Buffer) => string;
2
+ /**
3
+ * Run a shell command, swallowing failures (best-effort cleanup paths).
4
+ * Bounded: these run inside awaited sweeps, and a hung `drop` against an
5
+ * unreachable appliance used to stall retention forever while each later
6
+ * sweep piled another wedged `sh` onto the same marker.
7
+ */
8
+ export declare const runQuiet: (cmd: string, cwd: string) => Promise<void>;
9
+ export declare const fileHash: (path: string) => string | null;
10
+ /** One stat, no throw: what a listing of tens of thousands of files can afford per file. */
11
+ export declare const statOf: (p: string) => import("node:fs").Stats | undefined;
12
+ export declare const isFile: (p: string) => boolean;
13
+ export declare function globToRegex(pattern: string): RegExp;
14
+ export declare const matchesAny: (path: string, patterns: string[]) => boolean;
15
+ /** Resolve {{...}} placeholders against a nested context object. */
16
+ export declare function template(str: string, ctx: Record<string, unknown>): string;
17
+ export declare const templateEnv: (env: Record<string, string> | undefined, ctx: Record<string, unknown>) => Record<string, string>;
18
+ export declare class BrokerError extends Error {
19
+ readonly klass: 'work-error' | 'env-error' | 'infra-error';
20
+ readonly source?: string | undefined;
21
+ readonly logExcerpt?: string | undefined;
22
+ constructor(klass: 'work-error' | 'env-error' | 'infra-error', message: string, source?: string | undefined, logExcerpt?: string | undefined);
23
+ toJSON(): {
24
+ class: "work-error" | "env-error" | "infra-error";
25
+ message: string;
26
+ source: string | undefined;
27
+ logExcerpt: string | undefined;
28
+ };
29
+ }
30
+ /**
31
+ * A manifest-supplied relative path (sync.include, outputs, a datastore key)
32
+ * must stay INSIDE its base after resolution — never escape via `..` or an
33
+ * absolute path. Returns the safe joined absolute path, or throws work-error.
34
+ * This is the guard that keeps file ops from leaving runly's own dirs even
35
+ * on an honest `../shared/.env` typo, not only a malicious manifest.
36
+ */
37
+ export declare function safeJoin(base: string, rel: string, what: string): string;
38
+ /** A hint naming the daemon's environment when `output` looks like one, else undefined. */
39
+ export declare function environmentHint(output: string): string | undefined;
40
+ /**
41
+ * A failed repo command as an error: env-error with the hint when it reads as
42
+ * a missing tool or runtime, else `klass` (work-error: the code's fault).
43
+ */
44
+ export declare function commandFailure(message: string, source: string | undefined, output: string, klass?: 'work-error' | 'env-error'): BrokerError;
45
+ export declare const now: () => number;
46
+ export declare const shortId: () => string;
@@ -0,0 +1,168 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync, statSync } from 'node:fs';
3
+ import * as pathMod from 'node:path';
4
+ import { cmdTimeoutS, runBounded } from './exec.js';
5
+ export const sha256 = (data) => createHash('sha256').update(data).digest('hex');
6
+ /**
7
+ * Run a shell command, swallowing failures (best-effort cleanup paths).
8
+ * Bounded: these run inside awaited sweeps, and a hung `drop` against an
9
+ * unreachable appliance used to stall retention forever while each later
10
+ * sweep piled another wedged `sh` onto the same marker.
11
+ */
12
+ export const runQuiet = (cmd, cwd) => runBounded(cmd, cwd, cmdTimeoutS()).then(() => undefined);
13
+ export const fileHash = (path) => {
14
+ try {
15
+ return sha256(readFileSync(path));
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ };
21
+ /** One stat, no throw: what a listing of tens of thousands of files can afford per file. */
22
+ export const statOf = (p) => statSync(p, { throwIfNoEntry: false });
23
+ export const isFile = (p) => statOf(p)?.isFile() ?? false;
24
+ /**
25
+ * Minimal glob matcher for manifest patterns (caches, sync.include, outputs,
26
+ * upkeep and build `when:`). Supports **, *, ?. A bare name with no glob chars
27
+ * and no slash matches that path segment anywhere (node_modules). All patterns
28
+ * also protect their subtree (an implicit trailing /**).
29
+ *
30
+ * `**` is anchored at path segments: a leading or inner `**\/` stands for zero
31
+ * or more WHOLE directories, so `**\/bin` matches `bin` and `src/bin` but never
32
+ * `src/Cabin` (it used to compile to `^.*bin`, which made a .NET
33
+ * `caches: [**\/bin]` swallow every source directory whose name ends in "bin").
34
+ * `*` and `?` stay inside one segment. Dotfiles are not special.
35
+ *
36
+ * Compiled patterns are cached: the same few manifest globs are matched
37
+ * against every file of a worktree listing, and compiling them per file cost
38
+ * seconds per `up` on a large tree.
39
+ */
40
+ const globCache = new Map();
41
+ const GLOB_CACHE_MAX = 4096;
42
+ export function globToRegex(pattern) {
43
+ const hit = globCache.get(pattern);
44
+ if (hit)
45
+ return hit;
46
+ const re = compileGlob(pattern);
47
+ if (globCache.size >= GLOB_CACHE_MAX)
48
+ globCache.clear();
49
+ globCache.set(pattern, re);
50
+ return re;
51
+ }
52
+ function compileGlob(pattern) {
53
+ const p = pattern.replace(/^glob\((.*)\)$/, '$1').replace(/^\.\//, '');
54
+ if (!/[*?[]/.test(p) && !p.includes('/')) {
55
+ return new RegExp(`(^|/)${p.replace(/[.+^${}()|\\]/g, '\\$&')}(/|$)`);
56
+ }
57
+ let re = '';
58
+ for (let i = 0; i < p.length; i++) {
59
+ const c = p[i];
60
+ if (c === '*') {
61
+ if (p[i + 1] === '*') {
62
+ const segmentStart = i === 0 || p[i - 1] === '/';
63
+ if (segmentStart && p[i + 2] === '/') {
64
+ // `**/`: zero or more whole directories.
65
+ re += '(?:.*/)?';
66
+ i += 2;
67
+ }
68
+ else {
69
+ // A trailing `**` (everything below), or `**` inside a segment.
70
+ re += '.*';
71
+ i++;
72
+ }
73
+ }
74
+ else
75
+ re += '[^/]*';
76
+ }
77
+ else if (c === '?')
78
+ re += '[^/]';
79
+ else
80
+ re += c.replace(/[.+^${}()|\\[\]]/g, '\\$&');
81
+ }
82
+ return new RegExp(`^${re}(/.*)?$`);
83
+ }
84
+ export const matchesAny = (path, patterns) => {
85
+ for (const p of patterns)
86
+ if (globToRegex(p).test(path))
87
+ return true;
88
+ return false;
89
+ };
90
+ /** Resolve {{...}} placeholders against a nested context object. */
91
+ export function template(str, ctx) {
92
+ return str.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (_, expr) => {
93
+ const val = expr.split('.').reduce((acc, key) => {
94
+ if (acc && typeof acc === 'object' && key in acc) {
95
+ return acc[key];
96
+ }
97
+ return undefined;
98
+ }, ctx);
99
+ if (val === undefined || val === null)
100
+ throw new Error(`unresolved template variable {{${expr}}}`);
101
+ return String(val);
102
+ });
103
+ }
104
+ export const templateEnv = (env, ctx) => {
105
+ const out = {};
106
+ for (const [k, v] of Object.entries(env ?? {}))
107
+ out[k] = template(v, ctx);
108
+ return out;
109
+ };
110
+ export class BrokerError extends Error {
111
+ klass;
112
+ source;
113
+ logExcerpt;
114
+ constructor(klass, message, source, logExcerpt) {
115
+ super(message);
116
+ this.klass = klass;
117
+ this.source = source;
118
+ this.logExcerpt = logExcerpt;
119
+ }
120
+ toJSON() {
121
+ return { class: this.klass, message: this.message, source: this.source, logExcerpt: this.logExcerpt };
122
+ }
123
+ }
124
+ /**
125
+ * A manifest-supplied relative path (sync.include, outputs, a datastore key)
126
+ * must stay INSIDE its base after resolution — never escape via `..` or an
127
+ * absolute path. Returns the safe joined absolute path, or throws work-error.
128
+ * This is the guard that keeps file ops from leaving runly's own dirs even
129
+ * on an honest `../shared/.env` typo, not only a malicious manifest.
130
+ */
131
+ export function safeJoin(base, rel, what) {
132
+ const { resolve, isAbsolute } = pathMod;
133
+ if (isAbsolute(rel))
134
+ throw new BrokerError('work-error', `${what} must be a relative path, got absolute '${rel}'`, 'manifest');
135
+ const abs = resolve(base, rel);
136
+ const baseResolved = resolve(base);
137
+ if (abs !== baseResolved && !abs.startsWith(baseResolved + pathMod.sep)) {
138
+ throw new BrokerError('work-error', `${what} '${rel}' escapes its directory — path traversal is not allowed`, 'manifest');
139
+ }
140
+ return abs;
141
+ }
142
+ /**
143
+ * The output of a failed command that reads as a missing tool or runtime — the
144
+ * DAEMON's environment, not the repo's code. Under a unit it is the
145
+ * environment `runly daemon install` captured; a missing DOTNET_ROOT once made
146
+ * every template bake fail there, reported as the code's fault.
147
+ */
148
+ const ENVIRONMENTAL = /command not found|: not found\b|You must install or update \.NET|\.NET location: Not found|DOTNET_ROOT|spawn \S+ ENOENT|exec format error|cannot execute binary|env: '?[^\s']+'?: No such file or directory/i;
149
+ /** A hint naming the daemon's environment when `output` looks like one, else undefined. */
150
+ export function environmentHint(output) {
151
+ if (!ENVIRONMENTAL.test(output))
152
+ return undefined;
153
+ const sup = process.env.RUNLY_SUPERVISOR;
154
+ return sup === 'systemd' || sup === 'launchd'
155
+ ? ` — this looks like the daemon's environment, not the code: a tool or runtime was not found. The daemon runs under its ${sup} unit with the environment 'runly daemon install' captured (it prints it); re-run the install from a shell that has the tool, or add a variable with --env NAME, then 'runly daemon stop' — the next verb starts it again`
156
+ : ` — this looks like the daemon's environment, not the code: a tool or runtime was not found. The daemon kept the environment of the shell that started it; 'runly daemon stop', and the next verb starts it from yours`;
157
+ }
158
+ /**
159
+ * A failed repo command as an error: env-error with the hint when it reads as
160
+ * a missing tool or runtime, else `klass` (work-error: the code's fault).
161
+ */
162
+ export function commandFailure(message, source, output, klass = 'work-error') {
163
+ const hint = environmentHint(output);
164
+ return new BrokerError(hint ? 'env-error' : klass, `${message}${hint ?? ''}`, source, output.slice(-800));
165
+ }
166
+ export const now = () => Date.now();
167
+ export const shortId = () => Math.random().toString(36).slice(2, 8);
168
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"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"}