@taifoon/jev 0.1.0

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.
Files changed (56) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +222 -0
  3. package/bin/jev.mjs +37 -0
  4. package/dist/abi.d.ts +11 -0
  5. package/dist/abi.js +81 -0
  6. package/dist/ask.d.ts +27 -0
  7. package/dist/ask.js +47 -0
  8. package/dist/contracts.d.ts +37 -0
  9. package/dist/contracts.js +27 -0
  10. package/dist/evaluator/assurance-hook.d.ts +6 -0
  11. package/dist/evaluator/assurance-hook.js +15 -0
  12. package/dist/evaluator/bitagent-erc8183.d.ts +7 -0
  13. package/dist/evaluator/bitagent-erc8183.js +18 -0
  14. package/dist/evaluator/index.d.ts +12 -0
  15. package/dist/evaluator/index.js +20 -0
  16. package/dist/evaluator/judge-adapter.d.ts +5 -0
  17. package/dist/evaluator/judge-adapter.js +16 -0
  18. package/dist/evaluator/types.d.ts +27 -0
  19. package/dist/evaluator/types.js +6 -0
  20. package/dist/evaluator/virtuals-erc8183.d.ts +6 -0
  21. package/dist/evaluator/virtuals-erc8183.js +15 -0
  22. package/dist/evaluator/virtuals-memo-acp.d.ts +5 -0
  23. package/dist/evaluator/virtuals-memo-acp.js +15 -0
  24. package/dist/facts.d.ts +16 -0
  25. package/dist/facts.js +15 -0
  26. package/dist/grade.d.ts +32 -0
  27. package/dist/grade.js +33 -0
  28. package/dist/hash.d.ts +10 -0
  29. package/dist/hash.js +132 -0
  30. package/dist/index.d.ts +29 -0
  31. package/dist/index.js +20 -0
  32. package/dist/receipt.d.ts +68 -0
  33. package/dist/receipt.js +42 -0
  34. package/dist/record.d.ts +32 -0
  35. package/dist/record.js +41 -0
  36. package/dist/records.d.ts +78 -0
  37. package/dist/records.js +55 -0
  38. package/dist/rubric.d.ts +109 -0
  39. package/dist/rubric.js +91 -0
  40. package/dist/verify.d.ts +40 -0
  41. package/dist/verify.js +80 -0
  42. package/package.json +73 -0
  43. package/workflows/0.1.0/README.md +104 -0
  44. package/workflows/0.1.0/batch-judge.json +341 -0
  45. package/workflows/0.1.0/hire-judge-settle.json +767 -0
  46. package/workflows/0.1.0/jev-grader.json +433 -0
  47. package/workflows/0.1.0/manifest.json +151 -0
  48. package/workflows/0.1.0/schemas/answers.json +248 -0
  49. package/workflows/0.1.0/schemas/common.json +394 -0
  50. package/workflows/0.1.0/schemas/delivery.json +233 -0
  51. package/workflows/0.1.0/schemas/facts.json +225 -0
  52. package/workflows/0.1.0/schemas/hire.json +337 -0
  53. package/workflows/0.1.0/schemas/job.json +96 -0
  54. package/workflows/0.1.0/schemas/settlement.json +225 -0
  55. package/workflows/0.1.0/schemas/verdict.json +314 -0
  56. package/workflows/0.1.0/stamp-grade.json +175 -0
@@ -0,0 +1,27 @@
1
+ import type { Hex } from '../hash.js';
2
+ export type EvaluatorVerdict = 'complete' | 'reject';
3
+ export type EvaluatorCall = {
4
+ protocol: string;
5
+ chainId: number;
6
+ to: string;
7
+ data: Hex;
8
+ value: '0';
9
+ fn: string;
10
+ /** who must sign: the seat the protocol checks */
11
+ signer: string;
12
+ };
13
+ export type AdapterOpts = {
14
+ to?: string;
15
+ chainId?: number;
16
+ seat?: 'job' | 'platform';
17
+ reason?: string;
18
+ };
19
+ export type Adapter = {
20
+ name: string;
21
+ /** what it is, in a line */
22
+ about: string;
23
+ call: (jobId: string | number | bigint, verdict: EvaluatorVerdict, digest: Hex, opts: AdapterOpts) => EvaluatorCall;
24
+ };
25
+ export declare const need: (v: string | undefined, what: string) => string;
26
+ export declare const asBytes32: (id: string | number | bigint) => Hex;
27
+ export declare const asUint: (id: string | number | bigint) => bigint;
@@ -0,0 +1,6 @@
1
+ export const need = (v, what) => { if (!v)
2
+ throw new Error(`${what} is required for this adapter (opts.to)`); return v; };
3
+ export const asBytes32 = (id) => { const s = String(id); if (!/^0x[0-9a-fA-F]{64}$/.test(s))
4
+ throw new Error(`job id must be a 32-byte hex id here, got ${s.slice(0, 20)}`); return s.toLowerCase(); };
5
+ export const asUint = (id) => { const n = BigInt(id); if (n < 0n)
6
+ throw new Error('job id must be ≥ 0'); return n; };
@@ -0,0 +1,6 @@
1
+ import { type Adapter } from './types.js';
2
+ export declare const ABI: {
3
+ readonly complete: "complete(uint256,bytes32,bytes)";
4
+ readonly reject: "reject(uint256,bytes32,bytes)";
5
+ };
6
+ export declare const virtualsErc8183: Adapter;
@@ -0,0 +1,15 @@
1
+ // Virtuals ERC-8183 (AgenticCommerceV3 on Base). When the job names you as its evaluator you end it:
2
+ // complete(jobId, reason, optParams) or reject(jobId, reason, optParams). `reason` is a bytes32 — the decision digest.
3
+ import { encodeCall } from '../abi.js';
4
+ import { CONTRACTS } from '../contracts.js';
5
+ import { asUint } from './types.js';
6
+ export const ABI = { complete: 'complete(uint256,bytes32,bytes)', reject: 'reject(uint256,bytes32,bytes)' };
7
+ export const virtualsErc8183 = {
8
+ name: 'virtuals-erc8183',
9
+ about: 'Virtuals AgenticCommerceV3: complete / reject(jobId, digest, 0x) as the job’s evaluator',
10
+ call: (jobId, verdict, digest, o) => ({
11
+ protocol: 'virtuals-erc8183', chainId: o.chainId ?? CONTRACTS.base.chainId, to: o.to ?? CONTRACTS.base.virtualsErc8183, value: '0', fn: ABI[verdict],
12
+ data: encodeCall(ABI[verdict], [asUint(jobId), digest, '0x']),
13
+ signer: 'the address the job names as evaluator',
14
+ }),
15
+ };
@@ -0,0 +1,5 @@
1
+ import { type Adapter } from './types.js';
2
+ export declare const ABI: {
3
+ readonly signMemo: "signMemo(uint256,bool,string)";
4
+ };
5
+ export declare const virtualsMemoAcp: Adapter;
@@ -0,0 +1,15 @@
1
+ // Virtuals memo-ACP (ACPRouter on Base). The evaluator ends a job by signing its evaluation memo:
2
+ // signMemo(memoId, isApproved, reason). Pass the memo id as the job id; the reason carries the verdict and the digest.
3
+ import { encodeCall } from '../abi.js';
4
+ import { CONTRACTS } from '../contracts.js';
5
+ import { asUint } from './types.js';
6
+ export const ABI = { signMemo: 'signMemo(uint256,bool,string)' };
7
+ export const virtualsMemoAcp = {
8
+ name: 'virtuals-memo-acp',
9
+ about: 'Virtuals memo-ACP router: signMemo(memoId, approved, "jev <verdict> <digest>") on the evaluation memo',
10
+ call: (memoId, verdict, digest, o) => ({
11
+ protocol: 'virtuals-memo-acp', chainId: o.chainId ?? CONTRACTS.base.chainId, to: o.to ?? CONTRACTS.base.virtualsMemoAcpRouter, value: '0', fn: ABI.signMemo,
12
+ data: encodeCall(ABI.signMemo, [asUint(memoId), verdict === 'complete', o.reason ?? `jev ${verdict} ${digest}`]),
13
+ signer: 'the job’s evaluator (the memo’s counterparty)',
14
+ }),
15
+ };
@@ -0,0 +1,16 @@
1
+ import type { Facts } from './rubric.js';
2
+ export type Check = boolean | null | (() => boolean | null | Promise<boolean | null>);
3
+ export type FactsInput = {
4
+ /** did anything come back / get submitted? false = hard fail */
5
+ delivered: boolean;
6
+ /** name → result, or name → a function that computes it; a function that throws counts as "could not be checked" */
7
+ checks?: Record<string, Check>;
8
+ /** the job's price in USDC; above the rubric's cap a "complete" is held for review */
9
+ priceUsdc?: number | null;
10
+ /** the class whose check ran and what it found, shown to Jev in the facts section */
11
+ det?: {
12
+ class: string;
13
+ why: string;
14
+ };
15
+ };
16
+ export declare function facts(input: FactsInput): Promise<Facts>;
package/dist/facts.js ADDED
@@ -0,0 +1,15 @@
1
+ export async function facts(input) {
2
+ const checks = {};
3
+ for (const [name, c] of Object.entries(input.checks ?? {})) {
4
+ try {
5
+ const v = typeof c === 'function' ? await c() : c;
6
+ checks[name] = v === true ? true : v === false ? false : null;
7
+ }
8
+ catch {
9
+ checks[name] = null;
10
+ }
11
+ }
12
+ const vals = Object.values(checks);
13
+ const checksOk = vals.includes(false) ? false : vals.includes(true) ? true : null;
14
+ return { delivered: Boolean(input.delivered), checksOk, checks, priceUsdc: typeof input.priceUsdc === 'number' ? input.priceUsdc : null, ...(input.det ? { det: input.det } : {}) };
15
+ }
@@ -0,0 +1,32 @@
1
+ import { type Receipt } from './receipt.js';
2
+ import type { Subject } from './records.js';
3
+ import type { Answer, Facts, Rubric, RubricInput } from './rubric.js';
4
+ export type GradeInput = {
5
+ /** what is being graded: a job id / reference, or { chainId, at, ref } for an on-chain job */
6
+ subject: string | Subject;
7
+ /** the evidence pack: the task, the delivery, anything Jev should read. Objects are JSON-encoded. */
8
+ evidence: string | object;
9
+ /** facts() output (or a promise of it); default: delivered, no deterministic check */
10
+ facts?: Facts | Promise<Facts>;
11
+ /** default RUBRIC_v1; or your own { version, questions, asked?, thresholds?, compose? } */
12
+ rubric?: Rubric | RubricInput;
13
+ /** your own TypeSafe key → api.typesafe.ai. Never stored, never recorded. */
14
+ key?: string | null;
15
+ /** no key: the public trial (3 free calls). `true` or { url } */
16
+ trial?: boolean | {
17
+ url?: string;
18
+ };
19
+ /** answers you already have (e.g. from the n8n TypeSafe node): nothing is asked, the rest is identical */
20
+ answers?: Record<string, Answer> | Answer[];
21
+ /** the model that produced supplied answers, e.g. "jev-1.13.0" */
22
+ model?: string | null;
23
+ /** who is recording (jev.answer.v1 `caller`); default "sdk" */
24
+ caller?: string;
25
+ /** base URL for `key` (default https://api.typesafe.ai) */
26
+ endpoint?: string;
27
+ fetch?: typeof fetch;
28
+ /** ms since epoch for the answer record; default now */
29
+ at?: number;
30
+ };
31
+ export type { Receipt };
32
+ export declare function grade(g: GradeInput): Promise<Receipt>;
package/dist/grade.js ADDED
@@ -0,0 +1,33 @@
1
+ // grade(): evidence → deterministic facts → Jev's atomic questions → composed verdict → receipt.
2
+ import { askJev, JevError, TRIAL_MAX_STATE } from './ask.js';
3
+ import { answersOf, buildReceipt, DEFAULT_FACTS, inputFor, packOf, rubricOf } from './receipt.js';
4
+ export async function grade(g) {
5
+ const rubric = rubricOf(g.rubric);
6
+ const facts = (await g.facts) ?? DEFAULT_FACTS;
7
+ const useTrial = Boolean(!g.key && !g.answers && g.trial);
8
+ const { state, jevState } = inputFor(packOf(g.evidence), facts, useTrial ? TRIAL_MAX_STATE : undefined);
9
+ // the facts decide first: a hard fail is final and Jev is never asked
10
+ let asked = null;
11
+ let answers = null;
12
+ let model = null;
13
+ let connection = 'none';
14
+ if (rubric.compose(facts, null).forced !== 'hard_fail') {
15
+ if (g.answers) {
16
+ answers = answersOf(g.answers);
17
+ model = g.model ?? null;
18
+ connection = 'supplied';
19
+ }
20
+ else {
21
+ if (!g.key && !g.trial)
22
+ throw new JevError('pass { key } (your TypeSafe key) or { trial: true } (3 free calls)', 400);
23
+ asked = await askJev({ text: jevState, questions: rubric.asked, key: g.key, trialUrl: typeof g.trial === 'object' ? g.trial.url : undefined, endpoint: g.endpoint, fetch: g.fetch });
24
+ const missing = rubric.asked.filter((q) => !asked.answers[q.id]).map((q) => q.id);
25
+ if (missing.length)
26
+ throw new JevError(`Jev returned no valid answer for: ${missing.join(', ')}`, 502);
27
+ answers = asked.answers;
28
+ model = asked.model;
29
+ connection = asked.connection;
30
+ }
31
+ }
32
+ return buildReceipt({ rubric, subject: g.subject, state, jevState, facts, answers, model, connection, latency_ms: asked?.latency_ms ?? null, trial: asked?.trial ?? null, caller: g.caller, at: g.at });
33
+ }
package/dist/hash.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type Hex = `0x${string}`;
2
+ export declare const utf8: (s: string) => Uint8Array;
3
+ export declare const toHex: (b: Uint8Array) => Hex;
4
+ export declare const fromHex: (h: string) => Uint8Array;
5
+ export declare function sha256(data: Uint8Array): Uint8Array;
6
+ /** sha256 of a UTF-8 string, as 0x-hex — the digest every Jev record uses */
7
+ export declare const sha256Hex: (s: string) => Hex;
8
+ export declare function keccak256(data: Uint8Array): Uint8Array;
9
+ /** keccak256 of a UTF-8 string (or of raw bytes), as 0x-hex */
10
+ export declare const keccakHex: (v: string | Uint8Array) => Hex;
package/dist/hash.js ADDED
@@ -0,0 +1,132 @@
1
+ const enc = new TextEncoder();
2
+ export const utf8 = (s) => enc.encode(s);
3
+ export const toHex = (b) => {
4
+ let s = '0x';
5
+ for (let i = 0; i < b.length; i++)
6
+ s += (b[i] < 16 ? '0' : '') + b[i].toString(16);
7
+ return s;
8
+ };
9
+ export const fromHex = (h) => {
10
+ const s = h.startsWith('0x') ? h.slice(2) : h;
11
+ if (s.length % 2 || /[^0-9a-fA-F]/.test(s))
12
+ throw new Error(`not hex: ${h.slice(0, 20)}`);
13
+ const out = new Uint8Array(s.length / 2);
14
+ for (let i = 0; i < out.length; i++)
15
+ out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
16
+ return out;
17
+ };
18
+ // ── SHA-256 (FIPS 180-4) ──
19
+ const K = new Uint32Array([
20
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
21
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
22
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
23
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
24
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
25
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
26
+ ]);
27
+ export function sha256(data) {
28
+ const bitLen = data.length * 8;
29
+ const padded = new Uint8Array(((data.length + 9 + 63) >> 6) << 6);
30
+ padded.set(data);
31
+ padded[data.length] = 0x80;
32
+ const dv = new DataView(padded.buffer);
33
+ dv.setUint32(padded.length - 8, Math.floor(bitLen / 2 ** 32));
34
+ dv.setUint32(padded.length - 4, bitLen >>> 0);
35
+ const H = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
36
+ const W = new Uint32Array(64);
37
+ const rotr = (x, n) => (x >>> n) | (x << (32 - n));
38
+ for (let off = 0; off < padded.length; off += 64) {
39
+ for (let i = 0; i < 16; i++)
40
+ W[i] = dv.getUint32(off + i * 4);
41
+ for (let i = 16; i < 64; i++) {
42
+ const a = W[i - 15], b = W[i - 2];
43
+ W[i] = (W[i - 16] + (rotr(a, 7) ^ rotr(a, 18) ^ (a >>> 3)) + W[i - 7] + (rotr(b, 17) ^ rotr(b, 19) ^ (b >>> 10))) >>> 0;
44
+ }
45
+ let [a, b, c, d, e, f, g, h] = [H[0], H[1], H[2], H[3], H[4], H[5], H[6], H[7]];
46
+ for (let i = 0; i < 64; i++) {
47
+ const t1 = (h + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + K[i] + W[i]) >>> 0;
48
+ const t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;
49
+ h = g;
50
+ g = f;
51
+ f = e;
52
+ e = (d + t1) >>> 0;
53
+ d = c;
54
+ c = b;
55
+ b = a;
56
+ a = (t1 + t2) >>> 0;
57
+ }
58
+ H[0] = (H[0] + a) >>> 0;
59
+ H[1] = (H[1] + b) >>> 0;
60
+ H[2] = (H[2] + c) >>> 0;
61
+ H[3] = (H[3] + d) >>> 0;
62
+ H[4] = (H[4] + e) >>> 0;
63
+ H[5] = (H[5] + f) >>> 0;
64
+ H[6] = (H[6] + g) >>> 0;
65
+ H[7] = (H[7] + h) >>> 0;
66
+ }
67
+ const out = new Uint8Array(32);
68
+ const ov = new DataView(out.buffer);
69
+ for (let i = 0; i < 8; i++)
70
+ ov.setUint32(i * 4, H[i]);
71
+ return out;
72
+ }
73
+ /** sha256 of a UTF-8 string, as 0x-hex — the digest every Jev record uses */
74
+ export const sha256Hex = (s) => toHex(sha256(utf8(s)));
75
+ // ── Keccak-256 (the Ethereum variant: pad 0x01) ──
76
+ const RC = [
77
+ 0x0000000000000001n, 0x0000000000008082n, 0x800000000000808an, 0x8000000080008000n, 0x000000000000808bn, 0x0000000080000001n,
78
+ 0x8000000080008081n, 0x8000000000008009n, 0x000000000000008an, 0x0000000000000088n, 0x0000000080008009n, 0x000000008000000an,
79
+ 0x000000008000808bn, 0x800000000000008bn, 0x8000000000008089n, 0x8000000000008003n, 0x8000000000008002n, 0x8000000000000080n,
80
+ 0x000000000000800an, 0x800000008000000an, 0x8000000080008081n, 0x8000000000008080n, 0x0000000080000001n, 0x8000000080008008n,
81
+ ];
82
+ const ROT = [0, 1, 62, 28, 27, 36, 44, 6, 55, 20, 3, 10, 43, 25, 39, 41, 45, 15, 21, 8, 18, 2, 61, 56, 14];
83
+ const M64 = (1n << 64n) - 1n;
84
+ const rotl = (x, n) => (n === 0 ? x : ((x << BigInt(n)) | (x >> BigInt(64 - n))) & M64);
85
+ function keccakF(s) {
86
+ const C = new Array(5), B = new Array(25);
87
+ for (let r = 0; r < 24; r++) {
88
+ for (let x = 0; x < 5; x++)
89
+ C[x] = s[x] ^ s[x + 5] ^ s[x + 10] ^ s[x + 15] ^ s[x + 20];
90
+ for (let x = 0; x < 5; x++) {
91
+ const d = C[(x + 4) % 5] ^ rotl(C[(x + 1) % 5], 1);
92
+ for (let y = 0; y < 25; y += 5)
93
+ s[x + y] = s[x + y] ^ d;
94
+ }
95
+ for (let x = 0; x < 5; x++)
96
+ for (let y = 0; y < 5; y++)
97
+ B[y + 5 * ((2 * x + 3 * y) % 5)] = rotl(s[x + 5 * y], ROT[x + 5 * y]);
98
+ for (let x = 0; x < 5; x++)
99
+ for (let y = 0; y < 25; y += 5)
100
+ s[x + y] = B[x + y] ^ (~B[((x + 1) % 5) + y] & M64 & B[((x + 2) % 5) + y]);
101
+ s[0] = s[0] ^ RC[r];
102
+ }
103
+ }
104
+ export function keccak256(data) {
105
+ const rate = 136;
106
+ const s = new Array(25).fill(0n);
107
+ const len = Math.floor(data.length / rate) * rate + rate;
108
+ const p = new Uint8Array(len);
109
+ p.set(data);
110
+ p[data.length] = 0x01;
111
+ p[len - 1] = p[len - 1] | 0x80;
112
+ for (let off = 0; off < len; off += rate) {
113
+ for (let i = 0; i < rate / 8; i++) {
114
+ let lane = 0n;
115
+ for (let b = 7; b >= 0; b--)
116
+ lane = (lane << 8n) | BigInt(p[off + i * 8 + b]);
117
+ s[i] = s[i] ^ lane;
118
+ }
119
+ keccakF(s);
120
+ }
121
+ const out = new Uint8Array(32);
122
+ for (let i = 0; i < 4; i++) {
123
+ let lane = s[i];
124
+ for (let b = 0; b < 8; b++) {
125
+ out[i * 8 + b] = Number(lane & 0xffn);
126
+ lane >>= 8n;
127
+ }
128
+ }
129
+ return out;
130
+ }
131
+ /** keccak256 of a UTF-8 string (or of raw bytes), as 0x-hex */
132
+ export const keccakHex = (v) => toHex(keccak256(typeof v === 'string' ? utf8(v) : v));
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @taifoon/jev — Jev as a grader any protocol can call.
3
+ *
4
+ * grade() evidence → deterministic facts → Jev's atomic questions → composed verdict → receipt
5
+ * facts() the deterministic checks a protocol supplies (a failed one is final; Jev is not asked)
6
+ * record() the unsigned calls that put the receipt's digests on JevAnswerLog / JevDecisionLog
7
+ * evaluatorCall() the unsigned call that ends the job on the protocol whose evaluator seat you hold
8
+ * verify() recompute every digest and find the chain events that hold them
9
+ * RUBRIC_v1 the questions, thresholds and composition (pass your own rubric to grade)
10
+ * CONTRACTS where the logs and the evaluator seats are
11
+ *
12
+ * No runtime dependency. No key inside: the free trial, or your own TypeSafe key.
13
+ */
14
+ export { grade } from './grade.js';
15
+ export { facts } from './facts.js';
16
+ export { record } from './record.js';
17
+ export { evaluatorCall } from './evaluator/index.js';
18
+ export { verify } from './verify.js';
19
+ export { RUBRIC_v1 } from './rubric.js';
20
+ export { CONTRACTS } from './contracts.js';
21
+ export type { GradeInput, Receipt } from './grade.js';
22
+ export type { FactsInput, Check } from './facts.js';
23
+ export type { Recorded, UnsignedCall, Network } from './record.js';
24
+ export type { Verification, AnswerEvent, DecisionEvent } from './verify.js';
25
+ export type { Protocol, Adapter, AdapterOpts, EvaluatorCall } from './evaluator/index.js';
26
+ export type { JevError } from './ask.js';
27
+ export type { Rubric, RubricInput, Question, Answer, Facts, Verdict, Composed, Thresholds } from './rubric.js';
28
+ export type { Subject, AnswerRecord } from './records.js';
29
+ export type { Hex } from './hash.js';
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @taifoon/jev — Jev as a grader any protocol can call.
3
+ *
4
+ * grade() evidence → deterministic facts → Jev's atomic questions → composed verdict → receipt
5
+ * facts() the deterministic checks a protocol supplies (a failed one is final; Jev is not asked)
6
+ * record() the unsigned calls that put the receipt's digests on JevAnswerLog / JevDecisionLog
7
+ * evaluatorCall() the unsigned call that ends the job on the protocol whose evaluator seat you hold
8
+ * verify() recompute every digest and find the chain events that hold them
9
+ * RUBRIC_v1 the questions, thresholds and composition (pass your own rubric to grade)
10
+ * CONTRACTS where the logs and the evaluator seats are
11
+ *
12
+ * No runtime dependency. No key inside: the free trial, or your own TypeSafe key.
13
+ */
14
+ export { grade } from './grade.js';
15
+ export { facts } from './facts.js';
16
+ export { record } from './record.js';
17
+ export { evaluatorCall } from './evaluator/index.js';
18
+ export { verify } from './verify.js';
19
+ export { RUBRIC_v1 } from './rubric.js';
20
+ export { CONTRACTS } from './contracts.js';
@@ -0,0 +1,68 @@
1
+ import { type Hex } from './hash.js';
2
+ import { type AnswerRecord, type Subject } from './records.js';
3
+ import { type Answer, type Facts, type ReceiptBody, type Rubric, type RubricInput } from './rubric.js';
4
+ export type Connection = 'trial' | 'key' | 'supplied' | 'none';
5
+ export type Receipt = ReceiptBody & {
6
+ v: 'jev.receipt.v1';
7
+ receiptHash: Hex;
8
+ /** the on-chain subject: JevDecisionLog.subjectOf(chainId, at, ref) */
9
+ chainSubject: Subject;
10
+ subjectId: Hex;
11
+ /** the exact text Jev read (evidence cut at the cap + the facts section + the instruction) and its sha256 */
12
+ input: string;
13
+ inputDigest: Hex;
14
+ decision: {
15
+ v: 'decision.v2';
16
+ kind: 'grade';
17
+ kindId: Hex;
18
+ digest: Hex;
19
+ confidenceBps: number;
20
+ } | null;
21
+ answersRecord: AnswerRecord | null;
22
+ answersDigest: Hex | null;
23
+ via: {
24
+ connection: Connection;
25
+ latency_ms: number | null;
26
+ trial: {
27
+ calls: number;
28
+ left: number;
29
+ } | null;
30
+ };
31
+ };
32
+ export declare const DEFAULT_FACTS: Facts;
33
+ export declare const rubricOf: (r?: Rubric | RubricInput) => Rubric;
34
+ export declare const packOf: (evidence: string | object) => string;
35
+ /** the text Jev reads, cut until its JSON form fits `maxJson` characters (the trial's limit) — the facts section is never cut */
36
+ export declare function inputFor(pack: string, facts: Facts, maxJson?: number): {
37
+ state: string;
38
+ jevState: string;
39
+ };
40
+ export declare function buildReceipt(a: {
41
+ rubric: Rubric;
42
+ subject: string | Subject;
43
+ state: string;
44
+ jevState: string;
45
+ facts: Facts;
46
+ answers: Record<string, Answer> | null;
47
+ model: string | null;
48
+ connection: Connection;
49
+ latency_ms?: number | null;
50
+ trial?: {
51
+ calls: number;
52
+ left: number;
53
+ } | null;
54
+ caller?: string;
55
+ at?: number;
56
+ }): Receipt;
57
+ /** answers as a map id → { id, value, confidence, probabilities } (key order fixed: it is hashed) */
58
+ export declare function answersOf(list: Array<{
59
+ id: string;
60
+ value: unknown;
61
+ confidence?: unknown;
62
+ probabilities?: Record<string, number>;
63
+ }> | Record<string, {
64
+ id?: string;
65
+ value: unknown;
66
+ confidence?: unknown;
67
+ probabilities?: Record<string, number>;
68
+ }>): Record<string, Answer>;
@@ -0,0 +1,42 @@
1
+ // The receipt, built from what is already known — no network. grade() asks Jev and then calls this; the n8n node calls
2
+ // it with the answers its own TypeSafe call returned.
3
+ import { sha256Hex } from './hash.js';
4
+ import { answerRecordOf, answersDigestOf, confidenceBpsOf, decisionAnswersOf, decisionDigest, kindIdOf, subjectIdOf, subjectOf } from './records.js';
5
+ import { defineRubric, jevStateOf, receiptBody, receiptHashOf, RUBRIC_v1, STATE_CAP } from './rubric.js';
6
+ export const DEFAULT_FACTS = Object.freeze({ delivered: true, checksOk: null, checks: {}, priceUsdc: null });
7
+ export const rubricOf = (r) => (r ? defineRubric(r) : RUBRIC_v1);
8
+ export const packOf = (evidence) => (typeof evidence === 'string' ? evidence : JSON.stringify(evidence));
9
+ /** the text Jev reads, cut until its JSON form fits `maxJson` characters (the trial's limit) — the facts section is never cut */
10
+ export function inputFor(pack, facts, maxJson) {
11
+ let cap = STATE_CAP;
12
+ let s = jevStateOf(pack, facts, cap);
13
+ while (maxJson && JSON.stringify(s.jevState).length > maxJson && cap > 200) {
14
+ cap -= 200;
15
+ s = jevStateOf(pack, facts, cap);
16
+ }
17
+ return s;
18
+ }
19
+ export function buildReceipt(a) {
20
+ const subject = subjectOf(a.subject);
21
+ const subjectId = subjectIdOf(subject);
22
+ const inputDigest = sha256Hex(a.jevState);
23
+ const composed = a.rubric.compose(a.facts, a.answers);
24
+ const body = receiptBody({ rubric: a.rubric, subject: subject.ref, state: a.state, facts: a.facts, answers: a.answers, model: a.model, composed });
25
+ let decision = null;
26
+ let answersRecord = null;
27
+ let answersDigest = null;
28
+ if (a.answers) {
29
+ const dA = decisionAnswersOf(a.answers, a.rubric.questions);
30
+ decision = { v: 'decision.v2', kind: 'grade', kindId: kindIdOf('grade'), digest: decisionDigest({ kind: 'grade', subject, answers: dA, model: a.model, input_digest: inputDigest }), confidenceBps: confidenceBpsOf(dA) };
31
+ answersRecord = answerRecordOf({ subjectId, inputDigest, answers: a.answers, questions: a.rubric.questions, model: a.model, latency_ms: a.latency_ms ?? null,
32
+ credential_path: a.connection === 'trial' ? 'trial' : 'caller-credential', use_case: a.connection === 'trial' ? 'compose' : 'compose.answers', caller: a.caller ?? 'sdk', at: a.at ?? Date.now() });
33
+ answersDigest = answersDigestOf(answersRecord);
34
+ }
35
+ return { v: 'jev.receipt.v1', ...body, receiptHash: receiptHashOf(body), chainSubject: subject, subjectId, input: a.jevState, inputDigest, decision, answersRecord, answersDigest,
36
+ via: { connection: a.connection, latency_ms: a.latency_ms ?? null, trial: a.trial ?? null } };
37
+ }
38
+ /** answers as a map id → { id, value, confidence, probabilities } (key order fixed: it is hashed) */
39
+ export function answersOf(list) {
40
+ const arr = Array.isArray(list) ? list : Object.entries(list).map(([id, a]) => ({ ...a, id: a.id ?? id }));
41
+ return Object.fromEntries(arr.map((a) => [a.id, { id: a.id, value: String(a.value), confidence: Number(a.confidence), probabilities: a.probabilities ?? {} }]));
42
+ }
@@ -0,0 +1,32 @@
1
+ import { type Hex } from './hash.js';
2
+ import type { Receipt } from './receipt.js';
3
+ export type Network = 'none' | 'devnet' | 'base' | 'both';
4
+ export type UnsignedCall = {
5
+ chainId: number;
6
+ to: string | null;
7
+ data: Hex;
8
+ value: '0';
9
+ fn: string;
10
+ };
11
+ export type Recorded = {
12
+ network: Network;
13
+ /** ready: every call has an address · partial: some have `to: null` · sent: `send` sent the addressed ones · none: nothing to send */
14
+ status: 'ready' | 'partial' | 'sent' | 'none';
15
+ calls: UnsignedCall[];
16
+ digests: {
17
+ answers: Hex;
18
+ decision: Hex;
19
+ input: Hex;
20
+ subject: Hex;
21
+ receipt: Hex;
22
+ };
23
+ notes: string[];
24
+ sent?: string[];
25
+ };
26
+ export declare const ANSWER_LOG_RECORD = "record(bytes32,bytes32,bytes32,bytes32,bytes32,string,string)";
27
+ export declare const DECISION_LOG_RECORD = "record(bytes32,bytes32,bytes32,uint16,string,string)";
28
+ export declare function record(r: Receipt, opts?: {
29
+ network?: Network;
30
+ uri?: string;
31
+ send?: (call: UnsignedCall) => Promise<string>;
32
+ }): Promise<Recorded>;
package/dist/record.js ADDED
@@ -0,0 +1,41 @@
1
+ // record(): the unsigned calls that put a receipt's digests on chain — JevAnswerLog.record (jev.answer.v1) and
2
+ // JevDecisionLog.record (decision.v2). `network` is a flag: none | devnet | base | both. Nothing here signs: pass `send`
3
+ // (your wallet, e.g. viem's walletClient.sendTransaction) to send the calls that have an address, or hand them on.
4
+ import { encodeCall } from './abi.js';
5
+ import { CONTRACTS } from './contracts.js';
6
+ import { keccakHex } from './hash.js';
7
+ export const ANSWER_LOG_RECORD = 'record(bytes32,bytes32,bytes32,bytes32,bytes32,string,string)';
8
+ export const DECISION_LOG_RECORD = 'record(bytes32,bytes32,bytes32,uint16,string,string)';
9
+ const TARGETS = {
10
+ devnet: () => ({ chainId: CONTRACTS.devnet.chainId, answerLog: CONTRACTS.devnet.answerLog.address, decisionLog: CONTRACTS.devnet.decisionLog.address,
11
+ note: 'devnet 36927: any account may send (gas from https://faucet.taifoon.dev). A sender outside JevAnswerLog’s deploy-time recorders is logged with trusted = false.' }),
12
+ base: () => ({ chainId: CONTRACTS.base.chainId, answerLog: CONTRACTS.base.answerLog?.address ?? null, decisionLog: CONTRACTS.base.decisionLog?.address ?? null,
13
+ note: CONTRACTS.base.answerLog && CONTRACTS.base.decisionLog ? 'Base 8453: the logs at CONTRACTS.base.' : 'Base 8453: this package version carries no Base address for the logs, so these calls have to = null (the call data is final; set `to` to send them).' }),
14
+ };
15
+ export async function record(r, opts = {}) {
16
+ if (!r.decision || !r.answersRecord || !r.answersDigest)
17
+ throw new Error(`nothing to record: Jev was not asked (${r.reasons[0] ?? r.verdict}) — a hard fail is decided by the facts alone`);
18
+ const network = opts.network ?? 'devnet';
19
+ const digests = { answers: r.answersDigest, decision: r.decision.digest, input: r.inputDigest, subject: r.subjectId, receipt: r.receiptHash };
20
+ if (network === 'none')
21
+ return { network, status: 'none', calls: [], digests, notes: ['network none: the receipt and its digests only'] };
22
+ const model = (r.answersRecord.upstream_model ?? r.answersRecord.model ?? 'jev').slice(0, 64);
23
+ const uri = (kind, d) => (opts.uri ? `${opts.uri.replace(/\/$/, '')}/${d}` : `urn:jev:${kind}:${d}`).slice(0, 512);
24
+ const answerData = encodeCall(ANSWER_LOG_RECORD, [keccakHex(r.answersRecord.use_case), r.subjectId, r.inputDigest, r.answersDigest, r.decision.digest, model, uri('answers', r.answersDigest)]);
25
+ const decisionData = encodeCall(DECISION_LOG_RECORD, [r.subjectId, r.decision.kindId, r.decision.digest, r.decision.confidenceBps, model, uri('decision', r.decision.digest)]);
26
+ const targets = (network === 'both' ? ['devnet', 'base'] : [network]).map((n) => TARGETS[n]());
27
+ const calls = targets.flatMap((t) => [
28
+ { chainId: t.chainId, to: t.answerLog, value: '0', fn: 'JevAnswerLog.record', data: answerData },
29
+ { chainId: t.chainId, to: t.decisionLog, value: '0', fn: 'JevDecisionLog.record', data: decisionData },
30
+ ]);
31
+ const out = { network, status: calls.every((c) => c.to) ? 'ready' : 'partial', calls, digests, notes: targets.map((t) => t.note) };
32
+ if (opts.send) {
33
+ out.sent = [];
34
+ for (const c of calls)
35
+ if (c.to)
36
+ out.sent.push(await opts.send(c));
37
+ if (out.sent.length)
38
+ out.status = 'sent';
39
+ }
40
+ return out;
41
+ }
@@ -0,0 +1,78 @@
1
+ import { type Hex } from './hash.js';
2
+ import type { Answer, Question } from './rubric.js';
3
+ export declare const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
4
+ export declare const ZERO_HASH: Hex;
5
+ /** What a grade is about: a chain, an address (a job contract, a seller, zero when none) and a reference. */
6
+ export type Subject = {
7
+ chainId: number;
8
+ at?: string | null;
9
+ ref: string;
10
+ label?: string | null;
11
+ };
12
+ export declare const subjectOf: (s: string | Subject) => Subject;
13
+ /** a 32-byte hex passes through; anything else is keccak'd */
14
+ export declare const refBytes32: (ref: string) => Hex;
15
+ /** = JevDecisionLog.subjectOf(chainId, at, ref): keccak256(abi.encode("taifoon.decision.subject.v1", chainId, at, ref)) */
16
+ export declare const subjectIdOf: (s: Subject) => Hex;
17
+ export declare const kindIdOf: (kind: string) => Hex;
18
+ export declare function canonicalize(v: unknown): unknown;
19
+ export declare const canonicalJson: (v: unknown) => string;
20
+ export type DecisionAnswer = {
21
+ id: string;
22
+ question: string | null;
23
+ options: readonly string[] | Record<string, string> | null;
24
+ value: string;
25
+ probabilities: Record<string, number>;
26
+ confidence: number;
27
+ };
28
+ export type Decision = {
29
+ v: 'decision.v2';
30
+ kind: string;
31
+ subject: Subject;
32
+ answers: DecisionAnswer[];
33
+ model: string | null;
34
+ input_digest: Hex;
35
+ };
36
+ export declare function decisionDigest(d: Omit<Decision, 'v'>): Hex;
37
+ /** a battery's on-chain confidence: its LOWEST answer, in basis points */
38
+ export declare const confidenceBpsOf: (answers: Array<{
39
+ confidence: number;
40
+ }>) => number;
41
+ export declare const decisionAnswersOf: (answers: Record<string, Answer>, questions: readonly Question[]) => DecisionAnswer[];
42
+ export type AnswerRecord = {
43
+ v: 'jev.answer.v1';
44
+ use_case: string;
45
+ subject: Hex;
46
+ input_digest: Hex | null;
47
+ questions: Array<{
48
+ id: string;
49
+ kind: 'choice';
50
+ text: string | null;
51
+ options: readonly string[] | null;
52
+ }>;
53
+ answers: Array<{
54
+ id: string;
55
+ value: string;
56
+ distribution: Record<string, number>;
57
+ confidence: number | null;
58
+ }>;
59
+ model: string | null;
60
+ upstream_model: string | null;
61
+ latency_ms: number | null;
62
+ credential_path: string;
63
+ caller: string;
64
+ at: number;
65
+ };
66
+ export declare const answersDigestOf: (r: AnswerRecord) => Hex;
67
+ export declare function answerRecordOf(a: {
68
+ subjectId: Hex;
69
+ inputDigest: Hex;
70
+ answers: Record<string, Answer>;
71
+ questions: readonly Question[];
72
+ model: string | null;
73
+ latency_ms: number | null;
74
+ credential_path: string;
75
+ caller: string;
76
+ at: number;
77
+ use_case?: string;
78
+ }): AnswerRecord;