yaml-flow 8.1.1 → 8.2.1
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/browser/asset-integrity.json +3 -3
- package/browser/board-livecards-client.js +1 -1
- package/browser/board-livecards-localstorage.js +4 -6
- package/cli/{board-live-cards-lib-tjYsPt5U.d.ts → board-live-cards-lib-Iq_XAC09.d.ts} +1 -1
- package/cli/browser-api/board-live-cards-browser-adapter.d.ts +4 -3
- package/cli/browser-api/board-live-cards-browser-adapter.js +2 -2
- package/cli/browser-api/card-store-browser-api.d.ts +1 -1
- package/cli/node/artifacts-store-cli.js +8 -8
- package/cli/node/board-live-cards-cli.js +8 -8
- package/cli/node/card-store-cli.js +4 -4
- package/cli/node/fs-board-adapter.d.ts +6 -33
- package/cli/node/fs-board-adapter.js +10 -8
- package/cli/node/step-machine-cli.js +3 -3
- package/cli/{types-D2XnLbBj.d.ts → types--rXGWbSR.d.ts} +77 -5
- package/examples/board/.board-ws/cards/store/_index.json +17 -0
- package/examples/board/.board-ws/cards/store/card-market-prices.json +80 -0
- package/examples/board/.board-ws/cards/store/card-portfolio-value.json +90 -0
- package/examples/board/.board-ws/cards/store/card-portfolio.json +78 -0
- package/examples/board/cards/cardT-market-prices.json +6 -4
- package/examples/board/cards/cardT-portfolio-value.json +10 -38
- package/examples/board/cards/cardT-portfolio.json +9 -4
- package/examples/board/demo-shell-with-server.html +3 -3
- package/examples/board/server/board-server.js +593 -0
- package/examples/board/server/board-worker/source-def-flows/mock-handler/mock-db.js +13 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/.retain/compliance.db +0 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/.retain/optimus.db +0 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/query.cjs +51 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-cpm.cjs +197 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-cpmV2.cjs +128 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-optimus.cjs +352 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/sqlite-config.json +3 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/sqlite-handler.js +84 -0
- package/examples/board/{source-def-flows/url.flow.json → server/board-worker/source-def-flows/sqlite.flow.json} +7 -7
- package/examples/board/{source-def-handlers → server/board-worker/source-def-flows/url-handler}/http-source-handler.js +29 -21
- package/examples/board/server/board-worker/source-def-flows/url.flow.json +73 -0
- package/examples/board/{source_def_flows.json → server/board-worker/source_def_flows.json} +61 -115
- package/examples/board/server/board-worker/task-executor.js +475 -0
- package/examples/board/server/chat-flow/chat-clear-processing.js +41 -0
- package/examples/board/server/chat-flow/chat-open-turn.js +144 -0
- package/examples/board/server/chat-flow/chat-write-assistant.js +44 -0
- package/examples/board/server/chat-flow/copilot-chat/assistant.js +253 -0
- package/examples/board/server/chat-flow/echo-probe/assistant.js +28 -0
- package/examples/board/server/chat-flow/flow-steps.json +167 -0
- package/examples/board/server-config.json +22 -0
- package/examples/board/test/server-http-test.js +707 -0
- package/examples/board/test/{portfolio-tracker-sse-worker.js → sse-worker.js} +9 -8
- package/examples/board-local/demo-shell-localstorage.html +3 -3
- package/lib/{artifacts-store-lib-public-DBICnGL6.d.cts → artifacts-store-lib-public-C5UL5tyG.d.cts} +3 -31
- package/lib/{artifacts-store-lib-public-BWC3YuLa.d.ts → artifacts-store-lib-public-GD4H-fFp.d.ts} +3 -31
- package/lib/artifacts-store-public.d.cts +3 -3
- package/lib/artifacts-store-public.d.ts +3 -3
- package/lib/board-live-cards-node.cjs +10 -8
- package/lib/board-live-cards-node.d.cts +9 -8
- package/lib/board-live-cards-node.d.ts +9 -8
- package/lib/board-live-cards-node.js +10 -8
- package/lib/{board-live-cards-public-BF9FP0mL.d.cts → board-live-cards-public-BLXbcBNk.d.cts} +2 -2
- package/lib/{board-live-cards-public-dJAl5IL-.d.ts → board-live-cards-public-BZaNb2mi.d.ts} +2 -2
- package/lib/board-live-cards-public.cjs +2 -2
- package/lib/board-live-cards-public.d.cts +2 -2
- package/lib/board-live-cards-public.d.ts +2 -2
- package/lib/board-live-cards-public.js +2 -2
- package/lib/board-live-cards-server-runtime.cjs +4 -6
- package/lib/board-live-cards-server-runtime.d.cts +3 -3
- package/lib/board-live-cards-server-runtime.d.ts +3 -3
- package/lib/board-live-cards-server-runtime.js +4 -6
- package/lib/board-livegraph-runtime/index.cjs +2 -2
- package/lib/board-livegraph-runtime/index.js +2 -2
- package/lib/card-store-public.d.cts +2 -2
- package/lib/card-store-public.d.ts +2 -2
- package/lib/execution-refs.cjs +1 -1
- package/lib/execution-refs.js +1 -1
- package/lib/index.cjs +1 -1
- package/lib/index.d.cts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/server-runtime/index.cjs +4 -6
- package/lib/server-runtime/index.d.cts +4 -4
- package/lib/server-runtime/index.d.ts +4 -4
- package/lib/server-runtime/index.js +4 -6
- package/lib/step-machine-public/index.cjs +3 -3
- package/lib/step-machine-public/index.d.cts +27 -10
- package/lib/step-machine-public/index.d.ts +27 -10
- package/lib/step-machine-public/index.js +3 -3
- package/lib/{storage-interface-BhAON-gW.d.ts → storage-interface-B6ecOulj.d.cts} +25 -3
- package/lib/{storage-interface-BhAON-gW.d.cts → storage-interface-B6ecOulj.d.ts} +25 -3
- package/lib/stores/index.d.cts +1 -1
- package/lib/stores/index.d.ts +1 -1
- package/lib/stores/kv.d.cts +1 -1
- package/lib/stores/kv.d.ts +1 -1
- package/lib/{types-CXBzvC0s.d.cts → types-Bztd1KoK.d.cts} +75 -3
- package/lib/{types-D48hpnTR.d.ts → types-D-xVWPdY.d.ts} +75 -3
- package/package.json +1 -1
- package/examples/board/demo-chat-handler.js +0 -169
- package/examples/board/demo-server-config.json +0 -10
- package/examples/board/demo-server.js +0 -580
- package/examples/board/demo-task-executor.js +0 -721
- package/examples/board/gandalf-cards/card-source-kinds.json +0 -36
- package/examples/board/gandalf-cards/cards/_index.json +0 -7
- package/examples/board/gandalf-cards/cards/card-source-kinds.json +0 -64
- package/examples/board/scripts/copilot_wrapper.bat +0 -157
- package/examples/board/scripts/copilot_wrapper_helper.ps1 +0 -190
- package/examples/board/scripts/workiq_wrapper.mjs +0 -66
- package/examples/board/source-def-flows/copilot.flow.json +0 -33
- package/examples/board/source-def-flows/url-list.flow.json +0 -33
- package/examples/board/source-def-flows/workiq.flow.json +0 -34
- package/examples/board/source-def-handlers/copilot-source-handler.js +0 -141
- package/examples/board/test/demo-http-test.js +0 -317
- /package/examples/board/{source-def-flows → server/board-worker/source-def-flows}/mock.flow.json +0 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* seed-optimus.cjs — Create and seed the OPTIMUS threat hunting database.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node seed-optimus.cjs [--db <path>]
|
|
8
|
+
*
|
|
9
|
+
* Default db path: sqlite-handler/.retain/optimus.db
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const Database = require('better-sqlite3');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const dbArgIdx = args.indexOf('--db');
|
|
18
|
+
const dbPath = dbArgIdx !== -1 && args[dbArgIdx + 1]
|
|
19
|
+
? path.resolve(args[dbArgIdx + 1])
|
|
20
|
+
: path.resolve(__dirname, '.retain', 'optimus.db');
|
|
21
|
+
|
|
22
|
+
const dbDir = path.dirname(dbPath);
|
|
23
|
+
if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
|
|
24
|
+
if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
|
|
25
|
+
|
|
26
|
+
const db = new Database(dbPath);
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Schema
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
db.exec(`
|
|
32
|
+
CREATE TABLE attack_planes (
|
|
33
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
34
|
+
plane TEXT NOT NULL UNIQUE,
|
|
35
|
+
data_source TEXT NOT NULL,
|
|
36
|
+
signal_type TEXT,
|
|
37
|
+
t_weight REAL DEFAULT 0.0,
|
|
38
|
+
description TEXT
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE tapc_config (
|
|
42
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
43
|
+
component TEXT NOT NULL CHECK(component IN ('T','A','P','C','negative')),
|
|
44
|
+
signal TEXT NOT NULL,
|
|
45
|
+
weight REAL NOT NULL,
|
|
46
|
+
description TEXT
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE agents (
|
|
50
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
51
|
+
name TEXT NOT NULL UNIQUE,
|
|
52
|
+
role TEXT NOT NULL,
|
|
53
|
+
model TEXT,
|
|
54
|
+
tool_count INTEGER DEFAULT 0,
|
|
55
|
+
status TEXT DEFAULT 'idle' CHECK(status IN ('idle','scanning','graphing','validating','critiquing','complete','error')),
|
|
56
|
+
last_run TEXT,
|
|
57
|
+
description TEXT
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE TABLE scan_candidates (
|
|
61
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
62
|
+
entity TEXT NOT NULL,
|
|
63
|
+
org_id TEXT NOT NULL,
|
|
64
|
+
plane TEXT NOT NULL,
|
|
65
|
+
pre_graph_ta REAL DEFAULT 0.0,
|
|
66
|
+
t_score REAL DEFAULT 0.0,
|
|
67
|
+
a_score REAL DEFAULT 0.0,
|
|
68
|
+
p_score REAL,
|
|
69
|
+
c_score REAL,
|
|
70
|
+
tapc_final REAL,
|
|
71
|
+
promoted INTEGER DEFAULT 0,
|
|
72
|
+
run_date TEXT,
|
|
73
|
+
details TEXT,
|
|
74
|
+
FOREIGN KEY (plane) REFERENCES attack_planes(plane)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
CREATE TABLE findings (
|
|
78
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
79
|
+
entity TEXT NOT NULL,
|
|
80
|
+
org_id TEXT NOT NULL,
|
|
81
|
+
tapc_score REAL NOT NULL,
|
|
82
|
+
admiralty_code TEXT,
|
|
83
|
+
mitre_technique TEXT,
|
|
84
|
+
kill_chain_stage TEXT,
|
|
85
|
+
status TEXT DEFAULT 'validated' CHECK(status IN ('validated','downgraded','fp_killed','escalated')),
|
|
86
|
+
summary TEXT,
|
|
87
|
+
run_date TEXT
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CREATE TABLE fp_patterns (
|
|
91
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
92
|
+
pattern_name TEXT NOT NULL,
|
|
93
|
+
plane TEXT,
|
|
94
|
+
suppression_rule TEXT,
|
|
95
|
+
hits INTEGER DEFAULT 0,
|
|
96
|
+
added_date TEXT,
|
|
97
|
+
description TEXT
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE TABLE scan_history (
|
|
101
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
102
|
+
run_date TEXT NOT NULL,
|
|
103
|
+
plane TEXT NOT NULL,
|
|
104
|
+
candidates_found INTEGER DEFAULT 0,
|
|
105
|
+
promoted INTEGER DEFAULT 0,
|
|
106
|
+
findings_validated INTEGER DEFAULT 0,
|
|
107
|
+
fp_killed INTEGER DEFAULT 0,
|
|
108
|
+
FOREIGN KEY (plane) REFERENCES attack_planes(plane)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
CREATE TABLE mcp_tools (
|
|
112
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
113
|
+
server TEXT NOT NULL,
|
|
114
|
+
tool_name TEXT NOT NULL,
|
|
115
|
+
agent TEXT,
|
|
116
|
+
category TEXT,
|
|
117
|
+
description TEXT
|
|
118
|
+
);
|
|
119
|
+
`);
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Seed: Attack Planes
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
const insertPlane = db.prepare(`
|
|
125
|
+
INSERT INTO attack_planes (plane, data_source, signal_type, t_weight, description)
|
|
126
|
+
VALUES (?, ?, ?, ?, ?)
|
|
127
|
+
`);
|
|
128
|
+
|
|
129
|
+
const planes = [
|
|
130
|
+
['Identity Auth', 'IdentityLogonEvents', 'Legacy auth protocols', 0.90, 'Authentication events — legacy auth, MFA bypass, protocol abuse'],
|
|
131
|
+
['Identity Control', 'CloudAppEvents', 'Role/permission changes', 0.75, 'Administrative actions — role assignments, conditional access changes, app registrations'],
|
|
132
|
+
['Identity Recon', 'IdentityQueryEvents', 'LDAP/SAM-R enumeration', 0.65, 'Reconnaissance — directory queries, group enumeration, user discovery'],
|
|
133
|
+
['Cloud / SaaS', 'CloudAppEvents', 'Inbox rules, OAuth grants', 0.85, 'Cloud post-compromise — inbox rule creation, OAuth consent, data exfiltration'],
|
|
134
|
+
['Endpoint', 'MtpAlertEvidence', 'Process/file anomalies', 0.70, 'Endpoint indicators — suspicious processes, file drops, persistence mechanisms'],
|
|
135
|
+
['Network', 'IdentityLogonEvents', 'Geo-impossible travel, VPN', 0.60, 'Network-layer signals — impossible travel, known bad IPs, TOR exit nodes'],
|
|
136
|
+
['Detection Gaps', 'MtpAlerts (inverse)', 'Absence-of-alerts signal', 0.80, 'Inverse detection — accounts with telemetry anomalies but ZERO existing alerts'],
|
|
137
|
+
];
|
|
138
|
+
for (const p of planes) insertPlane.run(...p);
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Seed: TAPC Configuration
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
const insertTapc = db.prepare(`
|
|
144
|
+
INSERT INTO tapc_config (component, signal, weight, description)
|
|
145
|
+
VALUES (?, ?, ?, ?)
|
|
146
|
+
`);
|
|
147
|
+
|
|
148
|
+
const tapcConfig = [
|
|
149
|
+
// T — Threat Likeness (weight 0.4)
|
|
150
|
+
['T', 'autologon', 0.95, 'Autologon protocol — highest exploitation risk'],
|
|
151
|
+
['T', 'NTLMv1', 0.90, 'NTLMv1 authentication — trivially crackable'],
|
|
152
|
+
['T', 'WSTrust', 0.85, 'WS-Trust mixed endpoint — federation abuse vector'],
|
|
153
|
+
['T', 'deviceCode', 0.80, 'Device code flow — phishing-friendly OAuth grant'],
|
|
154
|
+
['T', 'WeakKerb', 0.75, 'Weak Kerberos encryption (RC4/DES)'],
|
|
155
|
+
['T', 'LegacyMail', 0.70, 'Legacy mail protocols (POP3/IMAP/SMTP AUTH)'],
|
|
156
|
+
['T', 'LDAPclear', 0.65, 'LDAP cleartext bind — credential exposure'],
|
|
157
|
+
['T', 'ADFS', 0.55, 'ADFS authentication — moderate federation risk'],
|
|
158
|
+
// A — Anomaly (weight 0.2)
|
|
159
|
+
['A', 'volume_spike', 0.80, 'Volume exceeds 3-sigma above 30-day baseline'],
|
|
160
|
+
['A', 'new_ip', 0.70, 'IP address never seen for this entity in 30 days'],
|
|
161
|
+
['A', 'geo_impossible', 0.90, 'Geographically impossible travel between auth events'],
|
|
162
|
+
['A', 'off_hours', 0.50, 'Activity outside normal working hours pattern'],
|
|
163
|
+
['A', 'spray_structure', 0.85, 'Password spray: high fail + many users + legacy proto'],
|
|
164
|
+
['A', 'cross_plane', 0.15, 'Bonus: entity appears in >=2 attack planes'],
|
|
165
|
+
// P — Progression (weight 0.3)
|
|
166
|
+
['P', 'credential', 0.10, 'P1: Initial credential access attempt'],
|
|
167
|
+
['P', 'token_exchange', 0.15, 'P2: Token exchange / elevation'],
|
|
168
|
+
['P', 'resource_access', 0.25, 'P3: Resource access (mail, files, APIs)'],
|
|
169
|
+
['P', 'persistence', 0.30, 'P4: Persistence mechanisms (inbox rules, app consent)'],
|
|
170
|
+
['P', 'lateral_movement',0.20, 'P5: Lateral movement to other accounts/systems'],
|
|
171
|
+
// C — Context (weight 0.1)
|
|
172
|
+
['C', 'admin_privilege', 1.50, 'Admin account multiplier (1.5x)'],
|
|
173
|
+
['C', 'sso_misconfigured',0.70, 'SSO misconfiguration detected in tenant'],
|
|
174
|
+
['C', 'prior_alerts', 0.60, 'Entity has existing (potentially unrelated) alerts'],
|
|
175
|
+
// Negative evidence
|
|
176
|
+
['negative', 'internal_inbox', -0.40, 'Forwarding to internal inbox only — benign pattern'],
|
|
177
|
+
['negative', 'managed_device', -0.20, 'Auth from corporate managed device'],
|
|
178
|
+
['negative', 'peer_prevalence', -0.30, 'IP seen across many peer accounts (shared infra)'],
|
|
179
|
+
['negative', 'svc_account', -0.20, 'Service account with expected legacy auth pattern'],
|
|
180
|
+
['negative', 'ip_100orgs', -0.30, 'IP appears in 100+ orgs — shared infrastructure'],
|
|
181
|
+
];
|
|
182
|
+
for (const t of tapcConfig) insertTapc.run(...t);
|
|
183
|
+
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Seed: Agents
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
const insertAgent = db.prepare(`
|
|
188
|
+
INSERT INTO agents (name, role, model, tool_count, status, last_run, description)
|
|
189
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
190
|
+
`);
|
|
191
|
+
|
|
192
|
+
const agentData = [
|
|
193
|
+
['BEACON', 'TAPC Observable Scanner', 'Claude Opus 4.6', 10, 'complete', '2026-04-27', 'Sweeps all 7 attack planes with TAPC T-signal scoring. Generates anomaly candidates and promotes findings >= 0.35 PreGraphTA.'],
|
|
194
|
+
['WEAVER', 'Graph Pattern Operator', 'Claude Opus 4.6', 8, 'complete', '2026-04-27', 'Builds observable graphs from promoted candidates. Detects temporal attack motifs and cross-tenant campaign infrastructure.'],
|
|
195
|
+
['CRUCIBLE', 'Stress-Test Validator', 'Claude Opus 4.6', 5, 'complete', '2026-04-27', 'Assumes every finding is FP. Applies 7-check validation framework. Generates Admiralty/NATO confidence codes.'],
|
|
196
|
+
['RUBBER DUCK', 'Independent Critique Agent', 'Claude Sonnet 4.6', 0, 'complete', '2026-04-27', 'Provides adversarial feedback. Catches overclaims, logic errors, FP blind spots. Separate model for independent reasoning.'],
|
|
197
|
+
];
|
|
198
|
+
for (const a of agentData) insertAgent.run(...a);
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Seed: Scan Candidates (Cycle 3: 2026-04-27 — latest)
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
const insertCandidate = db.prepare(`
|
|
204
|
+
INSERT INTO scan_candidates (entity, org_id, plane, pre_graph_ta, t_score, a_score, p_score, c_score, tapc_final, promoted, run_date, details)
|
|
205
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
206
|
+
`);
|
|
207
|
+
|
|
208
|
+
const c3 = '2026-04-27';
|
|
209
|
+
const candidates = [
|
|
210
|
+
// Promoted candidates (PreGraphTA >= 0.35)
|
|
211
|
+
['UserPII_a7f3e2d1', 'OrgPII_contoso', 'Identity Auth', 0.62, 0.90, 0.50, 0.25, 0.70, 0.68, 1, c3, 'WS-Trust mixed auth from new IP, volume 4.2-sigma above baseline, off-hours'],
|
|
212
|
+
['UserPII_b8c4f901', 'OrgPII_fabrikam', 'Identity Auth', 0.54, 0.85, 0.40, 0.15, 0.60, 0.57, 1, c3, 'Device code flow from residential IP, no MFA challenge, first-time protocol use'],
|
|
213
|
+
['UserPII_c9d5e012', 'OrgPII_contoso', 'Cloud / SaaS', 0.48, 0.85, 0.30, 0.30, 0.80, 0.62, 1, c3, 'New-InboxRule forwarding to external domain 2h after legacy auth'],
|
|
214
|
+
['UserPII_a7f3e2d1', 'OrgPII_contoso', 'Cloud / SaaS', 0.44, 0.70, 0.35, 0.25, 0.70, 0.55, 1, c3, 'OAuth consent grant for unknown app post-WSTrust auth (cross-plane)'],
|
|
215
|
+
['UserPII_d1e6f123', 'OrgPII_woodgrove', 'Identity Auth', 0.42, 0.75, 0.30, 0.10, 0.50, 0.47, 1, c3, 'Legacy SMTP AUTH from IP seen in only this org, 3.1-sigma spike'],
|
|
216
|
+
['UserPII_e2f7a234', 'OrgPII_contoso', 'Detection Gaps', 0.40, 0.80, 0.20, null, null, null, 1, c3, 'Admin account with 47 identity events, ZERO existing alerts'],
|
|
217
|
+
['UserPII_f3a8b345', 'OrgPII_fabrikam', 'Identity Recon', 0.38, 0.65, 0.30, null, null, null, 1, c3, 'LDAP enumeration of all global admin group members'],
|
|
218
|
+
['UserPII_a4b9c456', 'OrgPII_woodgrove', 'Endpoint', 0.36, 0.70, 0.20, null, null, null, 1, c3, 'Suspicious PowerShell execution post-auth from flagged IP'],
|
|
219
|
+
// Not promoted (below threshold)
|
|
220
|
+
['UserPII_x1y2z301', 'OrgPII_contoso', 'Network', 0.28, 0.60, 0.10, null, null, null, 0, c3, 'VPN from new geo — but managed device, peer prevalence high'],
|
|
221
|
+
['UserPII_x2y3z402', 'OrgPII_fabrikam', 'Identity Auth', 0.24, 0.55, 0.05, null, null, null, 0, c3, 'ADFS auth from known corporate range — benign service account'],
|
|
222
|
+
['UserPII_x3y4z503', 'OrgPII_contoso', 'Identity Control', 0.22, 0.40, 0.15, null, null, null, 0, c3, 'Role assignment — but part of scheduled rotation'],
|
|
223
|
+
['UserPII_x4y5z604', 'OrgPII_woodgrove', 'Cloud / SaaS', 0.20, 0.35, 0.12, null, null, null, 0, c3, 'Inbox rule but internal-only forwarding (same domain)'],
|
|
224
|
+
['UserPII_x5y6z705', 'OrgPII_fabrikam', 'Identity Auth', 0.18, 0.45, 0.00, null, null, null, 0, c3, 'Legacy auth but high peer prevalence — shared VPN gateway'],
|
|
225
|
+
['UserPII_x6y7z806', 'OrgPII_contoso', 'Network', 0.14, 0.30, 0.05, null, null, null, 0, c3, 'Off-hours access but from home IP consistently used'],
|
|
226
|
+
['UserPII_x7y8z907', 'OrgPII_woodgrove', 'Identity Recon', 0.12, 0.25, 0.05, null, null, null, 0, c3, 'Single LDAP query — normal admin behavior'],
|
|
227
|
+
];
|
|
228
|
+
for (const c of candidates) insertCandidate.run(...c);
|
|
229
|
+
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Seed: Validated Findings (Cycle 3)
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
const insertFinding = db.prepare(`
|
|
234
|
+
INSERT INTO findings (entity, org_id, tapc_score, admiralty_code, mitre_technique, kill_chain_stage, status, summary, run_date)
|
|
235
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
236
|
+
`);
|
|
237
|
+
|
|
238
|
+
const findingsData = [
|
|
239
|
+
['UserPII_a7f3e2d1', 'OrgPII_contoso', 0.68, 'B1', 'T1078.004 + T1098.003', 'Credential → Persistence', 'validated', 'WS-Trust auth from new IP → OAuth consent grant for unknown app → 2 cross-plane signals. Admin account, no existing alerts. Kill chain: credential access via federation abuse followed by persistence through malicious OAuth app.', c3],
|
|
240
|
+
['UserPII_b8c4f901', 'OrgPII_fabrikam', 0.57, 'B2', 'T1528', 'Credential → Token Theft', 'validated', 'Device code phishing pattern: residential IP, no prior auth history, token obtained without MFA. Corroborated across IdentityLogonEvents + CloudAppEvents. Same IP seen in 2 other DEX orgs.', c3],
|
|
241
|
+
['UserPII_c9d5e012', 'OrgPII_contoso', 0.62, 'B1', 'T1114.003 + T1078', 'Access → Exfil Staging', 'validated', 'Inbox rule forwarding to external domain created 2h after legacy SMTP auth. External domain registered 3 days prior. Classic BEC post-compromise pattern.', c3],
|
|
242
|
+
['UserPII_d1e6f123', 'OrgPII_woodgrove', 0.47, 'B2', 'T1110.003', 'Credential Spray', 'downgraded', 'Legacy SMTP spike corroborated but limited to single protocol. No post-compromise activity detected in 24h window. Downgraded from B1 to B2 by RUBBER DUCK — insufficient progression evidence.', c3],
|
|
243
|
+
['UserPII_e2f7a234', 'OrgPII_contoso', 0.40, 'B2', 'T1078 (suspected)', 'Detection Gap — No Actions', 'validated', 'Admin with 47 identity events and zero alerts. High T-signal (legacy auth protocols) but no confirmed post-compromise activity. Flagged as detection gap for monitoring — existing detection coverage insufficient.', c3],
|
|
244
|
+
];
|
|
245
|
+
for (const f of findingsData) insertFinding.run(...f);
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Seed: FP Pattern Library
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
const insertFP = db.prepare(`
|
|
251
|
+
INSERT INTO fp_patterns (pattern_name, plane, suppression_rule, hits, added_date, description)
|
|
252
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
253
|
+
`);
|
|
254
|
+
|
|
255
|
+
const fpPatterns = [
|
|
256
|
+
['same_domain_forward', 'Cloud / SaaS', 'inbox_rule.forward_to LIKE same_org_domain', 234, '2026-04-20', 'Inbox rule forwarding within same organization domain — benign delegation pattern'],
|
|
257
|
+
['managed_device_legacy', 'Identity Auth', 'device.compliant = true AND auth.legacy = true', 189, '2026-04-15', 'Legacy auth from managed device — often VPN client or legacy thick client'],
|
|
258
|
+
['shared_vpn_gateway', 'Identity Auth', 'ip.org_count >= 50', 156, '2026-04-15', 'IP used by 50+ orgs — shared VPN/proxy infrastructure'],
|
|
259
|
+
['svc_account_pattern', 'Identity Auth', 'account.type = service AND auth.pattern = recurring', 98, '2026-04-18', 'Service account with predictable legacy auth pattern'],
|
|
260
|
+
['admin_role_rotation', 'Identity Control', 'role_change.scheduled = true', 67, '2026-04-20', 'Planned admin role rotation per IT change management calendar'],
|
|
261
|
+
['geo_vpn_expected', 'Network', 'ip.geo_distance > 1000km AND ip.is_corp_vpn = true', 45, '2026-04-22', 'Impossible travel from known corporate VPN exit nodes'],
|
|
262
|
+
['monitoring_ldap', 'Identity Recon', 'query.source = monitoring_tool AND query.pattern = periodic', 34, '2026-04-25', 'LDAP queries from known monitoring tools (periodic health checks)'],
|
|
263
|
+
['stale_token_refresh', 'Cloud / SaaS', 'token.type = refresh AND token.age > 30d', 12, '2026-04-27', 'Stale refresh token usage — usually automated app re-auth, not attacker'],
|
|
264
|
+
];
|
|
265
|
+
for (const f of fpPatterns) insertFP.run(...f);
|
|
266
|
+
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// Seed: Scan History (3 cycles)
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
const insertHistory = db.prepare(`
|
|
271
|
+
INSERT INTO scan_history (run_date, plane, candidates_found, promoted, findings_validated, fp_killed)
|
|
272
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
273
|
+
`);
|
|
274
|
+
|
|
275
|
+
const historyData = [
|
|
276
|
+
// Cycle 1: 2026-04-13 — baseline, quiet period
|
|
277
|
+
['2026-04-13', 'Identity Auth', 8, 2, 0, 1],
|
|
278
|
+
['2026-04-13', 'Identity Control', 3, 0, 0, 0],
|
|
279
|
+
['2026-04-13', 'Identity Recon', 2, 0, 0, 0],
|
|
280
|
+
['2026-04-13', 'Cloud / SaaS', 5, 1, 0, 1],
|
|
281
|
+
['2026-04-13', 'Endpoint', 1, 0, 0, 0],
|
|
282
|
+
['2026-04-13', 'Network', 4, 1, 0, 0],
|
|
283
|
+
['2026-04-13', 'Detection Gaps', 2, 1, 0, 0],
|
|
284
|
+
// Cycle 2: 2026-04-20 — first anomalies, FP discovery
|
|
285
|
+
['2026-04-20', 'Identity Auth', 14, 5, 1, 2],
|
|
286
|
+
['2026-04-20', 'Identity Control', 4, 1, 0, 1],
|
|
287
|
+
['2026-04-20', 'Identity Recon', 6, 2, 0, 1],
|
|
288
|
+
['2026-04-20', 'Cloud / SaaS', 9, 3, 1, 1],
|
|
289
|
+
['2026-04-20', 'Endpoint', 3, 1, 0, 0],
|
|
290
|
+
['2026-04-20', 'Network', 5, 1, 0, 1],
|
|
291
|
+
['2026-04-20', 'Detection Gaps', 4, 2, 1, 0],
|
|
292
|
+
// Cycle 3: 2026-04-27 — campaign detected, multi-tenant
|
|
293
|
+
['2026-04-27', 'Identity Auth', 18, 8, 2, 3],
|
|
294
|
+
['2026-04-27', 'Identity Control', 5, 1, 0, 1],
|
|
295
|
+
['2026-04-27', 'Identity Recon', 7, 3, 0, 1],
|
|
296
|
+
['2026-04-27', 'Cloud / SaaS', 12, 4, 2, 2],
|
|
297
|
+
['2026-04-27', 'Endpoint', 4, 2, 0, 0],
|
|
298
|
+
['2026-04-27', 'Network', 6, 2, 0, 1],
|
|
299
|
+
['2026-04-27', 'Detection Gaps', 5, 3, 1, 0],
|
|
300
|
+
];
|
|
301
|
+
for (const h of historyData) insertHistory.run(...h);
|
|
302
|
+
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
// Seed: MCP Tools (representative subset)
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
306
|
+
const insertTool = db.prepare(`
|
|
307
|
+
INSERT INTO mcp_tools (server, tool_name, agent, category, description)
|
|
308
|
+
VALUES (?, ?, ?, ?, ?)
|
|
309
|
+
`);
|
|
310
|
+
|
|
311
|
+
const toolsData = [
|
|
312
|
+
['optimus', 'beacon_scan_all_planes', 'BEACON', 'scan', 'Parallel KQL sweep across all 7 attack planes'],
|
|
313
|
+
['optimus', 'beacon_scan_plane', 'BEACON', 'scan', 'Targeted single-plane anomaly scan'],
|
|
314
|
+
['optimus', 'beacon_fuse_results', 'BEACON', 'fusion', 'Cross-plane entity fusion and promotion'],
|
|
315
|
+
['optimus', 'beacon_hunt_password_spray', 'BEACON', 'hunt', 'Structural spray detection (post-promotion only)'],
|
|
316
|
+
['optimus', 'beacon_status', 'BEACON', 'status', 'Current BEACON agent state'],
|
|
317
|
+
['optimus', 'weaver_build_graph', 'WEAVER', 'graph', 'Kusto make-graph: user→IP→action edges'],
|
|
318
|
+
['optimus', 'weaver_find_motifs', 'WEAVER', 'graph', 'Temporal attack motif detection'],
|
|
319
|
+
['optimus', 'weaver_find_campaigns', 'WEAVER', 'graph', 'Cross-tenant campaign infrastructure (>=3 orgs)'],
|
|
320
|
+
['optimus', 'weaver_cross_plane', 'WEAVER', 'graph', 'Identity→Cloud plane traversal detection'],
|
|
321
|
+
['optimus', 'weaver_blast_radius', 'WEAVER', 'graph', 'Compromised account blast radius assessment'],
|
|
322
|
+
['optimus', 'weaver_enrich_pc', 'WEAVER', 'enrich', 'P/C score enrichment from graph context'],
|
|
323
|
+
['optimus', 'crucible_validate', 'CRUCIBLE', 'validate', '7-check validation framework'],
|
|
324
|
+
['optimus', 'crucible_investigate_deeper', 'CRUCIBLE', 'validate', 'Deep investigation KQL generation'],
|
|
325
|
+
['optimus', 'crucible_kill_fp', 'CRUCIBLE', 'validate', 'Add FP to negative evidence library'],
|
|
326
|
+
['optimus', 'optimus_run_cycle', 'MASTER', 'pipeline', 'Full TAPC-first pipeline: BEACON→WEAVER→CRUCIBLE'],
|
|
327
|
+
['optimus', 'optimus_get_tapc_config', 'MASTER', 'config', 'Full TAPC scoring configuration'],
|
|
328
|
+
['optimus', 'optimus_get_fp_patterns', 'MASTER', 'config', 'All known false positive patterns'],
|
|
329
|
+
['optimus', 'optimus_remember', 'MASTER', 'memory', 'Persist finding/pattern to knowledge store'],
|
|
330
|
+
['optimus', 'optimus_recall', 'MASTER', 'memory', 'Search persistent knowledge'],
|
|
331
|
+
['attack_intersectionality', 'lookup_technique', null, 'mitre', 'MITRE technique lookup by ID or name'],
|
|
332
|
+
['attack_intersectionality', 'analyze_intersection', null, 'mitre', 'Pairwise technique intersection analysis'],
|
|
333
|
+
['attack_intersectionality', 'match_attack_chain', null, 'mitre', 'Match observed techniques to known attack chains'],
|
|
334
|
+
['attack_intersectionality', 'tapc_score', null, 'tapc', 'TAPC model explanation and formula'],
|
|
335
|
+
['attack_intersectionality', 'validate_finding', null, 'validate', '7-check self-critic validation engine'],
|
|
336
|
+
['attack_intersectionality', 'investigate', null, 'pivot', 'Investigation pivot (IP/user/org)'],
|
|
337
|
+
['attack_intersectionality', 'scrub', null, 'ghost', 'SHA-1 PII hashing'],
|
|
338
|
+
['attack_intersectionality', 'unscrub', null, 'ghost', 'Rainbow table PII reversal'],
|
|
339
|
+
['kusto', 'kusto_query', null, 'kql', 'Execute KQL query against ADX cluster'],
|
|
340
|
+
];
|
|
341
|
+
for (const t of toolsData) insertTool.run(...t);
|
|
342
|
+
|
|
343
|
+
db.close();
|
|
344
|
+
console.log(`[seed-optimus] Database seeded: ${dbPath}`);
|
|
345
|
+
console.log(` attack_planes: ${planes.length}`);
|
|
346
|
+
console.log(` tapc_config: ${tapcConfig.length}`);
|
|
347
|
+
console.log(` agents: ${agentData.length}`);
|
|
348
|
+
console.log(` scan_candidates: ${candidates.length}`);
|
|
349
|
+
console.log(` findings: ${findingsData.length}`);
|
|
350
|
+
console.log(` fp_patterns: ${fpPatterns.length}`);
|
|
351
|
+
console.log(` scan_history: ${historyData.length}`);
|
|
352
|
+
console.log(` mcp_tools: ${toolsData.length}`);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* sqlite-handler.js — Query a SQLite database via the local query.cjs helper.
|
|
5
|
+
*
|
|
6
|
+
* DB filename is resolved relative to the configured defaultDbDir.
|
|
7
|
+
* Supports SELECT (returns row array) and exec mode for INSERT/UPDATE/DELETE.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { execFileSync } from 'node:child_process';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const HANDLER_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const SQLITE_CONFIG_FILE = path.join(HANDLER_DIR, 'sqlite-config.json');
|
|
17
|
+
const SQLITE_QUERY_SCRIPT = path.join(HANDLER_DIR, 'query.cjs');
|
|
18
|
+
|
|
19
|
+
function interpolate(template, args) {
|
|
20
|
+
return String(template).replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (_m, key) => {
|
|
21
|
+
const v = args?.[key];
|
|
22
|
+
if (v === undefined) return '';
|
|
23
|
+
return typeof v === 'string' ? v : JSON.stringify(v);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readJson(filePath) {
|
|
28
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loadSqliteConfig() {
|
|
32
|
+
try {
|
|
33
|
+
return readJson(SQLITE_CONFIG_FILE);
|
|
34
|
+
} catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveDbPath(dbRef, config) {
|
|
40
|
+
if (path.isAbsolute(dbRef) || dbRef.includes(path.sep) || dbRef.includes('/')) {
|
|
41
|
+
return path.resolve(dbRef);
|
|
42
|
+
}
|
|
43
|
+
const defaultDbDir = typeof config.defaultDbDir === 'string'
|
|
44
|
+
? path.resolve(HANDLER_DIR, config.defaultDbDir)
|
|
45
|
+
: path.resolve(HANDLER_DIR, '.retain');
|
|
46
|
+
return path.join(defaultDbDir, dbRef);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function execute(context) {
|
|
50
|
+
const sourceDef = context?.sourceDef || {};
|
|
51
|
+
const handlerConfig = loadSqliteConfig();
|
|
52
|
+
|
|
53
|
+
const cfg = typeof sourceDef.sqlite === 'object' ? sourceDef.sqlite : {};
|
|
54
|
+
if (!cfg.db || !cfg.query) {
|
|
55
|
+
return { result: 'failure', data: { error: 'sqlite: db and query are required' }, error: 'missing db/query' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const dbPath = resolveDbPath(cfg.db, handlerConfig);
|
|
59
|
+
const cliArgs = ['--db', dbPath, '--sql', cfg.query];
|
|
60
|
+
if (cfg.params) {
|
|
61
|
+
const resolvedParams = Array.isArray(cfg.params)
|
|
62
|
+
? cfg.params.map(p => typeof p === 'string' ? interpolate(p, sourceDef._projections || {}) : p)
|
|
63
|
+
: [];
|
|
64
|
+
cliArgs.push('--params', JSON.stringify(resolvedParams));
|
|
65
|
+
}
|
|
66
|
+
if (cfg.mode === 'exec') {
|
|
67
|
+
cliArgs.push('--mode', 'exec');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const raw = execFileSync(process.execPath, [SQLITE_QUERY_SCRIPT, ...cliArgs], {
|
|
72
|
+
encoding: 'utf-8',
|
|
73
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
74
|
+
timeout: 30_000,
|
|
75
|
+
cwd: process.cwd(),
|
|
76
|
+
windowsHide: true,
|
|
77
|
+
});
|
|
78
|
+
const resultValue = raw.trim() ? JSON.parse(raw) : [];
|
|
79
|
+
return { result: 'success', data: { resultValue } };
|
|
80
|
+
} catch (err) {
|
|
81
|
+
const msg = err.stderr ? err.stderr.trim() : (err.message || String(err));
|
|
82
|
+
return { result: 'failure', data: { error: `sqlite query failed: ${msg}` }, error: msg };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "demo-source-
|
|
2
|
+
"id": "demo-source-sqlite",
|
|
3
3
|
"settings": {
|
|
4
4
|
"start_step": "execute",
|
|
5
|
-
"max_total_steps":
|
|
6
|
-
"timeout_ms":
|
|
5
|
+
"max_total_steps": 5,
|
|
6
|
+
"timeout_ms": 30000
|
|
7
7
|
},
|
|
8
8
|
"steps": {
|
|
9
9
|
"execute": {
|
|
10
|
-
"description": "Execute
|
|
10
|
+
"description": "Execute SQLite query source def",
|
|
11
11
|
"handler": {
|
|
12
12
|
"type": "ref",
|
|
13
13
|
"howToRun": "demo-local-module",
|
|
14
|
-
"whatToRun": { "kind": "fs-path", "value": "./source-def-
|
|
14
|
+
"whatToRun": { "kind": "fs-path", "value": "./source-def-flows/sqlite-handler/sqlite-handler.js" }
|
|
15
15
|
},
|
|
16
16
|
"transitions": {
|
|
17
17
|
"success": "completed",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
},
|
|
22
22
|
"terminal_states": {
|
|
23
23
|
"completed": {
|
|
24
|
-
"description": "
|
|
24
|
+
"description": "SQLite query succeeded",
|
|
25
25
|
"return_intent": "success",
|
|
26
26
|
"return_artifacts": ["resultValue", "wroteOutputDirectly"]
|
|
27
27
|
},
|
|
28
28
|
"failed": {
|
|
29
|
-
"description": "
|
|
29
|
+
"description": "SQLite query failed",
|
|
30
30
|
"return_intent": "failure"
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -78,9 +78,9 @@ function resolveTickersArg(sourceDef, fetchArgs) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
async function executeUrl(sourceDef) {
|
|
81
|
-
const cfg = sourceDef?.
|
|
81
|
+
const cfg = sourceDef?.urls;
|
|
82
82
|
if (!cfg || typeof cfg !== 'object') {
|
|
83
|
-
throw new Error('
|
|
83
|
+
throw new Error('urls source requires object config');
|
|
84
84
|
}
|
|
85
85
|
const method = String(cfg.method || 'GET').toUpperCase();
|
|
86
86
|
const headers = cfg.headers && typeof cfg.headers === 'object' ? cfg.headers : {};
|
|
@@ -89,7 +89,7 @@ async function executeUrl(sourceDef) {
|
|
|
89
89
|
|
|
90
90
|
resolveTickersArg(sourceDef, fetchArgs);
|
|
91
91
|
if (sourceDef?.tickersFrom && !fetchArgs.tickers) {
|
|
92
|
-
throw new Error('
|
|
92
|
+
throw new Error('urls: tickersFrom resolved to empty list - skipping fetch');
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
const ctx = {
|
|
@@ -98,42 +98,50 @@ async function executeUrl(sourceDef) {
|
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
if (typeof cfg.url !== 'string' || !cfg.url) {
|
|
101
|
-
throw new Error('
|
|
101
|
+
throw new Error('urls source missing url template');
|
|
102
102
|
}
|
|
103
103
|
const url = interpolate(cfg.url, ctx);
|
|
104
104
|
return doFetchApi(url, method, headers, ttlMs);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
async function
|
|
108
|
-
const cfg = sourceDef?.
|
|
107
|
+
async function executeProjectedUrl(sourceDef, selectedUrl) {
|
|
108
|
+
const cfg = sourceDef?.urls;
|
|
109
109
|
if (!cfg || typeof cfg !== 'object') {
|
|
110
|
-
throw new Error('
|
|
110
|
+
throw new Error('urls source requires object config');
|
|
111
111
|
}
|
|
112
112
|
const method = String(cfg.method || 'GET').toUpperCase();
|
|
113
113
|
const headers = cfg.headers && typeof cfg.headers === 'object' ? cfg.headers : {};
|
|
114
114
|
const ttlMs = typeof cfg.cacheTimeout === 'number' ? cfg.cacheTimeout * 1000 : DEFAULT_CACHE_TTL_MS;
|
|
115
|
+
const fetchArgs = cfg.args && typeof cfg.args === 'object' ? { ...cfg.args } : {};
|
|
115
116
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
resolveTickersArg(sourceDef, fetchArgs);
|
|
118
|
+
|
|
119
|
+
const ctx = {
|
|
120
|
+
...(sourceDef?._projections || {}),
|
|
121
|
+
...fetchArgs,
|
|
122
|
+
};
|
|
120
123
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
results.push(await doFetchApi(String(u), method, headers, ttlMs));
|
|
124
|
+
if (typeof selectedUrl !== 'string' || !selectedUrl) {
|
|
125
|
+
throw new Error('urls projected execution requires a concrete URL string');
|
|
124
126
|
}
|
|
125
|
-
|
|
127
|
+
|
|
128
|
+
const url = interpolate(selectedUrl, ctx);
|
|
129
|
+
return doFetchApi(url, method, headers, ttlMs);
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
export async function execute(context) {
|
|
129
|
-
const kind = context?.kind;
|
|
130
|
-
const sourceDef = context?.sourceDef || {};
|
|
133
|
+
const kind = context?.kind || context?.expects_data?.kind;
|
|
134
|
+
const sourceDef = context?.sourceDef || context?.expects_data?.sourceDef || {};
|
|
131
135
|
try {
|
|
132
136
|
let resultValue;
|
|
133
|
-
if (kind === '
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
if (kind === 'urls' || sourceDef?.urls) {
|
|
138
|
+
const projectedUrl =
|
|
139
|
+
typeof context?.url === 'string' ? context.url :
|
|
140
|
+
typeof context?.item === 'string' ? context.item :
|
|
141
|
+
typeof context?.expects_data?.url === 'string' ? context.expects_data.url :
|
|
142
|
+
typeof context?.expects_data?.item === 'string' ? context.expects_data.item :
|
|
143
|
+
undefined;
|
|
144
|
+
resultValue = projectedUrl ? await executeProjectedUrl(sourceDef, projectedUrl) : await executeUrl(sourceDef);
|
|
137
145
|
} else {
|
|
138
146
|
throw new Error(`http-source-handler does not support kind: ${kind}`);
|
|
139
147
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "demo-source-urls",
|
|
3
|
+
"settings": {
|
|
4
|
+
"start_step": "prepare",
|
|
5
|
+
"max_total_steps": 10,
|
|
6
|
+
"timeout_ms": 60000
|
|
7
|
+
},
|
|
8
|
+
"steps": {
|
|
9
|
+
"prepare": {
|
|
10
|
+
"description": "Resolve URL targets",
|
|
11
|
+
"produces_data": ["target_urls", "fanout_mode", "sourceDef"],
|
|
12
|
+
"handler": {
|
|
13
|
+
"type": "compute-jsonata",
|
|
14
|
+
"expr": [
|
|
15
|
+
"data.fanout_mode = $exists(expects_data.sourceDef.urls.projectionList) and expects_data.sourceDef.urls.projectionList != ''",
|
|
16
|
+
"data.target_urls = data.fanout_mode ? $lookup(expects_data.sourceDef._projections, expects_data.sourceDef.urls.projectionList) : [expects_data.sourceDef.urls.url]",
|
|
17
|
+
"data.sourceDef = expects_data.sourceDef",
|
|
18
|
+
"result = $type(data.target_urls) = 'array' and $count(data.target_urls) > 0 ? 'success' : 'failure'"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"transitions": {
|
|
22
|
+
"success": "execute",
|
|
23
|
+
"failure": "failed"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"execute": {
|
|
27
|
+
"description": "Fetch each resolved URL",
|
|
28
|
+
"expects_data": ["target_urls", "fanout_mode", "sourceDef"],
|
|
29
|
+
"produces_data": ["fetched_values"],
|
|
30
|
+
"forEach": {
|
|
31
|
+
"items": "target_urls",
|
|
32
|
+
"as": "url",
|
|
33
|
+
"concurrency": 4,
|
|
34
|
+
"collectAs": "fetched_values"
|
|
35
|
+
},
|
|
36
|
+
"handler": {
|
|
37
|
+
"type": "ref",
|
|
38
|
+
"howToRun": "demo-local-module",
|
|
39
|
+
"whatToRun": { "kind": "fs-path", "value": "./source-def-flows/url-handler/http-source-handler.js" }
|
|
40
|
+
},
|
|
41
|
+
"transitions": {
|
|
42
|
+
"success": "finalize",
|
|
43
|
+
"failure": "failed"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"finalize": {
|
|
47
|
+
"description": "Normalize flow output",
|
|
48
|
+
"expects_data": ["fetched_values", "fanout_mode"],
|
|
49
|
+
"handler": {
|
|
50
|
+
"type": "compute-jsonata",
|
|
51
|
+
"expr": [
|
|
52
|
+
"data.resultValue = expects_data.fanout_mode ? expects_data.fetched_values : expects_data.fetched_values[0]",
|
|
53
|
+
"result = 'success'"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"transitions": {
|
|
57
|
+
"success": "completed",
|
|
58
|
+
"failure": "failed"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"terminal_states": {
|
|
63
|
+
"completed": {
|
|
64
|
+
"description": "URL fetch succeeded",
|
|
65
|
+
"return_intent": "success",
|
|
66
|
+
"return_artifacts": ["resultValue", "wroteOutputDirectly"]
|
|
67
|
+
},
|
|
68
|
+
"failed": {
|
|
69
|
+
"description": "URL fetch failed",
|
|
70
|
+
"return_intent": "failure"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|