@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,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded shell execution for repo-declared commands.
|
|
3
|
+
*
|
|
4
|
+
* Every datastore and appliance command comes from someone's runly.yml, so it
|
|
5
|
+
* can hang: an appliance `start:` that forgets `-d` stays in the foreground, a
|
|
6
|
+
* `psql` against a half-up server blocks on connect. Unbounded, that wedges the
|
|
7
|
+
* bind forever — and because the environment stays in the daemon's `busy` set,
|
|
8
|
+
* the sweeper will not expire its lease and teardown refuses it, so the
|
|
9
|
+
* environment is unreclaimable until the daemon is killed.
|
|
10
|
+
*
|
|
11
|
+
* So every such command is spawned as its own process group with a hard
|
|
12
|
+
* deadline, and the timeout kills the GROUP: killing only `sh` would leave the
|
|
13
|
+
* real client (psql, docker) running and still holding whatever it holds.
|
|
14
|
+
* This mirrors the engine's check runner, which learned the same lesson.
|
|
15
|
+
*/
|
|
16
|
+
import { spawn } from 'node:child_process';
|
|
17
|
+
/** Repo-declared commands are IO-bound setup steps, not builds. */
|
|
18
|
+
export const DEFAULT_CMD_TIMEOUT_S = 300;
|
|
19
|
+
/** Build/exec/check-grade work gets a longer leash than setup steps. */
|
|
20
|
+
export const LONG_CMD_TIMEOUT_S = 600;
|
|
21
|
+
/**
|
|
22
|
+
* The deadline for a repo-declared command. Callers pass the default that fits
|
|
23
|
+
* their site's class (setup steps 300s, or an upkeep rule's own `timeout`;
|
|
24
|
+
* build/exec-grade work 600s); BACKLOT_CMD_TIMEOUT_S overrides ALL of them —
|
|
25
|
+
* the escape hatch for a repo whose legitimate commands outrun the class default.
|
|
26
|
+
*/
|
|
27
|
+
export function cmdTimeoutS(fallback = DEFAULT_CMD_TIMEOUT_S) {
|
|
28
|
+
const e = process.env.BACKLOT_CMD_TIMEOUT_S;
|
|
29
|
+
if (e === undefined || e === '')
|
|
30
|
+
return fallback;
|
|
31
|
+
const n = Number(e);
|
|
32
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
33
|
+
}
|
|
34
|
+
export function runBounded(cmd, cwd, timeoutS = DEFAULT_CMD_TIMEOUT_S, env,
|
|
35
|
+
/** Sees every chunk as it arrives (a build log, decision 0038); `end` once per stream. */
|
|
36
|
+
onOutput) {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const proc = spawn('sh', ['-c', cmd], { cwd, env, stdio: ['ignore', 'pipe', 'pipe'], detached: true });
|
|
39
|
+
let out = '';
|
|
40
|
+
let settled = false;
|
|
41
|
+
let timedOut = false;
|
|
42
|
+
const sink = (stream) => (d) => {
|
|
43
|
+
const text = d.toString();
|
|
44
|
+
out = (out + text).slice(-16_000);
|
|
45
|
+
try {
|
|
46
|
+
onOutput?.data(stream, text);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* a log write must never fail the command */
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
proc.stdout.on('data', sink('out'));
|
|
53
|
+
proc.stderr.on('data', sink('err'));
|
|
54
|
+
proc.stdout.on('end', () => onOutput?.end('out'));
|
|
55
|
+
proc.stderr.on('end', () => onOutput?.end('err'));
|
|
56
|
+
const timer = setTimeout(() => {
|
|
57
|
+
timedOut = true;
|
|
58
|
+
try {
|
|
59
|
+
process.kill(-proc.pid, 'SIGKILL'); // the group, not just the sh wrapper
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
try {
|
|
63
|
+
proc.kill('SIGKILL');
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* already gone */
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}, timeoutS * 1000);
|
|
70
|
+
timer.unref();
|
|
71
|
+
const done = (r) => {
|
|
72
|
+
if (settled)
|
|
73
|
+
return;
|
|
74
|
+
settled = true;
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
resolve(r);
|
|
77
|
+
};
|
|
78
|
+
// A spawn failure (EAGAIN/EMFILE under load) emits 'error' with no 'exit';
|
|
79
|
+
// without this the promise never settles and the caller's lock wedges.
|
|
80
|
+
proc.on('error', (err) => done({ code: 1, output: `${out}\nspawn error: ${err.message}`, timedOut }));
|
|
81
|
+
proc.on('exit', (code) => done({ code: code ?? 1, output: out, timedOut }));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/core/exec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type { EnvState, Hygiene, LeaseKind, ServicePid } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The journal schema this build understands, stamped into `PRAGMA user_version`.
|
|
4
|
+
*
|
|
5
|
+
* Bump it when a change makes an OLDER daemon misread this journal — not for
|
|
6
|
+
* every additive column: a bump is unnecessary when an old daemon selecting
|
|
7
|
+
* a known subset of columns still reads the journal correctly.
|
|
8
|
+
*
|
|
9
|
+
* Schemas 1-3 belong to runly before 0.16 and are refused: their migrations
|
|
10
|
+
* were retired in 0.19 (upgrade through 0.18 first).
|
|
11
|
+
*
|
|
12
|
+
* Schema 4 (decision 0034): `active_services = '[]'` means "no services are
|
|
13
|
+
* wanted" (an older daemon reads an empty list as the whole app and would boot
|
|
14
|
+
* it), the `db_copies` table holds database copies an older daemon would never
|
|
15
|
+
* reap, and data-only environments are gone (0.18 migrated them away).
|
|
16
|
+
*
|
|
17
|
+
* What this exists to stop is the DOWNGRADE, which has already cost once. The
|
|
18
|
+
* sha256 env-id migration stranded pre-upgrade rows that then counted against
|
|
19
|
+
* POOL_MAX_TOTAL and held their ports forever (BACKLOG.md), because nothing on
|
|
20
|
+
* disk said which build wrote them. A running example of the same shape:
|
|
21
|
+
* `data_only` defaults to 0, so a 0.7.0 daemon re-binding a 0.8.0 data-only
|
|
22
|
+
* environment reads "not data-only" and boots the whole application into a
|
|
23
|
+
* test lane's database. Disk is truth (decision 0009), so the truth has to say
|
|
24
|
+
* what wrote it.
|
|
25
|
+
*/
|
|
26
|
+
export declare const JOURNAL_SCHEMA_VERSION = 4;
|
|
27
|
+
export interface EnvRow {
|
|
28
|
+
id: string;
|
|
29
|
+
stack: string;
|
|
30
|
+
stackRoot: string;
|
|
31
|
+
state: EnvState;
|
|
32
|
+
root: string;
|
|
33
|
+
ports: Record<string, number>;
|
|
34
|
+
datastoreNs: Record<string, string>;
|
|
35
|
+
fingerprints: Record<string, string>;
|
|
36
|
+
presets: Record<string, string>;
|
|
37
|
+
bindCount: number;
|
|
38
|
+
createdAt: number;
|
|
39
|
+
lastUsedAt: number;
|
|
40
|
+
servicePids: Record<string, ServicePid>;
|
|
41
|
+
/** Consecutive bind failures — >= 2 auto-escalates the next bind to pristine (decision 0007). */
|
|
42
|
+
failStreak: number;
|
|
43
|
+
/**
|
|
44
|
+
* The services this environment's lease WANTS up (decision 0034): `up`
|
|
45
|
+
* adds to it, `down` takes away. Undefined means every service the
|
|
46
|
+
* manifest declares (so a service added to the manifest later is included);
|
|
47
|
+
* `[]` means none — the shape `down` with no names leaves. The running set
|
|
48
|
+
* is the supervisor's; this is the intent a quiesce or a daemon restart
|
|
49
|
+
* keeps, and the next `up` restores.
|
|
50
|
+
*/
|
|
51
|
+
activeServices?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* When each public port last carried a client byte (decision 0035), by port
|
|
54
|
+
* key. Persisted (throttled) so a daemon restart does not reset every idle
|
|
55
|
+
* clock to "active"; the proxy's in-memory counters start from it.
|
|
56
|
+
*/
|
|
57
|
+
activity?: Record<string, number>;
|
|
58
|
+
/**
|
|
59
|
+
* How to drop each datastore namespace this environment holds, recorded
|
|
60
|
+
* when it is created (decision 0037): the templated drop command and where
|
|
61
|
+
* to run it, or the file to delete. Teardown needs neither the manifest nor
|
|
62
|
+
* the worktree — both may be gone by the time it runs.
|
|
63
|
+
*/
|
|
64
|
+
dropRecipes?: Record<string, DropRecipe>;
|
|
65
|
+
/** The template each datastore was last restored from (decision 0037: templates are kept by reference). */
|
|
66
|
+
templates?: Record<string, string>;
|
|
67
|
+
}
|
|
68
|
+
/** Everything needed to drop one namespace without the manifest (decision 0037). */
|
|
69
|
+
export interface DropRecipe {
|
|
70
|
+
cmd?: string;
|
|
71
|
+
cwd?: string;
|
|
72
|
+
/** The server-side namespace the drop removes (0.16.1+), referenced from before it is created. */
|
|
73
|
+
ns?: string;
|
|
74
|
+
/** A file (sqlite) to delete instead, with its -wal/-shm/-journal sidecars. */
|
|
75
|
+
path?: string;
|
|
76
|
+
}
|
|
77
|
+
export interface LeaseRow {
|
|
78
|
+
id: string;
|
|
79
|
+
envId: string;
|
|
80
|
+
kind: LeaseKind;
|
|
81
|
+
holder: string;
|
|
82
|
+
hygiene: Hygiene;
|
|
83
|
+
expiresAt: number;
|
|
84
|
+
/**
|
|
85
|
+
* The holder's process, when the caller supplied one.
|
|
86
|
+
*
|
|
87
|
+
* `holder` is a NAME (a worktree path by default) and nothing about a name
|
|
88
|
+
* can die — so an agent that crashed held its environment until the TTL
|
|
89
|
+
* expired, exempt from idle reclamation the whole time. A pid pinned by its
|
|
90
|
+
* start time can be checked, so a dead holder's lease is released in seconds.
|
|
91
|
+
*/
|
|
92
|
+
holderPid?: number;
|
|
93
|
+
holderStart?: number;
|
|
94
|
+
/** Active lease-scoped public preview tunnel, when `runly preview` is running. */
|
|
95
|
+
previewService?: string;
|
|
96
|
+
previewUrl?: string;
|
|
97
|
+
previewPid?: number;
|
|
98
|
+
previewStart?: number;
|
|
99
|
+
/** Local port the tunnel was published against, so a rebind can spot drift. */
|
|
100
|
+
previewPort?: number;
|
|
101
|
+
/**
|
|
102
|
+
* What `runly preview` published, kept until the preview is stopped on
|
|
103
|
+
* purpose (0.20, decision 0045): a daemon restart reaps the tunnel and
|
|
104
|
+
* publishes it again from this. Written only by `setLeasePreviewRestore`,
|
|
105
|
+
* never by `saveLease`, so a stale lease snapshot cannot erase it.
|
|
106
|
+
*/
|
|
107
|
+
previewRestore?: PreviewRestore;
|
|
108
|
+
}
|
|
109
|
+
/** A published preview, as a restart re-publishes it (decision 0045). */
|
|
110
|
+
export interface PreviewRestore {
|
|
111
|
+
service: string;
|
|
112
|
+
/** The URL it had; a publisher that pins its address gives the same one again. */
|
|
113
|
+
url: string;
|
|
114
|
+
/** The environment's public port it was aimed at. */
|
|
115
|
+
port: number;
|
|
116
|
+
/** The publisher, by name; absent for a preview published before 0.20 (the manifest's then). */
|
|
117
|
+
publisher?: string;
|
|
118
|
+
/** The settings that reproduce the address (the manifest's `preview` block plus what the publisher pinned). */
|
|
119
|
+
settings?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A database copy made by `runly db new|with` (decision 0034): a fresh restore
|
|
123
|
+
* from the same template an environment's datastore uses, with no lease, ports
|
|
124
|
+
* or services. Everything needed to DROP it is on the row — the command, where
|
|
125
|
+
* to run it, the files to remove — so it can be reaped after the worktree that
|
|
126
|
+
* made it is gone.
|
|
127
|
+
*/
|
|
128
|
+
export interface DbCopyRow {
|
|
129
|
+
name: string;
|
|
130
|
+
stack: string;
|
|
131
|
+
stackRoot: string;
|
|
132
|
+
datastore: string;
|
|
133
|
+
preset: string;
|
|
134
|
+
ns: string;
|
|
135
|
+
url: string;
|
|
136
|
+
/** `creating` until the restore finished; `dropping` once a drop started (or failed). */
|
|
137
|
+
state: 'creating' | 'ready' | 'dropping';
|
|
138
|
+
/** The caller's worktree, the same holder name `up` uses. */
|
|
139
|
+
holder: string;
|
|
140
|
+
/** The agent tether, when the caller named one (`--holder-pid`, BACKLOT_HOLDER_PID, or `db with`'s own CLI). */
|
|
141
|
+
holderPid?: number;
|
|
142
|
+
holderStart?: number;
|
|
143
|
+
/** The already-templated drop command (command-family drivers). */
|
|
144
|
+
dropCmd?: string;
|
|
145
|
+
/** Where it runs: the worktree, or the state root when that is gone. */
|
|
146
|
+
dropCwd?: string;
|
|
147
|
+
/** What to delete instead (sqlite): the copy's own directory under the state root. */
|
|
148
|
+
dropPath?: string;
|
|
149
|
+
createdAt: number;
|
|
150
|
+
dropAttempts: number;
|
|
151
|
+
nextDropAt: number;
|
|
152
|
+
/** The template it was restored from (`<stack>/<marker>`), so retention keeps it (decision 0037). */
|
|
153
|
+
template?: string;
|
|
154
|
+
/**
|
|
155
|
+
* `runly db with`'s command (decision 0039): its pid and start time, and its
|
|
156
|
+
* process group when it runs in its own. A copy dropped while the command
|
|
157
|
+
* still runs — its CLI was killed — takes the command down first, so
|
|
158
|
+
* nothing keeps working against a dropped database.
|
|
159
|
+
*/
|
|
160
|
+
child?: {
|
|
161
|
+
pid: number;
|
|
162
|
+
start?: number;
|
|
163
|
+
pgid?: number;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export declare class Journal {
|
|
167
|
+
private db;
|
|
168
|
+
constructor(path?: string);
|
|
169
|
+
/**
|
|
170
|
+
* One real sqlite transaction around a multi-statement write. Each of these
|
|
171
|
+
* sequences used to be N independent writes, so a daemon SIGKILLed between
|
|
172
|
+
* them left a half-state on disk — deleteEnv's env-gone-lease-left was the
|
|
173
|
+
* reviewed case. The sweeper tolerates those half-states (journals outlive
|
|
174
|
+
* releases); this stops new ones being minted. BEGIN IMMEDIATE takes the
|
|
175
|
+
* write lock up front, so the sequence can't interleave with the concurrent
|
|
176
|
+
* writers that busy_timeout exists for either.
|
|
177
|
+
*/
|
|
178
|
+
private withTx;
|
|
179
|
+
private rowToEnv;
|
|
180
|
+
saveEnv(e: EnvRow): void;
|
|
181
|
+
/**
|
|
182
|
+
* Record the proxy's activity clocks (decision 0035). Its own statement, so
|
|
183
|
+
* a snapshot saved by a verb can never roll them back, and merged so a
|
|
184
|
+
* clock only moves forward.
|
|
185
|
+
*/
|
|
186
|
+
saveActivity(id: string, activity: Record<string, number>): void;
|
|
187
|
+
getEnv(id: string): EnvRow | undefined;
|
|
188
|
+
envsForStack(stack: string): EnvRow[];
|
|
189
|
+
allEnvs(): EnvRow[];
|
|
190
|
+
private rowToDbCopy;
|
|
191
|
+
saveDbCopy(c: DbCopyRow): void;
|
|
192
|
+
getDbCopy(name: string): DbCopyRow | undefined;
|
|
193
|
+
allDbCopies(): DbCopyRow[];
|
|
194
|
+
deleteDbCopy(name: string): void;
|
|
195
|
+
deleteEnv(id: string): void;
|
|
196
|
+
/**
|
|
197
|
+
* A per-stack env sequence that NEVER reuses a number, even after envs are
|
|
198
|
+
* reaped — so a recycled env's id can't collide with a live one. Monotonic
|
|
199
|
+
* in the journal, survives daemon restarts.
|
|
200
|
+
*/
|
|
201
|
+
nextEnvSeq(stack: string): number;
|
|
202
|
+
/** Activity, NOT lease renewal: keeps idle reclamation honest without extending ownership. */
|
|
203
|
+
touchEnv(id: string): void;
|
|
204
|
+
/** Update just the recorded service pids (auto-restart keeps recovery honest). */
|
|
205
|
+
updateServicePids(id: string, pids: Record<string, ServicePid>): void;
|
|
206
|
+
saveLease(l: LeaseRow): void;
|
|
207
|
+
/**
|
|
208
|
+
* Forget a lease's preview tunnel — compare-and-swap on the pid.
|
|
209
|
+
*
|
|
210
|
+
* Every caller reached here holding a lease SNAPSHOT, so an unconditional
|
|
211
|
+
* clear let a slow one wipe the record of a tunnel someone else had already
|
|
212
|
+
* published in its place. That pid is then unnameable, and an unnameable
|
|
213
|
+
* preview pid is a public, unauthenticated URL that serves forever. Passing
|
|
214
|
+
* the pid that was actually stopped makes a stale caller a no-op; the
|
|
215
|
+
* pid-less form is only for a row that is being deleted outright.
|
|
216
|
+
*/
|
|
217
|
+
clearLeasePreview(id: string, stoppedPid?: number): void;
|
|
218
|
+
/** Record (or, with null, forget) what a restart re-publishes for this lease (decision 0045). */
|
|
219
|
+
setLeasePreviewRestore(id: string, restore: PreviewRestore | null): void;
|
|
220
|
+
private rowToLease;
|
|
221
|
+
leaseForHolder(holder: string, stack: string): LeaseRow | undefined;
|
|
222
|
+
leaseForEnv(envId: string): LeaseRow | undefined;
|
|
223
|
+
allLeases(): LeaseRow[];
|
|
224
|
+
deleteLease(id: string): void;
|
|
225
|
+
/** Shift every deadline by `ms` — the sleep pardon (decision 0009). */
|
|
226
|
+
pardon(ms: number): void;
|
|
227
|
+
}
|