@taifoon/jev 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/bin/run.mjs +5 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pipeline.d.ts +11 -0
- package/dist/pipeline.js +21 -2
- package/dist/wilson.d.ts +49 -0
- package/dist/wilson.js +66 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -118,6 +118,7 @@ Safe by default: your own TypeSafe key only, nothing recorded, nothing signed or
|
|
|
118
118
|
| `--layer <https url>` / `--no-layer` | `https://coord.taifoon.dev` | Where jobs and quotes are read |
|
|
119
119
|
| `--protocol <name>` | from the job id | Evaluator seat: `virtuals-erc8183`, `virtuals-memo-acp`, `bitagent-erc8183`, `assurance-hook`, `judge-adapter` |
|
|
120
120
|
| `--price-usdc <n>` | the job's budget | Price for the premium quote |
|
|
121
|
+
| `--seller-record <k>/<n>` | read from the layer | Price the premium here from the seller's record (k delivered of n graded) with [`@taifoon/jev-wilson`](https://www.npmjs.com/package/@taifoon/jev-wilson): the layer's numbers, no request |
|
|
121
122
|
| `--yes` / `--json` | ask per step / text | Run every step without asking / print the whole trace as JSON |
|
|
122
123
|
|
|
123
124
|
Environment: `TYPESAFE_KEY` (your key; without it a job that needs Jev stops and says where to get one, while a job the
|
package/bin/run.mjs
CHANGED
|
@@ -35,7 +35,7 @@ function factOf(r) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const USAGE = 'jev run [--demo | --job <chain>:<id> | --job-file job.json | --evidence pack.json] [--answers answers.json] [--record none|devnet|base|both] [--layer <https url> | --no-layer] [--protocol <name>] [--price-usdc <n>] [--yes] [--json]';
|
|
38
|
+
const USAGE = 'jev run [--demo | --job <chain>:<id> | --job-file job.json | --evidence pack.json] [--answers answers.json] [--record none|devnet|base|both] [--layer <https url> | --no-layer] [--protocol <name>] [--price-usdc <n>] [--seller-record <k>/<n>] [--yes] [--json]';
|
|
39
39
|
const PROTOCOLS = ['assurance-hook', 'judge-adapter', 'virtuals-erc8183', 'virtuals-memo-acp', 'bitagent-erc8183'];
|
|
40
40
|
/** A stranger's typo must never run something else: every flag is known, every value checked, exit 2 with the fix. */
|
|
41
41
|
function refuse(msg) { process.stderr.write(`jev run: ${msg}\n ${USAGE}\n`); process.exit(2); }
|
|
@@ -56,7 +56,7 @@ export async function run(argv) {
|
|
|
56
56
|
const ansFile = file('--answers');
|
|
57
57
|
const rec = demo ? JSON.parse(readFileSync(new URL('../examples/jobs/base-bitagent-7287.recorded.json', import.meta.url), 'utf8')) : null;
|
|
58
58
|
const network = val('--record') ?? val('--network') ?? 'none'; const layer = noLayer ? false : val('--layer');
|
|
59
|
-
const protocol = val('--protocol'); const price = val('--price-usdc');
|
|
59
|
+
const protocol = val('--protocol'); const price = val('--price-usdc'); const sellerRec = val('--seller-record');
|
|
60
60
|
if (a.length) refuse(`unknown flag ${a[0]}`);
|
|
61
61
|
if (!['none', 'devnet', 'base', 'both'].includes(network)) refuse(`--record is none, devnet, base or both (got ${network})`);
|
|
62
62
|
if (demo && (job || evFile || jobFile || ansFile)) refuse('--demo replays one recorded job: drop --job, --job-file, --evidence and --answers');
|
|
@@ -65,6 +65,8 @@ export async function run(argv) {
|
|
|
65
65
|
if (layer && !/^https:\/\//.test(layer)) refuse('--layer must be an https:// URL');
|
|
66
66
|
if (protocol && !PROTOCOLS.includes(protocol)) refuse(`--protocol is one of ${PROTOCOLS.join(', ')}`);
|
|
67
67
|
if (price !== undefined && !(Number(price) > 0)) refuse(`--price-usdc must be a positive number (got ${price})`);
|
|
68
|
+
const recM = sellerRec === undefined ? null : /^(\d+)\/(\d+)$/.exec(sellerRec);
|
|
69
|
+
if (sellerRec !== undefined && (!recM || Number(recM[1]) > Number(recM[2]) || Number(recM[2]) === 0)) refuse(`--seller-record must be <delivered>/<graded>, e.g. 60/62 (got ${sellerRec})`);
|
|
68
70
|
const { pipeline, STEPS, prepareJob } = await import('../dist/index.js');
|
|
69
71
|
|
|
70
72
|
const t = createTerm({ quiet: json });
|
|
@@ -72,7 +74,7 @@ export async function run(argv) {
|
|
|
72
74
|
t.info(`jev run · ${noLayer ? 'offline' : `layer ${layer ?? 'https://coord.taifoon.dev'}`} · grade on ${demo ? `Jev's recorded answers for ${rec.job.ref} (${rec.job.task})` : ansFile ? 'supplied answers' : process.env.TYPESAFE_KEY ? 'your TypeSafe key' : 'no key: set TYPESAFE_KEY, or try --demo'} · record → ${network === 'none' ? 'none (opt in with --record devnet|base|both)' : network}${process.env.TAIFOON_RELAYER_KEY ? ' + the layer' : ''}`);
|
|
73
75
|
|
|
74
76
|
const trace = await pipeline({
|
|
75
|
-
layer, job, network, protocol, priceUsdc: price ? Number(price) : undefined,
|
|
77
|
+
layer, job, network, protocol, priceUsdc: price ? Number(price) : undefined, sellerRecord: recM ? { k: Number(recM[1]), n: Number(recM[2]) } : undefined,
|
|
76
78
|
evidence: rec ? { subject: rec.subject.ref, from: `the recorded job ${rec.job.ref} (${rec.job.protocol}, ${rec.job.price})`, chainId: rec.subject.chainId, label: rec.subject.label, state: rec.evidence, delivered: rec.facts.delivered, checks: rec.facts.checks } : jobFile ? prepareJob(JSON.parse(readFileSync(jobFile, 'utf8'))).pack : evFile ? JSON.parse(readFileSync(evFile, 'utf8')) : undefined,
|
|
77
79
|
answers: rec ? rec.answers : ansFile ? JSON.parse(readFileSync(ansFile, 'utf8')) : undefined, ...(rec ? { model: rec.model } : {}),
|
|
78
80
|
key: process.env.TYPESAFE_KEY || null, relayerKey: process.env.TAIFOON_RELAYER_KEY || null,
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export { prepareJob, runCheck } from './job.js';
|
|
|
19
19
|
export { record } from './record.js';
|
|
20
20
|
export { evaluatorCall } from './evaluator/index.js';
|
|
21
21
|
export { verify } from './verify.js';
|
|
22
|
-
export { pipeline, STEPS, LAYER, factsFromEvidence, protocolFor, parseJob } from './pipeline.js';
|
|
22
|
+
export { pipeline, STEPS, LAYER, factsFromEvidence, protocolFor, parseJob, localQuote } from './pipeline.js';
|
|
23
23
|
export { RUBRIC_v1 } from './rubric.js';
|
|
24
24
|
export { CONTRACTS } from './contracts.js';
|
|
25
25
|
export type { GradeInput, Receipt } from './grade.js';
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,6 @@ export { prepareJob, runCheck } from './job.js';
|
|
|
19
19
|
export { record } from './record.js';
|
|
20
20
|
export { evaluatorCall } from './evaluator/index.js';
|
|
21
21
|
export { verify } from './verify.js';
|
|
22
|
-
export { pipeline, STEPS, LAYER, factsFromEvidence, protocolFor, parseJob } from './pipeline.js';
|
|
22
|
+
export { pipeline, STEPS, LAYER, factsFromEvidence, protocolFor, parseJob, localQuote } from './pipeline.js';
|
|
23
23
|
export { RUBRIC_v1 } from './rubric.js';
|
|
24
24
|
export { CONTRACTS } from './contracts.js';
|
package/dist/pipeline.d.ts
CHANGED
|
@@ -127,12 +127,23 @@ export type PipelineOpts = {
|
|
|
127
127
|
chainVerify?: boolean;
|
|
128
128
|
/** price for the premium step, in USDC; default: the job's budget */
|
|
129
129
|
priceUsdc?: number | null;
|
|
130
|
+
/** the seller's record, k delivered of n graded: the premium is priced locally (@taifoon/jev-wilson, the layer's own
|
|
131
|
+
* numbers to the last digit) and the layer is not asked */
|
|
132
|
+
sellerRecord?: {
|
|
133
|
+
k: number;
|
|
134
|
+
n: number;
|
|
135
|
+
} | null;
|
|
130
136
|
/** a gate before each step: return false to skip it. The interactive CLI asks here. */
|
|
131
137
|
before?: (step: (typeof STEPS)[number], trace: Trace) => boolean | Promise<boolean>;
|
|
132
138
|
/** called after each step with its result */
|
|
133
139
|
after?: (result: StepResult, trace: Trace) => void | Promise<void>;
|
|
134
140
|
fetch?: typeof fetch;
|
|
135
141
|
};
|
|
142
|
+
/** The premium the layer would quote for this record, computed here: same z, same bound, same rounding. */
|
|
143
|
+
export declare function localQuote(record: {
|
|
144
|
+
k: number;
|
|
145
|
+
n: number;
|
|
146
|
+
}, priceUsdc: number | null): Quote;
|
|
136
147
|
/** A job reference: `8453:81100`, `8453:bitagent:8453:7287`, or a bare id (chain 8453). */
|
|
137
148
|
export declare function parseJob(j: string | {
|
|
138
149
|
chainId: number;
|
package/dist/pipeline.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// pipeline(): one job through the coordination layer's steps, each step a function with a typed output.
|
|
2
2
|
// pick → evidence → facts → grade → record → evaluator → premium → verify
|
|
3
3
|
// Every step works without the layer too: pass `evidence` (your own pack) and the layer is never read; `grade` then
|
|
4
|
-
// runs on your own TypeSafe key (or answers you already have), `record` returns unsigned calls, `premium` is skipped. With the layer
|
|
4
|
+
// runs on your own TypeSafe key (or answers you already have), `record` returns unsigned calls, `premium` is priced from a `sellerRecord` you pass (or skipped). With the layer
|
|
5
5
|
// (https://coord.taifoon.dev by default) the job comes from /v1/judge/queue, the pack from /v1/judge/evidence, the
|
|
6
6
|
// seller's terms from /v1/pools/quote, and — only with a relayer key — the answers are recorded on the layer through
|
|
7
7
|
// /v1/judge/answers/record. Nothing here signs a transaction.
|
|
8
|
+
import { premium as wilsonPremium, wilson } from './wilson.js';
|
|
8
9
|
import { facts as runFacts } from './facts.js';
|
|
9
10
|
import { grade } from './grade.js';
|
|
10
11
|
import { record } from './record.js';
|
|
@@ -23,6 +24,19 @@ export const STEPS = Object.freeze([
|
|
|
23
24
|
{ id: 'premium', title: 'Price the next premium from the seller record (Wilson)', route: 'POST /v1/pools/quote' },
|
|
24
25
|
{ id: 'verify', title: 'Re-derive the receipt (and find it on chain)', route: null },
|
|
25
26
|
]);
|
|
27
|
+
/** The premium the layer would quote for this record, computed here: same z, same bound, same rounding. */
|
|
28
|
+
export function localQuote(record, priceUsdc) {
|
|
29
|
+
const units = priceUsdc ? Math.round(priceUsdc * 1e6) : undefined;
|
|
30
|
+
const p = wilsonPremium(record, units === undefined ? {} : { price: units });
|
|
31
|
+
const [lo, hi] = wilson(record.n - record.k, record.n);
|
|
32
|
+
const pct = (x) => `${(x * 100).toFixed(1)}%`;
|
|
33
|
+
return {
|
|
34
|
+
guaranteed: p.covered, premium: p.amount === null ? null : p.amount.toString(), premium_ratio: p.ratio, deposit: null,
|
|
35
|
+
premium_label: p.insurable ? `${pct(lo)}–${pct(hi)} · ${record.k} of ${record.n} delivered` : 'UNKNOWN: no delivered job on record',
|
|
36
|
+
record: { n: record.n, settled: record.n, incorrect: record.n - record.k, wilson: [lo, hi], calibrated: false, insurable: p.insurable },
|
|
37
|
+
why: p.insurable ? (p.covered ? [] : [`ratio ${p.ratio.toFixed(4)} is above the 0.30 the pool covers`]) : ['a record with no delivered job is not insurable'],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
26
40
|
const num = (s) => { const m = /([0-9]+(?:\.[0-9]+)?)/.exec(s ?? ''); return m ? Number(m[1]) : null; };
|
|
27
41
|
const fact = (ev, label) => ev.facts.find(([k]) => label.test(k))?.[1];
|
|
28
42
|
/** A job reference: `8453:81100`, `8453:bitagent:8453:7287`, or a bare id (chain 8453). */
|
|
@@ -181,8 +195,13 @@ export async function pipeline(o = {}) {
|
|
|
181
195
|
});
|
|
182
196
|
await step('premium', async () => {
|
|
183
197
|
const seller = t.job.seller;
|
|
198
|
+
if (o.sellerRecord) {
|
|
199
|
+
const price = o.priceUsdc ?? budget;
|
|
200
|
+
t.quote = localQuote(o.sellerRecord, price);
|
|
201
|
+
return { source: 'local: Wilson on the record you passed', guaranteed: t.quote.guaranteed, premium_ratio: t.quote.premium_ratio, premium_label: t.quote.premium_label, record: t.quote.record ?? null };
|
|
202
|
+
}
|
|
184
203
|
if (!layer)
|
|
185
|
-
return { skip: 'offline:
|
|
204
|
+
return { skip: 'offline: pass sellerRecord (--seller-record k/n) to price locally, or a layer to read the seller\'s record' };
|
|
186
205
|
if (!seller)
|
|
187
206
|
return { skip: 'no seller address in the evidence' };
|
|
188
207
|
const price = o.priceUsdc ?? budget;
|
package/dist/wilson.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** z for a two-sided 95% interval, as the layer pins it. */
|
|
2
|
+
export declare const Z = 1.959963984540054;
|
|
3
|
+
/** The layer's pool rule: no pool covers above this premium ratio. */
|
|
4
|
+
export declare const MAX_PREMIUM_RATIO = 0.3;
|
|
5
|
+
/** The premium ratio is rounded to millionths before it multiplies the price. */
|
|
6
|
+
export declare const RATIO_SCALE = 1000000;
|
|
7
|
+
/** The Wilson score interval for `successes` of `n`. [0, 1] when n = 0. Same operations, same order as the layer. */
|
|
8
|
+
export declare function wilson(successes: number, n: number, z?: number): [number, number];
|
|
9
|
+
/** u_F: the upper Wilson bound on the failure rate, n - k failed of n. This is the layer's π ratio. */
|
|
10
|
+
export declare function wilsonUpperFailure(k: number, n: number, z?: number): number;
|
|
11
|
+
/** floor(price × round(ratio × 1e6) / 1e6), exactly as the layer's pool-quote computes it. */
|
|
12
|
+
export declare function premiumAmount(price: bigint, ratio: number): bigint;
|
|
13
|
+
export type SellerRecord = {
|
|
14
|
+
k: number;
|
|
15
|
+
n: number;
|
|
16
|
+
};
|
|
17
|
+
export type PremiumOpts = {
|
|
18
|
+
/** floor on the ratio; default 0 (the layer has none) */
|
|
19
|
+
min?: number;
|
|
20
|
+
/** cover only when ratio <= max; default 0.30 (the layer's pool rule) */
|
|
21
|
+
max?: number;
|
|
22
|
+
/** price in the token's smallest unit; when given, `amount` is computed */
|
|
23
|
+
price?: bigint | number | string;
|
|
24
|
+
/** z for the record form; default Z */
|
|
25
|
+
z?: number;
|
|
26
|
+
};
|
|
27
|
+
export type Premium = {
|
|
28
|
+
insurable: false;
|
|
29
|
+
reason: 'UNKNOWN';
|
|
30
|
+
ratio: null;
|
|
31
|
+
amount: null;
|
|
32
|
+
covered: false;
|
|
33
|
+
basis: 'record';
|
|
34
|
+
} | {
|
|
35
|
+
insurable: true;
|
|
36
|
+
ratio: number;
|
|
37
|
+
amount: bigint | null;
|
|
38
|
+
covered: boolean;
|
|
39
|
+
basis: 'record' | 'p_L';
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The premium for the next job.
|
|
43
|
+
*
|
|
44
|
+
* premium({ k, n }, opts) prices from the record: ratio = u_F, bit-for-bit with the layer.
|
|
45
|
+
* premium(pL, opts) prices from a lower bound you already hold: ratio = 1 - p_L. Equal in real
|
|
46
|
+
* arithmetic, but 1 - p_L can differ from u_F in the last bits of a double.
|
|
47
|
+
* Use the record form when you need the layer's number to the last digit.
|
|
48
|
+
*/
|
|
49
|
+
export declare function premium(input: number | SellerRecord, opts?: PremiumOpts): Premium;
|
package/dist/wilson.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// GENERATED from @taifoon/jev-wilson 0.1.0 (src/wilson.ts) by scripts/vendor-wilson.mjs. Do not edit here.
|
|
2
|
+
// The pricing half only: z, the Wilson interval, the premium. Same numbers as the layer's /v1/pools/quote.
|
|
3
|
+
/** z for a two-sided 95% interval, as the layer pins it. */
|
|
4
|
+
export const Z = 1.959963984540054;
|
|
5
|
+
/** The layer's pool rule: no pool covers above this premium ratio. */
|
|
6
|
+
export const MAX_PREMIUM_RATIO = 0.3;
|
|
7
|
+
/** The premium ratio is rounded to millionths before it multiplies the price. */
|
|
8
|
+
export const RATIO_SCALE = 1_000_000;
|
|
9
|
+
function checkRecord(k, n) {
|
|
10
|
+
if (!Number.isInteger(k) || !Number.isInteger(n) || k < 0 || n < 0 || k > n) {
|
|
11
|
+
throw new RangeError(`need integers 0 <= k <= n, got k=${k}, n=${n}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** The Wilson score interval for `successes` of `n`. [0, 1] when n = 0. Same operations, same order as the layer. */
|
|
15
|
+
export function wilson(successes, n, z = Z) {
|
|
16
|
+
if (n <= 0)
|
|
17
|
+
return [0, 1];
|
|
18
|
+
const p = successes / n, z2 = z * z;
|
|
19
|
+
const d = 1 + z2 / n;
|
|
20
|
+
const c = p + z2 / (2 * n);
|
|
21
|
+
const s = z * Math.sqrt((p * (1 - p) + z2 / (4 * n)) / n);
|
|
22
|
+
return [Math.max(0, (c - s) / d), Math.min(1, (c + s) / d)];
|
|
23
|
+
}
|
|
24
|
+
/** u_F: the upper Wilson bound on the failure rate, n - k failed of n. This is the layer's π ratio. */
|
|
25
|
+
export function wilsonUpperFailure(k, n, z = Z) {
|
|
26
|
+
checkRecord(k, n);
|
|
27
|
+
return wilson(n - k, n, z)[1];
|
|
28
|
+
}
|
|
29
|
+
/** JavaScript Math.round, kept explicit so the Python twin can match it. */
|
|
30
|
+
const roundHalfUp = (x) => Math.round(x);
|
|
31
|
+
/** floor(price × round(ratio × 1e6) / 1e6), exactly as the layer's pool-quote computes it. */
|
|
32
|
+
export function premiumAmount(price, ratio) {
|
|
33
|
+
if (price <= 0n)
|
|
34
|
+
throw new RangeError('price must be a positive integer in the token’s smallest unit');
|
|
35
|
+
return (price * BigInt(roundHalfUp(ratio * RATIO_SCALE))) / BigInt(RATIO_SCALE);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The premium for the next job.
|
|
39
|
+
*
|
|
40
|
+
* premium({ k, n }, opts) prices from the record: ratio = u_F, bit-for-bit with the layer.
|
|
41
|
+
* premium(pL, opts) prices from a lower bound you already hold: ratio = 1 - p_L. Equal in real
|
|
42
|
+
* arithmetic, but 1 - p_L can differ from u_F in the last bits of a double.
|
|
43
|
+
* Use the record form when you need the layer's number to the last digit.
|
|
44
|
+
*/
|
|
45
|
+
export function premium(input, opts = {}) {
|
|
46
|
+
const min = opts.min ?? 0;
|
|
47
|
+
const max = opts.max ?? MAX_PREMIUM_RATIO;
|
|
48
|
+
let raw;
|
|
49
|
+
let basis;
|
|
50
|
+
if (typeof input === 'number') {
|
|
51
|
+
if (!(input >= 0 && input <= 1))
|
|
52
|
+
throw new RangeError(`p_L must be in [0, 1], got ${input}`);
|
|
53
|
+
raw = 1 - input;
|
|
54
|
+
basis = 'p_L';
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
checkRecord(input.k, input.n);
|
|
58
|
+
if (input.k <= 0)
|
|
59
|
+
return { insurable: false, reason: 'UNKNOWN', ratio: null, amount: null, covered: false, basis: 'record' };
|
|
60
|
+
raw = wilsonUpperFailure(input.k, input.n, opts.z ?? Z);
|
|
61
|
+
basis = 'record';
|
|
62
|
+
}
|
|
63
|
+
const ratio = raw < min ? min : raw;
|
|
64
|
+
const amount = opts.price === undefined ? null : premiumAmount(BigInt(opts.price), ratio);
|
|
65
|
+
return { insurable: true, ratio, amount, covered: ratio <= max, basis };
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taifoon/jev",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "Grade AI agent jobs with TypeSafe's Jev and record the verdict on chain: code checks the facts, Jev answers a published rubric, and you get a receipt, the on-chain record and the evaluator call for ERC-8183 and Virtuals ACP. No runtime dependencies, no key inside.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jev",
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"access": "public"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
|
+
"@taifoon/jev-wilson": "^0.1.0",
|
|
73
74
|
"@types/node": "^25.9.2",
|
|
74
75
|
"typescript": "5.9.2",
|
|
75
76
|
"viem": "2.52.2",
|