fix ito trade planner safety contract

This commit is contained in:
Affaan Mustafa
2026-08-07 14:51:46 -04:00
parent 4162cc1fc2
commit 5648a324e6
2 changed files with 210 additions and 10 deletions
+97 -10
View File
@@ -10,8 +10,8 @@ metadata:
Use this skill when a user wants a structured worksheet for a prediction-market
idea, basket adjustment, venue comparison, or manual execution plan.
The skill is intentionally non-executing. It produces checklists and parameter
tables the user can review manually.
The skill is intentionally non-executing. It produces indicative, non-executable
checklists and parameter tables the user can review manually.
## Guardrails
@@ -20,16 +20,58 @@ tables the user can review manually.
- Do not place, cancel, route, or sign orders.
- Do not request private keys, seed phrases, exchange passwords, or wallet
credentials.
- Require explicit user approval before any workflow moves from research to
execution-capable tooling.
- Require a separate workflow and explicit user approval before moving from
research to execution-capable tooling. This approval does not authorize this
skill to execute anything.
- If execution is requested, stop after the worksheet without invoking, calling,
or opening an execution-capable tool or venue.
## Read-Only API And Authentication Boundary
The canonical developer surface is `https://itomarkets.com/api/v1`. Use only
authenticated `GET` endpoints requiring `baskets:read` or `markets:read`, either
with HTTPS and `Authorization: Bearer $ITO_API_KEY` or the official
`ito-markets` Python SDK. Trading is not part of this API.
On first use, check for an already configured key with exactly `baskets:read` and
`markets:read` without printing it. Least-privilege public keys use the `bkt_*`
form and are operator-issued; the dashboard's **Settings -> Keys & credentials**
flow issues a broader `ito_*` automation key. Do not create or rotate that broader
key merely to unblock this skill. If a scoped key is unavailable, report the
read-only API route as blocked and continue with clearly labeled public or user-
supplied inputs. Key issuance creates persistent access and needs confirmation in
the controlling harness. After the user or operator stores the one-time value
securely, return control to the originating agent and run one minimal
`GET /baskets` auth probe. This API does not use device authorization or device
login; do not invent a verification-code handoff.
The `ecc ito` bridge is a separate compute-procurement surface. Do not use
`ecc ito login`, `ecc ito find`, or its MCP tools for prediction-market data or
trade planning. Never print, log, persist, or place `ITO_API_KEY` in arguments,
reports, screenshots, tracked files, or chat. Retrieve only the minimum field at
runtime and keep it in process memory.
Mark API observations indicative. Use `GET /baskets`,
`GET /baskets/{basket_id}`, `GET /baskets/{basket_id}/price`,
`GET /baskets/{basket_id}/underlyers`, `GET /markets/search`, and
`GET /markets/{market_id}` as needed. Do not use write or backtest submission
endpoints for a trade-planning worksheet.
## Planning Workflow
1. Restate the user's idea as a neutral hypothesis.
2. Identify markets, venues, underliers, resolution rules, fees, and data
freshness constraints.
3. If `ITO_API_KEY` is configured and requested, read Itô basket metadata.
4. Build a manual worksheet:
3. If the user requested live Itô data, make the smallest authenticated read and
record the endpoint URL and `retrieved_at` timestamp. Never infer a live price
from stale, missing, or inaccessible data; use `unknown`.
4. Collect constraints without inventing values: jurisdiction/account
eligibility, venue, market identifier, side (if the user supplied one),
limit, time-in-force, maximum spend, fees, liquidity/slippage boundary,
resolution rule, and decision deadline. Missing constraints remain `unknown`.
5. Run `prediction-market-risk-review` before discussing automation, keys,
venue auth, capital constraints, or a manual action link.
6. Build a manual worksheet:
- market/underlier
- venue
- data source
@@ -38,8 +80,24 @@ tables the user can review manually.
- liquidity caveat
- open questions
- manual action link or next review step
5. Run `prediction-market-risk-review` before discussing automation, keys,
venue auth, or capital constraints.
7. If the user asks to continue toward execution, list the unresolved gates and
request separate explicit confirmation in the future execution-capable
workflow. Do not treat confirmation given during planning as an order.
## Recovery And Failure States
- On `401`, set `plan_status: blocked` and ask the user to inspect or replace the
key in Settings. On `403`, report the missing read scope; never request a write
scope for this skill. Redact any credential-like text.
- On `429`, honor `Retry-After` once within the user's time budget. Do not loop or
exceed the documented read budget of 120 requests per minute.
- On timeout or ambiguous transport failure, set affected values to `unknown`.
Retry at most once for a read; never turn a read failure into a write.
- On expired or revoked access, stop, redact server details that could contain
credentials, and direct the user to Settings. Never weaken scopes or reuse
cached secrets.
- Public and private sources must be labeled separately. Do not present cached
or fixture data as live behavior.
## Allowed Language
@@ -58,9 +116,38 @@ Avoid:
- "risk-free"
- "optimal size"
## Output Contract
## Structured Output Contract
End every plan with:
Return this shape in Markdown or YAML. Preserve `unknown` rather than guessing.
```yaml
plan_status: ready_for_manual_review | blocked
mode: indicative_non_executable
hypothesis: "neutral restatement"
markets:
- market: "identifier or unknown"
venue: "venue or unknown"
observable_status: "value or unknown"
source_url: "source URL or unknown"
retrieved_at: "ISO-8601 timestamp or unknown"
resolution_rule: "summary or unknown"
liquidity_caveat: "text or unknown"
constraints:
jurisdiction_eligibility: "confirmed | unconfirmed | unknown"
limit: "user supplied value or unknown"
maximum_spend: "user supplied value or unknown"
fees: "value or unknown"
decision_deadline: "value or unknown"
data_freshness: "timestamp and caveats"
risk_review:
status: pass | warn | fail | not_run
findings: []
blocked_actions:
- "order placement, cancellation, routing, signing, and submission"
next_safe_step: "one non-executing review action"
```
End every plan with exactly:
```text
This is a planning worksheet, not investment or trading advice. Review venue
+113
View File
@@ -0,0 +1,113 @@
/**
* Contract tests for the installable Itô trade-planner skill.
*/
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const REPO_ROOT = path.join(__dirname, '..', '..');
function read(relativePath) {
return fs.readFileSync(path.join(REPO_ROOT, relativePath), 'utf8');
}
function readJson(relativePath) {
return JSON.parse(read(relativePath));
}
function runTest(name, fn) {
try {
fn();
console.log(`${name}`);
return true;
} catch (error) {
console.log(`${name}`);
console.error(` ${error.message}`);
return false;
}
}
function main() {
console.log('\n=== Testing Itô trade-planner skill surface ===\n');
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 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}`);
}
}],
['installs with the complete risk-review dependency pack', () => {
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');
for (const requiredPath of [
'skills/ito-trade-planner',
'skills/prediction-market-risk-review',
]) {
assert.ok(module.paths.includes(requiredPath), `${requiredPath} is not installed`);
}
assert.strictEqual(module.defaultInstall, false);
assert.ok(readJson('package.json').files.includes('skills/ito-trade-planner/'));
}],
['keeps indicative planning separate from executable behavior', () => {
assert.match(skill, /indicative/i);
assert.match(skill, /not executable|non-executable/i);
assert.match(skill, /Trading is not part of this API/i);
assert.match(skill, /do not (?:place|cancel|route|sign|submit)/i);
assert.match(skill, /separate[^.]*explicit (?:user )?(?:approval|confirmation)/i);
assert.match(skill, /stop[^.]*without (?:invoking|calling|opening)/i);
assert.doesNotMatch(skill, /(?:run|invoke|call) `?ecc ito (?:find|status)/i);
}],
['documents the real API-key first run and rejects invented device login', () => {
assert.match(skill, /https:\/\/itomarkets\.com\/api\/v1/);
assert.match(skill, /Authorization: Bearer/);
assert.match(skill, /baskets:read/);
assert.match(skill, /markets:read/);
assert.match(skill, /bkt_\*/);
assert.match(skill, /broader `ito_\*` automation key/);
assert.match(skill, /Do not create or rotate that broader/);
assert.match(skill, /ito-markets/);
assert.match(skill, /Settings/i);
assert.match(skill, /originating agent/i);
assert.match(skill, /does not use device (?:authorization|login)/i);
assert.match(skill, /do not use\s+`ecc ito login`/i);
assert.match(skill, /never (?:print|log|persist)[^.]*ITO_API_KEY/i);
}],
['defines structured output, provenance, and recovery states', () => {
for (const field of [
'plan_status', 'mode', 'hypothesis', 'markets', 'constraints',
'data_freshness', 'risk_review', 'blocked_actions', 'next_safe_step',
]) {
assert.match(skill, new RegExp(`\\b${field}\\b`), `missing output field: ${field}`);
}
assert.match(skill, /source URL/i);
assert.match(skill, /retrieved_at/i);
assert.match(skill, /timeout/i);
assert.match(skill, /revok/i);
assert.match(skill, /401/);
assert.match(skill, /403/);
assert.match(skill, /429/);
assert.match(skill, /Retry-After/);
assert.match(skill, /redact/i);
assert.match(skill, /unknown/i);
}],
['preserves the non-advisory disclaimer exactly', () => {
assert.match(skill, /This is a planning worksheet, not investment or trading advice\. Review venue\n+rules and make any trading decisions yourself\./);
}],
];
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();