@fibril/can-core 0.1.1 → 0.3.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.d.ts CHANGED
@@ -1,12 +1,7 @@
1
1
  import { CanFrameTx, ITransport, Unsubscribe } from "@fibril/can-transport";
2
2
 
3
3
  //#region src/ack-tracker.d.ts
4
- /**
5
- * In-flight ACK/NAK ledger keyed by `seq`. Mirrors the per-node `AckEntry`
6
- * map in `fibril_can_bridge/include/fibril_can_bridge/node_agent.hpp` but
7
- * pared down for the single-threaded TS master — no deadlines list, the
8
- * caller polls via {@link sweep}.
9
- */
4
+ /** In-flight ACK/NAK ledger keyed by `seq`. Caller polls via {@link sweep}. */
10
5
  type AckOutcome = {
11
6
  kind: 'acked';
12
7
  } | {
@@ -49,7 +44,7 @@ declare class AckTracker {
49
44
  * Standard ID (11 bit, §3.1) is dynamically allocated by the master and
50
45
  * has no fixed bitfield meaning — kept here only as the wire mask.
51
46
  */
52
- declare const PROTOCOL_VERSION = 1;
47
+ declare const PROTOCOL_VERSION = 2;
53
48
  declare const MAX_FRAME_PAYLOAD = 64;
54
49
  declare const CHAN_CONFIG = 0;
55
50
  declare const CHAN_SVC_REQUEST = 1;
@@ -84,6 +79,9 @@ declare const STATE_UNPROVISIONED = 0;
84
79
  declare const STATE_PROVISIONED = 1;
85
80
  declare const STATE_RUNNING = 2;
86
81
  declare const STATE_FAULT = 3;
82
+ declare const STATE_SUSPENDED = 4;
83
+ /** Human-readable name for a node_state byte. Unknown values → `unknown(N)`. */
84
+ declare function stateName(state: number): string;
87
85
  declare const FAULT_NONE = 0;
88
86
  declare const FAULT_NOMEM = 1;
89
87
  declare const FAULT_INDEX_OVERFLOW = 2;
@@ -117,13 +115,8 @@ declare function extSeg(id: number): number;
117
115
  //#endregion
118
116
  //#region src/config-plane.d.ts
119
117
  /**
120
- * Config-plane wire codec. Mirrors
121
- * `fibril_can_bridge/include/fibril_can_bridge/config_plane_codec.hpp`
122
- * and `fibril_can_bridge/src/config_plane_codec.cpp`.
123
- *
124
- * All integers are little-endian. Decoders return `null` on malformed input
125
- * (wrong length etc.); encoders produce CAN frames padded to the next DLC
126
- * step, matching what the slave sees on the bus.
118
+ * Config-plane wire codec. All integers little-endian; decoders return `null`
119
+ * on malformed input; encoders produce CAN-FD frames padded to the next DLC step.
127
120
  */
128
121
  /** ANNOUNCE payload (S→M, §5.4). */
129
122
  interface AnnounceMsg {
@@ -170,13 +163,9 @@ declare function encodeFrameDefine(nodeId: number, seq: number, stdId: number, p
170
163
  declare function encodeStart(nodeId: number, seq: number): CanFrameTx;
171
164
  declare function encodeStop(nodeId: number, seq: number): CanFrameTx;
172
165
  /**
173
- * Accumulator for SCHEMA_DATA chunks. The slave DLC-pads each response (§4.3)
174
- * so `chunk.bytes.length` is typically larger than what the master requested;
175
- * the accumulator clips against `expectedChunkLen` and the remaining blob
176
- * length to drop those padding bytes (parity with
177
- * `append_schema_chunk` in `fibril_can_bridge/src/config_plane_codec.cpp`).
178
- *
179
- * Out-of-order chunks are rejected (returns 0 from {@link append}).
166
+ * SCHEMA_DATA reassembly. Clips each chunk against `expectedChunkLen` and
167
+ * the remaining blob length so DLC padding is discarded. Out-of-order
168
+ * chunks are rejected ({@link append} returns 0).
180
169
  */
181
170
  declare class SchemaAccumulator {
182
171
  #private;
@@ -194,12 +183,8 @@ declare class SchemaAccumulator {
194
183
  //#endregion
195
184
  //#region src/scalar-codec.d.ts
196
185
  /**
197
- * Scalar type enumeration and little-endian wire codecs. Mirrors
198
- * `fibril_can_core/include/fibril_can_core/scalar_type.hpp` and
199
- * `fibril_can_core/src/scalar_type.cpp`.
200
- *
201
- * Type ordinal values are part of the protobuf schema blob's enum, so they
202
- * must not be renumbered.
186
+ * Scalar type enum and little-endian wire codecs. The ordinal values are
187
+ * part of the protobuf schema blob and must not be renumbered.
203
188
  */
204
189
  declare enum ScalarType {
205
190
  Bool = 0,
@@ -220,14 +205,9 @@ declare function scalarSize(type: ScalarType): number;
220
205
  declare function scalarName(type: ScalarType): string | undefined;
221
206
  declare function scalarFromName(name: string): ScalarType | undefined;
222
207
  /**
223
- * Pack an integer / bool value as little-endian bytes into `out` at `offset`.
224
- * Writes scalarSize(type) bytes — pass a wide-enough `out`.
225
- *
226
- * Pass `bigint` for 64-bit types (U64/I64). `number` is accepted for
227
- * everything (it is internally widened to bigint so signed callers get the
228
- * same sign-extension semantics as the C++ `pack_scalar_int_le(uint64_t)`).
229
- * Signed callers pass the negative number directly; the function performs the
230
- * 64-bit two's-complement cast.
208
+ * Pack an int/bool as little-endian into `out[offset..offset+scalarSize(type))`.
209
+ * `number` is widened to bigint so signed negatives sign-extend to 64-bit
210
+ * two's complement before truncation, matching C++ `pack_scalar_int_le`.
231
211
  */
232
212
  declare function packScalarIntLe(type: ScalarType, value: number | bigint, out: Uint8Array, offset?: number): void;
233
213
  /** Bit-cast a float32 to its IEEE-754 little-endian representation. */
@@ -311,74 +291,51 @@ interface NodeSchema {
311
291
  instances: SchemaBlockArray[];
312
292
  limits: WireLimits;
313
293
  }
294
+ interface DecodeSchemaResult {
295
+ schema: NodeSchema;
296
+ /** True when the blob's protocolVersion exceeds this build's {@link PROTOCOL_VERSION}. */
297
+ versionAheadOfMaster: boolean;
298
+ }
314
299
  /**
315
- * Parse a schema blob (proto3 binary) into the master-side IR.
316
- *
317
- * Throws {@link ProtoError} on malformed input or on
318
- * `protocolVersion !== 1` (the only version this codec understands —
319
- * mirrors C++ side's UnsupportedProtocolVersion behaviour).
300
+ * Parse a schema blob into the master-side IR. Throws {@link ProtoError}
301
+ * on malformed input or `protocolVersion === 0` (proto3 leaves an unset
302
+ * uint32 at 0, so zero means it was never populated).
320
303
  */
304
+ declare function decodeSchemaWithMeta(blob: Uint8Array): DecodeSchemaResult;
305
+ /** Shim for callers that don't need the metadata — prefer {@link decodeSchemaWithMeta}. */
321
306
  declare function decodeSchema(blob: Uint8Array): NodeSchema;
322
307
  /** Wire size in bytes for a single field (scalar or fixed array). */
323
308
  declare function fieldWireSize(field: SchemaField): number;
324
309
  //#endregion
325
310
  //#region src/dynamic-codec.d.ts
326
311
  /**
327
- * Field-driven dict ⇄ Uint8Array codec. The runtime equivalent of what
328
- * codegen would otherwise emit per message — encode/decode is a pure
329
- * function of the field list ({@link SchemaField}[]) and the JS dict.
330
- *
331
- * Conventions (matches CLAUDE.md "値の表現規約"):
332
- * - dict keys are the schema field name verbatim
333
- * - scalar ↔ JS `number`, except U64/I64 ↔ `bigint`
334
- * - bool ↔ JS `boolean`
335
- * - fixed-length array (`arrayLen > 0`) ↔ regular JS array
336
- * (`number[]` / `bigint[]` / `boolean[]`) of exactly `arrayLen` elements
337
- *
338
- * Throws on missing / wrong-shape inputs. Decode tolerates extra trailing
339
- * bytes (mirrors C++ side: the master ignores trailing slop so that future
340
- * schema additions don't break older decoders).
312
+ * Field-driven dict ⇄ Uint8Array codec. Throws on shape mismatch; decode
313
+ * tolerates trailing bytes so future schema additions don't break decoders.
341
314
  */
342
315
  declare class DynamicCodecError extends Error {
343
316
  constructor(message: string);
344
317
  }
345
- /** Tag union of accepted JS value shapes for one field. */
346
318
  type FieldValue = number | bigint | boolean | number[] | bigint[] | boolean[];
347
- /** Dict shape — string-keyed values, one entry per field. */
348
319
  type Dict = Record<string, FieldValue>;
349
320
  /**
350
321
  * Sum the wire size of a field list. The result is the byte count that
351
322
  * {@link encodeFields} will produce.
352
323
  */
353
324
  declare function fieldsWireSize(fields: readonly SchemaField[]): number;
354
- /**
355
- * Encode a dict into the canonical little-endian wire bytes for `fields`.
356
- * Returns a fresh `Uint8Array`. Throws {@link DynamicCodecError} on a
357
- * missing or wrong-shape field.
358
- */
325
+ /** Encode a dict into little-endian wire bytes. Throws on missing / wrong-shape fields. */
359
326
  declare function encodeFields(fields: readonly SchemaField[], value: Dict): Uint8Array;
360
- /**
361
- * Decode wire bytes into a dict keyed by field name. Extra trailing bytes
362
- * are ignored. Throws {@link DynamicCodecError} only if the payload is
363
- * shorter than the field list demands.
364
- */
327
+ /** Decode wire bytes into a dict. Trailing bytes are ignored; throws only if too short. */
365
328
  declare function decodeFields(fields: readonly SchemaField[], bytes: Uint8Array): Dict;
366
329
  //#endregion
367
330
  //#region src/numbering.d.ts
368
331
  /**
369
- * Flat-numbering tables for topics, services, and parameters (§4.2).
370
- *
371
- * Port of `fibril_can_core/src/numbering.cpp`. Numbering is a pure function
372
- * of (schema, instance-count-vector). The same tables are derived
373
- * independently by codegen (with max_count for static checks), the slave
374
- * runtime (with actual counts at boot) and the master (with counts from the
375
- * ANNOUNCE frame) — keeping this in one place is what guarantees they agree.
332
+ * Flat-numbering tables for topics, services, and parameters — a pure
333
+ * function of (schema, instance-count-vector). Codegen, slave runtime, and
334
+ * master all derive these tables through the same function.
376
335
  */
377
336
  declare const SERVICE_INDEX_CANCEL = 255;
378
337
  declare const SERVICE_INDEX_PARAM_SET = 253;
379
- /** Maximum first-class service_index produced by numbering (inclusive). */
380
338
  declare const SERVICE_INDEX_MAX = 252;
381
- /** Number of available first-class service slots. */
382
339
  declare const SERVICE_INDEX_CAPACITY: number;
383
340
  declare enum NumberingError {
384
341
  Ok = "ok",
@@ -388,23 +345,15 @@ declare enum NumberingError {
388
345
  InstanceCountMismatch = "actual_counts length != #instances",
389
346
  BlockTypeOutOfRange = "BlockArray::blockTypeIndex out of range"
390
347
  }
391
- /** Concrete location of one (instance, endpoint) within a NodeSchema. */
392
348
  interface EndpointKey {
393
- /** Index into NodeSchema.instances. */
394
349
  blockArray: number;
395
- /** 0..count-1 */
396
350
  instance: number;
397
- /** 0..nEndpoints(type)-1 */
398
351
  endpoint: number;
399
352
  }
400
- /** Forward + inverse maps for a single endpoint kind. */
401
353
  interface NumberingTable {
402
- /**
403
- * size = Σ_b(actualCounts[b] × nEndpoints(types[instances[b].blockTypeIndex]));
404
- * the array index IS the flat numbering.
405
- */
354
+ /** Array index is the flat numbering. */
406
355
  byIndex: EndpointKey[];
407
- /** Per-block-array base offsets (size = #blockArrays + 1; last is total). */
356
+ /** Per-block-array base offsets. Length = #blockArrays + 1; last is total. */
408
357
  bases: number[];
409
358
  }
410
359
  interface NumberingResult {
@@ -414,12 +363,8 @@ interface NumberingResult {
414
363
  status: NumberingError;
415
364
  }
416
365
  /**
417
- * Compute the three numbering tables. `actualCounts` must have the same
418
- * length as `schema.instances`. The result has `status !== Ok` if input is
419
- * malformed or service_index would overflow into the reserved range
420
- * (§4.2 §9.4).
421
- *
422
- * On error the returned tables are empty but still safe to inspect.
366
+ * `actualCounts.length` must equal `schema.instances.length`. On any error
367
+ * the returned tables are empty and `status !== Ok`.
423
368
  */
424
369
  declare function computeNumbering(schema: NodeSchema, actualCounts: readonly number[]): NumberingResult;
425
370
  //#endregion
@@ -458,10 +403,6 @@ interface ParamDescriptor {
458
403
  readonly maxValue: Uint8Array;
459
404
  }
460
405
  type EndpointDescriptor = TopicDescriptor | ServiceDescriptor | ParamDescriptor;
461
- /**
462
- * Path → descriptor index for one slave. Built once from
463
- * (schema, numbering) and reused for the lifetime of a snapshot.
464
- */
465
406
  declare class SchemaIndex {
466
407
  #private;
467
408
  constructor(schema: NodeSchema, numbering: NumberingResult, instanceCounts: readonly number[]);
@@ -509,13 +450,8 @@ declare class NoCallSlotError extends ServiceCallError {
509
450
  constructor(serviceIndex: number);
510
451
  }
511
452
  /**
512
- * Single-slave master service-call client. Port of NodeAgent's
513
- * begin_service_call / on_service_response / on_service_segment flow but
514
- * Promise-based and AbortSignal-aware.
515
- *
516
- * One {@link ServiceClient} attaches to the transport and demuxes incoming
517
- * SVC_RESPONSE frames for a single `nodeId`. NodeAgent owns one per slave
518
- * and exposes it on the running snapshot.
453
+ * Promise-based, AbortSignal-aware service-call client for one slave.
454
+ * NodeAgent owns one per slave and exposes it on the running snapshot.
519
455
  */
520
456
  declare class ServiceClient {
521
457
  #private;
@@ -542,24 +478,8 @@ declare class ServiceClient {
542
478
  //#endregion
543
479
  //#region src/typed-param.d.ts
544
480
  /**
545
- * Schema-driven typed wrapper around PARAM_SET (SPEC §8.2, reserved
546
- * service_index 0xFD).
547
- *
548
- * Per SPEC §8.1 PARAM is M→S one-way: the master holds the source of truth
549
- * and re-injects defaults at provisioning time, so this wrapper exposes
550
- * `.set()` only — there's no `.get()` that queries the slave. The
551
- * descriptor's decoded default is available via `defaultValue()` for
552
- * reference / hydration of master-side state.
553
- *
554
- * The wire shape for one PARAM_SET request is:
555
- * [0] count = 1
556
- * [1..2] u16 param_index (LE)
557
- * [3..] value (scalar little-endian or fixed-array contiguous)
558
- *
559
- * Response is `[0] status [1..2] failed_param_index (when status != OK)`.
560
- * `set()` resolves with the status code; non-OK includes `failedIndex` so
561
- * the caller can correlate with a multi-param batch (single-param sets
562
- * always blame this param).
481
+ * Typed wrapper around PARAM_SET (service_index 0xFD). PARAM is M→S
482
+ * one-way, so this exposes `.set()` and `.defaultValue()` only.
563
483
  */
564
484
  declare class ParamCodecError extends Error {
565
485
  constructor(message: string);
@@ -569,13 +489,9 @@ interface TypedParamSetOptions {
569
489
  signal?: AbortSignal;
570
490
  }
571
491
  interface TypedParamSetResult {
572
- /** SPEC §7.2 status (0 = SVC_OK). */
492
+ /** SVC_OK = 0; non-zero = slave error code. */
573
493
  readonly status: number;
574
- /**
575
- * On `status != SVC_OK`, the slave-reported param_index that failed.
576
- * For a single-param call this is always `descriptor.paramIndex` if the
577
- * slave fills the field, else `null` (slave omitted the field).
578
- */
494
+ /** Slave-reported failing param_index (non-OK only); null if omitted. */
579
495
  readonly failedIndex: number | null;
580
496
  }
581
497
  /** Build a PARAM_SET request payload for a single (descriptor, value) pair. */
@@ -585,11 +501,7 @@ declare class TypedParam<T> {
585
501
  readonly path: string;
586
502
  readonly descriptor: ParamDescriptor;
587
503
  constructor(client: ServiceClient, descriptor: ParamDescriptor);
588
- /**
589
- * Decode the schema-embedded default value (SPEC §9.1 `default`). Useful
590
- * to seed the master-side persistence store. Returns `null` if the
591
- * descriptor carries no default payload.
592
- */
504
+ /** Decoded schema default; `null` if the descriptor carries no default. */
593
505
  defaultValue(): T | null;
594
506
  /** Send a PARAM_SET with a single entry for this param. */
595
507
  set(value: T, opts?: TypedParamSetOptions): Promise<TypedParamSetResult>;
@@ -597,20 +509,9 @@ declare class TypedParam<T> {
597
509
  //#endregion
598
510
  //#region src/typed-service.d.ts
599
511
  /**
600
- * Schema-driven typed wrapper around {@link ServiceClient.call}.
601
- *
602
- * The generic parameters are compile-time only: at runtime, encoding is
603
- * driven entirely by the {@link ServiceDescriptor}'s field list. Callers
604
- * supply `Req` / `Resp` interfaces matching the schema fields by name —
605
- * the runtime trusts the dict layout and packs by field iteration order.
606
- *
607
- * Returned on success: `{ status, data }` where `status` is the SPEC §7.2
608
- * service status (0 = OK) and `data` is the decoded response dict typed as
609
- * `Resp`. Non-OK responses still return a `data` object — the slave is
610
- * required to fill the response payload even on failure (§7.2), and the
611
- * decoder will best-effort attempt it; if the body is shorter than the
612
- * schema demands, `data` is `null` instead of throwing (so callers can
613
- * surface the status to the user without losing the whole response).
512
+ * Result of a {@link TypedService.call}. `status` follows SPEC §7.2 (0 = OK);
513
+ * `data` is best-effort even on non-OK, and `null` if the response body is
514
+ * shorter than the schema demands.
614
515
  */
615
516
  interface TypedServiceResult<Resp extends Dict> {
616
517
  readonly status: number;
@@ -630,35 +531,20 @@ declare class TypedService<Req extends Dict, Resp extends Dict> {
630
531
  }
631
532
  //#endregion
632
533
  //#region src/node-agent.d.ts
633
- /**
634
- * One planned data-plane CAN frame, supplied by the caller (typically a
635
- * frame planner — P6) and consumed by {@link NodeAgent} when it issues
636
- * FRAME_DEFINE during bring-up. Mirrors `FrameSpec` in
637
- * `fibril_can_bridge/include/fibril_can_bridge/frame_planner.hpp`.
638
- */
534
+ /** One planned data-plane CAN frame consumed by FRAME_DEFINE during bring-up. */
639
535
  interface FramePlan {
640
536
  stdId: number;
641
537
  periodUs: number;
642
538
  dir: Direction;
643
539
  entries: readonly FrameDefineEntry[];
644
540
  }
645
- /**
646
- * Per-bring-up context handed to a {@link FramePlanFactory} callback so the
647
- * caller can branch on the specific slave being provisioned (e.g. read an
648
- * external per-nodeId field-enable override map). The same factory is
649
- * shared across every {@link NodeAgent} the controller spawns, so this is
650
- * the only way for the factory to discriminate slaves.
651
- */
541
+ /** Context handed to {@link FramePlanFactory} so it can branch on the slave. */
652
542
  interface FramePlanContext {
653
- /** The slave being provisioned (1..0x7E). */
654
543
  readonly nodeId: number;
655
544
  }
656
545
  /**
657
- * Builds the FRAME_DEFINE plan for a slave once its schema is decoded and
658
- * numbered. The optional `ctx` argument carries the nodeId so callers that
659
- * keep per-slave overrides (e.g. UI-controlled S→M field toggles) can fork
660
- * on it without threading state through {@link NodeAgentOptions}. The
661
- * 2-arg form is still accepted — extra parameters are simply ignored.
546
+ * Builds the FRAME_DEFINE plan once schema and numbering are known.
547
+ * `ctx.nodeId` lets a shared factory fork on the specific slave.
662
548
  */
663
549
  type FramePlanFactory = ((schema: NodeSchema, numbering: NumberingResult, ctx: FramePlanContext) => readonly FramePlan[]) | readonly FramePlan[];
664
550
  /** Phase machine — public for observers. */
@@ -675,17 +561,12 @@ type NodeProvisionState = {
675
561
  } | {
676
562
  kind: 'provisioning';
677
563
  pendingDefines: number;
678
- }
679
- /**
680
- * FRAME_DEFINE completed successfully but START has been withheld by
681
- * the {@link NodeAgentOptions.autoStart} gate. `startNow()` moves the
682
- * agent forward. `reprovision()` / a fresh ANNOUNCE re-enter the flow
683
- * from bring-up.
684
- */
685
- | {
564
+ } /** FRAME_DEFINE done, autoStart blocked START. `startNow()` moves forward. */ | {
686
565
  kind: 'awaiting-start';
687
566
  } | {
688
567
  kind: 'running';
568
+ } /** Slave reported SUSPENDED. Reverts to running on the next non-SUSPENDED HEARTBEAT. */ | {
569
+ kind: 'suspended';
689
570
  } | {
690
571
  kind: 'lost';
691
572
  reason: string;
@@ -707,22 +588,11 @@ interface NodeRunningSnapshot {
707
588
  stdIdToTopicIndex: Map<number, number>;
708
589
  /** §7 master-side RPC client. Stays valid for the lifetime of the snapshot. */
709
590
  services: ServiceClient;
710
- /**
711
- * Path → IR descriptor lookup. Drives the typed accessors below.
712
- * Exposed for sniffer-style introspection (`index.topicPaths()` etc.).
713
- */
591
+ /** Path → IR descriptor lookup; drives `service()` / `param()` below. */
714
592
  index: SchemaIndex;
715
- /**
716
- * Schema-driven typed RPC wrapper. Throws if `path` is unknown. Generic
717
- * `Req` / `Resp` are compile-time only — runtime encoding is driven by
718
- * the schema's field list.
719
- */
593
+ /** Typed RPC wrapper. Throws if `path` is unknown. */
720
594
  service<Req extends Dict, Resp extends Dict>(path: string): TypedService<Req, Resp>;
721
- /**
722
- * Schema-driven PARAM_SET wrapper. Throws if `path` is unknown.
723
- * `T` is `number` for scalar params (except U64/I64 → `bigint`),
724
- * `boolean` for bool, or the array form for fixed-length array params.
725
- */
595
+ /** Typed PARAM_SET wrapper. Throws if `path` is unknown. */
726
596
  param<T = number | bigint | boolean | number[] | bigint[] | boolean[]>(path: string): TypedParam<T>;
727
597
  }
728
598
  interface NodeAgentOptions {
@@ -732,59 +602,34 @@ interface NodeAgentOptions {
732
602
  transport: ITransport;
733
603
  /** Strategy for building the FRAME_DEFINE plan. */
734
604
  framePlan: FramePlanFactory;
735
- /**
736
- * Per-config-command ACK timeout in ms. Slaves must ACK within this
737
- * window or the agent abandons the bring-up attempt and waits for the
738
- * next ANNOUNCE. Default 250 ms (matches bridge's
739
- * `bring_up_ack_timeout_`).
740
- */
605
+ /** Per-config-command ACK timeout in ms. Default 250. */
741
606
  ackTimeoutMs?: number;
742
- /**
743
- * SCHEMA_READ chunk byte limit. SPEC §4.3 caps a single response at the
744
- * SINGLE-frame payload (≤59 data bytes after the 4-byte offset header).
745
- * Default 59.
746
- */
607
+ /** SCHEMA_READ chunk byte limit — caps at the SINGLE-frame payload. Default 59. */
747
608
  schemaChunkSize?: number;
748
- /**
749
- * Multiplier on `ackTimeoutMs` used as the schema-read retry budget.
750
- * Default 3 (one initial + two retries).
751
- */
609
+ /** SCHEMA_READ retry budget. Default 3. */
752
610
  schemaReadRetries?: number;
753
- /**
754
- * Heartbeat-loss budget. The agent expects a HEARTBEAT every 100 ms
755
- * (§14); after this much silence it transitions to `lost`. Default 350.
756
- */
611
+ /** HEARTBEAT-loss budget in ms. Default 350. */
757
612
  heartbeatTimeoutMs?: number;
758
613
  /** Per-call default timeout for `services.call()`. Default `ackTimeoutMs`. */
759
614
  serviceTimeoutMs?: number;
760
615
  /**
761
- * Gate consulted right before START is issued. When it returns `false`,
762
- * the agent stops in {@link NodeProvisionState} `awaiting-start` after
763
- * FRAME_DEFINE ACKs come back; the caller commits with `startNow()`.
764
- * Read fresh at each decision point so callers can flip the setting at
765
- * runtime without reconstructing the agent. Default `() => true`.
616
+ * Gate consulted before each START. Returning `false` parks in
617
+ * `awaiting-start`; caller commits with `startNow()`. Read fresh each
618
+ * time so the flag can flip at runtime. Default `() => true`.
766
619
  */
767
620
  autoStart?: () => boolean;
768
621
  /** Optional state-change observer. */
769
622
  onState?: (state: NodeProvisionState) => void;
770
- /** Optional non-fatal error observer (decode warnings, hash mismatch). */
623
+ /** Optional non-fatal error observer. */
771
624
  onError?: (err: Error) => void;
772
- /**
773
- * Override for the wall clock. Tests pass a deterministic counter.
774
- * Default `() => Date.now()`.
775
- */
625
+ /** Wall-clock override for tests. Default `() => Date.now()`. */
776
626
  now?: () => number;
777
627
  }
778
628
  /**
779
- * Per-slave provisioning state machine. Mirrors
780
- * `fibril_can_bridge::NodeAgent` lifecycle entry points but trimmed for the
781
- * single-threaded TS master — no ROS entity creation, no PARAM_SET
782
- * re-injection (P5), no plan computation (P6).
783
- *
784
- * Lifecycle: `idle` → `awaiting-announce` → `schema-reading` (skipped if
785
- * blob already cached on this agent instance) → `numbering` →
786
- * `provisioning` → `running`. HEARTBEAT loss drops to `lost`; the next
787
- * ANNOUNCE restarts the flow.
629
+ * Per-slave provisioning state machine.
630
+ * `idle` → `awaiting-announce` → `schema-reading` (skipped on cache hit) →
631
+ * `numbering` → `provisioning` → `running`. HEARTBEAT loss drops to `lost`;
632
+ * the next ANNOUNCE restarts the flow.
788
633
  */
789
634
  declare class NodeAgent {
790
635
  #private;
@@ -795,59 +640,32 @@ declare class NodeAgent {
795
640
  get state(): NodeProvisionState;
796
641
  get snapshot(): NodeRunningSnapshot | null;
797
642
  /**
798
- * Attach to the transport and wait for ANNOUNCE. Resolves immediately —
799
- * progress flows via {@link state} / {@link snapshot} / `onState`. Call
800
- * `stop()` to detach.
643
+ * Attach and wait for ANNOUNCE. Resolves immediately; progress flows via
644
+ * {@link state} / {@link snapshot} / `onState`.
801
645
  */
802
646
  start(): Promise<void>;
803
647
  /**
804
- * Re-run the FRAME_DEFINE / START handshake against the live slave using
805
- * the most recent ANNOUNCE + cached schema. The {@link FramePlanFactory}
806
- * is invoked again — callers that key off an external mutable state
807
- * (e.g. per-field TX-enable overrides) will see the latest values.
808
- *
809
- * The agent must have observed an ANNOUNCE already; `reprovision()`
810
- * before that throws. RUNNING is required so that the slave is known to
811
- * be reachable — calling during `lost` would race the heartbeat
812
- * watchdog. Pending service calls are failed and the current snapshot is
813
- * torn down before the new bring-up begins, mirroring what happens on a
814
- * boot_id change.
648
+ * Re-run FRAME_DEFINE / START against the live slave with the most recent
649
+ * ANNOUNCE + cached schema. Invokes {@link FramePlanFactory} again so
650
+ * per-slave overrides pick up their latest values. Requires a prior
651
+ * ANNOUNCE. Tears down the current snapshot and fails pending service
652
+ * calls, mirroring a boot_id change.
815
653
  */
816
654
  reprovision(): Promise<void>;
817
655
  /** Detach, send STOP if currently RUNNING, clear all in-flight state. */
818
656
  stop(): Promise<void>;
819
657
  /**
820
- * Commit the deferred START when {@link NodeAgentOptions.autoStart}
821
- * gated the previous bring-up. Only legal in `awaiting-start`; any
822
- * other state throws so callers spot logic bugs instead of racing
823
- * with a fresh ANNOUNCE.
658
+ * Commit the deferred START. Only legal in `awaiting-start`; throws
659
+ * elsewhere so callers spot logic bugs instead of racing a fresh ANNOUNCE.
824
660
  */
825
661
  startNow(): Promise<void>;
826
662
  }
827
663
  //#endregion
828
664
  //#region src/data-plane.d.ts
829
665
  /**
830
- * Master-side decoder for S→M data-plane frames.
831
- *
832
- * Listens on the transport for standard-ID frames whose IDs match one of
833
- * the std_ids configured in any attached slave's {@link FramePlan}. For
834
- * each match, unpacks the topic entries packed into the frame's payload
835
- * (in FRAME_DEFINE order) using the schema field bitmask and publishes
836
- * a decoded {@link Dict} per topic to subscribers.
837
- *
838
- * Lifecycle:
839
- * - `new DataPlaneReceiver({ transport })` registers an `onFrame` tap.
840
- * - `attachSlave(nodeId, framePlan, schemaIndex)` claims a slave's std_ids.
841
- * - `detachSlave(nodeId)` releases them (call on RUNNING → not-RUNNING).
842
- * - `dispose()` unsubscribes and clears all state.
843
- *
844
- * Two slaves cannot publish on the same std_id — attaching the second
845
- * throws. This mirrors the bus-level reality: identical std_ids would
846
- * collide on the wire.
847
- *
848
- * M→S frames (master output, dir `Direction.M2S`) are skipped during
849
- * attach so the receiver doesn't try to decode echo or self-loopback
850
- * frames as if they were slave-published.
666
+ * Master-side decoder for S→M data-plane frames. `attachSlave` claims a
667
+ * slave's std_ids; two slaves cannot share a std_id (throws). M→S frames
668
+ * are skipped so echo / self-loopback isn't decoded as slave-published.
851
669
  */
852
670
  interface DataPlaneReceiverOptions {
853
671
  transport: ITransport;
@@ -862,23 +680,16 @@ interface TopicSample {
862
680
  readonly tsMs: number;
863
681
  }
864
682
  interface TopicStats {
865
- /** Frames ever decoded into this topic since the slave attached. */
866
683
  readonly count: number;
867
684
  /** `now()` of the most recent frame. */
868
685
  readonly lastRxMs: number;
869
- /**
870
- * Rolling rate over the last few samples (Hz). 0 until at least
871
- * two frames have been seen.
872
- */
686
+ /** Rolling rate in Hz over the last {@link RATE_WINDOW} samples; 0 with <2 samples. */
873
687
  readonly ratePerSec: number;
874
688
  }
875
689
  declare class DataPlaneReceiver {
876
690
  #private;
877
691
  constructor(opts: DataPlaneReceiverOptions);
878
- /**
879
- * Claim a slave's S→M std_ids. Re-attaching the same nodeId atomically
880
- * detaches the prior plan first (re-provisioning case).
881
- */
692
+ /** Claim a slave's S→M std_ids. Re-attach atomically detaches the prior plan. */
882
693
  attachSlave(nodeId: number, framePlan: readonly FramePlan[], index: SchemaIndex): void;
883
694
  /** Release a slave's std_ids and drop its cached topic samples. */
884
695
  detachSlave(nodeId: number): void;
@@ -894,19 +705,10 @@ declare class DataPlaneReceiver {
894
705
  //#endregion
895
706
  //#region src/frame-planner.d.ts
896
707
  /**
897
- * Frame planner — turns a list of "I want field X at period Y" requests into
898
- * the concrete FRAME_DEFINE bodies the master ships to the slave (§6.3).
899
- *
900
- * Port of `fibril_can_core/src/frame_planner.cpp` — same bucketing rules:
901
- * - Group requests by (direction, period_us); never combine across either.
902
- * - Inside a bucket, bin-pack whole topics into ≤64-byte frames.
903
- * - A single topic whose declared fields exceed 64 bytes is the sole
904
- * field-split exception (`emitOversizedTopic`).
905
- *
906
- * Determinism is load-bearing: codegen, bridge, and this TS master must
907
- * produce identical std_id assignments for the same input. Both the
908
- * bucket map and the per-bucket topic map iterate in ascending key order
909
- * (matching `std::map` in the C++ reference).
708
+ * Field-requests → FRAME_DEFINE bodies. Groups by (dir, period_us) and
709
+ * bin-packs whole topics into ≤64-byte frames; a topic that exceeds 64 B
710
+ * is the sole field-split exception. Iteration order is ascending on
711
+ * every level so codegen / bridge / master produce identical std_ids.
910
712
  */
911
713
  interface FieldRequest {
912
714
  topicIndex: number;
@@ -926,9 +728,8 @@ interface FrameSpec {
926
728
  entries: FrameDefineEntry[];
927
729
  }
928
730
  /**
929
- * 11-bit Standard ID allocator with a free pool for IDs released after a
930
- * grace window (§11.4.1). `next()` returns `null` on exhaustion rather
931
- * than wrapping — wrapping past 0x7FF would alias on the wire.
731
+ * 11-bit std_id allocator with a delayed free pool. `next()` returns `null`
732
+ * on exhaustion — wrapping past 0x7FF would alias on the wire.
932
733
  */
933
734
  declare class StdIdAllocator {
934
735
  #private;
@@ -937,26 +738,16 @@ declare class StdIdAllocator {
937
738
  reset(start?: number): void;
938
739
  }
939
740
  /**
940
- * Plan a slave's frame layout. Returns `null` if the StdIdAllocator runs
941
- * out of 11-bit IDs mid-plan — the caller MUST surface that; a partial
942
- * plan would silently drop fields.
741
+ * Plan a slave's frame layout. Returns `null` on std_id exhaustion — a
742
+ * partial plan would silently drop fields.
943
743
  */
944
744
  declare function planFrames(requests: readonly FieldRequest[], alloc: StdIdAllocator): FrameSpec[] | null;
945
745
  //#endregion
946
746
  //#region src/default-plan.d.ts
947
747
  /**
948
- * Per-field predicate handed to {@link buildDefaultS2MRequests} (and via
949
- * it, {@link defaultS2MFramePlan}) so callers can apply a runtime mask on
950
- * top of the schema-declared `txEnabled`.
951
- *
952
- * Contract:
953
- * - Returning `false` skips the field entirely; the slave will not pack
954
- * it into any S→M frame. Returning `true` keeps it.
955
- * - Called only for S→M fields whose schema declares `txEnabled !==
956
- * false` (schema-disabled fields are filtered out unconditionally).
957
- * - `topicPath` is the same `'<expanded_ns>/<topic_name>'` string the
958
- * master uses elsewhere (mirrors {@link SchemaIndex.topicPaths}), so
959
- * UI override maps can key on the same identifier.
748
+ * Predicate for masking S→M fields on top of the schema-declared `txEnabled`.
749
+ * Only called for fields with `txEnabled !== false`. Return `false` to drop
750
+ * the field. `topicPath` mirrors {@link SchemaIndex.topicPaths}.
960
751
  */
961
752
  type S2MFieldFilter = (info: {
962
753
  topicPath: string;
@@ -966,84 +757,37 @@ type S2MFieldFilter = (info: {
966
757
  field: SchemaField;
967
758
  }) => boolean;
968
759
  /**
969
- * Synthesize the default {@link FieldRequest} list from a decoded schema +
970
- * numbering table — every S2M topic field at its declared period
971
- * (`field.periodUsOverride` if present, else the owning topic's
972
- * `defaultPeriodUs`), skipping `txEnabled === false` fields.
973
- *
974
- * Pass `filter` to layer a runtime per-field mask on top (UI toggles,
975
- * per-slave overrides). Schema-disabled fields are unconditionally
976
- * dropped before `filter` is consulted, so callers don't have to repeat
977
- * the `txEnabled` check.
978
- *
979
- * This is the natural input to {@link planFrames} when the master just
980
- * wants to "observe everything the slave publishes" — i.e. the
981
- * sniffer-style auto-provision path.
760
+ * Every schema-enabled S→M field at its declared period
761
+ * (`field.periodUsOverride` ?? topic's `defaultPeriodUs`), optionally masked
762
+ * further by `filter`. Feeds {@link planFrames} for the observe-everything
763
+ * auto-provision path.
982
764
  */
983
765
  declare function buildDefaultS2MRequests(schema: NodeSchema, numbering: NumberingResult, filter?: S2MFieldFilter): FieldRequest[];
984
766
  /**
985
- * One-line FramePlanFactory that subscribes to every S2M field at its
986
- * declared period using a freshly-allocated {@link StdIdAllocator}.
987
- *
988
- * Pass `filter` to apply a runtime per-field mask (e.g. a UI-controlled
989
- * field-tx-enable map). With no filter, every schema-enabled S→M field is
990
- * subscribed.
991
- *
992
- * Returns `[]` when planning fails (e.g. std_id exhaustion); the caller's
993
- * NodeAgent will then leave the slave in `provisioning` → `awaiting-announce`
994
- * with no frames defined, which is the right outcome — partial plans would
995
- * silently drop fields.
767
+ * FramePlanFactory subscribing to every schema-enabled S→M field (optionally
768
+ * masked by `filter`) with a fresh {@link StdIdAllocator}. Returns `[]` on
769
+ * planning failure so NodeAgent stalls in `awaiting-announce` — a partial
770
+ * plan would silently drop fields.
996
771
  */
997
772
  declare function defaultS2MFramePlan(schema: NodeSchema, numbering: NumberingResult, filter?: S2MFieldFilter): FramePlan[];
998
773
  //#endregion
999
774
  //#region src/dlc.d.ts
1000
- /**
1001
- * CAN FD valid payload sizes in ascending order (§4.3).
1002
- * Mirrors `fibril_can_core/include/fibril_can_core/packing.hpp::kCanFdDlcSteps`
1003
- * and `fibril_can_runtime/src/fcan_wire.c::fcan_quantize_dlc`.
1004
- */
775
+ /** Valid CAN FD payload sizes, ascending. */
1005
776
  declare const FD_DLC_STEPS: readonly number[];
1006
- /**
1007
- * Round `bytes` up to the next valid CAN FD payload step. Returns 64 for any
1008
- * input ≥ 64.
1009
- */
777
+ /** Round `bytes` up to the next valid CAN FD payload step (caps at 64). */
1010
778
  declare function quantizeDlc(bytes: number): number;
1011
779
  /**
1012
- * Grow `data` to the next DLC step by appending zero bytes. Returns a new
1013
- * Uint8Array. Never shrinks: if `data` already exceeds 64 bytes (which is
1014
- * a self-inconsistent frame the slave would misparse), the original is
1015
- * returned unchanged so the backend rejects it loudly (parity with
1016
- * `pad_to_dlc` in `fibril_can_bridge/src/config_plane_codec.cpp`).
780
+ * Pad `data` to the next DLC step with zeros. Over-length input (>64 B, which
781
+ * the slave would misparse) is returned unchanged so the backend rejects it.
1017
782
  */
1018
783
  declare function padToDlc(data: Uint8Array): Uint8Array;
1019
784
  //#endregion
1020
785
  //#region src/master-controller.d.ts
1021
786
  /**
1022
- * Multi-slave coordinator.
1023
- *
1024
- * One {@link MasterController} owns:
1025
- * - a single transport (the bus)
1026
- * - one {@link MasterHeartbeatBroadcaster} (shared across slaves)
1027
- * - a single broadcast DISCOVER at start() to flush every slave's frame
1028
- * table and pull fresh ANNOUNCEs onto the bus
1029
- * - a `Map<nodeId, NodeAgent>` of provisioned slaves — auto-spawned on
1030
- * the first ANNOUNCE / HEARTBEAT from a previously-unseen nodeId
1031
- *
1032
- * Periodic broadcast DISCOVER is intentionally NOT done. Broadcast
1033
- * DISCOVER is destructive (§5.11): every receiving slave clears its frame
1034
- * table and drops to UNPROVISIONED. Late-joining slaves are covered by
1035
- * their own spontaneous ANNOUNCE × 3 at boot (§5.4); the user-initiated
1036
- * "Discover" button (`discoverNow()`) is the escape hatch for the rare
1037
- * case where the master came up first and missed the burst.
1038
- *
1039
- * Provisioning per slave still goes through {@link NodeAgent}. The
1040
- * controller is the right place to add cross-slave concerns later (PARAM
1041
- * persistence, name ownership, lab-mode multiplexers) without bloating
1042
- * `NodeAgent`.
1043
- *
1044
- * The controller does NOT replace `NodeAgent` for single-slave embedded
1045
- * use cases (e.g. tests, a fixed-topology setup); it sits one layer above
1046
- * and is the natural entrypoint for the Web UI.
787
+ * Multi-slave coordinator. Owns the transport, one MASTER_HEARTBEAT
788
+ * broadcaster, and a `Map<nodeId, NodeAgent>` populated on first sight of
789
+ * ANNOUNCE / HEARTBEAT. Broadcast DISCOVER is destructive so it is fired
790
+ * only once at start(); use `discoverNow()` on demand.
1047
791
  */
1048
792
  /** Default policy hooks applied to every spawned NodeAgent. */
1049
793
  interface MasterControllerOptions {
@@ -1054,20 +798,11 @@ interface MasterControllerOptions {
1054
798
  /** MASTER_HEARTBEAT broadcast period in ms. Default 100 (§5.11). */
1055
799
  heartbeatPeriodMs?: number;
1056
800
  /**
1057
- * Broadcast DISCOVER period in ms. Default 0 (disabled). Setting >0 is
1058
- * destructive — every periodic broadcast DISCOVER wipes every slave's
1059
- * frame table and forces re-provisioning, breaking S→M topic flow until
1060
- * each NodeAgent re-pushes its FRAME_DEFINEs. Useful only for very
1061
- * niche reconnect-storm scenarios; prefer `discoverNow()` for on-demand
1062
- * rediscovery and rely on slave spontaneous ANNOUNCE × 3 (§5.4) for
1063
- * normal late-join.
801
+ * Periodic broadcast DISCOVER period in ms. Default 0 (disabled). Each
802
+ * firing wipes every slave's frame table — prefer `discoverNow()`.
1064
803
  */
1065
804
  discoverPeriodMs?: number;
1066
- /**
1067
- * Send a single broadcast DISCOVER inside `start()` to flush stale
1068
- * slave state from a previous master session. Default true. Set false
1069
- * in tests that drive ANNOUNCE manually and need a quiet bus.
1070
- */
805
+ /** One broadcast DISCOVER inside `start()` to flush stale slaves. Default true. */
1071
806
  initialDiscover?: boolean;
1072
807
  /** Per-NodeAgent ACK timeout in ms. Default 250 (matches NodeAgent). */
1073
808
  ackTimeoutMs?: number;
@@ -1075,12 +810,7 @@ interface MasterControllerOptions {
1075
810
  heartbeatTimeoutMs?: number;
1076
811
  /** Per-service-call default timeout in ms. Default `ackTimeoutMs`. */
1077
812
  serviceTimeoutMs?: number;
1078
- /**
1079
- * Read-through gate applied to every spawned NodeAgent right before it
1080
- * issues START. Callers pass a getter (not a boolean) so the flag can
1081
- * be flipped at runtime — see `NodeAgentOptions.autoStart`. Default
1082
- * `() => true`.
1083
- */
813
+ /** Getter (not a boolean) gating START on every spawned NodeAgent. Default `() => true`. */
1084
814
  autoStart?: () => boolean;
1085
815
  /** Optional clock override (tests). Default `() => Date.now()`. */
1086
816
  now?: () => number;
@@ -1105,48 +835,32 @@ declare class MasterController {
1105
835
  #private;
1106
836
  constructor(opts: MasterControllerOptions);
1107
837
  /**
1108
- * Master-side decoder for S→M topic frames. Auto-attached / detached as
1109
- * each slave transitions in and out of `running`. Subscribe with
1110
- * `dataPlane.onTopic(nodeId, path, listener)`; read latest values with
1111
- * `dataPlane.latest(nodeId, path)`.
838
+ * S→M topic decoder. Auto-attaches / detaches on each slave's `running`
839
+ * transition; subscribe via `dataPlane.onTopic` / read via `dataPlane.latest`.
1112
840
  */
1113
841
  get dataPlane(): DataPlaneReceiver;
1114
- /**
1115
- * Attach to the transport, start MASTER_HEARTBEAT, fire one initial
1116
- * broadcast DISCOVER to flush stale slave state from a previous master
1117
- * session. New slaves auto-spawn a NodeAgent on first sight.
1118
- */
1119
842
  start(): void;
1120
843
  /** Stop all internals, stop every NodeAgent, detach from the transport. */
1121
844
  stop(): Promise<void>;
1122
845
  /** Snapshot of all known slaves. Stable identity per call. */
1123
846
  snapshots(): ControllerSlaveSnapshot[];
1124
- /** Subscribe to slave-set / state-change updates. Fires on every change. */
847
+ /** Subscribe to slave-set / state-change updates. Fires immediately with the current snapshot, then on every change. */
1125
848
  onChange(cb: ControllerListener): Unsubscribe;
1126
- /**
1127
- * Access the NodeAgent for one slave, if it exists. Returns null until
1128
- * the slave has been observed. Useful for service / param calls from
1129
- * code that already knows the nodeId.
1130
- */
849
+ /** NodeAgent for one slave, or null if not yet observed. */
1131
850
  agent(nodeId: number): NodeAgent | null;
1132
- /** Issue an ad-hoc broadcast DISCOVER (between periodic firings). */
851
+ /** Ad-hoc broadcast DISCOVER. */
1133
852
  discoverNow(): Promise<void>;
1134
853
  /**
1135
- * Commit the deferred START on one slave that is parked in
1136
- * `awaiting-start` because the autoStart gate blocked its bring-up.
1137
- * Returns `false` if the slave is unknown or not currently waiting
1138
- * for a manual start; otherwise resolves once START has been queued
1139
- * on the wire (the RUNNING transition arrives later via `onChange`).
854
+ * Commit the deferred START on a slave parked in `awaiting-start`.
855
+ * Returns `false` if the slave is unknown or not waiting; the RUNNING
856
+ * transition arrives later via `onChange`.
1140
857
  */
1141
858
  startNode(nodeId: number): Promise<boolean>;
1142
859
  /**
1143
- * Re-run the FRAME_DEFINE / START handshake for one slave so the
1144
- * {@link FramePlanFactory} is consulted again. Use when external state
1145
- * the factory closes over has changed (e.g. per-field TX-enable
1146
- * toggles). Returns `false` when the slave is unknown or has never
1147
- * ANNOUNCEd; otherwise resolves once the new bring-up has been kicked
1148
- * off (it does not await the next RUNNING transition — listen via
1149
- * {@link onChange} instead).
860
+ * Re-run FRAME_DEFINE / START so the {@link FramePlanFactory} is consulted
861
+ * again (e.g. after a UI-driven field-tx-enable change). Returns `false`
862
+ * if unknown / never ANNOUNCEd; resolves once bring-up is kicked off, not
863
+ * when RUNNING is reached — listen via {@link onChange}.
1150
864
  */
1151
865
  reprovision(nodeId: number): Promise<boolean>;
1152
866
  }
@@ -1178,7 +892,7 @@ declare class ProtoReader {
1178
892
  constructor(buf: Uint8Array, end?: number);
1179
893
  get done(): boolean;
1180
894
  get position(): number;
1181
- /** Read a base-128 varint as a JS number (overflows ≥ 2^53 wrap silently). */
895
+ /** Read a base-128 varint as a JS number (schema uses uint32 only). */
1182
896
  readVarint(): number;
1183
897
  /** Read a length-delimited byte slice (no copy). */
1184
898
  readBytes(): Uint8Array;
@@ -1186,11 +900,7 @@ declare class ProtoReader {
1186
900
  readString(): string;
1187
901
  /** Read a length-delimited sub-message and return a new reader scoped to it. */
1188
902
  readMessage(): ProtoReader;
1189
- /**
1190
- * Read the next tag and return both the field number and wire type. The
1191
- * caller is expected to dispatch on field number; unknown fields can be
1192
- * passed to {@link skipValue}.
1193
- */
903
+ /** Read a tag as `{field, wire}`. Unknown fields dispatch to {@link skipValue}. */
1194
904
  readTag(): {
1195
905
  field: number;
1196
906
  wire: number;
@@ -1203,80 +913,45 @@ declare class ProtoError extends Error {
1203
913
  }
1204
914
  //#endregion
1205
915
  //#region src/schema-hash.d.ts
1206
- /**
1207
- * SHA-256 helpers for the schema blob (§5.4, §9.3).
1208
- *
1209
- * Both codegen (C++) and the bridge (C++) hash the entire schema blob with
1210
- * SHA-256 and pack the first 8 bytes as little-endian u64 into the ANNOUNCE
1211
- * frame. This module is the master-side equivalent: hash the blob we
1212
- * received over SCHEMA_DATA and compare with the schema_hash the slave
1213
- * advertised in ANNOUNCE.
1214
- *
1215
- * Uses Web Crypto (`crypto.subtle`) — available in modern browsers and Node
1216
- * ≥ 19 / Deno / Bun, no polyfill needed for our target environments.
1217
- */
1218
916
  declare function sha256(bytes: Uint8Array): Promise<Uint8Array>;
1219
- /**
1220
- * Compute the ANNOUNCE-shape schema hash: the first 8 bytes of SHA-256(blob),
1221
- * interpreted as a little-endian u64. Mirrors
1222
- * `fibril_can_core/sha256::hash_prefix_u64`.
1223
- */
917
+ /** First 8 bytes of SHA-256(blob) as little-endian u64 — the ANNOUNCE shape. */
1224
918
  declare function schemaHashPrefix(blob: Uint8Array): Promise<bigint>;
1225
919
  //#endregion
1226
920
  //#region src/segmented-transfer.d.ts
1227
921
  /**
1228
- * Build the §7.4 SVC_REQUEST frame stream for a master→slave call. Mirrors
1229
- * `make_svc_request_frames` in
1230
- * `fibril_can_bridge/include/fibril_can_bridge/svc_request.hpp`.
1231
- *
1232
- * - ≤64 byte payload → one SINGLE frame (seg=0).
1233
- * - Larger → seq=0 carries `[0]=0 [1..2]=total_len + ≤61B data`, subsequent
1234
- * seqs carry `[0]=seq + ≤63B data`. Throws on payloads >1024B.
922
+ * SVC_REQUEST frames for a master→slave call. ≤64 B → one SINGLE frame;
923
+ * larger → seq 0 carries `[0]=0 [1..2]=total + ≤61 B data`, later seqs
924
+ * carry `[0]=seq + ≤63 B data`. Throws on payloads >1024 B.
1235
925
  */
1236
926
  declare function makeSvcRequestFrames(nodeId: number, svcIdx: number, callId: number, payload: Uint8Array): CanFrameTx[];
1237
927
  /**
1238
- * Build §7.4 response segments. Used by mock slaves and any TS slave runtime.
1239
- * `channel` is the channel field (typically `CHAN_SVC_RESPONSE`); the payload
1240
- * is `[status, ...response]` so the caller bakes status into the buffer.
928
+ * Response segments (for mock slaves / TS slave runtime). `payload` must
929
+ * already carry `[status, ...body]`; `channel` is typically CHAN_SVC_RESPONSE.
1241
930
  */
1242
931
  declare function makeSvcSegmentedFrames(channel: number, nodeId: number, svcIdx: number, callId: number, payload: Uint8Array): CanFrameTx[];
1243
- /**
1244
- * Bitmask of segment seqs required to reassemble a `totalLen`-byte payload.
1245
- * Identical formula to `expected_mask` in
1246
- * `fibril_can_bridge/src/node_agent.cpp` and the slave's `seg_expected_mask`.
1247
- */
932
+ /** Bitmask of seg-seqs required to reassemble a `totalLen`-byte payload. */
1248
933
  declare function segExpectedMask(totalLen: number): number;
1249
- /**
1250
- * Per-(svc, call_id) reassembly slot. The caller drives one of these per
1251
- * outstanding incoming segmented transfer.
1252
- */
934
+ /** Per-(svc, call_id) reassembly slot for one segmented transfer. */
1253
935
  declare class SegmentReassembler {
1254
936
  #private;
1255
937
  totalLen: number;
1256
938
  rxBitmap: number;
1257
939
  lastRxMs: number;
1258
940
  /**
1259
- * Ingest one seg=1 frame. `data` is the frame payload (with the seq /
1260
- * total_len header still attached). Returns the fully-assembled payload
1261
- * once every required segment has arrived; otherwise null. Returns null
1262
- * on protocol violations and resets the slot.
941
+ * Ingest one seg=1 frame (payload with seq / total_len header attached).
942
+ * Returns the assembled payload once complete, else null. Resets the slot
943
+ * on protocol violation.
1263
944
  */
1264
945
  ingest(data: Uint8Array, nowMs: number): Uint8Array | null;
1265
946
  }
1266
947
  //#endregion
1267
948
  //#region src/seq-allocator.d.ts
1268
- /**
1269
- * Sequential u8 allocator used for the config-plane `seq` field shared by
1270
- * every command this master has issued to a given slave. Wraps at 256,
1271
- * matching `BridgeNode::alloc_seq` in
1272
- * `fibril_can_bridge/src/bridge_node.cpp` (the bridge wraps on the same
1273
- * boundary; the AckTracker indexes by the wrapped value).
1274
- */
949
+ /** Wrapping u8 counter for the config-plane `seq` byte. */
1275
950
  declare class SeqAllocator {
1276
951
  #private;
1277
952
  next(): number;
1278
953
  reset(): void;
1279
954
  }
1280
955
  //#endregion
1281
- export { type AckCallback, type AckOutcome, AckTracker, type AnnounceMsg, CHAN_CONFIG, CHAN_SVC_REQUEST, CHAN_SVC_RESPONSE, CMD_ACK, CMD_ANNOUNCE, CMD_DISCOVER, CMD_FRAME_DEFINE, CMD_HB_PERIOD, CMD_HEARTBEAT, CMD_MASTER_HEARTBEAT, CMD_NAK, CMD_SCHEMA_DATA, CMD_SCHEMA_READ, CMD_START, CMD_STOP, type ControllerListener, type ControllerSlaveSnapshot, DIR_RX_M2S, DIR_TX_S2M, DataPlaneReceiver, type DataPlaneReceiverOptions, type Dict, Direction, DynamicCodecError, type EndpointDescriptor, type EndpointKey, FAULT_ID_CONFLICT, FAULT_INDEX_OVERFLOW, FAULT_NOMEM, FAULT_NONE, FD_DLC_STEPS, type FieldRequest, type FieldValue, type FrameDefineEntry, type FramePlan, type FramePlanContext, type FramePlanFactory, type FrameSpec, type HeartbeatMsg, MAX_FRAME_PAYLOAD, MasterController, type MasterControllerOptions, MasterHeartbeatBroadcaster, NAK_BAD_LENGTH, NAK_BAD_PARAM, NAK_BAD_STATE, NAK_DIR_MISMATCH, NAK_ID_CONFLICT, NAK_INDEX_RANGE, NAK_NO_RESOURCE, NAK_SIZE_OVERFLOW, NAK_UNKNOWN_CMD, NODE_ID_BROADCAST, type NakMsg, NoCallSlotError, NodeAgent, type NodeAgentOptions, type NodeProvisionState, type NodeRunningSnapshot, type NodeSchema, NumberingError, type NumberingResult, type NumberingTable, PROTOCOL_VERSION, ParamCodecError, type ParamDescriptor, ProtoError, ProtoReader, type RosMapping, type S2MFieldFilter, SCALAR_NAMES, SEG_FIRST_DATA, SEG_MAX_SEQ, SEG_MAX_TOTAL, SEG_REST_DATA, SERVICE_INDEX_CANCEL, SERVICE_INDEX_CAPACITY, SERVICE_INDEX_MAX, SERVICE_INDEX_PARAM_SET, STATE_FAULT, STATE_PROVISIONED, STATE_RUNNING, STATE_UNPROVISIONED, STD_ID_MASK, SVC_ACCEPTED, SVC_APP_ERROR, SVC_BAD_INDEX, SVC_BAD_LENGTH, SVC_BAD_VALUE, SVC_BUSY, SVC_CANCEL, SVC_MAX_FIRST_CLASS, SVC_OK, SVC_PARAM_SET, SVC_UNAVAIL, ScalarType, SchemaAccumulator, type SchemaBlockArray, type SchemaBlockType, type SchemaDataMsg, type SchemaField, SchemaIndex, type SchemaParam, type SchemaService, type SchemaTopic, SegmentReassembler, SeqAllocator, ServiceCallError, type ServiceCallOptions, ServiceCancelledError, ServiceClient, type ServiceDescriptor, type ServiceResult, ServiceTimeoutError, ServiceUnavailableError, StdIdAllocator, type TopicDescriptor, type TopicListener, type TopicSample, type TopicStats, TypedParam, type TypedParamSetOptions, type TypedParamSetResult, TypedService, type TypedServiceCallOptions, type TypedServiceResult, type WireLimits, buildDefaultS2MRequests, computeNumbering, decodeAnnounce, decodeFields, decodeHeartbeat, decodeNak, decodeSchema, decodeSchemaData, defaultS2MFramePlan, encodeDiscover, encodeDiscoverTo, encodeFields, encodeFrameDefine, encodeMasterHeartbeat, encodeParamSetSingle, encodeSchemaRead, encodeStart, encodeStop, extChannel, extIndex, extNodeId, extSeg, extSeq, fieldWireSize, fieldsWireSize, makeExtId, makeSvcRequestFrames, makeSvcSegmentedFrames, packScalarF32Le, packScalarF64Le, packScalarIntLe, padToDlc, planFrames, quantizeDlc, scalarFromName, scalarName, scalarSize, schemaHashPrefix, segExpectedMask, segOffset, sha256, signExtend, unpackScalarF32Le, unpackScalarF64Le, unpackScalarIntLe };
956
+ export { type AckCallback, type AckOutcome, AckTracker, type AnnounceMsg, CHAN_CONFIG, CHAN_SVC_REQUEST, CHAN_SVC_RESPONSE, CMD_ACK, CMD_ANNOUNCE, CMD_DISCOVER, CMD_FRAME_DEFINE, CMD_HB_PERIOD, CMD_HEARTBEAT, CMD_MASTER_HEARTBEAT, CMD_NAK, CMD_SCHEMA_DATA, CMD_SCHEMA_READ, CMD_START, CMD_STOP, type ControllerListener, type ControllerSlaveSnapshot, DIR_RX_M2S, DIR_TX_S2M, DataPlaneReceiver, type DataPlaneReceiverOptions, type DecodeSchemaResult, type Dict, Direction, DynamicCodecError, type EndpointDescriptor, type EndpointKey, FAULT_ID_CONFLICT, FAULT_INDEX_OVERFLOW, FAULT_NOMEM, FAULT_NONE, FD_DLC_STEPS, type FieldRequest, type FieldValue, type FrameDefineEntry, type FramePlan, type FramePlanContext, type FramePlanFactory, type FrameSpec, type HeartbeatMsg, MAX_FRAME_PAYLOAD, MasterController, type MasterControllerOptions, MasterHeartbeatBroadcaster, NAK_BAD_LENGTH, NAK_BAD_PARAM, NAK_BAD_STATE, NAK_DIR_MISMATCH, NAK_ID_CONFLICT, NAK_INDEX_RANGE, NAK_NO_RESOURCE, NAK_SIZE_OVERFLOW, NAK_UNKNOWN_CMD, NODE_ID_BROADCAST, type NakMsg, NoCallSlotError, NodeAgent, type NodeAgentOptions, type NodeProvisionState, type NodeRunningSnapshot, type NodeSchema, NumberingError, type NumberingResult, type NumberingTable, PROTOCOL_VERSION, ParamCodecError, type ParamDescriptor, ProtoError, ProtoReader, type RosMapping, type S2MFieldFilter, SCALAR_NAMES, SEG_FIRST_DATA, SEG_MAX_SEQ, SEG_MAX_TOTAL, SEG_REST_DATA, SERVICE_INDEX_CANCEL, SERVICE_INDEX_CAPACITY, SERVICE_INDEX_MAX, SERVICE_INDEX_PARAM_SET, STATE_FAULT, STATE_PROVISIONED, STATE_RUNNING, STATE_SUSPENDED, STATE_UNPROVISIONED, STD_ID_MASK, SVC_ACCEPTED, SVC_APP_ERROR, SVC_BAD_INDEX, SVC_BAD_LENGTH, SVC_BAD_VALUE, SVC_BUSY, SVC_CANCEL, SVC_MAX_FIRST_CLASS, SVC_OK, SVC_PARAM_SET, SVC_UNAVAIL, ScalarType, SchemaAccumulator, type SchemaBlockArray, type SchemaBlockType, type SchemaDataMsg, type SchemaField, SchemaIndex, type SchemaParam, type SchemaService, type SchemaTopic, SegmentReassembler, SeqAllocator, ServiceCallError, type ServiceCallOptions, ServiceCancelledError, ServiceClient, type ServiceDescriptor, type ServiceResult, ServiceTimeoutError, ServiceUnavailableError, StdIdAllocator, type TopicDescriptor, type TopicListener, type TopicSample, type TopicStats, TypedParam, type TypedParamSetOptions, type TypedParamSetResult, TypedService, type TypedServiceCallOptions, type TypedServiceResult, type WireLimits, buildDefaultS2MRequests, computeNumbering, decodeAnnounce, decodeFields, decodeHeartbeat, decodeNak, decodeSchema, decodeSchemaData, decodeSchemaWithMeta, defaultS2MFramePlan, encodeDiscover, encodeDiscoverTo, encodeFields, encodeFrameDefine, encodeMasterHeartbeat, encodeParamSetSingle, encodeSchemaRead, encodeStart, encodeStop, extChannel, extIndex, extNodeId, extSeg, extSeq, fieldWireSize, fieldsWireSize, makeExtId, makeSvcRequestFrames, makeSvcSegmentedFrames, packScalarF32Le, packScalarF64Le, packScalarIntLe, padToDlc, planFrames, quantizeDlc, scalarFromName, scalarName, scalarSize, schemaHashPrefix, segExpectedMask, segOffset, sha256, signExtend, stateName, unpackScalarF32Le, unpackScalarF64Le, unpackScalarIntLe };
1282
957
  //# sourceMappingURL=index.d.ts.map