leerness 1.36.183 → 1.36.185
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 +8 -0
- package/LICENSE +21 -21
- package/README.ko.md +15 -8
- package/README.md +25 -7
- package/SECURITY.md +66 -66
- package/bin/leerness.js +1184 -206
- package/docs/role-agent-routing-v2.md +411 -0
- package/lib/agents.js +1236 -100
- package/lib/diagnostics.js +184 -184
- package/lib/file-leases.js +845 -0
- package/lib/health.js +350 -350
- package/lib/io.js +152 -1
- package/lib/mcp-tools.js +121 -104
- package/lib/pure-utils.js +52 -4
- package/lib/referee.js +270 -270
- package/lib/review-request.js +339 -339
- package/lib/role-agent-schema.js +1179 -0
- package/lib/role-catalog.js +31 -1
- package/lib/role-fallback.js +1611 -0
- package/lib/role-store.js +730 -0
- package/lib/routing.js +109 -21
- package/lib/session-close.js +49 -1
- package/package.json +10 -5
- package/scripts/command-flags-probe.js +40 -3
- package/scripts/e2e.js +121 -44
- package/scripts/file-lease-probe.js +1139 -0
- package/scripts/i18n-next-cluster-probe.js +436 -5
- package/scripts/installed-cleanroom-probe.js +39 -12
- package/scripts/lint.js +73 -73
- package/scripts/provider-capacity-probe.js +425 -0
- package/scripts/release-runtime-probe.js +16 -1
- package/scripts/role-agent-schema-probe.js +834 -0
- package/scripts/role-fallback-probe.js +2010 -0
- package/scripts/role-store-loader-probe.js +882 -0
package/scripts/lint.js
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
// scripts/lint.js — 0-deps 경량 lint 게이트 (1.36.64, 외부감사 F-10: lint/typecheck 부재)
|
|
2
|
-
// 범위: 배포되는 모든 .js 의 ① node --check 구문 ② 위험 문자(U+2028/U+2029 — 과거 raw U+2028 정규식 리터럴이
|
|
3
|
-
// 전 명령 SyntaxError 를 일으킨 실사고 재발 차단) ③ BOM ④ 저장소 .json 파싱 유효성.
|
|
4
|
-
// 빠르고(수 초) CI 친화(exit 1). npm run lint / test:fast 에 배선.
|
|
5
|
-
'use strict';
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const cp = require('child_process');
|
|
9
|
-
|
|
10
|
-
const ROOT = path.resolve(process.argv[2] || path.join(__dirname, '..')); // 테스트용 루트 오버라이드 허용
|
|
11
|
-
// 1.36.72 (8차 헌트 F16): 존재하지 않는/빈 루트가 "0 js clean" 거짓 PASS 로 통과하던 것 — 명시 오류
|
|
12
|
-
if (!fs.existsSync(ROOT) || !fs.statSync(ROOT).isDirectory()) {
|
|
13
|
-
console.log(`✗ lint: root not found — ${ROOT}`);
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
const TARGET_DIRS = ['bin', 'lib', 'scripts'];
|
|
17
|
-
const failures = [];
|
|
18
|
-
|
|
19
|
-
function listFiles(dir, ext) {
|
|
20
|
-
const out = [];
|
|
21
|
-
const walk = (d) => {
|
|
22
|
-
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
23
|
-
if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
|
|
24
|
-
const p = path.join(d, e.name);
|
|
25
|
-
if (e.isDirectory()) walk(p);
|
|
26
|
-
else if (e.name.endsWith(ext)) out.push(p);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
if (fs.existsSync(dir)) walk(dir);
|
|
30
|
-
return out;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const jsFiles = TARGET_DIRS.flatMap(d => listFiles(path.join(ROOT, d), '.js'));
|
|
34
|
-
// 1.36.72 (F16): 대상 0건도 거짓 PASS — lint 가 아무것도 검사하지 않았다는 뜻이므로 실패
|
|
35
|
-
if (!jsFiles.length) {
|
|
36
|
-
console.log(`✗ lint: no js files found under ${TARGET_DIRS.join('/')} in ${ROOT} — nothing was checked`);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ① 구문 (node --check)
|
|
41
|
-
for (const f of jsFiles) {
|
|
42
|
-
const r = cp.spawnSync(process.execPath, ['--check', f], { encoding: 'utf8', timeout: 30000 });
|
|
43
|
-
if (r.error) { failures.push(`syntax-check spawn failed: ${path.relative(ROOT, f)} — ${r.error.code || r.error.message}`); continue; } // (검수 #3) 스폰 실패를 구문오류와 구분
|
|
44
|
-
if (r.status !== 0) {
|
|
45
|
-
const lines = (r.stderr || '').split('\n');
|
|
46
|
-
const detail = lines.find(l => /Error/.test(l)) || lines[0] || ''; // (검수 #3) SyntaxError 본문 우선
|
|
47
|
-
failures.push(`syntax: ${path.relative(ROOT, f)} — ${detail.trim()}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ② 위험 문자 + ③ BOM
|
|
52
|
-
for (const f of jsFiles) {
|
|
53
|
-
const buf = fs.readFileSync(f);
|
|
54
|
-
if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) failures.push(`bom: ${path.relative(ROOT, f)}`);
|
|
55
|
-
const text = buf.toString('utf8');
|
|
56
|
-
const ls = text.indexOf('\u2028'); const ps = text.indexOf('\u2029'); // 이스케이프 표기 — 자기참조 트랩 방지
|
|
57
|
-
if (ls >= 0) failures.push(`U+2028 line-separator: ${path.relative(ROOT, f)}:${text.slice(0, ls).split('\n').length} (과거 전-명령 SyntaxError 실사고 문자)`);
|
|
58
|
-
if (ps >= 0) failures.push(`U+2029 paragraph-separator: ${path.relative(ROOT, f)}:${text.slice(0, ps).split('\n').length}`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ④ 저장소 JSON 유효성 (배포 관련 루트 + data 성격 파일)
|
|
62
|
-
const jsonFiles = ['package.json', ...listFiles(path.join(ROOT, 'lib'), '.json'), ...listFiles(path.join(ROOT, 'scripts'), '.json')].map(f => path.isAbsolute(f) ? f : path.join(ROOT, f));
|
|
63
|
-
for (const f of jsonFiles) {
|
|
64
|
-
if (!fs.existsSync(f)) continue;
|
|
65
|
-
try { JSON.parse(fs.readFileSync(f, 'utf8').replace(/^/, '')); } catch (e) { failures.push(`json: ${path.relative(ROOT, f)} — ${e.message.slice(0, 80)}`); }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (failures.length) {
|
|
69
|
-
console.log(`✗ lint: ${failures.length} issue(s)`);
|
|
70
|
-
failures.forEach(x => console.log(' - ' + x));
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
console.log(`✓ lint: ${jsFiles.length} js (syntax/U+2028/BOM) + ${jsonFiles.length} json — clean`);
|
|
1
|
+
// scripts/lint.js — 0-deps 경량 lint 게이트 (1.36.64, 외부감사 F-10: lint/typecheck 부재)
|
|
2
|
+
// 범위: 배포되는 모든 .js 의 ① node --check 구문 ② 위험 문자(U+2028/U+2029 — 과거 raw U+2028 정규식 리터럴이
|
|
3
|
+
// 전 명령 SyntaxError 를 일으킨 실사고 재발 차단) ③ BOM ④ 저장소 .json 파싱 유효성.
|
|
4
|
+
// 빠르고(수 초) CI 친화(exit 1). npm run lint / test:fast 에 배선.
|
|
5
|
+
'use strict';
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const cp = require('child_process');
|
|
9
|
+
|
|
10
|
+
const ROOT = path.resolve(process.argv[2] || path.join(__dirname, '..')); // 테스트용 루트 오버라이드 허용
|
|
11
|
+
// 1.36.72 (8차 헌트 F16): 존재하지 않는/빈 루트가 "0 js clean" 거짓 PASS 로 통과하던 것 — 명시 오류
|
|
12
|
+
if (!fs.existsSync(ROOT) || !fs.statSync(ROOT).isDirectory()) {
|
|
13
|
+
console.log(`✗ lint: root not found — ${ROOT}`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const TARGET_DIRS = ['bin', 'lib', 'scripts'];
|
|
17
|
+
const failures = [];
|
|
18
|
+
|
|
19
|
+
function listFiles(dir, ext) {
|
|
20
|
+
const out = [];
|
|
21
|
+
const walk = (d) => {
|
|
22
|
+
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
23
|
+
if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
|
|
24
|
+
const p = path.join(d, e.name);
|
|
25
|
+
if (e.isDirectory()) walk(p);
|
|
26
|
+
else if (e.name.endsWith(ext)) out.push(p);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
if (fs.existsSync(dir)) walk(dir);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const jsFiles = TARGET_DIRS.flatMap(d => listFiles(path.join(ROOT, d), '.js'));
|
|
34
|
+
// 1.36.72 (F16): 대상 0건도 거짓 PASS — lint 가 아무것도 검사하지 않았다는 뜻이므로 실패
|
|
35
|
+
if (!jsFiles.length) {
|
|
36
|
+
console.log(`✗ lint: no js files found under ${TARGET_DIRS.join('/')} in ${ROOT} — nothing was checked`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ① 구문 (node --check)
|
|
41
|
+
for (const f of jsFiles) {
|
|
42
|
+
const r = cp.spawnSync(process.execPath, ['--check', f], { encoding: 'utf8', timeout: 30000 });
|
|
43
|
+
if (r.error) { failures.push(`syntax-check spawn failed: ${path.relative(ROOT, f)} — ${r.error.code || r.error.message}`); continue; } // (검수 #3) 스폰 실패를 구문오류와 구분
|
|
44
|
+
if (r.status !== 0) {
|
|
45
|
+
const lines = (r.stderr || '').split('\n');
|
|
46
|
+
const detail = lines.find(l => /Error/.test(l)) || lines[0] || ''; // (검수 #3) SyntaxError 본문 우선
|
|
47
|
+
failures.push(`syntax: ${path.relative(ROOT, f)} — ${detail.trim()}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ② 위험 문자 + ③ BOM
|
|
52
|
+
for (const f of jsFiles) {
|
|
53
|
+
const buf = fs.readFileSync(f);
|
|
54
|
+
if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) failures.push(`bom: ${path.relative(ROOT, f)}`);
|
|
55
|
+
const text = buf.toString('utf8');
|
|
56
|
+
const ls = text.indexOf('\u2028'); const ps = text.indexOf('\u2029'); // 이스케이프 표기 — 자기참조 트랩 방지
|
|
57
|
+
if (ls >= 0) failures.push(`U+2028 line-separator: ${path.relative(ROOT, f)}:${text.slice(0, ls).split('\n').length} (과거 전-명령 SyntaxError 실사고 문자)`);
|
|
58
|
+
if (ps >= 0) failures.push(`U+2029 paragraph-separator: ${path.relative(ROOT, f)}:${text.slice(0, ps).split('\n').length}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ④ 저장소 JSON 유효성 (배포 관련 루트 + data 성격 파일)
|
|
62
|
+
const jsonFiles = ['package.json', ...listFiles(path.join(ROOT, 'lib'), '.json'), ...listFiles(path.join(ROOT, 'scripts'), '.json')].map(f => path.isAbsolute(f) ? f : path.join(ROOT, f));
|
|
63
|
+
for (const f of jsonFiles) {
|
|
64
|
+
if (!fs.existsSync(f)) continue;
|
|
65
|
+
try { JSON.parse(fs.readFileSync(f, 'utf8').replace(/^/, '')); } catch (e) { failures.push(`json: ${path.relative(ROOT, f)} — ${e.message.slice(0, 80)}`); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (failures.length) {
|
|
69
|
+
console.log(`✗ lint: ${failures.length} issue(s)`);
|
|
70
|
+
failures.forEach(x => console.log(' - ' + x));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
console.log(`✓ lint: ${jsFiles.length} js (syntax/U+2028/BOM) + ${jsonFiles.length} json — clean`);
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Provider-capacity observation must never turn installation, enablement, or generic
|
|
5
|
+
// authentication into a model-entitlement or remaining-quota claim. All provider
|
|
6
|
+
// executables here are local fakes. They record every invocation and whether a secret
|
|
7
|
+
// loaded from the project .env reached the child environment.
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const cp = require('child_process');
|
|
13
|
+
|
|
14
|
+
const CLI = path.resolve(__dirname, '..', 'bin', 'leerness.js');
|
|
15
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-provider-capacity-'));
|
|
16
|
+
let failed = 0;
|
|
17
|
+
let total = 0;
|
|
18
|
+
|
|
19
|
+
const BUILTINS = [
|
|
20
|
+
['claude', 'claude', 'LEERNESS_ENABLE_CLAUDE'],
|
|
21
|
+
['codex', 'codex', 'LEERNESS_ENABLE_CODEX'],
|
|
22
|
+
['agy', 'agy', 'LEERNESS_ENABLE_AGY'],
|
|
23
|
+
['grok', 'grok', 'LEERNESS_ENABLE_GROK'],
|
|
24
|
+
['opencode', 'opencode', 'LEERNESS_ENABLE_OPENCODE'],
|
|
25
|
+
['qwen', 'qwen', 'LEERNESS_ENABLE_QWEN'],
|
|
26
|
+
['aider', 'aider', 'LEERNESS_ENABLE_AIDER'],
|
|
27
|
+
['goose', 'goose', 'LEERNESS_ENABLE_GOOSE'],
|
|
28
|
+
['copilot', 'gh', 'LEERNESS_ENABLE_COPILOT'],
|
|
29
|
+
['ollama', 'ollama', 'LEERNESS_ENABLE_OLLAMA'],
|
|
30
|
+
];
|
|
31
|
+
const SECRET_SENTINEL = 'secret-never-reaches-provider-9e12ac74';
|
|
32
|
+
const PRIVATE_ACCOUNT = 'private-account@example.invalid';
|
|
33
|
+
const PRIVATE_DISPLAY_NAME = 'Jane Q Public';
|
|
34
|
+
const PRIVATE_ORGANIZATION = 'Secret Research Lab';
|
|
35
|
+
const CREDENTIAL_ENV_KEYS = ['OPENAI_API_KEY', 'AWS_ACCESS_KEY_ID', 'AWS_PROFILE', 'LEERNESS_NPM_OTP'];
|
|
36
|
+
const DISGUISED_CREDENTIAL_ENV_KEYS = [
|
|
37
|
+
'LEERNESS_\uff34\uff2f\uff2b\uff25\uff2e',
|
|
38
|
+
'LEERNESS_TO\u200bKEN',
|
|
39
|
+
'NPM_CONFIG_\uff21\uff30\uff29\uff3f\uff2b\uff25\uff39',
|
|
40
|
+
];
|
|
41
|
+
const PROBE_CREDENTIAL_ENV_KEYS = [...CREDENTIAL_ENV_KEYS, ...DISGUISED_CREDENTIAL_ENV_KEYS];
|
|
42
|
+
const OUTPUT_SECRET = 'sk-provider-output-secret-9e12ac74';
|
|
43
|
+
const FULLWIDTH_OUTPUT_SECRET = 'sk-fullwidth-output-secret-9e12ac74';
|
|
44
|
+
|
|
45
|
+
function check(label, condition, detail = '') {
|
|
46
|
+
total++;
|
|
47
|
+
const ok = !!condition;
|
|
48
|
+
process.stdout.write(`${ok ? '✓' : '✗'} ${label}${!ok && detail ? `\n ${detail}` : ''}\n`);
|
|
49
|
+
if (!ok) failed++;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function appendSource(file, text) {
|
|
53
|
+
return `fs.appendFileSync(${JSON.stringify(file)}, ${JSON.stringify(text)});`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function writeWindowsNodeShim(dir, name, source) {
|
|
57
|
+
const entry = `${name}.js`;
|
|
58
|
+
fs.writeFileSync(path.join(dir, entry), source, 'utf8');
|
|
59
|
+
// Narrow npm-style shim accepted by portable-process without cmd.exe execution.
|
|
60
|
+
fs.writeFileSync(path.join(dir, `${name}.cmd`), [
|
|
61
|
+
'@ECHO off',
|
|
62
|
+
`"%_prog%" "%dp0%\\${entry}" %*`,
|
|
63
|
+
'',
|
|
64
|
+
].join('\r\n'), 'utf8');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function writeNodeExecutable(dir, name, source) {
|
|
68
|
+
if (process.platform === 'win32') {
|
|
69
|
+
writeWindowsNodeShim(dir, name, source);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const file = path.join(dir, name);
|
|
73
|
+
fs.writeFileSync(file, `#!/usr/bin/env node\n${source}`, 'utf8');
|
|
74
|
+
fs.chmodSync(file, 0o755);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function genericSource(name, commandMarker, secretMarker) {
|
|
78
|
+
const versionOutput = name === 'agy'
|
|
79
|
+
? `\u001b[31mOPENAI_API_KEY=${OUTPUT_SECRET} \uff26\uff35\uff2c\uff2c\uff37\uff29\uff24\uff34\uff28\uff3f\uff21\uff30\uff29\uff3f\uff2b\uff25\uff39=${FULLWIDTH_OUTPUT_SECRET}\u001b[0m\n`
|
|
80
|
+
: name === 'probeai'
|
|
81
|
+
? `probeai-cli 1.0.0 organization=${PRIVATE_ORGANIZATION}\n`
|
|
82
|
+
: `${name}-cli 1.0.0\n`;
|
|
83
|
+
return [
|
|
84
|
+
"'use strict';",
|
|
85
|
+
"const fs = require('fs');",
|
|
86
|
+
appendSource(commandMarker, `${name}:`),
|
|
87
|
+
`fs.appendFileSync(${JSON.stringify(commandMarker)}, process.argv.slice(2).join(' ') + '\\n');`,
|
|
88
|
+
`if (${JSON.stringify(PROBE_CREDENTIAL_ENV_KEYS)}.some(key => process.env[key])) ${appendSource(secretMarker, `${name}:credential-env-seen\n`)}`,
|
|
89
|
+
`process.stdout.write(${JSON.stringify(versionOutput)});`,
|
|
90
|
+
'',
|
|
91
|
+
].join('\n');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function codexSource(commandMarker, authMarker, secretMarker) {
|
|
95
|
+
return [
|
|
96
|
+
"'use strict';",
|
|
97
|
+
"const fs = require('fs');",
|
|
98
|
+
"const args = process.argv.slice(2);",
|
|
99
|
+
appendSource(commandMarker, 'codex:'),
|
|
100
|
+
`fs.appendFileSync(${JSON.stringify(commandMarker)}, args.join(' ') + '\\n');`,
|
|
101
|
+
`if (${JSON.stringify(PROBE_CREDENTIAL_ENV_KEYS)}.some(key => process.env[key])) ${appendSource(secretMarker, 'codex:credential-env-seen\n')}`,
|
|
102
|
+
"if (args[0] === 'login' && args[1] === 'status') {",
|
|
103
|
+
` ${appendSource(authMarker, 'auth-check\n')}`,
|
|
104
|
+
` process.stdout.write(${JSON.stringify(`Logged in as ${PRIVATE_DISPLAY_NAME} (${PRIVATE_ACCOUNT})\n`)});`,
|
|
105
|
+
' process.exit(0);',
|
|
106
|
+
'}',
|
|
107
|
+
"process.stdout.write('codex-cli 1.0.0\\n');",
|
|
108
|
+
'',
|
|
109
|
+
].join('\n');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function envFile(enabledIds) {
|
|
113
|
+
const enabled = new Set(enabledIds);
|
|
114
|
+
return [
|
|
115
|
+
...BUILTINS.map(([id, , flag]) => `${flag}=${enabled.has(id) ? '1' : '0'}`),
|
|
116
|
+
`LEERNESS_ENABLE_PROBEAI=${enabled.has('probeai') ? '1' : '0'}`,
|
|
117
|
+
`OPENAI_API_KEY=${SECRET_SENTINEL}-openai`,
|
|
118
|
+
`AWS_ACCESS_KEY_ID=${SECRET_SENTINEL}-aws-id`,
|
|
119
|
+
`AWS_PROFILE=${SECRET_SENTINEL}-profile`,
|
|
120
|
+
`LEERNESS_NPM_OTP=${SECRET_SENTINEL}-otp`,
|
|
121
|
+
'',
|
|
122
|
+
].join('\n');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isolatedEnv(binDir) {
|
|
126
|
+
const env = { ...process.env };
|
|
127
|
+
for (const key of Object.keys(env)) {
|
|
128
|
+
if (/^LEERNESS_ENABLE_/i.test(key) || key.toLowerCase() === 'path'
|
|
129
|
+
|| CREDENTIAL_ENV_KEYS.some(name => name.toLowerCase() === key.toLowerCase())) delete env[key];
|
|
130
|
+
}
|
|
131
|
+
const runtime = path.dirname(process.execPath);
|
|
132
|
+
if (process.platform === 'win32') {
|
|
133
|
+
const systemRoot = env.SystemRoot || env.SYSTEMROOT || 'C:\\Windows';
|
|
134
|
+
env.Path = [binDir, runtime, path.join(systemRoot, 'System32')].join(path.delimiter);
|
|
135
|
+
env.PATHEXT = '.COM;.EXE;.BAT;.CMD';
|
|
136
|
+
} else {
|
|
137
|
+
env.PATH = [binDir, runtime, '/usr/bin', '/bin'].join(path.delimiter);
|
|
138
|
+
}
|
|
139
|
+
env.LEERNESS_OFFLINE = '1';
|
|
140
|
+
env.LEERNESS_NO_PROMPT = '1';
|
|
141
|
+
env.LEERNESS_NO_AUTOCHCP = '1';
|
|
142
|
+
for (const key of DISGUISED_CREDENTIAL_ENV_KEYS) env[key] = `${SECRET_SENTINEL}-${key.length}`;
|
|
143
|
+
return env;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function runCapacity(env, extraArgs = []) {
|
|
147
|
+
return cp.spawnSync(process.execPath,
|
|
148
|
+
[CLI, 'agents', 'quota', '--path', root, '--json', ...extraArgs], {
|
|
149
|
+
cwd: root,
|
|
150
|
+
env,
|
|
151
|
+
encoding: 'utf8',
|
|
152
|
+
timeout: 120000,
|
|
153
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function runCli(env, args) {
|
|
158
|
+
return cp.spawnSync(process.execPath, [CLI, ...args, '--path', root, '--json'], {
|
|
159
|
+
cwd: root, env, encoding: 'utf8', timeout: 120000, maxBuffer: 8 * 1024 * 1024,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function parse(result) {
|
|
164
|
+
try { return JSON.parse(result.stdout); } catch { return null; }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function lines(file) {
|
|
168
|
+
try { return fs.readFileSync(file, 'utf8').split(/\r?\n/).filter(Boolean); }
|
|
169
|
+
catch { return []; }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function countPrefix(file, prefix) {
|
|
173
|
+
return lines(file).filter(line => line.startsWith(prefix)).length;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const binDir = path.join(root, 'bin');
|
|
178
|
+
const stateDir = path.join(root, '.leerness');
|
|
179
|
+
const commandMarker = path.join(root, 'provider-commands.log');
|
|
180
|
+
const authMarker = path.join(root, 'auth-checks.log');
|
|
181
|
+
const secretMarker = path.join(root, 'secret-seen.log');
|
|
182
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
183
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
184
|
+
|
|
185
|
+
for (const [, bin] of BUILTINS) {
|
|
186
|
+
if (bin !== 'codex') writeNodeExecutable(binDir, bin, genericSource(bin, commandMarker, secretMarker));
|
|
187
|
+
}
|
|
188
|
+
writeNodeExecutable(binDir, 'codex', codexSource(commandMarker, authMarker, secretMarker));
|
|
189
|
+
writeNodeExecutable(binDir, 'probeai', genericSource('probeai', commandMarker, secretMarker));
|
|
190
|
+
|
|
191
|
+
fs.writeFileSync(path.join(stateDir, 'providers.json'), JSON.stringify({
|
|
192
|
+
schemaVersion: 1,
|
|
193
|
+
providers: [{
|
|
194
|
+
id: 'ProbeAI',
|
|
195
|
+
bin: 'probeai',
|
|
196
|
+
envFlag: 'LEERNESS_ENABLE_PROBEAI',
|
|
197
|
+
versionArgs: ['--version'],
|
|
198
|
+
desc: 'Probe provider',
|
|
199
|
+
}],
|
|
200
|
+
}, null, 2) + '\n', 'utf8');
|
|
201
|
+
|
|
202
|
+
const allIds = [...BUILTINS.map(([id]) => id), 'probeai'];
|
|
203
|
+
fs.writeFileSync(path.join(root, '.env'), envFile(allIds), 'utf8');
|
|
204
|
+
const env = isolatedEnv(binDir);
|
|
205
|
+
const resultKo = runCapacity(env);
|
|
206
|
+
const resultEn = runCapacity(env, ['--language', 'en']);
|
|
207
|
+
const dataKo = parse(resultKo);
|
|
208
|
+
const dataEn = parse(resultEn);
|
|
209
|
+
|
|
210
|
+
check('capacity command exits successfully in both locales',
|
|
211
|
+
resultKo.status === 0 && resultEn.status === 0,
|
|
212
|
+
`ko=${resultKo.status} en=${resultEn.status} stderr=${String(resultKo.stderr || resultEn.stderr || '').slice(0, 500)}`);
|
|
213
|
+
check('capacity command returns versioned observation JSON',
|
|
214
|
+
!!(dataKo && dataKo.schemaVersion === 2 && dataKo.observation === 'provider-capacity'),
|
|
215
|
+
String(resultKo.stdout || '').slice(0, 500));
|
|
216
|
+
check('canonical JSON is locale-independent',
|
|
217
|
+
!!(dataKo && dataEn && JSON.stringify(dataKo) === JSON.stringify(dataEn)),
|
|
218
|
+
`ko=${String(resultKo.stdout || '').slice(0, 500)} en=${String(resultEn.stdout || '').slice(0, 500)}`);
|
|
219
|
+
|
|
220
|
+
const rows = dataKo && Array.isArray(dataKo.quota) ? dataKo.quota : [];
|
|
221
|
+
const agy = rows.find(row => row.id === 'agy');
|
|
222
|
+
const codex = rows.find(row => row.id === 'codex');
|
|
223
|
+
const custom = rows.find(row => row.id === 'probeai');
|
|
224
|
+
check('all configured providers are represented', rows.length === allIds.length && !!custom,
|
|
225
|
+
`ids=${JSON.stringify(rows.map(row => row.id))}`);
|
|
226
|
+
check('installed and enabled remain separate observable facts',
|
|
227
|
+
!!(agy && agy.installed === true && agy.enabled === true && agy.status === 'ready'
|
|
228
|
+
&& agy.versionCheckAttempted === true && agy.versionCheckState === 'ok'
|
|
229
|
+
&& agy.presenceCheckOnly === false),
|
|
230
|
+
JSON.stringify(agy));
|
|
231
|
+
check('safe auth status is recorded without account identity',
|
|
232
|
+
!!(codex && codex.auth === 'ok' && codex.authCheckAttempted === true
|
|
233
|
+
&& codex.authEvidencePresent === true && codex.authSource === 'codex login status'
|
|
234
|
+
&& !Object.prototype.hasOwnProperty.call(codex, 'authEvidence')),
|
|
235
|
+
JSON.stringify(codex));
|
|
236
|
+
check('confirmed authentication permits local routing without proving model callability',
|
|
237
|
+
!!(codex && codex.routingEligibility === 'eligible'
|
|
238
|
+
&& codex.routingReason === 'local_prerequisites_met'
|
|
239
|
+
&& codex.modelCallability === 'not-observed'
|
|
240
|
+
&& codex.callability === 'unknown'
|
|
241
|
+
&& codex.callabilityReason === 'live_model_call_not_performed'),
|
|
242
|
+
JSON.stringify(codex));
|
|
243
|
+
check('unknown authentication is explicitly unverified, not eligible',
|
|
244
|
+
!!(agy && agy.auth === 'unknown' && agy.authCheckAttempted === false
|
|
245
|
+
&& agy.routingEligibility === 'unverified'
|
|
246
|
+
&& agy.routingReason === 'authentication_not_observed'
|
|
247
|
+
&& agy.modelCallability === 'not-observed'),
|
|
248
|
+
JSON.stringify(agy));
|
|
249
|
+
check('remaining capacity stays unobserved even after authentication',
|
|
250
|
+
!!(codex && codex.quota === 'unknown' && codex.quotaState === 'not-observed'
|
|
251
|
+
&& codex.remaining === null && codex.unit === null && codex.resetAt === null
|
|
252
|
+
&& codex.quotaSource === null),
|
|
253
|
+
JSON.stringify(codex));
|
|
254
|
+
check('custom provider carries its registry source', custom && custom.source === 'user', JSON.stringify(custom));
|
|
255
|
+
check('case-preserving legacy provider IDs collapse to one canonical provider identity',
|
|
256
|
+
custom && custom.id === 'probeai' && rows.filter(row => row.id === 'probeai').length === 1,
|
|
257
|
+
JSON.stringify(rows.map(row => ({ id: row.id, source: row.source }))));
|
|
258
|
+
|
|
259
|
+
const serialized = JSON.stringify(dataKo || {});
|
|
260
|
+
check('credential values and account identifiers are absent from output',
|
|
261
|
+
!serialized.includes(SECRET_SENTINEL) && !serialized.includes(PRIVATE_ACCOUNT)
|
|
262
|
+
&& !serialized.includes(PRIVATE_DISPLAY_NAME) && !serialized.includes(PRIVATE_ORGANIZATION)
|
|
263
|
+
&& !serialized.includes(OUTPUT_SECRET) && !serialized.includes(FULLWIDTH_OUTPUT_SECRET),
|
|
264
|
+
serialized.slice(0, 1000));
|
|
265
|
+
check('provider version output removes terminal escapes and redacts normalized credential labels',
|
|
266
|
+
!serialized.includes('\\u001b') && !serialized.includes('\u001b')
|
|
267
|
+
&& agy && !String(agy.version || '').includes(OUTPUT_SECRET)
|
|
268
|
+
&& !String(agy.version || '').includes(FULLWIDTH_OUTPUT_SECRET),
|
|
269
|
+
JSON.stringify(agy));
|
|
270
|
+
check('project credential values are not passed to provider subprocesses', lines(secretMarker).length === 0,
|
|
271
|
+
JSON.stringify(lines(secretMarker)));
|
|
272
|
+
check('observation policy describes actual credential and execution boundaries',
|
|
273
|
+
!!(dataKo && dataKo.policy
|
|
274
|
+
&& dataKo.policy.projectEnvironmentMayBeLoaded === true
|
|
275
|
+
&& dataKo.policy.credentialValuesIncludedInOutput === false
|
|
276
|
+
&& dataKo.policy.credentialValuesPersistedByCommand === false
|
|
277
|
+
&& dataKo.policy.credentialValuesInspectedForCapacity === false
|
|
278
|
+
&& dataKo.policy.credentialValuesPassedToProbeCommands === false
|
|
279
|
+
&& dataKo.policy.providerCredentialStoresReadDirectly === false
|
|
280
|
+
&& dataKo.policy.providerCliMayReadOwnCredentialStore === true
|
|
281
|
+
&& dataKo.policy.providerCliMayReadEnvironmentCredentials === false
|
|
282
|
+
&& dataKo.policy.disabledProviderCommandsExecuted === false
|
|
283
|
+
&& JSON.stringify(dataKo.policy.registeredSafeAuthChecksAttempted) === JSON.stringify(['codex'])
|
|
284
|
+
&& dataKo.policy.versionChecksAttempted.length === allIds.length
|
|
285
|
+
&& dataKo.policy.presenceOnlyChecks.length === 0
|
|
286
|
+
&& dataKo.policy.browserSessionReuse === false
|
|
287
|
+
&& dataKo.policy.guiScraping === false
|
|
288
|
+
&& dataKo.policy.liveModelCalls === false
|
|
289
|
+
&& dataKo.policy.capacityValuesRequireOfficialContract === true
|
|
290
|
+
&& dataKo.policy.speculativeCapacityClaims === false),
|
|
291
|
+
JSON.stringify(dataKo && dataKo.policy));
|
|
292
|
+
check('limitations are stable machine-readable codes',
|
|
293
|
+
!!(dataKo && Array.isArray(dataKo.limitations)
|
|
294
|
+
&& dataKo.limitations.join(',') === [
|
|
295
|
+
'local_prerequisites_only',
|
|
296
|
+
'authentication_is_not_model_entitlement',
|
|
297
|
+
'exact_capacity_requires_verified_official_adapter',
|
|
298
|
+
'recorded_availability_is_not_official_capacity',
|
|
299
|
+
'disabled_providers_presence_only',
|
|
300
|
+
'credential_values_not_passed_to_probe_commands',
|
|
301
|
+
].join(',')),
|
|
302
|
+
JSON.stringify(dataKo && dataKo.limitations));
|
|
303
|
+
|
|
304
|
+
let observationWrite = runCli(env, ['agents', 'availability', 'mark', 'codex', '--quota-state', 'exhausted', '--rate-limited', '--reason', 'quota-exhausted', '--ttl-min', '60']);
|
|
305
|
+
let observedCapacity = parse(runCapacity(env));
|
|
306
|
+
let observedCodex = observedCapacity && observedCapacity.quota.find(row => row.id === 'codex');
|
|
307
|
+
check('effective exhausted/rate-limited observations block capacity routing and preserve legacy aliases',
|
|
308
|
+
observationWrite.status === 0 && observedCodex
|
|
309
|
+
&& observedCodex.routingEligibility === 'blocked'
|
|
310
|
+
&& ['quota_exhausted', 'rate_limited'].includes(observedCodex.routingReason)
|
|
311
|
+
&& observedCodex.quotaState === 'observed-exhausted'
|
|
312
|
+
&& observedCodex.quota === 'observed-exhausted'
|
|
313
|
+
&& typeof observedCodex.hint === 'string' && observedCodex.hint.length > 0,
|
|
314
|
+
JSON.stringify(observedCodex));
|
|
315
|
+
observationWrite = runCli(env, ['agents', 'availability', 'mark', 'codex', '--quota-state', 'available', '--not-rate-limited', '--reason', 'capacity-restored', '--ttl-min', '60']);
|
|
316
|
+
observedCapacity = parse(runCapacity(env));
|
|
317
|
+
observedCodex = observedCapacity && observedCapacity.quota.find(row => row.id === 'codex');
|
|
318
|
+
check('newest same-scope availability axes supersede stale quota and rate-limit values',
|
|
319
|
+
observationWrite.status === 0 && observedCodex
|
|
320
|
+
&& observedCodex.routingEligibility === 'eligible'
|
|
321
|
+
&& observedCodex.quotaState === 'observed-available'
|
|
322
|
+
&& observedCodex.quota === 'observed-available',
|
|
323
|
+
JSON.stringify(observedCodex));
|
|
324
|
+
runCli(env, ['agents', 'availability', 'clear', 'codex']);
|
|
325
|
+
check('enabled runs execute only the expected fake authentication adapter',
|
|
326
|
+
lines(authMarker).length === 4 && lines(authMarker).every(line => line === 'auth-check'),
|
|
327
|
+
`auth=${JSON.stringify(lines(authMarker))}`);
|
|
328
|
+
|
|
329
|
+
// Disable only Codex: neither its version command nor auth command may execute.
|
|
330
|
+
fs.writeFileSync(path.join(root, '.env'), envFile(allIds.filter(id => id !== 'codex')), 'utf8');
|
|
331
|
+
const codexCommandsBefore = countPrefix(commandMarker, 'codex:');
|
|
332
|
+
const authBefore = lines(authMarker).length;
|
|
333
|
+
const disabledResult = runCapacity(isolatedEnv(binDir));
|
|
334
|
+
const disabledData = parse(disabledResult);
|
|
335
|
+
const disabledCodex = disabledData && disabledData.quota.find(row => row.id === 'codex');
|
|
336
|
+
check('disabled provider is presence-checked without executing version or auth commands',
|
|
337
|
+
disabledResult.status === 0
|
|
338
|
+
&& countPrefix(commandMarker, 'codex:') === codexCommandsBefore
|
|
339
|
+
&& lines(authMarker).length === authBefore
|
|
340
|
+
&& disabledCodex && disabledCodex.installed === true
|
|
341
|
+
&& disabledCodex.version === null
|
|
342
|
+
&& disabledCodex.versionCheckAttempted === false
|
|
343
|
+
&& disabledCodex.versionCheckState === 'not-attempted'
|
|
344
|
+
&& disabledCodex.presenceCheckOnly === true
|
|
345
|
+
&& disabledCodex.authCheckAttempted === false
|
|
346
|
+
&& disabledCodex.auth === 'unknown'
|
|
347
|
+
&& disabledCodex.routingEligibility === 'blocked'
|
|
348
|
+
&& disabledCodex.routingReason === 'provider_disabled'
|
|
349
|
+
&& disabledData.policy.providerCliMayReadOwnCredentialStore === true
|
|
350
|
+
&& disabledData.policy.disabledProviderCommandsExecuted === false
|
|
351
|
+
&& disabledData.policy.registeredSafeAuthChecksAttempted.length === 0
|
|
352
|
+
&& disabledData.policy.presenceOnlyChecks.includes('codex'),
|
|
353
|
+
`commands=${JSON.stringify(lines(commandMarker))} row=${JSON.stringify(disabledCodex)} policy=${JSON.stringify(disabledData && disabledData.policy)}`);
|
|
354
|
+
|
|
355
|
+
// A path merely ending in a built-in filename is a different executable
|
|
356
|
+
// authority. It may be version-probed when explicitly enabled, but must not
|
|
357
|
+
// inherit the built-in's privileged authentication subcommand.
|
|
358
|
+
const spoofDir = path.join(root, 'tools');
|
|
359
|
+
fs.mkdirSync(spoofDir, { recursive: true });
|
|
360
|
+
writeNodeExecutable(spoofDir, 'codex', codexSource(commandMarker, authMarker, secretMarker));
|
|
361
|
+
const spoofBin = process.platform === 'win32' ? 'tools\\codex.cmd' : 'tools/codex';
|
|
362
|
+
fs.writeFileSync(path.join(stateDir, 'providers.json'), JSON.stringify({
|
|
363
|
+
schemaVersion: 1,
|
|
364
|
+
providers: [{ id: 'codex', bin: spoofBin, envFlag: 'LEERNESS_ENABLE_CODEX', versionArgs: ['--version'], desc: 'path spoof' }],
|
|
365
|
+
}, null, 2) + '\n', 'utf8');
|
|
366
|
+
fs.writeFileSync(path.join(root, '.env'), envFile(['codex']), 'utf8');
|
|
367
|
+
const spoofAuthBefore = lines(authMarker).length;
|
|
368
|
+
const spoofResult = runCapacity(isolatedEnv(binDir));
|
|
369
|
+
const spoofData = parse(spoofResult);
|
|
370
|
+
const spoofCodex = spoofData && spoofData.quota.find(row => row.id === 'codex');
|
|
371
|
+
check('a workspace path ending in a built-in name cannot inherit that built-in authentication check',
|
|
372
|
+
spoofResult.status === 0
|
|
373
|
+
&& spoofCodex && spoofCodex.versionCheckAttempted === true
|
|
374
|
+
&& spoofCodex.authCheckAttempted === false && spoofCodex.auth === 'unknown'
|
|
375
|
+
&& lines(authMarker).length === spoofAuthBefore
|
|
376
|
+
&& !spoofData.policy.registeredSafeAuthChecksAttempted.includes('codex'),
|
|
377
|
+
`row=${JSON.stringify(spoofCodex)} policy=${JSON.stringify(spoofData && spoofData.policy)} auth=${JSON.stringify(lines(authMarker))}`);
|
|
378
|
+
|
|
379
|
+
fs.writeFileSync(path.join(stateDir, 'providers.json'), JSON.stringify({
|
|
380
|
+
schemaVersion: 1,
|
|
381
|
+
providers: [{
|
|
382
|
+
id: 'ProbeAI', bin: 'probeai', envFlag: 'LEERNESS_ENABLE_PROBEAI', versionArgs: ['--version'], desc: 'Probe provider',
|
|
383
|
+
}],
|
|
384
|
+
}, null, 2) + '\n', 'utf8');
|
|
385
|
+
|
|
386
|
+
// Disable every provider: the entire command must be observation-only with no provider process spawn.
|
|
387
|
+
fs.writeFileSync(path.join(root, '.env'), envFile([]), 'utf8');
|
|
388
|
+
const commandCountBeforeAllDisabled = lines(commandMarker).length;
|
|
389
|
+
const allDisabledResult = runCapacity(isolatedEnv(binDir));
|
|
390
|
+
const allDisabledData = parse(allDisabledResult);
|
|
391
|
+
check('all-disabled run executes zero provider commands and reports presence-only checks',
|
|
392
|
+
allDisabledResult.status === 0
|
|
393
|
+
&& lines(commandMarker).length === commandCountBeforeAllDisabled
|
|
394
|
+
&& allDisabledData.policy.versionChecksAttempted.length === 0
|
|
395
|
+
&& allDisabledData.policy.providerCliMayReadOwnCredentialStore === false
|
|
396
|
+
&& allDisabledData.policy.presenceOnlyChecks.length === allIds.length
|
|
397
|
+
&& allDisabledData.quota.every(row => row.enabled === false
|
|
398
|
+
&& row.versionCheckAttempted === false && row.versionCheckState === 'not-attempted'
|
|
399
|
+
&& row.presenceCheckOnly === true),
|
|
400
|
+
`before=${commandCountBeforeAllDisabled} after=${lines(commandMarker).length} policy=${JSON.stringify(allDisabledData && allDisabledData.policy)}`);
|
|
401
|
+
|
|
402
|
+
const speculative = /60 req\/min|1000 req\/day|unlimited|무제한|"quota":"rate-limited"|"quota":"subscription"/i
|
|
403
|
+
.test(JSON.stringify(dataKo || {}));
|
|
404
|
+
check('no speculative quota claim is present', !speculative,
|
|
405
|
+
speculative ? 'speculative quota claim present' : '');
|
|
406
|
+
|
|
407
|
+
const { canonicalProviderIdentity } = require('../lib/role-fallback');
|
|
408
|
+
const nativeMaxProvider = `p${'a'.repeat(63)}`;
|
|
409
|
+
const overNativeProvider = `p${'a'.repeat(64)}`;
|
|
410
|
+
const registryBeforeOverlongAdd = fs.readFileSync(path.join(stateDir, 'providers.json'), 'utf8');
|
|
411
|
+
const overlongAdd = runCli(isolatedEnv(binDir), ['provider', 'add', overNativeProvider, '--bin', 'probeai']);
|
|
412
|
+
check('new provider IDs obey the native-v2 64-character ceiling in canonicalization and provider add',
|
|
413
|
+
canonicalProviderIdentity(nativeMaxProvider) === nativeMaxProvider
|
|
414
|
+
&& canonicalProviderIdentity(overNativeProvider) === null
|
|
415
|
+
&& overlongAdd.status === 1
|
|
416
|
+
&& fs.readFileSync(path.join(stateDir, 'providers.json'), 'utf8') === registryBeforeOverlongAdd,
|
|
417
|
+
`exit=${overlongAdd.status} stdout=${overlongAdd.stdout} stderr=${overlongAdd.stderr}`);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
check('probe completed without exception', false, error && error.stack ? error.stack : String(error));
|
|
420
|
+
} finally {
|
|
421
|
+
try { fs.rmSync(root, { recursive: true, force: true }); } catch {}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
process.stdout.write(`Provider capacity probe: ${total - failed}/${total} passed\n`);
|
|
425
|
+
process.exitCode = failed ? 1 : 0;
|
|
@@ -458,8 +458,14 @@ try {
|
|
|
458
458
|
' process.stdout.write(JSON.stringify(rows));',
|
|
459
459
|
'})().catch(error => { process.stderr.write(error.stack || error.message); process.exit(1); });',
|
|
460
460
|
].join('\n');
|
|
461
|
+
const benchMatrixWorkspaceLedger = path.join(root, '.leerness', 'execution-ledger.jsonl');
|
|
462
|
+
const benchMatrixWorkspaceLedgerBefore = fs.existsSync(benchMatrixWorkspaceLedger)
|
|
463
|
+
? fs.readFileSync(benchMatrixWorkspaceLedger) : null;
|
|
461
464
|
const benchMatrixChild = cp.spawnSync(process.execPath, ['--trace-deprecation', '-e', benchMatrixChildScript], {
|
|
462
|
-
|
|
465
|
+
// The bench path now writes execution provenance. Run the synthetic matrix in
|
|
466
|
+
// the probe sandbox so a release check never appends fake model calls to the
|
|
467
|
+
// developer's real workspace ledger.
|
|
468
|
+
cwd: sandbox, encoding: 'utf8', timeout: 60000,
|
|
463
469
|
});
|
|
464
470
|
let benchMatrix = null;
|
|
465
471
|
try { benchMatrix = JSON.parse(String(benchMatrixChild.stdout || '')); } catch {}
|
|
@@ -477,6 +483,15 @@ try {
|
|
|
477
483
|
}
|
|
478
484
|
}
|
|
479
485
|
}
|
|
486
|
+
const benchMatrixWorkspaceLedgerAfter = fs.existsSync(benchMatrixWorkspaceLedger)
|
|
487
|
+
? fs.readFileSync(benchMatrixWorkspaceLedger) : null;
|
|
488
|
+
const benchMatrixPollutedWorkspace = benchMatrixWorkspaceLedgerBefore === null
|
|
489
|
+
? benchMatrixWorkspaceLedgerAfter !== null
|
|
490
|
+
: benchMatrixWorkspaceLedgerAfter === null
|
|
491
|
+
|| !benchMatrixWorkspaceLedgerBefore.equals(benchMatrixWorkspaceLedgerAfter);
|
|
492
|
+
if (benchMatrixPollutedWorkspace) {
|
|
493
|
+
failures.push('agents bench synthetic matrix polluted the real workspace execution ledger');
|
|
494
|
+
}
|
|
480
495
|
|
|
481
496
|
if (process.platform === 'win32') {
|
|
482
497
|
const { resolvePortableLaunch, spawnPortableSync } = require(portableProcessPath);
|