diff --git a/scripts/ito.js b/scripts/ito.js index 42326a669..d4420668c 100755 --- a/scripts/ito.js +++ b/scripts/ito.js @@ -15,12 +15,18 @@ const SUPPORTED_COMMANDS = Object.freeze([ "serve", "train", "workload-status", "workload-cancel", "workload-cleanup", ]); const CANONICAL_PACKAGE_PATH = "cli/ito-compute-cli"; -const CANONICAL_ENTRY_SEGMENTS = Object.freeze([ +const SOURCE_CANONICAL_ENTRY_SEGMENTS = Object.freeze([ ...CANONICAL_PACKAGE_PATH.split("/"), "dist", "bin", "ito.js", ]); +const NPM_CANONICAL_ENTRY_SEGMENTS = Object.freeze([ + "ito-compute-cli", + "dist", + "bin", + "ito.js", +]); const EXECUTABLE_OVERRIDE = "ECC_ITO_CLI_EXECUTABLE"; const MAX_OUTPUT_BYTES = 10 * 1024 * 1024; const NODE_QUALIFICATION_TIMEOUT_MS = 31 * 60 * 1000; @@ -213,6 +219,10 @@ function parseArgs(argv, environment = process.env) { "--entitlement", "--artifact-ref", "--image-digest", "--max-runtime-seconds", "--max-incremental-cost-usd", "--idempotency-key", ], ["--checkpoint-ref"]); + const costIndex = withoutJson.indexOf("--max-incremental-cost-usd"); + if (withoutJson[costIndex + 1] !== "0") { + throw new Error("--max-incremental-cost-usd must be exactly 0 until workload accounting is deployed."); + } } if (command === "workload-status" || command === "workload-cancel" || command === "workload-cleanup") { validateRunLifecycleArgs(withoutJson); @@ -275,13 +285,15 @@ function isCanonicalItoEntry(candidate) { .normalize(candidate) .split(path.sep) .filter(Boolean); - if (pathSegments.length < CANONICAL_ENTRY_SEGMENTS.length) return false; - const candidateTail = pathSegments.slice(-CANONICAL_ENTRY_SEGMENTS.length); - return candidateTail.every((segment, index) => { - const expected = CANONICAL_ENTRY_SEGMENTS[index]; - return process.platform === "win32" - ? segment.toLowerCase() === expected.toLowerCase() - : segment === expected; + return [SOURCE_CANONICAL_ENTRY_SEGMENTS, NPM_CANONICAL_ENTRY_SEGMENTS].some((expectedSegments) => { + if (pathSegments.length < expectedSegments.length) return false; + const candidateTail = pathSegments.slice(-expectedSegments.length); + return candidateTail.every((segment, index) => { + const expected = expectedSegments[index]; + return process.platform === "win32" + ? segment.toLowerCase() === expected.toLowerCase() + : segment === expected; + }); }); } diff --git a/skills/ito-inference/SKILL.md b/skills/ito-inference/SKILL.md index 31ad30234..5744d22b1 100644 --- a/skills/ito-inference/SKILL.md +++ b/skills/ito-inference/SKILL.md @@ -30,7 +30,7 @@ ecc ito serve \ --artifact-ref \ --image-digest \ --max-runtime-seconds \ - --max-incremental-cost-usd \ + --max-incremental-cost-usd 0 \ --idempotency-key ``` @@ -65,7 +65,8 @@ addresses, and do not claim endpoint readiness without functional evidence. ## What the backend does (Layer 0.2) -The desk backend, not ECC, runs the stages, and this skill only reports them: +These stages are the target acceptance contract for a future reviewed provider +adapter; they are not claims about deployed execution today: 1. Fabric gate — never launch on unverified metal. Blocks below 80% of fabric-expected bus bandwidth; advisory between 80% and 92%; fails loud on diff --git a/skills/ito-training/SKILL.md b/skills/ito-training/SKILL.md index 294ea9e5c..8e0df7a23 100644 --- a/skills/ito-training/SKILL.md +++ b/skills/ito-training/SKILL.md @@ -29,7 +29,7 @@ ecc ito train \ --artifact-ref \ --image-digest \ --max-runtime-seconds \ - --max-incremental-cost-usd \ + --max-incremental-cost-usd 0 \ --idempotency-key \ [--checkpoint-ref ] ``` @@ -77,8 +77,8 @@ cancel/cleanup lifecycle. ## What the backend does (Layer 0.3) -The desk backend runs a staged, eval-gated pipeline; this skill reports stage -gates and never overrides one: +These stages are the target acceptance contract for a future reviewed provider +adapter; they are not claims about deployed execution today: 1. Data prep — manifest, dedup, decontamination against the eval suite; 150M-ladder decision job as the cheap pre-check for custom data. diff --git a/tests/ci/ito-inference-skill.test.js b/tests/ci/ito-inference-skill.test.js index 263e6ade2..6e7c24ab4 100644 --- a/tests/ci/ito-inference-skill.test.js +++ b/tests/ci/ito-inference-skill.test.js @@ -33,43 +33,62 @@ function test(name, fn) { console.log("\n=== Testing Itô inference skill lifecycle ===\n"); const results = [ - test("uses canonical ito-inference and supersedes a standalone ito-serve skill", () => { + test("uses the canonical inference surface and truthful availability boundary", () => { const skill = read("skills/ito-inference/SKILL.md"); assert.match(skill, /^name: ito-inference$/m); - assert.ok(!fs.existsSync(path.join(REPO_ROOT, "skills", "ito-serve"))); - assert.match(skill, /serve a model|OpenAI-compatible endpoint/i); + assert.match(skill, /self-host|serve a model|OpenAI-compatible endpoint/i); + assert.doesNotMatch(skill, /^name: ito-serve$/m); assert.match(skill, /completed booking/i); assert.match(skill, /never books, reserves,\s+or spends/i); - assert.match(skill, /server-verified, active compute entitlement/i); - assert.match(skill, /ECC receives no confirmation secret/i); + assert.match(skill, /production entitlement, confirmation, credential-broker, and executor\s+adapters are not yet configured/i); assert.match(skill, /fails closed before contacting\s+a node or provider/i); - assert.match(skill, /never substitute direct SSH, a local runner, or a purchase\s+endpoint/i); - assert.match(skill, /Actual serving execution is \*\*NOT READY\*\*/i); + assert.match(skill, /Never substitute direct SSH, a local runner, or a purchase\s+endpoint/i); assert.doesNotMatch(skill, /ssh\s+root@|serve-status\.sh/i); - assert.doesNotMatch(skill, /ITO_WORKLOAD_CONFIRMATION_TOKEN|--confirmation-token|--api-key|--access-token/i); + for (const gate of [ + /server-verified, active compute entitlement/i, + /single-use same-origin confirmation state/i, + /approve the exact manifest and ceilings/i, + /idempotency/i, + /workload-status/i, + /workload-cancel/i, + /workload-cleanup/i, + /target acceptance contract/i, + /not claims about deployed execution/i, + ]) assert.match(skill, gate); + assert.doesNotMatch(skill, /--confirmation-ref|--confirmation-token|--api-key|--access-token/i); }), - test("routes typed serving through the bridge while rejecting the superseded interface", () => { + test("delegates canonical serving through the executable bridge without confirmation transport", () => { const bridge = read("scripts/ito.js"); - assert.match(bridge, /SUPPORTED_COMMANDS[\s\S]*?"serve"/); - assert.doesNotMatch(bridge, /ITO_WORKLOAD_CONFIRMATION_TOKEN|--confirmation-token/i); + assert.match(bridge, /SUPPORTED_COMMANDS[\s\S]+?"serve"[\s\S]+?"train"[\s\S]+?"workload-status"/); + assert.match(bridge, /Unsupported Itô command/); + assert.doesNotMatch(bridge, /ITO_WORKLOAD_CONFIRMATION_TOKEN|X-Ito-Workload-Confirmation/); const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ecc-ito-serve-reject-")); try { const canonicalDir = path.join(fixtureRoot, "cli", "ito-compute-cli", "dist", "bin"); fs.mkdirSync(canonicalDir, { recursive: true }); - const marker = path.join(fixtureRoot, "spawned"); + const marker = path.join(fixtureRoot, "invocation.json"); const executable = path.join(canonicalDir, "ito.js"); - fs.writeFileSync(executable, `require("fs").writeFileSync(${JSON.stringify(marker)}, "spawned");\n`); + fs.writeFileSync(executable, `require("fs").writeFileSync(${JSON.stringify(marker)}, JSON.stringify({ argv: process.argv.slice(2), confirmation: process.env.ITO_WORKLOAD_CONFIRMATION_TOKEN }));\n`); const result = spawnSync(process.execPath, [ path.join(REPO_ROOT, "scripts", "ecc.js"), "ito", "serve", - "--booking", "booking_test", "--model", "model_test", + "--entitlement", "ent_test", "--artifact-ref", "model@sha256:test", + "--image-digest", `sha256:${"d".repeat(64)}`, + "--max-runtime-seconds", "300", "--max-incremental-cost-usd", "0", + "--idempotency-key", "idem_test_001", ], { encoding: "utf8", env: { ...process.env, ECC_ITO_CLI_EXECUTABLE: executable }, }); - assert.notStrictEqual(result.status, 0); - assert.match(result.stderr, /Serve\/train accept only typed workload options/); - assert.ok(!fs.existsSync(marker), "rejected legacy serve interface spawned the canonical child"); + assert.strictEqual(result.status, 0, result.stderr); + const invocation = JSON.parse(fs.readFileSync(marker, "utf8")); + assert.deepStrictEqual(invocation.argv, [ + "serve", "--entitlement", "ent_test", "--artifact-ref", "model@sha256:test", + "--image-digest", `sha256:${"d".repeat(64)}`, + "--max-runtime-seconds", "300", "--max-incremental-cost-usd", "0", + "--idempotency-key", "idem_test_001", + ]); + assert.strictEqual(invocation.confirmation, undefined); } finally { fs.rmSync(fixtureRoot, { recursive: true, force: true }); } diff --git a/tests/ci/ito-trade-planner-skill.test.js b/tests/ci/ito-trade-planner-skill.test.js index 55d6265ff..eec5dc4ac 100644 --- a/tests/ci/ito-trade-planner-skill.test.js +++ b/tests/ci/ito-trade-planner-skill.test.js @@ -34,7 +34,7 @@ function main() { const skill = read('skills/ito-trade-planner/SKILL.md'); const tests = [ ['has portable discovery metadata and representative triggers', () => { - assert.match(skill, /^---\nname: ito-trade-planner\ndescription: [^\n]+\nmetadata:\n {2}origin: ECC\n---/); + assert.match(skill, /^---\nname: ito-trade-planner\ndescription: [^\n]+\nmetadata:\n origin: ECC\n---/); for (const trigger of ['trade plan', 'planning worksheet', 'venue comparison', 'basket adjustment']) { assert.match(skill, new RegExp(trigger, 'i'), `missing trigger phrase: ${trigger}`); } diff --git a/tests/scripts/ito-cli-bridge.test.js b/tests/scripts/ito-cli-bridge.test.js index d2f9e8ab1..4f2db1323 100644 --- a/tests/scripts/ito-cli-bridge.test.js +++ b/tests/scripts/ito-cli-bridge.test.js @@ -63,18 +63,12 @@ function runCliAndObserveFirstOutput(args, environment = {}) { }); } -function makeItoProbe(exitCode = 0) { +function makeItoProbe(exitCode = 0, layout = "source") { const directory = fs.mkdtempSync(path.join(os.tmpdir(), "ecc-ito-cli-")); const log = path.join(directory, "invocation.json"); - const script = path.join( - directory, - "ito-cloud-runtime", - "cli", - "ito-compute-cli", - "dist", - "bin", - "ito.js" - ); + const script = layout === "npm" + ? path.join(directory, "lib", "node_modules", "ito-compute-cli", "dist", "bin", "ito.js") + : path.join(directory, "ito-cloud-runtime", "cli", "ito-compute-cli", "dist", "bin", "ito.js"); const executable = script; fs.mkdirSync(path.dirname(script), { recursive: true }); fs.writeFileSync( @@ -183,6 +177,36 @@ async function main() { fs.rmSync(allowed.directory, { recursive: true, force: true }); } }], + ["accepts the exact verified global npm package entry", () => { + const probe = makeItoProbe(0, "npm"); + try { + const result = runCli(["ito", "auth"], { + ECC_ITO_CLI_EXECUTABLE: probe.executable, + }); + assert.strictEqual(result.status, 0, result.stderr); + assert.deepStrictEqual(readInvocation(probe).argv, ["auth"]); + } finally { + fs.rmSync(probe.directory, { recursive: true, force: true }); + } + }], + ["rejects nonzero workload cost before spawning", () => { + const probe = makeItoProbe(); + try { + const result = runCli([ + "ito", "serve", "--entitlement", "ent_001", + "--artifact-ref", "model@sha256:test", + "--image-digest", `sha256:${"d".repeat(64)}`, + "--max-runtime-seconds", "300", + "--max-incremental-cost-usd", "0.01", + "--idempotency-key", "idem_001", + ], { ECC_ITO_CLI_EXECUTABLE: probe.executable }); + assert.notStrictEqual(result.status, 0); + assert.match(result.stderr, /must be exactly 0/i); + assert.ok(!fs.existsSync(probe.log)); + } finally { + fs.rmSync(probe.directory, { recursive: true, force: true }); + } + }], ["rejects untyped workload, node-access, and secret arguments before spawning", () => { const base = [ "ito", "train",