@taifoon/jev 0.2.14 → 0.2.15

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,341 @@
1
+ {
2
+ "name": "Taifoon — batch judge scores for the coordination layer (TypeSafe/Jev)",
3
+ "nodes": [
4
+ {
5
+ "parameters": {},
6
+ "id": "trigger",
7
+ "name": "Grade a batch",
8
+ "type": "n8n-nodes-base.manualTrigger",
9
+ "typeVersion": 1,
10
+ "position": [
11
+ 200,
12
+ 300
13
+ ],
14
+ "notes": "in: — → out: — · start a batch (pass { subjects, jobId, threshold } or run the demo set)",
15
+ "notesInFlow": true
16
+ },
17
+ {
18
+ "parameters": {
19
+ "jsCode": "// The batch: subjects already delivered (a handshake, or a job read from the chain), one canonical HIRE each. The\n// judge builds every evidence pack itself from DELIVERY + FACTS (schemas/GRADEABLE.md) — nothing here hands Jev a\n// state. Pass { subjects: [{ handshake_id } | { chainId, jobId }], jobId, threshold } on the trigger, or run the demo set.\nconst t = $input.first().json || {};\nconst subjects = Array.isArray(t.subjects) && t.subjects.length ? t.subjects : [\n { handshake_id: 'hs_97dcf5942e294f7ca28efd0a' }, { handshake_id: 'hs_ec150f74dbe6568982c6c93d' }, { handshake_id: 'hs_c07ccb807a29b94dffb18459' },\n];\nreturn subjects.map((s) => {\n const hs = typeof s.handshake_id === 'string' && /^hs_[0-9a-f]{24}$/.test(s.handshake_id) ? s.handshake_id : null;\n const chainId = s.chainId != null ? Number(s.chainId) : hs ? 36927 : 8453;\n const id = hs ?? String(s.jobId);\n const job = hs ? { key: 'hs:' + hs, lane: 'handshake', chain_id: chainId, id: hs, contract: null }\n : { key: 'erc8183:' + chainId + ':' + id, lane: 'erc8183', chain_id: chainId, id, contract: '0x238e541bfefd82238730d00a2208e5497f1832e0' };\n return { json: {\n schema: 'taifoon.hire.v1', job, lane: job.lane, tenant: null, buyer: null, seller: null, seller_agent_key: null, seller_ids: [],\n evaluator: null, hook: null, evaluator_kind: null, task: null, required_skills: [], budget: null, funding: null,\n created_at: null, deadline: null, handshake_id: hs, offer_nonce: null, state: 'Delivered', ending: null,\n anchors: [{ kind: 'offchain', store: hs ? 'coordd:coordination_handshakes' : 'observatory', key: id, digest: null, anchored_by: null, what: 'the subject this batch grades' }],\n source: { kind: hs ? 'handshake' : 'observatory-job', url: hs ? 'https://www.taifoon.io/v1/handshake/' + hs : 'https://www.taifoon.io/v1/judge/evidence/' + chainId + '/' + id, read_at: Math.floor(Date.now() / 1000) },\n } };\n});"
20
+ },
21
+ "id": "e177aad3-f294-4ac5-beae-395993bd7de5",
22
+ "name": "Build the batch",
23
+ "type": "n8n-nodes-base.code",
24
+ "typeVersion": 2,
25
+ "position": [
26
+ 420,
27
+ 300
28
+ ],
29
+ "notes": "in: — → out: hire.json · one canonical HIRE per subject (a delivered handshake, or a job on chain); no state is built here",
30
+ "notesInFlow": true
31
+ },
32
+ {
33
+ "name": "Judge · prepare",
34
+ "type": "CUSTOM.taifoon",
35
+ "typeVersion": 1,
36
+ "position": [
37
+ 640,
38
+ 300
39
+ ],
40
+ "parameters": {
41
+ "resource": "judge",
42
+ "operation": "compose",
43
+ "composeSubject": "={{ $json.lane === 'handshake' ? 'handshake' : 'job' }}",
44
+ "composeHandshakeId": "={{ $json.handshake_id }}",
45
+ "composeMode": "prepare",
46
+ "composeJobId": "={{ $json.job.id }}",
47
+ "composeChainId": "={{ $json.job.chain_id }}"
48
+ },
49
+ "credentials": {
50
+ "taifoonRelayerApi": {
51
+ "id": "",
52
+ "name": "REPLACE: taifoonRelayerApi"
53
+ }
54
+ },
55
+ "id": "62dca0ae-d853-414f-ac98-89592623c7d2",
56
+ "notes": "in: hire.json → out: facts.json · POST /v1/judge/compose (prepare) per subject: the facts decide first; the pack is built from DELIVERY + FACTS; else the exact text Jev reads + prepare_digest",
57
+ "notesInFlow": true
58
+ },
59
+ {
60
+ "name": "Hard fail?",
61
+ "type": "n8n-nodes-base.if",
62
+ "typeVersion": 2.2,
63
+ "position": [
64
+ 860,
65
+ 300
66
+ ],
67
+ "parameters": {
68
+ "conditions": {
69
+ "options": {
70
+ "caseSensitive": true,
71
+ "leftValue": "",
72
+ "typeValidation": "strict",
73
+ "version": 2
74
+ },
75
+ "conditions": [
76
+ {
77
+ "id": "h1",
78
+ "leftValue": "={{ $json.hard_fail === true }}",
79
+ "rightValue": "",
80
+ "operator": {
81
+ "type": "boolean",
82
+ "operation": "true",
83
+ "singleValue": true
84
+ }
85
+ }
86
+ ],
87
+ "combinator": "and"
88
+ },
89
+ "options": {}
90
+ },
91
+ "id": "65ffd12f-ea5f-4a36-98c4-eea5f9afec86",
92
+ "notes": "in: facts.json → out: facts.json · branch: a hard fail is final (Jev is not asked) and goes straight to the batch",
93
+ "notesInFlow": true
94
+ },
95
+ {
96
+ "name": "Jev · TypeSafe (our credential)",
97
+ "type": "@taifoon/n8n-nodes-typesafe.taifoonTypeSafe",
98
+ "typeVersion": 1,
99
+ "position": [
100
+ 1080,
101
+ 420
102
+ ],
103
+ "parameters": {
104
+ "operation": "ask",
105
+ "connection": "direct",
106
+ "state": "={{ JSON.stringify($json.jev.state) }}",
107
+ "questions": {},
108
+ "questionsJson": "={{ JSON.stringify($json.jev.questions) }}",
109
+ "routing": "{}",
110
+ "replyLanguage": "off",
111
+ "failClosed": true
112
+ },
113
+ "credentials": {
114
+ "taifoonTypeSafeApi": {
115
+ "id": "",
116
+ "name": "REPLACE: taifoonTypeSafeApi"
117
+ }
118
+ },
119
+ "notes": "in: facts.json → out: answers.json · TypeSafe ask on the prepared text (jevStateOf): every RUBRIC_ASKED answer with its whole distribution",
120
+ "id": "f2965735-6523-490f-a551-865edd654baf",
121
+ "notesInFlow": true
122
+ },
123
+ {
124
+ "name": "Judge · answers",
125
+ "type": "CUSTOM.taifoon",
126
+ "typeVersion": 1,
127
+ "position": [
128
+ 1300,
129
+ 420
130
+ ],
131
+ "parameters": {
132
+ "resource": "judge",
133
+ "operation": "compose",
134
+ "composeSubject": "={{ $('Build the batch').item.json.lane === 'handshake' ? 'handshake' : 'job' }}",
135
+ "composeHandshakeId": "={{ $('Build the batch').item.json.handshake_id }}",
136
+ "composeMode": "answers",
137
+ "composeAnswers": "={{ JSON.stringify($json.answers) }}",
138
+ "composeModel": "={{ $json.model }}",
139
+ "composePrepareDigest": "={{ $('Judge · prepare').item.json.prepare_digest }}",
140
+ "composeAnsweredBy": "n8n-typesafe",
141
+ "composeExecutionId": "={{ $execution.id }}",
142
+ "composeLatencyMs": "={{ $json.latency_ms }}",
143
+ "composeJobId": "={{ $('Build the batch').item.json.job.id }}",
144
+ "composeChainId": "={{ $('Build the batch').item.json.job.chain_id }}"
145
+ },
146
+ "credentials": {
147
+ "taifoonRelayerApi": {
148
+ "id": "",
149
+ "name": "REPLACE: taifoonRelayerApi"
150
+ }
151
+ },
152
+ "id": "0ca9f21a-aff6-4fa1-8cc7-c0924dc1cce3",
153
+ "notes": "in: answers.json → out: verdict.json · POST /v1/judge/compose (answers): recompose under THRESHOLDS_v1, record decision.v2 and the answers (jev.answer.v1), anchor both",
154
+ "notesInFlow": true
155
+ },
156
+ {
157
+ "parameters": {
158
+ "jsCode": "// The batch outcome from the recorded verdicts — each item was composed under THRESHOLDS_v1 and recorded (decision.v2\n// + jev.answer.v1) by Judge · answers; a hard fail was final at prepare. complete → Conformant, reject → NonConformant,\n// needs_review → NoGrade (the verdict crate's batch mapping). Runs once per branch that reaches it; the batch record\n// is keyed by jobId, so the last (complete) run is the one that stays.\nconst t = $('Grade a batch').first().json || {};\nconst jobId = t.jobId || 'batch-' + $execution.id;\nconst threshold = typeof t.threshold === 'number' ? t.threshold : 0.2;\nconst prepared = $('Judge · prepare').all().map((i) => i.json);\nlet answered = [];\ntry { answered = $('Judge · answers').all().map((i) => i.json); } catch (e) { answered = []; }\nconst items = [];\nfor (const p of prepared.filter((x) => x.hard_fail === true)) items.push({ item_id: p.subject, verdict: 'NonConformant', composed: 'reject', forced: 'hard_fail', mode: 'Deterministic', decision_id: null, answers_digest: null });\nfor (const a of answered) {\n const v = a.receipt && a.receipt.verdict;\n items.push({ item_id: (a.decision && a.decision.subject_id) || null, verdict: v === 'complete' ? 'Conformant' : v === 'reject' ? 'NonConformant' : 'NoGrade', composed: v || null, forced: null, mode: 'Calibrated',\n decision_id: a.decision ? a.decision.id : null, decision_digest: a.decision ? a.decision.digest : null, answers_digest: a.decision ? a.decision.answers_digest ?? null : null });\n}\nconst conformant = items.filter((i) => i.verdict === 'Conformant').length, nonconformant = items.filter((i) => i.verdict === 'NonConformant').length, nograde = items.length - conformant - nonconformant;\nconst graded = conformant + nonconformant, failFraction = graded === 0 ? 1 : nonconformant / graded;\nreturn [{ json: { jobId, mode: 'Calibrated', total: items.length, conformant, nonconformant, nograde, failFraction, threshold,\n verdict: graded === 0 ? 'NoGrade' : failFraction <= threshold ? 'Conformant' : 'NonConformant', items } }];"
159
+ },
160
+ "id": "64dce113-21e8-454e-8a12-53858bd7a303",
161
+ "name": "Grade + aggregate",
162
+ "type": "n8n-nodes-base.code",
163
+ "typeVersion": 2,
164
+ "position": [
165
+ 1520,
166
+ 300
167
+ ],
168
+ "notes": "in: facts.json | verdict.json → out: — · the batch outcome from the recorded verdicts (Conformant / NonConformant / NoGrade counts, fail fraction vs threshold) — a batch record, not a canonical entity",
169
+ "notesInFlow": true
170
+ },
171
+ {
172
+ "parameters": {
173
+ "method": "POST",
174
+ "url": "https://www.taifoon.io/v1/judge/batch",
175
+ "authentication": "predefinedCredentialType",
176
+ "nodeCredentialType": "taifoonRelayerApi",
177
+ "sendBody": true,
178
+ "specifyBody": "json",
179
+ "jsonBody": "={{ JSON.stringify($json) }}",
180
+ "options": {}
181
+ },
182
+ "id": "record",
183
+ "name": "Record the batch to the coordination layer",
184
+ "type": "n8n-nodes-base.httpRequest",
185
+ "typeVersion": 4.2,
186
+ "position": [
187
+ 1740,
188
+ 300
189
+ ],
190
+ "credentials": {
191
+ "taifoonRelayerApi": {
192
+ "id": "",
193
+ "name": "REPLACE: taifoonRelayerApi"
194
+ }
195
+ },
196
+ "notes": "in: — → out: — · POST /v1/judge/batch (relayer key): the batch record, keyed batch:<jobId>",
197
+ "notesInFlow": true
198
+ }
199
+ ],
200
+ "connections": {
201
+ "Grade a batch": {
202
+ "main": [
203
+ [
204
+ {
205
+ "node": "Build the batch",
206
+ "type": "main",
207
+ "index": 0
208
+ }
209
+ ]
210
+ ]
211
+ },
212
+ "Build the batch": {
213
+ "main": [
214
+ [
215
+ {
216
+ "node": "Judge · prepare",
217
+ "type": "main",
218
+ "index": 0
219
+ }
220
+ ]
221
+ ]
222
+ },
223
+ "Judge · prepare": {
224
+ "main": [
225
+ [
226
+ {
227
+ "node": "Hard fail?",
228
+ "type": "main",
229
+ "index": 0
230
+ }
231
+ ]
232
+ ]
233
+ },
234
+ "Hard fail?": {
235
+ "main": [
236
+ [
237
+ {
238
+ "node": "Grade + aggregate",
239
+ "type": "main",
240
+ "index": 0
241
+ }
242
+ ],
243
+ [
244
+ {
245
+ "node": "Jev · TypeSafe (our credential)",
246
+ "type": "main",
247
+ "index": 0
248
+ }
249
+ ]
250
+ ]
251
+ },
252
+ "Jev · TypeSafe (our credential)": {
253
+ "main": [
254
+ [
255
+ {
256
+ "node": "Judge · answers",
257
+ "type": "main",
258
+ "index": 0
259
+ }
260
+ ]
261
+ ]
262
+ },
263
+ "Judge · answers": {
264
+ "main": [
265
+ [
266
+ {
267
+ "node": "Grade + aggregate",
268
+ "type": "main",
269
+ "index": 0
270
+ }
271
+ ]
272
+ ]
273
+ },
274
+ "Grade + aggregate": {
275
+ "main": [
276
+ [
277
+ {
278
+ "node": "Record the batch to the coordination layer",
279
+ "type": "main",
280
+ "index": 0
281
+ }
282
+ ]
283
+ ]
284
+ }
285
+ },
286
+ "settings": {
287
+ "executionOrder": "v1"
288
+ },
289
+ "active": false,
290
+ "meta": {
291
+ "taifoon": {
292
+ "contract": "https://www.taifoon.io/schemas/coordination/v1/",
293
+ "steps": {
294
+ "Grade a batch": {
295
+ "in": null,
296
+ "out": null,
297
+ "does": "start a batch (pass { subjects, jobId, threshold } or run the demo set)"
298
+ },
299
+ "Build the batch": {
300
+ "in": null,
301
+ "out": "https://www.taifoon.io/schemas/coordination/v1/hire.json",
302
+ "does": "one canonical HIRE per subject (a delivered handshake, or a job on chain); no state is built here"
303
+ },
304
+ "Judge · prepare": {
305
+ "in": "https://www.taifoon.io/schemas/coordination/v1/hire.json",
306
+ "out": "https://www.taifoon.io/schemas/coordination/v1/facts.json",
307
+ "does": "POST /v1/judge/compose (prepare) per subject: the facts decide first; the pack is built from DELIVERY + FACTS; else the exact text Jev reads + prepare_digest"
308
+ },
309
+ "Hard fail?": {
310
+ "in": "https://www.taifoon.io/schemas/coordination/v1/facts.json",
311
+ "out": "https://www.taifoon.io/schemas/coordination/v1/facts.json",
312
+ "does": "branch: a hard fail is final (Jev is not asked) and goes straight to the batch"
313
+ },
314
+ "Jev · TypeSafe (our credential)": {
315
+ "in": "https://www.taifoon.io/schemas/coordination/v1/facts.json",
316
+ "out": "https://www.taifoon.io/schemas/coordination/v1/answers.json",
317
+ "does": "TypeSafe ask on the prepared text (jevStateOf): every RUBRIC_ASKED answer with its whole distribution"
318
+ },
319
+ "Judge · answers": {
320
+ "in": "https://www.taifoon.io/schemas/coordination/v1/answers.json",
321
+ "out": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
322
+ "does": "POST /v1/judge/compose (answers): recompose under THRESHOLDS_v1, record decision.v2 and the answers (jev.answer.v1), anchor both"
323
+ },
324
+ "Grade + aggregate": {
325
+ "in": [
326
+ "https://www.taifoon.io/schemas/coordination/v1/facts.json",
327
+ "https://www.taifoon.io/schemas/coordination/v1/verdict.json"
328
+ ],
329
+ "out": null,
330
+ "does": "the batch outcome from the recorded verdicts (Conformant / NonConformant / NoGrade counts, fail fraction vs threshold) — a batch record, not a canonical entity"
331
+ },
332
+ "Record the batch to the coordination layer": {
333
+ "in": null,
334
+ "out": null,
335
+ "does": "POST /v1/judge/batch (relayer key): the batch record, keyed batch:<jobId>"
336
+ }
337
+ }
338
+ }
339
+ },
340
+ "tags": []
341
+ }