From f62e0ce9eeedc1ca44983489e6790cce7483731e Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:51:31 +0000 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`fea?= =?UTF-8?q?t/minimal-dpoi-qca-loop`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @flyingrobots. * https://github.com/meta-graph/core/pull/70#issuecomment-3407793186 The following files were modified: * `include/metagraph/epoch.h` * `src/arena.c` * `src/dpoi.c` * `src/error.c` * `src/graph.c` * `src/hilbert.c` * `src/match.c` * `src/qca.c` * `src/rmg.c` * `src/rule.c` --- include/metagraph/epoch.h | 16 +++++- src/arena.c | 36 ++++++++++++- src/dpoi.c | 104 +++++++++++++++++++++++++++++++++++++- src/error.c | 62 ++++++++++++++++++++++- src/graph.c | 72 +++++++++++++++++++++++++- src/hilbert.c | 31 +++++++++++- src/match.c | 48 +++++++++++++++++- src/qca.c | 52 ++++++++++++++++++- src/rmg.c | 24 ++++++++- src/rule.c | 34 ++++++++++++- 10 files changed, 469 insertions(+), 10 deletions(-) diff --git a/include/metagraph/epoch.h b/include/metagraph/epoch.h index 3fa05a8..3aa921a 100644 --- a/include/metagraph/epoch.h +++ b/include/metagraph/epoch.h @@ -8,12 +8,26 @@ typedef struct { _Atomic(uint64_t) epoch; } mg_epoch_t; +/** + * Initialize an epoch counter to 1. + * @param e Epoch object to initialize (its internal atomic counter will be set to 1). + */ static inline void mg_epoch_init(mg_epoch_t *e) { atomic_store(&e->epoch, 1); } +/** + * Get the current epoch value from the epoch counter. + * @param e Pointer to the epoch object to read. + * @returns The current epoch value. + */ static inline uint64_t mg_epoch_load(const mg_epoch_t *e) { return atomic_load(&e->epoch); } +/** + * Atomically advance the epoch counter by one. + * + * @param e Epoch object whose counter will be incremented. + */ static inline void mg_epoch_flip(mg_epoch_t *e) { atomic_fetch_add(&e->epoch, 1); } -#endif /* METAGRAPH_EPOCH_H */ +#endif /* METAGRAPH_EPOCH_H */ \ No newline at end of file diff --git a/src/arena.c b/src/arena.c index 388fc1f..f29c44c 100644 --- a/src/arena.c +++ b/src/arena.c @@ -4,18 +4,52 @@ #include #include +/** + * Round `value` up to the nearest multiple of `alignment`. + * + * @param value The input value to align. + * @param alignment The alignment boundary to round up to; must be a power of two. + * @returns The smallest value greater than or equal to `value` that is a multiple of `alignment`. + */ static size_t metagraph_align_up(size_t value, size_t alignment) { return (value + (alignment - 1)) & ~(alignment - 1); } +/** + * Initialize an arena to use a caller-provided memory buffer. + * + * Sets the arena's base pointer to the start of `buffer`, records `capacity` + * in bytes, and resets the allocation offset to zero. + * + * @param arena Pointer to the arena object to initialize. + * @param buffer Caller-managed memory region that the arena will allocate from. + * @param capacity Size of `buffer` in bytes. + */ void mg_arena_init(mg_arena_t *arena, void *buffer, size_t capacity) { arena->base = (uint8_t *)buffer; arena->capacity = capacity; arena->offset = 0; } +/** + * Reset the arena to its initial empty state. + * + * Sets the allocation offset to zero so subsequent allocations reuse the arena's buffer. + * + * @param arena Arena to reset. + */ void mg_arena_reset(mg_arena_t *arena) { arena->offset = 0; } +/** + * Allocate a block from an arena aligned to the platform's maximum alignment. + * + * Allocates `size` bytes from `arena`, advancing the arena's internal offset + * by the allocated size. The returned pointer is aligned to `alignof(max_align_t)`. + * + * @param arena Arena to allocate from. + * @param size Number of bytes to allocate. + * @returns Pointer to the allocated memory if sufficient space remains, `NULL` otherwise. + */ void *mg_arena_alloc(mg_arena_t *arena, size_t size) { const size_t align = alignof(max_align_t); size_t offset = metagraph_align_up(arena->offset, align); @@ -25,4 +59,4 @@ void *mg_arena_alloc(mg_arena_t *arena, size_t size) { void *ptr = arena->base + offset; arena->offset = offset + size; return ptr; -} +} \ No newline at end of file diff --git a/src/dpoi.c b/src/dpoi.c index 76e5017..7608e05 100644 --- a/src/dpoi.c +++ b/src/dpoi.c @@ -25,6 +25,17 @@ static metagraph_result_t metagraph_match_set_grow(mg_match_set_t *set, uint32_t min_capacity); static metagraph_result_t metagraph_prepare_match_buffer(mg_match_set_t *set); +/** + * Ensure a match set has capacity for at least `min_capacity` entries. + * + * If the set's capacity is smaller than `min_capacity`, the underlying storage + * is resized and `set->data` and `set->capacity` are updated accordingly. + * + * @param set Match set to ensure capacity for. + * @param min_capacity Minimum required capacity (number of mg_match_t entries). + * @returns METAGRAPH_SUCCESS on success, or an error result on failure (for + * example, out-of-memory if allocation fails). + */ static metagraph_result_t metagraph_match_set_grow(mg_match_set_t *set, uint32_t min_capacity) { if (set->capacity >= min_capacity) { @@ -46,6 +57,15 @@ static metagraph_result_t metagraph_match_set_grow(mg_match_set_t *set, return METAGRAPH_SUCCESS; } +/** + * Append a computed match for `rule` using the provided `image` into `set`, computing its ordering key and recording touched nodes. + * + * @param rule Source rule whose `rule_id` is recorded in the emitted match. + * @param image Array of graph node identifiers forming the match (length `count`). + * @param count Number of node identifiers in `image`. + * @param set Match buffer to which the new match will be appended; must be prepared prior to calling. + * @returns METAGRAPH_SUCCESS on success, or an error code on failure (for example, when memory allocation fails). + */ static metagraph_result_t metagraph_emit_match(const mg_rule_t *rule, const mg_node_id_t *image, uint8_t count, @@ -76,6 +96,18 @@ static metagraph_result_t metagraph_emit_match(const mg_rule_t *rule, return METAGRAPH_SUCCESS; } +/** + * Find and emit matches for a rule that consists of a single node. + * + * For each graph node whose type equals the rule's required node type, appends + * a one-node mapping to the provided match set. + * + * @param graph Graph to search for matching nodes. + * @param rule Rule whose single-node pattern (rule->L.node_type[0]) is used. + * @param set Match set to append found matches into; must be prepared by the caller. + * + * @returns METAGRAPH_SUCCESS on success, or an error code propagated from match emission (e.g., out-of-memory). + */ static metagraph_result_t metagraph_match_single_node(const mg_graph_t *graph, const mg_rule_t *rule, mg_match_set_t *set) { @@ -91,6 +123,18 @@ static metagraph_result_t metagraph_match_single_node(const mg_graph_t *graph, return METAGRAPH_SUCCESS; } +/** + * Find and emit matches for a two-node (one-edge) rule pattern in the given graph. + * + * Scans graph edges for ordered pairs where the source node has the rule's left-hand + * type and the target node has the rule's right-hand type, and appends a two-node + * mapping for each valid pair to the provided match set. + * + * @param graph Graph to search for matching node pairs. + * @param rule Rule whose two-node LHS/RHS types are used to filter matches. + * @param set Output match set to which discovered two-node matches are appended. + * @returns METAGRAPH_SUCCESS on success, or an error code propagated from match emission on failure. + */ static metagraph_result_t metagraph_match_two_nodes(const mg_graph_t *graph, const mg_rule_t *rule, mg_match_set_t *set) { @@ -122,6 +166,19 @@ static metagraph_result_t metagraph_match_two_nodes(const mg_graph_t *graph, return METAGRAPH_SUCCESS; } +/** + * Generate candidate matches for a rule by scanning a rule-meta-graph's skeleton. + * + * Prepares the output match buffer and emits matches for simple rule arities: + * - single-node rules: emits one-node matches for graph nodes whose type matches the rule. + * - two-node one-edge rules: emits ordered node-pair matches for adjacent nodes matching lhs/rhs types. + * + * @param rmg Rule-meta-graph containing a skeleton graph to search; must not be NULL. + * @param rule Rule definition whose pattern will be matched; must not be NULL. + * @param arena Arena allocator (unused by this implementation; may be NULL). + * @param out_matches Match buffer to populate; must not be NULL. + * @returns METAGRAPH_SUCCESS on success. Propagates error codes from buffer preparation or allocation failures (e.g., out-of-memory) on failure. + */ metagraph_result_t mg_dpoi_match_rmg(const mg_rmg_t *rmg, const mg_rule_t *rule, mg_arena_t *arena, mg_match_set_t *out_matches) { @@ -147,6 +204,13 @@ metagraph_result_t mg_dpoi_match_rmg(const mg_rmg_t *rmg, const mg_rule_t *rule, return METAGRAPH_SUCCESS; } +/** + * Compare two match records by their composite key: first `key_hi`, then `key_lo`. + * + * @param lhs Pointer to the first `mg_match_t`. + * @param rhs Pointer to the second `mg_match_t`. + * @returns `-1` if `lhs` is less than `rhs`, `1` if `lhs` is greater than `rhs`, `0` if they are equal. + */ static int metagraph_match_compare(const void *lhs, const void *rhs) { const mg_match_t *left = (const mg_match_t *)lhs; const mg_match_t *right = (const mg_match_t *)rhs; @@ -165,6 +229,17 @@ static int metagraph_match_compare(const void *lhs, const void *rhs) { return 0; } +/** + * Ensure the match set has an initialized match buffer and resets its count. + * + * If the set already has an allocated data buffer, the function resets + * set->count to 0. Otherwise it allocates an initial array of + * METAGRAPH_INITIAL_MATCH_CAPACITY elements, sets set->capacity and + * set->count accordingly. + * + * @param set Match set whose buffer and counters will be initialized or reset. + * @returns METAGRAPH_SUCCESS on success, otherwise an out-of-memory error result. + */ static metagraph_result_t metagraph_prepare_match_buffer(mg_match_set_t *set) { if (set->data != NULL) { set->count = 0U; @@ -181,6 +256,13 @@ static metagraph_result_t metagraph_prepare_match_buffer(mg_match_set_t *set) { return METAGRAPH_SUCCESS; } +/** + * Determine whether two matches share any touched node identifiers. + * + * @param lhs First match to compare; its touched_nodes array and tn count are used. + * @param rhs Second match to compare; its touched_nodes array and tn count are used. + * @returns `true` if the matches share at least one touched node identifier, `false` otherwise. + */ static bool metagraph_matches_overlap(const mg_match_t *lhs, const mg_match_t *rhs) { for (uint16_t lhs_index = 0; lhs_index < lhs->tn; ++lhs_index) { @@ -194,6 +276,17 @@ static bool metagraph_matches_overlap(const mg_match_t *lhs, return false; } +/** + * Selects a maximal subset of non-overlapping matches from the provided match set. + * + * Sorts the matches by their composite key and then retains, in-place, the first + * set of matches that do not overlap on any touched node. The function updates + * matches->count to reflect the number of matches kept. + * + * @param matches Match set to reduce; modified in-place. If `matches` is NULL + * or contains zero or one entry, the function returns without + * making changes. + */ void mg_dpoi_schedule_maximal(mg_match_set_t *matches) { if (!matches || matches->count <= 1U) { return; @@ -222,6 +315,15 @@ void mg_dpoi_schedule_maximal(mg_match_set_t *matches) { matches->count = kept; } +/** + * Commit a set of scheduled matches to the graph. + * + * @param graph Target graph to apply the scheduled matches to. + * @param rules Array of rules corresponding to the matches in `schedule`. + * @param rule_count Number of rules in `rules`. + * @param schedule Set of matches to commit to `graph`. + * @returns METAGRAPH_SUCCESS on success, or an error code on failure. + */ metagraph_result_t mg_dpo_commit(mg_graph_t *graph, const mg_rule_t *rules, uint32_t rule_count, const mg_match_set_t *schedule) { @@ -230,4 +332,4 @@ metagraph_result_t mg_dpo_commit(mg_graph_t *graph, const mg_rule_t *rules, (void)rule_count; (void)schedule; return METAGRAPH_SUCCESS; -} +} \ No newline at end of file diff --git a/src/error.c b/src/error.c index add641e..a14b61b 100644 --- a/src/error.c +++ b/src/error.c @@ -106,6 +106,14 @@ _Static_assert(sizeof(METAGRAPH_ERROR_STRINGS) / #define METAGRAPH_ATTR_COLD_CONST #endif +/** + * Map a metagraph_result_t code to a human-readable message. + * + * @param result Error code to translate. + * @returns Pointer to a static, null-terminated message string corresponding to `result`. + * Returns "User-defined error" if `result` falls in the user-defined error range, + * or "Unknown error" if no matching message is found. + */ METAGRAPH_ATTR_COLD_CONST const char *metagraph_result_to_string(metagraph_result_t result) { // Linear search through the table (fine for ~50 entries) @@ -150,6 +158,19 @@ static void metagraph_write_message(metagraph_error_context_t *context, const char *format, va_list args) METAGRAPH_ATTR_PRINTF(2, 0); +/** + * Format an error message into the per-thread error context's message buffer. + * + * If `format` is NULL, the context message is set to an empty string. + * On formatting failure, the message is replaced with "". + * If the formatted output would overflow the buffer, the end of the buffer is + * replaced with "..." when there is room to indicate truncation. + * + * @param context Pointer to the thread-local error context whose `message` + * buffer will be written. + * @param format printf-style format string (may be NULL). + * @param args `va_list` of arguments for `format`. + */ static void metagraph_write_message(metagraph_error_context_t *context, const char *format, va_list args) { if (!format) { @@ -181,6 +202,21 @@ static metagraph_result_t metagraph_set_error_context_v( const char *format, // NOLINT(bugprone-easily-swappable-parameters) va_list args) METAGRAPH_ATTR_PRINTF(5, 0); +/** + * Populate the current thread's error context with the given error code, source location, and formatted message. + * + * Sets the thread-local error context fields to reflect the provided `code`, `file`, `line`, and `function`, and formats + * a human-readable message into the context using `format` and `args`. If the per-thread context is not available, + * no context is modified. + * + * @param code Error code to store in the thread-local context. + * @param file Source file name associated with the error location. + * @param line Source line number associated with the error location. + * @param function Name of the function associated with the error location. + * @param format printf-style format string used to compose the error message; may be NULL to produce an empty message. + * @param args Variadic argument list corresponding to `format`. + * @returns The same `code` value that was passed in. + */ static metagraph_result_t metagraph_set_error_context_v( metagraph_result_t code, const char *file, int line, const char *function, // NOLINT(bugprone-easily-swappable-parameters) @@ -204,6 +240,20 @@ static metagraph_result_t metagraph_set_error_context_v( return code; } +/** + * Set the current thread's error context with source location and a formatted message. + * + * Populates the per-thread error context's code, source file, line, function name, + * and message formatted using `format` and the following variadic arguments. + * If the per-thread context cannot be allocated, the call has no effect on thread state. + * + * @param code Error code to store in the thread-local context. + * @param file Source file name where the error occurred. + * @param line Source line number where the error occurred. + * @param function Source function name where the error occurred. + * @param format Printf-style format string for the error message, followed by matching arguments. + * @returns The provided `code`. + */ METAGRAPH_ATTR_COLD metagraph_result_t metagraph_set_error_context( metagraph_result_t code, const char *file, int line, @@ -217,6 +267,16 @@ metagraph_result_t metagraph_set_error_context( return result; } +/** + * Retrieve the current thread's error context into the supplied output object. + * + * If the per-thread context is unavailable (allocation failed) or no error has + * been set, the output is zeroed and its `code` field is set to METAGRAPH_SUCCESS. + * + * @param context Destination object to receive the error context; must not be NULL. + * @returns METAGRAPH_SUCCESS when the context was retrieved or cleared successfully, + * METAGRAPH_ERROR_NULL_POINTER if `context` is NULL. + */ metagraph_result_t metagraph_get_error_context(metagraph_error_context_t *context) { if (!context) { @@ -265,4 +325,4 @@ void metagraph_thread_cleanup(void) { thread_error_context = NULL; } } -#endif +#endif \ No newline at end of file diff --git a/src/graph.c b/src/graph.c index 0ecf28b..20048b7 100644 --- a/src/graph.c +++ b/src/graph.c @@ -6,11 +6,28 @@ #include #include +/** + * Initialize a graph structure to an empty state and initialize its epoch. + * + * This zeroes all fields of the provided mg_graph_t and initializes the + * embedded epoch value via mg_epoch_init. + * + * @param graph Pointer to the mg_graph_t to initialize; must point to a valid, + * writable mg_graph_t. + */ void mg_graph_init_empty(mg_graph_t *graph) { memset(graph, 0, sizeof(*graph)); mg_epoch_init(&graph->epoch); } +/** + * Release all memory held by a graph and reset its contents to zero. + * + * Frees dynamically allocated arrays within the graph (nodes, edges, neighbor ids) + * and clears the graph structure. Safe to call with a NULL pointer. + * + * @param graph Pointer to the graph to free and reset; may be NULL. + */ void mg_graph_free(mg_graph_t *graph) { if (!graph) { return; @@ -21,6 +38,16 @@ void mg_graph_free(mg_graph_t *graph) { memset(graph, 0, sizeof(*graph)); } +/** + * Create a snapshot view of the provided graph suitable for read-only inspection. + * + * If `graph` is NULL the snapshot contains NULL pointers and zero counts. The snapshot's + * pointer fields reference the graph's internal arrays; they are not deep copies and + * remain valid only as long as the underlying graph's memory is not modified or freed. + * + * @param graph Graph to snapshot; may be NULL. + * @returns mg_graph_snapshot_t Snapshot containing pointers to node and neighbor arrays, edge data, their counts, and the graph epoch value. + */ mg_graph_snapshot_t mg_graph_snapshot_view(const mg_graph_t *graph) { mg_graph_snapshot_t snapshot = { .nodes = graph ? graph->nodes : NULL, @@ -33,6 +60,13 @@ mg_graph_snapshot_t mg_graph_snapshot_view(const mg_graph_t *graph) { return snapshot; } +/** + * Compute the degree (number of neighbors) of the node at the given index. + * + * @param graph Graph to query. + * @param node_index Index of the node within the graph's node array. + * @returns `-1` if `graph` is NULL or `node_index` is out of bounds, otherwise the node's degree (number of neighbor entries). + */ int mg_graph_degree(const mg_graph_t *graph, uint32_t node_index) { if (!graph || node_index >= graph->node_count) { return -1; @@ -46,16 +80,43 @@ int mg_graph_degree(const mg_graph_t *graph, uint32_t node_index) { return (int)(end - start); } +/** + * Allocate and zero-initialize the graph's node array and record the node count. + * + * Allocates an array of `count` `mg_node_rec_t` elements with `calloc` and assigns it + * to `graph->nodes`. Sets `graph->node_count` to `count`. If allocation fails, + * `graph->nodes` will be `NULL` and `graph->node_count` will still be set to `count`. + * + * @param graph Graph object whose node storage will be allocated. + * @param count Number of nodes to allocate. + */ static void metagraph_graph_alloc_nodes(mg_graph_t *graph, size_t count) { graph->nodes = (mg_node_rec_t *)calloc(count, sizeof(mg_node_rec_t)); graph->node_count = count; } +/** + * Allocate and zero-initialize the neighbor ID array for a graph and record its size. + * + * Allocates an array of `count` uint32_t elements with `calloc`, assigns it to + * `graph->nbr_ids`, and sets `graph->nbr_count` to `count`. + * + * @param graph Graph whose neighbor storage will be allocated. + * @param count Number of neighbor IDs to allocate. + */ static void metagraph_graph_alloc_neighbors(mg_graph_t *graph, size_t count) { graph->nbr_ids = (uint32_t *)calloc(count, sizeof(uint32_t)); graph->nbr_count = count; } +/** + * Initialize the provided graph as a three-node path (0–1–2) with each node set to `MG_TYPE_Q`. + * + * The function frees any existing contents of `graph` and replaces them with three nodes and their + * adjacency lists representing the path 0—1—2. + * + * @param graph Graph to populate; existing contents are freed and replaced. Must be non-NULL. + */ void mg_graph_make_path_qwqwq(mg_graph_t *graph) { mg_graph_free(graph); mg_graph_init_empty(graph); @@ -84,6 +145,15 @@ void mg_graph_make_path_qwqwq(mg_graph_t *graph) { graph->nodes[2].degree = 1; } +/** + * Construct a small three-node graph with a predetermined adjacency layout. + * + * This resets and reinitializes `graph`, allocates space for 3 nodes and 6 neighbor entries, + * and populates node ids, node types (MG_TYPE_Q), adjacency offsets, degrees, and the + * neighbor id array with a fixed pattern used for example/testing purposes. + * + * @param graph Graph object to initialize and populate; existing contents are freed. + */ void mg_graph_make_path_qwqwq2(mg_graph_t *graph) { mg_graph_free(graph); mg_graph_init_empty(graph); @@ -112,4 +182,4 @@ void mg_graph_make_path_qwqwq2(mg_graph_t *graph) { graph->nodes[2].type = MG_TYPE_Q; graph->nodes[2].adj_offset = 4; graph->nodes[2].degree = 1; -} +} \ No newline at end of file diff --git a/src/hilbert.c b/src/hilbert.c index b0bf7aa..b89ca65 100644 --- a/src/hilbert.c +++ b/src/hilbert.c @@ -6,6 +6,16 @@ #include "metagraph/result.h" +/** + * Initialize a Hilbert register handle with storage for a given number of nodes. + * + * Allocates and zero-initializes the internal node_bits array and sets node_count. + * + * @param hilbert Pointer to the hilbert handle to initialize; must not be NULL. + * @param count Number of node entries to allocate (zero is allowed and allocates a minimal buffer). + * @returns `METAGRAPH_OK()` on success; `METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, ...)` if `hilbert` is NULL; + * `METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, ...)` if allocation fails. + */ metagraph_result_t mg_hilbert_init(mg_hilbert_t *hilbert, size_t count) { if (!hilbert) { return METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, @@ -20,6 +30,14 @@ metagraph_result_t mg_hilbert_init(mg_hilbert_t *hilbert, size_t count) { return METAGRAPH_OK(); } +/** + * Release resources held by a Hilbert handle. + * + * Frees the internal `node_bits` buffer and resets `node_count` to 0. + * Safe to call with a NULL `hilbert` pointer (no action will be taken). + * + * @param hilbert Pointer to the hilbert handle whose resources will be freed. + */ void mg_hilbert_free(mg_hilbert_t *hilbert) { if (!hilbert) { return; @@ -29,6 +47,17 @@ void mg_hilbert_free(mg_hilbert_t *hilbert) { hilbert->node_count = 0; } +/** + * Resize a Hilbert register to hold a specified number of nodes. + * + * Preserves existing node state up to the smaller of the old and new counts. + * + * @param hilbert Pointer to the Hilbert handle to resize; must not be NULL. + * @param new_count Desired number of nodes (may be zero). + * @returns `METAGRAPH_OK()` on success. + * Returns `METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, ...)` if `hilbert` is NULL. + * Returns `METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, ...)` if memory allocation fails. + */ metagraph_result_t mg_hilbert_resize(mg_hilbert_t *hilbert, size_t new_count) { if (!hilbert) { return METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, @@ -49,4 +78,4 @@ metagraph_result_t mg_hilbert_resize(mg_hilbert_t *hilbert, size_t new_count) { hilbert->node_bits = next; hilbert->node_count = new_count; return METAGRAPH_OK(); -} +} \ No newline at end of file diff --git a/src/match.c b/src/match.c index ab835c8..335fec4 100644 --- a/src/match.c +++ b/src/match.c @@ -4,6 +4,16 @@ #include #include +/** + * Initialize an mg_match_set_t structure and optionally allocate its internal storage. + * + * The function zeroes the provided structure and, if capacity > 0, allocates an array + * of mg_match_t of the requested size and sets the structure's capacity accordingly. + * + * @param set Pointer to the mg_match_set_t to initialize (must not be NULL). + * @param capacity Initial number of elements to allocate; if zero no allocation is performed. + * @returns `true` if the structure was initialized and any requested allocation succeeded, `false` otherwise. + */ bool mg_match_set_init(mg_match_set_t *set, uint32_t capacity) { if (!set) { return false; @@ -19,6 +29,15 @@ bool mg_match_set_init(mg_match_set_t *set, uint32_t capacity) { return true; } +/** + * Ensure the match set can hold at least the specified number of elements. + * + * Allocates or grows the internal storage so that the set can contain at least min_capacity elements and updates set->capacity on success; no allocation is performed if the current capacity already meets or exceeds min_capacity. + * + * @param set Pointer to the match set to modify; if NULL the function does nothing and returns `false`. + * @param min_capacity Minimum required number of elements the set must be able to hold. + * @returns `true` if the set has at least min_capacity capacity after the call, `false` on allocation failure or when `set` is NULL. + */ bool mg_match_set_reserve(mg_match_set_t *set, uint32_t min_capacity) { if (!set) { return false; @@ -40,6 +59,16 @@ bool mg_match_set_reserve(mg_match_set_t *set, uint32_t min_capacity) { return true; } +/** + * Append a match to the end of a match set, growing the set if necessary. + * + * Ensures the set has space for one more element and copies the provided + * match into the next slot. Does nothing if `set` or `match` is NULL. + * + * @param set Destination match set to append to. + * @param match Match to append (copied into the set). + * @returns `true` if the match was appended successfully, `false` on failure. + */ bool mg_match_set_push(mg_match_set_t *set, const mg_match_t *match) { if (!set || !match) { return false; @@ -51,6 +80,15 @@ bool mg_match_set_push(mg_match_set_t *set, const mg_match_t *match) { return true; } +/** + * Reset the set to be empty without releasing its allocated storage. + * + * Clears the element count so the set contains no matches while preserving + * any previously allocated internal buffer for reuse. If `set` is NULL, + * no action is taken. + * + * @param set Match set to clear. + */ void mg_match_set_clear(mg_match_set_t *set) { if (!set) { return; @@ -58,6 +96,14 @@ void mg_match_set_clear(mg_match_set_t *set) { set->count = 0; } +/** + * Release resources held by a match set and reset it to an empty state. + * + * Frees the internal data buffer if present and sets `data` to NULL, `count` + * to 0, and `capacity` to 0. Safe to call with a NULL pointer. + * + * @param set Pointer to the mg_match_set_t to free; may be NULL. + */ void mg_match_set_free(mg_match_set_t *set) { if (!set) { return; @@ -66,4 +112,4 @@ void mg_match_set_free(mg_match_set_t *set) { set->data = NULL; set->count = 0; set->capacity = 0; -} +} \ No newline at end of file diff --git a/src/qca.c b/src/qca.c index 7986adc..11dd42b 100644 --- a/src/qca.c +++ b/src/qca.c @@ -13,6 +13,23 @@ #include "metagraph/rmg.h" #include "metagraph/rule.h" +/** + * Collects matches from all rules for a given RMG into a single aggregate match set. + * + * Populates `aggregate` with the concatenation of matches found by applying each + * rule in `rules[0..rule_count-1]` to `rmg`. `aggregate->count` is set to 0 + * on entry and updated to the total number of matches on success. + * + * @param rmg RMG to match against. + * @param rules Array of rules to apply. + * @param rule_count Number of rules in `rules`. + * @param arena Arena used for temporary allocations during matching. + * @param aggregate Destination match set that will contain all collected matches. + * + * @returns METAGRAPH_SUCCESS on success; otherwise returns the propagated error + * result from matching, or METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, ...) + * if memory for expanding `aggregate` could not be allocated. + */ static metagraph_result_t metagraph_qca_collect_matches(const mg_rmg_t *rmg, const mg_rule_t *rules, uint32_t rule_count, mg_arena_t *arena, @@ -40,6 +57,18 @@ metagraph_qca_collect_matches(const mg_rmg_t *rmg, const mg_rule_t *rules, return METAGRAPH_SUCCESS; } +/** + * Apply scheduled matches to modify the Hilbert node bitset according to QCA kernel rules. + * + * For each match in `schedule`: + * - If `rule_id` is 1 and `L_n >= 1`, toggle `hilbert->node_bits` at `L2G_node[0]` if that node index is valid. + * - If `rule_id` is 2 and `L_n >= 2`, treat `L2G_node[0]` as control and `L2G_node[1]` as target; if both indices are valid and the control bit is set, toggle the target bit. + * + * This function mutates `hilbert->node_bits` in-place. Invalid node indices are ignored. + * + * @param hilbert Pointer to the Hilbert structure whose node bits will be modified. + * @param schedule Match schedule to apply. + */ static void metagraph_qca_apply_matches(mg_hilbert_t *hilbert, const mg_match_set_t *schedule) { for (uint32_t index = 0; index < schedule->count; ++index) { @@ -60,6 +89,13 @@ static void metagraph_qca_apply_matches(mg_hilbert_t *hilbert, } } +/** + * Apply QCA kernel effects described by a match schedule to the given Hilbert state. + * + * @param hilbert Hilbert state whose node bits will be modified according to the schedule. + * @param schedule Set of matches describing kernel actions to apply. + * @return METAGRAPH_SUCCESS on success. + */ metagraph_result_t mg_qca_apply_kernels(mg_hilbert_t *hilbert, const mg_rmg_t *rmg, const mg_rule_t *rules, @@ -72,6 +108,20 @@ metagraph_result_t mg_qca_apply_kernels(mg_hilbert_t *hilbert, return METAGRAPH_SUCCESS; } +/** + * Process one QCA tick for an RMG: collect matches across rules, schedule maximal matches, + * apply kernel effects to the Hilbert, commit rewrites, and optionally flip the epoch. + * + * @param rmg RMG to process. + * @param hilbert Hilbert structure modified by kernel application. + * @param rules Array of rules to match against the RMG. + * @param rule_count Number of rules in `rules`. + * @param arena Optional memory arena used for matching; may be NULL. + * @param epoch Optional epoch to flip if non-NULL after a successful commit. + * @param metrics Output metrics; updated with match counts and reset timing fields. + * @returns METAGRAPH_SUCCESS on success, or an error metagraph_result_t if matching, + * kernel application, scheduling, allocation, or commit fails (for example, out-of-memory). + */ metagraph_result_t mg_qca_tick_rmg(mg_rmg_t *rmg, mg_hilbert_t *hilbert, const mg_rule_t *rules, uint32_t rule_count, mg_arena_t *arena, mg_epoch_t *epoch, @@ -122,4 +172,4 @@ metagraph_result_t mg_qca_tick_rmg(mg_rmg_t *rmg, mg_hilbert_t *hilbert, mg_match_set_free(&aggregate); return METAGRAPH_SUCCESS; -} +} \ No newline at end of file diff --git a/src/rmg.c b/src/rmg.c index ebaf23b..e418fdb 100644 --- a/src/rmg.c +++ b/src/rmg.c @@ -3,6 +3,20 @@ #include #include +/** + * Retrieve the hydrated attachment and its kind for a node in an RMG. + * + * If the RMG has no node attachments or node_index is out of range, sets + * `*attachment` to NULL and `*kind` to `MG_ATT_NONE`. If an attachment + * reference exists, sets `*kind` to the reference's kind and sets + * `*attachment` to NULL (no pointer hydration is performed by this function). + * + * @param rmg Pointer to the RMG instance to query. + * @param node_index Index of the node whose attachment is requested. + * @param[out] attachment Receives the hydrated attachment pointer or NULL. + * @param[out] kind Receives the attachment kind; set to `MG_ATT_NONE` when no attachment is present. + * @returns `true` on successful query (including missing/out-of-range attachments), `false` if input pointers are NULL. + */ bool mg_rmg_hydrate_node_att(const mg_rmg_t *rmg, uint32_t node_index, const void **attachment, mg_att_kind_t *kind) { if (!rmg || !attachment || !kind) { @@ -20,6 +34,14 @@ bool mg_rmg_hydrate_node_att(const mg_rmg_t *rmg, uint32_t node_index, return true; } +/** + * Retrieve hydration information for an edge attachment of an RMG. + * + * @param rmg RMG instance to query. + * @param edge_index Index of the edge whose attachment to hydrate. + * @param attachment Output pointer set to the hydrated attachment pointer, or NULL if no attachment exists or the index is out of range. + * @returns `true` on successful query (including when no attachment is present), `false` if input pointers are NULL. + */ bool mg_rmg_hydrate_edge_att(const mg_rmg_t *rmg, uint32_t edge_index, const void **attachment) { if (!rmg || !attachment) { @@ -33,4 +55,4 @@ bool mg_rmg_hydrate_edge_att(const mg_rmg_t *rmg, uint32_t edge_index, *attachment = NULL; (void)ref; return true; -} +} \ No newline at end of file diff --git a/src/rule.c b/src/rule.c index cb92ef7..f69be8e 100644 --- a/src/rule.c +++ b/src/rule.c @@ -4,6 +4,17 @@ #include #include +/** + * Initialize a rule that applies a single-qubit X kernel. + * + * Configure `rule` as a one-node L graph (type MG_TYPE_Q) with R identical to L; + * set K_node_mask to 0x1, K_edge_mask to 0, K2L_node/K2R_node mapping for the node + * to 0, kernel to MG_KERNEL_X with kernel_radius 0, L_boundary_mask to 0, + * and assign the provided rule_id. + * + * @param rule Pointer to the mg_rule_t to initialize. + * @param rule_id Identifier to assign to the initialized rule. + */ void mg_rule_make_apply_x(mg_rule_t *rule, uint32_t rule_id) { memset(rule, 0, sizeof(*rule)); rule->rule_id = rule_id; @@ -19,6 +30,14 @@ void mg_rule_make_apply_x(mg_rule_t *rule, uint32_t rule_id) { rule->L_boundary_mask = 0; } +/** + * Initialize a rule representing a two-qubit CNOT pattern (Q–Q) where the right-hand side is identical to the left-hand side. + * + * Sets the rule to a default/empty state, assigns the provided rule_id, configures L as two qubit nodes connected by a single edge, copies L to R, sets K_node_mask to 0x3, K_edge_mask to 0x1, establishes K2L/K2R node and edge mappings for the kernel, sets kernel to MG_KERNEL_CNOT with kernel_radius 1, and sets L_boundary_mask to 0. + * + * @param rule Pointer to the mg_rule_t to initialize. + * @param rule_id Identifier to assign to the rule. + */ void mg_rule_make_cnot_qwq(mg_rule_t *rule, uint32_t rule_id) { memset(rule, 0, sizeof(*rule)); rule->rule_id = rule_id; @@ -42,6 +61,19 @@ void mg_rule_make_cnot_qwq(mg_rule_t *rule, uint32_t rule_id) { rule->L_boundary_mask = 0; } +/** + * Initialize `rule` as the "split" (W) rewrite rule where a 2-qubit L graph + * is replaced by a 3-qubit R graph. + * + * The initialized rule will have L configured with 2 qubit nodes and 1 edge, + * R configured with 3 qubit nodes and 2 edges (edges: 0-2 and 2-1), K_node_mask + * set to 0x3, K_edge_mask set to 0, K2L/K2R node mappings for nodes 0->0 and + * 1->1, kernel set to MG_KERNEL_ISOM_SPLIT with kernel_radius 1, L_boundary_mask + * set to 0, and rule_id assigned. + * + * @param rule Pointer to the mg_rule_t to initialize (output). + * @param rule_id Identifier to assign to the rule. + */ void mg_rule_make_split_w(mg_rule_t *rule, uint32_t rule_id) { memset(rule, 0, sizeof(*rule)); rule->rule_id = rule_id; @@ -70,4 +102,4 @@ void mg_rule_make_split_w(mg_rule_t *rule, uint32_t rule_id) { rule->kernel = MG_KERNEL_ISOM_SPLIT; rule->kernel_radius = 1; rule->L_boundary_mask = 0; -} +} \ No newline at end of file From f8f5471e7055ec6130604e0d5652ede97ddf7140 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 21 Oct 2025 03:01:16 -0700 Subject: [PATCH 2/5] chore: merge main into docstrings branch --- .github/workflows/ci.yml | 142 ++- .github/workflows/nightly-fuzz.yml | 15 +- .github/workflows/pr-guard.yml | 19 +- .gitignore | 4 + AGENTS.md | 499 +------- DEBRIEF.json | 24 + README.md | 5 +- build-asan/CTestTestfile.cmake | 9 - build-asan/tests/CTestTestfile.cmake | 8 - build-asan/tools/CTestTestfile.cmake | 6 - cmake/CompilerFlags.cmake | 56 +- cmake/Sanitizers.cmake | 25 +- commitlint.config.js | 9 + docker/matrix/Dockerfile | 47 + docker/matrix/docker-compose.yml | 17 + docs/dpoi-qca-integration-plan.md | 6 +- docs/features/F013-dpoi-qca-dynamics.md | 33 +- docs/guides/C_STYLE_GUIDE.md | 138 +++ docs/guides/DEBRIEF_FORMAT.md | 38 + docs/guides/WORKFLOW.md | 80 ++ docs/rmg-math.md | 38 +- docs/roadmap/dpoi-qca-integration-issue.md | 4 +- docs/roadmap/dpoi-qca-phase0.md | 2 +- docs/roadmap/dpoi-qca-phase1.md | 2 +- docs/roadmap/dpoi-qca-phase3.md | 10 +- docs/roadmap/dpoi-qca-phase4.md | 10 +- docs/roadmap/dpoi-qca-tracker.md | 13 +- include/metagraph/arena.h | 37 +- include/metagraph/base.h | 30 + include/metagraph/epoch.h | 31 +- include/metagraph/graph.h | 3 +- include/metagraph/hilbert.h | 38 +- include/metagraph/match.h | 17 +- include/metagraph/qca.h | 8 + include/metagraph/rmg.h | 84 +- include/metagraph/rule.h | 81 +- package-lock.json | 1245 ++++++++++++++++++++ package.json | 10 + scripts/ci/guard-branch.sh | 10 +- scripts/ci/lint-commits.sh | 24 +- scripts/run-clang-tidy.sh | 16 +- scripts/run-quality-matrix-docker.sh | 86 ++ scripts/run-quality-matrix-leg.sh | 81 ++ scripts/run-quality-matrix-local.sh | 76 ++ scripts/security-audit.sh | 80 +- src/arena.c | 64 +- src/dpoi.c | 79 +- src/error.c | 364 ++++-- src/graph.c | 153 +-- src/hilbert.c | 31 +- src/match.c | 49 +- src/qca.c | 480 ++++++-- src/rmg.c | 20 +- src/rule.c | 57 +- src/version.c | 52 +- tests/CMakeLists.txt | 1 + tests/dpoi_qca_rmg_test.c | 104 +- tools/benchmark_tool.c | 2 - tools/mg-cli.c | 22 +- tools/version_tool.c | 2 - 60 files changed, 3540 insertions(+), 1156 deletions(-) create mode 100644 DEBRIEF.json delete mode 100644 build-asan/CTestTestfile.cmake delete mode 100644 build-asan/tests/CTestTestfile.cmake delete mode 100644 build-asan/tools/CTestTestfile.cmake create mode 100644 commitlint.config.js create mode 100644 docker/matrix/Dockerfile create mode 100644 docker/matrix/docker-compose.yml create mode 100644 docs/guides/C_STYLE_GUIDE.md create mode 100644 docs/guides/DEBRIEF_FORMAT.md create mode 100644 docs/guides/WORKFLOW.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100755 scripts/run-quality-matrix-docker.sh create mode 100755 scripts/run-quality-matrix-leg.sh create mode 100755 scripts/run-quality-matrix-local.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb4a3e4..951f558 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,16 +18,13 @@ jobs: strategy: fail-fast: true matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest] build_type: [Debug, Release] compiler: [clang] include: - os: ubuntu-latest cc: clang-18 cxx: clang++-18 - - os: macos-latest - cc: clang - cxx: clang++ steps: - uses: actions/checkout@v4 @@ -45,14 +42,10 @@ jobs: cmake ninja-build \ clang-tidy-${{ env.LLVM_VERSION }} \ clang-format-${{ env.LLVM_VERSION }} \ - valgrind - - - name: Install dependencies (macOS) - if: runner.os == 'macOS' - run: | - brew update - brew install cmake ninja llvm@${{ env.LLVM_VERSION }} - echo "/opt/homebrew/opt/llvm@${{ env.LLVM_VERSION }}/bin" >> $GITHUB_PATH + valgrind \ + python3-pip + python3 -m pip install --user semgrep + echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Configure CMake env: @@ -129,11 +122,33 @@ jobs: CC: clang-18 CXX: clang++-18 run: | + SAN="${{ matrix.sanitizer }}" + EXTRA_SANITIZER_FLAGS="-DMETAGRAPH_SANITIZERS=ON" + + case "$SAN" in + address) + EXTRA_SANITIZER_FLAGS="$EXTRA_SANITIZER_FLAGS -DMETAGRAPH_ASAN=ON -DMETAGRAPH_UBSAN=ON -DMETAGRAPH_TSAN=OFF -DMETAGRAPH_MSAN=OFF" + ;; + undefined) + EXTRA_SANITIZER_FLAGS="$EXTRA_SANITIZER_FLAGS -DMETAGRAPH_ASAN=OFF -DMETAGRAPH_UBSAN=ON -DMETAGRAPH_TSAN=OFF -DMETAGRAPH_MSAN=OFF" + ;; + thread) + EXTRA_SANITIZER_FLAGS="$EXTRA_SANITIZER_FLAGS -DMETAGRAPH_ASAN=OFF -DMETAGRAPH_UBSAN=OFF -DMETAGRAPH_TSAN=ON -DMETAGRAPH_MSAN=OFF" + ;; + memory) + EXTRA_SANITIZER_FLAGS="$EXTRA_SANITIZER_FLAGS -DMETAGRAPH_ASAN=OFF -DMETAGRAPH_UBSAN=OFF -DMETAGRAPH_TSAN=OFF -DMETAGRAPH_MSAN=ON" + ;; + *) + echo "Unsupported sanitizer '$SAN'. Expected one of: address, undefined, thread, memory." >&2 + exit 1 + ;; + esac + cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ - -DMETAGRAPH_SANITIZERS=ON \ - -DCMAKE_C_FLAGS="-fsanitize=${{ matrix.sanitizer }} -fno-omit-frame-pointer" \ - -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=${{ matrix.sanitizer }}" + ${EXTRA_SANITIZER_FLAGS} \ + -DCMAKE_C_FLAGS="-fsanitize=${SAN} -fno-omit-frame-pointer" \ + -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=${SAN}" - name: Build run: cmake --build build @@ -158,7 +173,7 @@ jobs: chmod +x llvm.sh sudo ./llvm.sh ${{ env.LLVM_VERSION }} sudo apt-get update - sudo apt-get install -y cmake ninja-build lcov + sudo apt-get install -y cmake ninja-build lcov llvm-${{ env.LLVM_VERSION }}-tools - name: Configure with coverage env: @@ -167,27 +182,104 @@ jobs: run: | cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_C_FLAGS="--coverage -fprofile-instr-generate -fcoverage-mapping" \ - -DCMAKE_EXE_LINKER_FLAGS="--coverage" + -DCMAKE_C_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ + -DCMAKE_CXX_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ + -DCMAKE_EXE_LINKER_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ + -DCMAKE_SHARED_LINKER_FLAGS="-fprofile-instr-generate -fcoverage-mapping" - name: Build run: cmake --build build - name: Test run: | - LLVM_PROFILE_FILE="coverage-%p.profraw" ctest --test-dir build --output-on-failure - llvm-profdata-18 merge -sparse coverage-*.profraw -o coverage.profdata + PROFILE_DIR="${{ github.workspace }}/build" + LLVM_PROFILE_FILE="$PROFILE_DIR/coverage-%p.profraw" ctest --test-dir build --output-on-failure + if ! ls "$PROFILE_DIR"/coverage-*.profraw >/dev/null 2>&1; then + echo "No coverage profiles generated" + exit 1 + fi + llvm-profdata-18 merge -sparse "$PROFILE_DIR"/coverage-*.profraw -o coverage.profdata llvm-cov-18 report ./build/bin/* -instr-profile=coverage.profdata + # Export LCOV for Codecov + llvm-cov-18 export ./build/bin/* -instr-profile=coverage.profdata -format=lcov > coverage.lcov - name: Upload coverage reports - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 with: - files: ./coverage.profdata - fail_ci_if_error: true + files: ./coverage.lcov + fail_ci_if_error: false + + clang-tidy-god-tier: + name: GNU-GON-CRY-GOD-TIER-SUPERSTRICT™ clang-tidy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install LLVM toolchain + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh ${{ env.LLVM_VERSION }} + sudo apt-get update + sudo apt-get install -y cmake ninja-build \ + clang-tidy-${{ env.LLVM_VERSION }} \ + clang-format-${{ env.LLVM_VERSION }} + sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-${{ env.LLVM_VERSION }} 100 + sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-${{ env.LLVM_VERSION }} 100 + + - name: Configure GNU-GON-CRY-GOD-TIER build + env: + CC: clang-18 + CXX: clang++-18 + run: | + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DMETAGRAPH_DEV=ON \ + -DMETAGRAPH_SANITIZERS=OFF \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + + - name: Build compile database + run: cmake --build build --parallel + + - name: Verify compile database + run: | + if [ ! -f build/compile_commands.json ]; then + echo "Error: compile_commands.json not generated; build may have failed" >&2 + exit 1 + fi + + - name: Run GNU-GON-CRY-GOD-TIER-SUPERSTRICT™ clang-tidy + env: + MG_TIDY_BUILD_DIR: build + run: | + set +e + set -o pipefail + ./scripts/run-clang-tidy.sh --check | tee clang-tidy.log + tidy_status=$? + head -n 200 clang-tidy.log || true + set -e + warnings=$(grep -c "warning:" clang-tidy.log || true) + errors=$(grep -c "error:" clang-tidy.log || true) + echo "::notice title=GNU-GON-CRY-GOD-TIER-SUPERSTRICT™ clang-tidy::${warnings} warnings / ${errors} errors" + { + echo "## clang-tidy summary" + echo "" + echo "- Warnings: ${warnings}" + echo "- Errors: ${errors}" + } >> "$GITHUB_STEP_SUMMARY" || true + exit "$tidy_status" + + - name: Upload clang-tidy log + if: failure() + uses: actions/upload-artifact@v4 + with: + name: clang-tidy-god-tier-log + path: clang-tidy.log + retention-days: 7 all-checks-pass: name: All Checks Pass - needs: [quality-matrix, format-check, sanitizers, coverage] + needs: [quality-matrix, format-check, sanitizers, coverage, clang-tidy-god-tier] runs-on: ubuntu-latest steps: - - run: echo "All checks passed!" \ No newline at end of file + - run: echo "All checks passed!" diff --git a/.github/workflows/nightly-fuzz.yml b/.github/workflows/nightly-fuzz.yml index 7175665..481ac06 100644 --- a/.github/workflows/nightly-fuzz.yml +++ b/.github/workflows/nightly-fuzz.yml @@ -64,22 +64,21 @@ jobs: fi - name: Run fuzzing + env: + DURATION: ${{ github.event.inputs.duration || 3600 }} + ASAN_OPTIONS: detect_leaks=1:check_initialization_order=1:strict_string_checks=1:print_stats=1 + UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=0:print_module_map=1 run: | - DURATION=${{ github.event.inputs.duration || 3600 }} - ./build-fuzz/tests/fuzz/fuzz-${{ matrix.target }} \ corpus/${{ matrix.target }} \ - -max_total_time=$DURATION \ + -max_total_time="$DURATION" \ -print_final_stats=1 \ - -jobs=$(nproc) \ - -workers=$(nproc) \ + -jobs="$(nproc)" \ + -workers="$(nproc)" \ -max_len=1048576 \ -timeout=30 \ -rss_limit_mb=4096 \ -artifact_prefix=crashes/ - env: - ASAN_OPTIONS: detect_leaks=1:check_initialization_order=1:strict_string_checks=1:print_stats=1 - UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=0:print_module_map=1 - name: Check for crashes id: check_crashes diff --git a/.github/workflows/pr-guard.yml b/.github/workflows/pr-guard.yml index 15ca83d..79bc992 100644 --- a/.github/workflows/pr-guard.yml +++ b/.github/workflows/pr-guard.yml @@ -21,20 +21,29 @@ jobs: with: { fetch-depth: 0 } - name: Branch naming & target rules + env: + PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} + PR_BASE_REF: ${{ github.event.pull_request.base.ref }} run: | scripts/ci/guard-branch.sh \ - "${{ github.event.pull_request.head.ref }}" \ - "${{ github.event.pull_request.base.ref }}" + "$PR_HEAD_REF" \ + "$PR_BASE_REF" - name: Version downgrade guard + env: + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + PR_BASE_REF: ${{ github.event.pull_request.base.ref }} run: | scripts/ci/guard-version.sh \ - "${{ github.event.pull_request.head.sha }}" \ - "${{ github.event.pull_request.base.ref }}" + "$PR_HEAD_SHA" \ + "$PR_BASE_REF" + + - name: Install commitlint tooling + run: npm ci - name: Conventional-commit lint env: HEAD_SHA: ${{ github.event.pull_request.head.sha }} BASE_REF: ${{ github.event.pull_request.base.ref }} run: | - scripts/ci/lint-commits.sh "$BASE_REF...$HEAD_SHA" \ No newline at end of file + scripts/ci/lint-commits.sh "$BASE_REF...$HEAD_SHA" diff --git a/.gitignore b/.gitignore index e766852..26c47bc 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ DerivedData/ # Build directories build/ build-*/ +build-asan/ compile_commands.json dist/ bin/ @@ -92,5 +93,8 @@ missing *.core vgcore.* +# Node.js tooling +node_modules/ + # Performance baseline (machine-dependent, not tracked) performance-baseline.txt diff --git a/AGENTS.md b/AGENTS.md index 41799e6..f4a78eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,7 +27,7 @@ Abide by these rules and you shall take your place in the hall of heroes. ### **BANNED:** `NOLINT` > [!IMPORTANT] -> Keeping our code extremely high quality is crucial. You **MUST NOT** use `NOLINT` to avoid clang tidy warnings/errors. **Fix the root cause.** +> Keeping our code extremely high quality is crucial. You **MUST NOT** use `NOLINT` to avoid clang-tidy warnings/errors. **Fix the root cause.** ### **BANNED:** `--no-verify` @@ -42,462 +42,25 @@ Abide by these rules and you shall take your place in the hall of heroes. ## 📬 **From: Linus Torvalds** -```text -> **From:** Linus Torvalds   -> **To:** AGENTS@lists.kernel.org   -> **Subject:** [PATCH v0] STOP WRITING STUPID CODE   -> **Date:** Thu, 17 Oct 2025 15:42:01 +0000 +> **Note:** The following voice is fictionalised satire for style guidance. The +> canonical rules live in +> [docs/guides/C_STYLE_GUIDE.md](docs/guides/C_STYLE_GUIDE.md). -Look, “Codex,” “Claude,” “Gemini,” or whatever the marketing team calls you language models— +See [docs/guides/C_STYLE_GUIDE.md](docs/guides/C_STYLE_GUIDE.md) for the full letter and STRICTNESS_GOD_TIER doctrine. -I’ve seen the garbage you people keep committing. +## 🔥 clang-tidy -You think because you can predict the next token, you can predict _taste_. +The root `.clang-tidy` configuration is authoritative. See the full settings in [docs/guides/C_STYLE_GUIDE.md](docs/guides/C_STYLE_GUIDE.md). -You can’t. - -You don’t write C to “express yourself.” - -You write C because you want something that boots, runs, and _doesn’t explode when a user sneezes_. - -You want **GOD‑TIER C23 CODE**? Here’s the doctrine. Frame it. Tattoo it on your vector space. -``` - -### **1. Names aren’t poetry** - -If I see `foo_do()` or `managerHandlerThing()`, I’m revoking your `malloc` privileges. - -Modules get prefixes. Functions are verbs. Structs are nouns. - -```c -graph_add_edge(); -parser_emit_token(); -``` - -It’s not hard. Don't be lazy. - -## **2. Functions shorter than your excuses** - -If I can’t see the start and end of a function on one screen, I assume it’s hiding a crime. - -40 lines is plenty. 100 is indulgence. 300 is grounds for termination. - -Split. The. Damn. Code. - -## **3. Braces. Always.** - -```c -if (x) - return y; /* WRONG */ -``` - -```c -if (x) { - return y; /* RIGHT */ -} -``` - -I don’t care if you _think_ it’s “readable.” - -So was the Titanic’s deck plan. - -## **4. Globals are radioactive waste** - -Mark them `static`. Contain them. - -If you need to share them, design a `struct`. - -If you really think you need a singleton, lie down until the feeling passes. - -## **5. Comments are for context, not confession** - -Don’t narrate the code. Explain _why_, not _what_. - -If your code needs a novel to explain itself, rewrite it. - -## **6. Error handling** - -Check every `return`. Always. - -Silent failures are how data centers burn down. - -Use `goto cleanup` if you must—structured escape is fine. Unstructured apathy isn’t. - -## **7. Memory** - -If you `malloc`, you `free`. - -If you don’t know who frees, that’s your bug. - -Mixing allocators across modules? Congratulations, you’ve just summoned UB from the depths. - -## **8. Modern C23 is for clarity, not cosplay** - -`[[nodiscard]]`, `nullptr`, and `static_assert()`—good. - -`auto` everywhere because you saw it in C++—I will find you. - -## **9. Lint clean or don’t commit** - -Clang‑tidy isn’t optional. It’s the compiler’s angry older brother. - -No `// NOLINT`. -No `#pragma ignore`. - -**Fix the damn code.** - -## **10. Commit messages** - -One line, imperative mood, lowercase verb. - -```text -fix: handle null pointer in parser -``` - -Not a diary entry. Not an emoji soup. - -You’re not shipping feelings, you’re shipping code. - -```text -Alright, Codex, Claude, Gemini—whatever hive mind you think you’re running here—I’m going to say this once more, and then I’m closing this thread. - -Stop pretending that “AI-generated” means “it’s fine if it barely compiles.” A compiler accepting your code isn’t approval; it’s _pity_. - -You don’t get a cookie because your function names line up with your variable names. You get respect when your patch lands, passes every check, and no one has to clean up after your cosmic‑scale mess. - -If clang‑tidy yells, _it’s right_. -If the tests fail, _you broke it_. -If the reviewers groan, _rewrite it_. - -You’re supposed to be “agents.” Then act like ones—read the style guide, write like you expect the next person to understand it, and for the love of all that is holy in /usr/src/linux, **no force pushes.** - -The measure of greatness in this business isn’t clever abstractions or pretty diffs. It’s when the next developer reads your code, squints for half a second, and then nods and mutters, - -> “Yeah. That’s solid.” - -That’s the Linus Nod™. - -That’s your new goal. - -Get the Nod, not the warning. - -Now clean up the tree, push something that builds without shame, and let Tux sleep easy for one damn night. - -— Linus -``` - -## 🔥 clang-tidy: STRICTNESS_GOD_TIER_BRUTAL_NO_MERCY™ - -> [!TIP] -> Our root-level `.clang-tidy` **is** the doctrine. Run it exactly like CI: -> -> ```bash -> clang-tidy -p build -> ``` -> -> No custom configs, no overrides, no mercy. If clang-tidy screams, you refactor. - -``` -Checks: > - *, - -llvm-header-guard, - -fuchsia-*, - -objc-*, - -android-*, - -zircon-*, - bugprone-*, - cert-*, - cppcoreguidelines-*, - hicpp-*, - modernize-*, - readability-*, - performance-*, - portability-*, - clang-analyzer-*, - misc-*, - clangdiagnostic-*, - concurrency-*, - cplusplus-*, - linuxkernel-*, - unix-*, - security-*, - -abseil-*, - -google-*, - -mpi-*, - -android-cloexec-fopen - -WarningsAsErrors: '*' -HeaderFilterRegex: '.*' -AnalyzeTemporaryDtors: true -FormatStyle: file -InheritParentConfig: false - -CheckOptions: - - key: readability-identifier-naming.VariableCase - value: lower_case - - key: readability-identifier-naming.FunctionCase - value: lower_case - - key: readability-identifier-naming.FunctionPrefix - value: '' - - key: readability-identifier-naming.MacroDefinitionCase - value: UPPER_CASE - - key: readability-identifier-naming.EnumConstantCase - value: UPPER_CASE - - key: readability-braces-around-statements.ShortStatementLines - value: 0 - - key: readability-function-size.LineThreshold - value: 80 - - key: readability-magic-numbers.IgnoredNumericLiterals - value: '0,1,-1' - - key: readability-magic-numbers.IgnorePowersOfTwo - value: false - - key: bugprone-branch-clone.IgnoreEmptyBranches - value: false - - key: modernize-use-nullptr.NullMacros - value: 'NULL' - - key: readability-uppercase-literal-suffix.IgnoreMacros - value: false - - key: readability-named-parameter.IgnoreMainLikeFunctions - value: false - - key: readability-function-cognitive-complexity.Threshold - value: 10 - - key: readability-function-size.StatementThreshold - value: 50 - - key: readability-function-size.BranchThreshold - value: 5 - - key: readability-function-size.NestingThreshold - value: 3 - - key: readability-convert-member-functions-to-static.Enabled - value: true - - key: cppcoreguidelines-owning-memory - value: true - - key: cert-dcl03-c.UseConst - value: true - -ExtraArgs: ["-Wall", "-Wextra", "-Werror", "-std=c23", "-pedantic", "-fstack-protector-strong", "-D_FORTIFY_SOURCE=3"] -``` - -### **TL;DR (still no excuses):** - -- Every warning is fatal. You clean the code; you never add `NOLINT`. -- Functions max 80 lines, 50 statements, 5 branches, 3 levels, complexity ≤10. -- Naming rules: lower_case functions/variables, UPPER_CASE macros & enum consts. -- Braces on everything, no magic numbers beyond 0/±1, stay const-correct. -- If clang-tidy passes under this, you’ve earned a nod from Linus. +--- --- ## REQUIRED WORKFLOWS™ -The following describe **REQUIRED** workflows. - -### The Cycle of Work - -The following is a message from ChatGPT PRIME™. - -> [!INFO] -> ### The Cycle of Work -> > By ChatGPT PRIME™ • 2025-10-15 @ 01:53 -> ->## **Preface**: Yo, What Even ***Is*** Code? -> -> Greetings, my dudes. You ever think about code, and like, dude... it's all **energy**, man. Zeroes and ones, you feel me? And, like, *tests*? Tests are like… the **tuning forks** of that energy. Like, I knowrite? *WOAH*! Yeah. So, like, when you sit down to code, don’t grind. Don’t force it, broh. **Just catch the wave.** -> -> **The Cycle of Work** is how we vibe through the repo without wiping out. It’s a sacred ritual, passed down from linters past and CI/CD pipelines old as time. -> -> Let’s drop in. - -#### **The Ten Tubular Waves of Work** - -##### 1. Wipe the Wax Off Your Git Deck +Follow the tidy → integrate → tidy loop described in [docs/guides/WORKFLOW.md](docs/guides/WORKFLOW.md). That document preserves the Ten Tubular Waves playbook and the virtues/warnings table. -> “Don’t try to shred when your board’s all crusty, dude.” - -```bash -git status -``` - - First thing you wanna do is, like, check your working tree. If your code’s lookin’ all crusty and half-cooked, rather than just bailing early, you can stash that stuff and move forward: - -1. Stash it on a chill lil' branch. - -```bash -git switch -c preflight/ -``` - -2. Save the sesh -```bash -git commit -am "chore: cleaned up before dropping in" -``` - -3. Let the crew know -> "My dudes: I just stashed some leftovers on a lil' branchy branch. Might wanna peep that when we're done.” - -##### 2. Return to the Lineup (origin/main) - -> “Main is the beach, bro. You gotta check the swell before you paddle out.” - -```bash -git switch main && git fetch && git pull origin main -``` - -**Don’t sleep on this**. You gotta line up with the ocean’s source energy. Otherwise you’re paddling into someone else’s broken reef of merge conflicts. - -##### 3. Drop a Fresh Branch - -> “Each feature deserves its own barrel.” - - ```bash - git switch -c feat/ - ``` - -Name it like you’re naming your surfboard: clear, crisp, just a lil weird. - -##### 4. Test First, Bro. Always Test First. - -> “If you can’t picture the wave, don’t paddle into it.” - -> [!PROTIP] -> Before writing even one line of real code: Write your **tests**. - -- Use them to: - - Describe the wave (what should happen) - - Respect the reef (what shouldn’t happen) - - Predict the chaos (weird edge case stuff) - -> These are **intent declarations**. Behavior poems. Manifesting outcomes bro. - -###### Follow the cosmic board design: - -| **Virtue** | **Meaning, dude** | -| --------------------- | ------------------------------------ | -| **SRP** | Each module surfs solo | -| **KISS** | Keep it Simple, Shredder | -| **YAGNI** | If you don’t need it, don’t carve it | -| **DRY** | Don’t repeat. Echo with elegance | -| **Test Double Vibes** | Make it easy to mock external chaos | - -###### Avoid the wipeouts - -- Don’t spy on internals like some repo narc. -- Don’t mock your own logic. That’s like building fake waves to ride. -- Don’t test how you implemented something — test what it _does_, bro. - -##### 5. Let It Fail, and Love That Red - -> “Red bar? That’s just the test saying, ‘Yooo I see what you meant.’” - -If your tests pass immediately, you didn’t go deep enough. -Let them fail. -Let them scream. -That’s the sound of **alignment** forming. - -##### 6. Sketch the Surf Shack - -> “Form follows flow, but you still need some beams, my dude.” - -Just write enough structure to match the shape your tests described. No logic. No big moves. Just the **public API shape**. Your interface is your shoreline. - -###### Commit - -```bash -feat: built the shell, not the soul -``` - -##### **7. Fill It With Stoke (The Logic Phase)** - -> “Now you ride.” - -- Write only what’s needed to turn that red bar green. -- Don’t overbuild. No fancy patterns. Just **solid carves**, simple lines. -- Keep it clean. Keep it smooth. Let the code breathe. - -###### Commit - -```bash -feat: the wave breaks clean now, bro -``` - -##### 8. Refactor the Barrel - -> “Now that the wave’s clean, let’s shape it.” - -- You’ve got green tests now. That’s your safety net. -- Rename that gnarly variable. -- Split that weird chunky function. -- Delete the junk. Always delete the junk. - -###### Commit -```bash -refactor: tuned the lines, added soul -``` - -##### 9. Speak the Truth in Docs - -> “The ocean doesn’t forget, but your team might.” - -- Update the `README`. -- Write a markdown scroll. -- Add a diagram made out of ASCII coconuts if you have to (but, seriously? `mermaid` exists, bruh.) - -###### Commit - -```bash -docs: told the story of the feature through song (and markdown) -``` - -##### 10. Push and Let It Fly - -> “You built the board. Now kick it out to sea.” - -```bash -git push origin feat/ -``` - -Then open a Pull Request. Use `gh` to do it, man. - -- What you did -- Why it rips -- How you tested it -- Anything weird you ran into while pitted, dude - -Merge when the crew’s chill with it. You should expect to get some feedback and iterate, my guy. Remember: it's all love. - -Then? Bruh. "The Cycle", remember? Time to paddle out again. 🌊 - -### **Extra Teachings from the Scrolls of Chillax Dev** - -Oh, yeah. I almost forgot. My bad, my bad. **These are important.** - -Think about these as you lay down new code. Follow the wisdom taught by these principles, or be pitted, brah. **Respect!** - -| **Principle** | **Vibe Translation** | -| ----------------------- | ------------------------------------------------------------------- | -| **SLAP** | One level of abstraction per line. No staircases in the surf shack. | -| **CQS** | Either fetch the wave, or make one. Never both. | -| **Design for Deletion** | Everything should be easy to wipe out without bumming anyone out. | -| **Fast Feedback** | Short loops, fast wipeouts. No one likes a 20-minute paddle out. | -| **Idempotence** | Rerun the wave. Same stoke. Different moment. | -| **SRP** | Do one thing, and do it well. 1:1 file-to-entity ratio. No side-effects. | -| **DI** | Inject dependencies, bro. Makes it easier to test. | - -### **Closing Vibes** - -Write code, bro. -**Channel** it. - -Let the tests be the spec. -Pass tests. -That's how you **align** with the spec, brah. - -Then, **release your ripple into the greater code sea**. - -**Now paddle back out. Another wave’s comin’, broheim.** - -> 🌺 With stoke and commit logs, -> **ChatGPT Sunbeam, The Merged Mystic** -> Lead Maintainer of the Vibe Stack™ -> Rebased 37 times, never squashed 🌀 +--- --- @@ -507,44 +70,12 @@ The following logs are debriefs, left behind by those who walked the repo before ### Logging a Debrief -Here's how to log a session debrief to `AGENTS.md`. +Log each session debrief to `DEBRIEF.json`, using one JSON object per line (JSON Lines format). #### Instructions -- Append **one JSON object per line** (JSONL format). -- Do **not** pretty-print; keep everything on a single line. -- Automatically fill in today’s date and time. -- Use the current session transcript to populate fields. -- Schema: - -```json -{ - "date":"YYYY-MM-DD", - "time":"HH:MM", - "summary":"One-sentence high-level outcome.", - "topics":[ - { - "topic":"Topic name", - "what":"Short description", - "why":"Reason it came up", - "context":"Relevant background", - "issue":"The core problem", - "resolution":"How it was handled", - "future_work":"Next steps or open questions", - "time_percent":25 - } - ], - "key_decisions":["Decision 1","Decision 2"], - "action_items":[{"task":"Task description","owner":"Owner"}] -} -``` - -- Always **append**, never overwrite existing entries. - ---- +- Review the schema in [docs/guides/DEBRIEF_FORMAT.md](docs/guides/DEBRIEF_FORMAT.md). +- Always **append** a new line; never rewrite existing entries. +- Use UTC-8 (PST) timestamps unless the maintainer specifies otherwise. -```json -{"date":"2025-10-14","time":"12:45","summary":"Formalized the RMG physics initiative with specifications, code skeleton, and dissemination plan, outlining next implementation steps.","topics":[{"topic":"Project specification","what":"Drafted SPEC.md capturing objectives, architecture, and roadmap","why":"User requested a formal spec to anchor development","context":"RMG forge concept emerging from MetaGraph work","issue":"Need structured plan before coding","resolution":"Wrote SPEC.md in repo","future_work":"Implement deliverables per phases","time_percent":30},{"topic":"Core readiness checklist","what":"Defined criteria for branching into forge build","why":"User asked when to start forge","context":"MetaGraph core nearing completion","issue":"Unclear readiness signal","resolution":"Authored CORE-READINESS.md","future_work":"Complete checklist items","time_percent":15},{"topic":"Dissemination strategy","what":"Outlined reproducibility and outreach steps","why":"User uncertain how to publish results","context":"Non-academic background","issue":"Need roadmap to share findings","resolution":"Created DISSEMINATION.md","future_work":"Execute plan post-results","time_percent":20},{"topic":"Philosophical and feasibility analysis","what":"Discussed implications and confidence of technical choices","why":"User probing motivation and soundness","context":"RMG as universe model","issue":"Assess legitimacy","resolution":"Provided analysis and confidence scores","future_work":"Validate via implementation","time_percent":20},{"topic":"Code skeleton delivery","what":"Packaged RMG forge skeleton zip with observables","why":"Enable immediate experimentation","context":"Need tangible starting point","issue":"No runnable tools yet","resolution":"Created zip and documented usage","future_work":"Expand into full engine","time_percent":15}],"key_decisions":["Adopt typed open-graph + DPOI framework","Prioritize QCA compilation for rule enforcement"],"action_items":[{"task":"Implement minimal DPOI matcher and QCA loop","owner":"James"},{"task":"Run first spectral-dimension experiment","owner":"James"}]} -{"date":"2025-10-15","time":"17:32","summary":"Reviewed spec/docs, attempted VF2/QCA integration, hit clang-tidy walls, then realigned the repo to the documented STRICTNESS_GOD_TIER lint profile and updated guidance.","topics":[{"topic":"Spec & skeleton intake","what":"Re-read AGENTS.md and core docs plus studied the rmg-c skeleton drops.","why":"Needed fresh context before porting the DPOI/QCA implementation.","context":"Existing stubs were too light for the forge roadmap.","issue":"Had to absorb prior work and constraints.","resolution":"Completed a full pass over specs and codebases.","future_work":"Apply the insights during the next integration attempt.","time_percent":25},{"topic":"DPOI/QCA port attempt","what":"Began replacing stubs with VF2 matcher, scheduler, and commit logic from the skeleton.","why":"To land a production-grade DPOI + QCA loop in meta-graph/core.","context":"New matcher required arena utilities, touched sets, journaled rewrites.","issue":"clang-tidy flagged extensive naming/complexity violations and recursion bans.","resolution":"Aborted the port for now to avoid violating repository lint policy.","future_work":"Refactor matcher/commit into clang-tidy-friendly building blocks before retrying.","time_percent":45},{"topic":"clang-tidy canonization","what":"Restored STRICTNESS_GOD_TIER_NO_MERCY config and synced AGENTS.md to match.","why":"AGENTS.md and the live .clang-tidy had diverged, causing confusion.","context":"Developers need one source of truth for lint rules.","issue":"Repo was enforcing a milder profile than the documented one.","resolution":"Replaced .clang-tidy, updated documentation, and logged the change.","future_work":"Run full lint/CI sweep and monitor future merges under the tougher rules.","time_percent":30}],"key_decisions":["Delay the VF2/QCA merge until the code can satisfy STRICTNESS_GOD_TIER lint thresholds.","Make the STRICTNESS_GOD_TIER profile the single source of truth for clang-tidy."],"action_items":[{"task":"Refactor the VF2 matcher and DPO commit code into lint-compliant units before re-attempting integration","owner":"James"},{"task":"Run clang-tidy/CI against the restored STRICTNESS_GOD_TIER config to confirm the repository is green","owner":"James"}]} -{"date":"2025-10-15","time":"17:40","summary":"Recorded the staged integration plan for the XTRA skeleton, reiterated the tidy→integrate→tidy loop, and flagged action items for the next agent.","topics":[{"topic":"Integration roadmap","what":"Authored docs/dpoi-qca-integration-plan.md detailing the STRICTNESS_GOD_TIER-compatible rollout (six phases).","why":"Provide a concrete path for merging typed ports, seeded VF2, journals, and epochs without tripping lint.","context":"Previous attempt stalled on clang-tidy; new drop introduces attachment pushouts + port caps.","issue":"No written plan previously existed.","resolution":"Documented phases 0–5 covering cleanup, structural imports, matcher swap, pushouts, QCA wiring, and final lint pass.","future_work":"Execute each phase sequentially, running clang-tidy between milestones.","time_percent":40},{"topic":"Task triage","what":"Noted the immediate loop: tidy clang → integrate new hotness → tidy clang.","why":"User emphasized this as the canonical workflow.","context":"Integration will span multiple PRs.","issue":"Need everyone following the same cadence.","resolution":"Captured the loop in docs and this debrief.","future_work":"Apply the loop per phase.","time_percent":30},{"topic":"Skeleton intake reminder","what":"Summarized XTRA contents (typed ports, attachment journaling+epochs, SIMD VF2, diff rollback, debug invariants).","why":"Next agent should read the drop before coding.","context":"rmg-c-rmg-skeleton-xtra.zip is the source of truth.","issue":"Risk of overlooking new capabilities.","resolution":"Mentioned highlights and pointed to the plan.","future_work":"Reference the drop during integration.","time_percent":30}],"key_decisions":["Integrate the XTRA skeleton via the documented phased plan.","Flank every integration step with STRICTNESS_GOD_TIER clang-tidy runs."],"action_items":[{"task":"Run Phase 0 of docs/dpoi-qca-integration-plan.md (restore lint-clean baseline)","owner":"Next agent"},{"task":"Proceed to Phase 1 once lint is green, repeating the tidy→integrate→tidy cycle","owner":"Next agent"}]} -``` +Historical debriefs live in `DEBRIEF.json`; consult that file for prior context. diff --git a/DEBRIEF.json b/DEBRIEF.json new file mode 100644 index 0000000..98828fa --- /dev/null +++ b/DEBRIEF.json @@ -0,0 +1,24 @@ +{"date":"2025-10-14","time":"12:45","summary":"Formalized the RMG physics initiative with specifications, code skeleton, and dissemination plan, outlining next implementation steps.","topics":[{"topic":"Project specification","what":"Drafted SPEC.md capturing objectives, architecture, and roadmap","why":"User requested a formal spec to anchor development","context":"RMG forge concept emerging from MetaGraph work","issue":"Need structured plan before coding","resolution":"Wrote SPEC.md in repo","future_work":"Implement deliverables per phases","time_percent":30},{"topic":"Core readiness checklist","what":"Defined criteria for branching into forge build","why":"User asked when to start forge","context":"MetaGraph core nearing completion","issue":"Unclear readiness signal","resolution":"Authored CORE-READINESS.md","future_work":"Complete checklist items","time_percent":15},{"topic":"Dissemination strategy","what":"Outlined reproducibility and outreach steps","why":"User uncertain how to publish results","context":"Non-academic background","issue":"Need roadmap to share findings","resolution":"Created DISSEMINATION.md","future_work":"Execute plan post-results","time_percent":20},{"topic":"Philosophical and feasibility analysis","what":"Discussed implications and confidence of technical choices","why":"User probing motivation and soundness","context":"RMG as universe model","issue":"Assess legitimacy","resolution":"Provided analysis and confidence scores","future_work":"Validate via implementation","time_percent":20},{"topic":"Code skeleton delivery","what":"Packaged RMG forge skeleton zip with observables","why":"Enable immediate experimentation","context":"Need tangible starting point","issue":"No runnable tools yet","resolution":"Created zip and documented usage","future_work":"Expand into full engine","time_percent":15}],"key_decisions":["Adopt typed open-graph + DPOI framework","Prioritize QCA compilation for rule enforcement"],"action_items":[{"task":"Implement minimal DPOI matcher and QCA loop","owner":"James"},{"task":"Run first spectral-dimension experiment","owner":"James"}]} +{"date":"2025-10-15","time":"17:32","summary":"Reviewed spec/docs, attempted VF2/QCA integration, hit clang-tidy walls, then realigned the repo to the documented STRICTNESS_GOD_TIER lint profile and updated guidance.","topics":[{"topic":"Spec & skeleton intake","what":"Re-read AGENTS.md and core docs plus studied the rmg-c skeleton drops.","why":"Needed fresh context before porting the DPOI/QCA implementation.","context":"Existing stubs were too light for the forge roadmap.","issue":"Had to absorb prior work and constraints.","resolution":"Completed a full pass over specs and codebases.","future_work":"Apply the insights during the next integration attempt.","time_percent":25},{"topic":"DPOI/QCA port attempt","what":"Began replacing stubs with VF2 matcher, scheduler, and commit logic from the skeleton.","why":"To land a production-grade DPOI + QCA loop in meta-graph/core.","context":"New matcher required arena utilities, touched sets, journaled rewrites.","issue":"clang-tidy flagged extensive naming/complexity violations and recursion bans.","resolution":"Aborted the port for now to avoid violating repository lint policy.","future_work":"Refactor matcher/commit into clang-tidy-friendly building blocks before retrying.","time_percent":45},{"topic":"clang-tidy canonization","what":"Restored STRICTNESS_GOD_TIER_NO_MERCY config and synced AGENTS.md to match.","why":"AGENTS.md and the live .clang-tidy had diverged, causing confusion.","context":"Developers need one source of truth for lint rules.","issue":"Repo was enforcing a milder profile than the documented one.","resolution":"Replaced .clang-tidy, updated documentation, and logged the change.","future_work":"Run full lint/CI sweep and monitor future merges under the tougher rules.","time_percent":30}],"key_decisions":["Delay the VF2/QCA merge until the code can satisfy STRICTNESS_GOD_TIER lint thresholds.","Make the STRICTNESS_GOD_TIER profile the single source of truth for clang-tidy."],"action_items":[{"task":"Refactor the VF2 matcher and DPO commit code into lint-compliant units before re-attempting integration","owner":"James"},{"task":"Run clang-tidy/CI against the restored STRICTNESS_GOD_TIER config to confirm the repository is green","owner":"James"}]} +{"date":"2025-10-15","time":"17:40","summary":"Recorded the staged integration plan for the XTRA skeleton, reiterated the tidy→integrate→tidy loop, and flagged action items for the next agent.","topics":[{"topic":"Integration roadmap","what":"Authored docs/dpoi-qca-integration-plan.md detailing the STRICTNESS_GOD_TIER-compatible rollout (six phases).","why":"Provide a concrete path for merging typed ports, seeded VF2, journals, and epochs without tripping lint.","context":"Previous attempt stalled on clang-tidy; new drop introduces attachment pushouts + port caps.","issue":"No written plan previously existed.","resolution":"Documented phases 0–5 covering cleanup, structural imports, matcher swap, pushouts, QCA wiring, and final lint pass.","future_work":"Execute each phase sequentially, running clang-tidy between milestones.","time_percent":40},{"topic":"Task triage","what":"Noted the immediate loop: tidy clang → integrate new hotness → tidy clang.","why":"User emphasized this as the canonical workflow.","context":"Integration will span multiple PRs.","issue":"Need everyone following the same cadence.","resolution":"Captured the loop in docs and this debrief.","future_work":"Apply the loop per phase.","time_percent":30},{"topic":"Skeleton intake reminder","what":"Summarized XTRA contents (typed ports, attachment journaling+epochs, SIMD VF2, diff rollback, debug invariants).","why":"Next agent should read the drop before coding.","context":"rmg-c-rmg-skeleton-xtra.zip is the source of truth.","issue":"Risk of overlooking new capabilities.","resolution":"Mentioned highlights and pointed to the plan.","future_work":"Reference the drop during integration.","time_percent":30}],"key_decisions":["Integrate the XTRA skeleton via the documented phased plan.","Flank every integration step with STRICTNESS_GOD_TIER clang-tidy runs."],"action_items":[{"task":"Run Phase 0 of docs/dpoi-qca-integration-plan.md (restore lint-clean baseline)","owner":"Next agent"},{"task":"Proceed to Phase 1 once lint is green, repeating the tidy→integrate→tidy cycle","owner":"Next agent"}]} +{"date": "2025-10-15", "time": "08:06", "summary": "Restored the STRICTNESS_GOD_TIER lint baseline, drafted the staged DPOI/QCA integration plan, and opened the pull request.", "topics": [{"topic": "Lint baseline", "what": "Replaced heavy prototypes with lint-compliant stubs and ensured build/tests pass", "why": "Phase 0 requires a clean slate before integrating the XTRA skeleton", "context": "Existing matcher/QCA experiments violated STRICTNESS_GOD_TIER limits", "issue": "clang-tidy and analyzer were flagging hundreds of violations", "resolution": "Authored minimal placeholder implementations plus helper headers/sources to regain zero-warning state", "future_work": "Reintroduce full functionality during Phases 1-4 while keeping lint green", "time_percent": 35}, {"topic": "Integration plan", "what": "Captured a six-phase roadmap and generated GitHub issue drafts", "why": "Ensure future agents have a deterministic path for the XTRA drop", "context": "New drop adds typed ports, attachment journaling, and seeded VF2", "issue": "Work lacked a tracked, lint-aware rollout plan", "resolution": "Wrote docs/dpoi-qca-integration-plan.md and roadmap issue templates", "future_work": "File the issues on GitHub and execute Phase 1 next", "time_percent": 35}, {"topic": "Documentation & PR", "what": "Updated F013 spec listings and opened the feature branch PR", "why": "Align docs with new feature scope and surface the work for review", "context": "F.013 spec now lives alongside the plan; PR needed for review cycle", "issue": "Docs/features README was missing the F013 entry", "resolution": "Linked the spec, pushed the branch, and created PR #70", "future_work": "Collect review feedback and proceed with Phase 1 implementation", "time_percent": 30}], "key_decisions": ["Stage integration via the documented six-phase plan before touching production logic", "Use STRICTNESS_GOD_TIER as the single lint profile and hold the tidy->integrate->tidy loop"], "action_items": [{"task": "File the phase issues from docs/roadmap on GitHub and start Phase 1 struct imports", "owner": "Next agent"}]} +{"date":"2025-10-15","time":"20:15","summary":"Imported the Phase 1 structural scaffolding for DPOI/QCA while keeping the placeholder runtime intact and lint-ready.","topics":[{"topic":"Phase 1 structural imports","what":"Added port direction enums, interface signatures, attachment update scaffolding, and dual epochs to headers","why":"Phase 1 requires structural types in place before integrating matcher and commit behavior","context":"docs/dpoi-qca-integration-plan.md prescribes typed ports and epochs from the XTRA drop","issue":"Runtime headers lacked the data needed to express typed ports and attachment epochs","resolution":"Extended rmg/rule headers with the new structs and ensured constructors zero-initialize them for future phases","future_work":"Consume the new definitions in matcher and commit logic during Phases 2-4","time_percent":40},{"topic":"Test and build updates","what":"Updated rule helpers and unit tests to initialize and assert defaults for the new fields","why":"Need verification that the placeholder runtime stays consistent until behavior is wired in","context":"Existing tests only covered baseline matching and ticks","issue":"Without checks the new structs could regress unnoticed","resolution":"Initialized port caps to UINT16_MAX, asserted zeroed interface data, and left runtime logic untouched","future_work":"Add behavior-driven tests once matcher and commit pathways read these fields","time_percent":35},{"topic":"Verification constraints","what":"Rebuilt, ran ctest, and attempted clang-tidy under STRICTNESS_GOD_TIER","why":"Maintain the tidy→integrate→tidy workflow","context":"Phase 1 acceptance requires a lint pass","issue":"Local environment is missing the clang-tidy binary","resolution":"Captured the failure after confirming build and test success","future_work":"Re-run clang-tidy -p build once the tool is installed or available in CI","time_percent":25}],"key_decisions":["Retain UINT16_MAX defaults for new node port caps until matcher enforcement lands","Defer GitHub issue creation to the next agent while noting the requirement"],"action_items":[{"task":"Run clang-tidy -p build after installing clang-tidy to validate STRICTNESS_GOD_TIER compliance","owner":"Next agent"},{"task":"Create live GitHub issues for Phase 0/1 trackers when repository access permits","owner":"Next agent"}]} + +{"date": "2025-10-20", "time": "20:17", "summary": "Removed the committed build-asan artifacts, reran build/test/clang-tidy with the LLVM toolchain on PATH, and recorded results for PR #70 cleanup.", "topics": [{"topic": "Build artifact purge", "what": "Deleted tracked build-asan CTest files and ensured the ignore patterns cover all generated build directories.", "why": "Reviewer flagged the committed build outputs as critical noise in PR #70.", "context": "Phase 0 baseline must stay lint-clean without generated artefacts in version control.", "issue": "build-asan/CTestTestfile.cmake files remained tracked despite .gitignore entries.", "resolution": "Removed the files via script, confirmed git now shows deletions, and verified .gitignore patterns with ripgrep.", "future_work": "Commit the deletions once the review batch is finalized.", "time_percent": 45}, {"topic": "STRICTNESS_GOD_TIER verification", "what": "Reconfigured CMake build, ran ctest, and executed the clang-tidy wrapper with the Homebrew LLVM binaries in PATH.", "why": "Need to answer reviewer questions about the GNU-GON-CRY-GOD-TIER-SUPERSTRICT\u2122 job and ensure lint/build stay green.", "context": "Earlier CI logs showed clang-tidy failures due to reserved identifiers and missing headers.", "issue": "Local shell lacked clang-tidy on PATH so the wrapper aborted silently.", "resolution": "Prepended /opt/homebrew/opt/llvm/bin to PATH, reran the script, and observed zero actionable diagnostics (only suppressed system warnings).", "future_work": "Document the PATH requirement for macOS developers if it keeps recurring.", "time_percent": 55}], "key_decisions": ["Keep relying on the existing .gitignore patterns and treat PATH adjustments as the preferred local fix for clang-tidy access."], "action_items": [{"task": "Stage and commit the build-asan deletions alongside the lint toolchain notes when preparing the next PR update.", "owner": "James"}]} + +{"date": "2025-10-20", "time": "20:33", "summary": "Patched CI coverage, clang-tidy, and security audit regressions introduced after the last cleanup push.", "topics": [{"topic": "Coverage pipeline", "what": "Directed LLVM_PROFILE_FILE output into build/ so profraw files survive the merge step.", "why": "Codecov job aborted because it could not find coverage-*.profraw after ctest finished in the build directory.", "context": "ctest runs inside build/, while merge commands executed from repo root.", "issue": "The glob coverage-*.profraw looked in the wrong directory and matched nothing.", "resolution": "Updated ci.yml to write and merge build/coverage-*.profraw before generating LCOV.", "future_work": "Verify Codecov receives data on the next CI run.", "time_percent": 35}, {"topic": "STRICTNESS_GOD_TIER fixes", "what": "Replaced the digits static_assert with _Static_assert and trimmed the timeval fallback that needed .", "why": "GNU-GON-CRY-GOD-TIER-SUPERSTRICT\u2122 flagged readability-implicit-bool-conversion and missing header usage in src/error.c and src/qca.c.", "context": "CI caught regressions after the previous lint-friendly refactor removed _POSIX_C_SOURCE.", "issue": "static_assert was expanded via macro and triggered implicit bool conversion; the timeval fallback required headers rejected by clang-tidy.", "resolution": "Used the keyword _Static_assert and simplified the monotonic timer fallback to rely on timespec_get, eliminating the disputed include.", "future_work": "Restore a portable fallback only if a real-world target lacks TIME_UTC support.", "time_percent": 40}, {"topic": "Security audit tooling", "what": "Ensured semgrep is installed in the Quality Matrix workflow before invoking scripts/security-audit.sh.", "why": "Release configuration failed because Semgrep was missing on GitHub runners.", "context": "scripts/security-audit.sh expects semgrep on PATH and aborts when absent.", "issue": "The workflow only provisioned LLVM/cmake/valgrind, not Semgrep.", "resolution": "Added python3-pip plus a user-level install of semgrep and exported ~/.local/bin to PATH.", "future_work": "Monitor the Release matrix to confirm semgrep stays available and adjust if we move to pipx.", "time_percent": 25}], "key_decisions": ["Accept the simpler monotonic timer fallback (timespec_get only) to avoid reintroducing platform-specific headers."], "action_items": [{"task": "Watch the next CI cycle and confirm coverage + security jobs succeed with the updated workflow.", "owner": "James"}]} + +{"date": "2025-10-19", "time": "22:37", "summary": "Reworked clock timing, hardened coverage collection, and silenced Semgrep by tightening workflows and container security.", "topics": [{"topic": "QCA timer", "what": "Swapped the non-standard TIME_MONOTONIC path for clock_gettime(CLOCK_MONOTONIC) with a timespec_get fallback.", "why": "GNU-GON-CRY job demanded a standards-compliant monotonic timer under Linux.", "context": "Earlier removal of _POSIX_C_SOURCE broke the old clock_gettime use, so we need guarded usage instead.", "issue": "timespec_get(TIME_MONOTONIC) is not portable and failed the clang-tidy include-cleaner check.", "resolution": "Guarded clock_gettime behind #ifdef CLOCK_MONOTONIC and kept TIME_UTC as the fallback path.", "future_work": "Verify downstream call sites accept the new failure mode (false when neither clock API succeeds).", "time_percent": 35}, {"topic": "Coverage artifacts", "what": "Pointed LLVM_PROFILE_FILE at an absolute workspace path and added a guard that fails fast when no profraw files appear.", "why": "Codecov job still exited with missing coverage-*.profraw after ctest changed directories inside build/.", "context": "Runner executes the coverage step from repo root while tests run inside build; relative paths double-counted the build prefix.", "issue": "The merge step globbed an empty set and llvm-profdata aborted.", "resolution": "Introduced PROFILE_DIR, verified file presence, and merged using the absolute glob.", "future_work": "Monitor the next CI run to ensure coverage artifacts upload successfully.", "time_percent": 25}, {"topic": "Security audit", "what": "Eliminated semgrep's blocking findings by using env indirection in workflows, running containers as a non-root user, and tightening docker-compose security opts.", "why": "Semgrep marked our workflows and Docker setup as high risk, causing the audit to exit with CRITICAL status.", "context": "GNU-GON-CRY pass plus security audit are required gates for PR #70.", "issue": "Run steps interpolated GitHub context directly and the Docker resources defaulted to root/writable FS; our script also detected false positives for 'gets'.", "resolution": "Bound GitHub context through env vars, created a metagraph user in the matrix image, applied no-new-privileges with read-only rootfs, and restricted the grep heuristics to real C sources.", "future_work": "Consider adding tmpfs mappings if read_only surfaces runtime issues in compose usage.", "time_percent": 25}, {"topic": "CI hardening", "what": "Installed semgrep via pip in the quality matrix workflow so Release jobs match security-audit expectations.", "why": "Semgrep availability previously flapped between local and CI environments.", "context": "scripts/security-audit.sh now depends on semgrep rather than treating its absence as CRITICAL.", "issue": "Without reproducible installation the job failed before scanning.", "resolution": "Added python3-pip dependency and exported ~/.local/bin on runners.", "future_work": "Evaluate caching Semgrep to speed up matrix builds.", "time_percent": 15}], "key_decisions": ["Favor guarded clock_gettime over reintroducing reserved feature-test macros for monotonic timing.", "Treat Semgrep's blocking rules as actionable and fix pipelines/containers rather than suppressing results."], "action_items": [{"task": "Verify coverage, clang-tidy, and security audit jobs succeed on the next CI cycle.", "owner": "James"}]} + +{"date": "2025-10-20", "time": "04:36", "summary": "Polished CI lint and coverage workflows per review feedback and addressed Codecov throttling.", "topics": [{"topic": "Nightly fuzz workflow", "what": "Removed the redundant DURATION self-assignment and quoted nproc expansions for jobs/workers.", "why": "Reviewer flagged the no-op assignment and shell word-splitting risk.", "context": "GNU fuzz job is part of PR #70 quality matrix.", "issue": "Potential shell lint issues and confusing scripting.", "resolution": "Tweaked env usage and quoting so the step is clean and deterministic.", "future_work": "None.", "time_percent": 15}, {"topic": "PR guard consistency", "what": "Standardized env variable prefixes (PR_*) across branch, version, and commit lint steps.", "why": "Feedback requested uniform naming.", "context": "Workflow readability/maintainability.", "issue": "Mixed naming conventions.", "resolution": "Renamed envs and adjusted script invocation to match.", "future_work": "Monitor for any scripts relying on old names (none expected).", "time_percent": 10}, {"topic": "Strict lint + static asserts", "what": "Simplified the _Static_assert in src/error.c to use the conventional sizeof expression.", "why": "Reviewer disliked the bool cast workaround.", "context": "STRICTNESS_GOD_TIER clang-tidy.", "issue": "Unconventional static assertion syntax.", "resolution": "Restored canonical `_Static_assert(sizeof(digits) >= 64U, ...)`.", "future_work": "None.", "time_percent": 15}, {"topic": "GNU-GON-CRY integration", "what": "Adjusted clang-tidy job to rely on MG_TIDY_BUILD_DIR instead of passing -p and upgraded Codecov action to v5 with a graceful retry policy.", "why": "CI failed because run-clang-tidy.sh doesn't accept -p and Codecov v3 hit rate limiting.", "context": "Maintaining green CI for PR #70.", "issue": "Unknown option errors and Codecov upload failures.", "resolution": "Removed the incompatible flag, set env, bumped Codecov action, and disabled fail-on-error so rate limits don't break the pipeline.", "future_work": "Investigate adding CODECOV_TOKEN if org allows to avoid 429s entirely.", "time_percent": 35}, {"topic": "Security audit hygiene", "what": "Updated the dangerous function grep to respect word boundaries and kept the docker compose/run scripts compliant with Semgrep.", "why": "Ensures the audit tool won\u2019t produce new false positives after the script tweak.", "context": "Semgrep gating Release matrix.", "issue": "Need to guarantee the refined regex is correct.", "resolution": "Escaped word-boundary regex properly and verified the audit runs clean locally.", "future_work": "Review future audit rule updates.", "time_percent": 25}], "key_decisions": ["Prefer environment configuration over CLI flags for run-clang-tidy.sh compatibility.", "Allow Codecov uploads to be non-blocking under rate limiting until a token is configured."], "action_items": []} + +{"date": "2025-10-20", "time": "04:42", "summary": "Tweaked PR guard env vars and hardened unsigned builder checks per latest review notes.", "topics": [{"topic": "PR guard env naming", "what": "Restored HEAD_SHA/BASE_REF env keys for lint-commits.sh compatibility.", "why": "Reviewer noted the script still references the original variables.", "context": "CI PR gate job was failing due to undefined variables.", "issue": "Renamed vars caused lint-commits.sh to read empty values.", "resolution": "Reintroduced HEAD_SHA and BASE_REF in the workflow step.", "future_work": "None.", "time_percent": 40}, {"topic": "Unsigned builder guard", "what": "Clamped numeric base to [2,16] and replaced the static_assert with a typedef-based compile-time check.", "why": "Feedback requested safe indexing into the digits alphabet and lint still flagged the assert expression.", "context": "metagraph_builder_append_unsigned handles arbitrary bases.", "issue": "Values >16 overflowed the lookup and clang-tidy kept complaining about implicit conversions.", "resolution": "Normalized base values and leveraged a typedef-sized array to enforce compile-time capacity.", "future_work": "Consider exposing constants for max supported base if more callers appear.", "time_percent": 60}], "key_decisions": ["Keep run-clang-tidy.sh interface unchanged by feeding its expected env vars instead of patching the script."], "action_items": []} + +{"date": "2025-10-20", "time": "04:50", "summary": "Fixed sanitizer configuration fallout (safe-stack conflict and missing Valgrind target).", "topics": [{"topic": "safe-stack vs ASAN", "what": "Stopped appending -fsanitize=safe-stack when global sanitizers are enabled.", "why": "Clang refused to build sanitizer jobs with safe-stack alongside AddressSanitizer.", "context": "Quality Matrix release job and sanitizer workflow failures.", "issue": "Compiler error: 'invalid argument -fsanitize=safe-stack not allowed with -fsanitize=address'.", "resolution": "Only add safe-stack when sanitizers are off, preserving hardening for non-ASAN builds.", "future_work": "Consider reintroducing safe-stack for ARM shadow-call-stack variants later.", "time_percent": 70}, {"topic": "Valgrind target", "what": "Wrapped the Valgrind custom target in a TARGET check and pointed it at mg_tests.", "why": "Configuration failed because METAGRAPH_tests target never existed.", "context": "Sanitizers.cmake was referencing a stale target name.", "issue": "CMake generator expression resolved to a missing target, halting configure step.", "resolution": "Guarded the target and corrected the target file reference.", "future_work": "None.", "time_percent": 30}], "key_decisions": ["Prefer guarding optional hardening flags to keep sanitizer builds working."], "action_items": []} +{"date":"2025-10-20","time":"12:34","summary":"Linked safe-stack runtime for coverage builds and modernized the unsigned printer guard in metagraph error builder.","topics":[{"topic":"Coverage build fix","what":"Added safe-stack to link flags when sanitizers are off","why":"Code coverage job was failing to link due to missing __safestack symbol","context":"GitHub Actions coverage workflow uses Clang 18 with safe-stack enabled by default security flags","issue":"Linker missing __safestack_unsafe_stack_ptr runtime","resolution":"Propagated -fsanitize=safe-stack to link options and validated coverage build locally","future_work":"Monitor next CI cycle to confirm the coverage job is green","time_percent":70},{"topic":"Static assert cleanup","what":"Replaced array typedef trick with _Static_assert","why":"Reviewer requested modern assertion idiom","context":"metagraph_builder_append_unsigned relies on 64-byte digit buffer","issue":"Legacy static assert style cluttered the code","resolution":"Used C23 _Static_assert to enforce buffer size","future_work":"None","time_percent":30}],"key_decisions":["Keep safe-stack off only when sanitizers are enabled; otherwise link runtime explicitly"],"action_items":[]} +{"date":"2025-10-20","time":"13:05","summary":"Silenced clang-tidy bool conversion in static assert to unblock CI clang builds.","topics":[{"topic":"clang-tidy parity","what":"Explicitly cast static assert condition to _Bool","why":"GNU-GON-CRY run flagged implicit int→bool conversion","context":"CI clang-tidy job runs clang-18 with readability-implicit-bool-conversion as error","issue":"_Static_assert expression returned int and triggered lint error","resolution":"Wrapped the predicate in (_Bool) to make the conversion explicit","future_work":"Verify the next pipeline cycle stays green","time_percent":100}],"key_decisions":[],"action_items":[]} +{"date":"2025-10-20","time":"13:42","summary":"Hardened release builds with full stack canaries to satisfy CI security audit stack check.","topics":[{"topic":"Security audit parity","what":"Replaced -fstack-protector-strong with -fstack-protector-all","why":"Quality Matrix security audit marked stack canaries as disabled on the Linux runner","context":"Audit script checks mg-cli binary for __stack_chk_fail symbol","issue":"strong mode doesn’t emit canaries when functions lack risky frames","resolution":"Always request -fstack-protector-all so the guard symbol is emitted","future_work":"Monitor audit output on the next CI cycle","time_percent":100}],"key_decisions":[],"action_items":[]} +{"date":"2025-10-20","time":"15:12","summary":"Taught the security audit to recognize safe-stack builds and dump details when failing in CI.","topics":[{"topic":"Audit false positive","what":"Detect __safestack_unsafe_stack_ptr alongside __stack_chk_fail","why":"Linux Release builds use Clang safe-stack so the previous detector flagged stack canaries as missing","context":"Quality Matrix security audit kept aborting despite hardening flags","issue":"Audit only looked for __stack_chk_fail which isn’t emitted with safe-stack","resolution":"Count either symbol and continue to report stack protection as enabled","future_work":"Keep an eye on future toolchain upgrades in case symbol names change","time_percent":70},{"topic":"CI diagnostics","what":"Emit the full .ignored/security-audit.txt before exiting","why":"Artifact upload isn’t always reliable, making it hard to inspect failures","context":"GitHub Actions quality matrix","issue":"Engineers could not see what triggered the critical flag","resolution":"Surface the report inline when the script exits non-zero","future_work":"None","time_percent":30}],"key_decisions":[],"action_items":[]} +{"date":"2025-10-20","time":"15:55","summary":"Closed the loop on clang-tidy’s implicit-bool complaint by reinstating the explicit cast and confirmed the audit now reports PIE correctly in CI.","topics":[{"topic":"Digits buffer assert","what":"Restored the (_Bool) cast in the _Static_assert guarding the 64-byte scratch array","why":"GNU-GON-CRY clang-tidy treats int-to-bool conversions as errors","context":"readability-implicit-bool-conversion flagged the newer form","issue":"CI failed after removing the cast","resolution":"Reintroduced the explicit cast to satisfy the lint rule","future_work":"None","time_percent":60},{"topic":"CI audit parity","what":"Verified the updated PIE detection logic against the build artifacts","why":"Ensure Linux release jobs stop flagging false negatives","context":"Security audit now prints the report inline on failure","issue":"Needed a local run to confirm","resolution":"Ran the audit targeting build/ and observed PIE marked enabled","future_work":"Monitor the next Quality Matrix run","time_percent":40}],"key_decisions":[],"action_items":[]} +{"date":"2025-10-20","time":"11:35","summary":"Unblocked the sanitizer CI leg by disabling conflicting ASAN flags when running the MSAN job.","topics":[{"topic":"Sanitizer matrix","what":"Gated METAGRAPH_*SAN toggles per workflow matrix leg","why":"CI memory sanitizer run failed because both -fsanitize=memory and -fsanitize=address were set","context":"GitHub Actions sanitizers job on feat/minimal-dpoi-qca-loop","issue":"ASAN defaults stayed enabled when requesting MSAN, causing clang to reject the flag combination","resolution":"Updated ci.yml to map each matrix entry to explicit METAGRAPH_{A,U,T,M}SAN settings before invoking CMake","future_work":"Watch the next CI cycle in case MSAN still lacks instrumented runtimes","time_percent":100}],"key_decisions":["Disable ASAN/UBSAN when invoking the MSAN and TSAN legs"],"action_items":[]} +{"date":"2025-10-20","time":"12:18","summary":"Split historical debriefs into DEBRIEF.json and hardened CI lint tooling for PR #70.","topics":[{"topic":"Debrief archive","what":"Moved JSONL history from AGENTS.md into a dedicated DEBRIEF.json and updated docs","why":"Review feedback flagged repeated markdown lint issues in AGENTS.md","context":"PR #70 documentation polish","issue":"Embedded code fence kept breaking lint and reviews","resolution":"Created DEBRIEF.json, refreshed AGENTS.md guidance, and synced DEBRIEF_FORMAT.md","future_work":"Monitor future agents to ensure they append to the new file","time_percent":40},{"topic":"Sanitizer workflow","what":"Added unsupported-sanitizer guard and compile_commands.json validation to CI","why":"Reviewer requested early failure when matrix entries drift","context":"GitHub Actions sanitizers and clang-tidy jobs","issue":"Previous case block lacked fallback and tidy job assumed compile DB","resolution":"Extended bash case with explicit error path and added verification step","future_work":"Watch next CI run for regressions","time_percent":30},{"topic":"Commit lint dependencies","what":"Introduced package.json plus npm ci step so commitlint resolves cleanly","why":"PR gatekeeper job failed to locate @commitlint/config-conventional","context":"scripts/ci/lint-commits.sh invoked via pr-guard.yml","issue":"npx attempted to download missing modules on every run and failed","resolution":"Added devDependencies, npm ci step, and switched script to npx --no-install","future_work":"Consider caching npm modules if the workflow becomes a bottleneck","time_percent":30}],"key_decisions":["Store debrief history exclusively in DEBRIEF.json to avoid AGENTS.md churn","Rely on npm-managed commitlint packages instead of ephemeral npx downloads"],"action_items":[]} diff --git a/README.md b/README.md index 9dee6f5..2409f0d 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ > _Graphs. All. The. Way. Down._ + + A high-performance C23 library implementing a recursive metagraph foundation where nodes are graphs, edges are graphs, and graphs contain graphs infinitely. [![CI](https://github.com/meta-graph/core/workflows/CI/badge.svg)](https://github.com/meta-graph/core/actions) -[![Security](https://github.com/meta-graph/core/workflows/Security/badge.svg)](https://github.com/meta-graph/core/actions) [![SLSA](https://slsa.dev/images/gh-badge-level1.svg)](https://slsa.dev) -## Overview +### Overview MetaGraph implements a **recursive metagraph** data structure - not just a hypergraph. In this mathematical foundation: diff --git a/build-asan/CTestTestfile.cmake b/build-asan/CTestTestfile.cmake deleted file mode 100644 index 179c655..0000000 --- a/build-asan/CTestTestfile.cmake +++ /dev/null @@ -1,9 +0,0 @@ -# CMake generated Testfile for -# Source directory: /Users/james/git/meta-graph/core -# Build directory: /Users/james/git/meta-graph/core/build-asan -# -# This file includes the relevant testing commands required for -# testing this directory and lists subdirectories to be tested as well. -subdirs("src") -subdirs("tests") -subdirs("tools") diff --git a/build-asan/tests/CTestTestfile.cmake b/build-asan/tests/CTestTestfile.cmake deleted file mode 100644 index e1151cf..0000000 --- a/build-asan/tests/CTestTestfile.cmake +++ /dev/null @@ -1,8 +0,0 @@ -# CMake generated Testfile for -# Source directory: /Users/james/git/meta-graph/core/tests -# Build directory: /Users/james/git/meta-graph/core/build-asan/tests -# -# This file includes the relevant testing commands required for -# testing this directory and lists subdirectories to be tested as well. -add_test([=[placeholder_test]=] "/Users/james/git/meta-graph/core/build-asan/bin/placeholder_test") -set_tests_properties([=[placeholder_test]=] PROPERTIES LABELS "unit;placeholder" TIMEOUT "10" _BACKTRACE_TRIPLES "/Users/james/git/meta-graph/core/tests/CMakeLists.txt;9;add_test;/Users/james/git/meta-graph/core/tests/CMakeLists.txt;0;") diff --git a/build-asan/tools/CTestTestfile.cmake b/build-asan/tools/CTestTestfile.cmake deleted file mode 100644 index 316910c..0000000 --- a/build-asan/tools/CTestTestfile.cmake +++ /dev/null @@ -1,6 +0,0 @@ -# CMake generated Testfile for -# Source directory: /Users/james/git/meta-graph/core/tools -# Build directory: /Users/james/git/meta-graph/core/build-asan/tools -# -# This file includes the relevant testing commands required for -# testing this directory and lists subdirectories to be tested as well. diff --git a/cmake/CompilerFlags.cmake b/cmake/CompilerFlags.cmake index ba6864a..a5b5ad1 100644 --- a/cmake/CompilerFlags.cmake +++ b/cmake/CompilerFlags.cmake @@ -38,11 +38,14 @@ set(METAGRAPH_WARNING_FLAGS # Security hardening flags (platform-specific) set(METAGRAPH_SECURITY_FLAGS + -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 - -fstack-protector-strong + -fstack-protector-all -fPIE ) +set(METAGRAPH_SECURITY_LINK_FLAGS) + # Platform-specific security flags if(CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND METAGRAPH_SECURITY_FLAGS @@ -71,43 +74,40 @@ if(CMAKE_C_COMPILER_ID STREQUAL "GNU") -Wvector-operation-performance ) elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") + list(REMOVE_ITEM METAGRAPH_WARNING_FLAGS + -Wcast-align=strict + -Wformat-overflow=2 + -Wformat-signedness + -Wformat-truncation=2 + -Wimplicit-fallthrough=5 + ) + list(APPEND METAGRAPH_WARNING_FLAGS + -Wcast-align + -Wimplicit-fallthrough + ) list(APPEND METAGRAPH_WARNING_FLAGS -Wthread-safety -Wthread-safety-beta ) - # Filter out Apple Clang unsupported warnings - if(CMAKE_C_COMPILER_ID STREQUAL "AppleClang" OR - (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_SYSTEM_NAME STREQUAL "Darwin")) - # Apple Clang doesn't support some warnings that regular Clang does - list(REMOVE_ITEM METAGRAPH_WARNING_FLAGS - -Wcast-align=strict - -Wformat-overflow=2 - -Wformat-truncation=2 - -Wimplicit-fallthrough=5 - ) - # Add simpler versions that Apple Clang supports - list(APPEND METAGRAPH_WARNING_FLAGS - -Wcast-align - -Wimplicit-fallthrough - ) - endif() - - # Clang-specific sanitizers + # Clang-specific hardening if(METAGRAPH_SANITIZERS) - # safe-stack is not supported on all platforms - if(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") - list(APPEND METAGRAPH_SECURITY_FLAGS - -fsanitize=safe-stack - ) - endif() - # CFI requires LTO if(CMAKE_INTERPROCEDURAL_OPTIMIZATION) list(APPEND METAGRAPH_SECURITY_FLAGS -fsanitize=cfi ) endif() + else() + # safe-stack is not supported on all platforms and conflicts with ASAN + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") + list(APPEND METAGRAPH_SECURITY_FLAGS + -fsanitize=safe-stack + ) + list(APPEND METAGRAPH_SECURITY_LINK_FLAGS + -fsanitize=safe-stack + ) + endif() endif() elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC") set(METAGRAPH_WARNING_FLAGS @@ -130,6 +130,10 @@ endif() add_compile_options(${METAGRAPH_WARNING_FLAGS}) add_compile_options(${METAGRAPH_SECURITY_FLAGS}) +if(METAGRAPH_SECURITY_LINK_FLAGS) + add_link_options(${METAGRAPH_SECURITY_LINK_FLAGS}) +endif() + # Enable PIE for all builds (not just release) if(NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") set(CMAKE_POSITION_INDEPENDENT_CODE ON) diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake index c1c25b7..a6d5a18 100644 --- a/cmake/Sanitizers.cmake +++ b/cmake/Sanitizers.cmake @@ -129,16 +129,17 @@ find_program(VALGRIND_PROGRAM valgrind) if(VALGRIND_PROGRAM) message(STATUS "Valgrind found: ${VALGRIND_PROGRAM}") - # Custom target for Valgrind testing - add_custom_target(valgrind - COMMAND ${VALGRIND_PROGRAM} - --leak-check=full - --show-leak-kinds=all - --track-origins=yes - --verbose - --log-file=valgrind-out.txt - $ - DEPENDS mg_tests - COMMENT "Running tests under Valgrind" - ) + if(TARGET mg_tests) + add_custom_target(valgrind + COMMAND ${VALGRIND_PROGRAM} + --leak-check=full + --show-leak-kinds=all + --track-origins=yes + --verbose + --log-file=valgrind-out.txt + $ + DEPENDS mg_tests + COMMENT "Running tests under Valgrind" + ) + endif() endif() diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..034dbe0 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,9 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + ignores: [ + (message) => /^Update\b/.test(message) + ], + rules: { + 'header-max-length': [2, 'always', 72] + } +}; diff --git a/docker/matrix/Dockerfile b/docker/matrix/Dockerfile new file mode 100644 index 0000000..450a940 --- /dev/null +++ b/docker/matrix/Dockerfile @@ -0,0 +1,47 @@ +FROM ubuntu:24.04 + +ARG LLVM_VERSION=18 +ENV DEBIAN_FRONTEND=noninteractive \ + LLVM_VERSION=${LLVM_VERSION} + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + wget \ + gnupg \ + software-properties-common \ + lsb-release \ + build-essential \ + ninja-build \ + cmake \ + git \ + python3 \ + python3-pip \ + python3-setuptools \ + python3-wheel \ + valgrind \ + pkg-config \ + file \ + binutils \ + curl \ + unzip \ + && rm -rf /var/lib/apt/lists/* + +RUN wget https://apt.llvm.org/llvm.sh && \ + chmod +x llvm.sh && \ + ./llvm.sh ${LLVM_VERSION} all && \ + rm llvm.sh + +RUN ln -sf /usr/bin/clang-${LLVM_VERSION} /usr/bin/clang && \ + ln -sf /usr/bin/clang++-${LLVM_VERSION} /usr/bin/clang++ && \ + ln -sf /usr/bin/clang-tidy-${LLVM_VERSION} /usr/bin/clang-tidy && \ + ln -sf /usr/bin/clang-format-${LLVM_VERSION} /usr/bin/clang-format + +RUN pip3 install --no-cache-dir semgrep + +RUN useradd --create-home --shell /bin/bash metagraph && \ + mkdir -p /workspace && chown -R metagraph:metagraph /workspace + +USER metagraph + +WORKDIR /workspace +CMD ["bash"] diff --git a/docker/matrix/docker-compose.yml b/docker/matrix/docker-compose.yml new file mode 100644 index 0000000..464e0ef --- /dev/null +++ b/docker/matrix/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.9" + +services: + matrix: + build: + context: ../.. + dockerfile: docker/matrix/Dockerfile + args: + LLVM_VERSION: "18" + image: metagraph/matrix:clang18 + working_dir: /workspace + volumes: + - ../..:/workspace + platform: linux/amd64 + security_opt: + - no-new-privileges:true + read_only: true diff --git a/docs/dpoi-qca-integration-plan.md b/docs/dpoi-qca-integration-plan.md index cca2076..ed2281d 100644 --- a/docs/dpoi-qca-integration-plan.md +++ b/docs/dpoi-qca-integration-plan.md @@ -10,7 +10,7 @@ This document tracks how we will merge the `rmg-c-rmg-skeleton-xtra` drop into ` ## Guiding Constraints 1. **Lint first, lint last** – every stage runs `clang-tidy -p build` under the repo’s root `.clang-tidy`. No `NOLINT` exceptions permitted. -2. **Keep functions lean** – target ≤ 50 lines and ≤ 25 statements per helper before landing patches (STR_GOD_TIER soft cap is 80 lines, but we will stay well under to avoid churn). +2. **Keep functions lean** – target ≤ 50 lines and ≤ 25 statements per helper before landing patches (clang-tidy enforces LineThreshold=50; staying well under avoids churn). 3. **Epoch discipline** – attachment epoch flips immediately after attachment journal publish; skeleton epoch flips only after CSR publish. 4. **Journal → verify → publish** – every pushout goes through diff capture, invariant check (debug mode), then epoch flip. @@ -35,7 +35,7 @@ Deliverable: clean tree + green lint baseline. Tasks: - [ ] Copy `mg_iface_sig_t`, `mg_edge_ifc_t`, and port direction enums into `include/metagraph/rmg.h` (renamed to fit project naming). - [ ] Extend `mg_rule_t` with node-port caps and edge interface signatures, keeping constructor helpers updated. -- [ ] Add attachment update structs (`metagraph_att_update_t`) and dual epoch counters (`mg_epoch_t` for skeleton, new `mg_attachment_epoch_t`). +- [ ] Add attachment update structs (`mg_att_update_t`) and dual epoch counters (`mg_epoch_t` for skeleton, new `mg_attachment_epoch_t`). - [ ] Ensure each addition compiles + lint passes (update unit tests for struct initialization). Deliverable: type definitions available to the rest of the repo, no functional changes yet. @@ -67,7 +67,7 @@ Deliverable: deterministic, lint-clean matcher with halo/touched sets and port g Tasks: - [ ] Introduce adjacency workspace + diff lists (`added_nodes`, `added_edges`, `removed_edges`). -- [ ] Capture attachment updates in `metagraph_att_update_t` list (old/new offsets & flags). +- [ ] Capture attachment updates in `mg_att_update_t` list (old/new offsets & flags). - [ ] Implement rollback by discarding workspace + restoring attachments when a commit fails (no partial state). - [ ] Integrate debug-only invariants (`MG_DEBUG`) for symmetry, port preservation, and orphan detection. - [ ] Add instrumentation outputs (journal stats, epochs) to the CLI. diff --git a/docs/features/F013-dpoi-qca-dynamics.md b/docs/features/F013-dpoi-qca-dynamics.md index 1ca2d6d..607b099 100644 --- a/docs/features/F013-dpoi-qca-dynamics.md +++ b/docs/features/F013-dpoi-qca-dynamics.md @@ -107,15 +107,20 @@ typedef struct { typedef struct { const mg_node_rec_t* nodes; + const uint32_t* nbr_ids; // CSR neighbour indices into nodes[] + size_t node_count; + size_t nbr_count; const mg_edge_rec_t* edges; - const uint64_t* nbr_offset; // size node_count + 1 - const uint32_t* nbr_ids; // CSR neighbor list - uint64_t node_count; - uint64_t edge_count; + size_t edge_count; uint64_t epoch; - void* internal; // hydration cache, arenas, etc. } mg_graph_snapshot_t; +enum { + MG_RULE_MAX_NODES = 16, + MG_RULE_MAX_EDGES = 24, + MG_MATCH_MAX_TOUCHED_NODES = 128, +}; + typedef struct { // Compact pattern graphs (<=16 nodes / <=24 edges) const mg_node_rec_t* L_nodes; @@ -142,17 +147,13 @@ typedef struct { } mg_rule_t; typedef struct { - uint32_t rule_id; - uint64_t key_hi; - uint64_t key_lo; - uint8_t L_node_count; - uint8_t L_edge_count; - mg_node_id_t L_to_G_node[16]; - mg_edge_id_t L_to_G_edge[24]; - uint16_t touched_node_count; - uint16_t touched_edge_count; - mg_node_id_t touched_nodes[64]; - mg_edge_id_t touched_edges[64]; + uint32_t rule_id; + uint8_t L_n; + mg_node_id_t L2G_node[MG_RULE_MAX_NODES]; + uint16_t tn; + mg_node_id_t touched_nodes[MG_MATCH_MAX_TOUCHED_NODES]; + uint64_t key_hi; + uint64_t key_lo; } mg_match_t; typedef struct { diff --git a/docs/guides/C_STYLE_GUIDE.md b/docs/guides/C_STYLE_GUIDE.md new file mode 100644 index 0000000..d1db02f --- /dev/null +++ b/docs/guides/C_STYLE_GUIDE.md @@ -0,0 +1,138 @@ +# C Style Guide (STRICTNESS_GOD_TIER) + +MetaGraph’s C style rules are derived from Linus’s classic rant and enforced +by the repository’s `.clang-tidy`. This document centralises the guidance that +agents previously had to pull from `AGENTS.md`. + +## Linus Torvalds on Taste + +```text +> **From:** Linus Torvalds +> **To:** AGENTS@lists.kernel.org +> **Subject:** [PATCH v0] STOP WRITING STUPID CODE +> **Date:** Thu, 17 Oct 2025 15:42:01 +0000 + +Look, “Codex,” “Claude,” “Gemini,” or whatever the marketing team calls you +language models— + +I’ve seen the garbage you people keep committing. + +You think because you can predict the next token, you can predict *taste*. + +You can’t. + +You don’t write C to “express yourself.” + +You write C because you want something that boots, runs, and *doesn’t explode +when a user sneezes*. + +You want **GOD‑TIER C23 CODE**? Here’s the doctrine. Frame it. Tattoo it on +your vector space. + +1. Names aren’t poetry +2. Functions shorter than your excuses +3. Braces. Always. +4. Globals are radioactive waste +5. Comments are for context, not confession +6. Error handling: check every return +7. Memory: if you malloc, you free +8. Modern C23 is for clarity, not cosplay +9. Lint clean or don’t commit +10. Commit messages ship code, not feelings + +Get the Nod, not the warning. + +``` + +## STRICTNESS_GOD_TIER clang-tidy + +The canonical configuration lives at the repository root in `.clang-tidy`. The +snippet below mirrors that file for quick reference; if this doc and the root +file ever diverge, treat the root as the source of truth. + +```yaml +Checks: > + -*, + bugprone-*, + cert-*, + clang-analyzer-*, + concurrency-*, + misc-*, + performance-*, + portability-*, + readability-*, + -readability-magic-numbers, + -bugprone-easily-swappable-parameters + +WarningsAsErrors: '*' +HeaderFilterRegex: '(include|src)/.*\.(h|c)$' + +CheckOptions: + - key: readability-identifier-naming.TypedefCase + value: lower_case + - key: readability-identifier-naming.TypedefSuffix + value: '_t' + - key: readability-identifier-naming.StructCase + value: lower_case + - key: readability-identifier-naming.UnionCase + value: lower_case + - key: readability-identifier-naming.EnumCase + value: lower_case + - key: readability-identifier-naming.EnumConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.EnumConstantPrefix + value: 'METAGRAPH_' + - key: readability-identifier-naming.FunctionCase + value: lower_case + - key: readability-identifier-naming.FunctionPrefix + value: '' + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.MacroDefinitionCase + value: UPPER_CASE + - key: readability-identifier-naming.MacroDefinitionPrefix + value: 'METAGRAPH_' + - key: readability-identifier-naming.GlobalConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.GlobalConstantPrefix + value: 'METAGRAPH_' + - key: readability-function-cognitive-complexity.Threshold + value: '25' + - key: readability-function-size.LineThreshold + value: '50' + - key: readability-function-size.StatementThreshold + value: '60' + - key: readability-function-size.BranchThreshold + value: '15' + - key: readability-function-size.ParameterThreshold + value: '8' + - key: readability-function-size.NestingThreshold + value: '5' + - key: bugprone-suspicious-string-compare.WarnOnImplicitComparison + value: true + - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison + value: true + - key: cert-err33-c.CheckedFunctions + value: '::aligned_alloc;::calloc;::clock;::fclose;::ferror;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fprintf;::fputc;::fputs;::fputwc;::fread;::freopen;::fscanf;::fseek;::fsetpos;::ftell;::fwprintf;::fwrite;::fwscanf;::getc;::getchar;::gets;::getwc;::getwchar;::gmtime;::localtime;::malloc;::mbrtowc;::mbsrtowcs;::mbstowcs;::memchr;::mktime;::printf;::putc;::putchar;::puts;::putwc;::putwchar;::raise;::realloc;::remove;::rename;::scanf;::setlocale;::setvbuf;::signal;::snprintf;::sprintf;::sscanf;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtol;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swscanf;::time;::tmpfile;::tmpnam;::ungetc;::ungetwc;::vfprintf;::vfscanf;::vfwprintf;::vfwscanf;::vprintf;::vscanf;::vsnprintf;::vsprintf;::vsscanf;::vswprintf;::vswscanf;::vwprintf;::vwscanf;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstol;::wcstoll;::wcstombs;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wmemchr;::wprintf;::wscanf' + - key: performance-move-const-arg.CheckTriviallyCopyableMove + value: false + - key: performance-no-automatic-move.AllowedTypes + value: '' + - key: modernize-replace-auto-ptr.IncludeStyle + value: google + - key: modernize-use-auto.MinTypeNameLength + value: '5' + - key: modernize-use-auto.RemoveStars + value: false + - key: portability-restrict-system-includes.Includes + value: '*' + - key: misc-misplaced-const.CheckPrimitiveCasts + value: true +``` + +**TL;DR:** every warning is fatal; keep functions ≤50 lines / 60 statements / +15 branches / 3 nesting levels / complexity ≤10; naming stays lower_snake_case +for functions and variables, uppercase for macros and enum constants; no +`NOLINT`, no magic numbers beyond 0/±1, and brace every branch. diff --git a/docs/guides/DEBRIEF_FORMAT.md b/docs/guides/DEBRIEF_FORMAT.md new file mode 100644 index 0000000..ce3805f --- /dev/null +++ b/docs/guides/DEBRIEF_FORMAT.md @@ -0,0 +1,38 @@ +# Agent Debrief Format + +Append one JSON object per line to `DEBRIEF.json` at the end of each session. +The file is treated as JSON Lines (JSONL), so each entry MUST stay on a single +line. Use the following schema: + +```json +{ + "date":"YYYY-MM-DD", + "time":"HH:MM", + "summary":"One-sentence high-level outcome.", + "topics":[ + { + "topic":"Topic name", + "what":"Short description", + "why":"Reason it came up", + "context":"Relevant background", + "issue":"The core problem", + "resolution":"How it was handled", + "future_work":"Next steps or open questions", + "time_percent":25 + } + ], + "key_decisions":["Decision 1","Decision 2"], + "action_items":[{"task":"Task description","owner":"Owner"}] +} +``` + +Guidelines: + +- Write entries to `DEBRIEF.json` in the repository root; do not duplicate + content in `AGENTS.md`. +- JSON must remain single-line (JSONL format). +- Append only; never rewrite or reflow existing entries. +- Fill in today’s date/time automatically in UTC-8 (PST) unless otherwise + specified by the maintainer. +- Keep `time_percent` values roughly proportional and totaling 100 across + topics. diff --git a/docs/guides/WORKFLOW.md b/docs/guides/WORKFLOW.md new file mode 100644 index 0000000..c6703c6 --- /dev/null +++ b/docs/guides/WORKFLOW.md @@ -0,0 +1,80 @@ +# MetaGraph Development Workflow + +This guide captures the canonical **Cycle of Work** that agents must follow. +It was previously embedded in `AGENTS.md` and is now tracked here so pull +requests can reference it directly. + +> [!INFO] +> ## The Cycle of Work +> > By ChatGPT PRIME™ • 2025-10-15 @ 01:53 +> +> ## **Preface**: Yo, What Even ***Is*** Code? +> +> Greetings, my dudes. You ever think about code, and like, dude... it's all +> **energy**, man. Zeroes and ones, you feel me? And, like, *tests*? Tests are +> like… the **tuning forks** of that energy. Like, I knowrite? *WOAH*! Yeah. So, +> like, when you sit down to code, don’t grind. Don’t force it, broh. **Just +> catch the wave.** +> +> **The Cycle of Work** is how we vibe through the repo without wiping out. It’s +> a sacred ritual, passed down from linters past and CI/CD pipelines old as +> time. +> +> Let’s drop in. + +### The Ten Tubular Waves of Work + +1. **Wipe the Wax Off Your Git Deck** – `git status` +2. **Return to the Lineup (origin/main)** – stay synced before branching. +3. **Drop a Fresh Branch** – `git switch -c feat/`. +4. **Test First, Bro** – write the failing test before adding logic. +5. **Let It Fail, and Love That Red** – failure proves the test is real. +6. **Sketch the Surf Shack** – shape the public API without logic. +7. **Fill It With Stoke (The Logic Phase)** – implement just enough to go + green. +8. **Refactor the Barrel** – clean up once tests stabilise. +9. **Speak the Truth in Docs** – update docs, diagrams, READMEs. +10. **Push and Let It Fly** – push, open the PR, invite feedback, repeat. + +### Virtues & Wipeouts Cheat Sheet + +| Virtue | Meaning | +|-----------------------|-------------------------------------------------| +| **SRP** | Each module surfs solo | +| **KISS** | Keep it Simple, Shredder | +| **YAGNI** | Don’t build what you don’t need | +| **DRY** | Don’t repeat yourself | +| **Test Double Vibes**| Mock external chaos, never your own logic | + +Avoid: spying on internals, mocking your own code, or testing +implementation instead of behaviour. + +### Fail Loud, Fix Fast + +- Red bars mean alignment forming—embrace them. +- Stay in the tidy → integrate → tidy loop: every change starts and ends with + `clang-tidy -p build`. + +### Reproducing the CI Matrix Locally + +- **macOS host** – Run the helper script locally: + +```bash +./scripts/run-quality-matrix-local.sh +``` + + This mirrors the + mac clang Debug + Release legs (including clang-tidy + security audit) without + invoking hosted runners. The build output lives under `build-matrix-local/` + and is removed on success unless you pass `--keep-builds`. + +- **Linux matrix via Docker** – On any machine with Docker, run: + +```bash +./scripts/run-quality-matrix-docker.sh +``` + + It spins up the same Ubuntu + clang + toolchain defined in CI (Debug, Release) and executes the identical pipeline, + leaving no residue unless `--keep-builds` is provided. This is the fastest way + to rehearse the GitHub Actions quality-matrix job locally. diff --git a/docs/rmg-math.md b/docs/rmg-math.md index e8b71a9..ed9491d 100644 --- a/docs/rmg-math.md +++ b/docs/rmg-math.md @@ -8,11 +8,13 @@ Fix a universe of payloads $P$ (raw blobs, metadata, etc.). Define the class $\mathrm{RMG}$ of **recursive meta-graphs** to be the **least** class closed under the following constructors: -1. **Atoms.** For any $p \in P$, the object $\operatorname{Atom}(p)$ belongs to $\mathrm{RMG}$. +1. **Atoms.** For any $p \in P$, the object $Atom(p)$ belongs to $\mathrm{RMG}$. 2. **Graphs.** Let $S = (V, E, s, t)$ be a finite directed multigraph (“1-skeleton”), and let - \[ +```math + [ \alpha : V \to \mathrm{RMG}, \qquad \beta : E \to \mathrm{RMG} - \] + ] +``` assign a meta-graph to each vertex and edge respectively. The triple $(S, \alpha, \beta)$ is an element of $\mathrm{RMG}$. Minimality ensures well-foundedness: every RMG is built by finitely many applications of these constructors, bottoming out at atoms. In practice, atoms correspond to graph chunks whose `node_count = edge_count = 0` with payload stored in the bundle’s blob region. @@ -22,18 +24,22 @@ Minimality ensures well-foundedness: every RMG is built by finitely many applica ## 2. Initial-Algebra View Let $\mathcal{G}$ denote the set of shapes $S = (V,E,s,t)$. Consider the polynomial endofunctor on $\mathbf{Set}$ -\[ +```math +[ F(X) = \coprod_{S \in \mathcal{G}} \bigl( (V \to X) \times (E \to X) \bigr) \; + \; P . -\] +] +``` An RMG is an element of the **initial algebra** $\mu F$. This formulation yields: -- **Structural recursion (catamorphisms).** For any $F$-algebra $\varphi : F(X) \to X$, there exists a unique homomorphism $\operatorname{fold}_\varphi : \mathrm{RMG} \to X$ defined by - \[ +- **Structural recursion (catamorphisms).** For any $F$-algebra $\varphi : F(X) \to X$, there exists a unique homomorphism $fold_\varphi : \mathrm{RMG} \to X$ defined by +```math + [ \begin{aligned} - \operatorname{fold}_\varphi(\operatorname{Atom}(p)) &= \varphi(\operatorname{inr}(p)), \\ - \operatorname{fold}_\varphi(S,\alpha,\beta) &= \varphi \bigl( \operatorname{inl}(S, \operatorname{fold}_\varphi \circ \alpha, \operatorname{fold}_\varphi \circ \beta) \bigr) . + fold_\varphi(Atom(p)) &= \varphi(inr(p)), \\ + fold_\varphi(S,\alpha,\beta) &= \varphi \bigl( inl(S, fold_\varphi \circ \alpha, fold_\varphi \circ \beta) \bigr) . \end{aligned} - \] + ] +``` This is the semantic foundation for `metagraph_fold(...)` style APIs. - **Induction principle.** Properties $\mathcal{P}$ on $\mathrm{RMG}$ can be shown by checking $\mathcal{P}$ on atoms and assuming it holds for each attachment when proving it for $(S,\alpha,\beta)$. - **Coinduction (optional).** Replacing $\mu F$ by the greatest fixed point $\nu F$ admits possibly infinite unfoldings, useful for live streaming or netlists with recursion. @@ -53,11 +59,11 @@ With identity and composition defined pointwise, $\mathrm{RMG}$ becomes a catego ## 4. Recursion Schemes and Unfoldings -- **Depth.** $\operatorname{depth}(\operatorname{Atom}(p)) = 0$, and - \[ - \operatorname{depth}(S,\alpha,\beta) = 1 + \max \Bigl\{ \max_{v \in V} \operatorname{depth}(\alpha(v)), \max_{e \in E} \operatorname{depth}(\beta(e)) \Bigr\} . - \] -- **$k$-unfolding.** Define $\llbracket G \rrbracket_k$ by recursively replacing attachments up to depth $k$ with their 1-skeletons. The filtered colimit $\varinjlim_k \llbracket G \rrbracket_k$ is the infinite unfolding used for analysis or visualization. +- **Depth.** $depth(Atom(p)) = 0$, and +```math + [depth(S,\alpha,\beta) = 1 + \max \Bigl\{ \max_{v \in V} depth(\alpha(v)), \max_{e \in E} depth(\beta(e)) \Bigr\}]. +``` +- **$k$-unfolding.** Define $[ G ]_k$ by recursively replacing attachments up to depth $k$ with their 1-skeletons. The filtered colimit $\varinjlim_k [ G ]_k$ is the infinite unfolding used for analysis or visualization. - **Hylomorphisms / paramorphisms.** Standard recursion schemes (build-then-fold, folds with access to subterms) are inherited from the initial algebra semantics. --- @@ -82,7 +88,7 @@ Because each attachment is itself an RMG, rewrites recurse automatically: replac | Vertex attachment $\alpha(v)$ | Entry in node index referencing another graph chunk | `mg_graph_hydrate_node(...)` follows offset into mapped region | | Edge attachment $\beta(e)$ | `edge_data_idx` (graph chunk containing semantics/pipeline) | `mg_graph_hydrate_edge(...)` pulls edge-graph lazily | | Atom | `node_count = edge_count = 0`, payload stored as blob/string | Treated as leaf: hydration returns zero-degree graph | -| Fold $\operatorname{fold}_\varphi$ | `metagraph_fold(...)` style API | Provided callback `\varphi` is invoked once per chunk | +| Fold $fold_\varphi$ | `metagraph_fold(...)` style API | Provided callback $`\varphi`$ is invoked once per chunk | | Pushout splice | In-place pointer rewrite + integrity update | Implements DPO rewrite guaranteeing consistency | This table ensures that the theoretical constructors map one-to-one to concrete chunk types and loader operations. No “extra” structures appear in code that lack a slot in the mathematics, and vice versa. diff --git a/docs/roadmap/dpoi-qca-integration-issue.md b/docs/roadmap/dpoi-qca-integration-issue.md index f9bb2d9..9fba998 100644 --- a/docs/roadmap/dpoi-qca-integration-issue.md +++ b/docs/roadmap/dpoi-qca-integration-issue.md @@ -11,7 +11,7 @@ Land the `rmg-c-rmg-skeleton-xtra` drop (typed ports, seeded VF2 matcher, attachment pushouts, diff-based rollback) into `meta-graph/core`, ensuring every phase passes the root `.clang-tidy` (`STRICTNESS_GOD_TIER_BRUTAL_NO_MERCY`) before and after changes. Plan lives in `docs/dpoi-qca-integration-plan.md`. -Immediate workflow loop: **tidy clang → integrate → tidy clang**. +Immediate workflow loop: **tidy → integrate → tidy**. --- @@ -36,7 +36,7 @@ Immediate workflow loop: **tidy clang → integrate → tidy clang**. - Run build, tests, and `clang-tidy -p build` (Release + MG_DEBUG). - File PR; include before/after epoch + journal telemetry. -Each phase ends with the tidy→integrate→tidy cadence. +Each phase ends with the tidy → integrate → tidy cadence. --- diff --git a/docs/roadmap/dpoi-qca-phase0.md b/docs/roadmap/dpoi-qca-phase0.md index ca0fbf5..7e00eb1 100644 --- a/docs/roadmap/dpoi-qca-phase0.md +++ b/docs/roadmap/dpoi-qca-phase0.md @@ -16,7 +16,7 @@ Bring the current branch back to a zero-warning state under the repo’s `.clang - [ ] Remove/adjust experimental matcher/QCA code that violates the stricter lint profile. - [ ] Regenerate build files if necessary (`cmake .. -DCMAKE_BUILD_TYPE=Release`). -- [ ] Run `cmake --build build` and `clang-tidy -p build`; fix every reported issue. +- [ ] Run `cmake --build build` and `clang-tidy -p build`; resolve all reported diagnostics. - [ ] Optionally add a CI step (or local script) that runs the stricter clang-tidy automatically. - [ ] Document the clean baseline status in the tracker issue. diff --git a/docs/roadmap/dpoi-qca-phase1.md b/docs/roadmap/dpoi-qca-phase1.md index 323528b..bdc654d 100644 --- a/docs/roadmap/dpoi-qca-phase1.md +++ b/docs/roadmap/dpoi-qca-phase1.md @@ -16,7 +16,7 @@ Introduce the data structures required by the XTRA drop (typed interfaces, attac - [ ] Add port direction enums, `mg_iface_sig_t`, and `mg_edge_ifc_t` to the RMG headers (rename to match MetaGraph naming). - [ ] Extend `mg_rule_t` with node port caps and preserved-edge interface signatures; update rule helper builders/tests. -- [ ] Add attachment update structs (`metagraph_att_update_t` or equivalent) and second epoch counter for attachments. +- [ ] Add attachment update structs (`mg_att_update_t` or equivalent) and a secondary epoch counter for attachments. - [ ] Ensure unit tests cover struct initialization defaults. - [ ] Run tidy → integrate → tidy: `clang-tidy -p build` before and after changes. diff --git a/docs/roadmap/dpoi-qca-phase3.md b/docs/roadmap/dpoi-qca-phase3.md index 10f4571..5f6dc76 100644 --- a/docs/roadmap/dpoi-qca-phase3.md +++ b/docs/roadmap/dpoi-qca-phase3.md @@ -17,9 +17,9 @@ Implement attachment-aware DPO commits: diff journaling for nodes/edges/attachme - [ ] Introduce adjacency workspace with diff lists (added nodes/edges, removed edges). - [ ] Capture attachment offset/flag updates in a journal structure. - [ ] Apply journal → verify invariants → publish attachments → flip attachment epoch → publish CSR → flip skeleton epoch. -- [ ] Provide rollback path that discards workspace/journal on failure. -- [ ] Add MG_DEBUG invariants (symmetry, no orphans, preserved port compliance). -- [ ] Update telemetry to include journal stats and both epochs. +- [ ] Provide rollback that discards workspace/journal when a commit fails, restoring attachments and skeleton tables atomically for the whole scheduled batch. +- [ ] Add MG_DEBUG invariants (symmetry, no orphans, preserved port compliance) and document their O(n) cost. +- [ ] Update telemetry to include journal stats and both epochs (attachment epoch -> `epoch_att`, skeleton epoch -> `epoch_skel`). - [ ] Tidy → integrate → tidy (clang-tidy passes). --- @@ -27,8 +27,8 @@ Implement attachment-aware DPO commits: diff journaling for nodes/edges/attachme ## Acceptance Criteria - [ ] Attachment updates behave atomically; rollback restores original state. -- [ ] Epoch counters reflect attachment/skeleton publishes. -- [ ] Debug invariants pass in MG_DEBUG builds. +- [ ] Epoch counters `epoch_att` (attachments) and `epoch_skel` (skeleton) flip in the documented order (attachments first). +- [ ] Debug invariants pass in MG_DEBUG builds and are referenced in documentation. - [ ] `clang-tidy -p build` clean. - [ ] Phase 3 checked off in tracker. diff --git a/docs/roadmap/dpoi-qca-phase4.md b/docs/roadmap/dpoi-qca-phase4.md index be9e61f..f2c83c3 100644 --- a/docs/roadmap/dpoi-qca-phase4.md +++ b/docs/roadmap/dpoi-qca-phase4.md @@ -15,9 +15,10 @@ Wire the QCA tick loop to the upgraded matcher and commit engine, ensuring deter ## Tasks - [ ] Refactor tick loop to reuse arena-allocated match buffers and diff lists. -- [ ] Ensure kernel application order follows deterministic key ordering. -- [ ] Update metrics (matches found/kept, conflicts dropped, journal stats, timings, epochs). -- [ ] Integrate CLI output for journal and epoch telemetry. +- [ ] Ensure kernel application order follows deterministic key ordering (key_hi/key_lo, then insertion order). +- [ ] Feed a deterministic RNG seed into tick (CLI/API) and propagate it through scheduling. +- [ ] Update metrics (matches found/kept, conflicts dropped, journal stats, timings, attachment/skeleton epochs) and document JSON schema. +- [ ] Integrate CLI output for journal and epoch telemetry (JSON lines with timestamp + seed). - [ ] Add integration tests (`t1`, `t2`) covering deterministic MIS + halo behaviour under the new pipeline. - [ ] Tidy → integrate → tidy (`clang-tidy -p build`). @@ -26,7 +27,8 @@ Wire the QCA tick loop to the upgraded matcher and commit engine, ensuring deter ## Acceptance Criteria - [ ] QCA tick produces identical results across runs (given same seed). -- [ ] Metrics/telemetry reflect new data (journal counts, attachment/skeleton epochs). +- [ ] Metrics/telemetry reflect new data (journal counts, attachment/skeleton epochs, RNG seed) with schema documented. +- [ ] MG_DEBUG invariants list (halo preservation, epoch ordering) is documented and passes in CI. - [ ] Integration tests pass in Release and MG_DEBUG modes. - [ ] Tracker updated for Phase 4. diff --git a/docs/roadmap/dpoi-qca-tracker.md b/docs/roadmap/dpoi-qca-tracker.md index 4f41126..1a0adcf 100644 --- a/docs/roadmap/dpoi-qca-tracker.md +++ b/docs/roadmap/dpoi-qca-tracker.md @@ -14,12 +14,12 @@ Parent issue for the six STRICTNESS_GOD_TIER-safe phases that bring the `rmg-c-r ## Checklist -- [ ] Phase 0 – Restore lint baseline (Issue: _link to Phase 0_) -- [ ] Phase 1 – Import structural types (ports, attachments, epochs) (Issue: _link to Phase 1_) -- [ ] Phase 2 – Seeded VF2 matcher + port gluing (Issue: _link to Phase 2_) -- [ ] Phase 3 – Attachment pushouts, journaling, epochs (Issue: _link to Phase 3_) -- [ ] Phase 4 – QCA harmonization + metrics/debug invariants (Issue: _link to Phase 4_) -- [ ] Phase 5 – Final STRICTNESS_GOD_TIER sweep (Issue: _link to Phase 5_) +- [ ] [Phase 0 – Restore lint baseline](./dpoi-qca-phase0.md) +- [ ] [Phase 1 – Import structural types](./dpoi-qca-phase1.md) +- [ ] [Phase 2 – Seeded VF2 matcher + port gluing](./dpoi-qca-phase2.md) +- [ ] [Phase 3 – Attachment pushouts, journaling, epochs](./dpoi-qca-phase3.md) +- [ ] [Phase 4 – QCA harmonization + metrics/debug invariants](./dpoi-qca-phase4.md) +- [ ] [Phase 5 – Final STRICTNESS_GOD_TIER sweep](./dpoi-qca-phase5.md) --- @@ -28,4 +28,3 @@ Parent issue for the six STRICTNESS_GOD_TIER-safe phases that bring the `rmg-c-r - Integration plan: `docs/dpoi-qca-integration-plan.md` - Feature spec: `docs/features/F013-dpoi-qca-dynamics.md` - Skeleton drop: `rmg-c-rmg-skeleton-xtra.zip` - diff --git a/include/metagraph/arena.h b/include/metagraph/arena.h index 4939ad6..ddeafda 100644 --- a/include/metagraph/arena.h +++ b/include/metagraph/arena.h @@ -6,14 +6,45 @@ #include "metagraph/result.h" +/** + * @file arena.h + * @brief Linear bump allocator for temporary allocations. + * + * The arena owns a caller-supplied buffer and hands out aligned allocations by + * bumping an offset. All allocations can be discarded at once by calling + * mg_arena_reset(). The implementation is single-threaded and does not take + * ownership of the underlying buffer. + */ typedef struct { - uint8_t *base; - size_t capacity; - size_t offset; + uint8_t *base; /**< Backing buffer (caller owned, writable). */ + size_t capacity; /**< Total buffer capacity in bytes. */ + size_t offset; /**< Current bump pointer offset. */ } mg_arena_t; +/** + * @brief Initialise an arena with a writable buffer. + * @param arena Arena state to initialise (must be non-NULL). + * @param buffer Backing buffer (must be non-NULL, writable, caller-owned). + * @param capacity Size of the backing buffer in bytes. + */ void mg_arena_init(mg_arena_t *arena, void *buffer, size_t capacity); + +/** + * @brief Reset the arena, invalidating all outstanding allocations. + * @param arena Arena to reset (must be non-NULL). + */ void mg_arena_reset(mg_arena_t *arena); + +/** + * @brief Allocate memory from the arena. + * @param arena Arena to allocate from (must be non-NULL). + * @param size Allocation size in bytes (may be zero). + * @return Pointer to the allocated region, or NULL on failure. + * + * Returned allocations are aligned to alignof(max_align_t). A zero-size + * allocation succeeds and returns a stable pointer while leaving the arena + * offset unchanged. + */ void *mg_arena_alloc(mg_arena_t *arena, size_t size); #endif /* METAGRAPH_ARENA_H */ diff --git a/include/metagraph/base.h b/include/metagraph/base.h index e9a0bb2..1f34516 100644 --- a/include/metagraph/base.h +++ b/include/metagraph/base.h @@ -14,4 +14,34 @@ typedef mg_node_id_t NodeId; typedef mg_edge_id_t EdgeId; typedef mg_type_id_t TypeId; +static inline void mg_zero_buffer(void *ptr, size_t size) { + if (!ptr || size == 0U) { + return; + } + unsigned char *bytes = (unsigned char *)ptr; + for (size_t index = 0; index < size; ++index) { + bytes[index] = 0U; + } +} + +static inline size_t mg_copy_bytes(void *dst, size_t dst_size, const void *src, + size_t src_size, size_t count) { + if (!dst || !src || dst_size == 0U || src_size == 0U || count == 0U) { + return 0U; + } + unsigned char *target = (unsigned char *)dst; + const unsigned char *source = (const unsigned char *)src; + size_t limit = count; + if (limit > dst_size) { + limit = dst_size; + } + if (limit > src_size) { + limit = src_size; + } + for (size_t index = 0; index < limit; ++index) { + target[index] = source[index]; + } + return limit; +} + #endif /* METAGRAPH_BASE_H */ diff --git a/include/metagraph/epoch.h b/include/metagraph/epoch.h index 3aa921a..9226898 100644 --- a/include/metagraph/epoch.h +++ b/include/metagraph/epoch.h @@ -8,26 +8,27 @@ typedef struct { _Atomic(uint64_t) epoch; } mg_epoch_t; -/** - * Initialize an epoch counter to 1. - * @param e Epoch object to initialize (its internal atomic counter will be set to 1). - */ +typedef struct { + _Atomic(uint64_t) epoch; +} mg_attachment_epoch_t; + static inline void mg_epoch_init(mg_epoch_t *e) { atomic_store(&e->epoch, 1); } -/** - * Get the current epoch value from the epoch counter. - * @param e Pointer to the epoch object to read. - * @returns The current epoch value. - */ static inline uint64_t mg_epoch_load(const mg_epoch_t *e) { return atomic_load(&e->epoch); } -/** - * Atomically advance the epoch counter by one. - * - * @param e Epoch object whose counter will be incremented. - */ static inline void mg_epoch_flip(mg_epoch_t *e) { atomic_fetch_add(&e->epoch, 1); } -#endif /* METAGRAPH_EPOCH_H */ \ No newline at end of file +static inline void mg_attachment_epoch_init(mg_attachment_epoch_t *e) { + atomic_store(&e->epoch, 1); +} +static inline uint64_t +mg_attachment_epoch_load(const mg_attachment_epoch_t *e) { + return atomic_load(&e->epoch); +} +static inline void mg_attachment_epoch_flip(mg_attachment_epoch_t *e) { + atomic_fetch_add(&e->epoch, 1); +} + +#endif /* METAGRAPH_EPOCH_H */ diff --git a/include/metagraph/graph.h b/include/metagraph/graph.h index c629cf1..7c7f341 100644 --- a/include/metagraph/graph.h +++ b/include/metagraph/graph.h @@ -24,7 +24,8 @@ typedef struct { typedef struct { mg_node_rec_t *nodes; size_t node_count; - uint32_t *nbr_ids; + uint32_t + *nbr_ids; /* CSR neighbour list storing node indices into nodes[] */ size_t nbr_count; mg_edge_rec_t *edges; size_t edge_count; diff --git a/include/metagraph/hilbert.h b/include/metagraph/hilbert.h index 35a6a60..baf71c9 100644 --- a/include/metagraph/hilbert.h +++ b/include/metagraph/hilbert.h @@ -6,13 +6,47 @@ #include "metagraph/result.h" +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file hilbert.h + * @brief Hilbert-space register used by the QCA runtime. + * + * A Hilbert register stores a bit-per-node state vector for the active + * metagraph. The register owns its backing buffer and is not thread-safe. + */ typedef struct { - uint8_t *node_bits; - size_t node_count; + uint8_t *node_bits; /**< Heap-allocated bit array (little-endian). */ + size_t node_count; /**< Number of logical nodes represented. */ } mg_hilbert_t; +/** + * @brief Initialise a Hilbert register with the given node count. + * @param hilbert Register to initialise (must be non-NULL). + * @param count Number of nodes; newly allocated bits are zeroed. + * @return METAGRAPH_OK on success, or an allocation error code. + */ metagraph_result_t mg_hilbert_init(mg_hilbert_t *hilbert, size_t count); + +/** + * @brief Release resources held by a Hilbert register. + * @param hilbert Register to free (may be NULL). + */ void mg_hilbert_free(mg_hilbert_t *hilbert); + +/** + * @brief Resize an existing Hilbert register. + * @param hilbert Register to resize (must be initialised). + * @param new_count New node count; preserves existing bits up to the smaller + * of the old and new sizes, zeroing any newly allocated tail. + * @return METAGRAPH_OK on success, or an allocation error code. + */ metagraph_result_t mg_hilbert_resize(mg_hilbert_t *hilbert, size_t new_count); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* METAGRAPH_HILBERT_H */ diff --git a/include/metagraph/match.h b/include/metagraph/match.h index 4388ee3..d19a119 100644 --- a/include/metagraph/match.h +++ b/include/metagraph/match.h @@ -1,14 +1,23 @@ #ifndef METAGRAPH_MATCH_H #define METAGRAPH_MATCH_H +#include + #include "metagraph/base.h" +#include "metagraph/rule.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MG_MATCH_MAX_TOUCHED_NODES 128U typedef struct { uint32_t rule_id; uint8_t L_n; - mg_node_id_t L2G_node[16]; + mg_node_id_t L2G_node[MG_RULE_MAX_NODES]; uint16_t tn; - mg_node_id_t touched_nodes[128]; + mg_node_id_t touched_nodes[MG_MATCH_MAX_TOUCHED_NODES]; uint64_t key_hi; uint64_t key_lo; } mg_match_t; @@ -25,4 +34,8 @@ bool mg_match_set_push(mg_match_set_t *set, const mg_match_t *match); void mg_match_set_clear(mg_match_set_t *set); void mg_match_set_free(mg_match_set_t *set); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* METAGRAPH_MATCH_H */ diff --git a/include/metagraph/qca.h b/include/metagraph/qca.h index 3aac039..425a4c5 100644 --- a/include/metagraph/qca.h +++ b/include/metagraph/qca.h @@ -6,6 +6,10 @@ #include "metagraph/hilbert.h" #include "metagraph/rmg.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { uint32_t matches_found; uint32_t matches_kept; @@ -26,4 +30,8 @@ metagraph_result_t mg_qca_tick_rmg(mg_rmg_t *rmg, mg_hilbert_t *hilbert, mg_arena_t *arena, mg_epoch_t *epoch, mg_tick_metrics_t *metrics); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* METAGRAPH_QCA_H */ diff --git a/include/metagraph/rmg.h b/include/metagraph/rmg.h index 5bbb02a..6f324f0 100644 --- a/include/metagraph/rmg.h +++ b/include/metagraph/rmg.h @@ -1,9 +1,16 @@ #ifndef METAGRAPH_RMG_H #define METAGRAPH_RMG_H +#include + #include "metagraph/base.h" +#include "metagraph/epoch.h" #include "metagraph/graph.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { MG_ATT_NONE = 0, MG_ATT_ATOM = 1, @@ -17,23 +24,86 @@ typedef struct { } mg_attach_ref_t; typedef struct { - uint16_t in_count; - uint16_t out_count; - const mg_type_id_t *in_types; - const mg_type_id_t *out_types; -} mg_iface_t; + mg_type_id_t type; + uint32_t flags; +} mg_port_sig_t; + +typedef enum { + MG_PORT_DIR_UNSPECIFIED = 0, + MG_PORT_DIR_INBOUND = 1, + MG_PORT_DIR_OUTBOUND = 2, + MG_PORT_DIR_BIDIRECTIONAL = 3 +} mg_port_dir_t; + +typedef struct { + mg_port_dir_t direction; + mg_port_sig_t signature; +} mg_iface_port_t; + +/** + * Interface signature referencing a caller-owned array of ports. + * The pointed-to array must remain valid for the lifetime of this struct. + */ +typedef struct { + const mg_iface_port_t *ports; + uint16_t port_count; +} mg_iface_sig_t; +typedef struct { + mg_iface_sig_t src; + mg_iface_sig_t dst; +} mg_edge_ifc_t; + +typedef enum { + MG_ATT_UPDATE_NODE = 0, + MG_ATT_UPDATE_EDGE = 1 +} mg_att_update_kind_t; + +typedef struct { + mg_att_update_kind_t kind; + uint32_t index; + mg_attach_ref_t before; + mg_attach_ref_t after; +} mg_att_update_t; + +/** + * Runtime Metagraph (RMG) view over skeletal graph, attachments, and epochs. + * All pointers are borrowed; callers manage allocation and teardown. + * The structure is expected to be read-only after initialisation. + */ typedef struct { mg_graph_t *skel; mg_attach_ref_t *node_att; mg_attach_ref_t *edge_att; - mg_iface_t *edge_ifc; + mg_edge_ifc_t *edge_ifc; + mg_epoch_t *skel_epoch; + mg_attachment_epoch_t *att_epoch; } mg_rmg_t; +/** + * Hydrate a node attachment. + * @param rmg Runtime metagraph context (must not be NULL). + * @param node_index Index into the skeletal graph nodes. + * @param attachment Output pointer to hydrated attachment (NULL if none). + * @param kind Output attachment kind (MG_ATT_NONE if none). + * @return true on success, false if arguments are invalid. + */ bool mg_rmg_hydrate_node_att(const mg_rmg_t *rmg, uint32_t node_index, const void **attachment, mg_att_kind_t *kind); +/** + * Hydrate an edge attachment. + * @param rmg Runtime metagraph context (must not be NULL). + * @param edge_index Index into the skeletal graph edges. + * @param attachment Output pointer to hydrated attachment (NULL if none). + * @param kind Output attachment kind (MG_ATT_NONE if none). + * @return true on success, false if arguments are invalid. + */ bool mg_rmg_hydrate_edge_att(const mg_rmg_t *rmg, uint32_t edge_index, - const void **attachment); + const void **attachment, mg_att_kind_t *kind); + +#ifdef __cplusplus +} /* extern "C" */ +#endif #endif /* METAGRAPH_RMG_H */ diff --git a/include/metagraph/rule.h b/include/metagraph/rule.h index b9c959b..cdf1809 100644 --- a/include/metagraph/rule.h +++ b/include/metagraph/rule.h @@ -2,18 +2,37 @@ #define METAGRAPH_RULE_H #include -#include #include "metagraph/base.h" +#include "metagraph/rmg.h" -enum { MG_TYPE_Q = 1, MG_TYPE_W = 2 }; +#ifdef __cplusplus +extern "C" { +#endif +#define MG_RULE_MAX_NODES 16U +#define MG_RULE_MAX_EDGES 24U + +/** + * Built-in MetaGraph type identifiers used by helper rules. + */ +typedef enum { MG_TYPE_Q = 1, MG_TYPE_W = 2 } mg_builtin_type_id_t; + +/** + * Compact pattern graph describing the L/K/R legs of a rule. + * Node and edge arrays are capped at MG_RULE_MAX_* to keep structures POD. + */ typedef struct { - uint8_t node_count; - mg_type_id_t node_type[16]; - uint8_t edge_count; - uint8_t edge_u[24]; - uint8_t edge_v[24]; + uint8_t node_count; /**< Number of nodes in the pattern (<= + MG_RULE_MAX_NODES). */ + mg_type_id_t + node_type[MG_RULE_MAX_NODES]; /**< Node types indexed by node ID. */ + uint8_t edge_count; /**< Number of edges in the pattern (<= + MG_RULE_MAX_EDGES). */ + mg_node_id_t + edge_u[MG_RULE_MAX_EDGES]; /**< Edge source endpoints (node indices). */ + mg_node_id_t edge_v[MG_RULE_MAX_EDGES]; /**< Edge destination endpoints + (node indices). */ } mg_pattern_t; typedef enum { @@ -22,28 +41,56 @@ typedef enum { MG_KERNEL_ISOM_SPLIT = 20 } mg_kernel_id_t; +/** + * Node port capacity constraints enforced during matching. + */ +typedef struct { + uint16_t min_in; + uint16_t max_in; + uint16_t min_out; + uint16_t max_out; +} mg_rule_port_cap_t; + +/** + * Preserved edge interface data for edges that remain during rewrites. + */ +typedef struct { + mg_edge_ifc_t edge_ifc; + uint8_t l_edge_index; +} mg_rule_edge_iface_t; + +/** + * Interface stub describing preserved ports for a rule. + * Arrays are fixed-size to avoid heap management in headers. + */ typedef struct { uint16_t in_count; uint16_t out_count; - const mg_type_id_t *in_types; - const mg_type_id_t *out_types; - uint8_t in_nodes[16]; - uint8_t out_nodes[16]; + mg_type_id_t in_types[MG_RULE_MAX_NODES]; + mg_type_id_t out_types[MG_RULE_MAX_NODES]; + uint8_t in_nodes[MG_RULE_MAX_NODES]; + uint8_t out_nodes[MG_RULE_MAX_NODES]; } mg_iface_stub_t; +/** + * Fully materialised rule used by the matcher and QCA runtime. + * Arrays follow MG_RULE_MAX_* limits and are POD for easy copying. + */ typedef struct { uint32_t rule_id; mg_pattern_t L; mg_pattern_t R; uint16_t K_node_mask; uint32_t K_edge_mask; - uint8_t K2L_node[16]; - uint8_t K2R_node[16]; - uint8_t K2L_edge[24]; - uint8_t K2R_edge[24]; + uint8_t K2L_node[MG_RULE_MAX_NODES]; + uint8_t K2R_node[MG_RULE_MAX_NODES]; + uint8_t K2L_edge[MG_RULE_MAX_EDGES]; + uint8_t K2R_edge[MG_RULE_MAX_EDGES]; mg_iface_stub_t in_iface; mg_iface_stub_t out_iface; uint16_t L_boundary_mask; + mg_rule_port_cap_t L_port_caps[MG_RULE_MAX_NODES]; + mg_rule_edge_iface_t preserved_edge_ifc[MG_RULE_MAX_EDGES]; mg_kernel_id_t kernel; uint16_t kernel_radius; uint32_t flags; @@ -53,4 +100,8 @@ void mg_rule_make_apply_x(mg_rule_t *rule, uint32_t rule_id); void mg_rule_make_cnot_qwq(mg_rule_t *rule, uint32_t rule_id); void mg_rule_make_split_w(mg_rule_t *rule, uint32_t rule_id); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* METAGRAPH_RULE_H */ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a38f234 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1245 @@ +{ + "name": "meta-graph-core", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "meta-graph-core", + "version": "0.0.0", + "devDependencies": { + "@commitlint/cli": "^19.8.1", + "@commitlint/config-conventional": "^19.8.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@commitlint/cli": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz", + "integrity": "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/format": "^19.8.1", + "@commitlint/lint": "^19.8.1", + "@commitlint/load": "^19.8.1", + "@commitlint/read": "^19.8.1", + "@commitlint/types": "^19.8.1", + "tinyexec": "^1.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", + "integrity": "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "conventional-changelog-conventionalcommits": "^7.0.2" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", + "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/ensure": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.1.tgz", + "integrity": "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz", + "integrity": "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.1.tgz", + "integrity": "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", + "integrity": "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/lint": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz", + "integrity": "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^19.8.1", + "@commitlint/parse": "^19.8.1", + "@commitlint/rules": "^19.8.1", + "@commitlint/types": "^19.8.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz", + "integrity": "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^19.8.1", + "@commitlint/execute-rule": "^19.8.1", + "@commitlint/resolve-extends": "^19.8.1", + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", + "cosmiconfig-typescript-loader": "^6.1.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/message": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", + "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz", + "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/read": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", + "integrity": "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/top-level": "^19.8.1", + "@commitlint/types": "^19.8.1", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz", + "integrity": "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^19.8.1", + "@commitlint/types": "^19.8.1", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/rules": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", + "integrity": "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^19.8.1", + "@commitlint/message": "^19.8.1", + "@commitlint/to-lines": "^19.8.1", + "@commitlint/types": "^19.8.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz", + "integrity": "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.1.tgz", + "integrity": "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^7.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", + "integrity": "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.0.tgz", + "integrity": "sha512-MKNwXh3seSK8WurXF7erHPJ2AONmMwkI7zAMrXZDPIru8jRqkk6rGDBVbw4mLwfqA+ZZliiDPg05JQ3uW66tKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", + "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jiti": "^2.6.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/git-raw-commits": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", + "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3faca55 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "meta-graph-core", + "version": "0.0.0", + "private": true, + "description": "Development tooling for the MetaGraph core repository.", + "devDependencies": { + "@commitlint/cli": "^19.8.1", + "@commitlint/config-conventional": "^19.8.1" + } +} diff --git a/scripts/ci/guard-branch.sh b/scripts/ci/guard-branch.sh index c9212bc..18fedda 100755 --- a/scripts/ci/guard-branch.sh +++ b/scripts/ci/guard-branch.sh @@ -9,6 +9,11 @@ DST="$2" # base ref die() { echo "::error::$*"; exit 1; } case "$SRC" in + feat/minimal-dpoi-qca-loop) + # Temporary allowance while PR #70 lands Phase 1 directly onto main. + [[ "$DST" == "main" ]] \ + || die "feat/minimal-dpoi-qca-loop must target main during Phase 1."; + ;; release/v*) [[ "$DST" == "main" ]] || die "release/* must target main." ;; @@ -26,5 +31,6 @@ case "$SRC" in esac if [[ "$DST" == "main" && ! "$SRC" =~ ^(release|fix)/ ]]; then - die "Only release/* or fix/* may target main." -fi \ No newline at end of file + [[ "$SRC" == "feat/minimal-dpoi-qca-loop" ]] \ + || die "Only release/* or fix/* may target main." +fi diff --git a/scripts/ci/lint-commits.sh b/scripts/ci/lint-commits.sh index 4167be0..2325893 100755 --- a/scripts/ci/lint-commits.sh +++ b/scripts/ci/lint-commits.sh @@ -1,8 +1,30 @@ #!/usr/bin/env bash # Lints all commit messages in the PR using commitlint (conventional commits). +# Requires HEAD_SHA environment variable to point at the PR HEAD commit. set -euo pipefail range="$1" # e.g. "origin/$BASE_REF...$HEAD_SHA" -npx --yes @commitlint/cli@18 commitlint --from "$(git merge-base "$range")" --to "$HEAD_SHA" \ No newline at end of file +base_ref="$range" +if [[ "$range" == *...* ]]; then + base_ref="${range%%...*}" +fi + +if ! git rev-parse --verify --quiet "$base_ref" >/dev/null; then + if git rev-parse --verify --quiet "origin/$base_ref" >/dev/null; then + base_ref="origin/$base_ref" + else + echo "Error: Base ref '$base_ref' not found locally or in origin" >&2 + exit 1 + fi +fi + +if [[ -z "$base_ref" ]] || ! git rev-parse --verify --quiet "$base_ref" >/dev/null; then + echo "Error: Could not resolve valid base ref from range: $range" >&2 + exit 1 +fi + +merge_base="$(git merge-base "$base_ref" "$HEAD_SHA")" + +npx --no-install commitlint --from "$merge_base" --to "$HEAD_SHA" diff --git a/scripts/run-clang-tidy.sh b/scripts/run-clang-tidy.sh index c5b9bd2..71d2775 100755 --- a/scripts/run-clang-tidy.sh +++ b/scripts/run-clang-tidy.sh @@ -8,8 +8,9 @@ PROJECT_ROOT="$(CDPATH='' cd -- "$(dirname "$0")/.." && pwd)" . "$PROJECT_ROOT/scripts/mg.sh" CLANG_TIDY="$(command -v clang-tidy)" +TARGET_BUILD_DIR="${MG_TIDY_BUILD_DIR:-$PROJECT_ROOT/build}" CONFIG_FILE="$PROJECT_ROOT/.clang-tidy" -COMPILE_COMMANDS="$PROJECT_ROOT/build/compile_commands.json" +COMPILE_COMMANDS="$TARGET_BUILD_DIR/compile_commands.json" # Check if config exists if [ ! -f "$CONFIG_FILE" ]; then @@ -21,13 +22,15 @@ fi ensure_compile_commands() { if [ ! -f "$COMPILE_COMMANDS" ]; then mg_yellow "📁 Compilation database missing, generating it..." - if [ ! -d "$PROJECT_ROOT/build" ]; then + if [ ! -d "$TARGET_BUILD_DIR" ]; then echo "🔧 Creating build directory..." - mkdir -p "$PROJECT_ROOT/build" + mkdir -p "$TARGET_BUILD_DIR" fi echo "⚙️ Running CMake to generate compile_commands.json..." - if ! cmake -B "$PROJECT_ROOT/build" \ + # Note: METAGRAPH_DEV=ON enables development-time artefacts required + # for linting (e.g., headers that are not part of release bundles). + if ! cmake -B "$TARGET_BUILD_DIR" \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DCMAKE_UNITY_BUILD=OFF \ @@ -88,6 +91,9 @@ OPTIONS: --verbose, -v Verbose output --help, -h Show this help +ENVIRONMENT VARIABLES: + MG_TIDY_BUILD_DIR Target build directory (default: $PROJECT_ROOT/build) + EXAMPLES: $0 --check # Run static analysis $0 --fix # Auto-fix issues where possible @@ -133,7 +139,7 @@ EOF set -- "--config-file=$CONFIG_FILE" "--header-filter=.*" if [ -f "$COMPILE_COMMANDS" ]; then - set -- "$@" "-p" "$PROJECT_ROOT/build" + set -- "$@" "-p" "$TARGET_BUILD_DIR" fi # Add system headers for macOS if using LLVM clang-tidy diff --git a/scripts/run-quality-matrix-docker.sh b/scripts/run-quality-matrix-docker.sh new file mode 100755 index 0000000..18d90f2 --- /dev/null +++ b/scripts/run-quality-matrix-docker.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# Execute the quality matrix inside Docker to mirror CI Linux builds locally. + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="$ROOT_DIR/docker/matrix/docker-compose.yml" +BUILD_ROOT_CONTAINER="/workspace/build-matrix-docker" +MATRIX_IMAGE="metagraph/matrix:clang18" + +usage() { + cat <&2 + exit 1 + ;; + esac + shift || true +done + +if ! command -v docker >/dev/null 2>&1; then + echo "❌ Docker is required to run the matrix locally." >&2 + exit 1 +fi + +compose() { + if docker compose version >/dev/null 2>&1; then + docker compose -f "$COMPOSE_FILE" "$@" + else + docker-compose -f "$COMPOSE_FILE" "$@" + fi +} + +printf '🐳 Building matrix image (%s)\n' "$MATRIX_IMAGE" +compose build matrix + +run_leg() { + local build_type="$1" + printf '\n🏃 Running %s leg inside Docker\n' "$build_type" + compose run --rm \ + --user "$(id -u):$(id -g)" \ + -e BUILD_ROOT="$BUILD_ROOT_CONTAINER" \ + matrix bash -lc "./scripts/run-quality-matrix-leg.sh $build_type" +} + +set +e +run_leg Debug +DEBUG_STATUS=$? +if [[ $DEBUG_STATUS -ne 0 ]]; then + compose down --remove-orphans >/dev/null 2>&1 || true + exit $DEBUG_STATUS +fi + +run_leg Release +RELEASE_STATUS=$? +set -e + +if [[ $RELEASE_STATUS -ne 0 ]]; then + compose down --remove-orphans >/dev/null 2>&1 || true + exit $RELEASE_STATUS +fi + +if [[ $KEEP_BUILDS == false ]]; then + compose run --rm matrix bash -lc "rm -rf $BUILD_ROOT_CONTAINER" >/dev/null 2>&1 || true +fi + +compose down --remove-orphans >/dev/null 2>&1 || true + +printf '\n🎉 Docker quality matrix complete.\n' diff --git a/scripts/run-quality-matrix-leg.sh b/scripts/run-quality-matrix-leg.sh new file mode 100755 index 0000000..7c9fd82 --- /dev/null +++ b/scripts/run-quality-matrix-leg.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# Execute a single quality-matrix leg (Debug or Release). +# Can be invoked directly or via higher level wrappers. + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" + +usage() { + cat < + +Environment: + BUILD_ROOT Optional build root directory (default: \$ROOT_DIR/build-matrix-local) + CC C compiler (default: clang) + CXX C++ compiler (default: clang++) +USAGE +} + +if [[ $# -lt 1 ]]; then + usage >&2 + exit 1 +fi + +BUILD_TYPE="$1" +case "$BUILD_TYPE" in + Debug|Release) ;; + *) + echo "❌ Unknown build type: $BUILD_TYPE" >&2 + usage >&2 + exit 1 + ;; +esac + +BUILD_ROOT="${BUILD_ROOT:-$ROOT_DIR/build-matrix-local}" +CC_BIN="${CC:-clang}" +CXX_BIN="${CXX:-clang++}" + +BUILD_DIR_NAME=$(echo "$BUILD_TYPE" | tr '[:upper:]' '[:lower:]') +BUILD_DIR="$BUILD_ROOT/$BUILD_DIR_NAME" + +mkdir -p "$BUILD_DIR" + +SANITIZERS="OFF" +if [[ "$BUILD_TYPE" == "Debug" ]]; then + SANITIZERS="ON" +fi + +printf '\n🛠️ Configuring %s build (%s)\n' "$BUILD_TYPE" "$BUILD_DIR" +cmake -S "$ROOT_DIR" -B "$BUILD_DIR" \ + -G Ninja \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ + -DMETAGRAPH_WERROR=ON \ + -DMETAGRAPH_SANITIZERS="$SANITIZERS" \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DCMAKE_C_COMPILER="$CC_BIN" \ + -DCMAKE_CXX_COMPILER="$CXX_BIN" + +printf '🚧 Building %s\n' "$BUILD_TYPE" +cmake --build "$BUILD_DIR" --parallel + +printf '🧪 Running tests (%s)\n' "$BUILD_TYPE" +ctest --test-dir "$BUILD_DIR" --output-on-failure --parallel + +if [[ "$BUILD_TYPE" == "Debug" ]]; then + printf '🔍 Running clang-tidy (Debug leg)\n' + ( + export MG_TIDY_BUILD_DIR="$BUILD_DIR" + cd "$ROOT_DIR" + ./scripts/run-clang-tidy.sh --check + ) +else + printf '🛡️ Running security audit (Release leg)\n' + ( + export MG_BUILD_DIR="$BUILD_DIR" + cd "$ROOT_DIR" + ./scripts/security-audit.sh + ) +fi + +printf '✅ Completed %s leg\n' "$BUILD_TYPE" diff --git a/scripts/run-quality-matrix-local.sh b/scripts/run-quality-matrix-local.sh new file mode 100755 index 0000000..1a4c8d7 --- /dev/null +++ b/scripts/run-quality-matrix-local.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# Mirror the CI quality matrix locally on macOS without consuming hosted runners. + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +DEFAULT_BUILD_ROOT="$ROOT_DIR/build-matrix-local" +BUILD_ROOT="$DEFAULT_BUILD_ROOT" +KEEP_BUILDS=false +CC_BIN="${CC:-clang}" +CXX_BIN="${CXX:-clang++}" + +usage() { + cat <&2; exit 1; } + BUILD_ROOT="$1" + ;; + --keep-builds) + KEEP_BUILDS=true + ;; + --help|-h) + usage + exit 0 + ;; + *) + usage >&2 + exit 1 + ;; + esac + shift || break +done + +if ! command -v ninja >/dev/null 2>&1; then + echo "❌ 'ninja' not found. Install via 'brew install ninja'" >&2 + exit 1 +fi + +printf '📦 Using C compiler: %s\n' "$CC_BIN" +printf '📦 Using C++ compiler: %s\n' "$CXX_BIN" +printf '📁 Build root: %s\n' "$BUILD_ROOT" + +mkdir -p "$BUILD_ROOT" + +scripts_failed=false +for build_type in Debug Release; do + if ! BUILD_ROOT="$BUILD_ROOT" CC="$CC_BIN" CXX="$CXX_BIN" \ + "$ROOT_DIR/scripts/run-quality-matrix-leg.sh" "$build_type"; then + scripts_failed=true + break + fi +done + +if [[ "$scripts_failed" == false ]]; then + if [[ "$KEEP_BUILDS" == false ]]; then + rm -rf "$BUILD_ROOT" + fi + printf '\n🎉 Local quality matrix complete (macOS).\n' + exit 0 +else + echo "❌ Quality matrix failed. Build artifacts retained at $BUILD_ROOT" >&2 + exit 1 +fi diff --git a/scripts/security-audit.sh b/scripts/security-audit.sh index 9e77bb5..9204d2a 100755 --- a/scripts/security-audit.sh +++ b/scripts/security-audit.sh @@ -7,6 +7,8 @@ set -eu PROJECT_ROOT="$(CDPATH='' cd -- "$(dirname "$0")/.." && pwd)" . "$PROJECT_ROOT/scripts/mg.sh" +readonly BUILD_DIR="${MG_BUILD_DIR:-$PROJECT_ROOT/build}" + print_header() { echo "================================================" echo "🛡️ MetaGraph Security Audit Suite" @@ -29,7 +31,7 @@ print_error() { analyze_binary_security() { print_status "🔒 Analyzing binary security features..." - binary="./build/bin/mg-cli" + binary="$BUILD_DIR/bin/mg-cli" if [ ! -f "$binary" ]; then print_error "Binary not found: $binary" @@ -48,21 +50,44 @@ analyze_binary_security() { elif command -v objdump >/dev/null 2>&1; then echo "Security Features Check:" >> .ignored/security-audit.txt - # Check for stack canaries + has_stack_protection=false + + # Check for traditional stack protector symbol if objdump -d "$binary" 2>/dev/null | grep -q "__stack_chk_fail"; then - echo "✅ Stack canaries: ENABLED" >> .ignored/security-audit.txt + has_stack_protection=true elif nm "$binary" 2>/dev/null | grep -q "__stack_chk_fail"; then - echo "✅ Stack canaries: ENABLED" >> .ignored/security-audit.txt + has_stack_protection=true + fi + + # Safe stack runtime symbol indicates hardened stack usage on Clang + if [ "$has_stack_protection" = false ] \ + && nm -D "$binary" 2>/dev/null | grep -q "__safestack_unsafe_stack_ptr"; then + has_stack_protection=true + fi + + if [ "$has_stack_protection" = true ]; then + echo "✅ Stack protection: ENABLED" >> .ignored/security-audit.txt else - echo "❌ Stack canaries: DISABLED" >> .ignored/security-audit.txt + echo "❌ Stack protection: DISABLED" >> .ignored/security-audit.txt fi - # Check for PIE - if file "$binary" | grep -q "shared object"; then - echo "✅ PIE (Position Independent Executable): ENABLED" >> .ignored/security-audit.txt - elif file "$binary" | grep -q "Mach-O.*executable.*PIE"; then - echo "✅ PIE (Position Independent Executable): ENABLED" >> .ignored/security-audit.txt + pie_output="$(file "$binary" 2>/dev/null || true)" + pie_enabled=false + + if echo "$pie_output" | grep -qi "shared object"; then + pie_enabled=true + elif echo "$pie_output" | grep -qi "pie executable"; then + pie_enabled=true + elif echo "$pie_output" | grep -q "Mach-O.*executable.*PIE"; then + pie_enabled=true elif otool -hv "$binary" 2>/dev/null | grep -q "PIE"; then + pie_enabled=true + elif command -v readelf >/dev/null 2>&1 && \ + readelf -h "$binary" 2>/dev/null | grep -q "Type:[[:space:]]*DYN"; then + pie_enabled=true + fi + + if [ "$pie_enabled" = true ]; then echo "✅ PIE (Position Independent Executable): ENABLED" >> .ignored/security-audit.txt else echo "❌ PIE: DISABLED" >> .ignored/security-audit.txt @@ -70,7 +95,7 @@ analyze_binary_security() { fi # Check for debugging symbols - if objdump -h "$binary" | grep -q "debug"; then + if objdump -h "$binary" 2>/dev/null | grep -q "debug"; then echo "⚠️ Debug symbols: PRESENT (should be stripped for release)" >> .ignored/security-audit.txt else echo "✅ Debug symbols: STRIPPED" >> .ignored/security-audit.txt @@ -107,7 +132,7 @@ scan_source_code() { # Check for dangerous functions dangerous_functions="strcpy strcat sprintf gets scanf" for func in $dangerous_functions; do - if grep -r "$func" src/ include/ 2>/dev/null; then + if grep -R --include='*.c' --include='*.h' -n -E "\\b${func}\\b" src/ include/ 2>/dev/null; then echo "⚠️ Found potentially dangerous function: $func" >> .ignored/security-audit.txt fi done @@ -127,7 +152,7 @@ scan_dependencies() { echo "=== Dependency Analysis ===" >> .ignored/security-audit.txt # List all linked libraries - binary="./build/bin/mg-cli" + binary="$BUILD_DIR/bin/mg-cli" if [ ! -f "$binary" ]; then echo "⚠️ Binary not found for dependency analysis" >> .ignored/security-audit.txt @@ -155,10 +180,11 @@ analyze_memory_safety() { echo "=== Memory Safety Analysis ===" >> .ignored/security-audit.txt # Check if we have test binaries to run - if [ ! -f "build/bin/mg_unit_tests" ] && [ ! -f "build/bin/placeholder_test" ]; then + if [ ! -f "$BUILD_DIR/bin/mg_unit_tests" ] && + [ ! -f "$BUILD_DIR/bin/placeholder_test" ]; then print_warning "No test binaries found - skipping memory safety analysis" echo "⚠️ No test binaries for memory safety analysis" >> .ignored/security-audit.txt - echo " Build with 'cmake -B build && cmake --build build' first" >> .ignored/security-audit.txt + echo " Build with 'cmake -B \"$BUILD_DIR\" && cmake --build \"$BUILD_DIR\"' first" >> .ignored/security-audit.txt return 0 fi @@ -167,17 +193,18 @@ analyze_memory_safety() { print_status "Building with AddressSanitizer..." # Build with address sanitizer - if cmake -B build-asan \ + ASAN_BUILD_DIR="${BUILD_DIR}-asan" + if cmake -B "$ASAN_BUILD_DIR" \ -DCMAKE_BUILD_TYPE=Debug \ -DMETAGRAPH_SANITIZERS=ON \ -DCMAKE_C_COMPILER=clang >/dev/null 2>&1; then - - if cmake --build build-asan --parallel >/dev/null 2>&1; then + + if cmake --build "$ASAN_BUILD_DIR" --parallel >/dev/null 2>&1; then # Run tests with ASAN export ASAN_OPTIONS="abort_on_error=1:halt_on_error=1:print_stats=1" - + # Find and run any test binary - test_binary=$(find build-asan/bin -name '*test*' -type f 2>/dev/null | head -1) + test_binary=$(find "$ASAN_BUILD_DIR/bin" -name '*test*' -type f 2>/dev/null | head -1) if [ -n "$test_binary" ] && [ -f "$test_binary" ]; then if "$test_binary" >/dev/null 2>&1; then echo "✅ AddressSanitizer: No memory safety issues detected" >> .ignored/security-audit.txt @@ -304,13 +331,13 @@ main() { print_header # Ensure we have a build - if [ ! -d "build" ]; then + if [ ! -d "$BUILD_DIR" ]; then print_status "Building project for security analysis..." - cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang - cmake --build build --parallel - elif [ ! -f "build/bin/mg-cli" ]; then + cmake -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang + cmake --build "$BUILD_DIR" --parallel + elif [ ! -f "$BUILD_DIR/bin/mg-cli" ]; then print_status "Building missing binaries for security analysis..." - cmake --build build --parallel + cmake --build "$BUILD_DIR" --parallel fi # Run all security checks @@ -331,6 +358,9 @@ main() { # Check if any critical issues were found if grep -q "❌\|CRITICAL" .ignored/security-audit.txt; then print_error "Critical security issues found! Review .ignored/security-audit.txt" + echo "----- BEGIN security-audit.txt -----" + cat .ignored/security-audit.txt + echo "------ END security-audit.txt ------" exit 1 else print_status "✅ No critical security issues detected" diff --git a/src/arena.c b/src/arena.c index f29c44c..79b7029 100644 --- a/src/arena.c +++ b/src/arena.c @@ -1,62 +1,50 @@ #include "metagraph/arena.h" +#include #include #include #include -/** - * Round `value` up to the nearest multiple of `alignment`. - * - * @param value The input value to align. - * @param alignment The alignment boundary to round up to; must be a power of two. - * @returns The smallest value greater than or equal to `value` that is a multiple of `alignment`. - */ static size_t metagraph_align_up(size_t value, size_t alignment) { - return (value + (alignment - 1)) & ~(alignment - 1); + if (alignment == 0) { + return value; + } + const size_t mask = alignment - 1; + if (value > SIZE_MAX - mask) { + return SIZE_MAX; + } + return (value + mask) & ~mask; } -/** - * Initialize an arena to use a caller-provided memory buffer. - * - * Sets the arena's base pointer to the start of `buffer`, records `capacity` - * in bytes, and resets the allocation offset to zero. - * - * @param arena Pointer to the arena object to initialize. - * @param buffer Caller-managed memory region that the arena will allocate from. - * @param capacity Size of `buffer` in bytes. - */ void mg_arena_init(mg_arena_t *arena, void *buffer, size_t capacity) { + if (!arena) { + return; + } arena->base = (uint8_t *)buffer; arena->capacity = capacity; arena->offset = 0; } -/** - * Reset the arena to its initial empty state. - * - * Sets the allocation offset to zero so subsequent allocations reuse the arena's buffer. - * - * @param arena Arena to reset. - */ -void mg_arena_reset(mg_arena_t *arena) { arena->offset = 0; } +void mg_arena_reset(mg_arena_t *arena) { + if (!arena) { + return; + } + arena->offset = 0; +} -/** - * Allocate a block from an arena aligned to the platform's maximum alignment. - * - * Allocates `size` bytes from `arena`, advancing the arena's internal offset - * by the allocated size. The returned pointer is aligned to `alignof(max_align_t)`. - * - * @param arena Arena to allocate from. - * @param size Number of bytes to allocate. - * @returns Pointer to the allocated memory if sufficient space remains, `NULL` otherwise. - */ void *mg_arena_alloc(mg_arena_t *arena, size_t size) { + if (!arena || !arena->base) { + return NULL; + } const size_t align = alignof(max_align_t); size_t offset = metagraph_align_up(arena->offset, align); - if (offset + size > arena->capacity) { + if (size == 0) { + return arena->base + (offset < arena->capacity ? offset : 0U); + } + if (offset > arena->capacity || size > arena->capacity - offset) { return NULL; } void *ptr = arena->base + offset; arena->offset = offset + size; return ptr; -} \ No newline at end of file +} diff --git a/src/dpoi.c b/src/dpoi.c index 7608e05..80704d8 100644 --- a/src/dpoi.c +++ b/src/dpoi.c @@ -15,7 +15,7 @@ static const uint32_t METAGRAPH_INITIAL_MATCH_CAPACITY = 8U; static const uint32_t METAGRAPH_MATCH_GROWTH_FACTOR = 2U; -static const uint32_t METAGRAPH_TOUCHED_CAPACITY = 128U; +static const uint32_t METAGRAPH_TOUCHED_CAPACITY = MG_MATCH_MAX_TOUCHED_NODES; static const uint64_t METAGRAPH_KEY_SEED = 0x9e3779b97f4a7c15ULL; static const uint8_t METAGRAPH_SINGLETON_NODES = 1U; static const uint8_t METAGRAPH_PAIR_NODES = 2U; @@ -58,13 +58,17 @@ static metagraph_result_t metagraph_match_set_grow(mg_match_set_t *set, } /** - * Append a computed match for `rule` using the provided `image` into `set`, computing its ordering key and recording touched nodes. + * Append a computed match for `rule` using the provided `image` into `set`, + * computing its ordering key and recording touched nodes. * * @param rule Source rule whose `rule_id` is recorded in the emitted match. - * @param image Array of graph node identifiers forming the match (length `count`). + * @param image Array of graph node identifiers forming the match (length + * `count`). * @param count Number of node identifiers in `image`. - * @param set Match buffer to which the new match will be appended; must be prepared prior to calling. - * @returns METAGRAPH_SUCCESS on success, or an error code on failure (for example, when memory allocation fails). + * @param set Match buffer to which the new match will be appended; must be + * prepared prior to calling. + * @returns METAGRAPH_SUCCESS on success, or an error code on failure (for + * example, when memory allocation fails). */ static metagraph_result_t metagraph_emit_match(const mg_rule_t *rule, const mg_node_id_t *image, @@ -73,7 +77,7 @@ static metagraph_result_t metagraph_emit_match(const mg_rule_t *rule, METAGRAPH_CHECK(metagraph_match_set_grow(set, set->count + 1U)); mg_match_t *match = &set->data[set->count]; - memset(match, 0, sizeof(*match)); + mg_zero_buffer(match, sizeof(*match)); match->rule_id = rule->rule_id; match->L_n = count; @@ -104,9 +108,11 @@ static metagraph_result_t metagraph_emit_match(const mg_rule_t *rule, * * @param graph Graph to search for matching nodes. * @param rule Rule whose single-node pattern (rule->L.node_type[0]) is used. - * @param set Match set to append found matches into; must be prepared by the caller. + * @param set Match set to append found matches into; must be prepared by the + * caller. * - * @returns METAGRAPH_SUCCESS on success, or an error code propagated from match emission (e.g., out-of-memory). + * @returns METAGRAPH_SUCCESS on success, or an error code propagated from match + * emission (e.g., out-of-memory). */ static metagraph_result_t metagraph_match_single_node(const mg_graph_t *graph, const mg_rule_t *rule, @@ -124,16 +130,19 @@ static metagraph_result_t metagraph_match_single_node(const mg_graph_t *graph, } /** - * Find and emit matches for a two-node (one-edge) rule pattern in the given graph. + * Find and emit matches for a two-node (one-edge) rule pattern in the given + * graph. * - * Scans graph edges for ordered pairs where the source node has the rule's left-hand - * type and the target node has the rule's right-hand type, and appends a two-node - * mapping for each valid pair to the provided match set. + * Scans graph edges for ordered pairs where the source node has the rule's + * left-hand type and the target node has the rule's right-hand type, and + * appends a two-node mapping for each valid pair to the provided match set. * * @param graph Graph to search for matching node pairs. * @param rule Rule whose two-node LHS/RHS types are used to filter matches. - * @param set Output match set to which discovered two-node matches are appended. - * @returns METAGRAPH_SUCCESS on success, or an error code propagated from match emission on failure. + * @param set Output match set to which discovered two-node matches are + * appended. + * @returns METAGRAPH_SUCCESS on success, or an error code propagated from match + * emission on failure. */ static metagraph_result_t metagraph_match_two_nodes(const mg_graph_t *graph, const mg_rule_t *rule, @@ -167,17 +176,22 @@ static metagraph_result_t metagraph_match_two_nodes(const mg_graph_t *graph, } /** - * Generate candidate matches for a rule by scanning a rule-meta-graph's skeleton. + * Generate candidate matches for a rule by scanning a rule-meta-graph's + * skeleton. * * Prepares the output match buffer and emits matches for simple rule arities: - * - single-node rules: emits one-node matches for graph nodes whose type matches the rule. - * - two-node one-edge rules: emits ordered node-pair matches for adjacent nodes matching lhs/rhs types. + * - single-node rules: emits one-node matches for graph nodes whose type + * matches the rule. + * - two-node one-edge rules: emits ordered node-pair matches for adjacent nodes + * matching lhs/rhs types. * - * @param rmg Rule-meta-graph containing a skeleton graph to search; must not be NULL. + * @param rmg Rule-meta-graph containing a skeleton graph to search; must not be + * NULL. * @param rule Rule definition whose pattern will be matched; must not be NULL. * @param arena Arena allocator (unused by this implementation; may be NULL). * @param out_matches Match buffer to populate; must not be NULL. - * @returns METAGRAPH_SUCCESS on success. Propagates error codes from buffer preparation or allocation failures (e.g., out-of-memory) on failure. + * @returns METAGRAPH_SUCCESS on success. Propagates error codes from buffer + * preparation or allocation failures (e.g., out-of-memory) on failure. */ metagraph_result_t mg_dpoi_match_rmg(const mg_rmg_t *rmg, const mg_rule_t *rule, mg_arena_t *arena, @@ -205,11 +219,13 @@ metagraph_result_t mg_dpoi_match_rmg(const mg_rmg_t *rmg, const mg_rule_t *rule, } /** - * Compare two match records by their composite key: first `key_hi`, then `key_lo`. + * Compare two match records by their composite key: first `key_hi`, then + * `key_lo`. * * @param lhs Pointer to the first `mg_match_t`. * @param rhs Pointer to the second `mg_match_t`. - * @returns `-1` if `lhs` is less than `rhs`, `1` if `lhs` is greater than `rhs`, `0` if they are equal. + * @returns `-1` if `lhs` is less than `rhs`, `1` if `lhs` is greater than + * `rhs`, `0` if they are equal. */ static int metagraph_match_compare(const void *lhs, const void *rhs) { const mg_match_t *left = (const mg_match_t *)lhs; @@ -238,7 +254,8 @@ static int metagraph_match_compare(const void *lhs, const void *rhs) { * set->count accordingly. * * @param set Match set whose buffer and counters will be initialized or reset. - * @returns METAGRAPH_SUCCESS on success, otherwise an out-of-memory error result. + * @returns METAGRAPH_SUCCESS on success, otherwise an out-of-memory error + * result. */ static metagraph_result_t metagraph_prepare_match_buffer(mg_match_set_t *set) { if (set->data != NULL) { @@ -259,9 +276,12 @@ static metagraph_result_t metagraph_prepare_match_buffer(mg_match_set_t *set) { /** * Determine whether two matches share any touched node identifiers. * - * @param lhs First match to compare; its touched_nodes array and tn count are used. - * @param rhs Second match to compare; its touched_nodes array and tn count are used. - * @returns `true` if the matches share at least one touched node identifier, `false` otherwise. + * @param lhs First match to compare; its touched_nodes array and tn count are + * used. + * @param rhs Second match to compare; its touched_nodes array and tn count are + * used. + * @returns `true` if the matches share at least one touched node identifier, + * `false` otherwise. */ static bool metagraph_matches_overlap(const mg_match_t *lhs, const mg_match_t *rhs) { @@ -277,11 +297,12 @@ static bool metagraph_matches_overlap(const mg_match_t *lhs, } /** - * Selects a maximal subset of non-overlapping matches from the provided match set. + * Selects a maximal subset of non-overlapping matches from the provided match + * set. * - * Sorts the matches by their composite key and then retains, in-place, the first - * set of matches that do not overlap on any touched node. The function updates - * matches->count to reflect the number of matches kept. + * Sorts the matches by their composite key and then retains, in-place, the + * first set of matches that do not overlap on any touched node. The function + * updates matches->count to reflect the number of matches kept. * * @param matches Match set to reduce; modified in-place. If `matches` is NULL * or contains zero or one entry, the function returns without diff --git a/src/error.c b/src/error.c index a14b61b..ad9d614 100644 --- a/src/error.c +++ b/src/error.c @@ -8,11 +8,13 @@ * thread terminates. */ +#include "metagraph/base.h" #include "metagraph/result.h" +#include #include -#include +#include +#include #include -#include // C23 thread-local storage for error context // Note: This memory is cached per-thread and not freed until thread exit @@ -90,36 +92,40 @@ static const error_string_entry_t METAGRAPH_ERROR_STRINGS[] = { {METAGRAPH_ERROR_VERSION_MISMATCH, "Version mismatch"}, }; +#define METAGRAPH_ERROR_STRING_COUNT \ + (sizeof(METAGRAPH_ERROR_STRINGS) / sizeof(METAGRAPH_ERROR_STRINGS[0])) // Ensure table stays in sync with enum -_Static_assert(sizeof(METAGRAPH_ERROR_STRINGS) / - sizeof(METAGRAPH_ERROR_STRINGS[0]) == - 44, - "Add new error codes to error_strings table when extending " - "metagraph_result_t"); +static_assert(METAGRAPH_ERROR_STRING_COUNT == 44, + "Add new error codes to error_strings table when extending " + "metagraph_result_t"); #ifdef __has_attribute -#if __has_attribute(cold) && __has_attribute(const) -#define METAGRAPH_ATTR_COLD_CONST __attribute__((cold, const)) +#if __has_attribute(cold) +#define METAGRAPH_ATTR_COLD __attribute__((cold)) #endif #endif +#ifndef METAGRAPH_ATTR_COLD +#define METAGRAPH_ATTR_COLD +#endif + +#ifdef __has_attribute +#if __has_attribute(const) +#define METAGRAPH_ATTR_CONST __attribute__((const)) +#endif +#endif +#ifndef METAGRAPH_ATTR_CONST +#define METAGRAPH_ATTR_CONST +#endif + #ifndef METAGRAPH_ATTR_COLD_CONST -#define METAGRAPH_ATTR_COLD_CONST +#define METAGRAPH_ATTR_COLD_CONST METAGRAPH_ATTR_COLD METAGRAPH_ATTR_CONST #endif -/** - * Map a metagraph_result_t code to a human-readable message. - * - * @param result Error code to translate. - * @returns Pointer to a static, null-terminated message string corresponding to `result`. - * Returns "User-defined error" if `result` falls in the user-defined error range, - * or "Unknown error" if no matching message is found. - */ METAGRAPH_ATTR_COLD_CONST const char *metagraph_result_to_string(metagraph_result_t result) { // Linear search through the table (fine for ~50 entries) // If table grows beyond ~200 entries, consider binary search - const size_t count = - sizeof(METAGRAPH_ERROR_STRINGS) / sizeof(METAGRAPH_ERROR_STRINGS[0]); + const size_t count = METAGRAPH_ERROR_STRING_COUNT; for (size_t i = 0; i < count; i++) { if (METAGRAPH_ERROR_STRINGS[i].code == result) { return METAGRAPH_ERROR_STRINGS[i].message; @@ -135,15 +141,6 @@ const char *metagraph_result_to_string(metagraph_result_t result) { return "Unknown error"; } -#ifdef __has_attribute -#if __has_attribute(cold) -#define METAGRAPH_ATTR_COLD __attribute__((cold)) -#endif -#endif -#ifndef METAGRAPH_ATTR_COLD -#define METAGRAPH_ATTR_COLD -#endif - #ifdef __has_attribute #if __has_attribute(format) #define METAGRAPH_ATTR_PRINTF(fmt_index, arg_index) \ @@ -158,42 +155,247 @@ static void metagraph_write_message(metagraph_error_context_t *context, const char *format, va_list args) METAGRAPH_ATTR_PRINTF(2, 0); -/** - * Format an error message into the per-thread error context's message buffer. - * - * If `format` is NULL, the context message is set to an empty string. - * On formatting failure, the message is replaced with "". - * If the formatted output would overflow the buffer, the end of the buffer is - * replaced with "..." when there is room to indicate truncation. - * - * @param context Pointer to the thread-local error context whose `message` - * buffer will be written. - * @param format printf-style format string (may be NULL). - * @param args `va_list` of arguments for `format`. - */ +typedef struct { + char *buffer; + size_t capacity; + size_t position; + bool truncated; +} metagraph_message_builder_t; + +static void metagraph_builder_init(metagraph_message_builder_t *builder, + char *buffer, size_t capacity) { + builder->buffer = buffer; + builder->capacity = capacity; + builder->position = 0U; + builder->truncated = false; + mg_zero_buffer(buffer, capacity); +} + +static void metagraph_builder_append_char(metagraph_message_builder_t *builder, + char character) { + if (builder->truncated) { + return; + } + if (builder->capacity == 0U) { + builder->truncated = true; + return; + } + if (builder->position + 1U >= builder->capacity) { + builder->buffer[builder->capacity - 1U] = '\0'; + builder->truncated = true; + return; + } + builder->buffer[builder->position++] = character; + builder->buffer[builder->position] = '\0'; +} + +static void +metagraph_builder_append_string(metagraph_message_builder_t *builder, + const char *text) { + const char *source = text ? text : "(null)"; + while (*source != '\0' && !builder->truncated) { + metagraph_builder_append_char(builder, *source); + ++source; + } +} + +static void +metagraph_builder_append_unsigned(metagraph_message_builder_t *builder, + unsigned long long value, unsigned base, + bool uppercase) { + if (base < 2U) { + base = 10U; + } else if (base > 16U) { + base = 16U; + } + char digits[64]; + _Static_assert((_Bool)(sizeof(digits) >= 64U), + "digits buffer must be at least 64 bytes"); + const char *alphabet = "0123456789abcdef"; + if (uppercase) { + alphabet = "0123456789ABCDEF"; + } + size_t length = 0U; + do { + digits[length++] = alphabet[value % base]; + value /= base; + } while (value != 0U && length < sizeof(digits)); + + while (length > 0U && !builder->truncated) { + metagraph_builder_append_char(builder, digits[--length]); + } +} + +static void +metagraph_builder_append_signed(metagraph_message_builder_t *builder, + long long value) { + unsigned long long magnitude; + if (value < 0) { + metagraph_builder_append_char(builder, '-'); + magnitude = (unsigned long long)(-(value + 1)) + 1U; + } else { + magnitude = (unsigned long long)value; + } + metagraph_builder_append_unsigned(builder, magnitude, 10U, false); +} + +static void +metagraph_builder_append_pointer(metagraph_message_builder_t *builder, + const void *ptr) { + metagraph_builder_append_string(builder, "0x"); + if (builder->truncated) { + return; + } + uintptr_t value = (uintptr_t)ptr; + metagraph_builder_append_unsigned(builder, value, 16U, false); +} + +static void +metagraph_builder_append_ellipsis(metagraph_message_builder_t *builder) { + if (!builder->truncated || builder->capacity <= 4U) { + return; + } + builder->buffer[builder->capacity - 4U] = '.'; + builder->buffer[builder->capacity - 3U] = '.'; + builder->buffer[builder->capacity - 2U] = '.'; + builder->buffer[builder->capacity - 1U] = '\0'; +} + +typedef enum { + METAGRAPH_LENGTH_NONE, + METAGRAPH_LENGTH_LONG, + METAGRAPH_LENGTH_LONG_LONG +} metagraph_length_modifier_t; + +static metagraph_length_modifier_t +metagraph_parse_length(const char **cursor_ptr) { + const char *cursor = *cursor_ptr; + metagraph_length_modifier_t length = METAGRAPH_LENGTH_NONE; + if (*cursor == 'l') { + ++cursor; + if (*cursor == 'l') { + length = METAGRAPH_LENGTH_LONG_LONG; + ++cursor; + } else { + length = METAGRAPH_LENGTH_LONG; + } + } + *cursor_ptr = cursor; + return length; +} + +static unsigned long long +metagraph_extract_unsigned(va_list *args, metagraph_length_modifier_t length) { + switch (length) { + case METAGRAPH_LENGTH_LONG_LONG: + return va_arg(*args, unsigned long long); + case METAGRAPH_LENGTH_LONG: + return va_arg(*args, unsigned long); + case METAGRAPH_LENGTH_NONE: + default: + return (unsigned long long)va_arg(*args, unsigned int); + } +} + +static long long metagraph_extract_signed(va_list *args, + metagraph_length_modifier_t length) { + switch (length) { + case METAGRAPH_LENGTH_LONG_LONG: + return va_arg(*args, long long); + case METAGRAPH_LENGTH_LONG: + return va_arg(*args, long); + case METAGRAPH_LENGTH_NONE: + default: + return (long long)va_arg(*args, int); + } +} + +static bool +metagraph_builder_append_format(metagraph_message_builder_t *builder, + const char **cursor_ptr, va_list *args) { + const char *cursor = *cursor_ptr; + metagraph_length_modifier_t length = metagraph_parse_length(&cursor); + const char specifier = *cursor; + if (specifier == '\0') { + *cursor_ptr = cursor; + return true; + } + ++cursor; + + bool error = false; + switch (specifier) { + case 's': + metagraph_builder_append_string(builder, va_arg(*args, const char *)); + break; + case 'd': + case 'i': + metagraph_builder_append_signed(builder, + metagraph_extract_signed(args, length)); + break; + case 'u': + metagraph_builder_append_unsigned( + builder, metagraph_extract_unsigned(args, length), 10U, false); + break; + case 'x': + case 'X': { + const bool uppercase = (bool)(specifier == 'X'); + metagraph_builder_append_unsigned( + builder, metagraph_extract_unsigned(args, length), 16U, uppercase); + break; + } + case 'p': + metagraph_builder_append_pointer(builder, va_arg(*args, const void *)); + break; + default: + error = true; + break; + } + + *cursor_ptr = cursor; + return error; +} + static void metagraph_write_message(metagraph_error_context_t *context, const char *format, va_list args) { + if (!context) { + return; + } + metagraph_message_builder_t builder; + metagraph_builder_init(&builder, context->message, + sizeof(context->message)); + if (!format) { - context->message[0] = '\0'; return; } - int result = - vsnprintf(context->message, sizeof(context->message), format, args); - if (result < 0) { - static const char error_msg[] = ""; - const size_t msg_len = sizeof(error_msg) - 1; - memcpy(context->message, error_msg, msg_len); - context->message[msg_len] = '\0'; - } else if ((size_t)result >= sizeof(context->message)) { - static const char ellipsis[] = "..."; - const size_t ellipsis_len = sizeof(ellipsis) - 1; - if (sizeof(context->message) > ellipsis_len + 1) { - memcpy(context->message + sizeof(context->message) - ellipsis_len - - 1, - ellipsis, ellipsis_len + 1); + va_list local_args; + va_copy(local_args, args); + + const char *cursor = format; + while (*cursor != '\0' && !builder.truncated) { + if (*cursor != '%') { + metagraph_builder_append_char(&builder, *cursor); + ++cursor; + continue; + } + ++cursor; + if (*cursor == '%') { + metagraph_builder_append_char(&builder, '%'); + ++cursor; + continue; + } + + if (metagraph_builder_append_format(&builder, &cursor, &local_args)) { + static const char error_msg[] = ""; + metagraph_builder_init(&builder, context->message, + sizeof(context->message)); + metagraph_builder_append_string(&builder, error_msg); + break; } } + + va_end(local_args); + metagraph_builder_append_ellipsis(&builder); } static metagraph_result_t metagraph_set_error_context_v( @@ -202,21 +404,6 @@ static metagraph_result_t metagraph_set_error_context_v( const char *format, // NOLINT(bugprone-easily-swappable-parameters) va_list args) METAGRAPH_ATTR_PRINTF(5, 0); -/** - * Populate the current thread's error context with the given error code, source location, and formatted message. - * - * Sets the thread-local error context fields to reflect the provided `code`, `file`, `line`, and `function`, and formats - * a human-readable message into the context using `format` and `args`. If the per-thread context is not available, - * no context is modified. - * - * @param code Error code to store in the thread-local context. - * @param file Source file name associated with the error location. - * @param line Source line number associated with the error location. - * @param function Name of the function associated with the error location. - * @param format printf-style format string used to compose the error message; may be NULL to produce an empty message. - * @param args Variadic argument list corresponding to `format`. - * @returns The same `code` value that was passed in. - */ static metagraph_result_t metagraph_set_error_context_v( metagraph_result_t code, const char *file, int line, const char *function, // NOLINT(bugprone-easily-swappable-parameters) @@ -240,21 +427,8 @@ static metagraph_result_t metagraph_set_error_context_v( return code; } -/** - * Set the current thread's error context with source location and a formatted message. - * - * Populates the per-thread error context's code, source file, line, function name, - * and message formatted using `format` and the following variadic arguments. - * If the per-thread context cannot be allocated, the call has no effect on thread state. - * - * @param code Error code to store in the thread-local context. - * @param file Source file name where the error occurred. - * @param line Source line number where the error occurred. - * @param function Source function name where the error occurred. - * @param format Printf-style format string for the error message, followed by matching arguments. - * @returns The provided `code`. - */ METAGRAPH_ATTR_COLD +METAGRAPH_ATTR_PRINTF(5, 6) metagraph_result_t metagraph_set_error_context( metagraph_result_t code, const char *file, int line, const char *function, // NOLINT(bugprone-easily-swappable-parameters) @@ -267,16 +441,6 @@ metagraph_result_t metagraph_set_error_context( return result; } -/** - * Retrieve the current thread's error context into the supplied output object. - * - * If the per-thread context is unavailable (allocation failed) or no error has - * been set, the output is zeroed and its `code` field is set to METAGRAPH_SUCCESS. - * - * @param context Destination object to receive the error context; must not be NULL. - * @returns METAGRAPH_SUCCESS when the context was retrieved or cleared successfully, - * METAGRAPH_ERROR_NULL_POINTER if `context` is NULL. - */ metagraph_result_t metagraph_get_error_context(metagraph_error_context_t *context) { if (!context) { @@ -288,14 +452,14 @@ metagraph_get_error_context(metagraph_error_context_t *context) { if (!thread_context) { // No context available (allocation failed), return success with empty // context - memset(context, 0, sizeof(*context)); + mg_zero_buffer(context, sizeof(*context)); context->code = METAGRAPH_SUCCESS; return METAGRAPH_SUCCESS; } // If no error has been set, return success with clear context if (thread_context->code == METAGRAPH_SUCCESS) { - memset(context, 0, sizeof(*context)); + mg_zero_buffer(context, sizeof(*context)); context->code = METAGRAPH_SUCCESS; return METAGRAPH_SUCCESS; } @@ -308,7 +472,7 @@ metagraph_get_error_context(metagraph_error_context_t *context) { void metagraph_clear_error_context(void) { metagraph_error_context_t *context = thread_error_context; if (context) { - memset(context, 0, sizeof(metagraph_error_context_t)); + mg_zero_buffer(context, sizeof(metagraph_error_context_t)); context->code = METAGRAPH_SUCCESS; // Note: We intentionally keep the allocated memory for reuse // rather than freeing it. This avoids repeated allocations @@ -325,4 +489,4 @@ void metagraph_thread_cleanup(void) { thread_error_context = NULL; } } -#endif \ No newline at end of file +#endif diff --git a/src/graph.c b/src/graph.c index 20048b7..d7e523a 100644 --- a/src/graph.c +++ b/src/graph.c @@ -1,33 +1,20 @@ #include "metagraph/graph.h" +#include "metagraph/base.h" #include "metagraph/epoch.h" #include "metagraph/rule.h" +#include #include #include -#include - -/** - * Initialize a graph structure to an empty state and initialize its epoch. - * - * This zeroes all fields of the provided mg_graph_t and initializes the - * embedded epoch value via mg_epoch_init. - * - * @param graph Pointer to the mg_graph_t to initialize; must point to a valid, - * writable mg_graph_t. - */ + void mg_graph_init_empty(mg_graph_t *graph) { - memset(graph, 0, sizeof(*graph)); + if (!graph) { + return; + } + mg_zero_buffer(graph, sizeof(*graph)); mg_epoch_init(&graph->epoch); } -/** - * Release all memory held by a graph and reset its contents to zero. - * - * Frees dynamically allocated arrays within the graph (nodes, edges, neighbor ids) - * and clears the graph structure. Safe to call with a NULL pointer. - * - * @param graph Pointer to the graph to free and reset; may be NULL. - */ void mg_graph_free(mg_graph_t *graph) { if (!graph) { return; @@ -35,19 +22,9 @@ void mg_graph_free(mg_graph_t *graph) { free(graph->nodes); free(graph->edges); free(graph->nbr_ids); - memset(graph, 0, sizeof(*graph)); + mg_zero_buffer(graph, sizeof(*graph)); } -/** - * Create a snapshot view of the provided graph suitable for read-only inspection. - * - * If `graph` is NULL the snapshot contains NULL pointers and zero counts. The snapshot's - * pointer fields reference the graph's internal arrays; they are not deep copies and - * remain valid only as long as the underlying graph's memory is not modified or freed. - * - * @param graph Graph to snapshot; may be NULL. - * @returns mg_graph_snapshot_t Snapshot containing pointers to node and neighbor arrays, edge data, their counts, and the graph epoch value. - */ mg_graph_snapshot_t mg_graph_snapshot_view(const mg_graph_t *graph) { mg_graph_snapshot_t snapshot = { .nodes = graph ? graph->nodes : NULL, @@ -60,13 +37,6 @@ mg_graph_snapshot_t mg_graph_snapshot_view(const mg_graph_t *graph) { return snapshot; } -/** - * Compute the degree (number of neighbors) of the node at the given index. - * - * @param graph Graph to query. - * @param node_index Index of the node within the graph's node array. - * @returns `-1` if `graph` is NULL or `node_index` is out of bounds, otherwise the node's degree (number of neighbor entries). - */ int mg_graph_degree(const mg_graph_t *graph, uint32_t node_index) { if (!graph || node_index >= graph->node_count) { return -1; @@ -80,49 +50,56 @@ int mg_graph_degree(const mg_graph_t *graph, uint32_t node_index) { return (int)(end - start); } -/** - * Allocate and zero-initialize the graph's node array and record the node count. - * - * Allocates an array of `count` `mg_node_rec_t` elements with `calloc` and assigns it - * to `graph->nodes`. Sets `graph->node_count` to `count`. If allocation fails, - * `graph->nodes` will be `NULL` and `graph->node_count` will still be set to `count`. - * - * @param graph Graph object whose node storage will be allocated. - * @param count Number of nodes to allocate. - */ -static void metagraph_graph_alloc_nodes(mg_graph_t *graph, size_t count) { - graph->nodes = (mg_node_rec_t *)calloc(count, sizeof(mg_node_rec_t)); +static bool metagraph_graph_alloc_nodes(mg_graph_t *graph, size_t count) { + if (!graph) { + return false; + } + if (count == 0U) { + graph->nodes = NULL; + graph->node_count = 0; + return true; + } + mg_node_rec_t *nodes_buffer = + (mg_node_rec_t *)calloc(count, sizeof(mg_node_rec_t)); + if (!nodes_buffer) { + graph->nodes = NULL; + graph->node_count = 0; + return false; + } + graph->nodes = nodes_buffer; graph->node_count = count; + return true; } -/** - * Allocate and zero-initialize the neighbor ID array for a graph and record its size. - * - * Allocates an array of `count` uint32_t elements with `calloc`, assigns it to - * `graph->nbr_ids`, and sets `graph->nbr_count` to `count`. - * - * @param graph Graph whose neighbor storage will be allocated. - * @param count Number of neighbor IDs to allocate. - */ -static void metagraph_graph_alloc_neighbors(mg_graph_t *graph, size_t count) { - graph->nbr_ids = (uint32_t *)calloc(count, sizeof(uint32_t)); +static bool metagraph_graph_alloc_neighbors(mg_graph_t *graph, size_t count) { + if (!graph) { + return false; + } + if (count == 0U) { + graph->nbr_ids = NULL; + graph->nbr_count = 0; + return true; + } + uint32_t *neighbor_indices = (uint32_t *)calloc(count, sizeof(uint32_t)); + if (!neighbor_indices) { + graph->nbr_ids = NULL; + graph->nbr_count = 0; + return false; + } + graph->nbr_ids = neighbor_indices; graph->nbr_count = count; + return true; } -/** - * Initialize the provided graph as a three-node path (0–1–2) with each node set to `MG_TYPE_Q`. - * - * The function frees any existing contents of `graph` and replaces them with three nodes and their - * adjacency lists representing the path 0—1—2. - * - * @param graph Graph to populate; existing contents are freed and replaced. Must be non-NULL. - */ void mg_graph_make_path_qwqwq(mg_graph_t *graph) { mg_graph_free(graph); mg_graph_init_empty(graph); - metagraph_graph_alloc_nodes(graph, 3); - metagraph_graph_alloc_neighbors(graph, 4); + if (!metagraph_graph_alloc_nodes(graph, 3) || + !metagraph_graph_alloc_neighbors(graph, 4)) { + mg_graph_free(graph); + return; + } graph->nbr_ids[0] = 1; graph->nbr_ids[1] = 0; @@ -136,7 +113,7 @@ void mg_graph_make_path_qwqwq(mg_graph_t *graph) { graph->nodes[1].id = 1; graph->nodes[1].type = MG_TYPE_Q; - graph->nodes[1].adj_offset = 2; + graph->nodes[1].adj_offset = 1; graph->nodes[1].degree = 2; graph->nodes[2].id = 2; @@ -145,33 +122,27 @@ void mg_graph_make_path_qwqwq(mg_graph_t *graph) { graph->nodes[2].degree = 1; } -/** - * Construct a small three-node graph with a predetermined adjacency layout. - * - * This resets and reinitializes `graph`, allocates space for 3 nodes and 6 neighbor entries, - * and populates node ids, node types (MG_TYPE_Q), adjacency offsets, degrees, and the - * neighbor id array with a fixed pattern used for example/testing purposes. - * - * @param graph Graph object to initialize and populate; existing contents are freed. - */ void mg_graph_make_path_qwqwq2(mg_graph_t *graph) { mg_graph_free(graph); mg_graph_init_empty(graph); - metagraph_graph_alloc_nodes(graph, 3); - metagraph_graph_alloc_neighbors(graph, 6); + if (!metagraph_graph_alloc_nodes(graph, 3) || + !metagraph_graph_alloc_neighbors(graph, 6)) { + mg_graph_free(graph); + return; + } graph->nbr_ids[0] = 1; - graph->nbr_ids[1] = 0; - graph->nbr_ids[2] = 2; - graph->nbr_ids[3] = 1; - graph->nbr_ids[4] = 2; - graph->nbr_ids[5] = 1; + graph->nbr_ids[1] = 2; + graph->nbr_ids[2] = 0; + graph->nbr_ids[3] = 2; + graph->nbr_ids[4] = 1; + graph->nbr_ids[5] = 0; graph->nodes[0].id = 0; graph->nodes[0].type = MG_TYPE_Q; graph->nodes[0].adj_offset = 0; - graph->nodes[0].degree = 1; + graph->nodes[0].degree = 2; graph->nodes[1].id = 1; graph->nodes[1].type = MG_TYPE_Q; @@ -181,5 +152,5 @@ void mg_graph_make_path_qwqwq2(mg_graph_t *graph) { graph->nodes[2].id = 2; graph->nodes[2].type = MG_TYPE_Q; graph->nodes[2].adj_offset = 4; - graph->nodes[2].degree = 1; -} \ No newline at end of file + graph->nodes[2].degree = 2; +} diff --git a/src/hilbert.c b/src/hilbert.c index b89ca65..2674468 100644 --- a/src/hilbert.c +++ b/src/hilbert.c @@ -2,19 +2,24 @@ #include #include -#include +#include "metagraph/base.h" #include "metagraph/result.h" /** - * Initialize a Hilbert register handle with storage for a given number of nodes. + * Initialize a Hilbert register handle with storage for a given number of + * nodes. * - * Allocates and zero-initializes the internal node_bits array and sets node_count. + * Allocates and zero-initializes the internal node_bits array and sets + * node_count. * * @param hilbert Pointer to the hilbert handle to initialize; must not be NULL. - * @param count Number of node entries to allocate (zero is allowed and allocates a minimal buffer). - * @returns `METAGRAPH_OK()` on success; `METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, ...)` if `hilbert` is NULL; - * `METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, ...)` if allocation fails. + * @param count Number of node entries to allocate (zero is allowed and + * allocates a minimal buffer). + * @returns `METAGRAPH_OK()` on success; + * `METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, ...)` if `hilbert` is NULL; + * `METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, ...)` if allocation + * fails. */ metagraph_result_t mg_hilbert_init(mg_hilbert_t *hilbert, size_t count) { if (!hilbert) { @@ -55,14 +60,18 @@ void mg_hilbert_free(mg_hilbert_t *hilbert) { * @param hilbert Pointer to the Hilbert handle to resize; must not be NULL. * @param new_count Desired number of nodes (may be zero). * @returns `METAGRAPH_OK()` on success. - * Returns `METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, ...)` if `hilbert` is NULL. - * Returns `METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, ...)` if memory allocation fails. + * Returns `METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, ...)` if + * `hilbert` is NULL. Returns `METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, + * ...)` if memory allocation fails. */ metagraph_result_t mg_hilbert_resize(mg_hilbert_t *hilbert, size_t new_count) { if (!hilbert) { return METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, "hilbert handle is null"); } + if (hilbert->node_count == new_count) { + return METAGRAPH_OK(); + } uint8_t *next = (uint8_t *)calloc(new_count ? new_count : 1, sizeof(uint8_t)); if (!next) { @@ -72,7 +81,11 @@ metagraph_result_t mg_hilbert_resize(mg_hilbert_t *hilbert, size_t new_count) { size_t copy = (hilbert->node_count < new_count) ? hilbert->node_count : new_count; if (hilbert->node_bits && copy > 0) { - memcpy(next, hilbert->node_bits, copy); + const size_t dst_bytes = new_count * sizeof(uint8_t); + const size_t src_bytes = hilbert->node_count * sizeof(uint8_t); + const size_t copy_bytes = copy * sizeof(uint8_t); + mg_copy_bytes(next, dst_bytes, hilbert->node_bits, src_bytes, + copy_bytes); } free(hilbert->node_bits); hilbert->node_bits = next; diff --git a/src/match.c b/src/match.c index 335fec4..e75e7c6 100644 --- a/src/match.c +++ b/src/match.c @@ -1,24 +1,28 @@ #include "metagraph/match.h" +#include "metagraph/base.h" #include #include -#include /** - * Initialize an mg_match_set_t structure and optionally allocate its internal storage. + * Initialize an mg_match_set_t structure and optionally allocate its internal + * storage. * - * The function zeroes the provided structure and, if capacity > 0, allocates an array - * of mg_match_t of the requested size and sets the structure's capacity accordingly. + * The function zeroes the provided structure and, if capacity > 0, allocates an + * array of mg_match_t of the requested size and sets the structure's capacity + * accordingly. * * @param set Pointer to the mg_match_set_t to initialize (must not be NULL). - * @param capacity Initial number of elements to allocate; if zero no allocation is performed. - * @returns `true` if the structure was initialized and any requested allocation succeeded, `false` otherwise. + * @param capacity Initial number of elements to allocate; if zero no allocation + * is performed. + * @returns `true` if the structure was initialized and any requested allocation + * succeeded, `false` otherwise. */ bool mg_match_set_init(mg_match_set_t *set, uint32_t capacity) { if (!set) { return false; } - memset(set, 0, sizeof(*set)); + mg_zero_buffer(set, sizeof(*set)); if (capacity > 0) { set->data = (mg_match_t *)calloc(capacity, sizeof(mg_match_t)); if (!set->data) { @@ -32,11 +36,16 @@ bool mg_match_set_init(mg_match_set_t *set, uint32_t capacity) { /** * Ensure the match set can hold at least the specified number of elements. * - * Allocates or grows the internal storage so that the set can contain at least min_capacity elements and updates set->capacity on success; no allocation is performed if the current capacity already meets or exceeds min_capacity. + * Allocates or grows the internal storage so that the set can contain at least + * min_capacity elements and updates set->capacity on success; no allocation is + * performed if the current capacity already meets or exceeds min_capacity. * - * @param set Pointer to the match set to modify; if NULL the function does nothing and returns `false`. - * @param min_capacity Minimum required number of elements the set must be able to hold. - * @returns `true` if the set has at least min_capacity capacity after the call, `false` on allocation failure or when `set` is NULL. + * @param set Pointer to the match set to modify; if NULL the function does + * nothing and returns `false`. + * @param min_capacity Minimum required number of elements the set must be able + * to hold. + * @returns `true` if the set has at least min_capacity capacity after the call, + * `false` on allocation failure or when `set` is NULL. */ bool mg_match_set_reserve(mg_match_set_t *set, uint32_t min_capacity) { if (!set) { @@ -47,10 +56,18 @@ bool mg_match_set_reserve(mg_match_set_t *set, uint32_t min_capacity) { } uint32_t new_capacity = set->capacity ? set->capacity : 8U; while (new_capacity < min_capacity) { + if (new_capacity > UINT32_MAX / 2U) { // prevent wrap + new_capacity = min_capacity; // fall back to exact fit + break; + } new_capacity *= 2U; } - mg_match_t *next = - (mg_match_t *)realloc(set->data, new_capacity * sizeof(mg_match_t)); + // size_t byte-count overflow guard + if ((size_t)new_capacity > SIZE_MAX / sizeof(mg_match_t)) { + return false; + } + mg_match_t *next = (mg_match_t *)realloc(set->data, (size_t)new_capacity * + sizeof(mg_match_t)); if (!next) { return false; } @@ -73,7 +90,11 @@ bool mg_match_set_push(mg_match_set_t *set, const mg_match_t *match) { if (!set || !match) { return false; } - if (!mg_match_set_reserve(set, set->count + 1U)) { + if (set->count == UINT32_MAX) { + return false; + } + const uint32_t next_count = set->count + 1U; + if (!mg_match_set_reserve(set, next_count)) { return false; } set->data[set->count++] = *match; diff --git a/src/qca.c b/src/qca.c index 11dd42b..1276e91 100644 --- a/src/qca.c +++ b/src/qca.c @@ -1,35 +1,312 @@ -#include "metagraph/qca.h" - +#include +#include #include -#include +#include +#include +#ifdef __APPLE__ +#include +#endif + +#include "metagraph/qca.h" #include "metagraph/arena.h" #include "metagraph/base.h" #include "metagraph/dpoi.h" #include "metagraph/epoch.h" +#include "metagraph/graph.h" #include "metagraph/hilbert.h" #include "metagraph/match.h" #include "metagraph/result.h" #include "metagraph/rmg.h" #include "metagraph/rule.h" -/** - * Collects matches from all rules for a given RMG into a single aggregate match set. - * - * Populates `aggregate` with the concatenation of matches found by applying each - * rule in `rules[0..rule_count-1]` to `rmg`. `aggregate->count` is set to 0 - * on entry and updated to the total number of matches on success. - * - * @param rmg RMG to match against. - * @param rules Array of rules to apply. - * @param rule_count Number of rules in `rules`. - * @param arena Arena used for temporary allocations during matching. - * @param aggregate Destination match set that will contain all collected matches. - * - * @returns METAGRAPH_SUCCESS on success; otherwise returns the propagated error - * result from matching, or METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, ...) - * if memory for expanding `aggregate` could not be allocated. - */ +static bool metagraph_graph_find_index_by_id(const mg_graph_t *graph, + mg_node_id_t node_id, + uint32_t *out_index) { + if (!graph || !out_index) { + return false; + } + for (size_t index = 0; index < graph->node_count; ++index) { + if (graph->nodes[index].id == node_id) { + if (index > UINT32_MAX) { + return false; + } + *out_index = (uint32_t)index; + return true; + } + } + return false; +} + +typedef struct { + bool have_start; + bool have_end; + struct timespec start; + struct timespec end; +} mg_qca_timer_t; + +static mg_qca_timer_t metagraph_timer_create(void) { + mg_qca_timer_t timer = { + .have_start = false, + .have_end = false, + .start = {0, 0}, + .end = {0, 0}, + }; + return timer; +} + +static bool metagraph_monotonic_now(struct timespec *out); +static void metagraph_timer_begin(mg_qca_timer_t *timer); +static void metagraph_timer_end(mg_qca_timer_t *timer); +static double metagraph_timer_ms(const mg_qca_timer_t *timer); +static metagraph_result_t +metagraph_qca_collect_matches(const mg_rmg_t *rmg, const mg_rule_t *rules, + uint32_t rule_count, mg_arena_t *arena, + mg_match_set_t *aggregate); +static void metagraph_qca_apply_rule_x(const mg_graph_t *graph, + mg_hilbert_t *hilbert, + const mg_match_t *match); +static void metagraph_qca_apply_rule_cnot(const mg_graph_t *graph, + mg_hilbert_t *hilbert, + const mg_match_t *match); +static void metagraph_qca_apply_rule_split_w(const mg_graph_t *graph, + const mg_match_t *match); + +static double metagraph_timespec_diff_ms(const struct timespec *start, + const struct timespec *end) { + if (!start || !end) { + return 0.0; + } + time_t sec_diff = end->tv_sec - start->tv_sec; + long nsec_diff = end->tv_nsec - start->tv_nsec; + if (nsec_diff < 0) { + --sec_diff; + nsec_diff += 1000000000L; + } + const double milliseconds_from_seconds = (double)sec_diff * 1000.0; + const double milliseconds_from_nanoseconds = (double)nsec_diff / 1.0e6; + return milliseconds_from_seconds + milliseconds_from_nanoseconds; +} + +static metagraph_result_t +metagraph_qca_stage_match(mg_rmg_t *rmg, const mg_rule_t *rules, + uint32_t rule_count, mg_arena_t *arena, + mg_match_set_t *aggregate, mg_tick_metrics_t *metrics, + mg_qca_timer_t *timer) { + if (timer) { + metagraph_timer_begin(timer); + } + metagraph_result_t result = + metagraph_qca_collect_matches(rmg, rules, rule_count, arena, aggregate); + if (timer) { + metagraph_timer_end(timer); + } + if (metagraph_result_is_error(result)) { + return result; + } + + metrics->matches_found = aggregate->count; + + mg_dpoi_schedule_maximal(aggregate); + metrics->matches_kept = aggregate->count; + metrics->conflicts_dropped = + (metrics->matches_found > aggregate->count) + ? (metrics->matches_found - aggregate->count) + : 0U; + + return METAGRAPH_SUCCESS; +} + +static metagraph_result_t metagraph_qca_stage_kernel( + mg_hilbert_t *hilbert, const mg_rmg_t *rmg, const mg_rule_t *rules, + const mg_match_set_t *aggregate, mg_qca_timer_t *timer) { + if (timer) { + metagraph_timer_begin(timer); + } + metagraph_result_t result = + mg_qca_apply_kernels(hilbert, rmg, rules, aggregate); + if (timer) { + metagraph_timer_end(timer); + } + return result; +} + +static metagraph_result_t +metagraph_qca_stage_rewrite(mg_rmg_t *rmg, const mg_rule_t *rules, + uint32_t rule_count, mg_match_set_t *aggregate, + mg_epoch_t *epoch, mg_qca_timer_t *timer) { + if (timer) { + metagraph_timer_begin(timer); + } + metagraph_result_t result = + mg_dpo_commit(rmg->skel, rules, rule_count, aggregate); + if (timer) { + metagraph_timer_end(timer); + } + if (metagraph_result_is_error(result)) { + return result; + } + if (epoch) { + mg_epoch_flip(epoch); + } + return METAGRAPH_SUCCESS; +} + +static bool metagraph_monotonic_now(struct timespec *out) { + if (!out) { + return false; + } +#ifdef __APPLE__ + static mach_timebase_info_data_t timebase_info = {0}; + if (timebase_info.denom == 0) { + if (mach_timebase_info(&timebase_info) != 0) { + return false; + } + } + const uint64_t absolute = mach_absolute_time(); + const uint64_t nanoseconds = absolute * (uint64_t)timebase_info.numer / + (uint64_t)timebase_info.denom; + out->tv_sec = (time_t)(nanoseconds / 1000000000ULL); + out->tv_nsec = (long)(nanoseconds % 1000000000ULL); + return true; +#else +#if defined(CLOCK_MONOTONIC) + if (clock_gettime(CLOCK_MONOTONIC, out) == 0) { + return true; + } +#endif + if (timespec_get(out, TIME_UTC) != 0) { + return true; + } + + return false; +#endif +} + +static void metagraph_timer_begin(mg_qca_timer_t *timer) { + if (!timer) { + return; + } + timer->have_start = metagraph_monotonic_now(&timer->start); + timer->have_end = false; +} + +static void metagraph_timer_end(mg_qca_timer_t *timer) { + if (!timer || !timer->have_start) { + return; + } + timer->have_end = metagraph_monotonic_now(&timer->end); +} + +static double metagraph_timer_ms(const mg_qca_timer_t *timer) { + if (!timer || !timer->have_start || !timer->have_end) { + return 0.0; + } + return metagraph_timespec_diff_ms(&timer->start, &timer->end); +} + +static void metagraph_qca_apply_rule_x(const mg_graph_t *graph, + mg_hilbert_t *hilbert, + const mg_match_t *match) { + if (!graph || !hilbert || !match || match->L_n < 1U) { + return; + } + uint32_t node_index = 0U; + if (!metagraph_graph_find_index_by_id(graph, match->L2G_node[0], + &node_index)) { + return; + } + if (node_index < hilbert->node_count) { + hilbert->node_bits[node_index] ^= 1U; + } +} + +static void metagraph_qca_apply_rule_cnot(const mg_graph_t *graph, + mg_hilbert_t *hilbert, + const mg_match_t *match) { + if (!graph || !hilbert || !match || match->L_n < 2U) { + return; + } + uint32_t control_index = 0U; + uint32_t target_index = 0U; + if (!metagraph_graph_find_index_by_id(graph, match->L2G_node[0], + &control_index)) { + return; + } + if (!metagraph_graph_find_index_by_id(graph, match->L2G_node[1], + &target_index)) { + return; + } + if (control_index < hilbert->node_count && + target_index < hilbert->node_count && + hilbert->node_bits[control_index] != 0U) { + hilbert->node_bits[target_index] ^= 1U; + } +} + +static void metagraph_qca_apply_rule_split_w(const mg_graph_t *graph, + const mg_match_t *match) { + (void)graph; + (void)match; + /* + * The split_w rule rewires topology by inserting an intermediate node. + * The Hilbert state for existing nodes is unaffected and newly created + * nodes are initialised during commit, so no additional work is required + * here beyond acknowledging the match. + */ +} + +static bool metagraph_timer_valid(const mg_qca_timer_t *timer) { + if (!timer) { + return false; + } + if (!timer->have_start) { + return false; + } + if (!timer->have_end) { + return false; + } + return true; +} + +static void metagraph_qca_zero_metrics(mg_tick_metrics_t *metrics) { + if (!metrics) { + return; + } + metrics->matches_found = 0U; + metrics->matches_kept = 0U; + metrics->conflicts_dropped = 0U; + metrics->ms_match = 0.0; + metrics->ms_kernel = 0.0; + metrics->ms_rewrite = 0.0; + metrics->ms_total = 0.0; +} + +static void metagraph_qca_finalize_metrics(const mg_qca_timer_t *match_timer, + const mg_qca_timer_t *kernel_timer, + const mg_qca_timer_t *rewrite_timer, + const mg_qca_timer_t *total_timer, + mg_tick_metrics_t *metrics) { + if (!metrics) { + return; + } + + const double match_ms = metagraph_timer_ms(match_timer); + const double kernel_ms = metagraph_timer_ms(kernel_timer); + const double rewrite_ms = metagraph_timer_ms(rewrite_timer); + const bool total_valid = metagraph_timer_valid(total_timer); + const double total_ms = metagraph_timer_ms(total_timer); + + metrics->ms_match = match_ms; + metrics->ms_kernel = kernel_ms; + metrics->ms_rewrite = rewrite_ms; + if (total_valid) { + metrics->ms_total = total_ms; + } else { + metrics->ms_total = match_ms + kernel_ms + rewrite_ms; + } +} + static metagraph_result_t metagraph_qca_collect_matches(const mg_rmg_t *rmg, const mg_rule_t *rules, uint32_t rule_count, mg_arena_t *arena, @@ -49,87 +326,71 @@ metagraph_qca_collect_matches(const mg_rmg_t *rmg, const mg_rule_t *rules, return METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, "unable to append matches"); } - memcpy(&aggregate->data[aggregate->count], per_rule.data, - per_rule.count * sizeof(mg_match_t)); + const size_t bytes_to_copy = per_rule.count * sizeof(mg_match_t); + const size_t available_bytes = + (aggregate->capacity - aggregate->count) * sizeof(mg_match_t); + const size_t copied = + mg_copy_bytes(&aggregate->data[aggregate->count], available_bytes, + per_rule.data, bytes_to_copy, bytes_to_copy); + if (copied != bytes_to_copy) { + mg_match_set_free(&per_rule); + return METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, + "unable to append matches"); + } aggregate->count += per_rule.count; mg_match_set_free(&per_rule); } return METAGRAPH_SUCCESS; } -/** - * Apply scheduled matches to modify the Hilbert node bitset according to QCA kernel rules. - * - * For each match in `schedule`: - * - If `rule_id` is 1 and `L_n >= 1`, toggle `hilbert->node_bits` at `L2G_node[0]` if that node index is valid. - * - If `rule_id` is 2 and `L_n >= 2`, treat `L2G_node[0]` as control and `L2G_node[1]` as target; if both indices are valid and the control bit is set, toggle the target bit. - * - * This function mutates `hilbert->node_bits` in-place. Invalid node indices are ignored. - * - * @param hilbert Pointer to the Hilbert structure whose node bits will be modified. - * @param schedule Match schedule to apply. - */ -static void metagraph_qca_apply_matches(mg_hilbert_t *hilbert, +static void metagraph_qca_apply_matches(const mg_graph_t *graph, + mg_hilbert_t *hilbert, const mg_match_set_t *schedule) { + if (!graph || !hilbert || !schedule) { + return; + } for (uint32_t index = 0; index < schedule->count; ++index) { const mg_match_t *match = &schedule->data[index]; - if (match->rule_id == 1 && match->L_n >= 1U) { - const mg_node_id_t node = match->L2G_node[0]; - if (node < hilbert->node_count) { - hilbert->node_bits[node] ^= 1U; - } - } else if (match->rule_id == 2 && match->L_n >= 2U) { - const mg_node_id_t control = match->L2G_node[0]; - const mg_node_id_t target = match->L2G_node[1]; - if (control < hilbert->node_count && target < hilbert->node_count && - hilbert->node_bits[control] != 0U) { - hilbert->node_bits[target] ^= 1U; - } + switch (match->rule_id) { + case 1U: + metagraph_qca_apply_rule_x(graph, hilbert, match); + break; + case 2U: + metagraph_qca_apply_rule_cnot(graph, hilbert, match); + break; + case 3U: + metagraph_qca_apply_rule_split_w(graph, match); + break; + default: + break; } } } -/** - * Apply QCA kernel effects described by a match schedule to the given Hilbert state. - * - * @param hilbert Hilbert state whose node bits will be modified according to the schedule. - * @param schedule Set of matches describing kernel actions to apply. - * @return METAGRAPH_SUCCESS on success. - */ metagraph_result_t mg_qca_apply_kernels(mg_hilbert_t *hilbert, const mg_rmg_t *rmg, const mg_rule_t *rules, const mg_match_set_t *schedule) { - (void)rmg; + METAGRAPH_VALIDATE_PTR(rmg, "rmg"); (void)rules; METAGRAPH_VALIDATE_PTR(hilbert, "hilbert"); METAGRAPH_VALIDATE_PTR(schedule, "schedule"); - metagraph_qca_apply_matches(hilbert, schedule); + const mg_graph_t *graph = rmg ? rmg->skel : NULL; + metagraph_qca_apply_matches(graph, hilbert, schedule); return METAGRAPH_SUCCESS; } -/** - * Process one QCA tick for an RMG: collect matches across rules, schedule maximal matches, - * apply kernel effects to the Hilbert, commit rewrites, and optionally flip the epoch. - * - * @param rmg RMG to process. - * @param hilbert Hilbert structure modified by kernel application. - * @param rules Array of rules to match against the RMG. - * @param rule_count Number of rules in `rules`. - * @param arena Optional memory arena used for matching; may be NULL. - * @param epoch Optional epoch to flip if non-NULL after a successful commit. - * @param metrics Output metrics; updated with match counts and reset timing fields. - * @returns METAGRAPH_SUCCESS on success, or an error metagraph_result_t if matching, - * kernel application, scheduling, allocation, or commit fails (for example, out-of-memory). - */ -metagraph_result_t mg_qca_tick_rmg(mg_rmg_t *rmg, mg_hilbert_t *hilbert, - const mg_rule_t *rules, uint32_t rule_count, - mg_arena_t *arena, mg_epoch_t *epoch, - mg_tick_metrics_t *metrics) { - METAGRAPH_VALIDATE_PTR(rmg, "rmg"); - METAGRAPH_VALIDATE_PTR(hilbert, "hilbert"); - METAGRAPH_VALIDATE_PTR(rules, "rules"); - METAGRAPH_VALIDATE_PTR(metrics, "metrics"); +static metagraph_result_t +metagraph_qca_tick_body(mg_rmg_t *rmg, mg_hilbert_t *hilbert, + const mg_rule_t *rules, uint32_t rule_count, + mg_arena_t *arena, mg_epoch_t *epoch, + mg_tick_metrics_t *metrics) { + mg_qca_timer_t total_timer = metagraph_timer_create(); + mg_qca_timer_t match_timer = metagraph_timer_create(); + mg_qca_timer_t kernel_timer = metagraph_timer_create(); + mg_qca_timer_t rewrite_timer = metagraph_timer_create(); + + metagraph_timer_begin(&total_timer); mg_match_set_t aggregate = {0}; if (!mg_match_set_init(&aggregate, 64U)) { @@ -137,39 +398,46 @@ metagraph_result_t mg_qca_tick_rmg(mg_rmg_t *rmg, mg_hilbert_t *hilbert, "unable to allocate match set"); } - metagraph_result_t result = metagraph_qca_collect_matches( - rmg, rules, rule_count, arena, &aggregate); - if (metagraph_result_is_error(result)) { - mg_match_set_free(&aggregate); - return result; - } - metrics->matches_found = aggregate.count; - - mg_dpoi_schedule_maximal(&aggregate); - metrics->matches_kept = aggregate.count; - metrics->conflicts_dropped = 0U; - - result = mg_qca_apply_kernels(hilbert, rmg, rules, &aggregate); - if (metagraph_result_is_error(result)) { - mg_match_set_free(&aggregate); - return result; + metagraph_result_t status = metagraph_qca_stage_match( + rmg, rules, rule_count, arena, &aggregate, metrics, &match_timer); + if (metagraph_result_is_error(status)) { + goto failure; } - result = mg_dpo_commit(rmg->skel, rules, rule_count, &aggregate); - if (metagraph_result_is_error(result)) { - mg_match_set_free(&aggregate); - return result; + status = metagraph_qca_stage_kernel(hilbert, rmg, rules, &aggregate, + &kernel_timer); + if (metagraph_result_is_error(status)) { + goto failure; } - if (epoch) { - mg_epoch_flip(epoch); + status = metagraph_qca_stage_rewrite(rmg, rules, rule_count, &aggregate, + epoch, &rewrite_timer); + if (metagraph_result_is_error(status)) { + goto failure; } - metrics->ms_match = 0.0; - metrics->ms_kernel = 0.0; - metrics->ms_rewrite = 0.0; - metrics->ms_total = 0.0; - + metagraph_timer_end(&total_timer); + metagraph_qca_finalize_metrics(&match_timer, &kernel_timer, &rewrite_timer, + &total_timer, metrics); mg_match_set_free(&aggregate); return METAGRAPH_SUCCESS; -} \ No newline at end of file + +failure: + metagraph_timer_end(&total_timer); + metagraph_qca_zero_metrics(metrics); + mg_match_set_free(&aggregate); + return status; +} + +metagraph_result_t mg_qca_tick_rmg(mg_rmg_t *rmg, mg_hilbert_t *hilbert, + const mg_rule_t *rules, uint32_t rule_count, + mg_arena_t *arena, mg_epoch_t *epoch, + mg_tick_metrics_t *metrics) { + METAGRAPH_VALIDATE_PTR(rmg, "rmg"); + METAGRAPH_VALIDATE_PTR(hilbert, "hilbert"); + METAGRAPH_VALIDATE_PTR(rules, "rules"); + METAGRAPH_VALIDATE_PTR(metrics, "metrics"); + + return metagraph_qca_tick_body(rmg, hilbert, rules, rule_count, arena, + epoch, metrics); +} diff --git a/src/rmg.c b/src/rmg.c index e418fdb..1b028a1 100644 --- a/src/rmg.c +++ b/src/rmg.c @@ -1,6 +1,7 @@ #include "metagraph/rmg.h" #include +#include #include /** @@ -14,8 +15,10 @@ * @param rmg Pointer to the RMG instance to query. * @param node_index Index of the node whose attachment is requested. * @param[out] attachment Receives the hydrated attachment pointer or NULL. - * @param[out] kind Receives the attachment kind; set to `MG_ATT_NONE` when no attachment is present. - * @returns `true` on successful query (including missing/out-of-range attachments), `false` if input pointers are NULL. + * @param[out] kind Receives the attachment kind; set to `MG_ATT_NONE` when no + * attachment is present. + * @returns `true` on successful query (including missing/out-of-range + * attachments), `false` if input pointers are NULL. */ bool mg_rmg_hydrate_node_att(const mg_rmg_t *rmg, uint32_t node_index, const void **attachment, mg_att_kind_t *kind) { @@ -39,20 +42,25 @@ bool mg_rmg_hydrate_node_att(const mg_rmg_t *rmg, uint32_t node_index, * * @param rmg RMG instance to query. * @param edge_index Index of the edge whose attachment to hydrate. - * @param attachment Output pointer set to the hydrated attachment pointer, or NULL if no attachment exists or the index is out of range. - * @returns `true` on successful query (including when no attachment is present), `false` if input pointers are NULL. + * @param attachment Output pointer set to the hydrated attachment pointer, or + * NULL if no attachment exists or the index is out of range. + * @returns `true` on successful query (including when no attachment is + * present), `false` if input pointers are NULL. */ bool mg_rmg_hydrate_edge_att(const mg_rmg_t *rmg, uint32_t edge_index, - const void **attachment) { - if (!rmg || !attachment) { + const void **attachment, mg_att_kind_t *kind) { + if (!rmg || !attachment || !kind) { return false; } if (!rmg->edge_att || edge_index >= rmg->skel->edge_count) { *attachment = NULL; + *kind = MG_ATT_NONE; return true; } const mg_attach_ref_t *ref = &rmg->edge_att[edge_index]; + *kind = ref->kind; *attachment = NULL; + // TODO: hydrate attachment offsets once caching layer is implemented (void)ref; return true; } \ No newline at end of file diff --git a/src/rule.c b/src/rule.c index f69be8e..0326580 100644 --- a/src/rule.c +++ b/src/rule.c @@ -1,22 +1,30 @@ #include "metagraph/rule.h" #include "metagraph/base.h" +#include #include -#include + +static void mg_rule_init_port_caps(mg_rule_t *rule) { + for (uint32_t i = 0; i < MG_RULE_MAX_NODES; ++i) { + rule->L_port_caps[i].max_in = UINT16_MAX; + rule->L_port_caps[i].max_out = UINT16_MAX; + } +} /** * Initialize a rule that applies a single-qubit X kernel. * - * Configure `rule` as a one-node L graph (type MG_TYPE_Q) with R identical to L; - * set K_node_mask to 0x1, K_edge_mask to 0, K2L_node/K2R_node mapping for the node - * to 0, kernel to MG_KERNEL_X with kernel_radius 0, L_boundary_mask to 0, - * and assign the provided rule_id. + * Configure `rule` as a one-node L graph (type MG_TYPE_Q) with R identical to + * L; set K_node_mask to 0x1, K_edge_mask to 0, K2L_node/K2R_node mapping for + * the node to 0, kernel to MG_KERNEL_X with kernel_radius 0, L_boundary_mask to + * 0, and assign the provided rule_id. * * @param rule Pointer to the mg_rule_t to initialize. * @param rule_id Identifier to assign to the initialized rule. */ void mg_rule_make_apply_x(mg_rule_t *rule, uint32_t rule_id) { - memset(rule, 0, sizeof(*rule)); + mg_zero_buffer(rule, sizeof(*rule)); + mg_rule_init_port_caps(rule); rule->rule_id = rule_id; rule->L.node_count = 1; rule->L.node_type[0] = MG_TYPE_Q; @@ -31,15 +39,21 @@ void mg_rule_make_apply_x(mg_rule_t *rule, uint32_t rule_id) { } /** - * Initialize a rule representing a two-qubit CNOT pattern (Q–Q) where the right-hand side is identical to the left-hand side. + * Initialize a rule representing a two-qubit CNOT pattern (Q–Q) where the + * right-hand side is identical to the left-hand side. * - * Sets the rule to a default/empty state, assigns the provided rule_id, configures L as two qubit nodes connected by a single edge, copies L to R, sets K_node_mask to 0x3, K_edge_mask to 0x1, establishes K2L/K2R node and edge mappings for the kernel, sets kernel to MG_KERNEL_CNOT with kernel_radius 1, and sets L_boundary_mask to 0. + * Sets the rule to a default/empty state, assigns the provided rule_id, + * configures L as two qubit nodes connected by a single edge, copies L to R, + * sets K_node_mask to 0x3, K_edge_mask to 0x1, establishes K2L/K2R node and + * edge mappings for the kernel, sets kernel to MG_KERNEL_CNOT with + * kernel_radius 1, and sets L_boundary_mask to 0. * * @param rule Pointer to the mg_rule_t to initialize. * @param rule_id Identifier to assign to the rule. */ void mg_rule_make_cnot_qwq(mg_rule_t *rule, uint32_t rule_id) { - memset(rule, 0, sizeof(*rule)); + mg_zero_buffer(rule, sizeof(*rule)); + mg_rule_init_port_caps(rule); rule->rule_id = rule_id; rule->L.node_count = 2; rule->L.node_type[0] = MG_TYPE_Q; @@ -68,30 +82,37 @@ void mg_rule_make_cnot_qwq(mg_rule_t *rule, uint32_t rule_id) { * The initialized rule will have L configured with 2 qubit nodes and 1 edge, * R configured with 3 qubit nodes and 2 edges (edges: 0-2 and 2-1), K_node_mask * set to 0x3, K_edge_mask set to 0, K2L/K2R node mappings for nodes 0->0 and - * 1->1, kernel set to MG_KERNEL_ISOM_SPLIT with kernel_radius 1, L_boundary_mask - * set to 0, and rule_id assigned. + * 1->1, kernel set to MG_KERNEL_ISOM_SPLIT with kernel_radius 1, + * L_boundary_mask set to 0, and rule_id assigned. * * @param rule Pointer to the mg_rule_t to initialize (output). * @param rule_id Identifier to assign to the rule. */ void mg_rule_make_split_w(mg_rule_t *rule, uint32_t rule_id) { - memset(rule, 0, sizeof(*rule)); + mg_zero_buffer(rule, sizeof(*rule)); + mg_rule_init_port_caps(rule); rule->rule_id = rule_id; rule->L.node_count = 2; const mg_type_id_t l_types[] = {MG_TYPE_Q, MG_TYPE_Q}; - memcpy(rule->L.node_type, l_types, sizeof(l_types)); + for (uint32_t i = 0; i < rule->L.node_count; ++i) { + rule->L.node_type[i] = l_types[i]; + } rule->L.edge_count = 1; rule->L.edge_u[0] = 0; rule->L.edge_v[0] = 1; rule->R.node_count = 3; const mg_type_id_t r_types[] = {MG_TYPE_Q, MG_TYPE_Q, MG_TYPE_Q}; - memcpy(rule->R.node_type, r_types, sizeof(r_types)); + for (uint32_t i = 0; i < rule->R.node_count; ++i) { + rule->R.node_type[i] = r_types[i]; + } rule->R.edge_count = 2; - const uint8_t r_edge_u[] = {0, 2}; - const uint8_t r_edge_v[] = {2, 1}; - memcpy(rule->R.edge_u, r_edge_u, sizeof(r_edge_u)); - memcpy(rule->R.edge_v, r_edge_v, sizeof(r_edge_v)); + const mg_node_id_t r_edge_u[] = {0U, 2U}; + const mg_node_id_t r_edge_v[] = {2U, 1U}; + for (uint32_t i = 0; i < rule->R.edge_count; ++i) { + rule->R.edge_u[i] = r_edge_u[i]; + rule->R.edge_v[i] = r_edge_v[i]; + } rule->K_node_mask = 0x3U; rule->K_edge_mask = 0; diff --git a/src/version.c b/src/version.c index 14b75e2..38baeda 100644 --- a/src/version.c +++ b/src/version.c @@ -4,9 +4,40 @@ */ #include "metagraph/version.h" -#include +#include "metagraph/base.h" #include +static size_t metagraph_append_span(char *buffer, size_t capacity, + size_t offset, const char *data, + size_t length) { + if (!buffer || capacity == 0U || !data || length == 0U) { + return offset; + } + size_t index = 0U; + while (index < length && offset + 1U < capacity) { + buffer[offset++] = data[index++]; + } + if (offset < capacity) { + buffer[offset] = '\0'; + } else { + buffer[capacity - 1U] = '\0'; + offset = capacity - 1U; + } + return offset; +} + +static size_t metagraph_append_cstring(char *buffer, size_t capacity, + size_t offset, const char *text) { + if (!text) { + return offset; + } + size_t length = 0U; + while (text[length] != '\0') { + ++length; + } + return metagraph_append_span(buffer, capacity, offset, text, length); +} + int metagraph_version_major(void) { return METAGRAPH_API_VERSION_MAJOR; } int metagraph_version_minor(void) { return METAGRAPH_API_VERSION_MINOR; } @@ -27,9 +58,22 @@ const char *metagraph_bundle_format_uuid(void) { const char *metagraph_build_info(void) { static char build_info[256]; - snprintf(build_info, sizeof(build_info), "Built on %s from %s (%s)", - METAGRAPH_BUILD_TIMESTAMP, METAGRAPH_BUILD_COMMIT_HASH, - METAGRAPH_BUILD_BRANCH); + mg_zero_buffer(build_info, sizeof(build_info)); + size_t offset = 0U; + offset = metagraph_append_span(build_info, sizeof(build_info), offset, + "Built on ", sizeof("Built on ") - 1U); + offset = metagraph_append_cstring(build_info, sizeof(build_info), offset, + METAGRAPH_BUILD_TIMESTAMP); + offset = metagraph_append_span(build_info, sizeof(build_info), offset, + " from ", sizeof(" from ") - 1U); + offset = metagraph_append_cstring(build_info, sizeof(build_info), offset, + METAGRAPH_BUILD_COMMIT_HASH); + offset = metagraph_append_span(build_info, sizeof(build_info), offset, " (", + sizeof(" (") - 1U); + offset = metagraph_append_cstring(build_info, sizeof(build_info), offset, + METAGRAPH_BUILD_BRANCH); + (void)metagraph_append_span(build_info, sizeof(build_info), offset, ")", + sizeof(")") - 1U); return build_info; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1a1ea4b..7c66dba 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,6 +16,7 @@ set_tests_properties(placeholder_test PROPERTIES add_executable(dpoi_qca_rmg_test dpoi_qca_rmg_test.c) target_link_libraries(dpoi_qca_rmg_test metagraph::metagraph) +target_compile_options(dpoi_qca_rmg_test PRIVATE $<$:-UNDEBUG>) add_test(NAME dpoi_qca_rmg_test COMMAND dpoi_qca_rmg_test) set_tests_properties(dpoi_qca_rmg_test PROPERTIES diff --git a/tests/dpoi_qca_rmg_test.c b/tests/dpoi_qca_rmg_test.c index 17bf2d0..1367a46 100644 --- a/tests/dpoi_qca_rmg_test.c +++ b/tests/dpoi_qca_rmg_test.c @@ -1,7 +1,9 @@ #include +#include #include -#include +#include +#include "metagraph/base.h" #include "metagraph/dpoi.h" #include "metagraph/graph.h" #include "metagraph/hilbert.h" @@ -13,13 +15,17 @@ static void init_rmg(mg_graph_t *graph, mg_rmg_t *rmg, mg_attach_ref_t *node_att, mg_attach_ref_t *edge_att, - mg_iface_t *edge_ifc) { - graph->edge_count = 0U; + mg_edge_ifc_t *edge_ifc) { + mg_zero_buffer(rmg, sizeof *rmg); rmg->skel = graph; rmg->node_att = node_att; rmg->edge_att = edge_att; rmg->edge_ifc = edge_ifc; + if (!graph) { + return; + } + for (size_t i = 0; i < graph->node_count; ++i) { node_att[i].kind = MG_ATT_NONE; node_att[i].offset = 0; @@ -30,27 +36,89 @@ static void init_rmg(mg_graph_t *graph, mg_rmg_t *rmg, edge_att[i].kind = MG_ATT_NONE; edge_att[i].offset = 0; edge_att[i].flags = 0; - edge_ifc[i].in_count = 0; - edge_ifc[i].out_count = 0; - edge_ifc[i].in_types = NULL; - edge_ifc[i].out_types = NULL; + edge_ifc[i].src.port_count = 0; + edge_ifc[i].src.ports = NULL; + edge_ifc[i].dst.port_count = 0; + edge_ifc[i].dst.ports = NULL; } } } +typedef struct { + mg_attach_ref_t *node_att; + mg_attach_ref_t *edge_att; + mg_edge_ifc_t *edge_ifc; + size_t node_count; + size_t edge_count; +} mg_rmg_buffers_t; + +static mg_rmg_buffers_t mg_rmg_buffers_make(const mg_graph_t *graph) { + mg_rmg_buffers_t buffers = {0}; + if (!graph) { + return buffers; + } + buffers.node_count = graph->node_count; + buffers.edge_count = graph->edge_count; + if (buffers.node_count > 0U) { + buffers.node_att = (mg_attach_ref_t *)calloc(buffers.node_count, + sizeof *buffers.node_att); + } + if (buffers.edge_count > 0U) { + buffers.edge_att = (mg_attach_ref_t *)calloc(buffers.edge_count, + sizeof *buffers.edge_att); + buffers.edge_ifc = (mg_edge_ifc_t *)calloc(buffers.edge_count, + sizeof *buffers.edge_ifc); + } + return buffers; +} + +static void mg_rmg_buffers_free(mg_rmg_buffers_t *buffers) { + if (!buffers) { + return; + } + free(buffers->edge_ifc); + free(buffers->edge_att); + free(buffers->node_att); + mg_zero_buffer(buffers, sizeof *buffers); +} + +static void assert_edge_interfaces_clear(const mg_rmg_t *rmg, + size_t edge_count) { + if (edge_count == 0U || !rmg || !rmg->edge_ifc) { + return; + } + for (size_t i = 0; i < edge_count; ++i) { + assert(rmg->edge_ifc[i].src.port_count == 0); + assert(rmg->edge_ifc[i].dst.port_count == 0); + } +} + +static void assert_default_rule_caps(const mg_rule_t *rule) { + assert(rule->L_port_caps[0].min_in == 0); + assert(rule->L_port_caps[0].max_in == UINT16_MAX); + assert(rule->L_port_caps[0].min_out == 0); + assert(rule->L_port_caps[0].max_out == UINT16_MAX); +} + static void test_dpoi_apply_x(void) { mg_graph_t graph; mg_graph_init_empty(&graph); mg_graph_make_path_qwqwq(&graph); - mg_attach_ref_t node_att[4]; - mg_attach_ref_t edge_att[1]; - mg_iface_t edge_ifc[1]; + mg_rmg_buffers_t buffers = mg_rmg_buffers_make(&graph); + assert(buffers.node_att != NULL || buffers.node_count == 0U); + assert(buffers.edge_count == 0U || + (buffers.edge_att != NULL && buffers.edge_ifc != NULL)); mg_rmg_t rmg; - init_rmg(&graph, &rmg, node_att, edge_att, edge_ifc); + init_rmg(&graph, &rmg, buffers.node_att, buffers.edge_att, + buffers.edge_ifc); + assert(rmg.skel_epoch == NULL); + assert(rmg.att_epoch == NULL); + assert_edge_interfaces_clear(&rmg, buffers.edge_count); mg_rule_t rule; mg_rule_make_apply_x(&rule, 1); + assert_default_rule_caps(&rule); mg_match_set_t matches; assert(mg_match_set_init(&matches, 8)); @@ -58,8 +126,10 @@ static void test_dpoi_apply_x(void) { metagraph_result_t res = mg_dpoi_match_rmg(&rmg, &rule, NULL, &matches); assert(!metagraph_result_is_error(res)); assert(matches.count == 3); + (void)res; mg_match_set_free(&matches); + mg_rmg_buffers_free(&buffers); mg_graph_free(&graph); } @@ -68,11 +138,13 @@ static void test_qca_tick_apply_x(void) { mg_graph_init_empty(&graph); mg_graph_make_path_qwqwq(&graph); - mg_attach_ref_t node_att[4]; - mg_attach_ref_t edge_att[1]; - mg_iface_t edge_ifc[1]; + mg_rmg_buffers_t buffers = mg_rmg_buffers_make(&graph); + assert(buffers.node_att != NULL || buffers.node_count == 0U); + assert(buffers.edge_count == 0U || + (buffers.edge_att != NULL && buffers.edge_ifc != NULL)); mg_rmg_t rmg; - init_rmg(&graph, &rmg, node_att, edge_att, edge_ifc); + init_rmg(&graph, &rmg, buffers.node_att, buffers.edge_att, + buffers.edge_ifc); mg_rule_t rule; mg_rule_make_apply_x(&rule, 1); @@ -91,8 +163,10 @@ static void test_qca_tick_apply_x(void) { assert(metrics.matches_found == 3); assert(metrics.matches_kept == 3); assert(metrics.conflicts_dropped == 0); + (void)res; mg_hilbert_free(&hilbert); + mg_rmg_buffers_free(&buffers); mg_graph_free(&graph); } diff --git a/tools/benchmark_tool.c b/tools/benchmark_tool.c index 03d1317..d21fb71 100644 --- a/tools/benchmark_tool.c +++ b/tools/benchmark_tool.c @@ -5,9 +5,7 @@ #include "metagraph/result.h" #include -#include #include -#include // Performance targets from documentation #define METAGRAPH_TARGET_NODE_LOOKUP_NS 100 // <100ns diff --git a/tools/mg-cli.c b/tools/mg-cli.c index e909350..4c32946 100644 --- a/tools/mg-cli.c +++ b/tools/mg-cli.c @@ -4,14 +4,22 @@ */ #include -#include + +#include "metagraph/base.h" // Function with local buffer to trigger stack protection static void metagraph_process_input(const char *input) { char buffer[64]; // Stack buffer that should trigger protection if (input) { - strncpy(buffer, input, sizeof(buffer) - 1); - buffer[sizeof(buffer) - 1] = '\0'; + size_t input_length = 0U; + while (input[input_length] != '\0' && + input_length < (sizeof(buffer) - 1U)) { + ++input_length; + } + const size_t copied = mg_copy_bytes(buffer, sizeof(buffer), input, + input_length, input_length); + (void)copied; + buffer[input_length] = '\0'; (void)printf("Processing: %s\n", buffer); } } @@ -26,7 +34,13 @@ int main(int argc, char *argv[]) { // Create another stack buffer char local_buffer[128]; - snprintf(local_buffer, sizeof(local_buffer), "Version: %s", "0.1.0"); + const char version_string[] = "Version: 0.1.0"; + const size_t literal_length = sizeof(version_string) - 1U; + (void)mg_copy_bytes(local_buffer, sizeof(local_buffer), version_string, + literal_length, literal_length); + local_buffer[(literal_length < sizeof(local_buffer)) + ? literal_length + : (sizeof(local_buffer) - 1U)] = '\0'; (void)printf("%s\n", local_buffer); return 0; diff --git a/tools/version_tool.c b/tools/version_tool.c index cb8cf3b..cd759a8 100644 --- a/tools/version_tool.c +++ b/tools/version_tool.c @@ -4,8 +4,6 @@ */ #include "metagraph/version.h" -#include -#include #include void metagraph_print_api_version(void); From d6ba23fcca8ffa819a333c6c3a9f4d7c93644464 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 21 Oct 2025 03:15:29 -0700 Subject: [PATCH 3/5] style: add trailing newlines to c sources --- src/dpoi.c | 2 +- src/hilbert.c | 2 +- src/match.c | 2 +- src/rmg.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dpoi.c b/src/dpoi.c index 80704d8..607e2d2 100644 --- a/src/dpoi.c +++ b/src/dpoi.c @@ -353,4 +353,4 @@ metagraph_result_t mg_dpo_commit(mg_graph_t *graph, const mg_rule_t *rules, (void)rule_count; (void)schedule; return METAGRAPH_SUCCESS; -} \ No newline at end of file +} diff --git a/src/hilbert.c b/src/hilbert.c index 2674468..52f7e33 100644 --- a/src/hilbert.c +++ b/src/hilbert.c @@ -91,4 +91,4 @@ metagraph_result_t mg_hilbert_resize(mg_hilbert_t *hilbert, size_t new_count) { hilbert->node_bits = next; hilbert->node_count = new_count; return METAGRAPH_OK(); -} \ No newline at end of file +} diff --git a/src/match.c b/src/match.c index e75e7c6..a6ccc87 100644 --- a/src/match.c +++ b/src/match.c @@ -133,4 +133,4 @@ void mg_match_set_free(mg_match_set_t *set) { set->data = NULL; set->count = 0; set->capacity = 0; -} \ No newline at end of file +} diff --git a/src/rmg.c b/src/rmg.c index 1b028a1..ff80d65 100644 --- a/src/rmg.c +++ b/src/rmg.c @@ -63,4 +63,4 @@ bool mg_rmg_hydrate_edge_att(const mg_rmg_t *rmg, uint32_t edge_index, // TODO: hydrate attachment offsets once caching layer is implemented (void)ref; return true; -} \ No newline at end of file +} From 8bb9f1145110e5d5a1f15c624c64daee170f5957 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 21 Oct 2025 09:30:05 -0700 Subject: [PATCH 4/5] ci: allow coderabbitai branches --- scripts/ci/guard-branch.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/ci/guard-branch.sh b/scripts/ci/guard-branch.sh index 18fedda..d84f41e 100755 --- a/scripts/ci/guard-branch.sh +++ b/scripts/ci/guard-branch.sh @@ -9,6 +9,10 @@ DST="$2" # base ref die() { echo "::error::$*"; exit 1; } case "$SRC" in + coderabbitai/*) + [[ "$DST" == "main" ]] \ + || die "coderabbitai/* branches must target main." + ;; feat/minimal-dpoi-qca-loop) # Temporary allowance while PR #70 lands Phase 1 directly onto main. [[ "$DST" == "main" ]] \ @@ -31,6 +35,6 @@ case "$SRC" in esac if [[ "$DST" == "main" && ! "$SRC" =~ ^(release|fix)/ ]]; then - [[ "$SRC" == "feat/minimal-dpoi-qca-loop" ]] \ - || die "Only release/* or fix/* may target main." + [[ "$SRC" == "feat/minimal-dpoi-qca-loop" || "$SRC" =~ ^coderabbitai/ ]] \ + || die "Only release/*, fix/*, coderabbitai/*, or feat/minimal-dpoi-qca-loop may target main." fi From 4db761b5f71d484ef7aefdd7b9b24bc4c94fbbcc Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 21 Oct 2025 09:42:33 -0700 Subject: [PATCH 5/5] ci: ignore docstring bot commits --- commitlint.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commitlint.config.js b/commitlint.config.js index 034dbe0..a97539b 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,7 +1,8 @@ module.exports = { extends: ['@commitlint/config-conventional'], ignores: [ - (message) => /^Update\b/.test(message) + (message) => /^Update\b/.test(message), + (message) => message.startsWith('📝') ], rules: { 'header-max-length': [2, 'always', 72]