@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/browser.mjs CHANGED
@@ -99,8 +99,8 @@ var RobotURI = class _RobotURI {
99
99
  };
100
100
 
101
101
  // src/version.ts
102
- var SPEC_VERSION = "1.9.0";
103
- var SDK_VERSION = "0.8.0";
102
+ var SPEC_VERSION = "2.1.0";
103
+ var SDK_VERSION = "1.1.0";
104
104
  function validateVersionCompat(incomingVersion, localVersion = SPEC_VERSION) {
105
105
  const parseParts = (v) => {
106
106
  const parts = v.split(".");
@@ -151,9 +151,14 @@ var MessageType = /* @__PURE__ */ ((MessageType2) => {
151
151
  MessageType2[MessageType2["CONTRIBUTE_RESULT"] = 34] = "CONTRIBUTE_RESULT";
152
152
  MessageType2[MessageType2["CONTRIBUTE_CANCEL"] = 35] = "CONTRIBUTE_CANCEL";
153
153
  MessageType2[MessageType2["TRAINING_DATA"] = 36] = "TRAINING_DATA";
154
- MessageType2[MessageType2["FEDERATION_SYNC"] = 23] = "FEDERATION_SYNC";
155
- MessageType2[MessageType2["ALERT"] = 26] = "ALERT";
156
- MessageType2[MessageType2["AUDIT"] = 16] = "AUDIT";
154
+ MessageType2[MessageType2["COMPETITION_ENTER"] = 37] = "COMPETITION_ENTER";
155
+ MessageType2[MessageType2["COMPETITION_SCORE"] = 38] = "COMPETITION_SCORE";
156
+ MessageType2[MessageType2["SEASON_STANDING"] = 39] = "SEASON_STANDING";
157
+ MessageType2[MessageType2["PERSONAL_RESEARCH_RESULT"] = 40] = "PERSONAL_RESEARCH_RESULT";
158
+ MessageType2[MessageType2["AUTHORITY_ACCESS"] = 41] = "AUTHORITY_ACCESS";
159
+ MessageType2[MessageType2["AUTHORITY_RESPONSE"] = 42] = "AUTHORITY_RESPONSE";
160
+ MessageType2[MessageType2["FIRMWARE_ATTESTATION"] = 43] = "FIRMWARE_ATTESTATION";
161
+ MessageType2[MessageType2["SBOM_UPDATE"] = 44] = "SBOM_UPDATE";
157
162
  return MessageType2;
158
163
  })(MessageType || {});
159
164
  var RCANMessageError = class extends Error {
@@ -190,6 +195,10 @@ var RCANMessage = class _RCANMessage {
190
195
  __publicField(this, "transportEncoding");
191
196
  /** v1.6: GAP-18 multi-modal media chunks */
192
197
  __publicField(this, "mediaChunks");
198
+ /** v2.1: SHA-256 of sender's firmware manifest */
199
+ __publicField(this, "firmwareHash");
200
+ /** v2.1: URI to sender's SBOM attestation endpoint */
201
+ __publicField(this, "attestationRef");
193
202
  if (!data.cmd || data.cmd.trim() === "") {
194
203
  throw new RCANMessageError("'cmd' is required");
195
204
  }
@@ -217,6 +226,13 @@ var RCANMessage = class _RCANMessage {
217
226
  this.loa = data.loa;
218
227
  this.transportEncoding = data.transportEncoding;
219
228
  this.mediaChunks = data.mediaChunks;
229
+ this.firmwareHash = data.firmwareHash;
230
+ this.attestationRef = data.attestationRef;
231
+ if (this.signature !== void 0 && this.signature["sig"] === "pending") {
232
+ throw new RCANMessageError(
233
+ "signature.sig:'pending' is not valid in RCAN v2.1. Sign the message before sending."
234
+ );
235
+ }
220
236
  if (this.confidence !== void 0) {
221
237
  if (this.confidence < 0 || this.confidence > 1) {
222
238
  throw new RCANMessageError(
@@ -258,6 +274,8 @@ var RCANMessage = class _RCANMessage {
258
274
  if (this.loa !== void 0) obj.loa = this.loa;
259
275
  if (this.transportEncoding !== void 0) obj.transportEncoding = this.transportEncoding;
260
276
  if (this.mediaChunks !== void 0) obj.mediaChunks = this.mediaChunks;
277
+ if (this.firmwareHash !== void 0) obj.firmwareHash = this.firmwareHash;
278
+ if (this.attestationRef !== void 0) obj.attestationRef = this.attestationRef;
261
279
  return obj;
262
280
  }
263
281
  /** Serialize to JSON string */
@@ -300,7 +318,9 @@ var RCANMessage = class _RCANMessage {
300
318
  readOnly: obj.readOnly,
301
319
  loa: obj.loa,
302
320
  transportEncoding: obj.transportEncoding,
303
- mediaChunks: obj.mediaChunks
321
+ mediaChunks: obj.mediaChunks,
322
+ firmwareHash: obj.firmwareHash,
323
+ attestationRef: obj.attestationRef
304
324
  });
305
325
  }
306
326
  };
@@ -2060,80 +2080,133 @@ function makeFaultReport(params) {
2060
2080
  }
2061
2081
 
2062
2082
  // src/identity.ts
2063
- var LevelOfAssurance = /* @__PURE__ */ ((LevelOfAssurance2) => {
2064
- LevelOfAssurance2[LevelOfAssurance2["ANONYMOUS"] = 1] = "ANONYMOUS";
2065
- LevelOfAssurance2[LevelOfAssurance2["EMAIL_VERIFIED"] = 2] = "EMAIL_VERIFIED";
2066
- LevelOfAssurance2[LevelOfAssurance2["HARDWARE_TOKEN"] = 3] = "HARDWARE_TOKEN";
2067
- return LevelOfAssurance2;
2068
- })(LevelOfAssurance || {});
2083
+ var Role = /* @__PURE__ */ ((Role2) => {
2084
+ Role2[Role2["GUEST"] = 1] = "GUEST";
2085
+ Role2[Role2["OPERATOR"] = 2] = "OPERATOR";
2086
+ Role2[Role2["CONTRIBUTOR"] = 3] = "CONTRIBUTOR";
2087
+ Role2[Role2["ADMIN"] = 4] = "ADMIN";
2088
+ Role2[Role2["M2M_PEER"] = 5] = "M2M_PEER";
2089
+ Role2[Role2["CREATOR"] = 6] = "CREATOR";
2090
+ Role2[Role2["M2M_TRUSTED"] = 7] = "M2M_TRUSTED";
2091
+ return Role2;
2092
+ })(Role || {});
2093
+ var LevelOfAssurance = Role;
2094
+ var ROLE_JWT_LEVEL = {
2095
+ [1 /* GUEST */]: 1,
2096
+ [2 /* OPERATOR */]: 2,
2097
+ [3 /* CONTRIBUTOR */]: 2.5,
2098
+ [4 /* ADMIN */]: 3,
2099
+ [5 /* M2M_PEER */]: 4,
2100
+ [6 /* CREATOR */]: 5,
2101
+ [7 /* M2M_TRUSTED */]: 6
2102
+ };
2103
+ var JWT_LEVEL_TO_ROLE = new Map(
2104
+ Object.entries(ROLE_JWT_LEVEL).map(
2105
+ ([role, level]) => [level, Number(role)]
2106
+ )
2107
+ );
2108
+ function roleFromJwtLevel(level) {
2109
+ return JWT_LEVEL_TO_ROLE.get(level);
2110
+ }
2111
+ var SCOPE_MIN_ROLE = {
2112
+ "status": 1 /* GUEST */,
2113
+ "discover": 1 /* GUEST */,
2114
+ "chat": 1 /* GUEST */,
2115
+ "observer": 1 /* GUEST */,
2116
+ "contribute": 3 /* CONTRIBUTOR */,
2117
+ "control": 2 /* OPERATOR */,
2118
+ "teleop": 2 /* OPERATOR */,
2119
+ "training": 4 /* ADMIN */,
2120
+ "training_data": 4 /* ADMIN */,
2121
+ "config": 4 /* ADMIN */,
2122
+ "authority": 4 /* ADMIN */,
2123
+ "admin": 6 /* CREATOR */,
2124
+ "safety": 6 /* CREATOR */,
2125
+ "estop": 6 /* CREATOR */,
2126
+ "fleet.trusted": 7 /* M2M_TRUSTED */
2127
+ };
2069
2128
  var DEFAULT_LOA_POLICY = {
2070
- minLoaDiscover: 1 /* ANONYMOUS */,
2071
- minLoaStatus: 1 /* ANONYMOUS */,
2072
- minLoaChat: 1 /* ANONYMOUS */,
2073
- minLoaControl: 1 /* ANONYMOUS */,
2074
- minLoaSafety: 1 /* ANONYMOUS */
2129
+ minRoleForDiscover: 1 /* GUEST */,
2130
+ minRoleForStatus: 1 /* GUEST */,
2131
+ minRoleForChat: 1 /* GUEST */,
2132
+ minRoleForControl: 1 /* GUEST */,
2133
+ minRoleForSafety: 1 /* GUEST */
2075
2134
  };
2076
2135
  var PRODUCTION_LOA_POLICY = {
2077
- minLoaDiscover: 1 /* ANONYMOUS */,
2078
- minLoaStatus: 1 /* ANONYMOUS */,
2079
- minLoaChat: 1 /* ANONYMOUS */,
2080
- minLoaControl: 2 /* EMAIL_VERIFIED */,
2081
- minLoaSafety: 3 /* HARDWARE_TOKEN */
2136
+ minRoleForDiscover: 1 /* GUEST */,
2137
+ minRoleForStatus: 1 /* GUEST */,
2138
+ minRoleForChat: 1 /* GUEST */,
2139
+ minRoleForControl: 2 /* OPERATOR */,
2140
+ minRoleForSafety: 6 /* CREATOR */
2082
2141
  };
2083
- function extractLoaFromJwt(token) {
2142
+ function decodeJwtPayload(token) {
2084
2143
  try {
2085
2144
  const parts = token.split(".");
2086
- if (parts.length < 2) return 1 /* ANONYMOUS */;
2145
+ if (parts.length < 2) return null;
2087
2146
  const payloadB64 = (parts[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
2088
2147
  const padded = payloadB64 + "=".repeat((4 - payloadB64.length % 4) % 4);
2089
- let json;
2090
- if (typeof atob !== "undefined") {
2091
- json = atob(padded);
2092
- } else {
2093
- json = Buffer.from(padded, "base64").toString("utf-8");
2094
- }
2095
- const claims = JSON.parse(json);
2096
- const loa = claims["loa"];
2097
- if (typeof loa === "number" && loa >= 1 && loa <= 3) {
2098
- return loa;
2099
- }
2148
+ return JSON.parse(atob(padded));
2100
2149
  } catch {
2150
+ return null;
2101
2151
  }
2102
- return 1 /* ANONYMOUS */;
2103
- }
2104
- function minLoaForScope(scope, policy) {
2105
- const s = scope.toLowerCase();
2106
- switch (s) {
2107
- case "discover":
2108
- return policy.minLoaDiscover;
2109
- case "status":
2110
- return policy.minLoaStatus;
2111
- case "chat":
2112
- return policy.minLoaChat;
2113
- case "contribute":
2114
- return policy.minLoaChat;
2115
- // v1.7: between chat and control
2116
- case "control":
2117
- return policy.minLoaControl;
2118
- case "safety":
2119
- return policy.minLoaSafety;
2120
- default:
2121
- return null;
2122
- }
2123
- }
2124
- function validateLoaForScope(loa, scope, policy = DEFAULT_LOA_POLICY) {
2125
- const min = minLoaForScope(scope, policy);
2126
- if (min === null) {
2127
- return { valid: true, reason: "unknown scope; allowed by default" };
2128
- }
2129
- if (loa >= min) {
2130
- return { valid: true, reason: "ok" };
2152
+ }
2153
+ function extractRoleFromJwt(token) {
2154
+ const payload = decodeJwtPayload(token);
2155
+ if (!payload) return 1 /* GUEST */;
2156
+ const rcanRole = payload["rcan_role"];
2157
+ if (rcanRole !== void 0 && rcanRole !== null) {
2158
+ const role = roleFromJwtLevel(Number(rcanRole));
2159
+ if (role !== void 0) return role;
2160
+ }
2161
+ const loa = payload["loa"];
2162
+ if (loa !== void 0 && loa !== null) {
2163
+ const role = roleFromJwtLevel(Number(loa));
2164
+ if (role !== void 0) return role;
2131
2165
  }
2166
+ return 1 /* GUEST */;
2167
+ }
2168
+ function extractLoaFromJwt(token) {
2169
+ return extractRoleFromJwt(token);
2170
+ }
2171
+ function extractIdentityFromJwt(token) {
2172
+ const payload = decodeJwtPayload(token);
2173
+ if (!payload) {
2174
+ return { sub: "", role: 1 /* GUEST */, jwtLevel: 1, scopes: [] };
2175
+ }
2176
+ const rcanRole = payload["rcan_role"];
2177
+ const loa = payload["loa"];
2178
+ const rawLevel = rcanRole !== void 0 ? Number(rcanRole) : loa !== void 0 ? Number(loa) : 1;
2179
+ const role = roleFromJwtLevel(rawLevel) ?? 1 /* GUEST */;
2180
+ const scopes = Array.isArray(payload["rcan_scopes"]) ? payload["rcan_scopes"] : Array.isArray(payload["scopes"]) ? payload["scopes"] : [];
2132
2181
  return {
2133
- valid: false,
2134
- reason: `LOA_INSUFFICIENT: scope=${scope} requires LoA>=${min}, caller has LoA=${loa}`
2182
+ sub: String(payload["sub"] ?? ""),
2183
+ role,
2184
+ jwtLevel: ROLE_JWT_LEVEL[role],
2185
+ registryUrl: payload["registry_url"],
2186
+ scopes,
2187
+ verifiedAt: payload["verified_at"],
2188
+ peerRrn: payload["peer_rrn"],
2189
+ fleetRrns: Array.isArray(payload["fleet_rrns"]) ? payload["fleet_rrns"] : void 0
2135
2190
  };
2136
2191
  }
2192
+ function validateRoleForScope(role, scope) {
2193
+ const required = SCOPE_MIN_ROLE[scope.toLowerCase()];
2194
+ if (required === void 0) {
2195
+ if (role >= 2 /* OPERATOR */) return { ok: true, reason: "" };
2196
+ return {
2197
+ ok: false,
2198
+ reason: `Unknown scope '${scope}': applying OPERATOR minimum. Caller has ${Role[role]}.`
2199
+ };
2200
+ }
2201
+ if (role >= required) return { ok: true, reason: "" };
2202
+ return {
2203
+ ok: false,
2204
+ reason: `Scope '${scope}' requires ${Role[required]} (JWT level ${ROLE_JWT_LEVEL[required]}), but caller has ${Role[role]} (JWT level ${ROLE_JWT_LEVEL[role]})`
2205
+ };
2206
+ }
2207
+ function validateLoaForScope(role, scope) {
2208
+ return validateRoleForScope(role, scope);
2209
+ }
2137
2210
 
2138
2211
  // src/federation.ts
2139
2212
  var RegistryTier = /* @__PURE__ */ ((RegistryTier2) => {
@@ -2259,12 +2332,12 @@ function generateId5() {
2259
2332
  }
2260
2333
  function makeFederationSync(source, target, syncType, payload) {
2261
2334
  return new RCANMessage({
2262
- rcan: "1.6",
2263
- rcanVersion: "1.6",
2335
+ rcan: "2.1.0",
2336
+ rcanVersion: "2.1.0",
2264
2337
  cmd: "federation_sync",
2265
2338
  target,
2266
2339
  params: {
2267
- msg_type: 23 /* FEDERATION_SYNC */,
2340
+ msg_type: 23 /* FLEET_COMMAND */,
2268
2341
  msg_id: generateId5(),
2269
2342
  source_registry: source,
2270
2343
  target_registry: target,
@@ -2291,17 +2364,17 @@ async function validateCrossRegistryCommand(msg, localRegistry, trustCache) {
2291
2364
  reason: `REGISTRY_UNKNOWN: ${sourceRegistry} is not in the local trust cache`
2292
2365
  };
2293
2366
  }
2294
- let loa = 1 /* ANONYMOUS */;
2367
+ let loa = 1 /* GUEST */;
2295
2368
  const registryJwt = msg.params?.["registry_jwt"];
2296
2369
  if (registryJwt) {
2297
2370
  loa = extractLoaFromJwt(registryJwt);
2298
2371
  } else if (typeof msg.loa === "number") {
2299
2372
  loa = msg.loa;
2300
2373
  }
2301
- if (loa < 2 /* EMAIL_VERIFIED */) {
2374
+ if (loa < 2 /* OPERATOR */) {
2302
2375
  return {
2303
2376
  valid: false,
2304
- reason: `LOA_INSUFFICIENT: cross-registry commands require LoA>=2 (EMAIL_VERIFIED), got LoA=${loa}`
2377
+ reason: `LOA_INSUFFICIENT: cross-registry commands require LoA>=2 (OPERATOR), got role=${loa}`
2305
2378
  };
2306
2379
  }
2307
2380
  return { valid: true, reason: "cross-registry command accepted" };
@@ -2740,24 +2813,312 @@ function isPreemptedBy(scopeLevel) {
2740
2813
  return scopeLevel >= 3;
2741
2814
  }
2742
2815
 
2816
+ // src/competition.ts
2817
+ var COMPETITION_SCOPE_LEVEL = 2;
2818
+ var _idCounter2 = 0;
2819
+ function _generateRunId() {
2820
+ return `run-${Date.now()}-${++_idCounter2}`;
2821
+ }
2822
+ function makeCompetitionEnter(params = {}) {
2823
+ return {
2824
+ type: 37 /* COMPETITION_ENTER */,
2825
+ competition_id: params.competition_id ?? "",
2826
+ competition_format: params.competition_format ?? "sprint",
2827
+ hardware_tier: params.hardware_tier ?? "",
2828
+ model_id: params.model_id ?? "",
2829
+ robot_rrn: params.robot_rrn ?? "",
2830
+ entered_at: params.entered_at ?? Date.now() / 1e3
2831
+ };
2832
+ }
2833
+ function makeCompetitionScore(params = {}) {
2834
+ const score = params.score ?? 0;
2835
+ if (score < 0 || score > 1) {
2836
+ throw new Error(`score must be in [0.0, 1.0], got ${score}`);
2837
+ }
2838
+ return {
2839
+ type: 38 /* COMPETITION_SCORE */,
2840
+ competition_id: params.competition_id ?? "",
2841
+ candidate_id: params.candidate_id ?? "",
2842
+ score,
2843
+ hardware_tier: params.hardware_tier ?? "",
2844
+ verified: params.verified ?? false,
2845
+ submitted_at: params.submitted_at ?? Date.now() / 1e3
2846
+ };
2847
+ }
2848
+ function makeSeasonStanding(params = {}) {
2849
+ return {
2850
+ type: 39 /* SEASON_STANDING */,
2851
+ season_id: params.season_id ?? "",
2852
+ class_id: params.class_id ?? "",
2853
+ standings: params.standings ?? [],
2854
+ days_remaining: params.days_remaining ?? 0,
2855
+ broadcast_at: params.broadcast_at ?? Date.now() / 1e3
2856
+ };
2857
+ }
2858
+ function makePersonalResearchResult(params = {}) {
2859
+ const score = params.score ?? 0;
2860
+ if (score < 0 || score > 1) {
2861
+ throw new Error(`score must be in [0.0, 1.0], got ${score}`);
2862
+ }
2863
+ return {
2864
+ type: 40 /* PERSONAL_RESEARCH_RESULT */,
2865
+ run_id: params.run_id ?? _generateRunId(),
2866
+ run_type: params.run_type ?? "personal",
2867
+ candidate_id: params.candidate_id ?? "",
2868
+ score,
2869
+ hardware_tier: params.hardware_tier ?? "",
2870
+ model_id: params.model_id ?? "",
2871
+ owner_uid: params.owner_uid ?? "",
2872
+ metrics: params.metrics ?? {
2873
+ success_rate: 0,
2874
+ p66_rate: 0,
2875
+ token_efficiency: 0,
2876
+ latency_score: 0
2877
+ },
2878
+ submitted_to_community: params.submitted_to_community ?? false,
2879
+ created_at: params.created_at ?? Date.now() / 1e3
2880
+ };
2881
+ }
2882
+ function validateCompetitionScope(scopeLevel) {
2883
+ return scopeLevel >= COMPETITION_SCOPE_LEVEL;
2884
+ }
2885
+
2886
+ // src/firmware.ts
2887
+ var FIRMWARE_MANIFEST_PATH = "/.well-known/rcan-firmware-manifest.json";
2888
+ function manifestToWire(m) {
2889
+ const wire = {
2890
+ rrn: m.rrn,
2891
+ firmware_version: m.firmwareVersion,
2892
+ build_hash: m.buildHash,
2893
+ components: m.components,
2894
+ signed_at: m.signedAt
2895
+ };
2896
+ if (m.signature) wire.signature = m.signature;
2897
+ return wire;
2898
+ }
2899
+ function manifestFromWire(w) {
2900
+ return {
2901
+ rrn: w.rrn,
2902
+ firmwareVersion: w.firmware_version,
2903
+ buildHash: w.build_hash,
2904
+ components: w.components ?? [],
2905
+ signedAt: w.signed_at ?? "",
2906
+ signature: w.signature
2907
+ };
2908
+ }
2909
+ function canonicalManifestJson(m) {
2910
+ const obj = {
2911
+ build_hash: m.buildHash,
2912
+ components: m.components.map((c) => ({
2913
+ hash: c.hash,
2914
+ name: c.name,
2915
+ version: c.version
2916
+ })),
2917
+ firmware_version: m.firmwareVersion,
2918
+ rrn: m.rrn,
2919
+ signed_at: m.signedAt
2920
+ };
2921
+ return JSON.stringify(obj);
2922
+ }
2923
+ var FirmwareIntegrityError = class extends Error {
2924
+ constructor(message) {
2925
+ super(message);
2926
+ this.name = "FirmwareIntegrityError";
2927
+ }
2928
+ };
2929
+ function validateManifest(m) {
2930
+ const errors = [];
2931
+ if (!m.rrn) errors.push("rrn is required");
2932
+ if (!m.firmwareVersion) errors.push("firmwareVersion is required");
2933
+ if (!m.buildHash) errors.push("buildHash is required");
2934
+ if (!m.buildHash.startsWith("sha256:")) errors.push("buildHash must start with 'sha256:'");
2935
+ if (!m.signedAt) errors.push("signedAt is required");
2936
+ if (!m.signature) errors.push("signature is required (manifest must be signed)");
2937
+ for (const [i, c] of m.components.entries()) {
2938
+ if (!c.name) errors.push(`components[${i}].name is required`);
2939
+ if (!c.version) errors.push(`components[${i}].version is required`);
2940
+ if (!c.hash.startsWith("sha256:")) errors.push(`components[${i}].hash must start with 'sha256:'`);
2941
+ }
2942
+ return errors;
2943
+ }
2944
+
2945
+ // src/authority.ts
2946
+ function authorityAccessToWire(p) {
2947
+ return {
2948
+ request_id: p.requestId,
2949
+ authority_id: p.authorityId,
2950
+ requested_data: p.requestedData,
2951
+ justification: p.justification,
2952
+ expires_at: p.expiresAt
2953
+ };
2954
+ }
2955
+ function authorityAccessFromWire(w) {
2956
+ return {
2957
+ requestId: w.request_id,
2958
+ authorityId: w.authority_id,
2959
+ requestedData: w.requested_data ?? [],
2960
+ justification: w.justification ?? "",
2961
+ expiresAt: w.expires_at ?? 0
2962
+ };
2963
+ }
2964
+ function validateAuthorityAccess(p) {
2965
+ const errors = [];
2966
+ if (!p.requestId) errors.push("requestId is required");
2967
+ if (!p.authorityId) errors.push("authorityId is required");
2968
+ if (!p.requestedData || p.requestedData.length === 0)
2969
+ errors.push("requestedData must include at least one category");
2970
+ if (!p.justification) errors.push("justification is required");
2971
+ if (!p.expiresAt || p.expiresAt <= 0) errors.push("expiresAt must be a positive Unix timestamp");
2972
+ if (p.expiresAt < Date.now() / 1e3) errors.push("expiresAt is in the past \u2014 request has expired");
2973
+ return errors;
2974
+ }
2975
+ function isAuthorityRequestValid(p) {
2976
+ return Date.now() / 1e3 < p.expiresAt && validateAuthorityAccess(p).length === 0;
2977
+ }
2978
+ var AUTHORITY_ERROR_CODES = {
2979
+ NOT_RECOGNIZED: "AUTHORITY_NOT_RECOGNIZED",
2980
+ REQUEST_EXPIRED: "AUTHORITY_REQUEST_EXPIRED",
2981
+ INVALID_TOKEN: "AUTHORITY_INVALID_TOKEN",
2982
+ RATE_LIMITED: "AUTHORITY_RATE_LIMITED"
2983
+ };
2984
+
2985
+ // src/m2m.ts
2986
+ var RRF_REVOCATION_URL = "https://api.rrf.rcan.dev/v2/revocations";
2987
+ var M2M_TRUSTED_ISSUER = "rrf.rcan.dev";
2988
+ var RRF_REVOCATION_CACHE_TTL_MS = 55e3;
2989
+ var M2MAuthError = class extends Error {
2990
+ constructor(message) {
2991
+ super(message);
2992
+ this.name = "M2MAuthError";
2993
+ }
2994
+ };
2995
+ function decodeJwtPayload2(token) {
2996
+ const parts = token.split(".");
2997
+ if (parts.length < 2) throw new M2MAuthError("Invalid JWT structure");
2998
+ const b64 = (parts[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
2999
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
3000
+ try {
3001
+ return JSON.parse(atob(padded));
3002
+ } catch (e) {
3003
+ throw new M2MAuthError(`JWT payload decode failed: ${String(e)}`);
3004
+ }
3005
+ }
3006
+ function parseM2mPeerToken(token) {
3007
+ const payload = decodeJwtPayload2(token);
3008
+ const exp = Number(payload["exp"] ?? 0);
3009
+ if (exp > 0 && Date.now() / 1e3 > exp) {
3010
+ throw new M2MAuthError(`M2M_PEER token expired (sub=${String(payload["sub"])})`);
3011
+ }
3012
+ const peerRrn = String(payload["peer_rrn"] ?? "");
3013
+ if (!peerRrn) throw new M2MAuthError("M2M_PEER token missing peer_rrn claim");
3014
+ return {
3015
+ sub: String(payload["sub"] ?? ""),
3016
+ peerRrn,
3017
+ scopes: Array.isArray(payload["rcan_scopes"]) ? payload["rcan_scopes"] : Array.isArray(payload["scopes"]) ? payload["scopes"] : [],
3018
+ exp,
3019
+ iss: String(payload["iss"] ?? "")
3020
+ };
3021
+ }
3022
+ function parseM2mTrustedToken(token) {
3023
+ const payload = decodeJwtPayload2(token);
3024
+ const iss = String(payload["iss"] ?? "");
3025
+ if (iss !== M2M_TRUSTED_ISSUER) {
3026
+ throw new M2MAuthError(
3027
+ `M2M_TRUSTED issuer must be '${M2M_TRUSTED_ISSUER}', got '${iss}'`
3028
+ );
3029
+ }
3030
+ const scopes = Array.isArray(payload["rcan_scopes"]) ? payload["rcan_scopes"] : Array.isArray(payload["scopes"]) ? payload["scopes"] : [];
3031
+ if (!scopes.includes("fleet.trusted")) {
3032
+ throw new M2MAuthError("M2M_TRUSTED token missing required 'fleet.trusted' scope");
3033
+ }
3034
+ const exp = Number(payload["exp"] ?? 0);
3035
+ if (exp > 0 && Date.now() / 1e3 > exp) {
3036
+ throw new M2MAuthError(`M2M_TRUSTED token expired (sub=${String(payload["sub"])})`);
3037
+ }
3038
+ const rrfSig = String(payload["rrf_sig"] ?? "");
3039
+ if (!rrfSig) throw new M2MAuthError("M2M_TRUSTED token missing rrf_sig claim");
3040
+ const fleetRrns = Array.isArray(payload["fleet_rrns"]) ? payload["fleet_rrns"] : [];
3041
+ return {
3042
+ sub: String(payload["sub"] ?? ""),
3043
+ fleetRrns,
3044
+ scopes,
3045
+ exp,
3046
+ iss,
3047
+ rrfSig
3048
+ };
3049
+ }
3050
+ function verifyM2mTrustedTokenClaims(token, targetRrn) {
3051
+ const claims = parseM2mTrustedToken(token);
3052
+ if (!claims.fleetRrns.includes(targetRrn)) {
3053
+ throw new M2MAuthError(
3054
+ `M2M_TRUSTED token does not authorize commanding '${targetRrn}'. Authorized fleet: [${claims.fleetRrns.join(", ")}]`
3055
+ );
3056
+ }
3057
+ return claims;
3058
+ }
3059
+ var _revocationCache = null;
3060
+ async function fetchRRFRevocations(url = RRF_REVOCATION_URL) {
3061
+ const now = Date.now();
3062
+ if (_revocationCache && now - _revocationCache.fetchedAt < RRF_REVOCATION_CACHE_TTL_MS) {
3063
+ return _revocationCache;
3064
+ }
3065
+ try {
3066
+ const resp = await fetch(url, { signal: AbortSignal.timeout?.(5e3) });
3067
+ const data = await resp.json();
3068
+ _revocationCache = {
3069
+ revokedOrchestrators: new Set(data.revoked_orchestrators ?? []),
3070
+ revokedJtis: new Set(data.revoked_jtis ?? []),
3071
+ fetchedAt: now
3072
+ };
3073
+ } catch {
3074
+ if (_revocationCache) return _revocationCache;
3075
+ _revocationCache = { revokedOrchestrators: /* @__PURE__ */ new Set(), revokedJtis: /* @__PURE__ */ new Set(), fetchedAt: now };
3076
+ }
3077
+ return _revocationCache;
3078
+ }
3079
+ async function isM2mTrustedRevoked(claims, jti) {
3080
+ const cache = await fetchRRFRevocations();
3081
+ if (cache.revokedOrchestrators.has(claims.sub)) return true;
3082
+ if (jti && cache.revokedJtis.has(jti)) return true;
3083
+ return false;
3084
+ }
3085
+ async function verifyM2mTrustedToken(token, targetRrn, options) {
3086
+ const claims = verifyM2mTrustedTokenClaims(token, targetRrn);
3087
+ if (!options?.skipRevocationCheck) {
3088
+ const revoked = await isM2mTrustedRevoked(claims);
3089
+ if (revoked) {
3090
+ throw new M2MAuthError(
3091
+ `M2M_TRUSTED orchestrator '${claims.sub}' is on the RRF revocation list`
3092
+ );
3093
+ }
3094
+ }
3095
+ return claims;
3096
+ }
3097
+
2743
3098
  // src/index.ts
2744
3099
  var VERSION = "0.6.0";
2745
3100
  var RCAN_VERSION = "1.6";
2746
3101
  export {
3102
+ AUTHORITY_ERROR_CODES,
2747
3103
  AuditChain,
2748
3104
  AuditError,
3105
+ COMPETITION_SCOPE_LEVEL,
2749
3106
  CONTRIBUTE_SCOPE_LEVEL,
2750
3107
  ClockDriftError,
2751
3108
  CommitmentRecord,
2752
3109
  ConfidenceGate,
2753
3110
  DEFAULT_LOA_POLICY,
2754
3111
  DataCategory,
3112
+ FIRMWARE_MANIFEST_PATH,
2755
3113
  FaultCode,
2756
3114
  FederationSyncType,
3115
+ FirmwareIntegrityError,
2757
3116
  GateError,
2758
3117
  HiTLGate,
2759
3118
  KeyStore,
2760
3119
  LevelOfAssurance,
3120
+ M2MAuthError,
3121
+ M2M_TRUSTED_ISSUER,
2761
3122
  MediaEncoding,
2762
3123
  MessageType,
2763
3124
  NodeClient,
@@ -2783,13 +3144,18 @@ export {
2783
3144
  RCANValidationError,
2784
3145
  RCANVersionIncompatibleError,
2785
3146
  RCAN_VERSION,
3147
+ ROLE_JWT_LEVEL,
3148
+ RRF_REVOCATION_CACHE_TTL_MS,
3149
+ RRF_REVOCATION_URL,
2786
3150
  RegistryClient,
2787
3151
  RegistryTier,
2788
3152
  ReplayCache,
2789
3153
  RevocationCache,
2790
3154
  RobotURI,
2791
3155
  RobotURIError,
3156
+ Role,
2792
3157
  SAFETY_MESSAGE_TYPE,
3158
+ SCOPE_MIN_ROLE,
2793
3159
  SDK_VERSION,
2794
3160
  SPEC_VERSION,
2795
3161
  TransportEncoding,
@@ -2800,6 +3166,9 @@ export {
2800
3166
  addMediaInline,
2801
3167
  addMediaRef,
2802
3168
  assertClockSynced,
3169
+ authorityAccessFromWire,
3170
+ authorityAccessToWire,
3171
+ canonicalManifestJson,
2803
3172
  checkClockSync,
2804
3173
  checkRevocation,
2805
3174
  decodeBleFrames,
@@ -2808,11 +3177,18 @@ export {
2808
3177
  encodeBleFrames,
2809
3178
  encodeCompact,
2810
3179
  encodeMinimal,
3180
+ extractIdentityFromJwt,
2811
3181
  extractLoaFromJwt,
3182
+ extractRoleFromJwt,
2812
3183
  fetchCanonicalSchema,
3184
+ fetchRRFRevocations,
3185
+ isAuthorityRequestValid,
3186
+ isM2mTrustedRevoked,
2813
3187
  isPreemptedBy,
2814
3188
  isSafetyMessage,
2815
3189
  makeCloudRelayMessage,
3190
+ makeCompetitionEnter,
3191
+ makeCompetitionScore,
2816
3192
  makeConfigUpdate,
2817
3193
  makeConsentDeny,
2818
3194
  makeConsentGrant,
@@ -2825,8 +3201,10 @@ export {
2825
3201
  makeFaultReport,
2826
3202
  makeFederationSync,
2827
3203
  makeKeyRotationMessage,
3204
+ makePersonalResearchResult,
2828
3205
  makeResumeMessage,
2829
3206
  makeRevocationBroadcast,
3207
+ makeSeasonStanding,
2830
3208
  makeStopMessage,
2831
3209
  makeStreamChunk,
2832
3210
  makeTrainingConsentDeny,
@@ -2834,7 +3212,14 @@ export {
2834
3212
  makeTrainingConsentRequest,
2835
3213
  makeTrainingDataMessage,
2836
3214
  makeTransparencyMessage,
3215
+ manifestFromWire,
3216
+ manifestToWire,
3217
+ parseM2mPeerToken,
3218
+ parseM2mTrustedToken,
3219
+ roleFromJwtLevel,
2837
3220
  selectTransport,
3221
+ validateAuthorityAccess,
3222
+ validateCompetitionScope,
2838
3223
  validateConfig,
2839
3224
  validateConfigAgainstSchema,
2840
3225
  validateConfigUpdate,
@@ -2843,13 +3228,17 @@ export {
2843
3228
  validateCrossRegistryCommand,
2844
3229
  validateDelegationChain,
2845
3230
  validateLoaForScope,
3231
+ validateManifest,
2846
3232
  validateMediaChunks,
2847
3233
  validateMessage,
2848
3234
  validateNodeAgainstSchema,
2849
3235
  validateReplay,
3236
+ validateRoleForScope,
2850
3237
  validateSafetyMessage,
2851
3238
  validateTrainingDataMessage,
2852
3239
  validateURI,
2853
- validateVersionCompat
3240
+ validateVersionCompat,
3241
+ verifyM2mTrustedToken,
3242
+ verifyM2mTrustedTokenClaims
2854
3243
  };
2855
3244
  //# sourceMappingURL=browser.mjs.map