fix: gate ECC node qualification before spawn

This commit is contained in:
Affaan Mustafa
2026-07-24 00:29:31 -04:00
parent 04062a8cf1
commit 0761e2fabf
6 changed files with 211 additions and 29 deletions
+4 -3
View File
@@ -1228,9 +1228,10 @@ skill](skills/ito-compute/SKILL.md) for the full RFQ authority and MCP setup
contract.
`find` submits a live authenticated RFQ. It does not reserve capacity.
`evals` requires the canonical CLI's double opt-in, a separately installed
`sixtytwo-cli==0.3.33`, an explicit node list, and an existing absolute
configuration directory. It cannot rent, launch, recover, repair, or purchase.
`evals` requires both `ITO_ENABLE_SIXTYTWO_LIVE=1` and `--live-sixtytwo`, a
separately installed `sixtytwo-cli==0.3.33`, an explicit node list, and an
existing absolute configuration directory. It cannot rent, launch, recover,
repair, or purchase.
ECC exposes no quote lock, purchase, workload, or inference path, and it never
replaces a missing client or failed live call with a local result.
+2 -1
View File
@@ -70,7 +70,8 @@ environment. It does not inspect or log the key.
or agent must gather every hard topology/economic constraint and obtain
explicit buyer authority before invoking it.
- `status` reads current RFQ and procurement status.
- `evals` runs only the canonical CLI's double-opt-in
- `evals` requires both `ITO_ENABLE_SIXTYTWO_LIVE=1` and
`--live-sixtytwo`, then runs only the canonical CLI's pinned
`sixtytwo-cli==0.3.33` qualification adapter against an explicit node list
and existing absolute configuration directory. It receives no `ITO_API_KEY`
or unrelated cloud/model credentials and cannot rent, launch, recover,
+5 -12
View File
@@ -4,7 +4,7 @@ const { spawnSync } = require('child_process');
const path = require('path');
const { listAvailableLanguages } = require('./lib/install-executor');
const { getComputeSponsorCopy } = require('./lib/compute-sponsor');
const { createSafeItoEnvironment } = require('./lib/ito-environment');
const { createSafeItoInvocationEnvironment } = require('./lib/ito-environment');
const COMMANDS = {
install: {
@@ -107,7 +107,7 @@ const PRIMARY_COMMANDS = [
];
function showHelp(exitCode = 0) {
console.log(`
process.stdout.write(`
ECC selective-install CLI
Usage:
@@ -227,22 +227,15 @@ function runCommand(commandName, args) {
if (!command) {
throw new Error(`Unknown command: ${commandName}`);
}
const itoSubcommand = args[0] === '--json' ? args[1] : args[0];
const isItoNodeQualification = commandName === 'ito' && itoSubcommand === 'evals';
const result = spawnSync(
process.execPath,
[path.join(__dirname, command.script), ...args],
{
cwd: process.cwd(),
env: commandName === 'ito'
? {
...createSafeItoEnvironment(process.env, {
includeControls: true,
includeItoRuntime: !isItoNodeQualification,
includeItoEvals: isItoNodeQualification,
}),
}
? createSafeItoInvocationEnvironment(process.env, args, {
includeControls: true,
})
: process.env,
encoding: 'utf8',
maxBuffer: 10 * 1024 * 1024,
+63 -11
View File
@@ -5,7 +5,9 @@
const fs = require("fs");
const path = require("path");
const { spawnSync } = require("child_process");
const { createSafeItoEnvironment } = require("./lib/ito-environment");
const {
createSafeItoInvocationEnvironment,
} = require("./lib/ito-environment");
const SUPPORTED_COMMANDS = Object.freeze(["auth", "find", "status", "evals"]);
const CANONICAL_REPOSITORY = "https://github.com/Ito-Markets/ito-cloud-runtime.git";
@@ -18,9 +20,10 @@ const CANONICAL_ENTRY_SEGMENTS = Object.freeze([
]);
const EXECUTABLE_OVERRIDE = "ECC_ITO_CLI_EXECUTABLE";
const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
const NODE_QUALIFICATION_TIMEOUT_MS = 31 * 60 * 1000;
function showHelp() {
console.log(`
process.stdout.write(`
ECC × Itô local CLI bridge
Usage:
@@ -63,15 +66,64 @@ The same package's MCP server exposes only:
Configure the MCP command as "node" with this absolute argument:
/absolute/path/to/ito-cloud-runtime/${CANONICAL_PACKAGE_PATH}/dist/bin/ito-mcp.js
Inject ITO_API_KEY into the child process from 1Password or the launching
environment. Never put the key in arguments, tracked files, or chat.
For auth, find, and status, inject ITO_API_KEY into the child process from
1Password or the launching environment. Never put the key in arguments,
tracked files, or chat.
Live node qualification requires ITO_ENABLE_SIXTYTWO_LIVE=1,
--live-sixtytwo, an explicit node list, and an existing absolute config
directory. The canonical CLI requires sixtytwo-cli==0.3.33 and fails closed.
directory. It forwards only named SIXTYTWO_API_TOKEN/SIXTYTWO_TOKEN and SSH
agent state; ITO_API_KEY is intentionally excluded. The canonical CLI requires
sixtytwo-cli==0.3.33 and fails closed.
`);
}
function requiredOptionValue(args, option) {
const indexes = args
.map((value, index) => (value === option ? index : -1))
.filter((index) => index >= 0);
if (indexes.length !== 1) {
throw new Error(`${option} is required exactly once for live node qualification.`);
}
const value = args[indexes[0] + 1];
if (!value || value.startsWith("--")) {
throw new Error(`${option} requires a non-empty value for live node qualification.`);
}
return value;
}
function validateNodeQualificationArgs(args, environment) {
if (environment.ITO_ENABLE_SIXTYTWO_LIVE !== "1") {
throw new Error(
"Live node qualification requires ITO_ENABLE_SIXTYTWO_LIVE=1 before any process is started."
);
}
if (args.filter((value) => value === "--live-sixtytwo").length !== 1) {
throw new Error(
"Live node qualification requires --live-sixtytwo exactly once before any process is started."
);
}
requiredOptionValue(args, "--cluster");
requiredOptionValue(args, "--nodes");
const configDirectory = requiredOptionValue(args, "--config-dir");
if (!path.isAbsolute(configDirectory)) {
throw new Error("--config-dir must be an existing absolute directory.");
}
try {
const resolved = fs.realpathSync.native(configDirectory);
if (
!fs.statSync(resolved).isDirectory()
|| !fs.statSync(path.join(resolved, "sixtytwo.yaml")).isFile()
) {
throw new Error("invalid qualification configuration");
}
} catch {
throw new Error(
"--config-dir must exist and contain a regular sixtytwo.yaml before any process is started."
);
}
}
function parseArgs(argv, environment = process.env) {
const args = [...argv];
if (
@@ -101,6 +153,9 @@ function parseArgs(argv, environment = process.env) {
`Unsupported Itô command "${command || "(missing)"}"; ECC permits only auth, find, status, and evals.`
);
}
if (command === "evals") {
validateNodeQualificationArgs(withoutJson, environment);
}
return Object.freeze({
help: false,
@@ -199,13 +254,9 @@ function invokeIto(executable, args, environment = process.env) {
const result = spawnSync(invocation.executable, invocation.args, {
cwd: process.cwd(),
encoding: "utf8",
env: {
...createSafeItoEnvironment(environment, {
includeItoRuntime: !isNodeQualification,
includeItoEvals: isNodeQualification,
}),
},
env: createSafeItoInvocationEnvironment(environment, args),
maxBuffer: MAX_OUTPUT_BYTES,
timeout: isNodeQualification ? NODE_QUALIFICATION_TIMEOUT_MS : undefined,
shell: false,
windowsHide: true,
});
@@ -245,6 +296,7 @@ module.exports = Object.freeze({
CANONICAL_PACKAGE_PATH,
CANONICAL_REPOSITORY,
EXECUTABLE_OVERRIDE,
NODE_QUALIFICATION_TIMEOUT_MS,
SUPPORTED_COMMANDS,
buildInvocation,
invokeIto,
+15
View File
@@ -42,6 +42,7 @@ const ECC_ITO_CONTROL_KEYS = Object.freeze([
"ECC_ITO_CLI_EXECUTABLE",
"NODE_ENV",
]);
const ITO_RUNTIME_COMMANDS = new Set(["auth", "find", "status"]);
function copyDefined(source, target, key) {
if (typeof source[key] === "string") {
@@ -79,10 +80,24 @@ function createSafeItoEnvironment(source = process.env, options = {}) {
return Object.freeze(safe);
}
function createSafeItoInvocationEnvironment(
source = process.env,
args = [],
options = {},
) {
const command = args.filter((value) => value !== "--json")[0];
return createSafeItoEnvironment(source, {
includeControls: options.includeControls === true,
includeItoRuntime: ITO_RUNTIME_COMMANDS.has(command),
includeItoEvals: command === "evals",
});
}
module.exports = Object.freeze({
ECC_ITO_CONTROL_KEYS,
ITO_EVAL_ENVIRONMENT_KEYS,
ITO_RUNTIME_ENVIRONMENT_KEYS,
SYSTEM_ENVIRONMENT_KEYS,
createSafeItoEnvironment,
createSafeItoInvocationEnvironment,
});
+122 -2
View File
@@ -13,7 +13,14 @@ const { spawnSync } = require("child_process");
const REPO_ROOT = path.join(__dirname, "..", "..");
const ECC_SCRIPT = path.join(REPO_ROOT, "scripts", "ecc.js");
const ITO_SCRIPT = path.join(REPO_ROOT, "scripts", "ito.js");
const CANONICAL_PACKAGE = "Ito-Markets/ito-cloud-runtime/cli/ito-compute-cli";
const {
NODE_QUALIFICATION_TIMEOUT_MS,
} = require("../../scripts/ito");
const {
createSafeItoInvocationEnvironment,
} = require("../../scripts/lib/ito-environment");
function runCli(args, environment = {}) {
return spawnSync(process.execPath, [ECC_SCRIPT, ...args], {
@@ -80,8 +87,8 @@ function main() {
console.log("\n=== Testing ECC × Itô real CLI bridge ===\n");
const tests = [
["forwards only the reviewed CLI surface to an explicit local executable", () => {
for (const command of ["auth", "find", "status", "evals"]) {
["forwards only the reviewed RFQ CLI surface to an explicit local executable", () => {
for (const command of ["auth", "find", "status"]) {
const probe = makeItoProbe();
try {
const result = runCli(["ito", command], {
@@ -156,6 +163,8 @@ function main() {
const probe = makeItoProbe();
try {
const configDirectory = path.join(probe.directory, "qualification");
fs.mkdirSync(configDirectory);
fs.writeFileSync(path.join(configDirectory, "sixtytwo.yaml"), "suite: full\n");
const result = runCli([
"ito",
"evals",
@@ -170,6 +179,7 @@ function main() {
ITO_INVENTORY_URL: "https://edge.example.test",
ITO_ENABLE_SIXTYTWO_LIVE: "1",
SIXTYTWO_API_TOKEN: "sixtytwo-test-token",
SIXTYTWO_TOKEN: "sixtytwo-legacy-test-token",
SSH_AUTH_SOCK: "/tmp/ecc-test-agent.sock",
ITO_CLI_DEMO: "1",
ITO_CLI_STATE_DIR: "/tmp/forbidden-paper-state",
@@ -187,6 +197,7 @@ function main() {
]);
assert.strictEqual(invocation.env.ITO_ENABLE_SIXTYTWO_LIVE, "1");
assert.strictEqual(invocation.env.SIXTYTWO_API_TOKEN, "sixtytwo-test-token");
assert.strictEqual(invocation.env.SIXTYTWO_TOKEN, "sixtytwo-legacy-test-token");
assert.strictEqual(invocation.env.SSH_AUTH_SOCK, "/tmp/ecc-test-agent.sock");
assert.strictEqual(invocation.env.ITO_API_KEY, undefined);
assert.strictEqual(invocation.env.ITO_API_URL, undefined);
@@ -199,6 +210,115 @@ function main() {
fs.rmSync(probe.directory, { recursive: true, force: true });
}
}],
["rejects every incomplete live qualification before spawning", () => {
const validArgs = [
"ito",
"evals",
"--cluster", "clu_prod_example",
"--live-sixtytwo",
"--nodes", "gpu-01,gpu-02",
];
const cases = [
{
label: "missing environment opt-in",
args: [...validArgs, "--config-dir", "__CONFIG__"],
env: {},
error: /ITO_ENABLE_SIXTYTWO_LIVE=1/,
},
{
label: "missing live flag",
args: validArgs.filter((value) => value !== "--live-sixtytwo")
.concat("--config-dir", "__CONFIG__"),
env: { ITO_ENABLE_SIXTYTWO_LIVE: "1" },
error: /--live-sixtytwo/,
},
{
label: "missing nodes",
args: [
"ito", "evals",
"--cluster", "clu_prod_example",
"--live-sixtytwo",
"--config-dir", "__CONFIG__",
],
env: { ITO_ENABLE_SIXTYTWO_LIVE: "1" },
error: /--nodes/,
},
{
label: "missing cluster",
args: [
"ito", "evals",
"--live-sixtytwo",
"--nodes", "gpu-01",
"--config-dir", "__CONFIG__",
],
env: { ITO_ENABLE_SIXTYTWO_LIVE: "1" },
error: /--cluster/,
},
{
label: "relative config directory",
args: [...validArgs, "--config-dir", "relative/config"],
env: { ITO_ENABLE_SIXTYTWO_LIVE: "1" },
error: /absolute/,
},
{
label: "missing config directory",
args: [...validArgs, "--config-dir", "__MISSING_CONFIG__"],
env: { ITO_ENABLE_SIXTYTWO_LIVE: "1" },
error: /sixtytwo\.yaml/,
},
];
for (const testCase of cases) {
const probe = makeItoProbe();
try {
const configDirectory = path.join(probe.directory, "qualification");
fs.mkdirSync(configDirectory);
fs.writeFileSync(path.join(configDirectory, "sixtytwo.yaml"), "suite: full\n");
const args = testCase.args.map((value) => (
value === "__CONFIG__"
? configDirectory
: value === "__MISSING_CONFIG__"
? path.join(probe.directory, "missing")
: value
));
const result = runCli(args, {
ECC_ITO_CLI_EXECUTABLE: probe.executable,
...testCase.env,
});
assert.notStrictEqual(result.status, 0, testCase.label);
assert.match(result.stderr, testCase.error, testCase.label);
assert.ok(
!fs.existsSync(probe.log),
`${testCase.label} must not spawn the canonical Itô CLI`,
);
} finally {
fs.rmSync(probe.directory, { recursive: true, force: true });
}
}
}],
["classifies Itō child environments once and fails closed on unknown prefixes", () => {
const safe = createSafeItoInvocationEnvironment(
{
PATH: process.env.PATH,
ECC_ITO_CLI_EXECUTABLE: "/operator/canonical/ito.js",
ITO_API_KEY: "must-not-cross",
SIXTYTWO_TOKEN: "must-not-cross",
},
["--future-ecc-flag", "evals"],
{ includeControls: true },
);
assert.strictEqual(safe.ECC_ITO_CLI_EXECUTABLE, "/operator/canonical/ito.js");
assert.strictEqual(safe.ITO_API_KEY, undefined);
assert.strictEqual(safe.SIXTYTWO_TOKEN, undefined);
}],
["bounds the outer node-qualification process beyond the canonical timeout", () => {
assert.strictEqual(NODE_QUALIFICATION_TIMEOUT_MS, 31 * 60 * 1000);
const source = fs.readFileSync(ITO_SCRIPT, "utf8");
assert.match(
source,
/timeout: isNodeQualification \? NODE_QUALIFICATION_TIMEOUT_MS : undefined/,
);
}],
["rejects unsupported browser, paper, and execution operations before spawning", () => {
for (const command of ["rent", "lock", "run", "inference", "mcp"]) {
const probe = makeItoProbe();