@rightkit/release 0.6.5 → 0.6.7
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/README.md +196 -11
- package/admission-files.mjs +3 -2
- package/advisory-db.json +4 -0
- package/advisory-db.mjs +124 -0
- package/advisory-exceptions.mjs +13 -10
- package/android-play-upload.mjs +4 -4
- package/app-check.mjs +702 -26
- package/apple-profile-create.mjs +4 -3
- package/apple-profile-renew.mjs +5 -4
- package/bin/darwin/rightkit-swap +0 -0
- package/bin/rightkit-agent-hook.mjs +9 -0
- package/bin/rightkit-gate.mjs +671 -133
- package/binary-license-notices.mjs +2 -1
- package/build-release.mjs +20 -12
- package/cache-command.mjs +4 -3
- package/cargo-license-notices.mjs +2 -1
- package/cargo-manifest-text.mjs +25 -0
- package/cargo-profile-contract.mjs +26 -13
- package/cli/right-release.mjs +37 -7
- package/command-spec.mjs +4 -5
- package/compile-inputs.mjs +66 -0
- package/consumer-contracts.json +1044 -0
- package/consumer-contracts.mjs +627 -0
- package/consumer-input-contracts.mjs +422 -0
- package/consumer-policy.mjs +588 -0
- package/consumer-source-contracts.mjs +469 -0
- package/deny-default.toml +42 -0
- package/deps.mjs +5 -4
- package/deterministic-scans.mjs +513 -54
- package/development.mjs +5 -5
- package/disk-command.mjs +270 -30
- package/disk-policy.json +50 -10
- package/disk-policy.mjs +482 -35
- package/disk-report.mjs +129 -0
- package/door-policy.mjs +228 -0
- package/gate-executor.mjs +30 -0
- package/gate-stages.mjs +110 -0
- package/github-release.mjs +5 -3
- package/github-updater-release.mjs +48 -8
- package/github-upload-gate.mjs +6 -6
- package/init-command.mjs +175 -8
- package/ios-upload.mjs +4 -4
- package/legion-semantic-contracts.mjs +428 -0
- package/mirror-root-artifact.mjs +3 -4
- package/npm-license-notices.mjs +2 -1
- package/outdated-command.mjs +91 -0
- package/ownership-class-rules.mjs +277 -0
- package/ownership-key-authority.mjs +3 -1
- package/ownership-mandatory-set.mjs +3 -2
- package/package.json +46 -3
- package/pipeline-admission.mjs +12 -0
- package/publish-cargo.mjs +219 -4
- package/publish-update.mjs +19 -14
- package/release-config.mjs +87 -11
- package/release-dependencies-command.mjs +4 -3
- package/release-qa-audit.mjs +12 -10
- package/release-qa-command.mjs +42 -7
- package/release-qa-hardening.mjs +10 -3
- package/release-qa-policy.mjs +214 -47
- package/release.mjs +4 -4
- package/rightkit-registry-resolver.mjs +5 -2
- package/rightkit-versions.json +178 -170
- package/shell-contract-bridge.json +251 -0
- package/shell-contract.mjs +1069 -0
- package/sign-updater.mjs +24 -4
- package/source-closure-rules.mjs +204 -0
- package/stage-sidecar-command.mjs +7 -6
- package/stage-sidecar.mjs +437 -114
- package/suite-api.generated.mjs +96 -6
- package/suite-trust-roots.mjs +198 -0
- package/test-command.mjs +3 -2
- package/upload-release.mjs +3 -2
- package/web-build.mjs +2 -1
package/development.mjs
CHANGED
|
@@ -3,7 +3,8 @@ import { spawnSync } from "./background-process.mjs";
|
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, lstatSync, readdirSync, realpathSync, renameSync, rmSync, statSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { fileURLToPath
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { defaultReleaseConfigFile, importReleaseConfig } from "./release-config.mjs";
|
|
7
8
|
import { assertPrimaryReleaseCheckout } from "./release-invocation.mjs";
|
|
8
9
|
import { assertReleaseExecutionAllowed } from "./execution-policy.mjs";
|
|
9
10
|
import { acquireManagedRunLease, managedRuntimeClient, managedRuntimeCommand, runCargoSync } from "./managed-cargo.mjs";
|
|
@@ -348,9 +349,9 @@ function resolveJavaScriptEntrypoint(candidate) {
|
|
|
348
349
|
function git(cwd, args) { const result = spawnSync("git", args, { cwd, encoding: "utf8", windowsHide: true }); if (result.status !== 0) throw new Error(`git ${args.join(" ")} failed`); return result.stdout.trim(); }
|
|
349
350
|
function inside(root, child) { const rel = path.relative(path.resolve(root), path.resolve(child)); return rel === "" || (rel !== ".." && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel)); }
|
|
350
351
|
export async function main(args = process.argv.slice(2)) {
|
|
351
|
-
let platform, configPath =
|
|
352
|
+
let platform, configPath = defaultReleaseConfigFile(), dryRun = false, buildOnly = false, serial;
|
|
352
353
|
for (let i = 0; i < args.length; i++) {
|
|
353
|
-
if (args[i] === "-h" || args[i] === "--help") { console.log("usage: right-release dev --platform win|mac|android|ios [--config right-release.config.
|
|
354
|
+
if (args[i] === "-h" || args[i] === "--help") { console.log("usage: right-release dev --platform win|mac|android|ios [--config right-release.config.json] [--dry-run] [--build-only] [--serial <adb serial | iOS device id>]"); return; }
|
|
354
355
|
if (args[i] === "--platform") platform = args[++i];
|
|
355
356
|
else if (args[i] === "--config") configPath = args[++i];
|
|
356
357
|
else if (args[i] === "--dry-run") dryRun = true;
|
|
@@ -366,8 +367,7 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
366
367
|
delete process.env.CARGO_INCREMENTAL;
|
|
367
368
|
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path");
|
|
368
369
|
if (pathKey) process.env[pathKey] = safeEnv[pathKey] ?? process.env[pathKey];
|
|
369
|
-
const
|
|
370
|
-
const config = loaded.default ?? loaded.config ?? loaded;
|
|
370
|
+
const config = await importReleaseConfig(absoluteConfig, { lenient: true });
|
|
371
371
|
const result = await runDevelopment({ root: path.dirname(absoluteConfig), platform, config, dryRun, buildOnly, serial });
|
|
372
372
|
if (dryRun) console.log(JSON.stringify(result.plan, null, 2));
|
|
373
373
|
return result;
|
package/disk-command.mjs
CHANGED
|
@@ -6,19 +6,74 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
6
6
|
import { spawnSync } from "./background-process.mjs";
|
|
7
7
|
import { compareVersions } from "./rightkit-sync.mjs";
|
|
8
8
|
import {
|
|
9
|
-
DISK_EXIT_WARNING, applySweep, checkDisk, ensureScratchRoots, formatBytes, listOpenPaths, logEvent, planSweep, platformKey, purgeHoldings, readDiskPolicy, setWorkspaceSweepAllowed,
|
|
9
|
+
DISK_EXIT_WARNING, applySweep, checkDisk, checkFloors, ensureScratchRoots, formatBytes, holdingBasesOnVolume, inUseByOpenPaths, lastEvent, listOpenPaths, logEvent, planSweep, platformKey, purgeHoldings, readDiskPolicy, readRecentEvents, setWorkspaceSweepAllowed,
|
|
10
|
+
createRunRoot, releaseRunRoot,
|
|
10
11
|
} from "./disk-policy.mjs";
|
|
12
|
+
import { buildDiskReport, measureScratchUnits, reportDue } from "./disk-report.mjs";
|
|
11
13
|
import { evaluateBinding, installStableRoot, readScheduleBinding, selfCheck } from "./disk-stable-root.mjs";
|
|
12
14
|
|
|
13
15
|
export const LAUNCHD_LABEL = "dev.rightkit.disk-hygiene";
|
|
14
16
|
export const WINDOWS_TASK = "RightKit Disk Hygiene";
|
|
15
17
|
|
|
18
|
+
// One trigger on both hosts, every hour, running `disk run --auto`. The first run after the full-pass time is the full pass;
|
|
19
|
+
// every other run is the light one (see fullPassDue), so no second trigger or launchd calendar key is needed.
|
|
20
|
+
export const RUN_INTERVAL_SECONDS = 3600;
|
|
21
|
+
export const FULL_PASS_HOUR = 3;
|
|
22
|
+
export const FULL_PASS_MINUTE = 30;
|
|
23
|
+
|
|
24
|
+
/** True when no full pass has finished since the most recent local `hour:minute` at or before `now` (a missed fire runs full
|
|
25
|
+
* at the next trigger, and after one full pass the remaining runs of that day are light). No full pass on record is due. */
|
|
26
|
+
export function fullPassDue({ now, lastFullMs, hour = FULL_PASS_HOUR, minute = FULL_PASS_MINUTE }) {
|
|
27
|
+
if (lastFullMs === null || lastFullMs === undefined || !Number.isFinite(lastFullMs)) return true;
|
|
28
|
+
const slot = new Date(now);
|
|
29
|
+
slot.setHours(hour, minute, 0, 0);
|
|
30
|
+
if (slot.getTime() > now) slot.setDate(slot.getDate() - 1);
|
|
31
|
+
return lastFullMs < slot.getTime();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The floor every run event reports: the agent volume's (readable or not: an unmounted D: logs null free bytes, never the internal
|
|
35
|
+
* drive's), else the first readable floor, else the first. Its `id` is logged next to the number as `floorId`. */
|
|
36
|
+
export function leadFloor(floors) {
|
|
37
|
+
return floors.find((f) => f.agentVolume) ?? floors.find((f) => !f.absent) ?? floors[0];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const floorKey = (f) => f.id ?? f.label;
|
|
41
|
+
|
|
42
|
+
/** Why an emergency purge must not run yet, or null when it may: `uncalibrated` (no `disk.calibrated` event), `calibration-stale` (the
|
|
43
|
+
* log holds one, but none lists every agentVolume floor of this policy with the threshold it has now; a floor that cannot be read has
|
|
44
|
+
* no threshold to compare), `log-unreadable` (any read error but a missing log: nothing proves a calibration, so it counts as none).
|
|
45
|
+
* The calibration is searched backwards through the whole log, so a busy day of sweep lines cannot bury it. */
|
|
46
|
+
export function calibrationGap({ logFile, floors }) {
|
|
47
|
+
const agent = floors.filter((f) => f.agentVolume);
|
|
48
|
+
const covers = (e) => Array.isArray(e.floors) && agent.every((f) => Number.isFinite(f.thresholdBytes) && e.floors.some((r) => r && r.id === floorKey(f) && r.thresholdBytes === f.thresholdBytes));
|
|
49
|
+
try {
|
|
50
|
+
if (lastEvent(logFile, ["disk.calibrated"], { accept: covers })) return null;
|
|
51
|
+
return lastEvent(logFile, ["disk.calibrated"]) ? "calibration-stale" : "uncalibrated";
|
|
52
|
+
} catch { return "log-unreadable"; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function renderFloor(f) {
|
|
56
|
+
if (f.error) return `${f.label}: free space cannot be read (${f.error}) WARN`;
|
|
57
|
+
return `${f.label}: ${formatBytes(f.freeBytes)} free of ${formatBytes(f.totalBytes)} (floor ${formatBytes(f.thresholdBytes)})${f.warn ? " WARN" : ""}${f.hardWarn ? ` BELOW THE ${formatBytes(f.hardFreeBytes)} BUILD REFUSAL LEVEL` : ""}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
16
60
|
export function renderCheck(result) {
|
|
17
|
-
const lines =
|
|
61
|
+
const lines = (result.floors ?? [result.floor]).map(renderFloor);
|
|
18
62
|
for (const w of result.writers) {
|
|
19
|
-
const size = w.error ? `error: ${w.error}` : w.matches === 0 ? "absent" : `${formatBytes(w.bytes)} / cap ${formatBytes(w.capBytes)}`;
|
|
63
|
+
const size = w.error ? `error: ${w.error}` : w.matches === 0 ? "absent" : `${formatBytes(w.bytes)} / cap ${formatBytes(w.capBytes)}${w.capSource === "rustright" ? " (RustRight budget)" : ""}`;
|
|
20
64
|
lines.push(` ${w.id.padEnd(28)} ${w.action.padEnd(6)} ${size}${w.over ? " OVER CAP" : ""}`);
|
|
21
65
|
}
|
|
66
|
+
for (const u of result.unmanaged ?? []) {
|
|
67
|
+
lines.push(` unmanaged remainder on ${u.floor} ${formatBytes(u.unmanagedBytes)} (estimate: used ${formatBytes(u.usedBytes)} - writers ${formatBytes(u.writerBytes)})`);
|
|
68
|
+
if (u.uncoveredTopLevel.length) {
|
|
69
|
+
lines.push(`Uncovered top-level on ${u.floor} (${u.uncoveredTopLevel.length}):`);
|
|
70
|
+
for (const e of u.uncoveredTopLevel) lines.push(` ${(e.symlink ? "symlink" : formatBytes(e.bytes)).padStart(9)} ${e.path}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (result.strays?.length) {
|
|
74
|
+
lines.push(`Stray cargo targets (${result.strays.length}):`);
|
|
75
|
+
for (const t of result.strays) lines.push(` ${formatBytes(t.bytes).padStart(9)} ${String(t.ageDays).padStart(4)}d ${t.path}`);
|
|
76
|
+
}
|
|
22
77
|
if (result.unknown?.length) {
|
|
23
78
|
lines.push(`Unknown top-level entries (${result.unknown.length}):`);
|
|
24
79
|
for (const u of result.unknown) lines.push(` ${(u.symlink ? "symlink" : formatBytes(u.bytes)).padStart(9)} ${String(u.ageDays).padStart(4)}d ${u.path}`);
|
|
@@ -43,12 +98,14 @@ export function renderSweep(plan, applied, { all = false } = {}) {
|
|
|
43
98
|
const reportOnly = plan.skipped.filter((s) => s.reason === "report-only");
|
|
44
99
|
if (reportOnly.length) lines.push(` workspace-unknown is report-only on this host: ${reportOnly.length} unknown entr${reportOnly.length === 1 ? "y" : "ies"} will not be moved (opt in with: right-release disk allow-workspace-sweep)`);
|
|
45
100
|
if (plan.skipped.length) lines.push(` skipped: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")}`);
|
|
101
|
+
// Items that were kept, leased or touched between the plan and the move are left where they are.
|
|
102
|
+
if (applied?.skipped?.length) lines.push(` left in place at apply time: ${applied.skipped.map((r) => `${path.basename(r.path)} (${r.reason})`).join(", ")}`);
|
|
46
103
|
return lines.join("\n");
|
|
47
104
|
}
|
|
48
105
|
|
|
49
|
-
function parse(rest) {
|
|
50
|
-
const known = new Set(["--json", "--dry-run", "--delete", "--all", "--days", "--policy"]);
|
|
51
|
-
const opts = { json: false, dryRun: false, deleteMode: false, all: false, days: undefined, policy: undefined };
|
|
106
|
+
function parse(rest, command) {
|
|
107
|
+
const known = new Set(["--json", "--dry-run", "--delete", "--all", "--days", "--policy", "--auto", "--light", "--calibrate"]);
|
|
108
|
+
const opts = { json: false, dryRun: false, deleteMode: false, all: false, auto: false, light: false, calibrate: false, days: undefined, policy: undefined };
|
|
52
109
|
for (let i = 0; i < rest.length; i += 1) {
|
|
53
110
|
const a = rest[i];
|
|
54
111
|
if (!known.has(a)) throw new Error(`disk: unknown option ${a}`);
|
|
@@ -56,10 +113,15 @@ function parse(rest) {
|
|
|
56
113
|
else if (a === "--dry-run") opts.dryRun = true;
|
|
57
114
|
else if (a === "--delete") opts.deleteMode = true;
|
|
58
115
|
else if (a === "--all") opts.all = true;
|
|
116
|
+
else if (a === "--auto") opts.auto = true;
|
|
117
|
+
else if (a === "--light") opts.light = true;
|
|
118
|
+
else if (a === "--calibrate") opts.calibrate = true;
|
|
59
119
|
else { const v = rest[++i]; if (!v) throw new Error(`disk: ${a} needs a value`); if (a === "--days") opts.days = Number(v); else opts.policy = v; }
|
|
60
120
|
}
|
|
61
121
|
if (opts.days !== undefined && !(opts.days >= 0)) throw new Error("disk: --days must be a non-negative number");
|
|
62
122
|
if (opts.dryRun && opts.deleteMode) throw new Error("disk sweep accepts either --dry-run or --delete");
|
|
123
|
+
if (opts.auto && opts.light) throw new Error("disk run accepts either --auto or --light");
|
|
124
|
+
if (opts.calibrate && command !== "check") throw new Error("disk: --calibrate is only valid with check");
|
|
63
125
|
return opts;
|
|
64
126
|
}
|
|
65
127
|
|
|
@@ -148,7 +210,7 @@ function defaultGlobalRoots() {
|
|
|
148
210
|
return roots;
|
|
149
211
|
}
|
|
150
212
|
|
|
151
|
-
export function scheduleSpec({ platform = process.platform, nodePath = process.execPath, cli = path.join(path.dirname(fileURLToPath(import.meta.url)), "cli", "right-release.mjs"), home = os.homedir()
|
|
213
|
+
export function scheduleSpec({ platform = process.platform, nodePath = process.execPath, cli = path.join(path.dirname(fileURLToPath(import.meta.url)), "cli", "right-release.mjs"), home = os.homedir() } = {}) {
|
|
152
214
|
if (platform === "darwin") {
|
|
153
215
|
// launchd cannot open log files on an external volume for a background job (spawn fails with EX_CONFIG);
|
|
154
216
|
// `disk run` writes its own structured log to disk-hygiene.jsonl on the heavy volume.
|
|
@@ -157,8 +219,8 @@ export function scheduleSpec({ platform = process.platform, nodePath = process.e
|
|
|
157
219
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
158
220
|
<plist version="1.0"><dict>
|
|
159
221
|
<key>Label</key><string>${LAUNCHD_LABEL}</string>
|
|
160
|
-
<key>ProgramArguments</key><array><string>${nodePath}</string><string>${cli}</string><string>disk</string><string>run</string></array>
|
|
161
|
-
<key>
|
|
222
|
+
<key>ProgramArguments</key><array><string>${nodePath}</string><string>${cli}</string><string>disk</string><string>run</string><string>--auto</string></array>
|
|
223
|
+
<key>StartInterval</key><integer>${RUN_INTERVAL_SECONDS}</integer>
|
|
162
224
|
<key>StandardOutPath</key><string>/dev/null</string>
|
|
163
225
|
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
164
226
|
<key>ProcessType</key><string>Background</string><key>LowPriorityIO</key><true/><key>Nice</key><integer>10</integer>
|
|
@@ -166,8 +228,8 @@ export function scheduleSpec({ platform = process.platform, nodePath = process.e
|
|
|
166
228
|
`;
|
|
167
229
|
return { platform: "mac", file, plist };
|
|
168
230
|
}
|
|
169
|
-
const script = `$a = New-ScheduledTaskAction -Execute '${nodePath}' -Argument '"${cli}" disk run'
|
|
170
|
-
$t = New-ScheduledTaskTrigger -
|
|
231
|
+
const script = `$a = New-ScheduledTaskAction -Execute '${nodePath}' -Argument '"${cli}" disk run --auto'
|
|
232
|
+
$t = New-ScheduledTaskTrigger -Once -At 00:05 -RepetitionInterval (New-TimeSpan -Hours ${RUN_INTERVAL_SECONDS / 3600}) -RepetitionDuration (New-TimeSpan -Days 3650)
|
|
171
233
|
$s = New-ScheduledTaskSettingsSet -Hidden -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 2)
|
|
172
234
|
$p = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\\$env:USERNAME" -LogonType Interactive -RunLevel Limited
|
|
173
235
|
Register-ScheduledTask -TaskName '${WINDOWS_TASK}' -Action $a -Trigger $t -Settings $s -Principal $p -Force | Out-Null`;
|
|
@@ -186,11 +248,35 @@ export function sourceCli({ explicit, env = process.env, self = path.join(here,
|
|
|
186
248
|
return resolve({ explicit, env });
|
|
187
249
|
}
|
|
188
250
|
|
|
189
|
-
|
|
251
|
+
const CALIBRATE_NOTICE = "before the first emergency purge can act: run `right-release disk check --json` and compare freeBytes with each floor, then `right-release disk check --calibrate`; until a calibration is recorded an emergency purges nothing";
|
|
252
|
+
|
|
253
|
+
const AGE_LIMIT_HOURS = 26;
|
|
254
|
+
|
|
255
|
+
const humanAge = (ms) => (ms < 3600_000 ? `${Math.max(0, Math.round(ms / 60_000))} min` : `${(ms / 3600_000).toFixed(1)} h`);
|
|
256
|
+
|
|
257
|
+
/** What the log says about the hourly job: the newest completed full pass (disk.run.ok or disk.floor.warn; a legacy line without
|
|
258
|
+
* `pass` is a full pass) and the newest light pass. A problem when there is no full pass on record or it is older than `maxHours`.
|
|
259
|
+
* A light pass alone never satisfies the requirement: it proves the job fires, not that the daily work ran. Pure. */
|
|
260
|
+
export function evaluateRunAge({ events, now, maxHours = AGE_LIMIT_HOURS }) {
|
|
261
|
+
const newest = (accept) => events.filter(accept).sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts)).at(-1) ?? null;
|
|
262
|
+
const lastFull = newest((e) => (e.event === "disk.run.ok" || e.event === "disk.floor.warn") && e.pass !== "light" && Number.isFinite(Date.parse(e.ts)));
|
|
263
|
+
const lastLight = newest((e) => e.event === "disk.run.light" && Number.isFinite(Date.parse(e.ts)));
|
|
264
|
+
const problems = [];
|
|
265
|
+
if (!lastFull) problems.push("no completed full run recorded");
|
|
266
|
+
else {
|
|
267
|
+
const hours = (now - Date.parse(lastFull.ts)) / 3600_000;
|
|
268
|
+
if (hours > maxHours) problems.push(`last completed full run was ${hours.toFixed(1)} h ago (limit ${maxHours} h)`);
|
|
269
|
+
}
|
|
270
|
+
return { ok: problems.length === 0, lastFull, lastLight, problems };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function scheduleStatus({ platform, stdout, env = process.env, home: defaultHome = os.homedir(), schedule: injected = {} }) {
|
|
274
|
+
const home = injected.home ?? defaultHome;
|
|
190
275
|
let binding = null;
|
|
191
276
|
let loaded = false;
|
|
192
|
-
try { binding = readScheduleBinding({ platform, home }); } catch (error) { stdout(`status: cannot read the schedule (${error.message})`); return 1; }
|
|
193
|
-
if (
|
|
277
|
+
try { binding = "binding" in injected ? injected.binding : readScheduleBinding({ platform, home }); } catch (error) { stdout(`status: cannot read the schedule (${error.message})`); return 1; }
|
|
278
|
+
if ("loaded" in injected) loaded = injected.loaded;
|
|
279
|
+
else if (platform === "darwin") {
|
|
194
280
|
const r = spawnSync("launchctl", ["print", `gui/${process.getuid()}/${LAUNCHD_LABEL}`], { encoding: "utf8" });
|
|
195
281
|
loaded = r.status === 0;
|
|
196
282
|
} else loaded = Boolean(binding);
|
|
@@ -198,14 +284,38 @@ function scheduleStatus({ platform, stdout, env = process.env, home = os.homedir
|
|
|
198
284
|
const verdict = evaluateBinding(binding, { platform, env, home });
|
|
199
285
|
stdout(`${loaded ? "installed" : "plist present but not loaded"} (${binding.source})`);
|
|
200
286
|
stdout(` target: ${binding.cli ?? "(none)"}`);
|
|
201
|
-
if (verdict.ok) {
|
|
202
|
-
|
|
203
|
-
|
|
287
|
+
if (!verdict.ok) {
|
|
288
|
+
for (const p of verdict.problems) stdout(` PROBLEM: ${p}`);
|
|
289
|
+
return 1;
|
|
290
|
+
}
|
|
291
|
+
stdout(" binding ok: stable root, target present");
|
|
292
|
+
const now = injected.now ?? Date.now();
|
|
293
|
+
const logFile = injected.logFile ?? path.join(readDiskPolicy().heavyVolume[platformKey(platform)], "disk-hygiene.jsonl");
|
|
294
|
+
// The newest full and light lines, each found by a backwards scan that grows until it hits one (a busy day can bury them under
|
|
295
|
+
// thousands of sweep lines). A log that cannot be read is said so: "no completed full run" would be a guess.
|
|
296
|
+
let events;
|
|
297
|
+
try {
|
|
298
|
+
events = injected.readEvents ? injected.readEvents(logFile) : [lastEvent(logFile, ["disk.run.ok", "disk.floor.warn"], { accept: (e) => e.pass !== "light" }), lastEvent(logFile, ["disk.run.light"])].filter(Boolean);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
stdout(` PROBLEM: the run log ${logFile} cannot be read (${error.code ?? error.message}: ${error.message}); the last run is unknown`);
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
const age = evaluateRunAge({ events, now });
|
|
304
|
+
if (age.lastFull) stdout(` last full run: ${humanAge(now - Date.parse(age.lastFull.ts))} ago (${age.lastFull.ts}, ${age.lastFull.event})`);
|
|
305
|
+
if (age.lastLight) stdout(` last light run: ${humanAge(now - Date.parse(age.lastLight.ts))} ago`);
|
|
306
|
+
else stdout(" last light run: none recorded");
|
|
307
|
+
for (const p of age.problems) stdout(` PROBLEM: ${p}`);
|
|
308
|
+
// Informational: the emergency purge is simply off until a calibration is recorded, which is not a fault of the job.
|
|
309
|
+
let calibrated = null;
|
|
310
|
+
try { calibrated = lastEvent(logFile, ["disk.calibrated"]); } catch { /* an unreadable log counts as no calibration */ }
|
|
311
|
+
const calibratedAt = calibrated ? Date.parse(calibrated.ts) : NaN;
|
|
312
|
+
stdout(Number.isFinite(calibratedAt) ? ` calibration: recorded ${humanAge(now - calibratedAt)} ago` : " calibration: NOT RECORDED (emergency purge disabled)");
|
|
313
|
+
return loaded && age.ok ? 0 : 1;
|
|
204
314
|
}
|
|
205
315
|
|
|
206
|
-
function schedule(sub, { stdout, cli, platform = process.platform, env = process.env }) {
|
|
316
|
+
function schedule(sub, { stdout, cli, platform = process.platform, env = process.env, injected }) {
|
|
207
317
|
if (!["install", "uninstall", "status"].includes(sub)) throw new Error("disk schedule install|uninstall|status");
|
|
208
|
-
if (sub === "status") return scheduleStatus({ platform, stdout, env });
|
|
318
|
+
if (sub === "status") return scheduleStatus({ platform, stdout, env, schedule: injected });
|
|
209
319
|
const spec = scheduleSpec({ platform });
|
|
210
320
|
if (platform === "darwin") {
|
|
211
321
|
const uid = String(process.getuid());
|
|
@@ -214,13 +324,15 @@ function schedule(sub, { stdout, cli, platform = process.platform, env = process
|
|
|
214
324
|
stdout(`${installed.unchanged ? "kept" : "installed"} @rightkit/release ${installed.version ?? "?"} in ${installed.stable}`);
|
|
215
325
|
const bound = scheduleSpec({ platform, cli: installed.cli });
|
|
216
326
|
mkdirSync(path.dirname(bound.file), { recursive: true });
|
|
217
|
-
|
|
218
|
-
|
|
327
|
+
const policy = readDiskPolicy();
|
|
328
|
+
mkdirSync(policy.heavyVolume[platformKey(platform)], { recursive: true });
|
|
329
|
+
ensureScratchRoots({ policy, platform, env });
|
|
219
330
|
writeFileSync(bound.file, bound.plist);
|
|
220
331
|
spawnSync("launchctl", ["bootout", `gui/${uid}/${LAUNCHD_LABEL}`], { encoding: "utf8" });
|
|
221
332
|
const r = spawnSync("launchctl", ["bootstrap", `gui/${uid}`, bound.file], { encoding: "utf8" });
|
|
222
333
|
if (r.status !== 0) throw new Error(`launchctl bootstrap failed: ${r.stderr}`);
|
|
223
|
-
stdout(`installed ${bound.file} (
|
|
334
|
+
stdout(`installed ${bound.file} (hourly: full pass after 03:30, light passes otherwise), bound to ${installed.cli}`);
|
|
335
|
+
stdout(CALIBRATE_NOTICE);
|
|
224
336
|
} else {
|
|
225
337
|
spawnSync("launchctl", ["bootout", `gui/${uid}/${LAUNCHD_LABEL}`], { encoding: "utf8" });
|
|
226
338
|
if (existsSync(spec.file)) unlinkSync(spec.file);
|
|
@@ -235,7 +347,8 @@ function schedule(sub, { stdout, cli, platform = process.platform, env = process
|
|
|
235
347
|
const bound = scheduleSpec({ platform, cli: installed.cli });
|
|
236
348
|
const r = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", bound.script], { encoding: "utf8" });
|
|
237
349
|
if (r.status !== 0) throw new Error(`Register-ScheduledTask failed: ${r.stderr || r.stdout}`);
|
|
238
|
-
stdout(`installed scheduled task "${WINDOWS_TASK}" (
|
|
350
|
+
stdout(`installed scheduled task "${WINDOWS_TASK}" (hourly, hidden: full pass after 03:30, light passes otherwise), bound to ${installed.cli}`);
|
|
351
|
+
stdout(CALIBRATE_NOTICE);
|
|
239
352
|
} else {
|
|
240
353
|
spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `Unregister-ScheduledTask -TaskName '${WINDOWS_TASK}' -Confirm:$false`], { encoding: "utf8" });
|
|
241
354
|
stdout("uninstalled");
|
|
@@ -533,13 +646,47 @@ function probeCommand(rest, { env, stdout, platform, deps }) {
|
|
|
533
646
|
return { exitCode: report.internalWritable || leftovers.length || problems.length ? 1 : 0, report, problems };
|
|
534
647
|
}
|
|
535
648
|
|
|
649
|
+
function runRootCommand(rest, { env, stdout, platform, deps }) {
|
|
650
|
+
const stderr = deps.stderr ?? console.error;
|
|
651
|
+
const opts = { name: undefined, days: undefined, release: false, json: false, policy: undefined };
|
|
652
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
653
|
+
const a = rest[i];
|
|
654
|
+
if (a === "--release") opts.release = true;
|
|
655
|
+
else if (a === "--json") opts.json = true;
|
|
656
|
+
else if (a === "--days" || a === "--policy") {
|
|
657
|
+
const v = rest[++i];
|
|
658
|
+
if (!v) throw new Error(`disk run-root: ${a} needs a value`);
|
|
659
|
+
if (a === "--days") opts.days = Number(v); else opts.policy = v;
|
|
660
|
+
} else if (a.startsWith("--")) throw new Error(`disk run-root: unknown option ${a}`);
|
|
661
|
+
else if (opts.name === undefined) opts.name = a;
|
|
662
|
+
else throw new Error("disk run-root: one run root name only");
|
|
663
|
+
}
|
|
664
|
+
if (opts.name === undefined) throw new Error("disk run-root <name> [--days N] | --release <name>");
|
|
665
|
+
if (opts.release && opts.days !== undefined) throw new Error("disk run-root: --release takes no --days");
|
|
666
|
+
const policy = readDiskPolicy(opts.policy);
|
|
667
|
+
const common = { policy, platform, env, name: opts.name, ...(deps.runRoot ?? {}) };
|
|
668
|
+
try {
|
|
669
|
+
if (opts.release) {
|
|
670
|
+
const r = releaseRunRoot(common);
|
|
671
|
+
stdout(opts.json ? JSON.stringify(r) : r.released ? `released ${r.path}: the lease is gone, the directory ages out by the scratch rules` : `${r.path} had no lease`);
|
|
672
|
+
return { exitCode: 0, result: r };
|
|
673
|
+
}
|
|
674
|
+
const r = createRunRoot({ ...common, days: opts.days });
|
|
675
|
+
stdout(opts.json ? JSON.stringify({ path: r.path, ...r.lease }) : `${r.renewed ? "renewed" : "leased"} run root ${r.path} until ${r.lease.leaseUntil}; the sweep skips it until then (end it early: right-release disk run-root --release ${opts.name})`);
|
|
676
|
+
return { exitCode: 0, result: r };
|
|
677
|
+
} catch (error) {
|
|
678
|
+
stderr(error.message);
|
|
679
|
+
return { exitCode: 1, error: error.message };
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
536
683
|
export async function runDiskCommand(args = process.argv.slice(2), { env = process.env, stdout = console.log, platform = process.platform, deps = {} } = {}) {
|
|
537
684
|
const [command, ...rest] = args;
|
|
538
685
|
if (command === "agent-env") return agentEnvCommand(rest, { env, stdout, platform, deps });
|
|
539
686
|
if (command === "probe") return probeCommand(rest, { env, stdout, platform, deps });
|
|
540
687
|
if (command === "schedule") {
|
|
541
688
|
const at = rest.indexOf("--cli");
|
|
542
|
-
const exitCode = schedule(rest[0], { stdout, cli: at === -1 ? undefined : rest[at + 1], platform, env });
|
|
689
|
+
const exitCode = schedule(rest[0], { stdout, cli: at === -1 ? undefined : rest[at + 1], platform, env, injected: deps.schedule });
|
|
543
690
|
return { exitCode };
|
|
544
691
|
}
|
|
545
692
|
if (command === "allow-workspace-sweep") {
|
|
@@ -548,7 +695,8 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
548
695
|
stdout(off ? `workspace-unknown sweep revoked on this host (${file}.revoked)` : `workspace-unknown sweep allowed on this host (${file}); review \`disk sweep --dry-run --all\` first, revoke with --off`);
|
|
549
696
|
return { exitCode: 0 };
|
|
550
697
|
}
|
|
551
|
-
|
|
698
|
+
if (command === "run-root") return runRootCommand(rest, { env, stdout, platform, deps });
|
|
699
|
+
const opts = parse(rest, command);
|
|
552
700
|
const policy = readDiskPolicy(opts.policy);
|
|
553
701
|
const key = platformKey(platform);
|
|
554
702
|
const logFile = path.join(policy.heavyVolume[key], "disk-hygiene.jsonl");
|
|
@@ -556,6 +704,30 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
556
704
|
const check = () => checkDisk({ policy, platform, env, ...deps.check });
|
|
557
705
|
const plan = () => planSweep({ policy, platform, env, staleDays: opts.days, openPaths: platform === "win32" ? null : (deps.openPaths ?? listOpenPaths({ platform })), ...deps.plan });
|
|
558
706
|
|
|
707
|
+
if (command === "check" && opts.calibrate) {
|
|
708
|
+
const nowMs = deps.now ?? Date.now();
|
|
709
|
+
const floors = checkFloors({ policy, platform, ...(deps.check?.space ? { space: deps.check.space } : {}) });
|
|
710
|
+
const below = floors.filter((f) => f.agentVolume && f.warn);
|
|
711
|
+
if (below.length) {
|
|
712
|
+
// Recording a calibration while the volume is already under its floor would make a chronically low volume an accident.
|
|
713
|
+
const lines = below.map((f) => (f.error ? `${f.label}: free space cannot be read (${f.error})` : `${f.label}: ${formatBytes(f.freeBytes)} free, ${formatBytes(f.thresholdBytes - f.freeBytes)} below its floor of ${formatBytes(f.thresholdBytes)}`));
|
|
714
|
+
lines.push("Nothing was recorded: lower the floor in disk-policy.json or free space first, so that running this volume low is a decision, never an accident.");
|
|
715
|
+
out({ calibrated: false, below: below.map((f) => ({ id: floorKey(f), freeBytes: f.freeBytes ?? null, thresholdBytes: f.thresholdBytes ?? null })) }, lines.join("\n"));
|
|
716
|
+
return { exitCode: 1, calibrated: false, floors };
|
|
717
|
+
}
|
|
718
|
+
const ts = new Date(nowMs).toISOString();
|
|
719
|
+
const recorded = floors.map((f) => ({ id: floorKey(f), label: f.label, path: f.path, thresholdBytes: f.thresholdBytes ?? null, freeBytes: f.freeBytes ?? null, agentVolume: Boolean(f.agentVolume) }));
|
|
720
|
+
logEvent(logFile, { ts, event: "disk.calibrated", floors: recorded });
|
|
721
|
+
// logEvent never throws; read the line back so "recorded" is only ever said about a line that is in the log.
|
|
722
|
+
let found = false;
|
|
723
|
+
try { found = Boolean(lastEvent(logFile, ["disk.calibrated"], { accept: (e) => e.ts === ts })); } catch { /* not found */ }
|
|
724
|
+
if (!found) {
|
|
725
|
+
out({ calibrated: false, error: `cannot write ${logFile}` }, `Nothing was recorded: ${logFile} cannot be written`);
|
|
726
|
+
return { exitCode: 1, calibrated: false, floors };
|
|
727
|
+
}
|
|
728
|
+
out({ calibrated: true, logFile, floors: recorded }, [`Calibration recorded in ${logFile}:`, ...recorded.map((f) => ` ${f.label}: ${f.freeBytes === null ? "free space unreadable" : `${formatBytes(f.freeBytes)} free`}, floor ${f.thresholdBytes === null ? "unknown" : formatBytes(f.thresholdBytes)}${f.agentVolume ? " (agent volume)" : ""}`), "Emergency purge is enabled while these floors stay as they are; changing a floor needs a new calibration."].join("\n"));
|
|
729
|
+
return { exitCode: 0, calibrated: true, floors: recorded };
|
|
730
|
+
}
|
|
559
731
|
if (command === "check") {
|
|
560
732
|
const result = check();
|
|
561
733
|
out(result, renderCheck(result));
|
|
@@ -582,6 +754,62 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
582
754
|
logEvent(logFile, { event: "disk.run.selfcheck.fail", problems: self.problems, version: self.version });
|
|
583
755
|
return { exitCode: 2, selfCheck: self };
|
|
584
756
|
}
|
|
757
|
+
const nowMs = deps.now ?? Date.now();
|
|
758
|
+
const log = (event) => logEvent(logFile, { ts: new Date(nowMs).toISOString(), ...event });
|
|
759
|
+
// The hourly job passes --auto: the first run after the full-pass time is the full pass, every other run is light.
|
|
760
|
+
// A bare `disk run` is always the full pass.
|
|
761
|
+
// A log that does not exist means the job never ran (full pass due). A log that exists but cannot be read (EIO, a volume that went away)
|
|
762
|
+
// proves nothing about the last full pass: the run stays light instead of repeating the full pass every hour.
|
|
763
|
+
let lastFull = null;
|
|
764
|
+
let logReadError = null;
|
|
765
|
+
if (opts.auto) {
|
|
766
|
+
try { lastFull = lastEvent(logFile, ["disk.run.ok", "disk.floor.warn"], { accept: (e) => e.pass !== "light" }); } catch (error) { logReadError = error; }
|
|
767
|
+
}
|
|
768
|
+
const light = opts.light || (opts.auto && (logReadError !== null || !fullPassDue({ now: nowMs, lastFullMs: lastFull ? Date.parse(lastFull.ts) : null })));
|
|
769
|
+
// Only the agent volume running below its soft floor is an emergency: the undo window is spent
|
|
770
|
+
// (days 0, so what this pass just moved is purged too). Warnings from other floors or writers are not.
|
|
771
|
+
const emergencyFloor = (floors) => floors.find((f) => f.agentVolume && f.warn && !f.absent);
|
|
772
|
+
// Purging without an undo window on numbers nobody has looked at is how a mis-set floor would empty every holding folder each
|
|
773
|
+
// hour, so it waits for a recorded calibration (`disk check --calibrate`). Without one the run is an ordinary run: null.
|
|
774
|
+
const emergencyPurge = (squeezed, floors) => {
|
|
775
|
+
const gap = calibrationGap({ logFile, floors });
|
|
776
|
+
if (gap) {
|
|
777
|
+
logEvent(logFile, { event: "disk.emergency.skipped", reason: gap, floorId: floorKey(squeezed), freeBytes: squeezed.freeBytes, thresholdBytes: squeezed.thresholdBytes });
|
|
778
|
+
stdout(`EMERGENCY SKIPPED (${gap}): ${squeezed.label} is below its floor but no calibration is recorded; run \`right-release disk check --json\`, compare freeBytes with the floor, then \`right-release disk check --calibrate\``);
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
// Only the holding bases on the squeezed volume: emptying another volume's undo window would free nothing here.
|
|
782
|
+
const bases = holdingBasesOnVolume(squeezed.path, { policy, platform, env, ...(deps.deviceOf ? { deviceOf: deps.deviceOf } : {}) });
|
|
783
|
+
const purged = purgeHoldings({ policy, platform, env, days: 0, bases, ...deps.purge });
|
|
784
|
+
stdout(`purged ${purged.results.filter((r) => r.ok).length} holding dir(s), ${formatBytes(purged.purgedBytes)}`);
|
|
785
|
+
stdout("EMERGENCY: the agent volume is below its soft floor, holding folders were purged without their undo window");
|
|
786
|
+
logEvent(logFile, { event: "disk.run.emergency", floor: squeezed.id ?? squeezed.label, freeBytes: squeezed.freeBytes, purgedBytes: purged.purgedBytes });
|
|
787
|
+
return purged;
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
if (light) {
|
|
791
|
+
// Stat-only: free space of the floors, the cheap temp-like writers and the scratch roots. No du of any writer, no workspace
|
|
792
|
+
// scan, no stray walk, and lsof only once a candidate exists. It purges nothing but in the emergency.
|
|
793
|
+
ensureScratchRoots({ policy, platform, env });
|
|
794
|
+
const floorOptions = { policy, platform, ...(deps.check?.space ? { space: deps.check.space } : {}) };
|
|
795
|
+
const floorsBefore = checkFloors(floorOptions);
|
|
796
|
+
const squeezed = emergencyFloor(floorsBefore);
|
|
797
|
+
let lockCheck = {};
|
|
798
|
+
if (platform !== "win32" && !deps.plan?.inUse) {
|
|
799
|
+
if (deps.openPaths !== undefined) lockCheck = { openPaths: deps.openPaths };
|
|
800
|
+
else { let open; let read = false; lockCheck = { inUse: (p) => { if (!read) { open = (deps.listOpenPaths ?? listOpenPaths)({ platform }); read = true; } return inUseByOpenPaths(p, open); } }; }
|
|
801
|
+
}
|
|
802
|
+
const p = planSweep({ policy, platform, env, staleDays: opts.days, light: true, ...lockCheck, ...deps.plan });
|
|
803
|
+
const applied = applySweep({ plan: p, policy, platform, ...deps.apply });
|
|
804
|
+
const purged = squeezed ? emergencyPurge(squeezed, floorsBefore) : null;
|
|
805
|
+
const floors = checkFloors(floorOptions);
|
|
806
|
+
const lead = leadFloor(floors);
|
|
807
|
+
if (logReadError) stdout(`WARNING: the run log ${logFile} cannot be read (${logReadError.message}), so this run stays light`);
|
|
808
|
+
log({ event: "disk.run.light", pass: "light", freeBytes: lead.freeBytes ?? null, floorId: lead.id ?? lead.label, ...(logReadError ? { logReadError: logReadError.message } : {}), floors: floors.map((f) => ({ id: f.id ?? f.label, freeBytes: f.freeBytes ?? null, warn: f.warn })), movedBytes: applied.movedBytes, emergency: Boolean(purged), kept: p.skipped.filter((s) => s.reason === "kept").length, leased: p.skipped.filter((s) => s.reason === "leased").length });
|
|
809
|
+
stdout(`light pass: moved ${formatBytes(applied.movedBytes)} (${applied.results.filter((r) => r.ok).length}/${applied.results.length}), ${lead.absent ? `${lead.label} is not readable` : `${lead.label} ${formatBytes(lead.freeBytes)} free`}`);
|
|
810
|
+
return { exitCode: 0, pass: "light", plan: p, applied, purged, floors, selfCheck: self };
|
|
811
|
+
}
|
|
812
|
+
|
|
585
813
|
ensureScratchRoots({ policy, platform, env });
|
|
586
814
|
const before = check();
|
|
587
815
|
stdout(renderCheck(before));
|
|
@@ -589,15 +817,27 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
589
817
|
stdout(renderSweep(p));
|
|
590
818
|
const applied = applySweep({ plan: p, policy, platform, ...deps.apply });
|
|
591
819
|
stdout(`moved ${formatBytes(applied.movedBytes)} (${applied.results.filter((r) => r.ok).length}/${applied.results.length})`);
|
|
592
|
-
const
|
|
593
|
-
|
|
820
|
+
const squeezed = emergencyFloor(before.floors ?? [before.floor]);
|
|
821
|
+
let purged = squeezed ? emergencyPurge(squeezed, before.floors ?? [before.floor]) : null;
|
|
822
|
+
if (!purged) {
|
|
823
|
+
purged = purgeHoldings({ policy, platform, env, ...deps.purge });
|
|
824
|
+
stdout(`purged ${purged.results.filter((r) => r.ok).length} holding dir(s), ${formatBytes(purged.purgedBytes)}`);
|
|
825
|
+
}
|
|
594
826
|
const after = check();
|
|
595
827
|
const warn = after.warn;
|
|
596
|
-
|
|
828
|
+
const lead = leadFloor(after.floors ?? [after.floor]);
|
|
829
|
+
log({ event: warn ? "disk.floor.warn" : "disk.run.ok", pass: "full", warnings: after.warnings, freeBytes: lead.freeBytes ?? null, floorId: lead.id ?? lead.label, movedBytes: applied.movedBytes, kept: p.skipped.filter((s) => s.reason === "kept").length, leased: p.skipped.filter((s) => s.reason === "leased").length });
|
|
830
|
+
// One report a UTC day: its per-unit sizes need a du per scratch run, which only the daily pass may afford.
|
|
831
|
+
let previousReport = null;
|
|
832
|
+
try { previousReport = lastEvent(logFile, ["disk.report"]); } catch { /* unreadable log: this report has no delta */ }
|
|
833
|
+
if (reportDue({ lastReportTs: previousReport?.ts, now: nowMs })) {
|
|
834
|
+
const scratchUnits = measureScratchUnits({ policy, platform, env, ...(deps.check?.size ? { size: deps.check.size } : {}) });
|
|
835
|
+
log(buildDiskReport({ check: after, previous: previousReport, now: nowMs, policy, scratchUnits, moved: applied.movedBytes, purged: purged.purgedBytes }));
|
|
836
|
+
}
|
|
597
837
|
if (warn) stdout(`WARNING: ${after.warnings.join("; ")}`);
|
|
598
|
-
return { exitCode:
|
|
838
|
+
return { exitCode: warn ? DISK_EXIT_WARNING : 0, pass: "full", before, after, applied, purged, selfCheck: self };
|
|
599
839
|
}
|
|
600
|
-
throw new Error("disk usage: check|sweep [--dry-run|--delete] [--all]|allow-workspace-sweep [--off] [--days N]|purge [--dry-run] [--days N]|run|schedule install|uninstall|status|agent-env [--root dir] [--format sh|ps1|cmd|json] [--check] [--include-agent-homes]|probe [--json]");
|
|
840
|
+
throw new Error("disk usage: check [--calibrate]|sweep [--dry-run|--delete] [--all]|allow-workspace-sweep [--off] [--days N]|purge [--dry-run] [--days N]|run [--auto|--light]|run-root <name> [--days N] [--release]|schedule install|uninstall|status|agent-env [--root dir] [--format sh|ps1|cmd|json] [--check] [--include-agent-homes]|probe [--json]");
|
|
601
841
|
}
|
|
602
842
|
|
|
603
843
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
package/disk-policy.json
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"staleDays": 7,
|
|
4
4
|
"floors": {
|
|
5
|
-
"mac":
|
|
6
|
-
|
|
5
|
+
"mac": [
|
|
6
|
+
{"id": "mac-internal", "path": "/System/Volumes/Data", "label": "Mac internal", "warnFreeBytes": 42949672960, "holdingSource": true},
|
|
7
|
+
{"id": "mac-d", "path": "/Volumes/D", "label": "Mac D:", "warnFreeBytes": 268435456000, "hardFreeBytes": 200000000000, "agentVolume": true}
|
|
8
|
+
],
|
|
9
|
+
"win": {"id": "win-d", "path": "D:\\", "label": "Dell D:", "warnFreeBytes": 429496729600, "warnFreePercent": 10, "agentVolume": true}
|
|
7
10
|
},
|
|
8
|
-
"holdingDays":
|
|
11
|
+
"holdingDays": 1,
|
|
9
12
|
"holding": {
|
|
10
13
|
"mac": [{"prefix": "/", "path": "/Volumes/D/.rightkit-swept", "mount": "/Volumes/D"}, {"prefix": "/", "path": "~/.rightkit-swept"}],
|
|
11
14
|
"win": [{"prefix": "C:\\", "path": "${LOCALAPPDATA}\\Orthic Labs\\.rightkit-swept"}]
|
|
@@ -15,18 +18,25 @@
|
|
|
15
18
|
"win": "D:\\claude\\tmp\\swept"
|
|
16
19
|
},
|
|
17
20
|
"unknownStaleDays": 3,
|
|
21
|
+
"scratchStaleDays": 1,
|
|
22
|
+
"strayTargets": {
|
|
23
|
+
"mac": [{"path": "/Volumes/D/claude", "depth": 4}, {"path": "/Volumes/D/scratch", "depth": 4}],
|
|
24
|
+
"win": [{"path": "D:\\claude", "depth": 4}, {"path": "D:\\scratch", "depth": 4}]
|
|
25
|
+
},
|
|
26
|
+
"strayTargetDays": 1,
|
|
18
27
|
"workspaceRoots": {
|
|
19
28
|
"mac": [{"path": "/Volumes/D/claude"}],
|
|
20
29
|
"win": [{"path": "D:\\claude"}]
|
|
21
30
|
},
|
|
22
31
|
"workspaceAllowlist": [".DS_Store", ".git", "node_modules", "tmp", "reports", "tasks", ".cache", "bin", "repos", "packages", "tests", "schemas", "assets", "migration", "archive", "catalog.db", "catalog.db-shm", "catalog.db-wal", "apple-skill-sources", "foundation-runs", "SEO", ".audit", ".commandcode", ".fastembed_cache", ".legion", ".model-bench", ".pnpm-store", ".pytest_cache", ".research", ".right-release", ".rightkit-managed", ".tether", ".venv-tools", ".vite"],
|
|
32
|
+
"scratchPrefixCaps": {"legion": 42949672960, "rk": 32212254720, "pulse": 21474836480, "hr": 21474836480},
|
|
23
33
|
"scratchRoots": {
|
|
24
34
|
"mac": ["/Volumes/D/scratch"],
|
|
25
35
|
"win": ["D:\\scratch"]
|
|
26
36
|
},
|
|
27
37
|
"writers": [
|
|
28
|
-
{"id": "claude-session-dirs", "platform": "mac", "path": "/private/tmp/claude-501/*/*", "capBytes": 2147483648, "action": "sweep", "unit": "self"},
|
|
29
|
-
{"id": "rightkit-qa-tmp", "platform": "mac", "path": "/private/tmp/rightkit-qa", "capBytes": 2147483648, "action": "sweep", "unit": "children"},
|
|
38
|
+
{"id": "claude-session-dirs", "platform": "mac", "path": "/private/tmp/claude-501/*/*", "capBytes": 2147483648, "action": "sweep", "unit": "self", "light": true},
|
|
39
|
+
{"id": "rightkit-qa-tmp", "platform": "mac", "path": "/private/tmp/rightkit-qa", "capBytes": 2147483648, "action": "sweep", "unit": "children", "light": true},
|
|
30
40
|
{"id": "cache-coreml-e5rt-tmp", "platform": "mac", "path": "~/Library/Caches/*/com.apple.e5rt.e5bundlecache/*/*/*.tmp.*", "capBytes": 4294967296, "action": "sweep", "unit": "self", "staleDays": 1},
|
|
31
41
|
{"id": "cache-coreml-e5rt", "platform": "mac", "path": "~/Library/Caches/*/com.apple.e5rt.e5bundlecache/*/*", "capBytes": 4294967296, "action": "sweep", "unit": "children", "staleDays": 2},
|
|
32
42
|
{"id": "cache-e2e", "platform": "mac", "path": "~/Library/Caches/e2e_*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
|
|
@@ -42,9 +52,14 @@
|
|
|
42
52
|
{"id": "documents-scratch", "platform": "mac", "path": "~/Documents", "capBytes": 21474836480, "action": "report"},
|
|
43
53
|
{"id": "shared-models", "platform": "mac", "path": "~/Library/Application Support/Orthic Labs/shared/models", "capBytes": 107374182400, "action": "report"},
|
|
44
54
|
{"id": "stale-release-tarballs", "platform": "mac", "path": "/Volumes/D/claude/rightkit/packages/release/rightkit-release-*.tgz", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
|
|
45
|
-
{"id": "
|
|
55
|
+
{"id": "agent-tmp", "platform": "mac", "path": "/Volumes/D/agent-cache/tmp", "capBytes": 21474836480, "action": "sweep", "unit": "children", "staleDays": 1, "category": "agent-cache", "light": true},
|
|
56
|
+
{"id": "agent-tmp", "platform": "win", "path": "D:\\agent-cache\\tmp", "capBytes": 21474836480, "action": "sweep", "unit": "children", "staleDays": 1, "category": "agent-cache", "light": true},
|
|
57
|
+
{"id": "build-home-tmp", "platform": "mac", "path": "/Volumes/D/.build-home/*/tmp", "capBytes": 10737418240, "action": "sweep", "unit": "children", "staleDays": 1, "light": true},
|
|
58
|
+
{"id": "build-home-tmp", "platform": "win", "path": "D:\\.build-home\\*\\tmp", "capBytes": 10737418240, "action": "sweep", "unit": "children", "staleDays": 1, "light": true},
|
|
59
|
+
{"id": "xcode-deriveddata-d", "platform": "mac", "path": "/Volumes/D/.xcode/DerivedData", "capBytes": 42949672960, "action": "sweep", "unit": "children"},
|
|
60
|
+
{"id": "win-temp", "platform": "win", "path": "${TEMP}", "capBytes": 10737418240, "action": "sweep", "unit": "children", "light": true},
|
|
46
61
|
{"id": "win-pnpm", "platform": "win", "path": "${LOCALAPPDATA}\\pnpm", "capBytes": 21474836480, "action": "report"},
|
|
47
|
-
{"id": "win-sccache", "platform": "win", "path": "D:\\.rightkit-managed\\rightkit-build-control\\global", "capBytes":
|
|
62
|
+
{"id": "win-sccache", "platform": "win", "path": "D:\\.rightkit-managed\\rightkit-build-control\\global\\compiler-cache", "capBytes": 55000000000, "action": "report", "budget": ["sccache"]},
|
|
48
63
|
{"id": "win-claude-workspace", "platform": "win", "path": "D:\\claude", "capBytes": 805306368000, "action": "report"},
|
|
49
64
|
{"id": "win-claude-tmp", "platform": "win", "path": "D:\\claude\\tmp", "capBytes": 107374182400, "action": "report"},
|
|
50
65
|
{"id": "win-shared-models", "platform": "win", "path": "${LOCALAPPDATA}\\Orthic Labs\\shared\\models", "capBytes": 107374182400, "action": "report"},
|
|
@@ -63,9 +78,34 @@
|
|
|
63
78
|
{"id": "leftover-pulse-bak", "platform": "win", "path": "${LOCALAPPDATA}\\Programs\\Pulse.bak-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
|
|
64
79
|
{"id": "qa-evidence-runs", "platform": "mac", "path": "/Volumes/D/claude/*/.cache/rightkit-qa/*", "capBytes": 5368709120, "action": "sweep", "unit": "self", "staleDays": 3, "skipTracked": true},
|
|
65
80
|
{"id": "qa-evidence-runs", "platform": "win", "path": "D:\\claude\\*\\.cache\\rightkit-qa\\*", "capBytes": 5368709120, "action": "sweep", "unit": "self", "staleDays": 3, "skipTracked": true},
|
|
66
|
-
{"id": "build-control-targets", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/rightkit-build-control", "capBytes":
|
|
67
|
-
{"id": "scratch-total", "platform": "mac", "path": "/Volumes/D/scratch", "capBytes": 322122547200, "action": "report"},
|
|
68
|
-
{"id": "claude-tmp", "platform": "mac", "path": "/Volumes/D/claude/tmp", "capBytes": 53687091200, "action": "report"}
|
|
81
|
+
{"id": "build-control-targets", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/rightkit-build-control", "capBytes": 250000000000, "action": "report", "budget": ["total"]},
|
|
82
|
+
{"id": "scratch-total", "platform": "mac", "path": "/Volumes/D/scratch", "capBytes": 322122547200, "action": "report", "category": "scratch"},
|
|
83
|
+
{"id": "claude-tmp", "platform": "mac", "path": "/Volumes/D/claude/tmp", "capBytes": 53687091200, "action": "report"},
|
|
84
|
+
{"id": "agent-cache", "platform": "mac", "path": "/Volumes/D/agent-cache", "capBytes": 64424509440, "action": "report"},
|
|
85
|
+
{"id": "agent-cache", "platform": "win", "path": "D:\\agent-cache", "capBytes": 64424509440, "action": "report"},
|
|
86
|
+
{"id": "dev-cache", "platform": "mac", "path": "/Volumes/D/.dev-cache", "capBytes": 85899345920, "action": "report"},
|
|
87
|
+
{"id": "dev-cache", "platform": "win", "path": "D:\\.dev-cache", "capBytes": 85899345920, "action": "report"},
|
|
88
|
+
{"id": "agent-home", "platform": "mac", "path": "/Volumes/D/agent-home", "capBytes": 21474836480, "action": "report"},
|
|
89
|
+
{"id": "agent-home", "platform": "win", "path": "D:\\agent-home", "capBytes": 21474836480, "action": "report"},
|
|
90
|
+
{"id": "build-home", "platform": "mac", "path": "/Volumes/D/.build-home", "capBytes": 21474836480, "action": "report"},
|
|
91
|
+
{"id": "build-home", "platform": "win", "path": "D:\\.build-home", "capBytes": 21474836480, "action": "report"},
|
|
92
|
+
{"id": "managed-runs", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/runs", "capBytes": 107374182400, "action": "report"},
|
|
93
|
+
{"id": "managed-runs", "platform": "win", "path": "D:\\.rightkit-managed\\runs", "capBytes": 107374182400, "action": "report"},
|
|
94
|
+
{"id": "managed-artifacts", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/artifacts", "capBytes": 42949672960, "action": "report"},
|
|
95
|
+
{"id": "managed-artifacts", "platform": "win", "path": "D:\\.rightkit-managed\\artifacts", "capBytes": 42949672960, "action": "report"},
|
|
96
|
+
{"id": "remote-workspaces", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/remote-workspaces", "capBytes": 64424509440, "action": "report"},
|
|
97
|
+
{"id": "remote-workspaces", "platform": "win", "path": "D:\\.rightkit-managed\\remote-workspaces", "capBytes": 64424509440, "action": "report"},
|
|
98
|
+
{"id": "holding-volume", "platform": "mac", "path": "/Volumes/D/.rightkit-swept", "capBytes": 107374182400, "action": "report", "category": "holding"},
|
|
99
|
+
{"id": "holding-volume", "platform": "win", "path": "D:\\.rightkit-swept", "capBytes": 107374182400, "action": "report", "category": "holding"},
|
|
100
|
+
{"id": "build-control-sccache", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/rightkit-build-control/global/compiler-cache", "capBytes": 55000000000, "action": "report", "budget": ["sccache"]},
|
|
101
|
+
{"id": "tart-home", "platform": "mac", "path": "/Volumes/D/tart-home", "capBytes": 161061273600, "action": "report"},
|
|
102
|
+
{"id": "xcode-home", "platform": "mac", "path": "/Volumes/D/.xcode", "capBytes": 64424509440, "action": "report"},
|
|
103
|
+
{"id": "remote-inputs", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/remote-inputs", "capBytes": 21474836480, "action": "report"},
|
|
104
|
+
{"id": "claude-workspace", "platform": "mac", "path": "/Volumes/D/claude", "capBytes": 805306368000, "action": "report"},
|
|
105
|
+
{"id": "legacy-rightkit-publish", "platform": "mac", "path": "/Volumes/D/rightkit-publish", "capBytes": 1073741824, "action": "report"},
|
|
106
|
+
{"id": "scratch-total", "platform": "win", "path": "D:\\scratch", "capBytes": 322122547200, "action": "report", "category": "scratch"},
|
|
107
|
+
{"id": "build-control-targets", "platform": "win", "path": "D:\\.rightkit-managed\\rightkit-build-control", "capBytes": 250000000000, "action": "report", "budget": ["total"]},
|
|
108
|
+
{"id": "win-build-control-local", "platform": "win", "path": "${LOCALAPPDATA}\\RightKit\\build-control", "capBytes": 5368709120, "action": "report"}
|
|
69
109
|
],
|
|
70
110
|
"protectedSegments": ["Application Support", "Keychains", "Keychain", "HeardRightNext", "Credentials", ".ssh", ".rightkit-managed", ".rightkit-swept"]
|
|
71
111
|
}
|