@evomap/evolver-core 2.0.0-beta.14 → 2.0.0-beta.15
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/dist/algo/index.d.ts +1 -0
- package/dist/algo/index.js +1 -0
- package/dist/algo/publishEligibility.d.ts +34 -0
- package/dist/algo/publishEligibility.js +52 -0
- package/dist/events/paths.d.ts +3 -1
- package/dist/events/paths.js +4 -0
- package/dist/events/public.d.ts +1 -1
- package/dist/events/public.js +1 -1
- package/dist/exec/autoExec.d.ts +15 -0
- package/dist/exec/autoExec.js +34 -0
- package/dist/exec/autonomousCycle.d.ts +3 -0
- package/dist/exec/autonomousCycle.js +1 -0
- package/dist/exec/claudeBridge.d.ts +7 -0
- package/dist/exec/claudeBridge.js +17 -0
- package/dist/hub/capability.d.ts +79 -2
- package/dist/trace/index.d.ts +2 -1
- package/dist/trace/index.js +2 -1
- package/dist/trace/learningTrace.d.ts +194 -0
- package/dist/trace/learningTrace.js +274 -0
- package/dist/verify/sandboxRunner.d.ts +28 -0
- package/dist/verify/sandboxRunner.js +218 -19
- package/dist/verify/sandboxedValidation.d.ts +9 -0
- package/dist/verify/sandboxedValidation.js +113 -13
- package/package.json +1 -1
|
@@ -3,10 +3,72 @@
|
|
|
3
3
|
// metachar rejection, node-eval-flag blocking, a scrubbed env, a SIGKILL timeout, and — where unprivileged
|
|
4
4
|
// namespaces exist — no network + hidden home secrets, so a validation command cannot phone home / exfiltrate to
|
|
5
5
|
// game the pass/fail. This is the "verifier outside the repo, untrusted caller" half the safety model relies on.
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync } from 'node:fs';
|
|
8
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
9
|
+
import { isolationCommand, makeSandboxRunner, sandboxResourceLimitsAvailable, unshareNetAvailable, } from './sandboxRunner.js';
|
|
9
10
|
import { runValidation, validationScriptPath } from './validation.js';
|
|
11
|
+
let readOnlyIsolationAvailableCache;
|
|
12
|
+
let readOnlyFilesystemIsolationAvailableCache;
|
|
13
|
+
export function readOnlyFilesystemIsolationAvailable() {
|
|
14
|
+
if (process.platform !== 'linux')
|
|
15
|
+
return false;
|
|
16
|
+
if (readOnlyFilesystemIsolationAvailableCache !== undefined)
|
|
17
|
+
return readOnlyFilesystemIsolationAvailableCache;
|
|
18
|
+
const probeRoot = mkdtempSync('/var/tmp/evolver-isolation-probe-');
|
|
19
|
+
const probeHome = mkdtempSync('/var/tmp/evolver-isolation-home-');
|
|
20
|
+
const scratch = join(probeRoot, 'session');
|
|
21
|
+
const probeCwd = join(probeRoot, 'cwd');
|
|
22
|
+
try {
|
|
23
|
+
mkdirSync(scratch);
|
|
24
|
+
mkdirSync(probeCwd);
|
|
25
|
+
const probe = isolationCommand(process.execPath, ['--version'], {
|
|
26
|
+
noNetwork: true,
|
|
27
|
+
hideHomeSecrets: true,
|
|
28
|
+
readOnlyFilesystem: true,
|
|
29
|
+
writableTmpDir: scratch,
|
|
30
|
+
readOnlyRoot: probeCwd,
|
|
31
|
+
cwd: probeCwd,
|
|
32
|
+
});
|
|
33
|
+
readOnlyFilesystemIsolationAvailableCache = spawnSync(probe.cmd, probe.args, {
|
|
34
|
+
cwd: probeCwd,
|
|
35
|
+
env: {
|
|
36
|
+
HOME: process.env.HOME ?? probeHome,
|
|
37
|
+
PATH: process.env.PATH ?? '/usr/bin:/bin',
|
|
38
|
+
},
|
|
39
|
+
input: '\n',
|
|
40
|
+
timeout: 5_000,
|
|
41
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
42
|
+
}).status === 0;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
readOnlyFilesystemIsolationAvailableCache = false;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
rmSync(probeRoot, { recursive: true, force: true });
|
|
49
|
+
rmSync(probeHome, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
return readOnlyFilesystemIsolationAvailableCache;
|
|
52
|
+
}
|
|
53
|
+
export function readOnlyIsolationAvailable() {
|
|
54
|
+
if (process.platform !== 'linux')
|
|
55
|
+
return false;
|
|
56
|
+
if (readOnlyIsolationAvailableCache !== undefined)
|
|
57
|
+
return readOnlyIsolationAvailableCache;
|
|
58
|
+
readOnlyIsolationAvailableCache = sandboxResourceLimitsAvailable() && readOnlyFilesystemIsolationAvailable();
|
|
59
|
+
return readOnlyIsolationAvailableCache;
|
|
60
|
+
}
|
|
61
|
+
function validationReadOnlyRoot(cwd) {
|
|
62
|
+
let current = resolve(cwd);
|
|
63
|
+
while (true) {
|
|
64
|
+
if (existsSync(join(current, '.git')))
|
|
65
|
+
return current;
|
|
66
|
+
const parent = dirname(current);
|
|
67
|
+
if (parent === current)
|
|
68
|
+
return resolve(cwd);
|
|
69
|
+
current = parent;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
10
72
|
/**
|
|
11
73
|
* Run validation commands in the hardened sandbox. Isolation (no-network + hidden home secrets) is requested only
|
|
12
74
|
* when unprivileged namespaces are available; elsewhere (Windows/macOS) it degrades to the non-namespace hardening
|
|
@@ -15,26 +77,37 @@ import { runValidation, validationScriptPath } from './validation.js';
|
|
|
15
77
|
* preserving prior behavior; note this differs from runValidation's own "no commands = not verified" stance.
|
|
16
78
|
*/
|
|
17
79
|
export async function runSandboxedValidation(cmds, cwd, opts = {}) {
|
|
18
|
-
const
|
|
80
|
+
const isolationCheck = opts.unshareCheck ?? (opts.requireIsolation ? readOnlyIsolationAvailable : unshareNetAvailable);
|
|
81
|
+
const isolated = isolationCheck();
|
|
82
|
+
if (opts.requireIsolation && !isolated) {
|
|
83
|
+
return { passed: false, score: 0.2, results: [], skipped: [], isolated: false };
|
|
84
|
+
}
|
|
85
|
+
let validationCwd = resolve(cwd);
|
|
86
|
+
let readOnlyRoot = opts.readOnlyRoot ? resolve(opts.readOnlyRoot) : validationReadOnlyRoot(validationCwd);
|
|
87
|
+
if (opts.requireIsolation && isolated) {
|
|
88
|
+
try {
|
|
89
|
+
validationCwd = realpathSync(validationCwd);
|
|
90
|
+
readOnlyRoot = realpathSync(readOnlyRoot);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return { passed: false, score: 0.2, results: [], skipped: [], isolated };
|
|
94
|
+
}
|
|
95
|
+
if (dirname(readOnlyRoot) === readOnlyRoot || !pathIsWithin(readOnlyRoot, validationCwd)) {
|
|
96
|
+
return { passed: false, score: 0.2, results: [], skipped: [], isolated };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
19
99
|
// ONLY a truly empty command set is a vacuous pass (nothing to verify). A non-empty set with blank entries is a
|
|
20
100
|
// malformed plan, NOT a pass: blanks are kept (trimmed to '') so they fall outside the allowlist and fail, rather
|
|
21
101
|
// than being silently dropped into a pass (Bugbot).
|
|
22
102
|
if (cmds.length === 0)
|
|
23
103
|
return { passed: true, score: 0.95, results: [], skipped: [], isolated };
|
|
24
|
-
const runner = makeSandboxRunner({
|
|
25
|
-
cwd,
|
|
26
|
-
noNetwork: isolated,
|
|
27
|
-
hideHomeSecrets: isolated,
|
|
28
|
-
...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
29
|
-
...(opts.unshareCheck ? { unshareCheck: opts.unshareCheck } : {}),
|
|
30
|
-
});
|
|
31
104
|
const list = cmds.map((c) => String(c ?? '').trim());
|
|
32
105
|
// Validation plans may reference repo-relative scripts that do not exist in this checkout; skip only those.
|
|
33
106
|
const skipped = [];
|
|
34
107
|
const runnable = [];
|
|
35
108
|
for (const cmd of list) {
|
|
36
109
|
const script = validationScriptPath(cmd);
|
|
37
|
-
if (script && !isAbsolute(script) && !existsSync(resolve(
|
|
110
|
+
if (script && !isAbsolute(script) && !existsSync(resolve(validationCwd, script))) {
|
|
38
111
|
skipped.push({ cmd, script, reason: 'missing_script' });
|
|
39
112
|
console.warn(`[Validation] Skipping validation command (script not in repoRoot): ${script}`);
|
|
40
113
|
continue;
|
|
@@ -43,8 +116,31 @@ export async function runSandboxedValidation(cmds, cwd, opts = {}) {
|
|
|
43
116
|
}
|
|
44
117
|
if (runnable.length === 0)
|
|
45
118
|
return { passed: false, score: 0.2, results: [], skipped, isolated };
|
|
119
|
+
const scratch = isolated && opts.requireIsolation
|
|
120
|
+
? mkdtempSync('/var/tmp/evolver-validation-')
|
|
121
|
+
: undefined;
|
|
122
|
+
const runner = makeSandboxRunner({
|
|
123
|
+
cwd: validationCwd,
|
|
124
|
+
noNetwork: isolated,
|
|
125
|
+
hideHomeSecrets: isolated,
|
|
126
|
+
readOnlyFilesystem: isolated && opts.requireIsolation,
|
|
127
|
+
resourceLimits: isolated && opts.requireIsolation,
|
|
128
|
+
writableTmpDir: scratch,
|
|
129
|
+
readOnlyRoot,
|
|
130
|
+
...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
131
|
+
...(opts.resourceGroupFactory ? { resourceGroupFactory: opts.resourceGroupFactory } : {}),
|
|
132
|
+
unshareCheck: () => isolated,
|
|
133
|
+
});
|
|
46
134
|
const allowlist = [...new Set(runnable.map((c) => c.split(/\s+/)[0] ?? '').filter(Boolean))];
|
|
47
|
-
|
|
135
|
+
let results;
|
|
136
|
+
let passed;
|
|
137
|
+
try {
|
|
138
|
+
({ results, passed } = await runValidation({ commands: runnable.map((cmd) => ({ cmd })), allowlist }, runner));
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
if (scratch)
|
|
142
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
143
|
+
}
|
|
48
144
|
const complete = skipped.length === 0;
|
|
49
145
|
return {
|
|
50
146
|
passed: passed && complete,
|
|
@@ -53,4 +149,8 @@ export async function runSandboxedValidation(cmds, cwd, opts = {}) {
|
|
|
53
149
|
skipped,
|
|
54
150
|
isolated,
|
|
55
151
|
};
|
|
152
|
+
}
|
|
153
|
+
function pathIsWithin(root, target) {
|
|
154
|
+
const rel = relative(root, target);
|
|
155
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
56
156
|
}
|