@taifoon/jev 0.2.5 → 0.2.7

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.
@@ -0,0 +1,819 @@
1
+ {
2
+ "name": "Taifoon — HIRE → JUDGE → SETTLE (devnet 36927)",
3
+ "nodes": [
4
+ {
5
+ "parameters": {},
6
+ "id": "n0",
7
+ "name": "Start",
8
+ "type": "n8n-nodes-base.manualTrigger",
9
+ "typeVersion": 1,
10
+ "position": [
11
+ 0,
12
+ 300
13
+ ],
14
+ "notes": "in: — → out: — · start a run (swap for a Webhook or Schedule trigger)",
15
+ "notesInFlow": true
16
+ },
17
+ {
18
+ "parameters": {
19
+ "assignments": {
20
+ "assignments": [
21
+ {
22
+ "id": "i1",
23
+ "name": "task",
24
+ "value": "Verify Base block 51828000 under the SuperRoot and return the portable V5 proof (class proof.verify.v5).",
25
+ "type": "string"
26
+ },
27
+ {
28
+ "id": "i2",
29
+ "name": "candidate",
30
+ "value": "0x3574999dd4c96eb73bd6e11d4177010c83e14f5b",
31
+ "type": "string"
32
+ },
33
+ {
34
+ "id": "i3",
35
+ "name": "endpoint",
36
+ "value": "https://www.taifoon.io/api/mcp",
37
+ "type": "string"
38
+ },
39
+ {
40
+ "id": "i4",
41
+ "name": "tool",
42
+ "value": "taifoon_proof",
43
+ "type": "string"
44
+ },
45
+ {
46
+ "id": "i5",
47
+ "name": "block_number",
48
+ "value": 51828000,
49
+ "type": "number"
50
+ },
51
+ {
52
+ "id": "i6",
53
+ "name": "buyer",
54
+ "value": "0xf6806EFcb34dDa2eE78967384D09367ECd364483",
55
+ "type": "string"
56
+ },
57
+ {
58
+ "id": "i7",
59
+ "name": "seller",
60
+ "value": "0xe6209aD65a66d5eF11E00A7B33Cd21ce7084715c",
61
+ "type": "string"
62
+ },
63
+ {
64
+ "id": "i8",
65
+ "name": "price",
66
+ "value": "1000000",
67
+ "type": "string"
68
+ },
69
+ {
70
+ "id": "i9",
71
+ "name": "chainId",
72
+ "value": 36927,
73
+ "type": "number"
74
+ },
75
+ {
76
+ "id": "i10",
77
+ "name": "lane",
78
+ "value": "a",
79
+ "type": "string"
80
+ },
81
+ {
82
+ "id": "i11",
83
+ "name": "tenant",
84
+ "value": "",
85
+ "type": "string"
86
+ },
87
+ {
88
+ "id": "i12",
89
+ "name": "deadline_sec",
90
+ "value": 300,
91
+ "type": "number"
92
+ }
93
+ ]
94
+ },
95
+ "options": {}
96
+ },
97
+ "id": "n1",
98
+ "name": "Inputs",
99
+ "type": "n8n-nodes-base.set",
100
+ "typeVersion": 3.4,
101
+ "position": [
102
+ 220,
103
+ 300
104
+ ],
105
+ "notes": "in: — → out: hire.json · the hire intent: task, candidate seller, endpoint/tool, buyer, price, chain (a HIRE in state Open)",
106
+ "notesInFlow": true
107
+ },
108
+ {
109
+ "parameters": {
110
+ "resource": "handshake",
111
+ "operation": "open",
112
+ "candidateAddress": "={{ $json.candidate }}",
113
+ "candidateKind": "mcp",
114
+ "task": "={{ $json.task }}",
115
+ "openOptions": {
116
+ "args": "={{ JSON.stringify({ chain_id: 8453, block_number: $json.block_number }) }}",
117
+ "chain": "={{ String($json.chainId) }}",
118
+ "dispatch": true,
119
+ "endpoint": "={{ $json.endpoint }}",
120
+ "hirer": "={{ $json.buyer }}",
121
+ "required_skills": "proof.verify.v5",
122
+ "tool": "={{ $json.tool }}"
123
+ }
124
+ },
125
+ "id": "n2",
126
+ "name": "Offer + deliver",
127
+ "type": "CUSTOM.taifoon",
128
+ "typeVersion": 1,
129
+ "position": [
130
+ 440,
131
+ 300
132
+ ],
133
+ "notes": "in: hire.json → out: job.json · POST /v1/handshake {dispatch:true}: the offer goes to the seller’s own endpoint; the reply is held with its keccak digest",
134
+ "notesInFlow": true,
135
+ "credentials": {
136
+ "taifoonRelayerApi": {
137
+ "id": "",
138
+ "name": "REPLACE: taifoonRelayerApi"
139
+ }
140
+ }
141
+ },
142
+ {
143
+ "parameters": {
144
+ "jsCode": "// Delivery: what came back over the wire. No digest = nothing to settle; say why and stop.\nconst r = $input.first().json;\nconst d = r.delivery || {};\nconst digest = d.reply_digest || d.digest || null;\nif (!r.handshake_id) throw new Error('no handshake id in ' + JSON.stringify(r).slice(0, 300));\nif (!digest) throw new Error('no delivery digest: dispatch status ' + (d.status || 'none') + (d.reason ? ' — ' + d.reason : ''));\nreturn [{ json: { handshake_id: r.handshake_id, delivery_status: d.status || null, protocol: d.protocol || null, latency_ms: d.latency_ms ?? null, reply_head: d.reply_head || null, evidence: digest, target: r.target || null } }];"
145
+ },
146
+ "id": "n3",
147
+ "name": "Delivery",
148
+ "type": "n8n-nodes-base.code",
149
+ "typeVersion": 2,
150
+ "position": [
151
+ 660,
152
+ 300
153
+ ],
154
+ "notes": "in: job.json → out: delivery.json · stop unless a reply digest is held (no digest = nothing to settle)",
155
+ "notesInFlow": true
156
+ },
157
+ {
158
+ "name": "Judge · prepare",
159
+ "type": "CUSTOM.taifoon",
160
+ "typeVersion": 1,
161
+ "position": [
162
+ 880,
163
+ 300
164
+ ],
165
+ "parameters": {
166
+ "resource": "judge",
167
+ "operation": "compose",
168
+ "composeSubject": "handshake",
169
+ "composeHandshakeId": "={{ $json.handshake_id }}",
170
+ "composePriceUsdc": 1,
171
+ "composeMode": "prepare"
172
+ },
173
+ "credentials": {
174
+ "taifoonRelayerApi": {
175
+ "id": "",
176
+ "name": "REPLACE: taifoonRelayerApi"
177
+ }
178
+ },
179
+ "id": "c2af3b0e-7e53-4102-88b8-4d9cea1be0d8",
180
+ "notes": "in: delivery.json → out: facts.json · POST /v1/judge/compose (prepare): the facts decide first; hard_fail ends here; else the exact text Jev reads + prepare_digest",
181
+ "notesInFlow": true
182
+ },
183
+ {
184
+ "name": "Hard fail?",
185
+ "type": "n8n-nodes-base.if",
186
+ "typeVersion": 2.2,
187
+ "position": [
188
+ 1100,
189
+ 300
190
+ ],
191
+ "parameters": {
192
+ "conditions": {
193
+ "options": {
194
+ "caseSensitive": true,
195
+ "leftValue": "",
196
+ "typeValidation": "strict",
197
+ "version": 2
198
+ },
199
+ "conditions": [
200
+ {
201
+ "id": "h1",
202
+ "leftValue": "={{ $json.hard_fail === true }}",
203
+ "rightValue": "",
204
+ "operator": {
205
+ "type": "boolean",
206
+ "operation": "true",
207
+ "singleValue": true
208
+ }
209
+ }
210
+ ],
211
+ "combinator": "and"
212
+ },
213
+ "options": {}
214
+ },
215
+ "id": "039a7992-9c11-4902-a64e-47518a3451c4",
216
+ "notes": "in: facts.json → out: facts.json · branch: a hard fail goes straight to the verdict without asking Jev",
217
+ "notesInFlow": true
218
+ },
219
+ {
220
+ "name": "Jev · TypeSafe (our credential)",
221
+ "type": "@taifoon/n8n-nodes-typesafe.taifoonTypeSafe",
222
+ "typeVersion": 1,
223
+ "position": [
224
+ 1320,
225
+ 420
226
+ ],
227
+ "parameters": {
228
+ "operation": "ask",
229
+ "connection": "direct",
230
+ "state": "={{ JSON.stringify($json.jev.state) }}",
231
+ "questions": {},
232
+ "questionsJson": "={{ JSON.stringify($json.jev.questions) }}",
233
+ "routing": "{}",
234
+ "replyLanguage": "off",
235
+ "failClosed": true
236
+ },
237
+ "credentials": {
238
+ "taifoonTypeSafeApi": {
239
+ "id": "",
240
+ "name": "REPLACE: taifoonTypeSafeApi"
241
+ }
242
+ },
243
+ "notes": "in: facts.json → out: answers.json · TypeSafe ask on the prepared text: every RUBRIC_ASKED answer with its whole distribution",
244
+ "id": "ba778a32-16e0-4076-b815-ffd923ee73f0",
245
+ "notesInFlow": true
246
+ },
247
+ {
248
+ "name": "Judge · answers",
249
+ "type": "CUSTOM.taifoon",
250
+ "typeVersion": 1,
251
+ "position": [
252
+ 1540,
253
+ 420
254
+ ],
255
+ "parameters": {
256
+ "resource": "judge",
257
+ "operation": "compose",
258
+ "composeSubject": "handshake",
259
+ "composeHandshakeId": "={{ $('Delivery').first().json.handshake_id }}",
260
+ "composePriceUsdc": 1,
261
+ "composeMode": "answers",
262
+ "composeAnswers": "={{ JSON.stringify($json.answers) }}",
263
+ "composeModel": "={{ $json.model }}",
264
+ "composePrepareDigest": "={{ $('Judge · prepare').first().json.prepare_digest }}",
265
+ "composeAnsweredBy": "n8n-typesafe",
266
+ "composeExecutionId": "={{ $execution.id }}",
267
+ "composeLatencyMs": "={{ $json.latency_ms }}",
268
+ "composeRecord": "devnet"
269
+ },
270
+ "credentials": {
271
+ "taifoonRelayerApi": {
272
+ "id": "",
273
+ "name": "REPLACE: taifoonRelayerApi"
274
+ }
275
+ },
276
+ "id": "7b18e907-2293-4bb6-8199-c2a090bb7839",
277
+ "notes": "in: answers.json → out: verdict.json · POST /v1/judge/compose (answers): recompose under THRESHOLDS_v1, record decision.v2, anchor on JevDecisionLog",
278
+ "notesInFlow": true
279
+ },
280
+ {
281
+ "parameters": {
282
+ "jsCode": "// The verdict, with what the settlement needs carried forward. Two roads reach here: a hard fail straight from\n// Judge · prepare (the facts decided; Jev was not asked), or Judge · answers (our TypeSafe credential answered, the layer composed).\n// The receipt is the composed verdict: verdict (complete | reject | needs_review), auto, reasons[], receiptHash.\nconst j = $input.first().json;\nconst del = $('Delivery').first().json;\nconst inp = $('Inputs').first().json;\nif (j.ok === false) throw new Error('judge refused: ' + (j.code ? j.code + ': ' : '') + (j.error || JSON.stringify(j).slice(0, 300)));\nconst decision = j.decision || {};\nconst rc = j.receipt || {};\nreturn [{ json: { ...inp, handshake_id: del.handshake_id, evidence: del.evidence, reply_head: del.reply_head, decision_id: decision.id || null, decision_digest: decision.digest || null, decision_anchor: decision.anchor || null, verdict: rc.verdict || null, verdict_auto: rc.auto ?? null, forced: rc.forced || null, reasons: rc.reasons || [], receipt_hash: rc.receiptHash || null, next: j.next || null, judge_called: j.judge_called ?? null, model: j.model || null, judge_latency_ms: j.latency_ms ?? null, answers_source: j.answers_source || null, execution: j.execution || null, prepare_digest: j.prepare_digest || null, quota: j.quota || null } }];"
283
+ },
284
+ "id": "n5",
285
+ "name": "Verdict",
286
+ "type": "n8n-nodes-base.code",
287
+ "typeVersion": 2,
288
+ "position": [
289
+ 1760,
290
+ 300
291
+ ],
292
+ "notes": "in: verdict.json | facts.json → out: verdict.json · the composed verdict (complete | reject | needs_review) with what settlement needs carried forward",
293
+ "notesInFlow": true
294
+ },
295
+ {
296
+ "parameters": {
297
+ "resource": "settlement",
298
+ "operation": "settle",
299
+ "kind": "agent",
300
+ "chainId": 36927,
301
+ "buyer": "={{ $json.buyer }}",
302
+ "seller": "={{ $json.seller }}",
303
+ "price": "={{ $json.price }}",
304
+ "evidence": "={{ $json.evidence }}",
305
+ "refId": "={{ $json.handshake_id }}",
306
+ "settleOptions": {
307
+ "endpoint": "={{ $json.endpoint }}",
308
+ "note": "={{ 'agent ' + $json.candidate + ' · decision ' + ($json.decision_id || 'none') + ' · verdict ' + ($json.verdict || 'n/a') }}",
309
+ "tenant": "={{ $json.tenant }}",
310
+ "deadline": "={{ Math.floor(Date.now() / 1000) + Number($json.deadline_sec || 3600) }}"
311
+ }
312
+ },
313
+ "credentials": {
314
+ "taifoonRelayerApi": {
315
+ "id": "",
316
+ "name": "REPLACE: taifoonRelayerApi"
317
+ }
318
+ },
319
+ "id": "n6",
320
+ "name": "Settle",
321
+ "type": "CUSTOM.taifoon",
322
+ "typeVersion": 1,
323
+ "position": [
324
+ 1980,
325
+ 300
326
+ ],
327
+ "notes": "in: verdict.json → out: settlement.json · POST /v1/settle: the plan (jobId, price, premium, deposit, calls[] in order, who signs each)",
328
+ "notesInFlow": true
329
+ },
330
+ {
331
+ "parameters": {
332
+ "callsField": "calls",
333
+ "onlySigner": "seller",
334
+ "onlyActions": "approve-deposit",
335
+ "sendRequires": false,
336
+ "wait": true
337
+ },
338
+ "credentials": {
339
+ "taifoonDevnetKeyApi": {
340
+ "id": "",
341
+ "name": "REPLACE: taifoonDevnetKeyApi"
342
+ }
343
+ },
344
+ "id": "n7",
345
+ "name": "Seller · approve deposit",
346
+ "type": "CUSTOM.taifoonDevnetSigner",
347
+ "typeVersion": 1,
348
+ "position": [
349
+ 2200,
350
+ 300
351
+ ],
352
+ "notes": "in: settlement.json → out: settlement.json · the seller approves D (> P); the tx joins settlement.txs",
353
+ "notesInFlow": true
354
+ },
355
+ {
356
+ "parameters": {
357
+ "callsField": "calls",
358
+ "onlySigner": "buyer",
359
+ "onlyActions": "fund-job",
360
+ "sendRequires": false,
361
+ "wait": true
362
+ },
363
+ "credentials": {
364
+ "taifoonDevnetKeyApi": {
365
+ "id": "",
366
+ "name": "REPLACE: taifoonDevnetKeyApi"
367
+ }
368
+ },
369
+ "id": "n8",
370
+ "name": "Buyer · approve + fund",
371
+ "type": "CUSTOM.taifoonDevnetSigner",
372
+ "typeVersion": 1,
373
+ "position": [
374
+ 2420,
375
+ 300
376
+ ],
377
+ "notes": "in: settlement.json → out: settlement.json · the buyer approves P + π and calls fundJob: machine Delivered → Funded",
378
+ "notesInFlow": true
379
+ },
380
+ {
381
+ "parameters": {
382
+ "callsField": "calls",
383
+ "onlySigner": "seller",
384
+ "onlyActions": "submit",
385
+ "sendRequires": false,
386
+ "wait": true
387
+ },
388
+ "credentials": {
389
+ "taifoonDevnetKeyApi": {
390
+ "id": "",
391
+ "name": "REPLACE: taifoonDevnetKeyApi"
392
+ }
393
+ },
394
+ "id": "n9",
395
+ "name": "Seller · submit",
396
+ "type": "CUSTOM.taifoonDevnetSigner",
397
+ "typeVersion": 1,
398
+ "position": [
399
+ 2640,
400
+ 300
401
+ ],
402
+ "notes": "in: settlement.json → out: settlement.json · submit(jobId, evidenceDigest = delivery.digest): machine Funded → Submitted",
403
+ "notesInFlow": true
404
+ },
405
+ {
406
+ "parameters": {
407
+ "callsField": "calls",
408
+ "onlySigner": "",
409
+ "onlyActions": "complete,capture",
410
+ "sendRequires": false,
411
+ "wait": true
412
+ },
413
+ "credentials": {
414
+ "taifoonDevnetKeyApi": {
415
+ "id": "",
416
+ "name": "REPLACE: taifoonDevnetKeyApi"
417
+ }
418
+ },
419
+ "id": "n10",
420
+ "name": "Buyer · complete + capture",
421
+ "type": "CUSTOM.taifoonDevnetSigner",
422
+ "typeVersion": 1,
423
+ "position": [
424
+ 3080,
425
+ 160
426
+ ],
427
+ "notes": "in: settlement.json → out: settlement.json · complete(jobId) (+ capture): machine → Settled",
428
+ "notesInFlow": true
429
+ },
430
+ {
431
+ "parameters": {
432
+ "jsCode": "// The record: every transaction with its proof and explorer link, the decision, the job, and whether money moved.\nconst legs = ['Seller · approve deposit', 'Buyer · approve + fund', 'Seller · submit', 'Buyer · complete + capture'];\nconst txs = [];\nfor (const n of legs) { let sent = []; try { sent = $(n).first().json.sent || []; } catch (e) { /* a leg the verdict skipped */ }\n for (const s of sent) txs.push({ leg: n, action: s.action, tx: s.tx, status: s.status, block: s.block, proof: 'https://www.taifoon.io/v1/proof/tx/36927/' + s.tx, scan: 'https://www.taifoon.io/scan/36927/tx/' + s.tx }); }\nlet held = null; try { held = $('Held for review').first().json; } catch (e) { held = null; }\nconst s = $('Settle').first().json;\nconst v = $('Verdict').first().json;\nconst complete = txs.find((t) => t.action === 'complete') || null;\nreturn [{ json: { handshake_id: v.handshake_id, decision_id: v.decision_id, decision_digest: v.decision_digest, decision_anchor_tx: v.decision_anchor ? v.decision_anchor.tx : null, verdict: v.verdict, verdict_auto: v.verdict_auto, reasons: v.reasons, receipt_hash: v.receipt_hash, judge: { called: v.judge_called, model: v.model, latency_ms: v.judge_latency_ms, answers_source: v.answers_source, execution: v.execution, prepare_digest: v.prepare_digest, quota: v.quota }, ending: complete ? 'completed: seller paid, deposit returned, payment captured' : held ? 'held: ' + held.next : 'incomplete', lane: v.lane || null, tenant: s.tenant || null, deadline: s.deadline ?? null, jobId: s.jobId, correlator: s.correlator, chainId: 36927, hook: s.hook, token: s.token, price: s.price, premium: s.premium, deposit: s.deposit, evidence: v.evidence, settlement_tx: complete ? complete.tx : null, settlement_proof: complete ? complete.proof : null, transactions: txs, all_mined: txs.every((t) => t.status === 'success') && txs.length === (complete ? 6 : 4) } }];"
433
+ },
434
+ "id": "n11",
435
+ "name": "Receipt",
436
+ "type": "n8n-nodes-base.code",
437
+ "typeVersion": 2,
438
+ "position": [
439
+ 3300,
440
+ 300
441
+ ],
442
+ "notes": "in: settlement.json → out: job.json · the whole JOB: hire, delivery, facts, verdict, settlement — every transaction with its proof link",
443
+ "notesInFlow": true
444
+ },
445
+ {
446
+ "parameters": {
447
+ "conditions": {
448
+ "options": {
449
+ "caseSensitive": true,
450
+ "leftValue": "",
451
+ "typeValidation": "strict",
452
+ "version": 2
453
+ },
454
+ "conditions": [
455
+ {
456
+ "id": "g1",
457
+ "leftValue": "={{ $('Verdict').first().json.verdict }}",
458
+ "rightValue": "complete",
459
+ "operator": {
460
+ "type": "string",
461
+ "operation": "equals"
462
+ }
463
+ }
464
+ ],
465
+ "combinator": "and"
466
+ },
467
+ "options": {}
468
+ },
469
+ "id": "n12",
470
+ "name": "Verdict says complete?",
471
+ "type": "n8n-nodes-base.if",
472
+ "typeVersion": 2.2,
473
+ "position": [
474
+ 2860,
475
+ 300
476
+ ],
477
+ "notes": "in: settlement.json → out: settlement.json · branch on the composed verdict: complete → complete; otherwise hold (Submitted → Held)",
478
+ "notesInFlow": true
479
+ },
480
+ {
481
+ "parameters": {
482
+ "jsCode": "// Held: the job is funded and the evidence is sealed; nothing is paid. Say what decides it next.\nconst v = $('Verdict').first().json;\nconst s = $('Settle').first().json;\nreturn [{ json: { held: true, verdict: v.verdict, reasons: v.reasons, next: v.next || (v.verdict === 'reject' ? 'reject: the buyer may reject on chain; the pool pays nothing on a rejection' : 'needs_review: open the evaluator path (bonded judge / challenge); do not auto-settle'), jobId: s.jobId, hook: s.hook, chainId: s.chainId, deadline: s.deadline, what_is_on_chain: 'fundJob (price + premium in escrow, seller deposit locked) and submit (evidence digest sealed); complete/reject not sent' } }];"
483
+ },
484
+ "id": "n13",
485
+ "name": "Held for review",
486
+ "type": "n8n-nodes-base.code",
487
+ "typeVersion": 2,
488
+ "position": [
489
+ 3080,
490
+ 480
491
+ ],
492
+ "notes": "in: settlement.json → out: settlement.json · needs_review / reject: funded and sealed, nothing paid; the evaluator leg or the deadline decides",
493
+ "notesInFlow": true
494
+ },
495
+ {
496
+ "parameters": {
497
+ "jsCode": "// Every transaction of the run is proven under the V5 superroot before the run ends (/v1/proof/tx/36927/<tx>).\n// The producer runs ~6-8 devnet blocks (~15 s) behind the head, and its blob route can answer \"pending\" under load:\n// a pending answer is waited out with backoff (5 s doubling to 60 s), never reported as a gap. The wait is capped at\n// 240 s (n8n's task-runner limit); what is still pending is listed, and the runner (workflows/e2e.sh) keeps waiting.\nconst r = $input.first().json;\nconst SITE = 'https://www.taifoon.io';\nconst txs = [...(r.transactions || []).map((t) => ({ leg: t.leg, action: t.action, tx: t.tx })), ...(r.decision_anchor_tx ? [{ leg: 'decision anchor', action: 'JevDecisionLog.record', tx: r.decision_anchor_tx }] : [])];\nconst until = Date.now() + 240000;\nconst sleep = (ms) => new Promise((res) => setTimeout(res, ms));\nconst final = (c) => Boolean(c && c.proof_served && c.block_hash_matches && c.within_verifiable_range);\nconst proofs = [];\nfor (const t of txs) {\n let wait = 5000, attempts = 0, last = null;\n for (;;) {\n attempts++;\n try { last = await this.helpers.httpRequest({ method: 'GET', url: SITE + '/v1/proof/tx/36927/' + t.tx + (attempts > 3 ? '?wait=1' : ''), json: true, timeout: 60000, ignoreHttpStatusErrors: true }); }\n catch (e) { last = { ok: false, error: String((e && e.message) || e).slice(0, 160) }; }\n if (final(last && last.checks)) break;\n if (last && last.code === 'PROVABLE_GAP' && last.gap) break; // the producer skipped this block: no wait fills it\n if (Date.now() + wait > until) break;\n await sleep(wait); wait = Math.min(wait * 2, 60000);\n }\n const c = (last && last.checks) || {};\n proofs.push({ ...t, finalized: final(c), gap: Boolean(last && last.gap), attempts, checks: c, proof: SITE + '/v1/proof/tx/36927/' + t.tx });\n}\nreturn [{ json: { ...r, proofs, all_proven: proofs.length > 0 && proofs.every((p) => p.finalized), proofs_pending: proofs.filter((p) => !p.finalized).map((p) => p.tx) } }];"
498
+ },
499
+ "id": "n17",
500
+ "name": "Proofs · wait",
501
+ "type": "n8n-nodes-base.code",
502
+ "typeVersion": 2,
503
+ "position": [
504
+ 3520,
505
+ 300
506
+ ],
507
+ "notes": "in: job.json → out: job.json · GET /v1/proof/tx/36927/<tx> for every transaction of the run; pending is retried with backoff (5 s -> 60 s, <= 240 s), never reported as a gap",
508
+ "notesInFlow": true
509
+ }
510
+ ],
511
+ "connections": {
512
+ "Start": {
513
+ "main": [
514
+ [
515
+ {
516
+ "node": "Inputs",
517
+ "type": "main",
518
+ "index": 0
519
+ }
520
+ ]
521
+ ]
522
+ },
523
+ "Inputs": {
524
+ "main": [
525
+ [
526
+ {
527
+ "node": "Offer + deliver",
528
+ "type": "main",
529
+ "index": 0
530
+ }
531
+ ]
532
+ ]
533
+ },
534
+ "Offer + deliver": {
535
+ "main": [
536
+ [
537
+ {
538
+ "node": "Delivery",
539
+ "type": "main",
540
+ "index": 0
541
+ }
542
+ ]
543
+ ]
544
+ },
545
+ "Delivery": {
546
+ "main": [
547
+ [
548
+ {
549
+ "node": "Judge · prepare",
550
+ "type": "main",
551
+ "index": 0
552
+ }
553
+ ]
554
+ ]
555
+ },
556
+ "Verdict": {
557
+ "main": [
558
+ [
559
+ {
560
+ "node": "Settle",
561
+ "type": "main",
562
+ "index": 0
563
+ }
564
+ ]
565
+ ]
566
+ },
567
+ "Settle": {
568
+ "main": [
569
+ [
570
+ {
571
+ "node": "Seller · approve deposit",
572
+ "type": "main",
573
+ "index": 0
574
+ }
575
+ ]
576
+ ]
577
+ },
578
+ "Seller · approve deposit": {
579
+ "main": [
580
+ [
581
+ {
582
+ "node": "Buyer · approve + fund",
583
+ "type": "main",
584
+ "index": 0
585
+ }
586
+ ]
587
+ ]
588
+ },
589
+ "Buyer · approve + fund": {
590
+ "main": [
591
+ [
592
+ {
593
+ "node": "Seller · submit",
594
+ "type": "main",
595
+ "index": 0
596
+ }
597
+ ]
598
+ ]
599
+ },
600
+ "Seller · submit": {
601
+ "main": [
602
+ [
603
+ {
604
+ "node": "Verdict says complete?",
605
+ "type": "main",
606
+ "index": 0
607
+ }
608
+ ]
609
+ ]
610
+ },
611
+ "Buyer · complete + capture": {
612
+ "main": [
613
+ [
614
+ {
615
+ "node": "Receipt",
616
+ "type": "main",
617
+ "index": 0
618
+ }
619
+ ]
620
+ ]
621
+ },
622
+ "Verdict says complete?": {
623
+ "main": [
624
+ [
625
+ {
626
+ "node": "Buyer · complete + capture",
627
+ "type": "main",
628
+ "index": 0
629
+ }
630
+ ],
631
+ [
632
+ {
633
+ "node": "Held for review",
634
+ "type": "main",
635
+ "index": 0
636
+ }
637
+ ]
638
+ ]
639
+ },
640
+ "Held for review": {
641
+ "main": [
642
+ [
643
+ {
644
+ "node": "Receipt",
645
+ "type": "main",
646
+ "index": 0
647
+ }
648
+ ]
649
+ ]
650
+ },
651
+ "Judge · prepare": {
652
+ "main": [
653
+ [
654
+ {
655
+ "node": "Hard fail?",
656
+ "type": "main",
657
+ "index": 0
658
+ }
659
+ ]
660
+ ]
661
+ },
662
+ "Hard fail?": {
663
+ "main": [
664
+ [
665
+ {
666
+ "node": "Verdict",
667
+ "type": "main",
668
+ "index": 0
669
+ }
670
+ ],
671
+ [
672
+ {
673
+ "node": "Jev · TypeSafe (our credential)",
674
+ "type": "main",
675
+ "index": 0
676
+ }
677
+ ]
678
+ ]
679
+ },
680
+ "Jev · TypeSafe (our credential)": {
681
+ "main": [
682
+ [
683
+ {
684
+ "node": "Judge · answers",
685
+ "type": "main",
686
+ "index": 0
687
+ }
688
+ ]
689
+ ]
690
+ },
691
+ "Judge · answers": {
692
+ "main": [
693
+ [
694
+ {
695
+ "node": "Verdict",
696
+ "type": "main",
697
+ "index": 0
698
+ }
699
+ ]
700
+ ]
701
+ },
702
+ "Receipt": {
703
+ "main": [
704
+ [
705
+ {
706
+ "node": "Proofs · wait",
707
+ "type": "main",
708
+ "index": 0
709
+ }
710
+ ]
711
+ ]
712
+ }
713
+ },
714
+ "settings": {
715
+ "executionOrder": "v1"
716
+ },
717
+ "active": false,
718
+ "meta": {
719
+ "taifoon": {
720
+ "contract": "https://www.taifoon.io/schemas/coordination/v1/",
721
+ "steps": {
722
+ "Start": {
723
+ "in": null,
724
+ "out": null,
725
+ "does": "start a run (swap for a Webhook or Schedule trigger)"
726
+ },
727
+ "Inputs": {
728
+ "in": null,
729
+ "out": "https://www.taifoon.io/schemas/coordination/v1/hire.json",
730
+ "does": "the hire intent: task, candidate seller, endpoint/tool, buyer, price, chain (a HIRE in state Open)"
731
+ },
732
+ "Offer + deliver": {
733
+ "in": "https://www.taifoon.io/schemas/coordination/v1/hire.json",
734
+ "out": "https://www.taifoon.io/schemas/coordination/v1/job.json",
735
+ "does": "POST /v1/handshake {dispatch:true}: the offer goes to the seller’s own endpoint; the reply is held with its keccak digest"
736
+ },
737
+ "Delivery": {
738
+ "in": "https://www.taifoon.io/schemas/coordination/v1/job.json",
739
+ "out": "https://www.taifoon.io/schemas/coordination/v1/delivery.json",
740
+ "does": "stop unless a reply digest is held (no digest = nothing to settle)"
741
+ },
742
+ "Judge · prepare": {
743
+ "in": "https://www.taifoon.io/schemas/coordination/v1/delivery.json",
744
+ "out": "https://www.taifoon.io/schemas/coordination/v1/facts.json",
745
+ "does": "POST /v1/judge/compose (prepare): the facts decide first; hard_fail ends here; else the exact text Jev reads + prepare_digest"
746
+ },
747
+ "Hard fail?": {
748
+ "in": "https://www.taifoon.io/schemas/coordination/v1/facts.json",
749
+ "out": "https://www.taifoon.io/schemas/coordination/v1/facts.json",
750
+ "does": "branch: a hard fail goes straight to the verdict without asking Jev"
751
+ },
752
+ "Jev · TypeSafe (our credential)": {
753
+ "in": "https://www.taifoon.io/schemas/coordination/v1/facts.json",
754
+ "out": "https://www.taifoon.io/schemas/coordination/v1/answers.json",
755
+ "does": "TypeSafe ask on the prepared text: every RUBRIC_ASKED answer with its whole distribution"
756
+ },
757
+ "Judge · answers": {
758
+ "in": "https://www.taifoon.io/schemas/coordination/v1/answers.json",
759
+ "out": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
760
+ "does": "POST /v1/judge/compose (answers): recompose under THRESHOLDS_v1, record decision.v2, anchor on JevDecisionLog"
761
+ },
762
+ "Verdict": {
763
+ "in": [
764
+ "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
765
+ "https://www.taifoon.io/schemas/coordination/v1/facts.json"
766
+ ],
767
+ "out": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
768
+ "does": "the composed verdict (complete | reject | needs_review) with what settlement needs carried forward"
769
+ },
770
+ "Settle": {
771
+ "in": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
772
+ "out": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
773
+ "does": "POST /v1/settle: the plan (jobId, price, premium, deposit, calls[] in order, who signs each)"
774
+ },
775
+ "Seller · approve deposit": {
776
+ "in": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
777
+ "out": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
778
+ "does": "the seller approves D (> P); the tx joins settlement.txs"
779
+ },
780
+ "Buyer · approve + fund": {
781
+ "in": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
782
+ "out": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
783
+ "does": "the buyer approves P + π and calls fundJob: machine Delivered → Funded"
784
+ },
785
+ "Seller · submit": {
786
+ "in": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
787
+ "out": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
788
+ "does": "submit(jobId, evidenceDigest = delivery.digest): machine Funded → Submitted"
789
+ },
790
+ "Verdict says complete?": {
791
+ "in": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
792
+ "out": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
793
+ "does": "branch on the composed verdict: complete → complete; otherwise hold (Submitted → Held)"
794
+ },
795
+ "Buyer · complete + capture": {
796
+ "in": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
797
+ "out": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
798
+ "does": "complete(jobId) (+ capture): machine → Settled"
799
+ },
800
+ "Held for review": {
801
+ "in": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
802
+ "out": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
803
+ "does": "needs_review / reject: funded and sealed, nothing paid; the evaluator leg or the deadline decides"
804
+ },
805
+ "Receipt": {
806
+ "in": "https://www.taifoon.io/schemas/coordination/v1/settlement.json",
807
+ "out": "https://www.taifoon.io/schemas/coordination/v1/job.json",
808
+ "does": "the whole JOB: hire, delivery, facts, verdict, settlement — every transaction with its proof link"
809
+ },
810
+ "Proofs · wait": {
811
+ "in": "https://www.taifoon.io/schemas/coordination/v1/job.json",
812
+ "out": "https://www.taifoon.io/schemas/coordination/v1/job.json",
813
+ "does": "GET /v1/proof/tx/36927/<tx> for every transaction of the run; pending is retried with backoff (5 s -> 60 s, <= 240 s), never reported as a gap"
814
+ }
815
+ }
816
+ }
817
+ },
818
+ "tags": []
819
+ }