46f93fd6eb
Deploy marketing to Cloud Run / deploy (push) Failing after 5s
A real attacker probed /api/share earlier today with <script>alert(1),
<iframe src=evil>, <img onerror>, <a href="javascript:...">, and a
<form action="/steal"> payload. Nothing executed because the chat
bubble at /share/<id> renders the served HTML inside marked.js's
already-escaped output, but the prior denylist sanitizer was fragile:
- It comment-wrapped dangerous tags ("<!--script>...-->") which a
literal "-->" inside an attacker-supplied attribute value can close
early, re-exposing the original payload.
- It renamed on*= attributes to data-x-on*= which left attack
indicators visible in the served HTML.
- It was a denylist; every new attack vector required a code change.
- It didn't validate <a href> URL schemes properly.
The replacement is a runtime-level state-machine allowlist parser
(foundation/el af480f6: el_html_sanitize). The product just specifies
the JSON allowlist of allowed tags + attributes; the runtime drops
everything else, validates href/src URL schemes (http/https/mailto/
fragment/relative only), and drops whole subtrees of script/style/
iframe/object/embed/form regardless of the allowlist.
Phase 4 of bl-dc55ae07: deletes sanitize_share_html (main.el) and
gal_sanitize_html (gallery.el); replaces 3 call sites with
el_html_sanitize(html, allowlist). Defines default_share_allowlist
in main.el and the identical gallery_share_allowlist in gallery.el
(separate bindings to avoid a forward-reference at build-concat
order — gallery is concatenated before main).
Phase 5: migrations/20260502185500_backfill_resanitize_share_cards.sql
nulls answer_html for any share_cards row older than 1 hour. Applied
via the Supabase Management API; 0 rows in scope (the column was
added today and existing rows pre-date its first write).
Also fixes an orthogonal duplicate-symbol bug: unix_timestamp() was
defined in both dist/web_stubs.c and the runtime (the latter is a
recent runtime addition picked up by the runtime sync). Removed the
stub.
Backlog: bl-dc55ae07
625 lines
33 KiB
C
625 lines
33 KiB
C
/*
|
|
* el_runtime.h — El language C runtime header
|
|
*
|
|
* Declares all built-in functions available to compiled El programs.
|
|
* Include this in every generated .c file.
|
|
*
|
|
* Value model:
|
|
* All El values are represented as el_val_t (= int64_t).
|
|
* On 64-bit systems a pointer fits in int64_t.
|
|
* String values are cast: (el_val_t)(uintptr_t)"hello"
|
|
* Integer values are stored directly.
|
|
* This lets arithmetic work naturally while still passing strings around.
|
|
*
|
|
* Type conventions (El -> C):
|
|
* String -> el_val_t (holds const char* via uintptr_t cast)
|
|
* Int -> el_val_t
|
|
* Bool -> el_val_t (0 = false, nonzero = true)
|
|
* Any -> el_val_t
|
|
* Void -> void
|
|
*
|
|
* Macros for convenience:
|
|
* EL_STR(s) cast string literal to el_val_t
|
|
* EL_CSTR(v) cast el_val_t back to const char*
|
|
* EL_INT(v) identity — el_val_t is already int64_t
|
|
*
|
|
* Link requirements:
|
|
* -lcurl — required for the HTTP client (http_get, http_post, llm_*).
|
|
* -lpthread — required for the HTTP server (one detached thread per
|
|
* connection, capped at 64 concurrent).
|
|
* -loqs — optional; required only when liboqs is installed and the
|
|
* pq_* / sha3_256_hex entry points are needed. Detected at
|
|
* compile time via __has_include(<oqs/oqs.h>).
|
|
* -lcrypto — optional; pulled in alongside -loqs. Used for X25519 in
|
|
* pq_hybrid_* and HKDF-SHA256 derivation.
|
|
*
|
|
* Canonical compile command:
|
|
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
|
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
|
*
|
|
* With liboqs (post-quantum stack):
|
|
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread -loqs -lcrypto \
|
|
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
typedef int64_t el_val_t;
|
|
|
|
#define EL_STR(s) ((el_val_t)(uintptr_t)(s))
|
|
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
|
|
#define EL_INT(v) (v)
|
|
#define EL_NULL ((el_val_t)0)
|
|
|
|
/* Float values share the el_val_t (int64) slot via a bit-cast.
|
|
* The codegen emits Float literals as `el_from_float(<dbl>)` so the
|
|
* underlying bits represent the IEEE 754 double. Float-aware builtins
|
|
* (math, format, json) round-trip via these helpers. */
|
|
static inline double el_to_float(el_val_t v) {
|
|
union { int64_t i; double f; } u;
|
|
u.i = (int64_t)v;
|
|
return u.f;
|
|
}
|
|
|
|
static inline el_val_t el_from_float(double f) {
|
|
union { double f; int64_t i; } u;
|
|
u.f = f;
|
|
return (el_val_t)u.i;
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* ── I/O ──────────────────────────────────────────────────────────────────── */
|
|
|
|
void println(el_val_t s);
|
|
void print(el_val_t s);
|
|
el_val_t readline(void);
|
|
|
|
/* ── String builtins ─────────────────────────────────────────────────────── */
|
|
|
|
el_val_t el_str_concat(el_val_t a, el_val_t b);
|
|
el_val_t str_eq(el_val_t a, el_val_t b);
|
|
el_val_t str_starts_with(el_val_t s, el_val_t prefix);
|
|
el_val_t str_ends_with(el_val_t s, el_val_t suffix);
|
|
el_val_t str_len(el_val_t s);
|
|
el_val_t str_concat(el_val_t a, el_val_t b);
|
|
el_val_t int_to_str(el_val_t n);
|
|
el_val_t str_to_int(el_val_t s);
|
|
el_val_t str_slice(el_val_t s, el_val_t start, el_val_t end);
|
|
el_val_t str_contains(el_val_t s, el_val_t sub);
|
|
el_val_t str_replace(el_val_t s, el_val_t from, el_val_t to);
|
|
el_val_t str_to_upper(el_val_t s);
|
|
el_val_t str_to_lower(el_val_t s);
|
|
el_val_t str_trim(el_val_t s);
|
|
|
|
/* ── Math ────────────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t el_abs(el_val_t n);
|
|
el_val_t el_max(el_val_t a, el_val_t b);
|
|
el_val_t el_min(el_val_t a, el_val_t b);
|
|
|
|
/* ── Refcount (ARC) ──────────────────────────────────────────────────────────
|
|
* Lists and Maps carry a refcount. Strings and ints do not — el_retain and
|
|
* el_release are safe no-ops on non-refcounted values (they sniff a magic
|
|
* header at offset 0 and only act if the magic matches).
|
|
*
|
|
* Codegen emits these at let-binding shadowing, function entry (params), and
|
|
* function exit (locals other than the returned value). The refcount lets
|
|
* el_list_append and el_map_set mutate in place when uniquely owned (cheap)
|
|
* and copy-on-write when shared (preserves persistent semantics across
|
|
* accumulator patterns in the compiler itself). */
|
|
|
|
void el_retain(el_val_t v);
|
|
void el_release(el_val_t v);
|
|
|
|
/* ── List ────────────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t el_list_new(el_val_t count, ...);
|
|
el_val_t el_list_len(el_val_t list);
|
|
el_val_t el_list_get(el_val_t list, el_val_t index);
|
|
el_val_t el_list_append(el_val_t list, el_val_t elem);
|
|
el_val_t el_list_empty(void);
|
|
el_val_t el_list_clone(el_val_t list);
|
|
|
|
/* ── Map ─────────────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t el_map_new(el_val_t pair_count, ...);
|
|
el_val_t el_get_field(el_val_t map, el_val_t key);
|
|
el_val_t el_map_get(el_val_t map, el_val_t key);
|
|
el_val_t el_map_set(el_val_t map, el_val_t key, el_val_t value);
|
|
|
|
/* ── HTTP ─────────────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t http_get(el_val_t url);
|
|
el_val_t http_post(el_val_t url, el_val_t body);
|
|
el_val_t http_post_json(el_val_t url, el_val_t json_body);
|
|
el_val_t http_get_with_headers(el_val_t url, el_val_t headers_map);
|
|
el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map);
|
|
el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header);
|
|
el_val_t http_delete(el_val_t url);
|
|
void http_serve(el_val_t port, el_val_t handler);
|
|
void http_set_handler(el_val_t name);
|
|
|
|
/* HTTP server v2 ─────────────────────────────────────────────────────────────
|
|
* Same dispatch model as http_serve, but the handler signature is widened:
|
|
*
|
|
* el_val_t handler(method, path, headers_map, body)
|
|
*
|
|
* `headers_map` is an ElMap from lowercased header name → header value (both
|
|
* Strings). Repeated headers are joined with ", " per RFC 7230.
|
|
*
|
|
* Response value: the handler may return either
|
|
* (a) a plain body string — same auto-content-type / 200-OK behaviour as
|
|
* http_serve (3-arg) — or
|
|
* (b) a response envelope built with `http_response(status, headers_json,
|
|
* body)`. The runtime detects the envelope discriminator
|
|
* `"el_http_response":1` at the start of the returned string and
|
|
* unpacks status / headers / body before sending.
|
|
*
|
|
* The 3-arg http_serve(port, handler) remains supported unchanged for
|
|
* existing handlers (e.g. products/web/server.el): it dispatches with
|
|
* (method, path, body), hardcodes 200 OK, and auto-detects content type. */
|
|
void http_serve_v2(el_val_t port, el_val_t handler);
|
|
void http_set_handler_v2(el_val_t name);
|
|
|
|
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
|
* literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
|
|
* returned string carries the discriminator `{"el_http_response":1,...}`
|
|
* which the runtime's send-path detects and unpacks. Detection happens
|
|
* uniformly inside http_send_response, so a 3-arg handler may also return
|
|
* an envelope. The 3-arg variant remains documented as a fixed 200-OK
|
|
* auto-content-type contract for legacy handlers that return plain bodies. */
|
|
el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
|
|
|
/* HTTP timeout — every libcurl request honors EL_HTTP_TIMEOUT_MS (default
|
|
* 60000ms). Read lazily on first use, so setting the env var any time before
|
|
* the first http_* call is sufficient. */
|
|
|
|
/* Streaming variants — write the response body straight to a file via
|
|
* libcurl's CURLOPT_WRITEFUNCTION = fwrite. These bypass the el_val_t string
|
|
* wrapper entirely, so binary payloads (audio/mpeg, image/png, etc.) survive
|
|
* embedded NUL bytes that would truncate a strlen()-based code path.
|
|
*
|
|
* Both honor EL_HTTP_TIMEOUT_MS, follow redirects, and accept the same
|
|
* `headers_map` shape as http_post_with_headers (ElMap of String→String).
|
|
*
|
|
* Return value: 1 on success (file fully written), 0 on any failure
|
|
* (network, file open, partial write). On failure the output file is removed
|
|
* so callers cannot mistake a partially-written file for a valid one. */
|
|
el_val_t http_post_to_file(el_val_t url, el_val_t body, el_val_t headers_map, el_val_t output_path);
|
|
el_val_t http_get_to_file(el_val_t url, el_val_t headers_map, el_val_t output_path);
|
|
|
|
/* ── URL encoding ────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t url_encode(el_val_t s); /* RFC 3986 unreserved set */
|
|
el_val_t url_decode(el_val_t s); /* '+' → space, %XX → byte */
|
|
|
|
/* ── HTML allowlist sanitizer ────────────────────────────────────────────────
|
|
* el_html_sanitize(input_html, allowlist_json) — strict allowlist HTML
|
|
* cleaner. State-machine parser; tag/attribute names compared case-
|
|
* insensitively against the allowlist; `<a href>` / `<… src>` URL schemes
|
|
* validated (http, https, mailto, fragment-only, or relative); whole-
|
|
* subtree drop for script / style / iframe / object / embed / form; HTML-
|
|
* escapes free text outside dropped subtrees.
|
|
*
|
|
* The allowlist is JSON of the form
|
|
* {"p":[],"a":["href","title"],"strong":[],...}
|
|
* where each value is the array of attribute names allowed for that tag. */
|
|
el_val_t el_html_sanitize(el_val_t input_html, el_val_t allowlist_json);
|
|
|
|
/* ── Filesystem ──────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t fs_read(el_val_t path);
|
|
el_val_t fs_write(el_val_t path, el_val_t content);
|
|
el_val_t fs_list(el_val_t path);
|
|
el_val_t fs_exists(el_val_t path);
|
|
el_val_t fs_mkdir(el_val_t path); /* mkdir -p, mode 0755 */
|
|
|
|
/* Length-explicit binary write. `length` is an Int (el_val_t holding the
|
|
* byte count). The caller knows the length from context — typically because
|
|
* `bytes` came from base64_decode (which produces a magic-tagged binary
|
|
* buffer with embedded NULs possible) and the caller already tracks the
|
|
* decoded length, OR because the bytes came from a fixed-size source
|
|
* (sha256_bytes = 32, hmac_sha256_bytes = 32). Bypasses strlen entirely.
|
|
*
|
|
* Returns 1 on success, 0 on failure (invalid path, can't open, partial
|
|
* write, negative length). On partial-write failure, the file is removed
|
|
* so callers cannot read back a truncated artefact. */
|
|
el_val_t fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t length);
|
|
|
|
/* ── JSON ────────────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t json_get(el_val_t json, el_val_t key);
|
|
el_val_t json_parse(el_val_t s);
|
|
el_val_t json_stringify(el_val_t v);
|
|
el_val_t json_get_string(el_val_t json_str, el_val_t key);
|
|
el_val_t json_get_int(el_val_t json_str, el_val_t key);
|
|
el_val_t json_get_float(el_val_t json_str, el_val_t key);
|
|
el_val_t json_get_bool(el_val_t json_str, el_val_t key);
|
|
el_val_t json_get_raw(el_val_t json_str, el_val_t key);
|
|
el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value);
|
|
el_val_t json_array_len(el_val_t json_str);
|
|
el_val_t json_array_get(el_val_t json_str, el_val_t index);
|
|
el_val_t json_array_get_string(el_val_t json_str, el_val_t index);
|
|
|
|
/* ── Time ────────────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t time_now(void);
|
|
el_val_t time_now_utc(void);
|
|
el_val_t sleep_secs(el_val_t secs);
|
|
el_val_t sleep_ms(el_val_t ms);
|
|
el_val_t time_format(el_val_t ts, el_val_t fmt);
|
|
el_val_t time_to_parts(el_val_t ts);
|
|
el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz);
|
|
el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit);
|
|
el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit);
|
|
|
|
/* ── Instant + Duration: first-class temporal types ──────────────────────────
|
|
* Both types share the el_val_t (int64) slot. Instants are nanoseconds
|
|
* since the Unix epoch; Durations are signed nanoseconds. Type discipline
|
|
* is enforced at codegen-time: BinOps on names registered as Instant or
|
|
* Duration route through the typed wrappers below; mismatches like
|
|
* Instant+Instant become #error at the C compiler.
|
|
*
|
|
* Postfix literals — `30.seconds`, `1.hour`, `500.millis`, `30.nanos` — are
|
|
* recognised by the parser as DurationLit AST nodes and lowered to literal
|
|
* int64 nanoseconds at codegen time. The runtime never sees the units. */
|
|
|
|
el_val_t el_now_instant(void);
|
|
el_val_t now(void);
|
|
el_val_t unix_seconds(el_val_t n);
|
|
el_val_t unix_millis(el_val_t n);
|
|
el_val_t instant_from_iso8601(el_val_t s);
|
|
|
|
el_val_t el_duration_from_nanos(el_val_t ns);
|
|
el_val_t duration_seconds(el_val_t n);
|
|
el_val_t duration_millis(el_val_t n);
|
|
el_val_t duration_nanos(el_val_t n);
|
|
|
|
el_val_t el_instant_add_dur(el_val_t inst, el_val_t dur);
|
|
el_val_t el_instant_sub_dur(el_val_t inst, el_val_t dur);
|
|
el_val_t el_instant_diff(el_val_t a, el_val_t b);
|
|
el_val_t el_duration_add(el_val_t a, el_val_t b);
|
|
el_val_t el_duration_sub(el_val_t a, el_val_t b);
|
|
el_val_t el_duration_scale(el_val_t dur, el_val_t scalar);
|
|
el_val_t el_duration_div(el_val_t dur, el_val_t scalar);
|
|
|
|
el_val_t el_instant_lt(el_val_t a, el_val_t b);
|
|
el_val_t el_instant_le(el_val_t a, el_val_t b);
|
|
el_val_t el_instant_gt(el_val_t a, el_val_t b);
|
|
el_val_t el_instant_ge(el_val_t a, el_val_t b);
|
|
el_val_t el_instant_eq(el_val_t a, el_val_t b);
|
|
el_val_t el_instant_ne(el_val_t a, el_val_t b);
|
|
el_val_t el_duration_lt(el_val_t a, el_val_t b);
|
|
el_val_t el_duration_le(el_val_t a, el_val_t b);
|
|
el_val_t el_duration_gt(el_val_t a, el_val_t b);
|
|
el_val_t el_duration_ge(el_val_t a, el_val_t b);
|
|
el_val_t el_duration_eq(el_val_t a, el_val_t b);
|
|
el_val_t el_duration_ne(el_val_t a, el_val_t b);
|
|
|
|
el_val_t instant_to_unix_seconds(el_val_t i);
|
|
el_val_t instant_to_unix_millis(el_val_t i);
|
|
el_val_t instant_to_iso8601(el_val_t i);
|
|
el_val_t duration_to_seconds(el_val_t d);
|
|
el_val_t duration_to_millis(el_val_t d);
|
|
el_val_t duration_to_nanos(el_val_t d);
|
|
|
|
el_val_t el_sleep_duration(el_val_t dur);
|
|
el_val_t unix_timestamp(void);
|
|
|
|
el_val_t ttl_cache_set(el_val_t key, el_val_t value);
|
|
el_val_t ttl_cache_get(el_val_t key, el_val_t max_age);
|
|
el_val_t ttl_cache_age(el_val_t key);
|
|
|
|
/* ── UUID ────────────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t uuid_new(void);
|
|
el_val_t uuid_v4(void);
|
|
|
|
/* ── Environment ─────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t env(el_val_t key);
|
|
|
|
/* ── In-process state K/V ────────────────────────────────────────────────── */
|
|
|
|
el_val_t state_set(el_val_t key, el_val_t value);
|
|
el_val_t state_get(el_val_t key);
|
|
el_val_t state_del(el_val_t key);
|
|
el_val_t state_keys(void);
|
|
|
|
/* ── Float formatting ────────────────────────────────────────────────────── */
|
|
|
|
el_val_t float_to_str(el_val_t f);
|
|
el_val_t int_to_float(el_val_t n);
|
|
el_val_t float_to_int(el_val_t f);
|
|
el_val_t format_float(el_val_t f, el_val_t decimals);
|
|
el_val_t decimal_round(el_val_t f, el_val_t decimals);
|
|
el_val_t str_to_float(el_val_t s);
|
|
|
|
/* ── Math (Float-aware) ──────────────────────────────────────────────────── */
|
|
|
|
el_val_t math_sqrt(el_val_t f);
|
|
el_val_t math_log(el_val_t f);
|
|
el_val_t math_ln(el_val_t f);
|
|
el_val_t math_sin(el_val_t f);
|
|
el_val_t math_cos(el_val_t f);
|
|
el_val_t math_pi(void);
|
|
|
|
/* ── String additions ────────────────────────────────────────────────────── */
|
|
|
|
el_val_t str_index_of(el_val_t s, el_val_t sub);
|
|
el_val_t str_split(el_val_t s, el_val_t sep);
|
|
el_val_t str_char_at(el_val_t s, el_val_t i);
|
|
el_val_t str_char_code(el_val_t s, el_val_t i);
|
|
el_val_t str_pad_left(el_val_t s, el_val_t width, el_val_t pad);
|
|
el_val_t str_pad_right(el_val_t s, el_val_t width, el_val_t pad);
|
|
el_val_t str_format(el_val_t fmt, el_val_t data);
|
|
el_val_t str_lower(el_val_t s);
|
|
el_val_t str_upper(el_val_t s);
|
|
|
|
/* ── List additions ──────────────────────────────────────────────────────── */
|
|
|
|
el_val_t list_push(el_val_t list, el_val_t elem);
|
|
el_val_t list_push_front(el_val_t list, el_val_t elem);
|
|
el_val_t list_join(el_val_t list, el_val_t sep);
|
|
el_val_t list_range(el_val_t start, el_val_t end);
|
|
|
|
/* ── Bool helpers ────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t bool_to_str(el_val_t b);
|
|
|
|
/* ── Numeric parsing ─────────────────────────────────────────────────────── */
|
|
|
|
el_val_t parse_int(el_val_t s, el_val_t default_val);
|
|
|
|
/* ── Process ─────────────────────────────────────────────────────────────── */
|
|
|
|
void exit_program(el_val_t code);
|
|
el_val_t getpid_now(void);
|
|
|
|
/* ── CGI identity ─────────────────────────────────────────────────────────────
|
|
* Called at the start of main() in CGI programs (those with a `cgi {}` block).
|
|
* Records the program's DHARMA identity before any other code executes. */
|
|
|
|
void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
|
el_val_t network, el_val_t engram);
|
|
|
|
/* ── DHARMA network builtins ─────────────────────────────────────────────────
|
|
* Available to CGI programs (declared with a `cgi {}` block).
|
|
*
|
|
* Peers are addressed by `dharma_id` of the form
|
|
* "<registry-id>@<transport-url>" e.g. "ntn-genesis@http://localhost:7770"
|
|
* If the @<url> portion is omitted, transport defaults to
|
|
* "http://localhost:7770" (the local CGI daemon assumption).
|
|
*
|
|
* Wire protocol (all peers expose):
|
|
* POST <url>/dharma/recv { channel, from, content } → response body
|
|
* POST <url>/dharma/event { type, payload, source, timestamp }
|
|
* POST <url>/api/activate { query } → list of nodes
|
|
*
|
|
* Hosting application's responsibility: an El program with a `cgi {}` block
|
|
* runs http_serve() with its own request handler; that handler should route
|
|
* "/dharma/event" requests by calling el_runtime_dharma_event_arrive() so
|
|
* incoming events feed dharma_field() queues. The runtime itself does not
|
|
* intercept any /dharma path. */
|
|
|
|
el_val_t dharma_connect(el_val_t cgi_id);
|
|
el_val_t dharma_send(el_val_t channel, el_val_t content);
|
|
el_val_t dharma_activate(el_val_t query);
|
|
void dharma_emit(el_val_t event_type, el_val_t payload);
|
|
el_val_t dharma_field(el_val_t event_type);
|
|
void dharma_strengthen(el_val_t cgi_id, el_val_t weight);
|
|
el_val_t dharma_relationship(el_val_t cgi_id);
|
|
el_val_t dharma_peers(void);
|
|
|
|
/* Public C API: called by an El program's HTTP handler when a /dharma/event
|
|
* request arrives. Pushes onto the per-event-type queue and signals any
|
|
* pending dharma_field() blockers. All three arguments must be NUL-terminated
|
|
* C strings (or NULL — then treated as empty). */
|
|
void el_runtime_dharma_event_arrive(const char* event_type,
|
|
const char* payload,
|
|
const char* source);
|
|
|
|
/* ── Engram local graph primitives ───────────────────────────────────────────
|
|
* Operate on the CGI's local Engram knowledge graph.
|
|
* `engram_activate` queries the local graph only; `dharma_activate` is
|
|
* network-wide across all connected CGI graphs. */
|
|
|
|
el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience);
|
|
el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
|
el_val_t salience, el_val_t importance, el_val_t confidence,
|
|
el_val_t tier, el_val_t tags);
|
|
/* Layered consciousness — see el_runtime.c for the layered architecture
|
|
* design notes (search "Layered consciousness architecture"). The five
|
|
* canonical layers (safety / core-identity / domain-knowledge / imprint /
|
|
* suit) are seeded automatically; engram_add_layer extends the registry
|
|
* with imprint or suit overlays at runtime. Nodes default to layer 1
|
|
* (core-identity) when created via engram_node / engram_node_full. */
|
|
el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t label,
|
|
el_val_t salience, el_val_t certainty, el_val_t confidence,
|
|
el_val_t status, el_val_t tags, el_val_t layer_id);
|
|
el_val_t engram_add_layer(el_val_t name, el_val_t priority, el_val_t suppressible,
|
|
el_val_t transparent, el_val_t injectable);
|
|
el_val_t engram_remove_layer(el_val_t layer_id);
|
|
el_val_t engram_list_layers(void);
|
|
el_val_t engram_get_node(el_val_t id);
|
|
void engram_strengthen(el_val_t node_id);
|
|
void engram_forget(el_val_t node_id);
|
|
el_val_t engram_node_count(void);
|
|
el_val_t engram_search(el_val_t query, el_val_t limit);
|
|
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset);
|
|
void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation);
|
|
el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id);
|
|
el_val_t engram_neighbors(el_val_t node_id);
|
|
el_val_t engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
|
el_val_t engram_edge_count(void);
|
|
/* Three-pass activation: background fan-out → working-memory promotion →
|
|
* Layer 0 override. See "Three-pass activation" in el_runtime.c. */
|
|
el_val_t engram_activate(el_val_t query, el_val_t depth);
|
|
el_val_t engram_save(el_val_t path);
|
|
el_val_t engram_load(el_val_t path);
|
|
|
|
/* JSON-string accessors — return pre-serialized JSON so HTTP handlers
|
|
* can pass results straight through without round-tripping ElList/ElMap
|
|
* through json_stringify. */
|
|
el_val_t engram_get_node_json(el_val_t id);
|
|
el_val_t engram_search_json(el_val_t query, el_val_t limit);
|
|
el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset);
|
|
el_val_t engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
|
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
|
el_val_t engram_activate_json(el_val_t query, el_val_t depth);
|
|
el_val_t engram_stats_json(void);
|
|
el_val_t engram_list_layers_json(void);
|
|
/* engram_compile_layered_json — produce a prompt-ready text block split
|
|
* into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
|
|
* and "[ENGRAM CONTEXT]" (standard suppressible layers). Returns "" if
|
|
* no nodes promoted to working memory. */
|
|
el_val_t engram_compile_layered_json(el_val_t intent, el_val_t depth);
|
|
|
|
/* ── LLM (Anthropic API client) ─────────────────────────────────────────────
|
|
* All functions call https://api.anthropic.com/v1/messages with the API key
|
|
* from env ANTHROPIC_API_KEY. Default model when empty: claude-sonnet-4-5. */
|
|
|
|
el_val_t llm_call(el_val_t model, el_val_t prompt);
|
|
el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt);
|
|
el_val_t llm_call_agentic(el_val_t model, el_val_t system, el_val_t user, el_val_t tools);
|
|
el_val_t llm_vision(el_val_t model, el_val_t system, el_val_t prompt, el_val_t image_url_or_b64);
|
|
el_val_t llm_models(void);
|
|
|
|
/* Register a tool handler by name. The handler is looked up via dlsym
|
|
* (mirroring http_set_handler), so any El `fn <name>(input)` compiles to
|
|
* a global C symbol that this function can locate at runtime.
|
|
* Handler signature: `el_val_t handler(el_val_t input_json)` — receives
|
|
* the tool input as a JSON-string el_val_t and returns a JSON-string
|
|
* el_val_t result. Used by llm_call_agentic. */
|
|
void llm_register_tool(el_val_t name, el_val_t handler_fn_name);
|
|
|
|
/* ── args() ─────────────────────────────────────────────────────────────────
|
|
* Provides access to command-line arguments passed to the program.
|
|
* Populated by el_runtime_init_args() before main() runs. */
|
|
|
|
el_val_t args(void);
|
|
void el_runtime_init_args(int argc, char** argv);
|
|
|
|
/* ── Crypto primitives ─────────────────────────────────────────────────────
|
|
* SHA-256, HMAC-SHA-256, and base64 (standard + URL-safe).
|
|
* Self-contained — no OpenSSL/libcrypto dependency. The implementations are
|
|
* adapted from public-domain reference code (Brad Conte / RFC 4648).
|
|
*
|
|
* Bytes-returning variants (sha256_bytes, hmac_sha256_bytes) return a string
|
|
* value whose contents are raw binary; callers usually feed these into
|
|
* base64_encode. Note that el_val_t strings are NUL-terminated by convention,
|
|
* so the binary payload may contain embedded NULs — pass it directly into
|
|
* base64_encode (which uses an explicit length) rather than treating it as
|
|
* a printable C string.
|
|
*
|
|
* The "base64" variants emit/accept RFC 4648 standard alphabet with padding.
|
|
* The "base64url" variants use URL-safe alphabet (`-`/`_`) with no padding,
|
|
* as used in JWTs. */
|
|
|
|
el_val_t sha256_hex(el_val_t input);
|
|
el_val_t sha256_bytes(el_val_t input);
|
|
el_val_t hmac_sha256_hex(el_val_t key, el_val_t message);
|
|
el_val_t hmac_sha256_bytes(el_val_t key, el_val_t message);
|
|
el_val_t base64_encode(el_val_t input);
|
|
el_val_t base64_decode(el_val_t input);
|
|
el_val_t base64url_encode(el_val_t input);
|
|
el_val_t base64url_decode(el_val_t input);
|
|
|
|
/* Length-aware variants (internal — exposed for the rare caller that already
|
|
* has a known-length binary buffer and doesn't want to round-trip through
|
|
* a NUL-terminated el_val_t string). Sha256_bytes and hmac_sha256_bytes feed
|
|
* these implicitly. */
|
|
el_val_t el_sha256_bytes_n(const unsigned char* data, size_t len);
|
|
el_val_t el_base64_encode_n(const unsigned char* data, size_t len, int url_safe);
|
|
|
|
/* ── Post-quantum primitives (liboqs-backed) ────────────────────────────────
|
|
* All inputs/outputs hex-encoded. Algorithm choices:
|
|
* Signature: CRYSTALS-Dilithium-3 (NIST level 3, balanced)
|
|
* KEM: CRYSTALS-Kyber-768 (NIST level 3)
|
|
* Hash: SHA3-256 (Keccak) (PQ-aware protocols favour SHA3 over SHA2)
|
|
*
|
|
* If liboqs is not linked (detected via __has_include(<oqs/oqs.h>) at compile
|
|
* time), the pq_* entry points return a JSON-shaped error string so callers
|
|
* fail loudly rather than silently fall back to classical schemes:
|
|
* {"error":"liboqs not linked, post-quantum primitives unavailable"}
|
|
*
|
|
* The hybrid handshake pairs X25519 with Kyber-768 per NIST PQ guidance and
|
|
* CNSA 2.0. Combined shared secret is HKDF-SHA256(x25519_ss || kyber_ss).
|
|
* Even if Kyber falls, X25519 holds; if X25519 falls under quantum attack,
|
|
* Kyber holds. SHA3-256 also remains usable independent of liboqs (the
|
|
* Keccak permutation is PQ-OK as a primitive). */
|
|
|
|
el_val_t pq_keygen_signature(void);
|
|
el_val_t pq_sign(el_val_t secret_key_hex, el_val_t message);
|
|
el_val_t pq_verify(el_val_t public_key_hex, el_val_t message, el_val_t signature_hex);
|
|
|
|
el_val_t pq_kem_keygen(void);
|
|
el_val_t pq_kem_encaps(el_val_t public_key_hex);
|
|
el_val_t pq_kem_decaps(el_val_t secret_key_hex, el_val_t ciphertext_hex);
|
|
|
|
el_val_t pq_hybrid_keygen(void);
|
|
el_val_t pq_hybrid_handshake(el_val_t remote_pub_combined);
|
|
|
|
el_val_t sha3_256_hex(el_val_t input);
|
|
|
|
/* ── AEAD: AES-256-GCM (libcrypto-backed) ───────────────────────────────────
|
|
* Symmetric authenticated encryption used to wrap envelopes after a KEM
|
|
* handshake. Caller MUST supply a 32-byte key (64 hex chars) — typically the
|
|
* Kyber-768 / hybrid shared_secret, optionally normalized via SHA3-256.
|
|
*
|
|
* aead_encrypt returns a JSON map {"nonce":"...","ciphertext":"..."} where
|
|
* ciphertext is the AES-256-GCM output with the 16-byte auth tag appended.
|
|
* Nonce is a fresh 12-byte CSPRNG draw — callers never pick the nonce, which
|
|
* structurally rules out the GCM nonce-reuse footgun.
|
|
*
|
|
* aead_decrypt returns the plaintext String, or "" on any failure (including
|
|
* auth-tag mismatch). Callers MUST check for "" before trusting the result. */
|
|
el_val_t aead_encrypt(el_val_t key_hex, el_val_t plaintext);
|
|
el_val_t aead_decrypt(el_val_t key_hex, el_val_t nonce_hex, el_val_t ciphertext_hex);
|
|
|
|
/* ── Native VM builtin aliases (for compiled El source) ─────────────────────
|
|
* These match the El VM's native_* builtins so that El source compiled
|
|
* to C can call the same names without modification. */
|
|
|
|
el_val_t native_list_get(el_val_t list, el_val_t index);
|
|
el_val_t native_list_len(el_val_t list);
|
|
el_val_t native_list_append(el_val_t list, el_val_t elem);
|
|
el_val_t native_list_empty(void);
|
|
el_val_t native_list_clone(el_val_t list);
|
|
el_val_t native_string_chars(el_val_t s);
|
|
el_val_t native_int_to_str(el_val_t n);
|
|
|
|
/* ── Method-call shorthand aliases ──────────────────────────────────────────
|
|
* The El method-call convention `obj.method(args)` compiles to
|
|
* `method(obj, args)`. These aliases expose the runtime functions under
|
|
* the short names that result from method calls in El source.
|
|
*
|
|
* Example: `myList.append(x)` → `append(myList, x)` (calls this alias)
|
|
* `myList.len()` → `len(myList)` (calls this alias) */
|
|
|
|
el_val_t append(el_val_t list, el_val_t elem); /* el_list_append */
|
|
el_val_t len(el_val_t list); /* el_list_len */
|
|
el_val_t get(el_val_t list, el_val_t index); /* el_list_get */
|
|
el_val_t map_get(el_val_t map, el_val_t key); /* el_map_get */
|
|
el_val_t map_set(el_val_t map, el_val_t key, el_val_t value); /* el_map_set */
|
|
|
|
/* ── OTLP/HTTP Observability ─────────────────────────────────────────────── */
|
|
/* See bottom of el_runtime.c for the implementation.
|
|
* Configured by env vars OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION.
|
|
* No-op when OTLP_ENDPOINT is unset. Drop-on-failure semantics. */
|
|
el_val_t emit_log(el_val_t level, el_val_t msg, el_val_t fields_json);
|
|
el_val_t emit_metric(el_val_t name, el_val_t value, el_val_t tags_json);
|
|
el_val_t trace_span_start(el_val_t name);
|
|
el_val_t trace_span_end(el_val_t span_handle);
|
|
el_val_t emit_event(el_val_t name, el_val_t duration_ms);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|