ultravisor 1.0.17 → 1.0.18
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 +2 -2
- package/source/services/Ultravisor-Beacon-Coordinator.cjs +15 -0
- package/source/services/Ultravisor-Beacon-Reachability.cjs +77 -1
- package/source/services/tasks/platform/Ultravisor-TaskConfigs-Platform.cjs +91 -14
- package/source/services/tasks/platform/definitions/file-transfer.json +11 -8
- package/source/services/tasks/platform/definitions/resolve-address.json +4 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultravisor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "Cyclic process execution with ai integration.",
|
|
5
5
|
"main": "source/Ultravisor.cjs",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"pict": "^1.0.361",
|
|
35
35
|
"pict-service-commandlineutility": "^1.0.19",
|
|
36
36
|
"pict-serviceproviderbase": "^1.0.4",
|
|
37
|
-
"ultravisor-beacon": "^0.0.
|
|
37
|
+
"ultravisor-beacon": "^0.0.9",
|
|
38
38
|
"ws": "^8.20.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -231,6 +231,16 @@ class UltravisorBeaconCoordinator extends libPictService
|
|
|
231
231
|
{
|
|
232
232
|
tmpExistingBeacon.BindAddresses = pBeaconInfo.BindAddresses;
|
|
233
233
|
}
|
|
234
|
+
// Refresh shared-fs identity on reconnect — host id can change between
|
|
235
|
+
// container restarts and a reconnecting beacon may have new mounts.
|
|
236
|
+
if (typeof pBeaconInfo.HostID === 'string' && pBeaconInfo.HostID.length > 0)
|
|
237
|
+
{
|
|
238
|
+
tmpExistingBeacon.HostID = pBeaconInfo.HostID;
|
|
239
|
+
}
|
|
240
|
+
if (Array.isArray(pBeaconInfo.SharedMounts))
|
|
241
|
+
{
|
|
242
|
+
tmpExistingBeacon.SharedMounts = pBeaconInfo.SharedMounts;
|
|
243
|
+
}
|
|
234
244
|
|
|
235
245
|
this.log.info(`BeaconCoordinator: reconnected beacon [${tmpExistingBeacon.BeaconID}] "${tmpName}" with session [${tmpExistingBeacon.SessionID}].`);
|
|
236
246
|
|
|
@@ -272,6 +282,11 @@ class UltravisorBeaconCoordinator extends libPictService
|
|
|
272
282
|
Tags: pBeaconInfo.Tags || {},
|
|
273
283
|
Contexts: pBeaconInfo.Contexts || {},
|
|
274
284
|
BindAddresses: Array.isArray(pBeaconInfo.BindAddresses) ? pBeaconInfo.BindAddresses : [],
|
|
285
|
+
// Shared-fs identity. Both fields are optional for backwards compatibility:
|
|
286
|
+
// older beacons that don't send them just get null/[] and the shared-fs
|
|
287
|
+
// strategy is silently skipped for them.
|
|
288
|
+
HostID: (typeof pBeaconInfo.HostID === 'string' && pBeaconInfo.HostID.length > 0) ? pBeaconInfo.HostID : null,
|
|
289
|
+
SharedMounts: Array.isArray(pBeaconInfo.SharedMounts) ? pBeaconInfo.SharedMounts : [],
|
|
275
290
|
RegisteredAt: new Date().toISOString()
|
|
276
291
|
};
|
|
277
292
|
|
|
@@ -362,9 +362,17 @@ class UltravisorBeaconReachability extends libPictService
|
|
|
362
362
|
* Determine the transfer strategy between a source beacon (file
|
|
363
363
|
* owner) and a requesting beacon (the one that needs the file).
|
|
364
364
|
*
|
|
365
|
+
* Strategy ordering (most efficient first):
|
|
366
|
+
* local — same beacon, no transport at all
|
|
367
|
+
* shared-fs — different beacons but on the same host with overlapping
|
|
368
|
+
* filesystem mounts, so the requesting beacon can read the
|
|
369
|
+
* source's file path directly with no copy
|
|
370
|
+
* direct — HTTP fetch from the source beacon's bind address
|
|
371
|
+
* proxy — HTTP fetch via the coordinator
|
|
372
|
+
*
|
|
365
373
|
* @param {string} pSourceBeaconID - Beacon that owns the resource
|
|
366
374
|
* @param {string} pRequestingBeaconID - Beacon that wants the resource
|
|
367
|
-
* @returns {object} { Strategy, DirectURL }
|
|
375
|
+
* @returns {object} { Strategy, DirectURL, SharedMountRoot? }
|
|
368
376
|
*/
|
|
369
377
|
resolveStrategy(pSourceBeaconID, pRequestingBeaconID)
|
|
370
378
|
{
|
|
@@ -386,6 +394,28 @@ class UltravisorBeaconReachability extends libPictService
|
|
|
386
394
|
return { Strategy: 'proxy', DirectURL: '' };
|
|
387
395
|
}
|
|
388
396
|
|
|
397
|
+
// Shared-fs detection — check whether both beacons live on the same host
|
|
398
|
+
// AND advertise at least one shared filesystem mount in common. When they
|
|
399
|
+
// do, the requesting beacon can read the source file directly from the
|
|
400
|
+
// shared mount without an HTTP transfer.
|
|
401
|
+
let tmpRequestingBeacon = tmpCoordinator.getBeacon(pRequestingBeaconID);
|
|
402
|
+
if (tmpRequestingBeacon
|
|
403
|
+
&& tmpSourceBeacon.HostID
|
|
404
|
+
&& tmpRequestingBeacon.HostID
|
|
405
|
+
&& tmpSourceBeacon.HostID === tmpRequestingBeacon.HostID)
|
|
406
|
+
{
|
|
407
|
+
let tmpSharedMount = this._findSharedMount(
|
|
408
|
+
tmpSourceBeacon.SharedMounts, tmpRequestingBeacon.SharedMounts);
|
|
409
|
+
if (tmpSharedMount)
|
|
410
|
+
{
|
|
411
|
+
return {
|
|
412
|
+
Strategy: 'shared-fs',
|
|
413
|
+
DirectURL: '',
|
|
414
|
+
SharedMountRoot: tmpSharedMount.Root
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
389
419
|
// Check matrix
|
|
390
420
|
let tmpEntry = this.getReachability(pRequestingBeaconID, pSourceBeaconID);
|
|
391
421
|
|
|
@@ -398,6 +428,52 @@ class UltravisorBeaconReachability extends libPictService
|
|
|
398
428
|
// unreachable or untested — fall back to proxy
|
|
399
429
|
return { Strategy: 'proxy', DirectURL: '' };
|
|
400
430
|
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Find the first MountID that appears in both beacons' SharedMounts arrays.
|
|
434
|
+
*
|
|
435
|
+
* The MountID is derived from `stat.dev` + the resolved root path on the
|
|
436
|
+
* beacon side, so two beacons that bind-mount the same host directory get
|
|
437
|
+
* the same ID, while two unrelated /media directories on different machines
|
|
438
|
+
* (or in different containers without shared mounts) get different IDs.
|
|
439
|
+
*
|
|
440
|
+
* @param {Array} pSourceMounts - SharedMounts reported by the source beacon
|
|
441
|
+
* @param {Array} pRequestingMounts - SharedMounts reported by the requester
|
|
442
|
+
* @returns {object|null} The matching mount entry from the source beacon,
|
|
443
|
+
* or null if no overlap exists
|
|
444
|
+
*/
|
|
445
|
+
_findSharedMount(pSourceMounts, pRequestingMounts)
|
|
446
|
+
{
|
|
447
|
+
if (!Array.isArray(pSourceMounts) || pSourceMounts.length === 0)
|
|
448
|
+
{
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
if (!Array.isArray(pRequestingMounts) || pRequestingMounts.length === 0)
|
|
452
|
+
{
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
for (let i = 0; i < pSourceMounts.length; i++)
|
|
456
|
+
{
|
|
457
|
+
let tmpSrc = pSourceMounts[i];
|
|
458
|
+
if (!tmpSrc || !tmpSrc.MountID)
|
|
459
|
+
{
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
for (let j = 0; j < pRequestingMounts.length; j++)
|
|
463
|
+
{
|
|
464
|
+
let tmpReq = pRequestingMounts[j];
|
|
465
|
+
if (!tmpReq || !tmpReq.MountID)
|
|
466
|
+
{
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (tmpSrc.MountID === tmpReq.MountID)
|
|
470
|
+
{
|
|
471
|
+
return tmpSrc;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
401
477
|
}
|
|
402
478
|
|
|
403
479
|
module.exports = UltravisorBeaconReachability;
|
|
@@ -60,7 +60,7 @@ module.exports =
|
|
|
60
60
|
{
|
|
61
61
|
return fCallback(null, {
|
|
62
62
|
EventToFire: 'Error',
|
|
63
|
-
Outputs: { URL: '', BeaconID: '', BeaconName: '', Context: '', Path: '', Filename: '' },
|
|
63
|
+
Outputs: { URL: '', BeaconID: '', BeaconName: '', Context: '', Path: '', Filename: '', LocalPath: '' },
|
|
64
64
|
Log: [`Resolve Address: could not resolve "${tmpAddress}". Beacon may be offline or context not registered.`]
|
|
65
65
|
});
|
|
66
66
|
}
|
|
@@ -74,7 +74,8 @@ module.exports =
|
|
|
74
74
|
Filename: tmpResolved.Filename,
|
|
75
75
|
Strategy: 'direct',
|
|
76
76
|
DirectURL: '',
|
|
77
|
-
ProxyURL: ''
|
|
77
|
+
ProxyURL: '',
|
|
78
|
+
LocalPath: ''
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
// Helper: build a full URL from a beacon's bind address + context path + resource path
|
|
@@ -115,7 +116,33 @@ module.exports =
|
|
|
115
116
|
let tmpStrategyResult = tmpReachability.resolveStrategy(tmpResolved.BeaconID, tmpRequestingBeaconID);
|
|
116
117
|
tmpOutputs.Strategy = tmpStrategyResult.Strategy;
|
|
117
118
|
|
|
118
|
-
if (tmpStrategyResult.Strategy === '
|
|
119
|
+
if (tmpStrategyResult.Strategy === 'shared-fs')
|
|
120
|
+
{
|
|
121
|
+
// Both beacons see the same filesystem mount. Look up the
|
|
122
|
+
// source beacon's context BasePath and join it with the inner
|
|
123
|
+
// resource path to get an absolute path that's also valid on
|
|
124
|
+
// the requesting beacon (because they share the mount).
|
|
125
|
+
let tmpSourceBeacon = tmpCoordinator.getBeacon(tmpResolved.BeaconID);
|
|
126
|
+
let tmpCtx = tmpSourceBeacon && tmpSourceBeacon.Contexts
|
|
127
|
+
? tmpSourceBeacon.Contexts[tmpResolved.Context]
|
|
128
|
+
: null;
|
|
129
|
+
if (tmpCtx && tmpCtx.BasePath)
|
|
130
|
+
{
|
|
131
|
+
let tmpAbsPath = libPath.join(tmpCtx.BasePath, tmpResolved.Path);
|
|
132
|
+
tmpOutputs.LocalPath = tmpAbsPath;
|
|
133
|
+
// URL is intentionally left as the original (relative) URL
|
|
134
|
+
// — file-transfer will see LocalPath and short-circuit, so
|
|
135
|
+
// the URL is never actually fetched.
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
{
|
|
139
|
+
// No BasePath available — fall back to direct so the
|
|
140
|
+
// transfer still works via HTTP.
|
|
141
|
+
pTask.log.warn(`Resolve Address: shared-fs strategy chosen but no BasePath available for context [${tmpResolved.Context}] on beacon [${tmpResolved.BeaconID}], falling back to direct.`);
|
|
142
|
+
tmpOutputs.Strategy = 'direct';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (tmpStrategyResult.Strategy === 'direct' && tmpStrategyResult.DirectURL)
|
|
119
146
|
{
|
|
120
147
|
tmpOutputs.DirectURL = _buildDirectURL(tmpStrategyResult.DirectURL);
|
|
121
148
|
tmpOutputs.URL = tmpOutputs.DirectURL;
|
|
@@ -158,19 +185,26 @@ module.exports =
|
|
|
158
185
|
tmpStateWrites[pResolvedSettings.Destination] = tmpOutputs;
|
|
159
186
|
}
|
|
160
187
|
|
|
161
|
-
|
|
188
|
+
let tmpResolvedDest = tmpOutputs.LocalPath || tmpOutputs.URL;
|
|
189
|
+
pTask.log.info(`Resolve Address: ${tmpAddress} → ${tmpResolvedDest} [${tmpOutputs.Strategy}] (beacon: ${tmpResolved.BeaconName})`);
|
|
190
|
+
|
|
191
|
+
let tmpLogLines = [
|
|
192
|
+
`Resolved: ${tmpAddress}`,
|
|
193
|
+
`URL: ${tmpOutputs.URL}`,
|
|
194
|
+
`Strategy: ${tmpOutputs.Strategy}`,
|
|
195
|
+
`Beacon: ${tmpResolved.BeaconName} (${tmpResolved.BeaconID})`,
|
|
196
|
+
`Context: ${tmpResolved.Context}, Path: ${tmpResolved.Path}`
|
|
197
|
+
];
|
|
198
|
+
if (tmpOutputs.LocalPath)
|
|
199
|
+
{
|
|
200
|
+
tmpLogLines.push(`LocalPath: ${tmpOutputs.LocalPath} (shared filesystem — no transfer needed)`);
|
|
201
|
+
}
|
|
162
202
|
|
|
163
203
|
return fCallback(null, {
|
|
164
204
|
EventToFire: 'Complete',
|
|
165
205
|
Outputs: tmpOutputs,
|
|
166
206
|
StateWrites: tmpStateWrites,
|
|
167
|
-
Log:
|
|
168
|
-
`Resolved: ${tmpAddress}`,
|
|
169
|
-
`URL: ${tmpOutputs.URL}`,
|
|
170
|
-
`Strategy: ${tmpOutputs.Strategy}`,
|
|
171
|
-
`Beacon: ${tmpResolved.BeaconName} (${tmpResolved.BeaconID})`,
|
|
172
|
-
`Context: ${tmpResolved.Context}, Path: ${tmpResolved.Path}`
|
|
173
|
-
]
|
|
207
|
+
Log: tmpLogLines
|
|
174
208
|
});
|
|
175
209
|
}
|
|
176
210
|
},
|
|
@@ -182,13 +216,56 @@ module.exports =
|
|
|
182
216
|
Execute: function (pTask, pResolvedSettings, pExecutionContext, fCallback)
|
|
183
217
|
{
|
|
184
218
|
let tmpSourceURL = pResolvedSettings.SourceURL;
|
|
219
|
+
let tmpSourceLocalPath = pResolvedSettings.SourceLocalPath;
|
|
185
220
|
let tmpFilename = pResolvedSettings.Filename;
|
|
186
221
|
|
|
222
|
+
// Shared-filesystem fast path: if the source beacon and the requesting
|
|
223
|
+
// beacon both report the same MountID, resolve-address will populate
|
|
224
|
+
// SourceLocalPath with the absolute on-disk path. Both beacons see the
|
|
225
|
+
// same file at the same path because they share the mount, so we hand
|
|
226
|
+
// it back as the LocalPath without copying anything. This is the
|
|
227
|
+
// difference between "instant" and "374 MB download to staging" on a
|
|
228
|
+
// stack-mode deployment where retold-remote and orator-conversion live
|
|
229
|
+
// in the same container.
|
|
230
|
+
if (tmpSourceLocalPath)
|
|
231
|
+
{
|
|
232
|
+
if (libFS.existsSync(tmpSourceLocalPath))
|
|
233
|
+
{
|
|
234
|
+
let tmpStat;
|
|
235
|
+
try
|
|
236
|
+
{
|
|
237
|
+
tmpStat = libFS.statSync(tmpSourceLocalPath);
|
|
238
|
+
}
|
|
239
|
+
catch (pStatError)
|
|
240
|
+
{
|
|
241
|
+
tmpStat = null;
|
|
242
|
+
}
|
|
243
|
+
pTask.log.info(`File Transfer: shared-fs hit, using ${tmpSourceLocalPath} directly (${tmpStat ? tmpStat.size : '?'} bytes, no copy).`);
|
|
244
|
+
return fCallback(null, {
|
|
245
|
+
EventToFire: 'Complete',
|
|
246
|
+
Outputs: {
|
|
247
|
+
LocalPath: tmpSourceLocalPath,
|
|
248
|
+
BytesTransferred: 0,
|
|
249
|
+
DurationMs: 0,
|
|
250
|
+
Strategy: 'shared-fs'
|
|
251
|
+
},
|
|
252
|
+
Log: [
|
|
253
|
+
`Shared filesystem detected — no transfer needed.`,
|
|
254
|
+
`Source path: ${tmpSourceLocalPath}`,
|
|
255
|
+
`Bytes transferred: 0 (zero-copy)`
|
|
256
|
+
]
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// Path was provided but doesn't exist on this beacon — log and fall
|
|
260
|
+
// through to the HTTP path so we still satisfy the request.
|
|
261
|
+
pTask.log.warn(`File Transfer: SourceLocalPath [${tmpSourceLocalPath}] does not exist on this beacon, falling back to HTTP transfer.`);
|
|
262
|
+
}
|
|
263
|
+
|
|
187
264
|
if (!tmpSourceURL)
|
|
188
265
|
{
|
|
189
266
|
return fCallback(null, {
|
|
190
267
|
EventToFire: 'Error',
|
|
191
|
-
Outputs: { LocalPath: '', BytesTransferred: 0, DurationMs: 0 },
|
|
268
|
+
Outputs: { LocalPath: '', BytesTransferred: 0, DurationMs: 0, Strategy: '' },
|
|
192
269
|
Log: ['File Transfer: no SourceURL provided.']
|
|
193
270
|
});
|
|
194
271
|
}
|
|
@@ -197,7 +274,7 @@ module.exports =
|
|
|
197
274
|
{
|
|
198
275
|
return fCallback(null, {
|
|
199
276
|
EventToFire: 'Error',
|
|
200
|
-
Outputs: { LocalPath: '', BytesTransferred: 0, DurationMs: 0 },
|
|
277
|
+
Outputs: { LocalPath: '', BytesTransferred: 0, DurationMs: 0, Strategy: '' },
|
|
201
278
|
Log: ['File Transfer: no Filename provided.']
|
|
202
279
|
});
|
|
203
280
|
}
|
|
@@ -467,7 +544,7 @@ function _pipeToFile(pResponse, pOutputPath, pStartTime, pFilename, pTask, fCall
|
|
|
467
544
|
|
|
468
545
|
return fCallback(null, {
|
|
469
546
|
EventToFire: 'Complete',
|
|
470
|
-
Outputs: { LocalPath: pOutputPath, BytesTransferred: tmpBytes, DurationMs: tmpDuration },
|
|
547
|
+
Outputs: { LocalPath: pOutputPath, BytesTransferred: tmpBytes, DurationMs: tmpDuration, Strategy: 'http' },
|
|
471
548
|
Log: [
|
|
472
549
|
`Downloaded: ${pFilename}`,
|
|
473
550
|
`Size: ${tmpBytes} bytes`,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"Hash": "file-transfer",
|
|
3
3
|
"Type": "file-transfer",
|
|
4
4
|
"Name": "File Transfer",
|
|
5
|
-
"Description": "Download a file from a URL to the operation's staging directory. Streams the download — no base64 encoding. Logs bytes transferred and duration for visibility in the manifest.",
|
|
5
|
+
"Description": "Download a file from a URL to the operation's staging directory. Streams the download — no base64 encoding. When SourceLocalPath is provided (set by resolve-address with a shared-fs strategy), the task short-circuits and returns that path directly without copying anything. Logs bytes transferred and duration for visibility in the manifest.",
|
|
6
6
|
"Category": "platform",
|
|
7
7
|
"Capability": "Data Transfer",
|
|
8
8
|
"Action": "Download",
|
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
{ "Name": "Error", "IsError": true }
|
|
14
14
|
],
|
|
15
15
|
"SettingsInputs": [
|
|
16
|
-
{ "Name": "SourceURL", "DataType": "String", "Required":
|
|
17
|
-
{ "Name": "
|
|
18
|
-
{ "Name": "
|
|
16
|
+
{ "Name": "SourceURL", "DataType": "String", "Required": false, "Description": "URL to download from. Required unless SourceLocalPath is provided." },
|
|
17
|
+
{ "Name": "SourceLocalPath", "DataType": "String", "Required": false, "Description": "Absolute filesystem path to read directly. When set and the file exists, the HTTP download is skipped entirely and BytesTransferred reports 0. Used by the shared-fs reachability strategy." },
|
|
18
|
+
{ "Name": "Filename", "DataType": "String", "Required": false, "Description": "Filename to save as in the staging directory. Required unless SourceLocalPath is provided." },
|
|
19
|
+
{ "Name": "AffinityKey", "DataType": "String", "Required": false, "Description": "Affinity key for caching (same key skips re-download)" },
|
|
20
|
+
{ "Name": "TimeoutMs", "DataType": "Number", "Required": false, "Description": "HTTP download timeout in milliseconds (default 300000)" }
|
|
19
21
|
],
|
|
20
22
|
"StateOutputs": [
|
|
21
|
-
{ "Name": "LocalPath", "DataType": "String", "Description": "Absolute path to the downloaded
|
|
22
|
-
{ "Name": "BytesTransferred", "DataType": "Number", "Description": "Size of the downloaded file in bytes" },
|
|
23
|
-
{ "Name": "DurationMs", "DataType": "Number", "Description": "Download duration in milliseconds" }
|
|
23
|
+
{ "Name": "LocalPath", "DataType": "String", "Description": "Absolute path to the file (downloaded to staging, or pointing at the shared-fs source when no copy was needed)" },
|
|
24
|
+
{ "Name": "BytesTransferred", "DataType": "Number", "Description": "Size of the downloaded file in bytes (0 when shared-fs zero-copy was used)" },
|
|
25
|
+
{ "Name": "DurationMs", "DataType": "Number", "Description": "Download duration in milliseconds" },
|
|
26
|
+
{ "Name": "Strategy", "DataType": "String", "Description": "Which path was taken: 'shared-fs' (zero-copy) or 'http' (downloaded)" }
|
|
24
27
|
],
|
|
25
|
-
"DefaultSettings": { "SourceURL": "", "Filename": "", "AffinityKey": "" }
|
|
28
|
+
"DefaultSettings": { "SourceURL": "", "SourceLocalPath": "", "Filename": "", "AffinityKey": "", "TimeoutMs": 300000 }
|
|
26
29
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"Hash": "resolve-address",
|
|
3
3
|
"Type": "resolve-address",
|
|
4
4
|
"Name": "Resolve Address",
|
|
5
|
-
"Description": "Resolve a universal address (>beacon/context/path) to a concrete URL and metadata, with optional transfer strategy resolution (local/direct/proxy) when a requesting beacon is specified.",
|
|
5
|
+
"Description": "Resolve a universal address (>beacon/context/path) to a concrete URL and metadata, with optional transfer strategy resolution (local/shared-fs/direct/proxy) when a requesting beacon is specified.",
|
|
6
6
|
"Category": "platform",
|
|
7
7
|
"Capability": "Address Resolution",
|
|
8
8
|
"Action": "Resolve",
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
{ "Name": "Context", "DataType": "String", "Description": "Context name (e.g. File, Cache)" },
|
|
25
25
|
{ "Name": "Path", "DataType": "String", "Description": "Path within the context" },
|
|
26
26
|
{ "Name": "Filename", "DataType": "String", "Description": "Filename portion of the path" },
|
|
27
|
-
{ "Name": "Strategy", "DataType": "String", "Description": "Transfer strategy: local, direct, or proxy" },
|
|
27
|
+
{ "Name": "Strategy", "DataType": "String", "Description": "Transfer strategy: local, shared-fs, direct, or proxy" },
|
|
28
28
|
{ "Name": "DirectURL", "DataType": "String", "Description": "Direct beacon-to-beacon URL (when strategy is direct)" },
|
|
29
|
-
{ "Name": "ProxyURL", "DataType": "String", "Description": "Proxy URL through Ultravisor (when strategy is proxy)" }
|
|
29
|
+
{ "Name": "ProxyURL", "DataType": "String", "Description": "Proxy URL through Ultravisor (when strategy is proxy)" },
|
|
30
|
+
{ "Name": "LocalPath", "DataType": "String", "Description": "Absolute path to the resource on the shared filesystem (when strategy is shared-fs); empty otherwise" }
|
|
30
31
|
],
|
|
31
32
|
"DefaultSettings": { "Address": "", "Destination": "", "RequestingBeaconID": "" }
|
|
32
33
|
}
|