@taifoon/jev 0.1.0 → 0.2.1
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 +97 -191
- package/bin/jev.mjs +9 -3
- package/bin/run.mjs +86 -0
- package/bin/term.mjs +154 -0
- package/dist/ask.d.ts +5 -8
- package/dist/ask.js +16 -28
- package/dist/contracts.d.ts +5 -1
- package/dist/contracts.js +7 -3
- package/dist/grade.d.ts +0 -4
- package/dist/grade.js +5 -6
- package/dist/index.d.ts +7 -1
- package/dist/index.js +5 -1
- package/dist/job.d.ts +65 -0
- package/dist/job.js +50 -0
- package/dist/pipeline.d.ts +156 -0
- package/dist/pipeline.js +193 -0
- package/dist/rubric.js +1 -1
- package/dist/verify.d.ts +6 -0
- package/dist/verify.js +48 -9
- package/docs/API.md +20 -0
- package/docs/DEVELOP.md +7 -0
- package/docs/EVALUATORS.md +20 -0
- package/docs/ON_CHAIN.md +44 -0
- package/docs/PIPELINE.md +76 -0
- package/docs/RECORDS.md +10 -0
- package/docs/WORKFLOWS.md +38 -0
- package/examples/grade-job.mjs +17 -0
- package/examples/jobs/base-bitagent-7287.recorded.json +80 -0
- package/examples/jobs/invoice-extraction.json +24 -0
- package/examples/jobs/research-report-no-sources.json +29 -0
- package/examples/jobs/research-report.json +34 -0
- package/examples/jobs/signal-unsupported.json +27 -0
- package/examples/quickstart.mjs +22 -0
- package/package.json +19 -11
- package/workflows/{0.1.0 → 0.2.1}/README.md +3 -3
- package/workflows/{0.1.0 → 0.2.1}/jev-grader.json +2 -2
- package/workflows/{0.1.0 → 0.2.1}/manifest.json +12 -12
- package/workflows/{0.1.0 → 0.2.1}/schemas/answers.json +17 -35
- package/workflows/{0.1.0 → 0.2.1}/schemas/common.json +1 -1
- package/workflows/{0.1.0 → 0.2.1}/schemas/delivery.json +15 -31
- package/workflows/{0.1.0 → 0.2.1}/schemas/facts.json +15 -31
- package/workflows/{0.1.0 → 0.2.1}/schemas/hire.json +22 -45
- package/workflows/{0.1.0 → 0.2.1}/schemas/job.json +7 -15
- package/workflows/{0.1.0 → 0.2.1}/schemas/settlement.json +17 -33
- package/workflows/{0.1.0 → 0.2.1}/schemas/verdict.json +17 -35
- package/workflows/0.2.1/stamp-grade.json +175 -0
- package/workflows/0.1.0/stamp-grade.json +0 -175
- /package/workflows/{0.1.0 → 0.2.1}/batch-judge.json +0 -0
- /package/workflows/{0.1.0 → 0.2.1}/hire-judge-settle.json +0 -0
package/dist/ask.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type { Answer, Question } from './rubric.js';
|
|
2
|
-
export declare const TRIAL_URL = "https://typesafe.taifoon.dev/v1/trial";
|
|
3
2
|
export declare const TYPESAFE_URL = "https://api.typesafe.ai";
|
|
4
|
-
|
|
3
|
+
/** The model every key-path call asks for. Pinned, not `jev-latest`: a record says which model answered, and an alias can move. */
|
|
4
|
+
export declare const JEV_MODEL = "jev-1.13.0";
|
|
5
5
|
export type Asked = {
|
|
6
6
|
answers: Record<string, Answer>;
|
|
7
7
|
model: string | null;
|
|
8
8
|
latency_ms: number | null;
|
|
9
|
-
connection: '
|
|
10
|
-
trial:
|
|
11
|
-
calls: number;
|
|
12
|
-
left: number;
|
|
13
|
-
} | null;
|
|
9
|
+
connection: 'key';
|
|
10
|
+
trial: null;
|
|
14
11
|
};
|
|
15
12
|
export declare class JevError extends Error {
|
|
16
13
|
readonly status: number;
|
|
@@ -21,7 +18,7 @@ export declare function askJev(a: {
|
|
|
21
18
|
text: string;
|
|
22
19
|
questions: readonly Question[];
|
|
23
20
|
key?: string | null;
|
|
24
|
-
trialUrl?: string;
|
|
25
21
|
endpoint?: string;
|
|
22
|
+
model?: string;
|
|
26
23
|
fetch?: typeof fetch;
|
|
27
24
|
}): Promise<Asked>;
|
package/dist/ask.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export const TRIAL_URL = 'https://typesafe.taifoon.dev/v1/trial';
|
|
2
1
|
export const TYPESAFE_URL = 'https://api.typesafe.ai';
|
|
3
|
-
|
|
2
|
+
/** The model every key-path call asks for. Pinned, not `jev-latest`: a record says which model answered, and an alias can move. */
|
|
3
|
+
export const JEV_MODEL = 'jev-1.13.0';
|
|
4
4
|
export class JevError extends Error {
|
|
5
5
|
status;
|
|
6
6
|
next;
|
|
@@ -15,33 +15,21 @@ const norm = (id, a) => ({ id, value: String(a.value ?? a.choice ?? ''), confide
|
|
|
15
15
|
export async function askJev(a) {
|
|
16
16
|
const f = a.fetch ?? fetch;
|
|
17
17
|
const started = Date.now();
|
|
18
|
-
if (a.key)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!r.ok)
|
|
26
|
-
throw new JevError(`TypeSafe answered ${r.status}${r.status === 401 || r.status === 403 ? ': the key was rejected' : r.status === 402 ? ': the account has no credit' : ''}`, r.status);
|
|
27
|
-
const answers = {};
|
|
28
|
-
for (const q of a.questions) {
|
|
29
|
-
const x = j.answers?.[q.id];
|
|
30
|
-
if (x)
|
|
31
|
-
answers[q.id] = norm(q.id, x);
|
|
32
|
-
}
|
|
33
|
-
return { answers, model: j.model ?? null, latency_ms: Date.now() - started, connection: 'key', trial: null };
|
|
34
|
-
}
|
|
35
|
-
const body = { state: a.text, questions: a.questions.map((q) => ({ id: q.id, kind: 'choice', text: q.text, options: [...q.options] })) };
|
|
36
|
-
const r = await f(a.trialUrl ?? TRIAL_URL, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), signal: AbortSignal.timeout(65_000) });
|
|
18
|
+
if (!a.key)
|
|
19
|
+
throw new JevError('a TypeSafe key is required: get one at console.typesafe.ai and pass { key }', 400, 'grade({ key: process.env.TYPESAFE_KEY })');
|
|
20
|
+
const base = (a.endpoint ?? TYPESAFE_URL).replace(/\/$/, '');
|
|
21
|
+
if (!/^https:\/\//.test(base))
|
|
22
|
+
throw new JevError('a key is only sent over https', 400);
|
|
23
|
+
const questions = Object.fromEntries(a.questions.map((q) => [q.id, { type: 'choice', instructions: q.text, criteria: Object.fromEntries(q.options.map((o) => [o, o])) }]));
|
|
24
|
+
const r = await f(`${base}/v1/systemone`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${a.key}` }, body: JSON.stringify({ model: a.model ?? JEV_MODEL, state: a.text, questions }), signal: AbortSignal.timeout(65_000) });
|
|
37
25
|
const j = (await r.json().catch(() => ({})));
|
|
38
|
-
if (!r.ok
|
|
39
|
-
throw new JevError(`
|
|
26
|
+
if (!r.ok)
|
|
27
|
+
throw new JevError(`TypeSafe answered ${r.status}${r.status === 401 || r.status === 403 ? ': the key was rejected' : r.status === 402 ? ': the account has no credit' : ''}`, r.status);
|
|
40
28
|
const answers = {};
|
|
41
|
-
for (const
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
answers[id] = norm(id, x);
|
|
29
|
+
for (const q of a.questions) {
|
|
30
|
+
const x = j.answers?.[q.id];
|
|
31
|
+
if (x)
|
|
32
|
+
answers[q.id] = norm(q.id, x);
|
|
45
33
|
}
|
|
46
|
-
return { answers, model: j.
|
|
34
|
+
return { answers, model: j.model ?? null, latency_ms: Date.now() - started, connection: 'key', trial: null };
|
|
47
35
|
}
|
package/dist/contracts.d.ts
CHANGED
|
@@ -17,18 +17,22 @@ export declare const CONTRACTS: Readonly<{
|
|
|
17
17
|
readonly judgeAdapter: "0xbd4c9e797a8DBbfdde9C77bc5A5b21ACf611Db8f";
|
|
18
18
|
readonly assuranceHook: "0x7110c8951d17b69054395742e265bbd98259564a";
|
|
19
19
|
};
|
|
20
|
-
/** Base (8453)
|
|
20
|
+
/** Base (8453): both logs are live. Only the KMS recorder 0xe9F0E71e7Fc66864126C0aE5588a7858b25dE51D is `trusted` on the answer log. */
|
|
21
21
|
readonly base: {
|
|
22
22
|
readonly chainId: 8453;
|
|
23
23
|
readonly rpc: "https://mainnet.base.org";
|
|
24
|
+
/** JevAnswerLog, redeployed 2026-09-27 (tx 0xeb7d2dac…ae87); supersedes 0x5bac70eb78224bbCBa83f5A36DF09D549d3f57fa */
|
|
24
25
|
readonly answerLog: {
|
|
25
26
|
address: string;
|
|
26
27
|
fromBlock: number;
|
|
27
28
|
} | null;
|
|
29
|
+
/** JevDecisionLog, deployed 2026-09-27 (tx 0x0e6300c0…4fc7); the recorder is msg.sender */
|
|
28
30
|
readonly decisionLog: {
|
|
29
31
|
address: string;
|
|
30
32
|
fromBlock: number;
|
|
31
33
|
} | null;
|
|
34
|
+
/** the one trusted recorder of the Base answer log */
|
|
35
|
+
readonly answerRecorder: "0xe9F0E71e7Fc66864126C0aE5588a7858b25dE51D";
|
|
32
36
|
readonly virtualsErc8183: "0x238E541BfefD82238730D00a2208E5497F1832E0";
|
|
33
37
|
readonly virtualsMemoAcpRouter: "0xa6C9BA866992cfD7fd6460ba912bfa405adA9df0";
|
|
34
38
|
readonly bitagentErc8183: "0x5009ABB3A309115a4a682C66BAf3BC9E0329BaB7";
|
package/dist/contracts.js
CHANGED
|
@@ -13,12 +13,16 @@ export const CONTRACTS = Object.freeze({
|
|
|
13
13
|
judgeAdapter: '0xbd4c9e797a8DBbfdde9C77bc5A5b21ACf611Db8f',
|
|
14
14
|
assuranceHook: '0x7110c8951d17b69054395742e265bbd98259564a',
|
|
15
15
|
},
|
|
16
|
-
/** Base (8453)
|
|
16
|
+
/** Base (8453): both logs are live. Only the KMS recorder 0xe9F0E71e7Fc66864126C0aE5588a7858b25dE51D is `trusted` on the answer log. */
|
|
17
17
|
base: {
|
|
18
18
|
chainId: 8453,
|
|
19
19
|
rpc: 'https://mainnet.base.org',
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
/** JevAnswerLog, redeployed 2026-09-27 (tx 0xeb7d2dac…ae87); supersedes 0x5bac70eb78224bbCBa83f5A36DF09D549d3f57fa */
|
|
21
|
+
answerLog: { address: '0x8e9B9cE86a2d55c10318607b0c815B7B8C66254d', fromBlock: 51_863_008 },
|
|
22
|
+
/** JevDecisionLog, deployed 2026-09-27 (tx 0x0e6300c0…4fc7); the recorder is msg.sender */
|
|
23
|
+
decisionLog: { address: '0x209490d6A0FFC5368A42b0c2208BDCda853f6a92', fromBlock: 51_856_200 },
|
|
24
|
+
/** the one trusted recorder of the Base answer log */
|
|
25
|
+
answerRecorder: '0xe9F0E71e7Fc66864126C0aE5588a7858b25dE51D',
|
|
22
26
|
virtualsErc8183: '0x238E541BfefD82238730D00a2208E5497F1832E0',
|
|
23
27
|
virtualsMemoAcpRouter: '0xa6C9BA866992cfD7fd6460ba912bfa405adA9df0',
|
|
24
28
|
bitagentErc8183: '0x5009ABB3A309115a4a682C66BAf3BC9E0329BaB7',
|
package/dist/grade.d.ts
CHANGED
|
@@ -12,10 +12,6 @@ export type GradeInput = {
|
|
|
12
12
|
rubric?: Rubric | RubricInput;
|
|
13
13
|
/** your own TypeSafe key → api.typesafe.ai. Never stored, never recorded. */
|
|
14
14
|
key?: string | null;
|
|
15
|
-
/** no key: the public trial (3 free calls). `true` or { url } */
|
|
16
|
-
trial?: boolean | {
|
|
17
|
-
url?: string;
|
|
18
|
-
};
|
|
19
15
|
/** answers you already have (e.g. from the n8n TypeSafe node): nothing is asked, the rest is identical */
|
|
20
16
|
answers?: Record<string, Answer> | Answer[];
|
|
21
17
|
/** the model that produced supplied answers, e.g. "jev-1.13.0" */
|
package/dist/grade.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
// grade(): evidence → deterministic facts → Jev's atomic questions → composed verdict → receipt.
|
|
2
|
-
import { askJev, JevError
|
|
2
|
+
import { askJev, JevError } from './ask.js';
|
|
3
3
|
import { answersOf, buildReceipt, DEFAULT_FACTS, inputFor, packOf, rubricOf } from './receipt.js';
|
|
4
4
|
export async function grade(g) {
|
|
5
5
|
const rubric = rubricOf(g.rubric);
|
|
6
6
|
const facts = (await g.facts) ?? DEFAULT_FACTS;
|
|
7
|
-
const
|
|
8
|
-
const { state, jevState } = inputFor(packOf(g.evidence), facts, useTrial ? TRIAL_MAX_STATE : undefined);
|
|
7
|
+
const { state, jevState } = inputFor(packOf(g.evidence), facts);
|
|
9
8
|
// the facts decide first: a hard fail is final and Jev is never asked
|
|
10
9
|
let asked = null;
|
|
11
10
|
let answers = null;
|
|
@@ -18,9 +17,9 @@ export async function grade(g) {
|
|
|
18
17
|
connection = 'supplied';
|
|
19
18
|
}
|
|
20
19
|
else {
|
|
21
|
-
if (!g.key
|
|
22
|
-
throw new JevError('pass { key } (your TypeSafe key) or {
|
|
23
|
-
asked = await askJev({ text: jevState, questions: rubric.asked, key: g.key,
|
|
20
|
+
if (!g.key)
|
|
21
|
+
throw new JevError('pass { key } (your TypeSafe key, from console.typesafe.ai) or { answers } you already have', 400);
|
|
22
|
+
asked = await askJev({ text: jevState, questions: rubric.asked, key: g.key, endpoint: g.endpoint, fetch: g.fetch });
|
|
24
23
|
const missing = rubric.asked.filter((q) => !asked.answers[q.id]).map((q) => q.id);
|
|
25
24
|
if (missing.length)
|
|
26
25
|
throw new JevError(`Jev returned no valid answer for: ${missing.join(', ')}`, 502);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @taifoon/jev — Jev as a grader any protocol can call.
|
|
3
3
|
*
|
|
4
|
+
* prepareJob() an agent job as the buyer laid it out → the evidence Jev reads + the code checks
|
|
4
5
|
* grade() evidence → deterministic facts → Jev's atomic questions → composed verdict → receipt
|
|
5
6
|
* facts() the deterministic checks a protocol supplies (a failed one is final; Jev is not asked)
|
|
6
7
|
* record() the unsigned calls that put the receipt's digests on JevAnswerLog / JevDecisionLog
|
|
7
8
|
* evaluatorCall() the unsigned call that ends the job on the protocol whose evaluator seat you hold
|
|
8
9
|
* verify() recompute every digest and find the chain events that hold them
|
|
10
|
+
* pipeline() one job through the coordination layer's steps: pick → evidence → facts → grade → record → evaluator → premium → verify
|
|
9
11
|
* RUBRIC_v1 the questions, thresholds and composition (pass your own rubric to grade)
|
|
10
12
|
* CONTRACTS where the logs and the evaluator seats are
|
|
11
13
|
*
|
|
12
|
-
* No runtime dependency. No key inside:
|
|
14
|
+
* No runtime dependency. No key inside: Jev is called with your own TypeSafe key (console.typesafe.ai).
|
|
13
15
|
*/
|
|
14
16
|
export { grade } from './grade.js';
|
|
15
17
|
export { facts } from './facts.js';
|
|
18
|
+
export { prepareJob, runCheck } from './job.js';
|
|
16
19
|
export { record } from './record.js';
|
|
17
20
|
export { evaluatorCall } from './evaluator/index.js';
|
|
18
21
|
export { verify } from './verify.js';
|
|
22
|
+
export { pipeline, STEPS, LAYER, factsFromEvidence, protocolFor, parseJob } from './pipeline.js';
|
|
19
23
|
export { RUBRIC_v1 } from './rubric.js';
|
|
20
24
|
export { CONTRACTS } from './contracts.js';
|
|
21
25
|
export type { GradeInput, Receipt } from './grade.js';
|
|
22
26
|
export type { FactsInput, Check } from './facts.js';
|
|
27
|
+
export type { JobSpec, JobCheck, PreparedJob } from './job.js';
|
|
23
28
|
export type { Recorded, UnsignedCall, Network } from './record.js';
|
|
24
29
|
export type { Verification, AnswerEvent, DecisionEvent } from './verify.js';
|
|
25
30
|
export type { Protocol, Adapter, AdapterOpts, EvaluatorCall } from './evaluator/index.js';
|
|
@@ -27,3 +32,4 @@ export type { JevError } from './ask.js';
|
|
|
27
32
|
export type { Rubric, RubricInput, Question, Answer, Facts, Verdict, Composed, Thresholds } from './rubric.js';
|
|
28
33
|
export type { Subject, AnswerRecord } from './records.js';
|
|
29
34
|
export type { Hex } from './hash.js';
|
|
35
|
+
export type { PipelineOpts, Trace, StepId, StepResult, Evidence, QueueRow, Quote } from './pipeline.js';
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @taifoon/jev — Jev as a grader any protocol can call.
|
|
3
3
|
*
|
|
4
|
+
* prepareJob() an agent job as the buyer laid it out → the evidence Jev reads + the code checks
|
|
4
5
|
* grade() evidence → deterministic facts → Jev's atomic questions → composed verdict → receipt
|
|
5
6
|
* facts() the deterministic checks a protocol supplies (a failed one is final; Jev is not asked)
|
|
6
7
|
* record() the unsigned calls that put the receipt's digests on JevAnswerLog / JevDecisionLog
|
|
7
8
|
* evaluatorCall() the unsigned call that ends the job on the protocol whose evaluator seat you hold
|
|
8
9
|
* verify() recompute every digest and find the chain events that hold them
|
|
10
|
+
* pipeline() one job through the coordination layer's steps: pick → evidence → facts → grade → record → evaluator → premium → verify
|
|
9
11
|
* RUBRIC_v1 the questions, thresholds and composition (pass your own rubric to grade)
|
|
10
12
|
* CONTRACTS where the logs and the evaluator seats are
|
|
11
13
|
*
|
|
12
|
-
* No runtime dependency. No key inside:
|
|
14
|
+
* No runtime dependency. No key inside: Jev is called with your own TypeSafe key (console.typesafe.ai).
|
|
13
15
|
*/
|
|
14
16
|
export { grade } from './grade.js';
|
|
15
17
|
export { facts } from './facts.js';
|
|
18
|
+
export { prepareJob, runCheck } from './job.js';
|
|
16
19
|
export { record } from './record.js';
|
|
17
20
|
export { evaluatorCall } from './evaluator/index.js';
|
|
18
21
|
export { verify } from './verify.js';
|
|
22
|
+
export { pipeline, STEPS, LAYER, factsFromEvidence, protocolFor, parseJob } from './pipeline.js';
|
|
19
23
|
export { RUBRIC_v1 } from './rubric.js';
|
|
20
24
|
export { CONTRACTS } from './contracts.js';
|
package/dist/job.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { FactsInput } from './facts.js';
|
|
2
|
+
import type { Subject } from './records.js';
|
|
3
|
+
/** A deterministic check on the delivery. `name` labels it in the facts Jev reads (defaults per kind). */
|
|
4
|
+
export type JobCheck = {
|
|
5
|
+
kind: 'includes';
|
|
6
|
+
terms: string[];
|
|
7
|
+
name?: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'sources';
|
|
10
|
+
min: number;
|
|
11
|
+
name?: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'json';
|
|
14
|
+
keys: string[];
|
|
15
|
+
name?: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'deadline';
|
|
18
|
+
submittedAt: string;
|
|
19
|
+
deadline: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
};
|
|
22
|
+
export type JobSpec = {
|
|
23
|
+
/** your job id (the subject's ref) */
|
|
24
|
+
id: string;
|
|
25
|
+
chainId?: number;
|
|
26
|
+
/** the contract or protocol address the job lives at, when there is one */
|
|
27
|
+
at?: string | null;
|
|
28
|
+
/** what the buyer asked for, in their words */
|
|
29
|
+
task: string;
|
|
30
|
+
/** what "done" means, one criterion per line; Jev reads them numbered */
|
|
31
|
+
criteria: string[];
|
|
32
|
+
/** what the agent delivered; null when nothing came back (a hard fail) */
|
|
33
|
+
delivered: string | null;
|
|
34
|
+
/** material the delivery must be faithful to (an invoice, a dataset, a page); optional */
|
|
35
|
+
source?: string | null;
|
|
36
|
+
checks?: JobCheck[];
|
|
37
|
+
priceUsdc?: number | null;
|
|
38
|
+
seller?: string | null;
|
|
39
|
+
};
|
|
40
|
+
export type PreparedJob = {
|
|
41
|
+
subject: Subject;
|
|
42
|
+
/** what Jev reads (grade() adds the facts section and the instruction) */
|
|
43
|
+
evidence: {
|
|
44
|
+
task: string;
|
|
45
|
+
acceptance_criteria: string;
|
|
46
|
+
source?: string;
|
|
47
|
+
delivered: string;
|
|
48
|
+
};
|
|
49
|
+
facts: FactsInput;
|
|
50
|
+
/** the same job as a pipeline() pack: pipeline({ layer: false, evidence: job.pack }) */
|
|
51
|
+
pack: {
|
|
52
|
+
subject: string;
|
|
53
|
+
state: string;
|
|
54
|
+
delivered: boolean;
|
|
55
|
+
checks: Record<string, boolean>;
|
|
56
|
+
priceUsdc: number | null;
|
|
57
|
+
seller?: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
/** Run one check on the delivery. Pure and synchronous, so the same job always yields the same facts. */
|
|
61
|
+
export declare function runCheck(c: JobCheck, delivered: string): {
|
|
62
|
+
name: string;
|
|
63
|
+
ok: boolean;
|
|
64
|
+
};
|
|
65
|
+
export declare function prepareJob(j: JobSpec): PreparedJob;
|
package/dist/job.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const URL_RE = /https?:\/\/[^\s)\]>"']+/g;
|
|
2
|
+
/** Run one check on the delivery. Pure and synchronous, so the same job always yields the same facts. */
|
|
3
|
+
export function runCheck(c, delivered) {
|
|
4
|
+
switch (c.kind) {
|
|
5
|
+
case 'includes': {
|
|
6
|
+
const low = delivered.toLowerCase();
|
|
7
|
+
return { name: c.name ?? `mentions ${c.terms.join(', ')}`, ok: c.terms.every((t) => low.includes(t.toLowerCase())) };
|
|
8
|
+
}
|
|
9
|
+
case 'sources':
|
|
10
|
+
return { name: c.name ?? `cites at least ${c.min} source${c.min === 1 ? '' : 's'}`, ok: new Set(delivered.match(URL_RE) ?? []).size >= c.min };
|
|
11
|
+
case 'json': {
|
|
12
|
+
let ok = false;
|
|
13
|
+
try {
|
|
14
|
+
const v = JSON.parse(delivered);
|
|
15
|
+
ok = !!v && typeof v === 'object' && !Array.isArray(v) && c.keys.every((k) => k in v);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
ok = false;
|
|
19
|
+
}
|
|
20
|
+
return { name: c.name ?? `JSON with ${c.keys.join(', ')}`, ok };
|
|
21
|
+
}
|
|
22
|
+
case 'deadline':
|
|
23
|
+
return { name: c.name ?? 'delivered by the deadline', ok: Date.parse(c.submittedAt) <= Date.parse(c.deadline) };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function prepareJob(j) {
|
|
27
|
+
if (!j.task.trim())
|
|
28
|
+
throw new Error('prepareJob: a job needs its task');
|
|
29
|
+
if (!j.criteria.length)
|
|
30
|
+
throw new Error('prepareJob: a job needs at least one acceptance criterion (what "done" means)');
|
|
31
|
+
const delivered = j.delivered ?? '';
|
|
32
|
+
const checks = {};
|
|
33
|
+
if (j.delivered != null)
|
|
34
|
+
for (const c of j.checks ?? []) {
|
|
35
|
+
const r = runCheck(c, delivered);
|
|
36
|
+
checks[r.name] = r.ok;
|
|
37
|
+
}
|
|
38
|
+
const evidence = {
|
|
39
|
+
task: j.task,
|
|
40
|
+
acceptance_criteria: j.criteria.map((c, i) => `${i + 1}. ${c}`).join('\n'),
|
|
41
|
+
...(j.source ? { source: j.source } : {}),
|
|
42
|
+
delivered: j.delivered ?? '(nothing was delivered)',
|
|
43
|
+
};
|
|
44
|
+
const subject = { chainId: j.chainId ?? 0, at: j.at ?? null, ref: j.id };
|
|
45
|
+
const facts = { delivered: j.delivered != null && delivered.trim() !== '', checks, priceUsdc: j.priceUsdc ?? null };
|
|
46
|
+
return {
|
|
47
|
+
subject, evidence, facts,
|
|
48
|
+
pack: { subject: j.id, state: JSON.stringify(evidence), delivered: facts.delivered, checks, priceUsdc: j.priceUsdc ?? null, ...(j.seller ? { seller: j.seller } : {}) },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { type FactsInput } from './facts.js';
|
|
2
|
+
import { type Network, type Recorded } from './record.js';
|
|
3
|
+
import { type Verification } from './verify.js';
|
|
4
|
+
import { type Protocol, type EvaluatorCall } from './evaluator/index.js';
|
|
5
|
+
import type { Receipt } from './receipt.js';
|
|
6
|
+
import type { Answer } from './rubric.js';
|
|
7
|
+
import type { Subject } from './records.js';
|
|
8
|
+
export declare const LAYER = "https://coord.taifoon.dev";
|
|
9
|
+
export type StepId = 'pick' | 'evidence' | 'facts' | 'grade' | 'record' | 'evaluator' | 'premium' | 'verify';
|
|
10
|
+
/** The steps in order, each with the layer route it reads (null: runs locally only). */
|
|
11
|
+
/** `required`: the later steps need its output, so saying no at the gate stops the run instead of skipping it. */
|
|
12
|
+
export declare const STEPS: ReadonlyArray<{
|
|
13
|
+
id: StepId;
|
|
14
|
+
title: string;
|
|
15
|
+
route: string | null;
|
|
16
|
+
required?: true;
|
|
17
|
+
}>;
|
|
18
|
+
/** One row of /v1/judge/queue, as far as the pipeline reads it. */
|
|
19
|
+
export type QueueRow = {
|
|
20
|
+
jobId: string;
|
|
21
|
+
why: string;
|
|
22
|
+
ending: string;
|
|
23
|
+
budget: number | null;
|
|
24
|
+
seller: string;
|
|
25
|
+
buyer: string;
|
|
26
|
+
task: string | null;
|
|
27
|
+
evidence: string;
|
|
28
|
+
evidence_ready: string;
|
|
29
|
+
};
|
|
30
|
+
/** /v1/judge/evidence/{chain}/{jobId}: `state` is the pack Jev reads; `facts` are [label, value] pairs. */
|
|
31
|
+
export type Evidence = {
|
|
32
|
+
chainId: number;
|
|
33
|
+
jobId: string;
|
|
34
|
+
state: string;
|
|
35
|
+
facts: Array<[string, string]>;
|
|
36
|
+
gaps?: string[];
|
|
37
|
+
};
|
|
38
|
+
export type Quote = {
|
|
39
|
+
guaranteed: boolean;
|
|
40
|
+
premium: string | null;
|
|
41
|
+
premium_ratio: number | null;
|
|
42
|
+
premium_label: string | null;
|
|
43
|
+
deposit: string | null;
|
|
44
|
+
record?: {
|
|
45
|
+
n: number;
|
|
46
|
+
settled: number;
|
|
47
|
+
incorrect: number;
|
|
48
|
+
wilson: [number, number];
|
|
49
|
+
calibrated: boolean;
|
|
50
|
+
insurable: boolean;
|
|
51
|
+
};
|
|
52
|
+
why?: string[];
|
|
53
|
+
};
|
|
54
|
+
export type LayerRecord = {
|
|
55
|
+
digest?: string;
|
|
56
|
+
uri?: string;
|
|
57
|
+
anchor?: {
|
|
58
|
+
tx?: string;
|
|
59
|
+
status?: string;
|
|
60
|
+
};
|
|
61
|
+
} & Record<string, unknown>;
|
|
62
|
+
export type StepResult<T = unknown> = {
|
|
63
|
+
id: StepId;
|
|
64
|
+
ok: boolean;
|
|
65
|
+
skipped?: string;
|
|
66
|
+
stopped?: true;
|
|
67
|
+
ms: number;
|
|
68
|
+
out?: T;
|
|
69
|
+
error?: string;
|
|
70
|
+
};
|
|
71
|
+
export type Trace = {
|
|
72
|
+
job: {
|
|
73
|
+
chainId: number;
|
|
74
|
+
jobId: string;
|
|
75
|
+
seller: string | null;
|
|
76
|
+
} | null;
|
|
77
|
+
steps: StepResult[];
|
|
78
|
+
receipt: Receipt | null;
|
|
79
|
+
recorded: Recorded | null;
|
|
80
|
+
layerRecord: LayerRecord | null;
|
|
81
|
+
evaluator: EvaluatorCall | null;
|
|
82
|
+
quote: Quote | null;
|
|
83
|
+
verification: Verification | null;
|
|
84
|
+
};
|
|
85
|
+
export type PipelineOpts = {
|
|
86
|
+
/** the coordination layer; `false` = never read it (independent mode: `evidence` is required) */
|
|
87
|
+
layer?: string | false;
|
|
88
|
+
/** a job to grade, `8453:81100` or { chainId, jobId }; omitted with the layer on: the first ready row of the queue */
|
|
89
|
+
job?: string | {
|
|
90
|
+
chainId: number;
|
|
91
|
+
jobId: string;
|
|
92
|
+
};
|
|
93
|
+
/** your own evidence pack; skips pick + evidence */
|
|
94
|
+
evidence?: {
|
|
95
|
+
subject: string;
|
|
96
|
+
chainId?: number;
|
|
97
|
+
label?: string;
|
|
98
|
+
state: string;
|
|
99
|
+
delivered?: boolean;
|
|
100
|
+
checks?: FactsInput['checks'];
|
|
101
|
+
priceUsdc?: number | null;
|
|
102
|
+
seller?: string;
|
|
103
|
+
};
|
|
104
|
+
/** your TypeSafe key from console.typesafe.ai (never stored, never recorded) */
|
|
105
|
+
key?: string | null;
|
|
106
|
+
/** answers you already have (e.g. from the n8n TypeSafe node): Jev is not asked */
|
|
107
|
+
answers?: Record<string, Answer> | Answer[];
|
|
108
|
+
/** the model that produced supplied `answers` (e.g. "jev-1.13.0"); it is part of the decision digest */
|
|
109
|
+
model?: string | null;
|
|
110
|
+
/** where record() points the calls; default devnet */
|
|
111
|
+
/** recording is opt-in: where record() points the calls and what the layer may record. Default none: the receipt and
|
|
112
|
+
* its digests only (recomputable offline), no transaction. */
|
|
113
|
+
network?: Network;
|
|
114
|
+
/** X-API-Key for POST /v1/judge/answers/record; omitted: nothing is written to the layer */
|
|
115
|
+
relayerKey?: string | null;
|
|
116
|
+
/** the job's protocol for the evaluator call; omitted: guessed from the job id */
|
|
117
|
+
protocol?: Protocol | null;
|
|
118
|
+
/** a tenant binding (e.g. Moonbeam: { chainId: 8453, at: <hook>, ref: jobId }): overrides the subject grade() records */
|
|
119
|
+
subject?: (job: {
|
|
120
|
+
chainId: number;
|
|
121
|
+
jobId: string;
|
|
122
|
+
}) => Subject;
|
|
123
|
+
/** who is grading, recorded in the answer record (e.g. 'moonbeam') */
|
|
124
|
+
caller?: string;
|
|
125
|
+
/** read the logs on chain in verify (default: only when record() sent something) */
|
|
126
|
+
chainVerify?: boolean;
|
|
127
|
+
/** price for the premium step, in USDC; default: the job's budget */
|
|
128
|
+
priceUsdc?: number | null;
|
|
129
|
+
/** a gate before each step: return false to skip it. The interactive CLI asks here. */
|
|
130
|
+
before?: (step: (typeof STEPS)[number], trace: Trace) => boolean | Promise<boolean>;
|
|
131
|
+
/** called after each step with its result */
|
|
132
|
+
after?: (result: StepResult, trace: Trace) => void | Promise<void>;
|
|
133
|
+
fetch?: typeof fetch;
|
|
134
|
+
};
|
|
135
|
+
/** A job reference: `8453:81100`, `8453:bitagent:8453:7287`, or a bare id (chain 8453). */
|
|
136
|
+
export declare function parseJob(j: string | {
|
|
137
|
+
chainId: number;
|
|
138
|
+
jobId: string;
|
|
139
|
+
}): {
|
|
140
|
+
chainId: number;
|
|
141
|
+
jobId: string;
|
|
142
|
+
};
|
|
143
|
+
/** The layer's queue rows a grader can actually read: evidence must resolve (decimal id or label:chain:id). */
|
|
144
|
+
export declare const readable: (r: QueueRow) => boolean;
|
|
145
|
+
/**
|
|
146
|
+
* The deterministic facts an ERC-8183 evidence pack supports on its own:
|
|
147
|
+
* delivered = the pack names a delivery; submitted_before_deadline when both times are on chain; priceUsdc from "price".
|
|
148
|
+
* A protocol that can re-derive the delivery (a proof, a hash, a file) adds its own checks on top.
|
|
149
|
+
*/
|
|
150
|
+
export declare function factsFromEvidence(ev: Evidence, extra?: FactsInput['checks']): FactsInput;
|
|
151
|
+
/** The evaluator seat for a job id, when the id says which protocol it is on. */
|
|
152
|
+
export declare function protocolFor(jobId: string): {
|
|
153
|
+
protocol: Protocol;
|
|
154
|
+
id: string;
|
|
155
|
+
} | null;
|
|
156
|
+
export declare function pipeline(o?: PipelineOpts): Promise<Trace>;
|