@continuonai/rcan-ts 0.8.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/dist/index.js CHANGED
@@ -30,20 +30,26 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AUTHORITY_ERROR_CODES: () => AUTHORITY_ERROR_CODES,
33
34
  AuditChain: () => AuditChain,
34
35
  AuditError: () => AuditError,
36
+ COMPETITION_SCOPE_LEVEL: () => COMPETITION_SCOPE_LEVEL,
35
37
  CONTRIBUTE_SCOPE_LEVEL: () => CONTRIBUTE_SCOPE_LEVEL,
36
38
  ClockDriftError: () => ClockDriftError,
37
39
  CommitmentRecord: () => CommitmentRecord,
38
40
  ConfidenceGate: () => ConfidenceGate,
39
41
  DEFAULT_LOA_POLICY: () => DEFAULT_LOA_POLICY,
40
42
  DataCategory: () => DataCategory,
43
+ FIRMWARE_MANIFEST_PATH: () => FIRMWARE_MANIFEST_PATH,
41
44
  FaultCode: () => FaultCode,
42
45
  FederationSyncType: () => FederationSyncType,
46
+ FirmwareIntegrityError: () => FirmwareIntegrityError,
43
47
  GateError: () => GateError,
44
48
  HiTLGate: () => HiTLGate,
45
49
  KeyStore: () => KeyStore,
46
50
  LevelOfAssurance: () => LevelOfAssurance,
51
+ M2MAuthError: () => M2MAuthError,
52
+ M2M_TRUSTED_ISSUER: () => M2M_TRUSTED_ISSUER,
47
53
  MediaEncoding: () => MediaEncoding,
48
54
  MessageType: () => MessageType,
49
55
  NodeClient: () => NodeClient,
@@ -69,13 +75,18 @@ __export(index_exports, {
69
75
  RCANValidationError: () => RCANValidationError,
70
76
  RCANVersionIncompatibleError: () => RCANVersionIncompatibleError,
71
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,
72
81
  RegistryClient: () => RegistryClient,
73
82
  RegistryTier: () => RegistryTier,
74
83
  ReplayCache: () => ReplayCache,
75
84
  RevocationCache: () => RevocationCache,
76
85
  RobotURI: () => RobotURI,
77
86
  RobotURIError: () => RobotURIError,
87
+ Role: () => Role,
78
88
  SAFETY_MESSAGE_TYPE: () => SAFETY_MESSAGE_TYPE,
89
+ SCOPE_MIN_ROLE: () => SCOPE_MIN_ROLE,
79
90
  SDK_VERSION: () => SDK_VERSION,
80
91
  SPEC_VERSION: () => SPEC_VERSION,
81
92
  TransportEncoding: () => TransportEncoding,
@@ -86,6 +97,9 @@ __export(index_exports, {
86
97
  addMediaInline: () => addMediaInline,
87
98
  addMediaRef: () => addMediaRef,
88
99
  assertClockSynced: () => assertClockSynced,
100
+ authorityAccessFromWire: () => authorityAccessFromWire,
101
+ authorityAccessToWire: () => authorityAccessToWire,
102
+ canonicalManifestJson: () => canonicalManifestJson,
89
103
  checkClockSync: () => checkClockSync,
90
104
  checkRevocation: () => checkRevocation,
91
105
  decodeBleFrames: () => decodeBleFrames,
@@ -94,11 +108,18 @@ __export(index_exports, {
94
108
  encodeBleFrames: () => encodeBleFrames,
95
109
  encodeCompact: () => encodeCompact,
96
110
  encodeMinimal: () => encodeMinimal,
111
+ extractIdentityFromJwt: () => extractIdentityFromJwt,
97
112
  extractLoaFromJwt: () => extractLoaFromJwt,
113
+ extractRoleFromJwt: () => extractRoleFromJwt,
98
114
  fetchCanonicalSchema: () => fetchCanonicalSchema,
115
+ fetchRRFRevocations: () => fetchRRFRevocations,
116
+ isAuthorityRequestValid: () => isAuthorityRequestValid,
117
+ isM2mTrustedRevoked: () => isM2mTrustedRevoked,
99
118
  isPreemptedBy: () => isPreemptedBy,
100
119
  isSafetyMessage: () => isSafetyMessage,
101
120
  makeCloudRelayMessage: () => makeCloudRelayMessage,
121
+ makeCompetitionEnter: () => makeCompetitionEnter,
122
+ makeCompetitionScore: () => makeCompetitionScore,
102
123
  makeConfigUpdate: () => makeConfigUpdate,
103
124
  makeConsentDeny: () => makeConsentDeny,
104
125
  makeConsentGrant: () => makeConsentGrant,
@@ -111,8 +132,10 @@ __export(index_exports, {
111
132
  makeFaultReport: () => makeFaultReport,
112
133
  makeFederationSync: () => makeFederationSync,
113
134
  makeKeyRotationMessage: () => makeKeyRotationMessage,
135
+ makePersonalResearchResult: () => makePersonalResearchResult,
114
136
  makeResumeMessage: () => makeResumeMessage,
115
137
  makeRevocationBroadcast: () => makeRevocationBroadcast,
138
+ makeSeasonStanding: () => makeSeasonStanding,
116
139
  makeStopMessage: () => makeStopMessage,
117
140
  makeStreamChunk: () => makeStreamChunk,
118
141
  makeTrainingConsentDeny: () => makeTrainingConsentDeny,
@@ -120,7 +143,14 @@ __export(index_exports, {
120
143
  makeTrainingConsentRequest: () => makeTrainingConsentRequest,
121
144
  makeTrainingDataMessage: () => makeTrainingDataMessage,
122
145
  makeTransparencyMessage: () => makeTransparencyMessage,
146
+ manifestFromWire: () => manifestFromWire,
147
+ manifestToWire: () => manifestToWire,
148
+ parseM2mPeerToken: () => parseM2mPeerToken,
149
+ parseM2mTrustedToken: () => parseM2mTrustedToken,
150
+ roleFromJwtLevel: () => roleFromJwtLevel,
123
151
  selectTransport: () => selectTransport,
152
+ validateAuthorityAccess: () => validateAuthorityAccess,
153
+ validateCompetitionScope: () => validateCompetitionScope,
124
154
  validateConfig: () => validateConfig,
125
155
  validateConfigAgainstSchema: () => validateConfigAgainstSchema,
126
156
  validateConfigUpdate: () => validateConfigUpdate,
@@ -129,14 +159,18 @@ __export(index_exports, {
129
159
  validateCrossRegistryCommand: () => validateCrossRegistryCommand,
130
160
  validateDelegationChain: () => validateDelegationChain,
131
161
  validateLoaForScope: () => validateLoaForScope,
162
+ validateManifest: () => validateManifest,
132
163
  validateMediaChunks: () => validateMediaChunks,
133
164
  validateMessage: () => validateMessage,
134
165
  validateNodeAgainstSchema: () => validateNodeAgainstSchema,
135
166
  validateReplay: () => validateReplay,
167
+ validateRoleForScope: () => validateRoleForScope,
136
168
  validateSafetyMessage: () => validateSafetyMessage,
137
169
  validateTrainingDataMessage: () => validateTrainingDataMessage,
138
170
  validateURI: () => validateURI,
139
- validateVersionCompat: () => validateVersionCompat
171
+ validateVersionCompat: () => validateVersionCompat,
172
+ verifyM2mTrustedToken: () => verifyM2mTrustedToken,
173
+ verifyM2mTrustedTokenClaims: () => verifyM2mTrustedTokenClaims
140
174
  });
141
175
  module.exports = __toCommonJS(index_exports);
142
176
 
@@ -231,8 +265,8 @@ var RobotURI = class _RobotURI {
231
265
  };
232
266
 
233
267
  // src/version.ts
234
- var SPEC_VERSION = "1.9.0";
235
- var SDK_VERSION = "0.8.0";
268
+ var SPEC_VERSION = "2.1.0";
269
+ var SDK_VERSION = "1.1.0";
236
270
  function validateVersionCompat(incomingVersion, localVersion = SPEC_VERSION) {
237
271
  const parseParts = (v) => {
238
272
  const parts = v.split(".");
@@ -283,9 +317,14 @@ var MessageType = /* @__PURE__ */ ((MessageType2) => {
283
317
  MessageType2[MessageType2["CONTRIBUTE_RESULT"] = 34] = "CONTRIBUTE_RESULT";
284
318
  MessageType2[MessageType2["CONTRIBUTE_CANCEL"] = 35] = "CONTRIBUTE_CANCEL";
285
319
  MessageType2[MessageType2["TRAINING_DATA"] = 36] = "TRAINING_DATA";
286
- MessageType2[MessageType2["FEDERATION_SYNC"] = 23] = "FEDERATION_SYNC";
287
- MessageType2[MessageType2["ALERT"] = 26] = "ALERT";
288
- MessageType2[MessageType2["AUDIT"] = 16] = "AUDIT";
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";
289
328
  return MessageType2;
290
329
  })(MessageType || {});
291
330
  var RCANMessageError = class extends Error {
@@ -321,6 +360,10 @@ var RCANMessage = class _RCANMessage {
321
360
  transportEncoding;
322
361
  /** v1.6: GAP-18 multi-modal media chunks */
323
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;
324
367
  constructor(data) {
325
368
  if (!data.cmd || data.cmd.trim() === "") {
326
369
  throw new RCANMessageError("'cmd' is required");
@@ -349,6 +392,13 @@ var RCANMessage = class _RCANMessage {
349
392
  this.loa = data.loa;
350
393
  this.transportEncoding = data.transportEncoding;
351
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
+ }
352
402
  if (this.confidence !== void 0) {
353
403
  if (this.confidence < 0 || this.confidence > 1) {
354
404
  throw new RCANMessageError(
@@ -390,6 +440,8 @@ var RCANMessage = class _RCANMessage {
390
440
  if (this.loa !== void 0) obj.loa = this.loa;
391
441
  if (this.transportEncoding !== void 0) obj.transportEncoding = this.transportEncoding;
392
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;
393
445
  return obj;
394
446
  }
395
447
  /** Serialize to JSON string */
@@ -432,7 +484,9 @@ var RCANMessage = class _RCANMessage {
432
484
  readOnly: obj.readOnly,
433
485
  loa: obj.loa,
434
486
  transportEncoding: obj.transportEncoding,
435
- mediaChunks: obj.mediaChunks
487
+ mediaChunks: obj.mediaChunks,
488
+ firmwareHash: obj.firmwareHash,
489
+ attestationRef: obj.attestationRef
436
490
  });
437
491
  }
438
492
  };
@@ -2186,80 +2240,133 @@ function makeFaultReport(params) {
2186
2240
  }
2187
2241
 
2188
2242
  // src/identity.ts
2189
- var LevelOfAssurance = /* @__PURE__ */ ((LevelOfAssurance2) => {
2190
- LevelOfAssurance2[LevelOfAssurance2["ANONYMOUS"] = 1] = "ANONYMOUS";
2191
- LevelOfAssurance2[LevelOfAssurance2["EMAIL_VERIFIED"] = 2] = "EMAIL_VERIFIED";
2192
- LevelOfAssurance2[LevelOfAssurance2["HARDWARE_TOKEN"] = 3] = "HARDWARE_TOKEN";
2193
- return LevelOfAssurance2;
2194
- })(LevelOfAssurance || {});
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
+ };
2195
2288
  var DEFAULT_LOA_POLICY = {
2196
- minLoaDiscover: 1 /* ANONYMOUS */,
2197
- minLoaStatus: 1 /* ANONYMOUS */,
2198
- minLoaChat: 1 /* ANONYMOUS */,
2199
- minLoaControl: 1 /* ANONYMOUS */,
2200
- minLoaSafety: 1 /* ANONYMOUS */
2289
+ minRoleForDiscover: 1 /* GUEST */,
2290
+ minRoleForStatus: 1 /* GUEST */,
2291
+ minRoleForChat: 1 /* GUEST */,
2292
+ minRoleForControl: 1 /* GUEST */,
2293
+ minRoleForSafety: 1 /* GUEST */
2201
2294
  };
2202
2295
  var PRODUCTION_LOA_POLICY = {
2203
- minLoaDiscover: 1 /* ANONYMOUS */,
2204
- minLoaStatus: 1 /* ANONYMOUS */,
2205
- minLoaChat: 1 /* ANONYMOUS */,
2206
- minLoaControl: 2 /* EMAIL_VERIFIED */,
2207
- minLoaSafety: 3 /* HARDWARE_TOKEN */
2296
+ minRoleForDiscover: 1 /* GUEST */,
2297
+ minRoleForStatus: 1 /* GUEST */,
2298
+ minRoleForChat: 1 /* GUEST */,
2299
+ minRoleForControl: 2 /* OPERATOR */,
2300
+ minRoleForSafety: 6 /* CREATOR */
2208
2301
  };
2209
- function extractLoaFromJwt(token) {
2302
+ function decodeJwtPayload(token) {
2210
2303
  try {
2211
2304
  const parts = token.split(".");
2212
- if (parts.length < 2) return 1 /* ANONYMOUS */;
2305
+ if (parts.length < 2) return null;
2213
2306
  const payloadB64 = (parts[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
2214
2307
  const padded = payloadB64 + "=".repeat((4 - payloadB64.length % 4) % 4);
2215
- let json;
2216
- if (typeof atob !== "undefined") {
2217
- json = atob(padded);
2218
- } else {
2219
- json = Buffer.from(padded, "base64").toString("utf-8");
2220
- }
2221
- const claims = JSON.parse(json);
2222
- const loa = claims["loa"];
2223
- if (typeof loa === "number" && loa >= 1 && loa <= 3) {
2224
- return loa;
2225
- }
2308
+ return JSON.parse(atob(padded));
2226
2309
  } catch {
2310
+ return null;
2227
2311
  }
2228
- return 1 /* ANONYMOUS */;
2229
- }
2230
- function minLoaForScope(scope, policy) {
2231
- const s = scope.toLowerCase();
2232
- switch (s) {
2233
- case "discover":
2234
- return policy.minLoaDiscover;
2235
- case "status":
2236
- return policy.minLoaStatus;
2237
- case "chat":
2238
- return policy.minLoaChat;
2239
- case "contribute":
2240
- return policy.minLoaChat;
2241
- // v1.7: between chat and control
2242
- case "control":
2243
- return policy.minLoaControl;
2244
- case "safety":
2245
- return policy.minLoaSafety;
2246
- default:
2247
- return null;
2248
- }
2249
- }
2250
- function validateLoaForScope(loa, scope, policy = DEFAULT_LOA_POLICY) {
2251
- const min = minLoaForScope(scope, policy);
2252
- if (min === null) {
2253
- return { valid: true, reason: "unknown scope; allowed by default" };
2254
- }
2255
- if (loa >= min) {
2256
- return { valid: true, reason: "ok" };
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;
2257
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"] : [];
2258
2341
  return {
2259
- valid: false,
2260
- reason: `LOA_INSUFFICIENT: scope=${scope} requires LoA>=${min}, caller has LoA=${loa}`
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
2261
2350
  };
2262
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
+ };
2360
+ }
2361
+ if (role >= required) return { ok: true, reason: "" };
2362
+ return {
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]})`
2365
+ };
2366
+ }
2367
+ function validateLoaForScope(role, scope) {
2368
+ return validateRoleForScope(role, scope);
2369
+ }
2263
2370
 
2264
2371
  // src/federation.ts
2265
2372
  var RegistryTier = /* @__PURE__ */ ((RegistryTier2) => {
@@ -2383,12 +2490,12 @@ function generateId5() {
2383
2490
  }
2384
2491
  function makeFederationSync(source, target, syncType, payload) {
2385
2492
  return new RCANMessage({
2386
- rcan: "1.6",
2387
- rcanVersion: "1.6",
2493
+ rcan: "2.1.0",
2494
+ rcanVersion: "2.1.0",
2388
2495
  cmd: "federation_sync",
2389
2496
  target,
2390
2497
  params: {
2391
- msg_type: 23 /* FEDERATION_SYNC */,
2498
+ msg_type: 23 /* FLEET_COMMAND */,
2392
2499
  msg_id: generateId5(),
2393
2500
  source_registry: source,
2394
2501
  target_registry: target,
@@ -2415,17 +2522,17 @@ async function validateCrossRegistryCommand(msg, localRegistry, trustCache) {
2415
2522
  reason: `REGISTRY_UNKNOWN: ${sourceRegistry} is not in the local trust cache`
2416
2523
  };
2417
2524
  }
2418
- let loa = 1 /* ANONYMOUS */;
2525
+ let loa = 1 /* GUEST */;
2419
2526
  const registryJwt = msg.params?.["registry_jwt"];
2420
2527
  if (registryJwt) {
2421
2528
  loa = extractLoaFromJwt(registryJwt);
2422
2529
  } else if (typeof msg.loa === "number") {
2423
2530
  loa = msg.loa;
2424
2531
  }
2425
- if (loa < 2 /* EMAIL_VERIFIED */) {
2532
+ if (loa < 2 /* OPERATOR */) {
2426
2533
  return {
2427
2534
  valid: false,
2428
- reason: `LOA_INSUFFICIENT: cross-registry commands require LoA>=2 (EMAIL_VERIFIED), got LoA=${loa}`
2535
+ reason: `LOA_INSUFFICIENT: cross-registry commands require LoA>=2 (OPERATOR), got role=${loa}`
2429
2536
  };
2430
2537
  }
2431
2538
  return { valid: true, reason: "cross-registry command accepted" };
@@ -2864,25 +2971,313 @@ function isPreemptedBy(scopeLevel) {
2864
2971
  return scopeLevel >= 3;
2865
2972
  }
2866
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
+
2867
3256
  // src/index.ts
2868
3257
  var VERSION = "0.6.0";
2869
3258
  var RCAN_VERSION = "1.6";
2870
3259
  // Annotate the CommonJS export names for ESM import in node:
2871
3260
  0 && (module.exports = {
3261
+ AUTHORITY_ERROR_CODES,
2872
3262
  AuditChain,
2873
3263
  AuditError,
3264
+ COMPETITION_SCOPE_LEVEL,
2874
3265
  CONTRIBUTE_SCOPE_LEVEL,
2875
3266
  ClockDriftError,
2876
3267
  CommitmentRecord,
2877
3268
  ConfidenceGate,
2878
3269
  DEFAULT_LOA_POLICY,
2879
3270
  DataCategory,
3271
+ FIRMWARE_MANIFEST_PATH,
2880
3272
  FaultCode,
2881
3273
  FederationSyncType,
3274
+ FirmwareIntegrityError,
2882
3275
  GateError,
2883
3276
  HiTLGate,
2884
3277
  KeyStore,
2885
3278
  LevelOfAssurance,
3279
+ M2MAuthError,
3280
+ M2M_TRUSTED_ISSUER,
2886
3281
  MediaEncoding,
2887
3282
  MessageType,
2888
3283
  NodeClient,
@@ -2908,13 +3303,18 @@ var RCAN_VERSION = "1.6";
2908
3303
  RCANValidationError,
2909
3304
  RCANVersionIncompatibleError,
2910
3305
  RCAN_VERSION,
3306
+ ROLE_JWT_LEVEL,
3307
+ RRF_REVOCATION_CACHE_TTL_MS,
3308
+ RRF_REVOCATION_URL,
2911
3309
  RegistryClient,
2912
3310
  RegistryTier,
2913
3311
  ReplayCache,
2914
3312
  RevocationCache,
2915
3313
  RobotURI,
2916
3314
  RobotURIError,
3315
+ Role,
2917
3316
  SAFETY_MESSAGE_TYPE,
3317
+ SCOPE_MIN_ROLE,
2918
3318
  SDK_VERSION,
2919
3319
  SPEC_VERSION,
2920
3320
  TransportEncoding,
@@ -2925,6 +3325,9 @@ var RCAN_VERSION = "1.6";
2925
3325
  addMediaInline,
2926
3326
  addMediaRef,
2927
3327
  assertClockSynced,
3328
+ authorityAccessFromWire,
3329
+ authorityAccessToWire,
3330
+ canonicalManifestJson,
2928
3331
  checkClockSync,
2929
3332
  checkRevocation,
2930
3333
  decodeBleFrames,
@@ -2933,11 +3336,18 @@ var RCAN_VERSION = "1.6";
2933
3336
  encodeBleFrames,
2934
3337
  encodeCompact,
2935
3338
  encodeMinimal,
3339
+ extractIdentityFromJwt,
2936
3340
  extractLoaFromJwt,
3341
+ extractRoleFromJwt,
2937
3342
  fetchCanonicalSchema,
3343
+ fetchRRFRevocations,
3344
+ isAuthorityRequestValid,
3345
+ isM2mTrustedRevoked,
2938
3346
  isPreemptedBy,
2939
3347
  isSafetyMessage,
2940
3348
  makeCloudRelayMessage,
3349
+ makeCompetitionEnter,
3350
+ makeCompetitionScore,
2941
3351
  makeConfigUpdate,
2942
3352
  makeConsentDeny,
2943
3353
  makeConsentGrant,
@@ -2950,8 +3360,10 @@ var RCAN_VERSION = "1.6";
2950
3360
  makeFaultReport,
2951
3361
  makeFederationSync,
2952
3362
  makeKeyRotationMessage,
3363
+ makePersonalResearchResult,
2953
3364
  makeResumeMessage,
2954
3365
  makeRevocationBroadcast,
3366
+ makeSeasonStanding,
2955
3367
  makeStopMessage,
2956
3368
  makeStreamChunk,
2957
3369
  makeTrainingConsentDeny,
@@ -2959,7 +3371,14 @@ var RCAN_VERSION = "1.6";
2959
3371
  makeTrainingConsentRequest,
2960
3372
  makeTrainingDataMessage,
2961
3373
  makeTransparencyMessage,
3374
+ manifestFromWire,
3375
+ manifestToWire,
3376
+ parseM2mPeerToken,
3377
+ parseM2mTrustedToken,
3378
+ roleFromJwtLevel,
2962
3379
  selectTransport,
3380
+ validateAuthorityAccess,
3381
+ validateCompetitionScope,
2963
3382
  validateConfig,
2964
3383
  validateConfigAgainstSchema,
2965
3384
  validateConfigUpdate,
@@ -2968,13 +3387,17 @@ var RCAN_VERSION = "1.6";
2968
3387
  validateCrossRegistryCommand,
2969
3388
  validateDelegationChain,
2970
3389
  validateLoaForScope,
3390
+ validateManifest,
2971
3391
  validateMediaChunks,
2972
3392
  validateMessage,
2973
3393
  validateNodeAgainstSchema,
2974
3394
  validateReplay,
3395
+ validateRoleForScope,
2975
3396
  validateSafetyMessage,
2976
3397
  validateTrainingDataMessage,
2977
3398
  validateURI,
2978
- validateVersionCompat
3399
+ validateVersionCompat,
3400
+ verifyM2mTrustedToken,
3401
+ verifyM2mTrustedTokenClaims
2979
3402
  });
2980
3403
  //# sourceMappingURL=index.js.map