@dsh-enhanced/assistant-isolation 0.1.65 → 0.1.67
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 +287 -9
- package/lib/audit-archive.d.ts +1 -1
- package/lib/audit-archive.d.ts.map +1 -1
- package/lib/audit-archive.js +31 -13
- package/lib/audit-archive.js.map +1 -1
- package/lib/cli.d.ts +7 -3
- package/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +104 -23
- package/lib/cli.js.map +1 -1
- package/lib/config.d.ts +15 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +49 -9
- package/lib/config.js.map +1 -1
- package/lib/diagnostics.js +2 -2
- package/lib/diagnostics.js.map +1 -1
- package/lib/index.d.ts +17 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +17 -1
- package/lib/index.js.map +1 -1
- package/lib/ledger.d.ts +19 -2
- package/lib/ledger.d.ts.map +1 -1
- package/lib/ledger.js +371 -44
- package/lib/ledger.js.map +1 -1
- package/lib/linux-cgroups.d.ts +75 -0
- package/lib/linux-cgroups.d.ts.map +1 -0
- package/lib/linux-cgroups.js +517 -0
- package/lib/linux-cgroups.js.map +1 -0
- package/lib/linux-execution-contract.d.ts +123 -0
- package/lib/linux-execution-contract.d.ts.map +1 -0
- package/lib/linux-execution-contract.js +279 -0
- package/lib/linux-execution-contract.js.map +1 -0
- package/lib/linux-execution.d.ts +123 -0
- package/lib/linux-execution.d.ts.map +1 -0
- package/lib/linux-execution.js +1264 -0
- package/lib/linux-execution.js.map +1 -0
- package/lib/linux-job-recovery.d.ts +5 -0
- package/lib/linux-job-recovery.d.ts.map +1 -0
- package/lib/linux-job-recovery.js +63 -0
- package/lib/linux-job-recovery.js.map +1 -0
- package/lib/linux-namespace.d.ts +80 -0
- package/lib/linux-namespace.d.ts.map +1 -0
- package/lib/linux-namespace.js +471 -0
- package/lib/linux-namespace.js.map +1 -0
- package/lib/linux-repository-admission.d.ts +26 -0
- package/lib/linux-repository-admission.d.ts.map +1 -0
- package/lib/linux-repository-admission.js +64 -0
- package/lib/linux-repository-admission.js.map +1 -0
- package/lib/linux-repository-runner.d.ts +54 -0
- package/lib/linux-repository-runner.d.ts.map +1 -0
- package/lib/linux-repository-runner.js +614 -0
- package/lib/linux-repository-runner.js.map +1 -0
- package/lib/linux-runner-state-migration.d.ts +94 -0
- package/lib/linux-runner-state-migration.d.ts.map +1 -0
- package/lib/linux-runner-state-migration.js +463 -0
- package/lib/linux-runner-state-migration.js.map +1 -0
- package/lib/linux-runner-state.d.ts +53 -0
- package/lib/linux-runner-state.d.ts.map +1 -0
- package/lib/linux-runner-state.js +332 -0
- package/lib/linux-runner-state.js.map +1 -0
- package/lib/linux-runtime-source.d.ts +28 -0
- package/lib/linux-runtime-source.d.ts.map +1 -0
- package/lib/linux-runtime-source.js +237 -0
- package/lib/linux-runtime-source.js.map +1 -0
- package/lib/linux-runtime.d.ts +37 -0
- package/lib/linux-runtime.d.ts.map +1 -0
- package/lib/linux-runtime.js +192 -0
- package/lib/linux-runtime.js.map +1 -0
- package/lib/linux-system-snapshot.d.ts +47 -0
- package/lib/linux-system-snapshot.d.ts.map +1 -0
- package/lib/linux-system-snapshot.js +704 -0
- package/lib/linux-system-snapshot.js.map +1 -0
- package/lib/linux-user-namespace.d.ts +79 -0
- package/lib/linux-user-namespace.d.ts.map +1 -0
- package/lib/linux-user-namespace.js +234 -0
- package/lib/linux-user-namespace.js.map +1 -0
- package/lib/linux-verifier-runner.d.ts +27 -0
- package/lib/linux-verifier-runner.d.ts.map +1 -0
- package/lib/linux-verifier-runner.js +346 -0
- package/lib/linux-verifier-runner.js.map +1 -0
- package/lib/native-cgroup-roots.d.ts +20 -0
- package/lib/native-cgroup-roots.d.ts.map +1 -0
- package/lib/native-cgroup-roots.js +121 -0
- package/lib/native-cgroup-roots.js.map +1 -0
- package/lib/native-resources.d.ts +44 -0
- package/lib/native-resources.d.ts.map +1 -0
- package/lib/native-resources.js +354 -0
- package/lib/native-resources.js.map +1 -0
- package/lib/probe.d.ts +10 -0
- package/lib/probe.d.ts.map +1 -1
- package/lib/probe.js +52 -2
- package/lib/probe.js.map +1 -1
- package/lib/service.d.ts +1 -1
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +122 -9
- package/lib/service.js.map +1 -1
- package/lib/storage.d.ts.map +1 -1
- package/lib/storage.js +36 -6
- package/lib/storage.js.map +1 -1
- package/lib/tools.js +1 -1
- package/lib/tools.js.map +1 -1
- package/lib/types.d.ts +5 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/verifier-runner.d.ts +4 -2
- package/lib/verifier-runner.d.ts.map +1 -1
- package/lib/verifier-runner.js +22 -1
- package/lib/verifier-runner.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +15 -10
- package/runtime/linux-candidate-entry.py +82 -0
- package/runtime/linux-namespace-worker.mjs +561 -0
- package/runtime/linux-seccomp-policy.LICENSE +202 -0
- package/runtime/linux-seccomp-policy.json +886 -0
- package/runtime/linux-seccomp.py +106 -0
- package/runtime/linux-userns-launcher.py +149 -0
- package/runtime/linux-userns-proof.py +299 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmodSync, lstatSync, mkdirSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, resolve, sep } from 'node:path';
|
|
4
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
import { setTimeout as pause } from 'node:timers/promises';
|
|
6
|
+
import { IsolationLedger, IsolationLedgerError } from './ledger.js';
|
|
7
|
+
import { linuxExecutionContractDigest, linuxJobResourceReservation, validateLinuxExecutionContract } from './linux-execution-contract.js';
|
|
8
|
+
import { linuxExecutionReceiptBinding, prepareLinuxRepositoryIntent, readLinuxExecutionPartialCleanupProof, readLinuxRepositoryReceipt, runLinuxRepositoryExecution } from './linux-execution.js';
|
|
9
|
+
import { reconcileLinuxJob } from './linux-job-recovery.js';
|
|
10
|
+
import { consumeLinuxLedgerAdmissionRefusal, linuxRepositoryAdmissionRefusal, withLinuxLedgerAdmissionAttempt } from './linux-repository-admission.js';
|
|
11
|
+
import { linuxRuntimeDigest, validateLinuxRuntime, verifyLinuxRuntime } from './linux-runtime.js';
|
|
12
|
+
import { observeStorage } from './storage.js';
|
|
13
|
+
import { inspectLinuxRunnerState, validateLinuxRunnerStateSeal } from './linux-runner-state.js';
|
|
14
|
+
import { verifyLinuxSystemSnapshotRuntimeDependencies } from './linux-system-snapshot.js';
|
|
15
|
+
import { linuxRepositoryImplementationDigest, verifyLinuxUserNamespaceMapping } from './linux-user-namespace.js';
|
|
16
|
+
const TTL = 30_000;
|
|
17
|
+
const MAX_MEMORY = 32 * 1024;
|
|
18
|
+
const MAX_INODES = 3_000_000;
|
|
19
|
+
const MAX_STATE = 1024 ** 3;
|
|
20
|
+
const GRANT = 'repository';
|
|
21
|
+
const canonical = (value) => Array.isArray(value) ? `[${value.map(canonical).join(',')}]`
|
|
22
|
+
: value && typeof value === 'object' ? `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`
|
|
23
|
+
: JSON.stringify(value);
|
|
24
|
+
const hash = (value) => createHash('sha256').update(canonical(value)).digest('hex');
|
|
25
|
+
const positive = (value) => Number.isSafeInteger(value) && value > 0;
|
|
26
|
+
const validKey = (key) => typeof key === 'string' && key.length > 0 && key.length <= 256 && !/[\p{Cc}\\]/u.test(key);
|
|
27
|
+
const safePath = (path) => typeof path === 'string' && path.length > 0 && path.length <= 4096
|
|
28
|
+
&& !isAbsolute(path) && !/[\p{Cc}\\]/u.test(path) && path.split('/').every(part => part && part !== '.' && part !== '..');
|
|
29
|
+
const uncertain = (job, reason = 'repository-in-progress-no-replay') => job.result
|
|
30
|
+
?? { jobId: job.id, status: 'unknown', quiescent: false, stdout: '', stderr: '', artifacts: [], reason };
|
|
31
|
+
/** Refuse mixed or historical ledgers before IsolationLedger can migrate,
|
|
32
|
+
* change WAL mode, claim a controller, or synchronize any grant. */
|
|
33
|
+
function preflightLedger(root, identity, schemaVersion) {
|
|
34
|
+
let directory;
|
|
35
|
+
try {
|
|
36
|
+
directory = lstatSync(root);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code === 'ENOENT')
|
|
40
|
+
return;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
if (!directory.isDirectory() || directory.isSymbolicLink() || directory.uid !== process.getuid?.()
|
|
44
|
+
|| directory.mode & 0o077 || realpathSync(root) !== root)
|
|
45
|
+
throw new Error('Linux repository state is not private');
|
|
46
|
+
const path = join(root, 'ledger.sqlite');
|
|
47
|
+
let file;
|
|
48
|
+
try {
|
|
49
|
+
file = lstatSync(path);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error.code === 'ENOENT') {
|
|
53
|
+
for (const suffix of ['-wal', '-shm']) {
|
|
54
|
+
try {
|
|
55
|
+
lstatSync(path + suffix);
|
|
56
|
+
}
|
|
57
|
+
catch (failure) {
|
|
58
|
+
if (failure.code === 'ENOENT')
|
|
59
|
+
continue;
|
|
60
|
+
throw failure;
|
|
61
|
+
}
|
|
62
|
+
throw new Error('Linux repository ledger sidecar has no database');
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
if (!file.isFile() || file.isSymbolicLink() || file.uid !== process.getuid?.() || file.mode & 0o077 || file.nlink !== 1
|
|
69
|
+
|| realpathSync(path) !== path)
|
|
70
|
+
throw new Error('Linux repository ledger is not private');
|
|
71
|
+
for (const suffix of ['-wal', '-shm']) {
|
|
72
|
+
const sidecar = path + suffix;
|
|
73
|
+
let info;
|
|
74
|
+
try {
|
|
75
|
+
info = lstatSync(sidecar);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error.code === 'ENOENT')
|
|
79
|
+
continue;
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
// SQLite can inherit 0644 before its initial DB chmod; the checked 0700
|
|
83
|
+
// parent keeps these owner-held, non-writable sidecars private as a unit.
|
|
84
|
+
if (!info.isFile() || info.isSymbolicLink() || info.uid !== process.getuid?.() || info.mode & 0o022 || info.nlink !== 1
|
|
85
|
+
|| realpathSync(sidecar) !== sidecar)
|
|
86
|
+
throw new Error('Linux repository ledger sidecar is unsafe');
|
|
87
|
+
}
|
|
88
|
+
const database = new DatabaseSync(path, { readOnly: true });
|
|
89
|
+
try {
|
|
90
|
+
database.exec('BEGIN');
|
|
91
|
+
if (database.prepare('PRAGMA user_version').get()?.user_version !== 9
|
|
92
|
+
|| database.prepare("SELECT value FROM schema_meta WHERE key='schema-version'").get()?.value !== '9')
|
|
93
|
+
throw new Error('Linux repository ledger requires schema 9');
|
|
94
|
+
const values = [GRANT, identity.principalDigest, identity.principalRecordId, identity.principalVersion, identity.workspace, identity.agentPreset];
|
|
95
|
+
const foreignGrant = database.prepare(`SELECT 1 FROM isolation_grants WHERE id<>? OR principal_digest<>? OR principal_record_id<>?
|
|
96
|
+
OR principal_version<>? OR workspace<>? OR agent_preset<>? LIMIT 1`).get(...values);
|
|
97
|
+
const foreignJob = database.prepare(`SELECT 1 FROM isolation_jobs WHERE grant_id<>? OR principal_digest<>? OR principal_record_id<>?
|
|
98
|
+
OR principal_version<>? OR workspace<>? OR agent_preset<>? OR backend_kind<>'linux-native' OR session_id<>?
|
|
99
|
+
OR COALESCE(json_extract(linux_contract_json,'$.schemaVersion'),0)<>? LIMIT 1`).get(...values, `authority:${identity.principalDigest}`, schemaVersion);
|
|
100
|
+
if (foreignGrant || foreignJob)
|
|
101
|
+
throw new Error('Linux repository state contains foreign authority or backend');
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
database.close();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Private finite repository authority. No archive factory or command is replayed
|
|
108
|
+
* for an existing key, including an interrupted or failed preparation. */
|
|
109
|
+
export class LinuxRepositoryRunner {
|
|
110
|
+
#input;
|
|
111
|
+
#contract;
|
|
112
|
+
#identity;
|
|
113
|
+
#ledger;
|
|
114
|
+
#executionRoot;
|
|
115
|
+
#abort = new AbortController();
|
|
116
|
+
#admitting = new Map();
|
|
117
|
+
#runGeneration = 0;
|
|
118
|
+
#pending = new Map();
|
|
119
|
+
#calls = new Set();
|
|
120
|
+
#inspections = new Set();
|
|
121
|
+
#ready;
|
|
122
|
+
#controller;
|
|
123
|
+
#timer;
|
|
124
|
+
#sweep;
|
|
125
|
+
#stateIdentity;
|
|
126
|
+
#ledgerIdentity;
|
|
127
|
+
#cursor = '';
|
|
128
|
+
#active = true;
|
|
129
|
+
#closePromise;
|
|
130
|
+
constructor(input) {
|
|
131
|
+
if (process.platform !== 'linux' || !['x64', 'arm64'].includes(process.arch) || !process.getuid?.()
|
|
132
|
+
|| !input || Object.keys(input).sort().join() !== ['authorityDigest', 'backend', 'cgroupRoots', 'expiresAt', 'limits', 'maxRuns', 'maxTotalDurationMs', 'runtime', 'stateRoot', ...(input.stateSeal !== undefined ? ['stateSeal'] : []), ...(input.userNamespace !== undefined ? ['systemSnapshot', 'userNamespace'] : [])].sort().join()
|
|
133
|
+
|| (input.userNamespace === undefined) !== (input.systemSnapshot === undefined) || input.backend !== 'linux-native' || !/^[a-f0-9]{64}$/u.test(input.authorityDigest)
|
|
134
|
+
|| !positive(input.expiresAt) || !positive(input.maxRuns) || input.maxRuns > 1000
|
|
135
|
+
|| !positive(input.maxTotalDurationMs) || input.maxTotalDurationMs > 86_400_000
|
|
136
|
+
|| !isAbsolute(input.stateRoot) || resolve(input.stateRoot) !== input.stateRoot || input.stateRoot === '/'
|
|
137
|
+
|| /[\p{Cc}\\]/u.test(input.stateRoot))
|
|
138
|
+
throw new Error('invalid Linux repository authority');
|
|
139
|
+
const stateSeal = input.stateSeal === undefined ? undefined : Object.freeze(validateLinuxRunnerStateSeal(input.stateSeal));
|
|
140
|
+
if (stateSeal)
|
|
141
|
+
inspectLinuxRunnerState({ stateRoot: input.stateRoot, stateSeal, kind: 'repository' });
|
|
142
|
+
const runtime = validateLinuxRuntime(input.runtime);
|
|
143
|
+
if (runtime.root.path === input.stateRoot || runtime.root.path.startsWith(input.stateRoot + sep)
|
|
144
|
+
|| input.stateRoot.startsWith(runtime.root.path + sep))
|
|
145
|
+
throw new Error('Linux repository state overlaps runtime');
|
|
146
|
+
const checked = validateLinuxExecutionContract({ schemaVersion: input.userNamespace !== undefined ? 3 : 2, backend: 'linux-native', profile: 'repository',
|
|
147
|
+
runtimeDigest: linuxRuntimeDigest(runtime), limits: input.limits, cgroupRoots: input.cgroupRoots,
|
|
148
|
+
keeperLimits: { memoryMiB: 256, pidsLimit: 64, cpus: 0.5 },
|
|
149
|
+
...(input.userNamespace !== undefined ? { userNamespace: input.userNamespace, systemSnapshot: input.systemSnapshot,
|
|
150
|
+
implementationDigest: linuxRepositoryImplementationDigest() } : {}) });
|
|
151
|
+
if (checked.schemaVersion === 1 || checked.limits.maxDurationMs > input.maxTotalDurationMs)
|
|
152
|
+
throw new Error('invalid Linux repository duration budget');
|
|
153
|
+
if (checked.schemaVersion === 3 && (checked.systemSnapshot.root.path === input.stateRoot
|
|
154
|
+
|| checked.systemSnapshot.root.path.startsWith(input.stateRoot + sep) || input.stateRoot.startsWith(checked.systemSnapshot.root.path + sep)))
|
|
155
|
+
throw new Error('Linux repository state overlaps system snapshot');
|
|
156
|
+
this.#contract = checked;
|
|
157
|
+
const reservation = this.#reservation();
|
|
158
|
+
if (reservation.memoryMiB > MAX_MEMORY || reservation.workspaceInodes > MAX_INODES)
|
|
159
|
+
throw new Error('Linux repository limits exceed resource pool');
|
|
160
|
+
this.#input = Object.freeze({ ...input, runtime, limits: checked.limits, cgroupRoots: checked.cgroupRoots,
|
|
161
|
+
...(stateSeal ? { stateSeal } : {}),
|
|
162
|
+
...(checked.schemaVersion === 3 ? { userNamespace: checked.userNamespace, systemSnapshot: checked.systemSnapshot } : {}) });
|
|
163
|
+
this.#identity = Object.freeze({ principalDigest: input.authorityDigest, principalRecordId: `repository:${input.authorityDigest}`,
|
|
164
|
+
principalVersion: 1, workspace: input.stateRoot, agentPreset: 'repository' });
|
|
165
|
+
preflightLedger(input.stateRoot, this.#identity, checked.schemaVersion);
|
|
166
|
+
mkdirSync(input.stateRoot, { mode: 0o700, recursive: true });
|
|
167
|
+
const info = lstatSync(input.stateRoot);
|
|
168
|
+
if (realpathSync(input.stateRoot) !== input.stateRoot || !info.isDirectory() || info.isSymbolicLink() || info.uid !== process.getuid?.()) {
|
|
169
|
+
throw new Error('Linux repository requires a private owned state root');
|
|
170
|
+
}
|
|
171
|
+
chmodSync(input.stateRoot, 0o700);
|
|
172
|
+
this.#executionRoot = join(input.stateRoot, 'linux-executions');
|
|
173
|
+
mkdirSync(this.#executionRoot, { mode: 0o700, recursive: true });
|
|
174
|
+
const native = lstatSync(this.#executionRoot);
|
|
175
|
+
if (realpathSync(this.#executionRoot) !== this.#executionRoot || !native.isDirectory() || native.isSymbolicLink()
|
|
176
|
+
|| native.uid !== process.getuid?.() || native.mode & 0o077)
|
|
177
|
+
throw new Error('Linux repository execution state is not private');
|
|
178
|
+
this.#ledger = new IsolationLedger(join(input.stateRoot, 'ledger.sqlite'));
|
|
179
|
+
this.#stateIdentity = { dev: info.dev, ino: info.ino };
|
|
180
|
+
const ledgerInfo = lstatSync(join(input.stateRoot, 'ledger.sqlite'));
|
|
181
|
+
this.#ledgerIdentity = { dev: ledgerInfo.dev, ino: ledgerInfo.ino };
|
|
182
|
+
this.#ready = this.#start();
|
|
183
|
+
void this.#ready.catch(() => { this.#active = false; this.#abort.abort(); });
|
|
184
|
+
}
|
|
185
|
+
get contractDigest() { return linuxExecutionContractDigest(this.#contract); }
|
|
186
|
+
get runtimeDigest() { return this.#contract.runtimeDigest; }
|
|
187
|
+
#reservation() {
|
|
188
|
+
const minimum = linuxJobResourceReservation(this.#contract);
|
|
189
|
+
return { ...minimum, memoryMiB: minimum.memoryMiB + 256, storageBytes: minimum.storageBytes + 96 * 1024 ** 2 };
|
|
190
|
+
}
|
|
191
|
+
#authority() {
|
|
192
|
+
if (this.#input.stateSeal)
|
|
193
|
+
inspectLinuxRunnerState({ stateRoot: this.#input.stateRoot,
|
|
194
|
+
stateSeal: this.#input.stateSeal, kind: 'repository' });
|
|
195
|
+
if (!this.#controller || !this.#ledger.hasController(this.#controller))
|
|
196
|
+
throw new Error('Linux repository controller unavailable');
|
|
197
|
+
return this.#controller;
|
|
198
|
+
}
|
|
199
|
+
#available(signal) {
|
|
200
|
+
if (!this.#active || signal?.aborted)
|
|
201
|
+
throw new Error('Linux repository unavailable');
|
|
202
|
+
this.#authority();
|
|
203
|
+
}
|
|
204
|
+
#owns(job) {
|
|
205
|
+
return job.backend === 'linux-native' && job.grantId === GRANT && canonical(job.identity) === canonical(this.#identity)
|
|
206
|
+
&& job.linuxContract?.schemaVersion === this.#contract.schemaVersion;
|
|
207
|
+
}
|
|
208
|
+
#proofIdentity() {
|
|
209
|
+
try {
|
|
210
|
+
if (this.#input.stateSeal)
|
|
211
|
+
inspectLinuxRunnerState({ stateRoot: this.#input.stateRoot,
|
|
212
|
+
stateSeal: this.#input.stateSeal, kind: 'repository' });
|
|
213
|
+
const path = join(this.#input.stateRoot, 'ledger.sqlite'), root = lstatSync(this.#input.stateRoot), file = lstatSync(path);
|
|
214
|
+
return realpathSync(this.#input.stateRoot) === this.#input.stateRoot && root.isDirectory() && !root.isSymbolicLink()
|
|
215
|
+
&& root.uid === process.getuid?.() && !(root.mode & 0o077) && root.dev === this.#stateIdentity.dev && root.ino === this.#stateIdentity.ino
|
|
216
|
+
&& realpathSync(path) === path && file.isFile() && !file.isSymbolicLink() && file.uid === process.getuid?.() && !(file.mode & 0o077)
|
|
217
|
+
&& file.nlink === 1 && file.dev === this.#ledgerIdentity.dev && file.ino === this.#ledgerIdentity.ino;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
#find(key) {
|
|
224
|
+
return this.#ledger.findJobByKey(this.#identity, `authority:${this.#input.authorityDigest}`, GRANT, key);
|
|
225
|
+
}
|
|
226
|
+
async #start() {
|
|
227
|
+
const owner = randomUUID(), deadline = Date.now() + 31_000;
|
|
228
|
+
for (;;) {
|
|
229
|
+
this.#abort.signal.throwIfAborted();
|
|
230
|
+
if (this.#input.stateSeal)
|
|
231
|
+
inspectLinuxRunnerState({ stateRoot: this.#input.stateRoot,
|
|
232
|
+
stateSeal: this.#input.stateSeal, kind: 'repository' });
|
|
233
|
+
try {
|
|
234
|
+
this.#controller = this.#ledger.claimController(owner, TTL);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
if (!(error instanceof IsolationLedgerError) || error.code !== 'unauthorized' || Date.now() >= deadline)
|
|
239
|
+
throw error;
|
|
240
|
+
await pause(Math.min(250, deadline - Date.now()), undefined, { signal: this.#abort.signal });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
this.#abort.signal.throwIfAborted();
|
|
244
|
+
this.#ledger.syncGrants([{ ...this.#identity, id: GRANT, revision: 1, expiresAt: this.#input.expiresAt,
|
|
245
|
+
maxRuns: this.#input.maxRuns, maxTotalDurationMs: this.#input.maxTotalDurationMs }], this.#controller);
|
|
246
|
+
this.#timer = setInterval(() => {
|
|
247
|
+
try {
|
|
248
|
+
if (this.#input.stateSeal)
|
|
249
|
+
inspectLinuxRunnerState({ stateRoot: this.#input.stateRoot,
|
|
250
|
+
stateSeal: this.#input.stateSeal, kind: 'repository' });
|
|
251
|
+
if (this.#controller && this.#ledger.renewController(this.#controller, TTL)) {
|
|
252
|
+
if (!this.#sweep) {
|
|
253
|
+
const sweep = this.#ready.then(() => this.#recover());
|
|
254
|
+
this.#sweep = sweep;
|
|
255
|
+
void sweep.catch(() => { }).finally(() => { if (this.#sweep === sweep)
|
|
256
|
+
this.#sweep = undefined; });
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch { /* A lost controller fence revokes every current admission. */ }
|
|
262
|
+
this.#active = false;
|
|
263
|
+
this.#abort.abort();
|
|
264
|
+
}, 5000);
|
|
265
|
+
this.#timer.unref();
|
|
266
|
+
await this.#recover();
|
|
267
|
+
}
|
|
268
|
+
async #recover() {
|
|
269
|
+
let visited = 0;
|
|
270
|
+
while (visited < 16 && this.#active) {
|
|
271
|
+
this.#authority();
|
|
272
|
+
const jobs = this.#ledger.recoverable(this.#cursor).slice(0, 16 - visited);
|
|
273
|
+
if (!jobs.length) {
|
|
274
|
+
this.#cursor = '';
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
for (const job of jobs) {
|
|
278
|
+
if (!this.#active)
|
|
279
|
+
return;
|
|
280
|
+
this.#cursor = job.id;
|
|
281
|
+
visited++;
|
|
282
|
+
if (!this.#owns(job) || this.#pending.has(job.id))
|
|
283
|
+
continue;
|
|
284
|
+
await reconcileLinuxJob(this.#ledger, this.#authority(), job, this.#executionRoot, this.#abort.signal);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
#request(input) {
|
|
289
|
+
if (!input || Object.keys(input).sort().join() !== 'archive,argv,artifacts' || !input.archive
|
|
290
|
+
|| Object.keys(input.archive).sort().join() !== 'bytes,sha256' || !positive(input.archive.bytes)
|
|
291
|
+
|| input.archive.bytes > this.#contract.limits.maxInputBytes || !/^[a-f0-9]{64}$/u.test(input.archive.sha256)
|
|
292
|
+
|| !Array.isArray(input.argv) || input.argv.length < 1 || input.argv.length > 64
|
|
293
|
+
|| input.argv.some((arg, index) => typeof arg !== 'string' || arg.length > 65_536 || arg.includes('\0')
|
|
294
|
+
|| index === 0 && (!isAbsolute(arg) || resolve(arg) !== arg || /[\r\n]/u.test(arg)))
|
|
295
|
+
|| !Array.isArray(input.artifacts) || input.artifacts.length > this.#contract.limits.maxFiles
|
|
296
|
+
|| input.artifacts.some(path => !safePath(path)) || new Set(input.artifacts).size !== input.artifacts.length)
|
|
297
|
+
throw new Error('invalid Linux repository request');
|
|
298
|
+
return Object.freeze({ argv: Object.freeze([...input.argv]), archive: Object.freeze({ ...input.archive }), artifacts: Object.freeze([...input.artifacts]) });
|
|
299
|
+
}
|
|
300
|
+
#output(result, artifacts = [], receiptBinding) {
|
|
301
|
+
return { result, artifacts, ...(receiptBinding ? { receiptBinding } : {}), contractDigest: this.contractDigest, runtimeDigest: this.runtimeDigest };
|
|
302
|
+
}
|
|
303
|
+
async #evidence(job, finalizing = false) {
|
|
304
|
+
const available = () => { if (finalizing)
|
|
305
|
+
this.#authority();
|
|
306
|
+
else
|
|
307
|
+
this.#available(); };
|
|
308
|
+
if (!job.dispatchAttempted)
|
|
309
|
+
return this.#output(uncertain(job));
|
|
310
|
+
if (!job.linuxContract || job.linuxContract.schemaVersion === 1 || !job.linuxIntent || job.linuxIntent.schemaVersion === 1)
|
|
311
|
+
return this.#output(uncertain(job));
|
|
312
|
+
const ctx = { stateRoot: this.#executionRoot, contract: job.linuxContract, intent: job.linuxIntent };
|
|
313
|
+
try {
|
|
314
|
+
const verified = await readLinuxRepositoryReceipt(ctx);
|
|
315
|
+
available();
|
|
316
|
+
if (verified && job.result?.quiescent) {
|
|
317
|
+
const binding = linuxExecutionReceiptBinding(verified.receipt);
|
|
318
|
+
if (job.status === 'unknown')
|
|
319
|
+
return this.#output(job.result);
|
|
320
|
+
if (canonical(job.result) !== canonical(verified.receipt.result))
|
|
321
|
+
throw new Error('repository result differs from receipt');
|
|
322
|
+
return this.#output(job.result, job.status === 'succeeded' ? verified.artifacts : [], binding);
|
|
323
|
+
}
|
|
324
|
+
if (await readLinuxExecutionPartialCleanupProof(ctx)) {
|
|
325
|
+
available();
|
|
326
|
+
return this.#output(uncertain(job));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
available();
|
|
331
|
+
}
|
|
332
|
+
if (job.status === 'unknown')
|
|
333
|
+
return this.#output({ ...uncertain(job), quiescent: false });
|
|
334
|
+
return this.#output({ ...uncertain(job), status: 'unknown', quiescent: false, artifacts: [], reason: 'repository receipt unavailable' });
|
|
335
|
+
}
|
|
336
|
+
/** Factories own their Host subprocess cleanup through the combined signal;
|
|
337
|
+
* late completion can never open native execution admission. */
|
|
338
|
+
run = (key, request, loadArchive, signal) => {
|
|
339
|
+
if (!validKey(key))
|
|
340
|
+
return Promise.reject(new Error('invalid Linux repository key or archive factory'));
|
|
341
|
+
this.#admitting.set(key, (this.#admitting.get(key) ?? 0) + 1);
|
|
342
|
+
const call = this.#run(key, request, loadArchive, signal, ++this.#runGeneration);
|
|
343
|
+
this.#calls.add(call);
|
|
344
|
+
const finished = () => {
|
|
345
|
+
this.#calls.delete(call);
|
|
346
|
+
const count = this.#admitting.get(key) - 1;
|
|
347
|
+
if (count)
|
|
348
|
+
this.#admitting.set(key, count);
|
|
349
|
+
else
|
|
350
|
+
this.#admitting.delete(key);
|
|
351
|
+
};
|
|
352
|
+
void call.then(finished, finished);
|
|
353
|
+
return call;
|
|
354
|
+
};
|
|
355
|
+
async #run(key, raw, loadArchive, signal, generation) {
|
|
356
|
+
if (typeof loadArchive !== 'function')
|
|
357
|
+
throw new Error('invalid Linux repository key or archive factory');
|
|
358
|
+
const request = this.#request(raw), contractDigest = this.contractDigest;
|
|
359
|
+
const requestDigest = hash({ request, authority: this.#input.authorityDigest, contract: contractDigest });
|
|
360
|
+
await this.#ready;
|
|
361
|
+
const authority = { ...this.#authority() };
|
|
362
|
+
let prepareAttempted = false;
|
|
363
|
+
const ledgerAttempt = {};
|
|
364
|
+
const current = () => this.#proofIdentity() && this.#active && this.#runGeneration === generation && this.contractDigest === contractDigest
|
|
365
|
+
&& this.#controller?.ownerId === authority.ownerId && this.#controller.fence === authority.fence
|
|
366
|
+
&& this.#ledger.hasController(authority) && !this.#find(key) && this.#proofIdentity();
|
|
367
|
+
try {
|
|
368
|
+
this.#available(signal);
|
|
369
|
+
const existing = this.#find(key);
|
|
370
|
+
if (existing)
|
|
371
|
+
return await this.#existing(existing, requestDigest);
|
|
372
|
+
if (this.#contract.schemaVersion === 3) {
|
|
373
|
+
await verifyLinuxUserNamespaceMapping(this.#contract.userNamespace);
|
|
374
|
+
await verifyLinuxSystemSnapshotRuntimeDependencies(this.#contract.systemSnapshot, this.#input.runtime);
|
|
375
|
+
}
|
|
376
|
+
else
|
|
377
|
+
await verifyLinuxRuntime(this.#input.runtime);
|
|
378
|
+
this.#available(signal);
|
|
379
|
+
const observation = await observeStorage(this.#input.stateRoot);
|
|
380
|
+
this.#available(signal);
|
|
381
|
+
const reservation = this.#reservation();
|
|
382
|
+
prepareAttempted = true;
|
|
383
|
+
const { job, created } = withLinuxLedgerAdmissionAttempt(this.#ledger, ledgerAttempt, () => this.#ledger.prepare({ identity: this.#identity, sessionId: `authority:${this.#input.authorityDigest}`,
|
|
384
|
+
grantId: GRANT, idempotencyKey: key, requestDigest, durationMs: this.#contract.limits.maxDurationMs,
|
|
385
|
+
maxActiveJobs: 1, authority: this.#authority(), backend: 'linux-native', linuxContract: this.#contract,
|
|
386
|
+
resourceReservation: { memoryMiB: reservation.memoryMiB, workspaceInodes: reservation.workspaceInodes,
|
|
387
|
+
maxMemoryMiB: MAX_MEMORY, maxWorkspaceInodes: MAX_INODES },
|
|
388
|
+
storageBudget: { maxStateBytes: MAX_STATE, maxJobRecords: 10_000, reservedBytes: reservation.storageBytes,
|
|
389
|
+
...(observation ? { observation } : {}) } }));
|
|
390
|
+
if (!created)
|
|
391
|
+
return await this.#existing(job, requestDigest);
|
|
392
|
+
const pending = this.#execute(job, request, loadArchive, AbortSignal.any([signal, this.#abort.signal]));
|
|
393
|
+
this.#pending.set(job.id, pending);
|
|
394
|
+
try {
|
|
395
|
+
return await pending;
|
|
396
|
+
}
|
|
397
|
+
finally {
|
|
398
|
+
this.#pending.delete(job.id);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
let eligible = false;
|
|
403
|
+
try {
|
|
404
|
+
eligible = this.#admitting.get(key) === 1 && current();
|
|
405
|
+
}
|
|
406
|
+
catch { /* Unavailable identity/fence is unknown. */ }
|
|
407
|
+
if (eligible && (!prepareAttempted || consumeLinuxLedgerAdmissionRefusal(error, { ledger: this.#ledger, key, requestDigest, authority, attempt: ledgerAttempt }))) {
|
|
408
|
+
throw linuxRepositoryAdmissionRefusal(error, { runner: this, key, request,
|
|
409
|
+
current: () => !this.#admitting.has(key) && current() });
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async #existing(job, requestDigest) {
|
|
415
|
+
if (!this.#owns(job) || job.requestDigest !== requestDigest || linuxExecutionContractDigest(job.linuxContract) !== this.contractDigest) {
|
|
416
|
+
throw new Error('repository idempotency key has a different request or contract');
|
|
417
|
+
}
|
|
418
|
+
const pending = this.#pending.get(job.id);
|
|
419
|
+
if (pending)
|
|
420
|
+
return await pending;
|
|
421
|
+
if (!job.result?.quiescent) {
|
|
422
|
+
await reconcileLinuxJob(this.#ledger, this.#authority(), job, this.#executionRoot, this.#abort.signal);
|
|
423
|
+
this.#available();
|
|
424
|
+
}
|
|
425
|
+
return await this.#evidence(this.#ledger.get(job.id));
|
|
426
|
+
}
|
|
427
|
+
async #load(loadArchive, signal, deadline, revoke) {
|
|
428
|
+
signal.throwIfAborted();
|
|
429
|
+
let stop = () => { };
|
|
430
|
+
const ended = new Promise((_resolve, reject) => { stop = () => reject(new Error('repository archive admission ended')); });
|
|
431
|
+
// Revoke the factory's capability before waking the settlement path. A
|
|
432
|
+
// rejected race alone leaves Host subprocesses running after capacity frees.
|
|
433
|
+
const timer = setTimeout(() => { revoke(); stop(); }, Math.max(1, deadline - Date.now()));
|
|
434
|
+
signal.addEventListener('abort', stop, { once: true });
|
|
435
|
+
try {
|
|
436
|
+
return await Promise.race([Promise.resolve().then(() => {
|
|
437
|
+
signal.throwIfAborted();
|
|
438
|
+
if (Date.now() >= deadline) {
|
|
439
|
+
revoke();
|
|
440
|
+
throw new Error('repository archive admission ended');
|
|
441
|
+
}
|
|
442
|
+
this.#authority();
|
|
443
|
+
return loadArchive(signal);
|
|
444
|
+
}), ended]);
|
|
445
|
+
}
|
|
446
|
+
finally {
|
|
447
|
+
clearTimeout(timer);
|
|
448
|
+
signal.removeEventListener('abort', stop);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async #execute(initial, request, loadArchive, signal) {
|
|
452
|
+
let job = initial;
|
|
453
|
+
const abort = new AbortController();
|
|
454
|
+
const current = () => this.#active && !signal.aborted && this.#ledger.hasController(this.#authority()) && this.#ledger.usable(job.id);
|
|
455
|
+
const timer = setInterval(() => { try {
|
|
456
|
+
if (current())
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
catch { /* A fence loss closes archive and process admission. */ } ; abort.abort(); }, 100);
|
|
460
|
+
timer.unref();
|
|
461
|
+
const combined = AbortSignal.any([signal, abort.signal]);
|
|
462
|
+
try {
|
|
463
|
+
if (!current())
|
|
464
|
+
throw new Error('repository authorization changed');
|
|
465
|
+
const loaded = await this.#load(loadArchive, combined, job.deadline, () => abort.abort());
|
|
466
|
+
if (!current())
|
|
467
|
+
throw new Error('repository authorization changed');
|
|
468
|
+
if (!(loaded instanceof Uint8Array) || loaded.byteLength !== request.archive.bytes)
|
|
469
|
+
throw new Error('repository archive length differs');
|
|
470
|
+
const bytes = Buffer.from(loaded);
|
|
471
|
+
if (createHash('sha256').update(bytes).digest('hex') !== request.archive.sha256)
|
|
472
|
+
throw new Error('repository archive digest differs');
|
|
473
|
+
const execution = { stateRoot: this.#executionRoot, contract: this.#contract, ...request,
|
|
474
|
+
archive: { bytes, sha256: request.archive.sha256 }, readOnlyRoots: [this.#input.runtime.root.path] };
|
|
475
|
+
const intent = await prepareLinuxRepositoryIntent({ ...execution, job: { id: job.id, requestDigest: job.requestDigest,
|
|
476
|
+
nativeResourceId: job.nativeResourceId, nativeNonce: job.nativeNonce, deadline: job.deadline } });
|
|
477
|
+
if (!current())
|
|
478
|
+
throw new Error('repository authorization changed');
|
|
479
|
+
job = this.#ledger.markLinuxCreateIntent(job.id, job.version, intent, this.#authority());
|
|
480
|
+
job = this.#ledger.markDispatched(job.id, job.version, this.#authority());
|
|
481
|
+
const outcome = await runLinuxRepositoryExecution({ ...execution, intent, signal: combined, recordResources: async (resources) => {
|
|
482
|
+
if (!current())
|
|
483
|
+
throw new Error('repository authorization changed');
|
|
484
|
+
job = this.#ledger.recordLinuxResources(job.id, job.version, resources, this.#authority());
|
|
485
|
+
}, authorizeStart: () => {
|
|
486
|
+
if (!current())
|
|
487
|
+
return false;
|
|
488
|
+
job = this.#ledger.start(job.id, job.version, this.#authority());
|
|
489
|
+
return true;
|
|
490
|
+
} });
|
|
491
|
+
if (!outcome.result.quiescent)
|
|
492
|
+
job = this.#ledger.settle(job.id, job.version, outcome.result, this.#authority());
|
|
493
|
+
// Closing revokes admission, while the still-owned controller must finish
|
|
494
|
+
// evidence and settlement before close() releases the ledger.
|
|
495
|
+
await reconcileLinuxJob(this.#ledger, this.#authority(), job, this.#executionRoot);
|
|
496
|
+
job = this.#ledger.get(job.id);
|
|
497
|
+
return await this.#evidence(job, true);
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
const cancelled = combined.aborted;
|
|
501
|
+
abort.abort();
|
|
502
|
+
const result = { jobId: job.id, status: job.dispatchAttempted ? 'unknown' : cancelled ? 'cancelled' : 'failed',
|
|
503
|
+
quiescent: !job.dispatchAttempted, stdout: '', stderr: '', artifacts: [],
|
|
504
|
+
reason: job.dispatchAttempted ? 'repository-dispatch-unconfirmed' : (error instanceof Error ? error.message : 'repository preparation failed').slice(0, 8192) };
|
|
505
|
+
try {
|
|
506
|
+
const fresh = this.#ledger.get(job.id);
|
|
507
|
+
if (fresh.result)
|
|
508
|
+
return this.#output(fresh.result);
|
|
509
|
+
const settled = this.#ledger.settle(job.id, job.version, result, this.#authority());
|
|
510
|
+
return this.#output(settled.result);
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
return this.#output({ ...result, status: 'unknown', quiescent: false, reason: 'repository-settlement-unconfirmed' });
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
finally {
|
|
517
|
+
clearInterval(timer);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/** Read the original successful execution only. This cannot admit, recover or
|
|
521
|
+
* replay a job, and close() drains every outstanding receipt read. */
|
|
522
|
+
inspectSuccessfulExecution = (key, signal) => {
|
|
523
|
+
if (!validKey(key))
|
|
524
|
+
return Promise.reject(new Error('invalid Linux repository key'));
|
|
525
|
+
const call = this.#inspectSuccessfulExecution(key, signal);
|
|
526
|
+
this.#inspections.add(call);
|
|
527
|
+
const finished = () => { this.#inspections.delete(call); };
|
|
528
|
+
void call.then(finished, finished);
|
|
529
|
+
return call;
|
|
530
|
+
};
|
|
531
|
+
async #inspectSuccessfulExecution(key, signal) {
|
|
532
|
+
const live = () => {
|
|
533
|
+
if (!this.#active || signal?.aborted || Date.now() >= this.#input.expiresAt)
|
|
534
|
+
throw new Error('Linux repository unavailable');
|
|
535
|
+
};
|
|
536
|
+
const available = () => {
|
|
537
|
+
live();
|
|
538
|
+
this.#available(signal);
|
|
539
|
+
if (!this.#proofIdentity() || !this.#ledger.permitsGrant(this.#identity, GRANT))
|
|
540
|
+
throw new Error('Linux repository authority unavailable');
|
|
541
|
+
};
|
|
542
|
+
live();
|
|
543
|
+
await this.#ready;
|
|
544
|
+
available();
|
|
545
|
+
try {
|
|
546
|
+
const job = this.#find(key);
|
|
547
|
+
if (!job || !this.#owns(job) || job.grantRevision !== 1 || !job.linuxContract || job.linuxContract.schemaVersion === 1 || linuxExecutionContractDigest(job.linuxContract) !== this.contractDigest
|
|
548
|
+
|| !job.dispatchAttempted || job.status !== 'succeeded' || job.result?.status !== 'succeeded' || !job.result.quiescent
|
|
549
|
+
|| !job.linuxIntent || job.linuxIntent.schemaVersion === 1 || !job.linuxResources || this.#pending.has(job.id))
|
|
550
|
+
return undefined;
|
|
551
|
+
const verified = await readLinuxRepositoryReceipt({ stateRoot: this.#executionRoot, contract: job.linuxContract, intent: job.linuxIntent });
|
|
552
|
+
available();
|
|
553
|
+
// The reader proves fresh kernel absence and actual spool integrity. Bind
|
|
554
|
+
// all of its identities to the original ledger, including after the await.
|
|
555
|
+
if (!verified || canonical(job) !== canonical(this.#find(key))
|
|
556
|
+
|| canonical(verified.receipt.intent) !== canonical(job.linuxIntent)
|
|
557
|
+
|| canonical(verified.receipt.resources) !== canonical(job.linuxResources)
|
|
558
|
+
|| canonical(verified.receipt.result) !== canonical(job.result))
|
|
559
|
+
return undefined;
|
|
560
|
+
return this.#output(job.result, verified.artifacts, linuxExecutionReceiptBinding(verified.receipt));
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
available();
|
|
564
|
+
return undefined;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
async inspectQuiescence(key) {
|
|
568
|
+
if (!validKey(key))
|
|
569
|
+
throw new Error('invalid Linux repository key');
|
|
570
|
+
await this.#ready;
|
|
571
|
+
this.#available();
|
|
572
|
+
let job = this.#find(key);
|
|
573
|
+
if (!job || !this.#owns(job) || this.#pending.has(job.id))
|
|
574
|
+
return 'unknown';
|
|
575
|
+
if (!job.result?.quiescent) {
|
|
576
|
+
await reconcileLinuxJob(this.#ledger, this.#authority(), job, this.#executionRoot, this.#abort.signal);
|
|
577
|
+
this.#available();
|
|
578
|
+
job = this.#ledger.get(job.id);
|
|
579
|
+
}
|
|
580
|
+
if (!job.dispatchAttempted)
|
|
581
|
+
return job.result?.quiescent ? 'quiescent' : 'unknown';
|
|
582
|
+
if (!job.linuxContract || job.linuxContract.schemaVersion === 1 || !job.linuxIntent || job.linuxIntent.schemaVersion === 1)
|
|
583
|
+
return 'unknown';
|
|
584
|
+
try {
|
|
585
|
+
const ctx = { stateRoot: this.#executionRoot, contract: job.linuxContract, intent: job.linuxIntent };
|
|
586
|
+
const proof = await readLinuxRepositoryReceipt(ctx) ?? await readLinuxExecutionPartialCleanupProof(ctx);
|
|
587
|
+
this.#available();
|
|
588
|
+
return proof && job.result?.quiescent ? 'quiescent' : 'unknown';
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
this.#available();
|
|
592
|
+
return 'unknown';
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
close = async () => {
|
|
596
|
+
if (this.#closePromise)
|
|
597
|
+
return await this.#closePromise;
|
|
598
|
+
this.#active = false;
|
|
599
|
+
this.#abort.abort();
|
|
600
|
+
clearInterval(this.#timer);
|
|
601
|
+
this.#closePromise = (async () => {
|
|
602
|
+
await Promise.allSettled([this.#ready, this.#sweep, ...this.#calls, ...this.#inspections]);
|
|
603
|
+
try {
|
|
604
|
+
if (this.#controller)
|
|
605
|
+
this.#ledger.releaseController(this.#controller);
|
|
606
|
+
}
|
|
607
|
+
finally {
|
|
608
|
+
this.#ledger.close();
|
|
609
|
+
}
|
|
610
|
+
})();
|
|
611
|
+
return await this.#closePromise;
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
//# sourceMappingURL=linux-repository-runner.js.map
|