@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,296 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
4
+ "title": "VERDICT — the decision recorded once, and the receipt it composes to",
5
+ "description": "decision.v1/v2 as the ledger writes it (the digest recomputes from kind, subject, model, answers and — v2 — input_digest), plus the RUBRIC_v1 composed verdict when the source states it. A REF verdict never produces a cheat; needs_review holds the job for the evaluator leg.",
6
+ "type": "object",
7
+ "properties": {
8
+ "schema": {
9
+ "const": "taifoon.verdict.v1",
10
+ "x-stage": "L",
11
+ "description": "which canonical entity this record is; fixed per schema"
12
+ },
13
+ "decision_id": {
14
+ "type": "string",
15
+ "pattern": "^decision-[0-9]+-[0-9a-f]{10}$",
16
+ "x-stage": "S5"
17
+ },
18
+ "version": {
19
+ "enum": [
20
+ "decision.v1",
21
+ "decision.v2"
22
+ ],
23
+ "x-stage": "S5"
24
+ },
25
+ "kind": {
26
+ "enum": [
27
+ "pad",
28
+ "suggest",
29
+ "assemble",
30
+ "ref",
31
+ "grade",
32
+ "study"
33
+ ],
34
+ "x-stage": "S5"
35
+ },
36
+ "subject": {
37
+ "type": "object",
38
+ "properties": {
39
+ "chain_id": {
40
+ "$ref": "common.json#/$defs/chain_id"
41
+ },
42
+ "at": {
43
+ "anyOf": [
44
+ {
45
+ "$ref": "common.json#/$defs/address"
46
+ },
47
+ {
48
+ "type": "null"
49
+ }
50
+ ]
51
+ },
52
+ "ref": {
53
+ "type": "string"
54
+ },
55
+ "label": {
56
+ "type": [
57
+ "string",
58
+ "null"
59
+ ]
60
+ }
61
+ },
62
+ "required": [
63
+ "chain_id",
64
+ "at",
65
+ "ref",
66
+ "label"
67
+ ],
68
+ "additionalProperties": false,
69
+ "x-stage": "S5"
70
+ },
71
+ "subject_id": {
72
+ "$ref": "common.json#/$defs/bytes32",
73
+ "x-stage": "S5"
74
+ },
75
+ "subject_job_key": {
76
+ "anyOf": [
77
+ {
78
+ "$ref": "common.json#/$defs/job_key"
79
+ },
80
+ {
81
+ "type": "null"
82
+ }
83
+ ],
84
+ "x-stage": "L"
85
+ },
86
+ "digest": {
87
+ "$ref": "common.json#/$defs/bytes32",
88
+ "x-stage": "S5"
89
+ },
90
+ "input_digest": {
91
+ "anyOf": [
92
+ {
93
+ "$ref": "common.json#/$defs/bytes32"
94
+ },
95
+ {
96
+ "type": "null"
97
+ }
98
+ ],
99
+ "x-stage": "S5"
100
+ },
101
+ "answers_digest": {
102
+ "anyOf": [
103
+ {
104
+ "$ref": "common.json#/$defs/bytes32"
105
+ },
106
+ {
107
+ "type": "null"
108
+ }
109
+ ],
110
+ "x-stage": "S5"
111
+ },
112
+ "answers": {
113
+ "type": "array",
114
+ "items": {
115
+ "type": "object",
116
+ "properties": {
117
+ "id": {
118
+ "type": "string"
119
+ },
120
+ "question": {
121
+ "type": [
122
+ "string",
123
+ "null"
124
+ ]
125
+ },
126
+ "options": {
127
+ "anyOf": [
128
+ {
129
+ "type": "array",
130
+ "items": {
131
+ "type": "string"
132
+ }
133
+ },
134
+ {
135
+ "type": "object",
136
+ "additionalProperties": {
137
+ "type": "string"
138
+ }
139
+ },
140
+ {
141
+ "type": "null"
142
+ }
143
+ ]
144
+ },
145
+ "value": {
146
+ "type": "string"
147
+ },
148
+ "probabilities": {
149
+ "type": "object",
150
+ "additionalProperties": {
151
+ "type": "number"
152
+ }
153
+ },
154
+ "confidence": {
155
+ "type": "number"
156
+ }
157
+ },
158
+ "required": [
159
+ "id",
160
+ "question",
161
+ "options",
162
+ "value",
163
+ "probabilities",
164
+ "confidence"
165
+ ],
166
+ "additionalProperties": false
167
+ },
168
+ "x-stage": "S5"
169
+ },
170
+ "confidence_bps": {
171
+ "type": "integer",
172
+ "minimum": 0,
173
+ "maximum": 10000,
174
+ "x-stage": "S5"
175
+ },
176
+ "model": {
177
+ "type": [
178
+ "string",
179
+ "null"
180
+ ],
181
+ "x-stage": "S5"
182
+ },
183
+ "answers_source": {
184
+ "enum": [
185
+ "layer",
186
+ "caller-credential"
187
+ ],
188
+ "x-stage": "S5"
189
+ },
190
+ "rubric": {
191
+ "type": [
192
+ "object",
193
+ "null"
194
+ ],
195
+ "properties": {
196
+ "version": {
197
+ "type": "string"
198
+ },
199
+ "hash": {
200
+ "$ref": "common.json#/$defs/bytes32"
201
+ }
202
+ },
203
+ "required": [
204
+ "version",
205
+ "hash"
206
+ ],
207
+ "additionalProperties": false,
208
+ "x-stage": "S5"
209
+ },
210
+ "composed": {
211
+ "type": [
212
+ "object",
213
+ "null"
214
+ ],
215
+ "properties": {
216
+ "verdict": {
217
+ "enum": [
218
+ "complete",
219
+ "reject",
220
+ "needs_review"
221
+ ]
222
+ },
223
+ "auto": {
224
+ "type": "boolean"
225
+ },
226
+ "forced": {
227
+ "anyOf": [
228
+ {
229
+ "const": "hard_fail"
230
+ },
231
+ {
232
+ "type": "null"
233
+ }
234
+ ]
235
+ },
236
+ "reasons": {
237
+ "type": "array",
238
+ "items": {
239
+ "type": "string"
240
+ }
241
+ },
242
+ "receipt_hash": {
243
+ "anyOf": [
244
+ {
245
+ "$ref": "common.json#/$defs/bytes32"
246
+ },
247
+ {
248
+ "type": "null"
249
+ }
250
+ ]
251
+ }
252
+ },
253
+ "required": [
254
+ "verdict",
255
+ "auto",
256
+ "forced",
257
+ "reasons",
258
+ "receipt_hash"
259
+ ],
260
+ "additionalProperties": false,
261
+ "x-stage": "S5"
262
+ },
263
+ "anchors": {
264
+ "type": "array",
265
+ "items": {
266
+ "$ref": "common.json#/$defs/anchor"
267
+ },
268
+ "x-stage": "S6"
269
+ },
270
+ "source": {
271
+ "$ref": "common.json#/$defs/source",
272
+ "x-stage": "L"
273
+ }
274
+ },
275
+ "required": [
276
+ "schema",
277
+ "decision_id",
278
+ "version",
279
+ "kind",
280
+ "subject",
281
+ "subject_id",
282
+ "subject_job_key",
283
+ "digest",
284
+ "input_digest",
285
+ "answers_digest",
286
+ "answers",
287
+ "confidence_bps",
288
+ "model",
289
+ "answers_source",
290
+ "rubric",
291
+ "composed",
292
+ "anchors",
293
+ "source"
294
+ ],
295
+ "additionalProperties": false
296
+ }
@@ -0,0 +1,175 @@
1
+ {
2
+ "name": "Taifoon — Stamp Grade (a recorded verdict → GradeStampRegistry on the devnet)",
3
+ "nodes": [
4
+ {
5
+ "parameters": {},
6
+ "id": "a1",
7
+ "name": "Start",
8
+ "type": "n8n-nodes-base.manualTrigger",
9
+ "typeVersion": 1,
10
+ "position": [
11
+ 0,
12
+ 0
13
+ ],
14
+ "notes": "in: — → out: — · start (pass { decision_id } to stamp another verdict)",
15
+ "notesInFlow": true
16
+ },
17
+ {
18
+ "parameters": {
19
+ "jsCode": "// ── canonical VERDICT (taifoon.verdict.v1) from a decision record — the same mapping as the Taifoon verdict schema\n// fromDecision, inlined because an n8n code node cannot import the schemas package. workflows/test.mjs runs this node\n// and compares its output with fromDecision field by field.\nconst SITE = 'https://www.taifoon.io';\nconst isHash = (s) => typeof s === 'string' && /^0x[0-9a-fA-F]{64}$/.test(s);\nconst chainAnchor = (chain_id, tx, what, x = {}) => ({ kind: 'chain', chain_id, tx: tx.toLowerCase(), block: x.block ?? null, ts: x.ts ?? null, log_index: x.log_index ?? null, contract: x.contract ? x.contract.toLowerCase() : null, what });\nfunction jobKeyOf(subject) {\n const ref = String(subject && subject.ref || ''), chain = Number(subject && subject.chainId) || 8453;\n if (/^hs_[0-9a-f]{24}$/.test(ref)) return 'hs:' + ref;\n if (/^([a-z][a-z0-9-]{0,31}):([1-9][0-9]{0,9}):([0-9]{1,78})$/.test(ref)) return ref;\n if (/^0x[0-9a-fA-F]{64}$/.test(ref)) return 'hook:' + chain + ':' + ref.toLowerCase();\n if (/^[0-9]{1,78}$/.test(ref)) return /^[0-9]{10,12}$/.test(ref) && BigInt(ref) >= 1000000001n ? 'memo-acp:8453:' + ref : 'erc8183:' + chain + ':' + ref;\n return null;\n}\nfunction verdictFromDecision(d, composed) {\n const rubric = /RUBRIC_v1 compose · rubric (0x[0-9a-f]{64})/.exec(String(d.note ?? ''));\n const anchors = [];\n if (d.anchor && isHash(d.anchor.tx)) anchors.push(chainAnchor(Number(d.anchor.chain), d.anchor.tx, `JevDecisionLog.record${d.anchor.index != null ? ` (decision index ${d.anchor.index})` : ''} · status ${d.anchor.status}`, { block: d.anchor.block ?? null, contract: d.anchor.contract ?? null }));\n anchors.push({ kind: 'offchain', store: 'coordd:decisions', key: String(d.id), digest: isHash(d.digest) ? d.digest.toLowerCase() : null, anchored_by: anchors[0] && anchors[0].kind === 'chain' ? anchors[0] : null, what: 'the decision record; digest = sha256(canonicalDecision)' });\n return {\n schema: 'taifoon.verdict.v1', decision_id: String(d.id), version: d.v, kind: d.kind,\n subject: { chain_id: Number(d.subject && d.subject.chainId), at: d.subject && d.subject.at ? String(d.subject.at).toLowerCase() : null, ref: String(d.subject && d.subject.ref), label: (d.subject && d.subject.label) ?? null },\n subject_id: String(d.subject_id).toLowerCase(), subject_job_key: jobKeyOf(d.subject), digest: String(d.digest).toLowerCase(),\n input_digest: isHash(d.input_digest) ? d.input_digest.toLowerCase() : null, answers_digest: isHash(d.answers_digest) ? d.answers_digest.toLowerCase() : null,\n answers: (d.answers || []).map((a) => ({ id: String(a.id), question: a.question ?? null, options: a.options ?? null, value: String(a.value), probabilities: a.probabilities ?? {}, confidence: Number(a.confidence) })),\n confidence_bps: d.confidence_bps, model: d.model ?? null, answers_source: d.answers_source ?? 'layer',\n rubric: rubric ? { version: 'RUBRIC_v1', hash: rubric[1] } : null,\n composed: composed ? { verdict: composed.verdict, auto: Boolean(composed.auto), forced: composed.forced ?? null, reasons: (composed.reasons || []).map(String), receipt_hash: isHash(composed.receiptHash) ? composed.receiptHash.toLowerCase() : null } : null,\n anchors, source: { kind: 'decision', url: d.uri ?? `${SITE}/v1/judge/decisions/${d.id}`, read_at: Math.floor(Date.now() / 1000) },\n };\n}\n// the GradeStampRegistry verdict code (1 delivered_as_specified · 2 delivered_with_defects · 3 not_delivered · 4 cannot_determine)\nconst STAMP_CODE = { delivered_as_specified: 1, delivered_with_defects: 2, not_delivered: 3, cannot_determine: 4 };\nfunction stampCodeOf(v) {\n if (v.composed) return v.composed.verdict === 'complete' ? 1 : v.composed.verdict === 'reject' ? 2 : 4;\n const ref = (v.answers || []).find((a) => STAMP_CODE[a.value]);\n return ref ? STAMP_CODE[ref.value] : 4;\n}\n// the stamp transaction(s) the devnet signer mined → appended to the verdict's anchors\nconst stampAnchors = (sent, chainId) => (sent || []).filter((s) => isHash(s.tx) && s.status === 'success').map((s) => chainAnchor(chainId, s.tx, 'GradeStampRegistry.stamp (' + (s.action || 'stamp') + ')', { block: s.block ?? null, contract: s.to ?? null }));\n\n// the verdict to stamp: a recorded decision (pass { decision_id } on the trigger; the default is job 81067's REF decision)\nconst t = $input.first().json || {};\nconst id = t.decision_id || 'decision-1790239759511-7fc88c5799';\nconst d = (await this.helpers.httpRequest({ method: 'GET', url: SITE + '/v1/judge/decisions/' + id, json: true })).decision;\nconst v = verdictFromDecision(d, null);\nreturn [{ json: { ...v, stamp: { code: stampCodeOf(v), mode: 2 } } }];"
20
+ },
21
+ "id": "e00116ba-ac40-4cbe-9c4c-bddf65b3fc6d",
22
+ "name": "Verdict",
23
+ "type": "n8n-nodes-base.code",
24
+ "typeVersion": 2,
25
+ "position": [
26
+ 420,
27
+ 300
28
+ ],
29
+ "notes": "in: — → out: verdict.json · GET /v1/judge/decisions/:id → the canonical VERDICT (fromDecision) and its stamp code",
30
+ "notesInFlow": true
31
+ },
32
+ {
33
+ "parameters": {
34
+ "resource": "assurance",
35
+ "operation": "stampGrade",
36
+ "chainId": 36927,
37
+ "stampSubject": "={{ $json.subject_id }}",
38
+ "stampDigest": "={{ $json.digest }}",
39
+ "stampVerdict": "={{ String($json.stamp.code) }}",
40
+ "stampMode": "={{ String($json.stamp.mode) }}",
41
+ "stampModel": "={{ $json.model || 'jev' }}",
42
+ "stampUri": "={{ $json.source.url }}"
43
+ },
44
+ "id": "b2",
45
+ "name": "Stamp Grade",
46
+ "type": "CUSTOM.taifoon",
47
+ "typeVersion": 1,
48
+ "position": [
49
+ 640,
50
+ 300
51
+ ],
52
+ "notes": "in: verdict.json → out: verdict.json · POST /v1/assurance/call stampGrade: the unsigned GradeStampRegistry.stamp(subject_id, digest, verdict code, mode)",
53
+ "notesInFlow": true
54
+ },
55
+ {
56
+ "parameters": {
57
+ "callsField": "calls",
58
+ "sendRequires": false,
59
+ "wait": true
60
+ },
61
+ "id": "2900d540-a3cd-488b-bc44-175b18575dcf",
62
+ "name": "Taifoon Devnet Signer",
63
+ "type": "CUSTOM.taifoonDevnetSigner",
64
+ "typeVersion": 1,
65
+ "position": [
66
+ 860,
67
+ 300
68
+ ],
69
+ "credentials": {
70
+ "taifoonDevnetKeyApi": {
71
+ "id": "",
72
+ "name": "REPLACE: taifoonDevnetKeyApi"
73
+ }
74
+ },
75
+ "notes": "in: verdict.json → out: verdict.json · sign and send the stamp on devnet 36927 (anvil #0)",
76
+ "notesInFlow": true
77
+ },
78
+ {
79
+ "parameters": {
80
+ "jsCode": "// ── canonical VERDICT (taifoon.verdict.v1) from a decision record — the same mapping as the Taifoon verdict schema\n// fromDecision, inlined because an n8n code node cannot import the schemas package. workflows/test.mjs runs this node\n// and compares its output with fromDecision field by field.\nconst SITE = 'https://www.taifoon.io';\nconst isHash = (s) => typeof s === 'string' && /^0x[0-9a-fA-F]{64}$/.test(s);\nconst chainAnchor = (chain_id, tx, what, x = {}) => ({ kind: 'chain', chain_id, tx: tx.toLowerCase(), block: x.block ?? null, ts: x.ts ?? null, log_index: x.log_index ?? null, contract: x.contract ? x.contract.toLowerCase() : null, what });\nfunction jobKeyOf(subject) {\n const ref = String(subject && subject.ref || ''), chain = Number(subject && subject.chainId) || 8453;\n if (/^hs_[0-9a-f]{24}$/.test(ref)) return 'hs:' + ref;\n if (/^([a-z][a-z0-9-]{0,31}):([1-9][0-9]{0,9}):([0-9]{1,78})$/.test(ref)) return ref;\n if (/^0x[0-9a-fA-F]{64}$/.test(ref)) return 'hook:' + chain + ':' + ref.toLowerCase();\n if (/^[0-9]{1,78}$/.test(ref)) return /^[0-9]{10,12}$/.test(ref) && BigInt(ref) >= 1000000001n ? 'memo-acp:8453:' + ref : 'erc8183:' + chain + ':' + ref;\n return null;\n}\nfunction verdictFromDecision(d, composed) {\n const rubric = /RUBRIC_v1 compose · rubric (0x[0-9a-f]{64})/.exec(String(d.note ?? ''));\n const anchors = [];\n if (d.anchor && isHash(d.anchor.tx)) anchors.push(chainAnchor(Number(d.anchor.chain), d.anchor.tx, `JevDecisionLog.record${d.anchor.index != null ? ` (decision index ${d.anchor.index})` : ''} · status ${d.anchor.status}`, { block: d.anchor.block ?? null, contract: d.anchor.contract ?? null }));\n anchors.push({ kind: 'offchain', store: 'coordd:decisions', key: String(d.id), digest: isHash(d.digest) ? d.digest.toLowerCase() : null, anchored_by: anchors[0] && anchors[0].kind === 'chain' ? anchors[0] : null, what: 'the decision record; digest = sha256(canonicalDecision)' });\n return {\n schema: 'taifoon.verdict.v1', decision_id: String(d.id), version: d.v, kind: d.kind,\n subject: { chain_id: Number(d.subject && d.subject.chainId), at: d.subject && d.subject.at ? String(d.subject.at).toLowerCase() : null, ref: String(d.subject && d.subject.ref), label: (d.subject && d.subject.label) ?? null },\n subject_id: String(d.subject_id).toLowerCase(), subject_job_key: jobKeyOf(d.subject), digest: String(d.digest).toLowerCase(),\n input_digest: isHash(d.input_digest) ? d.input_digest.toLowerCase() : null, answers_digest: isHash(d.answers_digest) ? d.answers_digest.toLowerCase() : null,\n answers: (d.answers || []).map((a) => ({ id: String(a.id), question: a.question ?? null, options: a.options ?? null, value: String(a.value), probabilities: a.probabilities ?? {}, confidence: Number(a.confidence) })),\n confidence_bps: d.confidence_bps, model: d.model ?? null, answers_source: d.answers_source ?? 'layer',\n rubric: rubric ? { version: 'RUBRIC_v1', hash: rubric[1] } : null,\n composed: composed ? { verdict: composed.verdict, auto: Boolean(composed.auto), forced: composed.forced ?? null, reasons: (composed.reasons || []).map(String), receipt_hash: isHash(composed.receiptHash) ? composed.receiptHash.toLowerCase() : null } : null,\n anchors, source: { kind: 'decision', url: d.uri ?? `${SITE}/v1/judge/decisions/${d.id}`, read_at: Math.floor(Date.now() / 1000) },\n };\n}\n// the GradeStampRegistry verdict code (1 delivered_as_specified · 2 delivered_with_defects · 3 not_delivered · 4 cannot_determine)\nconst STAMP_CODE = { delivered_as_specified: 1, delivered_with_defects: 2, not_delivered: 3, cannot_determine: 4 };\nfunction stampCodeOf(v) {\n if (v.composed) return v.composed.verdict === 'complete' ? 1 : v.composed.verdict === 'reject' ? 2 : 4;\n const ref = (v.answers || []).find((a) => STAMP_CODE[a.value]);\n return ref ? STAMP_CODE[ref.value] : 4;\n}\n// the stamp transaction(s) the devnet signer mined → appended to the verdict's anchors\nconst stampAnchors = (sent, chainId) => (sent || []).filter((s) => isHash(s.tx) && s.status === 'success').map((s) => chainAnchor(chainId, s.tx, 'GradeStampRegistry.stamp (' + (s.action || 'stamp') + ')', { block: s.block ?? null, contract: s.to ?? null }));\n\n// the same VERDICT with the GradeStampRegistry stamp transaction appended to its anchors\nconst v = { ...$('Verdict').first().json };\ndelete v.stamp;\nreturn [{ json: { ...v, anchors: [...v.anchors, ...stampAnchors($json.sent, 36927)] } }];"
81
+ },
82
+ "id": "49dcb63d-9ae5-46e8-b7c3-abdc60598643",
83
+ "name": "Verdict + stamp",
84
+ "type": "n8n-nodes-base.code",
85
+ "typeVersion": 2,
86
+ "position": [
87
+ 1080,
88
+ 300
89
+ ],
90
+ "notes": "in: verdict.json → out: verdict.json · the same VERDICT with the stamp transaction appended to anchors",
91
+ "notesInFlow": true
92
+ }
93
+ ],
94
+ "connections": {
95
+ "Start": {
96
+ "main": [
97
+ [
98
+ {
99
+ "node": "Verdict",
100
+ "type": "main",
101
+ "index": 0
102
+ }
103
+ ]
104
+ ]
105
+ },
106
+ "Verdict": {
107
+ "main": [
108
+ [
109
+ {
110
+ "node": "Stamp Grade",
111
+ "type": "main",
112
+ "index": 0
113
+ }
114
+ ]
115
+ ]
116
+ },
117
+ "Stamp Grade": {
118
+ "main": [
119
+ [
120
+ {
121
+ "node": "Taifoon Devnet Signer",
122
+ "type": "main",
123
+ "index": 0
124
+ }
125
+ ]
126
+ ]
127
+ },
128
+ "Taifoon Devnet Signer": {
129
+ "main": [
130
+ [
131
+ {
132
+ "node": "Verdict + stamp",
133
+ "type": "main",
134
+ "index": 0
135
+ }
136
+ ]
137
+ ]
138
+ }
139
+ },
140
+ "settings": {},
141
+ "active": false,
142
+ "meta": {
143
+ "taifoon": {
144
+ "contract": "https://www.taifoon.io/schemas/coordination/v1/",
145
+ "steps": {
146
+ "Start": {
147
+ "in": null,
148
+ "out": null,
149
+ "does": "start (pass { decision_id } to stamp another verdict)"
150
+ },
151
+ "Verdict": {
152
+ "in": null,
153
+ "out": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
154
+ "does": "GET /v1/judge/decisions/:id → the canonical VERDICT (fromDecision) and its stamp code"
155
+ },
156
+ "Stamp Grade": {
157
+ "in": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
158
+ "out": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
159
+ "does": "POST /v1/assurance/call stampGrade: the unsigned GradeStampRegistry.stamp(subject_id, digest, verdict code, mode)"
160
+ },
161
+ "Taifoon Devnet Signer": {
162
+ "in": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
163
+ "out": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
164
+ "does": "sign and send the stamp on devnet 36927 (anvil #0)"
165
+ },
166
+ "Verdict + stamp": {
167
+ "in": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
168
+ "out": "https://www.taifoon.io/schemas/coordination/v1/verdict.json",
169
+ "does": "the same VERDICT with the stamp transaction appended to anchors"
170
+ }
171
+ }
172
+ }
173
+ },
174
+ "tags": []
175
+ }