mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-08 18:57:55 +02:00
feat: add read-only Itō compute handoff (#2554)
* feat: add read-only Itō compute handoff * fix(ito): keep handoff portable under CI * test(ito): run npm welcome through Windows shell
This commit is contained in:
@@ -18,6 +18,30 @@ OAuth integration, billing flow, or rental mutation.
|
||||
No Phase 2 skill, CLI, MCP, OAuth, rental-management, or serving code should be
|
||||
built until Affaan approves the decisions at the end of this document.
|
||||
|
||||
### Narrow sandbox handoff authorized on 2026-07-23
|
||||
|
||||
Affaan separately authorized one smaller CLI slice:
|
||||
|
||||
ecc ito rent --accelerator h100 --count 1 --hours 24
|
||||
|
||||
That command is implemented as `ito.compute.handoff.v1`. It validates an
|
||||
explicit H100 intent, records `ecc-cli` provenance, fixes the authority ceiling
|
||||
at read-only sandbox mode, and opens only the `/desk` page, where sign-in may be
|
||||
required. `--dry-run` emits the identical structured handoff without opening a
|
||||
browser.
|
||||
|
||||
The current Itô desk does not expose a supported structured ECC deep link or a
|
||||
non-mutating external-intake endpoint. The CLI therefore returns
|
||||
`transport: manual_copy` and `acceptedByIto: false`; it does not pretend that
|
||||
opening `/desk` delivered the intent. A human may copy the bounded message into
|
||||
the conversational desk. ECC never reads credentials, files an RFQ, requests
|
||||
or accepts a quote, calls procurement, approves funds, creates an order, or
|
||||
contacts a counterparty. The user must stop before `Pay & buy`.
|
||||
|
||||
This narrow authorization does not approve the five skills, MCP, OAuth, rental
|
||||
management, inference serving, deployment, publication, or any live economic
|
||||
mutation described later in this document.
|
||||
|
||||
## Thesis
|
||||
|
||||
The distribution chain is:
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# ECC × Itô Sandbox Handoff Evidence
|
||||
|
||||
Date: 2026-07-23
|
||||
|
||||
Branch: `agent/ecc-ito-sandbox-handoff`
|
||||
|
||||
Base commit: `a3130f9ebfaeed075df5d5b52538acb0ee4bcdf8`
|
||||
|
||||
## Result
|
||||
|
||||
The canonical ECC CLI now supports:
|
||||
|
||||
```text
|
||||
ecc ito rent --accelerator h100 --count 1 --hours 24
|
||||
```
|
||||
|
||||
The command emits an `ito.compute.handoff.v1` envelope containing the exact
|
||||
accelerator, count, and duration, `ecc-cli` provenance, and a read-only sandbox
|
||||
authority ceiling. It opens only `https://compute.itomarkets.com/desk`.
|
||||
|
||||
No supported Itô deep link or non-mutating structured external-intake endpoint
|
||||
exists yet. The contract therefore reports `transport: manual_copy`,
|
||||
`acceptedByIto: false`, and `signInMayBeRequired: true`. It never claims that
|
||||
opening the page delivered the intent.
|
||||
|
||||
The CLI does not read credentials, call the network, file an RFQ, request or
|
||||
accept a quote, call procurement, approve funds, create an order, perform
|
||||
outreach, deploy, push, or merge. The human boundary remains before
|
||||
`Pay & buy`.
|
||||
|
||||
## Security boundary
|
||||
|
||||
Both subprocess boundaries use an explicit system-environment allowlist:
|
||||
|
||||
1. `ecc` to the `ito` command.
|
||||
2. `ito` to the OS browser opener.
|
||||
|
||||
The allowlist retains only runtime essentials such as path, home, temporary
|
||||
directory, locale, terminal, and desktop-session variables. ECC dry-run and
|
||||
test controls are passed only to the Itô command where needed. Browser-probe
|
||||
tests inject API-key, service-token, and password sentinels and prove none
|
||||
reach the browser child.
|
||||
|
||||
## Eval evidence
|
||||
|
||||
Focused handoff:
|
||||
|
||||
```text
|
||||
node tests/scripts/ito-handoff.test.js
|
||||
```
|
||||
|
||||
Result: 9 passed, 0 failed.
|
||||
|
||||
Covered scenarios:
|
||||
|
||||
- exact H100, count 1, 24-hour mapping;
|
||||
- provenance and sandbox authority;
|
||||
- global and local dry-run with zero browser invocation;
|
||||
- exact allowlisted browser URL and sanitized child environment;
|
||||
- direct Itô invocation with sanitized browser environment;
|
||||
- missing, duplicate, unsupported, and out-of-range input rejection;
|
||||
- absence of credential or mutating transports;
|
||||
- human-readable approval boundary;
|
||||
- POSIX-safe npm welcome output.
|
||||
|
||||
CLI and package regressions:
|
||||
|
||||
```text
|
||||
node tests/scripts/ecc.test.js
|
||||
node tests/scripts/npm-publish-surface.test.js
|
||||
```
|
||||
|
||||
Results: 21 passed and 2 passed, with 0 failures.
|
||||
|
||||
Full repository suite:
|
||||
|
||||
```text
|
||||
npm test
|
||||
```
|
||||
|
||||
Result: 3,155 passed, 0 failed.
|
||||
|
||||
Static and security gates:
|
||||
|
||||
```text
|
||||
npm run lint
|
||||
npm run security:ioc-scan -- --root .
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Results: all passed; the IOC scan inspected 207 files.
|
||||
|
||||
Packaged-bin execution:
|
||||
|
||||
```text
|
||||
npm pack --pack-destination <temporary-directory>
|
||||
node <unpacked-package>/scripts/ecc.js --dry-run ito rent \
|
||||
--accelerator h100 --count 1 --hours 24 --json
|
||||
```
|
||||
|
||||
Result: the packed `ecc-universal-2.0.0.tgz` included executable `ecc.js` and
|
||||
`ito.js`; the unpacked command returned the exact intent with `dryRun: true`,
|
||||
`opened: false`, and `transport: manual_copy`.
|
||||
|
||||
## Changed surfaces
|
||||
|
||||
- `scripts/ito.js`
|
||||
- `scripts/lib/ito-environment.js`
|
||||
- `scripts/ecc.js`
|
||||
- `package.json`
|
||||
- `tests/scripts/ito-handoff.test.js`
|
||||
- `tests/scripts/npm-publish-surface.test.js`
|
||||
- `docs/design/ecc-ito-compute-integration.md`
|
||||
- this evidence record
|
||||
|
||||
## Remaining limitation
|
||||
|
||||
ECC cannot automatically prefill or deliver this structured intent until Itô
|
||||
publishes a safe, authenticated, non-mutating intake or deep-link contract.
|
||||
Adding such a contract belongs in Itô and must preserve its existing human
|
||||
approval gates. This ECC slice deliberately does not invent another UI,
|
||||
transport, credential, or procurement path.
|
||||
@@ -110,6 +110,7 @@
|
||||
"scripts/hooks/",
|
||||
"scripts/install-apply.js",
|
||||
"scripts/install-plan.js",
|
||||
"scripts/ito.js",
|
||||
"scripts/lib/",
|
||||
"scripts/list-installed.js",
|
||||
"scripts/loop-status.js",
|
||||
|
||||
+11
-1
@@ -4,6 +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 COMMANDS = {
|
||||
install: {
|
||||
@@ -26,6 +27,10 @@ const COMMANDS = {
|
||||
script: 'control-pane.js',
|
||||
description: 'Run the local ECC2 operator control pane',
|
||||
},
|
||||
ito: {
|
||||
script: 'ito.js',
|
||||
description: 'Prepare a read-only sandbox handoff to the Itô compute desk',
|
||||
},
|
||||
'install-plan': {
|
||||
script: 'install-plan.js',
|
||||
description: 'Alias for plan',
|
||||
@@ -86,6 +91,7 @@ const PRIMARY_COMMANDS = [
|
||||
'catalog',
|
||||
'consult',
|
||||
'control-pane',
|
||||
'ito',
|
||||
'list-installed',
|
||||
'doctor',
|
||||
'repair',
|
||||
@@ -132,6 +138,8 @@ Examples:
|
||||
ecc catalog show framework:nextjs
|
||||
ecc consult "security reviews"
|
||||
ecc control-pane --port 8765
|
||||
ecc ito rent --accelerator h100 --count 1 --hours 24
|
||||
ecc --dry-run ito rent --accelerator h100 --count 1 --hours 24 --json
|
||||
ecc list-installed --json
|
||||
ecc doctor --target cursor
|
||||
ecc repair --dry-run
|
||||
@@ -223,7 +231,9 @@ function runCommand(commandName, args) {
|
||||
[path.join(__dirname, command.script), ...args],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
env: process.env,
|
||||
env: commandName === 'ito'
|
||||
? { ...createSafeItoEnvironment(process.env, { includeControls: true }) }
|
||||
: process.env,
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
}
|
||||
|
||||
Executable
+321
@@ -0,0 +1,321 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
const { spawnSync } = require("child_process");
|
||||
const { createSafeItoEnvironment } = require("./lib/ito-environment");
|
||||
|
||||
const DESK_URL = "https://compute.itomarkets.com/desk";
|
||||
const SCHEMA_VERSION = "ito.compute.handoff.v1";
|
||||
const SUPPORTED_ACCELERATORS = Object.freeze({
|
||||
h100: "h100",
|
||||
"h100-pcie": "h100-pcie",
|
||||
"h100-sxm": "h100-sxm",
|
||||
});
|
||||
const REQUIRED_INTENT_OPTIONS = Object.freeze([
|
||||
"accelerator",
|
||||
"count",
|
||||
"hours",
|
||||
]);
|
||||
|
||||
function showHelp() {
|
||||
console.log(`
|
||||
ECC × Itô compute handoff
|
||||
|
||||
Usage:
|
||||
ecc ito rent --accelerator <h100|h100-pcie|h100-sxm> --count <1-64> --hours <1-720> [options]
|
||||
|
||||
Options:
|
||||
--dry-run Emit the exact handoff without opening a browser
|
||||
--no-open Emit the exact handoff for manual browser navigation
|
||||
--json Emit the versioned response envelope as JSON
|
||||
--help Show this help
|
||||
|
||||
Example:
|
||||
ecc ito rent --accelerator h100 --count 1 --hours 24
|
||||
|
||||
This command creates a sandbox-only, read-only intent. It opens the Itô desk
|
||||
for manual copy; sign-in may be required. It does not send the intent into Itô,
|
||||
file an RFQ, request or accept a quote, call a procurement endpoint, approve
|
||||
funds, or place an order. Stop before "Pay & buy".
|
||||
`);
|
||||
}
|
||||
|
||||
function readValue(args, index, option) {
|
||||
const value = args[index + 1];
|
||||
if (value === undefined || value.startsWith("--")) {
|
||||
throw new Error(`--${option} requires a value`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseInteger(raw, option, minimum, maximum) {
|
||||
if (!/^[0-9]+$/.test(raw)) {
|
||||
throw new Error(`--${option} must be an integer between ${minimum} and ${maximum}`);
|
||||
}
|
||||
const value = Number(raw);
|
||||
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
||||
throw new Error(`--${option} must be an integer between ${minimum} and ${maximum}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseAccelerator(raw) {
|
||||
const normalized = String(raw).trim().toLowerCase();
|
||||
const accelerator = SUPPORTED_ACCELERATORS[normalized];
|
||||
if (!accelerator) {
|
||||
throw new Error(
|
||||
"--accelerator must be one of h100, h100-pcie, or h100-sxm"
|
||||
);
|
||||
}
|
||||
return accelerator;
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = [...argv];
|
||||
if (args.includes("--help") || args.includes("-h")) {
|
||||
return { help: true };
|
||||
}
|
||||
|
||||
const command = args.shift();
|
||||
if (command !== "rent") {
|
||||
throw new Error(`unsupported Itô command: ${command || "(missing)"}`);
|
||||
}
|
||||
|
||||
const values = {
|
||||
accelerator: null,
|
||||
count: null,
|
||||
hours: null,
|
||||
dryRun: false,
|
||||
json: false,
|
||||
noOpen: false,
|
||||
};
|
||||
const seen = new Set();
|
||||
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
if (arg === "--dry-run") {
|
||||
if (seen.has("dry-run")) throw new Error("--dry-run may only be provided once");
|
||||
seen.add("dry-run");
|
||||
values.dryRun = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--json") {
|
||||
if (seen.has("json")) throw new Error("--json may only be provided once");
|
||||
seen.add("json");
|
||||
values.json = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--no-open") {
|
||||
if (seen.has("no-open")) throw new Error("--no-open may only be provided once");
|
||||
seen.add("no-open");
|
||||
values.noOpen = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const option = arg.startsWith("--") ? arg.slice(2) : "";
|
||||
if (!REQUIRED_INTENT_OPTIONS.includes(option)) {
|
||||
throw new Error(`unsupported option: ${arg}`);
|
||||
}
|
||||
if (seen.has(option)) {
|
||||
throw new Error(`--${option} may only be provided once`);
|
||||
}
|
||||
seen.add(option);
|
||||
const raw = readValue(args, index, option);
|
||||
if (option === "accelerator") values.accelerator = parseAccelerator(raw);
|
||||
if (option === "count") values.count = parseInteger(raw, option, 1, 64);
|
||||
if (option === "hours") values.hours = parseInteger(raw, option, 1, 720);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
const missing = REQUIRED_INTENT_OPTIONS.filter((option) => values[option] === null);
|
||||
if (missing.length > 0) {
|
||||
throw new Error(
|
||||
`missing required rental intent option${missing.length === 1 ? "" : "s"}: `
|
||||
+ missing.map((option) => `--${option}`).join(", ")
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
help: false,
|
||||
command,
|
||||
options: Object.freeze({
|
||||
...values,
|
||||
dryRun: values.dryRun || process.env.ECC_DRY_RUN === "1",
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function buildHandoffMessage(intent) {
|
||||
const accelerator = intent.accelerator.toUpperCase();
|
||||
return [
|
||||
"[ECC sandbox-only compute handoff]",
|
||||
`Source: ECC CLI. Need ${intent.count} × ${accelerator} for ${intent.hours} hours.`,
|
||||
"Treat this as a read-only requirement for review.",
|
||||
"Do not file an RFQ, request or accept a quote, place an order, approve funds, or contact a counterparty without a separate explicit human action in Itô.",
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
function createEnvelope({ success, state, data, error }) {
|
||||
return Object.freeze({
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
success,
|
||||
state,
|
||||
data,
|
||||
error,
|
||||
links: Object.freeze({ desk: DESK_URL }),
|
||||
});
|
||||
}
|
||||
|
||||
function createInvalidEnvelope(message) {
|
||||
return createEnvelope({
|
||||
success: false,
|
||||
state: "invalid_request",
|
||||
data: null,
|
||||
error: Object.freeze({
|
||||
code: "INVALID_ARGUMENT",
|
||||
message,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function defaultOpenUrl(url) {
|
||||
let executable;
|
||||
let args;
|
||||
const testExecutable = process.env.NODE_ENV === "test"
|
||||
? process.env.ECC_ITO_BROWSER_EXECUTABLE
|
||||
: null;
|
||||
|
||||
if (testExecutable) {
|
||||
if (process.platform === "win32" && /\.(?:bat|cmd)$/i.test(testExecutable)) {
|
||||
executable = process.env.ComSpec || "cmd.exe";
|
||||
args = ["/d", "/s", "/c", testExecutable, url];
|
||||
} else {
|
||||
executable = testExecutable;
|
||||
args = [url];
|
||||
}
|
||||
} else if (process.platform === "darwin") {
|
||||
executable = "open";
|
||||
args = [url];
|
||||
} else if (process.platform === "win32") {
|
||||
executable = "cmd.exe";
|
||||
args = ["/d", "/s", "/c", "start", "", url];
|
||||
} else {
|
||||
executable = "xdg-open";
|
||||
args = [url];
|
||||
}
|
||||
|
||||
const result = spawnSync(executable, args, {
|
||||
env: { ...createSafeItoEnvironment(process.env) },
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
shell: false,
|
||||
});
|
||||
return !result.error && result.status === 0;
|
||||
}
|
||||
|
||||
function createHandoff(options, openUrl = defaultOpenUrl) {
|
||||
const shouldOpen = !options.dryRun && !options.noOpen;
|
||||
const opened = shouldOpen ? openUrl(DESK_URL) : false;
|
||||
const intent = Object.freeze({
|
||||
accelerator: options.accelerator,
|
||||
count: options.count,
|
||||
hours: options.hours,
|
||||
});
|
||||
|
||||
return createEnvelope({
|
||||
success: true,
|
||||
state: "manual_handoff",
|
||||
data: Object.freeze({
|
||||
authenticated: null,
|
||||
dryRun: options.dryRun,
|
||||
opened,
|
||||
intent,
|
||||
provenance: Object.freeze({
|
||||
source: "ecc-cli",
|
||||
command: "ecc ito rent",
|
||||
}),
|
||||
authority: Object.freeze({
|
||||
environment: "sandbox",
|
||||
readOnly: true,
|
||||
liveRfq: false,
|
||||
procurementMutation: false,
|
||||
quoteAcceptance: false,
|
||||
fundsApproval: false,
|
||||
orderCreation: false,
|
||||
outreach: false,
|
||||
}),
|
||||
handoff: Object.freeze({
|
||||
transport: "manual_copy",
|
||||
destination: "ito-desk",
|
||||
acceptedByIto: false,
|
||||
signInMayBeRequired: true,
|
||||
message: buildHandoffMessage(intent),
|
||||
limitation: "Itô /desk currently exposes no supported structured ECC intake or deep-link contract.",
|
||||
}),
|
||||
approvalGate: 'Stop before "Pay & buy".',
|
||||
priceQuote: null,
|
||||
orderId: null,
|
||||
}),
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
|
||||
function renderText(payload) {
|
||||
const { data } = payload;
|
||||
const browserState = data.dryRun
|
||||
? "Dry-run: browser not opened."
|
||||
: data.opened
|
||||
? "Opened the Itô desk; sign-in may be required."
|
||||
: "Browser not opened; sign-in may be required at the Itô desk.";
|
||||
return [
|
||||
"ECC × Itô sandbox compute handoff",
|
||||
"",
|
||||
browserState,
|
||||
`Desk: ${payload.links.desk}`,
|
||||
"Transport: manual copy; Itô has not accepted this intent.",
|
||||
`Message: ${data.handoff.message}`,
|
||||
`Limitation: ${data.handoff.limitation}`,
|
||||
`Approval gate: ${data.approvalGate}`,
|
||||
"",
|
||||
"ECC does not file an RFQ, request or accept a quote, use credentials, approve funds, create an order, or contact a counterparty.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function main(argv = process.argv.slice(2), dependencies = {}) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = parseArgs(argv);
|
||||
} catch (error) {
|
||||
const payload = createInvalidEnvelope(error.message);
|
||||
if (argv.includes("--json")) console.log(JSON.stringify(payload, null, 2));
|
||||
else console.error(`Error: ${error.message}`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (parsed.help) {
|
||||
showHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const payload = createHandoff(parsed.options, dependencies.openUrl);
|
||||
if (parsed.options.json) console.log(JSON.stringify(payload, null, 2));
|
||||
else console.log(renderText(payload));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
process.exitCode = main();
|
||||
}
|
||||
|
||||
module.exports = Object.freeze({
|
||||
DESK_URL,
|
||||
SCHEMA_VERSION,
|
||||
buildHandoffMessage,
|
||||
createHandoff,
|
||||
createInvalidEnvelope,
|
||||
main,
|
||||
parseAccelerator,
|
||||
parseArgs,
|
||||
renderText,
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
"use strict";
|
||||
|
||||
const SYSTEM_ENVIRONMENT_KEYS = Object.freeze([
|
||||
"CI",
|
||||
"ComSpec",
|
||||
"DISPLAY",
|
||||
"FORCE_COLOR",
|
||||
"HOME",
|
||||
"LANG",
|
||||
"LC_ALL",
|
||||
"NO_COLOR",
|
||||
"PATH",
|
||||
"PATHEXT",
|
||||
"SHELL",
|
||||
"SystemRoot",
|
||||
"TEMP",
|
||||
"TERM",
|
||||
"TMP",
|
||||
"TMPDIR",
|
||||
"USERPROFILE",
|
||||
"WAYLAND_DISPLAY",
|
||||
"WINDIR",
|
||||
"XDG_RUNTIME_DIR",
|
||||
]);
|
||||
|
||||
function copyDefined(source, target, key) {
|
||||
if (typeof source[key] === "string") {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
|
||||
function createSafeItoEnvironment(source = process.env, options = {}) {
|
||||
const safe = {};
|
||||
for (const key of SYSTEM_ENVIRONMENT_KEYS) {
|
||||
copyDefined(source, safe, key);
|
||||
}
|
||||
for (const [key] of Object.entries(source)) {
|
||||
if (key.startsWith("LC_")) copyDefined(source, safe, key);
|
||||
}
|
||||
|
||||
if (options.includeControls) {
|
||||
copyDefined(source, safe, "ECC_DRY_RUN");
|
||||
copyDefined(source, safe, "NODE_ENV");
|
||||
if (source.NODE_ENV === "test") {
|
||||
copyDefined(source, safe, "ECC_ITO_BROWSER_EXECUTABLE");
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze(safe);
|
||||
}
|
||||
|
||||
module.exports = Object.freeze({
|
||||
SYSTEM_ENVIRONMENT_KEYS,
|
||||
createSafeItoEnvironment,
|
||||
});
|
||||
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* End-to-end contract tests for ECC's read-only Itô compute handoff.
|
||||
*/
|
||||
|
||||
const assert = require("assert")
|
||||
const fs = require("fs")
|
||||
const os = require("os")
|
||||
const path = require("path")
|
||||
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 DESK_URL = "https://compute.itomarkets.com/desk"
|
||||
|
||||
function runCli(args, options = {}) {
|
||||
return spawnSync(process.execPath, [ECC_SCRIPT, ...args], {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: "test",
|
||||
...(options.env || {}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function parseJson(result, expectedStatus = 0) {
|
||||
assert.strictEqual(result.status, expectedStatus, result.stderr)
|
||||
return JSON.parse(result.stdout)
|
||||
}
|
||||
|
||||
function runTest(name, fn) {
|
||||
try {
|
||||
fn()
|
||||
console.log(` ✓ ${name}`)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`)
|
||||
console.error(` ${error.message}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function makeBrowserProbe() {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ecc-ito-browser-"))
|
||||
const log = path.join(dir, "opened-url.txt")
|
||||
const envLog = path.join(dir, "browser-env.txt")
|
||||
const executable = path.join(dir, process.platform === "win32" ? "browser-probe.cmd" : "browser-probe")
|
||||
if (process.platform === "win32") {
|
||||
fs.writeFileSync(executable, `@echo off\r\n<nul set /p =%1>"${log}"\r\nset >"${envLog}"\r\n`)
|
||||
} else {
|
||||
fs.writeFileSync(executable, `#!/bin/sh\nprintf '%s' "$1" > "${log}"\nenv > "${envLog}"\n`)
|
||||
fs.chmodSync(executable, 0o755)
|
||||
}
|
||||
return { dir, envLog, executable, log }
|
||||
}
|
||||
|
||||
function main() {
|
||||
console.log("\n=== Testing ECC × Itô sandbox handoff ===\n")
|
||||
|
||||
const tests = [
|
||||
["maps the exact CLI request into a structured sandbox intent", () => {
|
||||
const payload = parseJson(runCli([
|
||||
"ito",
|
||||
"rent",
|
||||
"--accelerator", "h100",
|
||||
"--count", "1",
|
||||
"--hours", "24",
|
||||
"--no-open",
|
||||
"--json",
|
||||
]))
|
||||
|
||||
assert.strictEqual(payload.schemaVersion, "ito.compute.handoff.v1")
|
||||
assert.strictEqual(payload.success, true)
|
||||
assert.strictEqual(payload.state, "manual_handoff")
|
||||
assert.deepStrictEqual(payload.data.intent, {
|
||||
accelerator: "h100",
|
||||
count: 1,
|
||||
hours: 24,
|
||||
})
|
||||
assert.deepStrictEqual(payload.data.provenance, {
|
||||
source: "ecc-cli",
|
||||
command: "ecc ito rent",
|
||||
})
|
||||
assert.strictEqual(payload.data.authority.environment, "sandbox")
|
||||
assert.strictEqual(payload.data.authority.readOnly, true)
|
||||
assert.strictEqual(payload.data.authority.liveRfq, false)
|
||||
assert.strictEqual(payload.data.authority.procurementMutation, false)
|
||||
assert.strictEqual(payload.data.authority.quoteAcceptance, false)
|
||||
assert.strictEqual(payload.data.authority.fundsApproval, false)
|
||||
assert.strictEqual(payload.data.authority.orderCreation, false)
|
||||
assert.strictEqual(payload.data.handoff.transport, "manual_copy")
|
||||
assert.strictEqual(payload.data.handoff.destination, "ito-desk")
|
||||
assert.strictEqual(payload.data.handoff.acceptedByIto, false)
|
||||
assert.strictEqual(payload.data.handoff.signInMayBeRequired, true)
|
||||
assert.match(payload.data.handoff.message, /1 × H100/)
|
||||
assert.match(payload.data.handoff.message, /24 hours/)
|
||||
assert.match(payload.data.handoff.message, /ECC CLI/)
|
||||
assert.match(payload.data.handoff.message, /sandbox-only/)
|
||||
assert.strictEqual(payload.links.desk, DESK_URL)
|
||||
assert.strictEqual(payload.data.opened, false)
|
||||
assert.strictEqual(payload.data.priceQuote, null)
|
||||
assert.strictEqual(payload.data.orderId, null)
|
||||
}],
|
||||
["global dry-run emits the same handoff and never opens a browser", () => {
|
||||
const probe = makeBrowserProbe()
|
||||
try {
|
||||
const payload = parseJson(runCli([
|
||||
"--dry-run",
|
||||
"ito",
|
||||
"rent",
|
||||
"--accelerator", "h100",
|
||||
"--count", "1",
|
||||
"--hours", "24",
|
||||
"--json",
|
||||
], {
|
||||
env: { ECC_ITO_BROWSER_EXECUTABLE: probe.executable },
|
||||
}))
|
||||
|
||||
assert.strictEqual(payload.data.dryRun, true)
|
||||
assert.strictEqual(payload.data.opened, false)
|
||||
assert.ok(!fs.existsSync(probe.log), "dry-run must not invoke the browser executable")
|
||||
} finally {
|
||||
fs.rmSync(probe.dir, { recursive: true, force: true })
|
||||
}
|
||||
}],
|
||||
["local dry-run is equivalent and rejects an open request", () => {
|
||||
const probe = makeBrowserProbe()
|
||||
try {
|
||||
const payload = parseJson(runCli([
|
||||
"ito",
|
||||
"rent",
|
||||
"--accelerator", "h100",
|
||||
"--count", "1",
|
||||
"--hours", "24",
|
||||
"--dry-run",
|
||||
"--json",
|
||||
], {
|
||||
env: { ECC_ITO_BROWSER_EXECUTABLE: probe.executable },
|
||||
}))
|
||||
|
||||
assert.strictEqual(payload.data.dryRun, true)
|
||||
assert.strictEqual(payload.data.opened, false)
|
||||
assert.ok(!fs.existsSync(probe.log), "local dry-run must not invoke the browser executable")
|
||||
} finally {
|
||||
fs.rmSync(probe.dir, { recursive: true, force: true })
|
||||
}
|
||||
}],
|
||||
["browser handoff opens only the allowlisted desk URL without inherited secrets", () => {
|
||||
const probe = makeBrowserProbe()
|
||||
try {
|
||||
const payload = parseJson(runCli([
|
||||
"ito",
|
||||
"rent",
|
||||
"--accelerator", "h100",
|
||||
"--count", "1",
|
||||
"--hours", "24",
|
||||
"--json",
|
||||
], {
|
||||
env: {
|
||||
ECC_ITO_BROWSER_EXECUTABLE: probe.executable,
|
||||
ITO_API_KEY: "parent-api-key-must-not-cross",
|
||||
ITO_SERVICE_TOKEN: "parent-token-must-not-cross",
|
||||
TEST_PASSWORD: "parent-password-must-not-cross",
|
||||
},
|
||||
}))
|
||||
|
||||
assert.strictEqual(payload.data.opened, true)
|
||||
assert.strictEqual(fs.readFileSync(probe.log, "utf8"), DESK_URL)
|
||||
assert.strictEqual(payload.links.desk, DESK_URL)
|
||||
const childEnvironment = fs.readFileSync(probe.envLog, "utf8")
|
||||
assert.doesNotMatch(childEnvironment, /parent-api-key-must-not-cross/)
|
||||
assert.doesNotMatch(childEnvironment, /parent-token-must-not-cross/)
|
||||
assert.doesNotMatch(childEnvironment, /parent-password-must-not-cross/)
|
||||
} finally {
|
||||
fs.rmSync(probe.dir, { recursive: true, force: true })
|
||||
}
|
||||
}],
|
||||
["direct Ito execution strips secrets from the browser child", () => {
|
||||
const probe = makeBrowserProbe()
|
||||
try {
|
||||
const result = spawnSync(process.execPath, [
|
||||
ITO_SCRIPT,
|
||||
"rent",
|
||||
"--accelerator", "h100",
|
||||
"--count", "1",
|
||||
"--hours", "24",
|
||||
"--json",
|
||||
], {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: "test",
|
||||
ECC_ITO_BROWSER_EXECUTABLE: probe.executable,
|
||||
ITO_API_KEY: "direct-api-key-must-not-cross",
|
||||
ITO_SERVICE_TOKEN: "direct-token-must-not-cross",
|
||||
TEST_PASSWORD: "direct-password-must-not-cross",
|
||||
},
|
||||
})
|
||||
const payload = parseJson(result)
|
||||
assert.strictEqual(payload.data.opened, true)
|
||||
const childEnvironment = fs.readFileSync(probe.envLog, "utf8")
|
||||
assert.doesNotMatch(childEnvironment, /direct-api-key-must-not-cross/)
|
||||
assert.doesNotMatch(childEnvironment, /direct-token-must-not-cross/)
|
||||
assert.doesNotMatch(childEnvironment, /direct-password-must-not-cross/)
|
||||
} finally {
|
||||
fs.rmSync(probe.dir, { recursive: true, force: true })
|
||||
}
|
||||
}],
|
||||
["fails closed on missing, unsupported, duplicate, and out-of-range intent", () => {
|
||||
for (const args of [
|
||||
["ito", "rent", "--count", "1", "--hours", "24", "--no-open", "--json"],
|
||||
["ito", "rent", "--accelerator", "a100", "--count", "1", "--hours", "24", "--no-open", "--json"],
|
||||
["ito", "rent", "--accelerator", "h100", "--count", "0", "--hours", "24", "--no-open", "--json"],
|
||||
["ito", "rent", "--accelerator", "h100", "--count", "1", "--hours", "721", "--no-open", "--json"],
|
||||
["ito", "rent", "--accelerator", "h100", "--accelerator", "h100-sxm", "--count", "1", "--hours", "24", "--no-open", "--json"],
|
||||
["ito", "dashboard", "--accelerator", "h100", "--count", "1", "--hours", "24", "--no-open", "--json"],
|
||||
]) {
|
||||
const result = runCli(args)
|
||||
assert.notStrictEqual(result.status, 0, args.join(" "))
|
||||
const payload = JSON.parse(result.stdout)
|
||||
assert.strictEqual(payload.success, false)
|
||||
assert.strictEqual(payload.state, "invalid_request")
|
||||
assert.strictEqual(payload.data, null)
|
||||
assert.strictEqual(payload.error.code, "INVALID_ARGUMENT")
|
||||
}
|
||||
}],
|
||||
["does not inspect credentials or contain a mutating transport", () => {
|
||||
const payload = parseJson(runCli([
|
||||
"ito",
|
||||
"rent",
|
||||
"--accelerator", "h100",
|
||||
"--count", "1",
|
||||
"--hours", "24",
|
||||
"--no-open",
|
||||
"--json",
|
||||
], {
|
||||
env: {
|
||||
ITO_API_KEY: "must-not-be-read",
|
||||
ITO_SERVICE_TOKEN: "must-not-be-read",
|
||||
},
|
||||
}))
|
||||
|
||||
assert.strictEqual(payload.data.authenticated, null)
|
||||
const source = fs.readFileSync(path.join(REPO_ROOT, "scripts", "ito.js"), "utf8")
|
||||
assert.doesNotMatch(source, /ITO_API_KEY|ITO_SERVICE_TOKEN|procurement\/orders|authorization/i)
|
||||
assert.doesNotMatch(source, /\bfetch\s*\(|https?\.request|child_process\.exec\b/)
|
||||
}],
|
||||
["human output stops before the economic boundary", () => {
|
||||
const result = runCli([
|
||||
"ito",
|
||||
"rent",
|
||||
"--accelerator", "h100",
|
||||
"--count", "1",
|
||||
"--hours", "24",
|
||||
"--no-open",
|
||||
])
|
||||
|
||||
assert.strictEqual(result.status, 0, result.stderr)
|
||||
assert.match(result.stdout, /manual copy/i)
|
||||
assert.match(result.stdout, /sandbox-only/i)
|
||||
assert.match(result.stdout, /sign-in may be required/i)
|
||||
assert.doesNotMatch(result.stdout, /signed-in/i)
|
||||
assert.match(result.stdout, /does not file an RFQ/i)
|
||||
assert.match(result.stdout, /Stop before ["“]Pay & buy["”]/i)
|
||||
assert.match(result.stdout, /no supported structured ECC intake/i)
|
||||
}],
|
||||
["npm welcome remains POSIX-safe around apostrophes", () => {
|
||||
const npmExecutable = process.platform === "win32" ? "npm.cmd" : "npm"
|
||||
const result = spawnSync(npmExecutable, ["run", "welcome"], {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: "utf8",
|
||||
shell: process.platform === "win32",
|
||||
})
|
||||
assert.strictEqual(result.status, 0, result.stderr)
|
||||
assert.match(result.stdout, /Itô/)
|
||||
}],
|
||||
]
|
||||
|
||||
let passed = 0
|
||||
let failed = 0
|
||||
for (const [name, fn] of tests) {
|
||||
if (runTest(name, fn)) passed += 1
|
||||
else failed += 1
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`)
|
||||
console.log(`Failed: ${failed}`)
|
||||
process.exit(failed > 0 ? 1 : 0)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -55,6 +55,7 @@ function buildExpectedPublishPaths(repoRoot) {
|
||||
"scripts/work-items.js",
|
||||
"scripts/install-apply.js",
|
||||
"scripts/install-plan.js",
|
||||
"scripts/ito.js",
|
||||
"scripts/list-installed.js",
|
||||
"scripts/loop-status.js",
|
||||
"scripts/observability-readiness.js",
|
||||
@@ -143,6 +144,7 @@ function main() {
|
||||
"scripts/ci/supply-chain-advisory-sources.js",
|
||||
"scripts/consult.js",
|
||||
"scripts/control-pane.js",
|
||||
"scripts/ito.js",
|
||||
"scripts/discussion-audit.js",
|
||||
"scripts/operator-readiness-dashboard.js",
|
||||
"scripts/preview-pack-smoke.js",
|
||||
|
||||
Reference in New Issue
Block a user