@continuonai/rcan-ts 0.2.0 → 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # rcan-ts
2
2
 
3
- Official TypeScript SDK for the **RCAN v1.2** Robot Communication and Accountability Network protocol.
3
+ Official TypeScript SDK for the **RCAN v1.5** Robot Communication and Addressing Network protocol.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/%40continuonai%2Frcan-ts.svg)](https://www.npmjs.com/package/@continuonai/rcan-ts)
6
6
  [![CI](https://github.com/continuonai/rcan-ts/actions/workflows/ci.yml/badge.svg)](https://github.com/continuonai/rcan-ts/actions)
@@ -9,6 +9,8 @@ Official TypeScript SDK for the **RCAN v1.2** Robot Communication and Accountabi
9
9
  npm install @continuonai/rcan-ts
10
10
  ```
11
11
 
12
+ > **v0.2.0** — RCAN v1.2 spec compliance including §17 Distributed Registry Node Protocol
13
+
12
14
  ### CDN / Browser (no build step)
13
15
 
14
16
  ```html
@@ -139,6 +141,89 @@ result.warnings.forEach((w) => console.warn("⚠️", w));
139
141
  | `validateURI` | Validate a Robot URI string |
140
142
  | `validateMessage` | Validate a RCAN message object |
141
143
  | `validateConfig` | L1/L2/L3 conformance check for a robot RCAN config |
144
+ | `NodeClient` | Resolve RRNs from federated registry nodes (§17) |
145
+ | `fetchCanonicalSchema` | Fetch the canonical JSON schema from rcan.dev |
146
+ | `validateConfigAgainstSchema` | Validate a config object against the live JSON schema |
147
+ | `validateNodeAgainstSchema` | Validate a node manifest against the node schema |
148
+
149
+ ---
150
+
151
+ ## Distributed Registry Nodes (§17)
152
+
153
+ RCAN v1.2 §17 introduces a federated registry network. `NodeClient` resolves RRNs from any node — root or delegated authoritative.
154
+
155
+ ```typescript
156
+ import { NodeClient } from '@continuonai/rcan-ts';
157
+
158
+ const client = new NodeClient();
159
+
160
+ // Resolve an RRN across the federation
161
+ const result = await client.resolve('RRN-BD-000000000001');
162
+ console.log(`Resolved by: ${result.resolved_by}`);
163
+ console.log(`Robot: ${result.record.name}`);
164
+
165
+ // Discover the authoritative node for a namespace
166
+ const node = await client.discover('RRN-BD-000000000001');
167
+ console.log(`Authoritative: ${node.operator} at ${node.api_base}`);
168
+
169
+ // List all known registry nodes
170
+ const nodes = await client.listNodes();
171
+ nodes.forEach(n => console.log(`${n.operator}: ${n.namespace_prefix}`));
172
+
173
+ // Verify a node manifest
174
+ const manifest = await client.getNodeManifest('https://registry.example.com');
175
+ const isValid = client.verifyNode(manifest);
176
+
177
+ // Error handling
178
+ import { RCANNodeNotFoundError, RCANNodeTrustError } from '@continuonai/rcan-ts';
179
+ try {
180
+ const result = await client.resolve('RRN-UNKNOWN-000000000001');
181
+ } catch (e) {
182
+ if (e instanceof RCANNodeNotFoundError) {
183
+ console.log(`Not found: ${e.rrn}`);
184
+ } else if (e instanceof RCANNodeTrustError) {
185
+ console.log(`Trust failure: ${e.reason}`);
186
+ }
187
+ }
188
+ ```
189
+
190
+ ### RRN Format
191
+
192
+ ```
193
+ Root namespace: RRN-000000000001 (12-digit recommended, 8-digit still valid)
194
+ Delegated: RRN-BD-000000000001 (prefix 2-8 alphanumeric chars)
195
+ Legacy (valid): RRN-00000001 (8-digit, backward compatible)
196
+ ```
197
+
198
+ ## Schema Validation
199
+
200
+ Validate configs against the canonical JSON schema published at rcan.dev:
201
+
202
+ ```typescript
203
+ import { validateConfigAgainstSchema, validateNodeAgainstSchema } from '@continuonai/rcan-ts';
204
+
205
+ // Validate a RCAN config against the canonical schema from rcan.dev
206
+ const result = await validateConfigAgainstSchema(myConfig);
207
+ if (!result.valid) {
208
+ console.error('Config invalid:', result.errors);
209
+ } else if (result.skipped) {
210
+ console.warn('Schema validation skipped (rcan.dev unreachable)');
211
+ }
212
+
213
+ // Validate a node manifest
214
+ const nodeResult = await validateNodeAgainstSchema(manifest);
215
+ ```
216
+
217
+ ### CDN / Browser Usage
218
+
219
+ ```html
220
+ <script src="https://unpkg.com/@continuonai/rcan-ts/dist/rcan.iife.js"></script>
221
+ <script>
222
+ const { validateConfig, NodeClient } = window.RCAN;
223
+ const client = new NodeClient();
224
+ client.resolve('RRN-000000000001').then(r => console.log(r));
225
+ </script>
226
+ ```
142
227
 
143
228
  ---
144
229
 
@@ -155,10 +240,12 @@ The Python and TypeScript SDKs share an identical API surface — `RobotURI`, `R
155
240
  ## Links
156
241
 
157
242
  - ⚡ [Quickstart](https://rcan.dev/quickstart) — from zero to first message in 5 min
158
- - 📖 [RCAN Spec](https://rcan.dev/spec) — full protocol specification
243
+ - 📖 [RCAN Spec v1.5](https://rcan.dev/spec) — full protocol specification
159
244
  - 🌐 [rcan.dev](https://rcan.dev) — robot registry and documentation
160
245
  - 🐍 [rcan-py](https://github.com/continuonai/rcan-py) — Python SDK
161
- - 🤖 [OpenCastor](https://github.com/craigm26/OpenCastor) — robot runtime with RCAN built in
246
+ - 🤖 [OpenCastor](https://github.com/craigm26/OpenCastor) — Python robot runtime (RCAN reference implementation)
247
+ - 🖥️ [OpenCastor Fleet UI](https://app.opencastor.com) — Flutter web app for remote fleet management (uses rcan-ts for message construction)
248
+ - 🏛️ [Robot Registry Foundation](https://robotregistryfoundation.org) — global robot identity registry
162
249
 
163
250
  ---
164
251
 
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import { NodeClient, validateConfig, VERSION, SPEC_VERSION } from '../dist/index.js';
3
+ import { readFileSync } from 'fs';
4
+ import { parseArgs } from 'util';
5
+
6
+ const { values, positionals } = parseArgs({
7
+ args: process.argv.slice(2),
8
+ options: {
9
+ version: { type: 'boolean', short: 'v' },
10
+ file: { type: 'string', short: 'f' },
11
+ },
12
+ allowPositionals: true,
13
+ });
14
+
15
+ if (values.version) {
16
+ console.log(`rcan-validate (ts) ${VERSION} (RCAN spec ${SPEC_VERSION})`);
17
+ process.exit(0);
18
+ }
19
+
20
+ const [subcommand, target] = positionals;
21
+
22
+ if (subcommand === 'node') {
23
+ if (!target) {
24
+ console.error('Usage: rcan-validate node <url>');
25
+ process.exit(1);
26
+ }
27
+
28
+ const client = new NodeClient();
29
+ console.log(`Validating node manifest: ${target}`);
30
+
31
+ try {
32
+ const manifest = await client.getNodeManifest(target);
33
+ const valid = client.verifyNode(manifest);
34
+
35
+ const checks = [
36
+ ['node_type', manifest.node_type, ['root','authoritative','resolver','cache'].includes(manifest.node_type)],
37
+ ['operator', manifest.operator, !!manifest.operator],
38
+ ['namespace_prefix', manifest.namespace_prefix, !!manifest.namespace_prefix],
39
+ ['public_key', manifest.public_key?.substring(0, 20) + '...', manifest.public_key?.startsWith('ed25519:')],
40
+ ['api_base', manifest.api_base, manifest.api_base?.startsWith('https://')],
41
+ ];
42
+
43
+ for (const [name, value, pass] of checks) {
44
+ console.log(` ${pass ? '✓' : '✗'} ${name}: ${value}`);
45
+ }
46
+
47
+ const passed = checks.filter(([,,p]) => p).length;
48
+ console.log(`\n${valid ? 'PASS' : 'FAIL'} (${passed}/${checks.length} checks)`);
49
+ process.exit(valid ? 0 : 1);
50
+ } catch (e) {
51
+ console.error(`❌ Error: ${e.message}`);
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ if (subcommand === 'config') {
57
+ const filePath = target || values.file;
58
+ if (!filePath) {
59
+ console.error('Usage: rcan-validate config <file>');
60
+ process.exit(1);
61
+ }
62
+
63
+ try {
64
+ const raw = readFileSync(filePath, 'utf-8');
65
+ const config = JSON.parse(raw);
66
+ const result = validateConfig(config);
67
+ if (result.ok) {
68
+ console.log('✓ Config valid');
69
+ if (result.warnings?.length) {
70
+ result.warnings.forEach(w => console.warn(` ⚠️ ${w}`));
71
+ }
72
+ process.exit(0);
73
+ } else {
74
+ console.error('✗ Config invalid:');
75
+ result.issues?.forEach(i => console.error(` ❌ ${i}`));
76
+ process.exit(1);
77
+ }
78
+ } catch (e) {
79
+ console.error(`❌ Error: ${e.message}`);
80
+ process.exit(1);
81
+ }
82
+ }
83
+
84
+ console.error(`Unknown subcommand: ${subcommand}`);
85
+ console.error('Usage: rcan-validate node <url> | rcan-validate config <file> | rcan-validate --version');
86
+ process.exit(1);