1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
/*
* Net C API Header
*
* High-performance event bus for AI runtime workloads.
* This header provides C-compatible bindings for use with CGO.
*/
#ifndef NET_SDK_H
#define NET_SDK_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Opaque handle to the event bus */
typedef void* net_handle_t;
/*
* Error codes.
*
* Kept in sync with the Rust-side `NetError` enum and the
* canonical `include/net.h`. The library has a regression test
* that scans both headers to detect drift.
*/
typedef enum {
NET_SUCCESS = 0,
NET_ERR_NULL_POINTER = -1,
NET_ERR_INVALID_UTF8 = -2,
NET_ERR_INVALID_JSON = -3,
NET_ERR_INIT_FAILED = -4,
NET_ERR_INGESTION_FAILED = -5,
NET_ERR_POLL_FAILED = -6,
NET_ERR_BUFFER_TOO_SMALL = -7,
NET_ERR_SHUTTING_DOWN = -8,
/* Response byte count exceeds c_int::MAX. */
NET_ERR_INT_OVERFLOW = -9,
/* Stream handle does not belong to the supplied node. */
NET_ERR_MISMATCHED_HANDLES = -10,
/* CString::new interior NUL — see net.h for full rationale. */
NET_ERR_INTERIOR_NUL = -11,
NET_ERR_UNKNOWN = -99,
/* CortEX / RedEX surface (compiled when the Rust cdylib has
* `netdb` + `redex-disk` features on). Codes below -99 so they
* don't collide with the event-bus surface above. */
NET_ERR_CORTEX_CLOSED = -100,
NET_ERR_CORTEX_FOLD = -101,
NET_ERR_NETDB = -102,
NET_ERR_REDEX = -103,
/* Mesh / channel surface (compiled when the Rust cdylib has the
* `net` feature on). */
NET_ERR_MESH_INIT = -110,
NET_ERR_MESH_HANDSHAKE = -111,
NET_ERR_MESH_BACKPRESSURE = -112,
NET_ERR_MESH_NOT_CONNECTED = -113,
NET_ERR_MESH_TRANSPORT = -114,
NET_ERR_CHANNEL = -115,
NET_ERR_CHANNEL_AUTH = -116,
/* Identity + permission-token surface (compiled when the Rust
* cdylib has the `net` feature on). Codes below -119 — one per
* `TokenError` kind so Go callers can `errors.Is` without
* parsing strings. Mirrors the PyO3 / NAPI prefix conventions
* (`"identity: ..."` / `"token: <kind>"`). */
NET_ERR_IDENTITY = -120,
NET_ERR_TOKEN_INVALID_FORMAT = -121,
NET_ERR_TOKEN_INVALID_SIGNATURE = -122,
NET_ERR_TOKEN_EXPIRED = -123,
NET_ERR_TOKEN_NOT_YET_VALID = -124,
NET_ERR_TOKEN_DELEGATION_EXHAUSTED = -125,
NET_ERR_TOKEN_DELEGATION_NOT_ALLOWED = -126,
NET_ERR_TOKEN_NOT_AUTHORIZED = -127,
/* Capability announce / find_nodes errors. Wraps core
* `AdapterError` when announcement dispatch fails. */
NET_ERR_CAPABILITY = -128,
/* NAT-traversal surface (compiled when the Rust cdylib has
* the `nat-traversal` feature on). Codes mirror core
* `TraversalError` variants — one integer per `kind` so Go
* callers can `errors.Is(err, net.ErrTraversalPunchFailed)`.
* Framing (plan §5): every traversal error is a missed
* optimization, not a connectivity failure — the routed-
* handshake path is always available. */
NET_ERR_TRAVERSAL_REFLEX_TIMEOUT = -130,
NET_ERR_TRAVERSAL_PEER_NOT_REACHABLE = -131,
NET_ERR_TRAVERSAL_TRANSPORT = -132,
NET_ERR_TRAVERSAL_RENDEZVOUS_NO_RELAY = -133,
NET_ERR_TRAVERSAL_RENDEZVOUS_REJECTED = -134,
NET_ERR_TRAVERSAL_PUNCH_FAILED = -135,
NET_ERR_TRAVERSAL_PORT_MAP_UNAVAILABLE = -136,
NET_ERR_TRAVERSAL_UNSUPPORTED = -137
} net_error_t;
/* Watch / tail cursor status codes. Returned from net_*_next functions
* instead of the negative error scheme; positive to distinguish
* "no event available" from an actual failure. */
#define NET_STREAM_TIMEOUT 1
#define NET_STREAM_ENDED 2
/*
* Initialize a new event bus with optional JSON configuration.
*
* @param config_json JSON configuration string (UTF-8, null-terminated), or NULL for defaults.
* @return Handle to the event bus, or NULL on failure.
*
* Example config:
* {
* "num_shards": 8,
* "ring_buffer_capacity": 1048576,
* "backpressure_mode": "DropOldest"
* }
*/
net_handle_t net_init(const char* config_json);
/*
* Ingest a single event (parses JSON).
*
* @param handle Event bus handle.
* @param event_json JSON event string.
* @param len Length of the event string.
* @return 0 on success, negative error code on failure.
*/
int net_ingest(net_handle_t handle, const char* event_json, size_t len);
/*
* Ingest a raw JSON string (fastest path, no parsing).
*
* @param handle Event bus handle.
* @param json JSON string.
* @param len Length of the JSON string.
* @return 0 on success, negative error code on failure.
*/
int net_ingest_raw(net_handle_t handle, const char* json, size_t len);
/*
* Ingest multiple raw JSON strings in a batch.
*
* @param handle Event bus handle.
* @param jsons Array of pointers to JSON strings.
* @param lens Array of lengths for each JSON string.
* @param count Number of events.
* @return Number of successfully ingested events.
*/
int net_ingest_raw_batch(
net_handle_t handle,
const char** jsons,
const size_t* lens,
size_t count
);
/*
* Ingest multiple events from a JSON array.
*
* @param handle Event bus handle.
* @param events_json JSON array of events.
* @return Number of ingested events, or negative error code.
*/
int net_ingest_batch(net_handle_t handle, const char* events_json);
/*
* Poll events from the bus.
*
* @param handle Event bus handle.
* @param request_json JSON request (e.g., {"limit": 100}).
* @param out_buffer Output buffer for JSON response.
* @param buffer_len Size of output buffer.
* @return Bytes written on success, negative error code on failure.
*/
int net_poll(
net_handle_t handle,
const char* request_json,
char* out_buffer,
size_t buffer_len
);
/*
* Get event bus statistics.
*
* @param handle Event bus handle.
* @param out_buffer Output buffer for JSON statistics.
* @param buffer_len Size of output buffer.
* @return Bytes written on success, negative error code on failure.
*/
int net_stats(net_handle_t handle, char* out_buffer, size_t buffer_len);
/*
* Flush pending batches to the adapter.
*
* @param handle Event bus handle.
* @return 0 on success, negative error code on failure.
*/
int net_flush(net_handle_t handle);
/*
* Get the number of shards.
*
* @param handle Event bus handle.
* @return Number of shards, or 0 if handle is null.
*/
uint16_t net_num_shards(net_handle_t handle);
/*
* Shut down the event bus and free resources.
*
* @param handle Event bus handle (invalid after this call).
* @return 0 on success, negative error code on failure.
*/
int net_shutdown(net_handle_t handle);
/*
* Get the library version.
*
* @return Version string (static, do not free).
*/
const char* net_version(void);
/*
* Generate a new Net keypair (requires Net feature).
*
* @return JSON string with hex-encoded public_key and secret_key,
* or NULL if Net is not enabled. Caller must free with net_free_string.
*/
char* net_generate_keypair(void);
/*
* Free a string returned by Net functions.
*
* @param s String to free (may be NULL).
*/
void net_free_string(char* s);
/* =========================================================================
* RedEX / CortEX / NetDb surface.
*
* Moved into the dedicated `net_cortex.h` header so C consumers who only
* want this slice of `libnet` (without the rest of `net.go.h`'s mesh /
* compute / capability surface) can include it standalone. The previous
* inline declarations stayed here through v0.16; the split lands in v0.17.
*
* `net_cortex.h` is self-contained — depends only on `<stdint.h>` and
* `<stddef.h>`. Symbols resolve when the cdylib is built with
* `--features "netdb redex-disk"`.
* ========================================================================= */
#include "net_cortex.h"
/* =========================================================================
* Redis Streams consumer-side dedup helper (`redis` feature).
*
* Mirrors `net::adapter::RedisStreamDedup` and the cross-language
* wrappers in the Node / Python SDKs. The Redis adapter writes a
* stable `dedup_id` field on every XADD entry
* (`"{producer_nonce:hex}:{shard_id}:{sequence_start}:{i}"`); this
* helper filters duplicates whose `dedup_id`s repeat — handling the
* producer-side `MULTI/EXEC`-timeout race that lands two stream
* entries for one logical event.
*
* Each handle wraps an LRU-bounded set of seen ids; the LRU is
* mutex-protected so concurrent calls from multiple goroutines /
* threads are safe but serialize. Production callers typically
* instantiate one helper per consumer goroutine and key on the
* `dedup_id` field they extract from each `XRANGE` / `XREAD`
* entry. See `bindings/go/README.md` and the Python / Node SDK
* READMEs for runnable examples.
* ========================================================================= */
typedef struct net_redis_dedup_s net_redis_dedup_t;
/* Create a new dedup helper. `capacity == 0` selects the default
* (4096); production callers should size to their dedup window
* (consumer at ~10k events/sec with a 1 min window: ~600,000).
* Never returns NULL. Free with `net_redis_dedup_free`.
*/
net_redis_dedup_t* net_redis_dedup_new(size_t capacity);
/* Free a helper handle. NULL is a no-op. */
void net_redis_dedup_free(net_redis_dedup_t* handle);
/* Test-and-insert. Returns:
* 1 — duplicate (caller should skip the entry)
* 0 — new (caller should process AND we've now marked it seen)
* -1 — NULL handle or NULL dedup_id
* -2 — invalid UTF-8 in dedup_id
*/
int net_redis_dedup_is_duplicate(net_redis_dedup_t* handle, const char* dedup_id);
/* Number of distinct ids currently tracked. Returns 0 on NULL
* handle (mirrors the "no ids" semantic).
*/
size_t net_redis_dedup_len(net_redis_dedup_t* handle);
/* Configured maximum capacity. Returns 0 on NULL handle. */
size_t net_redis_dedup_capacity(net_redis_dedup_t* handle);
/* Returns 1 if no ids are tracked, 0 if the helper has at least
* one id, -1 on NULL handle.
*/
int net_redis_dedup_is_empty(net_redis_dedup_t* handle);
/* Clear all tracked ids. Use after a consumer-group rebalance.
* NULL is a no-op.
*/
void net_redis_dedup_clear(net_redis_dedup_t* handle);
/* =========================================================================
* Mesh transport (`net` feature).
*
* Encrypted UDP mesh: handshake, per-peer streams, channels (named
* pub/sub), shard receive. Mirrors the Rust SDK's `Mesh` type; not
* full parity with the core `MeshNode`.
*
* Strings returned via `char**` are heap-allocated and must be freed
* with `net_free_string`.
* ========================================================================= */
typedef struct net_meshnode_s net_meshnode_t;
typedef struct net_mesh_stream_s net_mesh_stream_t;
/* ---- Lifecycle ---- */
/* Open a mesh node. `config_json`:
* { "bind_addr": "127.0.0.1:9000",
* "psk_hex": "<64 hex chars>",
* "heartbeat_ms": 5000, // optional
* "session_timeout_ms": 30000, // optional
* "num_shards": 4 } // optional
*/
int net_mesh_new(const char* config_json, net_meshnode_t** out);
void net_mesh_free(net_meshnode_t* handle);
int net_mesh_shutdown(net_meshnode_t* handle);
/* ---- Identity + handshake ---- */
int net_mesh_public_key_hex(net_meshnode_t* handle,
char** out_hex, size_t* out_len);
uint64_t net_mesh_node_id(net_meshnode_t* handle);
int net_mesh_connect(net_meshnode_t* handle,
const char* peer_addr,
const char* peer_pubkey_hex,
uint64_t peer_node_id);
int net_mesh_accept(net_meshnode_t* handle,
uint64_t peer_node_id,
char** out_addr, size_t* out_len);
int net_mesh_start(net_meshnode_t* handle);
/* ---- Per-peer streams ---- */
/* `config_json`:
* { "reliability": "reliable" | "fire_and_forget",
* "window_bytes": 65536,
* "fairness_weight": 1 }
* May be NULL for defaults.
*/
int net_mesh_open_stream(net_meshnode_t* handle,
uint64_t peer_node_id,
uint64_t stream_id,
const char* config_json,
net_mesh_stream_t** out_stream);
void net_mesh_stream_free(net_mesh_stream_t* handle);
/* Send a batch of payloads on an open stream.
*
* `payloads` is a pointer to an array of `count` byte-pointers;
* `lens` is the parallel array of lengths. Borrowed for the call
* duration only — caller owns the memory. Pass `node_handle` so the
* FFI can reach the owning runtime without creating a global index.
*
* Returns `NET_ERR_MESH_BACKPRESSURE` when the window is full,
* `NET_ERR_MESH_NOT_CONNECTED` when the peer is gone,
* `NET_ERR_MESH_TRANSPORT` for other I/O errors.
*/
int net_mesh_send(net_mesh_stream_t* stream,
const uint8_t* const* payloads,
const size_t* lens,
size_t count,
net_meshnode_t* node_handle);
int net_mesh_send_with_retry(net_mesh_stream_t* stream,
const uint8_t* const* payloads,
const size_t* lens,
size_t count,
uint32_t max_retries,
net_meshnode_t* node_handle);
int net_mesh_send_blocking(net_mesh_stream_t* stream,
const uint8_t* const* payloads,
const size_t* lens,
size_t count,
net_meshnode_t* node_handle);
/* Stream stats — JSON shape mirrors `StreamStats`. Writes `null` to
* *out_json when the stream isn't open. */
int net_mesh_stream_stats(net_meshnode_t* handle,
uint64_t peer_node_id,
uint64_t stream_id,
char** out_json, size_t* out_len);
/* ---- Shard receive ----
*
* Drain up to `limit` events from shard `shard_id`. Output is a JSON
* array of {id, payload_b64, insertion_ts, shard_id}.
*/
int net_mesh_recv_shard(net_meshnode_t* handle,
uint16_t shard_id, uint32_t limit,
char** out_json, size_t* out_len);
/* ---- Channels ----
*
* `config_json`:
* { "name": "sensors/temp",
* "visibility": "global" | "subnet-local" | "parent-visible" | "exported",
* "reliable": false,
* "require_token": false,
* "priority": 0,
* "max_rate_pps": 1000,
* "publish_caps": { ... CapabilityFilter ... }, // Stage G-4
* "subscribe_caps": { ... CapabilityFilter ... } } // Stage G-4
*/
int net_mesh_register_channel(net_meshnode_t* handle, const char* config_json);
int net_mesh_subscribe_channel(net_meshnode_t* handle,
uint64_t publisher_node_id,
const char* channel);
/* Subscribe with a serialized `PermissionToken` (161 bytes) attached.
* Required when the publisher set `require_token=true`, or when the
* subscriber's announced caps don't satisfy `subscribe_caps`. Parses
* the token client-side — malformed bytes return
* `NET_ERR_TOKEN_INVALID_FORMAT` with no network I/O. Signature
* tampering is caught server-side and surfaces as
* `NET_ERR_CHANNEL_AUTH`. */
int net_mesh_subscribe_channel_with_token(net_meshnode_t* handle,
uint64_t publisher_node_id,
const char* channel,
const uint8_t* token,
size_t token_len);
int net_mesh_unsubscribe_channel(net_meshnode_t* handle,
uint64_t publisher_node_id,
const char* channel);
/* Publish one payload to every subscriber. `config_json`:
* { "reliability": "reliable" | "fire_and_forget",
* "on_failure": "best_effort" | "fail_fast" | "collect",
* "max_inflight": 32 }
* May be NULL. Writes a JSON `PublishReport` to `*out_json`. */
int net_mesh_publish(net_meshnode_t* handle,
const char* channel,
const uint8_t* payload, size_t len,
const char* config_json,
char** out_json, size_t* out_len);
/* =========================================================================
* Identity + permission tokens (Stage G — security surface).
*
* `net_identity_t` is an opaque handle bundling an ed25519 keypair
* with a local token cache. Free with `net_identity_free`. Binary
* buffers returned as `uint8_t**` + `size_t*` are heap-allocated by
* Rust and must be released with `net_free_bytes(ptr, len)` — do NOT
* use `net_free_string` on them (CString-only).
* ========================================================================= */
typedef struct net_identity_s net_identity_t;
/* Free a byte buffer returned by any `net_*` function that returns
* raw token / entity-id bytes through `uint8_t**`. Uses `len` to
* reconstruct the Vec — pass the exact length returned via the
* matching `size_t*` out-param. No-op on NULL / len=0. */
void net_free_bytes(uint8_t* ptr, size_t len);
/* Lifecycle + seed round-trip */
int net_identity_generate(net_identity_t** out_handle);
int net_identity_from_seed(const uint8_t* seed, size_t seed_len,
net_identity_t** out_handle);
void net_identity_free(net_identity_t* handle);
/* Writes the 32-byte ed25519 seed into `out[32]`. Caller owns the
* buffer — treat the bytes as secret material. */
int net_identity_to_seed(net_identity_t* handle, uint8_t* out);
/* Writes the 32-byte entity id into `out[32]`. */
int net_identity_entity_id(net_identity_t* handle, uint8_t* out);
uint64_t net_identity_node_id(net_identity_t* handle);
uint64_t net_identity_origin_hash(net_identity_t* handle);
/* Signs `msg[len]`; writes a 64-byte ed25519 signature into `out_sig[64]`. */
int net_identity_sign(net_identity_t* handle,
const uint8_t* msg, size_t len,
uint8_t* out_sig);
/* Issue a token to `subject` (32 bytes). `scope_json` is a JSON
* array of strings: `["publish"]`, `["subscribe","delegate"]`, etc.
* `channel` is the canonical name (not the u16 hash). Writes a
* newly-allocated blob — free via `net_free_bytes`. */
int net_identity_issue_token(net_identity_t* signer,
const uint8_t* subject, size_t subject_len,
const char* scope_json,
const char* channel,
uint32_t ttl_seconds,
uint8_t delegation_depth,
uint8_t** out_token,
size_t* out_token_len);
/* Install a token received from another issuer. Signature check
* runs on insert — malformed / tampered tokens return the relevant
* `NET_ERR_TOKEN_*` code. */
int net_identity_install_token(net_identity_t* handle,
const uint8_t* token, size_t len);
/* Look up a cached token by `(subject, channel)`. On hit, writes a
* newly-allocated blob (free via `net_free_bytes`). On miss, writes
* NULL/0 and returns `NET_SUCCESS`. */
int net_identity_lookup_token(net_identity_t* handle,
const uint8_t* subject, size_t subject_len,
const char* channel,
uint8_t** out_token,
size_t* out_token_len);
uint32_t net_identity_token_cache_len(net_identity_t* handle);
/* Parse a serialized `PermissionToken` into a JSON dict with keys
* `issuer_hex`, `subject_hex`, `scope`, `channel_hash`, `not_before`,
* `not_after`, `delegation_depth`, `nonce`, `signature_hex`. Returns
* `NET_ERR_TOKEN_INVALID_FORMAT` on bad length / layout. */
int net_parse_token(const uint8_t* token, size_t len,
char** out_json, size_t* out_len);
/* Verify the token's ed25519 signature. Writes 1 (valid) or 0
* (tampered/wrong-subject) to `*out_ok`. Does NOT check time-bound
* validity — see `net_token_is_expired`. */
int net_verify_token(const uint8_t* token, size_t len, int* out_ok);
/* Writes 1 to `*out_expired` if `not_after` has passed. */
int net_token_is_expired(const uint8_t* token, size_t len, int* out_expired);
/* Delegate a token to a new subject. Parent must have the
* `delegate` scope and `delegation_depth > 0`; `signer` must be the
* subject of the parent. */
int net_delegate_token(net_identity_t* signer,
const uint8_t* parent, size_t parent_len,
const uint8_t* new_subject, size_t new_subject_len,
const char* restricted_scope_json,
uint8_t** out_token, size_t* out_token_len);
/* Writes the mesh's 32-byte ed25519 entity id into `out[32]`. */
int net_mesh_entity_id(net_meshnode_t* handle, uint8_t* out);
/* =========================================================================
* NAT traversal surface (compiled when the Rust cdylib has the
* `nat-traversal` feature on). Framing (plan §5): these APIs let
* the mesh upgrade to a *direct* path when the underlying NATs
* allow it — they are NOT required for NATed peers to
* communicate. The routed-handshake path reaches every peer
* regardless. Strings use the stable vocabulary
* `"open" | "cone" | "symmetric" | "unknown"`.
* ========================================================================= */
/* Write this mesh's NAT classification into `out_str`. Caller
* frees via `net_free_string`. */
int net_mesh_nat_type(net_meshnode_t* handle,
char** out_str, size_t* out_len);
/* Write this mesh's last-observed reflex `ip:port`. Empty string
* when classification has not yet produced an observation. */
int net_mesh_reflex_addr(net_meshnode_t* handle,
char** out_str, size_t* out_len);
/* Write `peer_node_id`'s advertised NAT classification (read
* from its `nat:*` capability tag). Returns `"unknown"` when we
* have no announcement from that peer. */
int net_mesh_peer_nat_type(net_meshnode_t* handle,
uint64_t peer_node_id,
char** out_str, size_t* out_len);
/* Send one reflex probe to `peer_node_id` and write the public
* `ip:port` the peer observed. Blocks on the shared runtime. */
int net_mesh_probe_reflex(net_meshnode_t* handle,
uint64_t peer_node_id,
char** out_str, size_t* out_len);
/* Explicitly re-run the classification sweep. No-op when fewer
* than 2 peers are connected; never returns an error. */
int net_mesh_reclassify_nat(net_meshnode_t* handle);
/* Fill cumulative traversal counters. Any of the out-pointers may
* be NULL to skip that field. Monotonic — counters never reset. */
int net_mesh_traversal_stats(net_meshnode_t* handle,
uint64_t* out_punches_attempted,
uint64_t* out_punches_succeeded,
uint64_t* out_relay_fallbacks);
/* Establish a session via rendezvous through `coordinator`. The
* pair-type matrix picks between a direct handshake and a
* coordinated punch. Always resolves (on punch-failed, falls
* back to routed). Inspect `net_mesh_traversal_stats` afterward
* to distinguish outcomes. */
int net_mesh_connect_direct(net_meshnode_t* handle,
uint64_t peer_node_id,
const char* peer_pubkey_hex,
uint64_t coordinator);
/* Install a runtime reflex override. `external` is a
* null-terminated "ip:port" string. Forces `nat_type` to "open"
* and `reflex_addr` to `external`; short-circuits any further
* classifier sweeps. */
int net_mesh_set_reflex_override(net_meshnode_t* handle,
const char* external);
/* Drop a previously-installed reflex override. The classifier
* resumes on its normal cadence. No-op when no override is
* active. */
int net_mesh_clear_reflex_override(net_meshnode_t* handle);
/* Hash a channel name to its canonical 64-bit substrate identifier
* (used for ACL/storage/config keys; the wire NetHeader fast-path
* hint is the low 16 bits of this value). */
int net_channel_hash(const char* channel, uint64_t* out_hash);
/* =========================================================================
* Capabilities (announce / find_nodes).
*
* `caps_json` is the same POJO shape as PyO3 / NAPI:
* { "hardware": {...}, "software": {...},
* "models": [{...}], "tools": [{...}],
* "tags": ["gpu", "prod"], "limits": {...} }
*
* `filter_json`:
* { "require_tags": [...], "require_models": [...],
* "require_tools": [...], "min_memory_gb": N,
* "require_gpu": bool, "gpu_vendor": "nvidia",
* "min_vram_gb": N, "min_context_length": N,
* "require_modalities": [...] }
* ========================================================================= */
int net_mesh_announce_capabilities(net_meshnode_t* handle,
const char* caps_json);
/* Writes a JSON array `[node_id, ...]` of matching nodes (including
* own node id when self-match). Free `*out_json` via `net_free_string`. */
int net_mesh_find_nodes(net_meshnode_t* handle,
const char* filter_json,
char** out_json, size_t* out_len);
/* Scoped variant of `net_mesh_find_nodes`. `scope_json` is a tagged
* union by `kind`:
* {"kind": "any"} — all non-SubnetLocal nodes
* {"kind": "global_only"} — only untagged nodes
* {"kind": "same_subnet"} — caller's subnet only
* {"kind": "tenant", "tenant": "<id>"} — that tenant + Global
* {"kind": "tenants", "tenants": ["<id>", …]} — any of those + Global
* {"kind": "region", "region": "<name>"} — that region + Global
* {"kind": "regions", "regions": ["<name>", …]}— any of those + Global
*
* Untagged nodes resolve to Global and stay visible under most
* filters; nodes tagged `scope:subnet-local` only show up under
* `same_subnet`. */
int net_mesh_find_nodes_scoped(net_meshnode_t* handle,
const char* filter_json,
const char* scope_json,
char** out_json, size_t* out_len);
/* Pick the best-scoring node for a placement requirement. Writes
* the winning node id to `*out_node_id` and `1` to `*out_has_match`
* on hit; writes `0` to `*out_has_match` on no match. Returns 0 in
* either case; non-zero only on input / parse error.
*
* `requirement_json` is `{"filter": <CapabilityFilter>,
* "prefer_more_memory": f, "prefer_more_vram": f,
* "prefer_faster_inference": f, "prefer_loaded_models": f}` —
* weights are optional and clamped to `[0.0, 1.0]`. */
int net_mesh_find_best_node(net_meshnode_t* handle,
const char* requirement_json,
uint64_t* out_node_id,
int* out_has_match);
/* Scoped variant of `net_mesh_find_best_node`. `scope_json` accepts
* the same shapes as `net_mesh_find_nodes_scoped`. Picks the highest-
* scoring node within the scope-filtered set. Same out-param
* contract as `net_mesh_find_best_node`. */
int net_mesh_find_best_node_scoped(net_meshnode_t* handle,
const char* requirement_json,
const char* scope_json,
uint64_t* out_node_id,
int* out_has_match);
/* Normalize a GPU vendor string to canonical lowercase
* (`"nvidia"` | `"amd"` | `"intel"` | `"apple"` | `"qualcomm"` |
* `"unknown"`). Writes the result via `*out` / `*out_len`; free via
* `net_free_string`. Matches the NAPI / PyO3 helper so every SDK
* produces the same wire-normalized value. */
int net_normalize_gpu_vendor(const char* raw,
char** out, size_t* out_len);
/* Stateless predicate evaluation (Phase 9c of
* CAPABILITY_SYSTEM_SDK_PLAN.md). Mirrors the SDK-layer
* `evaluate_predicate(pred, tags, metadata)` surface every
* binding ships, exposed at the C ABI for raw consumers
* (C / C++ / Zig / Swift / Java JNI / etc.) so they can filter
* candidates locally without going through nRPC.
*
* Inputs are NUL-terminated UTF-8 JSON strings:
* - predicate_json — wire-format `PredicateWire`.
* Cross-binding compat pinned by
* `tests/cross_lang_capability/predicate_eval.json`.
* - tags_json — `["hardware.gpu", "scope:tenant:foo"]`.
* Reserved-prefix tags accepted (privileged Tag::parse).
* - metadata_json — `{"key":"value"}`.
*
* Returns:
* 1 — predicate evaluated to true.
* 0 — predicate evaluated to false.
* NET_ERR_NULL_POINTER — any input pointer is NULL.
* NET_ERR_INVALID_UTF8 — input bytes aren't valid UTF-8.
* NET_ERR_INVALID_JSON — predicate / tags / metadata failed to
* parse, OR a tag string was malformed.
*
* Stateless. Thread-safe. Pure helper — no handle, no allocation
* out-params; the caller's buffers aren't retained. */
int net_predicate_evaluate(const char* predicate_json,
const char* tags_json,
const char* metadata_json);
/* Encode a wire-format Predicate as the canonical
* `net-where:` request-header pair (Phase 9b of
* CAPABILITY_SYSTEM_SDK_PLAN.md). Mirror of the Go SDK's
* `WhereHeader` helper.
*
* The returned `(name, value)` pair drops into the
* `(headers_ptr, header_count)` argument of
* `net_rpc_call_with_headers` / `_call_service_with_headers` /
* `_call_streaming_with_headers` (declared in `net_rpc.h`) for
* end-to-end predicate pushdown.
*
* Input (NUL-terminated UTF-8 JSON):
* - predicate_json — wire-format `PredicateWire`. Same shape
* `net_predicate_evaluate` accepts.
*
* Outputs:
* - *out_header_name/_len — `"net-where"` (always).
* Free with `net_free_string`.
* - *out_value_ptr/_len — JSON-encoded PredicateWire bytes,
* capped at `MAX_PREDICATE_RPC_HEADER_VALUE_LEN` (4096).
* Free with `net_free_string`.
*
* Returns:
* 0 — success.
* NET_ERR_NULL_POINTER — any pointer is NULL.
* NET_ERR_INVALID_UTF8 — input bytes aren't valid UTF-8.
* NET_ERR_INVALID_JSON — predicate failed to parse OR
* encoded bytes exceed the 4096-byte
* header-value cap.
*
* Cross-binding wire format pinned by
* `tests/cross_lang_capability/predicate_nrpc_envelope.json`. */
int net_predicate_to_where_header(const char* predicate_json,
char** out_header_name,
size_t* out_header_name_len,
char** out_value_ptr,
size_t* out_value_len);
/* Validate a wire-format `CapabilitySet` against the canonical
* `AXIS_SCHEMA` (Phase 9a of CAPABILITY_SYSTEM_SDK_PLAN.md).
* Mirrors the SDK-layer `validate_capabilities` surface every
* binding ships, exposed at the C ABI for raw consumers
* (C / C++ / Zig / Swift / Java JNI / etc.).
*
* Input is a NUL-terminated UTF-8 JSON `CapabilitySet`:
* {"tags": [...], "metadata": {...}}
*
* On success writes a JSON `ValidationReport` to `*out_report_json`
* / `*out_report_len`, free with `net_free_string`. Wire shape:
* {
* "errors": [{"kind": "<unknown_axis|type_mismatch|index_malformed>", ...}, ...],
* "warnings": [{"kind": "<unknown_key|metadata_oversize|legacy_tag>", ...}, ...]
* }
*
* Both arrays are sorted by JSON-stringified entry so cross-
* binding fixture comparisons are order-independent.
*
* Returns:
* 0 — success; report written.
* NET_ERR_NULL_POINTER — any pointer is NULL.
* NET_ERR_INVALID_UTF8 — input bytes aren't valid UTF-8.
* NET_ERR_INVALID_JSON — input failed to parse as a
* CapabilitySet.
*
* Stateless. Thread-safe. Cross-binding compat pinned by
* `tests/cross_lang_capability/capability_validation.json`. */
int net_validate_capabilities(const char* caps_json,
char** out_report_json,
size_t* out_report_len);
/* Predicate debug-session helpers (Phase 9d of
* CAPABILITY_SYSTEM_SDK_PLAN.md). Three pure helpers exposing
* what every binding ships at the SDK layer. Cross-binding
* contracts pinned by:
* - tests/cross_lang_capability/predicate_trace.json
* - tests/cross_lang_capability/predicate_debug_report.json
* - tests/cross_lang_capability/predicate_debug_report_redacted.json
*/
/* Evaluate a predicate against (tags, metadata) and produce a
* `ClauseTrace` tree mirroring the AST that actually ran (And /
* Or short-circuits drop unevaluated siblings).
*
* Inputs (NUL-terminated UTF-8 JSON):
* - predicate_json — wire-format `PredicateWire`.
* - tags_json — JSON array of tag strings.
* - metadata_json — JSON object of `string -> string`.
*
* Outputs:
* - *out_result — 1 if predicate matched, 0 otherwise.
* - *out_trace_json/_len — JSON `{label, result, children}`
* tree. Free with `net_free_string`.
*
* Returns 0 on success, NET_ERR_* (negative) otherwise. */
int net_predicate_evaluate_with_trace(const char* predicate_json,
const char* tags_json,
const char* metadata_json,
int* out_result,
char** out_trace_json,
size_t* out_trace_len);
/* Run `predicate` against every entry in `contexts_json` and
* produce a `PredicateDebugReport` aggregated across the corpus.
*
* Inputs (NUL-terminated UTF-8 JSON):
* - predicate_json — wire-format `PredicateWire`.
* - contexts_json — `[{"tags":[...], "metadata":{...}}, ...]`.
*
* Output: *out_report_json/_len — JSON
* {
* "total_candidates": N,
* "matched": M,
* "clause_stats": [{"label":..., "evaluated":..., "matched":...}, ...]
* }
* `clause_stats` sorted by label (BTreeMap iteration order on
* the substrate side). Free with `net_free_string`.
*
* Returns 0 on success, NET_ERR_* (negative) otherwise. */
int net_predicate_aggregate_debug_report(const char* predicate_json,
const char* contexts_json,
char** out_report_json,
size_t* out_report_len);
/* Rewrite metadata-clause labels in a `PredicateDebugReport` to
* scrub sensitive values before persistence / sharing. Pure
* host-side: the substrate doesn't ship a redaction impl; each
* binding implements it. This C ABI ports the same logic as TS /
* Python / Go.
*
* Rules:
* MetadataEquals(<key>=<value>) → MetadataEquals(<key>=<redacted>)
* MetadataMatches(<key> contains "<pat>") → MetadataMatches(<key> contains "<redacted>")
* MetadataNumericAtLeast(<key> >= <thr>) → MetadataNumericAtLeast(<key> >= <redacted>)
* MetadataExists(<key>) — unchanged (no value)
* non-metadata labels — unchanged
*
* Stats with the same redacted label merge; output is sorted by
* label. Idempotent: redact(redact(r,k),k) == redact(r,k).
*
* Inputs (NUL-terminated UTF-8 JSON):
* - report_json — wire-format `PredicateDebugReport` (output
* of net_predicate_aggregate_debug_report).
* - keys_json — `["api_key", "secret_token"]`.
*
* Output: *out_redacted_json/_len — same wire shape, free with
* `net_free_string`.
*
* Returns 0 on success, NET_ERR_* (negative) otherwise. */
int net_predicate_redact_metadata_keys(const char* report_json,
const char* keys_json,
char** out_redacted_json,
size_t* out_redacted_len);
/* CR-8: same redaction over a wire-format trace tree (output of
* net_predicate_evaluate_with_trace). Walks the tree and rewrites
* every `label` matching the metadata-clause shapes; preserves
* children order and `result` fields.
*
* Inputs (NUL-terminated UTF-8 JSON):
* - trace_json — wire-format ClauseTrace (`{"label", "result",
* "children": [...]}` recursively).
* - keys_json — `["api_key", "secret_token"]`.
*
* Output: *out_redacted_json/_len — same wire shape, free with
* `net_free_string`. Idempotent.
*
* Returns 0 on success, NET_ERR_* (negative) otherwise. */
int net_predicate_redact_trace_metadata_keys(const char* trace_json,
const char* keys_json,
char** out_redacted_json,
size_t* out_redacted_len);
/* =========================================================================
* Compute — MeshDaemon + migration. Stage 6 of
* SDK_COMPUTE_SURFACE_PLAN.md. Symbols live in `libnet_compute`
* (sibling shared library built from `bindings/go/compute-ffi`).
* Link with `-lnet -lnet_compute`.
* ========================================================================= */
/* Opaque handles. Go wrappers own the pointer lifetime via
* runtime.SetFinalizer — consistent with `net_meshnode_t`. */
typedef struct net_compute_runtime_s net_compute_runtime_t;
typedef struct net_compute_mesh_arc_s net_compute_mesh_arc_t;
typedef struct net_compute_cc_arc_s net_compute_cc_arc_t;
/* Compute error codes (negative). */
#define NET_COMPUTE_OK 0
#define NET_COMPUTE_ERR_NULL -1
#define NET_COMPUTE_ERR_CALL_FAILED -2
#define NET_COMPUTE_ERR_DUPLICATE_KIND -3
/* `_register_placement_filter`-only — surfaces when the dispatcher
* hasn't been installed yet via
* `net_compute_set_placement_filter_dispatcher`. Documented at the
* call site below. */
#define NET_COMPUTE_ERR_NO_DISPATCHER -4
/* `_register_placement_filter`-only — `id_ptr` bytes don't decode
* as UTF-8. Documented at the call site below. */
#define NET_COMPUTE_ERR_INVALID_UTF8 -5
/* --- Arc<MeshNode> / Arc<ChannelConfigRegistry> accessors ---
*
* Produced by `net_mesh_*_arc_clone` (in libnet); compute-ffi's
* `net_compute_runtime_new` CONSUMES them. Pair each clone with
* either a `net_compute_runtime_new` consume-call or a matching
* `_free`.
*/
net_compute_mesh_arc_t* net_mesh_arc_clone(net_meshnode_t* handle);
void net_mesh_arc_free(net_compute_mesh_arc_t* p);
net_compute_cc_arc_t* net_mesh_channel_configs_arc_clone(net_meshnode_t* handle);
void net_mesh_channel_configs_arc_free(net_compute_cc_arc_t* p);
/* --- Runtime lifecycle --- */
/* Build a DaemonRuntime sharing the given mesh's node + channel
* configs. Both Arc pointers are consumed on success (do not free
* afterwards). Returns NULL if any input is NULL. */
net_compute_runtime_t* net_compute_runtime_new(
net_compute_mesh_arc_t* node_arc,
net_compute_cc_arc_t* channel_configs_arc);
/* Free a runtime handle. The underlying MeshNode is untouched. */
void net_compute_runtime_free(net_compute_runtime_t* handle);
/* Return the monotonic, process-unique identifier assigned to this
* runtime by `net_compute_runtime_new`. Go uses this id to scope
* its factory map so two runtimes in the same process can register
* the same `kind` without colliding. Returns 0 on NULL input. */
uint64_t net_compute_runtime_id(const net_compute_runtime_t* handle);
/* Transition to Ready. On failure, writes a heap-allocated char*
* detail to `*err_out` (free via `net_compute_free_cstring`). */
int net_compute_runtime_start(
net_compute_runtime_t* handle,
char** err_out);
/* Tear down the runtime. `*err_out` carries detail on failure. */
int net_compute_runtime_shutdown(
net_compute_runtime_t* handle,
char** err_out);
/* 1 = ready, 0 = not-ready, NET_COMPUTE_ERR_NULL on NULL handle. */
int net_compute_runtime_is_ready(net_compute_runtime_t* handle);
/* Number of daemons registered. Returns -1 on NULL handle. */
int64_t net_compute_runtime_daemon_count(net_compute_runtime_t* handle);
/* Register a placeholder kind. Enables `spawn`; migration-target
* reconstruction falls back to NoopBridge (migrated-in daemons on
* this node run as no-op). Use `net_compute_register_factory_with_func`
* when you need migrated-in daemons to run user code. */
int net_compute_register_factory(
net_compute_runtime_t* handle,
const char* kind_ptr,
size_t kind_len);
/* Register a kind with a Go-side factory func (the caller already
* stored the func in the Go-side factoryFuncs map; we install an
* SDK factory closure that reaches back via the dispatcher's
* factory trampoline on every migration-target reconstruction). */
int net_compute_register_factory_with_func(
net_compute_runtime_t* handle,
const char* kind_ptr,
size_t kind_len);
/* Free a CString returned by compute-ffi (e.g., an err_out detail). */
void net_compute_free_cstring(char* s);
/* --- Callback dispatcher (sub-step 2) ---
*
* Go registers four trampolines with C linkage via
* `net_compute_set_dispatcher` in its `init()`. Rust invokes them
* whenever a bridged daemon's `process` / `snapshot` / `restore`
* method needs to run, plus a `free` callback on daemon drop so
* Go can release its registry entry.
*/
typedef struct net_compute_outputs_s net_compute_outputs_t;
typedef struct net_compute_daemon_handle_s net_compute_daemon_handle_t;
typedef int (*net_compute_process_fn)(
uint64_t daemon_id,
uint64_t origin_hash,
uint64_t sequence,
const uint8_t* payload_ptr,
size_t payload_len,
net_compute_outputs_t* outputs);
typedef int (*net_compute_snapshot_fn)(
uint64_t daemon_id,
uint8_t** out_ptr,
size_t* out_len);
typedef int (*net_compute_restore_fn)(
uint64_t daemon_id,
const uint8_t* state_ptr,
size_t state_len);
typedef void (*net_compute_free_fn)(uint64_t daemon_id);
typedef int (*net_compute_factory_fn)(
uint64_t runtime_id,
const char* kind_ptr,
size_t kind_len,
uint64_t* out_daemon_id);
/* Install the Go dispatcher. Call once from Go's init; second call
* is ignored (first registration wins). All five pointers must be
* non-NULL. */
int net_compute_set_dispatcher(
net_compute_process_fn process,
net_compute_snapshot_fn snapshot,
net_compute_restore_fn restore,
net_compute_free_fn free,
net_compute_factory_fn factory);
/* Push one output payload into the outputs vec. Called by Go's
* process trampoline. Copies `len` bytes. */
int net_compute_outputs_push(
net_compute_outputs_t* vec,
const uint8_t* data,
size_t len);
/* Free a snapshot buffer the Go side malloc'd and handed to Rust
* via `net_compute_snapshot_fn`. (The Rust bridge calls this after
* copying the bytes into its own Bytes.) Normal callers should not
* invoke this directly. */
void net_compute_snapshot_bytes_free(uint8_t* ptr, size_t len);
/* --- Daemon capability authoring (Phase 6 of
* CAPABILITY_SYSTEM_SDK_PLAN.md) ---
*
* Optional per-daemon capability declaration. Wires the
* substrate's `MeshDaemon::required_capabilities` /
* `optional_capabilities` (Phase G slice 2) through the C ABI.
*
* If the consumer never installs this dispatcher, daemons get
* empty cap sets — `StandardPlacement` treats them as "runs
* anywhere" and capability-driven placement decisions don't
* select for them. Consumers that DO install it can declare
* per-daemon caps so the in-tree axes (resource, intent,
* scope) and the hard-required check have something to work
* against.
*
* Wire shape: substrate calls back per `GoBridge` construction
* (both initial spawn AND migration-target reconstruction),
* passing the `daemon_id`. Consumer writes JSON-encoded
* `CapabilitySet`s ({tags: [...], metadata: {k:v}}) to the
* out-params. NULL out-pointer / zero len means "no caps
* declared for this side"; either side may be omitted
* independently. Buffers MUST be allocated via `C.malloc` /
* `libc::malloc` so Rust can release them via `libc::free`
* after parsing.
*
* Idempotent: invoked once per bridge construction, parsed once
* into a `CapabilitySet`, stored on the bridge for the daemon's
* lifetime. No re-call on event processing.
*/
typedef int (*net_compute_daemon_caps_fn)(
uint64_t daemon_id,
char** out_required_json, size_t* out_required_len,
char** out_optional_json, size_t* out_optional_len);
/* Install the optional daemon-caps dispatcher. First-call-wins
* (matches `_set_dispatcher` semantics). Returns NET_COMPUTE_OK
* on success, NET_COMPUTE_ERR_NULL on NULL pointer. */
int net_compute_set_daemon_caps_dispatcher(
net_compute_daemon_caps_fn dispatcher);
/* --- Custom PlacementFilter callback (Phase 7 of
* CAPABILITY_SYSTEM_SDK_PLAN.md) ---
*
* Custom predicate hook for `StandardPlacement.custom_filter_id`.
* The substrate calls back into the consumer (Go / language X)
* once per candidate when scoring a placement decision. Return
* `1` keeps the candidate, `0` drops it, negative is treated as
* veto + the consumer is responsible for logging detail.
*
* Wire shape: substrate marshals each candidate as JSON
* `{"node_id": uint64, "tags": [string], "metadata": map}`
* — keeps the C ABI tight (one byte buffer per call) at the
* cost of a per-call serde roundtrip on the consumer side. The
* Go binding's reference impl (`bindings/go/net/placement.go`)
* decodes inside the trampoline before invoking the user
* predicate; non-Go consumers parse the JSON the same way. The
* filter id and JSON buffers are owned by Rust for the call's
* duration only — copy them on the consumer side if needed.
*
* Lifecycle: call `_set_placement_filter_dispatcher` ONCE at
* process init (first-call-wins; subsequent calls are no-ops).
* Then `_register_placement_filter(mesh_arc, id_ptr, id_len)`
* for each filter; daemons spec'd with the matching id route
* scoring through the dispatcher. Counter
* `dataforts_placement_callback_invocations_total{binding}`
* increments per scoring call (binding label set by the consumer
* SDK at register-time on the Rust side).
*
* Compatible with `_set_dispatcher` above — the placement filter
* dispatcher is independent. */
typedef int (*net_compute_placement_filter_fn)(
const char* filter_id_ptr, size_t filter_id_len,
uint64_t node_id,
const char* candidate_json_ptr, size_t candidate_json_len);
/* Install the consumer-side placement-filter trampoline. Idempotent
* after first success (matches `_set_dispatcher` semantics).
* Returns NET_COMPUTE_OK on success, NET_COMPUTE_ERR_NULL on NULL
* pointer. */
int net_compute_set_placement_filter_dispatcher(
net_compute_placement_filter_fn dispatcher);
/* Register a placement-filter id under `mesh_arc`. The Rust side
* looks up `*Arc<MeshNode>` so the filter can resolve the
* candidate's `CapabilitySet` from the local index when scoring.
* `mesh_arc` is NOT consumed — caller still owns it.
*
* Returns:
* NET_COMPUTE_OK — registered.
* NET_COMPUTE_ERR_NULL — `mesh_arc` or `id_ptr` NULL.
* NET_COMPUTE_ERR_DUPLICATE_KIND — `id` already registered
* (no-overwrite contract; SDKs
* generate unique ids).
* NET_COMPUTE_ERR_NO_DISPATCHER — dispatcher not yet installed
* (call `_set_placement_filter_dispatcher`).
* NET_COMPUTE_ERR_INVALID_UTF8 — `id_ptr` is not valid UTF-8.
*
* Existing `Arc<dyn PlacementFilter>` clones held by in-flight
* scoring calls keep the predicate alive until those calls
* finish; `_unregister` is safe to call concurrently. */
int net_compute_register_placement_filter(
net_compute_mesh_arc_t* mesh_arc,
const char* id_ptr, size_t id_len);
/* Drop the placement-filter registration under `id`.
*
* Returns 1 if `id` was registered, 0 otherwise.
* NET_COMPUTE_ERR_NULL on NULL / non-UTF-8 `id_ptr`. */
int net_compute_unregister_placement_filter(
const char* id_ptr, size_t id_len);
/* Whether `id` is currently registered. Diagnostic helper.
* Returns 1 if registered, 0 otherwise. NET_COMPUTE_ERR_NULL on
* NULL / non-UTF-8 `id_ptr`. */
int net_compute_has_placement_filter(
const char* id_ptr, size_t id_len);
/* --- Spawn / stop / deliver --- */
/* Spawn a daemon. `daemon_id` is the Go-side registry key
* trampolines will receive on every callback. `identity_seed`
* points at 32 bytes of ed25519 seed. Writes the opaque handle to
* `*out_handle` on success. `auto_snapshot_interval` and
* `max_log_entries` take 0 for defaults. */
int net_compute_spawn(
net_compute_runtime_t* runtime,
const char* kind_ptr,
size_t kind_len,
const uint8_t* identity_seed,
uint64_t daemon_id,
uint64_t auto_snapshot_interval,
uint32_t max_log_entries,
net_compute_daemon_handle_t** out_handle,
char** err_out);
/* Read the origin_hash from a daemon handle. Returns 0 on NULL. */
uint64_t net_compute_daemon_handle_origin_hash(
const net_compute_daemon_handle_t* handle);
/* Copy the 32-byte entity_id from a daemon handle into `out`. */
int net_compute_daemon_handle_entity_id(
const net_compute_daemon_handle_t* handle,
uint8_t* out);
/* Free a daemon handle (does NOT stop the daemon — call
* `net_compute_runtime_stop(origin_hash)` separately). */
void net_compute_daemon_handle_free(
net_compute_daemon_handle_t* handle);
/* Stop a daemon by origin_hash. */
int net_compute_runtime_stop(
net_compute_runtime_t* runtime,
uint64_t origin_hash,
char** err_out);
/* Deliver one event. Writes a heap-allocated outputs vec to
* `*out_outputs`; caller reads via `net_compute_outputs_len`/`_at`
* and frees via `net_compute_outputs_free`. */
int net_compute_runtime_deliver(
net_compute_runtime_t* runtime,
uint64_t origin_hash,
uint64_t event_origin_hash,
uint64_t event_sequence,
const uint8_t* event_payload,
size_t event_payload_len,
net_compute_outputs_t** out_outputs,
char** err_out);
size_t net_compute_outputs_len(const net_compute_outputs_t* vec);
int net_compute_outputs_at(
const net_compute_outputs_t* vec,
size_t idx,
const uint8_t** out_ptr,
size_t* out_len);
void net_compute_outputs_free(net_compute_outputs_t* vec);
/* --- Snapshot + restore (sub-step 3) --- */
/* Take a snapshot of a running daemon. On success, `*out_outputs`
* carries the serialized StateSnapshot bytes as a single-entry
* outputs vec, or an empty vec for stateless daemons. */
int net_compute_runtime_snapshot(
net_compute_runtime_t* runtime,
uint64_t origin_hash,
net_compute_outputs_t** out_outputs,
char** err_out);
/* Spawn from a previously-taken snapshot. `snapshot_ptr` /
* `snapshot_len` must be the exact bytes returned by a prior
* `net_compute_runtime_snapshot`. Corrupted bytes fail fast with
* `daemon: snapshot decode failed`; identity mismatch surfaces via
* the SDK's existing `snapshot identity mismatch` error. */
int net_compute_spawn_from_snapshot(
net_compute_runtime_t* runtime,
const char* kind_ptr,
size_t kind_len,
const uint8_t* identity_seed,
const uint8_t* snapshot_ptr,
size_t snapshot_len,
uint64_t daemon_id,
uint64_t auto_snapshot_interval,
uint32_t max_log_entries,
net_compute_daemon_handle_t** out_handle,
char** err_out);
/* --- Migration (sub-step 4) ---
*
* Error messages from the migration surface use the prefix
* `migration: <kind>[: <detail>]` (written into the `err_out`
* CString) so the Go side can dispatch on the stable kind:
* `not-ready` | `factory-not-found` | `compute-not-supported` |
* `state-failed` | `already-migrating` | `identity-transport-failed` |
* `not-ready-timeout` | `daemon-not-found` | `target-unavailable` |
* `wrong-phase` | `snapshot-too-large`.
*/
typedef struct net_compute_migration_handle_s net_compute_migration_handle_t;
/* Start a migration. `transport_identity`: 0 = skip envelope,
* non-zero = seal the daemon's keypair into the snapshot.
* `retry_not_ready_ms`: 0 disables retry; otherwise the source
* backs off + re-initiates on `NotReady` up to this budget. */
int net_compute_start_migration(
net_compute_runtime_t* runtime,
uint64_t origin_hash,
uint64_t source_node,
uint64_t target_node,
uint8_t transport_identity,
uint64_t retry_not_ready_ms,
net_compute_migration_handle_t** out_handle,
char** err_out);
/* Declare that a migration will land on this node for
* `origin_hash`. Uses the snapshot's envelope to supply the
* keypair. */
int net_compute_expect_migration(
net_compute_runtime_t* runtime,
const char* kind_ptr,
size_t kind_len,
uint64_t origin_hash,
uint64_t auto_snapshot_interval,
uint32_t max_log_entries,
char** err_out);
/* Register an identity for target-side restore when the source
* migrates without an envelope (transport_identity=0). */
int net_compute_register_migration_target_identity(
net_compute_runtime_t* runtime,
const char* kind_ptr,
size_t kind_len,
const uint8_t* identity_seed,
uint64_t auto_snapshot_interval,
uint32_t max_log_entries,
char** err_out);
/* Query the orchestrator phase for `origin_hash`. Returns NULL
* if no migration is in flight, else a heap-allocated CString
* the caller frees with `net_compute_free_cstring`. */
char* net_compute_migration_phase(
net_compute_runtime_t* runtime,
uint64_t origin_hash);
/* Free a migration handle. Does NOT cancel the migration. */
void net_compute_migration_handle_free(
net_compute_migration_handle_t* handle);
uint64_t net_compute_migration_handle_origin_hash(
const net_compute_migration_handle_t* handle);
uint64_t net_compute_migration_handle_source_node(
const net_compute_migration_handle_t* handle);
uint64_t net_compute_migration_handle_target_node(
const net_compute_migration_handle_t* handle);
/* Current phase or NULL (same semantics as
* `net_compute_migration_phase`). Caller frees non-NULL result. */
char* net_compute_migration_handle_phase(
const net_compute_migration_handle_t* handle);
/* Block until terminal state. 0 on `complete`; err_out carries
* `migration: <kind>` body on abort/failure. */
int net_compute_migration_handle_wait(
net_compute_migration_handle_t* handle,
char** err_out);
int net_compute_migration_handle_wait_with_timeout(
net_compute_migration_handle_t* handle,
uint64_t timeout_ms,
char** err_out);
int net_compute_migration_handle_cancel(
net_compute_migration_handle_t* handle,
char** err_out);
/* Test-only helper — `net_compute_test_inject_synthetic_peer` —
* lives in the test-only Go file `groups_testhelpers_test.go`,
* gated at the Rust layer behind the `test-helpers` cargo
* feature on compute-ffi. Intentionally NOT declared here
* because this header ships with production consumers; the test
* binary supplies its own extern declaration. */
/* =========================================================================
* Groups — Stage 4 of SDK_GROUPS_SURFACE_PLAN.md.
*
* `ReplicaGroup` / `ForkGroup` / `StandbyGroup` overlays on top of
* `DaemonRuntime`. Errors use the stable prefix
* `group: <kind>[: <detail>]`, where `<kind>` is one of:
* `not-ready` | `factory-not-found` | `no-healthy-member` |
* `placement-failed` | `registry-failed` | `invalid-config` |
* `daemon`
* ========================================================================= */
typedef struct net_compute_replica_group_s net_compute_replica_group_t;
typedef struct net_compute_fork_group_s net_compute_fork_group_t;
typedef struct net_compute_standby_group_s net_compute_standby_group_t;
/* --- ReplicaGroup --- */
int net_compute_replica_group_spawn(
net_compute_runtime_t* runtime,
const char* kind_ptr, size_t kind_len,
uint32_t replica_count,
const uint8_t* group_seed,
const char* lb_strategy_ptr, size_t lb_strategy_len,
uint64_t auto_snapshot_interval,
uint32_t max_log_entries,
net_compute_replica_group_t** out_handle,
char** err_out);
void net_compute_replica_group_free(net_compute_replica_group_t* h);
int net_compute_replica_group_replica_count(const net_compute_replica_group_t* h);
int net_compute_replica_group_healthy_count(const net_compute_replica_group_t* h);
uint32_t net_compute_replica_group_group_id(const net_compute_replica_group_t* h);
/* status: 0=healthy 1=degraded 2=dead */
int net_compute_replica_group_health(
const net_compute_replica_group_t* h,
int* out_status, uint32_t* out_healthy, uint32_t* out_total);
int net_compute_replica_group_route_event(
const net_compute_replica_group_t* h,
const char* routing_key_ptr, size_t routing_key_len,
uint64_t* out_origin, char** err_out);
int net_compute_replica_group_scale_to(
const net_compute_replica_group_t* h,
uint32_t n, char** err_out);
void net_compute_replica_group_on_node_recovery(
const net_compute_replica_group_t* h, uint64_t node_id);
/* Returns JSON string; free with `net_compute_free_cstring`. */
char* net_compute_replica_group_members_json(
const net_compute_replica_group_t* h);
/* --- ForkGroup --- */
int net_compute_fork_group_spawn(
net_compute_runtime_t* runtime,
const char* kind_ptr, size_t kind_len,
uint64_t parent_origin,
uint64_t fork_seq,
uint32_t fork_count,
const char* lb_strategy_ptr, size_t lb_strategy_len,
uint64_t auto_snapshot_interval,
uint32_t max_log_entries,
net_compute_fork_group_t** out_handle,
char** err_out);
void net_compute_fork_group_free(net_compute_fork_group_t* h);
int net_compute_fork_group_fork_count(const net_compute_fork_group_t* h);
int net_compute_fork_group_healthy_count(const net_compute_fork_group_t* h);
uint64_t net_compute_fork_group_parent_origin(const net_compute_fork_group_t* h);
uint64_t net_compute_fork_group_fork_seq(const net_compute_fork_group_t* h);
/* Returns 1 if every fork's lineage verifies, 0 otherwise. */
int net_compute_fork_group_verify_lineage(const net_compute_fork_group_t* h);
int net_compute_fork_group_scale_to(
const net_compute_fork_group_t* h, uint32_t n, char** err_out);
void net_compute_fork_group_on_node_recovery(
const net_compute_fork_group_t* h, uint64_t node_id);
char* net_compute_fork_group_members_json(const net_compute_fork_group_t* h);
char* net_compute_fork_group_fork_records_json(const net_compute_fork_group_t* h);
/* --- StandbyGroup --- */
int net_compute_standby_group_spawn(
net_compute_runtime_t* runtime,
const char* kind_ptr, size_t kind_len,
uint32_t member_count,
const uint8_t* group_seed,
uint64_t auto_snapshot_interval,
uint32_t max_log_entries,
net_compute_standby_group_t** out_handle,
char** err_out);
void net_compute_standby_group_free(net_compute_standby_group_t* h);
int net_compute_standby_group_member_count(const net_compute_standby_group_t* h);
int net_compute_standby_group_standby_count(const net_compute_standby_group_t* h);
int net_compute_standby_group_active_index(const net_compute_standby_group_t* h);
uint64_t net_compute_standby_group_active_origin(const net_compute_standby_group_t* h);
int net_compute_standby_group_active_healthy(const net_compute_standby_group_t* h);
uint32_t net_compute_standby_group_group_id(const net_compute_standby_group_t* h);
int net_compute_standby_group_buffered_event_count(const net_compute_standby_group_t* h);
int net_compute_standby_group_sync_standbys(
const net_compute_standby_group_t* h,
uint64_t* out_through, char** err_out);
int net_compute_standby_group_promote(
const net_compute_standby_group_t* h,
uint64_t* out_origin, char** err_out);
void net_compute_standby_group_on_node_recovery(
const net_compute_standby_group_t* h, uint64_t node_id);
char* net_compute_standby_group_members_json(const net_compute_standby_group_t* h);
/* Returns "active" | "standby" (caller frees) or NULL for OOB. */
char* net_compute_standby_group_member_role(
const net_compute_standby_group_t* h, uint32_t index);
/* ========================================================================= */
/* Dataforts MeshBlobAdapter (v0.2) + active overflow (v0.3) */
/* */
/* All symbols require a libnet built with the */
/* `dataforts + netdb + redex-disk` feature triple to provide real impls; */
/* otherwise the symbols still resolve (via the `blob_stubs` module) and */
/* return NET_ERR_FEATURE_NOT_BUILT / NULL. */
/* ========================================================================= */
typedef struct net_mesh_blob_adapter_s net_mesh_blob_adapter_t;
/* Free a buffer the adapter returned via `*out_data` (fetch path). */
void net_blob_free_buffer(uint8_t* ptr, size_t len);
net_mesh_blob_adapter_t* net_mesh_blob_adapter_new(
net_redex_t* redex,
const char* adapter_id,
int persistent,
const char* overflow_json
);
void net_mesh_blob_adapter_free(net_mesh_blob_adapter_t* handle);
int net_mesh_blob_adapter_store(
const net_mesh_blob_adapter_t* handle,
const uint8_t* blob_ref_bytes,
size_t blob_ref_len,
const uint8_t* data,
size_t data_len
);
int net_mesh_blob_adapter_fetch(
const net_mesh_blob_adapter_t* handle,
const uint8_t* blob_ref_bytes,
size_t blob_ref_len,
uint8_t** out_data,
size_t* out_len
);
int net_mesh_blob_adapter_exists(
const net_mesh_blob_adapter_t* handle,
const uint8_t* blob_ref_bytes,
size_t blob_ref_len,
int* out_exists
);
char* net_mesh_blob_adapter_prometheus_text(const net_mesh_blob_adapter_t* handle);
int net_mesh_blob_adapter_overflow_enabled(const net_mesh_blob_adapter_t* handle);
int net_mesh_blob_adapter_overflow_active(const net_mesh_blob_adapter_t* handle);
char* net_mesh_blob_adapter_overflow_config(const net_mesh_blob_adapter_t* handle);
int net_mesh_blob_adapter_set_overflow_enabled(
const net_mesh_blob_adapter_t* handle, int enabled);
int net_mesh_blob_adapter_set_overflow_config(
const net_mesh_blob_adapter_t* handle, const char* config_json);
#ifdef __cplusplus
}
#endif
#endif /* NET_SDK_H */