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