aboutsummaryrefslogtreecommitdiffstats
path: root/include/RNT_C_API.h
blob: 707c04aa2941b1087400e63d51810b2ae0837cf9 (plain) (blame)
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
#pragma once

/**
 * @file RNT_C_API.h
 * @brief C-callable surface over the RNT manager pipeline for OCaml ctypes.
 *
 * All types are opaque void pointers on the C side; the implementation casts
 * them to the appropriate C++ types internally. Callers must never dereference
 * handle or cursor pointers directly.
 *
 * ## Memory contract
 *   - Strings returned via out-parameters (char**) are heap-allocated by the
 *     API and must be released with rnt_free_string().
 *   - Payloads returned via (uint8_t**, size_t*) are heap-allocated and must
 *     be released with rnt_free_bytes().
 *   - Handles and cursors are owned by the caller and must be closed before
 *     the program exits.
 *
 * ## Error convention
 *   - Functions returning int use 0 for success and a negative value for error.
 *   - Functions returning a pointer return NULL on failure.
 *
 * ## Thread safety
 *   This API is **not thread-safe**. The global runtime (g_rt) is a single
 *   in-process instance; all callers share the same ObjectManager and storage
 *   backend. The expected usage model is a single OCaml thread (or domain)
 *   driving the API at a time. If concurrent access is needed in the future,
 *   a per-object or per-relation mutex strategy should be introduced.
 *
 * @todo Implement AUTH_CLAIM::READ/WRITE enforcement in rnt_open_handle once
 *       PermissionsManager::Access is wired to a real policy engine. Currently
 *       all handles open with full access regardless of the claims parameter.
 */

#include <stddef.h>
#include <stdint.h>

#include "Api.h"

#ifdef __cplusplus
extern "C" {
#endif

/** Opaque handle to a registry object opened through HandlerManager. */
typedef void* rnt_handle_t;

/** Opaque cursor over a relation, driven by the VM or used directly. */
typedef void* rnt_cursor_t;

/* ------------------------------------------------------------------ */
/* Runtime lifecycle                                                  */
/* ------------------------------------------------------------------ */

/**
 * @brief Initializes the RNT runtime with the selected storage backend.
 *
 * Must be called before any other API function. Once the runtime is
 * successfully initialized, subsequent calls are no-ops and return 0.
 * If initialization fails (returns negative), the call may be retried
 * with corrected parameters — the runtime is left in a clean state.
 *
 * @param driver        Storage driver to use: "sqlite" or "memory".
 *                      "sqlite" persists data to @p storage_path.
 *                      "memory" keeps all data in process memory (ignores
 *                      @p storage_path); intended for tests.
 * @param storage_path  File path for the SQLite database, or ":memory:" for
 *                      an ephemeral SQLite store. Ignored when driver is
 *                      "memory".
 * @return 0 on success, negative on error.
 */
NT_API int rnt_init(const char* driver, const char* storage_path);

/* ------------------------------------------------------------------ */
/* Authentication                                                       */
/* ------------------------------------------------------------------ */

/**
 * @brief Runs PermissionsManager::Firewall for a connection.
 *
 * @param auth_method  "plain_text" or "certificate".
 * @param claims_out   Set to a heap-allocated, newline-separated claim string.
 *                     Release with rnt_free_string(). NULL on error.
 * @return 0 on success, negative when authentication is rejected.
 */
NT_API int rnt_firewall(const char* auth_method, char** claims_out);

/* ------------------------------------------------------------------ */
/* Session lifecycle                                                    */
/* ------------------------------------------------------------------ */

/**
 * @brief Opens a session and registers it at /system/sessions/<hash>.
 *
 * The runtime mints a random 256-bit hex hash for the session; the caller
 * has no influence over the value. The returned string is the opaque
 * identifier required by every other rnt_session_* call.
 *
 * @param connection_context  Caller-owned pointer carried on the session.
 *                            The runtime treats it as opaque; ownership of
 *                            the pointee remains with the caller.
 * @param session_hash_out    Set to a heap-allocated 64-char hex string.
 *                            Release with rnt_free_string().
 * @return 0 on success, negative on error.
 */
NT_API int rnt_session_open(void* connection_context, char** session_hash_out);

/**
 * @brief Closes a session, unregistering it from /system/sessions/<hash>.
 *
 * After this call the session hash is invalid; subsequent rnt_session_* or
 * resolver lookups against it will fail. Branch overrides held by the
 * session are dropped.
 *
 * @param session_hash  Hash returned by rnt_session_open.
 * @return 0 on success, negative when the hash does not match an active session.
 */
NT_API int rnt_session_close(const char* session_hash);

/**
 * @brief Sets a per-session override for a branch.
 *
 * While the override is in effect, paths resolved through
 * /system/sessions/<hash>/branches/<branch_name>/... point at
 * /system/snapshots/<target_hash>/... regardless of where the global branch
 * HEAD currently sits. Pass an empty string for @p target_hash to remove
 * the override and resume falling back to the global branch.
 *
 * Non-empty target hashes must correspond to an existing
 * /system/snapshots/<target_hash> entry; otherwise the call fails.
 *
 * @param session_hash  Session identifier.
 * @param branch_name   Branch the override applies to (e.g. "main").
 * @param target_hash   Snapshot hash to bind, or "" to clear.
 * @return 0 on success, negative on error.
 */
NT_API int rnt_session_set_branch(const char* session_hash,
                                  const char* branch_name,
                                  const char* target_hash);

/* ------------------------------------------------------------------ */
/* Handle lifecycle                                                     */
/* ------------------------------------------------------------------ */

/**
 * @brief Opens a handle to the object at the given slash-separated path.
 *
 * Runs the full HandlerManager::Open pipeline:
 * ObjectManager::Find → PermissionsManager::Access →
 * IdentityManager::CanOpen → LifecycleManager::Contention →
 * LifecycleManager::Monitor.
 *
 * @param path    Slash-separated logical path, e.g. "/system/branches/main".
 * @param claims  Claim string returned by rnt_firewall (may be NULL).
 * @return Handle pointer, or NULL when the path is not found or access is denied.
 */
NT_API rnt_handle_t rnt_open_handle(const char* path, const char* claims);

/**
 * @brief Closes a handle, running HandlerManager::Close and Unmonitor.
 * @return 0 on success, negative on error.
 */
NT_API int rnt_close_handle(rnt_handle_t handle);

/**
 * @brief Reads the current branch-tree root hash from a BRANCH object.
 *
 * Only valid for handles opened on BRANCH objects. The returned string is
 * the merkle_root of the BRANCH_TREE this branch currently points at — a
 * `Merkle<std::string>` root mapping mg_name → mg_hash. An empty string
 * indicates an unborn branch with no commits yet.
 *
 * @param handle           Handle to a BRANCH object.
 * @param target_hash_out  Set to a heap-allocated copy of the target hash
 *                         (possibly empty). Release with rnt_free_string().
 * @return 0 on success, negative when the handle is not a BRANCH.
 */
NT_API int rnt_branch_target(rnt_handle_t handle, char** target_hash_out);

/**
 * @brief Atomically advances a branch to point at a new branch-tree root.
 *
 * The new hash must already exist as a content-addressed blob in the KV
 * store — branch-tree roots are produced by mutating calls (rnt_link_tuple,
 * rnt_register_relation, …) and cannot be invented externally. Pass an
 * empty string to reset the branch to unborn.
 *
 * Write exclusion is structural: BRANCH objects carry @c exclusive=true in
 * their object_type, so LifecycleManager::Contention prevents a second handle
 * from being opened while a writer holds the branch. AUTH_CLAIM::WRITE is not
 * checked at the C API boundary.
 *
 * @param branch_path  Slash-separated branch path, e.g. "/system/branches/main".
 * @param new_hash     Branch-tree root to advance to (64-char lowercase hex),
 *                     or "" to reset to unborn.
 * @return 0 on success, negative when the branch is not found, the type is
 *         not BRANCH, or @p new_hash is non-empty but no matching blob
 *         exists in the KV store.
 */
NT_API int rnt_branch_advance(const char* branch_path, const char* new_hash);

/* ------------------------------------------------------------------ */
/* Object registration                                                  */
/* ------------------------------------------------------------------ */

/**
 * @brief Registers a RELATION object at the given path.
 *
 * Idempotent: if an object already exists at the path, the call succeeds
 * without re-registering.
 *
 * @param path Slash-separated path of the form
 *             "/system/branches/<branch>/multigroups/<mg>/relations/<rel>".
 * @return 0 on success, negative on error.
 */
NT_API int rnt_register_relation(const char* path);

/**
 * @brief Registers a BRANCH object at the given path pointing at a branch-tree root.
 *
 * If a BRANCH already exists at this path, returns 0 without modifying it.
 * When @p target_hash is non-NULL and non-empty it must already exist as a
 * content-addressed blob in the KV store (produced by an earlier mutation
 * through the same runtime); otherwise the call fails. Pass NULL or "" to
 * register an unborn branch.
 *
 * @param path         Slash-separated path, e.g. "/system/branches/main".
 * @param target_hash  Branch-tree root this branch points at (may be NULL
 *                     or "" for unborn).
 * @return 0 on success, negative on error.
 */
NT_API int rnt_register_branch(const char* path, const char* target_hash);

/**
 * @brief Lists all relations of a specific multigroup under a branch.
 *
 * Returns a newline-delimited string of "name\troot_hash" pairs, one per
 * relation in the named multigroup at the branch's current tree. An unborn
 * branch or an mg absent from the branch tree yields an empty string. The
 * caller must release the string with rnt_free_string().
 *
 * @param branch_mg_path  Slash-separated path of the form
 *                        "/system/branches/<name>/multigroups/<mg>".
 * @param out             Set to a heap-allocated "name\troot\n" string.
 *                        Release with rnt_free_string(). NULL on error.
 * @return 0 on success, negative when the path shape is wrong or the branch
 *         is not found.
 */
NT_API int rnt_list_relations(const char* branch_mg_path, char** out);

/**
 * @brief Lists all multigroups bound to a branch.
 *
 * Reads the branch-tree root from `/system/branches/<name>` and pages each
 * (mg_name, mg_hash) entry. Returns "name\thash\n" lines. Unborn branches
 * yield an empty string.
 *
 * @param branch_path  Slash-separated branch path, e.g. "/system/branches/main".
 * @param out          Heap-allocated string; release with rnt_free_string().
 * @return 0 on success, negative when the branch is not found.
 */
NT_API int rnt_list_branch_multigroups(const char* branch_path, char** out);

/**
 * @brief Lists all relations stored in a specific snapshot.
 *
 * Reads the multigroup codec directly from the snapshot at
 * /system/snapshots/<snapshot_hash> without following any branch HEAD.
 * Use this when the desired snapshot hash is already known (e.g. detached
 * checkout). Returns the same "name\troot_hash\n" format as rnt_list_relations.
 *
 * @param snapshot_hash  64-char hex hash of the snapshot.
 * @param out            Set to heap-allocated string. Release with rnt_free_string().
 * @return 0 on success, negative when the snapshot is not registered.
 */
NT_API int rnt_list_snapshot_relations(const char* snapshot_hash, char** out);

/* ------------------------------------------------------------------ */
/* Tuple storage                                                        */
/* ------------------------------------------------------------------ */

/**
 * @brief Stores a tuple and links it to the relation at the given path.
 *
 * Tuples are encoded as a flat key=value string, one attribute per line:
 *   "name=Blathers\nprofession=Museum Curator\n"
 * Attributes are sorted by name before hashing to ensure content-addressing
 * is order-independent.
 *
 * @param relation_path  Slash-separated relation path.
 * @param kv_attrs       Newline-delimited "key=value" attribute string.
 * @param hash_out       Set to the 64-character hex SHA-256 hash of the tuple.
 *                       Release with rnt_free_string(). NULL on error.
 * @return 0 on success, negative on error.
 */
NT_API int rnt_link_tuple(const char* relation_path,
                          const char* kv_attrs,
                          char**      hash_out);

/**
 * @brief Removes a tuple from the relation's Merkle tree and tuple store.
 *
 * Calls Merkle::Remove on the relation's current root and updates
 * ObjectManager::Relation::merkle_root atomically.  The tuple bytes remain in
 * the KV store (they may be referenced by older snapshots); only the
 * membership in the current tree is removed.
 *
 * @param relation_path  Slash-separated relation path.
 * @param tuple_hash     64-character hex SHA-256 of the tuple to remove.
 * @return 0 on success, negative when the relation is not found.
 */
NT_API int rnt_unlink_tuple(const char* relation_path, const char* tuple_hash);

/**
 * @brief Resets a relation's Merkle root to the empty-tree state.
 *
 * Sets ObjectManager::Relation::merkle_root to the empty string.  Existing
 * tuple bytes remain in the KV store.  After this call the relation contains
 * no tuples from the perspective of cursor iteration.
 *
 * @param relation_path  Slash-separated relation path.
 * @return 0 on success, negative when the relation is not found.
 */
NT_API int rnt_clear_relation(const char* relation_path);

/**
 * @brief Returns the current Merkle root hash for a relation.
 *
 * OCaml calls this after rnt_link_tuple or rnt_unlink_tuple to read the
 * updated root back and store it in the in-memory multigroup (tree_pointer).
 * An empty string is returned for a relation that contains no tuples.
 *
 * @param relation_path  Slash-separated relation path.
 * @param root_hash_out  Set to the 64-character hex root hash, or to an empty
 *                       heap-allocated string for an empty relation.
 *                       Release with rnt_free_string().
 * @return 0 on success, negative when the relation is not found.
 */
NT_API int rnt_relation_root(const char* relation_path, char** root_hash_out);

/* ------------------------------------------------------------------ */
/* Cursor and VM                                                        */
/* ------------------------------------------------------------------ */

/**
 * @brief Opens a cursor on the relation referenced by handle.
 *
 * The returned cursor is positioned before the first tuple. Advance it with
 * rnt_cursor_next(). The cursor must be closed with rnt_cursor_close() before
 * closing the handle.
 *
 * @param handle  Open RELATION handle.
 * @return Cursor pointer, or NULL on error.
 */
NT_API rnt_cursor_t rnt_cursor_open(rnt_handle_t handle);

/**
 * @brief Advances the cursor and returns the next tuple as a kv string.
 *
 * The tuple is encoded as a newline-delimited "key=value" string matching the
 * format accepted by rnt_link_tuple:
 *   "name=Blathers\nprofession=Museum Curator\n"
 *
 * @param cursor       Open cursor.
 * @param tuple_out    Set to a heap-allocated kv string, or NULL when exhausted.
 *                     Release with rnt_free_string() when non-NULL.
 * @return 1 when a tuple was returned, 0 when exhausted, negative on error.
 */
NT_API int rnt_cursor_next(rnt_cursor_t cursor, char** tuple_out);

/**
 * @brief Closes a cursor and releases its resources.
 * @return 0 on success, negative on error.
 */
NT_API int rnt_cursor_close(rnt_cursor_t cursor);

/* ------------------------------------------------------------------ */
/* VM plan builder                                                      */
/* ------------------------------------------------------------------ */

/** Opaque plan node tree, built incrementally via rnt_plan_* calls. */
typedef void* rnt_plan_t;

/**
 * @brief Creates a SCAN plan node that reads all tuples from a stored relation.
 *
 * Opens a RELATION handle and cursor internally. Both are transferred to the
 * plan and released when the resulting VM cursor is closed.
 *
 * @param relation_path  Absolute slash-separated path to the relation, e.g.
 *  "/system/branches/main/multigroups/warehouse/relations/public:users".
 * @return Plan node, or NULL when the relation does not exist or cannot be opened.
 */
NT_API rnt_plan_t rnt_plan_scan(const char* relation_path);

/**
 * @brief Creates a nested-loop JOIN plan node.
 *
 * Takes ownership of both @p left and @p right. On success the caller must not
 * free either child; they are released when the returned plan is freed or
 * executed. On failure (NULL return) both children are freed.
 *
 * @return Plan node, or NULL on error.
 */
NT_API rnt_plan_t rnt_plan_join(rnt_plan_t left, rnt_plan_t right);

/**
 * @brief Creates a TAKE plan node that limits output to at most @p limit tuples.
 *
 * Takes ownership of @p source. On failure @p source is freed.
 *
 * @return Plan node, or NULL on error.
 */
NT_API rnt_plan_t rnt_plan_take(rnt_plan_t source, size_t limit);

/**
 * @brief Releases a plan that was built but not yet executed.
 *
 * Closes any open cursors and handles owned by the plan tree, then frees all
 * nodes. Safe to call with NULL. Do NOT call after rnt_vm_execute_plan —
 * that function takes ownership.
 */
NT_API void rnt_plan_free(rnt_plan_t plan);

/**
 * @brief Executes a plan tree and returns a streaming VM cursor.
 *
 * Takes ownership of @p plan; the caller must not call rnt_plan_free after this.
 * The returned cursor is advanced with rnt_vm_cursor_next() and must be closed
 * with rnt_vm_cursor_close(), which also releases all plan resources.
 *
 * @return VM cursor, or NULL when the plan is NULL.
 */
NT_API rnt_cursor_t rnt_vm_execute_plan(rnt_plan_t plan);

/**
 * @brief Advances a VM cursor and returns the next merged tuple as a kv string.
 *
 * Same encoding and return-value semantics as rnt_cursor_next.
 *
 * @param vm_cursor  Cursor returned by rnt_vm_execute_plan.
 * @param tuple_out  Set to a heap-allocated kv string, or NULL when exhausted.
 *                   Release with rnt_free_string() when non-NULL.
 * @return 1 when a tuple was returned, 0 when exhausted, negative on error.
 */
NT_API int rnt_vm_cursor_next(rnt_cursor_t vm_cursor, char** tuple_out);

/**
 * @brief Closes a VM cursor, releases all plan nodes, cursors, and handles.
 * @return 0 on success, negative on error.
 */
NT_API int rnt_vm_cursor_close(rnt_cursor_t vm_cursor);

/* ------------------------------------------------------------------ */
/* Memory management                                                    */
/* ------------------------------------------------------------------ */

/** @brief Releases a string allocated by the API. Safe to call with NULL. */
NT_API void rnt_free_string(char* s);

/** @brief Releases a byte buffer allocated by the API. Safe to call with NULL. */
NT_API void rnt_free_bytes(uint8_t* p);

#ifdef __cplusplus
}
#endif