@themoltnet/node-red-contrib-core 0.8.0 → 0.10.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.
@@ -0,0 +1,663 @@
1
+ [
2
+ {
3
+ "disabled": false,
4
+ "id": "moltnet_axiom_triage_tab",
5
+ "info": "Axiom monitor event -> parser/enricher task -> codebase triage task -> judge task.\n\nPrimary path for plans without custom webhooks: poll Axiom monitor history through the Axiom API, dedupe open events, normalize them into msg.axiom, then start the same triage chain.\n\nOptional future path: configure an Axiom custom webhook notifier to POST to /axiom/monitor on this Node-RED instance. The flow acknowledges the webhook immediately, starts work only for trigger-like events, and chains MoltNet tasks through task: build + task: read artifactRef values so large evidence artifacts stay in MoltNet instead of Node-RED messages.\n\nRequired environment/config:\n- AXIOM_MONITORING_TOKEN available to Node-RED for polling and to the agent runtime for deeper Axiom queries. AXIOM_API_TOKEN is accepted as a fallback for local experiments.\n- Optional AXIOM_MONITOR_IDS comma-separated monitor ids. Defaults to the committed MoltNet monitor ids created by infra/axiom/monitors/apply.mjs.\n- Axiom datasets: moltnet and moltnet-metrics.\n- Optional AXIOM_WEBHOOK_SECRET header value; the validation function checks x-axiom-webhook-secret when the env var is set. This is a shared-secret convention for the custom webhook, not a native Axiom signature scheme.\n\nThe tasks are expected to use .agents/skills/axiom-alert-triage and produce JSON artifacts titled telemetry-evidence, triage-report, and triage-judgment.",
6
+ "label": "Axiom Alert Triage (example)",
7
+ "type": "tab"
8
+ },
9
+ {
10
+ "apiUrl": "https://api.themolt.net",
11
+ "clientId": "",
12
+ "diaryId": "",
13
+ "id": "moltnet_agent_cfg",
14
+ "name": "triage-agent",
15
+ "teamId": "",
16
+ "type": "moltnet-agent"
17
+ },
18
+ {
19
+ "id": "axiom_notifier_setup_comment",
20
+ "info": "Axiom notifier setup:\n1. Create a Custom Webhook notifier in Axiom.\n2. Set the URL to the public Node-RED endpoint, for example https://node-red.example.net/axiom/monitor.\n3. Use a JSON body based on Axiom's documented template variables: {\"action\":\"{{.Action}}\",\"monitorId\":\"{{.MonitorID}}\",\"monitorName\":\"{{.Title}}\",\"body\":\"{{.Body}}\",\"value\":\"{{.Value}}\",\"severity\":\"{{.Severity}}\"}. The flow accepts both camelCase and Axiom-style names.\n4. If your Axiom webhook configuration supports custom headers, add x-axiom-webhook-secret and set the same value as AXIOM_WEBHOOK_SECRET in Node-RED. If headers are not available, leave AXIOM_WEBHOOK_SECRET unset and rely on the unguessable public URL plus network controls.\n\nNode-RED exposure:\n- With the default httpRoot, this flow exposes POST /axiom/monitor on the same host/port as the Node-RED HTTP API, typically http://localhost:1880/axiom/monitor.\n- Behind a reverse proxy, publish only that path where possible and keep the editor/admin UI private.\n\nTailscale Funnel option:\n- Run Node-RED bound locally, for example 127.0.0.1:1880.\n- Enable funnel for the Node-RED service with Tailscale, for example: tailscale serve --bg --http=1880 http://127.0.0.1:1880 && tailscale funnel 1880.\n- Use the resulting https://<machine>.<tailnet>.ts.net/axiom/monitor URL in Axiom. Verify with a manual POST before attaching production monitors.",
21
+ "name": "configure Axiom webhook + exposure",
22
+ "type": "comment",
23
+ "x": 260,
24
+ "y": 140,
25
+ "z": "moltnet_axiom_triage_tab"
26
+ },
27
+ {
28
+ "id": "axiom_polling_comment",
29
+ "info": "Polling fallback for Axiom plans without Custom Webhook notifiers:\n- Set AXIOM_MONITORING_TOKEN in the Node-RED environment. The token needs monitor read/history permission and query read permission if agents reuse it for telemetry analysis.\n- AXIOM_API_TOKEN is accepted as a fallback for local experiments.\n- Optional: set AXIOM_MONITOR_IDS to a comma-separated list of monitor ids. If unset, the function uses the current MoltNet monitor ids created by infra/axiom/monitors/apply.mjs.\n- The inject node polls every 5 minutes and looks back 15 minutes by default. Set AXIOM_POLL_WINDOW_MINUTES to adjust.\n- The parser dedupes events in flow context by monitor id + state + timestamp, so repeated polling does not create duplicate triage tasks.\n- Only open/triggered events enter the triage chain. Closed/resolved events go to the no-op debug lane.",
30
+ "name": "polling fallback without webhooks",
31
+ "type": "comment",
32
+ "x": 260,
33
+ "y": 240,
34
+ "z": "moltnet_axiom_triage_tab"
35
+ },
36
+ {
37
+ "crontab": "",
38
+ "id": "poll_axiom_history",
39
+ "name": "poll Axiom history",
40
+ "once": false,
41
+ "onceDelay": 0.1,
42
+ "payload": "",
43
+ "payloadType": "date",
44
+ "props": [
45
+ {
46
+ "p": "payload"
47
+ }
48
+ ],
49
+ "repeat": "300",
50
+ "topic": "",
51
+ "type": "inject",
52
+ "wires": [["build_history_requests"]],
53
+ "x": 150,
54
+ "y": 320,
55
+ "z": "moltnet_axiom_triage_tab"
56
+ },
57
+ {
58
+ "func": "const token = env.get('AXIOM_MONITORING_TOKEN') || env.get('AXIOM_API_TOKEN');\nif (!token) {\n node.error('AXIOM_MONITORING_TOKEN is required for Axiom monitor polling', msg);\n return null;\n}\n\nconst defaultIds = [\n 'nqTSos0T5yFVk5ddpg', // MoltNet auth/client noise by status\n 'lmBV3iwC8xKRBFnDCi', // MoltNet event loop pressure by service\n 'DaoHjCgByMCE6JLTm8', // MoltNet MCP server 5xx by route\n 'mc6I3pddWkPzIHG1gb', // MoltNet heap pressure by service\n 'ULZWyXD6wmvVbwFmMY', // MoltNet request latency p95 by service\n 'a15YiYDnMct15XeAVJ' // MoltNet REST API 5xx by route\n];\nconst ids = String(env.get('AXIOM_MONITOR_IDS') || '')\n .split(',')\n .map((id) => id.trim())\n .filter(Boolean);\nconst monitorIds = ids.length ? ids : defaultIds;\nconst lookbackMinutes = Number(env.get('AXIOM_POLL_WINDOW_MINUTES') || 15);\nconst end = new Date();\nconst start = new Date(end.getTime() - lookbackMinutes * 60 * 1000);\n\nfor (const monitorId of monitorIds) {\n const out = { ...msg };\n out.method = 'GET';\n out.url = 'https://api.axiom.co/v2/monitors/' + encodeURIComponent(monitorId) + '/history?startTime=' + encodeURIComponent(start.toISOString()) + '&endTime=' + encodeURIComponent(end.toISOString());\n out.headers = { Authorization: 'Bearer ' + token };\n out.axiomPoll = {\n monitorId,\n window: { start: start.toISOString(), end: end.toISOString() }\n };\n node.send(out);\n}\nreturn null;",
59
+ "id": "build_history_requests",
60
+ "name": "build history requests",
61
+ "outputs": 1,
62
+ "type": "function",
63
+ "wires": [["fetch_monitor_history"]],
64
+ "x": 390,
65
+ "y": 320,
66
+ "z": "moltnet_axiom_triage_tab"
67
+ },
68
+ {
69
+ "id": "fetch_monitor_history",
70
+ "method": "use",
71
+ "name": "Axiom monitor history",
72
+ "persist": false,
73
+ "ret": "obj",
74
+ "tls": "",
75
+ "type": "http request",
76
+ "url": "",
77
+ "wires": [["normalize_history_events"]],
78
+ "x": 630,
79
+ "y": 320,
80
+ "z": "moltnet_axiom_triage_tab"
81
+ },
82
+ {
83
+ "func": "const events = Array.isArray(msg.payload) ? msg.payload : [];\nconst seen = flow.get('axiomSeenMonitorEvents') || {};\nconst now = Date.now();\nconst maxAgeMs = 24 * 60 * 60 * 1000;\nfor (const [key, value] of Object.entries(seen)) {\n if (now - Number(value) > maxAgeMs) delete seen[key];\n}\n\nfor (const event of events) {\n const monitorId = event.checkId || msg.axiomPoll?.monitorId || 'unknown-monitor';\n const state = String(event.state || '').toLowerCase();\n const timestamp = event.timestamp || new Date().toISOString();\n const dedupeKey = [monitorId, state || 'unknown', timestamp].join('|');\n if (seen[dedupeKey]) continue;\n seen[dedupeKey] = now;\n\n const name = String(event.name || 'Axiom monitor alert');\n const action = state === 'open' || /^triggered:/i.test(name) ? 'triggered' : state === 'closed' || /^resolved:/i.test(name) ? 'resolved' : state || 'unknown';\n const monitorName = name.replace(/^(Triggered|Resolved):\\s*/i, '') || 'Axiom monitor alert';\n const triggerLike = action === 'triggered' || action === 'open' || action === 'firing';\n\n const out = { ...msg };\n out.axiom = {\n raw: event,\n action,\n triggerLike,\n monitorId,\n monitorName,\n service: null,\n route: null,\n severity: 'unknown',\n startedAt: timestamp,\n window: msg.axiomPoll?.window || { start: 'now-15m', end: 'now' },\n datasets: { traces: 'moltnet', metrics: 'moltnet-metrics' },\n repo: { owner: 'getlarge', name: 'themoltnet' },\n source: 'axiom-monitor-history'\n };\n node.send(out);\n}\nflow.set('axiomSeenMonitorEvents', seen);\nreturn null;",
84
+ "id": "normalize_history_events",
85
+ "name": "normalize + dedupe history",
86
+ "outputs": 1,
87
+ "type": "function",
88
+ "wires": [["history_alerts_out"]],
89
+ "x": 880,
90
+ "y": 320,
91
+ "z": "moltnet_axiom_triage_tab"
92
+ },
93
+ {
94
+ "id": "axiom_webhook_in",
95
+ "method": "post",
96
+ "name": "Axiom monitor webhook",
97
+ "swaggerDoc": "",
98
+ "type": "http in",
99
+ "url": "/axiom/monitor",
100
+ "wires": [["parse_axiom_webhook"]],
101
+ "x": 150,
102
+ "y": 60,
103
+ "z": "moltnet_axiom_triage_tab"
104
+ },
105
+ {
106
+ "func": "function respond(statusCode, payload) {\n msg.statusCode = statusCode;\n msg.payload = payload;\n return [msg, null];\n}\n\nconst headerSecret = msg.req?.headers?.['x-axiom-webhook-secret'];\nconst expectedSecret = env.get('AXIOM_WEBHOOK_SECRET');\nif (expectedSecret && headerSecret !== expectedSecret) {\n return respond(401, { ok: false, error: 'invalid webhook secret' });\n}\n\nlet payload = msg.payload;\nif (typeof payload === 'string') {\n try {\n payload = JSON.parse(payload);\n } catch (err) {\n return respond(400, { ok: false, error: 'invalid JSON payload' });\n }\n}\n\nconst action = String(payload?.action || payload?.Action || payload?.state || payload?.State || '').toLowerCase();\nconst monitorId = payload?.monitorId || payload?.MonitorID || payload?.monitor_id || payload?.id || payload?.ID || 'unknown-monitor';\nconst monitorName = payload?.monitorName || payload?.MonitorName || payload?.name || payload?.Title || 'Axiom monitor alert';\nconst service = payload?.service || payload?.serviceName || payload?.['service.name'] || payload?.Service || null;\nconst route = payload?.route || payload?.httpRoute || payload?.['attributes.route'] || null;\nconst severity = payload?.severity || payload?.Severity || payload?.priority || 'unknown';\nconst startedAt = payload?.startedAt || payload?.timestamp || payload?.Timestamp || new Date().toISOString();\nconst start = payload?.startTime || payload?.StartTime || 'now-30m';\nconst end = payload?.endTime || payload?.EndTime || 'now';\n\nconst triggerLike = !action || ['trigger', 'triggered', 'open', 'alert', 'firing'].includes(action);\nmsg.axiom = {\n raw: payload,\n action: action || 'unknown',\n triggerLike,\n monitorId,\n monitorName,\n service,\n route,\n severity,\n startedAt,\n window: { start, end },\n datasets: { traces: 'moltnet', metrics: 'moltnet-metrics' },\n repo: { owner: 'getlarge', name: 'themoltnet' }\n};\n\nconst ack = {\n ...msg,\n statusCode: 202,\n payload: {\n ok: true,\n accepted: triggerLike,\n monitorId,\n monitorName,\n action: msg.axiom.action\n }\n};\nreturn [ack, msg];",
107
+ "id": "parse_axiom_webhook",
108
+ "name": "validate + normalize webhook",
109
+ "outputs": 2,
110
+ "type": "function",
111
+ "wires": [["axiom_webhook_response"], ["webhook_alerts_out"]],
112
+ "x": 390,
113
+ "y": 60,
114
+ "z": "moltnet_axiom_triage_tab"
115
+ },
116
+ {
117
+ "id": "axiom_webhook_response",
118
+ "name": "ack",
119
+ "type": "http response",
120
+ "wires": [],
121
+ "x": 620,
122
+ "y": 25,
123
+ "z": "moltnet_axiom_triage_tab"
124
+ },
125
+ {
126
+ "checkall": "true",
127
+ "id": "only_trigger_events",
128
+ "name": "trigger event?",
129
+ "outputs": 2,
130
+ "property": "axiom.triggerLike",
131
+ "propertyType": "msg",
132
+ "rules": [
133
+ {
134
+ "t": "true"
135
+ },
136
+ {
137
+ "t": "else"
138
+ }
139
+ ],
140
+ "type": "switch",
141
+ "wires": [["build_parser_task"], ["debug_resolution"]],
142
+ "x": 330,
143
+ "y": 470,
144
+ "z": "moltnet_axiom_triage_tab"
145
+ },
146
+ {
147
+ "func": "const alert = msg.axiom;\nconst brief = [\n 'Use the axiom-alert-triage skill.',\n 'You are the parser/enricher for an Axiom monitor alert.',\n 'Classify the alert family, choose the relevant Axiom API surfaces, query correlated logs/traces/metrics, and produce a JSON artifact titled telemetry-evidence.',\n 'Use the alert context attached to this task. Do not inspect the codebase yet unless needed to choose query dimensions. Do not mutate Axiom config.',\n '',\n 'Artifact requirements:',\n '- title: telemetry-evidence',\n '- kind: json',\n '- include monitor id/name/action/severity/window',\n '- include exact APL/MPL query text and result summaries',\n '- include correlated traceIds/taskIds/teamIds/diaryIds when present',\n '- include gaps and confidence'\n].join('\\n');\n\nmsg.payload = {\n taskType: 'freeform',\n title: 'Axiom alert evidence: ' + alert.monitorName,\n tags: ['axiom-alert', 'telemetry-evidence', alert.monitorId],\n input: { brief }\n};\nreturn msg;",
148
+ "id": "build_parser_task",
149
+ "name": "build parser/enricher task",
150
+ "outputs": 1,
151
+ "type": "function",
152
+ "wires": [["builder_parser_task"]],
153
+ "x": 560,
154
+ "y": 450,
155
+ "z": "moltnet_axiom_triage_tab"
156
+ },
157
+ {
158
+ "agent": "moltnet_agent_cfg",
159
+ "brief": "",
160
+ "constraints": [
161
+ "Do not mutate Axiom configuration.",
162
+ "Do not inspect the codebase unless query dimensions require it."
163
+ ],
164
+ "contexts": [
165
+ {
166
+ "binding": "skill",
167
+ "slug": "axiom-alert-triage",
168
+ "value": "axiom-alert-triage",
169
+ "valueType": "str"
170
+ },
171
+ {
172
+ "binding": "context_inline",
173
+ "slug": "alert",
174
+ "value": "axiom",
175
+ "valueType": "msg"
176
+ }
177
+ ],
178
+ "diaryId": "",
179
+ "diaryIdType": "str",
180
+ "expectedOutput": "A concise summary plus a JSON artifact titled telemetry-evidence.",
181
+ "id": "builder_parser_task",
182
+ "name": "task: build parser/enricher",
183
+ "referencesFrom": "",
184
+ "referencesRole": "context",
185
+ "schemaCid": "",
186
+ "submitOutputGate": false,
187
+ "tags": "",
188
+ "taskType": "freeform",
189
+ "teamId": "",
190
+ "teamIdType": "str",
191
+ "title": "",
192
+ "type": "moltnet-task-builder",
193
+ "wires": [["create_parser_task"]],
194
+ "workspace": "none",
195
+ "x": 790,
196
+ "y": 450,
197
+ "z": "moltnet_axiom_triage_tab"
198
+ },
199
+ {
200
+ "agent": "moltnet_agent_cfg",
201
+ "generateCorrelationId": true,
202
+ "id": "create_parser_task",
203
+ "name": "parser/enricher",
204
+ "taskType": "freeform",
205
+ "type": "moltnet-tasks-create",
206
+ "wires": [["wait_parser_task"]],
207
+ "x": 1030,
208
+ "y": 450,
209
+ "z": "moltnet_axiom_triage_tab"
210
+ },
211
+ {
212
+ "agent": "moltnet_agent_cfg",
213
+ "id": "wait_parser_task",
214
+ "kinds": "",
215
+ "name": "wait parser/enricher",
216
+ "pollIntervalSec": 10,
217
+ "tail": true,
218
+ "taskId": "",
219
+ "timeoutSec": 1800,
220
+ "type": "moltnet-task-wait",
221
+ "wires": [["parser_tail_out"], ["read_evidence_artifact"]],
222
+ "x": 1260,
223
+ "y": 450,
224
+ "z": "moltnet_axiom_triage_tab"
225
+ },
226
+ {
227
+ "artifactKind": "json",
228
+ "artifactTitle": "telemetry-evidence",
229
+ "id": "read_evidence_artifact",
230
+ "name": "read telemetry evidence",
231
+ "role": "context",
232
+ "source": "payload",
233
+ "type": "moltnet-task-reader",
234
+ "wires": [["evidence_ready_out"]],
235
+ "x": 1490,
236
+ "y": 450,
237
+ "z": "moltnet_axiom_triage_tab"
238
+ },
239
+ {
240
+ "func": "const evidence = msg.result?.artifactBody || null;\nconst artifactRef = msg.result?.artifactRef;\nconst outputRef = msg.result?.outputRef;\nif (!artifactRef && !outputRef) {\n node.error('Parser task did not produce an artifactRef or outputRef', msg);\n return null;\n}\nconst alert = msg.axiom || {};\nmsg.telemetryEvidence = evidence;\nmsg.telemetryEvidenceRef = artifactRef || outputRef;\nconst brief = [\n 'Use the axiom-alert-triage skill.',\n 'You are the codebase triage agent for an Axiom alert in getlarge/themoltnet.',\n 'Consume the referenced telemetry-evidence artifact. Query Axiom again only if the evidence has gaps that block a concrete conclusion.',\n 'Compare findings with the MoltNet codebase, open GitHub issues, and MoltNet diary entries. Do not patch code and do not mutate Axiom config.',\n '',\n 'Produce a JSON artifact titled triage-report with verdict, evidence, suspectedSubsystems, codeReferences, relatedIssues, relatedEntries, recommendedAction, confidence, and gaps.'\n].join('\\n');\n\nmsg.payload = {\n taskType: 'freeform',\n title: 'Axiom alert codebase triage: ' + (alert.monitorName || 'unknown monitor'),\n tags: ['axiom-alert', 'codebase-triage', alert.monitorId || 'unknown-monitor'],\n input: { brief }\n};\nreturn msg;",
241
+ "id": "build_triage_task",
242
+ "name": "build codebase triage task",
243
+ "outputs": 1,
244
+ "type": "function",
245
+ "wires": [["builder_triage_task"]],
246
+ "x": 380,
247
+ "y": 620,
248
+ "z": "moltnet_axiom_triage_tab"
249
+ },
250
+ {
251
+ "agent": "moltnet_agent_cfg",
252
+ "brief": "",
253
+ "constraints": [
254
+ "Do not patch code.",
255
+ "Do not mutate Axiom configuration.",
256
+ "Query Axiom again only when evidence gaps block a concrete conclusion."
257
+ ],
258
+ "contexts": [
259
+ {
260
+ "binding": "skill",
261
+ "slug": "axiom-alert-triage",
262
+ "value": "axiom-alert-triage",
263
+ "valueType": "str"
264
+ },
265
+ {
266
+ "binding": "context_inline",
267
+ "slug": "alert",
268
+ "value": "axiom",
269
+ "valueType": "msg"
270
+ },
271
+ {
272
+ "binding": "context_inline",
273
+ "slug": "telemetry-evidence-preview",
274
+ "value": "telemetryEvidence",
275
+ "valueType": "msg"
276
+ },
277
+ {
278
+ "binding": "context_inline",
279
+ "slug": "telemetry-evidence-ref",
280
+ "value": "telemetryEvidenceRef",
281
+ "valueType": "msg"
282
+ }
283
+ ],
284
+ "diaryId": "",
285
+ "diaryIdType": "str",
286
+ "expectedOutput": "A concise triage summary plus a JSON artifact titled triage-report.",
287
+ "id": "builder_triage_task",
288
+ "name": "task: build codebase triage",
289
+ "referencesFrom": "telemetryEvidenceRef",
290
+ "referencesRole": "context",
291
+ "schemaCid": "",
292
+ "submitOutputGate": false,
293
+ "tags": "",
294
+ "taskType": "freeform",
295
+ "teamId": "",
296
+ "teamIdType": "str",
297
+ "title": "",
298
+ "type": "moltnet-task-builder",
299
+ "wires": [["create_triage_task"]],
300
+ "workspace": "shared_mount",
301
+ "x": 630,
302
+ "y": 620,
303
+ "z": "moltnet_axiom_triage_tab"
304
+ },
305
+ {
306
+ "agent": "moltnet_agent_cfg",
307
+ "generateCorrelationId": false,
308
+ "id": "create_triage_task",
309
+ "name": "codebase triage",
310
+ "taskType": "freeform",
311
+ "type": "moltnet-tasks-create",
312
+ "wires": [["wait_triage_task"]],
313
+ "x": 880,
314
+ "y": 620,
315
+ "z": "moltnet_axiom_triage_tab"
316
+ },
317
+ {
318
+ "agent": "moltnet_agent_cfg",
319
+ "id": "wait_triage_task",
320
+ "kinds": "",
321
+ "name": "wait codebase triage",
322
+ "pollIntervalSec": 10,
323
+ "tail": true,
324
+ "taskId": "",
325
+ "timeoutSec": 3600,
326
+ "type": "moltnet-task-wait",
327
+ "wires": [["triage_tail_out"], ["read_triage_artifact"]],
328
+ "x": 1110,
329
+ "y": 620,
330
+ "z": "moltnet_axiom_triage_tab"
331
+ },
332
+ {
333
+ "artifactKind": "json",
334
+ "artifactTitle": "triage-report",
335
+ "id": "read_triage_artifact",
336
+ "name": "read triage report",
337
+ "role": "judged_work",
338
+ "source": "payload",
339
+ "type": "moltnet-task-reader",
340
+ "wires": [["triage_ready_out"]],
341
+ "x": 1340,
342
+ "y": 620,
343
+ "z": "moltnet_axiom_triage_tab"
344
+ },
345
+ {
346
+ "func": "const report = msg.result?.artifactBody || null;\nconst artifactRef = msg.result?.artifactRef;\nconst outputRef = msg.result?.outputRef;\nif (!artifactRef && !outputRef) {\n node.error('Triage task did not produce an artifactRef or outputRef', msg);\n return null;\n}\nconst alert = msg.axiom || {};\nmsg.triageReport = report;\nmsg.triageReportRef = artifactRef || outputRef;\nconst brief = [\n 'Use the axiom-alert-triage skill.',\n 'You are the judge for an Axiom alert triage report.',\n 'Review the referenced triage-report artifact and decide whether the analysis is actionable, needs revision, or should be treated as monitor noise.',\n 'Do not patch code, mutate Axiom config, or create GitHub issues.',\n '',\n 'Judge rubric:',\n '- report cites telemetry evidence',\n '- code references exist for actionable code hypotheses',\n '- severity uses monitor threshold/value/window context',\n '- related GitHub issues and MoltNet entries were checked when tooling was available',\n '- recommended action is concrete',\n '',\n 'Produce a JSON artifact titled triage-judgment with verdict pass|revise|noise, reasons, missingEvidence, and nextAction.'\n].join('\\n');\n\nmsg.payload = {\n taskType: 'freeform',\n title: 'Judge Axiom alert triage: ' + (alert.monitorName || 'unknown monitor'),\n tags: ['axiom-alert', 'triage-judge', alert.monitorId || 'unknown-monitor'],\n input: { brief }\n};\nreturn msg;",
347
+ "id": "build_judge_task",
348
+ "name": "build judge task",
349
+ "outputs": 1,
350
+ "type": "function",
351
+ "wires": [["builder_judge_task"]],
352
+ "x": 380,
353
+ "y": 770,
354
+ "z": "moltnet_axiom_triage_tab"
355
+ },
356
+ {
357
+ "agent": "moltnet_agent_cfg",
358
+ "brief": "",
359
+ "constraints": [
360
+ "Do not patch code.",
361
+ "Do not mutate Axiom configuration.",
362
+ "Do not create GitHub issues."
363
+ ],
364
+ "contexts": [
365
+ {
366
+ "binding": "skill",
367
+ "slug": "axiom-alert-triage",
368
+ "value": "axiom-alert-triage",
369
+ "valueType": "str"
370
+ },
371
+ {
372
+ "binding": "context_inline",
373
+ "slug": "alert",
374
+ "value": "axiom",
375
+ "valueType": "msg"
376
+ },
377
+ {
378
+ "binding": "context_inline",
379
+ "slug": "triage-report-preview",
380
+ "value": "triageReport",
381
+ "valueType": "msg"
382
+ },
383
+ {
384
+ "binding": "context_inline",
385
+ "slug": "triage-report-ref",
386
+ "value": "triageReportRef",
387
+ "valueType": "msg"
388
+ }
389
+ ],
390
+ "diaryId": "",
391
+ "diaryIdType": "str",
392
+ "expectedOutput": "A concise judgment plus a JSON artifact titled triage-judgment.",
393
+ "id": "builder_judge_task",
394
+ "name": "task: build judge",
395
+ "referencesFrom": "triageReportRef",
396
+ "referencesRole": "judged_work",
397
+ "schemaCid": "",
398
+ "submitOutputGate": false,
399
+ "tags": "",
400
+ "taskType": "freeform",
401
+ "teamId": "",
402
+ "teamIdType": "str",
403
+ "title": "",
404
+ "type": "moltnet-task-builder",
405
+ "wires": [["create_judge_task"]],
406
+ "workspace": "none",
407
+ "x": 620,
408
+ "y": 770,
409
+ "z": "moltnet_axiom_triage_tab"
410
+ },
411
+ {
412
+ "agent": "moltnet_agent_cfg",
413
+ "generateCorrelationId": false,
414
+ "id": "create_judge_task",
415
+ "name": "judge",
416
+ "taskType": "freeform",
417
+ "type": "moltnet-tasks-create",
418
+ "wires": [["wait_judge_task"]],
419
+ "x": 850,
420
+ "y": 770,
421
+ "z": "moltnet_axiom_triage_tab"
422
+ },
423
+ {
424
+ "agent": "moltnet_agent_cfg",
425
+ "id": "wait_judge_task",
426
+ "kinds": "",
427
+ "name": "wait judge",
428
+ "pollIntervalSec": 10,
429
+ "tail": true,
430
+ "taskId": "",
431
+ "timeoutSec": 1800,
432
+ "type": "moltnet-task-wait",
433
+ "wires": [["judge_tail_out"], ["read_judge_artifact"]],
434
+ "x": 1060,
435
+ "y": 770,
436
+ "z": "moltnet_axiom_triage_tab"
437
+ },
438
+ {
439
+ "artifactKind": "json",
440
+ "artifactTitle": "triage-judgment",
441
+ "id": "read_judge_artifact",
442
+ "name": "read judgment",
443
+ "role": "context",
444
+ "source": "payload",
445
+ "type": "moltnet-task-reader",
446
+ "wires": [["debug_done"]],
447
+ "x": 1270,
448
+ "y": 770,
449
+ "z": "moltnet_axiom_triage_tab"
450
+ },
451
+ {
452
+ "active": true,
453
+ "complete": "payload",
454
+ "console": false,
455
+ "id": "debug_tail",
456
+ "name": "task tail",
457
+ "tosidebar": true,
458
+ "type": "debug",
459
+ "wires": [],
460
+ "x": 1490,
461
+ "y": 925,
462
+ "z": "moltnet_axiom_triage_tab"
463
+ },
464
+ {
465
+ "active": true,
466
+ "complete": "axiom",
467
+ "console": false,
468
+ "id": "debug_resolution",
469
+ "name": "resolution/no-op",
470
+ "tosidebar": true,
471
+ "type": "debug",
472
+ "wires": [],
473
+ "x": 590,
474
+ "y": 515,
475
+ "z": "moltnet_axiom_triage_tab"
476
+ },
477
+ {
478
+ "active": true,
479
+ "complete": "result",
480
+ "console": false,
481
+ "id": "debug_done",
482
+ "name": "triage judgment",
483
+ "tosidebar": true,
484
+ "type": "debug",
485
+ "wires": [],
486
+ "x": 1490,
487
+ "y": 770,
488
+ "z": "moltnet_axiom_triage_tab"
489
+ },
490
+ {
491
+ "id": "history_alerts_out",
492
+ "links": ["alert_events_in"],
493
+ "mode": "link",
494
+ "name": "monitor events",
495
+ "type": "link out",
496
+ "wires": [],
497
+ "x": 1090,
498
+ "y": 320,
499
+ "z": "moltnet_axiom_triage_tab"
500
+ },
501
+ {
502
+ "id": "webhook_alerts_out",
503
+ "links": ["alert_events_in"],
504
+ "mode": "link",
505
+ "name": "monitor events",
506
+ "type": "link out",
507
+ "wires": [],
508
+ "x": 625,
509
+ "y": 75,
510
+ "z": "moltnet_axiom_triage_tab"
511
+ },
512
+ {
513
+ "id": "alert_events_in",
514
+ "links": [
515
+ "history_alerts_out",
516
+ "webhook_alerts_out",
517
+ "synthetic_alerts_out"
518
+ ],
519
+ "name": "monitor events",
520
+ "type": "link in",
521
+ "wires": [["only_trigger_events"]],
522
+ "x": 145,
523
+ "y": 470,
524
+ "z": "moltnet_axiom_triage_tab"
525
+ },
526
+ {
527
+ "id": "evidence_ready_out",
528
+ "links": ["evidence_ready_in"],
529
+ "mode": "link",
530
+ "name": "telemetry evidence",
531
+ "type": "link out",
532
+ "wires": [],
533
+ "x": 1685,
534
+ "y": 450,
535
+ "z": "moltnet_axiom_triage_tab"
536
+ },
537
+ {
538
+ "id": "evidence_ready_in",
539
+ "links": ["evidence_ready_out"],
540
+ "name": "telemetry evidence",
541
+ "type": "link in",
542
+ "wires": [["build_triage_task"]],
543
+ "x": 145,
544
+ "y": 620,
545
+ "z": "moltnet_axiom_triage_tab"
546
+ },
547
+ {
548
+ "id": "triage_ready_out",
549
+ "links": ["triage_ready_in"],
550
+ "mode": "link",
551
+ "name": "triage report",
552
+ "type": "link out",
553
+ "wires": [],
554
+ "x": 1535,
555
+ "y": 620,
556
+ "z": "moltnet_axiom_triage_tab"
557
+ },
558
+ {
559
+ "id": "triage_ready_in",
560
+ "links": ["triage_ready_out"],
561
+ "name": "triage report",
562
+ "type": "link in",
563
+ "wires": [["build_judge_task"]],
564
+ "x": 145,
565
+ "y": 770,
566
+ "z": "moltnet_axiom_triage_tab"
567
+ },
568
+ {
569
+ "id": "parser_tail_out",
570
+ "links": ["tail_events_in"],
571
+ "mode": "link",
572
+ "name": "tail events",
573
+ "type": "link out",
574
+ "wires": [],
575
+ "x": 1455,
576
+ "y": 415,
577
+ "z": "moltnet_axiom_triage_tab"
578
+ },
579
+ {
580
+ "id": "triage_tail_out",
581
+ "links": ["tail_events_in"],
582
+ "mode": "link",
583
+ "name": "tail events",
584
+ "type": "link out",
585
+ "wires": [],
586
+ "x": 1305,
587
+ "y": 585,
588
+ "z": "moltnet_axiom_triage_tab"
589
+ },
590
+ {
591
+ "id": "judge_tail_out",
592
+ "links": ["tail_events_in"],
593
+ "mode": "link",
594
+ "name": "tail events",
595
+ "type": "link out",
596
+ "wires": [],
597
+ "x": 1255,
598
+ "y": 735,
599
+ "z": "moltnet_axiom_triage_tab"
600
+ },
601
+ {
602
+ "id": "tail_events_in",
603
+ "links": ["parser_tail_out", "triage_tail_out", "judge_tail_out"],
604
+ "name": "tail events",
605
+ "type": "link in",
606
+ "wires": [["debug_tail"]],
607
+ "x": 1270,
608
+ "y": 925,
609
+ "z": "moltnet_axiom_triage_tab"
610
+ },
611
+ {
612
+ "id": "synthetic_alert_comment",
613
+ "info": "Manual smoke test path:\n- Click the inject node below to create a synthetic Axiom monitor trigger.\n- It uses the same normalized msg.axiom contract as polling/webhook events and enters through the common monitor-events link.\n- Keep msg.axiom.synthetic=true in downstream artifacts so humans and agents know this was not a real Axiom alert.\n- Use this to verify Node-RED wiring, MoltNet task creation, artifact chaining, and judge behavior without waiting for a real monitor transition.",
614
+ "name": "manual synthetic alert test",
615
+ "type": "comment",
616
+ "x": 260,
617
+ "y": 390,
618
+ "z": "moltnet_axiom_triage_tab"
619
+ },
620
+ {
621
+ "crontab": "",
622
+ "id": "inject_synthetic_alert",
623
+ "name": "inject synthetic alert",
624
+ "once": false,
625
+ "onceDelay": 0.1,
626
+ "payload": "",
627
+ "payloadType": "date",
628
+ "props": [
629
+ {
630
+ "p": "payload"
631
+ }
632
+ ],
633
+ "repeat": "",
634
+ "topic": "",
635
+ "type": "inject",
636
+ "wires": [["build_synthetic_alert"]],
637
+ "x": 150,
638
+ "y": 430,
639
+ "z": "moltnet_axiom_triage_tab"
640
+ },
641
+ {
642
+ "func": "const now = new Date();\nconst start = new Date(now.getTime() - 15 * 60 * 1000);\nmsg.axiom = {\n raw: {\n synthetic: true,\n reason: 'manual Node-RED smoke test',\n generatedAt: now.toISOString()\n },\n action: 'triggered',\n triggerLike: true,\n monitorId: 'synthetic-node-red-smoke-test',\n monitorName: 'Synthetic Node-RED triage smoke test',\n service: 'moltnet-rest-api',\n route: '/synthetic/node-red-smoke-test',\n severity: 'test',\n startedAt: now.toISOString(),\n window: { start: start.toISOString(), end: now.toISOString() },\n datasets: { traces: 'moltnet', metrics: 'moltnet-metrics' },\n repo: { owner: 'getlarge', name: 'themoltnet' },\n source: 'synthetic-node-red-inject',\n synthetic: true\n};\nreturn msg;",
643
+ "id": "build_synthetic_alert",
644
+ "name": "build synthetic alert",
645
+ "outputs": 1,
646
+ "type": "function",
647
+ "wires": [["synthetic_alerts_out"]],
648
+ "x": 390,
649
+ "y": 430,
650
+ "z": "moltnet_axiom_triage_tab"
651
+ },
652
+ {
653
+ "id": "synthetic_alerts_out",
654
+ "links": ["alert_events_in"],
655
+ "mode": "link",
656
+ "name": "monitor events",
657
+ "type": "link out",
658
+ "wires": [],
659
+ "x": 610,
660
+ "y": 430,
661
+ "z": "moltnet_axiom_triage_tab"
662
+ }
663
+ ]