harness-everything 0.20.6 → 0.21.0
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +20 -0
- package/benchmarks/fixtures/system-one-routing.json +549 -0
- package/benchmarks/results/system-one/cua-s1-forms-cpu-2026-09-23/README.md +62 -0
- package/benchmarks/results/system-one/cua-s1-forms-cpu-2026-09-23/report.json +856 -0
- package/ci/fixtures/system-one-provider.cjs +13 -0
- package/ci/mechanism-2ao-plugin-sync.test.js +23 -0
- package/ci/mechanism-system-one-contract.test.js +48 -0
- package/ci/mechanism-system-one-evaluation.test.js +87 -0
- package/ci/mechanism-system-one-install.test.js +95 -0
- package/ci/mechanism-system-one-provider.test.js +63 -0
- package/ci/mechanism-system-one-router.test.js +101 -0
- package/ci/system-one-adapter-test.py +56 -0
- package/docs/platform-capabilities.md +11 -0
- package/docs/system-one-routing.md +202 -0
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/system-one/contract.js +45 -0
- package/harness-everything/scripts/system-one/cua_adapter.py +94 -0
- package/harness-everything/scripts/system-one/evaluate.js +98 -0
- package/harness-everything/scripts/system-one/install.js +152 -0
- package/harness-everything/scripts/system-one/provider.js +76 -0
- package/harness-everything/scripts/system-one/router.js +37 -0
- package/harness-everything/scripts/tier-router.js +16 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +2 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/contract.js +45 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/cua_adapter.py +94 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/evaluate.js +98 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/install.js +152 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/provider.js +76 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/router.js +37 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +16 -0
- package/sbom.cdx.json +7 -7
- package/scripts/evaluate-system-one.js +57 -0
- package/scripts/plugin-sync.js +9 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
const { validateRequest } = require('./contract');
|
|
6
|
+
// Upstream cua_s1 source revision documented in docs/system-one-routing.md.
|
|
7
|
+
const PINNED_CUA_S1_REVISION = 'b7f7e2d8714609853a29c7d049140bc46aec0954';
|
|
8
|
+
const ADAPTER = path.join(__dirname, 'cua_adapter.py');
|
|
9
|
+
const unavailable = reason => ({ status: 'unavailable', reason });
|
|
10
|
+
const exact = (value, keys) => value && typeof value === 'object' && !Array.isArray(value)
|
|
11
|
+
&& Object.keys(value).length === keys.length && keys.every(k => Object.hasOwn(value, k));
|
|
12
|
+
const label = value => typeof value === 'string' && value.length > 0 && value.length <= 128 && !/[\u0000-\u001f]/.test(value);
|
|
13
|
+
function validateManifest(m) {
|
|
14
|
+
const required = ['schemaVersion', 'python', 'checkpoint', 'weightsSha256', 'configSha256', 'modelId', 'revision', 'domain'];
|
|
15
|
+
if (!m || typeof m !== 'object' || Array.isArray(m) || required.some(k => !Object.hasOwn(m, k))
|
|
16
|
+
|| Object.keys(m).some(k => ![...required, 'timeoutMs'].includes(k)) || m.schemaVersion !== 1
|
|
17
|
+
|| ['python', 'modelId', 'revision', 'domain'].some(k => typeof m[k] !== 'string' || !m[k].trim() || m[k].length > 4096 || m[k].includes('\0'))
|
|
18
|
+
|| typeof m.checkpoint !== 'string' || !path.isAbsolute(m.checkpoint) || path.extname(m.checkpoint) !== '.safetensors'
|
|
19
|
+
|| ['weightsSha256', 'configSha256'].some(k => typeof m[k] !== 'string' || !/^[a-f0-9]{64}$/.test(m[k]))
|
|
20
|
+
|| (m.timeoutMs !== undefined && (!Number.isInteger(m.timeoutMs) || m.timeoutMs < 1 || m.timeoutMs > 10000))
|
|
21
|
+
|| (m.modelId === 'cua-ai/cua-s1-forms' && m.domain !== 'forms-v1')) throw new TypeError('invalid-manifest');
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
function readManifest(manifestPath) {
|
|
25
|
+
if (typeof manifestPath !== 'string' || !path.isAbsolute(manifestPath) || fs.statSync(manifestPath).size > 65536) throw new Error();
|
|
26
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
27
|
+
validateManifest(manifest);
|
|
28
|
+
return manifest;
|
|
29
|
+
}
|
|
30
|
+
function spawnJson(executable, args, input, timeoutMs) {
|
|
31
|
+
let result;
|
|
32
|
+
try {
|
|
33
|
+
result = spawnSync(executable, args, { input, encoding: 'utf8', shell: false,
|
|
34
|
+
timeout: timeoutMs, killSignal: 'SIGKILL', maxBuffer: 1024 * 1024, windowsHide: true });
|
|
35
|
+
} catch (_) { return unavailable('provider-unavailable'); }
|
|
36
|
+
if (result.error) return unavailable(result.error.code === 'ETIMEDOUT' ? 'provider-timeout'
|
|
37
|
+
: result.error.code === 'ENOBUFS' ? 'provider-output-limit' : 'provider-unavailable');
|
|
38
|
+
if (result.status !== 0) return unavailable('provider-exit');
|
|
39
|
+
try { return { status: 'ok', value: JSON.parse(result.stdout) }; }
|
|
40
|
+
catch (_) { return unavailable('provider-json'); }
|
|
41
|
+
}
|
|
42
|
+
function runProvider(request, executable, args, timeoutMs = 2000) {
|
|
43
|
+
validateRequest(request);
|
|
44
|
+
const result = spawnJson(executable, args, JSON.stringify(request), timeoutMs);
|
|
45
|
+
return result.status === 'ok' ? { status: 'scored', response: result.value } : result;
|
|
46
|
+
}
|
|
47
|
+
function score(request, manifestPath) {
|
|
48
|
+
validateRequest(request);
|
|
49
|
+
let manifest;
|
|
50
|
+
try { manifest = readManifest(manifestPath); } catch (_) { return unavailable('provider-config'); }
|
|
51
|
+
// Send the exact validated manifest to the child on stdin: no config reread race.
|
|
52
|
+
return runProvider({ ...request }, manifest.python, [ADAPTER, JSON.stringify(manifest)], manifest.timeoutMs || 2000);
|
|
53
|
+
}
|
|
54
|
+
// The probe reports what is installed; only a PEP 610 VCS commit counts as a source revision.
|
|
55
|
+
function validateProvenance(p) {
|
|
56
|
+
if (!exact(p, ['schemaVersion', 'python', 'cuaS1', 'torch']) || p.schemaVersion !== 1
|
|
57
|
+
|| !exact(p.python, ['implementation', 'version']) || !label(p.python.implementation) || !label(p.python.version)
|
|
58
|
+
|| !exact(p.cuaS1, ['distribution', 'version', 'sourceRevision'])
|
|
59
|
+
|| ![p.cuaS1.distribution, p.cuaS1.version, p.torch].every(v => v === null || label(v))
|
|
60
|
+
|| (p.cuaS1.sourceRevision !== null && !(typeof p.cuaS1.sourceRevision === 'string' && /^[0-9a-f]{40}$/.test(p.cuaS1.sourceRevision)))) {
|
|
61
|
+
throw new TypeError('invalid-provenance');
|
|
62
|
+
}
|
|
63
|
+
const revision = p.cuaS1.sourceRevision;
|
|
64
|
+
return { python: { ...p.python }, torch: p.torch, cuaS1: { ...p.cuaS1, pinnedRevision: PINNED_CUA_S1_REVISION,
|
|
65
|
+
sourceRevisionStatus: revision === null ? 'unavailable' : revision === PINNED_CUA_S1_REVISION ? 'pinned' : 'mismatch' } };
|
|
66
|
+
}
|
|
67
|
+
function provenance(manifestPath) {
|
|
68
|
+
let manifest;
|
|
69
|
+
try { manifest = readManifest(manifestPath); } catch (_) { return unavailable('provider-config'); }
|
|
70
|
+
// Metadata scans can exceed the per-score budget; use the manifest cap. No model is loaded.
|
|
71
|
+
const result = spawnJson(manifest.python, [ADAPTER, '--provenance'], '', 10000);
|
|
72
|
+
if (result.status !== 'ok') return result;
|
|
73
|
+
try { return { status: 'recorded', provenance: validateProvenance(result.value) }; }
|
|
74
|
+
catch (_) { return unavailable('provider-json'); }
|
|
75
|
+
}
|
|
76
|
+
module.exports = { score, runProvider, validateManifest, provenance, validateProvenance, PINNED_CUA_S1_REVISION };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { createRequest, decide } = require('./contract');
|
|
3
|
+
const provider = require('./provider');
|
|
4
|
+
const TIER_OPTIONS = Object.freeze([
|
|
5
|
+
{ id: 'tier1', text: 'A small bounded edit or operation with clear scope.' },
|
|
6
|
+
{ id: 'tier2', text: 'Implementation or bug fix needing iterative testing.' },
|
|
7
|
+
{ id: 'tier3', text: 'Architecture, repository-wide change or multi-stage engineering.' },
|
|
8
|
+
{ id: 'unclassified', text: 'Unclear intent, insufficient context or outside these categories.' },
|
|
9
|
+
].map(Object.freeze));
|
|
10
|
+
const LABELS = { tier1: 'Tier 1 (Trivial)', tier2: 'Tier 2 (Standard Task)', tier3: 'Tier 3 (Macro Task)' };
|
|
11
|
+
const RANK = { tier1: 1, tier2: 2, tier3: 3 };
|
|
12
|
+
// input.floor is the deterministic structural minimum; a scorer may raise the tier, never lower it below the floor.
|
|
13
|
+
function selectTier(input, env = process.env, scorer = provider.score) {
|
|
14
|
+
const mode = env.HARNESS_SYSTEM_ONE_MODE || 'off';
|
|
15
|
+
if (mode === 'off') return { tier: input.tier, diagnostic: null };
|
|
16
|
+
const fallback = (reason, status = 'unavailable') => ({ tier: input.tier, diagnostic: { mode: ['shadow', 'prefer'].includes(mode) ? mode : 'invalid', status, reason } });
|
|
17
|
+
if (!['shadow', 'prefer'].includes(mode)) return fallback('invalid-mode');
|
|
18
|
+
if (input.floor != null && !Object.hasOwn(RANK, input.floor)) return fallback('invalid-request');
|
|
19
|
+
let request;
|
|
20
|
+
try { request = createRequest('tier', input.prompt, TIER_OPTIONS); }
|
|
21
|
+
catch (_) { return fallback('invalid-request'); }
|
|
22
|
+
let result;
|
|
23
|
+
try { result = scorer(request, env.HARNESS_SYSTEM_ONE_CONFIG); }
|
|
24
|
+
catch (_) { return fallback('provider-unavailable'); }
|
|
25
|
+
if (!result || result.status !== 'scored') {
|
|
26
|
+
const allowed = ['provider-timeout', 'provider-output-limit', 'provider-exit', 'provider-json', 'provider-config', 'provider-unavailable'];
|
|
27
|
+
return fallback(allowed.includes(result?.reason) ? result.reason : 'provider-unavailable');
|
|
28
|
+
}
|
|
29
|
+
const decision = decide(request, result.response);
|
|
30
|
+
if (decision.selectedId === 'unclassified') Object.assign(decision, { selectedId: null, status: 'abstain', reason: 'unclassified' });
|
|
31
|
+
const accepted = decision.status === 'accepted';
|
|
32
|
+
const belowFloor = accepted && input.floor != null && RANK[decision.selectedId] < RANK[input.floor];
|
|
33
|
+
const applied = mode === 'prefer' && accepted && !input.explicit && !belowFloor;
|
|
34
|
+
const reason = input.explicit && accepted ? 'explicit-precedence' : belowFloor ? 'structural-floor' : decision.reason;
|
|
35
|
+
return { tier: applied ? LABELS[decision.selectedId] : input.tier, diagnostic: { mode, ...decision, applied, reason } };
|
|
36
|
+
}
|
|
37
|
+
module.exports = { selectTier, TIER_OPTIONS };
|
|
@@ -313,6 +313,19 @@ function run(userPrompt, context) {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
const { selectTier } = require('./system-one/router');
|
|
317
|
+
const structuralFloor = hasMacroSignal ? 'tier3'
|
|
318
|
+
: !isTrivialDocsEdit && hasMultipleTasks && hasMultipleSentences ? 'tier2' : null;
|
|
319
|
+
const semantic = selectTier({ prompt: userPrompt, tier: recommendedTier, floor: structuralFloor,
|
|
320
|
+
explicit: Boolean(detectExplicitStrategy(userPrompt, detectFableModel(userPrompt))) });
|
|
321
|
+
if (semantic.diagnostic) console.log(`\n=> SYSTEM ONE: ${JSON.stringify(semantic.diagnostic)}`);
|
|
322
|
+
if (semantic.diagnostic?.applied) {
|
|
323
|
+
recommendedTier = semantic.tier;
|
|
324
|
+
rationale = 'System One selected a tier from the fixed catalog; deterministic policy remains authoritative.';
|
|
325
|
+
reasonCodes.splice(loadedConfig.reasonCodes.length);
|
|
326
|
+
addReason(reasonCodes, 'system-one-tier');
|
|
327
|
+
}
|
|
328
|
+
|
|
316
329
|
console.log(`\n=> RECOMMENDED TIER: ${recommendedTier}`);
|
|
317
330
|
console.log(`=> RATIONALE: ${rationale}`);
|
|
318
331
|
|
|
@@ -447,6 +460,7 @@ function run(userPrompt, context) {
|
|
|
447
460
|
return contract;
|
|
448
461
|
}
|
|
449
462
|
|
|
463
|
+
if (require.main === module) {
|
|
450
464
|
let userPrompt = process.argv[2] || '';
|
|
451
465
|
let hookContext = null;
|
|
452
466
|
|
|
@@ -471,3 +485,5 @@ if (process.argv[2]) {
|
|
|
471
485
|
run(userPrompt, hookContext);
|
|
472
486
|
});
|
|
473
487
|
}
|
|
488
|
+
}
|
|
489
|
+
module.exports = { run };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-everything",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Harness OS: An Orchestrated Agent Operating System for AI-driven Development",
|
|
5
5
|
"main": "harness-everything/scripts/bootstrap.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"test:release": "node ci/release-consistency-check.js && node ci/mechanism-2x-release-automation.test.js && node ci/mechanism-2x-release-commit-message.test.js && node ci/mechanism-2ap-sbom.test.js",
|
|
24
24
|
"test:release:semantics": "node ci/semantic-release-analyzer.test.mjs",
|
|
25
25
|
"test:install": "node scripts/verify-install.js",
|
|
26
|
+
"system-one:install": "node harness-everything/scripts/system-one/install.js",
|
|
26
27
|
"test:collision": "node ci/description-collision.js",
|
|
27
28
|
"test:routing:skills": "node ci/skill-routing-check.js",
|
|
28
29
|
"test:routing": "node ci/routing-check.js",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "harness-everything",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.21.0",
|
|
5
5
|
"description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Miya Daniel",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { createHash } = require('node:crypto');
|
|
3
|
+
const hash = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
4
|
+
const exact = (value, keys) => value && typeof value === 'object' && !Array.isArray(value)
|
|
5
|
+
&& Object.keys(value).length === keys.length && keys.every(k => Object.hasOwn(value, k));
|
|
6
|
+
const text = (value, max = 4096) => typeof value === 'string' && value.trim().length > 0 && Buffer.byteLength(value, 'utf8') <= max;
|
|
7
|
+
const id = value => typeof value === 'string' && /^[a-z0-9._-]{1,80}$/.test(value);
|
|
8
|
+
const probability = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
9
|
+
function fail(code) { throw Object.assign(new TypeError(code), { code }); }
|
|
10
|
+
function createRequest(task, context, options) {
|
|
11
|
+
if (!id(task) || !text(context, 65536) || !Array.isArray(options) || options.length < 2 || options.length > 256
|
|
12
|
+
|| options.some(o => !exact(o, ['id', 'text']) || !id(o.id) || !text(o.text))
|
|
13
|
+
|| new Set(options.map(o => o.id)).size !== options.length) fail('invalid-request');
|
|
14
|
+
const copy = options.map(o => ({ id: o.id, text: o.text }));
|
|
15
|
+
const catalogHash = hash({ task, options: copy });
|
|
16
|
+
return { schemaVersion: 1, task, context, options: copy, catalogHash, requestHash: hash({ catalogHash, context }) };
|
|
17
|
+
}
|
|
18
|
+
function validateRequest(req) {
|
|
19
|
+
if (!exact(req, ['schemaVersion', 'task', 'context', 'options', 'catalogHash', 'requestHash']) || req.schemaVersion !== 1) fail('invalid-request');
|
|
20
|
+
const rebuilt = createRequest(req.task, req.context, req.options);
|
|
21
|
+
if (req.catalogHash !== rebuilt.catalogHash || req.requestHash !== rebuilt.requestHash) fail('invalid-request');
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
function decide(req, res, policy = {}) {
|
|
25
|
+
validateRequest(req);
|
|
26
|
+
if (!policy || typeof policy !== 'object' || Array.isArray(policy)
|
|
27
|
+
|| Object.keys(policy).some(k => !['domain', 'minConfidence', 'minMargin'].includes(k))) fail('invalid-policy');
|
|
28
|
+
const { domain = 'harness-routing-v1', minConfidence = 0.9, minMargin = 0.2 } = policy;
|
|
29
|
+
if (!text(domain) || !probability(minConfidence) || !probability(minMargin)) fail('invalid-policy');
|
|
30
|
+
const empty = { status: 'invalid-output', reason: 'invalid-response', selectedId: null, confidence: null, margin: null, model: null };
|
|
31
|
+
if (!exact(res, ['schemaVersion', 'requestHash', 'catalogHash', 'model', 'scores'])
|
|
32
|
+
|| res.schemaVersion !== 1 || res.requestHash !== req.requestHash || res.catalogHash !== req.catalogHash
|
|
33
|
+
|| !exact(res.model, ['id', 'revision', 'domain']) || !Object.values(res.model).every(v => text(v))
|
|
34
|
+
|| !Array.isArray(res.scores) || res.scores.length !== req.options.length
|
|
35
|
+
|| res.scores.some((s, i) => !exact(s, ['id', 'probability']) || s.id !== req.options[i].id || !probability(s.probability))
|
|
36
|
+
|| Math.abs(res.scores.reduce((s, o) => s + o.probability, 0) - 1) > 1e-6) return empty;
|
|
37
|
+
const ranked = [...res.scores].sort((a, b) => b.probability - a.probability);
|
|
38
|
+
const confidence = ranked[0].probability;
|
|
39
|
+
const margin = confidence - ranked[1].probability;
|
|
40
|
+
const reason = res.model.domain !== domain ? 'domain-mismatch' : margin === 0 ? 'tie'
|
|
41
|
+
: confidence < minConfidence ? 'low-confidence' : margin < minMargin ? 'low-margin' : 'scored';
|
|
42
|
+
return { status: reason === 'scored' ? 'accepted' : 'abstain', reason,
|
|
43
|
+
selectedId: reason === 'scored' ? ranked[0].id : null, confidence, margin, model: { ...res.model } };
|
|
44
|
+
}
|
|
45
|
+
module.exports = { createRequest, validateRequest, decide };
|
package/plugins/harness-everything/skills/harness-everything/scripts/system-one/cua_adapter.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Optional CPU CUA-S1 bridge. Stdout is protocol-only; never execute UI actions."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def verify_artifacts(manifest):
|
|
10
|
+
weights = Path(manifest['checkpoint'])
|
|
11
|
+
if not weights.is_absolute() or weights.suffix != '.safetensors':
|
|
12
|
+
raise ValueError('checkpoint-format')
|
|
13
|
+
for file, key in [(weights, 'weightsSha256'), (weights.with_suffix('.json'), 'configSha256')]:
|
|
14
|
+
with file.open('rb') as handle:
|
|
15
|
+
digest = hashlib.file_digest(handle, 'sha256').hexdigest()
|
|
16
|
+
if digest != manifest[key]:
|
|
17
|
+
raise ValueError('artifact-hash')
|
|
18
|
+
return weights
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def check_limits(request, config):
|
|
22
|
+
if len(request['context'].encode('utf-8')) > config['context_tokens'] or any(
|
|
23
|
+
len(option['text'].encode('utf-8')) > config['option_tokens'] for option in request['options']
|
|
24
|
+
):
|
|
25
|
+
raise ValueError('input-too-long')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def infer(request, manifest):
|
|
29
|
+
weights = verify_artifacts(manifest)
|
|
30
|
+
import torch
|
|
31
|
+
from cua_s1.model import ChoiceExample, load_checkpoint
|
|
32
|
+
|
|
33
|
+
torch.set_num_threads(1)
|
|
34
|
+
torch.use_deterministic_algorithms(True)
|
|
35
|
+
model, collator, config = load_checkpoint(weights, 'cpu')
|
|
36
|
+
check_limits(request, config)
|
|
37
|
+
example = ChoiceExample(context=request['context'], options=tuple(o['text'] for o in request['options']), label=0)
|
|
38
|
+
with torch.inference_mode():
|
|
39
|
+
probabilities = model(collator([example])).softmax(-1)[0].tolist()
|
|
40
|
+
return {
|
|
41
|
+
'schemaVersion': 1, 'requestHash': request['requestHash'], 'catalogHash': request['catalogHash'],
|
|
42
|
+
'model': {'id': manifest['modelId'], 'revision': manifest['revision'], 'domain': manifest['domain']},
|
|
43
|
+
'scores': [{'id': option['id'], 'probability': probability} for option, probability in zip(request['options'], probabilities, strict=True)],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def source_revision(direct_url_text):
|
|
48
|
+
"""Return the PEP 610 VCS commit recorded at install time, or None when unprovable."""
|
|
49
|
+
try:
|
|
50
|
+
commit = json.loads(direct_url_text)['vcs_info']['commit_id']
|
|
51
|
+
except Exception:
|
|
52
|
+
return None
|
|
53
|
+
return commit if isinstance(commit, str) and re.fullmatch(r'[0-9a-f]{40}', commit) else None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def provenance():
|
|
57
|
+
"""Installed runtime/source facts only; never imports torch or loads a checkpoint."""
|
|
58
|
+
import importlib.metadata as metadata
|
|
59
|
+
import platform
|
|
60
|
+
|
|
61
|
+
cua = {'distribution': None, 'version': None, 'sourceRevision': None}
|
|
62
|
+
names = sorted(set(metadata.packages_distributions().get('cua_s1', [])))
|
|
63
|
+
if len(names) == 1:
|
|
64
|
+
dist = metadata.distribution(names[0])
|
|
65
|
+
cua = {'distribution': names[0], 'version': dist.version, 'sourceRevision': source_revision(dist.read_text('direct_url.json'))}
|
|
66
|
+
try:
|
|
67
|
+
torch = metadata.version('torch')
|
|
68
|
+
except metadata.PackageNotFoundError:
|
|
69
|
+
torch = None
|
|
70
|
+
return {'schemaVersion': 1, 'python': {'implementation': platform.python_implementation(), 'version': platform.python_version()},
|
|
71
|
+
'cuaS1': cua, 'torch': torch}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def main():
|
|
75
|
+
try:
|
|
76
|
+
if sys.argv[1:] == ['--provenance']:
|
|
77
|
+
print(json.dumps(provenance(), allow_nan=False))
|
|
78
|
+
return 0
|
|
79
|
+
manifest = json.loads(sys.argv[1])
|
|
80
|
+
raw = sys.stdin.buffer.read(2 * 1024 * 1024 + 1)
|
|
81
|
+
if len(raw) > 2 * 1024 * 1024:
|
|
82
|
+
raise ValueError('request-limit')
|
|
83
|
+
request = json.loads(raw)
|
|
84
|
+
result = infer(request, manifest)
|
|
85
|
+
print(json.dumps(result, allow_nan=False))
|
|
86
|
+
except Exception:
|
|
87
|
+
# No raw prompt, selected values, artifact paths, or traceback on the wire.
|
|
88
|
+
print('cua-provider-unavailable', file=sys.stderr)
|
|
89
|
+
return 1
|
|
90
|
+
return 0
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == '__main__':
|
|
94
|
+
sys.exit(main())
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { validateRequest } = require('./contract');
|
|
3
|
+
const { TIER_OPTIONS } = require('./router');
|
|
4
|
+
const exact = (obj, keys) => obj && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === keys.length && keys.every(k => Object.hasOwn(obj, k));
|
|
5
|
+
const nonempty = v => typeof v === 'string' && v.trim().length > 0;
|
|
6
|
+
const labels = ['tier1', 'tier2', 'tier3', null];
|
|
7
|
+
const finite = v => typeof v === 'number' && Number.isFinite(v);
|
|
8
|
+
const fail = () => { throw new TypeError('invalid-evaluation-input'); };
|
|
9
|
+
function validateCorpus(corpus) {
|
|
10
|
+
if (!exact(corpus, ['schemaVersion', 'cases']) || corpus.schemaVersion !== 1 || !Array.isArray(corpus.cases) || !corpus.cases.length) fail();
|
|
11
|
+
const ids = new Set(); const hashes = new Set(); const families = new Map();
|
|
12
|
+
for (const c of corpus.cases) {
|
|
13
|
+
if (!exact(c, ['id', 'family', 'split', 'language', 'source', 'reviewed', 'request', 'gold'])
|
|
14
|
+
|| ![c.id, c.family, c.source].every(nonempty) || !['train', 'validation', 'holdout'].includes(c.split)
|
|
15
|
+
|| !['en', 'zh-TW'].includes(c.language) || typeof c.reviewed !== 'boolean' || !labels.includes(c.gold)) fail();
|
|
16
|
+
validateRequest(c.request);
|
|
17
|
+
if (c.request.task !== 'tier' || JSON.stringify(c.request.options) !== JSON.stringify(TIER_OPTIONS)
|
|
18
|
+
|| ids.has(c.id) || hashes.has(c.request.requestHash) || (families.has(c.family) && families.get(c.family) !== c.split)) fail();
|
|
19
|
+
ids.add(c.id); hashes.add(c.request.requestHash); families.set(c.family, c.split);
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function prediction(decision) {
|
|
24
|
+
if (exact(decision, ['status', 'reason']) && decision.status === 'unavailable' && nonempty(decision.reason)) return null;
|
|
25
|
+
if (!exact(decision, ['status', 'reason', 'selectedId', 'confidence', 'margin', 'model'])
|
|
26
|
+
|| !['accepted', 'abstain', 'invalid-output'].includes(decision.status) || !nonempty(decision.reason)
|
|
27
|
+
|| !labels.includes(decision.selectedId) || (decision.status === 'accepted') !== (decision.selectedId !== null)) fail();
|
|
28
|
+
for (const key of ['confidence', 'margin']) if (decision[key] !== null && (!finite(decision[key]) || decision[key] < 0 || decision[key] > 1)) fail();
|
|
29
|
+
if (decision.model !== null && (!exact(decision.model, ['id', 'revision', 'domain']) || !Object.values(decision.model).every(nonempty))) fail();
|
|
30
|
+
if (decision.status === 'accepted' && (decision.confidence === null || decision.margin === null || decision.model?.domain !== 'harness-routing-v1')) fail();
|
|
31
|
+
return decision.selectedId;
|
|
32
|
+
}
|
|
33
|
+
// Scored decisions carry the full catalog-order probability vector; unavailable/invalid outputs carry null.
|
|
34
|
+
function checkScores(decision, scores) {
|
|
35
|
+
const scored = ['accepted', 'abstain'].includes(decision.status);
|
|
36
|
+
if (!scored) { if (scores !== null) fail(); return; }
|
|
37
|
+
if (!Array.isArray(scores) || scores.length !== TIER_OPTIONS.length
|
|
38
|
+
|| scores.some(p => !finite(p) || p < 0 || p > 1) || Math.abs(scores.reduce((s, p) => s + p, 0) - 1) > 1e-6) fail();
|
|
39
|
+
const ranked = [...scores].sort((a, b) => b - a);
|
|
40
|
+
if (decision.confidence !== ranked[0] || decision.margin !== ranked[0] - ranked[1]) fail();
|
|
41
|
+
}
|
|
42
|
+
function metrics(gold, predicted) {
|
|
43
|
+
const count = gold.length;
|
|
44
|
+
const accepted = predicted.filter(x => x !== null).length;
|
|
45
|
+
const correct = predicted.filter((x, i) => x === gold[i]).length;
|
|
46
|
+
const acceptedCorrect = predicted.filter((x, i) => x !== null && x === gold[i]).length;
|
|
47
|
+
const macroF1 = labels.reduce((sum, label) => {
|
|
48
|
+
const tp = gold.filter((g, i) => g === label && predicted[i] === label).length;
|
|
49
|
+
const fp = gold.filter((g, i) => g !== label && predicted[i] === label).length;
|
|
50
|
+
const fn = gold.filter((g, i) => g === label && predicted[i] !== label).length;
|
|
51
|
+
return sum + (2 * tp + fp + fn ? 2 * tp / (2 * tp + fp + fn) : 0);
|
|
52
|
+
}, 0) / labels.length;
|
|
53
|
+
return { accuracy: correct / count, macroF1, coverage: accepted / count, abstentionRate: 1 - accepted / count,
|
|
54
|
+
acceptedPrecision: accepted ? acceptedCorrect / accepted : null,
|
|
55
|
+
acceptedErrorRate: accepted ? (accepted - acceptedCorrect) / accepted : null };
|
|
56
|
+
}
|
|
57
|
+
function p95(values) { return values.length ? [...values].sort((a, b) => a - b)[Math.ceil(values.length * 0.95) - 1] : null; }
|
|
58
|
+
// sourceEvidence is the provider provenance result; only the pinned cua_s1 revision satisfies the gate.
|
|
59
|
+
function evaluate(corpus, records, sourceEvidence = null) {
|
|
60
|
+
validateCorpus(corpus);
|
|
61
|
+
const cases = corpus.cases.filter(c => c.split === 'holdout');
|
|
62
|
+
if (!cases.length || !Array.isArray(records) || records.length !== cases.length) fail();
|
|
63
|
+
const byId = new Map(); const models = new Set(); const cold = []; const warm = [];
|
|
64
|
+
for (const record of records) {
|
|
65
|
+
if (!exact(record, ['id', 'baseline', 'runs']) || byId.has(record.id) || !cases.some(c => c.id === record.id)
|
|
66
|
+
|| !labels.includes(record.baseline) || !Array.isArray(record.runs) || record.runs.length !== 2) fail();
|
|
67
|
+
for (const run of record.runs) {
|
|
68
|
+
if (!exact(run, ['decision', 'scores', 'latencyMs', 'coldStart']) || !finite(run.latencyMs) || run.latencyMs < 0 || typeof run.coldStart !== 'boolean') fail();
|
|
69
|
+
prediction(run.decision);
|
|
70
|
+
checkScores(run.decision, run.scores);
|
|
71
|
+
if (run.decision.model) models.add(JSON.stringify([run.decision.model.id, run.decision.model.revision, run.decision.model.domain]));
|
|
72
|
+
(run.coldStart ? cold : warm).push(run.latencyMs);
|
|
73
|
+
}
|
|
74
|
+
byId.set(record.id, record);
|
|
75
|
+
}
|
|
76
|
+
if (models.size > 1) fail();
|
|
77
|
+
const gold = cases.map(c => c.gold);
|
|
78
|
+
const baseline = metrics(gold, cases.map(c => byId.get(c.id).baseline));
|
|
79
|
+
const model = metrics(gold, cases.map(c => prediction(byId.get(c.id).runs[0].decision)));
|
|
80
|
+
const canonicalRun = ({ decision: d, scores }) => JSON.stringify([d.status, d.reason, d.selectedId ?? null, d.confidence ?? null, d.margin ?? null,
|
|
81
|
+
d.model ? [d.model.id, d.model.revision, d.model.domain] : null, scores]);
|
|
82
|
+
const agreement = records.filter(r => canonicalRun(r.runs[0]) === canonicalRun(r.runs[1])).length / cases.length;
|
|
83
|
+
const latency = { coldP95Ms: p95(cold), warmP95Ms: p95(warm), coldSamples: cold.length, warmSamples: warm.length };
|
|
84
|
+
const gates = {
|
|
85
|
+
reviewedHoldout: cases.length >= 200 && cases.every(c => c.reviewed)
|
|
86
|
+
&& ['en', 'zh-TW'].every(lang => cases.filter(c => c.language === lang).length >= 50),
|
|
87
|
+
acceptedPrecision: model.acceptedPrecision !== null && model.acceptedPrecision >= 0.98,
|
|
88
|
+
coverage: model.coverage >= 0.8,
|
|
89
|
+
macroF1: model.macroF1 >= baseline.macroF1,
|
|
90
|
+
repeatability: agreement === 1,
|
|
91
|
+
warmLatency: warm.length >= cases.length && latency.warmP95Ms <= 100,
|
|
92
|
+
sourceProvenance: sourceEvidence?.status === 'recorded' && sourceEvidence.provenance?.cuaS1?.sourceRevisionStatus === 'pinned',
|
|
93
|
+
policyEvidence: false,
|
|
94
|
+
liveHostEvidence: false,
|
|
95
|
+
};
|
|
96
|
+
return { schemaVersion: 1, cases: cases.length, baseline, model, agreement, latency, gates, rolloutReady: Object.values(gates).every(Boolean) };
|
|
97
|
+
}
|
|
98
|
+
module.exports = { validateCorpus, evaluate };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// Opt-in System One installer: a dedicated venv, pinned cua_s1 source, and a pinned CPU checkpoint.
|
|
4
|
+
// It is never run from hooks, and it never changes the router mode; it prints the environment to set.
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const { createHash } = require('node:crypto');
|
|
9
|
+
const { spawnSync } = require('node:child_process');
|
|
10
|
+
const { performance } = require('node:perf_hooks');
|
|
11
|
+
const { validateManifest, provenance, score, PINNED_CUA_S1_REVISION } = require('./provider');
|
|
12
|
+
const { createRequest, decide } = require('./contract');
|
|
13
|
+
const { TIER_OPTIONS } = require('./router');
|
|
14
|
+
|
|
15
|
+
const PINS = Object.freeze({
|
|
16
|
+
cuaS1Revision: PINNED_CUA_S1_REVISION,
|
|
17
|
+
cuaS1Spec: `cua-s1 @ git+https://github.com/trycua/cua@${PINNED_CUA_S1_REVISION}#subdirectory=libs/cua-s1/python`,
|
|
18
|
+
torchSpec: 'torch>=2.2,<3',
|
|
19
|
+
torchIndex: 'https://download.pytorch.org/whl/cpu',
|
|
20
|
+
modelId: 'cua-ai/cua-s1-forms',
|
|
21
|
+
modelRevision: 'f54adbf447f4ca6ec259f529ee3f2e3e09f8cc71',
|
|
22
|
+
domain: 'forms-v1',
|
|
23
|
+
// sha256/size from the Hugging Face tree API at modelRevision (LFS oid for weights; the JSON
|
|
24
|
+
// sidecar's git blob id d8f8426a... was re-hashed to confirm these exact CRLF bytes).
|
|
25
|
+
files: Object.freeze({
|
|
26
|
+
weights: Object.freeze({ name: 'cua-s1-forms.safetensors', size: 2828784, sha256: '05954c1caf51c2fb6c13ea4acbfc88a2e7653dea192252bb51dc89e76a356ddc' }),
|
|
27
|
+
config: Object.freeze({ name: 'cua-s1-forms.json', size: 1053, sha256: '62d31e2f9a001a8e9b6f8534c5194d07ebdd3f9d62ef1ac281906622992650ca' }),
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
const USAGE = 'Usage: install.js [--dir <absolute dir>] [--python <3.11-3.13 executable>] [--dry-run]';
|
|
31
|
+
|
|
32
|
+
const defaultDir = () => path.join(os.homedir(), '.agents', 'harness-everything', 'system-one');
|
|
33
|
+
const fileUrl = name => `https://huggingface.co/${PINS.modelId}/resolve/${PINS.modelRevision}/${name}`;
|
|
34
|
+
const checkpointDir = dir => path.join(dir, 'checkpoints', PINS.modelRevision);
|
|
35
|
+
const venvPython = (dir, platform = process.platform) => (platform === 'win32'
|
|
36
|
+
? path.join(dir, 'venv', 'Scripts', 'python.exe') : path.join(dir, 'venv', 'bin', 'python'));
|
|
37
|
+
function supportedPython(version) {
|
|
38
|
+
const match = typeof version === 'string' && /^3\.(\d+)\.\d+$/.exec(version);
|
|
39
|
+
return Boolean(match) && Number(match[1]) >= 11 && Number(match[1]) <= 13;
|
|
40
|
+
}
|
|
41
|
+
function pythonCandidates(platform = process.platform) {
|
|
42
|
+
return platform === 'win32'
|
|
43
|
+
? [['py', '-3.13'], ['py', '-3.12'], ['py', '-3.11'], ['python'], ['python3']]
|
|
44
|
+
: [['python3.13'], ['python3.12'], ['python3.11'], ['python3'], ['python']];
|
|
45
|
+
}
|
|
46
|
+
function parseArgs(argv) {
|
|
47
|
+
const opts = { dir: defaultDir(), python: null, dryRun: false };
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
for (let i = 0; i < argv.length; i++) {
|
|
50
|
+
const flag = argv[i];
|
|
51
|
+
if (seen.has(flag)) throw new Error('usage');
|
|
52
|
+
seen.add(flag);
|
|
53
|
+
if (flag === '--dry-run') opts.dryRun = true;
|
|
54
|
+
else if (flag === '--dir' || flag === '--python') {
|
|
55
|
+
const value = argv[++i];
|
|
56
|
+
if (!value || value.startsWith('--')) throw new Error('usage');
|
|
57
|
+
if (flag === '--dir' && !path.isAbsolute(value)) throw new Error('usage');
|
|
58
|
+
opts[flag.slice(2)] = flag === '--dir' ? path.resolve(value) : value;
|
|
59
|
+
} else throw new Error('usage');
|
|
60
|
+
}
|
|
61
|
+
return opts;
|
|
62
|
+
}
|
|
63
|
+
function buildManifest(dir, python) {
|
|
64
|
+
const manifest = { schemaVersion: 1, python, checkpoint: path.join(checkpointDir(dir), PINS.files.weights.name),
|
|
65
|
+
weightsSha256: PINS.files.weights.sha256, configSha256: PINS.files.config.sha256,
|
|
66
|
+
modelId: PINS.modelId, revision: PINS.modelRevision, domain: PINS.domain,
|
|
67
|
+
// Each score starts a fresh Python process that imports torch; cold start dominates latency.
|
|
68
|
+
timeoutMs: 10000 };
|
|
69
|
+
validateManifest(manifest);
|
|
70
|
+
return manifest;
|
|
71
|
+
}
|
|
72
|
+
const matches = (buf, spec) => buf.length === spec.size && createHash('sha256').update(buf).digest('hex') === spec.sha256;
|
|
73
|
+
async function httpFetch(url) {
|
|
74
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
75
|
+
if (!res.ok) throw new Error(`download-failed:${res.status}`);
|
|
76
|
+
return Buffer.from(await res.arrayBuffer());
|
|
77
|
+
}
|
|
78
|
+
async function ensureFile(url, dest, spec, fetcher = httpFetch) {
|
|
79
|
+
if (fs.existsSync(dest) && matches(fs.readFileSync(dest), spec)) return 'verified-existing';
|
|
80
|
+
const bytes = await fetcher(url);
|
|
81
|
+
if (!Buffer.isBuffer(bytes) || !matches(bytes, spec)) throw new Error(`download-verification:${spec.name}`);
|
|
82
|
+
const tmp = `${dest}.${process.pid}.tmp`;
|
|
83
|
+
fs.writeFileSync(tmp, bytes);
|
|
84
|
+
fs.renameSync(tmp, dest);
|
|
85
|
+
return 'downloaded';
|
|
86
|
+
}
|
|
87
|
+
function exec(cmd, args, capture = false) {
|
|
88
|
+
const result = spawnSync(cmd, args, { shell: false, windowsHide: true, encoding: 'utf8', stdio: capture ? 'pipe' : 'inherit' });
|
|
89
|
+
if (result.error || result.status !== 0) throw new Error(`command-failed: ${[cmd, ...args].join(' ')}`);
|
|
90
|
+
return capture ? result.stdout.trim() : '';
|
|
91
|
+
}
|
|
92
|
+
const VERSION_PROBE = ['-c', 'import sys; print("%d.%d.%d" % sys.version_info[:3])'];
|
|
93
|
+
function findPython(explicit) {
|
|
94
|
+
for (const [cmd, ...pre] of explicit ? [[explicit]] : pythonCandidates()) {
|
|
95
|
+
const result = spawnSync(cmd, [...pre, ...VERSION_PROBE], { shell: false, windowsHide: true, encoding: 'utf8' });
|
|
96
|
+
const version = result.status === 0 ? result.stdout.trim() : null;
|
|
97
|
+
if (supportedPython(version)) return { cmd, pre, version };
|
|
98
|
+
}
|
|
99
|
+
throw new Error(explicit ? 'python-unsupported: need Python 3.11-3.13' : 'python-not-found: install Python 3.11-3.13 or pass --python');
|
|
100
|
+
}
|
|
101
|
+
function plan(opts) {
|
|
102
|
+
const venvPy = venvPython(opts.dir);
|
|
103
|
+
const manifestPath = path.join(opts.dir, 'manifest.json');
|
|
104
|
+
return { dir: opts.dir, manifest: manifestPath,
|
|
105
|
+
steps: [
|
|
106
|
+
`create venv ${path.join(opts.dir, 'venv')} with ${opts.python || 'first Python 3.11-3.13 found'}`,
|
|
107
|
+
`${venvPy} -m pip install --index-url ${PINS.torchIndex} "${PINS.torchSpec}"`,
|
|
108
|
+
`${venvPy} -m pip install "${PINS.cuaS1Spec}"`,
|
|
109
|
+
...Object.values(PINS.files).map(f => `download ${fileUrl(f.name)} (sha256 ${f.sha256})`),
|
|
110
|
+
`write ${manifestPath}`,
|
|
111
|
+
'verify provenance (pinned revision) and one real CPU inference',
|
|
112
|
+
],
|
|
113
|
+
env: { HARNESS_SYSTEM_ONE_MODE: 'shadow', HARNESS_SYSTEM_ONE_CONFIG: manifestPath } };
|
|
114
|
+
}
|
|
115
|
+
function verify(manifestPath) {
|
|
116
|
+
const source = provenance(manifestPath);
|
|
117
|
+
const request = createRequest('tier', 'fix a typo in README', TIER_OPTIONS);
|
|
118
|
+
const start = performance.now();
|
|
119
|
+
const result = score(request, manifestPath);
|
|
120
|
+
const latencyMs = Math.round(performance.now() - start);
|
|
121
|
+
const decision = result.status === 'scored' ? decide(request, result.response) : result;
|
|
122
|
+
const scores = result.status === 'scored' && decision.status !== 'invalid-output' ? result.response.scores : null;
|
|
123
|
+
const ok = source.status === 'recorded' && source.provenance.cuaS1.sourceRevisionStatus === 'pinned' && scores !== null;
|
|
124
|
+
return { ok, source, decision, scores, latencyMs };
|
|
125
|
+
}
|
|
126
|
+
async function main(argv) {
|
|
127
|
+
const opts = parseArgs(argv);
|
|
128
|
+
const p = plan(opts);
|
|
129
|
+
if (opts.dryRun) { process.stdout.write(`${JSON.stringify({ dryRun: true, ...p }, null, 2)}\n`); return 0; }
|
|
130
|
+
const base = findPython(opts.python);
|
|
131
|
+
const venvPy = venvPython(opts.dir);
|
|
132
|
+
fs.mkdirSync(checkpointDir(opts.dir), { recursive: true });
|
|
133
|
+
if (!fs.existsSync(venvPy)) exec(base.cmd, [...base.pre, '-m', 'venv', path.join(opts.dir, 'venv')]);
|
|
134
|
+
const venvVersion = exec(venvPy, VERSION_PROBE, true);
|
|
135
|
+
if (!supportedPython(venvVersion)) throw new Error(`python-unsupported: existing venv uses ${venvVersion}`);
|
|
136
|
+
exec(venvPy, ['-m', 'pip', 'install', '--disable-pip-version-check', '--index-url', PINS.torchIndex, PINS.torchSpec]);
|
|
137
|
+
exec(venvPy, ['-m', 'pip', 'install', '--disable-pip-version-check', PINS.cuaS1Spec]);
|
|
138
|
+
for (const spec of Object.values(PINS.files)) {
|
|
139
|
+
console.error(`${spec.name}: ${await ensureFile(fileUrl(spec.name), path.join(checkpointDir(opts.dir), spec.name), spec)}`);
|
|
140
|
+
}
|
|
141
|
+
fs.writeFileSync(p.manifest, `${JSON.stringify(buildManifest(opts.dir, venvPy), null, 2)}\n`);
|
|
142
|
+
const check = verify(p.manifest);
|
|
143
|
+
process.stdout.write(`${JSON.stringify({ dryRun: false, ...p, verification: check }, null, 2)}\n`);
|
|
144
|
+
return check.ok ? 0 : 1;
|
|
145
|
+
}
|
|
146
|
+
if (require.main === module) {
|
|
147
|
+
main(process.argv.slice(2)).then(code => { process.exitCode = code; }, err => {
|
|
148
|
+
if (err.message === 'usage') { console.error(USAGE); process.exitCode = 2; return; }
|
|
149
|
+
console.error(`System One install failed: ${err.message}`); process.exitCode = 1;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
module.exports = { PINS, fileUrl, venvPython, supportedPython, pythonCandidates, parseArgs, buildManifest, ensureFile, plan, verify };
|