neuralos 3.9.4 → 3.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +48 -347
- package/cli.js +78 -0
- package/engine.js +124 -0
- package/index.js +160 -0
- package/package.json +37 -43
- package/test.js +20 -0
- package/tools.js +47 -0
- package/bin/gybackend.cjs +0 -398530
- package/plugins/agentspan-bridge/agentspan-bridge.extreme.spec.ts +0 -328
- package/plugins/agentspan-bridge/agentspan-bridge.phase2.extreme.spec.ts +0 -282
- package/plugins/agentspan-bridge/conductorClient.mjs +0 -182
- package/plugins/agentspan-bridge/index.mjs +0 -389
- package/plugins/agentspan-bridge/playbookToWorkflowDef.mjs +0 -209
- package/plugins/agentspan-bridge/plugin.json +0 -27
- package/plugins/fraudops/index.d.mts +0 -9
- package/plugins/fraudops/index.mjs +0 -185
- package/plugins/fraudops/plugin.json +0 -10
- package/plugins/iam-connector/index.d.mts +0 -10
- package/plugins/iam-connector/index.mjs +0 -229
- package/plugins/iam-connector/plugin.json +0 -10
- package/plugins/mitmproxy-bridge/index.d.mts +0 -20
- package/plugins/mitmproxy-bridge/index.mjs +0 -256
- package/plugins/mitmproxy-bridge/plugin.json +0 -17
- package/plugins/monid-bridge/index.mjs +0 -228
- package/plugins/monid-bridge/monid-bridge.extreme.spec.mjs +0 -75
- package/plugins/monid-bridge/plugin.json +0 -15
- package/plugins/netdata-rterm/index.mjs +0 -229
- package/plugins/netdata-rterm/netdata-rterm.extreme.spec.ts +0 -252
- package/plugins/netdata-rterm/plugin.json +0 -22
- package/plugins/netexec-bridge/index.d.mts +0 -28
- package/plugins/netexec-bridge/index.mjs +0 -325
- package/plugins/netexec-bridge/plugin.json +0 -20
- package/plugins/neuralos/index.mjs +0 -137
- package/plugins/neuralos/neuralos.extreme.spec.mts +0 -386
- package/plugins/neuralos/neuralosEngine.mjs +0 -369
- package/plugins/neuralos/plugin.json +0 -15
- package/plugins/numbat-bridge/index.mjs +0 -229
- package/plugins/numbat-bridge/numbat-bridge.extreme.spec.mjs +0 -148
- package/plugins/numbat-bridge/plugin.json +0 -21
- package/plugins/patch-manager/index.d.mts +0 -10
- package/plugins/patch-manager/index.mjs +0 -288
- package/plugins/patch-manager/plugin.json +0 -10
- package/plugins/promptfoo-redteam/index.d.mts +0 -15
- package/plugins/promptfoo-redteam/index.mjs +0 -277
- package/plugins/promptfoo-redteam/plugin.json +0 -16
- package/plugins/reactorpro-bridge/index.mjs +0 -499
- package/plugins/reactorpro-bridge/plugin.json +0 -26
- package/plugins/reactorpro-bridge/reactorpro-bridge.extreme.spec.mts +0 -409
- package/plugins/reactorpro-bridge/reactorproAgent.mjs +0 -399
- package/plugins/request-router/index.d.mts +0 -9
- package/plugins/request-router/index.mjs +0 -239
- package/plugins/request-router/plugin.json +0 -10
- package/plugins/sample-k8s-slo/index.ts +0 -58
- package/plugins/sample-k8s-slo/plugin.json +0 -11
- package/plugins/sop-assistant/index.d.mts +0 -9
- package/plugins/sop-assistant/index.mjs +0 -325
- package/plugins/sop-assistant/plugin.json +0 -10
- package/plugins/synapse-bridge/index.mjs +0 -484
- package/plugins/synapse-bridge/plugin.json +0 -29
- package/plugins/synapse-bridge/synapse-bridge.extreme.spec.mjs +0 -204
- package/plugins/synapse-bridge/synapseAgent.extreme.spec.mjs +0 -236
- package/plugins/synapse-bridge/synapseAgent.mjs +0 -254
- package/plugins/web-intel/index.mjs +0 -381
- package/plugins/web-intel/plugin.json +0 -28
- package/plugins/web-intel/sidecar.mjs +0 -134
- package/plugins/web-intel/web-intel.extreme.spec.ts +0 -263
- package/plugins/web-intel/wigoloClient.mjs +0 -145
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* playbookToWorkflowDef.mjs — map an RTerm playbook (PlaybookEntry) to a
|
|
3
|
-
* Netflix-Conductor WorkflowDef so AgentSpan agents can invoke it as a step
|
|
4
|
-
* (SUB_WORKFLOW) or RTerm can register + run it durably on the Conductor server.
|
|
5
|
-
*
|
|
6
|
-
* Pure mapping (no I/O):
|
|
7
|
-
* - command step → a run_command-style HTTP task carrying the command + target
|
|
8
|
-
* - script step → an HTTP task that references the saved script id (the
|
|
9
|
-
* Conductor worker resolves the script body by id)
|
|
10
|
-
* - wait step → a Conductor WAIT task (durationSeconds)
|
|
11
|
-
* - sequential + dependsOn DAG ordering → Conductor task `taskReferenceName`
|
|
12
|
-
* graph with JOIN on multiple dependencies
|
|
13
|
-
* - retries (onError=continue → retry, else fail-fast) → Conductor `retryCount`
|
|
14
|
-
* - rollback steps → compensating tasks appended after the main flow
|
|
15
|
-
*
|
|
16
|
-
* The emitted WorkflowDef is valid for `POST /api/metadata/workflow` on any
|
|
17
|
-
* Conductor OSS / AgentSpan server.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/** Sanitize a string into a Conductor-safe taskReferenceName ([A-Za-z0-9_]). */
|
|
21
|
-
export function taskRef(id, fallback) {
|
|
22
|
-
const s = String(id ?? fallback ?? 'step').replace(/[^A-Za-z0-9_]/g, '_')
|
|
23
|
-
return s.length > 0 ? s : 'step'
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Map a single RTerm playbook step to a Conductor task (no edges yet). */
|
|
27
|
-
export function stepToTask(step, index, opts = {}) {
|
|
28
|
-
const ref = taskRef(step.id ?? `step_${index}`)
|
|
29
|
-
const name = step.name || ref
|
|
30
|
-
const base = {
|
|
31
|
-
name,
|
|
32
|
-
taskReferenceName: ref,
|
|
33
|
-
retryCount: step.onError === 'continue' ? (opts.continueRetryCount ?? 0) : 0,
|
|
34
|
-
startDelay: 0,
|
|
35
|
-
optional: false,
|
|
36
|
-
asyncComplete: false,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (step.kind === 'wait') {
|
|
40
|
-
return {
|
|
41
|
-
...base,
|
|
42
|
-
type: 'WAIT',
|
|
43
|
-
inputParameters: { duration: step.waitSeconds ?? 0 },
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (step.kind === 'script') {
|
|
48
|
-
// Reference the saved script by id; the Conductor-side worker resolves the
|
|
49
|
-
// script body (kept out of the def so it stays compact + secret-free).
|
|
50
|
-
return {
|
|
51
|
-
...base,
|
|
52
|
-
type: 'SIMPLE',
|
|
53
|
-
inputParameters: {
|
|
54
|
-
kind: 'rterm_script',
|
|
55
|
-
scriptId: step.scriptId,
|
|
56
|
-
name,
|
|
57
|
-
},
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// kind === 'command' (default): an HTTP task that invokes RTerm's command
|
|
62
|
-
// execution surface with the command + target scope.
|
|
63
|
-
return {
|
|
64
|
-
...base,
|
|
65
|
-
type: 'HTTP',
|
|
66
|
-
inputParameters: {
|
|
67
|
-
http_request: {
|
|
68
|
-
uri: opts.execUri ?? '${workflow.input.rtermExecUri}',
|
|
69
|
-
method: 'POST',
|
|
70
|
-
headers: { 'content-type': 'application/json' },
|
|
71
|
-
body: {
|
|
72
|
-
kind: 'run_command',
|
|
73
|
-
command: step.command ?? '',
|
|
74
|
-
...(step.validate ? { validate: step.validate } : {}),
|
|
75
|
-
},
|
|
76
|
-
connectionTimeOut: opts.connectionTimeOut ?? 5000,
|
|
77
|
-
readTimeOut: opts.readTimeOut ?? 300000,
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Map a rollback action to a compensating Conductor task. */
|
|
84
|
-
export function rollbackToTask(rollback, stepRef, index) {
|
|
85
|
-
const ref = taskRef(`rollback_${stepRef}_${index}`)
|
|
86
|
-
if (rollback.kind === 'script') {
|
|
87
|
-
return {
|
|
88
|
-
name: ref,
|
|
89
|
-
taskReferenceName: ref,
|
|
90
|
-
type: 'SIMPLE',
|
|
91
|
-
inputParameters: { kind: 'rterm_script', scriptId: rollback.scriptId, compensating: true },
|
|
92
|
-
retryCount: 0,
|
|
93
|
-
optional: true, // a failing rollback never blocks the workflow result
|
|
94
|
-
asyncComplete: false,
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
name: ref,
|
|
99
|
-
taskReferenceName: ref,
|
|
100
|
-
type: 'HTTP',
|
|
101
|
-
inputParameters: {
|
|
102
|
-
http_request: {
|
|
103
|
-
uri: '${workflow.input.rtermExecUri}',
|
|
104
|
-
method: 'POST',
|
|
105
|
-
headers: { 'content-type': 'application/json' },
|
|
106
|
-
body: { kind: 'run_command', command: rollback.command ?? '', compensating: true },
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
retryCount: 0,
|
|
110
|
-
optional: true,
|
|
111
|
-
asyncComplete: false,
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Build the ordered task list with DAG edges from dependsOn.
|
|
117
|
-
* Returns { tasks, order } where order is the topological order of step refs.
|
|
118
|
-
* Steps with no dependsOn depend on the previous step (linear chain). Steps
|
|
119
|
-
* with multiple dependsOn get a JOIN task.
|
|
120
|
-
*/
|
|
121
|
-
export function buildDag(steps) {
|
|
122
|
-
const refs = steps.map((s, i) => taskRef(s.id ?? `step_${i}`))
|
|
123
|
-
const tasks = []
|
|
124
|
-
const joins = []
|
|
125
|
-
steps.forEach((s, i) => {
|
|
126
|
-
const ref = refs[i]
|
|
127
|
-
const deps = Array.isArray(s.dependsOn) && s.dependsOn.length > 0
|
|
128
|
-
? s.dependsOn.map((d) => taskRef(d))
|
|
129
|
-
: (i === 0 ? [] : [refs[i - 1]])
|
|
130
|
-
if (deps.length > 1) {
|
|
131
|
-
const joinRef = taskRef(`join_${ref}`)
|
|
132
|
-
joins.push({ ref: joinRef, type: 'JOIN', joinOn: deps, taskReferenceName: joinRef, name: joinRef })
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
return { refs, joins }
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Map a full RTerm playbook to a Conductor WorkflowDef.
|
|
140
|
-
* @param {object} playbook PlaybookEntry
|
|
141
|
-
* @param {object} [opts] { execUri, connectionTimeOut, readTimeOut, continueRetryCount }
|
|
142
|
-
* @returns {object} a Conductor WorkflowDef (POST /api/metadata/workflow)
|
|
143
|
-
*/
|
|
144
|
-
export function playbookToWorkflowDef(playbook, opts = {}) {
|
|
145
|
-
if (!playbook || !Array.isArray(playbook.steps)) {
|
|
146
|
-
throw new Error('playbookToWorkflowDef needs a playbook with a steps array')
|
|
147
|
-
}
|
|
148
|
-
const name = String(playbook.name || playbook.id || 'rterm_playbook').replace(/\s+/g, '_')
|
|
149
|
-
const steps = playbook.steps
|
|
150
|
-
const tasks = []
|
|
151
|
-
const rollbackTasks = []
|
|
152
|
-
|
|
153
|
-
// Main flow: map each step, then wire sequential/DAG ordering by emitting
|
|
154
|
-
// tasks in dependency order (Conductor executes top-down; dependsOn edges are
|
|
155
|
-
// expressed via JOINs for fan-in).
|
|
156
|
-
const refs = steps.map((s, i) => taskRef(s.id ?? `step_${i}`))
|
|
157
|
-
const emittedJoins = new Set()
|
|
158
|
-
|
|
159
|
-
steps.forEach((step, i) => {
|
|
160
|
-
const ref = refs[i]
|
|
161
|
-
const task = stepToTask(step, i, opts)
|
|
162
|
-
// Wire dependency: Conductor's default is sequential task order, but we
|
|
163
|
-
// explicitly model dependsOn fan-in as a JOIN before this task when the
|
|
164
|
-
// step has multiple dependencies.
|
|
165
|
-
const deps = Array.isArray(step.dependsOn) && step.dependsOn.length > 0
|
|
166
|
-
? step.dependsOn.map((d) => taskRef(d))
|
|
167
|
-
: []
|
|
168
|
-
if (deps.length > 1 && !emittedJoins.has(ref)) {
|
|
169
|
-
const joinRef = taskRef(`join_${ref}`)
|
|
170
|
-
tasks.push({
|
|
171
|
-
name: joinRef,
|
|
172
|
-
taskReferenceName: joinRef,
|
|
173
|
-
type: 'JOIN',
|
|
174
|
-
joinOn: deps,
|
|
175
|
-
})
|
|
176
|
-
emittedJoins.add(ref)
|
|
177
|
-
}
|
|
178
|
-
tasks.push(task)
|
|
179
|
-
if (step.rollback) {
|
|
180
|
-
rollbackTasks.push(rollbackToTask(step.rollback, ref, rollbackTasks.length))
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
// Compensating (rollback) tasks run after the main flow, in reverse step
|
|
185
|
-
// order (undo the most recent change first) — marked optional so they never
|
|
186
|
-
// mask the workflow's real result.
|
|
187
|
-
const orderedRollback = [...rollbackTasks].reverse()
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
name,
|
|
191
|
-
description: playbook.description || `RTerm playbook: ${playbook.name ?? name}`,
|
|
192
|
-
version: 1,
|
|
193
|
-
tasks: [...tasks, ...orderedRollback],
|
|
194
|
-
inputParameters: [],
|
|
195
|
-
outputParameters: {},
|
|
196
|
-
schemaVersion: 2,
|
|
197
|
-
restartable: true,
|
|
198
|
-
workflowStatusListenerEnabled: false,
|
|
199
|
-
ownerEmail: 'rterm@hyperspace.ng',
|
|
200
|
-
timeoutPolicy: 'ALERT_ONLY',
|
|
201
|
-
timeoutSeconds: 0,
|
|
202
|
-
variables: {},
|
|
203
|
-
inputTemplate: {
|
|
204
|
-
rtermExecUri: opts.execUri ?? 'http://localhost:17888/rpc/exec',
|
|
205
|
-
},
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export default { playbookToWorkflowDef, stepToTask, rollbackToTask, taskRef, buildDag }
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "agentspan-bridge",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "AgentSpan/Conductor bridge for RTerm — run durable, crash-resilient agents and workflows on an AgentSpan (Conductor) server from RTerm. Adds durable agent execution (resume-from-step on crash), plan-execute determinism, enterprise event triggers, and a live execution feed to RTerm's agent + dashboard. Server URL + optional auth are configured in Settings (agentspan block); secrets via the vault.",
|
|
5
|
-
"entry": "index.mjs",
|
|
6
|
-
"tools": [
|
|
7
|
-
"agentspan_health",
|
|
8
|
-
"agentspan_run",
|
|
9
|
-
"agentspan_status",
|
|
10
|
-
"agentspan_approve",
|
|
11
|
-
"agentspan_list",
|
|
12
|
-
"agentspan_stop",
|
|
13
|
-
"agentspan_export_playbook",
|
|
14
|
-
"agentspan_register_playbook",
|
|
15
|
-
"agentspan_delegate"
|
|
16
|
-
],
|
|
17
|
-
"triggers": [
|
|
18
|
-
"agentspan_execution_failed"
|
|
19
|
-
],
|
|
20
|
-
"panels": [
|
|
21
|
-
"agentspan-executions"
|
|
22
|
-
],
|
|
23
|
-
"permissions": [
|
|
24
|
-
"exec",
|
|
25
|
-
"readLedger:metrics"
|
|
26
|
-
]
|
|
27
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// fraudops plugin type declarations
|
|
2
|
-
export function register(ctx: any): void
|
|
3
|
-
export function buildPipelineHealthCommand(): string
|
|
4
|
-
export function buildNatsStatusCommand(): string
|
|
5
|
-
export function buildKafkaLagCommand(): string
|
|
6
|
-
export function parsePipelineHealth(output: string): { status: string; jobs: any[]; running: number; failed: number }
|
|
7
|
-
export function buildStrCase(txnId: string, decision: string, indicators?: string[], assignedTo?: string): any
|
|
8
|
-
export function buildDecisionSummary(decisions: Array<{ decision: string }> | null | undefined): { total: number; blocks: number; reviews: number; approves: number; blockRate: number; reviewRate: number; approveRate: number }
|
|
9
|
-
export default any
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* fraudops plugin — FraudOps for RTerm.
|
|
3
|
-
*
|
|
4
|
-
* Operational layer for the fraud detection pipeline. Monitors pipeline health
|
|
5
|
-
* (Flink, NATS, Kafka), manages STR workflow, tracks fraud incidents, and
|
|
6
|
-
* provides the unified fraud operations dashboard. Bridges Kafka/NATS metrics
|
|
7
|
-
* into RTerm's SRE pillar.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// --- Pure: build pipeline health check command ---
|
|
11
|
-
export function buildPipelineHealthCommand() {
|
|
12
|
-
return 'curl -s http://localhost:8081/jobs 2>/dev/null | head -20 || echo "flink not reachable"'
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// --- Pure: build NATS JetStream status command ---
|
|
16
|
-
export function buildNatsStatusCommand() {
|
|
17
|
-
return 'curl -s http://localhost:8222/streaming/channelsz 2>/dev/null | head -30 || echo "nats not reachable"'
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// --- Pure: build Kafka consumer lag command ---
|
|
21
|
-
export function buildKafkaLagCommand() {
|
|
22
|
-
return 'kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group fraud-pipeline 2>/dev/null | head -20 || echo "kafka not reachable"'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// --- Pure: parse pipeline health output ---
|
|
26
|
-
export function parsePipelineHealth(output) {
|
|
27
|
-
const health = { status: 'unknown', jobs: [], running: 0, failed: 0 }
|
|
28
|
-
try {
|
|
29
|
-
const parsed = JSON.parse(output)
|
|
30
|
-
if (parsed.jobs && Array.isArray(parsed.jobs)) {
|
|
31
|
-
health.jobs = parsed.jobs.map((j) => ({ id: j.jid, name: j.name, status: j.state }))
|
|
32
|
-
health.running = parsed.jobs.filter((j) => j.state === 'RUNNING').length
|
|
33
|
-
health.failed = parsed.jobs.filter((j) => j.state === 'FAILED').length
|
|
34
|
-
health.status = health.failed > 0 ? 'degraded' : health.running > 0 ? 'healthy' : 'unknown'
|
|
35
|
-
}
|
|
36
|
-
} catch { /* not JSON */ }
|
|
37
|
-
return health
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// --- Pure: build an STR case record ---
|
|
41
|
-
export function buildStrCase(txnId, decision, indicators, assignedTo) {
|
|
42
|
-
return {
|
|
43
|
-
id: `str-${txnId}-${Date.now().toString(36)}`,
|
|
44
|
-
txnId,
|
|
45
|
-
decision, // BLOCK | REVIEW
|
|
46
|
-
indicators: indicators ?? [],
|
|
47
|
-
assignedTo: assignedTo ?? 'unassigned',
|
|
48
|
-
status: 'pending', // pending | assigned | reviewed | filed | expired
|
|
49
|
-
deadline: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days (CBN requirement)
|
|
50
|
-
createdAt: Date.now(),
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// --- Pure: build a decision summary ---
|
|
55
|
-
export function buildDecisionSummary(decisions) {
|
|
56
|
-
const list = Array.isArray(decisions) ? decisions : []
|
|
57
|
-
const total = list.length
|
|
58
|
-
const blocks = list.filter((d) => d.decision === 'BLOCK').length
|
|
59
|
-
const reviews = list.filter((d) => d.decision === 'REVIEW').length
|
|
60
|
-
const approves = list.filter((d) => d.decision === 'APPROVE').length
|
|
61
|
-
return {
|
|
62
|
-
total,
|
|
63
|
-
blocks,
|
|
64
|
-
reviews,
|
|
65
|
-
approves,
|
|
66
|
-
blockRate: total > 0 ? Math.round((blocks / total) * 100) : 0,
|
|
67
|
-
reviewRate: total > 0 ? Math.round((reviews / total) * 100) : 0,
|
|
68
|
-
approveRate: total > 0 ? Math.round((approves / total) * 100) : 0,
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// --- Plugin entry ---
|
|
73
|
-
export function register(ctx) {
|
|
74
|
-
const { registerTool, registerTrigger, registerPanel, exec, readLedger, log } = ctx
|
|
75
|
-
const strCases = []
|
|
76
|
-
|
|
77
|
-
// Tool: fraudops_pipeline_status — check the health of the fraud pipeline
|
|
78
|
-
registerTool({
|
|
79
|
-
name: 'fraudops_pipeline_status',
|
|
80
|
-
description: 'Check the health of the fraud detection pipeline (Flink jobs, NATS JetStream, Kafka consumer lag). Returns status for each component.',
|
|
81
|
-
params: {},
|
|
82
|
-
handler: async () => {
|
|
83
|
-
const flinkOut = await exec(buildPipelineHealthCommand(), {})
|
|
84
|
-
const natsOut = await exec(buildNatsStatusCommand(), {})
|
|
85
|
-
const kafkaOut = await exec(buildKafkaLagCommand(), {})
|
|
86
|
-
const flink = parsePipelineHealth(flinkOut)
|
|
87
|
-
log(`[fraudops] pipeline status: flink=${flink.status} nats=${natsOut.slice(0, 50)} kafka=${kafkaOut.slice(0, 50)}`)
|
|
88
|
-
return { flink, nats: natsOut.slice(0, 500), kafka: kafkaOut.slice(0, 500) }
|
|
89
|
-
},
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
// Tool: fraudops_str_assign — assign an STR case to an analyst
|
|
93
|
-
registerTool({
|
|
94
|
-
name: 'fraudops_str_assign',
|
|
95
|
-
description: 'Assign an STR (Suspicious Transaction Report) case to an analyst. Creates the case with a 7-day CBN deadline.',
|
|
96
|
-
params: {
|
|
97
|
-
txnId: { type: 'string', description: 'Transaction ID' },
|
|
98
|
-
decision: { type: 'string', description: 'BLOCK or REVIEW' },
|
|
99
|
-
indicators: { type: 'array', description: 'Fraud indicators (e.g., ["high_velocity", "new_device"])' },
|
|
100
|
-
assignedTo: { type: 'string', description: 'Analyst username' },
|
|
101
|
-
},
|
|
102
|
-
handler: async (params) => {
|
|
103
|
-
const { txnId, decision, indicators, assignedTo } = params ?? {}
|
|
104
|
-
if (!txnId || !decision) return { error: 'txnId and decision are required' }
|
|
105
|
-
const strCase = buildStrCase(txnId, decision, indicators, assignedTo)
|
|
106
|
-
strCase.status = 'assigned'
|
|
107
|
-
strCases.push(strCase)
|
|
108
|
-
log(`[fraudops] STR case ${strCase.id} assigned to ${assignedTo}`)
|
|
109
|
-
return strCase
|
|
110
|
-
},
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
// Tool: fraudops_str_status — get STR case status
|
|
114
|
-
registerTool({
|
|
115
|
-
name: 'fraudops_str_status',
|
|
116
|
-
description: 'Get the status of STR cases. Filter by status, analyst, or overdue.',
|
|
117
|
-
params: {
|
|
118
|
-
status: { type: 'string', description: 'Filter by status: pending, assigned, reviewed, filed, expired' },
|
|
119
|
-
assignedTo: { type: 'string', description: 'Filter by analyst' },
|
|
120
|
-
overdue: { type: 'boolean', description: 'Only show overdue cases (past 7-day deadline)' },
|
|
121
|
-
},
|
|
122
|
-
handler: async (params) => {
|
|
123
|
-
let filtered = [...strCases]
|
|
124
|
-
if (params?.status) filtered = filtered.filter((c) => c.status === params.status)
|
|
125
|
-
if (params?.assignedTo) filtered = filtered.filter((c) => c.assignedTo === params.assignedTo)
|
|
126
|
-
if (params?.overdue) filtered = filtered.filter((c) => Date.now() > c.deadline)
|
|
127
|
-
return { total: filtered.length, cases: filtered }
|
|
128
|
-
},
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
// Tool: fraudops_decision_summary — summarize fraud decisions
|
|
132
|
-
registerTool({
|
|
133
|
-
name: 'fraudops_decision_summary',
|
|
134
|
-
description: 'Summarize fraud decisions (BLOCK/REVIEW/APPROVE counts and rates) from the decision stream.',
|
|
135
|
-
params: {
|
|
136
|
-
decisions: { type: 'array', description: 'Array of decision objects with { decision: "BLOCK"|"REVIEW"|"APPROVE" }' },
|
|
137
|
-
},
|
|
138
|
-
handler: async (params) => {
|
|
139
|
-
const decisions = params?.decisions ?? []
|
|
140
|
-
const summary = buildDecisionSummary(decisions)
|
|
141
|
-
log(`[fraudops] decision summary: ${summary.total} total, ${summary.blockRate}% block, ${summary.reviewRate}% review`)
|
|
142
|
-
return summary
|
|
143
|
-
},
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
// Trigger: fraudops_str_overdue — fires when an STR case is overdue
|
|
147
|
-
registerTrigger({
|
|
148
|
-
name: 'fraudops_str_overdue',
|
|
149
|
-
description: 'Fires when an STR case exceeds the 7-day CBN deadline. Use for escalation to senior analyst.',
|
|
150
|
-
match: (event) => {
|
|
151
|
-
if (event?.source !== 'fraudops') return false
|
|
152
|
-
return event.labels?.status === 'expired' || event.labels?.overdue === true
|
|
153
|
-
},
|
|
154
|
-
action: 'propose-change',
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
// Trigger: fraudops_pipeline_down — fires when a pipeline component is down
|
|
158
|
-
registerTrigger({
|
|
159
|
-
name: 'fraudops_pipeline_down',
|
|
160
|
-
description: 'Fires when a fraud pipeline component (Flink, NATS, Kafka) is detected as down. Use for immediate incident response.',
|
|
161
|
-
match: (event) => {
|
|
162
|
-
if (event?.source !== 'fraudops') return false
|
|
163
|
-
return event.labels?.status === 'down' || event.labels?.failed === true
|
|
164
|
-
},
|
|
165
|
-
action: 'run-playbook',
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
// Panel: fraudops-dashboard — unified fraud operations dashboard
|
|
169
|
-
registerPanel({
|
|
170
|
-
name: 'fraudops-dashboard',
|
|
171
|
-
title: 'Fraud Operations Dashboard',
|
|
172
|
-
render: (data) => {
|
|
173
|
-
const cases = Array.isArray(data?.cases) ? data.cases : strCases
|
|
174
|
-
const summary = data?.summary ?? { total: 0, blocks: 0, reviews: 0, approves: 0, blockRate: 0, reviewRate: 0, approveRate: 0 }
|
|
175
|
-
const rows = cases.map((c) =>
|
|
176
|
-
`<tr><td>${c.id}</td><td>${c.txnId}</td><td>${c.decision}</td><td>${c.assignedTo}</td><td>${c.status}</td><td>${new Date(c.deadline).toLocaleDateString()}</td></tr>`
|
|
177
|
-
).join('')
|
|
178
|
-
return `<div class="fraudops-dashboard"><h3>Fraud Operations Dashboard</h3><p>Decisions: ${summary.total} | Block: ${summary.blockRate}% | Review: ${summary.reviewRate}% | Approve: ${summary.approveRate}%</p><p>STR Cases: ${cases.length} | Overdue: ${cases.filter((c) => Date.now() > c.deadline).length}</p><table><thead><tr><th>ID</th><th>Txn ID</th><th>Decision</th><th>Assigned</th><th>Status</th><th>Deadline</th></tr></thead><tbody>${rows}</tbody></table></div>`
|
|
179
|
-
},
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
log('[fraudops] registered: 4 tools, 2 triggers, 1 panel')
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export default { register, buildPipelineHealthCommand, buildNatsStatusCommand, buildKafkaLagCommand, parsePipelineHealth, buildStrCase, buildDecisionSummary }
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "fraudops",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "FraudOps for RTerm — operational layer for the fraud detection pipeline. Monitors pipeline health (Flink, NATS, Kafka), manages STR workflow, tracks fraud incidents, and provides the unified fraud operations dashboard. Bridges Kafka/NATS metrics into RTerm's SRE pillar.",
|
|
5
|
-
"entry": "index.mjs",
|
|
6
|
-
"tools": ["fraudops_pipeline_status", "fraudops_str_assign", "fraudops_str_status", "fraudops_decision_summary"],
|
|
7
|
-
"triggers": ["fraudops_str_overdue", "fraudops_pipeline_down"],
|
|
8
|
-
"panels": ["fraudops-dashboard"],
|
|
9
|
-
"permissions": ["exec", "readLedger:metrics", "readLedger:incidents"]
|
|
10
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// iam-connector plugin type declarations
|
|
2
|
-
export function register(ctx: any): void
|
|
3
|
-
export function buildUserInfoCommand(username: string, os?: string): string
|
|
4
|
-
export function buildUserGroupsCommand(username: string, os?: string): string
|
|
5
|
-
export function buildDisableUserCommand(username: string, os?: string): string
|
|
6
|
-
export function buildAccessReviewCommand(os?: string): string
|
|
7
|
-
export function parseUserInfo(output: string | null | undefined, os?: string | null | undefined): { username: string; groups: string[]; enabled: boolean; locked: boolean }
|
|
8
|
-
export function parseAccessReview(output: string | null | undefined, os?: string | null | undefined): Array<{ username: string; groups?: string[]; enabled?: boolean }>
|
|
9
|
-
export function isPrivileged(userInfo: { groups: string[] } | null | undefined, privilegedGroups?: string[] | null | undefined): boolean
|
|
10
|
-
export default any
|