ultravisor 1.3.1 → 1.3.2
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/package.json +3 -3
- package/source/services/Ultravisor-Beacon-Coordinator.cjs +39 -17
- package/source/services/Ultravisor-Beacon-Scheduler.cjs +13 -0
- package/source/services/Ultravisor-ExecutionEngine.cjs +28 -14
- package/source/services/tasks/http-client/Ultravisor-TaskConfigs-HttpClient.cjs +13 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultravisor",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Cyclic process execution with ai integration.",
|
|
5
5
|
"main": "source/Ultravisor.cjs",
|
|
6
6
|
"bin": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"cron": "^4.4.0",
|
|
58
58
|
"meadow": "^2.0.37",
|
|
59
59
|
"meadow-connection-sqlite": "^1.0.19",
|
|
60
|
-
"meadow-migrationmanager": "^0.0.
|
|
60
|
+
"meadow-migrationmanager": "^0.0.15",
|
|
61
61
|
"orator": "^6.1.1",
|
|
62
62
|
"orator-authentication": "^1.0.1",
|
|
63
63
|
"orator-serviceserver-restify": "^2.0.10",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"pict-docuserve": "^0.1.5",
|
|
73
73
|
"puppeteer": "^24.40.0",
|
|
74
|
-
"quackage": "^1.2.
|
|
74
|
+
"quackage": "^1.2.2"
|
|
75
75
|
},
|
|
76
76
|
"mocha": {
|
|
77
77
|
"diff": true,
|
|
@@ -1154,29 +1154,51 @@ class UltravisorBeaconCoordinator extends libPictService
|
|
|
1154
1154
|
tmpDefaults.applyToWorkItem(tmpWorkItem, tmpSettings);
|
|
1155
1155
|
}
|
|
1156
1156
|
|
|
1157
|
-
//
|
|
1157
|
+
// Routing for AffinityKey:
|
|
1158
|
+
// 1) Name resolution — if AffinityKey matches a registered
|
|
1159
|
+
// beacon's Name, route directly to that beacon. This is
|
|
1160
|
+
// the explicit "send to this specific beacon" path the
|
|
1161
|
+
// data-platform initiative needs (many beacons of the
|
|
1162
|
+
// same capability, callers must designate which one).
|
|
1163
|
+
// 2) Sticky binding fallback — if no name match, behave like
|
|
1164
|
+
// a session-affinity key: the first item picks any
|
|
1165
|
+
// beacon, subsequent items with the same key go to the
|
|
1166
|
+
// same beacon (existing behavior).
|
|
1158
1167
|
if (tmpWorkItem.AffinityKey)
|
|
1159
1168
|
{
|
|
1160
|
-
let
|
|
1161
|
-
|
|
1162
|
-
if (tmpBinding && this._Beacons[tmpBinding.BeaconID])
|
|
1169
|
+
let tmpNamedBeacon = this.findBeaconByName(tmpWorkItem.AffinityKey);
|
|
1170
|
+
if (tmpNamedBeacon)
|
|
1163
1171
|
{
|
|
1164
|
-
|
|
1165
|
-
|
|
1172
|
+
tmpWorkItem.AssignedBeaconID = tmpNamedBeacon.BeaconID;
|
|
1173
|
+
tmpWorkItem.Status = 'Assigned';
|
|
1174
|
+
tmpWorkItem.ClaimedAt = new Date().toISOString();
|
|
1175
|
+
if (!tmpNamedBeacon.CurrentWorkItems) tmpNamedBeacon.CurrentWorkItems = [];
|
|
1176
|
+
tmpNamedBeacon.CurrentWorkItems.push(tmpWorkItemHash);
|
|
1177
|
+
this.log.info(`BeaconCoordinator: work item [${tmpWorkItemHash}] routed by name to beacon [${tmpNamedBeacon.BeaconID}] (Name=${tmpNamedBeacon.Name}) via AffinityKey [${tmpWorkItem.AffinityKey}].`);
|
|
1178
|
+
}
|
|
1179
|
+
else
|
|
1180
|
+
{
|
|
1181
|
+
let tmpBinding = this._AffinityBindings[tmpWorkItem.AffinityKey];
|
|
1182
|
+
|
|
1183
|
+
if (tmpBinding && this._Beacons[tmpBinding.BeaconID])
|
|
1166
1184
|
{
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1185
|
+
// Check if the binding has expired
|
|
1186
|
+
if (new Date(tmpBinding.ExpiresAt) > new Date())
|
|
1187
|
+
{
|
|
1188
|
+
tmpWorkItem.AssignedBeaconID = tmpBinding.BeaconID;
|
|
1189
|
+
tmpWorkItem.Status = 'Assigned';
|
|
1190
|
+
tmpWorkItem.ClaimedAt = new Date().toISOString();
|
|
1170
1191
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1192
|
+
let tmpBeacon = this._Beacons[tmpBinding.BeaconID];
|
|
1193
|
+
tmpBeacon.CurrentWorkItems.push(tmpWorkItemHash);
|
|
1173
1194
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1195
|
+
this.log.info(`BeaconCoordinator: work item [${tmpWorkItemHash}] pre-assigned to beacon [${tmpBinding.BeaconID}] via affinity [${tmpWorkItem.AffinityKey}].`);
|
|
1196
|
+
}
|
|
1197
|
+
else
|
|
1198
|
+
{
|
|
1199
|
+
// Binding expired, clean it up
|
|
1200
|
+
delete this._AffinityBindings[tmpWorkItem.AffinityKey];
|
|
1201
|
+
}
|
|
1180
1202
|
}
|
|
1181
1203
|
}
|
|
1182
1204
|
}
|
|
@@ -227,6 +227,19 @@ class UltravisorBeaconScheduler extends libPictService
|
|
|
227
227
|
return null;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
// Name-based routing: AffinityKey may designate a specific
|
|
231
|
+
// beacon by Name. Mirrors the resolution Coordinator does at
|
|
232
|
+
// enqueue time; we repeat it here so paths that bypass
|
|
233
|
+
// enqueueWorkItem (e.g. requeue) still honor it.
|
|
234
|
+
if (pItem.AffinityKey && typeof pCoordinator.findBeaconByName === 'function')
|
|
235
|
+
{
|
|
236
|
+
let tmpNamed = pCoordinator.findBeaconByName(pItem.AffinityKey);
|
|
237
|
+
if (tmpNamed)
|
|
238
|
+
{
|
|
239
|
+
return this._beaconCanTake(tmpNamed, pItem) ? tmpNamed : null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
230
243
|
let tmpBestBeacon = null;
|
|
231
244
|
let tmpBestLoad = Infinity;
|
|
232
245
|
let tmpBeacons = pCoordinator._Beacons || {};
|
|
@@ -1108,26 +1108,35 @@ class UltravisorExecutionEngine extends libPictService
|
|
|
1108
1108
|
fProcessNextDownstream();
|
|
1109
1109
|
};
|
|
1110
1110
|
|
|
1111
|
-
// Execute the task
|
|
1111
|
+
// Execute the task. Wrap the synchronous portion of the call in
|
|
1112
|
+
// try/catch — a misbehaving executor that throws synchronously
|
|
1113
|
+
// (e.g. simple-get's ERR_INVALID_ARG_TYPE on a non-string body)
|
|
1114
|
+
// would otherwise tear down the entire UV process. Treating the
|
|
1115
|
+
// throw as a task error fires the Error edge and lets the rest
|
|
1116
|
+
// of the operation graph clean up gracefully.
|
|
1112
1117
|
this.log.info(`[Engine] executeTask: running task type="${tmpNode.Type}" node=${pNodeHash}`);
|
|
1113
|
-
|
|
1118
|
+
let fHandleTaskError = (pError) =>
|
|
1114
1119
|
{
|
|
1115
|
-
|
|
1120
|
+
this.log.warn(`[Engine] executeTask: TASK ERROR node=${pNodeHash}: ${pError.message}`);
|
|
1121
|
+
this._log(pContext, `Task [${pNodeHash}] error: ${pError.message}`, 'error');
|
|
1122
|
+
if (tmpManifestService)
|
|
1116
1123
|
{
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1124
|
+
tmpManifestService.recordTaskError(pContext, pNodeHash, pError);
|
|
1125
|
+
tmpManifestService.recordEvent(pContext, pNodeHash, 'TaskError',
|
|
1126
|
+
`Error in [${pNodeHash}]: ${pError.message}`, 0);
|
|
1127
|
+
}
|
|
1128
|
+
this._enqueueDownstreamEvents(pNodeHash, 'Error', pContext);
|
|
1129
|
+
return fCallback(null);
|
|
1130
|
+
};
|
|
1131
|
+
try
|
|
1132
|
+
{
|
|
1133
|
+
tmpTaskInstance.execute(tmpResolvedSettings, tmpTaskContext, (pError, pResult) =>
|
|
1134
|
+
{
|
|
1135
|
+
if (pError)
|
|
1120
1136
|
{
|
|
1121
|
-
|
|
1122
|
-
tmpManifestService.recordEvent(pContext, pNodeHash, 'TaskError',
|
|
1123
|
-
`Error in [${pNodeHash}]: ${pError.message}`, 0);
|
|
1137
|
+
return fHandleTaskError(pError);
|
|
1124
1138
|
}
|
|
1125
1139
|
|
|
1126
|
-
// Fire error event if the task has one
|
|
1127
|
-
this._enqueueDownstreamEvents(pNodeHash, 'Error', pContext);
|
|
1128
|
-
return fCallback(null);
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
1140
|
if (!pResult)
|
|
1132
1141
|
{
|
|
1133
1142
|
this._log(pContext, `Task [${pNodeHash}] returned no result.`, 'warn');
|
|
@@ -1257,6 +1266,11 @@ class UltravisorExecutionEngine extends libPictService
|
|
|
1257
1266
|
return fCallback(null);
|
|
1258
1267
|
},
|
|
1259
1268
|
fFireIntermediateEvent);
|
|
1269
|
+
}
|
|
1270
|
+
catch (pSyncError)
|
|
1271
|
+
{
|
|
1272
|
+
return fHandleTaskError(pSyncError);
|
|
1273
|
+
}
|
|
1260
1274
|
}
|
|
1261
1275
|
|
|
1262
1276
|
// ====================================================================
|
|
@@ -305,16 +305,24 @@ module.exports =
|
|
|
305
305
|
|
|
306
306
|
let tmpRequestOptions = { url: tmpURL, method: tmpMethod, headers: tmpHeaders, timeout: pResolvedSettings.TimeoutMs || 30000 };
|
|
307
307
|
|
|
308
|
-
//
|
|
308
|
+
// Body for non-GET methods. The HTTP client's `body` option
|
|
309
|
+
// expects a string (or Buffer / stream); object bodies cause
|
|
310
|
+
// `Buffer.byteLength(object)` to throw ERR_INVALID_ARG_TYPE
|
|
311
|
+
// inside simple-get and crash the engine. Pass the configured
|
|
312
|
+
// Body through verbatim — Content-Type tells the server how
|
|
313
|
+
// to interpret it. (Validating-parse the JSON for an early
|
|
314
|
+
// clearer error, but always serialize back to a string.)
|
|
309
315
|
if (tmpMethod !== 'GET' && pResolvedSettings.Body)
|
|
310
316
|
{
|
|
311
|
-
|
|
317
|
+
if (typeof pResolvedSettings.Body === 'string')
|
|
312
318
|
{
|
|
313
|
-
|
|
319
|
+
try { JSON.parse(pResolvedSettings.Body); }
|
|
320
|
+
catch (pParseError) { /* ok — non-JSON bodies (e.g. form-encoded) pass through too */ }
|
|
321
|
+
tmpRequestOptions.body = pResolvedSettings.Body;
|
|
314
322
|
}
|
|
315
|
-
|
|
323
|
+
else
|
|
316
324
|
{
|
|
317
|
-
tmpRequestOptions.body = pResolvedSettings.Body;
|
|
325
|
+
tmpRequestOptions.body = JSON.stringify(pResolvedSettings.Body);
|
|
318
326
|
}
|
|
319
327
|
}
|
|
320
328
|
|