machine-bridge-mcp 2.0.0 → 3.0.0-beta.9
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/CHANGELOG.md +101 -0
- package/CONTRIBUTING.md +20 -25
- package/README.md +21 -14
- package/SECURITY.md +147 -123
- package/browser-extension/manifest.json +3 -2
- package/browser-extension/service-worker.js +31 -12
- package/docs/ARCHITECTURE.md +27 -13
- package/docs/AUDIT.md +85 -2
- package/docs/CLIENTS.md +2 -2
- package/docs/ENGINEERING.md +9 -3
- package/docs/GETTING_STARTED.md +10 -9
- package/docs/LOCAL_AUTHORIZATION.md +115 -72
- package/docs/LOGGING.md +12 -3
- package/docs/MANAGED_JOBS.md +24 -29
- package/docs/MULTI_ACCOUNT.md +98 -37
- package/docs/OPERATIONS.md +33 -22
- package/docs/OVERVIEW.md +7 -7
- package/docs/PROJECT_STANDARDS.md +9 -5
- package/docs/RELEASING.md +171 -64
- package/docs/TESTING.md +19 -7
- package/docs/THREAT_MODEL.md +138 -67
- package/docs/TOOL_REFERENCE.md +2 -2
- package/docs/UPGRADING.md +102 -18
- package/native/macos/MachineBridgeTrustBroker.swift +233 -0
- package/package.json +36 -5
- package/scripts/candidate-runtime-store.mjs +38 -0
- package/scripts/check-plan.mjs +19 -0
- package/scripts/check-runner.mjs +4 -1
- package/scripts/commit-message-check.mjs +5 -2
- package/scripts/coverage-check.mjs +30 -2
- package/scripts/github-push.mjs +11 -0
- package/scripts/github-release.mjs +38 -7
- package/scripts/install-published-prerelease.mjs +167 -0
- package/scripts/local-release-acceptance.mjs +23 -12
- package/scripts/npm-publication-policy.mjs +16 -0
- package/scripts/prerelease-activation.mjs +79 -0
- package/scripts/privacy-check.mjs +7 -5
- package/scripts/promotion-digest.mjs +121 -0
- package/scripts/publish-npm.mjs +51 -0
- package/scripts/published-release.mjs +80 -0
- package/scripts/release-acceptance.mjs +30 -1
- package/scripts/release-candidate-manifest.mjs +40 -0
- package/scripts/release-channel.mjs +106 -0
- package/scripts/release-impact-check.mjs +12 -22
- package/scripts/release-soak.mjs +237 -0
- package/scripts/start-release-candidate.mjs +113 -7
- package/src/local/account-access.mjs +6 -0
- package/src/local/account-admin.mjs +34 -16
- package/src/local/agent-context.mjs +45 -42
- package/src/local/app-automation.mjs +9 -4
- package/src/local/authority-context.mjs +106 -0
- package/src/local/browser-bridge-http.mjs +4 -1
- package/src/local/browser-bridge.mjs +14 -9
- package/src/local/browser-broker-routes.mjs +4 -0
- package/src/local/browser-broker-server.mjs +5 -4
- package/src/local/browser-extension-identity.mjs +50 -0
- package/src/local/browser-extension-protocol.mjs +9 -4
- package/src/local/browser-operation-service.mjs +7 -0
- package/src/local/browser-pairing-http.mjs +36 -0
- package/src/local/browser-pairing-store.mjs +56 -47
- package/src/local/cli-account-admin.mjs +41 -7
- package/src/local/cli-activate.mjs +90 -0
- package/src/local/cli-approval.mjs +14 -58
- package/src/local/cli-local-admin.mjs +5 -15
- package/src/local/cli-options.mjs +11 -7
- package/src/local/cli-service.mjs +35 -5
- package/src/local/cli.mjs +112 -58
- package/src/local/daemon-process.mjs +6 -0
- package/src/local/delegated-process-sandbox.mjs +147 -0
- package/src/local/device-identity.mjs +157 -48
- package/src/local/device-root-provider.mjs +87 -0
- package/src/local/errors.mjs +25 -3
- package/src/local/git-service.mjs +16 -15
- package/src/local/job-runner.mjs +35 -27
- package/src/local/log.mjs +14 -1
- package/src/local/macos-trust-broker.mjs +320 -0
- package/src/local/managed-job-lock.mjs +18 -0
- package/src/local/managed-job-projection.mjs +9 -1
- package/src/local/managed-job-runner.mjs +13 -1
- package/src/local/managed-job-storage.mjs +2 -1
- package/src/local/managed-job-terminal.mjs +138 -0
- package/src/local/managed-jobs.mjs +88 -32
- package/src/local/operation-authorization.mjs +75 -239
- package/src/local/operation-risk.mjs +28 -0
- package/src/local/operation-state-lock.mjs +7 -86
- package/src/local/owner-state-lock.mjs +104 -0
- package/src/local/policy.mjs +19 -0
- package/src/local/process-execution.mjs +21 -9
- package/src/local/process-sessions.mjs +27 -17
- package/src/local/process-tracker.mjs +30 -6
- package/src/local/process-tree-ownership.mjs +59 -0
- package/src/local/process-tree.mjs +22 -18
- package/src/local/relay-connection.mjs +47 -35
- package/src/local/relay-diagnostics.mjs +65 -0
- package/src/local/remote-configuration.mjs +48 -0
- package/src/local/runtime-activation.mjs +141 -0
- package/src/local/runtime-diagnostics.mjs +21 -0
- package/src/local/runtime-relay.mjs +9 -8
- package/src/local/runtime-reporting.mjs +8 -3
- package/src/local/runtime-tool-handlers.mjs +8 -8
- package/src/local/runtime.mjs +73 -39
- package/src/local/secure-file.mjs +5 -1
- package/src/local/security-audit-log.mjs +204 -0
- package/src/local/service-convergence.mjs +17 -1
- package/src/local/service-ownership.mjs +17 -0
- package/src/local/service-restart-handoff.mjs +50 -0
- package/src/local/service-restart-scheduler.mjs +49 -0
- package/src/local/service-status.mjs +47 -0
- package/src/local/service.mjs +142 -43
- package/src/local/state-inventory.mjs +21 -3
- package/src/local/state.mjs +155 -17
- package/src/local/stdio.mjs +5 -0
- package/src/local/tool-executor.mjs +39 -6
- package/src/local/tool-result-boundary.mjs +51 -0
- package/src/local/tools.mjs +1 -0
- package/src/local/trusted-executable.mjs +53 -0
- package/src/local/trusted-git-executable.mjs +34 -0
- package/src/local/trusted-github-cli.mjs +24 -0
- package/src/local/windows-service.mjs +20 -0
- package/src/local/worker-deployment.mjs +3 -4
- package/src/local/worker-secret-file.mjs +2 -2
- package/src/local/workspace-file-service.mjs +34 -31
- package/src/shared/admin-auth.d.mts +2 -1
- package/src/shared/admin-auth.mjs +3 -3
- package/src/shared/device-session-auth.d.mts +20 -0
- package/src/shared/device-session-auth.mjs +51 -0
- package/src/shared/server-metadata.json +1 -1
- package/src/shared/tool-catalog.json +2 -2
- package/src/worker/account-admin.ts +23 -22
- package/src/worker/authority.ts +9 -2
- package/src/worker/daemon-auth.ts +45 -67
- package/src/worker/daemon-sockets.ts +12 -2
- package/src/worker/device-session-verifier.ts +129 -0
- package/src/worker/dpop.ts +151 -0
- package/src/worker/http.ts +47 -16
- package/src/worker/index.ts +53 -16
- package/src/worker/nonce-store.ts +1 -5
- package/src/worker/oauth-client-admin.ts +45 -0
- package/src/worker/oauth-controller.ts +55 -6
- package/src/worker/oauth-refresh-families.ts +21 -7
- package/src/worker/oauth-state.ts +6 -0
- package/src/worker/oauth-tokens.ts +28 -8
- package/src/worker/websocket-protocol.ts +8 -2
|
@@ -4,6 +4,7 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import { assertStateMaintenanceAvailable, ensureOwnerOnlyDir } from "./state.mjs";
|
|
5
5
|
import { createToolAuthorizer } from "./policy.mjs";
|
|
6
6
|
import { BridgeError } from "./errors.mjs";
|
|
7
|
+
import { assertOwnedByContext, principalBinding, visibleToContext } from "./authority-context.mjs";
|
|
7
8
|
import { inspectResourceFile, normalizeResourceRegistry, validatePlan } from "./managed-job-plan.mjs";
|
|
8
9
|
export { inspectResourceFile, publicResourceRegistry, validateResourceName } from "./managed-job-plan.mjs";
|
|
9
10
|
import { clampInteger } from "./numbers.mjs";
|
|
@@ -13,18 +14,22 @@ import {
|
|
|
13
14
|
atomicWriteJson, readBoundedFile, readJson, readRequiredJson, resourceErrorClass, safeReadDir,
|
|
14
15
|
} from "./managed-job-storage.mjs";
|
|
15
16
|
import { launchRunner, runnerProcessIsCurrent } from "./managed-job-runner.mjs";
|
|
17
|
+
import {
|
|
18
|
+
isTerminalManagedJobResult, scrubManagedJobArtifacts, terminalStatusFromResult,
|
|
19
|
+
} from "./managed-job-terminal.mjs";
|
|
16
20
|
export { launchRunner } from "./managed-job-runner.mjs";
|
|
17
21
|
|
|
18
22
|
const JOB_ID = /^job_[A-Za-z0-9_-]{24,}$/;
|
|
19
23
|
const MAX_JOBS = 50;
|
|
20
24
|
const JOB_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
25
|
+
const STAGED_PLAN_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
21
26
|
const MAX_PLAN_BYTES = 1024 * 1024;
|
|
22
27
|
const MAX_RECOVERY_ATTEMPTS = 3;
|
|
23
28
|
const ACTIVE_JOB_STATES = new Set(["queued", "running", "cleaning", "interrupted"]);
|
|
24
29
|
const PLAN_RETAINING_STATES = new Set(["staged", ...ACTIVE_JOB_STATES]);
|
|
25
30
|
|
|
26
31
|
export class ManagedJobManager {
|
|
27
|
-
constructor({ jobRoot, workspace, policy, authorizeTool = null, resources = {}, resourceStatePath = "", stateRoot = "", logger = console, recover = true }) {
|
|
32
|
+
constructor({ jobRoot, workspace, policy, authorizeTool = null, policyForContext = null, resources = {}, resourceStatePath = "", stateRoot = "", logger = console, recover = true }) {
|
|
28
33
|
const jobRootInput = resolve(jobRoot);
|
|
29
34
|
ensureOwnerOnlyDir(jobRootInput);
|
|
30
35
|
this.jobRoot = realpathSync.native ? realpathSync.native(jobRootInput) : realpathSync(jobRootInput);
|
|
@@ -32,6 +37,7 @@ export class ManagedJobManager {
|
|
|
32
37
|
this.workspace = realpathSync.native ? realpathSync.native(workspaceInput) : realpathSync(workspaceInput);
|
|
33
38
|
this.policy = policy;
|
|
34
39
|
this.authorizeTool = createToolAuthorizer(this.policy, authorizeTool);
|
|
40
|
+
this.policyForContext = typeof policyForContext === "function" ? policyForContext : () => this.policy;
|
|
35
41
|
this.resources = normalizeResourceRegistry(resources);
|
|
36
42
|
this.resourceStatePath = resourceStatePath ? resolve(resourceStatePath) : "";
|
|
37
43
|
this.stateRoot = stateRoot ? resolve(stateRoot) : "";
|
|
@@ -62,7 +68,7 @@ export class ManagedJobManager {
|
|
|
62
68
|
};
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
listResources() {
|
|
71
|
+
listResources(_context = {}) {
|
|
66
72
|
this.authorizeTool("list_local_resources");
|
|
67
73
|
this.assertMaintenanceAvailable();
|
|
68
74
|
const resources = [];
|
|
@@ -94,16 +100,16 @@ export class ManagedJobManager {
|
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
|
|
97
|
-
stage(args = {}) {
|
|
103
|
+
stage(args = {}, context = {}) {
|
|
98
104
|
this.authorizeTool("stage_job");
|
|
99
105
|
this.assertMaintenanceAvailable();
|
|
100
|
-
return this.createJob(args, { launch: false });
|
|
106
|
+
return this.createJob(args, { launch: false }, context);
|
|
101
107
|
}
|
|
102
108
|
|
|
103
|
-
start(args = {}) {
|
|
109
|
+
start(args = {}, context = {}) {
|
|
104
110
|
this.authorizeTool("start_job");
|
|
105
111
|
this.assertMaintenanceAvailable();
|
|
106
|
-
return this.createJob(args, { launch: true });
|
|
112
|
+
return this.createJob(args, { launch: true }, context);
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
approve(args = {}, { localOperator = false } = {}) {
|
|
@@ -125,7 +131,7 @@ export class ManagedJobManager {
|
|
|
125
131
|
status.cleanup_guarantee = "best-effort-finally-and-recovery";
|
|
126
132
|
atomicWriteJson(statusFile, status, 256 * 1024);
|
|
127
133
|
try {
|
|
128
|
-
launchRunner(dir, false, "", { logger: this.logger });
|
|
134
|
+
launchRunner(dir, false, "", { logger: this.logger, fullEnv: plan.full_env === true });
|
|
129
135
|
} catch (error) {
|
|
130
136
|
failRunnerLaunch(dir, status, error);
|
|
131
137
|
throw error;
|
|
@@ -150,15 +156,16 @@ export class ManagedJobManager {
|
|
|
150
156
|
}
|
|
151
157
|
}
|
|
152
158
|
|
|
153
|
-
createJob(args, { launch }) {
|
|
159
|
+
createJob(args, { launch }, context = {}) {
|
|
154
160
|
this.prune();
|
|
155
161
|
const retained = safeReadDir(this.jobRoot).filter((entry) => entry.isDirectory() && JOB_ID.test(entry.name)).length;
|
|
156
162
|
if (retained >= MAX_JOBS) throw new Error(`managed job limit reached (${MAX_JOBS})`);
|
|
163
|
+
const effectivePolicy = this.policyForContext(context);
|
|
157
164
|
const plan = validatePlan(args, {
|
|
158
165
|
workspace: this.workspace,
|
|
159
166
|
resources: this.currentResources(),
|
|
160
|
-
fullEnv:
|
|
161
|
-
unrestrictedPaths:
|
|
167
|
+
fullEnv: effectivePolicy.minimalEnv === false,
|
|
168
|
+
unrestrictedPaths: effectivePolicy.unrestrictedPaths === true,
|
|
162
169
|
});
|
|
163
170
|
const planSha256 = createHash("sha256").update(JSON.stringify(plan)).digest("hex");
|
|
164
171
|
const id = `job_${randomBytes(24).toString("base64url")}`;
|
|
@@ -178,11 +185,12 @@ export class ManagedJobManager {
|
|
|
178
185
|
approval: launch ? "mcp" : "pending-local-operator",
|
|
179
186
|
plan_sha256: planSha256,
|
|
180
187
|
cleanup_guarantee: launch ? "best-effort-finally-and-recovery" : "not-started",
|
|
188
|
+
...principalBinding(context),
|
|
181
189
|
};
|
|
182
190
|
atomicWriteJson(join(dir, "status.json"), status, 256 * 1024);
|
|
183
191
|
if (launch) {
|
|
184
192
|
try {
|
|
185
|
-
launchRunner(dir, false, "", { logger: this.logger });
|
|
193
|
+
launchRunner(dir, false, "", { logger: this.logger, fullEnv: plan.full_env === true });
|
|
186
194
|
} catch (error) {
|
|
187
195
|
failRunnerLaunch(dir, status, error);
|
|
188
196
|
throw error;
|
|
@@ -209,14 +217,13 @@ export class ManagedJobManager {
|
|
|
209
217
|
status: "staged",
|
|
210
218
|
execution_started: false,
|
|
211
219
|
plan_sha256: planSha256,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
plan_expires_after_days: 7,
|
|
220
|
+
continuation: "start the staged job through the same authenticated client when ready",
|
|
221
|
+
plan_expires_after_hours: 24,
|
|
215
222
|
};
|
|
216
223
|
}
|
|
217
224
|
|
|
218
225
|
|
|
219
|
-
list(args = {}) {
|
|
226
|
+
list(args = {}, context = {}) {
|
|
220
227
|
this.authorizeTool("list_jobs");
|
|
221
228
|
this.assertMaintenanceAvailable();
|
|
222
229
|
this.prune();
|
|
@@ -228,23 +235,24 @@ export class ManagedJobManager {
|
|
|
228
235
|
try {
|
|
229
236
|
this.reconcileStatus(dir);
|
|
230
237
|
const status = readJson(join(dir, "status.json"), 256 * 1024, "job status");
|
|
231
|
-
if (!status) continue;
|
|
238
|
+
if (!status || !visibleToContext(status, context)) continue;
|
|
232
239
|
jobs.push(publicStatus(status));
|
|
233
240
|
} catch (error) {
|
|
234
241
|
this.logger.warn?.("managed job status is unreadable; retaining it for inspection", { error_class: resourceErrorClass(error) });
|
|
235
|
-
jobs.push({ job_id: entry.name, name: "unavailable", status: "unreadable", error_class: resourceErrorClass(error) });
|
|
242
|
+
if (context?.authority?.owner !== false) jobs.push({ job_id: entry.name, name: "unavailable", status: "unreadable", error_class: resourceErrorClass(error) });
|
|
236
243
|
}
|
|
237
244
|
}
|
|
238
245
|
jobs.sort((a, b) => String(b.created_at).localeCompare(String(a.created_at)));
|
|
239
246
|
return { jobs: jobs.slice(0, limit), retained: jobs.length, maximum: MAX_JOBS };
|
|
240
247
|
}
|
|
241
248
|
|
|
242
|
-
read(args = {}) {
|
|
249
|
+
read(args = {}, context = {}) {
|
|
243
250
|
this.authorizeTool("read_job");
|
|
244
251
|
this.assertMaintenanceAvailable();
|
|
245
252
|
const dir = this.jobDir(args.job_id);
|
|
246
253
|
this.reconcileStatus(dir);
|
|
247
254
|
const status = readRequiredJson(join(dir, "status.json"), 256 * 1024, "job status");
|
|
255
|
+
assertOwnedByContext(status, context, "managed job");
|
|
248
256
|
const result = readJson(join(dir, "result.json"), 4 * 1024 * 1024);
|
|
249
257
|
return {
|
|
250
258
|
...publicStatus(status),
|
|
@@ -266,7 +274,7 @@ export class ManagedJobManager {
|
|
|
266
274
|
};
|
|
267
275
|
}
|
|
268
276
|
|
|
269
|
-
cancel(args = {}) {
|
|
277
|
+
cancel(args = {}, context = {}) {
|
|
270
278
|
this.authorizeTool("cancel_job");
|
|
271
279
|
this.assertMaintenanceAvailable();
|
|
272
280
|
const dir = this.jobDir(args.job_id);
|
|
@@ -275,6 +283,7 @@ export class ManagedJobManager {
|
|
|
275
283
|
try {
|
|
276
284
|
this.reconcileStatus(dir);
|
|
277
285
|
const status = readRequiredJson(join(dir, "status.json"), 256 * 1024, "job status");
|
|
286
|
+
assertOwnedByContext(status, context, "managed job");
|
|
278
287
|
if (status.status === "staged") {
|
|
279
288
|
const now = new Date().toISOString();
|
|
280
289
|
status.status = "cancelled_before_start";
|
|
@@ -342,6 +351,15 @@ export class ManagedJobManager {
|
|
|
342
351
|
return;
|
|
343
352
|
}
|
|
344
353
|
if (runnerProcessIsCurrent(initial, dir)) return;
|
|
354
|
+
const terminalResult = readJson(join(dir, "result.json"), 4 * 1024 * 1024, "job result");
|
|
355
|
+
if (isTerminalManagedJobResult(terminalResult, initial.job_id)) {
|
|
356
|
+
const recoveredStatus = terminalStatusFromResult(initial, terminalResult, {
|
|
357
|
+
resultPersisted: true, updatedAt: new Date().toISOString(),
|
|
358
|
+
});
|
|
359
|
+
atomicWriteJson(file, recoveredStatus, 256 * 1024);
|
|
360
|
+
scrubFinishedPlan(dir, recoveredStatus);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
345
363
|
const updated = Date.parse(initial.updated_at || initial.created_at || "");
|
|
346
364
|
if (Number.isFinite(updated) && Date.now() - updated < 10_000) return;
|
|
347
365
|
|
|
@@ -399,6 +417,7 @@ export class ManagedJobManager {
|
|
|
399
417
|
continue;
|
|
400
418
|
}
|
|
401
419
|
}
|
|
420
|
+
if (status?.status === "staged" && stagedPlanExpired(status, mtime)) status = expireStagedJob(dir, status);
|
|
402
421
|
if (status && !PLAN_RETAINING_STATES.has(status.status)) scrubFinishedPlan(dir, status);
|
|
403
422
|
entries.push({ dir, status, mtime });
|
|
404
423
|
}
|
|
@@ -515,6 +534,8 @@ function markRecoveryExhausted(dir, statusFile, status, recoveryAttempts) {
|
|
|
515
534
|
}
|
|
516
535
|
|
|
517
536
|
function relaunchInterruptedJob(dir, statusFile, status, recoveryAttempts, recoveryToken, logger) {
|
|
537
|
+
const plan = readRequiredJson(join(dir, "plan.json"), MAX_PLAN_BYTES, "job plan");
|
|
538
|
+
assertPlanIntegrity(plan, status);
|
|
518
539
|
status.status = "interrupted";
|
|
519
540
|
status.updated_at = new Date().toISOString();
|
|
520
541
|
status.finished_at = status.updated_at;
|
|
@@ -523,7 +544,40 @@ function relaunchInterruptedJob(dir, statusFile, status, recoveryAttempts, recov
|
|
|
523
544
|
atomicWriteJson(statusFile, status, 256 * 1024);
|
|
524
545
|
rmSync(join(dir, "runtime"), { recursive: true, force: true });
|
|
525
546
|
rmSync(join(dir, "runner.pid"), { force: true });
|
|
526
|
-
return launchRunner(dir, true, recoveryToken, { logger });
|
|
547
|
+
return launchRunner(dir, true, recoveryToken, { logger, fullEnv: plan.full_env === true });
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function stagedPlanExpired(status, fallbackMtime) {
|
|
551
|
+
const createdAt = Date.parse(String(status?.created_at || ""));
|
|
552
|
+
const baseline = Number.isFinite(createdAt) ? createdAt : fallbackMtime;
|
|
553
|
+
return Number.isFinite(baseline) && Date.now() - baseline > STAGED_PLAN_RETENTION_MS;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function expireStagedJob(dir, status) {
|
|
557
|
+
const now = new Date().toISOString();
|
|
558
|
+
const expired = {
|
|
559
|
+
...status,
|
|
560
|
+
status: "expired_before_start",
|
|
561
|
+
updated_at: now,
|
|
562
|
+
finished_at: now,
|
|
563
|
+
current_phase: null,
|
|
564
|
+
current_step: null,
|
|
565
|
+
error_class: "expired",
|
|
566
|
+
cleanup_guarantee: "not-started",
|
|
567
|
+
};
|
|
568
|
+
atomicWriteJson(join(dir, "status.json"), expired, 256 * 1024);
|
|
569
|
+
atomicWriteJson(join(dir, "result.json"), {
|
|
570
|
+
job_id: expired.job_id,
|
|
571
|
+
name: expired.name,
|
|
572
|
+
status: expired.status,
|
|
573
|
+
steps: [],
|
|
574
|
+
finally_steps: [],
|
|
575
|
+
error_class: "expired",
|
|
576
|
+
cleanup_error_class: null,
|
|
577
|
+
finished_at: now,
|
|
578
|
+
}, 4 * 1024 * 1024);
|
|
579
|
+
scrubFinishedPlan(dir, expired);
|
|
580
|
+
return expired;
|
|
527
581
|
}
|
|
528
582
|
|
|
529
583
|
function planSha256(plan) {
|
|
@@ -540,16 +594,18 @@ function assertPlanIntegrity(plan, status) {
|
|
|
540
594
|
}
|
|
541
595
|
|
|
542
596
|
function scrubFinishedPlan(dir, status) {
|
|
543
|
-
if (PLAN_RETAINING_STATES.has(status.status)) return;
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
597
|
+
if (PLAN_RETAINING_STATES.has(status.status)) return { scrubbed: false, errorClass: null, failureCount: 0 };
|
|
598
|
+
const cleanup = scrubManagedJobArtifacts([
|
|
599
|
+
join(dir, "runtime"), join(dir, "plan.json"), join(dir, "runner.pid"), join(dir, "cancel"),
|
|
600
|
+
join(dir, "recovery.lock"), join(dir, "transition.lock"),
|
|
601
|
+
], (file) => rmSync(file, { recursive: true, force: true }), resourceErrorClass);
|
|
602
|
+
const pending = !cleanup.scrubbed;
|
|
603
|
+
if (status.artifact_cleanup_pending !== pending
|
|
604
|
+
|| (status.artifact_cleanup_error_class || null) !== cleanup.errorClass) {
|
|
605
|
+
status.artifact_cleanup_pending = pending;
|
|
606
|
+
status.artifact_cleanup_error_class = cleanup.errorClass;
|
|
607
|
+
status.updated_at = new Date().toISOString();
|
|
608
|
+
atomicWriteJson(join(dir, "status.json"), status, 256 * 1024);
|
|
609
|
+
}
|
|
610
|
+
return cleanup;
|
|
555
611
|
}
|