/** * Contract and lifecycle tests for the consolidated Itô baskets data skill. * No test contacts Itô, opens a browser, or submits an RFQ/order. */ "use strict"; const assert = require("assert"); const fs = require("fs"); const path = require("path"); const { spawnSync } = require("child_process"); const { parseArgs, run } = require("../../skills/ito-baskets/scripts/ito-baskets"); const REPO_ROOT = path.join(__dirname, "..", ".."); const SKILL_DIR = path.join(REPO_ROOT, "skills", "ito-baskets"); const SKILL_PATH = path.join(SKILL_DIR, "SKILL.md"); const CLIENT = path.join(SKILL_DIR, "scripts", "ito-baskets.js"); function readJson(relativePath) { return JSON.parse(fs.readFileSync(path.join(REPO_ROOT, relativePath), "utf8")); } function invoke(args, env = {}) { return spawnSync(process.execPath, [CLIENT, "--json", ...args], { encoding: "utf8", env: { PATH: process.env.PATH, ...env }, timeout: 5000, }); } const tests = []; function test(name, fn) { tests.push([name, fn]); } test("has valid discoverable frontmatter and consolidated trigger phrases", () => { const skill = fs.readFileSync(SKILL_PATH, "utf8"); assert.match(skill, /^---\nname: ito-baskets\ndescription: [^\n]+\nmetadata:\n {2}origin: ECC\n/); assert.match(skill, /aliases: ito-basket-compare, ito-market-intelligence, ito-data-atlas-agent, ito-trade-planner/); const lower = skill.toLowerCase(); for (const phrase of [ "compare this basket", "basket vs", "gap analysis", "stale assumptions", "watchlist", "event discovery", "venue comparison", "basket theme", "market brief", "planning worksheet", "basket catalog", "index", ]) { assert.ok(lower.includes(phrase), `missing trigger phrase: ${phrase}`); } }); test("states that it replaces the four former skills and routes their requests", () => { const skill = fs.readFileSync(SKILL_PATH, "utf8"); assert.match(skill, /replaces the\s+former `ito-basket-compare`, `ito-market-intelligence`, `ito-data-atlas-agent`,\s+and `ito-trade-planner`/); const modules = readJson("manifests/install-modules.json").modules; const module = modules.find((candidate) => candidate.id === "prediction-market-skills"); assert.ok(module, "prediction-market-skills module is missing"); assert.ok(module.paths.includes("skills/ito-baskets"), "consolidated skill is not installed by the module"); for (const removed of ["ito-basket-compare", "ito-market-intelligence", "ito-data-atlas-agent", "ito-trade-planner"]) { assert.ok(!module.paths.includes(`skills/${removed}`), `removed skill still in module: ${removed}`); assert.ok(!fs.existsSync(path.join(REPO_ROOT, "skills", removed)), `removed skill directory still exists: ${removed}`); } assert.strictEqual(module.defaultInstall, false); const packed = readJson("package.json").files; assert.ok(packed.includes("skills/ito-baskets/"), "consolidated skill missing from npm files"); for (const removed of ["ito-basket-compare", "ito-market-intelligence", "ito-data-atlas-agent", "ito-trade-planner"]) { assert.ok(!packed.includes(`skills/${removed}/`), `removed skill still packed: ${removed}`); } }); test("preserves the non-advisory, non-executing boundary from all four predecessors", () => { const skill = fs.readFileSync(SKILL_PATH, "utf8"); assert.match(skill, /never advise the user to buy, sell, hold, hedge, lever, allocate, or size/i); assert.match(skill, /never place, cancel, route, sign, simulate, or submit/i); assert.match(skill, /no execution path and no\s+confirmation can give it one/i); assert.match(skill, /`ecc ito find` submits an\s+authenticated RFQ/); assert.match(skill, /`ecc ito status` reads RFQ\/procurement status, not\s+basket data/); assert.match(skill, /UNSUPPORTED_OPERATION/); assert.match(skill, /prediction-market-risk-review/); assert.doesNotMatch(skill, /(?:run|invoke|call) `?ecc ito (?:find|status)/i); assert.match(skill, /never call a trade good, bad, best, optimal,\s+guaranteed, or risk-free/i); for (const advisory of [/\byou should buy\b/i, /\byou should sell\b/i, /\bbest trade\b/i, /\boptimal size\b/i]) { assert.doesNotMatch(skill, advisory); } }); test("documents anonymous, keyed, and SDK surfaces with scope and credential separation", () => { const skill = fs.readFileSync(SKILL_PATH, "utf8"); assert.match(skill, /\/api\/baskets\/bootstrap\?stream=1/); assert.match(skill, /ito\.public_basket_read\.v1/); assert.match(skill, /\/api\/markets\/hot/); assert.match(skill, /Keyed developer API\*\* at/); assert.match(skill, /https:\/\/itomarkets\.com\/api\/v1(?!\d)/, "missing versioned keyed API path"); assert.match(skill, /Authorization: Bearer/); assert.match(skill, /baskets:read/); assert.match(skill, /markets:read/); assert.match(skill, /bkt_\*/); assert.match(skill, /ito-markets/); assert.match(skill, /compute device credential[\s\S]*never a\s+substitute|never a\s+substitute[\s\S]*compute device credential/i); assert.match(skill, /never uses device authorization or `ecc ito login`/i); assert.match(skill, /x-ito-edge-cache/); assert.match(skill, /never send credentials to these routes/i); }); test("documents provenance, deterministic normalization, and recovery contracts", () => { const skill = fs.readFileSync(SKILL_PATH, "utf8"); for (const field of ["source_type", "source_uri", "retrieved_at", "as_of", "freshness_status", "access_mode"]) { assert.match(skill, new RegExp(`\\b${field}\\b`), `missing provenance field: ${field}`); } for (const code of ["INVALID_INPUT", "AUTH_MISSING", "AUTH_REJECTED", "AUTH_FORBIDDEN", "RATE_LIMITED", "TIMEOUT", "UPSTREAM_ERROR", "INVALID_RESPONSE", "STALE_SOURCE", "UNSUPPORTED_OPERATION"]) { assert.ok(skill.includes(code), `missing error code: ${code}`); } assert.match(skill, /Unicode NFKC/); assert.match(skill, /24 hours for market\/basket/); assert.match(skill, /30 days for notes\/research/); assert.match(skill, /identical output/i); assert.match(skill, /match.*conflict.*missing.*stale/is); assert.match(skill, /120 requests\/minute/); assert.match(skill, /untrusted data/i); assert.match(skill, /never treat[\s\S]*draft[\s\S]*approval|confirmation during planning is never an order/i); }); test("keeps every mode disclaimer exact", () => { const skill = fs.readFileSync(SKILL_PATH, "utf8"); assert.ok(skill.includes("This is market data, not investment or trading advice.")); assert.ok(skill.includes("This comparison is informational and not investment or trading advice.")); assert.ok(skill.includes("This is a planning worksheet, not investment or trading advice. Review venue rules and make any trading decisions yourself.")); }); test("ships agent metadata for the consolidated skill", () => { const agentMetadata = fs.readFileSync(path.join(SKILL_DIR, "agents", "openai.yaml"), "utf8"); assert.match(agentMetadata, /display_name: "Itô Baskets"/); assert.match(agentMetadata, /default_prompt: "Use \$ito-baskets /); }); test("keyed client keeps the GET-only contract and never echoes credentials", async () => { let result = invoke(["search-markets"]); assert.strictEqual(result.status, 1); assert.strictEqual(JSON.parse(result.stderr).error.code, "AUTH_MISSING"); assert.match(JSON.parse(result.stderr).error.message, /anonymous basket-index\/basket-detail/); result = invoke(["search-markets"], { ITO_API_KEY: "secret", ITO_MARKET_API_URL: "http://example.com/api/v1" }); assert.strictEqual(JSON.parse(result.stderr).error.code, "CONFIG"); assert.ok(!result.stderr.includes("secret")); const fetchSuccess = async (url, request) => { assert.strictEqual(request.method, "GET"); assert.strictEqual(request.headers.Authorization, "Bearer test-key"); assert.match(url.toString(), /\/markets\/search\?platform=all&limit=1$/); return new Response(JSON.stringify({ data: [{ market_id: "m1", title: "Example" }], meta: { updated_at: "2026-08-07T12:00:00Z" } }), { status: 200, headers: { "x-ratelimit-limit": "120", "x-ratelimit-remaining": "119", "x-ratelimit-reset": "1786128733" } }); }; const payload = await run(parseArgs(["node", CLIENT, "search-markets", "--platform", "all", "--limit", "1"]), { ITO_API_KEY: "test-key" }, fetchSuccess); assert.strictEqual(payload.ok, true); assert.strictEqual(payload.access_mode, "keyed"); assert.strictEqual(payload.source.provider, "Itô Markets"); assert.strictEqual(payload.freshness.source_updated_at, "2026-08-07T12:00:00Z"); assert.deepStrictEqual(payload.rate_limit, { limit: 120, remaining: 119, reset_epoch: 1786128733 }); assert.deepStrictEqual(payload.data, [{ market_id: "m1", title: "Example" }]); assert.ok(!JSON.stringify(payload).includes("test-key")); const fetchPage = async (url) => { assert.match(url.toString(), /\/baskets\?page=2&per_page=5$/); return new Response(JSON.stringify({ data: [], meta: { page: 2, per_page: 5 } }), { status: 200 }); }; const pagePayload = await run(parseArgs(["node", CLIENT, "list-baskets", "--page", "2", "--per-page", "5"]), { ITO_API_KEY: "test-key" }, fetchPage); assert.strictEqual(pagePayload.meta.per_page, 5); await assert.rejects( run(parseArgs(["node", CLIENT, "list-baskets"]), { ITO_API_KEY: "revoked" }, async () => new Response("{}", { status: 401 })), (error) => error.code === "AUTH_REJECTED" && !error.message.includes("revoked") ); await assert.rejects( run(parseArgs(["node", CLIENT, "list-baskets"]), { ITO_API_KEY: "key" }, async () => new Response("{}", { status: 429, headers: { "retry-after": "7" } })), (error) => error.code === "RATE_LIMITED" && error.details.retry_after_seconds === 7 ); await assert.rejects( run(parseArgs(["node", CLIENT, "--timeout-ms", "100", "list-baskets"]), { ITO_API_KEY: "key" }, async (_url, request) => new Promise((_resolve, reject) => { request.signal.addEventListener("abort", () => reject(Object.assign(new Error("aborted"), { name: "AbortError" }))); })), (error) => error.code === "TIMEOUT" && !error.message.includes("key") ); await assert.rejects( run(parseArgs(["node", CLIENT, "list-baskets"]), { ITO_API_KEY: "key" }, async () => new Response("bad gateway", { status: 502 })), (error) => error.code === "INVALID_RESPONSE" && !error.message.includes("bad gateway") ); }); test("anonymous index commands never send a credential and validate the public contract", async () => { const indexBody = { contractVersion: "ito.public_basket_read.v1", generated_at: "2026-08-12T00:00:00Z", baskets: [{ basket_id: "b1" }] }; const fetchIndex = async (url, request) => { assert.strictEqual(request.method, "GET"); assert.strictEqual(request.headers.Authorization, undefined); assert.strictEqual(url.hostname, "itomarkets.com"); assert.strictEqual(url.pathname, "/api/baskets/bootstrap"); assert.strictEqual(url.search, "?stream=1"); return new Response(JSON.stringify(indexBody), { status: 200, headers: { "cache-control": "public, max-age=30", "x-ito-edge-cache": "HIT" } }); }; // Even with ITO_API_KEY configured, anonymous commands must not transmit it. const payload = await run(parseArgs(["node", CLIENT, "basket-index"]), { ITO_API_KEY: "must-not-leak" }, fetchIndex); assert.strictEqual(payload.ok, true); assert.strictEqual(payload.access_mode, "anonymous"); assert.strictEqual(payload.freshness.source_updated_at, "2026-08-12T00:00:00Z"); assert.strictEqual(payload.cache.edge_cache, "HIT"); assert.ok(!JSON.stringify(payload).includes("must-not-leak")); await assert.rejects( run(parseArgs(["node", CLIENT, "basket-index"]), {}, async () => new Response(JSON.stringify({ contractVersion: "ito.public_basket_read.v0", baskets: [] }), { status: 200 })), (error) => error.code === "INVALID_RESPONSE" && /contract changed or missing/.test(error.message) ); await assert.rejects( run(parseArgs(["node", CLIENT, "basket-index"]), {}, async () => new Response(JSON.stringify({ contractVersion: "ito.public_basket_read.v1", generated_at: "2026-08-12T00:00:00Z" }), { status: 200 })), (error) => error.code === "INVALID_RESPONSE" && /baskets array/.test(error.message) ); await assert.rejects( run(parseArgs(["node", CLIENT, "basket-detail", "--basket-id", "b1"]), {}, async () => new Response(JSON.stringify({ contractVersion: "ito.public_basket_read.v1", generated_at: "2026-08-12T00:00:00Z", basket: {}, underlyers: [], charts: {}, metrics: {} }), { status: 200 })), (error) => error.code === "INVALID_RESPONSE" && /commentary/.test(error.message) ); const detailBody = { contractVersion: "ito.public_basket_read.v1", generated_at: "2026-08-12T00:00:00Z", basket: { basket_id: "b1" }, underlyers: [], charts: {}, metrics: {}, commentary: {} }; const detail = await run(parseArgs(["node", CLIENT, "basket-detail", "--basket-id", "b1"]), {}, async (url) => { assert.strictEqual(url.hostname, "itomarkets.com"); assert.strictEqual(url.pathname, "/api/baskets/b1/bootstrap"); return new Response(JSON.stringify(detailBody), { status: 200 }); }); assert.strictEqual(detail.ok, true); assert.strictEqual(detail.access_mode, "anonymous"); }); test("client rejects unknown commands, mutations, and bad options before any fetch", () => { for (const args of [["create-basket"], ["delete-basket"], ["order"], ["basket-detail"], ["basket-index", "--page", "1"]]) { const result = invoke(args, { ITO_API_KEY: "key" }); assert.strictEqual(result.status, 2, `expected USAGE exit 2 for: ${args.join(" ")}`); assert.strictEqual(JSON.parse(result.stderr).error.code, "USAGE"); } fs.accessSync(CLIENT, fs.constants.R_OK); }); (async () => { let passed = 0; let failed = 0; for (const [name, fn] of tests) { try { await fn(); console.log(` ✓ ${name}`); passed += 1; } catch (error) { console.log(` ✗ ${name}`); console.error(` ${error.message}`); failed += 1; } } console.log(`${passed} passed, ${failed} failed`); if (failed > 0) process.exitCode = 1; else console.log("PASS ito-baskets skill contract"); })();