@continuonai/rcan-ts 0.6.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -182
- package/dist/browser.d.mts +475 -43
- package/dist/browser.mjs +555 -83
- package/dist/browser.mjs.map +1 -1
- package/dist/index.d.mts +475 -43
- package/dist/index.d.ts +475 -43
- package/dist/index.js +606 -84
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +555 -83
- package/dist/index.mjs.map +1 -1
- package/dist/rcan-validate.js +17 -2
- package/dist/rcan.iife.js +3 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,24 +17,39 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
33
|
+
AUTHORITY_ERROR_CODES: () => AUTHORITY_ERROR_CODES,
|
|
23
34
|
AuditChain: () => AuditChain,
|
|
24
35
|
AuditError: () => AuditError,
|
|
36
|
+
COMPETITION_SCOPE_LEVEL: () => COMPETITION_SCOPE_LEVEL,
|
|
37
|
+
CONTRIBUTE_SCOPE_LEVEL: () => CONTRIBUTE_SCOPE_LEVEL,
|
|
25
38
|
ClockDriftError: () => ClockDriftError,
|
|
26
39
|
CommitmentRecord: () => CommitmentRecord,
|
|
27
40
|
ConfidenceGate: () => ConfidenceGate,
|
|
28
41
|
DEFAULT_LOA_POLICY: () => DEFAULT_LOA_POLICY,
|
|
29
42
|
DataCategory: () => DataCategory,
|
|
43
|
+
FIRMWARE_MANIFEST_PATH: () => FIRMWARE_MANIFEST_PATH,
|
|
30
44
|
FaultCode: () => FaultCode,
|
|
31
45
|
FederationSyncType: () => FederationSyncType,
|
|
46
|
+
FirmwareIntegrityError: () => FirmwareIntegrityError,
|
|
32
47
|
GateError: () => GateError,
|
|
33
48
|
HiTLGate: () => HiTLGate,
|
|
34
49
|
KeyStore: () => KeyStore,
|
|
35
50
|
LevelOfAssurance: () => LevelOfAssurance,
|
|
51
|
+
M2MAuthError: () => M2MAuthError,
|
|
52
|
+
M2M_TRUSTED_ISSUER: () => M2M_TRUSTED_ISSUER,
|
|
36
53
|
MediaEncoding: () => MediaEncoding,
|
|
37
54
|
MessageType: () => MessageType,
|
|
38
55
|
NodeClient: () => NodeClient,
|
|
@@ -58,13 +75,18 @@ __export(index_exports, {
|
|
|
58
75
|
RCANValidationError: () => RCANValidationError,
|
|
59
76
|
RCANVersionIncompatibleError: () => RCANVersionIncompatibleError,
|
|
60
77
|
RCAN_VERSION: () => RCAN_VERSION,
|
|
78
|
+
ROLE_JWT_LEVEL: () => ROLE_JWT_LEVEL,
|
|
79
|
+
RRF_REVOCATION_CACHE_TTL_MS: () => RRF_REVOCATION_CACHE_TTL_MS,
|
|
80
|
+
RRF_REVOCATION_URL: () => RRF_REVOCATION_URL,
|
|
61
81
|
RegistryClient: () => RegistryClient,
|
|
62
82
|
RegistryTier: () => RegistryTier,
|
|
63
83
|
ReplayCache: () => ReplayCache,
|
|
64
84
|
RevocationCache: () => RevocationCache,
|
|
65
85
|
RobotURI: () => RobotURI,
|
|
66
86
|
RobotURIError: () => RobotURIError,
|
|
87
|
+
Role: () => Role,
|
|
67
88
|
SAFETY_MESSAGE_TYPE: () => SAFETY_MESSAGE_TYPE,
|
|
89
|
+
SCOPE_MIN_ROLE: () => SCOPE_MIN_ROLE,
|
|
68
90
|
SDK_VERSION: () => SDK_VERSION,
|
|
69
91
|
SPEC_VERSION: () => SPEC_VERSION,
|
|
70
92
|
TransportEncoding: () => TransportEncoding,
|
|
@@ -75,6 +97,9 @@ __export(index_exports, {
|
|
|
75
97
|
addMediaInline: () => addMediaInline,
|
|
76
98
|
addMediaRef: () => addMediaRef,
|
|
77
99
|
assertClockSynced: () => assertClockSynced,
|
|
100
|
+
authorityAccessFromWire: () => authorityAccessFromWire,
|
|
101
|
+
authorityAccessToWire: () => authorityAccessToWire,
|
|
102
|
+
canonicalManifestJson: () => canonicalManifestJson,
|
|
78
103
|
checkClockSync: () => checkClockSync,
|
|
79
104
|
checkRevocation: () => checkRevocation,
|
|
80
105
|
decodeBleFrames: () => decodeBleFrames,
|
|
@@ -83,21 +108,34 @@ __export(index_exports, {
|
|
|
83
108
|
encodeBleFrames: () => encodeBleFrames,
|
|
84
109
|
encodeCompact: () => encodeCompact,
|
|
85
110
|
encodeMinimal: () => encodeMinimal,
|
|
111
|
+
extractIdentityFromJwt: () => extractIdentityFromJwt,
|
|
86
112
|
extractLoaFromJwt: () => extractLoaFromJwt,
|
|
113
|
+
extractRoleFromJwt: () => extractRoleFromJwt,
|
|
87
114
|
fetchCanonicalSchema: () => fetchCanonicalSchema,
|
|
115
|
+
fetchRRFRevocations: () => fetchRRFRevocations,
|
|
116
|
+
isAuthorityRequestValid: () => isAuthorityRequestValid,
|
|
117
|
+
isM2mTrustedRevoked: () => isM2mTrustedRevoked,
|
|
118
|
+
isPreemptedBy: () => isPreemptedBy,
|
|
88
119
|
isSafetyMessage: () => isSafetyMessage,
|
|
89
120
|
makeCloudRelayMessage: () => makeCloudRelayMessage,
|
|
121
|
+
makeCompetitionEnter: () => makeCompetitionEnter,
|
|
122
|
+
makeCompetitionScore: () => makeCompetitionScore,
|
|
90
123
|
makeConfigUpdate: () => makeConfigUpdate,
|
|
91
124
|
makeConsentDeny: () => makeConsentDeny,
|
|
92
125
|
makeConsentGrant: () => makeConsentGrant,
|
|
93
126
|
makeConsentRequest: () => makeConsentRequest,
|
|
127
|
+
makeContributeCancel: () => makeContributeCancel,
|
|
128
|
+
makeContributeRequest: () => makeContributeRequest,
|
|
129
|
+
makeContributeResult: () => makeContributeResult,
|
|
94
130
|
makeEstopMessage: () => makeEstopMessage,
|
|
95
131
|
makeEstopWithQoS: () => makeEstopWithQoS,
|
|
96
132
|
makeFaultReport: () => makeFaultReport,
|
|
97
133
|
makeFederationSync: () => makeFederationSync,
|
|
98
134
|
makeKeyRotationMessage: () => makeKeyRotationMessage,
|
|
135
|
+
makePersonalResearchResult: () => makePersonalResearchResult,
|
|
99
136
|
makeResumeMessage: () => makeResumeMessage,
|
|
100
137
|
makeRevocationBroadcast: () => makeRevocationBroadcast,
|
|
138
|
+
makeSeasonStanding: () => makeSeasonStanding,
|
|
101
139
|
makeStopMessage: () => makeStopMessage,
|
|
102
140
|
makeStreamChunk: () => makeStreamChunk,
|
|
103
141
|
makeTrainingConsentDeny: () => makeTrainingConsentDeny,
|
|
@@ -105,22 +143,34 @@ __export(index_exports, {
|
|
|
105
143
|
makeTrainingConsentRequest: () => makeTrainingConsentRequest,
|
|
106
144
|
makeTrainingDataMessage: () => makeTrainingDataMessage,
|
|
107
145
|
makeTransparencyMessage: () => makeTransparencyMessage,
|
|
146
|
+
manifestFromWire: () => manifestFromWire,
|
|
147
|
+
manifestToWire: () => manifestToWire,
|
|
148
|
+
parseM2mPeerToken: () => parseM2mPeerToken,
|
|
149
|
+
parseM2mTrustedToken: () => parseM2mTrustedToken,
|
|
150
|
+
roleFromJwtLevel: () => roleFromJwtLevel,
|
|
108
151
|
selectTransport: () => selectTransport,
|
|
152
|
+
validateAuthorityAccess: () => validateAuthorityAccess,
|
|
153
|
+
validateCompetitionScope: () => validateCompetitionScope,
|
|
109
154
|
validateConfig: () => validateConfig,
|
|
110
155
|
validateConfigAgainstSchema: () => validateConfigAgainstSchema,
|
|
111
156
|
validateConfigUpdate: () => validateConfigUpdate,
|
|
112
157
|
validateConsentMessage: () => validateConsentMessage,
|
|
158
|
+
validateContributeScope: () => validateContributeScope,
|
|
113
159
|
validateCrossRegistryCommand: () => validateCrossRegistryCommand,
|
|
114
160
|
validateDelegationChain: () => validateDelegationChain,
|
|
115
161
|
validateLoaForScope: () => validateLoaForScope,
|
|
162
|
+
validateManifest: () => validateManifest,
|
|
116
163
|
validateMediaChunks: () => validateMediaChunks,
|
|
117
164
|
validateMessage: () => validateMessage,
|
|
118
165
|
validateNodeAgainstSchema: () => validateNodeAgainstSchema,
|
|
119
166
|
validateReplay: () => validateReplay,
|
|
167
|
+
validateRoleForScope: () => validateRoleForScope,
|
|
120
168
|
validateSafetyMessage: () => validateSafetyMessage,
|
|
121
169
|
validateTrainingDataMessage: () => validateTrainingDataMessage,
|
|
122
170
|
validateURI: () => validateURI,
|
|
123
|
-
validateVersionCompat: () => validateVersionCompat
|
|
171
|
+
validateVersionCompat: () => validateVersionCompat,
|
|
172
|
+
verifyM2mTrustedToken: () => verifyM2mTrustedToken,
|
|
173
|
+
verifyM2mTrustedTokenClaims: () => verifyM2mTrustedTokenClaims
|
|
124
174
|
});
|
|
125
175
|
module.exports = __toCommonJS(index_exports);
|
|
126
176
|
|
|
@@ -215,8 +265,8 @@ var RobotURI = class _RobotURI {
|
|
|
215
265
|
};
|
|
216
266
|
|
|
217
267
|
// src/version.ts
|
|
218
|
-
var SPEC_VERSION = "1.
|
|
219
|
-
var SDK_VERSION = "
|
|
268
|
+
var SPEC_VERSION = "2.1.0";
|
|
269
|
+
var SDK_VERSION = "1.1.0";
|
|
220
270
|
function validateVersionCompat(incomingVersion, localVersion = SPEC_VERSION) {
|
|
221
271
|
const parseParts = (v) => {
|
|
222
272
|
const parts = v.split(".");
|
|
@@ -237,18 +287,18 @@ var MessageType = /* @__PURE__ */ ((MessageType2) => {
|
|
|
237
287
|
MessageType2[MessageType2["HEARTBEAT"] = 4] = "HEARTBEAT";
|
|
238
288
|
MessageType2[MessageType2["CONFIG"] = 5] = "CONFIG";
|
|
239
289
|
MessageType2[MessageType2["SAFETY"] = 6] = "SAFETY";
|
|
240
|
-
MessageType2[MessageType2["
|
|
241
|
-
MessageType2[MessageType2["
|
|
290
|
+
MessageType2[MessageType2["AUTH"] = 7] = "AUTH";
|
|
291
|
+
MessageType2[MessageType2["ERROR"] = 8] = "ERROR";
|
|
242
292
|
MessageType2[MessageType2["DISCOVER"] = 9] = "DISCOVER";
|
|
243
|
-
MessageType2[MessageType2["
|
|
244
|
-
MessageType2[MessageType2["
|
|
245
|
-
MessageType2[MessageType2["
|
|
246
|
-
MessageType2[MessageType2["
|
|
247
|
-
MessageType2[MessageType2["
|
|
248
|
-
MessageType2[MessageType2["
|
|
249
|
-
MessageType2[MessageType2["
|
|
293
|
+
MessageType2[MessageType2["PENDING_AUTH"] = 10] = "PENDING_AUTH";
|
|
294
|
+
MessageType2[MessageType2["INVOKE"] = 11] = "INVOKE";
|
|
295
|
+
MessageType2[MessageType2["INVOKE_RESULT"] = 12] = "INVOKE_RESULT";
|
|
296
|
+
MessageType2[MessageType2["INVOKE_CANCEL"] = 13] = "INVOKE_CANCEL";
|
|
297
|
+
MessageType2[MessageType2["REGISTRY_REGISTER"] = 14] = "REGISTRY_REGISTER";
|
|
298
|
+
MessageType2[MessageType2["REGISTRY_RESOLVE"] = 15] = "REGISTRY_RESOLVE";
|
|
299
|
+
MessageType2[MessageType2["TRANSPARENCY"] = 16] = "TRANSPARENCY";
|
|
250
300
|
MessageType2[MessageType2["COMMAND_ACK"] = 17] = "COMMAND_ACK";
|
|
251
|
-
MessageType2[MessageType2["
|
|
301
|
+
MessageType2[MessageType2["COMMAND_NACK"] = 18] = "COMMAND_NACK";
|
|
252
302
|
MessageType2[MessageType2["ROBOT_REVOCATION"] = 19] = "ROBOT_REVOCATION";
|
|
253
303
|
MessageType2[MessageType2["CONSENT_REQUEST"] = 20] = "CONSENT_REQUEST";
|
|
254
304
|
MessageType2[MessageType2["CONSENT_GRANT"] = 21] = "CONSENT_GRANT";
|
|
@@ -257,7 +307,24 @@ var MessageType = /* @__PURE__ */ ((MessageType2) => {
|
|
|
257
307
|
MessageType2[MessageType2["SUBSCRIBE"] = 24] = "SUBSCRIBE";
|
|
258
308
|
MessageType2[MessageType2["UNSUBSCRIBE"] = 25] = "UNSUBSCRIBE";
|
|
259
309
|
MessageType2[MessageType2["FAULT_REPORT"] = 26] = "FAULT_REPORT";
|
|
260
|
-
MessageType2[MessageType2["
|
|
310
|
+
MessageType2[MessageType2["KEY_ROTATION"] = 27] = "KEY_ROTATION";
|
|
311
|
+
MessageType2[MessageType2["COMMAND_COMMIT"] = 28] = "COMMAND_COMMIT";
|
|
312
|
+
MessageType2[MessageType2["SENSOR_DATA"] = 29] = "SENSOR_DATA";
|
|
313
|
+
MessageType2[MessageType2["TRAINING_CONSENT_REQUEST"] = 30] = "TRAINING_CONSENT_REQUEST";
|
|
314
|
+
MessageType2[MessageType2["TRAINING_CONSENT_GRANT"] = 31] = "TRAINING_CONSENT_GRANT";
|
|
315
|
+
MessageType2[MessageType2["TRAINING_CONSENT_DENY"] = 32] = "TRAINING_CONSENT_DENY";
|
|
316
|
+
MessageType2[MessageType2["CONTRIBUTE_REQUEST"] = 33] = "CONTRIBUTE_REQUEST";
|
|
317
|
+
MessageType2[MessageType2["CONTRIBUTE_RESULT"] = 34] = "CONTRIBUTE_RESULT";
|
|
318
|
+
MessageType2[MessageType2["CONTRIBUTE_CANCEL"] = 35] = "CONTRIBUTE_CANCEL";
|
|
319
|
+
MessageType2[MessageType2["TRAINING_DATA"] = 36] = "TRAINING_DATA";
|
|
320
|
+
MessageType2[MessageType2["COMPETITION_ENTER"] = 37] = "COMPETITION_ENTER";
|
|
321
|
+
MessageType2[MessageType2["COMPETITION_SCORE"] = 38] = "COMPETITION_SCORE";
|
|
322
|
+
MessageType2[MessageType2["SEASON_STANDING"] = 39] = "SEASON_STANDING";
|
|
323
|
+
MessageType2[MessageType2["PERSONAL_RESEARCH_RESULT"] = 40] = "PERSONAL_RESEARCH_RESULT";
|
|
324
|
+
MessageType2[MessageType2["AUTHORITY_ACCESS"] = 41] = "AUTHORITY_ACCESS";
|
|
325
|
+
MessageType2[MessageType2["AUTHORITY_RESPONSE"] = 42] = "AUTHORITY_RESPONSE";
|
|
326
|
+
MessageType2[MessageType2["FIRMWARE_ATTESTATION"] = 43] = "FIRMWARE_ATTESTATION";
|
|
327
|
+
MessageType2[MessageType2["SBOM_UPDATE"] = 44] = "SBOM_UPDATE";
|
|
261
328
|
return MessageType2;
|
|
262
329
|
})(MessageType || {});
|
|
263
330
|
var RCANMessageError = class extends Error {
|
|
@@ -293,6 +360,10 @@ var RCANMessage = class _RCANMessage {
|
|
|
293
360
|
transportEncoding;
|
|
294
361
|
/** v1.6: GAP-18 multi-modal media chunks */
|
|
295
362
|
mediaChunks;
|
|
363
|
+
/** v2.1: SHA-256 of sender's firmware manifest */
|
|
364
|
+
firmwareHash;
|
|
365
|
+
/** v2.1: URI to sender's SBOM attestation endpoint */
|
|
366
|
+
attestationRef;
|
|
296
367
|
constructor(data) {
|
|
297
368
|
if (!data.cmd || data.cmd.trim() === "") {
|
|
298
369
|
throw new RCANMessageError("'cmd' is required");
|
|
@@ -321,6 +392,13 @@ var RCANMessage = class _RCANMessage {
|
|
|
321
392
|
this.loa = data.loa;
|
|
322
393
|
this.transportEncoding = data.transportEncoding;
|
|
323
394
|
this.mediaChunks = data.mediaChunks;
|
|
395
|
+
this.firmwareHash = data.firmwareHash;
|
|
396
|
+
this.attestationRef = data.attestationRef;
|
|
397
|
+
if (this.signature !== void 0 && this.signature["sig"] === "pending") {
|
|
398
|
+
throw new RCANMessageError(
|
|
399
|
+
"signature.sig:'pending' is not valid in RCAN v2.1. Sign the message before sending."
|
|
400
|
+
);
|
|
401
|
+
}
|
|
324
402
|
if (this.confidence !== void 0) {
|
|
325
403
|
if (this.confidence < 0 || this.confidence > 1) {
|
|
326
404
|
throw new RCANMessageError(
|
|
@@ -362,6 +440,8 @@ var RCANMessage = class _RCANMessage {
|
|
|
362
440
|
if (this.loa !== void 0) obj.loa = this.loa;
|
|
363
441
|
if (this.transportEncoding !== void 0) obj.transportEncoding = this.transportEncoding;
|
|
364
442
|
if (this.mediaChunks !== void 0) obj.mediaChunks = this.mediaChunks;
|
|
443
|
+
if (this.firmwareHash !== void 0) obj.firmwareHash = this.firmwareHash;
|
|
444
|
+
if (this.attestationRef !== void 0) obj.attestationRef = this.attestationRef;
|
|
365
445
|
return obj;
|
|
366
446
|
}
|
|
367
447
|
/** Serialize to JSON string */
|
|
@@ -404,7 +484,9 @@ var RCANMessage = class _RCANMessage {
|
|
|
404
484
|
readOnly: obj.readOnly,
|
|
405
485
|
loa: obj.loa,
|
|
406
486
|
transportEncoding: obj.transportEncoding,
|
|
407
|
-
mediaChunks: obj.mediaChunks
|
|
487
|
+
mediaChunks: obj.mediaChunks,
|
|
488
|
+
firmwareHash: obj.firmwareHash,
|
|
489
|
+
attestationRef: obj.attestationRef
|
|
408
490
|
});
|
|
409
491
|
}
|
|
410
492
|
};
|
|
@@ -1377,6 +1459,7 @@ async function fetchCanonicalSchema(schemaName) {
|
|
|
1377
1459
|
try {
|
|
1378
1460
|
const controller = new AbortController();
|
|
1379
1461
|
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
1462
|
+
timer.unref?.();
|
|
1380
1463
|
const res = await fetch(`${SCHEMA_BASE}/${schemaName}`, { signal: controller.signal });
|
|
1381
1464
|
clearTimeout(timer);
|
|
1382
1465
|
if (!res.ok) return null;
|
|
@@ -2017,7 +2100,7 @@ function makeTrainingConsentDeny(params) {
|
|
|
2017
2100
|
return makeConsentDeny(params);
|
|
2018
2101
|
}
|
|
2019
2102
|
function validateTrainingDataMessage(msg) {
|
|
2020
|
-
if (msg.params.message_type !==
|
|
2103
|
+
if (msg.params.message_type !== 36 /* TRAINING_DATA */) {
|
|
2021
2104
|
return { valid: false, reason: "not a TRAINING_DATA message" };
|
|
2022
2105
|
}
|
|
2023
2106
|
const token = msg.params.consent_token;
|
|
@@ -2157,77 +2240,133 @@ function makeFaultReport(params) {
|
|
|
2157
2240
|
}
|
|
2158
2241
|
|
|
2159
2242
|
// src/identity.ts
|
|
2160
|
-
var
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2243
|
+
var Role = /* @__PURE__ */ ((Role2) => {
|
|
2244
|
+
Role2[Role2["GUEST"] = 1] = "GUEST";
|
|
2245
|
+
Role2[Role2["OPERATOR"] = 2] = "OPERATOR";
|
|
2246
|
+
Role2[Role2["CONTRIBUTOR"] = 3] = "CONTRIBUTOR";
|
|
2247
|
+
Role2[Role2["ADMIN"] = 4] = "ADMIN";
|
|
2248
|
+
Role2[Role2["M2M_PEER"] = 5] = "M2M_PEER";
|
|
2249
|
+
Role2[Role2["CREATOR"] = 6] = "CREATOR";
|
|
2250
|
+
Role2[Role2["M2M_TRUSTED"] = 7] = "M2M_TRUSTED";
|
|
2251
|
+
return Role2;
|
|
2252
|
+
})(Role || {});
|
|
2253
|
+
var LevelOfAssurance = Role;
|
|
2254
|
+
var ROLE_JWT_LEVEL = {
|
|
2255
|
+
[1 /* GUEST */]: 1,
|
|
2256
|
+
[2 /* OPERATOR */]: 2,
|
|
2257
|
+
[3 /* CONTRIBUTOR */]: 2.5,
|
|
2258
|
+
[4 /* ADMIN */]: 3,
|
|
2259
|
+
[5 /* M2M_PEER */]: 4,
|
|
2260
|
+
[6 /* CREATOR */]: 5,
|
|
2261
|
+
[7 /* M2M_TRUSTED */]: 6
|
|
2262
|
+
};
|
|
2263
|
+
var JWT_LEVEL_TO_ROLE = new Map(
|
|
2264
|
+
Object.entries(ROLE_JWT_LEVEL).map(
|
|
2265
|
+
([role, level]) => [level, Number(role)]
|
|
2266
|
+
)
|
|
2267
|
+
);
|
|
2268
|
+
function roleFromJwtLevel(level) {
|
|
2269
|
+
return JWT_LEVEL_TO_ROLE.get(level);
|
|
2270
|
+
}
|
|
2271
|
+
var SCOPE_MIN_ROLE = {
|
|
2272
|
+
"status": 1 /* GUEST */,
|
|
2273
|
+
"discover": 1 /* GUEST */,
|
|
2274
|
+
"chat": 1 /* GUEST */,
|
|
2275
|
+
"observer": 1 /* GUEST */,
|
|
2276
|
+
"contribute": 3 /* CONTRIBUTOR */,
|
|
2277
|
+
"control": 2 /* OPERATOR */,
|
|
2278
|
+
"teleop": 2 /* OPERATOR */,
|
|
2279
|
+
"training": 4 /* ADMIN */,
|
|
2280
|
+
"training_data": 4 /* ADMIN */,
|
|
2281
|
+
"config": 4 /* ADMIN */,
|
|
2282
|
+
"authority": 4 /* ADMIN */,
|
|
2283
|
+
"admin": 6 /* CREATOR */,
|
|
2284
|
+
"safety": 6 /* CREATOR */,
|
|
2285
|
+
"estop": 6 /* CREATOR */,
|
|
2286
|
+
"fleet.trusted": 7 /* M2M_TRUSTED */
|
|
2287
|
+
};
|
|
2166
2288
|
var DEFAULT_LOA_POLICY = {
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2289
|
+
minRoleForDiscover: 1 /* GUEST */,
|
|
2290
|
+
minRoleForStatus: 1 /* GUEST */,
|
|
2291
|
+
minRoleForChat: 1 /* GUEST */,
|
|
2292
|
+
minRoleForControl: 1 /* GUEST */,
|
|
2293
|
+
minRoleForSafety: 1 /* GUEST */
|
|
2172
2294
|
};
|
|
2173
2295
|
var PRODUCTION_LOA_POLICY = {
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2296
|
+
minRoleForDiscover: 1 /* GUEST */,
|
|
2297
|
+
minRoleForStatus: 1 /* GUEST */,
|
|
2298
|
+
minRoleForChat: 1 /* GUEST */,
|
|
2299
|
+
minRoleForControl: 2 /* OPERATOR */,
|
|
2300
|
+
minRoleForSafety: 6 /* CREATOR */
|
|
2179
2301
|
};
|
|
2180
|
-
function
|
|
2302
|
+
function decodeJwtPayload(token) {
|
|
2181
2303
|
try {
|
|
2182
2304
|
const parts = token.split(".");
|
|
2183
|
-
if (parts.length < 2) return
|
|
2305
|
+
if (parts.length < 2) return null;
|
|
2184
2306
|
const payloadB64 = (parts[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
|
|
2185
2307
|
const padded = payloadB64 + "=".repeat((4 - payloadB64.length % 4) % 4);
|
|
2186
|
-
|
|
2187
|
-
if (typeof atob !== "undefined") {
|
|
2188
|
-
json = atob(padded);
|
|
2189
|
-
} else {
|
|
2190
|
-
json = Buffer.from(padded, "base64").toString("utf-8");
|
|
2191
|
-
}
|
|
2192
|
-
const claims = JSON.parse(json);
|
|
2193
|
-
const loa = claims["loa"];
|
|
2194
|
-
if (typeof loa === "number" && loa >= 1 && loa <= 3) {
|
|
2195
|
-
return loa;
|
|
2196
|
-
}
|
|
2308
|
+
return JSON.parse(atob(padded));
|
|
2197
2309
|
} catch {
|
|
2310
|
+
return null;
|
|
2198
2311
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
}
|
|
2218
|
-
function
|
|
2219
|
-
const
|
|
2220
|
-
if (
|
|
2221
|
-
return {
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2312
|
+
}
|
|
2313
|
+
function extractRoleFromJwt(token) {
|
|
2314
|
+
const payload = decodeJwtPayload(token);
|
|
2315
|
+
if (!payload) return 1 /* GUEST */;
|
|
2316
|
+
const rcanRole = payload["rcan_role"];
|
|
2317
|
+
if (rcanRole !== void 0 && rcanRole !== null) {
|
|
2318
|
+
const role = roleFromJwtLevel(Number(rcanRole));
|
|
2319
|
+
if (role !== void 0) return role;
|
|
2320
|
+
}
|
|
2321
|
+
const loa = payload["loa"];
|
|
2322
|
+
if (loa !== void 0 && loa !== null) {
|
|
2323
|
+
const role = roleFromJwtLevel(Number(loa));
|
|
2324
|
+
if (role !== void 0) return role;
|
|
2325
|
+
}
|
|
2326
|
+
return 1 /* GUEST */;
|
|
2327
|
+
}
|
|
2328
|
+
function extractLoaFromJwt(token) {
|
|
2329
|
+
return extractRoleFromJwt(token);
|
|
2330
|
+
}
|
|
2331
|
+
function extractIdentityFromJwt(token) {
|
|
2332
|
+
const payload = decodeJwtPayload(token);
|
|
2333
|
+
if (!payload) {
|
|
2334
|
+
return { sub: "", role: 1 /* GUEST */, jwtLevel: 1, scopes: [] };
|
|
2335
|
+
}
|
|
2336
|
+
const rcanRole = payload["rcan_role"];
|
|
2337
|
+
const loa = payload["loa"];
|
|
2338
|
+
const rawLevel = rcanRole !== void 0 ? Number(rcanRole) : loa !== void 0 ? Number(loa) : 1;
|
|
2339
|
+
const role = roleFromJwtLevel(rawLevel) ?? 1 /* GUEST */;
|
|
2340
|
+
const scopes = Array.isArray(payload["rcan_scopes"]) ? payload["rcan_scopes"] : Array.isArray(payload["scopes"]) ? payload["scopes"] : [];
|
|
2341
|
+
return {
|
|
2342
|
+
sub: String(payload["sub"] ?? ""),
|
|
2343
|
+
role,
|
|
2344
|
+
jwtLevel: ROLE_JWT_LEVEL[role],
|
|
2345
|
+
registryUrl: payload["registry_url"],
|
|
2346
|
+
scopes,
|
|
2347
|
+
verifiedAt: payload["verified_at"],
|
|
2348
|
+
peerRrn: payload["peer_rrn"],
|
|
2349
|
+
fleetRrns: Array.isArray(payload["fleet_rrns"]) ? payload["fleet_rrns"] : void 0
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
function validateRoleForScope(role, scope) {
|
|
2353
|
+
const required = SCOPE_MIN_ROLE[scope.toLowerCase()];
|
|
2354
|
+
if (required === void 0) {
|
|
2355
|
+
if (role >= 2 /* OPERATOR */) return { ok: true, reason: "" };
|
|
2356
|
+
return {
|
|
2357
|
+
ok: false,
|
|
2358
|
+
reason: `Unknown scope '${scope}': applying OPERATOR minimum. Caller has ${Role[role]}.`
|
|
2359
|
+
};
|
|
2225
2360
|
}
|
|
2361
|
+
if (role >= required) return { ok: true, reason: "" };
|
|
2226
2362
|
return {
|
|
2227
|
-
|
|
2228
|
-
reason: `
|
|
2363
|
+
ok: false,
|
|
2364
|
+
reason: `Scope '${scope}' requires ${Role[required]} (JWT level ${ROLE_JWT_LEVEL[required]}), but caller has ${Role[role]} (JWT level ${ROLE_JWT_LEVEL[role]})`
|
|
2229
2365
|
};
|
|
2230
2366
|
}
|
|
2367
|
+
function validateLoaForScope(role, scope) {
|
|
2368
|
+
return validateRoleForScope(role, scope);
|
|
2369
|
+
}
|
|
2231
2370
|
|
|
2232
2371
|
// src/federation.ts
|
|
2233
2372
|
var RegistryTier = /* @__PURE__ */ ((RegistryTier2) => {
|
|
@@ -2351,12 +2490,12 @@ function generateId5() {
|
|
|
2351
2490
|
}
|
|
2352
2491
|
function makeFederationSync(source, target, syncType, payload) {
|
|
2353
2492
|
return new RCANMessage({
|
|
2354
|
-
rcan: "1.
|
|
2355
|
-
rcanVersion: "1.
|
|
2493
|
+
rcan: "2.1.0",
|
|
2494
|
+
rcanVersion: "2.1.0",
|
|
2356
2495
|
cmd: "federation_sync",
|
|
2357
2496
|
target,
|
|
2358
2497
|
params: {
|
|
2359
|
-
msg_type:
|
|
2498
|
+
msg_type: 23 /* FLEET_COMMAND */,
|
|
2360
2499
|
msg_id: generateId5(),
|
|
2361
2500
|
source_registry: source,
|
|
2362
2501
|
target_registry: target,
|
|
@@ -2383,17 +2522,17 @@ async function validateCrossRegistryCommand(msg, localRegistry, trustCache) {
|
|
|
2383
2522
|
reason: `REGISTRY_UNKNOWN: ${sourceRegistry} is not in the local trust cache`
|
|
2384
2523
|
};
|
|
2385
2524
|
}
|
|
2386
|
-
let loa = 1 /*
|
|
2525
|
+
let loa = 1 /* GUEST */;
|
|
2387
2526
|
const registryJwt = msg.params?.["registry_jwt"];
|
|
2388
2527
|
if (registryJwt) {
|
|
2389
2528
|
loa = extractLoaFromJwt(registryJwt);
|
|
2390
2529
|
} else if (typeof msg.loa === "number") {
|
|
2391
2530
|
loa = msg.loa;
|
|
2392
2531
|
}
|
|
2393
|
-
if (loa < 2 /*
|
|
2532
|
+
if (loa < 2 /* OPERATOR */) {
|
|
2394
2533
|
return {
|
|
2395
2534
|
valid: false,
|
|
2396
|
-
reason: `LOA_INSUFFICIENT: cross-registry commands require LoA>=2 (
|
|
2535
|
+
reason: `LOA_INSUFFICIENT: cross-registry commands require LoA>=2 (OPERATOR), got role=${loa}`
|
|
2397
2536
|
};
|
|
2398
2537
|
}
|
|
2399
2538
|
return { valid: true, reason: "cross-registry command accepted" };
|
|
@@ -2482,7 +2621,8 @@ async function sha256Bytes(input) {
|
|
|
2482
2621
|
const encoded = new TextEncoder().encode(input);
|
|
2483
2622
|
const ab = new ArrayBuffer(encoded.byteLength);
|
|
2484
2623
|
new Uint8Array(ab).set(encoded);
|
|
2485
|
-
const
|
|
2624
|
+
const subtle = globalThis.crypto?.subtle ?? (await import("crypto")).webcrypto.subtle;
|
|
2625
|
+
const hashBuffer = await subtle.digest("SHA-256", ab);
|
|
2486
2626
|
return new Uint8Array(hashBuffer);
|
|
2487
2627
|
}
|
|
2488
2628
|
async function encodeMinimal(message) {
|
|
@@ -2633,9 +2773,10 @@ function generateId6() {
|
|
|
2633
2773
|
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
|
|
2634
2774
|
}
|
|
2635
2775
|
async function computeSha256Hex(data) {
|
|
2776
|
+
const subtle = globalThis.crypto?.subtle ?? (await import("crypto")).webcrypto.subtle;
|
|
2636
2777
|
const ab = new ArrayBuffer(data.byteLength);
|
|
2637
2778
|
new Uint8Array(ab).set(data);
|
|
2638
|
-
const hashBuffer = await
|
|
2779
|
+
const hashBuffer = await subtle.digest("SHA-256", ab);
|
|
2639
2780
|
const hashArray = new Uint8Array(hashBuffer);
|
|
2640
2781
|
return Array.from(hashArray).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2641
2782
|
}
|
|
@@ -2728,7 +2869,7 @@ async function makeTrainingDataMessage(media) {
|
|
|
2728
2869
|
cmd: "training_data",
|
|
2729
2870
|
target: "rcan://training/data",
|
|
2730
2871
|
params: {
|
|
2731
|
-
msg_type:
|
|
2872
|
+
msg_type: 36 /* TRAINING_DATA */,
|
|
2732
2873
|
msg_id: generateId6()
|
|
2733
2874
|
},
|
|
2734
2875
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2761,7 +2902,7 @@ async function makeStreamChunk(streamId, data, mimeType, chunkIndex, isFinal) {
|
|
|
2761
2902
|
cmd: "stream_chunk",
|
|
2762
2903
|
target: "rcan://streaming/chunk",
|
|
2763
2904
|
params: {
|
|
2764
|
-
msg_type:
|
|
2905
|
+
msg_type: 29 /* SENSOR_DATA */,
|
|
2765
2906
|
msg_id: generateId6(),
|
|
2766
2907
|
stream_chunk: streamChunkMeta
|
|
2767
2908
|
},
|
|
@@ -2771,24 +2912,372 @@ async function makeStreamChunk(streamId, data, mimeType, chunkIndex, isFinal) {
|
|
|
2771
2912
|
return msg;
|
|
2772
2913
|
}
|
|
2773
2914
|
|
|
2915
|
+
// src/contribute.ts
|
|
2916
|
+
var CONTRIBUTE_SCOPE_LEVEL = 2.5;
|
|
2917
|
+
var _idCounter = 0;
|
|
2918
|
+
function _generateId() {
|
|
2919
|
+
return `cr-${Date.now()}-${++_idCounter}`;
|
|
2920
|
+
}
|
|
2921
|
+
function makeContributeRequest(params = {}) {
|
|
2922
|
+
return {
|
|
2923
|
+
type: 33 /* CONTRIBUTE_REQUEST */,
|
|
2924
|
+
request_id: params.request_id ?? _generateId(),
|
|
2925
|
+
project_id: params.project_id ?? "",
|
|
2926
|
+
project_name: params.project_name ?? "",
|
|
2927
|
+
work_unit_id: params.work_unit_id ?? "",
|
|
2928
|
+
resource_type: params.resource_type ?? "cpu",
|
|
2929
|
+
estimated_duration_s: params.estimated_duration_s ?? 0,
|
|
2930
|
+
priority: params.priority ?? 0,
|
|
2931
|
+
payload: params.payload ?? {},
|
|
2932
|
+
timestamp: params.timestamp ?? Date.now() / 1e3
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
function makeContributeResult(params = {}) {
|
|
2936
|
+
const result = {
|
|
2937
|
+
type: 34 /* CONTRIBUTE_RESULT */,
|
|
2938
|
+
request_id: params.request_id ?? "",
|
|
2939
|
+
work_unit_id: params.work_unit_id ?? "",
|
|
2940
|
+
status: params.status ?? "completed",
|
|
2941
|
+
resource_type: params.resource_type ?? "cpu",
|
|
2942
|
+
duration_s: params.duration_s ?? 0,
|
|
2943
|
+
compute_units: params.compute_units ?? 0,
|
|
2944
|
+
result_payload: params.result_payload ?? {},
|
|
2945
|
+
timestamp: params.timestamp ?? Date.now() / 1e3
|
|
2946
|
+
};
|
|
2947
|
+
if (params.error_message !== void 0) {
|
|
2948
|
+
result.error_message = params.error_message;
|
|
2949
|
+
}
|
|
2950
|
+
return result;
|
|
2951
|
+
}
|
|
2952
|
+
function makeContributeCancel(params = {}) {
|
|
2953
|
+
return {
|
|
2954
|
+
type: 35 /* CONTRIBUTE_CANCEL */,
|
|
2955
|
+
request_id: params.request_id ?? "",
|
|
2956
|
+
work_unit_id: params.work_unit_id ?? "",
|
|
2957
|
+
reason: params.reason ?? "",
|
|
2958
|
+
timestamp: params.timestamp ?? Date.now() / 1e3
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
function validateContributeScope(scopeLevel, action = "request") {
|
|
2962
|
+
if (action === "request" || action === "result") {
|
|
2963
|
+
return scopeLevel >= CONTRIBUTE_SCOPE_LEVEL;
|
|
2964
|
+
}
|
|
2965
|
+
if (action === "cancel") {
|
|
2966
|
+
return scopeLevel >= 2;
|
|
2967
|
+
}
|
|
2968
|
+
return false;
|
|
2969
|
+
}
|
|
2970
|
+
function isPreemptedBy(scopeLevel) {
|
|
2971
|
+
return scopeLevel >= 3;
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
// src/competition.ts
|
|
2975
|
+
var COMPETITION_SCOPE_LEVEL = 2;
|
|
2976
|
+
var _idCounter2 = 0;
|
|
2977
|
+
function _generateRunId() {
|
|
2978
|
+
return `run-${Date.now()}-${++_idCounter2}`;
|
|
2979
|
+
}
|
|
2980
|
+
function makeCompetitionEnter(params = {}) {
|
|
2981
|
+
return {
|
|
2982
|
+
type: 37 /* COMPETITION_ENTER */,
|
|
2983
|
+
competition_id: params.competition_id ?? "",
|
|
2984
|
+
competition_format: params.competition_format ?? "sprint",
|
|
2985
|
+
hardware_tier: params.hardware_tier ?? "",
|
|
2986
|
+
model_id: params.model_id ?? "",
|
|
2987
|
+
robot_rrn: params.robot_rrn ?? "",
|
|
2988
|
+
entered_at: params.entered_at ?? Date.now() / 1e3
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
function makeCompetitionScore(params = {}) {
|
|
2992
|
+
const score = params.score ?? 0;
|
|
2993
|
+
if (score < 0 || score > 1) {
|
|
2994
|
+
throw new Error(`score must be in [0.0, 1.0], got ${score}`);
|
|
2995
|
+
}
|
|
2996
|
+
return {
|
|
2997
|
+
type: 38 /* COMPETITION_SCORE */,
|
|
2998
|
+
competition_id: params.competition_id ?? "",
|
|
2999
|
+
candidate_id: params.candidate_id ?? "",
|
|
3000
|
+
score,
|
|
3001
|
+
hardware_tier: params.hardware_tier ?? "",
|
|
3002
|
+
verified: params.verified ?? false,
|
|
3003
|
+
submitted_at: params.submitted_at ?? Date.now() / 1e3
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
function makeSeasonStanding(params = {}) {
|
|
3007
|
+
return {
|
|
3008
|
+
type: 39 /* SEASON_STANDING */,
|
|
3009
|
+
season_id: params.season_id ?? "",
|
|
3010
|
+
class_id: params.class_id ?? "",
|
|
3011
|
+
standings: params.standings ?? [],
|
|
3012
|
+
days_remaining: params.days_remaining ?? 0,
|
|
3013
|
+
broadcast_at: params.broadcast_at ?? Date.now() / 1e3
|
|
3014
|
+
};
|
|
3015
|
+
}
|
|
3016
|
+
function makePersonalResearchResult(params = {}) {
|
|
3017
|
+
const score = params.score ?? 0;
|
|
3018
|
+
if (score < 0 || score > 1) {
|
|
3019
|
+
throw new Error(`score must be in [0.0, 1.0], got ${score}`);
|
|
3020
|
+
}
|
|
3021
|
+
return {
|
|
3022
|
+
type: 40 /* PERSONAL_RESEARCH_RESULT */,
|
|
3023
|
+
run_id: params.run_id ?? _generateRunId(),
|
|
3024
|
+
run_type: params.run_type ?? "personal",
|
|
3025
|
+
candidate_id: params.candidate_id ?? "",
|
|
3026
|
+
score,
|
|
3027
|
+
hardware_tier: params.hardware_tier ?? "",
|
|
3028
|
+
model_id: params.model_id ?? "",
|
|
3029
|
+
owner_uid: params.owner_uid ?? "",
|
|
3030
|
+
metrics: params.metrics ?? {
|
|
3031
|
+
success_rate: 0,
|
|
3032
|
+
p66_rate: 0,
|
|
3033
|
+
token_efficiency: 0,
|
|
3034
|
+
latency_score: 0
|
|
3035
|
+
},
|
|
3036
|
+
submitted_to_community: params.submitted_to_community ?? false,
|
|
3037
|
+
created_at: params.created_at ?? Date.now() / 1e3
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
function validateCompetitionScope(scopeLevel) {
|
|
3041
|
+
return scopeLevel >= COMPETITION_SCOPE_LEVEL;
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
// src/firmware.ts
|
|
3045
|
+
var FIRMWARE_MANIFEST_PATH = "/.well-known/rcan-firmware-manifest.json";
|
|
3046
|
+
function manifestToWire(m) {
|
|
3047
|
+
const wire = {
|
|
3048
|
+
rrn: m.rrn,
|
|
3049
|
+
firmware_version: m.firmwareVersion,
|
|
3050
|
+
build_hash: m.buildHash,
|
|
3051
|
+
components: m.components,
|
|
3052
|
+
signed_at: m.signedAt
|
|
3053
|
+
};
|
|
3054
|
+
if (m.signature) wire.signature = m.signature;
|
|
3055
|
+
return wire;
|
|
3056
|
+
}
|
|
3057
|
+
function manifestFromWire(w) {
|
|
3058
|
+
return {
|
|
3059
|
+
rrn: w.rrn,
|
|
3060
|
+
firmwareVersion: w.firmware_version,
|
|
3061
|
+
buildHash: w.build_hash,
|
|
3062
|
+
components: w.components ?? [],
|
|
3063
|
+
signedAt: w.signed_at ?? "",
|
|
3064
|
+
signature: w.signature
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
function canonicalManifestJson(m) {
|
|
3068
|
+
const obj = {
|
|
3069
|
+
build_hash: m.buildHash,
|
|
3070
|
+
components: m.components.map((c) => ({
|
|
3071
|
+
hash: c.hash,
|
|
3072
|
+
name: c.name,
|
|
3073
|
+
version: c.version
|
|
3074
|
+
})),
|
|
3075
|
+
firmware_version: m.firmwareVersion,
|
|
3076
|
+
rrn: m.rrn,
|
|
3077
|
+
signed_at: m.signedAt
|
|
3078
|
+
};
|
|
3079
|
+
return JSON.stringify(obj);
|
|
3080
|
+
}
|
|
3081
|
+
var FirmwareIntegrityError = class extends Error {
|
|
3082
|
+
constructor(message) {
|
|
3083
|
+
super(message);
|
|
3084
|
+
this.name = "FirmwareIntegrityError";
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
3087
|
+
function validateManifest(m) {
|
|
3088
|
+
const errors = [];
|
|
3089
|
+
if (!m.rrn) errors.push("rrn is required");
|
|
3090
|
+
if (!m.firmwareVersion) errors.push("firmwareVersion is required");
|
|
3091
|
+
if (!m.buildHash) errors.push("buildHash is required");
|
|
3092
|
+
if (!m.buildHash.startsWith("sha256:")) errors.push("buildHash must start with 'sha256:'");
|
|
3093
|
+
if (!m.signedAt) errors.push("signedAt is required");
|
|
3094
|
+
if (!m.signature) errors.push("signature is required (manifest must be signed)");
|
|
3095
|
+
for (const [i, c] of m.components.entries()) {
|
|
3096
|
+
if (!c.name) errors.push(`components[${i}].name is required`);
|
|
3097
|
+
if (!c.version) errors.push(`components[${i}].version is required`);
|
|
3098
|
+
if (!c.hash.startsWith("sha256:")) errors.push(`components[${i}].hash must start with 'sha256:'`);
|
|
3099
|
+
}
|
|
3100
|
+
return errors;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
// src/authority.ts
|
|
3104
|
+
function authorityAccessToWire(p) {
|
|
3105
|
+
return {
|
|
3106
|
+
request_id: p.requestId,
|
|
3107
|
+
authority_id: p.authorityId,
|
|
3108
|
+
requested_data: p.requestedData,
|
|
3109
|
+
justification: p.justification,
|
|
3110
|
+
expires_at: p.expiresAt
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
function authorityAccessFromWire(w) {
|
|
3114
|
+
return {
|
|
3115
|
+
requestId: w.request_id,
|
|
3116
|
+
authorityId: w.authority_id,
|
|
3117
|
+
requestedData: w.requested_data ?? [],
|
|
3118
|
+
justification: w.justification ?? "",
|
|
3119
|
+
expiresAt: w.expires_at ?? 0
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
function validateAuthorityAccess(p) {
|
|
3123
|
+
const errors = [];
|
|
3124
|
+
if (!p.requestId) errors.push("requestId is required");
|
|
3125
|
+
if (!p.authorityId) errors.push("authorityId is required");
|
|
3126
|
+
if (!p.requestedData || p.requestedData.length === 0)
|
|
3127
|
+
errors.push("requestedData must include at least one category");
|
|
3128
|
+
if (!p.justification) errors.push("justification is required");
|
|
3129
|
+
if (!p.expiresAt || p.expiresAt <= 0) errors.push("expiresAt must be a positive Unix timestamp");
|
|
3130
|
+
if (p.expiresAt < Date.now() / 1e3) errors.push("expiresAt is in the past \u2014 request has expired");
|
|
3131
|
+
return errors;
|
|
3132
|
+
}
|
|
3133
|
+
function isAuthorityRequestValid(p) {
|
|
3134
|
+
return Date.now() / 1e3 < p.expiresAt && validateAuthorityAccess(p).length === 0;
|
|
3135
|
+
}
|
|
3136
|
+
var AUTHORITY_ERROR_CODES = {
|
|
3137
|
+
NOT_RECOGNIZED: "AUTHORITY_NOT_RECOGNIZED",
|
|
3138
|
+
REQUEST_EXPIRED: "AUTHORITY_REQUEST_EXPIRED",
|
|
3139
|
+
INVALID_TOKEN: "AUTHORITY_INVALID_TOKEN",
|
|
3140
|
+
RATE_LIMITED: "AUTHORITY_RATE_LIMITED"
|
|
3141
|
+
};
|
|
3142
|
+
|
|
3143
|
+
// src/m2m.ts
|
|
3144
|
+
var RRF_REVOCATION_URL = "https://api.rrf.rcan.dev/v2/revocations";
|
|
3145
|
+
var M2M_TRUSTED_ISSUER = "rrf.rcan.dev";
|
|
3146
|
+
var RRF_REVOCATION_CACHE_TTL_MS = 55e3;
|
|
3147
|
+
var M2MAuthError = class extends Error {
|
|
3148
|
+
constructor(message) {
|
|
3149
|
+
super(message);
|
|
3150
|
+
this.name = "M2MAuthError";
|
|
3151
|
+
}
|
|
3152
|
+
};
|
|
3153
|
+
function decodeJwtPayload2(token) {
|
|
3154
|
+
const parts = token.split(".");
|
|
3155
|
+
if (parts.length < 2) throw new M2MAuthError("Invalid JWT structure");
|
|
3156
|
+
const b64 = (parts[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
|
|
3157
|
+
const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
|
|
3158
|
+
try {
|
|
3159
|
+
return JSON.parse(atob(padded));
|
|
3160
|
+
} catch (e) {
|
|
3161
|
+
throw new M2MAuthError(`JWT payload decode failed: ${String(e)}`);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
function parseM2mPeerToken(token) {
|
|
3165
|
+
const payload = decodeJwtPayload2(token);
|
|
3166
|
+
const exp = Number(payload["exp"] ?? 0);
|
|
3167
|
+
if (exp > 0 && Date.now() / 1e3 > exp) {
|
|
3168
|
+
throw new M2MAuthError(`M2M_PEER token expired (sub=${String(payload["sub"])})`);
|
|
3169
|
+
}
|
|
3170
|
+
const peerRrn = String(payload["peer_rrn"] ?? "");
|
|
3171
|
+
if (!peerRrn) throw new M2MAuthError("M2M_PEER token missing peer_rrn claim");
|
|
3172
|
+
return {
|
|
3173
|
+
sub: String(payload["sub"] ?? ""),
|
|
3174
|
+
peerRrn,
|
|
3175
|
+
scopes: Array.isArray(payload["rcan_scopes"]) ? payload["rcan_scopes"] : Array.isArray(payload["scopes"]) ? payload["scopes"] : [],
|
|
3176
|
+
exp,
|
|
3177
|
+
iss: String(payload["iss"] ?? "")
|
|
3178
|
+
};
|
|
3179
|
+
}
|
|
3180
|
+
function parseM2mTrustedToken(token) {
|
|
3181
|
+
const payload = decodeJwtPayload2(token);
|
|
3182
|
+
const iss = String(payload["iss"] ?? "");
|
|
3183
|
+
if (iss !== M2M_TRUSTED_ISSUER) {
|
|
3184
|
+
throw new M2MAuthError(
|
|
3185
|
+
`M2M_TRUSTED issuer must be '${M2M_TRUSTED_ISSUER}', got '${iss}'`
|
|
3186
|
+
);
|
|
3187
|
+
}
|
|
3188
|
+
const scopes = Array.isArray(payload["rcan_scopes"]) ? payload["rcan_scopes"] : Array.isArray(payload["scopes"]) ? payload["scopes"] : [];
|
|
3189
|
+
if (!scopes.includes("fleet.trusted")) {
|
|
3190
|
+
throw new M2MAuthError("M2M_TRUSTED token missing required 'fleet.trusted' scope");
|
|
3191
|
+
}
|
|
3192
|
+
const exp = Number(payload["exp"] ?? 0);
|
|
3193
|
+
if (exp > 0 && Date.now() / 1e3 > exp) {
|
|
3194
|
+
throw new M2MAuthError(`M2M_TRUSTED token expired (sub=${String(payload["sub"])})`);
|
|
3195
|
+
}
|
|
3196
|
+
const rrfSig = String(payload["rrf_sig"] ?? "");
|
|
3197
|
+
if (!rrfSig) throw new M2MAuthError("M2M_TRUSTED token missing rrf_sig claim");
|
|
3198
|
+
const fleetRrns = Array.isArray(payload["fleet_rrns"]) ? payload["fleet_rrns"] : [];
|
|
3199
|
+
return {
|
|
3200
|
+
sub: String(payload["sub"] ?? ""),
|
|
3201
|
+
fleetRrns,
|
|
3202
|
+
scopes,
|
|
3203
|
+
exp,
|
|
3204
|
+
iss,
|
|
3205
|
+
rrfSig
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
function verifyM2mTrustedTokenClaims(token, targetRrn) {
|
|
3209
|
+
const claims = parseM2mTrustedToken(token);
|
|
3210
|
+
if (!claims.fleetRrns.includes(targetRrn)) {
|
|
3211
|
+
throw new M2MAuthError(
|
|
3212
|
+
`M2M_TRUSTED token does not authorize commanding '${targetRrn}'. Authorized fleet: [${claims.fleetRrns.join(", ")}]`
|
|
3213
|
+
);
|
|
3214
|
+
}
|
|
3215
|
+
return claims;
|
|
3216
|
+
}
|
|
3217
|
+
var _revocationCache = null;
|
|
3218
|
+
async function fetchRRFRevocations(url = RRF_REVOCATION_URL) {
|
|
3219
|
+
const now = Date.now();
|
|
3220
|
+
if (_revocationCache && now - _revocationCache.fetchedAt < RRF_REVOCATION_CACHE_TTL_MS) {
|
|
3221
|
+
return _revocationCache;
|
|
3222
|
+
}
|
|
3223
|
+
try {
|
|
3224
|
+
const resp = await fetch(url, { signal: AbortSignal.timeout?.(5e3) });
|
|
3225
|
+
const data = await resp.json();
|
|
3226
|
+
_revocationCache = {
|
|
3227
|
+
revokedOrchestrators: new Set(data.revoked_orchestrators ?? []),
|
|
3228
|
+
revokedJtis: new Set(data.revoked_jtis ?? []),
|
|
3229
|
+
fetchedAt: now
|
|
3230
|
+
};
|
|
3231
|
+
} catch {
|
|
3232
|
+
if (_revocationCache) return _revocationCache;
|
|
3233
|
+
_revocationCache = { revokedOrchestrators: /* @__PURE__ */ new Set(), revokedJtis: /* @__PURE__ */ new Set(), fetchedAt: now };
|
|
3234
|
+
}
|
|
3235
|
+
return _revocationCache;
|
|
3236
|
+
}
|
|
3237
|
+
async function isM2mTrustedRevoked(claims, jti) {
|
|
3238
|
+
const cache = await fetchRRFRevocations();
|
|
3239
|
+
if (cache.revokedOrchestrators.has(claims.sub)) return true;
|
|
3240
|
+
if (jti && cache.revokedJtis.has(jti)) return true;
|
|
3241
|
+
return false;
|
|
3242
|
+
}
|
|
3243
|
+
async function verifyM2mTrustedToken(token, targetRrn, options) {
|
|
3244
|
+
const claims = verifyM2mTrustedTokenClaims(token, targetRrn);
|
|
3245
|
+
if (!options?.skipRevocationCheck) {
|
|
3246
|
+
const revoked = await isM2mTrustedRevoked(claims);
|
|
3247
|
+
if (revoked) {
|
|
3248
|
+
throw new M2MAuthError(
|
|
3249
|
+
`M2M_TRUSTED orchestrator '${claims.sub}' is on the RRF revocation list`
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
return claims;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
2774
3256
|
// src/index.ts
|
|
2775
3257
|
var VERSION = "0.6.0";
|
|
2776
3258
|
var RCAN_VERSION = "1.6";
|
|
2777
3259
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2778
3260
|
0 && (module.exports = {
|
|
3261
|
+
AUTHORITY_ERROR_CODES,
|
|
2779
3262
|
AuditChain,
|
|
2780
3263
|
AuditError,
|
|
3264
|
+
COMPETITION_SCOPE_LEVEL,
|
|
3265
|
+
CONTRIBUTE_SCOPE_LEVEL,
|
|
2781
3266
|
ClockDriftError,
|
|
2782
3267
|
CommitmentRecord,
|
|
2783
3268
|
ConfidenceGate,
|
|
2784
3269
|
DEFAULT_LOA_POLICY,
|
|
2785
3270
|
DataCategory,
|
|
3271
|
+
FIRMWARE_MANIFEST_PATH,
|
|
2786
3272
|
FaultCode,
|
|
2787
3273
|
FederationSyncType,
|
|
3274
|
+
FirmwareIntegrityError,
|
|
2788
3275
|
GateError,
|
|
2789
3276
|
HiTLGate,
|
|
2790
3277
|
KeyStore,
|
|
2791
3278
|
LevelOfAssurance,
|
|
3279
|
+
M2MAuthError,
|
|
3280
|
+
M2M_TRUSTED_ISSUER,
|
|
2792
3281
|
MediaEncoding,
|
|
2793
3282
|
MessageType,
|
|
2794
3283
|
NodeClient,
|
|
@@ -2814,13 +3303,18 @@ var RCAN_VERSION = "1.6";
|
|
|
2814
3303
|
RCANValidationError,
|
|
2815
3304
|
RCANVersionIncompatibleError,
|
|
2816
3305
|
RCAN_VERSION,
|
|
3306
|
+
ROLE_JWT_LEVEL,
|
|
3307
|
+
RRF_REVOCATION_CACHE_TTL_MS,
|
|
3308
|
+
RRF_REVOCATION_URL,
|
|
2817
3309
|
RegistryClient,
|
|
2818
3310
|
RegistryTier,
|
|
2819
3311
|
ReplayCache,
|
|
2820
3312
|
RevocationCache,
|
|
2821
3313
|
RobotURI,
|
|
2822
3314
|
RobotURIError,
|
|
3315
|
+
Role,
|
|
2823
3316
|
SAFETY_MESSAGE_TYPE,
|
|
3317
|
+
SCOPE_MIN_ROLE,
|
|
2824
3318
|
SDK_VERSION,
|
|
2825
3319
|
SPEC_VERSION,
|
|
2826
3320
|
TransportEncoding,
|
|
@@ -2831,6 +3325,9 @@ var RCAN_VERSION = "1.6";
|
|
|
2831
3325
|
addMediaInline,
|
|
2832
3326
|
addMediaRef,
|
|
2833
3327
|
assertClockSynced,
|
|
3328
|
+
authorityAccessFromWire,
|
|
3329
|
+
authorityAccessToWire,
|
|
3330
|
+
canonicalManifestJson,
|
|
2834
3331
|
checkClockSync,
|
|
2835
3332
|
checkRevocation,
|
|
2836
3333
|
decodeBleFrames,
|
|
@@ -2839,21 +3336,34 @@ var RCAN_VERSION = "1.6";
|
|
|
2839
3336
|
encodeBleFrames,
|
|
2840
3337
|
encodeCompact,
|
|
2841
3338
|
encodeMinimal,
|
|
3339
|
+
extractIdentityFromJwt,
|
|
2842
3340
|
extractLoaFromJwt,
|
|
3341
|
+
extractRoleFromJwt,
|
|
2843
3342
|
fetchCanonicalSchema,
|
|
3343
|
+
fetchRRFRevocations,
|
|
3344
|
+
isAuthorityRequestValid,
|
|
3345
|
+
isM2mTrustedRevoked,
|
|
3346
|
+
isPreemptedBy,
|
|
2844
3347
|
isSafetyMessage,
|
|
2845
3348
|
makeCloudRelayMessage,
|
|
3349
|
+
makeCompetitionEnter,
|
|
3350
|
+
makeCompetitionScore,
|
|
2846
3351
|
makeConfigUpdate,
|
|
2847
3352
|
makeConsentDeny,
|
|
2848
3353
|
makeConsentGrant,
|
|
2849
3354
|
makeConsentRequest,
|
|
3355
|
+
makeContributeCancel,
|
|
3356
|
+
makeContributeRequest,
|
|
3357
|
+
makeContributeResult,
|
|
2850
3358
|
makeEstopMessage,
|
|
2851
3359
|
makeEstopWithQoS,
|
|
2852
3360
|
makeFaultReport,
|
|
2853
3361
|
makeFederationSync,
|
|
2854
3362
|
makeKeyRotationMessage,
|
|
3363
|
+
makePersonalResearchResult,
|
|
2855
3364
|
makeResumeMessage,
|
|
2856
3365
|
makeRevocationBroadcast,
|
|
3366
|
+
makeSeasonStanding,
|
|
2857
3367
|
makeStopMessage,
|
|
2858
3368
|
makeStreamChunk,
|
|
2859
3369
|
makeTrainingConsentDeny,
|
|
@@ -2861,21 +3371,33 @@ var RCAN_VERSION = "1.6";
|
|
|
2861
3371
|
makeTrainingConsentRequest,
|
|
2862
3372
|
makeTrainingDataMessage,
|
|
2863
3373
|
makeTransparencyMessage,
|
|
3374
|
+
manifestFromWire,
|
|
3375
|
+
manifestToWire,
|
|
3376
|
+
parseM2mPeerToken,
|
|
3377
|
+
parseM2mTrustedToken,
|
|
3378
|
+
roleFromJwtLevel,
|
|
2864
3379
|
selectTransport,
|
|
3380
|
+
validateAuthorityAccess,
|
|
3381
|
+
validateCompetitionScope,
|
|
2865
3382
|
validateConfig,
|
|
2866
3383
|
validateConfigAgainstSchema,
|
|
2867
3384
|
validateConfigUpdate,
|
|
2868
3385
|
validateConsentMessage,
|
|
3386
|
+
validateContributeScope,
|
|
2869
3387
|
validateCrossRegistryCommand,
|
|
2870
3388
|
validateDelegationChain,
|
|
2871
3389
|
validateLoaForScope,
|
|
3390
|
+
validateManifest,
|
|
2872
3391
|
validateMediaChunks,
|
|
2873
3392
|
validateMessage,
|
|
2874
3393
|
validateNodeAgainstSchema,
|
|
2875
3394
|
validateReplay,
|
|
3395
|
+
validateRoleForScope,
|
|
2876
3396
|
validateSafetyMessage,
|
|
2877
3397
|
validateTrainingDataMessage,
|
|
2878
3398
|
validateURI,
|
|
2879
|
-
validateVersionCompat
|
|
3399
|
+
validateVersionCompat,
|
|
3400
|
+
verifyM2mTrustedToken,
|
|
3401
|
+
verifyM2mTrustedTokenClaims
|
|
2880
3402
|
});
|
|
2881
3403
|
//# sourceMappingURL=index.js.map
|